/** * Decision → provider-neutral tool-result + loop-action translator. * * One branch per `Decision.kind`. Returns: * - `toolResult` — the provider-neutral `ToolResultBlock` that goes back * to the model in the next user-role message (or `null` if no tool- * result is sent). * - `loopAction` — what the loop should do next: `continue` (next * iteration), `pause_for_user_confirmation` / `pause_for_defer` * (return outcome to adopter), or `complete_for_escalation` * (terminate the turn). * - `events` — `AgentEvent`s to push for audit / transcript display. * * **REWRITE** runs the executor against the *rewritten* envelope (NOT * the original) and surfaces a human-readable note in the tool-result * by default. * * **REWRITE re-adjudication contract (011/T4).** A REWRITE Decision only ever * reaches this translator from `adjudicateAndAudit` (the audited kernel path the * loop drives). That path re-enters the PURE kernel on the rewritten envelope — * re-deriving its `intentHash` fail-closed, re-running the full guard order, and * blocking a taint-elevating rewrite — and surfaces a `REWRITE` Decision ONLY * when the rewritten envelope passed a SECOND-pass EXECUTE. It also records the * EXECUTED (rewritten) hash in the audit row and claims the rewritten hash in * the ledger. So executing `decision.rewritten` here is NOT a raw, un-adjudicated * `invokeIntent`: the kernel already authorized and recorded these exact bytes. * `runExecute` defends this contract — it refuses to execute a rewritten envelope * whose `intentHash` does not re-derive from its own content (a forged Decision * spliced in outside the kernel path). * * **023 — resource-binding (executor honors the signed payload).** `runExecute` * now enforces the resource binding for BOTH EXECUTE and REWRITE before the side * effect: it re-derives the envelope's `intentHash` (`verifyResourceBinding`, * the untouched `intentHashInput` recipe) and constant-time-compares it against * the carried hash. This SUBSUMES the 011/T4 forged-rewrite check and EXTENDS the * same fence to the EXECUTE payload, so a `payload` / `resourceRefs` swapped * AFTER the kernel decision (anti-IDOR / anti-resource-swap) fail-closes and the * executor is never invoked (invariants #1, #6). It coexists with 012's READ * routing (reads serve via `invokeRead`, never `invokeIntent`, and never reach * this binding gate) and 013's required `auditSink` (the kernel crossing that * produced this Decision already emitted the durable record). * * **First non-continue Decision wins**: if multiple tool_use blocks fire * in the same assistant turn, the loop processes them in order but * stops translating the moment a non-continue Decision arrives. The * remaining blocks are surfaced as `not_processed_due_to_pause`. */ import type { AuditSink, BudgetGrant, Decision, ExecutorContract, IntentEnvelope, Ledger, ResourceBindingPolicy, TaintPolicy } from "@adjudicate/core"; import { type RuntimeContext } from "@adjudicate/core/kernel"; import type { PolicyBundle } from "@adjudicate/core/kernel"; import { AdapterError, AdapterErrorCode } from "./errors.js"; import type { ConfirmationStore, DeferRedis, ParkRedis } from "./persistence.js"; import type { AdopterExecutor, AgentEvent, AgentLogger, CapabilityGate, ToolClassification, ToolResultBlock } from "./types.js"; export interface DecisionTranslationContext { readonly decision: Decision; readonly envelope: IntentEnvelope; readonly toolUseId: string; readonly sessionId: string; readonly state: S; readonly executor: AdopterExecutor; readonly deferStore: DeferRedis & ParkRedis; readonly confirmationStore: ConfirmationStore; readonly historySnapshot: H; readonly rk: (raw: string) => string; readonly log?: AgentLogger; /** * Per-turn token generator. Adapter passes `crypto.randomUUID()` by * default; tests can inject a deterministic generator. */ readonly generateToken: () => string; /** * Optional executor output contract for this envelope's kind (resolved by the * loop from `PackV0.executorContract`). When present, `runExecute` validates * the executor's return value AFTER `invokeIntent` and PREPENDS an * `executor_contract_violation` event on mismatch — never altering the tool * result or loop action. */ readonly executorContract?: ExecutorContract; /** * 023 — resource-binding policy enforced at the executor seam before * `invokeIntent`. `"strict"` (default) / `"warn"` fail-close the EXECUTE when * the envelope's payload/resource-refs no longer re-derive its `intentHash` * (anti-IDOR / anti-resource-swap); `"off"` is the rollback dial that restores * the pre-023 seam. Mirrors `verifyParkedHash` on the parked-envelope path so * the two binding checks share one staged-rollout vocabulary. */ readonly resourceBindingPolicy?: ResourceBindingPolicy; /** * 024 — cap-gated executor. When present, `runExecute` BURNS the single-use * capability the loop minted into `capabilityGate.burnStore` (keyed by the * effective envelope's nonce), ed25519-VERIFIES it (`capabilityGate.verify` — * the injected `verifyCapabilitySignature`, NOT the forgeable hash-bind check), * and binds it to the effective envelope's `intentHash` BEFORE `invokeIntent`. * A burn miss/expiry, store error, bad signature, or hash mismatch fail-closes * the EXECUTE (invariants #1, #6). Absent (default) → the pre-024 seam. */ readonly capabilityGate?: CapabilityGate; } export type LoopAction = { readonly kind: "continue"; } | { readonly kind: "pause_for_user_confirmation"; readonly prompt: string; readonly token: string; } | { readonly kind: "pause_for_defer"; readonly signal: string; readonly intentHash: string; } | { readonly kind: "complete_for_escalation"; readonly to: "human" | "supervisor"; readonly reason: string; }; export interface DecisionTranslation { readonly toolResult: ToolResultBlock | null; readonly loopAction: LoopAction; readonly extraEvents: ReadonlyArray; } /** * 025 — shell budget burn-down (decrement-then-assert-grant). * * The IMPURE-shell authority step for capabilities-as-budgets. When the kernel * returns REQUEST_CONFIRMATION for an intent kind a standing budget grant covers, * the shell ATOMICALLY decrements the budget via the `ParkRedis.evalIncrCheck` * Lua primitive — increment-and-check against `limit` — and asserts the kernel * budget grant ONLY when the decrement stayed in-budget. This is the §6 atomic * burn-down: `evalIncrCheck(counterKey, windowSeconds, limit)` returns `0` when * the increment would exceed `limit` (the Lua script already rolled it back — * at-most-`limit` across replicas, NOT the non-atomic GET+DEL the confirmation * store documents), or the new count (`>= 1`) when in-budget. * * Authority stays in THIS single-use-counted counter — never the lossy * display-only approval registry. Fail-closed (§D #6 / index §C): over-limit, a * client without `evalIncrCheck`, or a store/IO error returns `false`, so the * caller re-uses the original REQUEST_CONFIRMATION (friction, never bypass). * Returns `true` ⇒ the caller may assert the kernel grant for ONE substitution. */ export declare function runBudgetBurnDown(args: { readonly store: Pick; readonly grant: BudgetGrant; readonly rk: (raw: string) => string; readonly log?: AgentLogger; }): Promise; /** * Translate a `Decision` into a provider-neutral `ToolResultBlock` plus * the next loop action. The caller (the send loop) appends the tool- * result to the next user-role message and either continues or pauses * based on `loopAction.kind`. */ export declare function translateDecision(ctx: DecisionTranslationContext): Promise; /** * Build the provider-neutral `ToolResultBlock` for an out-of-plan tool * call. Re-exported so tests + adopters can construct one from outside * the loop. */ export declare function makeOutOfPlanToolResult(toolUseId: string, toolName: string): ToolResultBlock; /** * 012 — read-authorization PolicyBundle. * * A model-proposed READ is no longer dispatched straight to `invokeRead`. It * builds an envelope and crosses `adjudicateAndAudit`, so the taint gate, the * required audit sink, and the ledger apply uniformly — restoring the §B * single-authority property (the kernel decides for READs too). * * The read envelope is adjudicated against THIS policy (derived per-call from * the Pack's own `taint` policy), not the Pack's mutation policy: * - `taint` = the Pack's taint policy, so a taint-protected read tool (one * whose `minimumFor` demands TRUSTED/SYSTEM) is REFUSED for an UNTRUSTED * proposal exactly like a protected intent — no UNTRUSTED read EXECUTEs * when the policy forbids it. * - no state/auth/business guards, because read tool *names* are not intent * kinds the Pack's guards are written against; the plan's * `visibleReadTools` membership is already enforced upstream by * `classifyIncomingToolUse` (an out-of-plan read never reaches here). * - `default: "EXECUTE"` so a visible, taint-passing read is authorized and * served. This is NOT a fail-open mutation default: an `EXECUTE` here only * ever reaches `invokeRead` — the READ-ONLY executor surface the * `safePlan` / `assertPlanReadOnly` contract guarantees is non-mutating. * Only a kernel `EXECUTE` reaches the executor (§D #1); any REFUSE (taint, * kill-switch, replay-suppression) means the read is NOT served. * * The kernel stays pure: this is an ordinary `PolicyBundle`, no heuristic or * IO is introduced inside `adjudicate()`. */ export declare function readAuthorizationPolicy(taint: TaintPolicy): PolicyBundle; export interface RouteReadContext { /** Typed READ classification produced by the bridge (`kind: "read"`). */ readonly classification: Extract; readonly toolUseId: string; readonly sessionId: string; readonly state: S; /** * READ-ONLY executor surface only — `invokeRead`. The mutating * `invokeIntent` is intentionally not part of this contract: a READ may * never reach it. K/P do not appear in `invokeRead`, so this is generic over * state only (no cast needed at the call site). */ readonly executor: Pick, "invokeRead">; /** Pack taint policy — the read envelope is adjudicated against it. */ readonly taint: TaintPolicy; /** * Required durable AuditSink (013/T1). The READ path crosses the same audited * kernel as intents — a missing sink is a construction-time type error, never a * silent `noopAuditSink()` no-op (invariant #6). */ readonly auditSink: AuditSink; readonly ledger?: Ledger; /** * Required tenant RuntimeContext (013/T3). Non-optional so the kernel * kill-switch is ALWAYS consulted on the READ path — an omitted control no * longer skips the check (§C: friction, never bypass). The adapter resolves it * to the process-wide default context when no tenant context is supplied. */ readonly runtimeContext: RuntimeContext; /** Plan snapshot accessor for the audit row (observability). */ readonly plan: () => { readonly visibleReadTools: ReadonlyArray; readonly allowedIntents: ReadonlyArray; }; /** Deterministic nonce derivation, mirroring the intent path. */ readonly nonce: string; /** History snapshot is unused by READs but kept for shape symmetry. */ readonly historySnapshot: H; } /** * 012 / T3 — route a classified READ through the audited kernel. * * Builds the read envelope (kind = read tool name, taint UNTRUSTED — reads are * model-originated, so they inherit the same untrusted provenance as intents), * crosses `adjudicateAndAudit`, and serves the read via `invokeRead` ONLY on a * kernel `EXECUTE`. A non-EXECUTE Decision (REFUSE on taint/kill/replay) * surfaces a tool result and never touches the executor — there is no direct, * unadjudicated `invokeRead` dispatch anywhere on this path. */ export declare function routeReadThroughKernel(ctx: RouteReadContext): Promise<{ readonly toolResult: ToolResultBlock; readonly extraEvents: ReadonlyArray; /** * 042 — true when the READ was authorized AND `invokeRead` actually returned * a datum that was reflected into the model's context (the laundering leg). * The loop folds this into the session contamination flag (treating the * returned data as `Retrieved` origin) when contamination is enabled. A * refused/kill-switched read or an executor error did NOT introduce an * untrusted datum, so it does not contaminate (`served: false`). */ readonly served: boolean; }>; export { AdapterError, AdapterErrorCode }; //# sourceMappingURL=decisions.d.ts.map