ToolPermission it returns (allow /
block / ask). It can block and redirect control flow; the event emitter cannot.
Keeping it a direct call — never an emitter subscriber — is the whole point.
Tiers (§11): 0 reads (allow) · 1 edits — str_replace/write_file (allow iff every
target path resolves inside the workspace) · 2 commands (allow) · 3+ destructive / external
(blocked by default in the non-interactive MVP; ask lands with the Phase 3 REPL).
Tier 1 is allowed for every task kind, including investigate (ADR-0005): transient
instrumentation is legal there, and the verifier’s net-zero-diff contract — not this
gate — enforces that the tree matches the pinned baseline at verification (detection
where prevention used to be). The sensitive-path denylist and workspace confinement
still apply to every kind.
Classes
PermissionPolicy
Evaluates a tool call against the §11 tier table before it runs.
Args:
sensitive_path_globs: The denylist enforced over every tool’s declared paths.
Defaults to the built-in set (secure by default); the runner threads the
configured HarnessConfig.sensitive_path_globs through.
PermissionPolicy.check(self, tool: ToolDefinition, raw_input: dict, state: TaskState, ws: Workspace) -> ToolPermission
Return the control decision for tool with raw_input (allow / block / ask).
Tier 1 (mutation) is allowed for every task kind: investigate tasks may
instrument transiently (ADR-0005), with the verifier’s net-zero-diff contract
as the enforcement point.
Args:
tool: The tool definition, carrying its permission_tier and declared paths.
raw_input: The proposed tool arguments.
state: The current task state (reserved for state-aware policies).
ws: The run-scoped workspace, used for path confinement.
Returns:
The ToolPermission the runner acts on.