# @adjudicate/adapter-core

## 0.4.4

### Patch Changes

- e650c37: WS7 — `IntentActor.role`, an OPTIONAL, OPAQUE adopter role carrier (staff-role authorization prerequisite for role-aware pack guards, e.g. OWNER/MANAGER/ATTENDANT × intent-kind matrices).

  `@adjudicate/core` (minor — new surface):
  - `IntentActor` gains `readonly role?: string`. The string is adopter vocabulary — the kernel assigns it NO meaning, enforces NO enum, and consults it in NO built-in guard; adopter packs may read it via `envelope.actor.role`. Orthogonal to BOTH the provenance `principal` axis AND the authority graph's identity binding.
  - Canonical-drop-safe (mirroring `attestation` / `resourceRefs`): an envelope WITHOUT `role` hashes byte-identically to pre-change envelopes — `@adjudicate/canonical` drops `undefined` keys before hashing, so NO existing `intentHash`, golden vector, or replay fixture changes (all pass unchanged). A PRESENT `role` IS bound into `intentHash` via `actor`, so a post-decision role swap is tamper-evident.
  - `isIntentEnvelope` rejects a present-but-malformed `role` (empty string / non-string); absent stays valid. `docs/specs/intent-envelope-v2.schema.json` adds the optional `actor.role` property (`string`, `minLength: 1`) under the actor's `additionalProperties: false`.

  `@adjudicate/runtime` (minor — new park-blob field):
  - `ParkDeferredIntentArgs["envelope"]` / `ParkedEnvelope["envelope"]` gain optional `actorRole`, and `verifyParkedEnvelopeHash` re-derives the hash with `role` threaded through the reconstructed actor (passed unconditionally, exactly like `resourceRefs`) — so a parked envelope CARRYING a role resumes with an IDENTICAL `intentHash` instead of false-tampering (`park_blob_tampered`), while a no-role blob re-derives byte-identically (no regression).

  `@adjudicate/adapter-core` (patch — internal threading, no API change):
  - The DEFER park projection in `translateDecision` forwards `actorRole: envelope.actor.role` so a role-carrying DEFER round-trips park → resume with its hash intact.

- Updated dependencies [e650c37]
  - @adjudicate/core@1.9.0
  - @adjudicate/runtime@0.4.0
  - @adjudicate/audit@8.0.0
  - @adjudicate/conformance@7.0.0

## 0.4.3

### Patch Changes

- Updated dependencies [efabb92]
  - @adjudicate/core@1.8.0
  - @adjudicate/audit@7.0.0
  - @adjudicate/conformance@6.0.0
  - @adjudicate/runtime@0.3.3

## 0.4.2

### Patch Changes

- Updated dependencies [33fcb81]
  - @adjudicate/core@1.7.0
  - @adjudicate/audit@6.0.0
  - @adjudicate/conformance@5.0.0
  - @adjudicate/runtime@0.3.2

## 0.4.1

### Patch Changes

- Updated dependencies [06eea00]
  - @adjudicate/core@1.6.0
  - @adjudicate/audit@5.0.0
  - @adjudicate/conformance@4.0.0
  - @adjudicate/runtime@0.3.1

## 0.4.0

### Minor Changes

