mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
2bb55590bf
Delete the entire turnstone/mq/ package (broker, bridge, protocol, client) and turnstone/sim/ package. Remove Redis as a dependency. Channel gateway and console now communicate with server nodes via direct HTTP (httpx + httpx-sse) instead of Redis pub/sub and queues. Single-node deployments work with zero infrastructure beyond the database. Key changes: - Channel adapters use httpx POST for create/send/approve/close and httpx-sse for per-workstream event streaming - Console collector discovers nodes via services table instead of Redis SCAN - Console scheduler dispatches tasks via HTTP POST with DB-based leader election - Server registers in services table with 30s heartbeat - Server accepts optional ws_id in create request (for Phase 2 console-generated routing) - SDK events gain IntentVerdictEvent and OutputWarningEvent types - All docs, examples, bootstrap wizard updated 63 files changed, -5968 net lines (Redis transport fully removed)
86 lines
2.6 KiB
Python
86 lines
2.6 KiB
Python
"""Tests for tool policy enforcement in the CLI entry point."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from turnstone.cli import TerminalUI
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CLI
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCLIPolicyEnforcement:
|
|
"""Tool policies should be enforced in CLI approve_tools()."""
|
|
|
|
def _make_items(self, *tool_names: str) -> list[dict]:
|
|
return [
|
|
{
|
|
"call_id": f"call_{i}",
|
|
"header": f"Tool: {name}",
|
|
"preview": "",
|
|
"func_name": name,
|
|
"approval_label": name,
|
|
"needs_approval": True,
|
|
}
|
|
for i, name in enumerate(tool_names)
|
|
]
|
|
|
|
def test_deny_policy_blocks_tool(self):
|
|
"""A 'deny' policy verdict should block the tool without prompting."""
|
|
ui = TerminalUI()
|
|
items = self._make_items("bash")
|
|
|
|
with (
|
|
patch(
|
|
"turnstone.core.policy.evaluate_tool_policies_batch",
|
|
return_value={"bash": "deny"},
|
|
),
|
|
patch(
|
|
"turnstone.core.storage._registry.get_storage",
|
|
return_value=MagicMock(),
|
|
),
|
|
):
|
|
approved, _ = ui.approve_tools(items)
|
|
|
|
assert items[0].get("denied") is True
|
|
assert items[0].get("error")
|
|
assert "policy" in items[0]["error"].lower()
|
|
|
|
def test_allow_policy_auto_approves(self):
|
|
"""An 'allow' policy verdict should auto-approve without prompting."""
|
|
ui = TerminalUI()
|
|
items = self._make_items("read_file")
|
|
|
|
with (
|
|
patch(
|
|
"turnstone.core.policy.evaluate_tool_policies_batch",
|
|
return_value={"read_file": "allow"},
|
|
),
|
|
patch(
|
|
"turnstone.core.storage._registry.get_storage",
|
|
return_value=MagicMock(),
|
|
),
|
|
):
|
|
approved, _ = ui.approve_tools(items)
|
|
|
|
assert approved is True
|
|
|
|
def test_no_storage_skips_policies(self):
|
|
"""When storage is unavailable, policies are skipped (best-effort)."""
|
|
ui = TerminalUI()
|
|
items = self._make_items("bash")
|
|
|
|
with (
|
|
patch(
|
|
"turnstone.core.storage._registry.get_storage",
|
|
return_value=None,
|
|
),
|
|
patch("builtins.input", return_value="y"),
|
|
):
|
|
approved, _ = ui.approve_tools(items)
|
|
|
|
# Should fall through to normal prompt (which we answered 'y')
|
|
assert approved is True
|