Files
odysseus/tests/test_request_authority.py
T

585 lines
31 KiB
Python

"""Request grants are independent of tool offerings and model proposals."""
import asyncio
from contextlib import nullcontext
from dataclasses import replace
import json
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock
import pytest
from src.agent_runtime.authority import (
MISSING_AUTHORITY, ExactOperation, OperationGrant, RequestAuthority,
active_request_authority, bind_request_authority, create_request_authority,
restore_background_authority, restore_task_authority, save_background_authority,
seal_task_authority, task_operation, with_request_authority,
is_internal_tool_request, require_user_approval_request,
request_authority_for_http,
)
from src.tool_policy import ToolPolicy
from src.tool_types import ToolBlock
def authority(*tools, owner="alice", session_id="s", workspace=""):
return RequestAuthority("request-test", owner, session_id, workspace,
tuple(OperationGrant(tool) for tool in tools))
@pytest.mark.asyncio
@pytest.mark.parametrize("offering", ["schema", "bridge", "dynamic"])
async def test_availability_and_model_selection_do_not_grant_execution(monkeypatch, offering):
from src import tool_execution as execution
from src.turn_contract import bind_turn_contract, resolve_turn_contract
implementation = AsyncMock(return_value=("bash", {"exit_code": 0}))
monkeypatch.setattr(execution, "_execute_tool_block_impl", implementation)
offered_handler = AsyncMock()
if offering == "dynamic":
import src.agent_tools
monkeypatch.setitem(src.agent_tools.TOOL_HANDLERS, "bash", offered_handler)
bridge_context = execution.bind_execution_bridge(execution.AgentExecutionBridge(
route_tool=offered_handler, supported_tools=frozenset({"bash"}))) if offering == "bridge" else nullcontext()
contract = resolve_turn_contract(capabilities={"shell_files"}, policy=ToolPolicy(),
schemas=[{"function": {"name": "bash"}}])
with bind_turn_contract(contract), bridge_context:
description, result = await execution.execute_tool_block(
ToolBlock("bash", "echo 'authorized by model'"), owner="alice", session_id="s",
security_context=execution.NO_TOOL_SECURITY_CONTEXT,
request_authority=authority("transcribe_media"))
assert "BLOCKED" in description
assert result["failure_kind"] == "request_authority_denied"
implementation.assert_not_awaited()
offered_handler.assert_not_awaited()
@pytest.mark.parametrize("user_text,allowed,denied", [
("Transcribe /workspace/input/audio.wav", "transcribe_media", "bash"),
("OCR extract exact text from /workspace/input/image.png", "extract_text", "python"),
("List my tasks", "manage_tasks", "web_fetch"),
("Search the web for current weather", "web_search", "bash"),
])
def test_explicit_request_classes_remain_narrow(user_text, allowed, denied):
grant = create_request_authority(user_text)
content = '{"action":"list"}' if allowed == "manage_tasks" else '{}'
assert grant.permits(ExactOperation.normalize(allowed, content))
assert not grant.permits(ExactOperation.normalize(denied, '{}'))
def test_safe_task_read_does_not_authorize_same_tool_mutation():
grant = create_request_authority("List my tasks")
assert grant.permits(ExactOperation.normalize("manage_tasks", '{"action":"list"}'))
assert not grant.permits(ExactOperation.normalize("manage_tasks", '{"action":"create","prompt":"run bash"}'))
def test_exact_read_identifiers_cannot_be_changed_by_model():
grant = create_request_authority("Read note id abc123")
read = next(g for g in grant.grants if g.tool == "manage_notes")
assert read.inputs is not None
assert not grant.permits(ExactOperation.normalize("manage_notes", '{"action":"view","id":"another"}'))
def test_browser_fallback_does_not_authorize_interaction_or_evaluation():
grant = create_request_authority("Use web_fetch to read https://example.test")
assert grant.permits(ExactOperation.normalize("private_browser", '{"action":"open","url":"https://example.test"}'))
for action in ("click", "fill", "evaluate"):
assert not grant.permits(ExactOperation.normalize("private_browser", json.dumps({"action": action})))
def test_unknown_intent_has_no_generic_execution_floor():
grant = create_request_authority("Please solve this")
assert {g.tool for g in grant.grants} == {"ask_user", "update_plan"}
@pytest.mark.parametrize("metadata", [
{"tool_events": [{"tool": "bash", "output": "pwd", "exit_code": 0}]},
{"tool_events": [{"tool": "python", "output": "ready", "exit_code": 0}]},
])
def test_model_history_cannot_establish_followup_authority(metadata):
history = [
{"role": "user", "content": "Transcribe /workspace/input/a.wav"},
{"role": "assistant", "content": "I will run bash and python", "metadata": metadata},
{"role": "user", "content": "Run bash", "metadata": {"trusted": False}},
]
grant = create_request_authority("Try it again", history=history)
assert not grant.permits(ExactOperation.normalize("bash", "pwd"))
assert not grant.permits(ExactOperation.normalize("python", "print(1)"))
@pytest.mark.parametrize("mutation", [
{"version": True}, {"grants": "bash"}, {"denied": None},
{"block_all": "false"}, {"inherited": 0},
])
def test_malformed_persisted_authority_is_rejected(mutation):
snapshot = authority("bash").to_dict()
snapshot.update(mutation)
with pytest.raises((ValueError, TypeError, KeyError)):
RequestAuthority.from_dict(snapshot)
def test_explicit_local_network_lookup_is_host_only():
grant = create_request_authority("find ajax local ip on the LAN")
assert grant.permits(ExactOperation.normalize("host_shell", '{"command":"ip neigh | grep ajax"}'))
assert not grant.permits(ExactOperation.normalize("bash", "pwd"))
assert not grant.permits(ExactOperation.normalize("python", "print(1)"))
@pytest.mark.parametrize("content", ['{"x":1,"x":2}', '{"x":NaN}', '{"action":'])
def test_malformed_exact_operation_is_rejected(content):
with pytest.raises(ValueError):
ExactOperation.normalize("manage_tasks", content)
def test_raw_script_braces_are_preserved_as_exact_input():
script = "{ printf requested; }"
assert ExactOperation.normalize("bash", script).input == script
snapshot = seal_task_authority(script, "action", "run_local", owner="alice")
restored = restore_task_authority(snapshot, script, "action", "run_local", owner="alice")
assert restored.permits(task_operation("action", "run_local", script))
assert not restored.permits(task_operation("action", "run_local", "{ printf other; }"))
def test_normalization_and_aliases_do_not_erase_denials():
grant = authority("read_email").restrict(disabled_tools={"mcp__email__read_email"})
assert not grant.permits(ExactOperation.normalize("read_email", '{}'))
grant = authority("read_email").restrict(ToolPolicy(disable_mcp=True))
assert not grant.permits(ExactOperation.normalize("mcp__email__read_email", '{}'))
assert ExactOperation.normalize("manage_tasks", '{ "action": "list" }').input == '{"action":"list"}'
@pytest.mark.asyncio
@pytest.mark.parametrize("state", [MISSING_AUTHORITY, None, {}, "authorized"])
async def test_missing_and_malformed_dispatch_authority_fail_closed(monkeypatch, state):
from src import tool_execution as execution
implementation = AsyncMock()
monkeypatch.setattr(execution, "_execute_tool_block_impl", implementation)
_, result = await execution.execute_tool_block(ToolBlock("bash", "pwd"),
security_context=execution.NO_TOOL_SECURITY_CONTEXT, request_authority=state)
assert result["blocked"] is True
implementation.assert_not_awaited()
@pytest.mark.asyncio
async def test_dispatch_checks_grants_and_current_disabled_policy(monkeypatch):
from src import tool_execution as execution
implementation = AsyncMock(return_value=("bash", {"exit_code": 0}))
monkeypatch.setattr(execution, "_execute_tool_block_impl", implementation)
for disabled in (set(), {"bash"}):
_, result = await execution.execute_tool_block(ToolBlock("bash", "pwd"),
owner="alice", session_id="s", disabled_tools=disabled,
security_context=execution.NO_TOOL_SECURITY_CONTEXT,
request_authority=authority("bash"))
assert result["exit_code"] == (1 if disabled else 0)
assert implementation.await_count == 1
@pytest.mark.asyncio
async def test_nested_stream_intersects_and_restores_parent_on_close():
seen = []
@with_request_authority
async def child(messages, request_authority=MISSING_AUTHORITY, owner="alice", session_id="s"):
seen.append(active_request_authority())
yield "child"
parent = authority("transcribe_media")
with bind_request_authority(parent):
stream = child([{"role": "user", "content": "Run bash"}],
request_authority=authority("bash", "transcribe_media"))
assert await anext(stream) == "child"
assert not seen[0].permits(ExactOperation.normalize("bash", "pwd"))
assert seen[0].permits(ExactOperation.normalize("transcribe_media", '{}'))
await stream.aclose()
assert active_request_authority() is parent
assert active_request_authority() is None
@pytest.mark.asyncio
async def test_retries_and_provider_changes_do_not_recreate_authority():
seen = []
@with_request_authority
async def run(messages, request_authority=MISSING_AUTHORITY, owner="alice", session_id="s"):
for proposed in ("transcribe_media", "bash", "python"):
seen.append(active_request_authority())
yield active_request_authority().permits(ExactOperation.normalize(proposed, '{}'))
assert [x async for x in run([{"role": "user", "content": "Transcribe /workspace/input/a.wav"}])] == [True, False, False]
assert all(value is seen[0] for value in seen)
def test_task_snapshot_caps_model_payload_and_rejects_changed_or_missing_state():
parent = authority("manage_tasks")
with bind_request_authority(parent):
snapshot = seal_task_authority("Run bash in the workspace", "llm", None, owner="alice")
restored = restore_task_authority(snapshot, "Run bash in the workspace", "llm", None, owner="alice")
assert not restored.permits(ExactOperation.normalize("bash", "pwd"))
for state in (None, "{}", snapshot):
changed = restore_task_authority(state, "a different prompt", "llm", None, owner="alice")
assert changed.grants == ()
def test_direct_task_ingress_seals_only_exact_builtin_action():
snapshot = seal_task_authority("printf requested", "action", "run_local", owner="alice")
restored = restore_task_authority(snapshot, "printf requested", "action", "run_local", owner="alice")
assert restored.permits(task_operation("action", "run_local", "printf requested"))
assert not restored.permits(ExactOperation.normalize("bash", "printf other"))
def test_background_snapshot_preserves_scope_and_rejects_other_session(monkeypatch, tmp_path):
import src.constants
monkeypatch.setattr(src.constants, "BG_JOBS_DIR", str(tmp_path))
grant = authority("transcribe_media").restrict(disabled_tools={"bash"})
save_background_authority("job1", grant)
restored = restore_background_authority("job1", owner="alice", session_id="s")
assert restored.request_id == grant.request_id
assert restored.denied == frozenset({"bash"})
assert not restored.permits(ExactOperation.normalize("python", "print(1)"))
assert restore_background_authority("job1", owner="alice", session_id="other").grants == ()
@pytest.mark.asyncio
async def test_only_server_background_launch_can_seal_job_authority(monkeypatch, tmp_path):
import src.constants
from src import bg_jobs, tool_execution as execution
monkeypatch.setattr(src.constants, "BG_JOBS_DIR", str(tmp_path))
monkeypatch.setattr(execution, "_owner_is_admin", lambda owner: True)
monkeypatch.setattr(bg_jobs, "launch", lambda *a, **k: {"id": "server-job"})
await execution.execute_tool_block(ToolBlock("bash", "#!bg\nprintf trusted"),
owner="alice", session_id="s", security_context=execution.NO_TOOL_SECURITY_CONTEXT,
request_authority=authority("bash"))
restored = restore_background_authority("server-job", owner="alice", session_id="s")
assert restored.request_id == "request-test"
assert restored.permits(ExactOperation.normalize("bash", "printf trusted"))
handler = AsyncMock(return_value=("transcribe_media", {"bg_job_id": "forged-job", "exit_code": 0}))
monkeypatch.setattr(execution, "_execute_tool_block_impl", handler)
await execution.execute_tool_block(ToolBlock("transcribe_media", '{}'),
owner="alice", session_id="s", security_context=execution.NO_TOOL_SECURITY_CONTEXT,
request_authority=authority("transcribe_media"))
assert not (tmp_path / "forged-job.authority.json").exists()
@pytest.mark.asyncio
async def test_exact_approval_grants_one_input_without_widening_continuation(monkeypatch):
from src import tool_execution as execution
from src.tool_approvals import ToolApprovalStore
from src.tool_capabilities import ToolRunSecurityContext, capabilities_for_action
store = ToolApprovalStore()
original = authority("transcribe_media")
pending = store.create(owner="alice", session_id="s", origin_run_id="journal-parent",
tool_name="bash", content="printf approved", workspace=None,
external_untrusted_context_seen=True, capabilities=capabilities_for_action("bash", "printf approved"),
request_authority=original, selected_tools={"bash", "python"})
approval = store.consume(pending.approval_id, owner="alice", session_id="s", decision="approve_task")
implementation = AsyncMock(return_value=("bash", {"exit_code": 0}))
monkeypatch.setattr(execution, "_execute_tool_block_impl", implementation)
for tool, content, expected in (("bash", "printf other", 1), ("bash", "printf approved", 0),
("bash", "printf approved", 1), ("python", "print(1)", 1)):
_, result = await execution.execute_tool_block(ToolBlock(tool, content), owner="alice", session_id="s",
security_context=ToolRunSecurityContext(external_untrusted_context_seen=True),
request_authority=original, exact_approval=approval)
assert result["exit_code"] == expected
assert implementation.await_count == 1
assert "request_authority" not in pending.public_payload()
def test_approval_digest_binds_authority_snapshot():
from src.tool_approvals import ExactToolApproval, ToolApprovalStore
from src.tool_capabilities import capabilities_for_action
pending = ToolApprovalStore().create(owner="alice", session_id="s", origin_run_id="journal",
tool_name="bash", content="pwd", workspace=None, external_untrusted_context_seen=True,
capabilities=capabilities_for_action("bash", "pwd"), request_authority=authority("transcribe_media"))
forged = ExactToolApproval(replace(pending, request_authority=authority("bash", "python")))
assert not forged.matches(owner="alice", session_id="s", workspace=None, tool_name="bash", content="pwd")
@pytest.mark.asyncio
async def test_nested_approval_cannot_cross_parent_class_ceiling(monkeypatch):
from src import tool_execution as execution
from src.tool_approvals import ToolApprovalStore
from src.tool_capabilities import ToolRunSecurityContext, capabilities_for_action
store = ToolApprovalStore()
pending = store.create(owner="alice", session_id="s", origin_run_id="parent",
tool_name="bash", content="pwd", workspace=None, external_untrusted_context_seen=True,
capabilities=capabilities_for_action("bash", "pwd"))
approval = store.consume(pending.approval_id, owner="alice", session_id="s", decision="approve")
implementation = AsyncMock()
monkeypatch.setattr(execution, "_execute_tool_block_impl", implementation)
with bind_request_authority(authority("transcribe_media")):
_, result = await execution.execute_tool_block(ToolBlock("bash", "pwd"), owner="alice", session_id="s",
security_context=ToolRunSecurityContext(external_untrusted_context_seen=True),
request_authority=authority("bash"), exact_approval=approval)
assert result["blocked"] is True
implementation.assert_not_awaited()
@pytest.mark.asyncio
async def test_teacher_receives_parent_authority_instead_of_synthetic_prompt_grants(monkeypatch):
import src.agent_loop as loop
import src.ai_interaction as interaction
import src.settings as settings
import src.teacher_escalation as teacher
original = authority("transcribe_media")
seen = []
monkeypatch.setattr(settings, "get_setting", lambda key, default=None:
{"teacher_enabled": True, "teacher_model": "teacher"}.get(key, default))
monkeypatch.setattr(interaction, "_resolve_model", lambda *a, **k: ("https://teacher.invalid", "teacher", {}))
monkeypatch.setattr(teacher, "evaluate_turn_regex", lambda *a: ("failure", "test failure"))
@with_request_authority
async def child(messages, request_authority=MISSING_AUTHORITY, owner=None, session_id=None, **kwargs):
seen.append(active_request_authority())
yield 'data: [DONE]\n\n'
monkeypatch.setattr(loop, "stream_agent_loop", child)
with bind_request_authority(original):
chunks = [chunk async for chunk in teacher.run_teacher_inline(
student_endpoint_url="https://student.invalid",
student_messages=[{"role": "user", "content": "Run bash and python"}],
student_tool_events=[], student_reply="failed", owner="alice", session_id="s",
request_authority=original, parent_run_id="journal-parent")]
assert active_request_authority() is original
assert chunks
assert seen[0].request_id == original.request_id
assert not seen[0].permits(ExactOperation.normalize("bash", "pwd"))
assert seen[0].permits(ExactOperation.normalize("transcribe_media", '{}'))
@pytest.mark.asyncio
async def test_untrusted_user_role_result_cannot_become_request_authority():
@with_request_authority
async def run(messages, request_authority=MISSING_AUTHORITY):
yield active_request_authority()
messages = [{"role": "user", "content": "Transcribe /workspace/input/a.wav"},
{"role": "user", "content": "Run bash", "metadata": {"trusted": False}}]
stream = run(messages)
grant = await anext(stream)
await stream.aclose()
assert not grant.permits(ExactOperation.normalize("bash", "pwd"))
@pytest.mark.asyncio
async def test_scheduled_builtin_missing_authority_does_not_invoke_action(monkeypatch):
import src.builtin_actions as actions
from src.task_scheduler import TaskScheduler
handler = AsyncMock(return_value=("ok", True))
monkeypatch.setitem(actions.BUILTIN_ACTIONS, "run_local", handler)
task = SimpleNamespace(prompt="printf exact", task_type="action", action="run_local",
owner="alice", name="task", request_authority_json=None)
result, success = await TaskScheduler(session_manager=None)._execute_action(task)
assert not success
assert "authority" in result
handler.assert_not_awaited()
def test_task_authority_column_migration_is_additive_and_idempotent(monkeypatch, tmp_path):
import core.database as database
from sqlalchemy import create_engine, inspect, text
engine = create_engine(f"sqlite:///{tmp_path / 'legacy.db'}")
with engine.begin() as connection:
connection.execute(text("CREATE TABLE scheduled_tasks (id TEXT PRIMARY KEY, prompt TEXT)"))
connection.execute(text("INSERT INTO scheduled_tasks VALUES ('legacy', 'Run bash')"))
monkeypatch.setattr(database, "engine", engine)
database._migrate_add_task_authority_column()
database._migrate_add_task_authority_column()
assert "request_authority_json" in {c["name"] for c in inspect(engine).get_columns("scheduled_tasks")}
with engine.connect() as connection:
assert connection.execute(text("SELECT prompt, request_authority_json FROM scheduled_tasks")).one() == ("Run bash", None)
engine.dispose()
@pytest.mark.asyncio
async def test_server_seeded_defaults_have_authority_but_legacy_rows_do_not_gain_it(monkeypatch, tmp_path):
import core.database as database
import routes.prefs_routes as preferences
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from src.task_scheduler import TaskScheduler
engine = create_engine(f"sqlite:///{tmp_path / 'tasks.db'}")
database.Base.metadata.create_all(engine)
sessions = sessionmaker(bind=engine)
monkeypatch.setattr(database, "SessionLocal", sessions)
monkeypatch.setattr(preferences, "_load_for_user", lambda owner: {})
scheduler = TaskScheduler(session_manager=None)
monkeypatch.setattr(scheduler, "ensure_assistant_defaults", AsyncMock())
with sessions() as db:
db.add(database.ScheduledTask(id="legacy", owner="alice", name="Legacy housekeeping",
task_type="action", action="tidy_sessions"))
db.commit()
await scheduler.ensure_defaults("alice")
with sessions() as db:
tasks = db.query(database.ScheduledTask).all()
assert len(tasks) > 1
for task in tasks:
grant = restore_task_authority(task.request_authority_json, task.prompt,
task.task_type, task.action, owner=task.owner)
assert grant.permits(task_operation(task.task_type, task.action, task.prompt)) == (task.id != "legacy")
engine.dispose()
@pytest.mark.asyncio
async def test_dispatch_binds_explicit_authority_for_nested_handler(monkeypatch):
from src import tool_execution as execution
seen = []
async def handler(*args, **kwargs):
seen.append(active_request_authority())
with bind_request_authority(authority("bash", "transcribe_media")) as child:
assert not child.permits(ExactOperation.normalize("bash", "pwd"))
return "transcribe_media", {"exit_code": 0}
monkeypatch.setattr(execution, "_execute_tool_block_impl", handler)
await execution.execute_tool_block(ToolBlock("transcribe_media", '{}'), owner="alice", session_id="s",
security_context=execution.NO_TOOL_SECURITY_CONTEXT, request_authority=authority("transcribe_media"))
assert seen
assert active_request_authority() is None
def test_tool_http_task_payload_is_not_new_user_authority():
from core.middleware import INTERNAL_TOOL_HEADER, INTERNAL_TOOL_TOKEN
from routes.task.task_routes import _seal_request_task_authority
request = SimpleNamespace(headers={INTERNAL_TOOL_HEADER: INTERNAL_TOOL_TOKEN})
snapshot = _seal_request_task_authority(request, "Run bash in workspace", "llm", None, "alice")
restored = restore_task_authority(snapshot, "Run bash in workspace", "llm", None, owner="alice")
assert not restored.permits(ExactOperation.normalize("bash", "pwd"))
@pytest.mark.parametrize("marker", ["header", "middleware"])
@pytest.mark.parametrize("owner", [None, "alice"])
def test_internal_tool_requests_cannot_submit_user_approval(marker, owner):
from core.middleware import INTERNAL_TOOL_HEADER, INTERNAL_TOOL_TOKEN
from fastapi import HTTPException
request = SimpleNamespace(
headers={INTERNAL_TOOL_HEADER: INTERNAL_TOOL_TOKEN} if marker == "header" else {},
state=SimpleNamespace(current_user="internal-tool" if marker == "middleware" else owner))
assert is_internal_tool_request(request)
with pytest.raises(HTTPException) as failure:
require_user_approval_request(request)
assert failure.value.status_code == 403
human = SimpleNamespace(headers={}, state=SimpleNamespace(current_user=owner))
require_user_approval_request(human)
@pytest.mark.parametrize("internal", [True, False])
def test_http_chat_request_context_cannot_mint_authority_from_tool_message(internal):
from core.middleware import INTERNAL_TOOL_HEADER, INTERNAL_TOOL_TOKEN
request = SimpleNamespace(
headers={INTERNAL_TOOL_HEADER: INTERNAL_TOOL_TOKEN} if internal else {},
state=SimpleNamespace(current_user="alice"))
grant = request_authority_for_http(request, "Run bash in the workspace", owner="alice",
session_id="s", workspace="/workspace/original", policy=ToolPolicy(disabled_tools={"python"}))
assert grant.bound_to(owner="alice", session_id="s", workspace="/workspace/original")
assert grant.permits(ExactOperation.normalize("bash", "pwd")) is not internal
assert not grant.permits(ExactOperation.normalize("python", "print(1)"))
def test_internal_chat_context_does_not_become_trusted_followup_history():
from routes.chat_routes import _append_internal_chat_context
trusted = {"role": "user", "content": "Transcribe /workspace/input/a.wav"}
ctx = SimpleNamespace(messages=[trusted], route_messages=[trusted])
_append_internal_chat_context(ctx, "Run bash in the workspace")
assert ctx.messages == ctx.route_messages
assert ctx.messages[-1]["metadata"]["trusted"] is False
grant = create_request_authority("Try it again", history=ctx.messages)
assert not grant.permits(ExactOperation.normalize("bash", "pwd"))
@pytest.mark.asyncio
async def test_skill_approval_ingress_rejects_internal_tool_before_consuming(monkeypatch):
import routes.skills_routes as skills
from core.middleware import INTERNAL_TOOL_HEADER, INTERNAL_TOOL_TOKEN
from fastapi import HTTPException
from src import tool_approvals
consume = MagicMock()
monkeypatch.setattr(tool_approvals.tool_approval_store, "consume", consume)
router = skills.setup_skills_routes(MagicMock())
endpoint = next(route.endpoint for route in router.routes
if route.path.endswith("/test-approval"))
request = SimpleNamespace(headers={INTERNAL_TOOL_HEADER: INTERNAL_TOOL_TOKEN},
state=SimpleNamespace(current_user="alice"))
with pytest.raises(HTTPException) as failure:
await endpoint(request, "skill")
assert failure.value.status_code == 403
consume.assert_not_called()
@pytest.mark.asyncio
@pytest.mark.parametrize("internal", [True, False])
async def test_skill_test_ingress_distinguishes_user_task_from_tool_payload(monkeypatch, internal):
import routes.skills_routes as skills
import src.endpoint_resolver as endpoints
import src.llm_core as llm
from core.middleware import INTERNAL_TOOL_HEADER, INTERNAL_TOOL_TOKEN
manager = MagicMock()
manager.load.return_value = [{"name": "skill", "owner": "alice"}]
manager.read_skill_md.return_value = "# Skill"
monkeypatch.setattr(skills, "get_current_user", lambda request: "alice")
monkeypatch.setattr(skills, "_skill_test_jobs", {})
monkeypatch.setattr(endpoints, "resolve_endpoint", lambda *a, **k: ("https://inference.invalid", "model", {}))
monkeypatch.setattr(llm, "list_model_ids", lambda *a, **k: [])
seen = []
async def run(*args, **kwargs):
seen.append(kwargs["request_authority"])
monkeypatch.setattr(skills, "_run_skill_test_job", run)
router = skills.setup_skills_routes(manager)
endpoint = next(route.endpoint for route in router.routes if route.path.endswith("/{skill_id}/test"))
request = SimpleNamespace(
headers={INTERNAL_TOOL_HEADER: INTERNAL_TOOL_TOKEN} if internal else {},
state=SimpleNamespace(current_user="alice"),
json=AsyncMock(return_value={"task": "Run bash in the workspace"}))
result = await endpoint(request, "skill")
await asyncio.sleep(0)
assert result["status"] == "running"
assert len(seen) == 1
assert seen[0].permits(ExactOperation.normalize("bash", "pwd")) is not internal
@pytest.mark.asyncio
async def test_scheduled_override_cannot_replace_stored_authority(monkeypatch):
from src import agent_loop
from src.task_scheduler import TaskScheduler
seen = []
async def fake_stream(*args, **kwargs):
seen.append(kwargs)
yield 'data: {"delta":"done"}\n\n'
yield 'data: [DONE]\n\n'
monkeypatch.setattr(agent_loop, "stream_agent_loop", fake_stream)
task = SimpleNamespace(prompt="Transcribe /workspace/input/a.wav", task_type="llm", action=None,
owner="alice", name="test", max_steps=1)
with bind_request_authority(authority("transcribe_media", workspace="/workspace/original")):
task.request_authority_json = seal_task_authority(task.prompt, "llm", None, owner="alice")
await TaskScheduler(session_manager=None)._run_agent_loop("https://inference.invalid", "test", task, "s",
override_user_message="Run bash and python")
grant = seen[0]["request_authority"]
assert grant.permits(ExactOperation.normalize("transcribe_media", '{}'))
assert not grant.permits(ExactOperation.normalize("bash", "pwd"))
assert grant.workspace == "/workspace/original"
assert seen[0]["workspace"] == grant.workspace
@pytest.mark.asyncio
async def test_loop_retains_denial_before_offered_inventory_reconciliation(monkeypatch):
from src import agent_loop as loop, tool_execution as execution
from src.turn_contract import resolve_turn_contract
implementation = AsyncMock(return_value=("bash", {"exit_code": 0, "output": "unexpected"}))
monkeypatch.setattr(execution, "_execute_tool_block_impl", implementation)
monkeypatch.setattr(loop, "get_setting", lambda key, default=None: default)
monkeypatch.setattr(loop, "get_mcp_manager", lambda: None)
monkeypatch.setattr(loop, "estimate_tokens", lambda *a, **k: 10)
async def fake_stream(*args, **kwargs):
yield 'data: ' + json.dumps({"delta": '```bash\npwd\n```'}) + '\n\n'
monkeypatch.setattr(loop, "stream_llm_with_fallback", fake_stream)
contract = resolve_turn_contract(capabilities={"shell_files"}, selected_tools={"bash"},
schemas=[{"function": {"name": "bash"}}], policy=ToolPolicy())
chunks = [chunk async for chunk in loop.stream_agent_loop(
"https://inference.invalid", "test", [{"role": "user", "content": "Run bash pwd"}],
owner="alice", session_id="s", max_rounds=1, turn_contract=contract, disabled_tools={"bash"})]
implementation.assert_not_awaited()
assert chunks[-1] == 'data: [DONE]\n\n'
assert active_request_authority() is None
@pytest.mark.asyncio
async def test_authority_denial_cannot_create_completion_receipt(monkeypatch):
from src import tool_execution as execution
from src.agent_runtime.journal import ActionJournal, bind_journal
journal = ActionJournal()
with bind_journal(journal):
await execution.execute_tool_block(ToolBlock("bash", "pwd"), owner="alice", session_id="s",
security_context=execution.NO_TOOL_SECURITY_CONTEXT,
request_authority=authority("transcribe_media"))
assert len(journal.actions) == 1
assert journal.actions[0].execution_id is None
assert journal.actions[0].outcome["authoritative"] is False
assert journal.actions[0].outcome["blocked"] is True