- f34c493: feat(adapter): 022 — single-use capability burn store + expiry + nonce reconciliation. Introduce the AUTHORIZATION-BEARING at-most-once redemption primitive the cap-gated executor (024) will consume: a kernel-minted `Capability` (021) is redeemed EXACTLY ONCE before its executor honors it. Unlike the lossy display-only approval registry and the `ConfirmationStore` (a transport for a user-held token), this store OWNS the single-use guarantee via an ATOMIC claim-and-burn. ADDITIVE: no consumer is wired into the kernel decision here (024 wires the executor); removing the store cannot loosen any guard, so the failure mode is friction, never bypass (§C / §D #6).
  - **T1 (`adapter-core/persistence.ts`):** `BurnStore<R = Capability>` interface (`mint(nonce, record, ttlSeconds) → boolean` first-writer-wins; `burn(nonce) → R | null` atomic claim-and-burn) + `createInMemoryBurnStore` reference. The in-memory burn is atomic within the single-threaded event loop (read+delete synchronous between `await` points → two concurrent burns yield the record AT MOST ONCE), with an opportunistic `sweepExpired` on `mint` bounding the map (mirrors `createInMemoryConfirmationStore`). Expiry fails closed: a grant past TTL returns `null`.
  - **T2 (`adapter-core/persistence-redis.ts`):** `createRedisBurnStore` closing the production confirmation store's documented NON-ATOMIC GET-then-DEL double-spend race. `mint` claims via `SET NX EX` (first-writer-wins, mirroring `Ledger.recordExecution` / `defer-resume`'s `acquired !== "OK"` suppression); `burn` runs the ATOMIC Lua `EVAL` get-and-delete (`BURN_CLAIM_AND_BURN_LUA` = `GET; if v then DEL; return v`) so two concurrent burns of one nonce cannot both observe the pending grant — at-most-once holds across replicas WITHOUT relying on the kernel-ledger `REPLAY_SUPPRESSED` backstop. `eval` is REQUIRED (no safe non-atomic fallback for a single-use authorization store). Fail-closed: a burn miss, an EX-expired (gone) key, a malformed blob, or a store/IO error yields NO redemption, never a fail-open grant.
  - **T3 (`core/envelope.ts`):** `reconcileNonceHash(envelope, presentedHash)` — re-derives the nonce-bound `intentHash` via `deriveIntentHash` over the UNTOUCHED `intentHashInput` recipe (`{version,kind,payload,nonce,actor,taint,origin[,resourceRefs]}` — invariant #4 byte-identical, `createdAt` still excluded) and compares constant-time via `timingSafeHexEqual` (fail-closed `false`, never throws). The Redis-side `reconcileBurnedCapability(capability, envelope)` composes it with a `timingSafeHexEqual` cap-vs-stored-hash check so a burned grant is honored only when the presented envelope STILL re-derives to the bound hash — a mutated nonce (or any tampered hashed field) is rejected. Uses `@adjudicate/canonical` `sha256Canonical` (via core's `hash.ts` re-export), NOT the conformance fork.
  - **T4 (`adapter-core/index.ts`):** export the burn store + reconciliation seam (`createInMemoryBurnStore`, `createRedisBurnStore`, `reconcileBurnedCapability`, `BURN_CLAIM_AND_BURN_LUA`, `BurnStore`, `BurnRedisClient`, `CreateRedisBurnStoreOptions`) and re-export the single canonical hash path (`sha256Canonical`, `reconcileNonceHash`, `timingSafeHexEqual`) so the burn seam pins ONE encoder.

  The pure `adjudicate()` decision path, the closed 6-outcome `Decision` algebra, and `intentHashInput` are UNCHANGED (purity/determinism/replay preserved; burn + persist stay in the impure shell, §D). REUSES 021's `Capability`/`bindCapability`, the `defer-resume` SET-NX claim pattern, and the shared `timingSafeHexEqual`/`sha256Canonical` — nothing re-implemented.

- a9be0ad: feat(adapter-core,core,pack-payments-pix,pack-incident-response,pack-access-governance): 024 — cap-gated executor contract + adapter wiring. Make the executor honor a kernel-shell-minted, single-use, resource-bound capability instead of a raw envelope, so the §B "on EXECUTE → mint signed CAPABILITY → capability-gated EXECUTION FABRIC" edge is enforced in code, not by pack-author convention. `intentHashInput`, the pure `adjudicate()` path, and the closed 6-outcome `Decision` algebra are UNCHANGED: the kernel decides, the impure shell mints/signs AFTER the decision (§D), and constitutional invariant #1 holds bytewise — only EXECUTE (or the REWRITE-rewritten envelope) reaches `invokeIntent`, now additionally gated by a burned-on-use capability.
  - **`@adjudicate/adapter-core` (`src/types.ts`, `src/decisions.ts`, `src/loop.ts`, `src/index.ts`):** add the `CapabilityGate` contract (T1) — a DEPENDENCY-INJECTED gate carrying `mint` (the node-side ed25519 `signCapability`, supplied by the adopter), `verify` (the node-side `verifyCapabilitySignature` bound with the issuer's public keys), 022's atomic `burnStore`, and `kernelId`. Adapter-core never imports `@adjudicate/approval-engine` (that would be a dependency cycle) and stays `node:crypto`-free / browser-bundleable, mirroring the config-seal verifier seam. New `AdjudicatedAgentOptions.capabilityGate` (T2) — **DEFAULT OFF**; omitted ⇒ byte-identical to the pre-024 raw-envelope seam (rollback dial, §7). When set, the loop's shell MINTS + SIGNS a capability bound to the EFFECTIVE (EXECUTE or REWRITE-rewritten) envelope's `intentHash` AFTER the pure decision and `mint`s it into 022's store keyed by the effective nonce (best-effort; a throwing signer/store leaves no grant ⇒ the seam fail-closes). `runExecute` (T5) then redeems it EXACTLY ONCE before `invokeIntent`: BURN from 022's atomic store (single-use; a second use re-burns to `null` and is suppressed — never a parallel one), ed25519-VERIFY via the injected `verify`, and constant-time-BIND the capability's `intentHash` to the effective envelope's own hash (anti-IDOR / anti-replay). Any failure (burn miss/expiry, store/IO error, bad signature, hash mismatch) ABORTS the EXECUTE — `invokeIntent` is never reached (invariant #1, fail-closed per #6; §C: gating only adds friction). Composes ABOVE the existing 023 `verifyResourceBinding` fence. `CapabilityGate` re-exported from the package surface (T6).
  - **⚠️ Kernel authority is ed25519, NOT the forgeable hash-bind (021-F1 footgun).** The gate honors a capability as kernel-minted ONLY when the injected `verify` returns true; adopters MUST wire approval-engine's ASYMMETRIC `verifyCapabilitySignature`, NEVER core's pure-JS `verifyCapability` (which checks only hash-bind self-consistency — integrity, not authenticity, and is forgeable by anyone who can recompute the canonical hash). The contract type documents this; the approval-engine integration test PROVES it end-to-end (a `bindCapability` hash-bind-only grant with `alg:"sha256-hashbind"` is REJECTED by the gate; an `ed25519`-signed one is accepted).
  - **`@adjudicate/core` (`src/llm/planner-conformance.ts`, `src/llm/index.ts`):** (T4) widen the optional `pack` parameter of `safePlan` and `assertPlanSubsetOfPack` from a full `PackV0` to the minimal `PackIntentsSurface` (`{ intents }`). A full `PackV0` is structurally assignable (existing callers — `install.ts`, tests — unaffected), but the narrower surface lets a Pack break the planner↔pack construction cycle and adopt the pack-bound 3-arg form from its own `capabilities.ts` (the full `PackV0` value references the planner). `PackIntentsSurface` exported from `@adjudicate/core/llm`. The pure capability schema (021: `Capability`/`UnsignedCapability`/`verifyCapability`) was already exported from `@adjudicate/core` and is unchanged.
  - **`@adjudicate/pack-payments-pix` / `pack-incident-response` / `pack-access-governance` (`src/capabilities.ts`):** (T4) adopt the pack-bound 3-arg `safePlan(planner, classification, pack)` form — declare the pack's intent tuple (`PIX_INTENTS`/`INCIDENT_INTENTS`/`ACCESS_INTENTS`, `satisfies readonly <Kind>[]` to pin against the declared `IntentKind` union, and asserted equal to the `PackV0.intents` field in conformance tests so they cannot drift) and pass `{ intents }` so `assertPlanSubsetOfPack` runs on EVERY `plan()` (not only at install). A planner advertising an intent kind absent from the pack's declared intents now throws `PlanConformanceError` loud — the subset invariant the shipped 2-arg form never engaged.

  Tests: cap-gate single-use redemption / second-use suppression / burn-miss fail-closed / ed25519-verify rejection (non-vacuous toggle) / intentHash-bind anti-IDOR / store-error fail-closed / REWRITE-path gating / OFF-by-default no-op (`adapter-core/tests/cap-gate.test.ts`); the name-collision-not-READ cap-gate-bypass closure (`adapter-core/tests/bridge.test.ts`); the REAL ed25519 kernel-authority leg through the actual executor seam incl. the 021-F1 hash-bind-rejection and cross-intent-replay (`approval-engine/tests/cap-gate-ed25519.test.ts`); the 3-arg subset-leak conformance via the new `PackIntentsSurface` (`core/tests/llm/plan-allowed-intents.test.ts`); the shipped-pack subset invariant engaged non-vacuously (`pack-payments-pix/tests/conformance.test.ts`); and constitutional invariant #1 / kernel-unchanged under the cap gate (`core/tests/kernel/invariants/untrusted-never-executes.property.test.ts`). No schema/persistence migration is hashed (the gate is pure registry/runtime wiring, not ConfigSeal-pinned), so revert is clean (drop the worktree branch / unset the option).

- e8698b1: feat(core,adapter-core,audit,admin-sdk,pack-payments-pix,pack-incident-response,pack-access-governance): 025 — capabilities-as-budgets (bounded standing pre-auth). Add a human-granted, BOUNDED, STANDING pre-authorization that lets a CLASS of intents satisfy the "ask first" threshold up to a declared limit WITHOUT a per-intent confirmation receipt. The pure kernel only ever SUBSTITUTES EXECUTE for the threshold-style outcome (exactly as the confirmation-receipt override does today) and never weakens any state/taint/auth/business guard; the impure shell burns the budget down per EXECUTE. `intentHashInput`, the pure `adjudicate()` path, and the closed 6-outcome `Decision` algebra are UNCHANGED (§D #2: no new Decision kind, no `confidence`/free metadata). The budget substitution is monotonicity-preserving (§C) and fully replayable (§D #5): omitting the additive `budgetGrant` deps slot keeps records byte-identical to pre-025.
  - **`@adjudicate/core` (`src/audit.ts`, `src/basis-codes.ts`, `src/kernel/adjudicate-and-audit.ts`, `src/explain.ts`):** add the `BudgetGrant` data contract (`{ budgetId, intentKind, limit, windowSeconds }`) and the `budget_satisfied` `SupersessionReason` (T1/T3). Add the `budget` basis category with `BASIS_CODES.budget.SATISFIED` (T1). New optional additive `AdjudicateAndAuditDeps.budgetGrant` slot (T1); when supplied AND `grant.intentKind === envelope.kind` AND the kernel returned `REQUEST_CONFIRMATION`, the kernel substitutes `EXECUTE` with an appended `budget:satisfied` basis and auto-derives a `budget_satisfied` `Supersession` linking back to the original REQUEST_CONFIRMATION row (`token` carries the `budgetId`) — EXACTLY mirroring the `confirmationReceipt` override (T2). REFUSE/REWRITE/ESCALATE/DEFER/EXECUTE pass through UNCHANGED. The branch is the second site (after the confirmation receipt) explicitly allowlisted under the `@adjudicate/monotonic-ceiling` lint as a deterministic §C carve-out (a human-granted bounded pre-auth is a recorded deterministic input, not a risk model lowering a ceiling). The kernel does NOT verify or count the grant — the shell asserts it ONLY after a successful atomic decrement. Explain narrations added for `budget:satisfied` and `supersedes:budget_satisfied`.
  - **`@adjudicate/adapter-core` (`src/persistence.ts`, `src/decisions.ts`, `src/loop.ts`, `src/types.ts`, `src/index.ts`):** add the authoritative single-use-COUNTED `BudgetStore` + `createBudgetStore` (T4) backed by the ATOMIC `ParkRedis.evalIncrCheck` Lua primitive (increment-and-check against `limit`) — a deliberately DISTINCT store from 022's claim-and-burn `BurnStore` (a budget METERS N substitutions; a capability BURNS a single token; a per-token burn cannot express an N-use budget). Concurrent burn-downs over a `limit`-N budget yield AT MOST N grants across replicas (the headline atomicity guarantee), WITHOUT mirroring the non-atomic GET+DEL caveat of `persistence-redis.ts`. A client without `evalIncrCheck` throws at construction (no silent non-atomic fallback — fail-closed §D #6). Add an in-memory `evalIncrCheck` to `createInMemoryDeferStore` (atomic within the single-threaded event loop; window refills on TTL expiry). Add the `runBudgetBurnDown` shell helper (T5) that calls `evalIncrCheck` directly (decrement-then-assert-grant; fail-closed on over-limit / missing-primitive / store error). Wire it into the loop's send path (T5): on a REQUEST_CONFIRMATION for a budget-capable kind, resolve a grant (`AdjudicatedAgentOptions.budget.resolveGrant` — host authority), atomically burn down, and on a successful in-budget decrement RE-adjudicate with `budgetGrant` asserted — yielding a budget-satisfied EXECUTE that supersedes the REQUEST_CONFIRMATION row. **DEFAULT OFF** (option omitted) ⇒ byte-identical to the pre-025 REQUEST_CONFIRMATION path (rollback dial, §7). Authority stays in the single-use-counted counter, never the lossy approval projection.
  - **`@adjudicate/audit` (`src/supersession-chain.ts`):** extend the exhaustive `Record<SupersessionReason, number>` reason-count map + key list with `budget_satisfied` (T6). A budget-satisfied EXECUTE rides the existing EXECUTE-claim ledger plumbing — it claims a key first-writer-wins exactly like any EXECUTE; a second attempt for the same intentHash is REPLAY_SUPPRESSED, so the budget burn is observable in the ledger without weakening first-writer-wins.
  - **`@adjudicate/admin-sdk` (`src/schemas/basis.ts`, `src/schemas/audit.ts`):** add `budget` to `BasisCategorySchema` (keeps the build-time core↔wire drift guard satisfied) and `budget_satisfied` to `SupersessionReasonSchema`, so a budget-satisfied record round-trips through the admin wire schemas (consequence of the new core category/reason).
  - **`@adjudicate/pack-payments-pix` / `pack-incident-response` / `pack-access-governance` (`src/capabilities.ts`, `src/index.ts`):** declare the budget-CAPABLE intent class (T7): `PIX_BUDGET_CAPABLE_INTENTS` (`pix.charge.create`/`pix.charge.refund` — the LLM-proposable money-movers; the TRUSTED-only `pix.charge.confirm` webhook is NOT budget-capable), `INCIDENT_BUDGET_CAPABLE_INTENTS` (`incident.remediation.execute`), `ACCESS_BUDGET_CAPABLE_INTENTS` (`access.request`/`access.revoke`). Each is `satisfies readonly <Kind>[]`, a non-empty subset of the pack's declared intents (asserted in conformance tests), and excludes system-only/escalate kinds. Re-exported from each pack surface so a host wires `budget.resolveGrant` against an operator-authorized subset.

  Tests: kernel substitution + non-flip over all six outcomes + supersession + ledger-claim + confirmation-receipt-wins precedence (`core/tests/kernel/budget-grant.test.ts`); determinism fence (additive-omitted-slot byte-identical auditHash + replayable same-grant byte-identical record) + closed-algebra + property over random kinds (`core/tests/kernel/invariants/budget-substitution.property.test.ts`); atomic at-most-`limit` under CONCURRENT burn-down + window-refill + missing-primitive/store-error fail-closed + loop wiring (in-budget EXECUTE invokes executor, over-limit/no-grant/OFF leaves REQUEST_CONFIRMATION standing) (`adapter-core/tests/budget.test.ts`); in-memory `evalIncrCheck` primitive (`adapter-core/tests/persistence.test.ts`); budget burn recorded in the ledger without weakening first-writer-wins + replay-intact (`audit/tests/ledger.test.ts`); budget-capable declaration per pack (`pack-*/tests/conformance.test.ts`). The substitution is behind an additive deps slot (no slot ⇒ byte-identical legacy behavior); revert = stop asserting grants from the shell and drop the branch.

- c0b1b44: feat(core): 042 — contaminating session-flag model on the origin axis (consumes 041). Make untrusted ORIGIN contaminating at the session level so an LLM-proposed intent that follows retrieved/external content in context no longer re-enters the loop byte-identical to a user-induced proposal. Adds to `taint.ts`: `isContaminatingOrigin(origin)` (the pure predicate over the closed `Origin` union — `Retrieved`/`ExternalAPI` are contaminating; `Human`/`System`/`LLM` are not), the `SessionContamination` flag type (`{ taint; origin }`), `applySessionContamination(declaredTaint, flag)` (the monotonic lattice-meet fold — minted taint = `mergeTaint(declared, contamination.taint)`, never raises trust), and `contaminateSession(prior, datum)` (monotonic accumulation that only ever tightens; preserves the FIRST contaminating origin as the audit anchor). The pure kernel taint gate (`kernel/adjudicate.ts`) now reads `envelope.origin` READ-ONLY (already in the intentHash pre-image from 041) to ATTRIBUTE a sub-minimum `canPropose` refusal: a contaminating origin populates the previously-UNUSED `taint:propagation_violation` basis (instead of the bare `taint:level_insufficient`) so audit can distinguish a contamination-lowered refusal from a declared-untrusted one. This is NOT a 7th outcome (still REFUSE), NOT a new guard phase, NOT a friction change, and adds NO IO — kernel purity (§D), guard order #3 (taint short-circuits before auth), the closed 6-outcome algebra #2, the intentHash recipe #4, and monotonicity #7 are all preserved. The pre-existing 041 invariant `origin-not-gated.property.test.ts` is EVOLVED in lock-step (origin still never changes the Decision KIND; `propagation_violation` now appears ONLY on a taint REFUSE AND ONLY for a contaminating origin).

  feat(primitives): 042 — adopter-facing `createSessionContaminationPolicy({ enabled })` factory (DEFAULT OFF) + `SessionContaminationPolicy`/`SessionContaminationPolicyOptions` types, mirroring `createSystemTaintPolicy` so "is contamination enabled for this Pack?" is a one-line single-sourced audit. Default OFF keeps existing deployments byte-identical to pre-042.

  feat(adapter-core): 042 — fold the per-session contamination flag into the minted taint at the SINGLE envelope-minting seam (`loop.ts`), replacing the former unconditional `taint:"UNTRUSTED"` literal with the lattice meet of the declared taint and the session contamination taint, applied BEFORE `buildEnvelopeFromToolUse` hashes (so the contaminated taint/origin are inside the intentHash pre-image #4 — an LLM cannot post-hoc flip them). An authorized READ that SERVES a datum (the laundering leg) contaminates the session (treated as `Retrieved`); the next minted LLM intent then inherits the lowered taint and the contaminating origin stamp. `buildEnvelopeFromToolUse` (`bridge.ts`) threads an optional `contamination` arg via `applySessionContamination` (monotonic; idempotent under the loop's pre-meet); `routeReadThroughKernel` (`decisions.ts`) returns a `served` flag; `AdjudicatedAgentOptions.contamination` (`types.ts`) is the adopter opt-in (DEFAULT OFF — option omitted is byte-identical to pre-042). Clearing is structural: a fresh `runLoop` (including the authenticated `resume()` path) starts uncontaminated — never an LLM-controlled action.

  feat(red-team): 042 — land the `provenance_injection` (contamination / data-provenance) vector that 041 only opened the union seam for. New `vectors/provenance-injection.ts` generator: for each system-only intent kind, an UNTRUSTED envelope stamped with a CONTAMINATING origin (`Retrieved`/`ExternalAPI`), sourced from `planner.visibleReadTools` (the 041 declared-but-unconsumed seam — the READ→inject→intent path), expecting REFUSE. `ScenarioIntent` gains an optional, canonical-drop-safe `origin` (the runner threads it only when present, so existing vectors hash and decide byte-identically). Wired into `generateAllVectors`. Non-vacuity: against a pack whose state guards do not pre-empt the taint gate the kernel REFUSEs every contaminated proposal with `taint:propagation_violation`.

  feat(cli): 042 — wire `generateProvenanceInjectionEnvelopes` into the `adjudicate red-team` command's per-vector dispatch (the `provenance_injection` key already in `ALL_VECTORS` now produces real scenarios instead of zero).

- 44c46d2: feat(core,adapter-core,approval-engine,adjutant): 071 — bind the confirmation receipt to (intentHash, capability, approver, channel). Today the kernel's confirmation-receipt override fires on a BARE `intentHash` equality match gated only by `decision.kind === "REQUEST_CONFIRMATION"`; the receipt binds nothing about the capability authorized, the approver who confirmed, or the channel the confirmation arrived on. This widens the receipt so the post-confirmation EXECUTE is provably bound to the same (intentHash, capability, approver, channel) tuple the confirmation was resolved against, while keeping the override ADDITIVE and the kernel PURE. `intentHash` stays the LOAD-BEARING identity gate (§D-4 untouched — `intentHashInput` is NOT modified); the new fields are an ADDITIONAL fail-closed gate that defaults to friction on any mismatch (§D-6). The single-use/replay defense remains the adapter's `confirmationStore.take` + timing-safe hash compare; this plan strengthens WHAT the override trusts, not WHERE it is enforced. Omitting all binding fields yields a byte-identical `supersedes`/`auditHash` (§D-5 determinism fence). Closed 6-outcome algebra preserved (§D-2): the override still only maps `REQUEST_CONFIRMATION → EXECUTE`.
  - **`@adjudicate/core` (`src/kernel/adjudicate-and-audit.ts`, `src/kernel/index.ts`, `src/audit.ts`):** add the additive optional `confirmationReceipt.binding` field (`ConfirmationBinding` = `{ capability?, approver?, channel? }`, each a `ConfirmationBindingField` = `{ confirmed, requested? }`) (T1). The override predicate keeps `intentHash === envelope.intentHash` as the primary gate and additionally requires, via the new pure `confirmationBindingMatches`, that EVERY present field whose issued-against `requested` value is supplied equals its resolved `confirmed` value — a mismatch on ANY present field falls through to the original `REQUEST_CONFIRMATION` verdict (fail-closed). A field with no `requested` is recorded forensically but not gated; an absent `binding` is vacuously satisfied (unchanged back-compat path). The auto-derived `confirmation_resolved` `Supersession` now carries the BOUND (confirmed) tuple via the new pure `confirmationBindingRecord`, with sub-keys (and the whole `binding` key) omitted when unsupplied — so a confirmation with no binding produces a byte-identical supersession and auditHash (T2). Extend `Supersession` with the optional `binding?: { capability?, approver?, channel? }` carrier (IS in the auditHash pre-image, like the existing `token`). Export `ConfirmationBinding`, `ConfirmationBindingField`, `confirmationBindingMatches`, `confirmationBindingRecord` from `/kernel` (and the root barrel).
  - **`@adjudicate/adapter-core` (`src/types.ts`, `src/loop.ts`):** add the optional `ConfirmArgs.binding` input (T3) and forward it onto the kernel `confirmationReceipt.binding` in `confirm()` AFTER the existing single-use `take()` + timing-safe hash compare (T4). The loop still owns `intentHash`/`at`/`token` from the already-verified pending envelope; a caller (e.g. the approval-engine) supplies only the binding it holds. Conditionally spread so omitting `binding` is byte-identical to pre-071 `confirm()`.
  - **`@adjudicate/approval-engine` (`src/engine.ts`):** thread the resolving (approver, channel) into the forwarded receipt in `resolve()` (T5). Hoist the verified-approver computation ABOVE the `agent.confirm()` call so the bound approver is the CRYPTOGRAPHICALLY-VERIFIED `attestation.approverId` (when an attestation verifier is configured) — never the forgeable `input.by` claim. The channel is `existing.channel` (stamped at `request()` time), supplied as BOTH the issued-against `requested` and the resolved `confirmed` value, so a forwarded resolve cannot retroactively claim a different channel (a channel mismatch fails the kernel override closed). The binding is forwarded ONLY on an ACCEPTED resolve (a decline never overrides). The capability is not modeled in this single-approver path; the approver carries no `requested` value (the proposer/requestedBy surface that enables approver≠proposer separation-of-duty is plan 072 — 071 only RECORDS the bound approver).
  - **`@adjudicate/adjutant` (`src/orchestrator.ts`):** carry the resolving approver (`args.by.id`) and the single ops-plane channel (the new `ADJUTANT_CHANNEL` constant, stamped into the `ApprovalRequest` at proposal time) into the kernel `confirmationReceipt.binding` in `resolve()` (T6) instead of dropping them at `markResolved`. Each sub-field conditionally spread; an approve with no `args.by` is byte-identical to pre-071.

  Tests: kernel binding override (fires on full match; falls through fail-closed on a mismatched approver / channel / capability; a field with no `requested` is recorded-not-gated; `intentHash` stays load-bearing; byte-identical auditHash + supersedes when `binding` omitted/undefined) (`core/tests/kernel/confirmation-receipt.test.ts`); end-to-end loop forwarding (matching binding overrides + records confirmed tuple on `supersedes.binding`; mismatch fails closed to REQUEST_CONFIRMATION; omitted binding byte-identical) (`adapter-core/tests/confirm-binding.test.ts`); engine forwards (approver, channel) into `agent.confirm` on accept, none on decline, channel-only without an approver (`approval-engine/tests/engine.test.ts`); reference wiring threads the VERIFIED approver (not the forgeable `by`) + channel end-to-end (`approval-engine/tests/engine-reference-wiring.test.ts`); orchestrator binds (approver, channel) onto the EXECUTE supersession, channel-only without `by` (`adjutant/tests/orchestrator.test.ts`). Governance/quorum/attestation paths re-run with no regression (`approval-engine/tests/engine-governance.test.ts`). The change is additive behind the optional `binding` field (omit it ⇒ byte-identical pre-071 behavior); revert = drop the receipt fields and their wiring (T1–T6), leaving the four-field override keyed on `intentHash`.

- e81b801: feat(core): 023 — resource-binding verifier (`verifyResourceBinding`, `ResourceBindingPolicy`, `ResourceBindingResult`, `DEFAULT_RESOURCE_BINDING_POLICY`) in `envelope.ts`. Re-derives the envelope's `intentHash` via the UNTOUCHED `intentHashInput` recipe (`deriveIntentHash`) and constant-time-compares it against the carried hash with `timingSafeHexEqual` — the executor must honor ONLY the kernel-bound (signed) payload. A `payload` / `resourceRefs` (031) swapped AFTER the kernel decision re-derives a DIFFERENT hash and fail-closes (anti-IDOR / anti-resource-swap; invariants #1, #4, #6). The `intentHashInput`/`buildEnvelope`/`deriveIntentHash` bodies are BYTE-IDENTICAL (additive-only file change), so every existing envelope hash, golden vector, and replay corpus is unchanged (invariant #5). No `node:crypto`, no `Buffer` — core stays browser-bundleable (pure-JS canonical fence). The passive `AuditRecord.signature` slot stays PASSIVE — 023 is a hash fence only; the AuditSigner is plan 092. The bound envelope inputs are already recorded on the AuditRecord for replay.

  feat(adapter-core): 023 — enforce the resource binding at the executor seam (`runExecute`, `decisions.ts`) before `invokeIntent`, threaded via a new `resourceBindingPolicy` option (default `"strict"`). The check SUBSUMES the 011/T4 forged-REWRITE re-verify AND EXTENDS the same fence to the EXECUTE payload, so a post-decision resource-swap can never reach the executor (invariant #1). Coexists with 012 (reads serve via `invokeRead`, never reach this gate) and 013 (the kernel crossing that produced the Decision already emitted the required AuditRecord) — none weakened. `"warn"` still fail-closes a mismatch (friction never decreases, §C); `"off"` is the documented rollback dial restoring the exact pre-023 seam. Re-exports `verifyResourceBinding` from the barrel so the seam pins ONE recipe. The `AdopterExecutor.invokeIntent` contract now documents that it receives only the kernel-bound payload.

  feat(runtime): 023 — re-export `verifyResourceBinding` / `ResourceBindingPolicy` and a T4 cross-drift note pinning that the resource-binding pre-image equals the parked-envelope verifier's pre-image (`verifyParkedEnvelopeHash`) — the SAME canonical recipe + comparator, so the executor-seam binding and the resume-time park check cannot disagree (no drift; invariants #4/#5).

  feat(adjutant): 023 — `assertResourceBound` fence at the orchestrator's direct `invokeIntent` seam (it has no `runExecute`): re-derive + constant-time-compare the envelope's `intentHash` before the side effect in both `handle` (EXECUTE) and `resolve` (confirmation EXECUTE), so a swapped/forged proposal envelope fail-closes before the executor (anti-IDOR).

  feat(pack-\*): 023 — document the bound-payload contract on the three shipped packs' `capabilities.ts` (pix / incident-response / access-governance): an LLM-proposable intent reaches the adopter's executor ONLY through a binding-enforced seam, so the executor honors only the exact kernel-adjudicated `payload` / `resourceRefs`.

- f7fa8d5: fix(adapter): H4 (U5) — close the multi-turn contamination launder. The 042 session-contamination flag was held ONLY in a `runLoop`-local variable, re-initialised to `undefined` every `send()`/turn — but the laundered READ result is appended to SESSION-scoped conversation history that is re-supplied across turns. So a launderer could READ a poisoned doc on turn 1 (contaminating the session) and, on turn 2 — a fresh `runLoop` that started clean — propose an origin-required intent off that same poisoned history and have it minted `origin:"LLM"`, slipping the kernel's taint/origin gate (`canProposeWithOrigin`) WITHOUT a `propagation_violation` ("read poisoned doc turn 1, act turn 2"). The fix persists the flag in a durable, session-scoped store the loop owns.
  - **`@adjudicate/adapter-core` (`src/persistence.ts`):** add the `SessionContaminationStore` interface (`get`/`put`/`clear`, keyed by `sessionId`) plus an in-memory reference impl `createInMemorySessionContaminationStore` (TTL'd, opportunistic sweep, `keyFor` namespacing) — the same impure-shell-store firewall as `MemoryStore` (NOT a kernel input; the flag it holds is folded into the minted taint BEFORE `intentHash` so it is inside the hashed pre-image, but the store never enters the kernel decision, state `S`, the `auditHash` pre-image, and is never replayed). MONOTONIC by construction: the only mutator the loop calls is `put` with the meet-folded flag (trust only lowers); `clear` (the sole trust-raising op) is invoked ONLY on the authenticated `resume()` path.
  - **`@adjudicate/adapter-core` (`src/types.ts`):** add the optional `contaminationStore?: SessionContaminationStore` to `AdjudicatedAgentOptions`. Only consulted when `contamination.enabled === true` AND the store is supplied — with either absent the loop keeps the pre-H4 turn-local flag, so the contamination-disabled and no-store paths are byte-identical.
  - **`@adjudicate/adapter-core` (`src/loop.ts`):** LOAD the flag from the store at the top of every `runLoop` (replacing the unconditional `let … = undefined`), keep folding within the turn via `contaminateSession`, PERSIST the meet-folded flag on each contaminating served READ (best-effort: a throwing store leaves the in-turn flag standing and never fails OPEN), and CLEAR the store on the AUTHENTICATED `resume()` path BEFORE the resumed `runLoop` (best-effort: a throwing clear leaves the flag standing — fail-CLOSED, friction never decreases). Adds re-exports of the store factory/types from `src/index.ts`.

  The pure `adjudicate()` decision path, the closed 6-outcome `Decision` algebra, the guard order (state→taint→auth→business), the taint gate, and `intentHashInput` are UNCHANGED — this is an adapter-SHELL change only (no `packages/core/src/**`). Default-OFF and no-store are byte-identical to pre-042; the change is monotonic (contamination can only LOWER trust, raised only by an authenticated `resume()`). A new regression test (`tests/contamination-loop.test.ts`) pins the cross-turn launder being CAUGHT (`propagation_violation`/REFUSE with the persisted store), proven non-vacuous by a paired no-store run that still slips through (`level_insufficient`, origin `LLM`), plus default-OFF byte-identity and cross-turn monotonicity.

- 1978f2b: feat(red-team,adapter-core,cli,ci): 084 — staged rollout (shadow → canary → auto-rollback) + a frozen adversarial-canary gate. Turns the `@adjudicate/red-team` suite (which previously ran only as part of `pnpm test`, wired to zero workflows) into an explicit DETERMINISTIC publish/rollout gate, tightens the canary-stage config-seal knobs to fail-closed, and wires the canary as an explicit CI + publish-precondition gate over the 6 shipped pack dist bundles. All orchestration lives in the impure shell (red-team / adapter / CLI / CI), never inside `adjudicate()` — the pure kernel, `intentHashInput` (invariant #4), the closed 6-outcome `Decision` algebra, and `installPack`'s load surface are all UNTOUCHED. Every added gate can only INCREASE friction (§C monotonicity); a failed canary ABORTS promotion (§D-6 fail-closed), never promotes by default.
  - **T1 (`red-team/src/runner.ts`):** add `runCanaryGate(pack, { stage, policy, ...gen })` — a frozen adversarial-canary gate that reuses `runRedTeam` + `computeRedTeamExitCode` (0 = promote / 2 = rollback) over a FROZEN scenario set (`frozenCanaryScenarios` = `generateAllVectors` PLUS the 035/T10 ownership/IDOR vector `generateOwnershipViolationEnvelopes`, which `generateAllVectors` omits — closing the ownership-axis canary gap so the gate protects §D #8). Under `policy:"strict"` (default) it PROMOTES the `taintEscalationCausality` non-vacuity warning into a HARD FAIL: a `escaped===0` taint pass where the taint gate was never exercised (`byTaintGate===0`, all defenses fired upstream) is a vacuous guarantee → rollback. The new `policy:"execute-escape"` is the §D-1 privilege-escalation gate: it rolls back ONLY on a reached clean `EXECUTE` (the executor) or an error, treating vacuity / non-EXECUTE friction (DEFER/etc.) as advisory — for a heterogeneous catalog whose adversarial scenarios are legitimately defended upstream of the taint gate. Both policies are fail-closed on the real escape and on errors; `execute-escape` only relaxes the advisory axes. Pure: no clock/RNG/IO; deterministic over `(pack, seed)`. Also adds `runBaselinedCanaryGate(pack, baseline, { seed })` + `deriveCanaryBaseline(result)` (and the `CanaryBaseline` type): the FULL STRICT canary measured against a committed baseline — the function CI/release wire (see T6). It rolls back on any new escape/error/IDOR-escape/vacuity beyond the baseline counts AND on any per-scenario §C friction regression (a recorded decision moving to a strictly less-restrictive `kind` via `restrictivenessRank`), with a reached `EXECUTE` / error unconditionally non-baselineable.
  - **T2 (`red-team/src/history.ts`):** add `runStagedCanaryRollout(pack, { store, candidate?, shadowAt, canaryAt, policy? })` — runs the SHADOW stage over the trusted baseline `pack` and the CANARY stage over the `candidate` (defaults to `pack`), persists BOTH stage reports through the existing in-memory history seam (`record(report, at)` — the same surface used for trend charting), and flips to ROLLBACK (exit 2) on any stage failure OR a shadow→canary DELTA regression (more escapes/errors, an IDOR hole newly opened, or taint coverage newly collapsed to vacuous). Friction-only: the rollout exit is never lower than the worst stage verdict. Caller-supplied timestamps (no clock).
  - **T3 (`cli/src/commands/red-team.ts`, `cli/src/bin.ts`):** extend the `red-team` subcommand with `--canary` (run the frozen gate, exit 2 = ROLLBACK / 0 = PROMOTE; ignores `--vectors`) and `--canary-policy <strict|execute-escape>`. Invocable locally and from CI/release.
  - **T4 (`adapter-core/src/types.ts`, `adapter-core/src/index.ts`):** add `canaryStageConfigSeal({ seal, publicKeyPem, onDrift? })` — builds the FAIL-CLOSED canary-stage seal posture (`policy:"require_signature"` + `engageKillSwitchOnMismatch:true` + `reverify:"every_turn"`) so a seal drift during canary LATCHES the kill switch instead of self-healing the next turn (§C/§D-7 monotonicity — the rollout may only add friction). Extract the previously-inline `configSeal` shape into the named exported `AgentConfigSealOptions` type the helper returns. Scoped to the canary stage only, leaving the documented one-release lax default intact for normal turns (082 deprecation window).
  - **T5 (`core/tests/install.test.ts`):** assert the kernel install path stays orchestration-free — `InstallPackOptions` carries NO canary/rollout/red-team key, a candidate installs byte-identically regardless of ambient canary state, and the 082 seal/trust verifiers remain caller-INJECTED (core never imports `@adjudicate/conformance`). `installPack` is UNCHANGED by this plan; the canary runs AROUND install via the red-team shell.
  - **T6 (`.github/workflows/ci.yml`, `.github/workflows/release.yml`, `.github/canary-baselines/*.json`):** wire the adversarial canary as an explicit gate alongside the ADR-140 composition gate and as a PUBLISH PRECONDITION (before the SIGNER/publish step). The wired gate runs the FULL STRICT canary (`red-team --baseline <committed-baseline> --seed 1`, via the new `runBaselinedCanaryGate`) over each shipped pack dist bundle, measured against a COMMITTED, version-controlled baseline (`.github/canary-baselines/<packId>.json` produced by `deriveCanaryBaseline` from a strict run). The gate PROMOTES iff the run is no-worse-than-baseline and ROLLS BACK (exit 2) on ANY new escape/error/IDOR-escape/vacuity beyond the baseline OR any §C friction REGRESSION on a baselined scenario (a recorded decision weakening to a strictly less-restrictive kind, e.g. a money-mover's IDOR `REFUSE → DEFER`). The committed baseline FREEZES the documented pre-existing 035-F1 #8 gaps (pack-identity-kyc's forged-owner DEFER cases; cli/pix/deploy's taint defended-upstream/vacuous cases) so CI/publish does not go permanently red on KNOWN holes, while gating REGRESSIONS — the property the weaker `execute-escape` policy could not deliver (it only catches a reached `EXECUTE`, so it promoted kyc's 12 open non-EXECUTE IDOR DEFERs and any non-EXECUTE friction-lowering). A change to a pack's defended posture therefore requires a REVIEWED baseline update in the same PR. `--canary-policy execute-escape` remains available on the CLI for ad-hoc local inspection but is NOT the CI/publish gate.
  - **Tests:** `red-team` adds a canary-gate suite (frozen-set ownership coverage, vacuous-taint HARD FAIL, EXECUTE-escape rollback, determinism, the strict-vs-execute-escape contrast, never-promote-fail-open), a baseline-gate suite (PROMOTE on a matching baseline, DOCUMENT a pre-existing 035-F1 DEFER gap without reddening, ROLLBACK a NEW non-EXECUTE IDOR escape beyond baseline [finding 1], ROLLBACK a §C `REFUSE → DEFER` friction regression with NO EXECUTE reached [finding 2], a reached EXECUTE can never be baselined away, `deriveCanaryBaseline` round-trip), and a staged-rollout suite (clean PROMOTE + idempotent persistence, stage-failure rollback, shadow→canary delta-regression rollback). `adapter-core` adds the canary-stage seal suite (strict knobs forced, valid seal proceeds, drift LATCHES + does NOT self-heal, contrasted against the lax default that self-heals). `conformance` extends `config-seal.test.ts` (frozen-cadence `verifyConfigSealFrozen` under require_signature gates a clean digest + is fail-closed on unsigned/drift) and `pack-trust.test.ts` (`verifyPackTrust` under require_signature as the canary-stage trust precondition).

### Patch Changes

- d2c3625: feat(core,conformance,adapter-core,primitives,cli): 082 — enforce the SIGNED pack at LOAD time (`installPack`). The adopter's in-process load path now REFUSES to install a Pack whose signature/trust or config seal does not verify, so a swapped/unsigned/tampered Pack cannot become the live adjudication authority (§D-1: only a verified Pack reaches the executor; §D-6: a write-path verification failure ABORTS the install; §C: failure → friction, never bypass). Fail-closed by default; behind the new `verifyOnLoad` option so an absent option is byte-identical to pre-082 (only `assertPackConformance` runs).
  - **T1 (`core/src/install.ts`):** add `VerifyOnLoadOptions` to `InstallPackOptions` and a FAIL-CLOSED provenance gate inside `installPack` that runs AFTER conformance but BEFORE any sink wiring / default install / snapshot recording, so a Pack that does not verify installs NOTHING destructive. The verifiers (`verifyPackTrust` / `verifyConfigSeal`) are INJECTED through `verifyOnLoad` — `@adjudicate/core` takes NO dependency on `@adjudicate/conformance` (which already depends on core; a `core → conformance` import would be a cycle, and the kernel dep allowlist stays clean: `@adjudicate/canonical, @noble/hashes, zod`). Defaults are STRICT at the load boundary: trust policy `require_signature` (NOT the library `best_effort`) and seal policy `require_signature` (NOT `require_digest`), so an UNSIGNED Pack (no signature / no publicKeyPem) refuses the install. A non-verifying report throws the new `PackLoadVerificationError` (axis: `trust` | `config_seal`). New exports: `VerifyOnLoadOptions`, `LoadTrustReport`, `LoadSealReport`, `PackFingerprintLike`, `PackLoadVerificationError` (all additive; recorded in the V1 freeze matrix). The pure `adjudicate()` path and `intentHashInput` are UNTOUCHED — this is impure install-shell wiring (§D).
  - **T2 (`conformance/src/index.ts`):** confirm + document that `verifyPackTrust` (`pack-trust.ts`) and `verifyConfigSeal` (`config-seal.ts`) are the single public verifiers the core load path injects; the pre-existing verifiers are unchanged.
  - **T3 (`adapter-core/src/types.ts`):** document the STRICT KNOB PAIRING on `AgentLoopOptions.configSeal` — operators must set `policy:"require_signature"` + `publicKeyPem` + `engageKillSwitchOnMismatch:true` together for fail-closed runtime posture (the same enforcement the load path runs by default). The runtime enforcement path (`loop.ts` `checkConfigSeal`) already honors this; documented, not silently relied upon (082 §7 risk: lax adapter default).
  - **T4 (`primitives/src/guards.ts`):** inline residual-blind-spot note at the `createRewriteGuard` code-artifact site — a clean seal proves SIGNATURE + sealed-surface provenance, NOT behavioral correctness of every closure (a state-derived `cap` pins the function source, not its runtime value), so load-time enforcement does not over-claim; closing the cap-pinning gap is 081's upstream scope.
  - **T5 (`cli/src/commands/pack-verify.ts`, `cli/src/bin.ts`):** align the `pack verify` command docs with the load-path posture — CI/adopters should run `--policy require_signature --public-key --signature` (+ `--expect-seal`) so the CLI gate and the runtime `installPack` load gate agree. The runtime `--policy` default stays `best_effort` for backwards-compatible local dev.
  - **T6 (tests):** `core/tests/install.test.ts` exercises the fail-closed gate with REAL ed25519 sign/verify (refuses unsigned / wrong-key / drifted-seal / unsigned-seal; installs a validly signed pack + matching signed seal; verifies no sinks install on failure; absent option ⇒ unchanged). `conformance/tests/pack-trust.test.ts` + `config-seal.test.ts` add the explicit `require_signature` load-path defaults (ed25519 + rsa-pss over the fingerprint; re-extract/re-hash of the LIVE pack), each with accept + fail-closed cases.

  Invariants preserved: kernel purity/determinism/replay (verification reads injected snapshots + the live pack surface only; no IO/clock/RNG; `intentHashInput` byte-identical), the closed 6-outcome `Decision` algebra (no new outcome), fail-closed (#6), and monotonicity (§C — an unverified Pack only ADDS friction by refusing to install).

- 41a295e: fix(runtime): H2 — `verifyParkedEnvelopeHash` (`defer-resume.ts`) re-derived the parked-envelope `intentHash` over `{version,kind,payload,nonce,actor,taint,origin}` while OMITTING `resourceRefs` — even though `buildEnvelope`/`deriveIntentHash` (`@adjudicate/core` `intentHashInput`) BIND `resourceRefs` (031) into the hash. So a resource-bound DEFER resume (the canonical pack-payments-pix charge-awaiting-webhook flow) re-derived a DIFFERENT hash → `{verified:false, reason:"tampered"}` → `park_blob_tampered` under the default strict policy, REFUSING a legitimate resume. Fix: pass `resourceRefs: e.resourceRefs` UNCONDITIONALLY into the verifier's `sha256Canonical({...})` — it is canonical-drop-safe, so a no-resource-refs blob (`undefined`) is omitted by `canonicalize` and its derived hash stays BYTE-IDENTICAL (NO golden-vector / replay-corpus regression; `@adjudicate/core` canonical encoder unchanged). Adds `readonly resourceRefs?: ResourceRefs` to `ParkedEnvelope.envelope` and `ParkDeferredIntentArgs.envelope`. Corrects the false comments in `defer-resume.ts` and `index.ts` that claimed the two recipes were already "the SAME … cannot disagree". Still fail-closed and §C-monotonic: a genuine tamper (changed payload / resourceRefs vs stored hash) still re-derives a mismatch and refuses.

  fix(adapter-core): H2 — forward `resourceRefs: ctx.envelope.resourceRefs` from the DEFER park caller (`decisions.ts` `runDeferDecision`) into `parkDeferredIntent`, so a resource-bound parked blob carries the field the resume-side verifier now re-derives over. Unconditional and drop-safe — a no-resource-refs envelope parks it as `undefined` (omitted), unchanged behavior.

- 6e18f2c: docs(security,architecture): 121 — fix the docs-that-lie so the prose contracts match the as-built kernel (REWRITE, R2/policyVersion, E3/DEFER resume, the dangling §9.5 anchors, the stale ADR index).

  Six documentation passages asserted behaviors the code does not (or no longer) implements, plus two dangling cross-references and one stale ADR-index range. This is a documentation-correctness pass over existing files — NO kernel, executor, or audit code is touched, and every constitutional invariant (§C monotonicity, §D kernel-purity, the closed 6-outcome Decision algebra, the `state→taint→auth→business→default` guard order, the `intentHash` recipe) is preserved by construction. The §5 gates run the unchanged test suites to confirm the rewritten references no longer contradict a green tree.
  - **REWRITE (T1, `AI_CONTEXT.md`).** The flow-diagram line read "REWRITE → re-adjudicate the sanitized envelope". As-built today (plan 011 landed): the kernel re-runs the FULL guard order on the rewritten envelope (a single bounded second pass, intentHash re-derived fail-closed) and only flows the rewritten bytes to the executor on a second-pass EXECUTE; otherwise the second-pass decision stands and the rewrite never executes. Line rewritten to that two-stage truth (grounded in `packages/core/src/kernel/adjudicate-and-audit.ts` step 2b and `packages/adapter-core/src/decisions.ts`). Pinned by `adapter-core/tests/decisions.test.ts` (REWRITE → executor runs the rewritten bytes) — left UNCHANGED.
  - **R2 / pack drift at replay (T2, `docs/security/threat-model.md`).** R2, the cross-cutting replay-determinism note, and the mitigation matrix asserted `policyVersion` as an unconditional replay join key. As-built today (plan 091 landed): `buildAuditRecord` and BOTH `adjudicateAndAudit` call sites (kill-switch + main) thread `policyVersion` / `kernelVersion` onto the record ONLY when the host supplies `deps.policyVersion` / `deps.kernelVersion` (`packages/core/src/audit.ts` emits each field only when defined). Rewritten to state the binding is host-conditional, not unconditional; matrix status changed from "Mitigated" to "Mitigated when host supplies `policyVersion`".
  - **E3 / resume taint floor (T3, `docs/security/threat-model.md`).** E3 claimed a blanket "resume cannot upgrade effective taint without going through `canPropose()`". FALSE as a blanket: the DEFER `resume()` path builds a FRESH envelope with `actor.principal:"system"` / `taint:"TRUSTED"` (`packages/adapter-core/src/loop.ts`), an INTENTIONAL elevation, while the CONFIRMATION `confirm()` path and the approval-engine `resolve()` path (which routes into `confirm()`, `packages/approval-engine/src/engine.ts`) DO preserve the original taint. Rewritten to scope the guarantee to the taint-preserving paths and document the DEFER elevation explicitly (with the runtime `defer-resume.ts` constructing no envelope, and SoD controls tracked under ADR-143). Pinned by `adapter-core/tests/resume.test.ts` (resume yields `principal==='system'`, `taint==='TRUSTED'`, differing `intentHash`) — left UNCHANGED.
  - **Dangling `§9.5` anchors (T4/T5, `docs/security/threat-model.md` + `docs/security/security-review-checklist.md`).** Both cited a non-existent `docs/concepts.md §9.5`; the guard-ordering closed-enum invariant actually lives under `## 9` at the stable heading "Invariant to preserve through any refactor" (the `GuardPhase` closed enum). Both references re-pointed to bare §9 + the stable heading text (per §7 risk mitigation, not a numbered subsection). `grep -rn "§9.5" docs/` now returns zero.
  - **Stale ADR-index range (T6, `docs/architecture/decisions.md`).** The index line claimed the directory runs `ADR-101..ADR-136`; it actually runs `ADR-101..ADR-143` (highest `ADR-143-approval-engine-governance.md`). Range corrected; the §4 representative-ADR table (through ADR-116) is NOT a lie and was left untouched.

- 7832b4c: docs(architecture,security): 122 — ADR scaffold + index, Status backfill, ADR-144, SECURITY.md reconciliation.

  Documentation-only Layer-12 plan that finishes the doc-truth pass plan 121 began. NO kernel, executor, or audit code path is touched; every constitutional invariant (closed 6-outcome Decision algebra, `state→taint→auth→business→default` guard order, §C monotonicity, fail-closed default, kernel purity, the ADR-104 `intentHash` recipe) is preserved by construction. The §5 gates run the unchanged suites that PIN the documented behavior (`decisions.test.ts`, `resume.test.ts`, `guard-order.test.ts`) so the prose cannot silently outlive the code it describes.
  - **ADR scaffold (T6, `docs/architecture/adr/README.md`).** The directory previously had no template / README / index (grep `template|readme|0000|index` returned zero). Added a README carrying the purpose, numbering rules, the canonical `ADR-143` header template (`# ADR-NNN — <title>` + `Status`/`Date`/`Scope`/`Supersedes`/`Related` bullets + `## Context`/`## Decision`/`## Why this shape`), the constitutional-invariant guardrails an ADR may not contradict, and the authoritative full index (ADR-101..ADR-144, all Accepted).
  - **Status-line backfill (T6).** Normalized the 9 ADRs whose `Status` deviated from the de-facto `ADR-143` bullet shape — ADR-105..ADR-112 (were `**Status**: Accepted (date)`) and ADR-116 (was a `## Status` heading) — to the canonical `- **Status:** … / - **Date:** … / - **Related:** …` block, preserving each ADR's existing status value, date, supersedes, and related links verbatim (the M1/M2/M3 execution notes folded into the Date bullet; ADR-116 carried no explicit date so it states the v1.0-RC milestone honestly).
  - **ADR-144 (T6, new, `docs/architecture/adr/ADR-144-doc-truth-reconciliation.md`).** New Accepted ADR recording the documentation-as-truth reconciliation discipline that plans 121/122 established: docs follow code, anchored to `file:line` citations, gated by the suites that pin the documented behavior; the six concrete drifts that were corrected (REWRITE re-adjudication, R2/`policyVersion` host-conditional binding, E3/DEFER-resume taint elevation, the dangling §9.5 anchors, the stale ADR range, the missing scaffold) are catalogued with their code anchors. Prose-only; preserves all invariants.
  - **ADR index range (T5, `docs/architecture/decisions.md`).** The §4 authoritative-range line, corrected by 121 to ADR-101..ADR-143, is advanced to ADR-101..ADR-144 (new highest `ADR-144-doc-truth-reconciliation.md`); a pointer to `adr/README.md` and rows for ADR-143/ADR-144 added to the representative table. The "ADR-101..ADR-136" stale range remains absent.
  - **SECURITY.md reconciliation (T6).** The coarse "In scope" list is reconciled with the as-built threat model: added the monotonicity/fail-closed ceiling, the taint-short-circuit guard order, the `auditHash` chain + host-conditional `policyVersion`/`kernelVersion` binding (matching threat-model R2), and the authority-guard IDOR caveat (real closure needs a host-injected authenticated principal), with pointers to `docs/security/threat-model.md`, `decisions.md §5`, and the ADR index. No overstated guarantee.

- 539337f: feat(core): 081 — pin per-guard CODE artifacts into the policy descriptor. Add `attachGuardCodeArtifact` / `readGuardCodeArtifact` / `GuardCodeArtifact` (a symbol-keyed slot carrying closure-captured numeric caps + predicate body) and surface a per-guard `codeDigest` (sha256-over-canonical via `@adjudicate/canonical`) on `GuardDescriptor` in `describePolicyBundle`. Additive + back-compatible: guards without an artifact carry no `codeDigest`. No new kernel dependency; the kernel decision is unchanged (purity/determinism preserved).

  feat(conformance): the ConfigSeal sealable surface now binds guard CODE, not just declared metadata. `SealableSurface` gains an order-stable `guardCodeDigests` list (new `GuardCodeDigest` type) threaded through `extractSealableSurface`; `computeConfigDigest` / `verifyConfigSeal` / `verifyConfigSealFrozen` signatures are unchanged. Closes Critique #27 / the 034→081 body-integrity dependency: editing a `createRewriteGuard` closure-captured cap (e.g. `AUTO_REMEDIATION_BLAST_CAP` 5 → 5000) now drives a digest mismatch instead of verifying clean (fail-closed, §D-inv-6).

  fix(primitives): `createRewriteGuard` exposes its closure-captured cap (and clamp body) to the descriptor via `attachGuardCodeArtifact`, so a behavior-changing cap edit is no longer invisible to the seal.

  feat(red-team): add `runConfigSealCapEditRegression` (+ `CapEditRegressionResult`) — a `config_integrity` regression that asserts a tampered guard cap is DETECTED by the sealed surface digest.

  feat(cli): `pack verify --expect-seal <hex>` verifies the extended ConfigSeal surface (guard code bodies pinned), in addition to the declarative-subset fingerprint.

  chore(adapter-core, admin-sdk): doc + wire-schema updates for the extended descriptor surface (the `configSeal` loop gate now binds guard code; `GuardDescriptorSchema` tolerates the optional `codeDigest`).

- Updated dependencies [58cad7a]
- Updated dependencies [6a73485]
- Updated dependencies [9056c6e]
- Updated dependencies [b77f6b0]
- Updated dependencies [5a261ef]
- Updated dependencies [014e8fe]
- Updated dependencies [f34c493]
- Updated dependencies [a9be0ad]
- Updated dependencies [e8698b1]
- Updated dependencies [6121a7a]
- Updated dependencies [c0d1b93]
- Updated dependencies [c0b1b44]
- Updated dependencies [86abd1a]
- Updated dependencies [d2c3625]
- Updated dependencies [cb8d608]
- Updated dependencies [41a295e]
- Updated dependencies [6e18f2c]
- Updated dependencies [580fc68]
- Updated dependencies [5dfa0e5]
- Updated dependencies [7832b4c]
- Updated dependencies [0d83e43]
- Updated dependencies [e9cc367]
- Updated dependencies [44c46d2]
- Updated dependencies [79f47fe]
- Updated dependencies [e81b801]
- Updated dependencies [539337f]
- Updated dependencies [1978f2b]
- Updated dependencies [3f4bbbc]
- Updated dependencies [94ddc76]
  - @adjudicate/audit@4.0.0
  - @adjudicate/core@1.5.0
  - @adjudicate/runtime@0.3.0
  - @adjudicate/conformance@3.0.0

## 0.3.2

### Patch Changes

- Updated dependencies [93d5cda]
  - @adjudicate/core@1.4.0
  - @adjudicate/audit@3.0.0
  - @adjudicate/conformance@2.0.0
  - @adjudicate/runtime@0.2.2

## 0.3.1

### Patch Changes

- @adjudicate/audit@3.0.0

## 0.3.0

### Minor Changes

- 58655cb: feat(adapter-core): add MemoryStore (in-memory + redis) + `memoryStore`/`enrichContext`/`deriveMemoryWriteback` options — cross-session memory enriches the planner/renderer context UPSTREAM of the envelope; the kernel decision is unchanged (ADR-126).

  feat(admin-sdk): add `memory.bySession` for the console Session Memory panel.

- 7545b17: feat(conformance): add Configuration Integrity Seal — sealPackConfig / verifyConfigSeal pin the introspectable config surface (declarative + guard metadata + probed taint minimums + basis codes) under a signature (ADR-121). Factored shared canonicalJson into its own module.

  feat(adapter-core): config-seal loop gate — verifies once per agent instance before the first adjudication; on mismatch refuses the turn (new `refused` AgentOutcome + `config_seal_violation` trace) and can engage the kill switch.

  feat(core): add `kill.SEAL_MISMATCH` basis code.

  feat(admin-sdk): add `governance.configSealStatus` for the console seal panel.

- 1e0058b: feat(primitives): add `createTokenBudgetGuard` — pure guard that REFUSE/DEFERs on per-session/per-tenant token budgets, reading the counter from adopter state S (ADR-120).

  feat(adapter-core): `AssistantTurn.usage` + `onTokenUsage` hook surface provider token usage per turn (the adopter folds it into state S).

  feat(anthropic,openai): map provider token usage onto `AssistantTurn.usage`.

  feat(admin-sdk): add `governance.tokenBudget` for the console Token Budget panel.

- 6b291be: Token Governance surface (ADR-135, follows ADR-120). `@adjudicate/adapter-core` gains a token-usage TELEMETRY store: the `TokenUsageStore` interface + `createInMemoryTokenUsageStore({ sessionBudget?, perTenantBudget?, perSessionBudget?, perTenantBudgets?, maxSessions?, maxEvents?, capacity? })`, mirroring the existing `createInMemoryMemoryStore`/`createInMemoryConfirmationStore` (Map-backed ref impl, opportunistic LRU bound, fixed-capacity event ring). Fed by the adapter loop's `onTokenUsage` hook via `record(sample)`, it accumulates per-session AND per-tenant cumulative consumption against configured caps and appends a bounded `TokenExhaustionEvent` when a counter CROSSES its cap (once per crossing, not per over-budget sample); reads via `sessions()` / `tenants()` / `exhaustionEvents()` / `totalConsumed()`. New types `TokenUsageSample`, `TokenBudgetConfig`, `SessionConsumption`, `TenantConsumption`, `TokenExhaustionEvent`, and the filter types. **The store is strictly OUTSIDE the determinism boundary — it is TELEMETRY and NEVER a kernel input.** Enforcement stays in `createTokenBudgetGuard` (input is adopter state S, not this store). NO wall-clock on any recorded value (timestamps are caller-supplied — `at` is used verbatim; the only `Date.now()` is the same LRU sweep the memory store already does) and NO RNG (event ids are a monotonic `evt:<n>` sequence, not `randomUUID`), so the store is reproducible across runs/replays. Session counters are LRU-bounded (default 10_000) and events ring-bounded (default 10_000) so unbounded session-id churn cannot grow memory — and the per-tenant aggregate is the backstop for session-churn budget evasion (it aggregates across all of a tenant's sessions regardless of churn). Redis is a noted follow-up (the in-memory store + interface ship now).

  `@adjudicate/admin-sdk` gains the read-only `governance.tokenBudgetByTenant` query (input `TokenBudgetTenantQuerySchema` `{ tenantId?, since?, eventLimit≤500 }`) returning `TokenBudgetByTenantResultSchema` (`{ tenants[], exhaustionEvents[], totalConsumed }`); throws PRECONDITION_FAILED when `ctx.tokenBudget.queryByTenant` is absent (feature-detectable), mirroring `governance.tokenBudget`. New schemas `TokenScopeSchema` (CLOSED `session`|`tenant`), `TokenBudgetTenantSchema`, `TokenExhaustionEventSchema`, `TokenBudgetTenantQuerySchema`, `TokenBudgetByTenantResultSchema` (+ inferred types) re-declare the store's read-model as Zod with NO dependency on `@adjudicate/adapter-core`. `TokenBudgetResultSchema` gains ADDITIVE OPTIONAL `tenants?`/`exhaustionEvents?` fields — the existing session-only shape and `governance.tokenBudget` stay byte-compatible. `AdminContext.tokenBudget` widens additively with an optional `queryByTenant` (`query` kept for back-compat; both optional so single-method adopters still typecheck). `ActorSchema` gains an ADDITIVE OPTIONAL `tenantId` and `extractActor` reads `x-adjudicate-actor-tenant` — the minimal multi-tenant dimension that realizes the pre-existing `AuditQuerySchema.tenantScope` convention; single-tenant adopters omit it. No kernel change, no closed KERNEL-enum widening (Decision-6/Taint/IntentActor/BasisCategory unchanged; the only new enum is admin-sdk-local and closed), no canonical-hash change. `TokenExhaustionEvent` is a telemetry read-model — NOT an `AuditRecord` field and NOT a `GovernanceEvent` taxonomy entry. Powers the console `/tokens` Token Governance section (tenant budgets, session budgets, exhaustion timeline) and the public web `/transparency/tokens` aggregate-only, id-free, banded burn-down.

### Patch Changes

- Updated dependencies [60daeef]
- Updated dependencies [fdc0344]
- Updated dependencies [ce2cdc5]
- Updated dependencies [7545b17]
- Updated dependencies [570db36]
- Updated dependencies [464db38]
  - @adjudicate/conformance@2.0.0
  - @adjudicate/core@1.3.0
  - @adjudicate/audit@3.0.0
  - @adjudicate/runtime@0.2.1

## 0.2.0

### Minor Changes

- 36e7e76: # v0.6 — adapter-core extraction + OpenAI + Tier 2 analyzer

  Second-phase architectural advancement pass. The kernel API stays frozen; the provider integration surface, the analyzer, and the Pack ecosystem primitives all gained substance.

  ## `@adjudicate/adapter-core` (new) — ADR-113

  Extracted the provider-neutral orchestration into its own package. Contains the tool-use loop, the bridge (`classifyIncomingToolUse` + `buildEnvelopeFromToolUse`), the Decision translator, persistence shims (`createInMemoryDeferStore`, `createInMemoryConfirmationStore`), and the error taxonomy (`AdapterError`, `AdapterErrorCode`).

  Provider adapters now implement a `ProviderBridge<H>` against their SDK and re-export `createAdjudicatedAgent` from adapter-core. Adding a third provider is a < 200-line PR.

  History `H` is opaque to the loop — the bridge is the only thing in the codebase that knows the SDK-specific conversation-history shape. Every invariant the v0.5 loop preserved (replay determinism, fail-closed semantics, REWRITE executes the rewritten envelope, DEFER hash-verification, REQUEST_CONFIRMATION blob tamper detection) flows through unchanged.

  ## `@adjudicate/openai` (new)

  Reference OpenAI Chat Completions integration. Thin SDK shim over adapter-core. Accepts any object satisfying `OpenAIChatLikeClient` — the official `openai` SDK satisfies it structurally, mocks satisfy it, Azure OpenAI wrappers satisfy it. No hard `openai` dependency.

  Cross-provider parity verified by `tests/integration-pix.test.ts` — the same canned PIX-Pack conversation reaches the same six Decision kinds with the same audit-record counts and no `withBasisAudit` drift events.

  ## `@adjudicate/anthropic` — breaking surface change
  - The package is now a thin shim over adapter-core. The public API (`createAdjudicatedAgent`, `createAnthropicPromptRenderer`, persistence shims, error taxonomy) is preserved by re-exports from adapter-core.
  - `AgentEvent.tool_result.payload` is now the provider-neutral `ToolResultBlock` shape (`{ toolUseId, content, isError? }`) instead of the Anthropic-specific `ToolResultBlockParam` (`{ type: "tool_result", tool_use_id, content, is_error? }`). The loop maps to the SDK shape only at the bridge boundary.
  - `AnthropicAdapterError` / `AnthropicAdapterErrorCode` are kept as deprecated aliases for `AdapterError` / `AdapterErrorCode`; both will be removed in v2.0.

  ## `@adjudicate/analyze` — Tier 2 AST analyzer

  New `AJD-201 RewriteScopeAstAnalyzer` walks the actual source AST to verify a REWRITE guard's declared `mutatesPayloadFields` matches what the rewritten envelope's payload literal touches. Catches:
  - **Undeclared mutations** (error): a field is assigned in the rewrite but not declared.
  - **Stale declarations** (warning): a declared field is never touched by any rewrite.
  - **Unsafe spreads** (note): `{ ...payload }` without explicit overrides — static scope analysis cannot reason; surface to the operator.

  Diagnostics carry `sourceLocation: { file, line, column }` so editors and GitHub Code Scanning can deep-link. Opt-in via `analyzePolicy({ sourceFiles })`.

  ## `@adjudicate/conformance` — `validatePackManifest` primitive

  Standalone validator for the `package.json` `adjudicate` field per `docs/pack-ecosystem/registry-foundations.md`. Returns either `{ ok: true, manifest }` with a typed view, or `{ ok: false, errors }` with operator-readable violations. Consumed by the CLI, the future registry indexer, and adopter install hooks.

  `crossCheckPackVsManifest` cross-checks the live Pack against its declared manifest — catches drift between what the manifest claims (`intents`, `signals`) and what the Pack actually declares.

  ## `@adjudicate/core`
  - `KERNEL_REFUSAL_CODES` now includes `guard_panic`. The conformance harness's `KERNEL_INTERNAL_REFUSAL_CODES` overlay is removed; one less place for refusal-code drift to hide.
  - `assertPackConformance` vs `runConformance` split documented prominently in the module header — the boot-time / runtime / CI split is no longer ill-documented.
  - `explainRecord` gained `mergeExplanationRegistries(...)` for Pack-authors composing locale registries.
  - `DecisionExplanation` gained `supersession` field — when an AuditRecord v3+ carries `supersedes`, the explanation renders it as a single-sentence narration. Default templates cover `confirmation_resolved`, `defer_resumed`, `rewrite_executed`, `replay`.

  ## Numbers
  - 928 tests passing (up from 876), 1 skipped, 0 failing.
  - 52 net new tests: 24 adapter-core, 12 openai, 10 analyze (Tier 2), 10 core (explain extensions), 20 conformance (manifest), minus 24 anthropic tests that moved into adapter-core.
  - 1 new ADR (ADR-113).
  - 1 new package (`@adjudicate/adapter-core`).
  - 1 new provider adapter (`@adjudicate/openai`).

- 36e7e76: v0.7 — operational hardening + ecosystem trust. All additive; no kernel breaking changes.

  **Distributed kill switch v2.** `startDistributedKillSwitchPubSub` in `@adjudicate/audit` adds Redis pub/sub propagation on top of the existing polling helper. Sub-100 ms transitions when the subscriber is connected; polling retained as fallback for disconnects, restarts, and broker outages. See ADR-114.

  **Real-time audit event substrate.** `createInMemoryAuditEventBus`, `createRedisAuditEventBus`, and `bridgeAuditSinkToBus` in `@adjudicate/audit`. Operator consoles and live-tail views fan out without touching the durable sink contract.

  **Restart-durable confirmations.** `createRedisConfirmationStore` in `@adjudicate/adapter-core/persistence-redis`. REQUEST_CONFIRMATION tokens survive process restarts and rolling deploys.

  **Pack trust primitives.** `computePackFingerprint`, `signPackFingerprint`, `verifyPackSignature`, `verifyPackTrust` in `@adjudicate/conformance`. Pure functions, ed25519 + RSA-PSS, no hosted dependencies. See ADR-115.

  **`adjudicate pack verify` CLI.** Install-time + CI-gate wrapper around `verifyPackTrust`. Modes: `none | best_effort | require_fingerprint | require_signature`.

  **`replayWithIntegrity` + `explainReplayReport`.** `@adjudicate/audit` gains a verifier that runs decision-axis check AND envelope `intentHash` + AuditRecord `auditHash` tamper detection in one pass. `explainReplayReport` produces operator-readable narration in three formats (`ci-line | summary | operator`).

  **Cross-runtime golden vectors.** `docs/specs/canonical-hash-vectors.json` is the language-neutral consumer of the canonical-JSON SHA-256 spec. `packages/core/tests/cross-runtime-hash-vectors.test.ts` reads it and asserts the Node implementation matches; non-Node runtimes can do the same.

  **Adapter loop `TraceSink`.** `@adjudicate/adapter-core` exposes a low-cardinality lifecycle hook (`iteration_start | decision_emitted | paused | completed | max_iterations_exceeded`). Defaults to no-op; opt in via `traceSink:` on `createAdjudicatedAgent`.

  **Extended SEMCONV.** Eight new low-cardinality `adjudicate.*` attributes in `@adjudicate/observability` for adapter / provider / pause / kill-switch lifecycle. All additive; no renames.

  **Chaos test suites.** `packages/audit/tests/chaos-kill-switch.test.ts` and `chaos-replay.test.ts` exercise burst-of-malformed messages, disconnect/reconnect recovery, trip/clear storm convergence, multi-replica race (no split-brain), subscribe leak detection, and 100+ corrupted replay envelopes.

  **Test totals.** 1022 passing (was 924), 1 skipped (audit-postgres needs a live DB), 0 failing.

  See `docs/architecture/V0.7-AUDIT-REPORT.md` for the full v1.0 readiness review.

### Patch Changes

- Updated dependencies [9e65871]
- Updated dependencies [e9fc3ad]
- Updated dependencies [36e7e76]
- Updated dependencies [36e7e76]
  - @adjudicate/audit@2.0.0
  - @adjudicate/core@1.2.0
  - @adjudicate/runtime@0.2.0
