feat(policy,claude_sdk,cli,config_loader): pretooluse policy rules per agent, tool call audit, sibling imports for config
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user