from __future__ import annotations
from dataclasses import dataclass, field
from typing import Callable

@dataclass(frozen=True)
class Proposal:
    tool: str
    argument: str

@dataclass
class RunState:
    events: list[str] = field(default_factory=list)
    result: str | None = None
    status: str = "CREATED"

class Policy:
    def __init__(self, allowed: set[str]):
        self.allowed = allowed
    def authorize(self, proposal: Proposal) -> bool:
        return proposal.tool in self.allowed

class ToolExecutor:
    def __init__(self, tools: dict[str, Callable[[str], str]]):
        self.tools = tools
    def execute(self, proposal: Proposal) -> str:
        return self.tools[proposal.tool](proposal.argument)

class Runner:
    def __init__(self, policy: Policy, executor: ToolExecutor):
        self.policy, self.executor = policy, executor
    def run(self, proposal: Proposal) -> RunState:
        state = RunState(status="RUNNING")
        state.events.append("action.proposed")
        if not self.policy.authorize(proposal):
            state.status = "DENIED"
            state.events.append("policy.denied")
            return state
        state.events.append("policy.allowed")
        try:
            state.result = self.executor.execute(proposal)
            state.status = "COMPLETED"
            state.events.append("tool.completed")
        except Exception:
            state.status = "FAILED"
            state.events.append("tool.failed")
        return state

if __name__ == "__main__":
    tools = ToolExecutor({"lookup": lambda q: f"evidence:{q}"})
    runner = Runner(Policy({"lookup"}), tools)
    ok = runner.run(Proposal("lookup", "Agent System"))
    denied = runner.run(Proposal("delete_file", "/important"))
    assert ok.status == "COMPLETED" and ok.result == "evidence:Agent System"
    assert denied.status == "DENIED" and "policy.denied" in denied.events
    print(ok.status, ok.events)
    print(denied.status, denied.events)
