/** Whether a dispatch was declared read-only, and what it was going to run. */ export interface ReadOnlyRequest { /** The caller declared this dispatch must not mutate anything. */ readOnly: boolean; /** `"answer"` posts to the relay and spawns no harness, so it has no filesystem access. */ mode: "agent" | "answer"; /** The working directory the dispatch would use, before any default is applied. */ cwd: string | undefined; /** The tree the caller is protecting — its own checkout. */ callerRoot: string; } export type ReadOnlyVerdict = { ok: true; cwd: string; } | { ok: false; refusal: string; }; /** * Decide whether a read-only dispatch may proceed, and say why not when it may not. * * Containment is tested on RESOLVED paths with a separator boundary, never a bare `startsWith`: * `C:/caller/tree-other` shares a prefix with `C:/caller/tree` and is not inside it, and a literal * `..` segment resolves at the OS level before the comparison — the `checkCwd` defect closed * 2026-09-03 (docs/history/audit-findings-2026-09-03.md finding 1 / DR-002), applied here in the direction * where getting it wrong would WRONGLY PERMIT a mutation. * * Answer mode is always allowed: `startLane` skips the cwd/spawn path entirely for a `relay` rung * in answer mode, so there is no process and no working directory to confine. */ export declare function readOnlyVerdict(req: ReadOnlyRequest): ReadOnlyVerdict; /** Resolve the directory a dispatch will actually run in: the caller's `cwd`, else the default. */ export declare function resolveReadOnlyCwd(cwd: string | undefined, fallback?: string): string; /** A lane invocation as `dispatch.ts` renders it and `lane-runner.ts` spawns it. */ export interface LaneInvocation { command: string; args: string[]; env?: Record; } /** * Claude Code's read-only built-in tools. `Task`/`Agent` are excluded on purpose: a subagent is * another tool loop, and confining the parent says nothing about it. `WebFetch`/`WebSearch` read * the network, never the tree. */ export declare const CLAUDE_READ_ONLY_TOOLS: readonly ["Read", "Glob", "Grep", "WebFetch", "WebSearch"]; /** * The tools a read-only Claude lane is DENIED by name, over and above being absent from the allow * list. Belt and braces: `--tools` removes them from the model's view, `--permission-mode dontAsk` * auto-denies anything not allowed (MCP tools included), and this names the writers explicitly so * a future built-in that slips into the default set is still refused. Every first-party mutation * tool from `DEFAULT_DESTRUCTIVE` (`config.ts`) is here. */ export declare const CLAUDE_READ_ONLY_DENIED: readonly ["Bash", "BashOutput", "KillShell", "Edit", "Write", "MultiEdit", "NotebookEdit", "Task", "Agent"]; export type ReadOnlyInvokeVerdict = { ok: true; invoke: LaneInvocation; binding: string; } | { ok: false; reason: string; }; /** * Rewrite a lane invocation so the lane's OWN CLI runs it read-only, or say why that cannot be * done for this lane kind. Pure over the invocation; never spawns, never reads config. * * - `claude`: the harness's documented flags. `--permission-mode dontAsk` (CLAUDE.md: "`dontAsk` for * a read-only [lane] that fails loudly instead of silently" — NEVER `plan`, which a headless * `claude -p` can never leave), `--tools` + `--allowedTools` narrowed to `CLAUDE_READ_ONLY_TOOLS`, * and `--disallowedTools` naming the writers. Any permission flag the template carried is * REPLACED, so `acceptEdits` and a wide allow list cannot survive beside the read-only set. * - `codex`: `--sandbox read-only`, Codex's documented read-only policy, with `--full-auto` and * every bypass flag stripped. Enforcement is Codex's own sandbox; the relay states the policy. * - `opencode`: refused. Its tool permissions live per agent in its own `opencode.json`, which the * relay neither owns nor can verify from a command line. * - `agy`: refused. Its permission flags are unverified here, and a denied tool discards its whole * answer, so a bound AGY lane would return nothing where a claude lane returns a review. * - anything else: refused — an unrecognised binary is the unknown case, and unknown never claims. */ export declare function readOnlyInvoke(invoke: LaneInvocation): ReadOnlyInvokeVerdict;