local-review.yml (push) Invalid workflow file
Auto-version and Release / release (push) Successful in 10s
CI Pipeline / lint (push) Failing after 30s
CI Pipeline / go-surface (push) Failing after 19s
CI Pipeline / rust-surface (push) Successful in 4m15s
CI Pipeline / unit-tests (push) Failing after 5m23s
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 7s
CI Pipeline / canary (push) Failing after 59s
CI Pipeline / type-check (push) Failing after 34s
CI Pipeline / gitea-offline (push) Successful in 18s
deploy / deploy (push) Failing after 15m11s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 9s
- #31: no ambient scan (repo_map is tool-only, TUI mount performs no walk, operator performs no scan) — 3 pins - #32: confirmation semantics (read-only executes, destructive requires, unknown fails closed, no TUI bypass) — 4 pins - #33: outcome causality (receipts derive from the journal, REJECT is negative evidence) — 3 pins - p1_02 mock updated to the #141 session_id signature 10/10 green. These issues' conditions hold in-tree; the tests pin them so they can never silently regress.
61 lines
2.4 KiB
Python
61 lines
2.4 KiB
Python
"""test_p1_09_confirmation_semantics.py — #32: permission semantics.
|
|
|
|
- read-only tools execute without confirmation
|
|
- destructive/external tools require confirmation
|
|
- unknown tools FAIL CLOSED (never auto-approved)
|
|
- the TUI never auto-approves owner-required actions (no auto_approve
|
|
path that bypasses tool_requires_approval)
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
REPO = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(REPO / "src"))
|
|
sys.path.insert(0, str(REPO / "src" / "rune"))
|
|
|
|
import tool_protocol as tp # noqa: E402
|
|
|
|
|
|
class TestConfirmationSemantics:
|
|
def test_read_only_needs_no_approval(self):
|
|
args_by_tool = {
|
|
"read_file": {"path": "/tmp"},
|
|
"list_directory": {"path": "/tmp"},
|
|
"grep_files": {"pattern": "x"},
|
|
"git_diff": {}, "git_log": {}, "repo_map": {"path": "/tmp"},
|
|
}
|
|
for name, arguments in args_by_tool.items():
|
|
req = tp.ToolRequest(call_id="c", name=name,
|
|
arguments=arguments, source="test")
|
|
assert tp.tool_requires_approval(req) is False, name
|
|
|
|
def test_destructive_requires_approval(self):
|
|
args_by_tool = {
|
|
"write_file": {"path": "/tmp/x", "content": "y"},
|
|
"edit_file": {"path": "/tmp/x", "search": "a", "replace": "b"},
|
|
"run_command": {"command": "ls"},
|
|
"git_commit": {"message": "m"},
|
|
}
|
|
for name, arguments in args_by_tool.items():
|
|
req = tp.ToolRequest(call_id="c", name=name,
|
|
arguments=arguments, source="test")
|
|
assert tp.tool_requires_approval(req) is True, name
|
|
|
|
def test_unknown_fails_closed(self):
|
|
req = tp.ToolRequest(call_id="c", name="no_such_tool",
|
|
arguments={}, source="test")
|
|
with pytest.raises(ValueError, match="unknown tool"):
|
|
tp.tool_requires_approval(req)
|
|
|
|
def test_no_tui_auto_approve_bypass(self):
|
|
"""No auto_approve path in the TUI that bypasses the protocol."""
|
|
src = (REPO / "src" / "rune" / "rune_tui.py").read_text()
|
|
for m in re.finditer(r"auto_approve", src):
|
|
context = src[max(0, m.start()-200):m.end()+200]
|
|
assert "tool_requires_approval" in context or "requires-approval" in context or "confirm" in context.lower(), \
|
|
f"auto_approve without protocol check at {src.count(chr(10), 0, m.start())}"
|