feat(policy,claude_sdk,cli,config_loader): pretooluse policy rules per agent, tool call audit, sibling imports for config

This commit is contained in:
hh
2026-08-29 15:27:26 +02:00
parent 23cfb16228
commit 2895e677c0
10 changed files with 420 additions and 1 deletions
+64
View File
@@ -458,3 +458,67 @@ async def test_deltas_reach_the_caller_before_the_turn_ends(cwd: Path) -> None:
GatedClient.gate.set()
rest = [e async for e in events]
assert isinstance(rest[-1], RawMessageStopEvent)
async def test_policy_hook_denies_and_audits(cwd: Path) -> None:
from beaver_gateway.core.policy import Deny, ToolCall
def no_days(c: ToolCall):
p = c.path()
if c.tool == "Write" and p is not None and "📅 дни" in p.parts:
return Deny(reason="дневник только читать")
c.state["seen"] = c.state.get("seen", 0) + 1
return None
audits = []
async def sink(a):
audits.append(a)
backend = _backend(cwd, InMemorySessionStore(), policy=(no_days,))
backend._audit_sink = sink
await _drain(
backend.complete(
agent=backend.agent,
messages=[{"role": "user", "content": "hi"}],
conversation_id="conv-1",
)
)
hook = FakeClient.instances[0].options.hooks["PreToolUse"][0].hooks[0]
denied = await hook(
{"tool_name": "Write", "tool_input": {"file_path": "/vault/📅 дни/x.md"}},
"t1",
{},
)
assert denied["hookSpecificOutput"]["permissionDecision"] == "deny"
assert (
denied["hookSpecificOutput"]["permissionDecisionReason"]
== "дневник только читать"
)
allowed = await hook(
{"tool_name": "Write", "tool_input": {"file_path": "/vault/мета/бобер/x.md"}},
"t2",
{},
)
assert allowed == {}
await hook(
{"tool_name": "Read", "tool_input": {"file_path": "/vault/a.md"}}, "t3", {}
)
assert backend.live("conv-1").state["seen"] == 2
assert [(a.tool, a.decision) for a in audits] == [
("Write", "deny"),
("Write", "allow"),
("Read", "allow"),
]
assert audits[0].brief == "/vault/📅 дни/x.md"
assert audits[0].kind == "deep" and audits[0].conversation == "conv-1"
async def test_no_policy_no_hooks(cwd: Path) -> None:
backend = _backend(cwd, InMemorySessionStore())
await _drain(
backend.complete(
agent=backend.agent, messages=[{"role": "user", "content": "hi"}]
)
)
assert FakeClient.instances[0].options.hooks is None