diff --git a/docs/en/README.md b/docs/en/README.md index 095742c..ce00c64 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -33,6 +33,11 @@ English is the canonical source for all Benchwork documentation. - [RFC-0008: Codex Plugin-first Host Architecture](rfcs/RFC-0008-codex-plugin-first-host-architecture.md) - [RFC-0009: MCP as the Scientific Control Plane](rfcs/RFC-0009-mcp-scientific-control-plane.md) - [RFC-0010: The Invitation Installer Contract](rfcs/RFC-0010-invitation-installer-contract.md) +- [RFC-0011: Sanctum Execution Model](rfcs/RFC-0011-sanctum-execution-model.md) +- [RFC-0012: Job, Lease, and Worker Protocol](rfcs/RFC-0012-job-lease-worker-protocol.md) +- [RFC-0013: Artifact Storage Model](rfcs/RFC-0013-artifact-storage-model.md) +- [RFC-0014: Patch Promotion Protocol](rfcs/RFC-0014-patch-promotion-protocol.md) +- [RFC-0015: Experiment Executor API](rfcs/RFC-0015-experiment-executor-api.md) ## Architecture diff --git a/docs/en/ROADMAP.md b/docs/en/ROADMAP.md index e0ff431..a0ba882 100644 --- a/docs/en/ROADMAP.md +++ b/docs/en/ROADMAP.md @@ -5,120 +5,445 @@ canonical: true # Roadmap -## M0: Foundation +## Strategic direction + +Benchwork is evolving from a trusted scientific control plane into a trusted +execution platform, then into a research operating system and an infrastructure +ecosystem. + +```text +Trusted Research Control Plane + -> Trusted Research Execution Platform + -> Research Operating System + -> Research Infrastructure +``` + +The roadmap is capability-gated rather than date-driven. A phase advances only +when its contracts, failure behavior, and acceptance evidence are complete. +Adding more Skills, Agents, or prompts is not a strategic milestone by itself. +The durable advantages are trusted state, safe execution, reproducibility, +scientific lineage, and a governed extension ecosystem. + +## Current baseline + +- Phase 2 was sealed by M17 in `0.3.0rc1` as + `PASS_WITH_ACCEPTED_EXCEPTIONS` and `FROZEN_ALPHA`. Kernel, MCP, Plugin, and + Codex CLI gates passed. Claude Code CLI remains + `PENDING_HOST_VALIDATION`. +- `main` is prepared as the `0.3.0rc2` candidate. M18 installer implementation + is present, but the exact `v0.3.0rc2` tag and retained installer acceptance + evidence are still pending. Its acceptance decision remains `REPAIR`. +- The Phase 2 contracts remain Alpha. Existing identifiers must never be + silently reinterpreted, but breaking Alpha changes remain possible through + an accepted RFC, migration guidance, and replay or contract coverage. +- Phase 3's RFC gate is open. No current release claims executor-enforced + isolation, remote jobs, GPU scheduling, or a production Artifact store. + +See the [Phase 2 acceptance matrix](PHASE2_ACCEPTANCE.md), +[Compatibility Policy](COMPATIBILITY.md), and +[Installer RC acceptance](install/ACCEPTANCE.md) for the normative status. + +## Non-negotiable boundaries + +- Athanor remains the only authority for canonical transitions. Agent, + Provider, Executor, and Observatory outputs remain Proposals until Athanor + accepts them and issues a Receipt. +- Chronicle remains the canonical research source. Research Graphs, memory, + indexes, and recommendations must be rebuildable projections. +- Executor Job state is separate from immutable scientific Run state. A + successful Job does not automatically create a Run, Artifact, Assessment, or + Decision. +- Failed Jobs, Attempts, patches, Runs, negative results, Deviations, and open + Issues remain part of the record. +- Human confirmation remains mandatory for RQ, Protocol, and Decision Seals. +- The MCP control plane does not gain a universal shell, filesystem, Git, web, + or `execute anything` escape hatch. +- Third-party executable code is not trusted merely because it is packaged as + a Plugin or Grimoire. + +## Evolution + +| Release line | Phase | Codename | Strategic capability | +| --- | --- | --- | --- | +| `0.1` | Phase 0 | Foundation | Project language, governance, and initial contracts | +| `0.2` | Phase 1 | Athanor Kernel | Deterministic scientific state and provenance | +| `0.3.0rc1` | Phase 2 | Instrumentarium | Sealed host-neutral scientific control-plane baseline | +| post-rc1 `0.3.x` | Phase 2.5 | Consolidation | Long-lived control-plane foundation | +| `0.4` | Phase 3 | Sanctum | Experimental trusted-execution contracts and reference runtime | +| `0.5` | Phase 4 | Forge | Production-grade experiment runtime | +| `0.6-0.7` | Phase 5 | Observatory | Research-state intelligence | +| `0.8` | Phase 6 | Grimoire Ecosystem | Trusted research extension ecosystem | +| `1.0` | Phase 7 | Research Runtime | Complete Research Operating System | +| `2.0` | North star | Research Infrastructure | Product form defined from post-1.0 evidence | + +## Phase 2.5 — Consolidation (`0.3.x`) + +### Objective + +Make the Phase 2 control plane a durable foundation before expanding the +execution surface. + +### Capability gates + +1. **Release closure** + - Complete the exact `v0.3.0rc2` installer acceptance across its declared + platforms and backends. + - Run equivalent source-state gates on pull requests and `main`, validate + immutable candidate assets from the exact tag, and complete public-URL + smoke tests after publication but before channel promotion or final + acceptance. + - Retain release assets, checksums, SBOM, provenance, rollback, uninstall, + and `Project state: NOT_TOUCHED` evidence. + +2. **Alpha contract baseline** + - Maintain the existing Alpha policy rather than claiming Stable + compatibility. + - Add machine-checkable baselines for the 38 MCP tools, seven Skills, + published Schema identifiers, Capability identifiers, old request/result + fixtures, and Chronicle replay. + - Require an accepted RFC, migration guidance, and compatibility evidence + for every breaking Alpha change. + +3. **Claude Host acceptance** + - Preserve the existing Host-neutral Capability, Capsule, Result, approval, + and Receipt contracts. + - Complete a real Claude Code CLI trial covering discovery, a bounded Task + lifecycle, Seal/actor provenance, Athanor acceptance, and retained + evidence before claiming Host parity. + +4. **Declarative developer experience** + - Expand `CONTRIBUTING.md` and publish a Developer Guide, declarative Plugin + SDK, Capability SDK, and validation workflow for Capability contracts, + data-only Rites, Skills, and Host packaging. + - Provide clean-project examples and conformance checks that a third party + can run without editing the Benchwork kernel. + - Keep arbitrary executable Worker, Storage, and Executor adapters outside + the `0.3.x` SDK. + +5. **Research dogfood** + - Complete independent Paper Reproduction, Model Improvement, and Research + Direction Discovery cases. + - Each case must preserve inputs, environment identity, Receipts, failed and + excluded Runs, restart replay, Deep Doctor evidence, and a final explicit + Decision where scientifically appropriate. + +### Exit gate + +Consolidation closes only when the release candidate, Claude Host, compatibility +baseline, declarative SDK, and three dogfood cases all have retained, +reproducible evidence. No additional Agent or Skill count can substitute for +these gates. + +## Phase 3 — Sanctum (`0.4`) + +### Objective + +Define how an approved task may act in a real environment without weakening +the scientific control plane, and publish an experimental local reference +runtime for controlled dogfood. + +The accepted Arcana meanings remain unchanged: + +- **Sanctum** is the isolated Agent context. +- **Circle** declares the per-task boundary. +- **Ward** evaluates and enforces policy. +- **Crucible** is the mutable worktree, container, sandbox, or remote + workspace where change is tested. + +```text +Task Capsule + -> Sanctum context + -> Circle policy + Ward enforcement + -> Execution Job + Lease + -> Crucible workspace + -> Worker result Proposal + -> Athanor validation + -> Chronicle Receipt +``` + +### RFC sequence + +1. `RFC-0011: Sanctum Execution Model` + - Control-plane and execution-plane ownership, terminology, threat model, + assurance levels, and operational versus canonical state. +2. `RFC-0012: Job, Lease, and Worker Protocol` + - Worker identity and capability, claim, heartbeat, fencing, renewal, + cancellation, timeout, retry identity, logs, terminal states, and crash + recovery. +3. `RFC-0013: Artifact Storage Model` + - Logical Artifact versus physical Blob or Replica, content identity, + transfer integrity, provenance, retention, and backend boundaries. +4. `RFC-0014: Patch Promotion Protocol` + - Base identity, Patch Proposal, validation evidence, stale/conflict + handling, explicit human promotion, idempotency, and recovery. +5. `RFC-0015: Experiment Executor API` + - Typed start, observe, cancel, and result operations built on the accepted + Job, Lease, Worker, and Artifact contracts. + +### Reference runtime + +`0.4` may expose experimental local execution for controlled use. It must: + +- use new versioned execution contracts rather than reinterpret frozen Phase 2 + fields; +- keep Job, Lease, Attempt, and Worker state separate from scientific Runs; +- return bounded results and patches as Proposals; +- demonstrate expiration, cancellation, duplicate delivery, stale-result + rejection, and restart recovery; +- state its assurance level and avoid production-grade isolation claims. + +Remote Workers, cluster scheduling, automatic Provider invocation, and broad +GPU support are non-goals for `0.4`. + +### Exit gate + +All five RFCs, executable Schemas and examples, threat-model review, +conformance suite, and the local reference vertical slice must agree on the +same state and trust boundaries. A successful prototype alone is insufficient. + +## Phase 4 — Forge (`0.5`) + +### Objective + +Move from defining trusted execution to operating reproducible experiments. + +### Capabilities + +- Production-grade local and container execution with explicit filesystem, + network, CPU, memory, time, cancellation, and log controls. +- Resource queues, quotas, priorities, and auditable scheduling decisions. +- A local content-addressed Artifact Registry with pluggable remote backend + contracts. +- Dataset versions bound to content identity, provenance, and license + metadata. +- Versioned Protocol, Analysis Spec, and Rite contracts for multi-Experiment + studies, ablation matrices, Pilot/Formal readiness, and completion + aggregation without changing existing v1 replay semantics. +- Promotion of validated patches and execution outputs through explicit + Athanor acceptance rather than direct canonical mutation. + +Slurm, Kubernetes, cloud, and remote Artifact adapters may be added behind the +accepted interfaces, but no particular remote backend is a `0.5` release +blocker. + +### Exit gate + +Forge closes only when every capability above has conformance evidence. That +evidence must include the trusted local/container lifecycle; queue, quota, and +priority enforcement; the local Artifact Registry and pluggable backend +contract; Dataset identity, provenance, and license handling; and the new +Protocol, Analysis Spec, and Rite versions in a reproducible multi-Experiment +study. Every failed Attempt must remain preserved, and Executor success must +never be confused with scientific acceptance. -Completed: repository structure, RFC-0000, public terminology, and project governance. +## Phase 5 — Observatory (`0.6-0.7`) + +### Objective + +Understand research state, surface weaknesses, and propose the next +scientifically useful action without taking scientific authority from the +researcher. + +### Capabilities + +- A Research Graph relating Papers, Evidence, Claims, Hypotheses, Protocols, + Experiments, Runs, Assessments, Decisions, Artifacts, Issues, and + Deviations. +- Scientific Memory that preserves why a design was chosen, why a direction + was abandoned, and what failed or remained uncertain. +- A Next Action Engine whose recommendations include their supporting state, + missing evidence, uncertainty, and disconfirming conditions. +- Provider-neutral scientific Review Capabilities that may be served by + Claude, GPT, local models, or deterministic tools under the same disclosure + and acceptance boundaries. -## M1: Athanor +### Exit gate + +Every graph edge, memory item, and recommendation must trace back to canonical +state or be labelled as an unaccepted Proposal. Observatory may recommend +`CONTINUE`, `REPAIR`, `PIVOT`, or `STOP`; it may not Seal the Decision. + +## Phase 6 — Grimoire Ecosystem (`0.8`) + +### Objective + +Evolve the existing local, data-only Open Grimoire into a trusted research +extension ecosystem. + +### Capabilities + +- Publisher identity, signatures, provenance, compatibility resolution, and + an auditable Registry. +- Domain Grimoires for machine learning, bioinformatics, robotics, + reproduction, benchmarking, and surveys. +- Distribution of validated Rites, Capability Packs, Policy Packs, Schemas, + examples, and benchmarks. +- Executable Worker, Storage, or Executor adapters only after signature, + isolation, permission, and supply-chain policies are enforced. + +### Exit gate + +Remote installation must fail closed on unknown publishers, invalid +signatures, incompatible contracts, path escapes, dependency conflicts, and +untrusted executable content. Installing a Grimoire must never grant scientific +approval or canonical authority. + +## Phase 7 — Research Runtime (`1.0`) + +### Objective + +Deliver Benchwork as a complete Research Operating System. + +```text +Research intent + -> Evidence and Claim map + -> Research Question and Protocol + -> Human Seal + -> Sanctum and Forge execution + -> Alembic analysis + -> Observatory interpretation + -> Human Decision + -> Continue | Repair | Pivot | Stop +``` + +`1.0` requires stable, documented contracts for the supported control, +execution, intelligence, and ecosystem surfaces; explicit migrations from +supported prerelease state; and end-to-end reproduction evidence from multiple +research domains. + +## Research Infrastructure (`2.0` north star) + +`2.0` intentionally remains a north star rather than a committed feature list. +Its product form will be defined from evidence gathered after `1.0`, while +preserving portable research state, verifiable lineage, bounded execution, and +researcher authority. + +## Technology direction + +Python remains the implementation language throughout `0.x` while semantics +and protocols are evolving. A Rust Athanor may be evaluated after `1.0` only +when profiling, deployment, integrity, or concurrency evidence shows that a +kernel rewrite solves a measured problem. + +## Historical milestone ledger + +### M0: Foundation + +Completed: repository structure, RFC-0000, public terminology, and project +governance. + +### M1: Athanor Completed: a local Chronicle with chained receipts, replayed projections, Protocol drafting and sealing, and a Schema-validated CLI. The published Schema contracts define scientific objects before provider integration begins. -## M2: Circle +### M2: Circle Completed: local Capability registration, Task Capsule validation, Circle boundaries, Ward evaluation, and canonical human approval receipts. Agent Results remain proposals until accepted by Athanor. -## M3: Twin Gate +### M3: Twin Gate -Completed: symmetric Codex and Claude Code Host adapters, both operating -through the same Capability contracts and local canonical state. +Completed: host-neutral Codex and Claude Code adapters that produce proposals +through the same Capability contracts and local canonical state. This adapter +symmetry does not claim a completed Claude Code interactive Host trial. -## M4: First Rite +### M4: First Rite Completed: `computational-study@0.1.0` and its protocol-bound Working state machine. -## M5: Alembic +### M5: Alembic Completed: canonical Experiment and Run provenance plus schema-validated `result-bundle/1.1` deterministic descriptive aggregation. -## M6: Open Grimoire +### M6: Open Grimoire Completed for public Alpha: local, data-only Grimoire manifests; exact API and SemVer pins; canonical Rite Sigils; collision and path-escape protection; and Working execution from copied, inspectable definitions. -## M7: Scientific Canon +### M7: Scientific Canon Completed: sourced and monotonically verified Evidence; explicit Claim relations; Claim-backed, Protocol-registered Hypotheses; Result Bundle Assessments; human-Sealed Decisions; and cross-object Chronicle lineage. -## M8: Integrity and Recovery +### M8: Integrity and Recovery Completed: canonical, content-addressed Artifacts with producer and input lineage; open-to-resolved research Issues; and immutable Deviations that record post-Seal changes without rewriting the Protocol commitment. -## M9: Command Surface and Agent Handoff +### M9: Command Surface and Agent Handoff Completed: all RFC Capability contracts; direct research verbs that create Ward-checked Task Proposals; canonical Agent Result acceptance bound to the original Capsule and input Sigil; Chronicle and Sigil inspection; and the documented Grimoire, Rite, Working, Seal, and typed Trace command forms. -## M10: The Seal +### M10: The Seal Release candidate implemented: Phase 1 hardening for Receipt integrity, state-bound Tasks, explicit scientific Gates, canonical Working and Run lifecycles, registered Alembic comparisons, deterministic CLI project context, governance, and golden release acceptance. Automatic Provider execution remains -outside M10. Final acceptance requires the complete stacked CI suite to pass. +outside M10. -## M11: The Grimoire +### M11: The Grimoire Implemented: Codex Plugin-first RFCs, the repository marketplace, seven trigger-specific Skills, and concise repository agent rules. -## M12: The Instrument +### M12: The Instrument Implemented: `bwork mcp serve`, STDIO transport, server instructions, stable response envelopes, cursor pagination, and read-only project tools. -## M12.5: Host Boundary Verification +### M12.5: Host Boundary Verification Implemented: tiered Kernel, CLI, and IDE Host acceptance; a machine-readable Host capability matrix; environment-blocked IDE exceptions that do not weaken Core/MCP gates; and a host-neutral external disclosure policy. -## M13: The Familiar +### M13: The Familiar Implemented: the interactive Task open/get/complete/fail loop with automatic semantic output blobs, Sigils, Host Session provenance, Athanor acceptance, and same-session Receipts. -## M13.5: Review Provenance +### M13.5: Review Provenance Implemented: local and external Review Capability boundaries, Review Request and Review Artifact Schemas, a fail-closed disclosure Gate, and Chronicle events for request, approval, completion, and acceptance. Benchwork records Review provenance but does not invoke or upload content to a provider. -## M14: The Wards +### M14: The Wards Implemented: trusted SessionStart, PreToolUse, and PostToolUse hooks with -bounded output, direct `.benchwork/` write protection, and adversarial fixtures. +bounded output, direct `.benchwork/` write protection, and adversarial +fixtures. -## M15: The Working +### M15: The Working Implemented: canonical scientific write tools, immutable preview/commit Seals, Experiment and Run tools, deterministic Alembic invocation, and specialized implementation, pilot, evaluation, and recovery Skills. -## M16: Trial of the Familiar +### M16: Trial of the Familiar -Prerelease candidate: local marketplace packaging, plugin and Skill validation, +Prerelease candidate: local marketplace packaging, Plugin and Skill validation, in-memory and spawned-STDIO protocol tests, and the Phase 2 acceptance matrix. Codex CLI acceptance is complete. IDE validation is an accepted `BLOCKED_BY_ENVIRONMENT` Tier 2 exception until a graphical extension Host is available; external diff review remains `WAITING_FOR_DISCLOSURE_AUTHORIZATION`. -## M17: The Instrumentarium Seal +### M17: The Instrumentarium Seal Completed for `0.3.0rc1`: Phase 2 API and protocol freeze; 38-tool machine-readable MCP Registry; versioned Plugin and Skill compatibility @@ -136,18 +461,16 @@ own Tier 1 PASS on 2026-07-31, after its trial found and repaired a Task Capsule Host defect. See the [Host Support Matrix](HOST_SUPPORT_MATRIX.md). -## M18: The Invitation +### M18: The Invitation -Implemented for `0.3.0rc2`: an auditable POSIX installer; exact release and -channel manifests; uv-first and pipx-isolated bootstrap; installation-level -diagnostics; safe versioned plugin staging; opt-in Codex and experimental -Claude MCP setup; PATH, repair, rollback, and uninstall ownership; release -assets, SBOM, provenance, Pages publication, and installer CI. +Implemented on `main` for the `0.3.0rc2` candidate: an auditable POSIX +installer; exact release and channel manifests; uv-first and pipx-isolated +bootstrap; installation-level diagnostics; safe versioned Plugin staging; +opt-in Codex and experimental Claude MCP setup; PATH, repair, rollback, and +uninstall ownership; release-asset, SBOM, provenance, and Pages-publication +automation; and installer CI. M18 does not change the Phase 2 scientific state machine. Installer operations -must continue to report `Project state: NOT_TOUCHED`. - -Phase 3 — The Sanctum may now begin through new RFCs for sandboxing, -filesystem isolation, execution policy, patch promotion, experiment executors, -GPU/remote jobs, and Artifact storage. Phase 3 must not weaken the frozen -Phase 2 scientific control-plane boundary. +must continue to report `Project state: NOT_TOUCHED`. M18 is not a completed +tagged release until the exact-tag acceptance report records all required +evidence and changes its final decision from `REPAIR`. diff --git a/docs/en/rfcs/RFC-0011-sanctum-execution-model.md b/docs/en/rfcs/RFC-0011-sanctum-execution-model.md new file mode 100644 index 0000000..8c0b091 --- /dev/null +++ b/docs/en/rfcs/RFC-0011-sanctum-execution-model.md @@ -0,0 +1,2035 @@ +--- +title: "RFC-0011: Sanctum Execution Model" +document_id: BW-RFC-0011 +version: 0.1 +status: draft +owner: unassigned +date: 2026-07-31 +language: en +canonical: true +--- + +# RFC-0011: Sanctum Execution Model + +## Status + +This draft defines the Phase 3 trust and ownership model for controlled +execution. It refines RFC-0007 and explicitly amends RFC-0009 only to permit +typed delegation to an Executor. It does not weaken the accepted Phase 2 +control-plane boundary or make MCP an execution runtime. + +This RFC does not authorize an implementation by itself. Job, Lease, and +Worker state machines, Artifact storage, Patch promotion, and the typed +Executor API require RFC-0012 through RFC-0015 and their executable Schemas. + +## Problem + +Phase 2 can create an immutable Task Capsule, evaluate its declarative Circle +through Ward, obtain a bound human approval, and accept a structured Agent +Result through Athanor. The interactive Host still performs repository, +shell, Git, and web actions with native tools. A `PASS` Ward decision therefore +means that a request is permitted by the declared Capability contract; it is +not evidence that filesystem, network, process, resource, or time boundaries +were enforced. + +Phase 3 needs to execute an approved Task in a real environment while +preserving four separations: + +1. permission is not enforcement; +2. execution success is not scientific acceptance; +3. mutable execution state is not immutable scientific Run state; and +4. a workspace or container name is not an isolation guarantee. + +Without an explicit model, an Executor could silently reinterpret the frozen +Phase 2 Circle, write operational events into Chronicle, grant a Worker broad +Host access, treat a successful process as a scientific Run, or claim +isolation from the mere use of a subprocess or container. + +## Decision + +Benchwork separates the scientific control plane from a bounded execution +plane. + +```text +Task Capsule + pinned Snapshot + Capability Contract + | + v + Ward authorization and policy resolution + | + v + versioned Execution Specification + | + v + Executor -> Job -> Lease -> Attempt -> Worker + | + v + Sanctum context + Crucible workspace + | + v + bounded result, logs, and output Sigils + | + v + Proposal + | + v + Athanor validation -> Chronicle Receipt +``` + +The control plane owns identity, authorization, policy validation, scientific +validation, and canonical transitions. The execution plane owns scheduling, +leases, process and sandbox control, mutable workspaces, runtime observation, +and result transport. The execution plane cannot append Chronicle events or +mutate canonical projections. + +Phase 3 execution requires new closed `capability-registry/2.0`, +`capability-contract/2.0`, and `task-capsule/2.0` contracts plus a closed, +versioned Execution Specification. The v2 Registry stores execution-capable +contracts and their Sigils without changing `capability-registry/1.1`. The v2 +contracts must explicitly authorize filesystem scopes, executable and process +capabilities, network policy, environment and credential policy, resource +ceilings, visible inputs, bounded outputs, and the minimum evidence state +required for each control dimension. The Execution Specification pins those +contracts, the Research Snapshot, resolved Circle, requested assurance, inputs, +output limits, runtime requirements, assurance-profile ID, version, and Sigil, +and permitted conformance-suite identity and Sigil. Its exact Schema and +relationship to Job creation are defined by RFC-0012 and RFC-0015. + +`capability-contract/1.0` and `task-capsule/1.1` remain valid Phase 2 +control-plane contracts for unassured native-Host workflows. They are not +eligible for Executor launch, do not receive a Sanctum assurance claim, and are +not silently reinterpreted as executable sandbox contracts. + +A Phase 3 Execution Specification may narrow a v2 Task Capsule, but it may not +add a tool, filesystem scope, executable, process capability, network +permission, credential, budget, input, or output that the pinned Capability +and Task did not authorize. Any broader permission requires a new Task and, +when applicable, a new approval bound to the complete execution policy. + +Whenever the resolved approval branch is `REQUIRED`, whether required by the +Capability or upgraded by the Task, the Phase 3 approval binds the exact +Capability Contract Sigil, Task Capsule Sigil, pre-approval Specification +subject, assurance-profile ID, version, and Sigil, permitted +conformance-suite identity and Sigil, and resolved permission set. The +pre-approval subject deterministically covers every final Execution +Specification member except the final self-Sigil and the approval Receipt +binding that is not yet available. The final Specification then binds the +Receipt ID and Sigil and receives its own self-Sigil. Exact match is required. +A narrower or otherwise changed Execution Specification changes the +pre-approval subject and requires a new approval; Benchwork does not infer +subset authorization. A Phase 2 approval Receipt never authorizes a v2 Task. + +Absence is not permission. When a pinned contract has no field for an +execution dimension, the Executor treats that dimension as denied. It may not +infer filesystem or command authority from a tool name, objective, Host +conversation, repository layout, or ambient process environment. + +Every execution produces operational evidence and, at most, a Proposal. +Athanor remains the only authority that can turn a validated Proposal into +canonical research state and issue a Receipt. + +## Owned executable v2 contracts + +This RFC owns the following five JSON Schema Draft 2020-12 contracts. Their +filenames and identifiers are normative; an implementation must not publish a +different Schema under one of these identifiers. + +| Instance contract | Repository filename | Exact `$id` | +| --- | --- | --- | +| `capability-registry/2.0` | `schemas/capability-registry-2.0.json` | `https://benchwork.dev/schemas/capability-registry/2.0` | +| `capability-contract/2.0` | `schemas/capability-contract-2.0.json` | `https://benchwork.dev/schemas/capability-contract/2.0` | +| `task-capsule/2.0` | `schemas/task-capsule-2.0.json` | `https://benchwork.dev/schemas/task-capsule/2.0` | +| `execution-approval-subject/1.0` | `schemas/execution-approval-subject-1.0.json` | `https://benchwork.dev/schemas/execution-approval-subject/1.0` | +| `execution-approval-transition-request/1.0` | `schemas/execution-approval-transition-request-1.0.json` | `https://benchwork.dev/schemas/execution-approval-transition-request/1.0` | + +Each Schema has `$schema` equal to +`https://json-schema.org/draft/2020-12/schema`. The definitions below are the +complete source contract for those Schemas, not illustrative examples. Every +record, whether named, inline, top-level, or nested, is a JSON object for which +every listed member is required and `additionalProperties` is false. Every +union is a Draft 2020-12 `oneOf`; composed branches also set +`unevaluatedProperties` to false. A decoder rejects duplicate object keys +before Schema evaluation. There are no implicit defaults. + +Some content rules cannot be expressed portably by JSON Schema alone. A +conforming validator therefore consists of Draft 2020-12 Schema validation +followed by the semantic checks in this section. Schema-valid but +semantically invalid input is invalid for Ward, Executor launch, Sigil +calculation, and persistence. + +### Common scalar and collection rules + +The five Schemas use these exact aliases: + +| Alias | Exact rule | +| --- | --- | +| `Sigil` | ASCII string matching `^sha256:[0-9a-f]{64}$`; 71 bytes. | +| `U63` | JSON integer from 0 through 9,223,372,036,854,775,807. | +| `PositiveU63` | JSON integer from 1 through 9,223,372,036,854,775,807. | +| `Timestamp` | RFC 3339 date-time normalized to UTC with a terminal `Z`, whole seconds only, exactly 20 ASCII bytes. | +| `Version` | ASCII string matching `^[0-9]+[.][0-9]+(?:[.][0-9]+)?$`, 3 through 32 bytes. | +| `CapabilityId` | ASCII string matching `^bench[.][a-z0-9]+(?:[.][a-z0-9]+)+$`, 9 through 128 bytes. | +| `UpperId(P)` | ASCII string matching `^P-[A-Z0-9]+(?:[._-][A-Z0-9]+)*$`, including prefix, 4 through 128 bytes. | +| `WardDecisionId` | ASCII string matching `^WD-[A-Z0-9]+(?:[._-][A-Z0-9]+)*$`, including prefix, 4 through 128 bytes. | +| `StoragePolicyId` | Exact RFC-0013 `SP-ID`: ASCII string matching `^SP-[A-Za-z0-9][A-Za-z0-9._:-]*$`, including prefix, 3 through 128 bytes. | +| `Token` | ASCII string matching `^[A-Za-z0-9][A-Za-z0-9._:-]*$`, 1 through 128 bytes. | +| `LogicalName` | ASCII string matching `^[a-z][a-z0-9_-]*$`, 1 through 64 bytes. | +| `SchemaId` | ASCII string matching `^[a-z][a-z0-9-]*/[0-9]+[.][0-9]+$`, 5 through 128 bytes. | +| `MediaType` | Lowercase ASCII type and subtype matching `^[a-z0-9!#$&^_.+-]+/[a-z0-9!#$&^_.+-]+$`, 3 through 128 bytes. | +| `RelativePath` | UTF-8 string of 1 through 1,024 bytes using `/`; it is relative, NFC-normalized, contains no NUL, empty segment, `.` segment, or `..` segment, and has no trailing `/`. | +| `HostName` | Lowercase ASCII DNS name of 1 through 253 bytes; no wildcard, IP literal, trailing dot, user information, or embedded port. | +| `EnvironmentName` | ASCII string matching `^[A-Z_][A-Z0-9_]*$`, 1 through 128 bytes. | +| `HumanText(N)` | NFC-normalized UTF-8 string containing no NUL, from 1 through `N` bytes. | + +The concrete `UpperId` prefixes used here are `CR` for a Registry, `TK` for a +Task, `RP` for a Program, `SS` for a Snapshot, `CI` for a Circle, `POL` for a +policy, `TL` for a tool, `EX` for an executable, `RT` for a runtime, `BE` for +a backend, `FS` for a filesystem grant, `ND` for a network destination, `CH` +for a credential handle, `SE` for a side effect, `AP` for an approval policy, +`EA` for an execution-approval subject, `EAT` for an +execution-approval transition request, `AA` for an +Attempt-authorization subject, `RC` for a Receipt, and `CS` for a conformance +suite. A field named with one of those nouns uses that prefix. A research +object ID uses the prefix required by its object Schema and is 3 through 128 +ASCII bytes. + +A `WardDecisionId` resolves through Ward's immutable decision store to one +complete Ward decision record and its Sigil. Resolution is single-assignment: +the same ID can never resolve to different bytes or a different Sigil. + +`Set` means an array with those inclusive item bounds, +`uniqueItems: true`, no null item, and a semantic key that occurs exactly once. +Items are in strictly increasing order by the unsigned UTF-8 bytes of `key`; +equal adjacent keys are invalid even when the complete JSON objects differ. +Scalar sets use the complete scalar. Tuple keys are compared component by +component. A definition that says `enum order` instead uses its explicitly +printed enum order and still rejects a repeated enum value. A map is serialized +with keys in strictly increasing unsigned UTF-8 order. The semantic validator +enforces UTF-8 byte limits, NFC, strict ordering, Set-key and map-key +uniqueness, timestamp normalization, and cross-field conditions; JSON Schema +enforces the corresponding code-point, pattern, type, and item bounds. Every +subset comparison is by this unique semantic key; one parent item can +authorize at most one child item. + +`segment_depth(p)` is the positive number of `/`-delimited segments in a +`RelativePath`. A path `q` is within `(path_prefix = p, maximum_depth = d)` +exactly when `q` equals `p`, or `q` is a descendant of `p` and +`segment_depth(q) - segment_depth(p) <= d`. To narrow a parent prefix +`(p_parent, d_parent)` to `(p_child, d_child)`, let +`delta = segment_depth(p_child) - segment_depth(p_parent)`. The child prefix +must equal or descend from the parent and checked arithmetic must prove +`delta + d_child <= d_parent`; overflow is invalid. Equality consumes zero +depth. These rules use the parent prefix as the absolute comparison basis and +apply identically to filesystem and output prefixes. + +All Sigils use the canonical JSON encoding defined by RFC-0001: recursively +sorted keys, ASCII escapes, compact separators, and no non-finite number. +`contract_sigil` is SHA-256 over the complete Capability Contract with only +`contract_sigil` omitted. `registry_sigil` is SHA-256 over the complete +Registry with only `registry_sigil` omitted; embedded Capability +`contract_sigil` values remain present. `circle_sigil` is SHA-256 over the +complete Circle with only `circle_sigil` omitted. `capsule_sigil` is SHA-256 +over the complete Task Capsule with only `capsule_sigil` omitted; the Circle's +valid `circle_sigil` remains present. Each value is encoded as lowercase +`sha256:` plus the 64 hexadecimal digest digits. A placeholder, all-zero +digest, digest computed with its own member present, or digest of a different +canonical byte stream is invalid. + +The only nullable members in these contracts are +`supersedes_registry_sigil`, `expires_at`, the null fields in the +`NOT_REQUIRED` approval branches, and `idempotency_scope_sigil` under the +condition stated below. Conditional union members are absent rather than +null. No other member accepts null. + +### Capability policy types + +The following closed aliases are shared by the Capability Contract and the +Task Circle. A Task value is a proposed narrowing of the corresponding +Capability value; the comparison is defined later in this section. + +`ToolAuthority` is exactly one of: + +```text +DENY: + mode = DENY + grants = Set + +ALLOWLIST: + mode = ALLOWLIST + grants = Set + +ToolGrant: + tool_id: UpperId(TL) + tool_version: Version + tool_sigil: Sigil + maximum_invocations: PositiveU63 + side_effect_id: UpperId(SE) +``` + +An observational tool uses a declared `READ_ONLY` Side Effect rather than +omitting `side_effect_id`. + +`FilesystemAuthority` is exactly one of: + +```text +DENY: + mode = DENY + grants = Set + +ALLOWLIST: + mode = ALLOWLIST + grants = Set + +FilesystemGrant: + grant_id: UpperId(FS) + root_kind: SNAPSHOT_INPUT, CRUCIBLE, DECLARED_OUTPUT, or EPHEMERAL + path_prefix: RelativePath + operations: Set + maximum_read_bytes: U63 + maximum_write_bytes: U63 + maximum_entries: U63 + maximum_depth: U63 + symlink_policy = NOFOLLOW + mount_policy = NO_NEW_MOUNTS + +FilesystemOperation enum order: + STAT, READ, LIST, CREATE, WRITE, RENAME, DELETE, EXECUTE +``` + +A zero read or write ceiling denies that byte-producing operation even when +the operation token is present. `SNAPSHOT_INPUT` grants may not contain a +write operation; `DECLARED_OUTPUT` grants may not contain `EXECUTE`. + +`ExecutableAuthority` is exactly one of: + +```text +DENY: + mode = DENY + grants = Set + +ALLOWLIST: + mode = ALLOWLIST + grants = Set + +ExecutableGrant: + executable_id: UpperId(EX) + executable_version: Version + executable_sigil: Sigil + runtime_id: UpperId(RT) + runtime_version: Version + runtime_sigil: Sigil + entrypoint: RelativePath + argument_schema_id: SchemaId + argument_schema_sigil: Sigil + maximum_starts: PositiveU63 + side_effect_id: UpperId(SE) +``` + +`ProcessAuthority` is exactly one of: + +```text +DENY: + mode = DENY + maximum_processes = 0 + maximum_tree_depth = 0 + child_executable_ids = Set + process_group_signals = false + dynamic_loading = false + +ALLOWLIST: + mode = ALLOWLIST + maximum_processes: PositiveU63 + maximum_tree_depth: PositiveU63 + child_executable_ids: Set + process_group_signals: boolean + dynamic_loading: boolean +``` + +The initial executable is authorized by `ExecutableAuthority`; every child +executable must also occur in `child_executable_ids`. `dynamic_loading` never +authorizes a library whose Blob Sigil is not part of the selected runtime +identity. + +`NetworkAuthority` is exactly one of: + +```text +DENY: + mode = DENY + dns_mode = DENY + destinations = Set + maximum_connections = 0 + maximum_requests = 0 + maximum_egress_bytes = 0 + +ALLOWLIST: + mode = ALLOWLIST + dns_mode = PINNED_ANSWER + destinations = Set + maximum_connections: PositiveU63 + maximum_requests: PositiveU63 + maximum_egress_bytes: PositiveU63 + +NetworkDestination: + destination_id: UpperId(ND) + protocol: TCP, UDP, HTTP, or HTTPS + host: HostName + ports: Set + address_policy_sigil: Sigil + redirect_policy: DENY or SAME_DESTINATION + side_effect_id: UpperId(SE) +``` + +`PINNED_ANSWER` requires the selected address-policy Sigil to bind every +resolved address before launch. Redirects never add a host, port, protocol, +or address. + +`EnvironmentAuthority` is exactly one of: + +```text +DENY: + mode = DENY + inheritance = NONE + variables = Set + +ALLOWLIST: + mode = ALLOWLIST + inheritance = NONE + variables = Set + +EnvironmentVariable: + name: EnvironmentName + source_kind: CONSTANT, SNAPSHOT_METADATA, RUNTIME_METADATA, + or CREDENTIAL_HANDLE + source_binding_id: Token + source_sigil: Sigil + required: boolean +``` + +An environment rule authorizes only the exact content or opaque source bound +by `source_sigil`; the contract never carries a secret value. Ambient +inheritance, path-derived variables, and unlisted variables are denied. + +`CredentialAuthority` is exactly one of: + +```text +DENY: + mode = DENY + ambient_credentials = false + value_exposure = NEVER + handles = Set + +OPAQUE_HANDLES: + mode = OPAQUE_HANDLES + ambient_credentials = false + value_exposure = NEVER + handles = Set + +CredentialHandle: + handle_id: UpperId(CH) + credential_class: Token + scope_sigil: Sigil + maximum_uses: PositiveU63 + side_effect_id: UpperId(SE) +``` + +Credentials are opaque handles supplied by a separately trusted broker. +Neither branch permits a raw credential, inherited token, credential path, or +Worker-readable secret value. + +The resource and lifecycle aliases are closed records: + +```text +AttemptBudget: + cpu_time_seconds: U63 + peak_memory_bytes: U63 + storage_bytes_written: U63 + output_bytes: U63 + log_bytes: U63 + process_starts: U63 + network_egress_bytes: U63 + network_requests: U63 + +JobBudget: + attempts: PositiveU63 + cpu_time_seconds: U63 + storage_bytes_written: U63 + output_bytes: U63 + log_bytes: U63 + process_starts: U63 + network_egress_bytes: U63 + network_requests: U63 + +ResourceAuthority: + attempt_budget: AttemptBudget + job_budget: JobBudget + maximum_open_files: U63 + maximum_file_count: U63 + maximum_concurrent_threads: U63 + +DeadlineAuthority: + lease_duration_seconds: PositiveU63 + heartbeat_interval_seconds: PositiveU63 + heartbeat_timeout_seconds: PositiveU63 + lease_claim_timeout_seconds: PositiveU63 + attempt_wall_time_seconds: PositiveU63 + cancellation_grace_seconds: U63 + job_wall_time_seconds: PositiveU63 + clock_uncertainty_tolerance_seconds: U63 + +RetryAuthority: + maximum_attempts: PositiveU63 + allowed_retryable_terminal_reasons: + Set + allowed_backoff_kinds: + Set + maximum_backoff_base_seconds: U63 + maximum_backoff_cap_seconds: U63 + allowed_resume_policies: + Set + +BackoffKind enum order: + NONE, FIXED, EXPONENTIAL + +ResumePolicy enum order: + FRESH_ONLY, ALLOW_IMMUTABLE_RESUME + +RetryableTerminalReason enum order: + ATTEMPT_DEADLINE, HEARTBEAT_TIMEOUT, LEASE_CLAIM_EXPIRED, + LEASE_ACTIVE_EXPIRED, LEASE_REVOKED, RECOVERY_FENCE, CLOCK_UNCERTAIN, + EXECUTOR_EPOCH_CHANGED, SESSION_CHANNEL_LOST, PREFLIGHT_REJECTED, + START_FAILED, COMPUTATION_FAILED, VALIDATION_FAILED, RESULT_CONFLICT, + RESULT_REQUIREMENT_FAILED, OUTPUT_VALIDATION_FAILED, + TERMINAL_SOURCE_RETENTION_FAILED, TERMINATION_FAILED, CLEANUP_FAILED, + LOST_OWNERSHIP, ASSURANCE_UNMET, ASSURANCE_UNVERIFIABLE, + BUDGET_EXHAUSTED, ATTEMPT_REJECTED, FATAL_INFRASTRUCTURE + +LoggingAuthority: + stdout_maximum_bytes: U63 + stderr_maximum_bytes: U63 + structured_maximum_bytes: U63 + aggregate_maximum_bytes: U63 + chunk_maximum_bytes: U63 + allowed_overflow_behaviors: + Set + +OverflowBehavior enum order: + TRUNCATE, TERMINATE + +ResultAuthority: + allowed_worker_result_modes: + Set + +WorkerResultMode enum order: + REQUIRED, OPTIONAL, FORBIDDEN + +PostTerminalAuthority: + allowed_modes: + Set + retention_policies: + Set + terminal_source_maximum_files: U63 + terminal_source_maximum_bytes: U63 + retention_duration_seconds: U63 + source_identity_profiles: + Set + +PostTerminalMode enum order: + NONE, CODE_MODIFICATION + +SourceIdentityProfile enum order: + BENCHWORK_SOURCE_TREE_V1 + +RetentionPolicyBinding: + retention_policy_id: StoragePolicyId + retention_policy_version: Version, constant "1.0" + retention_policy_sigil: Sigil + +LifecycleAuthority: + deadlines: DeadlineAuthority + retry: RetryAuthority + logging: LoggingAuthority + result: ResultAuthority + post_terminal: PostTerminalAuthority +``` + +`allowed_worker_result_modes` has exactly one of the four canonical values +`[REQUIRED]`, `[OPTIONAL]`, `[FORBIDDEN]`, or +`[REQUIRED, FORBIDDEN]`. `OPTIONAL` semantically admits all three resolved +modes; `REQUIRED` admits only `REQUIRED`; `FORBIDDEN` admits only `FORBIDDEN`; +and `[REQUIRED, FORBIDDEN]` admits either strict branch but not `OPTIONAL`. +Any array that combines `OPTIONAL` with another token is non-canonical and +invalid. A Task result set narrows a Capability result set exactly when its +admitted resolved-mode set is a subset. A Specification selects one member of +the intersection of the Capability and Task admitted sets, not necessarily a +token printed literally in both arrays. + +A `RetentionPolicyBinding` is an exact import of one RFC-0013 +`artifact-retention-policy/1.0` record. Its `retention_policy_id` equals that +record's `policy_id`, `retention_policy_sigil` equals its `record_sigil`, and +`retention_policy_version` is the importing contract-version constant `1.0`; +it is not an independent version field on the RFC-0013 record. Resolution of a +missing record, a non-`SP-` ID, a version other than `1.0`, or a mismatched +record Sigil fails closed. + +`retention_duration_seconds` is a maximum lifetime for the execution-owned +Storage hold that protects retained terminal source. It is not a minimum +physical-retention duration, does not rewrite the imported RFC-0013 policy's +absolute `retain_until`, and cannot extend any policy selected by that record. +RFC-0012 derives one absolute `release_due_at` by checked UTC addition of the +trusted parent Job terminal Event's `recorded_at` and the resolved duration. +Worker time, backend time, filesystem metadata, and retrieval time are never +inputs. An unrepresentable addition fails closed and cannot saturate, wrap, or +be interpreted as an indefinite hold. + +Once that execution root is inactive, `release_due_at` is its immutable +scheduled release point; restart and Recovery may complete the same release +but may not choose or persist a later deadline. A coordinator reconciles the +hold at the first trusted opportunity at which RFC-0012 and RFC-0013 can prove +the complete ownership prefix and due predicate. The hold may remain active +after the scheduled point only while trusted time, replay, or ownership proof +is unavailable or ambiguous; that fail-closed overdue state is not a deadline +extension and no policy may deliberately schedule release later. This releases +neither an RFC-0013 canonical-reference pin nor a legal, preservation, or other +policy hold and does not itself authorize deletion. Those protections are +additive and may retain the bytes after the execution hold expires. + +`JobBudget.attempts` equals `RetryAuthority.maximum_attempts`. Every aggregate +Job ceiling is at least the corresponding Attempt ceiling. The heartbeat +interval is less than the heartbeat timeout, the timeout does not exceed the +lease duration, claim timeout does not exceed lease duration, and lease and +Attempt wall time do not exceed Job wall time. Logging aggregate bytes do not +exceed the sum of the three stream ceilings, and chunk bytes do not exceed the +aggregate ceiling. When maximum attempts is greater than one, the retryable +reason set is non-empty. If only `NONE` backoff is allowed, both backoff maxima +are zero; otherwise the base maximum is positive and the cap maximum is no +smaller. If `CODE_MODIFICATION` is absent, the retention-policy and +source-profile sets are empty and all three post-terminal numeric ceilings +are zero. If it is present, those sets and ceilings are non-empty or positive +as their element types require. + +Those user-selectable post-terminal retention policies are distinct from the +mandatory execution-root operational hold policy. Every non-empty RFC-0012 +`execution-storage-root-manifest/1.0`, including `JOB_INPUT`, +`ATTEMPT_INPUT`, and a `NONE`-mode `ATTEMPT_OUTPUT`, uses the exact installed +RFC-0013 policy whose ID is `SP-EXECUTION-ROOT-HOLD-V1`. That policy is not a +member of either authority's selectable retention-policy set and grants no +new execution or post-terminal derivation authority. RFC-0012 fixes its +resolved policy Sigil in the preallocated protection plan, and RFC-0013 fixes +the complete installed record and hold authorization. Missing, changed, or +project-mismatched resolution blocks root planning. + +The resolved RFC-0012 retry branch keeps all fields present. `NONE` selects +zero base and cap; `FIXED` selects a positive base and an equal cap; and +`EXPONENTIAL` selects a positive base and a cap no smaller than the base. The +selected values remain within both authorities' maxima. The resolved resume +policy is exactly `FRESH_ONLY` or `ALLOW_IMMUTABLE_RESUME` and occurs in both +allowed sets. + +Input and output authority use these aliases: + +```text +InputAuthority is exactly one of: + DENY: + mode = DENY + maximum_inputs = 0 + maximum_aggregate_bytes = 0 + allowed_object_types = + Set + allowed_media_types = Set + require_immutable_identity = true + require_content_sigil = true + ALLOWLIST: + mode = ALLOWLIST + maximum_inputs: PositiveU63 + maximum_aggregate_bytes: PositiveU63 + allowed_object_types: + Set + allowed_media_types: + Set + require_immutable_identity = true + require_content_sigil = true + +SnapshotObjectType enum order: + research-program, evidence, claim, hypothesis, protocol, working, + experiment, run, result-bundle, assessment, decision, artifact, issue, + deviation, reproduction-record, review-request, review-artifact + +SnapshotObjectId prefix by object type: + research-program = RP, evidence = EV, claim = CL, hypothesis = HY, + protocol = PT, working = WK, experiment = EX, run = RUN, + result-bundle = RB, assessment = AS, decision = DE, artifact = AR, + issue = IS, deviation = DV, reproduction-record = RR, + review-request = RV, review-artifact = RV + Each branch is UpperId(the printed prefix), selected by object_type. + +PathRule is exactly one of: + FIXED: + kind = FIXED + relative_path: RelativePath + PREFIX: + kind = PREFIX + relative_prefix: RelativePath + maximum_depth: U63 + +OutputGrant: + logical_name: LogicalName + schema_id: SchemaId + schema_sigil: Sigil + maximum_bytes: PositiveU63 + maximum_count: PositiveU63 + path_rule: PathRule + side_effect_id: UpperId(SE) + +OutputAuthority is exactly one of: + DENY: + mode = DENY + maximum_outputs = 0 + maximum_aggregate_bytes = 0 + grants = Set + ALLOWLIST: + mode = ALLOWLIST + maximum_outputs: PositiveU63 + maximum_aggregate_bytes: PositiveU63 + grants = Set +``` + +In the `ALLOWLIST` branch, `maximum_outputs` is no greater than the sum of +grant `maximum_count` values, and `maximum_aggregate_bytes` is no greater than +the resource output ceilings. Each output path must also be covered by a +writable `DECLARED_OUTPUT` filesystem grant. A fixed path narrows an equal +fixed path or a parent prefix exactly when its segment delta is within the +parent maximum depth. A child prefix narrows a parent prefix only under the +checked `delta + d_child <= d_parent` rule above. A prefix never narrows a +fixed path. + +Runtime, side-effect, approval, and assurance aliases are: + +```text +IdentityBinding: + identity_id: Token + identity_version: Version + identity_sigil: Sigil + +ConstraintBinding: + constraint_id: Token + constraint_sigil: Sigil + +IdentityRequirements: + require_host_identity: boolean + require_backend_configuration_identity: boolean + require_runtime_content_identity: boolean + +RuntimeAuthority: + runtime_identities: + Set + backend_identities: + Set + host_constraints: + Set + identity_requirements: IdentityRequirements + +SideEffectGrant: + side_effect_id: UpperId(SE) + kind: READ_ONLY, IDEMPOTENT_WRITE, or SINGLE_USE_EXTERNAL + authority_sigil: Sigil + maximum_invocations: PositiveU63 + retry_mode: NO_RETRY, SAME_IDEMPOTENCY_KEY, + or NEW_AUTHORIZATION_EACH_ATTEMPT + idempotency_scope_sigil: Sigil or null + requires_approval: boolean + +SideEffectAuthority: + grants: Set + +ApprovalAuthority is exactly one of: + NOT_REQUIRED: + requirement = NOT_REQUIRED + policy_id = null + policy_version = null + policy_sigil = null + binding_scope = NONE + REQUIRED: + requirement = REQUIRED + policy_id: UpperId(AP) + policy_version: Version + policy_sigil: Sigil + binding_scope = EXACT_EXECUTION_POLICY + +ControlRequirement: + control_dimension: ControlDimension + required_state: ANY_RECORDED, OBSERVED, VERIFIED, or ENFORCED + required_evidence_kinds: + Set + requirement_evidence_profile_sigil: Sigil + +AssuranceGrant: + requested_level: SANCTUM-A0, SANCTUM-A1, or SANCTUM-A2 + profile_version: Version + profile_sigil: Sigil + conformance_suite_id: UpperId(CS) + conformance_suite_sigil: Sigil + control_requirements: + Set + +AssuranceAuthority: + grants: + Set +``` + +`idempotency_scope_sigil` is non-null exactly for +`SAME_IDEMPOTENCY_KEY` and null for the other retry modes. +`SINGLE_USE_EXTERNAL` requires `maximum_invocations` equal to one and may use +only `NO_RETRY` or `NEW_AUTHORIZATION_EACH_ATTEMPT`. In a Capability Contract, +a grant with `requires_approval: true` is valid only when the Capability +approval branch is `REQUIRED`. A Task or resolved Specification may narrow +`false` to `true` only when its own approval branch is `REQUIRED`, the exact +side-effect ID is covered by that approval, and the policy tuple satisfies the +rules below. Every `side_effect_id` referenced by a tool, executable, +destination, credential handle, or output grant exists exactly once in +`SideEffectAuthority`. + +The exact `ControlDimension` order is: + +```text +IDENTITY_AUTHORIZATION +FILESYSTEM +NETWORK +PROCESS_EXECUTABLE +RESOURCE +ENVIRONMENT_CREDENTIAL +LOG_OUTPUT_CAPTURE +RUNTIME_INPUT_OUTPUT_IDENTITY +CANCELLATION_FENCING +TERMINATION_CLEANUP +``` + +The exact `EvidenceKind` order is: + +```text +TASK_BINDING +CAPABILITY_BINDING +SNAPSHOT_BINDING +WARD_DECISION +APPROVAL_RECEIPT +BACKEND_CONFIGURATION +HOST_IDENTITY +POLICY_RESOLUTION +BASE_IDENTITY +INPUT_IDENTITY +MATERIALIZATION_IDENTITY +ENVIRONMENT_CONSTRUCTION +FILESYSTEM_POLICY +NETWORK_POLICY +EXECUTABLE_SELECTION +PROCESS_TREE +WALL_TIME_ENFORCEMENT +RESOURCE_ACCOUNTING +CREDENTIAL_NONINHERITANCE +LOG_CAPTURE +OUTPUT_VALIDATION +STORAGE_OBSERVATION +FENCE_TOMBSTONE +TERMINATION +HANDLE_REVOCATION +CLEANUP +QUARANTINE +TERMINAL_SOURCE_VERIFICATION +CONFORMANCE_FIXTURE +``` + +The ten control requirements occur exactly once and in the printed order. +Their requested state and evidence profile may be stronger than the selected +assurance profile's minimum but never weaker. Requirement satisfaction is not +a total ordering: `ANY_RECORDED` accepts `UNAVAILABLE`, `OBSERVED`, +`VERIFIED`, or `ENFORCED`; `OBSERVED` accepts `OBSERVED`, `VERIFIED`, or +`ENFORCED`; `VERIFIED` accepts only `VERIFIED`; and `ENFORCED` accepts only +`ENFORCED`. + +### `capability-contract/2.0` + +The top-level Capability Contract has exactly these required members: + +| Field | Exact type or value | +| --- | --- | +| `schema_version` | Constant `capability-contract/2.0`. | +| `capability_id` | `CapabilityId`. | +| `contract_version` | Constant `2.0`. | +| `display_name` | `HumanText(128)`. | +| `purpose` | `HumanText(4096)`. | +| `tools` | `ToolAuthority`. | +| `filesystem` | `FilesystemAuthority`. | +| `executable` | `ExecutableAuthority`. | +| `process` | `ProcessAuthority`. | +| `network` | `NetworkAuthority`. | +| `environment` | `EnvironmentAuthority`. | +| `credential` | `CredentialAuthority`. | +| `resource` | `ResourceAuthority`. | +| `input` | `InputAuthority`. | +| `output` | `OutputAuthority`. | +| `runtime` | `RuntimeAuthority`. | +| `lifecycle` | `LifecycleAuthority`. | +| `side_effects` | `SideEffectAuthority`. | +| `assurance` | `AssuranceAuthority`. | +| `approval` | `ApprovalAuthority`. | +| `issued_at` | `Timestamp`. | +| `expires_at` | `Timestamp` or null. | +| `contract_sigil` | Self-Sigil defined above. | + +`expires_at`, when non-null, is later than `issued_at`. IDs used by a grant +are unique across that grant type. Every executable names an allowed runtime; +all process child IDs name allowed executable grants. Every credential-backed +environment source names an allowed credential handle. Denied facilities +have their exact `DENY` branch and zero bounds; a missing facility is invalid, +not denied. + +Cross-dimension bounds are checked in both a Capability and a Task Circle. +Executable starts do not exceed the process-start budgets; network requests +and egress do not exceed their Attempt and Job counters; output and log +aggregates do not exceed their matching counters; and bounded filesystem +writes fit the storage-written counters. For each Side Effect, the sum of +maximum invocations through its referencing grants does not exceed the Side +Effect maximum. A zero resource counter therefore denies the corresponding +facility even if a separate allowlist accidentally contains a grant. + +### `capability-registry/2.0` + +The top-level Capability Registry has exactly these required members: + +| Field | Exact type or value | +| --- | --- | +| `schema_version` | Constant `capability-registry/2.0`. | +| `registry_id` | `UpperId(CR)`. | +| `registry_revision` | `U63`. | +| `created_at` | `Timestamp`. | +| `supersedes_registry_sigil` | `Sigil` or null. | +| `capabilities` | Map of 1 through 4,096 `CapabilityId` keys to complete `capability-contract/2.0` values. | +| `registry_sigil` | Self-Sigil defined above. | + +At revision zero, `supersedes_registry_sigil` is null. At every later +revision it is non-null and equals the Registry Sigil at revision minus one; +the creation time does not move backward. Revisions are contiguous. Map keys +are sorted and unique by `CapabilityId`; a duplicate JSON key fails before +Schema validation. Each value is the complete v2 Contract, including its +valid `contract_sigil`, and the map key is byte-for-byte equal to that value's +`capability_id`. A v1 contract, abbreviated binding, `$ref` object, null +value, alias, or key/value mismatch is invalid. A Capability ID occurs once +per revision and denotes the same capability lineage across revisions; +changing its Contract keeps the ID but changes the complete value and Sigils +in a new revision. + +### `task-capsule/2.0` + +Task-specific aliases are: + +```text +RegistryBinding: + registry_id: UpperId(CR) + registry_revision: U63 + registry_sigil: Sigil + +CapabilityBinding: + capability_id: CapabilityId + contract_version = 2.0 + contract_sigil: Sigil + +SnapshotBinding: + snapshot_id: UpperId(SS) + snapshot_sigil: Sigil + +VisibleInput: + logical_name: LogicalName + object_id: SnapshotObjectId matched to object_type + object_type: SnapshotObjectType + object_sigil: Sigil + media_type: MediaType + maximum_bytes: PositiveU63 + mount_path: RelativePath + +ExpectedOutput: + logical_name: LogicalName + schema_id: SchemaId + schema_sigil: Sigil + maximum_bytes: PositiveU63 + maximum_count: PositiveU63 + path_rule: PathRule + side_effect_id: UpperId(SE) + +Circle: + circle_id: UpperId(CI) + tools: ToolAuthority + filesystem: FilesystemAuthority + executable: ExecutableAuthority + process: ProcessAuthority + network: NetworkAuthority + environment: EnvironmentAuthority + credential: CredentialAuthority + resource: ResourceAuthority + input: InputAuthority + output: OutputAuthority + runtime: RuntimeAuthority + lifecycle: LifecycleAuthority + side_effects: SideEffectAuthority + circle_sigil: Sigil + +RequestedAssurance: + requested_level: SANCTUM-A0, SANCTUM-A1, or SANCTUM-A2 + profile_version: Version + profile_sigil: Sigil + conformance_suite_id: UpperId(CS) + conformance_suite_sigil: Sigil + control_requirements: + Set + +ApprovalBinding is exactly one of: + NOT_REQUIRED: + requirement = NOT_REQUIRED + policy_id = null + policy_version = null + policy_sigil = null + binding_scope = NONE + bound_side_effect_ids = Set + REQUIRED: + requirement = REQUIRED + policy_id: UpperId(AP) + policy_version: Version + policy_sigil: Sigil + binding_scope = EXACT_EXECUTION_POLICY + bound_side_effect_ids: + Set +``` + +The top-level Task Capsule has exactly these required members: + +| Field | Exact type or value | +| --- | --- | +| `schema_version` | Constant `task-capsule/2.0`. | +| `task_id` | `UpperId(TK)`. | +| `host` | Exactly `cli`, `codex`, or `claude-code`. | +| `program_id` | `UpperId(RP)`. | +| `objective` | `HumanText(16384)`. | +| `registry_binding` | `RegistryBinding`. | +| `capability_binding` | `CapabilityBinding`. | +| `snapshot_binding` | `SnapshotBinding`. | +| `visible_inputs` | `Set`. | +| `expected_outputs` | `Set`. | +| `circle` | `Circle`. | +| `requested_assurance` | `RequestedAssurance`. | +| `approval_binding` | `ApprovalBinding`. | +| `created_at` | `Timestamp`. | +| `expires_at` | `Timestamp` or null. | +| `capsule_sigil` | Self-Sigil defined above. | + +The Registry binding resolves one immutable v2 Registry. The Capability +binding resolves exactly the complete value at its matching map key and +repeats that value's version and Sigil. The Snapshot resolves one immutable +`research-snapshot/1.0` whose `program_id` equals the Task `program_id`. +Every visible input occurs in that Snapshot with byte-for-byte equal object +ID, type, and Sigil; logical names and mount paths are separately unique. +Visible-input counts, aggregate byte maxima, types, and media types satisfy +both Capability and Circle input authorities. No repository file, +conversation turn, ambient directory, or unpinned research object is an +input. + +`created_at` is no earlier than the bound Registry creation time, Capability +issue time, or Snapshot creation time. The Capability and Task are unexpired +at Ward authorization; expiration after launch is handled only by the closed +RFC-0012 deadline and cancellation rules and never extends authority. + +Every expected output is byte-for-byte equal to its unique-key Circle output +grant. The matching Capability grant has the same logical name, Schema ID, +Schema Sigil, and side-effect ID; its path rule is equal to or broader than +the Task path rule under the fixed/prefix and checked-depth rules, and its +count and byte maxima are equal or higher. Logical names and fixed paths are +unique. Their aggregate maxima fit both output and resource ceilings. The +Circle output grant array and `expected_outputs` are byte-for-byte equal; the +duplicated location makes the declared output set directly inspectable but +cannot create a second authority. The Execution Specification resolves +`output_contracts` from this exact ordered array and may retain an explicit +subset, narrow a path rule, or lower its bounds; no undeclared output becomes +a Proposal. + +At Specification resolution, `REQUIRED` requires a non-empty resolved +`output_contracts` array, `FORBIDDEN` requires an empty array and a resolved +output `DENY` branch, and `OPTIONAL` permits either. The Task may advertise +more than one allowed result mode, but the Specification selects exactly one +and applies this relation without inventing an output. + +The Circle contains every execution dimension even when it denies that +dimension. Ward validates its `circle_sigil` before comparing authority; the +Capsule self-Sigil covers that already validated Circle including its +`circle_sigil`. It is invalid if any member is broader than the pinned +Capability. Its side-effect set contains exactly the grants referenced by its +selected tools, executables, destinations, credential handles, and outputs. +The requested assurance tuple and all ten control requirements equal one +Capability assurance grant. `approval_binding.bound_side_effect_ids` is +exactly the sorted Circle side-effect IDs whose grants require approval. If +that set is non-empty or the Capability requires approval, the Task branch is +`REQUIRED` and its approval-policy tuple is byte-for-byte equal to the +Capability tuple. A Task may upgrade a `NOT_REQUIRED` Capability to a +`REQUIRED` Task only with a closed approval-policy tuple recognized by Ward's +versioned policy set; an unknown tuple fails closed. It cannot downgrade or +substitute a Capability-required policy. + +An approval Receipt is deliberately not embedded in the Capsule. The Task's +`approval_binding` is the immutable requirement; RFC-0012 `authorization` +carries the resulting Receipt ID and Sigil. `expires_at`, when non-null, is +later than `created_at`; an expired Task is well-formed but ineligible for a +new Ward authorization or launch. + +For a `REQUIRED` branch, Ward first constructs the complete final +`execution-specification/1.0` candidate with two deliberate omissions: +`specification_sigil` is absent, and `authorization.approval_receipt_id` plus +`authorization.approval_receipt_sigil` are absent. The authorization object +still contains its exact Ward-decision fields and +`approval_requirement: REQUIRED`; every other final member and value is +already fixed. `preapproval_specification_sigil` is SHA-256 over that +canonical candidate. This digest preimage is not a Schema instance and is +never persisted or launched. Omitting any additional field, using a +placeholder, or changing a field after this digest is invalid. + +The closed approval aliases are: + +```text +ApprovalAssuranceBinding: + requested_level: SANCTUM-A0, SANCTUM-A1, or SANCTUM-A2 + profile_version: Version + profile_sigil: Sigil + conformance_suite_id: UpperId(CS) + conformance_suite_sigil: Sigil + +ExecutionApprovalSubject: + schema_version = execution-approval-subject/1.0 + approval_subject_id: UpperId(EA) + specification_id: RFC-0012 Specification ID + preapproval_specification_sigil: Sigil + registry_binding: RegistryBinding + task_id: UpperId(TK) + task_capsule_sigil: Sigil + capability_binding: CapabilityBinding + snapshot_binding: SnapshotBinding + circle_id: UpperId(CI) + circle_sigil: Sigil + ward_decision_id: WardDecisionId + ward_decision_sigil: Sigil + approval_policy_id: UpperId(AP) + approval_policy_version: Version + approval_policy_sigil: Sigil + assurance_requirement: ApprovalAssuranceBinding + resolved_permission_set_sigil: Sigil + approval_subject_sigil: Sigil + +ExecutionApprovalTransitionRequest: + schema_version = execution-approval-transition-request/1.0 + transition_request_id: EAT-ID + event_type = execution.approval.granted + purpose = EXECUTION_APPROVAL + expected_chronicle_head: RFC-0013 ChronicleHeadRef + approval_subject: ExecutionApprovalSubject + actor: RFC-0015 ActorBinding + host_invocation: RFC-0015 HostInvocationBinding + chronicle_actor: exact RFC-0001 actor/1.0 + idempotency_key_sigil: Sigil + requested_at: Timestamp + transition_request_sigil: Sigil + +ExecutionApprovalGrantedPayload: + transition_request_id: EAT-ID + transition_request_sigil: Sigil + purpose = EXECUTION_APPROVAL + approval_subject_id: UpperId(EA) + approval_subject_sigil: Sigil + actor: RFC-0015 ActorBinding + occurred_at: Timestamp +``` + +`resolved_permission_set_sigil` is SHA-256 over one closed canonical object +containing the candidate's exact `policies`, `output_contracts`, +`result_requirement`, `runtime_constraints`, `assurance_requirement`, +`deadline_policy`, `attempt_budget`, `job_budget`, `retry_policy`, +`logging_policy`, `post_terminal_derivation`, and +`conformance_policy_version` members. `approval_subject_sigil` is the +self-Sigil over the complete subject with only that member omitted. + +`approval_subject_id` is content-derived and exactly 67 ASCII bytes: + +```text +approval_subject_id = + "EA-" + UPPER_HEX(SHA256(canonical_json( + ["execution-approval-subject-id/1.0", + specification_id, + preapproval_specification_sigil, + registry_binding, + task_id, + task_capsule_sigil, + capability_binding, + snapshot_binding, + circle_id, + circle_sigil, + ward_decision_id, + ward_decision_sigil, + approval_policy_id, + approval_policy_version, + approval_policy_sigil, + assurance_requirement, + resolved_permission_set_sigil]))) +``` + +The subject resolver is immutable and single-assignment by that derived +`approval_subject_id`. It rederives the ID before storage or use; the same ID +cannot resolve to different bytes or a different `approval_subject_sigil`. +Changing any semantic member therefore requires a different subject ID, while +presenting changed bytes under an existing ID is an integrity conflict. + +The raw approval idempotency key accepted at the authenticated request boundary +is an NFC-normalized UTF-8 string of 1 through 256 bytes containing no NUL or +Unicode control character. It is never persisted in the transition request. +Its exact durable projection is: + +```text +idempotency_key_sigil = + Sigil(["execution-approval-idempotency-key/1.0", + raw_approval_idempotency_key]) +``` + +`EAT-ID` is exactly `EAT-` followed by the uppercase 64-hex SHA-256 digest of +canonical JSON: + +```text +["execution-approval-transition-request-id/1.0", + approval_subject.task_id, + idempotency_key_sigil] +``` + +The request's self-Sigil covers every other request member. Its +`approval_subject` is the complete subject above, and the expected Head is the +exact Phase 3-admissible RFC-0013 `ChronicleHeadRef`. The authenticated caller +fixes the bounded idempotency key, actor, Host invocation, Chronicle actor, +request time, and expected Head before the request becomes durable. The +`chronicle_actor` is the complete canonical RFC-0001 `actor/1.0` +`{actor_id, actor_type, host, authenticated_by}`. It has no independent +self-Sigil but is covered by `transition_request_sigil`. +`actor.authentication_context_sigil == +host_invocation.authentication_context_sigil`; both binding self-Sigils +validate; and all three records are produced byte-for-byte from the same +authenticated invocation context rather than caller text. +`chronicle_actor.actor_id == actor.actor_id`; the closed kind mapping is +`USER -> human`, `AGENT -> agent`, and `SYSTEM -> policy|tool`. The Chronicle +`host` and `authenticated_by` values equal that context's canonical audit +Host and authentication mechanism and are not inferred from a Host-identity +Sigil, invocation ID, or caller text. The immutable resolver is +single-assignment by `transition_request_id`, and the unique operation scope is +`(EXECUTION_APPROVAL, approval_subject.task_id, idempotency_key_sigil)`: an +exact retry reuses the same bytes and any changed subject, actor, invocation, +Chronicle actor, Head, time, purpose, or key is an idempotency conflict rather +than a new interpretation of the old request. In particular, a caller cannot +escape that conflict by presenting a different derived `EA-ID`. + +The successful canonical Event is an exact `chronicle-event/1.1` whose `type` +is `execution.approval.granted`, whose `object_id` equals +`approval_subject_id`, and whose payload is exactly +`ExecutionApprovalGrantedPayload`. Its payload resolves the exact transition +request by ID and Sigil and requires the same purpose, subject ID and Sigil, +actor, and request time byte-for-byte; `occurred_at == requested_at` and the +outer Event's `occurred_at` is the same value. The outer Event's complete +`actor` is byte-for-byte the request's `chronicle_actor`; its payload +`actor` remains the RFC-0015 `ActorBinding`. The transition commits only +against `expected_chronicle_head`. A changed or missing request, an Event-body +Sigil mismatch, a different subject, purpose, payload Actor, outer Chronicle +Actor, or an Event at another Head fails closed. + +The paired `receipt/1.1` is resolved by exact Receipt ID and Sigil. It binds the +canonical Event only through `event_id` and `event_body_sigil`, and the ordinary +RFC-0001 equalities for `previous_receipt_sigil` and `accepted_at` must also +hold. The Receipt Schema contains no purpose, subject ID, subject Sigil, +transition-request ID, or final Specification Sigil. Purpose and subject are +proved only by the verified chain: + +```text +receipt/1.1 + -> chronicle-event/1.1 execution.approval.granted + -> execution-approval-transition-request/1.0 + -> execution-approval-subject/1.0 +``` + +After that complete chain validates, Ward inserts only the exact Receipt ID and +Sigil, recomputes the pre-approval digest from the now-final object using the +three-field omission rule, requires equality with the resolved request +subject's `preapproval_specification_sigil`, and then computes +`specification_sigil` over every other final member. Thus the final +Specification binds the Receipt, the Receipt binds the Event, and the Event +resolves the request and pre-approval subject without a digest cycle. A +different candidate, subject, request, Event, Receipt, or final binding +requires a fresh subject, request, and canonical approval. + +### Capability, Task, and Execution Specification comparison + +Ward performs this algorithm before it signs an RFC-0012 Execution +Specification. It is deterministic and fail-closed: + +1. Validate the Registry, every embedded Contract, their self-Sigils, + ordering, revision linkage, and timestamp relations. Resolve the Task's + exact Registry binding; no newer Registry may be substituted. +2. Validate the Circle and Task Capsule self-Sigils. Resolve the Capability + map key and require exact Capability ID, version, and Contract Sigil. + Resolve the Snapshot and every visible object by exact ID, type, and Sigil. +3. Compare every authority-bearing Circle member to the matching Capability + member. `circle_id` and `circle_sigil` are immutable identity, not authority + values and have no Capability counterpart. A broader Task is invalid; Ward + does not silently trim it. After this check, the Capability intersection + with the Task is exactly the Task authority. +4. Resolve every RFC-0012 field from that intersection. The Specification may + make a further explicit narrowing, but every changed value participates in + a new pre-approval subject when approval is required and in the final + `specification_sigil`. +5. Reject an absent, unknown, duplicate, unordered, null, unresolved, or + unmappable authority. There is no default, ambient fallback, or + best-effort policy. + +The comparison primitives are exact. An allowed set may only become a subset +under its stated semantic relation. A numeric permission or budget may only +decrease. There is no global boolean ordering: a boolean not named below must +remain exact. `process_group_signals` and `dynamic_loading` may change only +from `true` to `false`; environment `required`, each +`IdentityRequirements` member, and side-effect `requires_approval` may change +only from `false` to `true`; the two Input identity requirements remain +constant `true`; and derived `external_side_effects_retry_safe` is recomputed +rather than compared as an authority bit. `DENY` dominates every allow branch, +and a Task or Specification may not replace a Capability denial with a +non-empty set or non-zero ceiling. An identity, version, Schema ID, and Sigil +must match byte-for-byte; a semantically similar object or newer version is +not a substitute. Null never grants authority. + +The field-by-field resolution into `execution-specification/1.0` is: + +| Capability and Task source | RFC-0012 destination and exact narrowing rule | +| --- | --- | +| Resolver-owned envelope | `schema_version` is constant `execution-specification/1.0`; `specification_id` is a new Execution Specification ID; `created_at` is a normalized creation time; `conformance_policy_version` is constant `sanctum-authority-intersection/1.0`; and `specification_sigil` covers every other RFC-0012 member. None conveys execution authority. | +| Task identity and self-Sigil | `task_binding`; exact Task ID and Capsule Sigil. | +| Capability identity, version, and self-Sigil | `capability_binding`; exact tuple from the bound Registry value. | +| Snapshot identity and Sigil | `snapshot_binding`; exact tuple. | +| Ward and Task approval requirement | `authorization`; exact Ward decision binding, exact requirement, and Receipt fields present only for `REQUIRED`. The Ward decision binds the Registry tuple, complete resolved permission set, assurance tuple, and approval-policy tuple. A required Receipt resolves its exact `execution.approval.granted` Event, transition request, and `ExecutionApprovalSubject`; that subject's recomputed pre-approval digest matches the final Specification after omitting exactly `specification_sigil`, `approval_receipt_id`, and `approval_receipt_sigil`. | +| Filesystem Circle | `policies.filesystem`; grants are matched by unique grant ID, root kind is exact, operation sets are subsets, byte and entry ceilings do not increase, and a descendant prefix is valid only when checked arithmetic proves `delta + child.maximum_depth <= parent.maximum_depth`. | +| Tool and executable Circle | `policies.executable`; each resolved rule carries the exact tool or executable IDs, versions, Sigils, runtime tuple, argument-Schema tuple, entrypoint, and side-effect ID. Sets and invocation or start ceilings only decrease. No tool authority remains only in an objective string. | +| Process Circle | `policies.process`; `DENY` dominates, child executable IDs are a subset, numeric ceilings decrease, and either boolean can change only from true to false. | +| Network Circle | `policies.network`; `DENY` dominates; destination ID, protocol, host, address-policy Sigil, redirect policy, and side-effect ID remain exact; the destination set is a subset and each selected destination's port set is an explicit subset of its matching parent port set; all three ceilings decrease. | +| Environment Circle | `policies.environment`; inheritance remains `NONE`; variable names are a subset and source kind, binding ID, and source Sigil are exact. `required` may change only from false to true. | +| Credential Circle | `policies.credential`; ambient credentials remain false and exposure remains `NEVER`; handle IDs are a subset, class, scope Sigil, and side-effect ID are exact, and use ceilings decrease. | +| Resource Circle | `policies.resource`, `attempt_budget`, and `job_budget`; every counter is no greater than both parents, a denied facility has zero, and `job_budget.attempts` exactly equals resolved retry `max_attempts`. Open-file, file-count, and thread rules remain in the closed resource policy. | +| Visible inputs and input Circle | `policies.input`; each input has the exact Snapshot object identity and Sigil, mount path, media type, and equal-or-lower byte ceiling. Count and aggregate ceilings decrease. Immutable identity and content-Sigil requirements remain true. | +| Expected outputs and output Circle | `policies.output` and `output_contracts`; the ordered output array is exact or an explicit subset, identities and side-effect IDs are exact, count and byte ceilings decrease, and each path rule is equal or narrower. | +| Runtime Circle | `runtime_constraints`; runtime and backend identities and Host constraints are subsets with exact versions and Sigils. A true identity requirement cannot become false. | +| Lifecycle deadlines | `deadline_policy`; each of the eight integer values is no greater than both Capability and Task maxima and retains all deadline cross-field relations. | +| Lifecycle result rule | `result_requirement`; `successful_worker_outcome` is constant `COMPLETED`. The selected mode must occur in the intersection of the two admitted-mode expansions defined above; `REQUIRED` and `FORBIDDEN` are each narrower than `OPTIONAL` but are incomparable with one another. | +| Lifecycle retry rule | `retry_policy`; attempts and backoff values do not increase, terminal-reason and enum choices are subsets, and the selected backoff and resume values obey their closed RFC-0012 branches. | +| Lifecycle logging rule | `logging_policy`; all five byte ceilings decrease, aggregate is no greater than their sum, and overflow behavior is selected from both allowed sets. | +| Lifecycle post-terminal rule | `post_terminal_derivation`; `NONE` grants nothing. `CODE_MODIFICATION` is valid only when allowed by both parents, uses an exact immutable Base identity and Sigil from a visible Snapshot input, selects an exact permitted retention-policy tuple and source profile, and decreases all file, byte, and execution-owned-hold lifetime ceilings. The selected duration is never an RFC-0013 minimum physical-retention duration. | +| Requested assurance | `assurance_requirement`; the five-member profile and suite tuple is copied exactly. The selected profile entry must reproduce the Task's ten ordered control requirements and the installed suite must cover them; no level, profile, suite, Sigil, state, or evidence-profile substitution is allowed. | +| Side-effect Circle | The relevant resolved tool, executable, network, credential, and output rules retain exact side-effect IDs, kinds, authority Sigils, retry modes, approval bits, and conditional idempotency-scope Sigils. Grants form a unique-key subset and invocation ceilings decrease. `NO_RETRY` is narrower than either retrying mode; `SAME_IDEMPOTENCY_KEY` and `NEW_AUTHORIZATION_EACH_ATTEMPT` may only remain exact and are incomparable; `requires_approval` may change only from false to true. The latter retry mode derives the exact immutable `AttemptAuthorizationRequirement` carried by every RFC-0012 Attempt. No side-effect authority may be left without an RFC-0012 policy or Attempt carrier. | + +Every RFC-0012 resolved policy has exactly `policy_id`, `policy_version`, +`policy_sigil`, and the policy-specific closed rules above. Its Sigil covers +that complete resolved object except the Sigil member, using the same +canonical encoding and self-Sigil algorithm as these contracts. Policy IDs are +`UpperId(POL)` and policy versions are `Version`; together they identify the +deterministic resolver and rule format but never replace the field +comparison. A policy with an unknown rule, omitted denial, unbound side-effect +reference, or rule not representable by RFC-0012 is rejected rather than +approximated. + +For retry, `external_side_effects_retry_safe` is true exactly when at least +one selected non-`READ_ONLY` effect exists, every such effect uses +`SAME_IDEMPOTENCY_KEY`, the Specification binds the exact non-null +idempotency-scope Sigil into each carrying policy, and its invocation and Job +ceilings permit the selected attempt count. It is false when there is no +external write or when any selected effect uses `NO_RETRY` or +`NEW_AUTHORIZATION_EACH_ATTEMPT`. A selected non-`READ_ONLY` `NO_RETRY` +effect requires resolved `max_attempts` equal to one. A selected +`NEW_AUTHORIZATION_EACH_ATTEMPT` effect permits a greater value only through +the per-Attempt binding below. `SINGLE_USE_EXTERNAL` is never made retry-safe +by a boolean. + +### Per-Attempt authorization carrier requirement + +`NEW_AUTHORIZATION_EACH_ATTEMPT` requires a new, immutable authorization after +each Attempt binding exists and before that Attempt can preflight, acquire a +Lease, launch, or receive any side-effect handle. This requirement applies to +the first Attempt as well as every retry. It is distinct from the +Specification-level approval Receipt and never changes the immutable +Specification. + +The RFC-0011-side source contract for the RFC-0012 carrier is: + +```text +AttemptAuthorizationEffect: + side_effect_id: UpperId(SE) + authority_sigil: Sigil + +AttemptAuthorizationRequirement is exactly one of: + NONE: + kind = NONE + effects = Set + REQUIRED: + kind = REQUIRED + effects = + Set + +AttemptAuthorizationSubject: + schema_version = attempt-authorization-subject/1.0 + authorization_subject_id: UpperId(AA) + job_id: RFC-0012 Job ID + job_binding_sigil: Sigil + attempt_id: RFC-0012 Attempt ID + attempt_binding_sigil: Sigil + retry_ordinal: PositiveU63 + specification_id: RFC-0012 Specification ID + specification_sigil: Sigil + effects: + Set + authorization_subject_sigil: Sigil + +AttemptAuthorizationBinding: + authorization_subject_id: UpperId(AA) + authorization_subject_sigil: Sigil + authorization_transition_request_id: RFC-0012 AAT-ID + authorization_transition_request_sigil: Sigil + authorization_event_id: Chronicle Event ID + authorization_event_body_sigil: Sigil + authorization_receipt_id: UpperId(RC) + authorization_receipt_sigil: Sigil + authorization_binding_sigil: Sigil + +AttemptAuthorizationState is exactly one of: + NONE: + kind = NONE + PENDING: + kind = PENDING + BOUND: + kind = BOUND + authorization_subject: AttemptAuthorizationSubject + attempt_authorization_binding: AttemptAuthorizationBinding +``` + +The subject and binding are closed objects. Their respective self-Sigils omit +only `authorization_subject_sigil` and `authorization_binding_sigil`. +`AttemptAuthorizationRequirement.effects` is derived exactly from the +resolved carrying policies: it contains every and only selected side effect +whose retry mode is `NEW_AUTHORIZATION_EACH_ATTEMPT`, with the exact +`authority_sigil`. A non-empty derived set selects `REQUIRED`; an empty set +selects `NONE`. + +RFC-0012 must carry the requirement as an immutable required member of +`execution-attempt/1.0`, covered by `attempt_binding_sigil`. +`job.attempt_allocated` initializes the replayed authorization state to +`PENDING` for `REQUIRED` and `NONE` otherwise. A new +`attempt.authorization_bound` event is lifecycle-state-neutral, advances the +owning Attempt revision, changes only the orthogonal authorization projection +`PENDING -> BOUND`, and has exact payload members `authorization_subject` and +`attempt_authorization_binding`; the event and projection retain both complete +closed objects. The binding's subject ID and Sigil equal the embedded subject +byte-for-byte. No other event may assign or replace that binding. + +The authorization Receipt resolves by exact ID and Sigil and binds only its +`authorization_event_id` and `authorization_event_body_sigil` through the +standard `receipt/1.1` fields. That canonical Event must have type +`attempt.authorization.granted` and must resolve the exact RFC-0012 +`attempt-authorization-transition-request/1.0` by the two request fields in +the binding. The request carries purpose `ATTEMPT_AUTHORIZATION` and resolves +the complete embedded subject. A Receipt itself contains neither that purpose +nor any subject field. The subject's Job, Attempt, retry ordinal, and +Specification ID/Sigil must be byte-for-byte equal to the allocated Attempt +and its immutable parents. Its effect set must equal both the immutable +Attempt requirement and the selected carrying-policy projection. Each +authority named by `authority_sigil` must validate the same request and subject +chain before the operational `attempt.authorization_bound` event can commit. + +An authorization-subject ID or Sigil, transition-request ID or Sigil, +canonical Event ID or body Sigil, Receipt ID or Sigil, or +authorization-binding Sigil used by one Attempt is ineligible for every other +Attempt, including another Attempt of the same Job. An Attempt authorization +Receipt is also distinct from and may not reuse the Specification-level +approval Receipt. A subject, request, Event, or Receipt may commit at most +once, and `BOUND` is single-assignment. Retry therefore requires fresh subject, +request, Event, and Receipt identities after the new Attempt binding exists; +copying, rebinding, widening, or replaying a predecessor's authorization fails +closed. +A missing or invalid binding permits only the closed rejection or stop path +and never preflight, Lease authority, launch, or handle issuance. An RFC-0012 +implementation that lacks any field, state, or event above must reject +`NEW_AUTHORIZATION_EACH_ATTEMPT` rather than treating the +Specification-level approval as its substitute. + +If approval is `REQUIRED`, RFC-0012 `authorization` contains both Receipt +fields, and Ward resolves the exact Receipt -> `execution.approval.granted` +Event -> transition request -> `ExecutionApprovalSubject` chain. The subject +binds the Registry, Capability, Task, Snapshot, Circle, Ward decision, +assurance profile and suite tuple, approval-policy tuple, complete resolved +permission set, and `preapproval_specification_sigil`; it deliberately does +not bind the final `specification_sigil`. If approval is `NOT_REQUIRED`, both +Receipt fields are absent and no approval subject, request, or Event is +inferred. A narrower Specification changes the pre-approval digest and never +reuses the old subject, request, Event, or Receipt. + +## Terminology + +| Term | Meaning in Phase 3 | +| --- | --- | +| **Task Capsule** | Immutable control-plane statement of one objective, pinned Capability, Research Snapshot, expected outputs, and declared Circle. Executor launch requires the Phase 3 v2 contract. | +| **Execution Specification** | Immutable, versioned, identity-bound resolution of one Task into enforceable runtime requirements. It can narrow but never broaden the authorized Task. | +| **Sanctum** | Logical per-Attempt Agent context containing only the objective, visible inputs, tools, budget, policy, and output contract granted to that Attempt. | +| **Circle** | Per-Task permission and context boundary. Its declaration is policy input; its realized controls and evidence determine the actual assurance claim. | +| **Ward** | Control-plane policy authority that validates the Task and Execution Specification before launch and rechecks applicable postconditions before result acceptance. | +| **Executor** | Coordinator that persists operational state, selects an eligible backend and Worker, issues Leases, supervises Attempts, and transports bounded results. | +| **Job** | Operational execution request bound to exactly one Task Capsule and Execution Specification. A Job is not a scientific Run. | +| **Attempt** | One execution try for a Job. Retry creates a new Attempt identity and never rewrites the preceding Attempt. | +| **Lease** | Time-bounded, fenced authority for one Worker to act on one Attempt. Detailed semantics belong to RFC-0012. | +| **Worker** | Untrusted or partially trusted runtime subject that performs one Attempt. It has no canonical authority. | +| **Crucible** | Mutable workspace materialized for an Attempt. It is an execution resource, not a canonical Artifact and not the Sanctum itself. | +| **Proposal** | Bounded output that may be submitted to Athanor. Process exit code, Job completion, or output existence does not imply acceptance. | +| **Scientific Run** | Immutable terminal research observation accepted under the Run contract. It is distinct from Job and Attempt lifecycle state. | + +A retry receives a fresh Sanctum instance. A Crucible is materialized fresh by +default. A1 or higher may resume only from an immutable base plus an explicit +resume content identity after recovery validation proves that the preceding +process tree and handles are fenced, the materialization is intact, and the +new Attempt has exclusive access. An implementation may not infer continuity +or exclusivity from a path. + +## Ownership and authority + +| Component | Owns | Must not | +| --- | --- | --- | +| Researcher or interactive Host | Task intent, requested operation, required human confirmations | Infer that approval or execution success is a scientific Seal | +| MCP control surface | Typed requests that submit an already resolved Job to the Executor, observe or cancel it, and submit bounded results | Interpret or run Worker commands, or expose a universal shell, filesystem, Git, web, or arbitrary-execution escape hatch | +| Ward | Contract comparison, approval binding, policy resolution, launch decision, applicable postcondition checks | Execute Worker code or grant authority beyond the pinned contracts | +| Executor | Job, Attempt, Lease, Worker, backend, cancellation, timeout, logs, and recovery state | Append Chronicle events, edit canonical projections, or manufacture missing provenance | +| Enforcement backend | Realize and report the controls supported by its declared assurance profile | Self-upgrade an assurance claim because it uses a named technology such as a container | +| Worker | Perform the bounded Attempt and return declared outputs | Change its policy, Lease, audit record, assurance claim, or canonical state | +| Athanor | Validate Proposals, enforce scientific Gates, append accepted events, issue Receipts | Treat Executor success as sufficient scientific evidence | +| Chronicle | Preserve accepted research events and reconstruct canonical research state | Act as the queue or heartbeat store for live execution | + +The trusted computing base for canonical integrity contains Athanor, Chronicle +verification, Schema validation, and Ward. The operational trusted computing +base for A2 assurance additionally contains the Executor coordinator, +execution journal implementation, enforcement backend, Host kernel, and +assurance-evidence verifier. Compromise of any of those components can +invalidate the operational claim. Worker code, Task inputs, repository content, +Provider output, generated patches, and mutable Crucibles are untrusted. + +This is an authority boundary, not a new cryptographic defense for local +storage. The current unsigned Chronicle does not protect against a malicious +same-user process that can rewrite the ledger, Head, and Receipts together. +At A2, the Worker cannot reach that storage; protecting it from a compromised +Executor, control plane, or Host administrator remains outside this RFC. + +Human approval authorizes a particular bound operation. It does not make its +code, data, Worker, Provider, or outputs trustworthy. + +## Operational and canonical state + +Phase 3 uses two distinct authorities: + +- Chronicle is the source of canonical research state. +- A durable execution journal is the source of operational Job state. + +The execution journal is authoritative for scheduling and recovery but has no +scientific authority. Its format, locking, and replay rules are defined by +RFC-0012. + +| State | Classification | Transition authority | +| --- | --- | --- | +| Research Program, Evidence, Claim, Protocol, Experiment, scientific Run, Assessment, Decision, registered Artifact | canonical research state | Athanor and Chronicle | +| Human approval Receipt | canonical authorization evidence | Athanor and Chronicle | +| Task Capsule, Snapshot, and Execution Specification | immutable pinned control records | Their versioned control-plane stores and validators | +| Job, Attempt, Lease, Worker, queue position, heartbeat, cancellation request, exit status, resource sample | durable operational state | Executor | +| Crucible files, temporary outputs, stdout, and stderr | mutable or captured execution material | Enforcement backend and Executor | +| Agent Result, execution result, patch, candidate Run, candidate Artifact | Proposal | Worker or Executor until Athanor accepts a defined transition | + +Job scheduling, heartbeat, retry, cancellation, timeout, and terminal events do +not enter Chronicle merely because they occurred. An accepted Proposal may +carry Job and Attempt identities and Sigils as provenance. Logs or outputs +become canonical Artifacts only through an explicit Artifact registration and +Receipt. + +Operational records are never overwritten to hide failure. Failed, cancelled, +expired, fenced, policy-violating, and lost Attempts remain in the execution +journal. Retrying appends a new Attempt. A retention or archival operation must +be explicit, auditable, and preserve terminal summaries and content identities; +the Phase 3 reference runtime retains all terminal records by default. + +A terminal Job does not create or update a scientific Run. If execution +produces a candidate Run, Athanor separately validates its Program, Protocol, +Experiment, phase, terminal status, metrics, Artifacts, and analysis +disposition. This preserves failed and negative scientific Runs without +conflating them with infrastructure failure. + +## Authorization and execution boundary + +Before every Attempt, the control and execution planes must: + +1. load and verify the exact Task Capsule, Capsule Sigil, Snapshot, Capability + Contract, and Execution Specification; +2. require Ward to return `PASS` for the complete bound policy and require any + approval Receipt to match that policy; +3. verify Snapshot integrity and freshness at launch; +4. select a backend whose verified profile meets the requested minimum + assurance and every required control; +5. materialize only declared inputs into a fresh or explicitly resumed + Crucible; +6. record the backend identity, policy Sigil, input identities, requested + assurance, planned control profile, and preflight eligibility before Worker + code starts; and +7. acquire a valid fenced Lease for the Attempt. + +An unsupported control, unknown policy field, failed preflight, missing +approval, stale Snapshot, or unmet assurance requirement rejects launch. +Benchwork does not silently relax policy, downgrade assurance, or ask the +Worker to enforce its own boundary. + +During execution: + +- policy enforcement and audit state remain outside the Worker; +- the Execution Specification never grants direct `.benchwork/` access; +- credentials and unrelated Host environment variables are absent by default; +- the Executor bounds runtime, captured output, and result transport; +- cancellation and Lease expiry fence later Worker output and begin resource + revocation; and +- every detected control violation is recorded. + +At A0 and A1, these rules limit what Benchwork grants but do not claim +containment against a hostile Worker that discovers ambient Host paths. At A2 +or higher, the backend must make the entire `.benchwork/` tree unreachable. +Required research inputs are exported as explicitly selected, immutable, +Sigil-bound materializations rather than mounting canonical or operational +control-plane storage. Other undeclared Host paths and credentials are also +unreachable. + +At A2 or higher, result fencing alone is insufficient. Cancellation, Lease +loss, or policy revocation must terminate the complete process tree and revoke +the Attempt's filesystem, network, credential, and output handles. A mutable +sink that cannot revoke a handle must validate the current fencing token on +every side-effecting operation. A replacement Attempt cannot receive the same +mutable Crucible, output namespace, or external sink until the preceding +Attempt is confirmed terminated or the resource is quarantined. + +At A2 or higher, a violation of an isolation, resource, credential, or +side-effect boundary terminates the complete process tree, revokes its handles, +and makes the result ineligible. A non-security output-format or result-Schema +failure may invalidate the result without an additional kill after Worker +execution has already ended. + +On completion, the Executor records terminal evidence before offering a +bounded Proposal. Only after terminal state, process-tree termination, cleanup, +and required postconditions are verified may the evidence verifier issue an +immutable realized assurance claim. Athanor then revalidates the Capsule and +Capability Sigils, Execution Specification Sigil, Snapshot freshness, expected +output Schemas, Blob Sigils, Ward decision, Job and Attempt identity, terminal +Lease eligibility, realized-claim Sigil, assurance profile and conformance +suite, realized level against the requested minimum, and runtime provenance. A +stale, duplicate, late, fenced, malformed, under-assured, or policy-violating +result is preserved as operational evidence but rejected for scientific +acceptance. + +## Assurance model + +`sanctum-assurance-profile/1.0` defines the Phase 3 assurance vocabulary. +Before launch, an Attempt records `requested_assurance` and a preflight +eligibility decision. Neither is an assurance claim. A realized assurance claim +may be issued only after terminal and cleanup evidence exists. It is +per-Attempt, not a product-wide label, and binds the profile version and Sigil, +level, backend identity and version, Host platform, policy and +backend-configuration Sigils, conformance-suite identity and Sigil, and +retained evidence Sigil. An incomplete, crashed, or unverifiable Attempt has no +realized claim at the requested level; Benchwork records the missing evidence +and does not silently downgrade it. Unknown profiles, versions, or suite +identities fail closed. The levels are cumulative minimums: + +| Level | Name | Minimum meaning | +| --- | --- | --- | +| `SANCTUM-A0` | Declared Attempt | A Phase 3 Job and Attempt exist and Ward validated their pinned policy, but the Attempt has no execution-plane enforcement claim. Phase 2 native-Host activity is outside this assurance model, not A0, because it has no Attempt evidence. | +| `SANCTUM-A1` | Supervised | An exclusive Crucible materialized from a pinned fresh or validated resume identity, constructed allowlisted environment, tracked process group, supervisor-enforced wall-time and cancellation, bounded log and output capture, exact runtime/input/output identities, and durable terminal and cleanup evidence are all present. The workload is assumed cooperative. This level is not a security sandbox. | +| `SANCTUM-A2` | Isolated | A1 plus enforcement outside the Worker for deny-by-default filesystem scope, network policy, process containment, resource ceilings, secret non-inheritance, and controlled output paths. It is intended to contain untrusted non-privileged user-space code under a stated Host threat model. | +| `SANCTUM-A3` | Reserved: Attested | Reserved for a later remote-trust RFC. Phase 3 defines no claimable A3 profile or conformance path. | + +Every A1 requirement in the table is mandatory. A Host that cannot enforce +wall time and cancellation for the tracked cooperative process group, construct +the required environment, bound capture, or retain the required identities and +terminal evidence may claim only A0. Implementations cannot interpret +"supervised" as "whatever this Host happens to support." + +Each Attempt also records a control matrix for at least: + +- filesystem reads and writes; +- network ingress and egress; +- process creation and executable selection; +- CPU, memory, storage, and wall time; +- environment variables, credentials, and secret material; +- log and output capture; +- runtime and input identity; and +- cancellation, fencing, and cleanup. + +Each dimension reports `ENFORCED`, `VERIFIED`, `OBSERVED`, or `UNAVAILABLE`, +with backend-specific evidence. For A1 or higher, a level may be claimed only +when its required policy controls are `ENFORCED` and its identity or evidence +requirements are `VERIFIED`. A0 has no enforcement minimum; it requires +verified Task, policy, Job, Attempt, and Ward identities and reports execution +controls at their actual `ENFORCED`, `VERIFIED`, `OBSERVED`, or `UNAVAILABLE` +state. Partial enforcement remains visible but cannot aggregate into A1 unless +every A1 requirement is satisfied. `OBSERVED` is diagnostic evidence, not +policy enforcement. Missing or unknown evidence fails the requested claim +closed. + +Subprocesses, virtual environments, containers, worktrees, namespaces, virtual +machines, and remote sandboxes are mechanisms, not assurance levels. No +mechanism earns a level by name. The backend must pass the conformance suite in +the exact supported configuration. + +The `0.4` local reference runtime must provide at least `SANCTUM-A1` and label +it experimental. It may claim `SANCTUM-A2` only on a declared backend and Host +combination that passes the A2 adversarial conformance profile. An A1-only +runtime must be described as supervised execution, not isolated or sandboxed +execution. It may not claim A3. Production-grade isolation remains a Phase 4 +gate. + +## Threat model + +### Assets + +The model protects: + +- Chronicle, Receipts, Seals, projections, and other `.benchwork/` state; +- the exact Task, policy, inputs, runtime identity, outputs, and provenance; +- Host files, credentials, processes, and network access outside the Circle; +- completeness of terminal Job and Attempt history, including failures; and +- the distinction between operational evidence, Proposals, and accepted + scientific state. + +### Adversaries and failures + +The design treats repository content, input data, generated code, Provider +output, Plugin content, and Worker code as potentially malformed or malicious. +A0 and A1 do not claim to contain a malicious Worker; A2 is the first level +whose threat model includes hostile non-privileged user-space code. A1 +conformance therefore tests a cooperative workload, while A2 conformance uses +adversarial workloads. The model also covers non-malicious crashes, duplicate +delivery, out-of-order messages, stale snapshots, lost heartbeats, expired +Leases, partial writes, disk exhaustion, process trees that outlive a parent, +and Host restart. + +Required adversarial cases include: + +- path traversal, absolute paths, symlink or hard-link escape, and special + files; +- writes to `.benchwork/`, undeclared paths, or immutable inputs; +- undeclared network egress, loopback access, and inherited proxy settings; +- credential or Host-environment inheritance; +- fork, subprocess, daemon, and orphan-process escape; +- CPU, memory, storage, time, log, file-count, and output-size exhaustion; +- forged backend identity, policy evidence, logs, exit status, or output + Sigils; +- Lease replay, split-brain Workers, late results, and duplicate completion; +- stale Capsule, Capability, Snapshot, Crucible base, or patch identity; and +- attempts to turn Job success directly into a Run, Artifact, Assessment, + Decision, or Seal. + +### Trust limits + +`SANCTUM-A2` does not claim protection from a compromised kernel, hypervisor, +Benchwork control plane, Executor coordinator, execution journal, +assurance-evidence verifier, enforcement backend, malicious same-user local +writer, or Host administrator. A3 does not define confidential computing or +protection from a malicious remote operator; those claims require a separate +RFC and evidence model. + +Availability against an administrator, physical attacker, or infrastructure +operator is outside Phase 3. Scientific validity is also outside the sandbox +claim: isolation can preserve execution boundaries and provenance, but it +cannot establish that a method, measurement, or interpretation is correct. + +## Failure and recovery principles + +- Every Job and Attempt has a unique identity. Retry never reuses an Attempt + identity. +- Only the current unexpired fenced Lease may produce an eligible result. +- At A2 or higher, Lease loss, cancellation, and policy revocation also revoke + side-effecting handles and terminate the complete process tree. Fencing only + the returned result is insufficient. +- A replacement Attempt cannot share a mutable resource with an unfenced + predecessor. The predecessor must be confirmed terminated, or the resource + must be quarantined and rematerialized under a new identity. +- Cancellation, timeout, policy violation, and Worker loss are distinct + terminal evidence and are not rewritten as generic failure. +- Executor restart reconstructs Job state from the durable execution journal, + not from conversation, process names, or Crucible directory names. +- Ambiguous ownership after restart fences the old Worker before another + Attempt can become eligible. +- Output publication is atomic with respect to its recorded content identity. + Partial outputs remain quarantined and ineligible. +- Cleanup failure is retained as a policy or infrastructure failure even when + Worker computation succeeded. +- Loss or corruption of required provenance prevents assurance and result + acceptance; Benchwork never reconstructs it from best guesses. + +RFC-0012 defines the exact states, fencing tokens, heartbeat and renewal +rules, terminal precedence, and crash-recovery algorithm. + +## Invariants + +- Athanor remains the only authority for canonical transitions. +- Chronicle remains the source of canonical research state. +- An Executor, Worker, backend, Job, Attempt, or successful process has no + scientific authority. +- Job, Attempt, and Lease state remains separate from immutable scientific Run + state. +- A Ward `PASS` is authorization, not proof of enforcement. +- A realized assurance claim is issued only after terminal and cleanup + verification and binds one Attempt, a versioned profile and + conformance-suite Sigil, and retained enforcement evidence. +- A Phase 3 execution policy may narrow but never broaden its pinned Task and + Capability contracts. +- Existing versioned Phase 2 identifiers keep their accepted meanings. +- Human confirmation remains mandatory for Research Question, Protocol, and + Decision Seals. +- Failed and negative operational and scientific outcomes are preserved in + their respective records. +- MCP remains typed and bounded and gains no general execution escape hatch. +- The Worker cannot access `.benchwork/`, or write its policy, Lease, or audit + record, at A2 or higher. +- Unknown fields, controls, backends, evidence formats, and assurance versions + fail closed. + +## Compatibility and migration + +This RFC extends RFC-0007's reserved Executor boundary and preserves RFC-0002, +RFC-0005, and RFC-0008. It amends RFC-0009 only by allowing MCP to submit, +observe, and cancel a typed Job through the future RFC-0015 API. MCP still does +not interpret or run a command, edit a repository, perform Git or web actions, +or invoke a Provider. + +No Phase 2 Schema is changed in place. In particular: + +- `task-capsule/1.1.circle.tools`, `network`, and + `time_budget_seconds` retain their declarative meanings; +- `agent-result/1.1` remains a proposal contract and receives no inferred + execution authority; +- `run/1.1` and `run/1.2` retain terminal scientific statuses only; +- `artifact/1.0` remains a logical canonical record rather than a physical + Blob or Replica contract; and +- current Codex and Claude Code native-tool workflows remain valid but are + outside the Sanctum assurance model unless a Phase 3 Job and Attempt wrap the + activity. + +Phase 3 adds independent versioned execution Schemas, including +`capability-registry/2.0`, `capability-contract/2.0`, and +`task-capsule/2.0`. Registry v1.1 never stores a v2 Contract. Importing a +Capability into Registry v2 is an explicit validation and registration +operation that creates a new Contract Sigil; it does not mutate the v1 +Registry or carry approvals forward. + +Existing v1 Task Capsules are never launched by the Executor. A researcher may +explicitly create a new v2 Task from the same objective and Snapshot, but +permissions absent from v1 must be selected and approved explicitly. There is +no automatic or lossy Task migration, and no migration from Host conversation +history or native tool activity into Job or Attempt records. + +An accepted Phase 3 result may require a new Agent Result or provenance +version. It must not encode Job semantics into an existing field with a new +meaning. Any breaking Alpha change requires an accepted RFC, migration +guidance, and replay or contract coverage. + +## Relationship to later RFCs + +- RFC-0012 defines Job, Attempt, Lease, Worker, logs, terminal states, retry, + fencing, and recovery. +- RFC-0013 defines logical Artifact, Blob, Replica, transfer integrity, + retention, and storage backend boundaries. +- RFC-0014 defines Crucible base identity, Patch Proposal, validation, + conflict handling, and explicit human promotion. +- RFC-0015 defines typed start, observe, cancel, and result operations over the + accepted execution contracts. + +Those RFCs may refine implementation details but may not weaken this RFC's +ownership, authority, state-separation, or assurance invariants without +explicitly superseding it. + +## Security and integrity + +Execution metadata, policies, inputs, outputs, logs, and backend evidence are +untrusted until their Schemas, identities, bounds, and relationships validate. +Policy and result transport must be length-bounded and reject ambiguous +encoding, unknown fields, path escapes, and identity mismatches. + +The control plane passes capabilities, not ambient Host authority. Worker +processes receive a constructed environment and explicit handles rather than +the launching Host's complete environment. A1 removes ambient credentials and +unrelated environment variables from the launched process but does not claim +hostile-code containment. At A2 or higher, the backend enforces that the Host +home directory, credential stores, ambient Host IPC or control sockets, +inherited Agent sockets, and the complete `.benchwork/` tree are absent. +Network sockets created by the backend may exist only as allowed by the bound +network policy. Selected research state is copied into the Sanctum only through +a bounded export that records its Schema and Sigil. + +At A2 or higher, enforcement occurs outside the Worker and remains effective +for its complete process tree. Preflight success alone is insufficient: +postconditions must verify termination, output boundaries, and cleanup. Any +detected escape or unverifiable cleanup prevents a realized claim at the +requested level and invalidates result eligibility. + +Secrets are denied by default. Phase 3 does not define a secret broker. +Redacting a secret after capture is not equivalent to preventing disclosure. + +## Alternatives + +- **Put execution inside Athanor.** Rejected because mutable, fallible runtime + coordination would enlarge the canonical transition authority and couple + Chronicle integrity to process control. +- **Treat Job as an in-progress Run.** Rejected because queue and retry state + are operational, while a Run is an immutable terminal scientific + observation. +- **Add a generic MCP shell or filesystem tool.** Rejected because it erases + typed permission boundaries and creates a universal escape hatch. +- **Execute `task-capsule/1.1` directly.** Rejected because its accepted Circle + is declarative and cannot express the enforcement, identity, resource, and + evidence requirements of Phase 3. Phase 3 uses explicit v2 Capability and + Task contracts instead. +- **Equate a container or worktree with isolation.** Rejected because a + mechanism name says nothing about mounts, network, credentials, resources, + process containment, or the tested threat model. +- **Write every heartbeat and retry into Chronicle.** Rejected because + operational churn is not canonical scientific state and would make replay + depend on runtime scheduling. +- **Automatically register successful outputs.** Rejected because execution + completion cannot replace Athanor validation or human scientific authority. + +## Non-goals + +- automatic Provider invocation or model routing; +- remote Workers, cluster scheduling, Slurm, Kubernetes, or broad GPU support; +- production-grade isolation or a claim against kernel or administrator + compromise; +- physical Artifact storage, replication, or garbage collection; +- Patch application, merge, or promotion; +- a secret-management or credential-brokering system; +- automatic creation of Runs, Artifacts, Assessments, Decisions, or Seals; +- changing Phase 2 Host symmetry or native-tool workflows; and +- defining the detailed protocols reserved for RFC-0012 through RFC-0015. + +## Acceptance tests + +Acceptance of the Phase 3 execution model requires the five executable +Schemas owned by this RFC plus the dependent Schemas, examples, threat-model +review, and conformance tests supplied by RFC-0012 through RFC-0015. The +combined suite must demonstrate: + +1. existing Phase 2 fixtures retain their accepted meanings and no v1 Schema + is reinterpreted; +2. Registry v1.1 rejects v2 Contracts, v1 Task Capsules are ineligible for + Executor launch, Registry v2 rejects abbreviated or v1 values, duplicate or + unordered Capability IDs, key/ID mismatches, invalid embedded or self + Sigils, and every fixture proves field-by-field that an Execution + Specification cannot broaden its v2 Task, Capability, approval, Snapshot, + expected outputs, or any Circle dimension; +3. Phase 2 approvals never authorize v2 Tasks, and any change to an approved + Execution Specification, assurance-profile ID, version or Sigil, or + permitted conformance-suite identity or Sigil requires a new exact-match + approval; +4. launch records requested assurance and preflight eligibility but no realized + claim; a realized claim appears only after terminal and cleanup evidence is + immutable; +5. unknown controls, assurance profiles, profile or conformance-suite Sigils, + unmet assurance, missing evidence, and implicit downgrades fail before + launch or prevent the requested realized claim; +6. A0 is claimed only for a recorded Phase 3 Attempt, every A1 mandatory + control passes cooperative conformance, and A1 cannot claim A2; +7. fresh and resumed A1 Crucibles are exclusive, content-identified, and + recovery-validated before launch; +8. each A2 backend makes `.benchwork/` unreachable and denies path escape, + undeclared network access, Host credential inheritance, process escape, and + declared resource overruns; +9. at A2, cancellation, Lease loss, policy revocation, and any isolation, + resource, credential, or side-effect violation terminate the process tree, + revoke or fence every side-effecting sink, and prevent a replacement Attempt + from sharing mutable resources with an unfenced predecessor; +10. cancellation, timeout, Lease expiry, duplicate delivery, late results, + stale results, and restart recovery are deterministic and preserve every + Attempt; +11. the Executor may append valid operational transitions but cannot rewrite + or delete prior records; an A2 Worker receives a read-only projected policy + but cannot modify its bound policy or Lease or access their authority + stores, the execution journal, Chronicle, or canonical projections; +12. Job completion leaves Chronicle, Runs, Artifacts, Assessments, Decisions, + and Seals unchanged until an explicit Athanor transition succeeds; +13. accepted Proposals retain Capsule, policy, Job, Attempt, backend, input, + output, assurance-profile, conformance-suite, and evidence provenance; +14. Athanor rejects a missing, mismatched, late, terminally ineligible, or + under-assured realized claim; +15. partial outputs, forged Sigils, corrupt logs, and missing provenance are + quarantined and ineligible for acceptance; +16. A2 cleanup and process-tree termination are verified, while A1 records + cooperative cleanup without claiming hostile containment, and every + cleanup failure is retained; +17. an MCP start request only submits a closed typed Job to the Executor, and + no Phase 3 MCP tool exposes generic command, filesystem, Git, web, or + arbitrary-execution authority; +18. a `REQUIRED` approval fixture constructs the pre-approval candidate, + subject, transition request, canonical `execution.approval.granted` Event, + paired Receipt, and final Specification in that order; the exact + Receipt-to-Event-to-request-to-subject chain and three-field omission + recomputation succeed without a digest cycle, while a changed post-subject + field, wrong Event type or body Sigil, mismatched request, changed subject + bytes under one EA-ID, a changed subject under the same + `(Task-ID, idempotency_key_sigil)` operation scope, reused request ID, + reused Receipt, or any claimed final-Specification binding fails; +19. every boolean-direction fixture follows its field-specific rule: Process + permits only true-to-false, environment `required`, runtime identity + requirements, and side-effect approval permit only false-to-true, Input + identity booleans remain true, and an unlisted boolean must remain exact; +20. every `Set` fixture rejects equal semantic keys even when the full objects + differ, including duplicate tool IDs, environment names, output logical + names, Host constraint IDs, retention tuples, and assurance tuples in a + Task Circle; +21. filesystem and output prefix fixtures accept parent `a` depth one narrowed + to child `a/x` depth zero, reject child `a/x` depth one, reject checked + addition overflow, and apply the same boundary to a fixed output path; +22. `IdentityRequirements` rejects a missing member, an extra member, null, + and an unknown nested property; +23. result-mode fixtures accept Capability `[OPTIONAL]` narrowed to Task + `[REQUIRED]`, `[FORBIDDEN]`, or `[REQUIRED, FORBIDDEN]`, reject Task + `[OPTIONAL]` under Capability `[REQUIRED]`, and reject every array that + combines `OPTIONAL` with another token; +24. a Task fixture validates `circle_sigil` from the Circle preimage with only + that field omitted and then validates `capsule_sigil` with the valid Circle + Sigil retained; omission, placeholder, wrong omission, or a Circle mutation + fails both eligibility and launch; and +25. each Attempt selecting `NEW_AUTHORIZATION_EACH_ATTEMPT` starts with the + exact immutable requirement, cannot preflight while `PENDING`, reaches + `BOUND` through one exact event, and rejects a missing field, wrong Job, + Attempt, ordinal, Specification, effect set, authority Sigil, subject Sigil, + Receipt binding, reused predecessor subject or Receipt, second assignment, + or RFC-0012 implementation lacking the complete carrier; and +26. every post-terminal retention binding resolves one exact RFC-0013 + `artifact-retention-policy/1.0` record by `SP-` ID and `record_sigil`, uses + the importing version constant `1.0`, and rejects the former `POL-` + namespace, any other version, an unresolved policy, or a mismatched Sigil + before Execution Specification or Job creation; +27. both Specification approval and per-Attempt authorization reject a + `receipt/1.1` with any invented purpose or subject member, resolve purpose + and subject only through the exact canonical Event and immutable transition + request, and reject a wrong request ID/Sigil, Event ID/body Sigil, purpose, + subject, payload Actor, outer Chronicle Actor, authentication-context + mapping, or cross-Specification/cross-Attempt reuse; and +28. retention fixtures prove that narrowing + `retention_duration_seconds` can only move the execution-owned hold's + checked absolute `release_due_at` earlier, overflow fails closed, expiry and + crash Recovery release that exact hold without extending its deadline, and + an additive canonical pin or legal, preservation, or stricter RFC-0013 + policy may retain the bytes afterward without keeping the execution hold + active or treating its duration as a physical-retention minimum. + +The `0.4` reference vertical slice must retain evidence for its exact Host and +backend configuration and must state the highest assurance level that the +evidence actually supports. diff --git a/docs/en/rfcs/RFC-0012-job-lease-worker-protocol.md b/docs/en/rfcs/RFC-0012-job-lease-worker-protocol.md new file mode 100644 index 0000000..5cf5364 --- /dev/null +++ b/docs/en/rfcs/RFC-0012-job-lease-worker-protocol.md @@ -0,0 +1,4837 @@ +--- +title: "RFC-0012: Job, Lease, and Worker Protocol" +document_id: BW-RFC-0012 +version: 0.1 +status: draft +owner: unassigned +date: 2026-07-31 +language: en +canonical: true +--- + +# RFC-0012: Job, Lease, and Worker Protocol + +## Status + +This draft defines the Phase 3 operational protocol beneath RFC-0011. It +specifies the closed execution contracts, Job, Attempt, Lease, and Worker state +machines, fencing, liveness, retry, logs, result eligibility, assurance +evaluation, journal replay, and local crash recovery required by the `0.4` +reference runtime. + +This RFC does not make operational state canonical research state, authorize a +generic MCP execution surface, define physical Artifact storage or Patch +promotion, or accept a Worker result scientifically. RFC-0013 through RFC-0015 +remain required for those boundaries. No implementation may claim conformance +until this RFC is accepted and its executable Schemas and conformance fixtures +exist. + +## Problem + +RFC-0011 establishes that an Executor may coordinate a bounded Attempt without +gaining Athanor's canonical authority. That boundary is insufficient unless +all implementations agree on what happens when: + +- two Workers believe they own the same work; +- a Lease expires while a process or external handle remains live; +- a heartbeat or renewal races with cancellation or timeout; +- a Worker sends a duplicate, conflicting, or late result; +- an Attempt fails and a retry begins; +- logs or outputs exceed their bounds; +- computation succeeds but required cleanup or assurance evidence does not; +- the Executor or Host restarts with ambiguous process ownership; or +- an operational journal is truncated, corrupt, or contains an illegal + transition. + +Ad hoc answers can produce split-brain side effects, reuse mutable state across +Attempts, turn a stale result into a Proposal, erase failed Attempts, infer +runtime provenance after a crash, or claim assurance from preflight intent +rather than terminal evidence. + +## Decision + +Phase 3 uses an append-only operational protocol: + +```text +validated execution contracts + | + v + Job + | + v + Attempt allocation -- fresh fence generation + | + v + preflight + Crucible materialization + | + v + Lease offer -> Worker claim -> supervised execution + | | + | +-> bounded heartbeats, logs, result + v + revoke | expire | release | fence + | + v + termination + cleanup + | + v + terminal Attempt -> assurance evaluation + | + v + retry or terminal Job + | + v + eligible execution Proposal, never a canonical transition +``` + +The durable execution journal is the sole source of operational truth. Every +state transition is an append. Job, Attempt, Lease, and Worker projections are +reconstructed from that journal and may be discarded and rebuilt. A Worker, +backend, projection file, process table, Crucible path, queue, or API response +is never authoritative on its own. + +One Job binds exactly one immutable Task Capsule and one immutable Execution +Specification. One Attempt is one try for that Job. Each Attempt receives one +strictly increasing Job-scoped fencing generation and may receive at most one +Lease; a preflight-rejected Attempt receives none. A failed claim, expired +offer, retry, reassignment, or recovery never reuses an Attempt or Lease +identity. + +The `0.4` reference runtime is local and single-coordinator. The journal must +still make concurrent requests, duplicate delivery, Executor restart, and +surviving child processes deterministic. Remote Worker transport and +multi-coordinator consensus are non-goals, not permission to omit fencing. + +## Executable contract set + +The implementation must publish closed JSON Schema Draft 2020-12 documents +with the following exact identifiers and conventional filenames: + +| Contract identifier | Filename | Purpose | +| --- | --- | --- | +| `execution-specification/1.0` | `execution-specification-1.0.json` | Immutable, Sigil-bound resolution of one v2 Task into enforceable runtime, lifecycle, logging, retry, output, and assurance requirements. | +| `execution-job/1.0` | `execution-job-1.0.json` | Immutable Job submission record bound to the Task Capsule and Execution Specification. | +| `execution-attempt/1.0` | `execution-attempt-1.0.json` | Immutable allocation of one retry ordinal, fencing generation, planned backend, and Crucible identity. | +| `attempt-authorization-subject/1.0` | `attempt-authorization-subject-1.0.json` | Deterministic, Attempt-specific approval subject required by `NEW_AUTHORIZATION_EACH_ATTEMPT`. | +| `attempt-authorization-transition-request/1.0` | `attempt-authorization-transition-request-1.0.json` | Closed Athanor request whose canonical Event and Receipt grant one exact Attempt authorization. | +| `execution-worker/1.0` | `execution-worker-1.0.json` | Stable Worker definition and its declared and Executor-verified capability manifest; it is not a live session. | +| `execution-worker-session/1.0` | `execution-worker-session-1.0.json` | One immutable, non-resumable Worker control session bound to one Executor epoch. | +| `execution-lease/1.0` | `execution-lease-1.0.json` | One time-bounded Lease offer bound to a Job, Attempt, Worker definition, Worker Session, Executor epoch, and fencing generation. | +| `execution-heartbeat/1.0` | `execution-heartbeat-1.0.json` | Bounded Worker- or Lease-scoped liveness and resource sample. | +| `execution-log-chunk/1.0` | `execution-log-chunk-1.0.json` | Content-identified, sequence-bound stdout, stderr, or structured-log chunk. | +| `execution-result/1.0` | `execution-result-1.0.json` | Bounded Worker completion message and staged-output manifest; it is not an Agent Result or Receipt. | +| `benchwork-source-tree/1.0` | `benchwork-source-tree-1.0.json` | Canonical retained terminal-source manifest and deterministic bundle identity for `BENCHWORK_SOURCE_TREE_V1`. | +| `execution-storage-root-manifest/1.0` | `execution-storage-root-manifest-1.0.json` | Single-assignment typed Blob/source closure and preallocated protection plan for one execution root. | +| `execution-root-hold-release-authorization/1.0` | `execution-root-hold-release-authorization-1.0.json` | Single-assignment authorization for the only three legal execution-root hold-release bases. | +| `execution-output-storage-observation-set/1.0` | `execution-output-storage-observation-set-1.0.json` | Immutable, resolvable fixed-prefix storage observations for one terminal Attempt's outputs, three Log streams, resource evidence, and terminal source. | +| `execution-control-evidence/1.0` | `execution-control-evidence-1.0.json` | Per-control preflight, runtime, termination, and cleanup evidence retained for one Attempt. | +| `execution-control-evidence-set/1.0` | `execution-control-evidence-set-1.0.json` | Immutable, resolvable closure of the exact ten per-control evidence records used to terminalize one Attempt. | +| `execution-quarantine-binding-set/1.0` | `execution-quarantine-binding-set-1.0.json` | Immutable, resolvable subject-to-Quarantine bindings used by one Attempt's terminal storage observation. | +| `sanctum-assurance-profile/1.0` | `sanctum-assurance-profile-1.0.json` | Versioned definitions of `SANCTUM-A0` through the claimable Phase 3 levels and their required controls. | +| `sanctum-assurance-claim/1.0` | `sanctum-assurance-claim-1.0.json` | Immutable post-terminal realized assurance evaluation for exactly one Attempt. | +| `execution-journal-event/1.0` | `execution-journal-event-1.0.json` | Hash-chained operational event with a closed event type and closed type-specific payload. | +| `execution-journal-head/1.0` | `execution-journal-head-1.0.json` | Replaceable journal-head cache binding the journal ID, last sequence, and last event Sigil. | +| `execution-recovery-action-set/1.0` | `execution-recovery-action-set-1.0.json` | Deterministically derived, immutable ordered recovery intents for one Recovery phase. | +| `execution-state/1.0` | `execution-state-1.0.json` | Replayed Executor epoch plus Job, Attempt, Lease, Worker-definition, Worker-Session, liveness, log, budget, deadline, and recovery projections. | + +These contracts use `$id` values under +`https://benchwork.dev/schemas/`. Every object is closed +with `additionalProperties: false`; identifier-bearing maps also constrain +`propertyNames`. Unknown versions, fields, enum values, event types, assurance +profiles, conformance suites, or evidence kinds fail closed. + +The Schema family depends on the RFC-0011 `capability-registry/2.0`, +`capability-contract/2.0`, and `task-capsule/2.0` contracts. The output +storage-observation and quarantine-binding-set contracts additionally import +the exact RFC-0013 `EventRef`, Blob, Replica, backend-object, verification, +Quarantine, owner-ID, and State-Sigil types; they cannot clone or widen them. +No v1 Capability or Task contract is valid input to +`execution-specification/1.0`. + +### Common identity and encoding rules + +The contracts use these identity classes: + +| Object | Identifier pattern | +| --- | --- | +| Execution Specification | `ES-[A-Z0-9]+` | +| Job | `JB-[A-Z0-9]+` | +| Attempt | `AT-[A-Z0-9]+` | +| Attempt authorization subject | `AA-[A-F0-9]{64}` | +| Attempt authorization transition | `AAT-[A-F0-9]{64}` | +| Lease | `LS-[A-Z0-9]+` | +| Worker | `WK-[A-Z0-9]+` | +| Worker Session | `WS-[A-Z0-9]+` | +| Executor instance | `XI-[A-Z0-9]+` | +| Recovery transaction | `RY-[A-Z0-9]+` | +| Journal | `EJ-[A-Z0-9]+` | +| Journal event | `JE-[A-Z0-9]+` | +| Log stream | `LG-[A-Z0-9]+` | +| Log chunk | `LC-[A-Z0-9]+` | +| Output storage observation set | `OS-[A-F0-9]{64}` | +| Benchwork source tree | `BTS-[A-F0-9]{64}` | +| Execution storage-root manifest | `ESM-[A-F0-9]{64}` | +| Execution-root hold release authorization | `EHR-[A-F0-9]{64}` | +| Control evidence | `CE-[A-Z0-9]+` | +| Control evidence set | `CES-[A-F0-9]{64}` | +| Quarantine binding set | `QBS-[A-F0-9]{64}` | +| Assurance claim | `AC-[A-Z0-9]+` | + +Every non-fixed operational identifier in this table is ASCII and has a total +length, including its prefix, of 3 through 128 bytes; its printed pattern +still requires at least one suffix character. The seven hash-derived `JB-`, +`AA-`, `OS-`, `BTS-`, `ESM-`, `CES-`, and `QBS-` domains are exactly 67 +ASCII bytes, while `AAT-` and `EHR-` are exactly 68 ASCII bytes; none admits +a shortened or extended form. Every occurrence of one of these ID classes, +including inside an imported or projected binding, uses the same bound. + +Identifiers are opaque and never derived from a PID, path, timestamp, queue +position, Worker name, or scientific Run identity. Attempt ordinals are +RFC-0011 `PositiveU63` values scoped to one Job and are diagnostic ordering, +not identity. + +All Sigils use Benchwork canonical JSON and the existing `sha256:<64 lowercase +hex>` representation. A document's own Sigil field is omitted when computing +that Sigil. Byte payloads such as logs and staged outputs use byte-level Blob +Sigils rather than JSON canonicalization. Times are exactly RFC-0013 +`Timestamp`: normalized RFC 3339 UTC values with a terminal `Z`, no leap +second, and at most six fractional digits. Durations and limits are +non-negative or positive integer units as stated by their field. All durable +deadlines use a `*_due_at` UTC field. +Worker-reported timestamps are observations only. Executor receive time, the +durable due time, and the monotonic-anchor rules below determine protocol +ordering. + +Every execution-journal Event sequence, Event count, and field whose declared +meaning is an execution-journal sequence uses RFC-0013 `U63`; a one-based +Event sequence uses `PositiveU63`. This includes `sequence`, +`last_sequence`, `through_sequence`, `derived_through_sequence`, +`replay_through_sequence`, `target_sequence`, `effective_sequence`, +`disposition_sequence`, `evaluation_sequence`, and nullable +`last_heartbeat_sequence`. All addition is checked before allocation, and an +operation that would allocate `U63_MAX + 1` fails closed without a partial +frame or projection change. Executor epochs, fencing generations, and mutable +entity revisions remain `U64`; they are not journal sequences. + +Every mutable projection carries a non-negative `revision`. A transition event +states the exact expected preceding revision and the next revision. Replay +rejects gaps, duplicate state-changing revision assignments with different +event Sigils, and transitions whose immutable bindings differ from the +object's creation record. An equal-revision entry that only validates a +non-owner relationship is not an assignment. The only events whose primary +state owner is required to use an equal revision are terminal-Job +`job.cancellation_observed` and +`storage_root.hold_release_observed`; any number of distinct, valid request +bindings or release bindings may observe the same unchanged Job revision. + +### Execution Specification + +The v1 top-level object has exactly these required members: + +| Field | Exact v1 value or closed object | +| --- | --- | +| `schema_version` | Constant `execution-specification/1.0`. | +| `specification_id` | Execution Specification ID. | +| `created_at` | UTC creation time. | +| `task_binding` | Exactly `task_id` and `task_capsule_sigil`. | +| `capability_binding` | Exactly `capability_id`, `contract_version`, and `capability_contract_sigil`. | +| `snapshot_binding` | Exactly `snapshot_id` and `snapshot_sigil`. | +| `authorization` | Exactly `ward_decision_id`, `ward_decision_sigil`, `approval_requirement`, and the conditional approval fields below. | +| `policies` | Exactly `filesystem`, `executable`, `process`, `network`, `environment`, `credential`, `resource`, `input`, and `output`; each value is a closed resolved-policy object with `policy_id`, `policy_version`, `policy_sigil`, and its policy-specific bounded rules. | +| `output_contracts` | Bounded ordered array of closed entries containing `logical_name`, `schema_id`, `schema_sigil`, `maximum_bytes`, `maximum_count`, `path_rule`, and `side_effect_id`. | +| `result_requirement` | Exactly `worker_result_mode` and `successful_worker_outcome`; the mode is `REQUIRED`, `OPTIONAL`, or `FORBIDDEN`, and successful outcome is constant `COMPLETED`. | +| `runtime_constraints` | Exactly `runtime_identities`, `backend_identities`, `host_constraints`, and `identity_requirements`; all are bounded closed sets or closed objects. | +| `assurance_requirement` | Exactly `requested_level`, `profile_version`, `profile_sigil`, `conformance_suite_id`, and `conformance_suite_sigil`. | +| `deadline_policy` | Exact integer members `lease_duration_seconds`, `heartbeat_interval_seconds`, `heartbeat_timeout_seconds`, `lease_claim_timeout_seconds`, `attempt_wall_time_seconds`, `cancellation_grace_seconds`, `job_wall_time_seconds`, and `clock_uncertainty_tolerance_seconds`. | +| `attempt_budget` | Exact per-Attempt ceilings described below. | +| `job_budget` | Exact aggregate Job ceilings described below. | +| `retry_policy` | Exactly `max_attempts`, `retryable_terminal_reasons`, `backoff_kind`, `backoff_base_seconds`, `backoff_cap_seconds`, `resume_policy`, and `external_side_effects_retry_safe`. | +| `logging_policy` | Exactly `stdout_maximum_bytes`, `stderr_maximum_bytes`, `structured_maximum_bytes`, `aggregate_maximum_bytes`, `chunk_maximum_bytes`, and `overflow_behavior`. | +| `post_terminal_derivation` | Closed `NONE` or `CODE_MODIFICATION` branch described below. | +| `conformance_policy_version` | Closed conformance-policy version. | +| `specification_sigil` | Sigil over every other member. | + +`attempt_budget` has exactly `cpu_time_seconds`, `peak_memory_bytes`, +`storage_bytes_written`, `output_bytes`, `log_bytes`, `process_starts`, +`network_egress_bytes`, and `network_requests`. `job_budget` has exactly +`attempts`, `cpu_time_seconds`, `storage_bytes_written`, `output_bytes`, +`log_bytes`, `process_starts`, `network_egress_bytes`, and +`network_requests`. Every value is a non-negative integer; `attempts` is +positive and equals `retry_policy.max_attempts`. A denied facility has a zero +limit rather than an omitted field. Job wall time is represented by +`job_wall_time_seconds` and the derived durable Job `deadline_due_at`, not by +a second ambiguous budget counter. + +The only conditional members in v1 are: + +| Container | Conditional fields | Rule | +| --- | --- | --- | +| `authorization` | `approval_receipt_id`, `approval_receipt_sigil` | Both are required exactly when `approval_requirement` is `REQUIRED`; both are absent when it is `NOT_REQUIRED`. | +| each resolved policy | Policy-specific branch selected by its closed mode enum | Exactly one Schema `oneOf` branch is present; no generic policy dictionary exists. | +| `post_terminal_derivation` | For `NONE`, exactly `mode`. For `CODE_MODIFICATION`, exactly `mode`, `crucible_base_identity`, `crucible_base_sigil`, `retention_policy_id`, `retention_policy_version`, `retention_policy_sigil`, `terminal_source_maximum_files`, `terminal_source_maximum_bytes`, `retention_duration_seconds`, and `source_identity_profile`. | A code-modification or other Patch-producing Task must use `CODE_MODIFICATION`; the immutable Base and complete bounded retention authority are fixed before Job creation. | + +There are no other optional top-level members. Backoff fields remain present +and use the exact `backoff_kind` enum `NONE`, `FIXED`, or `EXPONENTIAL`. +`NONE` requires base and cap zero; `FIXED` requires a positive base and a cap +equal to it; `EXPONENTIAL` requires a positive base and a cap no smaller than +the base. `resume_policy` is exactly `FRESH_ONLY` or +`ALLOW_IMMUTABLE_RESUME`. Network limits remain present and zero when network +is denied. This exact shape is the source for the eventual closed JSON Schema +rather than an illustrative minimum. + +In the `CODE_MODIFICATION` branch, both terminal-source maxima and +`retention_duration_seconds` are positive integers and +`source_identity_profile` is the constant +`BENCHWORK_SOURCE_TREE_V1`. The predeclared Base pair and retention-policy +tuple are non-null and must validate against the pinned Task, Snapshot, and +resolved output policy before the Specification Sigil is accepted. +`retention_policy_id` is exactly the RFC-0013 `SP-ID`, +`retention_policy_version` is the constant `1.0`, and +`retention_policy_sigil` resolves the exact +`artifact-retention-policy/1.0.record_sigil` for that ID. The duration is the +maximum lifetime of the execution-owned `ATTEMPT_OUTPUT` Reference-Set hold; +it does not replace, shorten, or reinterpret that physical retention policy. + +`worker_result_mode` must be equal to or narrower than the v2 Task and +Capability. `REQUIRED` needs one accepted `COMPLETED` result for Attempt and +Job success; `OPTIONAL` permits an accepted `COMPLETED` result or explicit +`result_binding: NONE`; `FORBIDDEN` requires `NONE` and rejects every Worker +result message. A `FAILED` Worker outcome can never satisfy successful +completion, although it remains operational evidence. + +Every authority in the Specification must be equal to or narrower than the +pinned v2 Task and Capability. Lifecycle settings may consume less time or +fewer resources but may not extend their bounds. Retry does not multiply an +otherwise single-use external side-effect permission unless the Task and +Capability explicitly make that side effect retry-safe and idempotent. + +Every closed resolved policy rule that carries a Side-effect ID also carries +the exact closed `side_effect_authorization` object with +`side_effect_id`, `kind`, `authority_sigil`, `maximum_invocations`, +`retry_mode`, `idempotency_scope_sigil`, and `requires_approval`. +`retry_mode` is exactly `NO_RETRY`, `SAME_IDEMPOTENCY_KEY`, or +`NEW_AUTHORIZATION_EACH_ATTEMPT`; `kind` and the null matrix are the exact +RFC-0011 values. Repeated use of one Side-effect ID across policy rules must +repeat this object byte-for-byte, and the unique projection by Side-effect ID +must equal the pinned Task/Circle subset. No side-effect authority may survive +only as an unbound ID. + +`AttemptAuthorizationEffect` is the closed object +`{side_effect_id, authority_sigil}`. The exact +`attempt_authorization_requirement` derived from that projection is +`NONE {kind, effects}` with an empty array or +`REQUIRED {kind, effects}` with one to 128 entries. The latter contains every +and only side effect whose retry mode is +`NEW_AUTHORIZATION_EACH_ATTEMPT`, strictly sorted by unique Side-effect ID, +with the exact authority Sigil. A non-`READ_ONLY` `NO_RETRY` effect requires +`max_attempts: 1`. A `SAME_IDEMPOTENCY_KEY` effect retains its exact non-null +scope Sigil in every carrying rule. A `NEW_AUTHORIZATION_EACH_ATTEMPT` effect +may coexist with `max_attempts > 1` only with +`external_side_effects_retry_safe: false` and the per-Attempt carrier below. + +The Specification is immutable. Any change, including a narrower permission, +different retry count, different backend constraint, or different assurance +profile or suite, creates a new Specification Sigil and requires a new exact +approval where RFC-0011 requires approval. + +`CODE_MODIFICATION` authorizes only bounded retention of terminal source for a +later RFC-0014 derivation. It does not authorize Patch preparation, acceptance, +or promotion. Its non-null `crucible_base_identity` and +`crucible_base_sigil` are copied from the pinned Task/Snapshot input +resolution and are therefore available even when no Attempt is ever +allocated. Every allocated Attempt must copy that exact pair. +`attempt.cleaning` and the Attempt terminal event bind the same immutable +Crucible Base identity and exact terminal-source binding. A verified branch +binds retained source identity, source Sigil, storage BlobRef, +retention-policy Sigil, byte/file counts, +storage status, and independent verifier-evidence Sigil; a quarantined branch +binds the same known fields, explicit null source identity/Sigil/BlobRef when nothing +content-identified was retained, zero counts, and closed reasons. The Job +terminal event repeats that exact binding for the selected Attempt, or +constructs the fixed no-Attempt quarantine branch from the Job-bound +Specification. A failure cannot be reconstructed or backfilled after the Job +Outcome is derived. + +### Canonical retained source tree + +`BENCHWORK_SOURCE_TREE_V1` means exactly the +`benchwork-source-tree/1.0` contract; it is not a tar, zip, directory path, or +implementation-selected serialization. The closed top-level record has +exactly `schema_version`, `source_tree_id`, `manifest`, `bundle_blob`, +`verified_at`, and `source_tree_sigil`. The Schema version is constant, +`bundle_blob` is the exact RFC-0013 `{blob_sigil, size_bytes: U63}`, and the +self-Sigil covers every other member. + +`manifest` has exactly `format_version: BENCHWORK_SOURCE_TREE_V1`, `job_id`, +`attempt_id`, `attempt_binding_sigil`, `crucible_base_identity`, +`crucible_base_sigil`, `scope`, `path_semantics`, `entries`, `file_count`, +`byte_count`, and `manifest_sigil`. Its self-Sigil covers every other member. +`scope` is exactly `{root_kind: CRUCIBLE, included_paths, excluded_paths}`; +both path arrays are sorted unique sets of normalized project-relative paths, +are disjoint, and have at most 100,000 members. `path_semantics` is exactly +`{separator: SLASH, unicode_normalization: NFC, case_mode: +SENSITIVE|INSENSITIVE_REJECT_COLLISIONS, dot_segments: REJECT, +link_traversal: NOFOLLOW, reserved_name_policy: REJECT}`. `.benchwork/`, +`.git/`, absolute paths, empty or dot segments, NUL/control characters, +normalization or case collisions, and platform-reserved names are always +excluded and cannot be reintroduced by `included_paths`. + +`entries` is a `0..100000` array sorted strictly by normalized path bytes, +unique by path, and contains exactly one of: + +- `DIRECTORY {path, kind, entry_sigil}`; +- `FILE {path, kind, blob: RFC-0013 BlobRef, executable}`; or +- `SYMLINK {path, kind, target, target_sigil}`. + +All objects are closed. A file Blob is the byte-level SHA-256 and U63 size of +its complete content. A symlink target is NFC UTF-8 of `0..4096` bytes and is +hashed as link data; it is never followed. `file_count` is the checked count +of `FILE` plus `SYMLINK` entries. `byte_count` is the checked sum of file +sizes plus symlink-target byte lengths. Both must fit the Specification +maxima. Unsupported types, hard-link ambiguity, sparse or partial traversal, +changed input, arithmetic overflow, or an incomplete scope fail verification. + +The `bundle_blob` bytes are deterministic: + +```text +ASCII "BWSOURCE1\n" +U64BE(length(canonical_json(manifest))) +canonical_json(manifest) +for each FILE entry in entries order: + U64BE(entry.blob.size_bytes) + exact file bytes +``` + +The decoder rejects a length, order, content Sigil, trailing-byte, manifest, +or count mismatch. `source_tree_id` is `BTS-` followed by uppercase SHA-256 +hex over canonical JSON +`["benchwork-source-tree-id/1.0", job_id, attempt_id, +attempt_binding_sigil, manifest_sigil]`. Independent scan and bundle +readback must reproduce the complete manifest, bundle BlobRef, and record +self-Sigil. + +For a verified terminal-source binding, +`terminal_source_identity == source_tree_id`, +`terminal_source_sigil == source_tree_sigil`, and +`storage_blob == bundle_blob`; its Base, counts, and identity profile equal +the manifest and Specification byte-for-byte. A quarantined branch preserves +the same three values when a complete claimed tree exists; all three are null +when none exists. RFC-0014 exports only after resolving this record and +verifying the deterministic bundle rather than reading a mutable Crucible. + +### Job, Attempt, Worker, and Lease bindings + +The following tables are the exact required top-level members of the v1 +binding documents. No document has an optional top-level member except where +the conditional rules say so. + +`execution-job/1.0`: + +| Field | Meaning | +| --- | --- | +| `schema_version` | Constant `execution-job/1.0`. | +| `job_id` | Job identity. | +| `submitted_at` | Durable UTC submission time. | +| `submission_idempotency_key_sigil` | Sigil of the bounded Start idempotency key. | +| `start_request_sigil` | Complete RFC-0015 Start-request Sigil. | +| `task_id`, `task_capsule_sigil` | Exact Task binding. | +| `specification_id`, `specification_sigil` | Exact Specification binding. | +| `admission_chronicle_head` | Exact RFC-0013 Phase 3-admissible Chronicle Head observed under the outer gate before any Job side effect. | +| `admission_chronicle_head_evidence` | Closed admission evidence defined below. | +| `assurance_requirement` | Exact copy of the Specification tuple. | +| `deadline_due_at` | Durable UTC Job deadline derived once at submission. | +| `job_budget` | Exact copy of the aggregate ceilings. | +| `job_storage_roots` | Array of zero or one pre-held `JOB_INPUT` storage-root binding; its ESM covers every Job input and has this `job_id` and null `attempt_id`. | +| `job_binding_sigil` | Sigil over every other member. | + +The admission evidence has exactly `profile`, `job_id`, +`start_request_sigil`, `observed_chronicle_head`, `observed_at`, +`validator_build_sigil`, and `evidence_sigil`; the profile is constant +`PHASE3_CHRONICLE_HEAD_ADMISSION_V1`, its observed Head equals the Job field, +its Job/request fields equal the enclosing Job, and the self-Sigil is the +common canonical-JSON self-Sigil over every other field. The Head +must have `event_count < U63_MAX`. Start acquires the outer canonical- +reference gate, replays Chronicle, validates this capacity bound, and freezes +this complete admission evidence in a Job-ID-keyed single-assignment pending +resolver before creating its Job-input ESM or any Storage side effect. The +complete `execution-job/1.0` document is assembled only after its root binding +exists, and embeds those already frozen evidence bytes; it is never claimed +to predate the hold whose EventRef it contains. An interrupted retry reuses +the exact frozen admission evidence but, under the gate, again requires the +then-current valid Chronicle Head to remain below `U63_MAX`; exhaustion, +ambiguity, or different evidence for the same Job ID fails before a new +Reference Set, hold, or execution event. + +The Job contains no scientific Run ID and grants no canonical authority. The +replayed Job projection initializes `fencing_counter`, `fence_floor`, every +budget `consumed` counter, and every budget `reserved` counter to zero. Only +the atomic allocation, budget-settlement, and Lease-terminal events named +below may advance those values. Every non-empty `job_storage_roots` hold is +already durable under the outer gate before `job.submitted`; the submission +event activates exactly that immutable array. + +For RFC-0015 Start, the key Sigil and Job ID are deterministic before any +Storage side effect: + +```text +submission_idempotency_key_sigil = + Sigil(["execution-start-idempotency-key/1.0", idempotency_key]) + +job_id = + "JB-" + UPPER_HEX(SHA256(canonical_json( + ["execution-job-id/1.0", + task_id, + submission_idempotency_key_sigil]))) +``` + +The resulting Job ID is exactly 67 ASCII bytes and still belongs to `JB-ID`. +The `START_JOB` idempotency `scope_id` is the immutable Task ID, not the +allocated Job ID. Thus `(START_JOB, task_id, +submission_idempotency_key_sigil)` is caller-reconstructible and selects one +Job before holds. Reusing it with a different `start_request_sigil` is a +conflict. + +`execution-attempt/1.0`: + +| Field | Meaning | +| --- | --- | +| `schema_version` | Constant `execution-attempt/1.0`. | +| `attempt_id`, `job_id`, `job_binding_sigil` | Attempt and exact parent binding. | +| `retry_ordinal`, `fencing_generation` | RFC-0011 `PositiveU63` ordinal and unused strictly increasing unsigned 64-bit Job generation. | +| `assurance_requirement` | Exact requested tuple. | +| `backend_identity`, `backend_version`, `backend_configuration_sigil` | Planned enforcement backend. | +| `base_identity`, `base_sigil`, `input_identities` | Immutable Base identity/Sigil pair and bounded sorted input bindings. Under `CODE_MODIFICATION`, the pair is byte-for-byte equal to the Specification's predeclared `crucible_base_identity` and `crucible_base_sigil`. | +| `resume_mode` | `FRESH` or `IMMUTABLE_RESUME`. | +| `resume_source_identity` | Required exactly for `IMMUTABLE_RESUME` and absent for `FRESH`. | +| `crucible_id` | Newly allocated mutable materialization identity. | +| `output_namespace_id` | Newly allocated exclusive output namespace. | +| `log_stream_ids` | Exactly `STDOUT`, `STDERR`, and `STRUCTURED`, each bound to a fresh Log-stream ID. | +| `budget_reservation` | Exact Job-budget reservation described under Retry. | +| `attempt_authorization_requirement` | Exact immutable RFC-0011 `NONE` or `REQUIRED` branch derived from the Specification's carrying policies. | +| `created_at`, `deadline_due_at` | Creation time and durable Attempt deadline. | +| `attempt_binding_sigil` | Sigil over every other member. | + +The only conditional Attempt member is `resume_source_identity`, required +exactly for `IMMUTABLE_RESUME` and absent for `FRESH`. +`attempt.preflight_passed` later binds the verified materialization content +identity. A retry always creates a new Attempt, Crucible, and output namespace. + +### Per-Attempt authorization + +`attempt-authorization-subject/1.0` is the executable RFC-0011 +`AttemptAuthorizationSubject`. Its conventional filename is +`attempt-authorization-subject-1.0.json` and its exact `$id` is +`https://benchwork.dev/schemas/attempt-authorization-subject/1.0`. +The closed object has exactly `schema_version`, +`authorization_subject_id`, `job_id`, `job_binding_sigil`, `attempt_id`, +`attempt_binding_sigil`, `retry_ordinal`, `specification_id`, +`specification_sigil`, `effects`, and `authorization_subject_sigil`. +`schema_version` is constant `attempt-authorization-subject/1.0`; `effects` +is the non-empty one-to-128 RFC-0011 `AttemptAuthorizationEffect` set; the +self-Sigil covers every other field; and `retry_ordinal` is the exact +RFC-0011 `PositiveU63` value from the immutable Attempt. + +The subject ID is `AA-` followed by the uppercase 64-hex SHA-256 digest of +canonical JSON: + +```text +["attempt-authorization-subject-id/1.0", + job_id, + job_binding_sigil, + attempt_id, + attempt_binding_sigil, + retry_ordinal, + specification_id, + specification_sigil, + effects] +``` + +The subject is constructed only after the immutable Attempt document exists. +Its owner and Specification fields equal that Attempt and Job byte-for-byte, +and `effects` equals both its immutable +`attempt_authorization_requirement.effects` and the complete unique +projection of the Specification's +`NEW_AUTHORIZATION_EACH_ATTEMPT` carrying policies. + +`attempt-authorization-transition-request/1.0` is the only v1 request that +can ask Athanor to grant this subject. Its closed object has exactly +`schema_version`, `transition_request_id`, `event_type`, +`expected_chronicle_head`, `authorization_subject`, `authority_evidence`, +`actor`, `host_invocation`, `chronicle_actor`, `idempotency_key_sigil`, +`requested_at`, and `transition_request_sigil`. `event_type` is the constant +`attempt.authorization.granted`; the Head is the exact Phase 3-admissible +RFC-0013 `ChronicleHeadRef`; `actor` and `host_invocation` are the exact +authenticated RFC-0015 bindings; `chronicle_actor` is the complete canonical +RFC-0001 `actor/1.0` `{actor_id, actor_type, host, authenticated_by}`; and the +self-Sigil covers every other field, including that Chronicle Actor. +`actor.authentication_context_sigil == +host_invocation.authentication_context_sigil`; both binding self-Sigils +validate; and all three records are produced byte-for-byte by the same +authenticated invocation context rather than caller text. +`chronicle_actor.actor_id == actor.actor_id`; the closed kind mapping is +`USER -> human`, `AGENT -> agent`, and `SYSTEM -> policy|tool`. The Chronicle +`host` and `authenticated_by` values equal that context's canonical audit +Host and authentication mechanism and are not inferred from a Host-identity +Sigil, invocation ID, or caller text. +`authority_evidence` is the closed object +`{purpose: ATTEMPT_AUTHORIZATION, ward_decision_id, ward_decision_sigil, +authorization_policy_sigil, effects}`. Its `effects` equals the subject +byte-for-byte. Each effect is authorized at the expected Head by the pinned +Task, Capability, Circle, Snapshot, Specification, Ward decision, and +authenticated actor; the authority is re-evaluated rather than inferred from +the mere presence of their IDs. `authority_evidence_sigil` is the common +canonical-JSON Sigil of this complete object and is the value copied into the +canonical Event. + +The transition ID is deterministic: + +```text +transition_request_id = + "AAT-" + UPPER_HEX(SHA256(canonical_json( + ["attempt-authorization-transition-request-id/1.0", + authorization_subject.authorization_subject_id, + idempotency_key_sigil]))) +``` + +The caller fixes the bounded idempotency key before the request. The immutable +request resolver is single-assignment by this ID: the first complete bytes, +including Actor, Host invocation, Chronicle Actor, Head, evidence, and +`requested_at`, win; an exact retry returns them and any different bytes +conflict. This resolver write precedes the Athanor call. + +The successful canonical Event has type +`attempt.authorization.granted`. Its closed payload has exactly +`transition_request_id`, `transition_request_sigil`, +`authorization_subject_id`, `authorization_subject_sigil`, +`authority_evidence_sigil`, `actor`, and `occurred_at`. It resolves the exact +request and requires, byte-for-byte, the same subject, authority-evidence +Sigil, actor, and request time (`occurred_at == requested_at`). The Event is +admitted only at `expected_chronicle_head`; its outer `actor` is +byte-for-byte the request's `chronicle_actor`, while its payload `actor` +remains the RFC-0015 `ActorBinding`. It is not the operational +`attempt.authorization_bound` event. + +`AttemptAuthorizationBinding` is the exact closed object +`{authorization_subject_id, authorization_subject_sigil, +authorization_transition_request_id, authorization_transition_request_sigil, +authorization_event_id, authorization_event_body_sigil, +authorization_receipt_id, authorization_receipt_sigil, +authorization_binding_sigil}`. Receipt ID uses `RC-[A-Z0-9]+`; its Sigil and +the binding self-Sigil use the common Sigil domain. The binding self-Sigil +covers every other field. The Receipt is the exact `receipt/1.1` for +`authorization_event_id` and `authorization_event_body_sigil`; the Event then +resolves the exact transition request, and that request resolves the complete +subject and purpose above. A Receipt itself does not contain a purpose or +subject, so no implementation may validate this binding by reading such +fictional Receipt fields. Every authority named in `effects` validates that +exact subject. An Attempt-specific subject, transition request, Event, +Receipt, or binding cannot equal or be reused by another Attempt or by the +Specification-level approval. + +The replayed orthogonal `attempt_authorization_state` is exactly +`NONE {kind}`, `PENDING {kind}`, or +`BOUND {kind, authorization_subject, attempt_authorization_binding}`. +Allocation initializes it to `NONE` exactly for a `NONE` immutable +requirement and to `PENDING` exactly for `REQUIRED`. +`attempt.authorization_bound` is legal only while the Attempt lifecycle state +is `CREATED`, its authorization state is `PENDING`, and no stop is latched. +Its exact payload contains the complete `authorization_subject` and +`attempt_authorization_binding`; the nested subject ID and Sigil agree +byte-for-byte. The event advances only the Attempt revision and assigns +`BOUND` once. No event replaces or clears it. + +The AA-ID and AAT-ID make a crash before either Event commitment retry the +exact same subject and immutable request. Athanor is idempotent by request ID +and Sigil: it returns the same Event and Receipt for the same complete request +and rejects ID reuse with different bytes. A crash after the Receipt exists +but before `attempt.authorization_bound` therefore resolves that exact +request/Event/Receipt chain and appends only the missing operational binding +event. Recovery is read-only toward Chronicle: it cannot construct a new +request, resample Head/time/actor, or mint a replacement authority. An +unknown or conflicting request, Event, or Receipt fails closed and the +Attempt may only stop or reject. + +A `REQUIRED` Attempt cannot append `attempt.preflight_started`, receive or +claim a Lease, launch, issue credentials, or obtain any side-effecting handle +until `BOUND`. This guard applies to the first Attempt and every retry. A +predecessor's subject, Receipt, binding, idempotency scope, or side-effect +handle never authorizes its successor. + +`execution-worker/1.0` is a stable definition, not a process: + +| Field | Meaning | +| --- | --- | +| `schema_version` | Constant `execution-worker/1.0`. | +| `worker_id` | Stable Worker identity reused only across distinct Sessions of this definition. | +| `definition_revision` | Non-negative immutable definition revision. | +| `supersedes_worker_binding_sigil` | Required exactly when `definition_revision` is greater than zero and absent at revision zero. | +| `implementation_name`, `implementation_version` | Worker implementation binding. | +| `supported_runtimes`, `resource_ceilings` | Bounded declared capability sets. | +| `declared_backend_capabilities` | Bounded declarations with no evidentiary authority. | +| `verified_capability_tuples` | Bounded Executor-verified backend/profile/suite/configuration tuples. | +| `maximum_concurrency` | Positive verified definition ceiling. | +| `definition_created_at` | UTC time of this definition revision. | +| `worker_binding_sigil` | Sigil over every other member. | + +`supersedes_worker_binding_sigil` is required exactly when +`definition_revision` is greater than zero and absent at revision zero. +Changing implementation, capabilities, verification, or concurrency creates +a new immutable definition revision; it never mutates a Worker Session. + +`execution-worker-session/1.0` is one immutable live-session binding: + +| Field | Meaning | +| --- | --- | +| `schema_version` | Constant `execution-worker-session/1.0`. | +| `worker_session_id`, `worker_id`, `worker_binding_sigil` | New Session identity and exact stable definition. | +| `executor_instance_id`, `executor_epoch` | Coordinator process and epoch to which this Session is exclusively attached. | +| `host_identity_sigil`, `backend_session_identity` | Exact Host and backend process/session evidence identities. | +| `control_channel_identity_sigil` | Protected bounded control-channel identity. | +| `verified_capability_tuple_sigil` | Selected subset of the Worker definition verified for this Session. | +| `maximum_concurrency` | Session ceiling no greater than the definition ceiling. | +| `opened_at` | UTC registration time. | +| `worker_session_binding_sigil` | Sigil over every other member. | + +A fresh Worker process, control-channel re-establishment, Executor epoch, or +revalidation creates a new `worker_session_id` and a new immutable Session +document. A Session ID, backend session identity, or control-channel identity +is never reused. There is no Session generation counter and no +`OFFLINE -> REGISTERED` resurrection. + +`execution-lease/1.0`: + +| Field | Meaning | +| --- | --- | +| `schema_version` | Constant `execution-lease/1.0`. | +| `lease_id`, `job_id`, `attempt_id` | Exact Lease scope. | +| `worker_id`, `worker_binding_sigil` | Stable Worker definition binding. | +| `worker_session_id`, `worker_session_binding_sigil` | Exact immutable Session receiving authority. | +| `executor_instance_id`, `executor_epoch` | Issuing coordinator binding. | +| `fencing_generation` | Exact Attempt generation. | +| `offered_at`, `claim_due_at`, `initial_expiry_due_at`, `maximum_expiry_due_at` | Durable UTC authority bounds. | +| `heartbeat_policy` | Exact interval and timeout copied from the Specification. | +| `lease_credential_digest` | Digest only, never the credential. | +| `lease_binding_sigil` | Sigil over every other member. | + +The unguessable Lease credential is delivered only through the protected +control handle. It is never placed in a log, projection, policy file, +environment dump, Worker-readable journal, or evidence export. + +### Recovery action-set binding + +`execution-recovery-action-set/1.0` has exactly +`schema_version`, `recovery_id`, `phase`, `derived_from_journal_id`, +`derived_through_sequence`, `derived_through_event_sigil`, +`supersedes_action_set_sigil`, `actions`, and `action_set_sigil`. +`supersedes_action_set_sigil` is null for an initial phase set and identifies +the current phase set replaced after a new clock/epoch interruption. `phase` +is `STARTED`, `FENCING`, `RECONCILING`, or `FINALIZING`. Each action has +exactly `ordinal`, `action_kind`, `entity_kind`, `entity_id`, +`expected_revision`, `target_event_id`, `target_sequence`, +`target_event_type`, `prerequisite_event_ids`, and `parameters`. Ordinals are +contiguous from zero; prerequisite IDs are sorted and refer only to events in +the derivation prefix or earlier actions' committed events. `entity_kind`, +`entity_id`, and `expected_revision` name the primary owner. For a +multi-entity target event, every secondary owner's exact preceding and next +revision is deterministically projected from the bound prefix plus earlier +ordinal events and must appear in the event envelope; it is not duplicated as +mutable action metadata. + +The exact action kinds and parameter branches are: + +| `action_kind` | Exact `parameters` members | +| --- | --- | +| `COMMIT_DUE_EVENT` | `deadline_kind`, `due_at`, `deadline_entity_id` | +| `RESTORE_CLOCK` | `clock_uncertain_event_id`, `trusted_time_source_sigil`, `clock_uncertainty_tolerance_seconds` | +| `FENCE_LEASE` | `lease_id`, `lease_binding_sigil`, `prior_fence_floor`, `tombstone_generation` | +| `REPUBLISH_TOMBSTONE` | `lease_id`, `tombstone_generation`, `original_terminal_event_sigil`, `sink_ids` | +| `OFFLINE_SESSION` | `worker_session_id`, `last_heartbeat_sequence`, `active_lease_ids` | +| `TERMINATE_PROCESS_TREE` | Closed union `TRACKED {kind, attempt_id, process_tree_identity, backend_configuration_sigil, termination_policy_sigil}` or `START_HANDLE {kind, attempt_id, backend_start_handle_sigil, backend_configuration_sigil, termination_policy_sigil}`. | +| `REVOKE_HANDLES` | `attempt_id`, `handle_set_sigil`, `sink_ids` | +| `CLOSE_SESSION` | `worker_session_id`, `backend_session_identity`, `control_channel_identity_sigil` | +| `CLOSE_LOG` | `attempt_id`, `stream`, `last_committed_sequence` | +| `VERIFY_OUTPUT_STORAGE` | `attempt_id`, `expected_output_set_sigil`, `storage_journal_id`, `storage_through_sequence`, `storage_through_event_sigil` | +| `VERIFY_TERMINAL_SOURCE` | `attempt_id`, `crucible_base_identity`, `crucible_base_sigil`, `retention_policy_sigil` | +| `BIND_DURABLE_ATTEMPT_AUTHORIZATION` | `attempt_id`, `authorization_subject_id`, `authorization_subject_sigil`, `authorization_transition_request_id`, `authorization_transition_request_sigil`, `authorization_event_id`, `authorization_event_body_sigil`, `authorization_receipt_id`, `authorization_receipt_sigil` | +| `RELEASE_INACTIVE_EXECUTION_INPUT_HOLD` | `root_kind`, `storage_root`, `owner_terminal_basis`, `release_authorization_id`, `release_authorization_sigil`; `root_kind` is `JOB_INPUT` or `ATTEMPT_INPUT`, `storage_root` is the complete matching binding, and `owner_terminal_basis` is the complete EHR `OWNER_TERMINAL` branch. | +| `RELEASE_DUE_EXECUTION_HOLD` | `job_id`, `release_schedule`, `release_authorization_id`, `release_authorization_sigil`; `release_schedule` is the complete matching `output_hold_release_schedules` entry, including its hold-set Event, duration, inactivation Event, due time, and deadline status. | +| `QUARANTINE_RESOURCE` | `attempt_id`, `resource_ids`, `reason_codes` | +| `ADVANCE_ATTEMPT` | `attempt_id`, `from_state`, `transition_cause` | +| `CLEAN_RESOURCE` | `attempt_id`, `resource_ids`, `cleanup_policy_sigil` | +| `COLLECT_ACCOUNTING` | `job_id`, `attempt_id`, `reservation`, `accounting_scope_sigil`, `backend_configuration_sigil`, `accounting_policy_sigil` | +| `SETTLE_BUDGET` | `job_id`, `attempt_id`, `reservation`, `accounting_capture_event_id`, `accounting_capture_event_sigil` | +| `EVALUATE_ATTEMPT_ASSURANCE` | `attempt_id`, `terminal_event_id`, `assurance_input_event_ids`; the input IDs are sorted and identify derivation-prefix or earlier-action evidence. | +| `EVALUATE_JOB_ASSURANCE` | Closed union `ATTEMPT {kind, job_id, attempt_id, attempt_assurance_event_id}` or `NOT_APPLICABLE {kind, job_id, reason}`; the latter reason is constant `NO_ATTEMPT_ALLOCATED`. | +| `ADVANCE_JOB` | `job_id`, `from_state`, `selected_attempt_id`, `transition_cause` | + +Nullable values remain present: `last_heartbeat_sequence` is null when none, +`last_committed_sequence` is null for an empty stream, and +`selected_attempt_id` is null when no Attempt exists. No other action +parameter is nullable. + +The action set is derived solely from the bound replay prefix. Sorting starts +with `(phase ordinal, action-kind ordinal)`, where action-kind ordinals follow +the printed order above. For `COMMIT_DUE_EVENT`, the remaining exact key is +`(due_at normalized instant, fixed deadline priority, deadline_entity_id +unsigned ASCII, target_event_type unsigned ASCII)`. For every other kind it +is `(entity-kind ordinal, entity_id unsigned ASCII, target_event_type unsigned +ASCII, canonical logical action unsigned bytes)`, with entity-kind order +`EXECUTOR`, `RECOVERY`, `WORKER`, `WORKER_SESSION`, `JOB`, `ATTEMPT`, +`LEASE`, `LOG_STREAM`. Duplicate logical actions are invalid. The canonical +logical action is the action object without +`ordinal`, `target_event_id`, or `target_sequence`; within a +`RECOVERY_DERIVATION` transition-cause template, it also replaces the +self-referential trigger event ID and effective sequence with the domain +constants `SELF_EVENT` and `SELF_SEQUENCE`. The reserved ID and sequence are +substituted only after sorting, avoiding a reservation cycle. External +observations may satisfy or fail an action but never change the frozen action +identity or ordering. + +For a `STARTED` set, due-action derivation performs the ordinary deadline +sweep as a pure simulation over the bound prefix: it selects the least +applicable key, projects that primary event, virtually applies its complete +mandatory dependent stop/fence closure, then selects the next still-applicable +key. The set contains one `COMMIT_DUE_EVENT` only for each primary event that +survives that simulation, in selection order. The virtual dependent events +are not target events in `STARTED`; they become the exact `FENCING` actions +derived after the primary set completes. Thus a lower-priority key made +inapplicable by an earlier virtual closure is absent rather than committed +under a competing cause. + +Each termination, revocation, close, cleanup, or verification action has a +fail-closed quarantine outcome within that same target event; it never derives +a new mid-phase action. `QUARANTINE_RESOURCE` is emitted separately only for +resources already known to require quarantine in the phase's derivation +prefix. + +Allowed phase membership is exact: + +- `STARTED` contains only `COMMIT_DUE_EVENT`; +- `FENCING` contains `FENCE_LEASE`, `REPUBLISH_TOMBSTONE`, + `OFFLINE_SESSION`, `BIND_DURABLE_ATTEMPT_AUTHORIZATION`, and the + `ADVANCE_ATTEMPT` or `ADVANCE_JOB` actions whose target transition is + `STOPPING`; +- `RECONCILING` contains `TERMINATE_PROCESS_TREE`, `REVOKE_HANDLES`, + `CLOSE_SESSION`, `CLOSE_LOG`, `VERIFY_OUTPUT_STORAGE`, + `VERIFY_TERMINAL_SOURCE`, `QUARANTINE_RESOURCE`, + `ADVANCE_ATTEMPT` actions that enter `CLEANING`, + `CLEAN_RESOURCE`, and `COLLECT_ACCOUNTING`; and +- `FINALIZING` contains `RESTORE_CLOCK`, `ADVANCE_ATTEMPT` actions that enter + a terminal Attempt state, `SETTLE_BUDGET`, + `EVALUATE_ATTEMPT_ASSURANCE`, `EVALUATE_JOB_ASSURANCE`, and remaining + `ADVANCE_JOB` actions, plus `RELEASE_INACTIVE_EXECUTION_INPUT_HOLD` and + `RELEASE_DUE_EXECUTION_HOLD`. + +An action kind in any other phase is invalid. The phase boundary is also the +dependency boundary: all verification and quarantine observations precede +terminal Attempt selection. Trusted accounting is captured in +`attempt.cleanup_progressed` after the Attempt enters `CLEANING` and after +every other metered Recovery action, so the later settlement and Job target +event are deterministic from the `FINALIZING` derivation prefix; action-kind +order then places terminal Attempt events before settlement, Attempt +assurance before Job assurance, and Job advancement last. +`RESTORE_CLOCK` is present exactly when the clock gate remains `UNCERTAIN`; +its target is the sole `executor.clock_restored` event, which carries the +exact Recovery action binding. If clock trust cannot be restored, that action +cannot complete and Recovery remains gated in `FINALIZING`. +The journal allocator deterministically reserves each pending action's unique +`target_event_id` from the domain-separated tuple `(journal_id, recovery_id, +phase, derived_through_event_sigil, ordinal, canonical logical action)` and +sets `target_sequence = derived_through_sequence + 2 + ordinal`: the +set-binding Recovery event occupies the intervening sequence. It does so +before the action-set Sigil is computed. Action-completion events commit +strictly by ordinal and use those exact envelope values. No ordinary external +mutation may interleave a current action set; a newly committed clock/epoch +interruption instead supersedes it through the rebase rule below. Reserved IDs +in a superseded set remain unusable history. These reservations also make an +enclosing `RECOVERY_DERIVATION` transition cause fully concrete before Sigil +computation. +An `executor.clock_uncertain` event or the first +`executor.epoch_started` event of a restarted process is the only event +allowed to consume a pending reserved sequence. It immediately invalidates +all uncommitted reservations in that set; only the required clock/epoch +control events and `recovery.action_set_rebased` may follow before the +replacement reservations become current. +Every action-completion event carries the common recovery-action binding +defined below, so replay can distinguish pending from completed actions +without inspecting a process table or mutable path. + +When clock uncertainty or a new epoch interrupts an active Recovery, a +replacement set for the same phase is derived from the new verified prefix. +It preserves already completed action events, contains every still-required +old or new action in canonical order, binds the prior set in +`supersedes_action_set_sigil`, and becomes current only through +`recovery.action_set_rebased`. The superseded document remains immutable +history and cannot receive new completion bindings. Initial sets bound by +`recovery.started` or `recovery.phase_advanced` have a null +`supersedes_action_set_sigil`; a replacement set has that member equal to +`prior_action_set_sigil` in the rebasing event. `carried_completion_event_ids` +is unique, sorted by event sequence, and contains exactly those completion +events from the prior set or its transitive carry chain whose postconditions +remain valid in the new prefix. Their actions are omitted from the replacement +set. An action whose postcondition must be re-established is not carried and +appears as a fresh replacement-set action. +The rebasing event's `reason_event_id` identifies +`executor.clock_uncertain` when clock uncertainty consumed the reservation, +and otherwise the interrupting startup `executor.epoch_started`; +`new_epoch` equals the latest epoch in the replacement derivation prefix. + +## Worker eligibility and identity + +A Worker is an operational subject, not a principal with scientific authority. +Before it becomes schedulable, the Executor must: + +1. validate the current Worker-definition revision and binding Sigil and + require definition state `ENABLED`; +2. create and validate one fresh Worker Session bound to the current Executor + instance and epoch; +3. verify the Session's backend identity, configuration Sigil, supported runtime, Host + platform, resource ceiling, assurance profile, and conformance-suite tuple; +4. establish a bounded control channel that does not expose the journal or + other Jobs' credentials; +5. record the independently verified capability tuple in the journal; and +6. place the Session in `READY` only if no reused Session, backend-session, or + control-channel identity exists and every prior Session is unable to hold + current authority. + +The `0.4` runtime may implement the Worker in the same installation as the +Executor, but it must still allocate a fresh Worker Session identity and +enforce the protocol. A PID is recorded only as backend evidence and is +insufficient identity because PIDs are reusable. + +One Worker Session may hold only the number of concurrent active Leases in its +verified `maximum_concurrency`. The `0.4` reference Worker defaults to one. +Capacity is scheduling information, not permission to share a Crucible, +credential, output namespace, or mutable sink between Attempts. + +## Lifecycle overview + +The journal serializes all transitions. The tables below are exhaustive: +transitions not listed are illegal. Terminal states have no outbound +transitions. A repeated request that carries the same idempotency key and exact +payload Sigil returns the originally committed event without appending a +transition. Reuse of that key with a different payload fails closed and is +recorded as a rejected protocol message. + +Observation events such as a heartbeat, resource sample, log chunk, or +duplicate delivery do not change lifecycle state. They still require the exact +current object revision and bindings where their Schema calls for them. + +A journal event may update several related projections atomically. In +particular, `job.attempt_allocated` changes the Job and creates its Attempt, +increments its fence generation, and reserves its exact Job-budget share, +while `lease.claimed` changes the Lease and Attempt and accounts for Worker +Session capacity. Lease release, revocation, expiry, and fencing also account +for Session capacity and advance the Job fence floor to an unassigned +tombstone in the same event. Replay applies all revisions and ledgers in such +an event or none of them; there is no valid prefix in which only part of the +relationship changed. + +Attempt terminal state describes computation, termination, cleanup, and +quarantine only. It never asserts assurance or final Job-result eligibility. +`attempt.assurance_evaluated` is the only post-terminal Attempt observation +that may attach assurance evidence. It follows the one +`job.budget_settled` event for that Attempt and commits exactly once with +`CLAIMED`, `UNMET`, or `UNVERIFIABLE`; `CLAIMED` attaches one immutable claim +after validating the terminal-event and settlement Sigils, while the other +outcomes bind closed failure reasons and evidence Sigils. The event also +finalizes the parent Job's Attempt summary under the exact multi-owner +revision effects below. `NOT_APPLICABLE` is not valid for an allocated +Attempt because every Attempt has the pinned assurance profile. + +Separately, `job.assurance_evaluated` commits exactly once before every Job +terminal event. Its closed `evaluation` enum is `CLAIMED`, `UNMET`, +`UNVERIFIABLE`, or `NOT_APPLICABLE`. For a Job with a selected or final +Attempt, it references that Attempt's completed evaluation and must agree with +it. `NOT_APPLICABLE` is permitted only when no Attempt was ever allocated, +binds the closed reason `NO_ATTEMPT_ALLOCATED`, and can never enable Job +success. Both evaluation events are state-neutral and cannot rewrite an +Attempt terminal fact. + +### Job state machine + +Job states are: + +| State | Meaning | +| --- | --- | +| `SUBMITTED` | The closed Job and all immutable bindings were validated and durably appended. | +| `QUEUED` | The Job is eligible for Attempt allocation. | +| `ACTIVE` | An Attempt has been allocated and its retry or Job outcome has not yet been resolved. | +| `RETRY_WAIT` | The preceding Attempt is terminal and a permitted retry is waiting for its recorded eligibility time. | +| `STOPPING` | A terminal trigger is latched and no new Attempt may be allocated. | +| `SUCCEEDED` | One Attempt passed computation, result, termination, and cleanup checks and the separate Job assurance evaluation made its immutable Outcome acceptance-eligible. | +| `FAILED` | The Job ended without an eligible result for a non-policy, non-timeout, non-cancellation reason. | +| `CANCELLED` | An explicit cancellation won terminal ordering and shutdown completed or was safely quarantined. | +| `TIMED_OUT` | The Job deadline won terminal ordering. | +| `POLICY_VIOLATION` | A security or policy violation was established before terminalization. | + +Legal Job transitions are: + +The exact `job.stop_latched` cause-code enum is `CANCEL_REQUESTED`, +`JOB_DEADLINE`, `POLICY_VIOLATION`, `ADMISSION_INVALID`, +`FATAL_INFRASTRUCTURE`, `INTEGRITY_FAILURE`, `CLOCK_UNCERTAIN`, +`ATTEMPT_NONRETRYABLE`, `ATTEMPT_REJECTED`, `RETRY_EXHAUSTED`, +`RESULT_REQUIREMENT_FAILED`, `OUTPUT_VALIDATION_FAILED`, +`TERMINAL_SOURCE_RETENTION_FAILED`, `TERMINATION_FAILED`, `CLEANUP_FAILED`, `ASSURANCE_UNMET`, +`ASSURANCE_UNVERIFIABLE`, and `BUDGET_EXHAUSTED`. + +| From | To | Required journal event and guard | +| --- | --- | --- | +| none | `SUBMITTED` | `job.submitted`; exact contracts, Ward decision, approval, freshness, idempotency, and deadline validate. | +| `SUBMITTED` | `QUEUED` | `job.queued`; admission remains valid. | +| `QUEUED` | `ACTIVE` | `job.attempt_allocated`; atomically creates one new Attempt in `CREATED`, allocates a new fencing generation and exact budget reservation, and confirms no other non-terminal Attempt. | +| `ACTIVE` | `RETRY_WAIT` | `job.retry_scheduled`; preceding Attempt is terminal, assurance evaluation and budget settlement are complete, its operational outcome is retryable, a next reservation can fit, and retry eligibility time is recorded. | +| `RETRY_WAIT` | `QUEUED` | `job.retry_ready`; eligibility time arrived, contracts remain valid, and Job deadline has not passed. | +| `SUBMITTED`, `QUEUED`, `RETRY_WAIT` | `STOPPING` | `job.stop_latched`; the cause is one of `CANCEL_REQUESTED`, `JOB_DEADLINE`, `POLICY_VIOLATION`, `ADMISSION_INVALID`, `FATAL_INFRASTRUCTURE`, `INTEGRITY_FAILURE`, or `CLOCK_UNCERTAIN`. | +| `ACTIVE` | `STOPPING` | `job.stop_latched`; any exact cause is legal when its bound evidence establishes the external trigger, a non-retryable/rejected Attempt, exhausted retry or budget, failed result/output/termination/cleanup, or completed `UNMET`/`UNVERIFIABLE` Job assurance evaluation. | +| `ACTIVE` | `SUCCEEDED` | `job.succeeded`; the selected Attempt is terminal `SUCCEEDED`, its result requirement and frozen storage observations pass, `CODE_MODIFICATION` terminal source is `VERIFIED`, budget settlement and `job.assurance_evaluated: CLAIMED` are complete, the claim satisfies the exact request, and no precedence conflict exists. | +| `STOPPING` | `FAILED` | `job.failed`; budget settlement where applicable and one `job.assurance_evaluated` are complete, and the cause resolves to infrastructure, validation, exhaustion, cleanup, or assurance failure. | +| `STOPPING` | `CANCELLED` | `job.cancelled`; explicit cancellation is the winning cause and one `job.assurance_evaluated` is complete. | +| `STOPPING` | `TIMED_OUT` | `job.timed_out`; Job deadline is the winning cause and one `job.assurance_evaluated` is complete. | +| `STOPPING` | `POLICY_VIOLATION` | `job.policy_violated`; a policy violation is established and one `job.assurance_evaluated` is complete. | + +A non-retryable or exhausted Attempt first moves the Job from `ACTIVE` to +`STOPPING` with its precise cause, then to the applicable terminal state. +The v1 `queue_key` has exactly `ready_sequence` and `job_id`. +`ready_sequence` is the enclosing `job.queued` or `job.retry_ready` sequence, +assigned before Sigil computation. Among Jobs eligible for a particular +verified Worker Session, allocation order is ascending +`(ready_sequence, job_id unsigned ASCII)`. There is no hidden priority, +directory-order, clock-time, or process-local tie breaker. +Terminal Job events include all Attempt IDs and terminal states, the selected +result disposition and assurance-claim Sigils if any, cleanup or quarantine +status, selected Worker Session binding, first stop-or-fence binding, frozen +storage-observation ID-and-Sigil binding, the budget ledger Sigil, and the +`job.assurance_evaluated` event Sigil. No Job terminal event can precede that +evaluation. A cancellation against an already terminal Job appends the +state-neutral `job.cancellation_observed` event described below and never +changes this state machine. + +### Attempt state machine + +Attempt states are: + +| State | Meaning | +| --- | --- | +| `CREATED` | Identity, retry ordinal, fence generation, and immutable bindings exist. | +| `PREFLIGHTING` | Policy, Snapshot, backend, assurance eligibility, inputs, and Crucible are being verified or materialized. | +| `READY` | Preflight passed and the exclusive Crucible is eligible for a Lease. | +| `LEASED` | A Lease was offered and claimed by the bound Worker. | +| `STARTING` | The backend is creating the process tree and control handles. | +| `RUNNING` | Worker execution is live under the current Lease. | +| `DRAINING` | The process reported completion or exited; logs, outputs, and runtime evidence are being closed and validated. | +| `STOPPING` | Authority is revoked or a terminal trigger requires process and handle shutdown. | +| `CLEANING` | Termination status, mutable-resource isolation, output quarantine/publication, and cleanup evidence are being finalized. | +| `SUCCEEDED` | Computation, the closed result requirement, termination, and required cleanup passed; realized assurance is evaluated afterward without changing this terminal state. | +| `FAILED` | Computation, result validation, output bounds, required terminal-source retention, infrastructure, termination, or cleanup failed. | +| `CANCELLED` | Explicit cancellation won terminal ordering. | +| `TIMED_OUT` | The Attempt or Job wall-time deadline won terminal ordering. | +| `POLICY_VIOLATION` | A policy or security boundary violation was established. | +| `LEASE_EXPIRED` | Lease expiry won terminal ordering before valid completion. | +| `LOST` | Worker/process ownership or termination could not be established; affected resources are quarantined. | +| `FENCED` | Recovery or authority replacement invalidated the Attempt before completion. | +| `REJECTED` | Preflight failed before Worker authority was granted. | + +Legal Attempt transitions are: + +| From | To | Required journal event and guard | +| --- | --- | --- | +| none | `CREATED` | `job.attempt_allocated`; the same event atomically changes the Job to `ACTIVE`, creates the Attempt, assigns an unused ordinal, and allocates a fencing generation greater than every prior generation for the Job. | +| `CREATED` | `PREFLIGHTING` | `attempt.preflight_started`; immutable bindings revalidate and per-Attempt authorization is `NONE` or `BOUND`. | +| `CREATED` | `STOPPING` | `attempt.stop_latched`; cancellation, deadline, recovery fence, fatal integrity, or budget invalidation was committed before preflight began. | +| `PREFLIGHTING` | `READY` | `attempt.preflight_passed`; all required checks and immutable materialization identities are durably recorded. | +| `PREFLIGHTING` | `STOPPING` | `attempt.stop_latched`; preflight rejection, cancellation, deadline, or policy failure. | +| `READY` | `LEASED` | `lease.claimed`; the same event atomically activates the one offered Lease and accounts for the selected Worker Session's capacity. | +| `READY` | `STOPPING` | `attempt.stop_latched`; cancellation, deadline, unclaimed Lease-offer/claim expiry, recovery fence, or loss of preflight validity. | +| `LEASED` | `STARTING` | `attempt.starting`; active Lease and backend start handle are recorded. | +| `LEASED` | `STOPPING` | `attempt.stop_latched`; active Lease expiry or authority loss, cancellation, timeout, policy revocation, or recovery fence. | +| `STARTING` | `RUNNING` | `attempt.running`; exact process-tree identity and active control handles are recorded before Worker code proceeds. | +| `STARTING` | `STOPPING` | `attempt.stop_latched`; start failure, cancellation, timeout, Lease loss, policy violation, or recovery fence. | +| `RUNNING` | `DRAINING` | `attempt.draining`; a preceding `attempt.result_accepted` or `attempt.result_rejected` disposition, or the explicit absence of a Worker result after observed process exit, is durably bound. | +| `RUNNING` | `STOPPING` | `attempt.stop_latched`; cancellation, deadline, Lease/heartbeat loss, policy violation, or recovery fence. | +| `DRAINING` | `CLEANING` | `attempt.cleaning`; result, logs, outputs, and runtime evidence are closed or explicitly quarantined. | +| `DRAINING` | `STOPPING` | `attempt.stop_latched`; a still-live process or handle requires revocation because validation exposed a violation. | +| `STOPPING` | `CLEANING` | `attempt.cleaning`; termination/revocation was verified or ambiguous resources were quarantined. | +| `CLEANING` | any terminal Attempt state | Matching `attempt.` event; terminal precedence, result disposition, computation, termination, cleanup, quarantine, immutable assurance inputs, and the exact durable output-storage-observation-set reference are complete; realized assurance remains a later evaluation. | + +Every terminal Attempt requires `storage_observation_binding: FROZEN`, +including a preflight rejection or an Attempt with no Result, no outputs, +three empty Log streams, or no Blob-bearing resource evidence; those cases use +the observation contract's explicit `NONE` and `EMPTY` branches. +`attempt.succeeded` additionally requires an eligible storage disposition for +every required output and, under `CODE_MODIFICATION`, +`terminal_source_binding: VERIFIED`. A quarantined or unavailable required +output or terminal source selects `attempt.failed` with the precise +validation, retention, or cleanup cause; it can never produce Job +`SUCCEEDED`. + +For `CODE_MODIFICATION`, source retention verification or quarantine finishes +before `attempt.cleaning` commits. That event freezes the exact +`terminal_source_binding`; cleanup progress cannot replace it, and the +Attempt terminal event and selected Job terminal event repeat it byte for +byte. A crash after `attempt.cleaning` therefore resumes other missing cleanup +without recrawling a mutable Crucible or changing the source disposition. + +`PREFLIGHTING`, `DRAINING`, `STOPPING`, and `CLEANING` may record their +bounded progress events without changing state. `attempt.cleanup_progressed` +is the shared finalization-progress event in `DRAINING`, `STOPPING`, or +`CLEANING`; its closed `step` determines which state's evidence is legal. +Progress may not skip required evidence. No transition goes directly from a +live state to terminal because termination and cleanup must be evaluated +first. +Before any terminal Attempt event, `CLEANING` also commits exactly one +`attempt.cleanup_progressed` with `step: ACCOUNTING_CAPTURED`, binding the +trusted supervisor's final per-dimension accounting evidence or explicit +unavailability. In that branch, `cleanup_evidence_sigil` is the complete +immutable accounting-evidence-set Sigil from which every later settlement +field is derived, and `finalization_bindings` is +`FROZEN {kind, control_evidence_set_binding, +quarantine_binding_set_binding, terminalization_storage_manifest_binding, +output_root_protection}`. The three record bindings are their exact +resolvable ID-and-Sigil pairs; all referenced closed documents and the +selected hold, when present, are durable before the event. Every earlier cleanup-progress step instead has +`finalization_bindings: NONE {kind}`. `ACCOUNTING_CAPTURED` is legal only in +`CLEANING`, after every metered backend, retention, output, evidence, +Quarantine, and cleanup action is finished. After it commits, only the bounded +immutable terminalization payloads reserved as Executor control overhead, +journal events, settlement, and assurance/Job evaluation may follow; any need +for further metered Blob, Replica, backend, retention, output, evidence, +Quarantine, or cleanup work prevents terminalization rather than silently +escaping the capture. The terminal event cannot precede that immutable +capture. + +`attempt.result_accepted` is state-neutral and legal only in `RUNNING`. +`attempt.result_rejected` is state-neutral: the first result disposition may +occur in `RUNNING`, and a novel late or conflicting message may be recorded in +any later state. Exactly one first disposition is committed before +`attempt.draining` when a Worker result arrives. Acceptance validates the +current Lease and stores the immutable result Sigil; rejection stores the +result or message Sigil and closed reasons. An exact duplicate returns the +historical disposition without appending an event, changing revisions, +restoring liveness, or reactivating Lease authority. A conflicting second +result appends `attempt.result_rejected` only when it is a new bounded protocol +message; it never replaces the first disposition. + +An Attempt terminal event preserves separately: + +- the winning terminal trigger and its journal sequence; +- Worker-reported status and process exit information; +- exact Worker ID, Worker binding Sigil, Worker Session ID, and Session binding + Sigil, or explicit absence before authority; +- Lease terminal state; +- the first stop-or-fence event ID, Sigil, type, and effective sequence, or + explicit absence; +- process-tree termination status; +- cleanup or quarantine status; +- log and output closure status; +- frozen Storage Journal prefix and immutable, resolvable + output-storage-observation-set ID and Sigil; +- accepted or rejected result disposition and rejection reasons; +- requested assurance and the pending assurance-evaluation status; and +- the exact frozen control-evidence-set, quarantine-binding-set, output ESM, + and output-root-protection bindings bound by the final accounting event. + +For `CODE_MODIFICATION`, it additionally preserves the immutable Crucible Base +identity and Sigil, retained terminal-source identity, Sigil, and storage +BlobRef, exact +retention-policy Sigil, file and byte counts, storage or quarantine status, +and verifier-evidence Sigil. Those conditional fields are absent for `NONE`. + +This prevents a `CANCELLED`, `LOST`, or `LEASE_EXPIRED` label from hiding a +computation failure, cleanup failure, or policy observation. + +### Lease state machine + +Lease states are: + +| State | Meaning | +| --- | --- | +| `OFFERED` | A time-bounded authority is addressed to exactly one immutable Worker Session but is not yet claimed. | +| `ACTIVE` | The bound Worker Session proved possession of the Lease credential before the claim deadline. | +| `RELEASED` | The Worker and Executor completed normal handoff and no further use is valid. | +| `REVOKED` | Cancellation, timeout, policy change, or fatal failure withdrew authority. | +| `EXPIRED` | The claim or active Lease deadline passed before a valid release. | +| `FENCED` | Executor recovery or authority replacement invalidated the Lease generation. | + +Legal Lease transitions are: + +| From | To | Required journal event and guard | +| --- | --- | --- | +| none | `OFFERED` | `lease.offered`; Attempt is `READY`, Worker is `READY` or `BUSY` below verified capacity, and the Lease bindings and credential digest are durable. | +| `OFFERED` | `ACTIVE` | `lease.claimed`; exact Worker Session, credential proof, Executor epoch, fence generation, and claim deadline validate. | +| `OFFERED` | `EXPIRED` | `lease.expired`; claim deadline passed before a valid claim. | +| `OFFERED` | `REVOKED` | `lease.revoked`; Job or Attempt stop trigger was committed. | +| `OFFERED` | `FENCED` | `lease.fenced`; Executor epoch changed or ownership became ambiguous. | +| `ACTIVE` | `ACTIVE` | `lease.renewed`; explicit renewal committed before expiry and within all maximum deadlines. | +| `ACTIVE` | `RELEASED` | `lease.released`; process tree and side-effect handles are closed and Worker completion handoff is durable. | +| `ACTIVE` | `REVOKED` | `lease.revoked`; cancellation, timeout, policy revocation, or fatal failure withdrew authority. | +| `ACTIVE` | `EXPIRED` | `lease.expired`; trusted Executor time reached expiry before release or renewal committed. | +| `ACTIVE` | `FENCED` | `lease.fenced`; Executor epoch changed or recovery invalidated ownership. | + +Renewal changes only the active Lease projection's expiry, renewal counter, and +revision. It does not create a new Lease, rotate the fence generation, or +erase earlier expiry values. A terminal Lease can never be renewed, reclaimed, +or released a second time. + +`lease.expired` commits before its causally linked +`attempt.stop_latched`. For an expired `OFFERED` Lease the Attempt is still +`READY`, and the next event uses the legal `READY -> STOPPING` path with cause +`LEASE_CLAIM_EXPIRED`. For an expired `ACTIVE` Lease it uses the Attempt's +current authority-bearing state with cause `LEASE_ACTIVE_EXPIRED`. A crash +between the two events leaves a replay-visible required action that Recovery +must complete; no Worker claim or message can enter that prefix. + +### Worker-definition and Worker-Session state machines + +A stable Worker definition and a live Worker Session have separate +projections. Worker-definition states are: + +| State | Meaning | +| --- | --- | +| `REGISTERED` | One immutable definition revision is recorded but not enabled. | +| `ENABLED` | The current definition revision may create eligible Sessions. | +| `DRAINING` | No new Session or Lease may be admitted; existing Sessions are being closed. | +| `QUARANTINED` | Definition, verification, or policy failure prevents use. | +| `RETIRED` | The Worker identity is administratively terminal. | + +Legal Worker-definition transitions are: + +| From | To | Required event and guard | +| --- | --- | --- | +| none | `REGISTERED` | `worker.definition_registered`; definition revision zero and its exact Sigil validate. | +| `REGISTERED` | `ENABLED` | `worker.enabled`; independent capability verification is complete. | +| `REGISTERED` | `QUARANTINED`, `RETIRED` | `worker.quarantined` or `worker.retired`; validation fails or administration retires it. | +| `ENABLED` | `DRAINING`, `QUARANTINED` | `worker.draining` or `worker.quarantined`; all affected Sessions atomically stop accepting Leases. | +| `ENABLED` | `RETIRED` | `worker.retired`; no non-terminal Session or Lease remains. | +| `DRAINING` | `ENABLED` | `worker.enabled`; a new immutable definition revision is bound and revalidated before admission resumes. | +| `DRAINING` | `QUARANTINED`, `RETIRED` | `worker.quarantined` or `worker.retired`; retirement requires every Session and Lease terminal. | +| `QUARANTINED` | `ENABLED` | `worker.enabled`; an explicit new immutable definition revision and independent revalidation pass. | +| `QUARANTINED` | `RETIRED` | `worker.retired`; every Session and Lease is terminal. | + +`RETIRED` has no outbound transition. Definition revisions never carry +liveness or Lease capacity. + +Worker-Session states are: + +| State | Meaning | +| --- | --- | +| `REGISTERED` | One immutable Session is bound to the current Executor epoch but not schedulable. | +| `READY` | Verification and liveness are current and no active Lease is held. | +| `BUSY` | One or more active Leases are held below or at the Session ceiling. | +| `DRAINING` | No new Lease is accepted while existing work is revoked or completed. | +| `OFFLINE` | Liveness is lost or the bound Executor epoch is obsolete. | +| `QUARANTINED` | Session identity, protocol, policy, or evidence failed. | +| `CLOSED` | The Session is terminal and can never receive or renew authority. | + +Legal Worker-Session transitions are: + +| From | To | Required event and guard | +| --- | --- | --- | +| none | `REGISTERED` | `worker_session.registered`; fresh Session, backend-session, and control-channel identities, current epoch, enabled definition, and exact bindings validate. | +| `REGISTERED` | `READY`, `OFFLINE`, `QUARANTINED`, `CLOSED` | `worker_session.ready`, `worker_session.offline`, `worker_session.quarantined`, or `worker_session.closed`. | +| `READY` | `BUSY` | `lease.claimed`; capacity accounting is atomic with Lease and Attempt transitions. | +| `READY` | `DRAINING`, `OFFLINE`, `QUARANTINED`, `CLOSED` | Matching `worker_session.` event; direct close requires no active Lease. | +| `BUSY` | `BUSY` | `lease.claimed` below capacity, or one of several Leases terminalizes while another remains active. | +| `BUSY` | `READY` | A Lease-terminal event releases the final capacity slot while definition and Session remain eligible. | +| `BUSY` | `DRAINING`, `OFFLINE`, `QUARANTINED` | Matching Session event atomically prevents admission and latches revocation or fencing for every active Lease. | +| `DRAINING` | `OFFLINE`, `QUARANTINED`, `CLOSED` | Matching Session event; close requires every Lease terminal. Revalidation creates a new Session rather than returning this one to `READY`. | +| `OFFLINE`, `QUARANTINED` | `CLOSED` | `worker_session.closed`; all Leases and backend handles are terminal, revoked, fenced, or quarantined. | + +`CLOSED` has no outbound transition. `OFFLINE` and `QUARANTINED` Sessions +cannot receive or renew a Lease and can only close. A returning process or +re-established channel creates a new Session ID; it never reactivates an old +Session or Lease. + +## Fencing and exclusive side effects + +Each Job owns a monotonically increasing unsigned 64-bit `fencing_counter` and +a durable `fence_floor`. Allocation of every Attempt atomically increments the +counter, assigns that value as the Attempt's `fencing_generation`, and moves +the floor to it. Release, revocation, expiry, or fencing of a Lease atomically +increments the counter again and publishes the new value as an unassigned +fence tombstone. This makes the preceding credential stale even when no +replacement Attempt exists. A later Attempt receives a value strictly greater +than the tombstone. Counter exhaustion fails the Job closed; it never wraps. + +Executor recovery also advances a global unsigned 64-bit `executor_epoch`. +The effective public fence tuple for an active Attempt is: + +```text +(journal_id, executor_epoch, job_id, attempt_id, lease_id, + fencing_generation) +``` + +The unguessable Lease credential proves possession but is not the fencing +order. Every Worker message repeats the public fence tuple and proves the +credential through the protected control channel. A valid credential with a +stale tuple is rejected; a current tuple without credential proof is rejected. + +The Executor and each side-effecting sink maintain the highest accepted +Executor epoch and the Job's current fence floor. They reject generations +below the floor and reject a different Attempt or Lease at an assigned current +generation. A tombstone has no valid Attempt or Lease. A new assigned +generation is published durably before its credential is delivered, and every +tombstone is published before revocation is acknowledged. No Worker controls +or increments either counter. + +Result fencing is mandatory at every assurance level: only the current, +unexpired, active Lease may contribute Worker-originated result, log, output, +or runtime observations. Late material is retained or quarantined with a +rejection reason but cannot become eligible through retry or manual +relabeling. After Lease termination, only the trusted Executor, backend, or +evidence verifier may append termination, revocation, cleanup, and assurance +evidence, bound to both the Attempt generation and the terminal fence +tombstone. + +Result acceptance is evaluated at the journal sequence of +`attempt.result_accepted`. If the Lease was current, active, unexpired, and +unrevoked at that sequence, the accepted result remains historically +well-fenced evidence after a later normal `lease.released` and the mandatory +higher fence-floor tombstone. The later tombstone does not retroactively turn +that historical acceptance into a stale message, and replay must preserve the +original disposition. + +Historical acceptance is necessary but not sufficient for Outcome selection. +An accepted result can be selected only when: + +1. no `job.stop_latched`, `attempt.stop_latched`, `lease.revoked`, + `lease.expired`, `lease.fenced`, `executor.clock_uncertain`, or + policy/integrity trigger has an effective trigger sequence no later than + the result-acceptance sequence; +2. no later evidence proves that authority had already been lost when the + result was accepted; +3. no conflicting second result or output identity exists; +4. the normal termination, release, cleanup, budget settlement, and assurance + evaluation complete; and +5. the Attempt and Job terminal guards select that exact acceptance-event and + result Sigil. + +Normal release or a tombstone committed after accepted completion satisfies +fence closure rather than violating item 1. A duplicate result delivered +after release, revocation, expiry, fencing, restart, or terminalization returns +the original historical disposition only; it cannot restore authority, append +a second acceptance, or make the result newly selectable. + +At `SANCTUM-A2`, result fencing alone is not sufficient. On cancellation, +timeout, policy revocation, heartbeat loss, Lease expiry, or recovery, the +backend must: + +1. prevent new process creation; +2. terminate the complete process tree; +3. revoke filesystem, network, credential, IPC, output, and external-sink + handles; +4. publish the revocation generation to every sink that supports fencing; +5. verify termination and revocation independently of Worker testimony; and +6. quarantine any resource whose exclusivity cannot be proved. + +A sink that cannot revoke an issued handle must validate the current fence +tuple against Executor state on every side-effecting operation. A sink that +can do neither is ineligible for A2 and cannot be shared with a replacement +Attempt. + +At A1, the supervisor must still enforce wall-time and cancellation for the +tracked cooperative process group and must not reuse an ambiguously owned +Crucible or output namespace. A1 makes no hostile-containment claim. At A0, +stale result rejection and operational identity remain mandatory even though +process containment is not claimed. + +## Heartbeat and renewal protocol + +Worker and Lease liveness are separate: + +- a Worker heartbeat proves only that the registered Worker control session + responded; +- a Lease heartbeat reports bounded progress and resource observations for one + Attempt under the current fence tuple; +- neither heartbeat extends a Lease; and +- a Worker heartbeat never proves that its process tree, policy controls, or + side-effect handles are healthy. + +The `execution-heartbeat/1.0` common object has exactly these required +members: `schema_version`, `scope`, `worker_id`, `worker_session_id`, +`worker_session_binding_sigil`, `executor_epoch`, `sequence`, +`prior_accepted_sequence`, `status`, `resource_sample`, `observed_at`, and +`message_sigil`. `scope` is `WORKER` or `LEASE`; `prior_accepted_sequence` is +the preceding unsigned sequence or `null` only for sequence zero. The +resource sample is a closed bounded object with exact CPU, memory, storage, +process, and network entries. Every entry has exactly `status` and `value`; +`status` is `OBSERVED` with a non-negative integer value or `UNAVAILABLE` with +`value: null`. A denied and observed facility reports zero rather than +`UNAVAILABLE`. + +For `LEASE`, the fields `job_id`, `attempt_id`, `lease_id`, +`lease_binding_sigil`, `fence_tuple`, and `credential_proof` are all required. +For `WORKER`, all six are absent. `challenge_response` is required exactly +when the active Session policy names an outstanding Executor challenge and is +otherwise absent. No Worker-instance or session-generation field exists. + +The Executor records its receive time. For each scope: + +- a new sequence with valid bindings is appended before it updates liveness; +- an exact duplicate sequence and message Sigil is idempotent; +- a duplicate sequence with different content is a protocol violation; +- a lower sequence is stale and rejected; +- a skipped sequence is recorded as a gap but may be accepted only when the + profile declares loss-tolerant heartbeats; and +- heartbeats arriving after a committed stop, Lease expiry, session change, or + Executor epoch change are late and cannot restore liveness. + +The Execution Specification sets `heartbeat_interval_seconds` and +`heartbeat_timeout_seconds` within the assurance profile's limits. Heartbeat +timeout must be shorter than the active Lease duration. When the trusted +receive-time deadline passes, the Executor first appends the due liveness-loss +event, then revokes or fences the Lease. It does not accept a concurrently +arriving heartbeat first merely because deadline processing was delayed. + +A Worker may request renewal, but only the Executor grants it. Renewal is +valid only when: + +1. the Lease is `ACTIVE` and the request arrives and commits before its current + expiry; +2. Worker Session, credential proof, Executor epoch, fence tuple, heartbeat + sequence, and Lease revision match; +3. Job and Attempt are not stopping or terminal; +4. Snapshot, policy, backend eligibility, and any revocation state remain + valid; +5. the latest required heartbeat is within its deadline; +6. the proposed expiry is later than the current expiry but no later than the + Attempt deadline, Job deadline, Lease maximum expiry, or policy ceiling; and +7. the durable `lease.renewed` event commits before the Executor acknowledges + renewal. + +A renewal request after expiry is rejected even if the Worker believes it was +sent earlier. Clock skew cannot resurrect authority. The Worker clock is not +used to decide expiry. + +## Cancellation, timeouts, and terminal ordering + +Cancellation is an idempotent Job-scoped request containing a cancellation +request ID, idempotency-key Sigil, actor and Host provenance, bounded reason, +request time, Job ID and binding Sigil, and expected Job revision. RFC-0015 +defines its transport. This RFC defines its effect. + +Every Job or Attempt deadline, Lease claim or expiry, heartbeat timeout, retry +eligibility time, and cancellation grace has one durable UTC `due_at`. On +process start, and after each newly committed or renewed due time, the +Executor creates an in-process anchor: + +```text +(trusted_utc_at_anchor, monotonic_at_anchor, due_at) +remaining = due_at - trusted_utc_at_anchor +``` + +If `remaining <= 0`, the deadline is already due. Otherwise the live timer +expires when monotonic elapsed time reaches `remaining`; a later wall-clock +adjustment cannot extend it. Monotonic values are process-local observations +and are never persisted or compared across restart. After restart, replay +reads each durable `due_at`, verifies the current UTC clock against the last +trusted journal time, and establishes fresh monotonic anchors before any +authority can be issued. + +Before committing any heartbeat, renewal, result, cancellation, or scheduling +transition, the Executor collects every already-due deadline and commits them +in ascending exact key order: + +```text +(due_at, fixed_priority, entity_id) +``` + +UTC text is compared as its normalized instant and `entity_id` by unsigned +ASCII bytes. The closed priorities, lowest first, are: + +| Priority | Deadline kind | Entity ID | +| --- | --- | --- | +| 10 | `JOB_DEADLINE` | Job ID | +| 20 | `ATTEMPT_DEADLINE` | Attempt ID | +| 30 | `HEARTBEAT_TIMEOUT` | Worker Session ID or Lease ID | +| 40 | `LEASE_EXPIRY` | Lease ID | +| 50 | `LEASE_CLAIM_DEADLINE` | Lease ID | +| 60 | `CANCELLATION_GRACE` | Attempt ID | +| 70 | `RETRY_ELIGIBILITY` | Job ID | + +Only a deadline applicable to the replayed state participates: +`JOB_DEADLINE` applies to `SUBMITTED`, `QUEUED`, `ACTIVE`, or `RETRY_WAIT`; +`ATTEMPT_DEADLINE` applies to `CREATED`, `PREFLIGHTING`, `READY`, `LEASED`, +`STARTING`, `RUNNING`, or `DRAINING`; Worker-Session heartbeat timeout applies +to `READY`, `BUSY`, or `DRAINING`; Lease heartbeat timeout and +`LEASE_EXPIRY` apply only to `ACTIVE`; `LEASE_CLAIM_DEADLINE` applies only to +`OFFERED`; and cancellation grace applies to `STOPPING` only until +`FORCE_TERMINATION_DUE` is committed. `RETRY_ELIGIBILITY` applies only to +`RETRY_WAIT`. A state transition in an earlier key removes any +now-inapplicable later key before the next due item is selected. + +Processing one key also commits its mandatory dependent stop/fence events +under the same writer lock before selecting the next key. Those dependent +events use the primary event as their `PRIOR_EVENT` trigger and copy its +effective sequence. A crash between the primary and dependent events leaves a +deterministic Recovery action for that exact propagation. Consequently, for +example, a simultaneous Job and Attempt deadline latches `JOB_DEADLINE` on +both Job and Attempt, while a heartbeat timeout ordered before Lease expiry +latches the heartbeat-loss chain rather than relabeling it as expiry. + +The sole append-order exception is Recovery `STARTED`: authority is already +globally gated, so its `COMMIT_DUE_EVENT` actions commit the causally surviving +primary due events in the exact order produced by the virtual-closure sweep +above. The subsequently derived `FENCING` action set materializes each +previously simulated dependent Lease, Attempt, Session, and Job transition +before any authority can resume. No request or ordinary event may interleave. +Each dependent transition identifies its earliest applicable primary event as +the `PRIOR_EVENT` trigger and copies that event's effective sequence. This +two-phase representation has exactly the same winning causes and surviving +due keys as immediate propagation, while preserving the invariant that one +Recovery action owns one reserved target event. + +Before a pending Cancel request is compared, each due chain that affects its +Job advances that Job revision exactly once: directly through +`job.stop_latched`, through a Lease-terminal fence-floor update, or, when no +Lease event exists, through the parent ordering projection in +`attempt.stop_latched`. Thus an already-due Attempt deadline cannot leave the +same expected Job revision and be relabeled by a concurrent cancellation. + +This key, not timer callback order, directory order, thread order, or receive +timestamp, decides simultaneous due work. After all due events commit, +journal sequence decides genuinely non-due concurrent triggers. The first +committed terminal trigger is latched and cannot be replaced merely to obtain +a more convenient status. + +There are four independent authority bounds plus one stop-escalation bound: + +| Bound | Effect | +| --- | --- | +| Job `deadline_due_at` | Stops the Job and prevents all retries. | +| Attempt `deadline_due_at` | Stops only the current Attempt; retry depends on policy and remaining Job time. | +| Lease `claim_due_at` | Expires an unclaimed offer and terminates that Attempt. | +| Lease expiry or heartbeat `due_at` | Removes Worker authority and terminates or quarantines the Attempt. | +| Attempt `grace_due_at` | Ends cooperative cancellation grace and invokes mandatory termination without changing the already latched cause. | + +The exact durable calculations use checked UTC arithmetic: + +```text +job.deadline_due_at = + job.submitted_at + job_wall_time_seconds +attempt.deadline_due_at = + min(attempt.created_at + attempt_wall_time_seconds, + job.deadline_due_at) +lease.claim_due_at = + min(lease.offered_at + lease_claim_timeout_seconds, + attempt.deadline_due_at, job.deadline_due_at) +lease.initial_expiry_due_at = + min(lease.offered_at + lease_duration_seconds, + lease.maximum_expiry_due_at, + attempt.deadline_due_at, job.deadline_due_at) +worker_session_heartbeat.initial_due_at = + worker_session.ready.recorded_at + heartbeat_timeout_seconds +worker_session_heartbeat.next_due_at = + last_accepted_receive_time + heartbeat_timeout_seconds +lease_heartbeat.next_due_at = + min(last_accepted_receive_time + heartbeat_timeout_seconds, + lease.current_expiry_due_at, + attempt.deadline_due_at, job.deadline_due_at) +attempt.grace_due_at = + attempt.stop_latched.recorded_at + cancellation_grace_seconds +``` + +`lease.maximum_expiry_due_at` is the immutable minimum of the Attempt, Job, +and resolved policy ceilings. A renewal's `new_expiry_due_at` is the minimum +of its validated proposal and those same ceilings and must be strictly later +than current expiry. Checked-arithmetic failure rejects creation or renewal; +it never saturates to a later time. `worker_session.ready` durably binds the +initial heartbeat due time before the Session is schedulable. The +Worker-Session heartbeat branch has no Lease, Attempt, or Job ceiling. When +cancellation grace becomes due, the Executor appends +`attempt.stop_progressed` with `step: FORCE_TERMINATION_DUE` before invoking +the mandatory backend path. + +If UTC moves backward beyond `clock_uncertainty_tolerance_seconds`, wall and +monotonic elapsed time diverge beyond that tolerance, the monotonic source +resets, suspend/resume behavior is unknowable, or UTC trust otherwise cannot +be established, the Executor must commit `executor.clock_uncertain` using a +non-decreasing time derived from its last trusted anchor. Outside the initial +startup clock check, including during an active Recovery, it does so under the +writer lock, immediately gates authority, and appends a strictly greater +`executor.epoch_started`. At startup, step 3 below has already created a fresh +gated epoch; uncertainty found by step 4 appends +`executor.clock_uncertain` in that epoch and does not allocate a redundant +second epoch for the same observation. It then follows exactly one branch: + +- with no active Recovery, it derives and durably stores a new `STARTED` + action set and appends `recovery.started` with a fresh `recovery_id` and the + most recent completed Recovery as `prior_recovery_id`; or +- with an incomplete Recovery, it retains that `recovery_id` and phase, + derives a replacement current-phase action set from the new prefix, and + appends `recovery.action_set_rebased` before any further action completion. + +That new or rebased runtime Recovery: + +1. disables scheduling, Session readiness, Lease offer, claim, renewal, result + acceptance, and new side-effect authority; +2. fences every `OFFERED` or `ACTIVE` Lease and publishes its tombstone; +3. moves every affected non-terminal Attempt through `STOPPING`; +4. treats a durable deadline as due whenever it cannot prove that the deadline + remains in the future; and +5. terminates or quarantines live processes and handles. + +Clock uncertainty never lengthens authority. `executor.clock_restored` may +commit only after a trusted UTC source and fresh monotonic anchor are +validated within that Recovery. It does not restore any old Session, Lease, or +Attempt authority; the new Recovery must reach `COMPLETED`, and normal new +Session/Attempt identities are still required. A crash anywhere in this +ordering replays the clock gate and either continues the incomplete Recovery +or creates only the missing action-set start/rebase event; it never creates a +second active Recovery or resumes the pre-uncertainty epoch. + +An explicit cancellation committed before a due deadline wins over a later +timeout. A due deadline committed first cannot be relabeled cancellation. +Policy violations discovered before terminal commitment are an escalation: +they are always retained, make the result ineligible, and terminalize the +Attempt and Job as `POLICY_VIOLATION` even if another stop trigger was already +latched. Ambiguous process ownership terminalizes the Attempt as `LOST` and +forces quarantine; it is never reported as clean cancellation or timeout. + +For a non-terminal Job, the first valid Cancel request is bound by +`job.stop_latched`, including `cancellation_request_id`, +`idempotency_key_sigil`, complete `cancel_request_sigil`, actor/Host +provenance, reason, and expected revision. An exact replay returns that +original cancellation record and a current observation without another event. +Conflicting reuse of either identity or key fails closed. + +For a Job already terminal when the request is evaluated, and only when +`expected_job_revision` equals that terminal revision, the Executor appends +exactly one `job.cancellation_observed` for that request binding. The event +carries the same exact fields plus the existing terminal event ID and Sigil. +It is state-neutral: the Job state, cause, terminal event, and revision do not +change. Exact replay returns the original observation event; a conflicting key +is rejected. + +If mandatory due-deadline processing changes state or revision before the +Cancel comparison, the request returns RFC-0015 `EXECUTION_CONFLICT` with the +new revision and appends no cancellation event. A later retry using the new +matching terminal revision may append `job.cancellation_observed`. Thus a +deadline is never relabeled cancellation and restart never turns a terminal +no-op into a cancellation transition. + +After a stop trigger, no new Lease, renewal, retry, log stream, or result can +become eligible. The Executor requests graceful cooperative stop only for the +bounded cancellation grace. It then invokes the backend's mandatory +termination path. A2 termination and handle revocation must be independently +verified. + +Terminal classification does not erase secondary facts. For example, a +cancelled Attempt can retain `cleanup_status: FAILED`, and a timed-out Attempt +can retain an observed non-zero exit. A computation that otherwise succeeded +but fails result validation, output bounds, or required cleanup terminalizes as +`FAILED`, with the computation outcome retained separately. Requested +assurance is evaluated only after terminalization; an unmet request makes the +result ineligible and the Job `FAILED` without rewriting the Attempt. + +## Retry and resume + +The Execution Specification is the only source of retry authority. Its retry +policy declares: + +- `max_attempts`, including the first Attempt; +- the exact retryable Attempt terminal states and reason codes; +- deterministic fixed or bounded exponential backoff without random hidden + state; +- whether an explicit immutable resume identity is permitted; and +- whether every declared external side effect is idempotent and fence-aware. + +`backoff_ordinal` equals the preceding Attempt's positive retry ordinal. With +`t` equal to that Attempt terminal event's durable `recorded_at`, retry timing +is exactly: + +```text +NONE: eligible_due_at = t +FIXED: eligible_due_at = t + backoff_base_seconds +EXPONENTIAL: eligible_due_at = + t + min(backoff_cap_seconds, + backoff_base_seconds * 2^(backoff_ordinal - 1)) +``` + +Arithmetic is checked before `job.retry_scheduled`; overflow or an +unrepresentable UTC result prevents retry and latches `RETRY_EXHAUSTED`. +There is no random jitter, process-local seed, or restart-relative delay. + +`CANCELLED`, `POLICY_VIOLATION`, `REJECTED` due to an invalid contract or +approval, forged identity or Sigil, stale Snapshot, and any unknown failure are +never retryable. `FAILED`, `TIMED_OUT`, `LEASE_EXPIRED`, `LOST`, or `FENCED` +are retryable only when both the exact reason code and policy allow them. +Cleanup ambiguity can retry only into completely new mutable resources; it +never authorizes reuse. + +Retry requires all of the following: + +1. the preceding Attempt is terminal and preserved; +2. no cancellation, Job timeout, policy violation, or fatal integrity event is + active; +3. the preceding Attempt's budget settlement and assurance evaluation are + durable, and a complete next-Attempt reservation fits; +4. Task, Capability, Execution Specification, approval, Snapshot freshness, + backend eligibility, and requested assurance preflight revalidate; +5. a new Attempt ID, next ordinal, higher fencing generation, and new mutable + resource identities are allocated; when its immutable authorization + requirement is `REQUIRED`, a fresh deterministic Attempt subject and + distinct valid Receipt bind exactly once before preflight; this binding + cannot reuse the predecessor or Specification approval; a new Lease ID and + Worker authority follow only if preflight passes; and +6. `job.retry_scheduled`, `job.retry_ready`, and the atomic + `job.attempt_allocated` event commit in order before materialization or + Worker launch. + +The default is a fresh Crucible from the pinned immutable base and inputs. +Resume is permitted at A1 or higher only when the Specification explicitly +allows it and a content-identified immutable resume source validates. The +Executor must prove the predecessor's process tree and handles are fenced, +validate every resumed byte against the resume identity, allocate a new +mutable materialization and output namespace, and record lineage to the prior +Attempt. A mutable directory, path, container name, or surviving process is +never a resume identity. + +Retry does not merge logs, outputs, metrics, or results. The Job projection +lists every Attempt. Only one terminal Attempt can supply the selected result +candidate for the later Job assurance and Outcome decision, while every +preceding failure remains inspectable. + +### Job aggregate budget ledger + +The Job budget is enforced by a replayed +`{limit, reserved, consumed}` ledger for every aggregate dimension. Worker +reports and heartbeats are never trusted accounting. The enforcement backend +or Executor supervisor measures counters outside the Worker and binds each +measurement to the Attempt, process-tree identity, backend-configuration +Sigil, interval, and evidence Sigil. + +`budget_reservation` has exactly `attempts`, `cpu_time_seconds`, +`storage_bytes_written`, `output_bytes`, `log_bytes`, `process_starts`, +`network_egress_bytes`, and `network_requests`. For v1, +`job.attempt_allocated` reserves `attempts: 1` and the full corresponding +Execution Specification `attempt_budget` ceiling for every other dimension. +Allocation is illegal unless, for every dimension: + +```text +consumed + reserved + requested_reservation <= limit +``` + +All ledger arithmetic is checked unsigned arithmetic. Overflow fails closed +before mutation and is never treated as remaining capacity. + +The event atomically adds the reservation, creates the Attempt, and stores the +same reservation and exact derived `attempt_authorization_requirement` in its +immutable binding. Replay initializes the orthogonal authorization state to +`NONE` or `PENDING` in that same event. No authorization subject, +materialization, process start, or later retry scheduling may precede that +commit. `peak_memory_bytes` remains a per-Attempt instantaneous ceiling +enforced by the supervisor and is not summed in the Job ledger. + +While the Attempt is `CLEANING`, after all measured activity has stopped, the +Executor commits the one `ACCOUNTING_CAPTURED` progress event described +above. After the terminal Attempt event, `job.budget_settled` commits exactly +once before retry scheduling or Job terminalization. Its closed payload +contains the Attempt ID, reservation, accounting-capture event ID and Sigil, +`usage_status`, exact per-dimension `measured`, exact per-dimension `charged`, +accounting interval, supervisor identity, complete accounting-evidence-set +Sigil, and resulting ledger Sigil. +`usage_status` is `MEASURED`, `PARTIAL`, or `UNAVAILABLE`. A verified measured +dimension is charged its measured value and releases the unused reservation. +Each missing, discontinuous, corrupt, or Worker-only dimension is charged its +full reservation. `UNAVAILABLE` therefore charges every reserved dimension in +full. Attempt-count charge is always one. + +Exceeding an enforced reservation is a policy violation and cannot be hidden +by settlement. A crash between Attempt terminalization and settlement resumes +that exact settlement; if trusted counters cannot be reconstructed, it +charges the full preserved reservation. A reservation is never silently +released, transferred to another Attempt, or recomputed under a changed +Specification. Retry eligibility is decided only from the post-settlement +ledger and requires another full reservation. This makes retry count and +aggregate resource use deterministic under replay. + +## Logs and bounded result transport + +The Executor creates separate ordered streams for `STDOUT`, `STDERR`, and +`STRUCTURED`. The Worker cannot select an arbitrary Host path as a log sink. +`execution-log-chunk/1.0` has exactly these required members: + +| Field group | Exact fields | +| --- | --- | +| Contract and identity | `schema_version`, `chunk_id`, `log_stream_id`, `job_id`, `attempt_id`, `lease_id`, `worker_id`, `worker_session_id`, `executor_epoch`. | +| Immutable bindings | `attempt_binding_sigil`, `lease_binding_sigil`, `worker_session_binding_sigil`, `fence_tuple`. | +| Stream position | `stream`, `sequence`; `stream` is `STDOUT`, `STDERR`, or `STRUCTURED`, and sequence is zero-based and local to that stream. | +| Content | `byte_length`, `media_type`, `encoding`, `blob_sigil`, `staging_reference`. | +| Timing and boundaries | `observed_at`, `received_at`, `split_utf8_boundary`, `split_line`. | +| Integrity | `chunk_record_sigil`. | + +There are no conditional members. Boundary flags remain present for binary +encodings and are `false`; the staging reference is a closed +Executor-generated opaque value, never a Host path. + +The staging reference is operational and confers no read or write authority. +RFC-0013 defines durable Blob and Replica storage. Until then, the `0.4` +runtime may use a local content-addressed execution spool outside the Worker, +with atomic write-before-event publication and the complete `.benchwork/` +tree unreachable from an A2 Worker. + +Chunk sequence and content identity are validated before journal commitment. +An exact repeated sequence and Blob Sigil is idempotent. Conflicting content at +the same sequence, a stale fence, a chunk after stream closure, an unexpected +stream, or an oversized chunk is rejected and recorded. Partial writes remain +quarantined and absent from the committed stream. + +Per-stream and aggregate byte limits are enforced outside the Worker. On +limit: + +- the Executor appends `log.truncated` exactly once with captured and dropped + byte counts; +- further bytes are drained or discarded without unbounded buffering; +- overflow behavior follows the closed Specification enum `TRUNCATE` or + `TERMINATE`; +- truncation remains visible in result and assurance evidence; and +- redaction is never treated as secret non-inheritance or containment. + +Log timestamps and text are untrusted observations. Logs cannot change +lifecycle state, issue commands, prove policy enforcement, or become canonical +Artifacts without explicit Athanor acceptance under RFC-0013 and RFC-0015. + +`execution-result/1.0` has exactly these required members: + +| Field group | Exact fields | +| --- | --- | +| Contract and identity | `schema_version`, `job_id`, `attempt_id`, `lease_id`, `worker_id`, `worker_session_id`, `executor_epoch`. | +| Immutable bindings | `job_binding_sigil`, `attempt_binding_sigil`, `lease_binding_sigil`, `worker_session_binding_sigil`, `fence_tuple`. | +| Worker outcome | `worker_outcome`, whose enum is exactly `COMPLETED` or `FAILED`. | +| Process observation | `termination_observation`, a closed union described below. | +| Runtime observation | `runtime_observation`, exactly `started_at`, `ended_at`, `cpu_time_seconds`, `peak_memory_bytes`, `process_count`, and `observation_evidence_sigil`. | +| Logs | `log_stream_summaries`, exactly one closed entry for each configured stream sorted by stream enum, and `log_set_sigil`. | +| Outputs | `outputs`, an array of at most 4,096 unique entries sorted by `(logical_name unsigned ASCII, blob_sigil)`; every entry has exactly `logical_name`, `schema_id`, `schema_sigil`, `staging_reference`, `byte_size`, and `blob_sigil`. | +| Diagnostics | Bounded closed arrays `diagnostics` and `limitations`. | +| Integrity | `result_sigil`. | + +Each output `staging_reference` is the closed +`ATTEMPT_OUTPUT {kind, storage_subject_id, output_handle_id, transfer_id, +transfer_attempt_id}` branch. The Executor preallocates the RFC-0013 +`ST-ID`/`SA-ID` pair and unguessable handle before exposing it to the current +Lease. The stable subject ID is fixed at the same time as +`Sigil(["execution-attempt-output-storage-subject-id/1.0", job_id, +attempt_id, output_handle_id])`; it does not depend on bytes that do not yet +exist. The later ESM entry and RFC-0013 TransferRef must use the same subject, +handle, and transfer IDs. No path, URL, backend locator, Replica ID, or prior +Attempt handle is a legal branch. + +`termination_observation.kind` is `EXITED`, `SIGNALED`, or `UNKNOWN`. +`EXITED` requires `exit_code` and forbids `signal`; `SIGNALED` requires +`signal` and forbids `exit_code`; `UNKNOWN` forbids both. Every branch also +requires `observed_at` and `observation_source`. There are no other +conditional result members. + +The Worker never writes a realized assurance claim. While authority is +current, the Executor accepts at most one result Sigil for an Attempt, commits +its accepted or rejected disposition before `attempt.draining`, and preserves +the disposition sequence. +An exact duplicate returns that disposition without an event. A conflicting +second result is a protocol violation. A missing, late, stale, malformed, +over-limit, wrong-Schema, wrong-path, wrong-Sigil, or wrong-fence result is +retained as rejected operational evidence and cannot be selected by the Job. + +Outputs are staged atomically with respect to their recorded byte identity. +Existence, process exit zero, Worker `COMPLETED`, Attempt `SUCCEEDED`, or Job +`SUCCEEDED` does not register an Artifact, create a scientific Run, or append a +Chronicle event. The result remains an execution Proposal until RFC-0015's +typed acceptance path invokes Athanor and Athanor independently validates it. + +### Execution storage-root manifest + +`execution-storage-root-manifest/1.0` is the only source document from which +an RFC-0012 execution Reference Set may be extracted. It freezes the complete +typed subject-to-storage closure before a root can become visible. Its closed +top-level object has exactly: + +| Field | Exact v1 value | +| --- | --- | +| `schema_version` | Constant `execution-storage-root-manifest/1.0`. | +| `manifest_id` | Deterministic `ESM-ID` below. | +| `root_kind` | `JOB_INPUT`, `ATTEMPT_INPUT`, or `ATTEMPT_OUTPUT`. | +| `job_id`, `attempt_id` | Exact owner IDs; Attempt ID is null only for `JOB_INPUT`. | +| `owner_binding` | The one closed owner branch below. | +| `entries` | Complete sorted array of zero to 4,096 `StorageRootEntry` values. | +| `blob_refs` | Sorted unique projection of every committed managed Blob in `entries`, zero to 4,096 RFC-0013 `BlobRef` values. | +| `protection_plan` | Closed `NONE` or `PLANNED` branch below. | +| `created_at` | Time fixed by first single-assignment creation. | +| `manifest_sigil` | Sigil over every other top-level field. | + +The owner branches are exact: + +| `root_kind` | Exact `owner_binding` fields | +| --- | --- | +| `JOB_INPUT` | `kind`, `start_request_sigil`, `task_id`, `task_capsule_sigil`, `specification_id`, `specification_sigil`, `input_set_sigil`. | +| `ATTEMPT_INPUT` | `kind`, `job_binding_sigil`, `attempt_binding_sigil`, `preflight_plan_sigil`, `materialization_id`, `materialization_record_sigil`, `input_set_sigil`. | +| `ATTEMPT_OUTPUT` | `kind`, `job_binding_sigil`, `attempt_binding_sigil`, `result_binding`, `log_set_sigil`, `output_set_sigil`, `control_evidence_set_binding`, `terminal_source_binding`, `quarantine_plan_sigil`. | + +Every owner field resolves the exact immutable record or execution-journal +binding named by the owner. An `ATTEMPT_OUTPUT` manifest is created only after +the ten-record control-evidence closure and terminal-source disposition are +durable, and before `ACCOUNTING_CAPTURED`; it never refers to the later QBS, +OS, Attempt-terminal event, or Outcome. + +Each `StorageRootEntry` has exactly `storage_subject_id`, `subject`, +`claimed_blob`, `storage_origin`, and `entry_sigil`. The self-Sigil covers the +other four fields. For every branch except `ATTEMPT_OUTPUT`, +`storage_subject_id` is the lowercase Sigil of +`["execution-storage-subject-id/1.0", root_kind, job_id, attempt_id, +subject]`. For `ATTEMPT_OUTPUT`, it is the preallocated Result +staging-reference value derived from the opaque output handle by the formula +above, and `staging_reference_sigil` resolves that complete branch. It is a +stable identity, not a path or backend locator. `subject` is exactly one of: + +| Entry subject | Exact fields | +| --- | --- | +| `JOB_INPUT` | `kind`, `input_ordinal`, `input_identity`, `input_sigil`. | +| `ATTEMPT_INPUT` | `kind`, `input_ordinal`, `input_identity`, `input_sigil`, `materialization_id`. | +| `ATTEMPT_OUTPUT` | `kind`, `logical_name`, `schema_id`, `schema_sigil`, `staging_reference_sigil`, `byte_size`, `blob_sigil`. | +| `LOG_STREAM` | `kind`, `stream`, `log_stream_id`, `stream_set_sigil`, `byte_size`, `blob_sigil`. | +| `RESOURCE_EVIDENCE` | `kind`, `control_evidence_id`, `control_evidence_sigil`, `control_dimension`, `phase`, `phase_evidence_entry_sigil`, `byte_size`, `blob_sigil`. | +| `TERMINAL_SOURCE` | `kind`, `disposition`, `terminal_source_identity`, `terminal_source_sigil`, `storage_blob`, `retention_policy_sigil`, `file_count`, `byte_count`. | + +The arrays are grouped in this table order and then sorted by the natural +immutable key already defined for that subject family; input ordinals are +contiguous from zero. Subjects and `storage_subject_id` values are unique. +The manifest contains every Job input or Attempt input in its bound input set. +For `ATTEMPT_OUTPUT` it contains every accepted output, all three closed Log +streams, every Blob-bearing entry reachable through the frozen CES, and the +non-null terminal source. Nothing may be omitted to meet the bound. + +`claimed_blob` is an RFC-0013 `BlobRef` or null. It is non-null and equals the +subject's Blob identity and byte size for `COMMITTED_BLOB` or `QUARANTINE`; +it is null exactly for `NOT_STORED`. The `storage_origin` union is: + +| Origin | Exact fields and rule | +| --- | --- | +| `COMMITTED_BLOB` | `kind`, `transfer`, `provenance_id`, `provenance_sigil`, `blob_record_sigil`, `terminal_event`; `transfer` is the exact RFC-0013 `TransferRef`, the provenance record is `CAPTURED` or `IMPORTED` as appropriate and names that same transfer and Blob, and `terminal_event` equals `transfer.terminal_event`. | +| `QUARANTINE` | `kind`, `transfer`, `provenance_id`, `provenance_sigil`, `quarantine_id`, `quarantine_origin_event`; the transfer attempt and Quarantine owner are identical and the provenance preserves that same attempted ingest. | +| `NOT_STORED` | `kind`, `transfer`, `terminal_reason`, `evidence_sigil`; `transfer` is the exact failed `TransferRef` or null only when no transfer was started, and the reason proves why no managed Blob or retained Quarantine exists. | + +For an `ATTEMPT_OUTPUT` subject, every non-null transfer is the current +Attempt's transfer, not merely the creator of a deduplicated Replica. Its +request has `direction: INGEST`, `purpose: ATTEMPT_OUTPUT`, +`source.kind: ATTEMPT_OUTPUT`, `destination.kind: MANAGED_BACKEND`, and +`execution.kind: LEASED`. The source and top-level execution objects equal +each other and the exact execution-journal ID, Executor epoch, Job, Attempt, +Lease, Worker, Worker Session, and public fence tuple that authorized the +output. `source.output_handle_id` equals the Result staging-reference +`output_handle_id`; that branch also carries this `storage_subject_id` and +the same Transfer ID/Attempt ID, and its Sigil equals +`subject.staging_reference_sigil`. Expected Blob, +exact byte bound, selected backend, committed Blob, transfer terminal Event, +and provenance Blob all equal `claimed_blob`. A pre-existing Blob or Replica +may satisfy the bytes, but the current TransferRef and current provenance +remain mandatory; an older Replica's creator cannot impersonate this +Attempt. + +`blob_refs` equals every and only `claimed_blob` whose origin is +`COMMITTED_BLOB`, sorted by `(blob_sigil, size_bytes)` and de-duplicated by +the complete pair. A Quarantine entry is protected by its exact isolated +Quarantine lifecycle rather than by pretending it is a registered Blob; a +negative entry creates no Blob edge. + +`protection_plan` is `NONE {kind}` exactly when `blob_refs` is empty. +Otherwise it is +`PLANNED {kind, reference_set_registration_event_id, hold_id, +hold_set_event_id, policy_id, policy_sigil, hold_lifetime}`. +The registration Event ID is the deterministic RFC-0013 `SE-ID` below; the +hold and hold-set Event IDs are freshly preallocated RFC-0013 `SH-ID` and +`SE-ID` values. All three become the exact later registration or hold +identities: + +```text +reference_set_registration_event_id = + "SE-" + UPPER_HEX(SHA256(canonical_json( + ["artifact-storage-execution-root-reference-set-registration-event-id/1.0", + manifest_id]))) +``` + +`policy_id` is constant `SP-EXECUTION-ROOT-HOLD-V1`, and `policy_sigil` +resolves the one installed project-scoped RFC-0013 neutral operational policy +with that ID. This tuple is mandatory for every non-empty root, including a +`NONE`-mode output root. It is independent of, and cannot substitute for, the +user-selected `CODE_MODIFICATION` terminal-source retention policy. +`hold_lifetime` is: + +- `OWNER_TERMINAL {kind}` for `JOB_INPUT` or `ATTEMPT_INPUT`; or +- `OUTPUT_RETENTION {kind, maximum_duration_seconds}` for + `ATTEMPT_OUTPUT`, where the duration equals the bound Specification's + `retention_duration_seconds` for `CODE_MODIFICATION` and is zero for + `NONE`. + +The plan intentionally contains no future Reference Set ID/Sigil, Storage +Event Sigil, or hold-set Event Sigil. After the complete ESM is durable, +RFC-0013 deterministically derives the Reference Set from the ESM source; a +future Sigil inside this self-Sigiled record would create a hash cycle. + +The manifest ID is: + +```text +manifest_id = + "ESM-" + UPPER_HEX(SHA256(canonical_json( + ["execution-storage-root-manifest-id/1.0", + root_kind, job_id, attempt_id, owner_binding]))) +``` + +There is at most one ESM per owner and root kind. The resolver is +single-assignment by ESM-ID: first creation fixes all entries, the protection +plan, `created_at`, and self-Sigil; an exact retry reuses it, while any second +byte sequence for that ID is an integrity failure. The ESM is durable before +Reference Set registration, so a crash can resolve the exact source without +enumerating files, transfers, Replicas, or current State. + +Its RFC-0013 Reference Set source is exactly +`{kind: OPERATIONAL_CONTROL_RECORD, identity: manifest_id, +schema_version: execution-storage-root-manifest/1.0, +sigil: manifest_sigil}`. The extractor is the installed constant +`{extractor_id: benchwork.execution-storage-root-manifest, +extractor_version: 1.0, extractor_sigil}` and the validator is the installed +constant `{validator_id: benchwork.execution-storage-root-manifest, +validator_version: 1.0, validator_sigil, source_validation_sigil, +evidence_sigils}`. Their Sigils are computed from their complete closed +profiles by these fixed formulas: + +```text +extractor_sigil = + Sigil(["execution-storage-root-extractor-profile/1.0", + "benchwork.execution-storage-root-manifest", "1.0"]) + +validator_sigil = + Sigil(["execution-storage-root-validator-profile/1.0", + "benchwork.execution-storage-root-manifest", "1.0"]) +``` + +`evidence_sigils` is computed first. It is the sorted unique union of +`manifest_sigil`; every Sigil-typed leaf in `owner_binding`; every +`entry_sigil` and Sigil-typed leaf in each entry's `subject`; the planned +`policy_sigil`; and, for each entry, the Sigils present in its closed +storage-origin branch: the Transfer request and Attempt record Sigils plus +terminal Event Sigil for every non-null `TransferRef`; the provenance Sigil +and Blob-record Sigil for `COMMITTED_BLOB`; the provenance Sigil and +quarantine-origin Event Sigil for `QUARANTINE`; or the negative +`evidence_sigil` for `NOT_STORED`. It contains no Reference Set Sigil, +registration Event Sigil, hold authorization, hold-set Event Sigil, EHR, or +release Event Sigil. A missing, extra, duplicated, or differently ordered +evidence value is invalid. + +The source-validation value is then exactly: + +```text +source_validation_sigil = + Sigil(["execution-storage-root-source-validation/1.0", + manifest_id, + manifest_sigil, + extractor_sigil, + validator_sigil, + [[entry.storage_subject_id, entry.entry_sigil], ...], + blob_refs, + evidence_sigils]) +``` + +The entry pairs preserve ESM order. `evidence_sigils` is the sorted unique +projection just defined. The extractor emits exactly one Blob edge for each +`blob_refs` member: `JOB_INPUT` uses +`JOB_REQUIRES_BLOB`, while both Attempt kinds use +`CONTROL_RETAINS_BLOB`. No other edge is legal. + +When `protection_plan` is `PLANNED`, Storage registers that exact set at +`reference_set_registration_event_id`, then appends +`retention.hold_set` at `hold_set_event_id`. The hold uses the planned +`hold_id`, policy tuple, `target_kind: REFERENCE_SET`, and +`target_id` equal to the newly derived Reference Set ID. Its +`authorization_sigil` is exactly: + +```text +Sigil(["execution-root-hold-set-authorization/1.0", + {manifest_id, manifest_sigil}, + {reference_set_id, reference_set_sigil, + registration_event: }, + {hold_id, hold_set_event_id}, + {policy_id, policy_sigil}]) +``` + +Every member equals the ESM, registered Reference Set, registration Event, and +plan byte-for-byte. The formula is evaluated only after registration and is +therefore intentionally outside the ESM. The closed +`storage_root_binding` then adds `storage_root_manifest_id` and +`storage_root_manifest_sigil` to the exact RFC-0013 root fields and must +resolve this ESM, set, active hold, its exact set-authorization Sigil, and both +planned Event IDs. When the plan is `NONE`, no Reference Set, hold, or visible +execution root is fabricated. + +`terminalization_storage_manifest_binding` is +`PENDING {kind}` before the output ESM exists or +`FROZEN {kind, storage_root_manifest_id, storage_root_manifest_sigil}` +afterward. `output_root_protection` is `PENDING {kind}` before capture, then +exactly +`NO_HOLD {kind, terminalization_storage_manifest_binding}` when the frozen +ESM has no Blob refs, or +`HELD {kind, terminalization_storage_manifest_binding, +storage_root_binding}` when its active Reference-Set hold is durable. These +two bindings are frozen before `ACCOUNTING_CAPTURED`. + +### Execution-root hold-release authorization + +`execution-root-hold-release-authorization/1.0` is the only authority that +may release a hold created from an execution ESM. Its closed top-level object +has exactly `schema_version`, `release_authorization_id`, +`execution_journal_id`, `storage_root`, `policy`, +`hold_set_authorization_sigil`, `basis`, and +`release_authorization_sigil`. `storage_root` is the complete closed +`storage_root_binding` above. `policy` has exactly `policy_id` and +`policy_sigil`, byte-for-byte equal to the ESM plan and resolved hold. +`hold_set_authorization_sigil` is the exact set-authorization value above. + +The `basis` union is exactly: + +```text +OWNER_TERMINAL: + kind = OWNER_TERMINAL + activation_event_id: JE-ID + activation_event_sequence: PositiveU63 + activation_event_sigil: Sigil + terminal_event_id: JE-ID + terminal_event_sequence: PositiveU63 + terminal_event_sigil: Sigil + terminal_event_type: closed execution Event type + verification_head: complete execution-journal-head/1.0 + verification_state_sigil: Sigil + +ORPHAN_ABORT: + kind = ORPHAN_ABORT + intended_activation_event_type: closed execution Event type + absence_head: complete execution-journal-head/1.0 + absence_state_sigil: Sigil + +OUTPUT_DEADLINE: + kind = OUTPUT_DEADLINE + activation_event_id: JE-ID + activation_event_sequence: PositiveU63 + activation_event_sigil: Sigil + release_schedule: complete output_hold_release_schedules entry + terminal_event_sequence: PositiveU63 + terminal_event_sigil: Sigil + verification_head: complete execution-journal-head/1.0 + verification_state_sigil: Sigil +``` + +Its deterministic identity and self-Sigil are: + +```text +release_authorization_id = + "EHR-" + UPPER_HEX(SHA256(canonical_json( + ["execution-root-hold-release-authorization-id/1.0", + storage_root.hold_id]))) + +release_authorization_sigil = + Sigil() +``` + +The resolver is immutable and single-assignment by `EHR-ID`; the first legal +basis for a hold wins and changed bytes are an integrity conflict. +Construction, Storage release, and any execution-journal observation occur +under the outer gate. The authorization is durable before +`retention.hold_released`. A crash resolves the same EHR record and existing +Storage Event or appends only the missing release. + +`OWNER_TERMINAL` is legal only for `JOB_INPUT` with its exact parent Job +terminal Event, or `ATTEMPT_INPUT` with its exact owner Attempt terminal +Event. The Event ID, Sigil, type, owner IDs, and activated +`storage_root_binding` all come from replay through `verification_head`; the +activation fields identify the sole activation Event and the terminal fields +identify its sole inactivation Event in that same prefix, the Head ends at or +after the terminal sequence, and `verification_state_sigil` equals the State +projected through that exact Head. +`OUTPUT_DEADLINE` is legal only for `ATTEMPT_OUTPUT`; its schedule is +byte-for-byte one entry from the exact parent Job terminal Event, its +`terminal_event_sigil` names that Event, the activation fields identify the +owner Attempt terminal Event, both occur in order in the complete replay +through `verification_head`, `verification_state_sigil` equals that prefix's +State, and trusted time is at or after the schedule's immutable due predicate +under the rules below. + +`ORPHAN_ABORT` is legal only when its complete, self-Sigiled +`absence_head` is the gate-held current verified Execution Journal Head and +`absence_state_sigil` equals the State projected through it; replay through +that Head proves that the sole activating Event for the exact ESM/root did not +commit. The intended activation type is `job.submitted` for +`JOB_INPUT`, `attempt.preflight_passed` for `ATTEMPT_INPUT`, or the exact +planned Attempt terminal type for `ATTEMPT_OUTPUT`. Once an `ORPHAN_ABORT` +EHR exists, that ESM/root is permanently activation-ineligible; retry may +finish its release but may not install a fresh hold or later activate it. +New work requires a new Job or Attempt identity. + +Before any sole activating Event commits, replay must resolve the planned hold +as `ACTIVE` and prove that no EHR exists for its deterministic +`release_authorization_id`. Missing or ambiguous proof blocks activation. +This rule closes the race between orphan proof and a late activation. None of +the three bases authorizes byte deletion, another hold, or a canonical, +policy, legal, or preservation reference. + +### Closed terminalization input sets + +`execution-control-evidence-set/1.0` is the only v1 closure of the individual +`execution-control-evidence/1.0` records used to terminalize one Attempt. Its +conventional Schema filename is +`execution-control-evidence-set-1.0.json` and its exact `$id` is +`https://benchwork.dev/schemas/execution-control-evidence-set/1.0`. +The closed top-level object has exactly: + +| Field | Exact v1 value | +| --- | --- | +| `schema_version` | Constant `execution-control-evidence-set/1.0`. | +| `control_evidence_set_id` | The deterministic `CES-ID` below. | +| `job_id`, `attempt_id`, `attempt_binding_sigil` | Exact immutable owner bindings. | +| `control_evidence_refs` | Exactly ten closed references, one per control dimension. | +| `control_evidence_set_sigil` | Sigil over every other top-level member. | + +Each `control_evidence_refs` member has exactly `control_dimension`, +`control_evidence_id`, and `control_evidence_sigil`. The array has exactly ten +members in the control-dimension matrix order printed below. Its dimensions +and IDs are both unique. Each pair resolves one complete +`execution-control-evidence/1.0` document whose Job, Attempt, dimension, ID, +and self-Sigil equal the reference byte-for-byte. The ten resolved documents +are the complete evidence family for the Attempt: a document outside this +closure cannot satisfy assurance, create a resource-evidence observation, or +replace a missing dimension. + +Within every resolved document, each of the four `phase_evidence` arrays is +unique by canonical JSON and sorted by +`(evidence_kind enum order, producer_identity unsigned ASCII, +verifier_identity unsigned ASCII, collected_at, blob_sigil null first, +staging_reference canonical JSON)`. The derived +`phase_evidence_entry_sigil` is the Sigil of that exact closed entry; it is +not a new field in `execution-control-evidence/1.0`. This gives every +Blob-bearing entry a stable, independently recomputable identity. + +`control_evidence_set_id` is `CES-` followed by the uppercase 64-hex SHA-256 +digest of canonical JSON: + +```text +["execution-control-evidence-set-id/1.0", + job_id, + attempt_id, + attempt_binding_sigil] +``` + +`control_evidence_set_binding` is exactly +`PENDING {kind}` or +`FROZEN {kind, control_evidence_set_id, control_evidence_set_sigil}`. +The ten evidence documents and set document are durable before the final +`ACCOUNTING_CAPTURED` progress event; that event carries only the `FROZEN` +branch. One deterministic, single-assignment pending resolver slot exists per +Attempt. It is addressable by the `CES-ID` only while replay has the Attempt +in `CLEANING`, no `ACCOUNTING_CAPTURED` event exists, and the owner binding +matches. Once written, it returns the one self-Sigil and complete canonical +set document. The event and every later consumer use the exact ID-and-Sigil +pair; ID-only lookup, Sigil-only lookup, enumeration, and substitution of a +newer evidence family are invalid. A crash after record durability but before +the event reuses the same record; a different self-Sigil for that ID is an +integrity failure. There are at most `MAX_ATTEMPTS` 4,096 such records. + +`execution-quarantine-binding-set/1.0` is the only v1 complete mapping from +an execution subject to an RFC-0013 Quarantine item, including retained and +terminal-negative outcomes. Its conventional Schema +filename is `execution-quarantine-binding-set-1.0.json` and its exact `$id` +is +`https://benchwork.dev/schemas/execution-quarantine-binding-set/1.0`. +The closed top-level object has exactly: + +| Field | Exact v1 value | +| --- | --- | +| `schema_version` | Constant `execution-quarantine-binding-set/1.0`. | +| `quarantine_binding_set_id` | The deterministic `QBS-ID` below. | +| `job_id`, `attempt_id`, `attempt_binding_sigil` | Exact immutable owner bindings. | +| `quarantine_plan_sigil` | Exact plan frozen by `attempt.cleaning`. | +| `terminalization_storage_manifest_binding` | Exact frozen output ESM ID and Sigil prepared before accounting capture. | +| `storage_event` | Exact RFC-0013 `EventRef` at the final pre-accounting Storage prefix. | +| `storage_state_sigil` | Sigil of the complete RFC-0013 State replayed through `storage_event`. | +| `bindings` | Complete sorted subject-to-Quarantine bindings; empty when no subject is quarantined. | +| `quarantine_binding_set_sigil` | Sigil over every other top-level member. | + +Every `bindings` member has exactly `storage_subject_id`, +`manifest_entry_sigil`, `subject`, `storage_origin`, `quarantine_ref`, +`disposition`, and `quarantine_binding_sigil`, whose self-Sigil covers every +other field. The first two fields select exactly one entry in the frozen ESM; +`subject` and `storage_origin` equal that entry byte-for-byte. +`quarantine_ref` has exactly `quarantine_id`, `owner_kind`, `owner_id`, +`quarantine_record_sigil`, `origin_event`, `observation_event`, `state`, +`source_object`, `destination_object`, `source_cleanup`, and `reason`. It +imports the RFC-0013 `SQ-ID`, owner domains, State fields, and complete +`EventRef` without restatement. `subject` is exactly one of these closed +branches: + +| Subject branch | Exact fields | +| --- | --- | +| `ATTEMPT_OUTPUT` | `kind`, `logical_name`, `schema_id`, `schema_sigil`, `staging_reference_sigil`, `byte_size`, `blob_sigil`. | +| `LOG_STREAM` | `kind`, `stream`, `log_stream_id`, `stream_set_sigil`, `byte_size`, `blob_sigil`. | +| `RESOURCE_EVIDENCE` | `kind`, `control_evidence_id`, `control_evidence_sigil`, `control_dimension`, `phase`, `phase_evidence_entry_sigil`, `byte_size`, `blob_sigil`. | +| `TERMINAL_SOURCE` | `kind`, `disposition`, `terminal_source_identity`, `terminal_source_sigil`, `storage_blob`, `retention_policy_sigil`, `file_count`, `byte_count`. | + +The branch order above is the primary sort key. Remaining sort keys are, +respectively, the output member key, Log stream enum order, the resource +member key plus phase-entry Sigil, and terminal-source Sigil, all compared by +unsigned ASCII where not already an enum or integer. Subjects and Quarantine +IDs are unique. The array has at most 4,096 members. + +The set is derived after all cleanup and storage work is complete from exactly +the accepted Result outputs, three closed Log streams, every Blob-bearing +entry reached through the frozen control-evidence set, and the frozen +terminal-source binding. While holding the RFC-0013 outer gate, the Executor +replays one complete Storage prefix and binds its exact EventRef and State +Sigil before constructing the set. It contains one binding if and only if +that ESM entry has `storage_origin: QUARANTINE`, including both retained and +terminal-negative outcomes. `storage_origin.transfer.transfer_attempt_id` +equals `quarantine_ref.owner_id`, whose owner kind is +`TRANSFER_ATTEMPT`; this is always the current execution transfer fixed by +the ESM. The subject copies the source fields byte-for-byte. + +`origin_event` is exactly `quarantine.recorded` or `quarantine.failed`. +For both origins, the owning RFC-0013 Transfer Attempt terminal Event is +exactly `transfer.quarantined`, names this Q-ID, carries +`quarantine_event == origin_event`, and equals +`storage_origin.transfer.terminal_event`. A `transfer.failed` terminal Event +is legal only for `NOT_STORED`, never for an ESM `QUARANTINE` origin. +`observation_event` is the exact last Event that produced the frozen State and +is equal to or later than `origin_event`. `INTENT_RECORDED`, `INSPECTING`, or +`DISPOSING` is not terminal and blocks `ACCOUNTING_CAPTURED`. + +The origin-and-state matrix is closed: + +| Origin Event | Frozen State | Exact disposition | +| --- | --- | --- | +| `quarantine.recorded` | `HELD` | `RETAINED` | +| `quarantine.recorded` | `DISPOSAL_FAILED` | `RETAINED` | +| `quarantine.recorded` | `DISPOSED` | `TERMINAL_NEGATIVE` | +| `quarantine.failed` | `FAILED` | `TERMINAL_NEGATIVE` | +| `quarantine.failed` | `DISPOSAL_FAILED` | `TERMINAL_NEGATIVE` | +| `quarantine.failed` | `DISPOSED` | `TERMINAL_NEGATIVE` | + +Every other pair is invalid. `RETAINED` additionally requires the exact +`quarantine.recorded.verification` to authenticate the non-null destination +generation and the claimed Blob Sigil and size. For `DISPOSAL_FAILED`, the +exact observation Event and its failure evidence must also authenticate that +same destination generation as still present, isolated, and byte-identical at +the frozen prefix; missing or ambiguous presence blocks +`ACCOUNTING_CAPTURED`. Failed-origin +`DISPOSAL_FAILED` never acquires that verification and cannot become retained +content. `RETAINED` maps to the observation contract's `QUARANTINED` branch. +`TERMINAL_NEGATIVE` maps only to `QUARANTINE_TERMINAL_NEGATIVE`: it proves +unavailable, ineligible bytes and can never be relabeled retained Quarantine +or Blob. `source_object`, `destination_object`, cleanup, reason, record Sigil, +and both Events equal the exact frozen RFC-0013 projection. There is no +matching by Blob alone, size alone, pathname, filename, timestamp, or current +State enumeration. + +`quarantine_binding_set_id` is `QBS-` followed by the uppercase 64-hex +SHA-256 digest of canonical JSON: + +```text +["execution-quarantine-binding-set-id/1.0", + job_id, + attempt_id, + attempt_binding_sigil, + quarantine_plan_sigil, + terminalization_storage_manifest_binding] +``` + +`quarantine_binding_set_binding` is exactly +`PENDING {kind}` or +`FROZEN {kind, quarantine_binding_set_id, quarantine_binding_set_sigil}`. +The set document is durable before the final `ACCOUNTING_CAPTURED` event, +which carries only the `FROZEN` branch. Its deterministic per-Attempt +single-assignment resolver has the same pre-event-only access, record-before- +event, exact-pair lookup, conflict, orphan-reuse, and 4,096-record rules as +the control-evidence-set resolver. An empty set is still a resolvable closed +record and is the only proof that the complete subject traversal found no +Quarantine binding. Recovery of a pending set replays its historical +`storage_event` and validates `storage_state_sigil`; it never refreshes the +set from a later Storage Head. + +### Frozen output storage-observation set + +`execution-output-storage-observation-set/1.0` is the only v1 document that +freezes terminal storage facts for an allocated Attempt. Its conventional +Schema filename is +`execution-output-storage-observation-set-1.0.json` and its exact `$id` is +`https://benchwork.dev/schemas/execution-output-storage-observation-set/1.0`. +The Schema is JSON Schema Draft 2020-12, every object and union branch is +closed, and every field below is required unless its branch explicitly +requires `null`. + +The top-level object has exactly: + +| Field | Exact v1 value | +| --- | --- | +| `schema_version` | Constant `execution-output-storage-observation-set/1.0`. | +| `observation_set_id` | The deterministic `OS-ID` below. | +| `job_id`, `attempt_id`, `attempt_binding_sigil` | Exact immutable owner bindings. | +| `result_binding` | The exact `execution-journal-event/1.0#/$defs/result_binding` branch frozen for the Attempt. | +| `log_closure_sigil`, `output_closure_sigil` | Exact non-null terminalization inputs. | +| `control_evidence_set_binding` | Exact frozen `CES-ID` and self-Sigil bound by `ACCOUNTING_CAPTURED`. | +| `quarantine_binding_set_binding` | Exact frozen `QBS-ID` and self-Sigil bound by `ACCOUNTING_CAPTURED`. | +| `terminalization_storage_manifest_binding` | Exact frozen output ESM ID and Sigil bound by `ACCOUNTING_CAPTURED`. | +| `output_root_protection` | Exact pre-terminal `NO_HOLD` or `HELD` protection branch bound by `ACCOUNTING_CAPTURED`. | +| `terminal_source_binding` | The exact `execution-journal-event/1.0#/$defs/terminal_source_binding` frozen by `attempt.cleaning`. | +| `storage_event` | Exact RFC-0013 `EventRef` at the observed Storage Journal prefix. | +| `storage_state_sigil` | Sigil of the complete RFC-0013 State replayed through `storage_event`. | +| `members` | The bounded, sorted, unique closed member union below. | +| `blob_sigils` | Sorted unique aggregate of every non-null Blob identity in `members`. | +| `observation_set_sigil` | Sigil over every other top-level member. | + +`observation_set_id` is `OS-` followed by the uppercase 64-hex SHA-256 digest +of canonical JSON: + +```text +["execution-output-storage-observation-set-id/1.0", + job_id, + attempt_id, + attempt_binding_sigil, + result_binding, + log_closure_sigil, + output_closure_sigil, + control_evidence_set_binding, + quarantine_binding_set_binding, + terminalization_storage_manifest_binding, + output_root_protection, + terminal_source_binding] +``` + +This is one of the three terminalization-record deterministic-ID exceptions +for otherwise opaque execution IDs. It lets an interrupted pre-terminal +construction resolve the +same immutable record rather than enumerate files or guess a content hash. +Construction may use the deterministic ID to read the one single-assignment +pending resolver slot for that Attempt only when Execution Journal replay has +that Attempt in `CLEANING`, its `ACCOUNTING_CAPTURED` event is durable, and no +terminal event exists. The slot is created only after every ID input above is +immutable. Once present, it returns exactly one +`observation_set_sigil` and complete canonical document. A terminal event and +all later consumers resolve only the exact +`(observation_set_id, observation_set_sigil)` pair carried by the event. The +resolver rejects an unknown pair, a second Sigil for the same ID, or bytes +that do not validate both identities. The document is durable before the +Attempt terminal event that first references it. A record whose durable write +completed but whose event did not is a pending terminalization payload, not +an active root or terminal fact; Recovery reuses its frozen Storage prefix +even if the current Storage Head later advances. + +`storage_event` imports RFC-0013 `EventRef` byte-for-byte and therefore has +exactly `journal_id`, `event_id`, `sequence`, and `event_sigil`. +`storage_state_sigil` equals the self-Sigil of the closed RFC-0013 State +obtained by replaying that same Journal through that Event. A document is +invalid if its Event is not at that sequence, the State has a different +Journal ID, applied count, last Event Sigil, or State Sigil, or a member is +resolved from a later State. A current State cache, backend listing, path, +filename, or retrieval time is never an observation input. +Both fields equal the resolved quarantine-binding-set document's +`storage_event` and `storage_state_sigil` byte-for-byte. That pre-accounting +prefix is the single storage fact boundary for the QBS and OS documents; OS +construction after `ACCOUNTING_CAPTURED` does not sample a second prefix. + +Both terminalization-set bindings resolve their exact canonical documents +before any observation member is derived. Their Job, Attempt, and Attempt +binding equal this document byte-for-byte. The control-evidence set has +exactly ten valid resolvable evidence references; the quarantine-binding set +has the same `quarantine_plan_sigil` frozen for this Attempt and is complete +for the source traversal. Both the QBS and OS resolve the same exact output +ESM. `output_root_protection` proves either that this ESM has an empty +`blob_refs` set or that its exact Reference Set and active hold were durable +before accounting capture. A missing record, an ID/Sigil mismatch, a second +record for one deterministic ID, an unresolved member reference, or a +different owner invalidates the observation document. + +`members` has `minItems: 6` and +`maxItems: MAX_OUTPUT_STORAGE_OBSERVATION_MEMBERS`, whose fixed value is +4,096. It uses exactly these closed branches: + +| Member branch | Exact fields | +| --- | --- | +| `ATTEMPT_OUTPUTS_NONE` | `kind`, `reason`; reason is `NO_RESULT`, `RESULT_REJECTED`, or `OUTPUT_ARRAY_EMPTY`. | +| `ATTEMPT_OUTPUT` | `kind`, `storage_subject_id`, `manifest_entry_sigil`, `logical_name`, `schema_id`, `schema_sigil`, `staging_reference_sigil`, `byte_size`, `blob_sigil`, `storage_origin`, `storage`. | +| `LOG_STREAM` | `kind`, `storage_subject_id`, `manifest_entry_sigil`, `stream`, `log_stream_id`, `final_sequence`, `captured_bytes`, `dropped_bytes`, `truncated`, `stream_set_sigil`, `closure_event_id`, `closure_event_sigil`, `storage_origin`, `content`. | +| `RESOURCE_EVIDENCE_NONE` | `kind`. | +| `RESOURCE_EVIDENCE` | `kind`, `storage_subject_id`, `manifest_entry_sigil`, `control_evidence_id`, `control_evidence_sigil`, `control_dimension`, `phase`, `phase_evidence_entry_sigil`, `evidence_kind`, `producer_identity`, `verifier_identity`, `collected_at`, `byte_size`, `blob_sigil`, `storage_origin`, `storage`. | +| `TERMINAL_SOURCE_NOT_APPLICABLE` | `kind`. | +| `TERMINAL_SOURCE_NONE` | `kind`, `reason_codes`. | +| `TERMINAL_SOURCE` | `kind`, `storage_subject_id`, `manifest_entry_sigil`, `disposition`, `terminal_source_identity`, `terminal_source_sigil`, `storage_blob`, `retention_policy_sigil`, `file_count`, `byte_count`, `storage_origin`, `storage`. | + +The executable Schema publishes these branches as the canonical named +`$defs` `attempt_outputs_none_member`, `attempt_output_member`, +`log_stream_member`, `resource_evidence_none_member`, +`resource_evidence_member`, `terminal_source_not_applicable_member`, +`terminal_source_none_member`, and `terminal_source_member`. It also +publishes `storage_observation`, `control_evidence_set_binding`, and +`quarantine_binding_set_binding`, +`terminalization_storage_manifest_binding`, and +`output_root_protection`. A consumer imports the exact +`https://benchwork.dev/schemas/execution-output-storage-observation-set/1.0#/$defs/` +URI; a cloned or widened local branch is not equivalent. + +Every named Sigil uses the common lowercase representation. Byte counts, +member counts, file counts, and non-null Log sequences use RFC-0013 `U63`; +`final_sequence` is `U63|null`, `truncated` is boolean, and +`collected_at` is the exact common UTC Timestamp. Job, Attempt, Log-stream, +control-evidence, and execution-Event IDs use their RFC-0012 domains. +All remaining identities use RFC-0013 bounded `Opaque`; no string exceeds 256 +UTF-8 bytes or contains NUL or control characters. `staging_reference_sigil` +is the Sigil of the complete canonical staging-reference value and is not a +locator. `phase`, `control_dimension`, `evidence_kind`, `disposition`, and +terminal-source reason codes use only their already printed closed RFC-0012 +enums. These are imported Schema definitions, not unconstrained local strings. + +The kind value is the branch name. Member cardinality and order are closed: + +1. the first group is either one `ATTEMPT_OUTPUTS_NONE` member or between one + and 4,096 `ATTEMPT_OUTPUT` members, never both; +2. the next group is exactly three `LOG_STREAM` members in fixed + `STDOUT`, `STDERR`, `STRUCTURED` order, with exactly the three immutable + Log-stream IDs allocated to the Attempt; +3. the next group is either one `RESOURCE_EVIDENCE_NONE` member or between + one and 4,096 `RESOURCE_EVIDENCE` members, never both; and +4. the final group is exactly one of the three terminal-source branches. + +The 4,096-member maximum applies to the combined union, not independently to +each group. `blob_sigils` is the duplicate-free unsigned-ASCII-sorted union +of every non-null Blob Sigil named by an output, Log content, resource +evidence, terminal source, `BLOB` storage branch, or +`QUARANTINED.claimed_blob_sigil` or +`QUARANTINE_TERMINAL_NEGATIVE.claimed_blob_sigil`; it has +`maxItems: MAX_OUTPUT_STORAGE_OBSERVATION_BLOBS`, also 4,096. Construction +rejects a 4,097th member or distinct Blob before making the document durable; +it cannot omit a Log stream, evidence member, output, or terminal source to +fit. Result acceptance likewise rejects an output manifest whose complete +projected observation set would exceed either aggregate bound. + +Every non-placeholder member resolves exactly one entry in the frozen output +ESM by `storage_subject_id` and `manifest_entry_sigil`; its subject fields, +claimed Blob, and `storage_origin` are copied byte-for-byte. Therefore an +output observation always retains the current Attempt's exact TransferRef and +provenance even when the selected Blob projection points at a deduplicated +Replica created by an older transfer. The OS cannot synthesize provenance +from `replica.creator`, a shared Blob, or current backend state. + +Output members are unique and sorted by +`(logical_name unsigned ASCII, blob_sigil)` and equal the accepted Result's +complete output array after replacing the opaque staging reference with its +canonical-JSON Sigil. `ATTEMPT_OUTPUTS_NONE` uses `NO_RESULT` only with +`result_binding: NONE`, `RESULT_REJECTED` only with `REJECTED`, and +`OUTPUT_ARRAY_EMPTY` only with an accepted Result whose output array is +empty. Resource members are unique and sorted by +`(control_evidence_id unsigned ASCII, phase order, evidence-kind enum order, +phase_evidence_entry_sigil)`, where phase order is `PREFLIGHT`, `RUNTIME`, +`TERMINATION`, `CLEANUP`. Resolve the exact control-evidence-set ID-and-Sigil +pair, then every one of its ten ID-and-Sigil member references. Resource +members equal every Blob-bearing phase entry in that closed traversal: +`control_evidence_sigil` equals the resolved parent record, and +`phase_evidence_entry_sigil` equals the independently recomputed Sigil of the +exact entry whose remaining fields are copied byte-for-byte. The `NONE` +branch is legal only when that complete traversal is empty. A control +evidence Sigil without its set ID and closed member references cannot supply +this collection. + +Each `LOG_STREAM.content` is exactly +`EMPTY {kind, blob_sigil, storage}` or +`CAPTURED {kind, blob_sigil, storage}`. `EMPTY` requires null +`final_sequence`, zero captured bytes, the SHA-256 Blob identity of the empty +byte string, and a storage observation for those zero bytes. `CAPTURED` +requires non-null final sequence, positive captured bytes, and a Blob whose +logical bytes are the sequence-ordered concatenation of all committed chunks +through that final sequence. In both branches, the content Blob Sigil, +captured byte count, closure Event, and complete chunk sequence recompute the +exact `stream_set_sigil`; dropped bytes are not silently appended to the +content. Thus even an empty or truncated stream has one explicit stable +branch, and Outcome derivation never guesses a Blob from a log-set hash. + +`TERMINAL_SOURCE_NOT_APPLICABLE` is legal only with the +`terminal_source_binding: NOT_APPLICABLE` branch. +`TERMINAL_SOURCE_NONE` is legal only with `QUARANTINED` whose source identity +and Sigil and `storage_blob` are all null and whose counts are both zero; its +non-empty sorted `reason_codes` equal that binding. `TERMINAL_SOURCE` copies +the non-null identity, Sigil, storage BlobRef, retention-policy Sigil, file +count, and byte count from +`VERIFIED` or +`QUARANTINED`, and `disposition` names that exact branch. A `VERIFIED` +terminal source requires `storage: BLOB`; a quarantined terminal source +uses `storage: QUARANTINED` only for a QBS `RETAINED` disposition and +`storage: QUARANTINE_TERMINAL_NEGATIVE` for `TERMINAL_NEGATIVE`. + +Every member's `storage` or log `content.storage` is exactly one of these +closed branches: + +| Storage branch | Exact fields | +| --- | --- | +| `NONE` | `kind`, `terminal_storage_status`; status is constant `UNAVAILABLE`. | +| `BLOB` | `kind`, `terminal_storage_status`, `blob_sigil`, `size_bytes`, `blob_record_sigil`, `availability`, `availability_as_of`, `availability_basis_sigil`, `integrity_event_sigils`, `quarantine`, `replica`. | +| `QUARANTINED` | `kind`, `terminal_storage_status`, `claimed_blob_sigil`, `claimed_size_bytes`, `quarantine_binding_sigil`, `quarantine_id`, `owner_kind`, `owner_id`, `state`, `source_object`, `destination_object`, `quarantine_record_sigil`, `quarantine_event`, `reason`. | +| `QUARANTINE_TERMINAL_NEGATIVE` | `kind`, `terminal_storage_status`, `claimed_blob_sigil`, `claimed_size_bytes`, `quarantine_binding_sigil`, `quarantine_id`, `owner_kind`, `owner_id`, `state`, `source_object`, `destination_object`, `quarantine_record_sigil`, `origin_event`, `observation_event`, `reason`; status is constant `UNAVAILABLE` and state is `FAILED`, `DISPOSAL_FAILED`, or `DISPOSED`. | + +`NONE` is legal only when the source bytes never became an RFC-0013 Blob or +Quarantine record. It is explicit negative evidence and cannot make a Job +succeed. In `BLOB`, `terminal_storage_status` and `availability` are the same +RFC-0013 value, exactly `AVAILABLE`, `DEGRADED`, `UNAVAILABLE`, or +`INCIDENT`. `blob_sigil`, `size_bytes`, `blob_record_sigil`, +`availability_as_of`, `availability_basis_sigil`, and the sorted unique +`integrity_event_sigils` equal the complete frozen RFC-0013 Blob projection. +The member's independently declared Blob identity and size must equal this +branch. `availability_as_of` is an exact RFC-0013 `EventRef` no later than +`storage_event`, not a Timestamp. + +`BLOB.quarantine` is exactly `NONE {kind}`. A directly quarantined member +uses the `QUARANTINED` storage branch instead, whose +`terminal_storage_status` is constant `QUARANTINED`. +`claimed_blob_sigil` and `claimed_size_bytes` are non-null and equal the +member's claimed identity and size. The member deterministically constructs +its closed subject branch and resolves the observation document's exact +quarantine-binding-set ID-and-Sigil pair. Exactly one binding has that subject +byte-for-byte; `quarantine_binding_sigil`, Quarantine ID, owner, record Sigil, +and complete `quarantine_event` equal that binding's `observation_event`. The remaining fields +copy that one exact RFC-0013 Quarantine State item, including its nullable +source object, non-null destination `BackendObjectRef`, and closed `Reason`. + +Only a QBS `RETAINED` binding is legal in `QUARANTINED`: its origin is +`quarantine.recorded` and its State is `HELD` or `DISPOSAL_FAILED`. In both +cases the +referenced destination generation remains durably isolated and its +`blob_sigil` and `size_bytes` equal the claimed pair. `INTENT_RECORDED`, +`INSPECTING`, and `DISPOSING` block accounting. A QBS +`TERMINAL_NEGATIVE` binding uses `FAILED`, `DISPOSAL_FAILED`, or `DISPOSED` +only with the exact origin/state matrix above and is legal only in +`QUARANTINE_TERMINAL_NEGATIVE`, using the QBS binding's exact origin and +observation Events; that branch is unavailable and never success-eligible. +A failed move, failed-origin disposition failure, or completed disposition +cannot be reported as retained Quarantine. If `source_object` is non-null, its size +equals the claimed size and its Blob Sigil is either null exactly as preserved +by RFC-0013's unauthenticated-intent branch or equals the claimed Sigil; no +other identity is legal. Source and destination objects retain their complete +backend/object/locator identity Sigils and immutable generations. The +Quarantine State record's last EventRef is no later than `storage_event`. +The complete quarantine-binding set contains every and only quarantined +observation subject, so a Quarantine item cannot be selected by shared Blob, +size, filename, directory enumeration, most-recent time, or an unrelated +owner. + +`BLOB.replica` is exactly `NONE {kind}` or +`SELECTED {kind, replica_id, replica_record_sigil, state, backend, object, +verification}`. The selected branch imports RFC-0013 `BackendRef`, +`BackendObjectRef`, and `VerificationRef` without restatement; `state` is +constant `AVAILABLE`, so the observation retains the backend identity, +backend profile version and Sigil, object identity and locator Sigils, +immutable backend generation, verification method and evidence, verification +time, and next due time. For every `BLOB` branch, the selector is total: +replay the frozen Blob's exact sorted `eligible_replica_ids`; use `NONE` iff +that array is empty, otherwise select the unsigned-ASCII smallest Replica ID +and copy that complete frozen Replica projection. The selected Replica's +Blob, size, backend object Blob, state, generation, and verification must +match the member and frozen Blob. No caller preference, backend priority, +filesystem order, current availability, or retry may select a different +Replica. + +Before the mandatory final `ACCOUNTING_CAPTURED` progress event, when logs, +outputs, terminal-source disposition, the complete output ESM and its +Blob/Reference-Set hold, and every +metered storage, evidence, Quarantine, and cleanup action are closed, the +Executor acquires the RFC-0013 outer gate and then the Storage Journal lock. +It verifies the one complete Storage prefix and State, durably creates or +reuses the exact CES and QBS resolver records, and fixes the QBS historical +prefix. It releases the Storage Journal lock while retaining the outer gate, +then appends `ACCOUNTING_CAPTURED` with the CES and QBS `FROZEN` pairs, the +frozen output ESM pair, and its exact `NO_HOLD` or `HELD` protection branch. + +Immediately afterward, still under that gate, it constructs this observation +document from the QBS-bound historical prefix only when the deterministic OS +pending slot is absent. If that slot already exists, it instead replays and +validates the record's same bound historical prefix and reuses the exact pair. +It appends the Attempt terminal event with that `FROZEN` reference and only +afterward releases the outer gate. All three terminalization documents and +resolvers are pre-reserved Executor control overhead; their writes cannot +create or change a Blob, Replica, Quarantine, hold, or backend object after +accounting capture. Every `BLOB` member that must survive +terminalization is covered by one of the terminal event's pre-held +`ATTEMPT_OUTPUT` Reference Sets; one Reference Set may cover many members. A +crash can therefore leave at most one pending immutable record and +conservative holds for an Attempt, but never an accounting capture that +omitted later metered work or a terminal event whose referenced record or Blob +protection was not already durable. + +Idempotency is byte-exact. Before terminal commitment, retry under the same +owner and terminal inputs derives the same OS-ID. If its pending resolver slot +exists, the retry must reuse that record's Storage EventRef, State Sigil, +members, and self-Sigil; it cannot refresh the observation from a later +Storage Head. After terminal commitment, replay or a repeated internal +finalization request returns the already committed terminal event and its +exact resolver pair without rebuilding the document. Reuse of an OS-ID with +a changed member, prefix, owner, or self-Sigil is an integrity failure, not a +second observation and not permission to search for another hash. Because an +Attempt's ID inputs freeze before construction, at most one such record exists +per Attempt and the global record count is bounded by `MAX_ATTEMPTS` 4,096. + +### Storage-root linearization + +RFC-0013 owns Blob, Reference Set, hold, and GC semantics. This RFC owns when +an execution root becomes visible or inactive. The two journals use the +RFC-0013 outer canonical-reference gate to make that boundary conservative +under every crash. + +V1 has exactly three root kinds and activation events: + +| Root kind | Owner equality | Sole activating execution event | Sole inactivating execution event | +| --- | --- | --- | --- | +| `JOB_INPUT` | Entry `job_id` equals the submitted Job; `attempt_id` is null. | `job.submitted` in its required `job_storage_roots`. | That Job's terminal event. | +| `ATTEMPT_INPUT` | Entry `job_id` and `attempt_id` equal the preflight Attempt and its immutable parent. | `attempt.preflight_passed` in its required `input_storage_roots`. | That Attempt's terminal event. | +| `ATTEMPT_OUTPUT` | Entry `job_id` and `attempt_id` equal the terminal Attempt and its immutable parent. | That Attempt's terminal event in its required `output_storage_roots`. | The parent Job's terminal event. | + +Every array is present and contains zero or one entry. One ESM and one +Reference Set cover the complete Blob closure for that owner and root kind; +splitting a closure across multiple roots is invalid. No other event can +carry, activate, rename, or inactivate an execution root. `ACTIVE_CONTROL` is +forbidden in RFC-0012 v1: +the event and State Schemas reject it, and an implementation that needs such a +root requires a new RFC and Schema version with a complete event lifecycle. + +Before `job.submitted`, the Executor holds the gate, registers the exact +Reference Sets for the immutable Job inputs, and appends every Storage hold. +It releases the Storage Journal lock, appends `job.submitted` with the exact +pre-held `job_storage_roots`, and then releases the outer gate. Immediately +before that append it re-resolves each hold as `ACTIVE` and proves that no +deterministic EHR record exists. A hold without the submission event is an +orphan conservative hold, never a live Job root. The Job roots remain active +through every retry and become inactive only when the Job terminal event +commits. + +Before `attempt.preflight_passed` may expose a managed Attempt input, the +Executor performs the same hold-first order and appends that event with only +`ATTEMPT_INPUT` entries. Output transfer follows the same order: verified +Blob, Reference Set, and hold are durable before the Attempt terminal event +binds only `ATTEMPT_OUTPUT` entries. Pending input or output whose hold exists +but whose activating event does not is an orphan conservative Storage hold +and is not an active execution root or eligible output. Each activating Event +performs the same final `ACTIVE`-hold and no-EHR check under the gate. + +Root removal uses the reverse safe order while holding the same gate. The +Execution Journal first appends the transition that makes the root inactive; +only then may the Executor durably create the matching `OWNER_TERMINAL` or +`OUTPUT_DEADLINE` EHR and Storage append `retention.hold_released`. An +unactivated orphan instead requires the exact `ORPHAN_ABORT` EHR above. A +crash therefore leaves an extra hold, never an unprotected live execution +reference. `JOB_INPUT` is released only by its Job-terminal +`OWNER_TERMINAL` basis and `ATTEMPT_INPUT` only by its Attempt-terminal +`OWNER_TERMINAL` basis. State retains each historical binding after +inactivation, but +`ExecutionRootSnapshot` includes it only from the activating event through the +prefix immediately before its inactivating event. Output holds may remain +after Job terminal only through their immutable execution-owned retention +schedule below; lingering hold duration does not reactivate the execution +root. A canonical RFC-0015 reference, legal hold, or RFC-0013 retention policy +uses its own independent protection and is never represented by prolonging an +execution hold. + +Every Job terminal payload contains sorted +`output_hold_release_schedules`, one for each child `ATTEMPT_OUTPUT` root and +none for an output manifest whose protection branch is `NO_HOLD`. Each closed +entry has exactly `attempt_id`, `storage_root_manifest_id`, +`storage_root_manifest_sigil`, `reference_set_id`, `reference_set_sigil`, +`hold_id`, `hold_set_event`, `retention_duration_seconds`, +`root_inactivation_event_id`, `release_due_at`, and `deadline_status`. +`root_inactivation_event_id` equals the enclosing Job terminal Event ID. +The duration is the bound Specification value for `CODE_MODIFICATION` and +zero for `NONE`; it is the maximum lifetime of this execution-owned hold, not +the minimum physical lifetime of any RFC-0013 policy. With checked Timestamp +arithmetic, `release_due_at = terminal recorded_at + duration` and +`deadline_status: EXACT`. If addition is unrepresentable, the entry instead +uses the terminal `recorded_at` and `OVERFLOW_FAIL_CLOSED`; that Job cannot +be `SUCCEEDED` under `CODE_MODIFICATION`. + +Release uses the same outer gate. The Executor first proves by complete +Execution replay that the named Job terminal Event inactivated the root and +that trusted time is at or after `release_due_at`, then creates or resolves the +exact `OUTPUT_DEADLINE` EHR. Storage appends the one +`retention.hold_released` for exactly this `SH-ID` with +`authorization_sigil == release_authorization_sigil`; afterward Execution +appends `storage_root.hold_release_observed` with the complete schedule, EHR +ID and Sigil, and exact Storage release `EventRef`. A zero-duration schedule +follows this order immediately after the Job terminal Event under the same +gate. A crash at any point leaves either an extra hold or a durable EHR or +Storage release that Recovery can reuse; it never releases before +inactivation. Clock uncertainty, unavailable replay, or an ambiguous Storage +prefix preserves the hold. + +The observation payload's `release_binding` is a closed union: + +- `OWNER_TERMINAL {kind, storage_root, release_authorization_id, + release_authorization_sigil}` for an activated `JOB_INPUT` or + `ATTEMPT_INPUT`; or +- `OUTPUT_DEADLINE {kind, release_schedule, release_authorization_id, + release_authorization_sigil}` for `ATTEMPT_OUTPUT`. + +The owner branch's root and EHR equal the complete release authorization; the +output branch's schedule is byte-for-byte one entry from its terminal Job. +`storage_release_event` resolves the exact RFC-0013 +`retention.hold_released` Event. Its hold ID, reason branch, and authorization +Sigil equal the resolved EHR. One observation is legal per hold; exact replay +returns it and conflicting binding, EHR, or Storage Event substitution fails. +`ORPHAN_ABORT` has no observation branch: by definition no activating +execution Event made a Job or Attempt owner visible, so the durable EHR and +the exact Storage release Event are its complete operational evidence. + +The release removes only the execution-owned hold. It neither deletes bytes +nor alters a policy hold, legal hold, canonical reference intent, accepted +Agent Result pin, or another root. RFC-0013 independently decides physical GC +eligibility after all remaining protections are applied. + +Recovery acquires the gate, replays both journals one at a time in the order +defined by RFC-0013, and reconciles only toward more protection when either +side is unavailable or ambiguous. An orphan has no execution-journal owner +and therefore is not an action in +`execution-recovery-action-set/1.0`. Only after no Execution Recovery is +active, a Storage-side orphan reconciler may hold the gate, replay through the +complete current Execution Head, prove absence of the sole activation, create +or resolve the deterministic `ORPHAN_ABORT` EHR, and reuse or append only that +hold's Storage release. The same rule applies inside exact Start recovery +before a new activation is considered. For an inactive input root, Execution +Recovery creates or resolves `OWNER_TERMINAL`; for an output root it executes +`RELEASE_DUE_EXECUTION_HOLD` only for an `EXACT` due schedule or the immediate +`OVERFLOW_FAIL_CLOSED` schedule and resolves `OUTPUT_DEADLINE`. In every +branch it reuses an existing EHR and Storage release before any missing +observation. An ambiguous Head, prefix, clock, EHR, or release retains the +hold. This protocol lets RFC-0013 GC derive its closed +`ExecutionRootSnapshot`; a filename, result message, current process, or +unlocked Head comparison is never an execution root decision. + +## Requested and realized assurance + +Requested assurance is immutable input; realized assurance is terminal +evidence. + +The Execution Specification and Job bind: + +```text +(requested level, + assurance-profile version and Sigil, + permitted conformance-suite identity and Sigil) +``` + +Each Attempt repeats that tuple and records a planned backend identity, +backend-configuration Sigil, and preflight eligibility decision. Worker +capability, backend selection, conformance history, and `PREFLIGHTING -> READY` +show only that the Attempt may try to meet the request. None is a realized +claim. + +`sanctum-assurance-profile/1.0` has exactly `schema_version`, +`profile_version`, `levels`, `conformance_suites`, and `profile_sigil`. +`schema_version` is the constant `sanctum-assurance-profile/1.0`; every other +member is covered by `profile_sigil`. `levels` contains exactly three entries, +in increasing `rank`, and each closed entry has exactly `level`, `rank`, +`name`, `threat_model_sigil`, `control_requirements`, and `level_sigil`. The +three fixed `(level, rank, name)` tuples are +`(SANCTUM-A0, 0, DECLARED_ATTEMPT)`, +`(SANCTUM-A1, 1, SUPERVISED)`, and +`(SANCTUM-A2, 2, ISOLATED)`. A3 is reserved by RFC-0011 and is not a v1 enum +value. Each `control_requirements` array has exactly one entry for each of the +ten control dimensions in the printed matrix order. An entry has exactly +`control_dimension`, `required_state`, `required_evidence_kinds`, and +`requirement_evidence_profile_sigil`. `required_evidence_kinds` is a unique +non-empty array in the evidence-kind enum order defined below; the evidence +profile Sigil binds the closed verifier predicates applied to those kinds. + +The A0 and A1 `required_state` values are exactly the matrix below. The A2 +vector, in that same control-dimension order, is exactly +`VERIFIED`, `ENFORCED`, `ENFORCED`, `ENFORCED`, `ENFORCED`, `ENFORCED`, +`ENFORCED`, `VERIFIED`, `ENFORCED`, `VERIFIED`. Its evidence-profile Sigils +bind the externally enforced isolation, adversarial fixture, and Host threat +model required by RFC-0011; an A1 evidence profile cannot be reused as an A2 +entry. + +`conformance_suites` is a non-empty bounded array sorted by +`(level rank, conformance_suite_id unsigned ASCII, suite_version unsigned +ASCII)`. Every closed entry has exactly `conformance_suite_id`, +`suite_version`, `level`, `backend_constraints_sigil`, +`host_constraints_sigil`, `fixture_set_sigil`, +`evidence_oracle_sigil`, and `conformance_suite_sigil`. IDs are unique within +a profile version. Each suite's `level` names one of the three profile +entries, and its fixture and oracle Sigils must test every requirement through +that level. An Execution Specification's `profile_version` and +`profile_sigil` identify this complete document, while its +`conformance_suite_id` and `conformance_suite_sigil` identify exactly one +entry whose level rank is at least the requested rank. No member is optional +or nullable, and unknown levels, states, dimensions, suites, or duplicate +entries fail closed. + +The `execution-control-evidence/1.0` top-level object has exactly: + +| Field | Exact v1 meaning | +| --- | --- | +| `schema_version` | Constant `execution-control-evidence/1.0`. | +| `control_evidence_id`, `job_id`, `attempt_id` | Evidence and execution scope. | +| `control_dimension` | One of the exact dimensions in the matrix below. | +| `required_state` | Exactly `ENFORCED`, `VERIFIED`, `OBSERVED`, or `ANY_RECORDED`. | +| `realized_state` | Exactly `ENFORCED`, `VERIFIED`, `OBSERVED`, or `UNAVAILABLE`. | +| `phase_evidence` | Exactly `preflight`, `runtime`, `termination`, and `cleanup`; each is a `0..4096` array, present even when empty. | +| `bindings` | Exactly `backend_sigil`, `host_sigil`, `runtime_sigil`, `policy_set_sigil`, `input_set_sigil`, and `output_set_sigil`. | +| `deficiencies` | Zero to 4,096 sorted closed reason entries; present even when empty. | +| `control_evidence_sigil` | Sigil over every other member. | + +Every `phase_evidence` entry has exactly `evidence_kind`, +`producer_identity`, `verifier_identity`, `collected_at`, `byte_size`, +`blob_sigil`, and `staging_reference`. `byte_size` and `blob_sigil` are either +both null or respectively RFC-0013 `U63` and `Sigil`. +`staging_reference` is exactly +`NONE {kind}` when that pair is null or +`TRANSFER {kind, storage_subject_id, transfer: RFC-0013 TransferRef, +provenance_id, provenance_sigil}` when it is non-null. The transfer and +provenance resolve the same Blob and exact current Attempt owner. Across all +four arrays of one document there are at most 4,096 entries, and across the +ten documents selected by one CES there are at most 4,096 Blob-bearing +entries; overflow fails before the CES or output ESM is durable. +The exact `control_dimension` enum is +`IDENTITY_AUTHORIZATION`, `FILESYSTEM`, `NETWORK`, +`PROCESS_EXECUTABLE`, `RESOURCE`, `ENVIRONMENT_CREDENTIAL`, +`LOG_OUTPUT_CAPTURE`, `RUNTIME_INPUT_OUTPUT_IDENTITY`, +`CANCELLATION_FENCING`, and `TERMINATION_CLEANUP`. There is no generic +dimension or evidence dictionary. + +The exact `evidence_kind` enum is `TASK_BINDING`, `CAPABILITY_BINDING`, +`SNAPSHOT_BINDING`, `WARD_DECISION`, `APPROVAL_RECEIPT`, +`BACKEND_CONFIGURATION`, `HOST_IDENTITY`, `POLICY_RESOLUTION`, +`BASE_IDENTITY`, `INPUT_IDENTITY`, `MATERIALIZATION_IDENTITY`, +`ENVIRONMENT_CONSTRUCTION`, `FILESYSTEM_POLICY`, `NETWORK_POLICY`, +`EXECUTABLE_SELECTION`, `PROCESS_TREE`, `WALL_TIME_ENFORCEMENT`, +`RESOURCE_ACCOUNTING`, `CREDENTIAL_NONINHERITANCE`, `LOG_CAPTURE`, +`OUTPUT_VALIDATION`, `STORAGE_OBSERVATION`, `FENCE_TOMBSTONE`, +`TERMINATION`, `HANDLE_REVOCATION`, `CLEANUP`, `QUARANTINE`, +`TERMINAL_SOURCE_VERIFICATION`, or `CONFORMANCE_FIXTURE`. Each +`deficiencies` entry has exactly `reason_code`, `phase`, `evidence_kind`, and +`detail_sigil`; `phase` is `PREFLIGHT`, `RUNTIME`, `TERMINATION`, or +`CLEANUP`, and its reason uses the closed protocol reason enum below. + +Requirement satisfaction is an explicit relation, not an inferred ordering: +`ANY_RECORDED` accepts any realized state; `OBSERVED` accepts +`OBSERVED`, `VERIFIED`, or `ENFORCED`; `VERIFIED` accepts only `VERIFIED`; +and `ENFORCED` accepts only `ENFORCED`. An `ENFORCED` document may carry +independent verifier evidence, but enforcement is not silently relabeled +verification and vice versa. + +Only the trusted evidence verifier, after the Attempt is terminal and its +termination and cleanup evidence is immutable, may emit +`sanctum-assurance-claim/1.0`. Its exact required top-level members are +`schema_version`, `assurance_claim_id`, `job_id`, `attempt_id`, +`attempt_terminal_event_id`, `attempt_terminal_event_sigil`, +`requested_assurance`, `realized_level`, `satisfies_request`, +`profile_sigil`, `conformance_suite_sigil`, `backend_identity`, +`backend_configuration_sigil`, `host_identity_sigil`, `policy_set_sigil`, +`result_binding`, `control_evidence_set_binding`, `termination_status`, +`cleanup_status`, `issued_at`, and `assurance_claim_sigil`. +`result_binding` is a closed union: `NONE` has exactly `kind`; `ACCEPTED` has +exactly `kind`, `result_sigil`, and `result_acceptance_event_sigil`. There are +no other optional top-level fields. + +A claim may describe a failed, cancelled, timed-out, or policy-violating +Attempt when its boundary evidence is complete; assurance describes execution +controls, not computational or scientific success. A lower realized level may +be recorded as explicit diagnostic evidence with +`satisfies_request: false`, but it never downgrades the request, makes a result +eligible, or lets a Job succeed. Missing or unverifiable evidence produces no +claim at that level. + +A terminal `SUCCEEDED` Attempt is only a candidate for Job success. It requires: + +1. its accepted result when required, explicit no-result disposition when + permitted, and all selected outputs validate; +2. its Lease was current through accepted completion; +3. its process tree and required handles are terminated or released; +4. cleanup and output publication postconditions pass; and +5. no policy violation, stop trigger, stale binding, or conflicting result + precedes terminal commitment. + +After that terminal event and its budget settlement, +`attempt.assurance_evaluated` records one closed evaluation outcome without +changing the Attempt state. `CLAIMED` includes the valid claim Sigil; `UNMET` +or `UNVERIFIABLE` includes no claim and binds its evidence and reasons. The +Job can become `SUCCEEDED` only when a claim has `satisfies_request: true` and +its realized level is at least the requested level under the exact pinned +profile. + +If computation completed but assurance is missing or insufficient, the +Attempt remains terminal `SUCCEEDED` as an operational computation outcome, +while its result remains ineligible and the Job becomes `FAILED` with reason +`ASSURANCE_UNMET`. The Executor never changes the requested level, substitutes +a different profile or suite, or reports preflight eligibility as realized +assurance. + +### Exact A0 and A1 control/evidence matrix + +For this RFC, `ANY_RECORDED` requires a control-evidence document that +truthfully uses one of the four realized evidence states; it does not imply +enforcement. These are the exact v1 minima: + +| Control dimension | `SANCTUM-A0` minimum | `SANCTUM-A1` minimum and required evidence | +| --- | --- | --- | +| `IDENTITY_AUTHORIZATION` | `VERIFIED`: v2 Task, Capability, Snapshot, Ward, approval when required, Specification, Job, and Attempt identities and Sigils. | Same `VERIFIED` set, plus selected backend/profile/suite/configuration bindings. | +| `FILESYSTEM` | `ANY_RECORDED`; no containment claim. | `VERIFIED`: exclusive Crucible materialized from the pinned immutable Base or validated immutable resume identity, new mutable identity, and no reuse of ambiguous predecessor state. | +| `NETWORK` | `ANY_RECORDED`; no containment claim. | `OBSERVED`: closed network policy and supervisor observation are retained; this remains a cooperative-workload statement, not network isolation. | +| `PROCESS_EXECUTABLE` | `ANY_RECORDED`; no process-control claim. | `ENFORCED`: allowlisted initial executable, exact runtime identity, tracked cooperative process group, and supervisor start/exit evidence. | +| `RESOURCE` | `ANY_RECORDED`; Worker samples alone are diagnostic. | `ENFORCED`: durable wall-time/cancellation timers, per-Attempt ceilings, trusted Job-budget accounting, and terminal resource evidence. | +| `ENVIRONMENT_CREDENTIAL` | `ANY_RECORDED`; no hostile secret-exclusion claim. | `ENFORCED`: constructed allowlisted environment and absence of ambient credentials and unrelated inherited variables, with supervisor construction evidence. | +| `LOG_OUTPUT_CAPTURE` | `ANY_RECORDED`; bounds and stale-message rejection still apply operationally. | `ENFORCED`: bounded external capture, deterministic truncation/termination behavior, exact output contracts, and verified stream/output closure. | +| `RUNTIME_INPUT_OUTPUT_IDENTITY` | `VERIFIED` for pinned Job/Attempt input bindings; runtime/output absence is explicit. | `VERIFIED`: exact runtime, immutable inputs, produced outputs, Blob Sigils, byte counts, and storage disposition. | +| `CANCELLATION_FENCING` | `VERIFIED`: current fence tuple decides message disposition; this claims no process containment. | `ENFORCED`: supervisor wall-time and cooperative cancellation, Lease tombstone publication, no authority resurrection, and no ambiguous mutable-resource reuse. | +| `TERMINATION_CLEANUP` | `ANY_RECORDED`; missing evidence may be `UNAVAILABLE`. | `VERIFIED`: durable terminal event, tracked process-group termination or safe quarantine, handle disposition, cleanup, and retained evidence. | + +Every A1 cell is mandatory. A policy whose own minimum evidence state is +stronger remains stronger; the matrix never weakens a v2 Capability or Task. +A1 does not claim hostile filesystem, network, process, resource, credential, +or `.benchwork/` isolation; those are A2 properties. + +Required negative fixtures include: + +- Phase 2 native-Host activity, or a Job with no Attempt, cannot claim A0; +- a missing Ward/approval/Sigil binding cannot claim A0 even if a process ran; +- Worker self-report, PID existence, or preflight eligibility cannot replace + an A0 fence decision or an A1 verifier; +- a reused mutable Crucible, mutable resume path, inherited ambient + credentials, untracked child, process-local-only wall timer, unbounded or + unclosed capture, Worker-only accounting, or missing terminal/cleanup + evidence prevents A1; +- A1 evidence cannot be relabeled A2 merely because a container, worktree, or + namespace mechanism was present; and +- an incomplete control is recorded at its actual lower state and yields + `UNMET` or `UNVERIFIABLE`, never an inferred claim. + +## Operational journal + +The execution journal is append-only operational state and is physically and +logically separate from Chronicle. It must not allocate Chronicle event types +or Receipt IDs, write Chronicle Heads/Seals/projections, or use canonical +object stores as its journal. It may carry an exact immutable external +Chronicle Event/Receipt reference where this RFC explicitly requires one, +such as Attempt authorization; that reference grants no execution-journal +writer authority. No Job or heartbeat event is appended to Chronicle. + +### Journal Head and replayed State contracts + +The closed limit profile is the constant +`EXECUTION_JOURNAL_V1_FIXED_LIMITS`. Its Schema constants are: + +| Constant | Exact inclusive maximum | +| --- | ---: | +| `MAX_RECOVERIES` | 4,096 | +| `MAX_WORKERS` | 4,096 | +| `MAX_WORKER_SESSIONS` | 4,096 | +| `MAX_JOBS` | 4,096 | +| `MAX_ATTEMPTS` | 4,096 | +| `MAX_EXECUTION_STORAGE_ROOT_MANIFESTS` | 12,288 | +| `MAX_EXECUTION_STORAGE_ROOT_MANIFEST_ENTRIES` | 4,096 per manifest | +| `MAX_OUTPUT_STORAGE_OBSERVATION_RECORDS` | 4,096 | +| `MAX_OUTPUT_STORAGE_OBSERVATION_MEMBERS` | 4,096 | +| `MAX_OUTPUT_STORAGE_OBSERVATION_BLOBS` | 4,096 | +| `MAX_LEASES` | 4,096 | +| `MAX_LOG_STREAMS` | 12,288 | +| `MAX_DEADLINES` | 32,768 | +| `MAX_IDEMPOTENCY_RECORDS` | 65,536 | +| `MAX_ENTITY_PROJECTIONS` | 36,865, including the singleton Executor | +| `MAX_STATE_ROWS` | 135,169, including entity, deadline, and idempotency rows | +| `MAX_ENTITY_REVISIONS_PER_EVENT` | 5 | + +One Job-input ESM plus at most one input and one output ESM per Attempt +explains the 12,288 manifest limit; the actual total is additionally bounded +by the created Job and Attempt counts. The three Log streams created per +Attempt explain the 12,288 Log-stream limit. The +single-assignment OS-ID rule makes the observation-record limit equal to +`MAX_ATTEMPTS`; pending and terminal records count identically. Counts include +terminal and failed history and never decrease. A `CREATE` or adjunct record +that would exceed its collection, entity-total, or State-total limit is +rejected before event-ID/sequence reservation and cannot be made legal by +dropping history. Existing entities may still advance so cleanup and +terminalization remain possible. + +`execution-journal-head/1.0` has exactly `schema_version`, `limit_profile`, +`journal_id`, `last_sequence`, `last_event_id`, `last_event_sigil`, +`updated_at`, and `head_sigil`. `schema_version` is constant +`execution-journal-head/1.0`; `last_sequence` is `PositiveU63` because a valid +journal begins with `executor.epoch_started`; the event ID and Sigil +must identify that exact sequence; and `head_sigil` covers every other +member. `limit_profile` is the constant above. Head validation replays or +checks the bound prefix's collection counts against every constant before the +cache can be installed. `updated_at` is diagnostic cache metadata and never +participates in event ordering. No field is optional or nullable. + +`execution-state/1.0` is a closed replaceable replay cache, not an authority +source. Its exact top-level members are `schema_version`, `limit_profile`, +`journal_binding`, `executor`, `recoveries`, `workers`, `worker_sessions`, +`jobs`, `attempts`, `leases`, `log_streams`, `deadlines`, +`idempotency_records`, and `state_sigil`. `schema_version` is constant +`execution-state/1.0`; `limit_profile` is the constant above. +`journal_binding` has exactly `journal_id`, `through_sequence`, +`through_event_id`, and `through_event_sigil`; it must equal the verified +journal prefix used to derive every other field, and `through_sequence` is +`PositiveU63`. `state_sigil` covers every +other member. All arrays are present even when empty, contain no duplicate +identity, include every entity created in the bound journal prefix, and are +bounded respectively by the matching fixed `MAX_*` constants. Their entity +sum and complete row sum must also fit the two total constants. A limit cannot +be used to omit terminal or failed history. + +The State Schema publishes these closed reusable projection aliases: + +- `public_fence_tuple` has exactly `journal_id`, `executor_epoch`, `job_id`, + `attempt_id`, `lease_id`, and `fencing_generation`; +- `executor_build_binding` has exactly `implementation_id`, + `implementation_version`, `source_revision_sigil`, + `executable_artifact_sigil`, `dependency_set_sigil`, + `build_configuration_sigil`, and `executor_build_sigil`. The first two are + non-empty bounded `Opaque` values, every remaining value is `Sigil`, and + `executor_build_sigil` is the self-Sigil over the other six fields; +- `terminal_event_binding` is exactly `NONE {kind}` or + `PRESENT {kind, terminal_state, terminal_event_id, terminal_event_sigil, + terminal_sequence, terminal_recorded_at}`. Context restricts + `terminal_state` to the printed terminal enum for the owning entity; +- `attempt_authorization_requirement` is the exact immutable RFC-0011 + `NONE {kind, effects}` or `REQUIRED {kind, effects}` branch above; +- `attempt_authorization_binding` is the exact nine-field closed binding + above, and its subject ID/Sigil always resolves the complete canonical + `attempt-authorization-subject/1.0`; its request, Event, and Receipt fields + resolve the complete authorization chain above; +- `attempt_authorization_state` is exactly `NONE {kind}`, `PENDING {kind}`, + or `BOUND {kind, authorization_subject, + attempt_authorization_binding}`; the embedded subject uses the canonical + contract `$ref` and the binding uses the alias immediately above; +- `attempt_assurance_binding` is exactly `PENDING {kind}`, + `CLAIMED {kind, evaluation_event_id, evaluation_event_sigil, + evaluation_sequence, assurance_claim_sigil, evidence_set_sigil}`, + `UNMET {kind, evaluation_event_id, evaluation_event_sigil, + evaluation_sequence, reason_codes, evidence_set_sigil}`, or + `UNVERIFIABLE {kind, evaluation_event_id, evaluation_event_sigil, + evaluation_sequence, reason_codes, evidence_set_sigil}`; +- `job_assurance_binding` is exactly `PENDING {kind}`, + `CLAIMED {kind, evaluation_event_id, evaluation_event_sigil, + evaluation_sequence, attempt_id, attempt_assurance_event_sigil, + assurance_claim_sigil, evidence_set_sigil}`, + `UNMET {kind, evaluation_event_id, evaluation_event_sigil, + evaluation_sequence, attempt_id, attempt_assurance_event_sigil, + reason_codes, evidence_set_sigil}`, + `UNVERIFIABLE {kind, evaluation_event_id, evaluation_event_sigil, + evaluation_sequence, attempt_id, attempt_assurance_event_sigil, + reason_codes, evidence_set_sigil}`, or + `NOT_APPLICABLE {kind, evaluation_event_id, evaluation_event_sigil, + evaluation_sequence, reason, evidence_set_sigil}`; the last branch has + constant reason `NO_ATTEMPT_ALLOCATED`; +- `budget_dimension` has exactly `limit`, `reserved`, `consumed`, and + `exhaustion_status`. The counters are non-negative checked integers. + `exhaustion_status` is exactly `AVAILABLE` when + `consumed + reserved < limit`, `EXHAUSTED` when it equals the limit, or + `EXCEEDED` when trusted settlement proves use beyond the limit; only + settlement may create `EXCEEDED`, and no further reservation is legal; and +- `budget_ledger` has exactly `attempts`, `cpu_time_seconds`, + `storage_bytes_written`, `output_bytes`, `log_bytes`, `process_starts`, + `network_egress_bytes`, `network_requests`, and `budget_ledger_sigil`; + each dimension is the closed object above and the Sigil covers all eight; +- `budget_settlement_binding` is exactly `PENDING {kind}` or + `SETTLED {kind, event_id, event_sigil, accounting_capture_event_id, + accounting_capture_event_sigil, usage_status}`; `usage_status` is + `MEASURED`, `PARTIAL`, or `UNAVAILABLE`; and +- `storage_root_binding` is byte-for-byte the journal-event `$def` of the same + name described below. State Schema `$ref` uses its canonical RFC-0012 URI + rather than redefining it; +- `terminalization_storage_manifest_binding` is the exact `PENDING` or + `FROZEN` ESM pair above; and +- `output_root_protection` is `PENDING {kind}` before capture and the exact + `NO_HOLD` or `HELD` branch above afterward. Both are published as named + journal-event `$defs` and imported rather than cloned. + +The executable Schemas also publish these named definitions at their canonical +URIs without changing the enums printed elsewhere: + +- `https://benchwork.dev/schemas/execution-state/1.0#/$defs/executor_build_binding` + is the exact closed build object above; +- `https://benchwork.dev/schemas/execution-state/1.0#/$defs/job_state` is the + scalar enum `SUBMITTED`, `QUEUED`, `ACTIVE`, `RETRY_WAIT`, `STOPPING`, + `SUCCEEDED`, `FAILED`, `CANCELLED`, `TIMED_OUT`, or `POLICY_VIOLATION`; +- `https://benchwork.dev/schemas/execution-state/1.0#/$defs/attempt_state` is + the scalar enum `CREATED`, `PREFLIGHTING`, `READY`, `LEASED`, `STARTING`, + `RUNNING`, `DRAINING`, `STOPPING`, `CLEANING`, `SUCCEEDED`, `FAILED`, + `CANCELLED`, `TIMED_OUT`, `POLICY_VIOLATION`, `LEASE_EXPIRED`, `LOST`, + `FENCED`, or `REJECTED`; +- `https://benchwork.dev/schemas/execution-state/1.0#/$defs/worker_session_state` + is the scalar enum `REGISTERED`, `READY`, `BUSY`, `DRAINING`, `OFFLINE`, + `QUARANTINED`, or `CLOSED`; +- `https://benchwork.dev/schemas/execution-journal-event/1.0#/$defs/event_type` + is the exact 73-value enum block below; and +- `https://benchwork.dev/schemas/execution-journal-event/1.0#/$defs/storage_root_binding` + is the exact closed object below. + +Every State or event field carrying one of these values uses the named `$ref`; +RFC-0015 observation Schemas import the same URI rather than cloning a local +enum. + +The exact `executor` object has `executor_instance_id`, `executor_epoch`, +`executor_build_binding`, `revision`, `clock_state`, `last_trusted_utc`, +`clock_uncertain_event_id`, `active_recovery_id`, `authority_gates`, +`last_event_id`, and `last_event_sigil`. `clock_state` is `TRUSTED` or +`UNCERTAIN`; `clock_uncertain_event_id` is non-null exactly in `UNCERTAIN`. +`active_recovery_id` is non-null exactly while one Recovery is active. +`authority_gates` is a unique array in the fixed order +`INTEGRITY_FAILURE`, `CLOCK_UNCERTAIN`, `RECOVERY_ACTIVE`; it is empty only +when none applies. + +Every `recoveries` entry has exactly `recovery_id`, `revision`, `state`, +`prior_recovery_id`, `started_event_sigil`, `current_action_set_sigil`, +`last_event_id`, and `last_event_sigil`. `state` is `STARTED`, `FENCING`, +`RECONCILING`, `FINALIZING`, or `COMPLETED`; `prior_recovery_id` is null only +for the first Recovery. Entries are sorted by their `recovery.started` +sequence. + +Every `workers` entry has exactly `worker_id`, `revision`, `state`, +`worker_binding_sigil`, `definition_revision`, `worker_session_ids`, +`last_event_id`, and `last_event_sigil`. Every `worker_sessions` entry has +exactly `worker_session_id`, `revision`, `state`, `worker_id`, +`worker_binding_sigil`, `worker_session_binding_sigil`, `executor_epoch`, +`capacity`, `capacity_in_use`, `last_heartbeat_sequence`, +`last_heartbeat_message_sigil`, `next_heartbeat_due_at`, `lease_ids`, +`last_event_id`, and `last_event_sigil`. Capacity is null only before +`worker_session.ready`; `capacity_in_use` is zero while capacity is null and +otherwise a non-negative integer no greater than capacity. Both +last-heartbeat fields are null before the first accepted heartbeat and +thereafter identify the same message; next due time is null in `REGISTERED`, +`OFFLINE`, `QUARANTINED`, or `CLOSED`. Identity arrays are unsigned-ASCII +sorted. Workers and Sessions use their exact state enums above. + +Every `jobs` entry has exactly `job_id`, `revision`, `state`, +`job_binding_sigil`, `job_storage_roots`, `attempt_ids`, +`current_attempt_id`, +`fencing_counter`, `fence_floor`, `budget_ledger`, `deadline_due_at`, +`retry_eligible_due_at`, `queue_key`, `attempt_summaries`, +`selected_attempt_binding`, `completion_anchor_binding`, +`first_stop_or_fence_binding`, `final_fence_binding`, +`storage_observation_binding`, `terminal_source_binding`, +`output_hold_release_schedules`, `job_assurance_binding`, +`terminal_event_binding`, `last_event_id`, and +`last_event_sigil`. `current_attempt_id` is set by +`job.attempt_allocated` and remains that Attempt ID through Attempt +terminalization and `job.budget_settled`; it becomes null only in the +subsequent `attempt.assurance_evaluated` multi-owner event. It is otherwise +null, including before the first allocation and between a finalized Attempt +and retry allocation. Retry due time is non-null exactly in `RETRY_WAIT`; +queue key is non-null exactly in `QUEUED`. +`fencing_counter` and `fence_floor` are unsigned 64-bit integers with +`fence_floor <= fencing_counter`; both start at zero and only the atomic +events named below advance them. +`attempt_summaries` contains one exact `FINAL` summary only for an Attempt +whose terminal event, Job-budget settlement, and Attempt assurance evaluation +have all committed. A terminal-but-unsettled or settled-but-unevaluated +Attempt remains represented by `current_attempt_id` and its Attempt +projection, with no partial summary. `attempt.assurance_evaluated` atomically +adds the one final summary and clears `current_attempt_id`. At Job +terminalization the array covers every allocated Attempt and is +byte-for-byte equal to the terminal payload's sorted array. +`job_storage_roots` is byte-for-byte equal to the immutable Job document and +`job.submitted` payload. It contains only `JOB_INPUT`, remains historical +after terminalization, and is active for Storage-root derivation only from +submission through the prefix immediately before the Job terminal event. +`selected_attempt_binding`, `completion_anchor_binding`, +`final_fence_binding`, `storage_observation_binding`, and +`terminal_source_binding` are null before Job terminalization and, when +terminal, are byte-for-byte equal to the terminal event. The first-stop +binding and assurance binding are always their closed union rather than null. +`output_hold_release_schedules` is empty before terminalization and then +equals the terminal payload's complete immutable sorted schedule; later +release-observation events do not rewrite it or the terminal binding. +A terminal Job with no Attempt uses `storage_observation_binding: +NOT_APPLICABLE`; every terminal Job with an allocated selected Attempt copies +that Attempt's exact `FROZEN` branch. + +Every `attempts` entry has exactly `attempt_id`, `revision`, `state`, +`attempt_binding_sigil`, `job_id`, `retry_ordinal`, +`fencing_generation`, `lease_executor_epoch`, `public_fence_tuple`, +`worker_session_binding`, `lease_id`, `result_binding`, +`attempt_authorization_requirement`, `attempt_authorization_state`, +`completion_anchor_binding`, `lease_terminal_binding`, +`first_stop_or_fence_binding`, `storage_observation_binding`, +`terminal_source_binding`, `accounting_capture_binding`, +`budget_settlement_binding`, `attempt_assurance_binding`, +`input_storage_roots`, `output_storage_roots`, +`control_evidence_set_binding`, `quarantine_binding_set_binding`, +`terminalization_storage_manifest_binding`, `output_root_protection`, +`deadline_due_at`, `grace_due_at`, +`terminal_event_binding`, `last_event_id`, and `last_event_sigil`. +Lease epoch, fence tuple, and Lease ID are null until an offer exists and are +thereafter immutable. The authorization requirement equals the immutable +Attempt document; its state is `NONE` or `PENDING` at allocation as selected +by that branch and may advance once from `PENDING` to `BOUND` only through +`attempt.authorization_bound`. `accounting_capture_binding` is exactly +`PENDING {kind}` or +`CAPTURED {kind, event_id, event_sigil, usage_status, +accounting_evidence_set_sigil}`. `budget_settlement_binding` is exactly +`PENDING {kind}` or +`SETTLED {kind, event_id, event_sigil, accounting_capture_event_id, +accounting_capture_event_sigil, usage_status}`. Grace due time is non-null +only in `STOPPING`. Both terminalization-set bindings and +`terminalization_storage_manifest_binding` are `PENDING` before +`ACCOUNTING_CAPTURED` and their exact `FROZEN` pairs afterward; +`output_root_protection` is `PENDING {kind}` before capture and its exact +`NO_HOLD` or `HELD` branch afterward. None is null. +`lease_terminal_binding` is `NONE` before an offer, +null from offer until the Lease terminal event, and `TERMINAL` thereafter. +`storage_observation_binding` is null before Attempt terminalization and is +the terminal event's exact `FROZEN` branch thereafter; an allocated Attempt +never uses `NOT_APPLICABLE`. Under a `CODE_MODIFICATION` Specification, +`terminal_source_binding` is null until `attempt.cleaning` and then +`VERIFIED` or `QUARANTINED`; under `NONE` it is always `NOT_APPLICABLE`. +`input_storage_roots` is empty until +`attempt.preflight_passed` and then byte-for-byte equals that event's sorted +array of only `ATTEMPT_INPUT`; those roots are active until the prefix +immediately before Attempt terminalization. `output_storage_roots` is empty +until Attempt terminalization and then equals its terminal event's sorted +array of only `ATTEMPT_OUTPUT`; those roots are active until the prefix +immediately before parent Job terminalization. Both historical arrays remain +present after inactivation, contain at most one `storage_root_binding`, +enforce exact owner equality, and use the RFC-0013 root tuple. All other +binding fields use the exact reusable unions in this RFC and +never null. + +The finalization prefix is therefore always constructible. Immediately after +an Attempt terminal event, its `terminal_event_binding` is `PRESENT` while +`budget_settlement_binding` and `attempt_assurance_binding` remain `PENDING`; +the parent Job still names it as `current_attempt_id` and has no summary for +it. `job.budget_settled` then advances both the Job and Attempt, updates the +Job ledger and the Attempt's settlement binding atomically, but leaves +`current_attempt_id` and the absent summary unchanged. +`attempt.assurance_evaluated` is legal only after that settlement; it advances +both projections, freezes the Attempt assurance branch, appends the complete +Job `attempt_summary`, and clears `current_attempt_id` atomically. A crash +after any of these three events replays to exactly one of those states. Retry +scheduling, Job assurance evaluation, and Job terminalization require +`current_attempt_id: null` and therefore cannot observe or serialize a +partial summary. + +Every `leases` entry has exactly `lease_id`, `revision`, `state`, +`lease_binding_sigil`, `job_id`, `attempt_id`, `worker_id`, +`worker_session_id`, `executor_epoch`, `fencing_generation`, +`claim_due_at`, `expiry_due_at`, `maximum_expiry_due_at`, +`last_heartbeat_sequence`, `last_heartbeat_message_sigil`, +`last_resource_sample_sigil`, `next_heartbeat_due_at`, `renewal_counter`, +`terminal_event_binding`, `tombstone_generation`, +`tombstone_event_sigil`, `last_event_id`, and `last_event_sigil`. +All three last-heartbeat fields are null before the first accepted Lease +heartbeat and thereafter identify the same message; +`next_heartbeat_due_at` is null in `OFFERED` and every terminal state but +non-null in `ACTIVE`; +tombstone fields are both non-null exactly in a terminal Lease state. + +Every `log_streams` entry has exactly `log_stream_id`, `revision`, `state`, +`attempt_id`, `stream`, `next_sequence`, `captured_bytes`, `dropped_bytes`, +`truncated`, `final_sequence`, `stream_set_sigil`, `last_event_id`, and +`last_event_sigil`. `state` is `OPEN` or `CLOSED`; final sequence and stream +set Sigil are null in `OPEN`, while final sequence alone may remain null for +an empty closed stream. `stream` is `STDOUT`, `STDERR`, or `STRUCTURED`. + +Each `deadlines` entry has exactly `deadline_kind`, `due_at`, +`fixed_priority`, `entity_id`, `source_event_id`, and +`source_event_sigil`. It represents one currently applicable deadline and is +sorted by the exact deadline key below. Each `idempotency_records` entry has +exactly `operation_kind`, `scope_id`, `idempotency_key_sigil`, +`request_sigil`, `disposition_event_id`, and `disposition_event_sigil`; +`operation_kind` is exactly `START_JOB`, `CANCEL_JOB`, +`REGISTER_WORKER_SESSION`, `CLAIM_LEASE`, `RENEW_LEASE`, `RELEASE_LEASE`, +`SUBMIT_RESULT`, or `APPEND_LOG_CHUNK`. Records are sorted by +`(operation_kind, scope_id unsigned ASCII, idempotency_key_sigil)` and bind +the first durable disposition forever. Scope is respectively the immutable +Task ID for Start, allocated Job ID for Cancel, Worker Session ID for +registration, Lease ID for the three Lease operations, Attempt ID for result +submission, and Log-stream ID for chunk append. + +The entity arrays are sorted by creation-event sequence and then identity. +Every `last_event_id`/`last_event_sigil` pair identifies the event that +produced that entry's current revision. Any null outside the conditions above, +any extra projection member, or any cache value that differs from full replay +invalidates the cache; replay discards and rebuilds it without changing the +journal. + +### Event envelope + +Every `execution-journal-event/1.0` has exactly these required top-level +members: + +| Field | Exact v1 rule | +| --- | --- | +| `schema_version` | Constant `execution-journal-event/1.0`. | +| `journal_id`, `event_id`, `sequence` | Exact journal, unique event, and one-based contiguous sequence. | +| `event_type` | One value from the exact enum below. | +| `executor_instance_id`, `executor_epoch`, `executor_build_sigil` | Appending coordinator identity, durable epoch, and immutable build Sigil fixed by that epoch's `executor.epoch_started`. | +| `recorded_at` | Non-decreasing trusted UTC protocol time. | +| `observed_at` | UTC observation time or `null`; it never orders the protocol. | +| `entity_revisions` | Non-empty bounded array sorted by `(entity_kind, entity_id)`. | +| `causation_event_id` | Direct cause event ID or `null`. | +| `idempotency_key_sigil` | Bound mutation-key Sigil or `null`. | +| `recovery_action_binding` | Closed Recovery action binding or `null` for a non-Recovery action. | +| `payload` | Exactly one closed branch selected by `event_type`. | +| `previous_event_sigil` | `null` only at sequence one; otherwise the preceding event Sigil. | +| `event_sigil` | Sigil over every other member. | + +The `executor.epoch_started` payload carries the complete +`executor_build_binding` whose self-Sigil equals the envelope +`executor_build_sigil`. Replay stores that binding in the Executor projection. +Every later Event with the same `(executor_instance_id, executor_epoch)` must +copy the epoch-start build Sigil byte-for-byte. An epoch transition may select +a different valid build binding, but it never relabels an Event, Lease, +Attempt, or authority binding from an older epoch. + +Each `entity_revisions` entry has exactly `entity_kind`, `entity_id`, +`preceding_revision`, and `next_revision`. Creation uses +`preceding_revision: null` and `next_revision: 0`. Every owner projection +changed by an event, including state-neutral evidence, advances exactly by +one; an entity listed only to validate an unchanged relationship uses equal +non-null revisions. The exact table below, including its two closed +conditional augmentations, is the sole authority for which changed or +unchanged projections appear. A multi-entity event lists every effect in that +table and no other entity. + +A journal revision entry's exact `entity_kind` enum and ID class are: + +| `entity_kind` | Required `entity_id` class | +| --- | --- | +| `EXECUTOR` | Executor instance ID | +| `RECOVERY` | Recovery transaction ID | +| `WORKER` | Worker ID | +| `WORKER_SESSION` | Worker Session ID | +| `JOB` | Job ID | +| `ATTEMPT` | Attempt ID | +| `LEASE` | Lease ID | +| `LOG_STREAM` | Log-stream ID | + +This printed order is the canonical `entity_revisions` sort order. Budget, +deadline, result, assurance, and retention projections are owned by their Job +or Attempt; they do not invent anonymous entity kinds. + +The revision-effect algebra is closed: + +- `CREATE(K, id)` requires no prior projection for `(K, id)`, encodes + `(preceding_revision: null, next_revision: 0)`, and initializes exactly the + state named by the event; +- `ADVANCE(K, id)` requires the current non-negative revision `r`, encodes + `(r, r + 1)` with checked arithmetic, and makes this event the projection's + `last_event_id`/`last_event_sigil`; +- `EQUAL(K, id)` requires the current revision `r`, encodes `(r, r)`, and + validates the named unchanged relationship without changing state, + revision, or last-event binding. + +For the table below, `X` is the envelope `executor_instance_id`; `R` is the +payload or recovery-action `recovery_id`; and `W`, `S`, `J`, `A`, `L`, and +`G` are the Worker, Worker Session, Job, Attempt, Lease, and Log-stream IDs +resolved from the event's immutable binding or its uniquely typed revision +entry. `J`, `A`, `L`, and `S` must equal the parent/child IDs in every +referenced immutable document. `G0`, `G1`, and `G2` are respectively the +fresh `STDOUT`, `STDERR`, and `STRUCTURED` Log-stream IDs in the newly created +Attempt binding. + +Each row is the exact base set of revision entries for that one event: + +| Event type | Exact base revision effects | +| --- | --- | +| `executor.epoch_started` | `CREATE(EXECUTOR, X)` exactly at sequence one; otherwise `ADVANCE(EXECUTOR, X)` for the existing Executor ID. | +| `executor.clock_uncertain` | `ADVANCE(EXECUTOR, X)` | +| `executor.clock_restored` | `ADVANCE(EXECUTOR, X)` | +| `recovery.started` | `ADVANCE(EXECUTOR, X)`, `CREATE(RECOVERY, R)` | +| `recovery.action_set_rebased` | `ADVANCE(RECOVERY, R)` | +| `recovery.phase_advanced` | `ADVANCE(RECOVERY, R)` | +| `recovery.completed` | `ADVANCE(EXECUTOR, X)`, `ADVANCE(RECOVERY, R)` | +| `worker.definition_registered` | `CREATE(WORKER, W)` | +| `worker.enabled` | `ADVANCE(WORKER, W)` | +| `worker.draining` | `ADVANCE(WORKER, W)` | +| `worker.quarantined` | `ADVANCE(WORKER, W)` | +| `worker.retired` | `ADVANCE(WORKER, W)` | +| `worker_session.registered` | `CREATE(WORKER_SESSION, S)` | +| `worker_session.ready` | `ADVANCE(WORKER_SESSION, S)` | +| `worker_session.draining` | `ADVANCE(WORKER_SESSION, S)` | +| `worker_session.offline` | `ADVANCE(WORKER_SESSION, S)` | +| `worker_session.quarantined` | `ADVANCE(WORKER_SESSION, S)` | +| `worker_session.closed` | `ADVANCE(WORKER_SESSION, S)` | +| `worker_session.heartbeat_accepted` | `ADVANCE(WORKER_SESSION, S)` | +| `worker_session.message_rejected` | `ADVANCE(WORKER_SESSION, S)` | +| `job.submitted` | `CREATE(JOB, J)` | +| `job.queued` | `ADVANCE(JOB, J)` | +| `job.attempt_allocated` | `ADVANCE(JOB, J)`, `CREATE(ATTEMPT, A)`, `CREATE(LOG_STREAM, G0)`, `CREATE(LOG_STREAM, G1)`, `CREATE(LOG_STREAM, G2)`; the three streams initialize `OPEN` atomically. | +| `job.budget_settled` | `ADVANCE(JOB, J)`, `ADVANCE(ATTEMPT, A)` | +| `job.retry_scheduled` | `ADVANCE(JOB, J)` | +| `job.retry_ready` | `ADVANCE(JOB, J)` | +| `job.stop_latched` | `ADVANCE(JOB, J)` | +| `job.cancellation_observed` | `EQUAL(JOB, J)` | +| `job.assurance_evaluated` | `ADVANCE(JOB, J)` | +| `job.succeeded` | `ADVANCE(JOB, J)` | +| `job.failed` | `ADVANCE(JOB, J)` | +| `job.cancelled` | `ADVANCE(JOB, J)` | +| `job.timed_out` | `ADVANCE(JOB, J)` | +| `job.policy_violated` | `ADVANCE(JOB, J)` | +| `storage_root.hold_release_observed` | `EQUAL(JOB, J)` | +| `job.message_rejected` | `ADVANCE(JOB, J)` | +| `attempt.authorization_bound` | `ADVANCE(ATTEMPT, A)` | +| `attempt.preflight_started` | `ADVANCE(ATTEMPT, A)` | +| `attempt.preflight_progressed` | `ADVANCE(ATTEMPT, A)` | +| `attempt.preflight_passed` | `ADVANCE(ATTEMPT, A)` | +| `attempt.starting` | `ADVANCE(ATTEMPT, A)` | +| `attempt.running` | `ADVANCE(ATTEMPT, A)` | +| `attempt.result_accepted` | `ADVANCE(ATTEMPT, A)` | +| `attempt.result_rejected` | `ADVANCE(ATTEMPT, A)` | +| `attempt.draining` | `ADVANCE(ATTEMPT, A)` | +| `attempt.stop_latched` | `ADVANCE(ATTEMPT, A)` plus `ADVANCE(JOB, J)` when no earlier event in the same effective-sequence due chain advanced `J`; otherwise `EQUAL(JOB, J)`. | +| `attempt.stop_progressed` | `ADVANCE(ATTEMPT, A)` | +| `attempt.cleaning` | `ADVANCE(ATTEMPT, A)` | +| `attempt.cleanup_progressed` | `ADVANCE(ATTEMPT, A)` | +| `attempt.succeeded` | `ADVANCE(ATTEMPT, A)` | +| `attempt.failed` | `ADVANCE(ATTEMPT, A)` | +| `attempt.cancelled` | `ADVANCE(ATTEMPT, A)` | +| `attempt.timed_out` | `ADVANCE(ATTEMPT, A)` | +| `attempt.policy_violated` | `ADVANCE(ATTEMPT, A)` | +| `attempt.lease_expired` | `ADVANCE(ATTEMPT, A)` | +| `attempt.lost` | `ADVANCE(ATTEMPT, A)` | +| `attempt.fenced` | `ADVANCE(ATTEMPT, A)` | +| `attempt.rejected` | `ADVANCE(ATTEMPT, A)` | +| `attempt.assurance_evaluated` | `ADVANCE(ATTEMPT, A)`, `ADVANCE(JOB, J)` | +| `lease.offered` | `CREATE(LEASE, L)`, `ADVANCE(WORKER_SESSION, S)`, `ADVANCE(ATTEMPT, A)` | +| `lease.claimed` | `ADVANCE(WORKER_SESSION, S)`, `ADVANCE(ATTEMPT, A)`, `ADVANCE(LEASE, L)` | +| `lease.heartbeat_accepted` | `ADVANCE(LEASE, L)` | +| `lease.renewed` | `ADVANCE(LEASE, L)` | +| `lease.released` | `ADVANCE(WORKER_SESSION, S)`, `ADVANCE(JOB, J)`, `ADVANCE(ATTEMPT, A)`, `ADVANCE(LEASE, L)` | +| `lease.revoked` | `ADVANCE(WORKER_SESSION, S)`, `ADVANCE(JOB, J)`, `ADVANCE(ATTEMPT, A)`, `ADVANCE(LEASE, L)` | +| `lease.expired` | `ADVANCE(WORKER_SESSION, S)`, `ADVANCE(JOB, J)`, `ADVANCE(ATTEMPT, A)`, `ADVANCE(LEASE, L)` | +| `lease.fenced` | `ADVANCE(WORKER_SESSION, S)`, `ADVANCE(JOB, J)`, `ADVANCE(ATTEMPT, A)`, `ADVANCE(LEASE, L)` | +| `lease.tombstone_republished` | `ADVANCE(LEASE, L)` | +| `lease.message_rejected` | `ADVANCE(LEASE, L)` | +| `log.chunk_committed` | `ADVANCE(LOG_STREAM, G)` | +| `log.chunk_rejected` | `ADVANCE(LOG_STREAM, G)` | +| `log.truncated` | `ADVANCE(LOG_STREAM, G)` | +| `log.closed` | `ADVANCE(LOG_STREAM, G)` | + +There are exactly 73 base rows. A non-null `recovery_action_binding` adds +exactly one `EQUAL(RECOVERY, R)` entry unless the base row already names `R`; +phase-transition and Recovery control events have a null binding and receive +no augmentation. For `attempt.stop_latched`, “already advanced” is true +exactly when replay finds an earlier event with the same +`transition_cause.effective_sequence` whose exact row contains +`ADVANCE(JOB, J)`; no process-local flag participates. The event Schema +rejects every missing, duplicate, or extra revision entry, a wrong ID or mode, +an unsorted entry array, and any array longer than the fixed v1 maximum of +five. Immutable references not named by a row are validated through their +Sigils and relationships but do not create optional `EQUAL` entries. + +A non-null `recovery_action_binding` has exactly `recovery_id`, `phase`, +`action_set_sigil`, and `action_ordinal`. It must identify the unique frozen +action whose target event ID, sequence, type, entity, expected revision, and +prerequisites match this event. A phase-transition event itself uses `null`. + +The exact v1 `event_type` enum is: + +```text +executor.epoch_started +executor.clock_uncertain +executor.clock_restored +recovery.started +recovery.action_set_rebased +recovery.phase_advanced +recovery.completed +worker.definition_registered +worker.enabled +worker.draining +worker.quarantined +worker.retired +worker_session.registered +worker_session.ready +worker_session.draining +worker_session.offline +worker_session.quarantined +worker_session.closed +worker_session.heartbeat_accepted +worker_session.message_rejected +job.submitted +job.queued +job.attempt_allocated +job.budget_settled +job.retry_scheduled +job.retry_ready +job.stop_latched +job.cancellation_observed +job.assurance_evaluated +job.succeeded +job.failed +job.cancelled +job.timed_out +job.policy_violated +storage_root.hold_release_observed +job.message_rejected +attempt.authorization_bound +attempt.preflight_started +attempt.preflight_progressed +attempt.preflight_passed +attempt.starting +attempt.running +attempt.result_accepted +attempt.result_rejected +attempt.draining +attempt.stop_latched +attempt.stop_progressed +attempt.cleaning +attempt.cleanup_progressed +attempt.succeeded +attempt.failed +attempt.cancelled +attempt.timed_out +attempt.policy_violated +attempt.lease_expired +attempt.lost +attempt.fenced +attempt.rejected +attempt.assurance_evaluated +lease.offered +lease.claimed +lease.heartbeat_accepted +lease.renewed +lease.released +lease.revoked +lease.expired +lease.fenced +lease.tombstone_republished +lease.message_rejected +log.chunk_committed +log.chunk_rejected +log.truncated +log.closed +``` + +The payload branches use these closed reusable objects, each published under +its printed name in `execution-journal-event/1.0#/$defs`: + +- `transition_cause` has exactly `code`, `trigger_kind`, + `trigger_event_id`, `effective_sequence`, and `evidence_sigil`. + `trigger_kind` is `PRIOR_EVENT`, `CURRENT_REQUEST`, `DUE_DEADLINE`, or + `RECOVERY_DERIVATION`; the event ID and sequence identify the prior event + for `PRIOR_EVENT` and the enclosing event itself for the other three. + `trigger_event_id` is never null: the enclosing event ID and sequence are + assigned before Sigil computation, so a request, deadline, or recovery + trigger binds its own event ID and sequence without a circular Sigil; + a `RECOVERY_DERIVATION` cause uses its action set's + `derived_through_event_sigil` as `evidence_sigil`, never the still-circular + action-set or set-binding-event Sigil; +- `transition_cause.code` is exactly one of `COMPLETION_ESTABLISHED`, + `CANCEL_REQUESTED`, + `JOB_DEADLINE`, `ATTEMPT_DEADLINE`, `HEARTBEAT_TIMEOUT`, + `LEASE_CLAIM_EXPIRED`, `LEASE_ACTIVE_EXPIRED`, `LEASE_REVOKED`, + `RECOVERY_FENCE`, `CLOCK_UNCERTAIN`, `EXECUTOR_EPOCH_CHANGED`, + `SESSION_CHANNEL_LOST`, `POLICY_VIOLATION`, `ADMISSION_INVALID`, + `PREFLIGHT_REJECTED`, `START_FAILED`, `COMPUTATION_FAILED`, + `VALIDATION_FAILED`, + `RESULT_CONFLICT`, `RESULT_REQUIREMENT_FAILED`, + `OUTPUT_VALIDATION_FAILED`, `TERMINAL_SOURCE_RETENTION_FAILED`, + `TERMINATION_FAILED`, `CLEANUP_FAILED`, + `LOST_OWNERSHIP`, `ASSURANCE_UNMET`, `ASSURANCE_UNVERIFIABLE`, + `BUDGET_EXHAUSTED`, `RETRY_EXHAUSTED`, `ATTEMPT_NONRETRYABLE`, + `ATTEMPT_REJECTED`, `FATAL_INFRASTRUCTURE`, or `INTEGRITY_FAILURE`; + `COMPLETION_ESTABLISHED` is valid only for `attempt.succeeded` or + `job.succeeded`, uses `PRIOR_EVENT`, and identifies the exact + `RESULT_ACCEPTED` or `NO_RESULT` completion-anchor event and sequence; +- `budget_vector` has exactly `attempts`, `cpu_time_seconds`, + `storage_bytes_written`, `output_bytes`, `log_bytes`, `process_starts`, + `network_egress_bytes`, and `network_requests`; +- `storage_root_binding` has exactly `root_kind`, `job_id`, `attempt_id`, + `storage_root_manifest_id`, `storage_root_manifest_sigil`, + `reference_set_id`, `reference_set_sigil`, `hold_id`, and + `hold_set_event`. `root_kind` is `JOB_INPUT`, `ATTEMPT_INPUT`, + or `ATTEMPT_OUTPUT`; `ACTIVE_CONTROL` is not a v1 value. IDs are bounded + operational strings and `reference_set_sigil` is a Sigil. `JOB_INPUT` + requires null `attempt_id`; both Attempt kinds require a non-null Attempt + ID. In every event or State container, `job_id` must equal the owning Job + and a non-null `attempt_id` must equal the owning Attempt whose immutable + parent is that Job. `hold_set_event` has exactly `journal_id`, `event_id`, + `sequence`, and `event_sigil`, using the RFC-0013 `EventRef` types. The + manifest pair resolves the exact ESM whose owner and planned Event IDs equal + this root. The member shape is byte-for-byte compatible with RFC-0013 + `ExecutionStorageRoot`, while this v1 enum is its closed admissible subset; +- `result_binding` is exactly one of `NONE {kind}`, + `ACCEPTED {kind, result_sigil, disposition_event_id, + disposition_event_sigil, disposition_sequence}`, or + `REJECTED {kind, message_sigil, disposition_event_id, + disposition_event_sigil, disposition_sequence, reason_codes}`; +- `completion_anchor_binding` is exactly + `NOT_ESTABLISHED {kind}`, + `RESULT_ACCEPTED {kind, event_id, event_sigil, sequence, result_sigil}`, or + `NO_RESULT {kind, event_id, event_sigil, sequence, + process_exit_observation_sigil}`. `RESULT_ACCEPTED` identifies + `attempt.result_accepted`; `NO_RESULT` identifies `attempt.draining` and + its process-exit observation Sigil is non-null; +- `worker_session_binding` is exactly `NONE {kind}` or + `BOUND {kind, worker_id, worker_binding_sigil, worker_session_id, + worker_session_binding_sigil}`. `NONE` is legal only when the Attempt never + received a Lease offer; an `OFFERED` or later Lease requires `BOUND` even + when it was never claimed; +- `first_stop_or_fence_binding` is exactly `NONE {kind}` or + `PRESENT {kind, event_id, event_type, event_sigil, effective_sequence}`. It + is the earliest applicable authority-ineligibility trigger known through + the selected Attempt terminal event. A later Job-only stop is preserved by + the Job terminal `transition_cause` and never rewrites this frozen binding. + For a no-Attempt Outcome, the direct binding is instead derived from the + Job's first stop trigger; +- `storage_observation_binding` is exactly `NOT_APPLICABLE {kind}` or + `FROZEN {kind, storage_journal_id, through_sequence, + through_event_sigil, output_storage_observation_set_id, + output_storage_observation_set_sigil}`. `NOT_APPLICABLE` is legal only for + a terminal Job with no allocated Attempt. `FROZEN` is mandatory for every + terminal Attempt; its ID and Sigil resolve exactly one + `execution-output-storage-observation-set/1.0`, and its three prefix fields + equal that document's `storage_event.journal_id`, `sequence`, and + `event_sigil` byte-for-byte; +- `selected_attempt_binding` is exactly `NONE {kind}` or + `SELECTED {kind, attempt_id, attempt_binding_sigil, + attempt_terminal_event_id, attempt_terminal_event_sigil, + attempt_authorization_state, worker_session_binding, result_binding, + completion_anchor_binding, + first_stop_or_fence_binding, storage_observation_binding}`. `NONE` is legal + iff no Attempt was allocated; otherwise `SELECTED` names the terminal + highest-retry-ordinal Attempt available when the final Job decision is + committed; +- `attempt_summary`, used by each `attempt_summaries` entry, has exactly + `attempt_id`, + `attempt_binding_sigil`, `retry_ordinal`, `terminal_state`, + `terminal_event_id`, `terminal_event_sigil`, `worker_session_binding`, + `attempt_authorization_requirement`, + `attempt_authorization_state`, + `budget_settlement_event_sigil`, and + `assurance_evaluation_event_sigil`, sorted by retry ordinal. It is a + `FINAL` record even though it has no redundant `kind`: both trailing Sigils + are non-null, the terminal event precedes budget settlement, and settlement + precedes assurance evaluation. No pending or partially populated + `attempt_summary` branch exists; + +Each summary's `attempt_authorization_requirement` is copied byte-for-byte +from its immutable Attempt. A `NONE` requirement permits only state `NONE`. +A `REQUIRED` requirement permits a non-selected failed Attempt to retain +`PENDING`; any `BOUND` state must resolve its complete subject and binding, +must have `authorization_subject.effects` equal to the requirement's effects, +and must be unique to that Attempt. The selected successful Attempt must be +exactly `NONE/NONE` or `REQUIRED/BOUND`. A Receipt or subject reused across +Attempts, substituted from Specification approval, or bound to different +effects invalidates the terminal Job event. The selected binding's +`attempt_authorization_state` equals the matching summary's state +byte-for-byte; its requirement is obtained only from that matching summary, +never inferred from the state. + +- `lease_terminal_binding` is exactly `NONE {kind}` or + `TERMINAL {kind, lease_id, lease_state, terminal_event_id, + terminal_event_sigil, final_fence_floor, tombstone_event_sigil}`. `NONE` is + legal iff no Lease was ever offered to the Attempt; otherwise the Lease must + already be terminal; +- `final_fence_binding` is exactly + `NO_ATTEMPT {kind, final_fence_floor}`, + `ASSIGNED_NO_LEASE {kind, final_fence_floor, attempt_id, + attempt_terminal_event_sigil}`, or + `TOMBSTONE {kind, final_fence_floor, lease_id, + lease_terminal_event_sigil, tombstone_event_sigil}`. `NO_ATTEMPT` requires + floor zero; `ASSIGNED_NO_LEASE` requires the last allocated generation and + proves that Attempt never received a Lease; `TOMBSTONE` requires the + terminal Lease event's tombstone generation to equal the final floor; +- `terminal_source_binding` is exactly `NOT_APPLICABLE {kind}`, + `VERIFIED {kind, crucible_base_identity, crucible_base_sigil, + terminal_source_identity, terminal_source_sigil, storage_blob, + retention_policy_sigil, file_count, byte_count, storage_status, + verifier_evidence_sigil}`, or + `QUARANTINED {kind, crucible_base_identity, crucible_base_sigil, + terminal_source_identity, terminal_source_sigil, storage_blob, + retention_policy_sigil, file_count, byte_count, storage_status, + verifier_evidence_sigil, + reason_codes}`. In `VERIFIED`, every identity/Sigil is non-null, + `storage_status` is constant `DURABLE_VERIFIED`, and counts are within the + predeclared bounds. In `QUARANTINED`, Base, policy, and verifier bindings + remain non-null, `storage_status` is constant `QUARANTINED`, and + `reason_codes` is a non-empty sorted set drawn exactly from + `BASE_BINDING_INVALID`, `SOURCE_ABSENT`, `SOURCE_PARTIAL`, + `SOURCE_MUTABLE`, `SOURCE_IDENTITY_MISMATCH`, `FILE_LIMIT_EXCEEDED`, + `BYTE_LIMIT_EXCEEDED`, `STORAGE_UNVERIFIED`, + `RETENTION_POLICY_MISMATCH`, and `VERIFIER_UNAVAILABLE`. + `terminal_source_identity`, `terminal_source_sigil`, and `storage_blob` are + either all non-null or all null; null is allowed only when no + content-identified source was retained and then both counts are zero. When + non-null they resolve one exact `benchwork-source-tree/1.0` and its + deterministic bundle as defined above; and +- `attempt_terminal_evidence` has exactly `transition_cause`, + `computation_status`, `worker_status`, `process_termination_status`, + `handle_revocation_status`, `cleanup_status`, + `mutable_resource_isolation_status`, `output_publication_status`, + `quarantine_status`, `termination_evidence_sigil`, + `handle_disposition_evidence_sigil`, `cleanup_summary_sigil`, + `quarantine_evidence_sigil`, + `log_closure_sigil`, `output_closure_sigil`, `result_binding`, + `attempt_authorization_state`, + `completion_anchor_binding`, + `worker_session_binding`, `lease_terminal_binding`, + `first_stop_or_fence_binding`, `storage_observation_binding`, + `accounting_capture_event_id`, `accounting_capture_event_sigil`, + `control_evidence_set_binding`, `quarantine_binding_set_binding`, + `terminalization_storage_manifest_binding`, `output_root_protection`, + `assurance_input_set_sigil`, and `terminal_source_binding`. The four + evidence/summary Sigils, both closure Sigils, both accounting bindings, both + terminalization-set bindings, ESM/protection bindings, and the assurance + input-set Sigil are + non-null for every allocated Attempt, including a preflight rejection; + evidence that a facility never became active is still explicit evidence + rather than null. Both set bindings are `FROZEN`. Its + `storage_observation_binding` is always `FROZEN`; the referenced observation + document's owner, `result_binding`, both closure Sigils, both + terminalization-set bindings, the ESM/protection bindings, and + `terminal_source_binding` are byte-for-byte equal to this terminal evidence + and its owning Attempt. Any mismatch invalidates the terminal event rather + than being repaired from the observation-set Sigil. + +The terminal payload's `output_storage_roots` is empty exactly when +`output_root_protection.kind == NO_HOLD`; otherwise it contains exactly the +one `HELD.storage_root_binding`. Thus `ACCOUNTING_CAPTURED` proves protection +before the terminal Event activates the same root, and the terminal Event +cannot substitute another ESM, Reference Set, hold, or Storage prefix. + +The terminal authorization state is the exact replayed value. `PENDING` is +legal only for an Attempt that never passed preflight and terminalized through +a closed rejection or stop path; it is never acceptance-eligible. +Every `SUCCEEDED` Attempt whose immutable requirement is `REQUIRED` carries +the one valid `BOUND` subject and Receipt, while a `NONE` requirement carries +state `NONE`. The terminal event cannot omit or abbreviate either complete +`BOUND` object. + +The eight terminal-status fields are closed enums: + +| Field | Exact values | +| --- | --- | +| `computation_status` | `NOT_STARTED`, `COMPLETED`, `FAILED`, `UNKNOWN` | +| `worker_status` | `NOT_REPORTED`, `COMPLETED`, `FAILED`, `CANCELLED`, `TIMED_OUT`, `LOST` | +| `process_termination_status` | `NOT_STARTED`, `EXITED`, `TERMINATED`, `QUARANTINED`, `UNVERIFIABLE` | +| `handle_revocation_status` | `NOT_APPLICABLE`, `REVOKED`, `FENCED`, `QUARANTINED`, `UNVERIFIABLE` | +| `cleanup_status` | `NOT_APPLICABLE`, `VERIFIED`, `PARTIAL`, `FAILED`, `QUARANTINED` | +| `mutable_resource_isolation_status` | `NOT_APPLICABLE`, `VERIFIED_ISOLATED`, `QUARANTINED`, `UNVERIFIED` | +| `output_publication_status` | `NOT_APPLICABLE`, `VERIFIED`, `QUARANTINED`, `FAILED` | +| `quarantine_status` | `NOT_REQUIRED`, `COMPLETE`, `PARTIAL`, `FAILED` | + +The assurance claim's `termination_status` uses the +`process_termination_status` enum and its `cleanup_status` uses the same +cleanup enum. These facts remain independent of the Attempt terminal-state +enum. Any `UNVERIFIABLE`, `PARTIAL`, or `FAILED` termination, handle, +cleanup, or mutable-resource fact that leaves authority or exclusivity +ambiguous requires the corresponding resource to be represented by +`quarantine_status: COMPLETE`; otherwise terminalization is illegal. +Each status row is also published as the same-named scalar `$def` in +`execution-journal-event/1.0`; consumers import those component definitions +without importing the enclosing terminal-evidence object. + +`NOT_APPLICABLE` terminal source is required when the Specification branch is +`NONE`; `VERIFIED` or `QUARANTINED` is required for `CODE_MODIFICATION`. +If a `CODE_MODIFICATION` Job terminalizes without any Attempt, its Job +terminal event uses `QUARANTINED` with the predeclared Base and retention +policy, null terminal-source identity and Sigil, zero counts, +null storage BlobRef, +`SOURCE_ABSENT`, and non-null verifier evidence; `NOT_APPLICABLE` remains +forbidden. For an allocated Attempt, the Job copy is byte-for-byte equal to +that Attempt's frozen branch. +Every `SUCCEEDED` Attempt has an established completion anchor: +`RESULT_ACCEPTED` for `REQUIRED` or an accepted `OPTIONAL` result, and +`NO_RESULT` for a permitted absent result. `FORBIDDEN` can use only +`NO_RESULT`. The `result_binding`, completion anchor, selected Attempt, and +Job-terminal copies must agree exactly. First stop-or-fence ordering is always +compared to `completion_anchor_binding.sequence`, so no-result completion has +the same deterministic race semantics as accepted-result completion. + +The remaining event payload scalar domains are exact: + +- `startup_reason` is `INITIAL_START`, `PROCESS_RESTART`, + `RECOVERY_RESTART`, or `ADMINISTRATIVE_RESTART`; +- `monotonic_status` is `RESET`, `UTC_DIVERGED`, `UNAVAILABLE`, or + `SUSPEND_UNPROVABLE`; +- `message_kind` is `WORKER_SESSION_REGISTRATION`, + `WORKER_SESSION_HEARTBEAT`, `LEASE_CLAIM`, `LEASE_HEARTBEAT`, + `LEASE_RENEWAL`, `LEASE_RELEASE`, or `CANCEL_REQUEST`; +- `attempt.preflight_progressed.step` is `TASK_BINDINGS_VERIFIED`, + `POLICIES_VERIFIED`, `BASE_INPUTS_VERIFIED`, `BACKEND_VERIFIED`, + `MATERIALIZATION_CREATED`, or `OUTPUT_NAMESPACE_CREATED`; +- `attempt.stop_progressed.step` is `COOPERATIVE_STOP_REQUESTED`, + `FORCE_TERMINATION_DUE`, `PROCESS_TREE_TERMINATED`, or + `HANDLES_REVOKED`; and +- `attempt.cleanup_progressed.step` is `LOGS_CLOSED`, `OUTPUTS_CLOSED`, + `TERMINATION_VERIFIED`, `RESOURCES_CLEANED`, `QUARANTINE_VERIFIED`, or + `ACCOUNTING_CAPTURED`. The first two are legal in `DRAINING`; the first + five are legal as applicable in `STOPPING` or `CLEANING`; + `ACCOUNTING_CAPTURED` is legal only in `CLEANING` and only as the final + metered progress step. Its `remaining_resource_ids` is empty and its + `finalization_bindings` carries the already durable frozen CES, QBS, output + ESM, and output-root protection bindings. Every earlier branch uses + `finalization_bindings: NONE`. A step is + recorded at most once per Attempt. + +`worker_session.message_rejected` restricts `message_kind` to the two +Worker-Session values, `job.message_rejected` requires `CANCEL_REQUEST`, and +`lease.message_rejected` restricts it to the four Lease values. Result and +log rejection events have fixed kinds by event type and therefore do not +repeat a `message_kind` member. + +Unless a field has a smaller enum in this RFC, every machine +`reason_code`, `reason_codes`, or `terminal_reason` value is drawn from the +exact `transition_cause.code` enum plus +`ADMIN_DRAIN`, `ADMIN_RETIRE`, `DEFINITION_SUPERSEDED`, +`CAPABILITY_REVOKED`, `VERIFICATION_FAILED`, `CONTROL_CHANNEL_LOST`, +`WORKER_SHUTDOWN`, `BACKEND_CLOSE_FAILED`, `MALFORMED_MESSAGE`, +`UNKNOWN_SCHEMA`, `INVALID_SIGIL`, `WRONG_ENTITY`, `WRONG_SESSION`, +`WRONG_EPOCH`, `STALE_REVISION`, `CONFLICTING_DUPLICATE`, `SEQUENCE_GAP`, +`STATE_INELIGIBLE`, `CREDENTIAL_INVALID`, `LEASE_NOT_CURRENT`, +`FENCE_STALE`, `DEADLINE_ALREADY_DUE`, `MESSAGE_TOO_LARGE`, +`LIMIT_EXCEEDED`, `RESULT_SCHEMA_INVALID`, `OUTPUT_SCHEMA_INVALID`, +`EVIDENCE_MISSING`, `EVIDENCE_INVALID`, `PROFILE_MISMATCH`, +`CONFORMANCE_SUITE_MISMATCH`, `CLAIM_INVALID`, `ACCOUNTING_PARTIAL`, +`ACCOUNTING_UNAVAILABLE`, `RESOURCE_AMBIGUOUS`, and +`NO_ATTEMPT_ALLOCATED`. Reason arrays are unique, +unsigned-ASCII sorted, and non-empty except that a `CLAIMED` assurance +evaluation has an empty array. The no-Attempt Job evaluation has exactly +`[NO_ATTEMPT_ALLOCATED]`. The terminal-source reason array remains restricted +to its smaller enum above. Actor-supplied cancellation `reason` is instead a +required, normalized, bounded 1--1024-byte UTF-8 string and has no authority +outside its request Sigil. + +Attempt assurance `evaluation` is exactly `CLAIMED`, `UNMET`, or +`UNVERIFIABLE`; Job assurance adds `NOT_APPLICABLE`. `CLAIMED` requires a +claim Sigil and empty reasons. `UNMET` and `UNVERIFIABLE` require a null claim +Sigil and non-empty reasons. `NOT_APPLICABLE` is legal only for a Job with no +allocated Attempt, uses the fixed reason above, and requires all Attempt and +claim fields null. Budget `usage_status` is exactly `MEASURED`, `PARTIAL`, or +`UNAVAILABLE`; budget exhaustion is exactly `AVAILABLE`, `EXHAUSTED`, or +`EXCEEDED` under the arithmetic rule in `budget_dimension`. Attempt terminal +states are exactly `SUCCEEDED`, `FAILED`, `CANCELLED`, `TIMED_OUT`, +`POLICY_VIOLATION`, `LEASE_EXPIRED`, `LOST`, `FENCED`, and `REJECTED`; Job +terminal states are exactly `SUCCEEDED`, `FAILED`, `CANCELLED`, `TIMED_OUT`, +and `POLICY_VIOLATION`; Lease terminal states are exactly `RELEASED`, +`REVOKED`, `EXPIRED`, and `FENCED`. + +Arrays named below are bounded, contain closed entries, and use the canonical +sort key named by their field. The exact event ownership and payload fields +are: + +| Event type | State owner and transition | Exact payload members | +| --- | --- | --- | +| `executor.epoch_started` | Executor epoch creation/increment | `prior_epoch`, `new_epoch`, `startup_reason`, `host_identity_sigil`, `executor_build_binding` | +| `executor.clock_uncertain` | Executor clock gate `TRUSTED -> UNCERTAIN` | `last_trusted_utc`, `detected_utc`, `monotonic_status`, `divergence_seconds`, `affected_lease_ids` | +| `executor.clock_restored` | Executor clock gate `UNCERTAIN -> TRUSTED` | `trusted_time_source_sigil`, `restored_utc`, `fenced_lease_ids`, `new_anchor_evidence_sigil` | +| `recovery.started` | Recovery transaction creation in `STARTED`; Executor sets `active_recovery_id` and closes `RECOVERY_ACTIVE` gate | `recovery_id`, `prior_recovery_id`, `replay_through_sequence`, `replay_through_event_sigil`, `old_epoch`, `new_epoch`, `nonterminal_job_ids`, `nonterminal_attempt_ids`, `nonterminal_lease_ids`, `nonterminal_worker_session_ids`, `initial_action_set_sigil` | +| `recovery.action_set_rebased` | Active Recovery remains in its current phase | `recovery_id`, `phase`, `prior_action_set_sigil`, `replacement_action_set_sigil`, `reason_event_id`, `new_epoch`, `carried_completion_event_ids` | +| `recovery.phase_advanced` | Recovery phase named in payload | `recovery_id`, `from_phase`, `to_phase`, `completed_action_set_sigil`, `next_action_set_sigil`, `completed_entity_ids`, `quarantined_entity_ids` | +| `recovery.completed` | Recovery `FINALIZING -> COMPLETED`; Executor clears `active_recovery_id` and reopens `RECOVERY_ACTIVE` gate | `recovery_id`, `completed_action_set_sigil`, `recovered_state_sigil`, `fence_tombstone_event_ids`, `quarantined_entity_ids`, `resumable_job_ids` | +| `worker.definition_registered` | Worker definition none `-> REGISTERED` | `worker_binding_sigil`, `definition_revision`, `supersedes_worker_binding_sigil` | +| `worker.enabled` | Worker definition `REGISTERED`, `DRAINING`, or `QUARANTINED -> ENABLED` | `worker_binding_sigil`, `verification_evidence_set_sigil` | +| `worker.draining` | Worker definition `ENABLED -> DRAINING` | `reason_code`, `affected_worker_session_ids` | +| `worker.quarantined` | Worker definition `REGISTERED`, `ENABLED`, or `DRAINING -> QUARANTINED` | `reason_codes`, `evidence_set_sigil`, `affected_worker_session_ids` | +| `worker.retired` | Worker definition `REGISTERED`, `ENABLED`, `DRAINING`, or `QUARANTINED -> RETIRED` | `reason_code`, `closed_worker_session_ids` | +| `worker_session.registered` | Worker Session none `-> REGISTERED` | `worker_session_binding_sigil`, `worker_binding_sigil`, `backend_session_identity`, `control_channel_identity_sigil` | +| `worker_session.ready` | Session `REGISTERED -> READY` | `verification_evidence_set_sigil`, `capacity`, `initial_heartbeat_due_at` | +| `worker_session.draining` | Session `READY` or `BUSY -> DRAINING` | `reason_code`, `active_lease_ids` | +| `worker_session.offline` | Session `REGISTERED`, `READY`, `BUSY`, or `DRAINING -> OFFLINE` | `transition_cause`, `last_heartbeat_sequence`, `active_lease_ids` | +| `worker_session.quarantined` | Session `REGISTERED`, `READY`, `BUSY`, or `DRAINING -> QUARANTINED` | `reason_codes`, `evidence_set_sigil`, `active_lease_ids` | +| `worker_session.closed` | Session `REGISTERED`, `READY`, `DRAINING`, `OFFLINE`, or `QUARANTINED -> CLOSED`, with no authoritative Lease | `reason_code`, `terminal_lease_ids`, `backend_close_evidence_sigil` | +| `worker_session.heartbeat_accepted` | Session `READY`, `BUSY`, or `DRAINING`, state-neutral | `heartbeat_message_sigil`, `sequence`, `prior_accepted_sequence`, `received_at`, `next_heartbeat_due_at` | +| `worker_session.message_rejected` | Session state-neutral | `message_kind`, `message_sigil`, `reason_codes`, `historical_disposition_event_id` | +| `job.submitted` | Job none `-> SUBMITTED`; activates its pre-held `JOB_INPUT` roots | `job_binding_sigil`, `start_request_sigil`, `submission_idempotency_key_sigil`, `admission_chronicle_head`, `admission_chronicle_head_evidence`, `deadline_due_at`, `job_budget`, `job_storage_roots` | +| `job.queued` | Job `SUBMITTED -> QUEUED` | `admission_evidence_sigil`, `queue_key` | +| `job.attempt_allocated` | Job `QUEUED -> ACTIVE`; Attempt creation; fence and budget ledgers | `attempt_binding_sigil`, `retry_ordinal`, `fencing_generation`, `prior_fencing_counter`, `resulting_fence_floor`, `budget_reservation`, `resulting_budget_ledger_sigil` | +| `job.budget_settled` | Job `ACTIVE` or `STOPPING` and terminal Attempt, both state-neutral; Job ledger and Attempt settlement binding advance atomically | `attempt_id`, `reservation`, `accounting_capture_event_id`, `accounting_capture_event_sigil`, `usage_status`, `measured`, `charged`, `accounting_started_at`, `accounting_ended_at`, `supervisor_identity`, `accounting_evidence_set_sigil`, `resulting_budget_ledger_sigil` | +| `job.retry_scheduled` | Job `ACTIVE -> RETRY_WAIT` | `preceding_attempt_id`, `terminal_reason`, `eligible_due_at`, `backoff_ordinal`, `post_settlement_budget_ledger_sigil` | +| `job.retry_ready` | Job `RETRY_WAIT -> QUEUED` | `eligible_due_at`, `deadline_check_sigil`, `freshness_evidence_sigil`, `queue_key` | +| `job.stop_latched` | Job `SUBMITTED`, `QUEUED`, `ACTIVE`, or `RETRY_WAIT -> STOPPING` | `transition_cause`, `request_binding`; `request_binding` is `NONE {kind}` or `CANCELLATION {kind, cancellation_request_id, idempotency_key_sigil, cancel_request_sigil, actor_sigil, host_provenance_sigil, reason, expected_job_revision}` | +| `job.cancellation_observed` | Terminal Job state-neutral | `cancellation_request_id`, `idempotency_key_sigil`, `cancel_request_sigil`, `actor_sigil`, `host_provenance_sigil`, `reason`, `expected_job_revision`, `terminal_event_id`, `terminal_event_sigil` | +| `job.assurance_evaluated` | Job `ACTIVE` or `STOPPING`, state-neutral | `evaluation`, `attempt_id`, `attempt_assurance_event_sigil`, `assurance_claim_sigil`, `reason_codes`, `evidence_set_sigil`; Attempt fields are null only for `NOT_APPLICABLE`, and claim Sigil is non-null only for `CLAIMED` | +| `job.succeeded`, `job.failed`, `job.cancelled`, `job.timed_out`, `job.policy_violated` | Job matching source in the Job table `->` matching terminal state; inactivates its `JOB_INPUT` and every child `ATTEMPT_OUTPUT` root | `transition_cause`, `attempt_summaries`, `selected_attempt_binding`, `completion_anchor_binding`, `first_stop_or_fence_binding`, `job_assurance_event_sigil`, `budget_ledger_sigil`, `final_fence_binding`, `cleanup_summary_sigil`, `storage_observation_binding`, `terminal_source_binding`, `output_hold_release_schedules` | +| `storage_root.hold_release_observed` | Job state-neutral; proves one owner-terminal or scheduled output execution-hold release | `job_id`, `release_binding`, `storage_release_event` | +| `job.message_rejected` | Job state-neutral | `message_kind`, `message_sigil`, `reason_codes`, `historical_disposition_event_id` | +| `attempt.authorization_bound` | Attempt lifecycle `CREATED`, authorization `PENDING -> BOUND`, lifecycle-state-neutral | `authorization_subject`, `attempt_authorization_binding` | +| `attempt.preflight_started` | Attempt `CREATED -> PREFLIGHTING` | `preflight_plan_sigil`, `freshness_evidence_sigil` | +| `attempt.preflight_progressed` | Attempt `PREFLIGHTING`, state-neutral | `step`, `progress_evidence_sigil` | +| `attempt.preflight_passed` | Attempt `PREFLIGHTING -> READY`; activates only its pre-held `ATTEMPT_INPUT` roots | `materialization_identity`, `materialization_sigil`, `preflight_evidence_set_sigil`, `input_storage_roots` | +| `attempt.starting` | Attempt `LEASED -> STARTING` | `backend_start_handle_sigil`, `start_request_sigil` | +| `attempt.running` | Attempt `STARTING -> RUNNING` | `process_tree_identity`, `process_tree_evidence_sigil`, `side_effect_handle_set_sigil` | +| `attempt.result_accepted` | Attempt `RUNNING`, state-neutral | `result_sigil`, `lease_revision`, `fence_tuple`, `received_at`, `validation_evidence_sigil` | +| `attempt.result_rejected` | Attempt state-neutral; a first disposition only in `RUNNING`, or a novel late/conflicting message in `DRAINING`, `STOPPING`, `CLEANING`, or any terminal Attempt state | `message_sigil`, `claimed_result_sigil`, `received_at`, `reason_codes`, `historical_disposition_event_id` | +| `attempt.draining` | Attempt `RUNNING -> DRAINING` | `result_binding`, `process_exit_observation_sigil` | +| `attempt.stop_latched` | Attempt `CREATED`, `PREFLIGHTING`, `READY`, `LEASED`, `STARTING`, `RUNNING`, or `DRAINING -> STOPPING`; parent Job ordering projection advances when no event in the same due chain already advanced it | `transition_cause`, `grace_due_at` | +| `attempt.stop_progressed` | Attempt `STOPPING`, state-neutral | `step`, `termination_evidence_sigil`, `remaining_handle_ids` | +| `attempt.cleaning` | Attempt `DRAINING` or `STOPPING -> CLEANING` | `log_closure_sigil`, `output_closure_sigil`, `termination_evidence_sigil`, `quarantine_plan_sigil`, `terminal_source_binding` | +| `attempt.cleanup_progressed` | Attempt `DRAINING`, `STOPPING`, or `CLEANING`, state-neutral finalization progress | `step`, `cleanup_evidence_sigil`, `remaining_resource_ids`, `finalization_bindings`; `NONE {kind}` except `ACCOUNTING_CAPTURED`, which requires `FROZEN {kind, control_evidence_set_binding, quarantine_binding_set_binding, terminalization_storage_manifest_binding, output_root_protection}` | +| `attempt.succeeded`, `attempt.failed`, `attempt.cancelled`, `attempt.timed_out`, `attempt.policy_violated`, `attempt.lease_expired`, `attempt.lost`, `attempt.fenced`, `attempt.rejected` | Attempt `CLEANING ->` matching terminal state; inactivates its `ATTEMPT_INPUT` roots and activates only its pre-held `ATTEMPT_OUTPUT` roots | `attempt_terminal_evidence`, `fencing_generation`, `final_fence_floor`, `output_storage_roots` | +| `attempt.assurance_evaluated` | Settled terminal Attempt state-neutral; Attempt assurance and parent Job final summary/current-attempt fields advance atomically | `evaluation`, `assurance_claim_sigil`, `reason_codes`, `evidence_set_sigil`; claim Sigil is non-null only for `CLAIMED` | +| `lease.offered` | Lease none `-> OFFERED` | `lease_binding_sigil`, `credential_digest`, `claim_due_at`, `initial_expiry_due_at`, `maximum_expiry_due_at` | +| `lease.claimed` | Lease `OFFERED -> ACTIVE`; Attempt `READY -> LEASED`; Session capacity updated | `credential_proof_sigil`, `claimed_at`, `next_heartbeat_due_at`, `session_capacity_after` | +| `lease.heartbeat_accepted` | Active Lease state-neutral | `heartbeat_message_sigil`, `sequence`, `prior_accepted_sequence`, `received_at`, `next_heartbeat_due_at`, `resource_sample_sigil` | +| `lease.renewed` | Lease `ACTIVE -> ACTIVE` | `renewal_request_sigil`, `prior_expiry_due_at`, `new_expiry_due_at`, `renewal_counter` | +| `lease.released` | Lease `ACTIVE -> RELEASED`; Session capacity and Job fence floor updated | `release_evidence_sigil`, `prior_fence_floor`, `tombstone_generation`, `tombstone_publication_sigil`, `session_capacity_after` | +| `lease.revoked` | Lease `OFFERED` or `ACTIVE -> REVOKED`; Session capacity and Job fence floor updated | `transition_cause`, `prior_fence_floor`, `tombstone_generation`, `tombstone_publication_sigil`, `session_capacity_after` | +| `lease.expired` | Lease `OFFERED` or `ACTIVE -> EXPIRED`; Session capacity and Job fence floor updated | `deadline_kind`, `due_at`, `prior_fence_floor`, `tombstone_generation`, `tombstone_publication_sigil`, `session_capacity_after` | +| `lease.fenced` | Lease `OFFERED` or `ACTIVE -> FENCED`; Session capacity and Job fence floor updated | `transition_cause`, `prior_fence_floor`, `tombstone_generation`, `tombstone_publication_sigil`, `session_capacity_after` | +| `lease.tombstone_republished` | Terminal Lease state-neutral | `tombstone_generation`, `original_terminal_event_sigil`, `sink_ids`, `publication_evidence_sigil` | +| `lease.message_rejected` | Lease state-neutral | `message_kind`, `message_sigil`, `reason_codes`, `historical_disposition_event_id` | +| `log.chunk_committed` | Open log stream state-neutral append | `log_stream_id`, `chunk_record_sigil`, `stream`, `sequence`, `blob_sigil`, `captured_bytes_after` | +| `log.chunk_rejected` | Log stream state-neutral | `log_stream_id`, `stream`, `sequence`, `message_sigil`, `reason_codes`, `historical_disposition_event_id` | +| `log.truncated` | Open log stream state-neutral latch | `log_stream_id`, `stream`, `limit_bytes`, `captured_bytes`, `dropped_bytes`, `overflow_behavior` | +| `log.closed` | Log stream open `-> CLOSED` | `log_stream_id`, `stream`, `final_sequence`, `captured_bytes`, `dropped_bytes`, `stream_set_sigil` | + +Every field shown in the payload column is required. Fields explicitly +described as nullable remain present with `null`; no other null or conditional +member is allowed. In addition, `prior_epoch` is null only for the first +epoch; `recovery.started.old_epoch` is zero, not null, only when no prior +epoch exists. `prior_recovery_id` is null only when no earlier Recovery exists, +`supersedes_worker_binding_sigil` only at Worker definition revision zero, +heartbeat prior sequence only at sequence zero, +`claimed_result_sigil` only when a malformed message has no valid claimed +Sigil, `process_exit_observation_sigil` only when no exit was observed, and +`historical_disposition_event_id` only when no prior disposition exists. +`final_sequence` is null only for an empty log stream. In a quarantined +terminal-source branch, source identity, Sigil, and storage BlobRef are null only when no +content-identified source could be retained; counts remain zero and the +reason is explicit. A Job terminal event's `storage_observation_binding`, +`terminal_source_binding`, `completion_anchor_binding`, +`first_stop_or_fence_binding`, and any selected Attempt's copies must be +exactly equal. A terminal Attempt's storage branch is always `FROZEN`, its +resolver pair must validate before replay applies the event, and the +observation record's owner, terminal inputs, full RFC-0013 EventRef, and State +Sigil must satisfy the equalities above. With no Attempt, selection is +`NONE`, completion is `NOT_ESTABLISHED`, storage is `NOT_APPLICABLE`, final +fence is `NO_ATTEMPT`, and first stop is derived from the Job stop trigger. +`worker_session.message_rejected`, +`job.message_rejected`, `attempt.result_rejected`, +`lease.message_rejected`, and `log.chunk_rejected` preserve a new rejected +message. Exact duplicate replay returns the referenced historical disposition +and appends no event. + +Adding an event type, payload member, enum value, owner, or transition meaning +requires a new Schema version and an explicit replay compatibility rule. + +### Append and durability + +Before acknowledging a state-changing operation, the Executor must: + +1. hold the journal's exclusive writer lock; +2. replay or validate the current Head and relevant entity revisions; +3. evaluate due deadlines before the requested operation; +4. validate the complete event and transition; +5. make every referenced immutable payload and its exact resolver entry + durable; +6. append the event bytes atomically or through a framed record whose checksum + detects a partial tail; +7. make the appended event durable; +8. atomically replace the journal-head cache; and +9. only then release authority, a Lease credential, renewal response, result + disposition, or API success to the caller. + +The Head is a cache, not authority. A missing or stale Head is rebuilt from the +journal. A Head that points beyond or conflicts with the verified chain is an +integrity failure. Filesystem rename or write completion without required +durability is not sufficient evidence after a Host crash. + +The `0.4` reference runtime uses one writer lock and one Executor epoch at a +time. A second coordinator that cannot acquire the lock must not schedule, +renew, cancel, recover, or terminalize work. This RFC does not define +distributed consensus. + +### Replay + +Replay starts from the first event and: + +1. validates every event against its exact installed Schema and resolves each + immutable payload by every ID-and-Sigil field carried by that event; +2. requires the expected journal ID, contiguous sequence, and hash chain; +3. recomputes every event Sigil; +4. requires a valid initial `executor.epoch_started`; +5. validates entity identity, immutable binding, revision, relationship, and + idempotency constraints; +6. applies only the legal state-machine transitions in this RFC; +7. validates that at most one non-terminal Attempt exists per Job, at most one + Lease exists per Attempt, every `LEASED` or later Attempt has exactly one, + fencing generations increase, Worker Session capacity is not exceeded, and + terminal objects never transition; +8. reconstructs durable due times, in-process-anchor prerequisites, Worker + definitions and Sessions, budget reservations and settlements, log + sequence and limits, historical result dispositions, cleanup, terminal + source retention, output-storage-observation references, assurance, retry, + and recovery state; and +9. validates the resulting projection against `execution-state/1.0`. + +The replayed projection must be byte-for-byte deterministic under canonical +JSON for the same journal and installed Schema set. Queue ordering and +deterministic retry eligibility are derived from committed events, not current +directory order or wall-clock iteration. + +Unknown versions or event types, an invalid Sigil, missing middle event, +partial non-tail record, illegal transition, revision mismatch, fence +regression, duplicate identity, impossible relationship, or broken referenced +payload stops replay at the last verified prefix and places the Executor in +integrity-failure mode. It may inspect and export evidence but may not schedule, +renew, accept results, or silently skip, reorder, repair, or infer an event. +For a `FROZEN` storage observation, replay resolves the exact observation-set +ID and Sigil, replays RFC-0013 through its complete EventRef, recomputes the +State Sigil and every member/Replica selector, and compares all terminal +bindings byte-for-byte. It never searches for a record by Sigil alone or +substitutes a later Replica. + +A single clearly partial final frame may be quarantined only by an explicit +repair procedure defined in a later integrity RFC. Automatic truncation is +forbidden. The `0.4` reference runtime retains the complete journal and all +terminal Attempt events. Checkpoints or compacted projections may accelerate +observation only after verifying their prefix Event Sigil; they never replace +the journal or authorize deletion of failed history. + +## Restart and recovery + +Executor startup and runtime clock uncertainty use the same journaled Recovery +transaction. At most one Recovery transaction is active; a completed +transaction remains terminal history but does not prevent a later fresh +`recovery_id`. Its states and only legal transitions are: + +| From | To | Event and committed postcondition | +| --- | --- | --- | +| no active Recovery | `STARTED` | `recovery.started`; replay prefix, prior completed Recovery if any, old/new epoch, all non-terminal entity sets, and the durable `STARTED` action-set Sigil are bound, and every authority-creating operation is gated off. | +| `STARTED`, `FENCING`, `RECONCILING`, or `FINALIZING` | same phase | `recovery.action_set_rebased`; the same `recovery_id` remains active, the replacement current-phase action set binds the immediately prior set and the new verified prefix, valid completion events are carried exactly once, and no further action completion may bind the superseded set. | +| `STARTED` | `FENCING` | `recovery.phase_advanced`; the `STARTED` action set is complete and the exact durable `FENCING` action set is bound. | +| `FENCING` | `RECONCILING` | `recovery.phase_advanced`; the `FENCING` action set is complete, all old `OFFERED`/`ACTIVE` Leases have durable tombstones, and the exact `RECONCILING` action set is bound. | +| `RECONCILING` | `FINALIZING` | `recovery.phase_advanced`; reconciliation actions verified, terminated, settled, or quarantined resources and the exact `FINALIZING` action set is bound. | +| `FINALIZING` | `COMPLETED` | `recovery.completed`; the `FINALIZING` action set, Attempt, assurance, retry, and Job consequences are durable and the replayed state Sigil validates. | + +`COMPLETED` is terminal. Each action-set document is made durable before the +event that binds its Sigil. A phase event is appended only after every action +in `completed_action_set_sigil` has one matching event with the exact common +Recovery binding; carried completion events are retained by the transitive +rebase chain but do not satisfy a replacement-set action. The action events +are the normal exact v1 events above, so phase work is idempotent: replay +enumerates the frozen current set, skips actions with a matching committed +event, re-publishes a tombstone without changing its generation, and never +derives or emits a substitute action. + +Startup performs these ordered steps: + +1. acquire the exclusive writer lock and validate the complete journal; +2. if replay fails, stop before epoch allocation in integrity-failure mode; +3. append one strictly greater `executor.epoch_started`; +4. verify UTC and establish fresh monotonic anchors, or enter + `executor.clock_uncertain`; +5. create `recovery.started` only when no incomplete Recovery transaction + exists; otherwise retain that exact `recovery_id` and replayed phase, + derive a replacement action set from the new-epoch prefix, and append + `recovery.action_set_rebased` before any further action completion; +6. re-execute the current phase's fixed action set until its postcondition is + durable, then advance one phase; in `FENCING`, every + `BIND_DURABLE_ATTEMPT_AUTHORIZATION` for an already existing exact + request/Event/Receipt precedes the same Attempt's stop action, performs + read-only resolution only, and is omitted when no such complete chain + exists; and +7. issue no scheduling, Session readiness, Lease claim, renewal, result + acceptance, or side-effect authority until `recovery.completed`. + +A crash before an action event leaves no action to infer. A crash after the +action event but before phase advancement replays that action and appends only +the missing phase event. A crash after phase advancement resumes the next +phase. Thus repeated crashes never duplicate an Attempt, Lease, budget charge, +result disposition, assurance evaluation, terminal-source binding, or fence +tombstone. + +### Job recovery table + +| Replayed Job state | Mandatory recovery behavior | +| --- | --- | +| `SUBMITTED` | Commit any due Job deadline first. Otherwise revalidate the bound admission evidence and append the one missing `job.queued`, or latch a precise stop cause. Never create a second Job for the Start key. | +| `QUEUED` | Commit due deadline/freshness failures or preserve `QUEUED`. No Attempt is allocated until Recovery is `COMPLETED`; later allocation uses the normal atomic reservation event. | +| `ACTIVE` | Reconcile its one non-terminal or recently terminal Attempt by the Attempt table, settle its reservation, complete assurance evaluation, then deterministically retry, stop, or succeed. The Job state alone never implies process outcome. | +| `RETRY_WAIT` | Preserve the recorded `eligible_due_at`; commit a due Job deadline first. If eligibility is due, append one `job.retry_ready`; otherwise retain the exact wait. Allocation still waits for Recovery completion. | +| `STOPPING` | Create no Attempt or retry. Finish the current Attempt if any, settle budget, append the required Job assurance evaluation, and commit the terminal state selected by the latched cause and higher-precedence evidence. | +| any terminal Job | Preserve state, cause, revision, terminal event, the selected Attempt's exact CES, QBS, and storage-observation resolver pairs, and immutable Outcome inputs. Resolve and revalidate all referenced records; missing or mismatched bytes are an integrity failure, never a reconstruction request. Resume only an idempotent pending terminal Cancel no-op lookup and the closed execution-root hold-release reconciliation defined below. That reconciliation may resolve or create only the exact EHR, append the matching RFC-0013 Storage release, and append one state-neutral `storage_root.hold_release_observed`; it never rebuilds terminalization or Outcome-input evidence and never changes the terminal Job or Outcome. | + +### Attempt recovery table + +| Replayed Attempt state | Mandatory recovery behavior | +| --- | --- | +| `CREATED` | No preflight is inferred. If operational binding was interrupted after the exact canonical Receipt became durable, derive the same AA-ID and AAT-ID, resolve only the exact immutable request, `attempt.authorization.granted` Event, and Receipt, and execute `BIND_DURABLE_ATTEMPT_AUTHORIZATION` before any stop only when that complete chain validates. Never submit or rebuild a request. Then use the legal `CREATED -> STOPPING` transition with `RECOVERY_FENCE`, clean or quarantine allocated namespaces, and terminalize `FENCED` unless a higher-precedence cause applies. | +| `PREFLIGHTING` | Latch recovery stop, quarantine any incompletely materialized Crucible/input/output state, enter `CLEANING`, and terminalize `FENCED`, `REJECTED`, or `LOST` according to durable evidence. | +| `READY` | No Worker authority exists, but the old Attempt generation is not reused. Latch stop, clean the materialization, and terminalize `FENCED`; a retry receives new identities. | +| `LEASED` | Fence the active Lease and Session capacity first, latch stop, verify no process began or terminate it, clean/quarantine, then terminalize `FENCED` or `LOST`. | +| `STARTING` | Fence the Lease, locate the process tree only by bound backend identity, prevent further creation, terminate/revoke, clean/quarantine, and terminalize `FENCED`, `LOST`, or a higher-precedence state. | +| `RUNNING` | Fence the Lease and sinks before trusting new messages, terminate the complete tracked tree and handles, then `STOPPING -> CLEANING -> FENCED/LOST` unless an earlier cancellation, timeout, or policy cause wins. A result accepted before the crash remains historical evidence but no missing `attempt.draining` event is inferred. | +| `DRAINING` | The result disposition or permitted no-result completion anchor is already durable. Fence any still-active Lease, close logs/outputs, verify termination and cleanup, and continue to `CLEANING`. An accepted-result or no-result anchor before the later recovery tombstone remains selectable only if no earlier authority-loss trigger exists and every terminal postcondition passes. | +| `STOPPING` | Re-run the idempotent termination and handle-revocation plan, quarantine ambiguity, append the one missing `attempt.cleaning`, and terminalize under the already latched cause or higher-precedence evidence. | +| `CLEANING` | Preserve the terminal-source disposition and Quarantine plan frozen by `attempt.cleaning`; re-run only cleanup steps absent from the journal and verify or quarantine logs, outputs, evidence, and mutable resources. If `ACCOUNTING_CAPTURED` is absent, deterministically derive or resolve the CES, output ESM, its exact Reference Set/hold when non-empty, and QBS; validate every reference and subject mapping; then append the final capture with all four frozen bindings. If it is present, resolve only those exact bindings. Then derive or resolve the OS-ID, reusing its historical RFC-0013 EventRef/State Sigil when already pending, and append the one matching terminal event. Later evidence or storage state never refreshes any terminalization record. | +| terminal, assurance pending | Never change terminal state. Resolve and validate the exact `FROZEN` CES, QBS, and observation records, settle the preserved budget reservation, then produce only trusted post-terminal evidence and append exactly one `attempt.assurance_evaluated`. Missing evidence yields `UNVERIFIABLE`; a missing or contradictory referenced record is an integrity failure, not reconstruction. | +| terminal, assurance complete | Preserve the event, all three terminalization resolver pairs, and claim/evaluation Sigils byte-for-byte; continue only pending Job settlement/evaluation. | + +### Lease and Worker recovery table + +| Entity and replayed state | Mandatory recovery behavior | +| --- | --- | +| Lease `OFFERED` | Append `lease.fenced`, advance and publish the Job tombstone, and reject all later claim proofs. | +| Lease `ACTIVE` | Append `lease.fenced`, advance and publish the Job tombstone, revoke the protected credential and all bound handles, and release Session capacity atomically. | +| Lease `RELEASED`, `REVOKED`, `EXPIRED`, or `FENCED` | Preserve its terminal event and generation. Verify or idempotently republish the existing tombstone; never allocate another tombstone for the same terminal transition. | +| Worker definition `REGISTERED` | Preserve the stable definition; it remains unschedulable until independently enabled after Recovery. | +| Worker definition `ENABLED` | Preserve the definition state; all old-epoch Sessions are handled separately and no new Session becomes ready before Recovery completes. | +| Worker definition `DRAINING` | Preserve the drain latch and close its Sessions; do not enable implicitly. | +| Worker definition `QUARANTINED` | Preserve quarantine and evidence; recovery cannot revalidate it implicitly. | +| Worker Session `REGISTERED` | Append `worker_session.offline`, fence any unexpected Lease, then close after backend/channel disposition is durable. | +| Worker Session `READY` | Append `worker_session.offline`; it held no active authority and closes without reuse. | +| Worker Session `BUSY` | Append `worker_session.offline`, fence every active Lease, terminate/revoke its handles, then close. | +| Worker Session `DRAINING` | Append `worker_session.offline`, finish fencing/termination for all Leases, then close. | +| Worker Session `OFFLINE` | Preserve offline cause and close after every Lease and backend handle is terminal. | +| Worker Session `QUARANTINED` | Preserve quarantine evidence and close after every Lease and handle is terminal or quarantined. | +| Worker Session `CLOSED` | Preserve terminal state. A returning process must create a new Session ID. | + +### Adjunct recovery table + +| Replayed intermediate evidence | Mandatory recovery behavior | +| --- | --- | +| Open log stream | Resume from its last committed sequence, drain only a still-bound live backend during reconciliation, then append one `log.closed` or preserve a prior truncation. Partial uncommitted bytes remain quarantined. | +| Accepted/rejected result disposition | Return the historical disposition for duplicates. Never append another acceptance or revive authority; use its committed sequence in terminal conflict checks. | +| Attempt authorization Receipt with ambiguous operational acknowledgement | Recompute the subject, AA-ID, and AAT-ID from the immutable Attempt and stored idempotency identity. If replay already has the exact `BOUND` event, return it. If absent, resolve by the exact request ID/Sigil and verify its `attempt.authorization.granted` Event and `receipt/1.1` Event pair before appending the one missing operational event while the Attempt is still `CREATED`; a missing or conflicting request, Event, or Receipt forces the closed stop path. Never mint or bind a replacement. | +| Reserved but unsettled budget | Settle once from trusted supervisor evidence. Any unrecoverable dimension is charged at its full reservation. | +| Attempt terminal with no assurance evaluation | Append one `CLAIMED`, `UNMET`, or `UNVERIFIABLE` evaluation from immutable evidence; never infer Worker controls. | +| Job stopping with no Job assurance evaluation | Append one roll-up evaluation, using `NOT_APPLICABLE` only when no Attempt ever existed, before any Job terminal event. | +| `CODE_MODIFICATION` terminal-source staging before `attempt.cleaning` | Validate the predeclared Base, source content, file/byte bounds, retention-policy Sigil, durable storage, and verifier evidence, then freeze the exact verified or quarantined binding in `attempt.cleaning`. Any partial, mutable, absent, or unverifiable source is quarantined; recovery never replaces that frozen branch. | +| Durable CES or QBS record with no `ACCOUNTING_CAPTURED` event | Recompute its deterministic ID from the immutable owner and, for QBS, Quarantine-plan input; resolve the single pending pair by that ID and reuse it only when every canonical byte, evidence reference, subject mapping, and owner validates. The pending record alone does not close accounting or permit terminalization. | +| `ACCOUNTING_CAPTURED` with frozen CES, QBS, ESM, and output-root protection | Resolve only the exact bindings carried by the event and validate their complete documents, ESM edge closure, and active hold when `HELD`. Missing or changed bytes are an integrity failure; recovery cannot rebuild any binding from later evidence, current files, current RFC-0013 State, or a Sigil-only search. | +| Durable output-storage-observation record with no Attempt terminal event | Derive its OS-ID again from the immutable terminal inputs, resolve the single pending pair by that ID, and reuse it only if every canonical byte and its historical Storage prefix validate. A later Storage Head never refreshes or replaces it. The pending record activates no root and cannot imply terminalization by itself. | +| Attempt terminal event with a `FROZEN` observation reference | Resolve its exact CES, QBS, and OS ID-and-Sigil pairs, replay RFC-0013 through the bound EventRef, compare the State Sigil, every evidence reference and Quarantine subject mapping, all observation members, the minimum-eligible-Replica selections, and every terminal input byte-for-byte, and then preserve the records. Missing, changed, or unresolvable evidence closes the integrity gate; recovery never scans by hash or substitutes current evidence or storage. | +| Inactive Job- or Attempt-input root with an active hold | Under the outer gate, replay through the exact owner-terminal prefix and State, derive or resolve the deterministic `OWNER_TERMINAL` EHR, append or reuse only that hold's RFC-0013 release, and append the one missing `storage_root.hold_release_observed` owner branch. An absent activation, owner mismatch, ambiguous prefix/EHR, or clock uncertainty retains the hold; an unactivated orphan uses only the separate `ORPHAN_ABORT` protocol. | +| Due output-hold release schedule | Under the outer gate, prove the exact parent Job terminal Event inactivated the root, require trusted time at or after the immutable due time, resolve the deterministic `OUTPUT_DEADLINE` EHR, and resolve or append only that hold's RFC-0013 `retention.hold_released`; then append the missing `storage_root.hold_release_observed` with that EHR ID/Sigil. If either journal, time, or authorization is unavailable, retain the hold. Never release a policy, canonical, legal, or unrelated hold and never delete bytes directly. | +| Terminal Job or derived Outcome | Resolve the selected Attempt's exact observation record and direct-copy it under the derivation rules below. Never crawl a mutable Crucible, add a missing retained-source binding, replace a quarantine disposition, or backfill the immutable Outcome. Later RFC-0014 derivation consumes only the terminal evidence already bound. | + +An old Worker Session cannot reattach to its Lease after the epoch changes, +even if it still has the credential and believes the Lease time remains. Its +messages are late evidence. A replacement Attempt never shares mutable +resources unless the predecessor is verified terminated and an explicit +immutable resume identity passes the resume protocol. + +The `0.4` runtime always fences live Leases across restart. A future backend +may preserve live authority only under a new RFC defining durable coordinator +continuity, reattachment proof, clock behavior, and equivalent assurance +evidence. Process existence alone is not continuity. If a process cannot be +found or killed, the affected Attempt becomes `LOST`, resources remain +quarantined, and no assurance requiring verified termination is claimed. +Unrelated Jobs resume only when the backend proves resource and authority +separation; otherwise the Executor remains fail-closed. + +## Result eligibility and scientific acceptance + +The operational projection exposes result dispositions and eligibility +evidence, not a Receipt. Worker `execution-result/1.0`, terminal Attempt, +terminal Job, RFC-0015 `execution-job-outcome/1.0`, and Athanor +`agent-result/2.0` are distinct layers. + +### Deterministic `execution-job-outcome/1.0` prerequisites + +RFC-0015 owns the Outcome Schema and API. This RFC supplies the operational +prerequisites. Its Schema must import, without restatement or widening, the +RFC-0012 `$defs` for `public_fence_tuple`, +`attempt_assurance_binding`, `job_assurance_binding`, `budget_dimension`, +`budget_ledger`, `budget_settlement_binding`, +`attempt_authorization_requirement`, and `attempt_authorization_state` from +`execution-state/1.0`, and `transition_cause`, +`result_binding`, `completion_anchor_binding`, `worker_session_binding`, +`first_stop_or_fence_binding`, `selected_attempt_binding`, +`lease_terminal_binding`, `final_fence_binding`, +`storage_observation_binding`, `terminal_source_binding`, +`attempt_summary`, `computation_status`, `worker_status`, +`process_termination_status`, `handle_revocation_status`, `cleanup_status`, +`mutable_resource_isolation_status`, `output_publication_status`, and +`quarantine_status` from +`execution-journal-event/1.0`. A `$ref` targets the installed exact +`https://benchwork.dev/schemas/#/$defs/` URI. +RFC-0015 also validates +`execution-output-storage-observation-set/1.0` at its exact `$id` and derives +each storage-backed Outcome collection from that complete document; the +observation-set Sigil is not a substitute for resolving it. +Its Outcome `outputs`, `logs`, `resource_evidence`, and +`terminal_source_observation` import the named member `$defs` above and +together preserve the exact ordered member union. RFC-0015 may wrap those +values only in a closed object that names where the value came from; it may +not rename a member, add a null alternative, collapse an explicit union +branch, or substitute a projection cache. Every direct Outcome copy is +byte-for-byte equal under canonical JSON to the referenced terminal-prefix +value. RFC-0015's later `agent-result/2.0` output projection is distinct: it +may use only the explicit field mapping printed by that RFC after eligibility +has validated the complete source member and selected Replica. + +RFC-0015 `runtime_outcome` is intentionally its own closed derived object, not +an import of the complete `attempt_terminal_evidence` object. In its Attempt +branch, every same-named status, evidence/summary Sigil, closure Sigil, and +accounting binding is copied byte-for-byte from the selected Attempt terminal +evidence. Its no-Attempt branch uses RFC-0015's fixed explicit status and null +values. It may add only the closed derivation fields RFC-0015 specifies and +must not weaken any imported component enum. + +For every terminal Job, Outcome derivation must: + +1. validate the journal and installed exact Schema set through the terminal + Job event; +2. use only that fixed execution prefix, the terminal event's `recorded_at`, + the pinned derivation-profile identity, and the one immutable + output-storage observation document resolved by the terminal + `storage_observation_binding` ID-and-Sigil pair; verify its OS-ID, self + Sigil, RFC-0013 EventRef, replayed State Sigil, owner and terminal-input + equalities, then resolve and validate its exact CES-ID/Sigil and QBS-ID/ + Sigil pairs, all ten control references, and every Quarantine subject + mapping before reading a member. Retrieval time, later execution, evidence, + or Storage events and mutable current projections are not inputs; +3. verify the immutable Job, Task Capsule, Capability Contract, Snapshot, + Ward, Specification approval, Execution Specification, Program, Circle, + and input bindings; for every Attempt, also verify its immutable + authorization requirement and exact terminal authorization state, and + resolve each required `BOUND` subject and distinct Receipt; +4. include the exact sorted `attempt_summaries`, verify every Attempt binding + and terminal event, and verify exactly one budget settlement and one + Attempt assurance evaluation per allocated Attempt; +5. verify the pre-terminal `job.assurance_evaluated` event and its exact link + from the terminal Job event; +6. verify the exact `selected_attempt_binding`; for `SELECTED`, copy its + `result_binding` directly into the Outcome, while `NONE` requires direct + `result_binding: NONE`. Verify the direct `completion_anchor_binding`; all + applicable direct copies equal the selected and Job terminal values. A + selected result anchor binds its + `attempt.result_accepted` event ID, Sigil, and sequence strictly before + `attempt.draining`, while a permitted no-result anchor binds that + `attempt.draining` event ID, Sigil, sequence, and process-exit observation; + either anchor precedes Attempt and Job terminalization; +7. apply the exact authority-history branch: a selected Attempt that ever + received an offer has `worker_session_binding: BOUND`, a non-null Executor + epoch and `public_fence_tuple`, and terminal Lease/tombstone evidence; a + selected Attempt never offered a Lease has `worker_session_binding: NONE`, + no Lease-bound Executor epoch or public tuple, + `lease_terminal_binding: NONE`, and + `final_fence_binding: ASSIGNED_NO_LEASE`; its allocation event's Executor + instance/epoch may be copied as provenance but confers no authority. A Job + with no allocated Attempt has all Attempt authority identities absent, + `lease_terminal_binding: NONE`, and + `final_fence_binding: NO_ATTEMPT`. Also verify the exact + first stop-or-fence event and effective sequence or explicit absence; +8. verify process termination, handle revocation, cleanup, mutable-resource + isolation, log/output closure, publication or quarantine, the complete + resolvable control-evidence and Quarantine-binding sets carried by + `ACCOUNTING_CAPTURED`, and the accounting ledger; +9. bind the requested assurance tuple, evaluation event, optional realized + claim, backend, Host, profile, suite, and configuration Sigils; +10. derive outputs, exactly three selected-Attempt Log streams, + resource-evidence references, and terminal-source storage only by + byte-for-byte projection of the observation set's ordered closed member + union. Bind its exact ID and Sigil, Storage Journal EventRef and State + Sigil, Blob records, availability and integrity evidence, Quarantine + branches, and the unsigned-ASCII-minimum eligible Replica's backend object + and generation. Preserve explicit `NONE`/`EMPTY` branches and the combined + 4,096-member and 4,096-distinct-Blob bounds; and +11. for `CODE_MODIFICATION`, bind the exact terminal-source branch already + present in the Attempt and Job terminal events: a `VERIFIED` branch carries + immutable Crucible Base, retained source, retention policy, bounds, storage + status, and verifier evidence, while `QUARANTINED` carries explicit absence + or quarantined identity and closed reasons. + +Outcome ID is deterministically derived from Job ID and terminal-event Sigil. +Outcome Sigil covers every field except itself. The Outcome includes +`acceptance_eligible` and RFC-0015's closed sorted ineligibility reasons. +`acceptance_eligible` is true only when all of these predicates hold: + +- Job and selected Attempt are both `SUCCEEDED`; +- the selected Attempt's authorization state is `NONE` exactly for a `NONE` + requirement or one valid `BOUND` subject/Receipt exactly for `REQUIRED`; +- the selected result is `ACCEPTED` when the Specification mode is + `REQUIRED`, is accepted or explicitly absent under `OPTIONAL`, and is absent + under `FORBIDDEN`; any acceptance disposition precedes draining and + terminalization; +- the exact first stop-or-fence binding is absent or its effective sequence is + strictly later than the completion-anchor sequence, and no revocation, + expiry, clock, policy, integrity, or conflicting result in the fixed prefix + proves earlier authority loss; +- the later Lease terminal event and higher fence-floor tombstone are present; +- budget settlement, termination, handle disposition, cleanup, logs, outputs, + storage, and any required terminal-source retention pass without + quarantine; +- `job.assurance_evaluated` is `CLAIMED`, its Attempt claim satisfies the exact + requested profile and suite, and the complete control evidence validates; + and +- no closed ineligibility reason remains. + +A normal Lease release, recovery closure, or higher tombstone after accepted +completion does not retroactively stale the selected result. Its acceptance +event is historical evidence. It is selectable only when the ordering and +postconditions above pass. A permitted `NO_RESULT` draining anchor is likewise +historical completion evidence and uses the same ordering test. A rejected +late or duplicate message after the Job terminal event is preserved outside +the fixed Outcome prefix and cannot replace selected evidence or mutate the +immutable Outcome. + +Failed, cancelled, timed-out, policy-violating, lost, fenced, rejected, and +preflight-rejected Jobs still derive one deterministic Outcome, normally with +`acceptance_eligible: false`, even when no Worker result, Lease, or assurance +claim exists. `NOT_APPLICABLE` makes that absence explicit only for a Job with +no Attempt and never makes it eligible. + +Repeated derivation from the same verified terminal prefix and installed +Schema set and the same referenced immutable storage observations must be +byte-for-byte equal. Terminal storage status is the frozen observation, not a +query of current availability. A missing observation record, changed +ID/Sigil pair, State-Sigil mismatch, 4,097th member or distinct Blob, or +non-minimum selected Replica makes derivation fail closed; it is never repaired +by scanning immutable records or the current backend. The Outcome never +contains or later backfills +an Agent Result Receipt, Patch Proposal, Patch state, Run, Artifact, or +Experiment state. In particular, RFC-0014 may derive a Patch only from the +predeclared retained terminal source after an accepted `agent-result/2.0` +Receipt; it cannot crawl a mutable Crucible or modify the Outcome. + +Eligibility does not imply scientific acceptance. RFC-0015 asks Athanor to +rederive the exact Outcome Sigil and revalidate current canonical +preconditions, including current RFC-0013 Blob/Replica availability and +integrity rather than treating the frozen terminal observation as current. +Only Athanor may append a Chronicle event and issue a Receipt. If Athanor +rejects the Proposal as stale, malformed, duplicate, +scientifically invalid, or otherwise ineligible, the operational Job, Attempt, +Outcome, and negative evidence remain unchanged. + +## Invariants + +- Chronicle is the source of canonical research state; the execution journal + is the source of operational execution state. +- Athanor remains the only canonical transition authority. +- Exactly one immutable Task Capsule and Execution Specification bind a Job. +- At most one non-terminal Attempt exists for a Job. +- An Attempt has at most one Lease, every `LEASED` or later Attempt has exactly + one, and reassignment creates a new Attempt. +- Attempt IDs, Lease IDs, Worker Session IDs, backend-session identities, + control-channel identities, Executor epochs, and fence generations are never + reused. +- Only the current active, unexpired Lease can contribute newly accepted + Worker material; a later tombstone preserves, but does not create, + historical acceptance. +- Heartbeats do not renew Leases, Worker clocks do not decide deadlines, and + renewal never resurrects expired authority. +- Durable UTC due times plus in-process monotonic anchors decide deadlines; + clock uncertainty fences authority and cannot extend it. +- Terminal state never transitions, and retry never rewrites an Attempt. +- Failed, cancelled, timed-out, rejected, expired, lost, fenced, and + policy-violating Attempts remain replayable. +- A replacement Attempt never shares mutable resources with an unfenced or + ambiguously owned predecessor. +- Preflight eligibility, Worker capability, and mechanism names are not + realized assurance. +- Attempt terminal state describes computation, termination, and cleanup, not + assurance; every terminal Job references one prior closed Job assurance + evaluation. +- Realized assurance is per-Attempt, post-terminal, evidence-bound, and cannot + silently downgrade requested assurance. +- Every Attempt allocation reserves aggregate Job budget atomically, and every + terminal Attempt settles it once from trusted accounting or a full + fail-closed charge. +- Every terminal Attempt names one resolvable `FROZEN` output-storage + observation set whose owner, terminal inputs, RFC-0013 EventRef, State + Sigil, at most 4,096 members, and at most 4,096 distinct Blob identities + validate byte-for-byte; only a no-Attempt terminal Job uses + `NOT_APPLICABLE`. +- Each Attempt's frozen terminal inputs derive one OS-ID and permit at most one + pending-or-terminal observation record, so the complete execution record + family contains at most `MAX_ATTEMPTS` 4,096 such records. +- A frozen Blob observation selects no Replica exactly when its eligible set + is empty and otherwise selects the unsigned-ASCII-smallest eligible Replica; + no later backend or availability state changes that fact. +- A Worker definition is stable across fresh immutable Sessions; an offline, + quarantined, closed, or old-epoch Session never re-registers. +- A terminal Job Outcome is a deterministic fixed-prefix derivation and is + never backfilled with later acceptance, Patch, Run, Artifact, or retained + source state. +- Job success is not a Run, Artifact, Assessment, Decision, Seal, or Receipt. +- Logs and outputs are bounded, content-identified, untrusted operational + material until explicitly accepted. +- Unknown contracts, controls, events, identities, evidence, or transitions + fail closed. +- An A2 Worker cannot access or mutate the execution journal, Lease authority + store, policy source, Chronicle, canonical projections, or any part of + `.benchwork/`. + +## Compatibility + +This RFC refines RFC-0007's reserved Executor contract and is subordinate to +RFC-0011's trust and assurance model. It does not change any accepted Phase 2 +Schema or identifier. + +In particular: + +- `task-capsule/1.1` remains declarative and is never wrapped or reinterpreted + as an executable Job; +- `agent-result/1.1` remains a Phase 2 Proposal and is distinct from + `execution-result/1.0`; +- scientific `run/1.1` and `run/1.2` terminal states do not gain queued, + leased, running, retrying, lost, or fenced meanings; +- Chronicle Heads and Receipts are not reused as execution journal Heads or + events; +- current interactive Codex and Claude Code native-tool Tasks remain outside + this protocol; and +- a Phase 2 approval never authorizes an Execution Specification or Job. + +An Alpha implementation changing any state, transition, terminal precedence, +event payload, fence behavior, or replay meaning must publish a new accepted +RFC version, new executable Schema version where required, migration guidance, +and replay fixtures. Historical execution journals are never replayed under +new semantics by guesswork. + +## Security and integrity + +Worker messages, Worker clocks, process exit data, resource samples, logs, +outputs, and backend evidence are untrusted until their identities, bounds, +Sigils, fence tuple, and relationships validate. Control and journal channels +must reject ambiguous encoding, path traversal, special-file substitution, +oversized frames, decompression bombs, and identifier confusion. + +Lease credentials are capabilities. They must be unguessable, scoped to one +Lease, stored outside Worker-readable state, compared without leaking useful +partial information, rotated by new Lease rather than renewal, and destroyed +or revoked at terminalization. Logs, exception text, process listings, and +diagnostic exports must not disclose them. + +The Executor journal and evidence verifier are part of the operational trusted +computing base. The Worker cannot write its own state, fence generation, +heartbeat acceptance, committed log-set summary, result eligibility, cleanup +status, or assurance claim. At A2 the enforcement backend, rather than Worker +cooperation, controls process, filesystem, network, resources, credentials, +and output handles. + +An output-storage observation exposes only RFC-0013 object-identity and +locator Sigils, immutable generations, and closed verification evidence. It +never exposes a backend locator, credential, Host path, or Worker-usable +handle. The resolver accepts only the exact OS-ID and self-Sigil from a +verified terminal event; pre-terminal construction's only exception is the +single-assignment pending lookup by the deterministic OS-ID. Enumeration, +prefix matching, and Sigil-only lookup are forbidden. + +The local hash chain detects accidental damage and inconsistent replay; it +does not protect against a malicious same-user process able to rewrite the +entire journal, Head, Schemas, and evidence store. RFC-0011's trust limits +remain. + +## Alternatives + +- **Reuse scientific Run states for scheduling.** Rejected because operational + retry and liveness churn would change immutable scientific meaning. +- **Allow a Worker to pull any queued Job.** Rejected because selection, + verified capability, Lease identity, and permission would become an + unbounded ambient queue capability. +- **Use only an unguessable token, without a fence generation.** Rejected + because a leaked or delayed old token cannot order split-brain side effects. +- **Fence only returned results.** Rejected because an expired Worker could + still mutate a Crucible, output namespace, or external sink. +- **Treat heartbeat as renewal.** Rejected because liveness data would silently + extend authority without policy and deadline validation. +- **Reassign the same Attempt after Worker loss.** Rejected because provenance, + logs, mutable state, and side effects from the two Workers would be + ambiguous. +- **Trust a surviving process after Executor restart.** Rejected for `0.4` + because process identity does not prove coordinator continuity, current + authority, handle fencing, or evidence completeness. +- **Store runtime events in Chronicle.** Rejected because the Executor has no + canonical authority and operational replay has different failure semantics. +- **Drop old logs and Attempts after success.** Rejected because failures, + retries, truncation, and recovery are required operational evidence. + +## Non-goals + +- remote Workers or multi-coordinator scheduling; +- consensus, leader election, or network partitions between Executors; +- Slurm, Kubernetes, cloud queues, or broad GPU scheduling; +- production-grade A2 isolation on every Host; +- `SANCTUM-A3`, remote attestation, or confidential computing; +- physical Artifact replication, retention, or garbage collection; +- Patch validation, application, merge, or promotion; +- secret brokering or granting network credentials to Workers; +- automatic Provider invocation; +- automatic scientific Run or Artifact creation; and +- a generic command, shell, filesystem, Git, web, or arbitrary-execution MCP + operation. + +## Acceptance tests + +Acceptance requires executable Schemas, positive and adversarial fixtures, +deterministic replay tests, crash-injection tests, and retained evidence for +the exact `0.4` Host/backend configuration. The suite must prove: + +1. every listed Schema has the exact identifier, top-level fields, + conditional branches, bounds, and enums in this RFC, rejects every unknown + field/value, validates complete golden fixtures, publishes the fixed + `EXECUTION_JOURNAL_V1_FIXED_LIMITS` constants, and resolves the canonical + named `$defs` `job_state`, `attempt_state`, `worker_session_state`, + `event_type`, and `storage_root_binding` at their printed URIs; the + 24-contract set includes the Attempt-authorization subject and transition + request, retained source-tree, execution-storage-root-manifest, + execution-root hold-release authorization, control-evidence-set, + quarantine-binding-set, and observation-set filenames and `$id` values, + exact JB-ID, AA-ID, AAT-ID, BTS-ID, ESM-ID, EHR-ID, CES-ID, QBS-ID, and + OS-ID algorithms, + top-level fields, all reference/member/storage branches, and + additional-property rejection at every nesting level; +2. the journal Schema accepts exactly the v1 event set and payload table, + rejects an omitted required payload field or extra event type, and enforces + the exact 73-row base revision-effect closure, including each exact owner + kind, owner ID, `CREATE`/`ADVANCE`/`EQUAL` mode, conditional + `attempt.stop_latched` Job mode, Recovery augmentation, five-entry maximum, + and rejection of every missing, duplicate, extra, or wrongly ordered + revision, as well as every closed reason, message, progress, status, + assurance, and budget enum; +3. v1 Capability and Task contracts are ineligible, while the Execution + Specification cannot broaden its v2 Task, Capability, Snapshot, approval, + runtime, retry, output, retention, budget, or assurance authority; +4. all listed Job, Attempt, Lease, Worker-definition, and Worker-Session + transitions succeed only from their stated source states, including + `CREATED -> STOPPING`, and every unlisted lifecycle transition fails; +5. Attempt terminal events depend only on computation, result disposition, + termination, cleanup, and quarantine; assurance failure never rewrites an + Attempt terminal state; +6. every Job terminal path, including no-Attempt cancellation, timeout, + validation failure, and policy failure, has exactly one preceding + `job.assurance_evaluated` with the legal + `CLAIMED`/`UNMET`/`UNVERIFIABLE`/`NOT_APPLICABLE` branch; +7. one Job cannot have two non-terminal Attempts, one Attempt cannot have two + Leases, Session capacity cannot be exceeded, and identities or fence + generations cannot be reused; +8. a stable Worker definition creates multiple fresh immutable Sessions, while + process restart, channel re-establishment, epoch change, offline, + quarantine, or close always requires a new Session ID and never reactivates + an old Lease; +9. Lease claim requires the exact Worker definition, Worker Session, + credential proof, Executor epoch, fence tuple, and unexpired durable claim + due time; +10. heartbeat alone never changes Lease expiry; accepted ordering, exact + duplicates, conflicting duplicates, gaps, stale messages, and late + messages follow the closed disposition rules; +11. renewal commits durably before acknowledgement, respects every durable + maximum due time, and cannot revive expired, revoked, released, fenced, + stopping, offline, quarantined, closed, or old-epoch authority; +12. UTC `due_at` plus a live monotonic anchor does not extend a deadline after + wall-clock adjustment, and restart reconstructs anchors only after clock + validation; +13. simultaneous due work commits in exact + `(due_at, fixed_priority, entity_id)` order across randomized timer, + thread, and receive ordering, and each higher-priority primary event + finishes its dependent stop/fence propagation before a lower-priority + still-applicable key can latch a cause; Recovery `STARTED` derives the + identical surviving primary set by virtual dependent closure and its + `FENCING` phase then materializes exactly those deferred dependencies; +14. clock rollback, monotonic reset, excessive divergence, and unprovable + suspend duration append `executor.clock_uncertain`, forbid new authority, + fence active/offered Leases, stop Attempts, and never restore old + authority after `executor.clock_restored`; an interruption during an + active Recovery retains its ID and phase, invalidates pending target + reservations, binds one replacement set, and never creates a second active + Recovery; +15. repeated exact non-terminal cancellation returns one `job.stop_latched`, + conflicting key reuse fails, and cancellation prevents later authority or + retry; +16. cancellation of a terminal Job appends one state-neutral + `job.cancellation_observed` per exact matching-revision request, preserves + state/cause/revision, replays the same no-op after restart, and appends + nothing when due processing first causes a revision conflict; +17. Attempt allocation atomically reserves the full exact budget vector, + creates the Attempt and exactly three `OPEN` `STDOUT`, `STDERR`, and + `STRUCTURED` Log-stream projections in the same event with exactly five + revision effects, rejects any vector that does not fit, and performs no + materialization before reservation durability; +18. trusted measured settlement releases unused reservation, partial or + unavailable accounting charges each unknown dimension in full, every + terminal Attempt has one prior immutable `ACCOUNTING_CAPTURED` event, + whose `FROZEN` finalization bindings resolve the exact ten-member + control-evidence set and complete subject-to-Quarantine set; settlement + binds that exact event and occurs exactly once after crash, and retry + requires a new full reservation; replay and crash injection after + Attempt terminalization, `job.budget_settled`, and + `attempt.assurance_evaluated` prove the exact terminal-without-summary, + settled-without-summary, and final-summary prefixes, both cross-owner + revision advances, and the exact `current_attempt_id` lifetime; measured + overage is retained, produces `EXCEEDED`, and forces policy-ineligible + termination rather than being capped or erased; +19. every retry preserves the prior Attempt, allocates new Attempt, Lease, + Crucible, output-namespace, Session authority, and higher fence identities, + and never retries a forbidden cause; +20. fresh retry and immutable resume follow their distinct branches; mutable + paths, surviving processes, ambiguous cleanup, or predecessor authority + cannot become resume identity; +21. log chunk order, duplicate disposition, conflicting duplicates, atomic + publication, stream closure, truncation, terminate-on-overflow, and late + rejection are deterministic and bounded; every terminal Attempt's + observation set carries exactly three ordered Log members, including + explicit empty-stream content; resource members resolve the exact CES + parent and phase-entry Sigils; Quarantine fixtures require the exact QBS + subject mapping, reject a same-Blob/size unrelated Quarantine, reject + every non-retained state and object/generation mismatch; and exact-limit + fixtures accept 4,096 combined members and distinct Blob identities while + rejecting either 4,097th value before record durability; +22. the first Worker result commits `attempt.result_accepted` or + `attempt.result_rejected` before `attempt.draining`; duplicates return the + historical disposition without an event or authority change; +23. malformed, conflicting, stale-fence, late, wrong-Schema, wrong-Sigil, + over-limit, and partial results remain rejected evidence and cannot replace + an accepted result; +24. a valid accepted result followed by normal Lease release and a higher + fence-floor tombstone remains historically well-fenced, while any + authority-loss trigger effective no later than its completion anchor makes + it unselectable; an `OPTIONAL`/`FORBIDDEN` no-result anchor uses the same + test against its draining sequence; +25. requested assurance is immutable; preflight creates no claim; a claim + appears only after Attempt terminalization and immutable termination/ + cleanup evidence and binds every exact profile, suite, backend, Host, + policy, input, output, and control-evidence identity through the frozen + CES-ID-and-Sigil pair and all ten resolvable member references; +26. A0 positive and negative fixtures enforce the exact matrix, including + verified Phase 3 Job/Attempt/Ward identities and truthful lower-state + controls without an execution-enforcement claim; +27. A1 positive and negative fixtures cover exclusive materialization, + constructed environment, tracked process group, wall-time/cancellation, + trusted accounting, bounded capture, exact identities, and terminal + evidence, and never claim A2 hostile isolation; +28. lower realized assurance remains visible with + `satisfies_request: false`; a computationally successful Attempt remains + `SUCCEEDED`, while the separate assurance roll-up makes the Job + `FAILED/ASSURANCE_UNMET`; +29. A2 cancellation and Lease loss kill the complete adversarial process tree, + revoke or fence every side-effecting handle, make `.benchwork/` + unreachable, and prevent ambiguous mutable-resource sharing; +30. journal replay validates Schema, sequence, chain, Sigils, revisions, + relationships, exact event ownership, idempotency, fences, deadlines, + budgets, retention, every fixed collection/entity/row/revisions-per-event + limit, and the complete closed `execution-state/1.0`, including its exact + `limit_profile`, every projection alias, and every conditional null rule; + every terminal Attempt is `FROZEN`, only a no-Attempt terminal Job is + `NOT_APPLICABLE`, and ID-only, Sigil-only, missing, mismatched-owner, + mismatched-prefix, or mismatched-State references fail closed; +31. replay from the same journal is byte-for-byte deterministic, stale or + missing `execution-journal-head/1.0` or State caches rebuild safely, + both caches carry the exact fixed `limit_profile`, conflicting Heads fail + closed, checkpointed observation equals full replay, and a create that + would exceed any fixed limit fails before ID/sequence reservation without + deleting terminal or failed history; +32. corrupt middle events, invalid Sigils, sequence gaps, illegal transitions, + partial records, unknown events, and conflicting Heads fail closed without + automatic truncation, repair, or scheduling; +33. crash injection in every Recovery phase and before/after payload + durability, event append, Head replacement, Lease delivery, renewal + acknowledgement, result disposition, budget settlement, assurance + evaluation, terminal-source retention, output-storage-observation record + and resolver durability, control-evidence-set and quarantine-binding-set + record/resolver durability, Storage hold creation, each sole + root-activating event, each sole root-inactivating event, hold release, and + terminal commitment yields the exact idempotent state; pre-event retry + resolves each single pending CES-ID/Sigil and QBS-ID/Sigil and + pre-terminal retry resolves the single pending OS-ID/Sigil; later evidence + or Storage prefixes cannot overwrite them, pending records activate no + root, no ID-only lookup is legal outside its stated pre-event recovery + window, each complete family never exceeds 4,096 records, and phase + membership, canonical action order, + reserved event IDs/sequences, action bindings, rebase carry set, + superseded-set rejection, and conservative orphan holds all validate; +34. each non-terminal Job, Attempt, Lease, Worker-definition, and + Worker-Session state follows its recovery-table row, including repeated + crashes with no duplicate transition or tombstone; observation recovery + resolves the exact three terminalization-record pairs, replays the frozen + RFC-0013 State, reproduces every control reference, quarantine subject + mapping, observation member, and minimum-eligible-Replica selection + byte-for-byte, and never scans by hash or substitutes current evidence or + storage; +35. old Sessions and Leases cannot reattach after restart; every surviving + process, handle, Crucible, output namespace, and sink is terminated, + verified, or quarantined before `recovery.completed`; root fixtures accept + only `JOB_INPUT`, `ATTEMPT_INPUT`, and `ATTEMPT_OUTPUT`, reject + `ACTIVE_CONTROL`, enforce exact Job/Attempt owner IDs and sorted bounds, + and expose each root only over its specified activation interval; +36. `CODE_MODIFICATION` requires predeclared bounded retention and terminal + events bind immutable Base/source/policy/verifier evidence; the + Specification's non-null Base identity/Sigil copy into every Attempt and + deterministically supply the no-Attempt branch; missing or partial + retention quarantines, a null source is permitted only as the paired + null/zero-count `QUARANTINED` branch, and no terminal Outcome is later + backfilled; +37. every terminal Job derives one byte-stable + `execution-job-outcome/1.0`, including no-result negative paths, from its + fixed verified terminal prefix and referenced immutable frozen storage + observation resolved by exact OS-ID and Sigil, with exact Session, + Attempt-summary, accepted-result event, first stop/fence, final-fence union + (including a final pre-Lease Attempt), Storage EventRef and State Sigil, + exact CES and QBS pairs, all ten control references, every Quarantine + subject mapping, direct output, three-Log, resource-evidence, and + terminal-source observation groups, Blob, availability, integrity, and + selected Replica/backend-generation bindings; + its Schema resolves every mandatory RFC-0012 component and the complete + observation contract through the printed canonical `$ref`, including + `budget_settlement_binding` and every named observation-member `$def`, + preserves explicit `NONE`/`EMPTY` without rename or flattening, enforces + both aggregate 4,096 limits, and rejects any cloned, widened, missing, or + 4,097-member import; +38. later duplicate, rejected, or terminal-cancellation observation events do + not mutate an Outcome, current storage availability does not change its + frozen terminal observation, a later lexically smaller Replica does not + replace the frozen minimum eligible Replica, and Outcome derivation + changes neither execution journal nor Chronicle; +39. terminal Jobs and all failed, cancelled, timed-out, expired, rejected, + lost, fenced, policy-violating, under-assured, quarantined, and partial + evidence remain present after replay; and +40. Job success and Outcome retrieval leave Chronicle, Runs, Artifacts, + Assessments, Decisions, Patches, Seals, and Receipts unchanged, while + RFC-0015 exposes only closed typed operations and no execution escape + hatch; and +41. an empty `NEW_AUTHORIZATION_EACH_ATTEMPT` projection initializes + authorization `NONE`, while every non-empty first or retried Attempt + initializes `PENDING`, derives the exact AA-ID only after immutable + allocation, resolves one distinct Receipt through the purpose-bound + request/Event chain, commits one + `attempt.authorization_bound` before preflight/Lease/handle authority, and + reaches `BOUND` exactly once; crash before or after Receipt/event + durability reuses the same subject and Receipt, while predecessor, + cross-Attempt, Specification-Receipt, changed-effect, missing, and + conflicting reuse all fail closed without launch; +42. Attempt authorization fixtures derive the exact AAT-ID, persist the + immutable transition request before Athanor, resolve + `attempt.authorization.granted` and its `receipt/1.1` by exact Event + ID/body Sigil, and reject any validation that invents purpose/subject + fields on the Receipt; the payload Actor, outer canonical + `actor/1.0`, and authenticated-context mapping all match byte-for-byte; + Recovery binds only an already durable complete + chain and never resamples Head, actor, Chronicle actor, time, or authority; +43. retained-source fixtures deterministically reproduce the complete + `benchwork-source-tree/1.0` manifest and `BWSOURCE1` bundle, enforce the + identity/Sigil/storage-Blob all-null-or-all-non-null matrix, reject + traversal, scope, ordering, byte, count, and bundle mismatches, and allow + RFC-0014 export only from exact verified bundle readback; +44. each execution root resolves one single-assignment ESM and, when + non-empty, the exact installed extractor/validator, complete typed Blob + edges, ESM-ID-derived registration Event ID, deterministic Reference Set, + neutral installed operational policy, exact post-registration set + authorization, and active hold targeting that Set; crash injection at ESM, + registration, hold, accounting capture, and root activation boundaries + proves the graph has no future-Sigil cycle and leaves conservative + protection; +45. output ESM and QBS fixtures bind every output, Log, Blob-bearing resource + entry, terminal source, current TransferRef, provenance, byte size, and + Quarantine owner; both origins terminate through the exact + `transfer.quarantined` Event; only + `quarantine.recorded + (HELD|DISPOSAL_FAILED)` maps to retained + `QUARANTINED`, while recorded-origin `DISPOSED` and failed-origin + `FAILED|DISPOSAL_FAILED|DISPOSED` map only to + `QUARANTINE_TERMINAL_NEGATIVE`; in-progress or unlisted pairs and every + 4,097th phase, deficiency, member, or Blob value fail before capture; +46. every output hold receives the immutable checked release schedule, + duration zero releases only after the Job terminal event, positive + duration waits for trusted due time, overflow fails closed, and crash or + clock uncertainty leaves an extra hold; input-owner terminal, orphan-abort, + and output-deadline fixtures each derive the one EHR from the hold ID, + activation rejects any pre-existing EHR, owner/deadline release and + observation are idempotent, and orphan release is idempotent with no + fabricated execution observation; none removes policy, legal, canonical, + or other-root protection or deletes bytes; +47. Start validates a current Phase 3 Chronicle Head with + `event_count < U63_MAX` under the outer gate before Job document, ESM, + Reference Set, hold, or execution Event durability, freezes the exact + admission evidence, and rejects exhausted, unavailable, invalid, or + conflicting retries without side effects; and +48. an `ATTEMPT_OUTPUT` storage entry accepts only the current leased + Attempt's exact INGEST request, staging handle, fence, backend, + TransferRef, terminal Event, Blob, and provenance; deduplication may reuse + verified bytes but never an old Replica creator, transfer, or provenance + as current-attempt evidence. + +The reference vertical slice must publish its highest realized assurance only +for the exact tested backend, Host, profile, conformance suite, and +configuration Sigils. The `0.4` release must demonstrate expiration, +cancellation, duplicate delivery, stale-result rejection, retry, bounded logs, +assurance failure, journal replay, and restart recovery end to end. diff --git a/docs/en/rfcs/RFC-0013-artifact-storage-model.md b/docs/en/rfcs/RFC-0013-artifact-storage-model.md new file mode 100644 index 0000000..0056793 --- /dev/null +++ b/docs/en/rfcs/RFC-0013-artifact-storage-model.md @@ -0,0 +1,4613 @@ +--- +title: "RFC-0013: Artifact Storage Model" +document_id: BW-RFC-0013 +version: 0.1 +status: draft +owner: unassigned +date: 2026-07-31 +language: en +canonical: true +--- + +# RFC-0013: Artifact Storage Model + +## Status + +This draft defines the Phase 3 storage boundary used by Sanctum execution. It +builds on RFC-0011 and consumes the Job, Attempt, Lease, and fencing identities +defined by RFC-0012. It does not authorize automatic Artifact registration, +reinterpret an accepted `artifact/1.0` record, or claim a production Artifact +Registry. It does add a versioned Alpha safety restriction for new +`artifact/1.0` registrations so that canonical v1 paths cannot alias managed +storage. + +Phase 3 must publish closed executable Schemas, fixtures, a local reference +backend, and conformance evidence before this model can be accepted. Remote +backends and production retention automation remain Phase 4 work. + +## Problem + +Phase 2 records a canonical `artifact/1.0` only after Athanor verifies a local +root-contained file against its SHA-256 Sigil. Current validation resolves the +URI under the project root and also accepts an absolute URI whose resolved +target remains inside that root. That record gives the +scientific object a stable ID, a Program, a kind, a producing object, input +lineage, a registration time, and a Receipt. It does not define a storage +service, a physical object lifecycle, replication, transfer, quarantine, +retention, or garbage collection. + +Phase 3 introduces execution outputs that may be partial, duplicated, late, +hostile, too large, corrupt in transit, or produced after a Lease is fenced. +It must also materialize immutable inputs into a Crucible without exposing +canonical or operational control-plane storage to the Worker. Treating every +path, object-store key, cache entry, or Worker claim as an Artifact would erase +the distinction between observed bytes, durable storage, and scientific +acceptance. + +The storage model therefore needs to answer: + +1. what identity belongs to the logical Artifact and what identity belongs to + its bytes; +2. when transferred bytes become an eligible Blob and a usable Replica; +3. how imports, exports, materializations, retries, and crashes retain + end-to-end integrity and provenance; +4. which storage state is operational and which state is canonical; +5. how retention and garbage collection avoid deleting live or scientifically + referenced material; and +6. what a backend must prove before Benchwork relies on it. + +## Decision + +Benchwork separates a canonical logical **Artifact** from an immutable +content-identified **Blob** and from each physical **Replica** of that Blob. + +```text +logical Artifact (Chronicle, Athanor authority) + | + | binds one byte Sigil + v + Blob (immutable byte identity) + / | \ + v v v + Replica A Replica B unavailable + local remote identity only +``` + +The relationships are: + +- an `artifact/1.0` is a canonical research object; +- a Blob is one finite, opaque byte string identified by its byte-level Sigil; +- a Replica is one backend's physical realization of exactly one Blob; +- multiple logical Artifacts may bind the same Blob; +- one Blob may have zero, one, or many Replicas; +- a Blob or Replica may exist without any canonical Artifact; and +- storing, copying, exporting, deleting, or restoring bytes never creates, + updates, or deletes a canonical Artifact. + +A materialized file, extracted tree, download stream, staging object, log +capture, or Crucible output is not a Blob or Artifact merely because it exists. +It becomes an eligible Blob only through the verified commit protocol in this +RFC. It becomes a canonical Artifact only through an explicit Athanor +transition and Receipt. + +Phase 3 adds independent storage contracts. The exact contract set owned and +required by this RFC is: + +- `artifact-blob/1.0`; +- `artifact-replica/1.0`; +- `artifact-transfer/1.0`; +- `artifact-transfer-attempt/1.0`; +- `artifact-provenance/1.0`; +- `artifact-materialization/1.0`; +- `artifact-storage-backend/1.0`; +- `artifact-retention-policy/1.0`; +- `artifact-storage-reference-set/1.0`; +- `artifact-storage-reference-intent/1.0`; +- `artifact-storage-legacy-protection/1.0`; +- `artifact-gc-plan/1.0`; +- `artifact-storage-disposition/1.0`; +- `artifact-storage-journal-event/1.0`; +- `artifact-storage-journal-head/1.0`; +- `artifact-storage-state/1.0`; +- `artifact-storage-tail-evidence/1.0`; +- `artifact-storage-recovery-marker/1.0`; and +- `artifact-storage-doctor-report/1.0`. + +Each conventional filename replaces `/` with `-` and appends `.json`; for +example, `artifact-storage-journal-event/1.0` is +`artifact-storage-journal-event-1.0.json`. Requiring another storage contract +or changing one of these meanings requires a new RFC or contract version; an +implementation cannot satisfy this version with an unnamed private record. + +These are operational contracts. None is a replacement, extension, or new +interpretation of `artifact/1.0`. Their exact records are stored in a durable +Storage Journal and catalog, not in Chronicle. Accepted research state may +refer to a verified content identity through an explicitly versioned future +canonical contract, but storage records themselves have no scientific +authority. + +The Phase 3 local reference runtime uses one built-in local backend whose +entire managed namespace is `.benchwork/storage/`. Its versioned internal +layout contains only coordinator-owned journal, state-cache, Blob, staging, +quarantine, lock, and recovery material. A path under that namespace is never +a v1 Artifact materialization, and a backend locator or backend URI is never +made acceptable by spelling it as a project-relative path. + +After this RFC's implementation is enabled, Athanor rejects every new +`artifact/1.0` registration whose normalized project-root-relative lexical +location or resolved target is equal to or below `.benchwork/storage/`. It +still verifies ordinary root-contained v1 locations outside that namespace. +Before the managed store +is initialized in an upgraded project, migration preflight must find every +already accepted v1 Artifact whose recorded URI aliases the reserved +namespace, verify its existing bytes and Receipt, copy the bytes into a normal +managed Replica, and append a legacy-protection record. The recorded v1 path +remains in place as an immutable compatibility anchor, is excluded from +backend key allocation, and can never be selected by GC or an authorized +storage disposition. An unresolved, corrupt, or internal-layout-colliding +legacy reference blocks managed-store activation rather than being moved, +deleted, or guessed. + +The local reference runtime retains committed and quarantined Attempt bytes by +default and exposes only explicit, auditable cleanup. A production local +Artifact Registry, automated policy retention, remote Replica adapters, and +dataset storage policy are Phase 4 capabilities. + +## Terminology + +| Term | Meaning | +| --- | --- | +| **Artifact** | A logical, canonical research object accepted by Athanor. In the existing contract it is an immutable `artifact/1.0` projection with an `AR-` ID and Chronicle Receipt. | +| **Blob** | One finite sequence of bytes whose primary identity is a byte-level Sigil. A Blob has no Program, scientific kind, producer, or acceptance status by itself. | +| **Replica** | One immutable physical realization of a Blob in one declared backend namespace, bound to backend object identity and generation evidence. | +| **Staging object** | Incomplete, uncommitted bytes in a transfer-scoped namespace. It is never an eligible Blob or Replica. | +| **Transfer** | One bounded operational attempt to ingest, copy, verify, export, or materialize bytes. Retrying creates a new transfer-attempt identity. | +| **Import** | A verified transfer from an external, local, or Attempt-scoped source into storage. Import is not Artifact registration. | +| **Export** | A verified transfer from a selected Blob to an authorized destination. Export is not scientific acceptance or Patch promotion. | +| **Materialization** | A bounded export of exact Blob bytes into a Sanctum, Crucible, or project-relative destination for a declared purpose. It is an operational view, not a new Replica unless explicitly committed as one. | +| **Storage Journal** | Durable operational history for transfers, Replica observations, provenance, retention, quarantine, and garbage collection. It has no canonical research authority. | +| **Quarantine** | An isolated operational namespace and state for partial, corrupt, mismatched, stale, unsafe, or unverifiable bytes. | +| **Backend profile** | A closed declaration of one storage adapter's identity, version, consistency, durability, verification, conditional-operation, and deletion capabilities. | +| **Locator** | Opaque backend-specific information used by the storage coordinator to reach a Replica. It is not a content identity and is not exposed as general Worker filesystem or object-store authority. | +| **Legacy v1 protection** | Operational hold binding an already accepted v1 Artifact, Receipt, recorded URI, verified bytes, and managed copy so reservation of `.benchwork/storage/` cannot invalidate or delete the historical path. | + +An unavailable Blob is still a meaningful content identity recorded in +history, but Benchwork must not claim that its bytes are retrievable. A +canonical Artifact can likewise remain canonical while its recorded +`artifact/1.0` location is missing or corrupt; that is an integrity failure, +not permission to rewrite the Artifact. + +## Content identity + +### Byte identity + +Phase 3 Blob identity uses the existing Sigil form: + +```text +sha256:<64 lowercase hexadecimal characters> +``` + +The digest is SHA-256 over the exact logical byte sequence from offset zero +through the declared final byte. No path, file name, media type, timestamp, +POSIX mode, backend key, compression setting, encryption envelope, producer, +or provenance field participates in that identity. `size_bytes` is mandatory +verification metadata and must agree for every observation of a Sigil, but it +does not change the accepted `artifact/1.0` identity rule. + +The Phase 3 profile accepts only `sha256`. Unknown algorithms and malformed or +non-canonical encodings fail closed. A future algorithm transition requires a +new versioned contract that records both identities and explicit migration +evidence; an implementation may not silently replace or reinterpret an +existing Sigil. + +Content identity is calculated over logical bytes exposed to Benchwork. +Backend-transparent encryption, compression, or chunking may change the stored +representation, but the backend must reconstruct and stream the identical +logical bytes for verification. A Benchwork-visible compression, newline +normalization, archive rewrite, serialization change, or other transformation +creates a new Blob with a new Sigil and derived provenance. + +All managed-store hashing is incremental and streaming. Import, export, +materialization, readback, recovery, and deep Doctor checks update SHA-256 and +an overflow-checked byte counter through a bounded buffer; they do not load an +unbounded payload into memory. A source hash and a destination readback hash +are independent passes. The Phase 3 resume profile recomputes SHA-256 over the +complete final logical byte stream before commit; serialized hash state, +per-chunk success, or a backend checksum cannot substitute for that final +pass. The same bounded streaming implementation is used when the upgraded +Athanor verifies a new v1 Artifact location, without changing what its Sigil +means. + +### Files, trees, and structured content + +A Blob is always bytes, never an implicitly walked directory. The storage +layer treats every structured payload, including a Patch Bundle, as opaque +bytes. It computes identity and enforces storage policy but does not interpret +patch operations, validate scientific meaning, or discover child references +by opening the Blob. + +A directory or multi-file result may be represented by a higher-layer closed, +versioned bundle manifest whose canonical bytes are themselves a Blob. The +higher-layer validator, not the storage backend, validates each normalized +relative path, entry type, byte Sigil, and size and emits an explicit +`artifact-storage-reference-set/1.0` when member bytes require transitive +retention. That validator must reject: + +- absolute, empty, dot, parent, duplicate, case-colliding, or + normalization-colliding paths; +- symlinks, hard links, devices, sockets, FIFOs, and other special entries + unless a later explicit bundle version defines safe semantics; +- undeclared entries and mutable external references; and +- unbounded file count, path length, per-entry size, or aggregate size. + +An archive, manifest, or Patch Bundle remains opaque to the storage layer. +Storage verification does not make parsing or extraction safe. Parsing or +extraction requires a separate bounded validator and cannot change the source +Blob's identity. Missing, unknown, or invalid reference metadata fails GC +closed; the storage layer never guesses relationships from filenames, media +types, or digest-looking strings inside bytes. + +### Equality, deduplication, and collision response + +Equal verified Sigils identify the same Blob under the SHA-256 security +assumption. Deduplication may reuse an already verified Replica, but it must: + +- compare the expected and observed byte count; +- preserve every distinct import and provenance observation; +- use conditional create rather than overwrite; +- verify the existing Replica before relying on it when its integrity is stale + or uncertain; and +- never merge logical Artifact identity, Program membership, retention policy, + or producer lineage merely because the bytes match. + +Any evidence that one Sigil names different sizes or different byte sequences +is a suspected collision or integrity incident. Benchwork quarantines the new +input, marks all affected Replica claims ineligible, disables deduplication for +that identity, and requires explicit investigation. It never selects one +version by path, timestamp, or backend precedence. + +## Operational and canonical boundary + +Chronicle and the Storage Journal have different authorities. + +| State | Classification | Transition authority | +| --- | --- | --- | +| `artifact/1.0`, its producer and input lineage, registration time, status, and Receipt | canonical research state | Athanor and Chronicle | +| Candidate Artifact descriptor, execution output, patch, captured log, or candidate Run attachment | Proposal | Worker or Executor until an explicit Athanor transition | +| Blob identity record, Replica state, backend locator, generation, availability, integrity observation, transfer, materialization, quarantine, retention, and GC state | durable operational storage state | Storage coordinator and verified backend operations | +| Staging bytes, temporary export files, transfer buffers, and Crucible paths | mutable or incomplete execution material | Enforcement backend and storage coordinator | + +The Storage Journal is authoritative for what the storage subsystem attempted +and last verified. It is not authoritative for scientific meaning. Chronicle +is authoritative for which logical Artifacts were accepted, but it is not a +live Replica catalog. + +A successful Job or Transfer may create a verified Blob and Replica. It does +not create an Artifact, Run, Assessment, Decision, or Seal. Conversely, +`artifact.registered` does not prove indefinite Replica availability, +replication count, backend durability, authenticity, absence of malware, or +scientific validity. + +Storage state changes do not append Chronicle events merely because they +occurred. An explicit Athanor transition may later accept a bounded Proposal +that includes Blob, Transfer, Job, Attempt, and verification identities as +provenance. The corresponding Receipt records that scientific transition, not +ownership of the backend. + +Failures are append-only history. A failed, cancelled, fenced, corrupt, or +quarantined transfer is never rewritten as successful. Replica deletion leaves +a tombstoned Replica record, Blob identity, transfer history, provenance, and +GC decision. Restoration creates a new Replica record or new verification +observation; it does not erase the outage or deletion. + +## Managed namespace and Storage Journal + +### Local layout + +The built-in backend reserves this complete project-relative namespace: + +```text +.benchwork/storage/ + format.json + journal.frames + journal-head.json + state.json + records/ + blobs/ + staging/ + quarantine/ + locks/ + recovery/ +``` + +`format.json` pins the storage-format version, project identity, backend +profile and Sigil, and local conformance-suite Sigil. `journal.frames` is the +authority for operational history. `journal-head.json` and `state.json` are +replaceable verified caches. `records/` contains immutable, content-addressed +closed control records made durable before the Event that refers to them. The +remaining directories contain only material owned through journal records, +except for the one format-defined interrupted-tail marker and its immutable +evidence under `recovery/`; those have only the pre-journal authority defined +by the exact recovery protocol below. A name or digest-looking path in this +layout is not authoritative without a valid replayed record or that one +validated marker. + +The only permitted pre-existing exceptions are historical v1 compatibility +anchors discovered and registered during migration preflight. They remain at +their recorded paths, are explicit permanent exclusions in State, and are +never treated as layout members or backend objects. An anchor that occupies or +aliases a required internal path blocks activation. + +Only the storage coordinator writes this namespace. Workers, MCP callers, +Artifact producers, and native Host file operations receive neither raw paths +nor general read, write, list, or delete authority. A2 makes the complete +namespace unreachable. A1 does not claim hostile-code containment, but the +coordinator still withholds paths and handles and uses a constructed +environment. + +The cross-journal canonical-reference gate is deliberately outside this +not-yet-initialized namespace at the fixed project-kernel path +`.benchwork/locks/canonical-reference.lock`. Athanor and storage open it +descriptor-relative with no-follow validation. Its external location lets the +same gate linearize v1 Artifact registration, migration preflight, store +activation, later canonical-reference commits, and GC before +`.benchwork/storage/` exists. It is not a Blob, Replica, control record, or +source of replay authority. + +Initialization is a transaction. Read-only preflight first verifies Chronicle, +all v1 Artifact locations, namespace conflicts, free capacity, and the exact +legacy-protection plan. It then durably installs `format.json`, an empty +`journal.frames`, and the closed zero-count Head before any frame append. A +crash before all three exist leaves no initialized store and is restartable +only after repeating preflight; a mixed or conflicting set fails closed. The +first journal event creates an `INITIALIZING` store. Its `tail_recovery` is +null in the ordinary path; an interrupted first frame is repaired only by the +empty-prefix `storage.initialized` marker branch below, which repeats preflight +and records the non-null `TailRecovery`. Each accepted historical +v1 alias is copied through an internal Transfer whose closed purpose is +`LEGACY_V1_PROTECTION`. This is the only Transfer purpose legal while +`INITIALIZING` and it uses the ordinary reservation, staging, commit-intent, +readback, and recovery protocol. The alias then receives a +`legacy_v1.protection_registered` event after its compatibility anchor and +managed copy verify. `storage.activation_completed` changes the store to +`ACTIVE` only when every required protection exists. A crash or failure leaves +the store unable to import, materialize, delete, or run non-migration +Transfers until recovery completes; it never treats a partially initialized +layout as active. + +Immediately after `storage.initialized`, initialization registers exactly two +immutable policies before any protection Transfer. The first is the +migration-protection policy. Its scope is the project, minimum Replica count +is one, retain-until is null, `automatic_gc_allowed` is false, and its system +authorization forbids both anchor disposition and managed-copy GC. Every +`LegacyExclusion.policy_id` cites it. + +The second is the neutral execution-root hold carrier. Its exact +`artifact-retention-policy/1.0` fields are: + +```text +policy_id = SP-EXECUTION-ROOT-HOLD-V1 +scope = {kind: PROJECT, project_id: } +minimum_replica_count = 0 +required_backends = [] +required_failure_domains = [] +maximum_integrity_age_seconds = null +deletion_grace_seconds = 0 +retain_until = null +automatic_gc_allowed = true +authorization_sigil = + Sigil(["execution-root-hold-policy-authorization/1.0", + project_id, + storage_format_version, + backend_profile_sigil, + conformance_suite_sigil]) +``` + +`registered_at` is fixed by its one registration and `record_sigil` is its +ordinary self-Sigil. The policy itself imposes no minimum physical retention; +an `ACTIVE` execution hold supplies protection. These two exact registrations +are the only `retention.policy_registered` branches legal in +`INITIALIZING`. Activation requires both records to replay validly, and a +missing, changed, or project-mismatched execution policy blocks every +non-empty RFC-0012 ESM hold. + +The upgraded `artifact.registered` transition acquires this outer gate before +validating its v1 location and retains it through its Chronicle commit. +Initialization acquires the gate before its first Chronicle replay and retains +it until `storage.activation_completed` is durable or activation aborts. It +replays Chronicle again under the gate immediately before installing +`format.json`; the discovered historical-alias set and Head must equal the +preflight plan. A concurrent new registration therefore commits entirely +before that snapshot and is protected, or waits until the +reserved-namespace rule is active. Upgrade requires exclusive use of a +gate-aware Athanor build; detecting an incompatible or ungated writer blocks +activation rather than racing it. + +### Journal contracts and identities + +The journal family consists of: + +- `artifact-storage-journal-event/1.0`, a closed hash-chained event; +- `artifact-storage-journal-head/1.0`, a replaceable cache of journal ID, + event count, last sequence, last event Sigil, and projected state Sigil; + and +- `artifact-storage-state/1.0`, the deterministic replay projection. + +All three are closed JSON Schemas with `additionalProperties: false`, bounded +strings and collections, canonical JSON encoding, and lowercase SHA-256 +Sigils. They reject duplicate object keys, non-finite numbers, out-of-range +integers, unknown enum values, and unknown Schema versions. + +Operational identifiers are opaque. The Phase 3 patterns are `SJ-` for a +Storage Journal, `SE-` for a journal event, `ST-` for a Transfer request, +`SA-` for a transfer attempt, `SR-` for a Replica, `SM-` for a +Materialization, `SQ-` for a Quarantine record, `SP-` for a retention policy, +`SH-` for a hold, `RS-` for a reference set, `RI-` for a canonical-reference +intent, `SG-` for a GC plan, and `SD-` for a disposition. They are never +derived from a path, PID, time, queue position, or scientific Run ID. Blob +identity remains its byte Sigil rather than gaining an arbitrary operational +ID. The imported RFC-0012 `ESM-ID` uses the same bounds and the `ESM-` +prefix; its deterministic derivation is owned by +`execution-storage-root-manifest/1.0`. + +Every event has exactly this common envelope plus the selected closed payload +branch: + +- `schema_version`, `journal_id`, `event_id`, one-based contiguous `sequence`, and + closed `event_type`; +- `coordinator_id` and monotonically increasing local `epoch`; +- `recorded_at` and nullable bounded `observed_at`; +- sorted `entity_revisions`, each containing entity type and ID, exact + preceding revision or null, and exact next revision; +- nullable `causation_event_id` and `idempotency_key_sigil`; +- sorted closed `quota_effects`, present and empty when the event has no quota + consequence; +- one closed type-specific `payload`; +- `previous_event_sigil`, with null only at sequence one; and +- `event_sigil` over canonical JSON with that field omitted. + +`recorded_at` is non-decreasing protocol time. On a detected rollback, the +clock-uncertain event retains the prior protocol time in its envelope and +places both observed clock values in its payload; it never backdates the +chain. + +The Head contains exactly its Schema version, journal ID, storage-format +version, event count, last sequence, last event Sigil, +`committed_byte_length`, current epoch, state Sigil, and update time. For an +initialized journal, `event_count == last_sequence`; the only empty Head is the +pre-initialization value with zero count, zero sequence, zero committed byte +length, and null last-event and state Sigils. `committed_byte_length` is the +exclusive byte offset immediately after the last complete committed frame. +The `state_sigil` identifies the canonical bytes of the matching State. Where +this RFC names a Head Sigil, it means SHA-256 over the complete canonical Head +bytes; it is an observation identity, not an added Head field. +Head journal ID is `SJ-ID`, storage-format version is `Opaque`, counts, +sequence, committed length, and epoch are `U63`, event and State Sigils are +`Sigil|null` under the empty-Head rule, and update time is `Timestamp`. + +The State contains exactly its Schema version, journal ID, storage-format version, +project identity, backend-profile identity and Sigil, conformance-profile +identity and suite Sigil, store status, active recovery identity and origin +status, clock status and anchor, current epoch, applied event count, last event +Sigil, and these sorted closed projection +collections: +`recoveries`, `blobs`, `replicas`, `transfer_requests`, `transfer_attempts`, +`materializations`, `quarantines`, `provenance`, `retention_policies`, +`holds`, `reference_sets`, `legacy_v1_protections`, `gc_plans`, +`canonical_reference_intents`, `dispositions`, `quota_reservations`, +`open_intents`, and `incidents`. It also contains derived availability and +quota counters and its own `state_sigil` computed with that field omitted. +Every projection carries its identity, revision, state, and the minimal typed +relationships required to validate it. Collections are sorted by identity; +replay cannot depend on filesystem enumeration order. +State journal ID is `SJ-ID`; storage format, project, backend-profile and +conformance-profile IDs are `Opaque`; their suite/profile identities are +`Sigil`; active recovery ID is `Opaque|null`; recovery origin is +`INITIALIZING|ACTIVE|null`; clock status is `TRUSTED|UNCERTAIN`; clock anchor +is `ClockRef`; epoch and applied count are `U63`; last-event Sigil is +`Sigil|null`; and `state_sigil` is `Sigil`. + +Referenced immutable control records and byte payloads are durable before the +event that makes them visible. One event may atomically update related +projections, such as committing a Transfer while creating or selecting its +Blob and Replica. Replay accepts all those revisions or none. + +### Closed Schema construction rules + +The prose names below are normative JSON Schema aliases. `Sigil` is a string +matching `^sha256:[a-f0-9]{64}$`. `U63` is an integer in +`0..9223372036854775807`. `PositiveU63` starts at one. `U64` is an integer +in `0..18446744073709551615` and is used only where an imported RFC-0012 or +RFC-0014 counter requires that exact domain; parsers that cannot preserve it +exactly fail closed. `Timestamp` is a +UTC RFC 3339 string ending in `Z`, with no leap second and at most six +fractional digits. `Opaque` is printable UTF-8, length `1..256`, without NUL +or control characters. An operational ID is ASCII, length `3..128`, begins +with its assigned prefix, and otherwise matches +`[A-Za-z0-9][A-Za-z0-9._:-]*`. Collections have at most 4096 members unless +their owning policy declares a smaller bound. Every set is encoded as a +duplicate-free array in ascending Unicode-code-point order; tuple sets use +lexicographic field order stated here. Nullable fields are present with JSON +`null`; omission is not an alternative. + +`VerificationMethod` is the closed string enum +`FULL_READBACK_SHA256` or `CONFORMANCE_END_TO_END`. +`ProvenanceRelation` is the closed string enum `CAPTURED`, `IMPORTED`, +`COPIED`, `DERIVED`, `EXPORTED`, `MATERIALIZED`, `VERIFIED`, or `DELETED`. + +These reusable objects are exact and closed: + +| Alias | Exact fields | +| --- | --- | +| `ControlRef` | `schema_version: Opaque`, `record_id: Opaque`, `record_sigil: Sigil` | +| `EventRef` | `journal_id: SJ-ID`, `event_id: SE-ID`, `sequence: PositiveU63`, `event_sigil: Sigil` | +| `EntityRevision` | `entity_type: EntityType`, `entity_id: Opaque`, `previous_revision: U63\|null`, `next_revision: PositiveU63` | +| `ChronicleHeadRef` | Phase 3-admissible `chronicle-head/1.1` value with the exact three-field closed shape `schema_version: "chronicle-head/1.1"`, `event_count: U63`, `terminal_receipt_sigil: Sigil\|null`; the existing Chronicle Schema has no upper bound on its integer, so `U63` is an additional RFC-0013 admission bound rather than a claim that the two integer domains are identical | +| `ChronicleCommitRef` | `event_id: Opaque`, `event_body_sigil: Sigil`, `receipt_id: Opaque`, `receipt_sigil: Sigil`, `head: ChronicleHeadRef` | +| `ExternalEventRef` | `protocol_id: Opaque`, `protocol_version: Opaque`, `journal_id: Opaque`, `event_id: Opaque`, `sequence: U64` in `1..18446744073709551615`, `event_type: Opaque`, `event_sigil: Sigil` | +| `CanonicalAbortAuthority` | `kind: "HEAD_SUPERSEDED_WITHOUT_BOUND_EVENT"`, `reference_intent_id: RI-ID`, `reference_intent_record_sigil: Sigil`, `transition_request_id: Opaque`, `transition_request_sigil: Sigil`, `expected_chronicle_head: ChronicleHeadRef`, `verified_chronicle_head: ChronicleHeadRef`, `absence_evidence_sigil: Sigil`, `authority_sigil: Sigil` | +| `Reason` | `code: ReasonCode`, `evidence_sigils: [Sigil]` | +| `QuotaClaim` | `quota_class: QuotaClass`, `byte_count: U63`, `object_count: U63`, `inode_count: U63`, `stream_count: U63`, `journal_bytes: U63`, `control_record_bytes: U63` | +| `OperationCapacityPlan` | `allowed_event_types: [StorageEventType]`, `max_event_frame_count: PositiveU63`, `max_control_record_count: U63`, `max_recovery_evidence_count: U63`, `max_event_frame_bytes: PositiveU63`, `max_control_record_bytes: PositiveU63`, `max_recovery_evidence_bytes: PositiveU63` | +| `ReservationRef` | `reservation_id: Opaque`, `claims: [QuotaClaim]`, `capacity_plan: OperationCapacityPlan`, `expires_at: Timestamp\|null`, `created_clock: ClockRef`, `remaining_micros_at_creation: U63\|null` | +| `BackendObjectRef` | `backend_id: Opaque`, `object_identity_sigil: Sigil`, `locator_sigil: Sigil`, `generation: Opaque`, `size_bytes: U63`, `blob_sigil: Sigil\|null` | +| `ExecutionFenceRef` | `execution_journal_id: Opaque`, `executor_epoch: U64`, `job_id: Opaque`, `attempt_id: Opaque`, `lease_id: Opaque`, `fencing_generation: U64`, `execution_event_sigil: Sigil`, `job_fence_floor: U64`, `tombstone_present: boolean` | +| `VerificationRef` | `method: VerificationMethod`, `evidence_sigil: Sigil`, `verified_at: Timestamp`, `next_due_at: Timestamp\|null` | +| `ClockRef` | `utc: Timestamp`, `monotonic_anchor_id: Opaque`, `monotonic_ticks: U63`, `monotonic_frequency_hz: PositiveU63`, `uncertainty_micros: U63`, `observation_sigil: Sigil` | +| `QuotaSnapshot` | `quota_class: QuotaClass`, `dimension: QuotaDimension`, `limit: U63`, `used: U63`, `reserved: U63`, `pressure_state: "CLEAR"\|"PRESSURED"` | +| `QuotaEffect` | `INITIALIZE {kind, snapshot: QuotaSnapshot}`; `RESERVE {kind, reservation: ReservationRef, owner_kind: QuotaOwnerKind, owner_id: Opaque, purpose: QuotaPurpose}`; `SETTLE {kind, reservation_id: Opaque, state_after: "RETAINED"\|"SETTLED", consumed_claims: [QuotaClaim], released_claims: [QuotaClaim], remaining_claims: [QuotaClaim], usage_additions: [QuotaClaim], retained_for_event_types: [StorageEventType]}`; `USAGE_REMOVED {kind, removal: QuotaClaim, owner_kind: QuotaOwnerKind, owner_id: Opaque}`; or `PRESSURE {kind, from_state: "CLEAR"\|"PRESSURED", to_state: "CLEAR"\|"PRESSURED", snapshot: QuotaSnapshot}` | +| `TailRecovery` | `prior_head_sigil: Sigil`, `old_committed_byte_length: U63`, `last_complete_byte_length: U63`, `discarded_suffix_size: PositiveU63`, `discarded_suffix_sigil: Sigil`, `evidence_record_sigil: Sigil` | + +`HoldReleaseAuthority` is the exact closed union: + +```text +EXECUTION_ROOT: + kind = EXECUTION_ROOT + authorization: ControlRef + +PATCH_OPERATIONAL_ROOT: + kind = PATCH_OPERATIONAL_ROOT + protocol_id = benchwork.patch-promotion + protocol_version = 1.0 + operational_journal_id: Opaque + operational_root_id: Opaque + operational_root_sigil: Sigil + operational_root_plan: ControlRef + reference_set: ReferenceSetRef + hold_set_event: EventRef + activation_event: ExternalEventRef + inactivation_event: ExternalEventRef + release_condition: + NON_PARTIAL_OUTCOME_OBSERVED | + RESOLVED_RECOVERY_OBSERVED | + ABANDONED_WITH_DISPOSITION | + JOURNAL_RECOVERY_COMPLETED + terminal_authority: ControlRef + release_evidence: ControlRef + validator_id = benchwork.patch-operational-root-release + validator_version = 1.1 + validator_sigil: Sigil + authority_sigil: Sigil +``` + +The execution branch's `ControlRef` must name +`execution-root-hold-release-authorization/1.0`; its record ID and Sigil are +validated below. The patch branch's `authority_sigil` is the self-Sigil over +every other branch member. Its external Event refs all have +`protocol_id: benchwork.patch-promotion` and `protocol_version: 1.0`; RFC-0014 +narrows their IDs, event types, and release-condition matrix. +`operational_root_plan` must name +`patch-operational-root-plan/1.0`; `release_evidence` must name +`patch-operational-root-release-evidence/1.0`. Both are complete immutable +records resolved by ID and Sigil, never arbitrary audit pointers. No third +branch or untyped authority map is admitted. + +`QuotaClass` is exactly `JOURNAL`, `CONTROL_RECORD`, `STAGING`, +`QUARANTINE`, `COMMITTED`, `MATERIALIZATION`, `STREAM`, or `INODE`. +`QuotaDimension` is exactly `BYTE`, `OBJECT`, `INODE`, `STREAM`, +`JOURNAL_BYTE`, or `CONTROL_RECORD_BYTE`. The only legal +class/dimension pairs are `JOURNAL/JOURNAL_BYTE`, +`CONTROL_RECORD/CONTROL_RECORD_BYTE`, `STREAM/STREAM`, `INODE/INODE`, and +`BYTE` or `OBJECT` for each of `STAGING`, `QUARANTINE`, `COMMITTED`, and +`MATERIALIZATION`. A `QuotaClaim` has at least one non-zero component and has +non-zero values only in the dimensions legal for its class. Claim arrays are +unique, contain at most one member for each quota class, and are sorted by +quota class in ascending Unicode-code-point order. A quota-counter projection +identity is exactly `quota-counter::`. The +dimension is mandatory even for a class that has only one legal dimension; a +class-only quota entity ID is invalid. + +`QuotaOwnerKind` is exactly `TRANSFER_ATTEMPT`, `MATERIALIZATION`, +`QUARANTINE`, `GC_TARGET`, `DISPOSITION`, `CANONICAL_REFERENCE`, +`LEGACY_PROTECTION`, or `RECOVERY`. `QuotaPurpose` is exactly +`PAYLOAD_LIFECYCLE`, `TERMINAL_OUTCOME`, `QUARANTINE_MOVE`, +`DELETION_OUTCOME`, `CANONICAL_PIN_LIFECYCLE`, or `RECOVERY_EVIDENCE`. +`StorageEventType` is the exact event enum below. `quota_effects` are sorted by +`(kind ordinal, reservation_id or snapshot class/dimension or removal +class/dimension)`, where the kind order is `INITIALIZE`, `RESERVE`, `SETTLE`, +`USAGE_REMOVED`, `PRESSURE` and strings compare by Unicode code point. No +event may address the same reservation or counter twice. + +An `OperationCapacityPlan.allowed_event_types` array is non-empty, sorted in +Storage-event enum order, and contains only events in the owning operation's +closed capacity-source row below. Its three per-item maxima use the same +`56..8388664`, `1..8388664`, and `1..8388664` bounds as the corresponding +system maxima. Checked, overflow-free arithmetic defines: + +```text +required_journal_bytes = + max_event_frame_count * max_event_frame_bytes +required_control_bytes = + max_control_record_count * max_control_record_bytes + + max_recovery_evidence_count * max_recovery_evidence_bytes +``` + +The Reservation's total `JOURNAL/JOURNAL_BYTE` claim is at least +`required_journal_bytes`, and its total +`CONTROL_RECORD/CONTROL_RECORD_BYTE` claim is at least +`required_control_bytes`. Each maximum is immutable for the Reservation. An +event or record whose canonical durable length exceeds its per-item maximum, or +an operation that reaches any count maximum before a legal terminal outcome, +fails before another side effect and retains its existing Reservation for +Recovery. `remaining_micros_at_creation` is null exactly when `expires_at` is +null; otherwise it is the non-negative microsecond difference from +`created_clock.utc` to `expires_at`, rejected on `U63` overflow. It is creation +evidence, not a monotonic value reusable after restart. + +A `RESERVE` adds every claim to the matching `reserved` counters and is legal +only when every resulting counter is within its limit. A `SETTLE` partitions +the prior remaining claims component-wise into `consumed_claims`, +`released_claims`, and `remaining_claims`; it cannot create or reclassify a +claim. `usage_additions` equals `consumed_claims` byte-for-byte and moves +those amounts from `reserved` to `used`. `released_claims` only subtract from +`reserved`. `RETAINED` requires non-empty remaining claims and a non-empty +exact future-event set; `SETTLED` requires both to be empty. A later +`SETTLE` on a retained reservation is legal only for an event in that set. +`USAGE_REMOVED` is legal only for exact amounts already charged as used and +physically removed by the same authorized disposition, materialization +cleanup, or GC event; it can never create, discover, or reclassify usage. +A `USAGE_REMOVED.removal` has exactly one non-zero component, so removal of a +byte-and-object allocation uses two effects ordered by class/dimension and +produces two distinct quota-counter revisions. +A `PRESSURE` snapshot is the complete counter after the transition, and its +`pressure_state` equals `to_state`. + +The remaining shared `$defs` are also exact and closed: + +| Alias | Exact shape | +| --- | --- | +| `BlobRef` | `blob_sigil: Sigil`, `size_bytes: U63` | +| `BackendRef` | `backend_id: Opaque`, `backend_profile_version: Opaque`, `backend_profile_sigil: Sigil` | +| `TransferBounds` | `max_bytes: U63`, `max_duration_millis: U63`, `max_file_count: U63\|null`, `max_chunk_count: U63`, `buffer_bytes: PositiveU63` | +| `ExecutionContext` | `NONE {kind: "NONE"}`; `ATTEMPT {kind: "ATTEMPT", execution_journal_id: Opaque, executor_epoch: U64, job_id: Opaque, attempt_id: Opaque}`; or `LEASED {kind: "LEASED", execution_journal_id: Opaque, executor_epoch: U64, job_id: Opaque, attempt_id: Opaque, lease_id: Opaque, worker_id: Opaque, worker_session_id: Opaque, fence: ExecutionFenceRef}` | +| `SourceDescriptor` | `LOCAL_FILE {kind: "LOCAL_FILE", authorization_scope_sigil: Sigil, lexical_identity_sigil: Sigil, resolved_file_identity_sigil: Sigil}`; `REPLICA {kind: "REPLICA", blob: BlobRef, replica_id: SR-ID, verification_sigil: Sigil}`; `ATTEMPT_OUTPUT {kind: "ATTEMPT_OUTPUT", execution: ExecutionContext, output_handle_id: Opaque}`; `EXTERNAL {kind: "EXTERNAL", source_class: Opaque, sanitized_identity_sigil: Sigil, authorization_sigil: Sigil}`; `STAGING {kind: "STAGING", transfer_attempt_id: SA-ID, object: BackendObjectRef}`; or `LEGACY_ARTIFACT {kind: "LEGACY_ARTIFACT", artifact_id: Opaque, receipt_sigil: Sigil, location_sigil: Sigil}` | +| `DestinationDescriptor` | `MANAGED_BACKEND {kind: "MANAGED_BACKEND", backend: BackendRef}`; `EXPORT {kind: "EXPORT", destination_class: Opaque, destination_identity_sigil: Sigil, authorization_sigil: Sigil}`; or `MATERIALIZATION {kind: "MATERIALIZATION", materialization_id: SM-ID, destination_class: Opaque, destination_identity_sigil: Sigil}` | +| `TransformationRef` | `NONE {kind: "NONE"}` or `DERIVED {kind: "DERIVED", contract_id: Opaque, contract_version: Opaque, implementation_sigil: Sigil, parameters_sigil: Sigil, input_blobs: [BlobRef]}` | +| `TimeSet` | `observed_at: Timestamp\|null`, `started_at: Timestamp`, `committed_at: Timestamp\|null`, `verified_at: Timestamp\|null`, `terminal_at: Timestamp` | +| `CleanupResult` | `state: NOT_REQUIRED\|PENDING\|CLEANED\|FAILED`, `evidence_sigil: Sigil\|null`, `reason: Reason\|null` | +| `TransferCommitIntent` | `intent_id: Opaque`, `provisional_replica_id: SR-ID`, `staging_object: BackendObjectRef`, `target_object: BackendObjectRef`, `computed_blob: BlobRef`, `execution_fence: ExecutionFenceRef\|null`, `reservation: ReservationRef`, `recorded_clock: ClockRef` | +| `ResidualStagingCleanup` | `state: NOT_REQUIRED\|PENDING\|CLEANED\|FAILED\|HELD_FOR_DISPOSITION`, `staging_object: BackendObjectRef\|null`, `evidence_sigil: Sigil\|null`, `reason: Reason\|null` | +| `MaterializationCommitIntent` | `intent_id: Opaque`, `staging_object: BackendObjectRef`, `destination_object: BackendObjectRef`, `expected_blob: BlobRef`, `destination_identity_sigil: Sigil`, `execution_fence: ExecutionFenceRef\|null`, `reservation: ReservationRef`, `recorded_clock: ClockRef` | +| `TransferRef` | `transfer_id: ST-ID`, `transfer_attempt_id: SA-ID`, `request_record_sigil: Sigil`, `attempt_record_sigil: Sigil`, `terminal_event: EventRef` | +| `ExpectedSizeOrBound` | `EXACT {kind, size_bytes: U63}` or `UPPER_BOUND {kind, max_size_bytes: U63}` | +| `PolicyScope` | `PROJECT {kind: "PROJECT", project_id: Opaque}`; `PROGRAM {kind: "PROGRAM", program_id: Opaque}`; `BLOB {kind: "BLOB", blob_sigil: Sigil}`; or `REFERENCE_SET {kind: "REFERENCE_SET", reference_set_id: RS-ID, reference_set_sigil: Sigil}` | +| `ReferenceSetRef` | `reference_set_id: RS-ID`, `reference_set_sigil: Sigil` | +| `ReferenceSource` | `kind: "CANONICAL_OBJECT"\|"OPERATIONAL_CONTROL_RECORD"\|"BLOB_MANIFEST"`, `identity: Opaque`, `schema_version: Opaque`, `sigil: Sigil` | +| `ReferenceExtractor` | `extractor_id: Opaque`, `extractor_version: Opaque`, `extractor_sigil: Sigil` | +| `ReferenceValidation` | `validator_id: Opaque`, `validator_version: Opaque`, `validator_sigil: Sigil`, `source_validation_sigil: Sigil`, `evidence_sigils: [Sigil]` | +| `ReferenceEdge` | `relationship: ReferenceRelationship`, `target_kind: ReferenceTargetKind`, `target_identity: Opaque`, `target_sigil: Sigil` | +| `LegacyExclusion` | `anchor_disposition: "PERMANENTLY_EXCLUDED"`, `managed_copy_gc: "PERMANENTLY_PROTECTED"`, `policy_id: SP-ID`, `authorization_sigil: Sigil` | +| `BackendIsolation` | `scope: "SINGLE_PROJECT"\|"SINGLE_TENANT_PROJECT"`, `namespace_enforcement: "DESCRIPTOR_RELATIVE_NOFOLLOW"\|"ADAPTER_SCOPED"`, `worker_access: "NONE"`, `worker_credentials_exposed: false` | +| `SystemReserveLimit` | `reserve_class: SystemReserveClass`, `max_event_frame_count: PositiveU63`, `max_control_record_count: U63`, `max_recovery_evidence_count: U63`, `max_event_frame_bytes: PositiveU63`, `max_control_record_bytes: PositiveU63`, `max_recovery_evidence_bytes: PositiveU63` | +| `BackendLimits` | `max_object_bytes: U63`, `transfer_bounds: TransferBounds`, `max_concurrent_streams: U63`, `max_inventory_entries: U63`, `max_tail_recovery_retries: PositiveU63`, `system_reserve_limits: [SystemReserveLimit]`, `system_journal_reserve_bytes: PositiveU63`, `system_control_record_reserve_bytes: PositiveU63`, `system_recovery_reserve_bytes: PositiveU63` | +| `BackendConsistency` | `read_after_write: "STRONG"\|"EVENTUAL"`, `list_consistency: "STRONG"\|"EVENTUAL"`, `atomic_visibility: boolean`, `immutable_generation: boolean`, `exact_generation_reads: boolean` | +| `BackendDurability` | `commit_semantics: "FSYNC_FILE_AND_PARENT"\|"REMOTE_DURABLE_ACK"`, `logical_readback: boolean`, `transparent_encryption: boolean`, `transparent_compression: boolean` | +| `BackendRangeResume` | `range_reads: boolean`, `resumable_stage_writes: boolean`, `resume_binding: "UNSUPPORTED"\|"GENERATION_OFFSET_PREFIX_SIGIL"` | +| `BackendConditionalOperations` | `conditional_create: boolean`, `no_overwrite_finalize: boolean`, `exact_generation_stat: boolean`, `exact_generation_delete: boolean` | +| `BackendDeletionCapabilities` | `exact_generation_delete: boolean`, `wildcard_delete: false`, `retention_lock: "NONE"\|"ENFORCED"`, `verification: "POST_DELETE_STAT"\|"RECEIPT_AND_RECONCILIATION"` | +| `BackendFencing` | `mode: "COORDINATOR_PRECOMMIT"\|"BACKEND_PUBLIC_FENCE"`, `attempt_staging_isolated: boolean`, `executor_epoch_checked: boolean`, `job_fence_floor_checked: boolean`, `tombstone_checked: boolean` | +| `BackendConformance` | `profile_id: "LOCAL-PHASE3/1.0"\|"PORTABLE-PHASE4/1.0"`, `suite_version: Opaque`, `suite_sigil: Sigil`, `host_platform_sigil: Sigil`, `evidence_sigil: Sigil` | +| `TraversalBounds` | `max_roots`, `max_nodes`, `max_edges`, `max_depth`, `max_control_record_bytes`, `max_wall_millis`, all `U63` | +| `ExecutionStorageRoot` | `root_kind: "JOB_INPUT"\|"ATTEMPT_INPUT"\|"ATTEMPT_OUTPUT"`, `job_id: Opaque`, `attempt_id: Opaque\|null`, `storage_root_manifest_id: ESM-ID`, `storage_root_manifest_sigil: Sigil`, `reference_set_id: RS-ID`, `reference_set_sigil: Sigil`, `hold_id: SH-ID`, `hold_set_event: EventRef` | +| `ExecutionRootSnapshot` | `execution_journal_id: Opaque`, `execution_event_count: U63`, `execution_last_event_sigil: Sigil\|null`, `roots: [ExecutionStorageRoot]`, `root_set_sigil: Sigil` | +| `GCRootSnapshot` | `chronicle_head: ChronicleHeadRef`, `storage_event: EventRef`, `execution_roots: ExecutionRootSnapshot`, `hold_set_sigil: Sigil`, `legacy_protection_set_sigil: Sigil`, `reference_intent_set_sigil: Sigil`, `reference_set_sigils: [Sigil]`, `policy_set_sigil: Sigil` | +| `ClosureProof` | `root_set_sigil: Sigil`, `extractor_suite_sigil: Sigil`, `bounds: TraversalBounds`, `visited_node_count: U63`, `visited_edge_count: U63`, `cycle_summary_sigil: Sigil`, `reachable_blob_sigils: [Sigil]`, `reachable_replica_ids: [SR-ID]`, `proof_sigil: Sigil` | +| `GCTarget` | `target_id: Opaque`, `replica_id: SR-ID`, `blob: BlobRef`, `backend_object: BackendObjectRef`, `reason_code: ReasonCode`, `expected_remaining_replica_ids: [SR-ID]` | +| `GCTargetState` | `target_id: Opaque`, `state: "PLANNED"\|"SKIPPED"\|"DELETING"\|"DELETED"\|"FAILED"`, `deletion_intent_id: Opaque\|null`, `root_revalidation_sigil: Sigil\|null`, `outcome_reservation: ReservationRef\|null`, `deletion_evidence_sigil: Sigil\|null`, `terminal_reason: Reason\|null`, `last_event_sigil: Sigil` | +| `OpenIntentProjection` | the six exact discriminated branches defined below, with no common untyped fallback | +| `DoctorSubjectKind` | `"JOURNAL"\|"STATE"\|"BACKEND_OBJECT"\|"REPLICA"\|"LEGACY_PROTECTION"\|"QUARANTINE"\|"QUOTA"\|"REFERENCE_SET"\|"HOLD"\|"OPEN_INTENT"\|"RECOVERY_EVIDENCE"` | +| `IncidentSubjectKind` | `"BLOB"\|"REPLICA"\|"TRANSFER_ATTEMPT"\|"MATERIALIZATION"\|"QUARANTINE"\|"BACKEND_OBJECT"\|"JOURNAL"\|"LEGACY_PROTECTION"` | +| `CheckResult` | `check_id: Opaque`, `status: "PASS"\|"WARN"\|"FAIL"\|"INCOMPLETE"`, `subject_kind: DoctorSubjectKind`, `subject_id: Opaque`, `evidence_sigils: [Sigil]`, `reason: Reason\|null` | +| `BackendInventoryEntry` | `backend_object: BackendObjectRef`, `owner_kind: "REPLICA"\|"TRANSFER_ATTEMPT"\|"MATERIALIZATION"\|"QUARANTINE"\|"UNJOURNALED"`, `owner_id: Opaque\|null`, `status: "OWNED"\|"UNJOURNALED"\|"CONFLICTING"\|"INACCESSIBLE"`, `evidence_sigils: [Sigil]` | +| `DoctorBounds` | `max_inventory_entries: U63`, `max_control_records: U63`, `max_rehash_bytes: U63`, `max_wall_millis: U63`, `inventory_truncated: boolean`, `rehash_truncated: boolean` | + +Every union uses its explicitly named `kind`, `state`, `intent_kind`, or +enclosing event discriminator as a required constant discriminator. Fields shown +without an explicit type in a compact field-set table receive their type only +from an explicit binding paragraph in this RFC. A field with neither an +inline type nor one such binding is a Schema defect; it does not default to +`Opaque`. Only a field explicitly written `: Opaque` uses that alias. No +branch admits an untyped parameter map. + +`EntityType` is exactly the entity-type enum in the revision section below. +`ReferenceTargetKind` is exactly `BLOB`, `REPLICA`, `REFERENCE_SET`, +`CANONICAL_OBJECT`, or `OPERATIONAL_CONTROL_RECORD`. +`ReferenceRelationship` is exactly: + +```text +BUNDLE_RETAINS_MEMBER +CANONICAL_BINDS_BLOB +CANONICAL_RETAINS_OBJECT +CONTROL_RETAINS_BLOB +CONTROL_RETAINS_CONTROL +HOLD_PROTECTS_BLOB +JOB_REQUIRES_BLOB +PATCH_RETAINS_BASE +PATCH_RETAINS_POSTIMAGE +REFERENCE_SET_RETAINS_SET +TRANSFER_PINS_REPLICA +``` + +The relationship/source/target matrix is closed: + +| Relationship | Exact `source.kind` | Exact legal `target_kind` | +| --- | --- | --- | +| `BUNDLE_RETAINS_MEMBER` | `BLOB_MANIFEST` | `BLOB` | +| `CANONICAL_BINDS_BLOB` | `CANONICAL_OBJECT` | `BLOB` | +| `CANONICAL_RETAINS_OBJECT` | `CANONICAL_OBJECT` | `CANONICAL_OBJECT`, `OPERATIONAL_CONTROL_RECORD`, or `REFERENCE_SET` | +| `CONTROL_RETAINS_BLOB` | `OPERATIONAL_CONTROL_RECORD` | `BLOB` | +| `CONTROL_RETAINS_CONTROL` | `OPERATIONAL_CONTROL_RECORD` | `OPERATIONAL_CONTROL_RECORD` | +| `HOLD_PROTECTS_BLOB` | `OPERATIONAL_CONTROL_RECORD` | `BLOB` | +| `JOB_REQUIRES_BLOB` | `OPERATIONAL_CONTROL_RECORD` | `BLOB` | +| `PATCH_RETAINS_BASE` | `BLOB_MANIFEST` | `BLOB` | +| `PATCH_RETAINS_POSTIMAGE` | `BLOB_MANIFEST` | `BLOB` | +| `REFERENCE_SET_RETAINS_SET` | `OPERATIONAL_CONTROL_RECORD` | `REFERENCE_SET` | +| `TRANSFER_PINS_REPLICA` | `OPERATIONAL_CONTROL_RECORD` | `REPLICA` | + +For a `BLOB` target, `target_identity` is the Blob Sigil and equals +`target_sigil`. For `REPLICA`, `REFERENCE_SET`, `CANONICAL_OBJECT`, and +`OPERATIONAL_CONTROL_RECORD`, `target_identity` is respectively an `SR-ID`, +`RS-ID`, the canonical object's operational identity, or the control record's +operational identity; `target_sigil` is the verified immutable record or +Reference Set Sigil. A `BLOB_MANIFEST` source has `identity == sigil`, both +equal to the manifest Blob Sigil. A `REFERENCE_SET_RETAINS_SET` source has +`schema_version: "artifact-storage-reference-set/1.0"`. Every other source +identity and Schema version must resolve to the exact object whose verified +Sigil is `source.sigil`. Any relationship/source/target combination or +identity/Sigil mismatch outside this matrix is a Schema rejection, not an +unknown edge preserved for later interpretation. + +`CleanupResult` and `ResidualStagingCleanup` use these exact field/null +matrices: + +| Object and state | Object field | Evidence | Reason | Terminal legality | +| --- | --- | --- | --- | --- | +| `CleanupResult.NOT_REQUIRED` | n/a | null | null | yes | +| `CleanupResult.PENDING` | n/a | null | null | no | +| `CleanupResult.CLEANED` | n/a | non-null | null | yes | +| `CleanupResult.FAILED` | n/a | non-null | non-null | yes | +| `ResidualStagingCleanup.NOT_REQUIRED` | null | null | null | yes | +| `ResidualStagingCleanup.PENDING` | non-null | null | null | no | +| `ResidualStagingCleanup.CLEANED` | non-null | non-null | null | yes | +| `ResidualStagingCleanup.FAILED` | non-null | non-null | non-null | no; Recovery remains open | +| `ResidualStagingCleanup.HELD_FOR_DISPOSITION` | non-null | non-null | non-null | yes | + +`SystemReserveClass` is exactly `INITIALIZATION`, `COORDINATOR_LIVENESS`, +`CLOCK_PRESSURE`, `CATALOG_ADMINISTRATION`, `VERIFICATION_INCIDENT`, +`RETENTION_REFERENCE`, `GC_ADMINISTRATION`, or `RECOVERY`. A backend's +`system_reserve_limits` contains exactly one member for every class, sorted in +that enum order. Counts are lifetime maxima for the installed backend-profile +version, not advisory telemetry. `max_event_frame_bytes` is in +`56..8388664`, the binary-frame overhead plus the Event-byte bound; +`max_control_record_bytes` and `max_recovery_evidence_bytes` are in +`1..8388664`. Each installed value must be at least the largest canonical item +assigned to that class by conformance fixtures. + +`OpenIntentProjection` has exactly these branches. Every branch additionally +has `source_event: EventRef`, `intent_sigil: Sigil`, `revision: 1`, and +`last_event_sigil: Sigil`; both Sigils equal `source_event.event_sigil`. + +| `intent_kind` | Exact remaining fields and bindings | +| --- | --- | +| `TRANSFER_COMMIT` | `intent_id: Opaque`, `owner_id: SA-ID`, `staging_object: BackendObjectRef`, `target_object: BackendObjectRef`, `authorization_expires_at: null` | +| `MATERIALIZATION_COMMIT` | `intent_id: Opaque`, `owner_id: SM-ID`, `staging_object: BackendObjectRef`, `target_object: BackendObjectRef`, `authorization_expires_at: null` | +| `QUARANTINE_MOVE` | `intent_id: SQ-ID`, `owner_id: SA-ID\|SM-ID`, `source_object: BackendObjectRef`, `target_object: BackendObjectRef`, `authorization_expires_at: null` | +| `GC_DELETE` | `intent_id: Opaque`, `owner_id: SG-ID`, `target_id: Opaque`, `target_object: BackendObjectRef`, `authorization_expires_at: Timestamp` | +| `DISPOSITION` | `intent_id: Opaque`, `owner_id: SD-ID`, `target_kind: "STAGING"\|"MATERIALIZATION_STAGING"\|"MATERIALIZATION_DESTINATION"\|"QUARANTINE"`, `target_object: BackendObjectRef`, `authorization_expires_at: Timestamp` | +| `CANONICAL_REFERENCE` | `intent_id: RI-ID`, `owner_id: RI-ID`, `expected_chronicle_head: ChronicleHeadRef`, `blob_sigils: [Sigil]`, `reference_set_sigils: [Sigil]`, `authorization_expires_at: null` | + +Every field exposed by `TRANSFER_COMMIT` equals the corresponding field in the +owning Attempt's `TransferCommitIntent`; the Materialization and Quarantine +branches likewise equal the corresponding fields in their complete source +intent payloads. The GC and disposition fields equal their started-event +payload and referenced immutable authorization, and the canonical-reference +fields equal its immutable Reference Intent. Fields not duplicated in this +projection are recovered only by resolving the mandatory `source_event`; the +projection never invents a default or substitutes a later record. Open-intent +arrays are unique and sorted by `(intent_kind, intent_id)`. An `intent_id` is +globally unique among all open-intent kinds in one Storage Journal, so the +sorted `open_intent_ids` payload cannot alias two branches. + +In a `LEASED` `ExecutionContext`, `execution_journal_id`, `executor_epoch`, +`job_id`, `attempt_id`, and `lease_id` equal the same-named fields inside +`fence` byte-for-byte. Any mismatch is `FENCE_REJECTED`. + +`ReasonCode` is exactly: + +```text +AUTHORIZATION_DENIED +BACKEND_AMBIGUOUS +BACKEND_CONFLICT +BACKEND_UNAVAILABLE +BOUNDS_EXCEEDED +CANCELLED +CHRONICLE_REFERENCE_CHANGED +CLOCK_UNCERTAIN +CONFLICT +CORRUPT +DESTINATION_CONFLICT +DISPOSITION_EXPIRED +DURABILITY_UNVERIFIED +EXECUTION_HOLD_LIFETIME_EXPIRED +EXECUTION_ROOT_ORPHAN_ABORTED +EXECUTION_ROOT_OWNER_TERMINATED +FENCE_REJECTED +GC_ROOT_CHANGED +GENERATION_MISMATCH +HASH_MISMATCH +INTEGRITY_FAILURE +INVALID_MESSAGE +LEASE_EXPIRED +LEGACY_PROTECTION_FAILED +MINIMUM_REPLICA_VIOLATION +OPERATIONAL_ROOT_TERMINATED +PAYLOAD_LOST +POLICY_REJECTED +QUARANTINE_RESERVATION_FAILED +QUOTA_EXHAUSTED +RECOVERY_INTERRUPTED_PRECOMMIT +RECOVERY_PAYLOAD_LOST +RETENTION_BLOCKED +SOURCE_CHANGED +TAIL_INCOMPLETE +TIMEOUT +UNKNOWN_SCHEMA +UNSAFE_PATH +VERIFICATION_FAILED +``` + +Every immutable control record in `records/` is canonical JSON, has a +top-level `schema_version`, and has exactly the remaining fields assigned by +its Schema. Its terminal self-Sigil field is `record_sigil`, except that the +Reference Set and Doctor report use the deliberately specific names +`reference_set_sigil` and `report_sigil`; each is computed with itself +omitted. It is stored under a path derived solely by the coordinator from that +self-Sigil. The path is not part of the record or its identity. The exact +top-level field sets are: + +| Contract | Exact top-level fields | +| --- | --- | +| `artifact-blob/1.0` | `schema_version`, `blob_sigil`, `size_bytes`, `first_verified_at`, `availability`, `availability_as_of`, `availability_basis_sigil`, `effective_policy_set_sigil`, `next_verification_due_at`, `known_replica_ids`, `eligible_replica_ids`, `integrity_event_sigils`, `media_type_observations`, `filename_observations`, `revision`, `record_sigil` | +| `artifact-replica/1.0` | `schema_version`, `replica_id`, `blob_sigil`, `size_bytes`, `backend`, `object`, `state`, `created_by_transfer_attempt_id`, `verification`, `retention_policy_ids`, `revision`, `record_sigil` | +| `artifact-transfer/1.0` | `schema_version`, `transfer_id`, `direction`, `purpose`, `source`, `destination`, `expected_blob_sigil`, `bounds`, `backend`, `authorization_sigil`, `idempotency_key_sigil`, `execution`, `verification_method`, `provenance_policy_id`, `retention_policy_ids`, `created_at`, `record_sigil` | +| `artifact-transfer-attempt/1.0` | `schema_version`, `transfer_attempt_id`, `transfer_id`, `attempt_number`, `state`, `staging_state`, `reservation`, `staging_object`, `commit_intent`, `residual_staging_cleanup`, `computed_blob_sigil`, `computed_size_bytes`, `selected_replica_id`, `quarantine_id`, `terminal_reason`, `started_at`, `terminal_at`, `revision`, `record_sigil` | +| `artifact-provenance/1.0` | `schema_version`, `provenance_id`, `relation`, `blob`, `source`, `destination`, `actor_id`, `authorization_sigil`, `execution`, `backend`, `transfer`, `verification_sigils`, `transformation`, `times`, `terminal_reason`, `record_sigil` | +| `artifact-materialization/1.0` | `schema_version`, `materialization_id`, `source_blob_sigil`, `source_replica_id`, `source_verification_sigil`, `destination_class`, `destination_sigil`, `task_id`, `attempt_id`, `access_mode`, `bounds`, `reservation`, `destination_staging_object`, `commit_intent`, `residual_staging_cleanup`, `state`, `verification`, `cleanup`, `created_at`, `terminal_at`, `revision`, `record_sigil` | +| `artifact-storage-backend/1.0` | `schema_version`, `backend_id`, `adapter_id`, `adapter_version`, `protocol_version`, `adapter_sigil`, `configuration_sigil`, `namespace_sigil`, `isolation`, `limits`, `consistency`, `durability`, `verification_methods`, `range_resume`, `conditional_operations`, `deletion_capabilities`, `credential_class`, `fencing`, `conformance`, `record_sigil` | +| `artifact-retention-policy/1.0` | `schema_version`, `policy_id`, `scope`, `minimum_replica_count`, `required_backends`, `required_failure_domains`, `maximum_integrity_age_seconds`, `deletion_grace_seconds`, `retain_until`, `automatic_gc_allowed`, `authorization_sigil`, `registered_at`, `record_sigil` | +| `artifact-storage-reference-set/1.0` | `schema_version`, `reference_set_id`, `source`, `extractor`, `edges`, `validation`, `registration_event_id`, `created_at`, `reference_set_sigil` | +| `artifact-storage-reference-intent/1.0` | `schema_version`, `reference_intent_id`, `transition_request_id`, `transition_request_sigil`, `canonical_event_type`, `expected_chronicle_head`, `reference_sets`, `blob_sigils`, `actor_id`, `authorization_sigil`, `idempotency_key_sigil`, `requested_at`, `record_sigil` | +| `artifact-storage-legacy-protection/1.0` | `schema_version`, `protection_id`, `artifact_id`, `program_id`, `artifact_receipt_sigil`, `recorded_uri_sigil`, `lexical_identity_sigil`, `resolved_file_identity_sigil`, `anchor_observation_sigil`, `blob`, `protected_replica_id`, `transfer`, `verification`, `exclusion`, `registered_at`, `record_sigil` | +| `artifact-gc-plan/1.0` | `schema_version`, `gc_plan_id`, `policy_id`, `root_snapshot`, `extractor_suite_sigil`, `bounds`, `closure_proof`, `targets`, `created_at`, `grace_ends_at`, `record_sigil` | +| `artifact-storage-disposition/1.0` | `schema_version`, `disposition_id`, `target_kind`, `target_id`, `target_generation`, `expected_blob_sigil`, `expected_size_or_bound`, `reason_code`, `actor_id`, `policy_sigil`, `approval_evidence_sigil`, `authorization_sigil`, `authorized_at`, `expires_at`, `idempotency_key_sigil`, `record_sigil` | +| `artifact-storage-journal-event/1.0` | `schema_version`, `journal_id`, `event_id`, `sequence`, `event_type`, `coordinator_id`, `epoch`, `recorded_at`, `observed_at`, `entity_revisions`, `causation_event_id`, `idempotency_key_sigil`, `quota_effects`, `payload`, `previous_event_sigil`, `event_sigil` | +| `artifact-storage-journal-head/1.0` | `schema_version`, `journal_id`, `storage_format_version`, `event_count`, `last_sequence`, `last_event_sigil`, `committed_byte_length`, `current_epoch`, `state_sigil`, `updated_at` | +| `artifact-storage-state/1.0` | `schema_version`, `journal_id`, `storage_format_version`, `project_id`, `backend_profile_id`, `backend_profile_sigil`, `conformance_profile_id`, `conformance_suite_sigil`, `store_status`, `active_recovery_id`, `recovery_origin_status`, `clock_status`, `clock_anchor`, `current_epoch`, `applied_event_count`, `last_event_sigil`, `recoveries`, `blobs`, `replicas`, `transfer_requests`, `transfer_attempts`, `materializations`, `quarantines`, `provenance`, `retention_policies`, `holds`, `reference_sets`, `legacy_v1_protections`, `gc_plans`, `canonical_reference_intents`, `dispositions`, `quota_reservations`, `open_intents`, `incidents`, `availability_counters`, `quota_counters`, `state_sigil` | +| `artifact-storage-tail-evidence/1.0` | `schema_version`, `evidence_id`, `recovery_id`, `journal_id`, `kind`, `frame_start`, `observed_size`, `observed_bytes_sigil`, `previous_evidence_record_sigil`, `created_at`, `record_sigil` | +| `artifact-storage-recovery-marker/1.0` | `schema_version`, `recovery_id`, `journal_id`, `prior_head_sigil`, `old_committed_byte_length`, `last_complete_byte_length`, `discarded_suffix_size`, `discarded_suffix_sigil`, `evidence_record_sigil`, `phase`, `recovery_event_id`, `recovery_event_sigil`, `recovery_event_seed_record_sigil`, `recovery_frame_start`, `recovery_frame_size`, `recovery_frame_sigil`, `prepared_frame_evidence_record_sigil`, `retry_count`, `latest_retry_evidence_record_sigil`, `created_at`, `updated_at`, `record_sigil` | +| `artifact-storage-doctor-report/1.0` | `schema_version`, `report_id`, `mode`, `project_id`, `storage_journal_id`, `journal_head_sigil`, `chronicle_head_sigil`, `storage_format_version`, `backend_profile_id`, `backend_profile_sigil`, `conformance_profile_id`, `conformance_suite_sigil`, `coordinator_epoch`, `journal_verification`, `state_verification`, `backend_inventory`, `replica_checks`, `legacy_checks`, `quarantine_checks`, `quota_checks`, `reference_checks`, `open_intent_checks`, `bounds`, `incomplete_reasons`, `overall_status`, `started_at`, `completed_at`, `report_sigil` | + +For every row, `schema_version` is the literal contract name in the first +column. A named self-Sigil is `Sigil` and is computed with itself omitted. +Unless a field is explicitly nullable below, it is required and non-null. +The following bindings complete the compact top-level field sets: + +- Journal Event `schema_version` is the literal + `artifact-storage-journal-event/1.0`; `journal_id` is `SJ-ID`; `event_id` is + `SE-ID`; `sequence` is `PositiveU63`; `event_type` is `StorageEventType`; + `coordinator_id` is `Opaque`; `epoch` is `PositiveU63`; `recorded_at` is + `Timestamp`; `observed_at` is `Timestamp|null`; `entity_revisions` is + `[EntityRevision]`; `causation_event_id` is `SE-ID|null`; + `idempotency_key_sigil` is `Sigil|null`; `quota_effects` is + `[QuotaEffect]`; `payload` is the exact event-type union below; + `previous_event_sigil` is `Sigil|null`; and `event_sigil` is `Sigil`. + `previous_event_sigil` is null exactly at sequence one and otherwise equals + the immediately preceding event's Sigil. A non-null causation ID resolves to + an earlier event in the same Journal. `observed_at` is null only when no + authenticated external observation time exists; for a payload with `clock` + it equals `clock.utc`, for `storage.clock_uncertain` it equals + `detected_clock.utc`, and for `storage.clock_restored` it equals + `new_clock.utc`. It never orders replay. `storage.initialized` has epoch one; + a recovery or epoch-start envelope epoch equals its payload `next_epoch`; + every other Event equals replayed State `current_epoch`. + `coordinator_id` is stable within one epoch. When an owning immutable record + contains an idempotency Sigil, a non-null envelope value equals it + byte-for-byte; otherwise the envelope field is null. + `event_sigil` is SHA-256 over the complete canonical Event with only that + field omitted. +- Reference Set identity is `RS-ID`; `source`, `extractor`, `edges`, and + `validation` are `ReferenceSource`, `ReferenceExtractor`, `[ReferenceEdge]`, + and `ReferenceValidation`; `registration_event_id` is `SE-ID`; `created_at` + is `Timestamp`; and `reference_set_sigil` is `Sigil`. Edges are sorted and + unique by `(relationship, target_kind, target_identity, target_sigil)`. + Validation evidence is non-empty, sorted, and unique. The registration event payload's + Reference Set ID, Sigil, source identity, and source Sigil equal the record + byte-for-byte, and its Event ID equals `registration_event_id`. +- Reference Intent identity is `RI-ID`; `transition_request_id` and `actor_id` + are `Opaque`; `transition_request_sigil`, `authorization_sigil`, + `idempotency_key_sigil`, and `record_sigil` are `Sigil`; + `canonical_event_type` uses the closed enum below; + `expected_chronicle_head` is `ChronicleHeadRef`; `reference_sets` is a + non-empty `[ReferenceSetRef]` sorted and unique by `reference_set_id`; + `blob_sigils` is a sorted unique `[Sigil]` of length `0..4096`; and + `requested_at` is `Timestamp`. Every member resolves to the complete + registered Reference Set with the same Sigil. `blob_sigils` equals, + byte-for-byte, the exact sorted Blob set produced by the bounded typed + transitive closure of all listed `reference_sets`, as defined under + Schema-aware reference closure. The empty array is legal exactly when that + closure reaches no Blob. The `canonical_reference.intent_recorded` + payload's expected Head, Blob array, and Reference Set Sigil array equal the + immutable record, its `ControlRef` names this Schema, ID, and + `record_sigil`, and its lifecycle Reservation owner ID equals + `reference_intent_id`. That Reservation has + `expires_at == null` and `remaining_micros_at_creation == null`; a + time-limited canonical pin is invalid. + +Reference Set and Reference Intent identities are deterministic: + +```text +reference_set_id = + "RS-" + UPPER_HEX(SHA256(canonical_json( + ["artifact-storage-reference-set-id/1.0", + source, extractor, edges, validation]))) + +registration_event_id = + if source.kind == "OPERATIONAL_CONTROL_RECORD" + and source.schema_version == "execution-storage-root-manifest/1.0": + "SE-" + UPPER_HEX(SHA256(canonical_json( + ["artifact-storage-execution-root-reference-set-registration-event-id/1.0", + source.identity]))) + else: + "SE-" + UPPER_HEX(SHA256(canonical_json( + ["artifact-storage-reference-set-registration-event-id/1.0", + reference_set_id]))) + +reference_intent_id = + "RI-" + UPPER_HEX(SHA256(canonical_json( + ["artifact-storage-reference-intent-id/1.0", + canonical_event_type, + transition_request_id]))) +``` + +Before allocating any registration Event or using a receive time in a +Reference Set, Storage atomically creates or resolves one pending candidate at +the deterministic `reference_set_id`. First creation fixes `created_at` and +the complete record bytes, including the deterministic +`registration_event_id`; retry reuses those bytes. A complete matching +registered set and Event are returned without another append. Different bytes +at the same identity are an integrity conflict. A torn candidate or ambiguous +Event acknowledgement is recovered by that same ID/Event-ID pair before any +higher-layer request is rebuilt. + +For the execution-root branch, `source.identity` is the RFC-0012 `ESM-ID` and +the special Event ID equals the ESM's precomputed +`reference_set_registration_event_id`. Storage additionally maintains one +single-assignment candidate per `(ESM-ID, special registration Event ID)`. +It first resolves the complete ESM, then derives the fixed source, installed +extractor and validator, exact edges, and exact validation fields specified by +RFC-0012, and only then derives the RS-ID and Reference Set Sigil. A second +candidate, alternate RS-ID, generic RS-derived Event ID, changed +extractor/validator, or different registration Event for that ESM is an +integrity conflict. Execution-root `validation.evidence_sigils` is the exact +RFC-0012 projection and contains no future Reference Set Sigil, registration +Event Sigil, hold authorization, or hold-set Event Sigil. The dependency is +therefore acyclic: + +```text +owner binding -> ESM-ID -> registration SE-ID -> ESM bytes/Sigil + -> source/extractor/edges/validation -> RS-ID/Sigil + -> registration Event body/Sigil +``` + +Likewise, `(canonical_event_type, transition_request_id)` has one global +Reference Intent identity and one projection entry. Atomic create-if-absent +fixes the complete record bytes; an exact retry reuses its existing +`intent_recorded` Event or finishes the one missing Event, while different +bytes are an idempotency conflict. State replay, Doctor, and recovery reject +two records or two creation Events for that identity even if their RI-IDs, +request keys, or otherwise valid fields differ. No scan or newly allocated +RI-ID is permitted. +- Blob identity and basis Sigils are `Sigil`; size and revision are `U63` and + `PositiveU63`; times are `Timestamp` or the stated nullable time; + availability is `AVAILABLE`, `DEGRADED`, `UNAVAILABLE`, or `INCIDENT`; + `availability_as_of` is `EventRef`; Replica IDs are `[SR-ID]`; integrity + identities are `[Sigil]`; and media-type and filename observations are + `[Opaque]`. +- Replica ID is `SR-ID`; its Blob identity and size are `Sigil` and `U63`; + backend and object are `BackendRef` and `BackendObjectRef`; state is + `COMMITTING`, `AVAILABLE`, `VERIFYING`, `STALE`, `CORRUPT`, `ABANDONED`, + `DELETING`, `DELETED`, or `DELETE_FAILED`; creator is `SA-ID`; + verification is `VerificationRef|null`; policy IDs are `[SP-ID]`; and + revision is `PositiveU63`. Verification is null exactly in `COMMITTING` or + `ABANDONED`; it is non-null in `AVAILABLE`, `VERIFYING`, `STALE`, + `DELETING`, `DELETED`, and `DELETE_FAILED`. `CORRUPT` retains a non-null last + successful verification when one exists and is null only when corruption + was proven before any successful committed-generation verification. +- Transfer ID is `ST-ID`; source, destination, bounds, backend, and execution + use their named aliases; all authorization, idempotency, and policy-set + identities are `Sigil`; retention policy IDs are `[SP-ID]`; and creation is + `Timestamp`. +- Transfer Attempt IDs are `SA-ID` and `ST-ID`; attempt number and revision + are `PositiveU63`; state uses the transfer-attempt state enum; + `staging_state` is `NOT_CREATED`, `PRESENT`, `MISSING`, or + `HELD_FOR_DISPOSITION`; reservation is `ReservationRef`; staging object, + commit intent, computed Blob Sigil and size, selected Replica, + Quarantine ID, terminal reason, and terminal time are nullable with types + `BackendObjectRef`, `TransferCommitIntent`, `Sigil`, `U63`, `SR-ID`, + `SQ-ID`, `Reason`, and `Timestamp`; residual cleanup is + `ResidualStagingCleanup`; and start time is `Timestamp`. +- Provenance identity and actor are `Opaque`; relation is + `ProvenanceRelation`; Blob, source, destination, execution, backend, transfer, + transformation, and times use their named aliases; authorization and + verification identities are `Sigil` and `[Sigil]`; terminal reason is + `Reason|null`. +- Materialization ID is `SM-ID`; source Blob and verification identities and + destination identity are `Sigil`; source Replica is `SR-ID`; + destination class is `SANCTUM_INPUT`, `CRUCIBLE_WORKSPACE`, or + `PROJECT_EXPORT`; Task and Attempt IDs are `Opaque|null`; access mode is + `READ_ONLY`, `COPY_ON_WRITE`, or `WRITABLE_COPY`; bounds, reservation, + staging object, commit intent, residual staging cleanup, verification, and + cleanup use the bindings below; state uses the materialization state enum; + creation is `Timestamp`, terminal time is `Timestamp|null`, and revision is + `PositiveU63`. +- Legacy-protection IDs, Artifact IDs, and Program IDs are `Opaque`; all + Receipt, URI, path-identity, anchor, and record identities are `Sigil`; + Blob, transfer, verification, and exclusion use their named aliases; + protected Replica is `SR-ID`; and registration is `Timestamp`. +- GC plan ID and policy ID are `SG-ID` and `SP-ID`; root snapshot, bounds, + closure proof, and targets use their named aliases; extractor identity is + `Sigil`; creation and grace end are `Timestamp`. +- Disposition ID is `SD-ID`; target kind is `STAGING`, + `MATERIALIZATION_STAGING`, `MATERIALIZATION_DESTINATION`, or `QUARANTINE`; + target ID and generation and actor ID are `Opaque`; expected Blob is + `Sigil|null`; size/bound uses `ExpectedSizeOrBound`; reason is + `ReasonCode`; all policy, approval, authorization, and idempotency + identities are `Sigil`; and both times are `Timestamp`. + `target_id` is an `SA-ID`, `SM-ID`, `SM-ID`, or `SQ-ID` respectively for + those four target kinds, and `target_generation` equals the selected + `BackendObjectRef.generation`. + +Tail-evidence records are closed immutable recovery records. `kind` is exactly +`ORIGINAL_INTERRUPTED_APPEND`, `RECOVERY_EVENT_SEED`, +`RECOVERY_FRAME_TEMPLATE`, or `RECOVERY_FRAME_RETRY_SUFFIX`. `frame_start` and +`observed_size` are `U63`; +`evidence_id` and `recovery_id` are `Opaque`, `journal_id` is `SJ-ID`, all +named Sigils are `Sigil`, and `created_at` is `Timestamp`. +all four kinds require a positive observed size. A seed's raw bytes are the +complete canonical recovery Event bytes; a template's are the complete binary +frame. The exact raw bytes are stored next to the record under a +content-derived name and must hash to `observed_bytes_sigil`; a record without +those bytes is invalid. `previous_evidence_record_sigil` is null only for the +original suffix. The seed points to the original, the template points to the +seed, and each retry suffix points to the immediately preceding template or +retry record, forming a bounded verified chain without an array that can be +silently truncated. + +The Recovery Marker's phase is exactly `EVIDENCE_DURABLE`, +`TAIL_TRUNCATED`, `RECOVERY_EVENT_ID_DURABLE`, `RECOVERY_EVENT_PREPARED`, +`RECOVERY_EVENT_RETRY_EVIDENCE_DURABLE`, or +`RECOVERY_EVENT_COMMITTED`. In the first two phases the event ID, event Sigil, +seed Sigil, all `recovery_frame_*` fields, and prepared-template field are +null. In `RECOVERY_EVENT_ID_DURABLE`, the event ID, event Sigil, and seed +record Sigil are non-null and immutable while all frame and prepared-template +fields remain null. From `RECOVERY_EVENT_PREPARED` onward, those three fields, +frame start, positive frame size, frame Sigil, and template evidence-record +Sigil are non-null and immutable. `retry_count` is zero before the retry phase; +`latest_retry_evidence_record_sigil` is null exactly when `retry_count` is +zero and otherwise identifies the last chained retry suffix. `retry_count` +cannot exceed the installed backend's `limits.max_tail_recovery_retries`. +Every phase replacement preserves the same recovery identity and original +tail fields and advances `updated_at`; changing any prepared-event field is an +integrity failure. +Marker recovery ID is `Opaque`, journal ID is `SJ-ID`, byte offsets, lengths, +and retry count are `U63` with the stated positive constraints, all named +Sigils are `Sigil|null` where the phase matrix permits null, recovery event ID +is `SE-ID|null`, and both times are `Timestamp`. + +Every nested object named by those contracts is also closed and is assigned +once in the executable Schema `$defs`; it cannot be a free-form map. `source`, +`destination`, `backend`, `object`, `bounds`, `execution`, `transfer`, +`transformation`, `times`, `cleanup`, `root_snapshot`, `closure_proof`, +`authorization`, and `outcome` are discriminated unions with an explicit +`kind` or `state`. Unknown discriminators are invalid. The executable Schema +suite must contain one positive fixture for every branch and one +additional-property rejection fixture for every object definition before this +RFC can advance from draft. + +Specifically, Replica `backend` and `object` use `BackendRef` and +`BackendObjectRef`. Transfer `source`, `destination`, `bounds`, `backend`, and +`execution` use `SourceDescriptor`, `DestinationDescriptor`, +`TransferBounds`, `BackendRef`, and `ExecutionContext`. +Transfer Attempt `reservation`, `staging_object`, `commit_intent`, and +`residual_staging_cleanup` use `ReservationRef`, nullable +`BackendObjectRef`, nullable `TransferCommitIntent`, and +`ResidualStagingCleanup`. +Provenance `blob`, `source`, `destination`, `execution`, `backend`, +`transfer`, `transformation`, and `times` use `BlobRef`, the two descriptors, +`ExecutionContext`, `BackendRef`, `TransferRef`, `TransformationRef`, and +`TimeSet`. +Materialization `bounds`, `reservation`, `verification`, `cleanup`, and +`residual_staging_cleanup` use `TransferBounds`, `ReservationRef`, nullable +`VerificationRef`, `CleanupResult`, and `ResidualStagingCleanup`; its +`destination_staging_object` is `BackendObjectRef|null`, and `commit_intent` +is `MaterializationCommitIntent|null`. Before commit intent, residual cleanup +is `NOT_REQUIRED`; at commit intent it becomes `PENDING`; every terminal +Materialization uses only `NOT_REQUIRED`, `CLEANED`, or +`HELD_FOR_DISPOSITION`. Retention `scope` uses `PolicyScope`. +Legacy protection `blob`, `transfer`, `verification`, and `exclusion` use +`BlobRef`, `TransferRef`, `VerificationRef`, and `LegacyExclusion`. GC `root_snapshot`, +`bounds`, `closure_proof`, and every target use `GCRootSnapshot`, +`TraversalBounds`, `ClosureProof`, and `GCTarget`. Disposition +`expected_size_or_bound` uses `ExpectedSizeOrBound`. + +Backend `isolation`, `limits`, `consistency`, `durability`, `range_resume`, +`conditional_operations`, `deletion_capabilities`, `fencing`, and +`conformance` use the same-named `Backend*` aliases above. +`verification_methods` is a non-empty sorted unique array whose members are +exactly `FULL_READBACK_SHA256` or `CONFORMANCE_END_TO_END`. +`credential_class` is exactly `COORDINATOR_HOST_LOCAL` or +`COORDINATOR_SCOPED_REMOTE`; the local profile requires the former. Backend +IDs and version fields are `Opaque`; `adapter_sigil`, `configuration_sigil`, +`namespace_sigil`, and `record_sigil` are `Sigil`. + +Retention `policy_id` is `SP-ID`, `scope` is `PolicyScope`, +`minimum_replica_count` is `U63`, `required_backends` and +`required_failure_domains` are sorted unique `[Opaque]`, +`maximum_integrity_age_seconds` and `deletion_grace_seconds` are +`U63|null`, `retain_until` is `Timestamp|null`, `automatic_gc_allowed` is +`boolean`, `authorization_sigil` is `Sigil`, and `registered_at` is +`Timestamp`. + +Doctor `journal_verification` and `state_verification` are `CheckResult`; +`backend_inventory` is `[BackendInventoryEntry]`; `replica_checks`, +`legacy_checks`, `quarantine_checks`, `quota_checks`, `reference_checks`, and +`open_intent_checks` are `[CheckResult]`; `bounds` is `DoctorBounds`; +`incomplete_reasons` is `[Reason]`; and both times are `Timestamp`. +`mode` and `overall_status` use the enums in the Storage Doctor section. +`project_id` and `report_id` are `Opaque`, `report_sigil` is `Sigil`, and +`storage_journal_id` is `SJ-ID|null`. The report's observed +`journal_head_sigil`, `chronicle_head_sigil`, `backend_profile_sigil`, and +`conformance_suite_sigil` are `Sigil|null`; its storage-format, +backend-profile, and conformance-profile IDs are `Opaque|null`; and +`coordinator_epoch` is `U63|null`. + +Transfer `direction` is exactly `INGEST`, `COPY`, `EXPORT`, or +`MATERIALIZE`; `purpose` is exactly `ARTIFACT_IMPORT`, `ATTEMPT_OUTPUT`, +`LEGACY_V1_PROTECTION`, `REPLICA_COPY`, `REPLICA_REPAIR`, +`SANCTUM_INPUT`, `PATCH_BUNDLE`, `USER_EXPORT`, or +`QUARANTINE_REINSPECTION`. `expected_blob_sigil` is `Sigil|null`. +`verification_method` is `VerificationMethod`. + +`entity_revisions` members contain exactly `entity_type`, `entity_id`, +`previous_revision`, and `next_revision`. `previous_revision` is `U63|null`; +`next_revision` is `PositiveU63`, equals one when the prior value is null, and +otherwise equals the prior value plus one. Members are unique and sorted by +`(entity_type, entity_id)`. `entity_type` is exactly `STORE`, `BLOB`, +`REPLICA`, `TRANSFER_REQUEST`, `TRANSFER_ATTEMPT`, `MATERIALIZATION`, +`QUARANTINE`, `PROVENANCE`, `RETENTION_POLICY`, `HOLD`, `REFERENCE_SET`, +`REFERENCE_INTENT`, `LEGACY_PROTECTION`, `GC_PLAN`, `DISPOSITION`, `QUOTA`, +or `INCIDENT`. + +`entity_revisions` contains only directly owned mutable projections. A +policy-, hold-, reference-, or trusted-clock event that changes a derived +Blob availability view lists its owning entity revisions exactly as shown in +the event table and does not enumerate fan-out `BLOB` revisions. Replay must +still recompute the affected closed Blob views and availability counters from +the new global basis. Omitting a directly changed Blob or Replica is invalid; +adding a synthetic fan-out revision is also invalid. + +State collections are not generic maps. Their item shapes are exactly: + +| State collection | Exact item fields | +| --- | --- | +| `recoveries` | `recovery_id: Opaque`, `origin_status: INITIALIZING\|ACTIVE`, `state: ACTIVE\|COMPLETED`, `started_event_sigil: Sigil`, `epoch_ids: [U63]`, `tail_recovery_evidence_record_sigils: [Sigil]`, `completed_event_sigil: Sigil\|null`, `resume_status: INITIALIZING\|ACTIVE\|null` | +| `blobs` | `record: artifact-blob/1.0`, `last_event_sigil: Sigil` | +| `replicas` | `record: artifact-replica/1.0`, `last_event_sigil: Sigil` | +| `transfer_requests` | `transfer_id: ST-ID`, `request_record_sigil: Sigil`, `state: ACTIVE\|SATISFIED`, `attempt_ids: [SA-ID]`, `selected_attempt_id: SA-ID\|null`, `revision: PositiveU63`, `last_event_sigil: Sigil` | +| `transfer_attempts` | `record: artifact-transfer-attempt/1.0`, `last_event_sigil: Sigil` | +| `materializations` | `record: artifact-materialization/1.0`, `last_event_sigil: Sigil` | +| `quarantines` | `quarantine_id: SQ-ID`, `owner_kind: TRANSFER_ATTEMPT\|MATERIALIZATION`, `owner_id: Opaque`, `state: INTENT_RECORDED\|HELD\|INSPECTING\|DISPOSING\|DISPOSED\|DISPOSAL_FAILED\|FAILED`, `source_object: BackendObjectRef\|null`, `destination_object: BackendObjectRef\|null`, `source_cleanup: ResidualStagingCleanup`, `reservation: ReservationRef`, `reason: Reason`, `revision: PositiveU63`, `last_event_sigil: Sigil`, `record_sigil: Sigil`; `owner_id` is `SA-ID` or `SM-ID` exactly as selected by `owner_kind` | +| `provenance` | `provenance_id: Opaque`, `record_sigil: Sigil`, `revision: 1`, `last_event_sigil: Sigil` | +| `retention_policies` | `policy_id: SP-ID`, `record_sigil: Sigil`, `revision: 1`, `last_event_sigil: Sigil` | +| `holds` | `hold_id: SH-ID`, `target_kind: BLOB\|REPLICA\|REFERENCE_SET`, `target_id: Opaque`, `policy_id: SP-ID`, `state: ACTIVE\|RELEASED`, `set_authorization_sigil: Sigil`, `release_authorization_sigil: Sigil\|null`, `revision: PositiveU63`, `last_event_sigil: Sigil` | +| `reference_sets` | `reference_set_id: RS-ID`, `reference_set_sigil: Sigil`, `source_identity: Opaque`, `revision: 1`, `last_event_sigil: Sigil` | +| `legacy_v1_protections` | `PROTECTED {protection_id: Opaque, artifact_id: Opaque, state: "PROTECTED", record: artifact-storage-legacy-protection/1.0, receipt_sigil: Sigil, reason: null, revision: PositiveU63, last_event_sigil: Sigil}` or `FAILED {protection_id: Opaque, artifact_id: Opaque, state: "FAILED", record: null, receipt_sigil: Sigil\|null, reason: Reason, revision: PositiveU63, last_event_sigil: Sigil}` | +| `gc_plans` | `gc_plan_id: SG-ID`, `record_sigil: Sigil`, `state: DRAFT\|AUTHORIZED\|EXECUTING\|COMPLETED\|PARTIAL\|ABORTED`, `authorization_sigil: Sigil\|null`, `authorized_at: Timestamp\|null`, `authorization_expires_at: Timestamp\|null`, `target_states: [GCTargetState]`, `revision: PositiveU63`, `last_event_sigil: Sigil` | +| `canonical_reference_intents` | `reference_intent_id: RI-ID`, `record_sigil: Sigil`, `state: OPEN\|COMMITTED\|RELEASED`, `chronicle_commit: ChronicleCommitRef\|null`, `release_kind: "ABORTED_BEFORE_CANONICAL_COMMIT"\|null`, `release_authority_sigil: Sigil\|null`, `release_reason: Reason\|null`, `revision: PositiveU63`, `last_event_sigil: Sigil`; the four terminal fields are all null in `OPEN`, only `chronicle_commit` is non-null in `COMMITTED`, and only the three release fields are non-null in `RELEASED` | +| `dispositions` | `disposition_id: SD-ID`, `record_sigil: Sigil`, `state: AUTHORIZED\|EXECUTING\|COMPLETED\|FAILED`, `execution_intent_id: Opaque\|null`, `outcome_sigil: Sigil\|null`, `revision: PositiveU63`, `last_event_sigil: Sigil` | +| `quota_reservations` | `reservation: ReservationRef`, `owner_kind: QuotaOwnerKind`, `owner_id: Opaque`, `purpose: QuotaPurpose`, `state: ACTIVE\|RETAINED\|SETTLED`, `consumed_claims: [QuotaClaim]`, `released_claims: [QuotaClaim]`, `remaining_claims: [QuotaClaim]`, `retained_for_event_types: [StorageEventType]`, `revision: PositiveU63`, `last_event_sigil: Sigil` | +| `open_intents` | `OpenIntentProjection` | +| `incidents` | `incident_id: Opaque`, `subject_kind: IncidentSubjectKind`, `subject_id: Opaque`, `reason: Reason`, `state: "OPEN"`, `revision: 1`, `last_event_sigil: Sigil` | + +For a GC target, `deletion_intent_id` and `outcome_reservation` are null in +`PLANNED` or `SKIPPED` and non-null in `DELETING`, `DELETED`, or `FAILED`; +`root_revalidation_sigil` is non-null after skip or deletion start; +`deletion_evidence_sigil` is non-null only in `DELETED`; and +`terminal_reason` is non-null exactly in `SKIPPED` or `FAILED`. A Disposition's +`execution_intent_id` is null in `AUTHORIZED` and in the pre-start +`AUTHORIZED -> FAILED` expiry branch. It is non-null in `EXECUTING`, +`COMPLETED`, and the post-start `EXECUTING -> FAILED` branch. +`outcome_sigil` is null in `AUTHORIZED` or `EXECUTING`; in `COMPLETED` or +either `FAILED` branch it equals the terminal Event Sigil. + +`availability_counters` contains exactly `available_blobs`, +`degraded_blobs`, `unavailable_blobs`, and `incident_blobs`, all `U63`. +`quota_counters` is a sorted array with exactly one `QuotaSnapshot` for every +legal class/dimension pair, ordered by that pair. All other State arrays +are sorted by their first identity field, or by the identity field inside +`record` for the four record-wrapper collections; `open_intents` alone uses +the `(intent_kind, intent_id)` order defined above. Every wrapper is closed; +duplicate keys are invalid. +Each Quarantine projection's `record_sigil` covers that complete closed item +with `record_sigil` omitted; it changes with the revision and is not an +immutable control-record identity. + +GC target-state fields obey this exact matrix. `PLANNED` has all four +nullable evidence fields null. `SKIPPED` has non-null root-revalidation Sigil +and terminal reason, with null reservation and deletion evidence. `DELETING` +has non-null root-revalidation Sigil and outcome reservation, with null +deletion evidence and reason. `DELETED` preserves those two non-null fields, +adds non-null deletion evidence, and has null reason. `FAILED` preserves the +non-null revalidation Sigil and reservation, has null deletion evidence, and +has a non-null reason. Target states are sorted by `target_id`. + +`store_status` is exactly `INITIALIZING`, `ACTIVE`, or `RECOVERING`. +`active_recovery_id` and `recovery_origin_status` are both null outside +`RECOVERING` and both non-null inside it; the origin is exactly +`INITIALIZING` or `ACTIVE`. A completed recovery's `resume_status` equals its +origin and its completion Sigil is non-null. An active recovery has null +completion and resume fields. Recovery entries are sorted by their +`storage.recovery_started` sequence; `epoch_ids` is non-empty, strictly +increasing, and appends the current epoch after every recovery re-crash. + +A quota reservation follows `NONE -> ACTIVE -> RETAINED`, +`RETAINED -> RETAINED`, and `ACTIVE|RETAINED -> SETTLED`. +Every transition is driven by one matching `QuotaEffect`; there is no +directory-derived or implicit release. A retained reservation can be settled +only by one of its exact `retained_for_event_types`. State replay applies all +effects in array order, stores cumulative consumed and released claims, +verifies each resulting `QuotaSnapshot`, and then recomputes +`quota_counters`; arithmetic overflow, a negative balance, an unknown +reservation, an illegal class/dimension pair, or a snapshot mismatch is +`INTEGRITY_FAILURE`. + +### Closed event set + +The `artifact-storage-journal-event/1.0` event-type enum is exactly: + +```text +storage.initialized +storage.activation_completed +storage.epoch_started +storage.clock_uncertain +storage.clock_restored +storage.recovery_started +storage.recovery_completed +storage.message_rejected +legacy_v1.protection_registered +legacy_v1.protection_failed +quota.pressure_entered +quota.pressure_cleared +transfer.prepared +transfer.streaming_started +transfer.verification_started +transfer.commit_intent_recorded +transfer.committed +transfer.failed +transfer.cancelled +transfer.quarantined +replica.verification_started +replica.verified +replica.verification_expired +replica.corrupt +blob.incident_recorded +materialization.prepared +materialization.streaming_started +materialization.verification_started +materialization.commit_intent_recorded +materialization.committed +materialization.cleanup_started +materialization.cleaned +materialization.cleanup_failed +materialization.failed +materialization.quarantined +quarantine.intent_recorded +quarantine.recorded +quarantine.failed +quarantine.reinspection_started +quarantine.reinspection_completed +provenance.recorded +retention.policy_registered +retention.hold_set +retention.hold_released +reference_set.registered +canonical_reference.intent_recorded +canonical_reference.committed +canonical_reference.released +gc.plan_created +gc.plan_authorized +gc.execution_started +gc.target_skipped +gc.target_deletion_started +gc.target_deleted +gc.target_failed +gc.completed +gc.aborted +disposition.authorized +disposition.started +disposition.completed +disposition.failed +``` + +The following tables are the complete event-to-payload construction. Every +listed payload field is required; `|null` is the only nullable notation. +`[T]` is a sorted unique bounded array, `Object` means `BackendObjectRef`, +`Fence` means `ExecutionFenceRef`, `Verify` means `VerificationRef`, `Ref` +means `ControlRef`, and `Clock` means `ClockRef`. The revision column is the +exact directly owned `entity_type` set. Each non-`QUOTA` entity appears once. +`QUOTA` means the exact one-or-more quota projection revisions selected +one-to-one by that event's non-empty `quota_effects`; their entity IDs are the +reservation IDs or +`quota-counter::` keys, then the resulting +members are independently placed by the mandatory entity-revision sort. Every +`INITIALIZE` or `PRESSURE` effect uses the class and dimension in its snapshot; +every `USAGE_REMOVED` counter revision uses the class and sole non-zero legal +dimension in its removal claim. The twelve initialization effects therefore +produce twelve distinct `QUOTA` revisions, in the same class/dimension order +as `quota_counters`. +An event whose row omits `QUOTA` requires empty `quota_effects`; `none` +requires an empty revision array. Where a row names alternatives separated by +`or`, the Event Schema uses `oneOf` selected by the stated discriminator and +admits no other combination. + +Every `Ref` is further narrowed by its payload field: `backend`, +`protection`, `transfer`, `transfer_attempt`, `replica`, +`provisional_replica`, `materialization`, `provenance`, `policy`, +`reference_intent`, `gc_plan`, and `disposition` require the matching owned +contract's literal `schema_version`, operational record ID, and self-Sigil. A +Ref to a different Schema or ID class is invalid even when its Sigil resolves. + +| Event type | Exact payload fields | Exact revisions | +| --- | --- | --- | +| `storage.initialized` | `project_id: Opaque`, `storage_format_version: Opaque`, `backend: Ref`, `conformance_profile_id: Opaque`, `conformance_suite_sigil: Sigil`, `quota_snapshots: [QuotaSnapshot]`, `tail_recovery: TailRecovery\|null`, `clock: Clock` | `STORE`, `QUOTA` | +| `storage.activation_completed` | `legacy_protection_ids: [Opaque]`, `activation_evidence_sigil: Sigil`, `clock: Clock` | `STORE` | +| `storage.epoch_started` | `previous_epoch: U63`, `next_epoch: PositiveU63`, `active_recovery_id: Opaque\|null`, `tail_recovery: TailRecovery\|null`, `reason: Reason`, `clock: Clock` | `STORE` | +| `storage.clock_uncertain` | `previous_clock: Clock`, `detected_clock: Clock`, `divergence_micros: U63`, `affected_reservation_ids: [Opaque]`, `reason: Reason` | `STORE` | +| `storage.clock_restored` | `previous_observation_sigil: Sigil`, `new_clock: Clock`, `expired_entity_ids: [Opaque]`, `evidence_sigils: [Sigil]` | `STORE` | +| `storage.recovery_started` | `recovery_id: Opaque`, `origin_status: "INITIALIZING"\|"ACTIVE"`, `previous_epoch: U63`, `next_epoch: PositiveU63`, `tail_recovery: TailRecovery\|null`, `open_intent_ids: [Opaque]`, `clock: Clock` | `STORE` | +| `storage.recovery_completed` | `recovery_id: Opaque`, `epoch: PositiveU63`, `resume_status: "INITIALIZING"\|"ACTIVE"`, `resolved_intent_ids: [Opaque]`, `evidence_sigils: [Sigil]`, `clock: Clock` | `STORE` | +| `storage.message_rejected` | `message_class: Opaque`, `message_sigil: Sigil\|null`, `reason: Reason` | `none` | +| `legacy_v1.protection_registered` | `protection: Ref`, `artifact_id: Opaque`, `receipt_sigil: Sigil`, `anchor_observation_sigil: Sigil`, `blob_sigil: Sigil`, `size_bytes: U63`, `replica_id: SR-ID` | `LEGACY_PROTECTION`, `BLOB`, `REPLICA` | +| `legacy_v1.protection_failed` | `protection_id: Opaque`, `artifact_id: Opaque`, `receipt_sigil: Sigil\|null`, `reason: Reason` | `LEGACY_PROTECTION` | +| `quota.pressure_entered` | `snapshot: QuotaSnapshot`, `reason: Reason`, `clock: Clock` | `QUOTA` | +| `quota.pressure_cleared` | `snapshot: QuotaSnapshot`, `evidence_sigils: [Sigil]`, `clock: Clock` | `QUOTA` | + +| Event type | Exact payload fields | Exact revisions | +| --- | --- | --- | +| `transfer.prepared` | `transfer: Ref`, `transfer_attempt: Ref`, `reservation: ReservationRef`, `execution_fence: Fence\|null` | `TRANSFER_REQUEST`, `TRANSFER_ATTEMPT`, `QUOTA` | +| `transfer.streaming_started` | `transfer_id: ST-ID`, `transfer_attempt_id: SA-ID`, `staging_object: Object`, `execution_fence: Fence\|null`, `clock: Clock` | `TRANSFER_ATTEMPT` | +| `transfer.verification_started` | `transfer_id: ST-ID`, `transfer_attempt_id: SA-ID`, `staging_object: Object`, `computed_blob_sigil: Sigil`, `computed_size_bytes: U63`, `verification_method: VerificationMethod`, `execution_fence: Fence\|null` | `TRANSFER_ATTEMPT` | +| `transfer.commit_intent_recorded` | `transfer_id: ST-ID`, `transfer_attempt_id: SA-ID`, `commit_intent: TransferCommitIntent`, `provisional_replica: Ref` | `TRANSFER_ATTEMPT`, `BLOB`, `REPLICA` | +| `transfer.committed` | `transfer_id: ST-ID`, `transfer_attempt_id: SA-ID`, `outcome: "NEW_REPLICA"\|"DEDUPLICATED_REPLICA"`, `provisional_replica_id: SR-ID`, `provisional_target_absence_evidence_sigil: Sigil\|null`, `blob_sigil: Sigil`, `size_bytes: U63`, `replica: Ref`, `backend_object: Object`, `verification: Verify`, `residual_staging_cleanup: ResidualStagingCleanup`, `execution_fence: Fence\|null` | `TRANSFER_REQUEST`, `TRANSFER_ATTEMPT`, `BLOB`, provisional `REPLICA`, `QUOTA`; the selected existing Replica in the dedup branch is verified but not revised | +| `transfer.failed` | `transfer_id: ST-ID`, `transfer_attempt_id: SA-ID`, `reason: Reason`, `staging_object: Object\|null`, `staging_state: "NOT_CREATED"\|"MISSING"\|"HELD_FOR_DISPOSITION"`, `residual_staging_cleanup: ResidualStagingCleanup`, `provisional_replica_id: SR-ID\|null`, `provisional_target_absence_evidence_sigil: Sigil\|null`, `blob_sigil: Sigil\|null`, `execution_fence: Fence\|null` | pre-intent: `TRANSFER_REQUEST`, `TRANSFER_ATTEMPT`, `QUOTA`; post-intent: those plus `BLOB`, `REPLICA`, selected by the provisional ID and Blob fields both being null or non-null | +| `transfer.cancelled` | `transfer_id: ST-ID`, `transfer_attempt_id: SA-ID`, `reason: Reason`, `staging_object: Object\|null`, `staging_state: "NOT_CREATED"\|"MISSING"\|"HELD_FOR_DISPOSITION"`, `residual_staging_cleanup: ResidualStagingCleanup`, `provisional_replica_id: SR-ID\|null`, `provisional_target_absence_evidence_sigil: Sigil\|null`, `blob_sigil: Sigil\|null`, `execution_fence: Fence\|null` | pre-intent: `TRANSFER_REQUEST`, `TRANSFER_ATTEMPT`, `QUOTA`; post-intent: those plus `BLOB`, `REPLICA`, selected by the provisional ID and Blob fields both being null or non-null | +| `transfer.quarantined` | `transfer_id: ST-ID`, `transfer_attempt_id: SA-ID`, `quarantine_id: SQ-ID`, `quarantine_event: EventRef`, `residual_staging_cleanup: ResidualStagingCleanup`, `reason: Reason`, `provisional_replica_id: SR-ID\|null`, `provisional_target_absence_evidence_sigil: Sigil\|null`, `blob_sigil: Sigil\|null`, `execution_fence: Fence\|null` | pre-intent: `TRANSFER_REQUEST`, `TRANSFER_ATTEMPT`, `QUARANTINE`, `QUOTA`; post-intent: those plus `BLOB`, `REPLICA`, selected by the provisional ID and Blob fields both being null or non-null | +| `replica.verification_started` | `replica_id: SR-ID`, `backend_object: Object`, `method: VerificationMethod`, `clock: Clock` | `REPLICA`, `BLOB` | +| `replica.verified` | `replica_id: SR-ID`, `backend_object: Object`, `verification: Verify`, `clock: Clock` | `REPLICA`, `BLOB` | +| `replica.verification_expired` | `replica_id: SR-ID`, `due_at: Timestamp`, `clock: Clock` | `REPLICA`, `BLOB` | +| `replica.corrupt` | `replica_id: SR-ID`, `backend_object: Object`, `reason: Reason`, `verification: Verify\|null` | `REPLICA`, `BLOB` | +| `blob.incident_recorded` | `blob_sigil: Sigil`, `incident_id: Opaque`, `reason: Reason`, `conflicting_record_sigils: [Sigil]` | `BLOB`, `INCIDENT` | + +| Event type | Exact payload fields | Exact revisions | +| --- | --- | --- | +| `materialization.prepared` | `materialization: Ref`, `source_replica_id: SR-ID`, `source_verification_sigil: Sigil`, `reservation: ReservationRef`, `execution_fence: Fence\|null` | `MATERIALIZATION`, `QUOTA` | +| `materialization.streaming_started` | `materialization_id: SM-ID`, `destination_staging_object: Object`, `clock: Clock` | `MATERIALIZATION` | +| `materialization.verification_started` | `materialization_id: SM-ID`, `destination_staging_object: Object`, `expected_blob_sigil: Sigil`, `expected_size_bytes: U63` | `MATERIALIZATION` | +| `materialization.commit_intent_recorded` | `materialization_id: SM-ID`, `commit_intent: MaterializationCommitIntent` | `MATERIALIZATION` | +| `materialization.committed` | `materialization_id: SM-ID`, `commit_intent_id: Opaque`, `destination_object: Object`, `destination_identity_sigil: Sigil`, `verification: Verify`, `residual_staging_cleanup: ResidualStagingCleanup`, `execution_fence: Fence\|null` | `MATERIALIZATION`, `QUOTA` | +| `materialization.cleanup_started` | `materialization_id: SM-ID`, `destination_identity_sigil: Sigil`, `authorization_sigil: Sigil`, `clock: Clock` | `MATERIALIZATION`, `QUOTA` | +| `materialization.cleaned` | `materialization_id: SM-ID`, `destination_identity_sigil: Sigil`, `cleanup_evidence_sigil: Sigil`, `clock: Clock` | `MATERIALIZATION`, `QUOTA` | +| `materialization.cleanup_failed` | `materialization_id: SM-ID`, `destination_identity_sigil: Sigil`, `cleanup_evidence_sigil: Sigil`, `reason: Reason`, `clock: Clock` | `MATERIALIZATION`, `QUOTA` | +| `materialization.failed` | `materialization_id: SM-ID`, `reason: Reason`, `destination_staging_object: Object\|null`, `staging_state: "NOT_CREATED"\|"MISSING"\|"HELD_FOR_DISPOSITION"`, `residual_staging_cleanup: ResidualStagingCleanup`, `destination_object: Object\|null`, `destination_state: "NOT_CREATED"\|"MISSING"\|"HELD_FOR_DISPOSITION"`, `destination_cleanup: CleanupResult` | `MATERIALIZATION`, `QUOTA` | +| `materialization.quarantined` | `materialization_id: SM-ID`, `quarantine_id: SQ-ID`, `quarantine_event: EventRef`, `residual_staging_cleanup: ResidualStagingCleanup`, `reason: Reason` | `MATERIALIZATION`, `QUARANTINE`, `QUOTA` | +| `quarantine.intent_recorded` | `quarantine_id: SQ-ID`, `owner_kind: "TRANSFER_ATTEMPT"\|"MATERIALIZATION"`, `owner_id: Opaque`, `source_object: Object`, `destination_object: Object`, `reservation: ReservationRef`, `expected_blob_sigil: Sigil\|null`, `expected_size_bytes: U63\|null`, `reason: Reason` | `QUARANTINE`, `QUOTA`, and exactly one of `TRANSFER_ATTEMPT` or `MATERIALIZATION` selected by `owner_kind` | +| `quarantine.recorded` | `quarantine_id: SQ-ID`, `owner_kind: "TRANSFER_ATTEMPT"\|"MATERIALIZATION"`, `owner_id: Opaque`, `destination_object: Object`, `verification: Verify`, `source_cleanup: ResidualStagingCleanup` | `QUARANTINE`, `QUOTA`, and exactly one of `TRANSFER_ATTEMPT` or `MATERIALIZATION` selected by `owner_kind` | +| `quarantine.failed` | `quarantine_id: SQ-ID`, `owner_kind: "TRANSFER_ATTEMPT"\|"MATERIALIZATION"`, `owner_id: Opaque`, `source_object: Object\|null`, `source_cleanup: ResidualStagingCleanup`, `destination_object: Object\|null`, `destination_state: "NOT_CREATED"\|"MISSING"\|"HELD_FOR_DISPOSITION"`, `reason: Reason` | `QUARANTINE`, `QUOTA`, and exactly one of `TRANSFER_ATTEMPT` or `MATERIALIZATION` selected by `owner_kind` | +| `quarantine.reinspection_started` | `quarantine_id: SQ-ID`, `reinspection_id: Opaque`, `authorization_sigil: Sigil`, `reservation: ReservationRef`, `clock: Clock` | `QUARANTINE`, `QUOTA` | +| `quarantine.reinspection_completed` | `quarantine_id: SQ-ID`, `reinspection_id: Opaque`, `outcome: "REMAINS_HELD"\|"NEW_TRANSFER_PREPARED"`, `evidence_sigils: [Sigil]`, `new_transfer_id: ST-ID\|null` | `QUARANTINE`, `QUOTA` | + +| Event type | Exact payload fields | Exact revisions | +| --- | --- | --- | +| `provenance.recorded` | `provenance: Ref`, `blob_sigil: Sigil`, `relation: ProvenanceRelation` | `PROVENANCE` | +| `retention.policy_registered` | `policy: Ref` | `RETENTION_POLICY` | +| `retention.hold_set` | `hold_id: SH-ID`, `target_kind: "BLOB"\|"REPLICA"\|"REFERENCE_SET"`, `target_id: Opaque`, `policy_id: SP-ID`, `authorization_sigil: Sigil` | `HOLD` | +| `retention.hold_released` | `hold_id: SH-ID`, `release_authority: HoldReleaseAuthority`, `authorization_sigil: Sigil`, `reason: Reason`, `clock: Clock` | `HOLD` | +| `reference_set.registered` | `reference_set_id: RS-ID`, `reference_set_sigil: Sigil`, `source_identity: Opaque`, `source_sigil: Sigil` | `REFERENCE_SET` | +| `canonical_reference.intent_recorded` | `reference_intent: Ref`, `expected_chronicle_head: ChronicleHeadRef`, `blob_sigils: [Sigil]`, `reference_set_sigils: [Sigil]`, `lifecycle_reservation: ReservationRef` | `REFERENCE_INTENT`, `QUOTA` | +| `canonical_reference.committed` | `reference_intent_id: RI-ID`, `chronicle_commit: ChronicleCommitRef` | `REFERENCE_INTENT`, `QUOTA` | +| `canonical_reference.released` | `reference_intent_id: RI-ID`, `release_kind: constant "ABORTED_BEFORE_CANONICAL_COMMIT"`, `chronicle_commit: constant null`, `abort_authority: CanonicalAbortAuthority`, `reason: Reason` | `REFERENCE_INTENT`, `QUOTA` | +| `gc.plan_created` | `gc_plan: Ref`, `root_snapshot_sigil: Sigil`, `closure_proof_sigil: Sigil`, `target_ids: [Opaque]` | `GC_PLAN` | +| `gc.plan_authorized` | `gc_plan_id: SG-ID`, `authorization_sigil: Sigil`, `authorized_target_ids: [Opaque]`, `expires_at: Timestamp`, `clock: Clock` | `GC_PLAN` | +| `gc.execution_started` | `gc_plan_id: SG-ID`, `root_revalidation_sigil: Sigil`, `clock: Clock` | `GC_PLAN` | +| `gc.target_skipped` | `gc_plan_id: SG-ID`, `target_id: Opaque`, `replica_id: SR-ID`, `backend_object: Object`, `reason: Reason`, `root_revalidation_sigil: Sigil` | `GC_PLAN` | +| `gc.target_deletion_started` | `gc_plan_id: SG-ID`, `target_id: Opaque`, `deletion_intent_id: Opaque`, `replica_id: SR-ID`, `backend_object: Object`, `root_revalidation_sigil: Sigil`, `authorization_sigil: Sigil`, `outcome_reservation: ReservationRef` | `GC_PLAN`, `REPLICA`, `BLOB`, `QUOTA` | +| `gc.target_deleted` | `gc_plan_id: SG-ID`, `target_id: Opaque`, `deletion_intent_id: Opaque`, `replica_id: SR-ID`, `backend_object: Object`, `deletion_evidence_sigil: Sigil`, `clock: Clock` | `GC_PLAN`, `REPLICA`, `BLOB`, `QUOTA` | +| `gc.target_failed` | `gc_plan_id: SG-ID`, `target_id: Opaque`, `deletion_intent_id: Opaque`, `replica_id: SR-ID`, `backend_object: Object`, `reason: Reason`, `clock: Clock` | `GC_PLAN`, `REPLICA`, `BLOB`, `QUOTA` | +| `gc.completed` | `gc_plan_id: SG-ID`, `deleted_target_ids: [Opaque]`, `skipped_target_ids: [Opaque]`, `failed_target_ids: [Opaque]`, `outcome_sigil: Sigil`, `clock: Clock` | `GC_PLAN` | +| `gc.aborted` | `gc_plan_id: SG-ID`, `reason: Reason`, `clock: Clock` | `GC_PLAN` | +| `disposition.authorized` | `disposition: Ref`, `target_kind: "STAGING"\|"MATERIALIZATION_STAGING"\|"MATERIALIZATION_DESTINATION"\|"QUARANTINE"`, `target_id: Opaque`, `target_object: Object`, `outcome_reservation: ReservationRef`, `clock: Clock` | `DISPOSITION`, `QUOTA` | +| `disposition.started` | `disposition_id: SD-ID`, `execution_intent_id: Opaque`, `target_kind: "STAGING"\|"MATERIALIZATION_STAGING"\|"MATERIALIZATION_DESTINATION"\|"QUARANTINE"`, `target_id: Opaque`, `target_object: Object`, `authorization_sigil: Sigil`, `clock: Clock` | `DISPOSITION`, plus `QUARANTINE` only for `QUARANTINE`; Transfer and Materialization lifecycle state remains terminal while the open intent records execution | +| `disposition.completed` | `disposition_id: SD-ID`, `execution_intent_id: Opaque`, `target_kind: "STAGING"\|"MATERIALIZATION_STAGING"\|"MATERIALIZATION_DESTINATION"\|"QUARANTINE"`, `target_id: Opaque`, `target_object: Object`, `deletion_evidence_sigil: Sigil`, `clock: Clock` | `DISPOSITION`, `QUOTA`, plus `TRANSFER_ATTEMPT` for `STAGING`, `MATERIALIZATION` for either materialization target, or `QUARANTINE` for `QUARANTINE` | +| `disposition.failed` | `disposition_id: SD-ID`, `execution_intent_id: Opaque\|null`, `target_kind: "STAGING"\|"MATERIALIZATION_STAGING"\|"MATERIALIZATION_DESTINATION"\|"QUARANTINE"`, `target_id: Opaque`, `target_object: Object`, `reason: Reason`, `clock: Clock` | pre-start expiry: `DISPOSITION`, `QUOTA`, with every target owner unchanged; post-start: those plus `QUARANTINE` only for `QUARANTINE`, while other target owners remain byte-for-byte unchanged | + +The following cross-field rules are part of those Event Schema branches: + +- A normal `storage.initialized` has null `tail_recovery`. A non-null value is + legal only for the empty-prefix interrupted-initialization recovery branch + below; each of its six fields equals the corresponding fixed Recovery Marker + field byte-for-byte. +- In `transfer.prepared`, `transfer.record_id` equals the immutable Transfer + request's `transfer_id`, `transfer_attempt.record_id` equals the immutable + Attempt's `transfer_attempt_id`, and + `transfer_attempt.transfer_id == transfer.record_id`. The payload + Ref Sigils equal the two resolved records' self-Sigils, and the Reservation + equals the Attempt's Reservation byte-for-byte. The payload fence equals + `transfer.execution.fence` when that request has a `LEASED` execution + context and is null otherwise. +- In `transfer.verification_started`, both IDs resolve the exact request and + Attempt selected by `transfer.prepared`; the post-event Attempt's + `transfer_id`, current `staging_object`, computed Blob Sigil, computed size, + and execution fence equal the payload byte-for-byte. + `verification_method` equals the immutable Transfer request's + `verification_method` and is supported by its bound backend profile. In + `replica.verification_started`, `replica_id` resolves the exact Replica, + `backend_object` equals that Replica's `object` byte-for-byte, and `method` + is supported by the Replica's bound backend profile. +- In `transfer.commit_intent_recorded`, the `commit_intent.reservation` equals + the Attempt Reservation, its fence equals the request's current public fence, + and its computed Blob equals the immediately preceding verified size and + Sigil. `provisional_replica` names + `artifact-replica/1.0`, has + `replica_id == commit_intent.provisional_replica_id`, state `COMMITTING`, + null verification, the same Blob, target object, backend, creator Attempt, + and revision one. The Blob revision uses + `commit_intent.computed_blob.blob_sigil`; the Replica revision uses the + provisional ID. The Attempt's post-event `commit_intent` equals the complete + payload object byte-for-byte and the new `TRANSFER_COMMIT` open intent is + derived from this Event. +- Every post-intent Transfer terminal payload has + `provisional_replica_id == attempt.commit_intent.provisional_replica_id` + and `blob_sigil == attempt.commit_intent.computed_blob.blob_sigil`; + `transfer.committed.size_bytes` also equals the intent's computed size. + For `NEW_REPLICA`, `replica.record_id == provisional_replica_id`, + `backend_object == commit_intent.target_object`, the supplied verification + covers that exact generation and Blob, and the provisional Replica moves + `COMMITTING -> AVAILABLE`; + `provisional_target_absence_evidence_sigil` is null. For + `DEDUPLICATED_REPLICA`, that evidence Sigil is non-null and authenticates + exact-generation absence, and + `replica.record_id != provisional_replica_id`; the selected record must + already replay `AVAILABLE`, match the Blob and size, and have the same + non-expired verification as the payload. More exactly, + `payload.backend_object == selected_replica.object` and + `payload.verification == selected_replica.verification` byte-for-byte; that + verification's evidence authenticates the exact tuple + `(backend_object.generation, blob_sigil, size_bytes)` carried by the Event + and selected Replica. It receives no revision, while the provisional Replica + alone moves `COMMITTING -> ABANDONED` with null verification. Failure, + cancellation, and quarantine after intent likewise abandon that Replica. In + each of those three Events the provisional ID, + absence-evidence Sigil, and Blob Sigil are all null exactly when the Attempt + has no commit intent; otherwise the ID and Blob have the equalities above + and the evidence Sigil is non-null. All five post-intent terminal Schema + branches remove the exact `TRANSFER_COMMIT` open intent. `ABANDONED` also + means that the intent's exact provisional target generation is proved + absent: DEDUP never created it, and a failure, cancellation, or quarantine + either proves it was never created or conditionally removes only that exact + unpublished generation under the durable commit intent. If absence cannot + be authenticated, the Attempt remains `COMMITTING` in Recovery and the open + intent and Reservation remain live. +- For `transfer.failed` and `transfer.cancelled`, `staging_state: + NOT_CREATED` requires null `staging_object` and cleanup `NOT_REQUIRED`; + `MISSING` requires the exact non-null last staged object and cleanup + `CLEANED` for that object with absence evidence; and + `HELD_FOR_DISPOSITION` requires the exact non-null last staged object and + the identically named cleanup state and object. The Attempt record stores + the payload cleanup byte-for-byte. +- `transfer.quarantined.residual_staging_cleanup` equals the referenced + `quarantine_event` payload's `source_cleanup` byte-for-byte, and the + terminal Attempt stores that same object. The EventRef must identify the + exact preceding `quarantine.recorded` or `quarantine.failed` Event for the + payload's `quarantine_id` and Transfer Attempt owner, whose source object is + the exact last Transfer staging generation and never the provisional target + object. +- In `materialization.committed`, `commit_intent_id` equals the replayed + Materialization's `commit_intent.intent_id`; + `destination_object == commit_intent.destination_object`, + `destination_identity_sigil` equals both + `commit_intent.destination_identity_sigil` and + `materialization.destination_sigil`, and + `execution_fence == commit_intent.execution_fence`, all byte-for-byte. The + intent's `expected_blob.blob_sigil` equals + `materialization.source_blob_sigil`, its size equals the exact selected + source Replica size, and the payload destination has that non-null Blob + Sigil and size. The payload `verification` equals + `materialization.verification` byte-for-byte and authenticates the exact + destination generation, Blob Sigil, and size in that destination object. + No different destination generation or merely byte-equal object can satisfy + this branch. +- `materialization.committed.residual_staging_cleanup` is terminal and names + exactly the intent's staging object unless `NOT_REQUIRED`. The + Materialization record stores it byte-for-byte. A failed Materialization + uses the state matrix in the next rule. + `materialization.quarantined.residual_staging_cleanup` equals the referenced + `quarantine_event` payload's `source_cleanup` byte-for-byte, and the + terminal Materialization stores that same object; the EventRef identifies + the exact preceding `quarantine.recorded` or `quarantine.failed` Event for + the payload's `quarantine_id` and Materialization owner, whose source object + is the exact last materialization staging generation and never its final + destination. Every + Materialization terminal branch removes `MATERIALIZATION_COMMIT` when that + open intent exists. +- In `materialization.failed`, each `NOT_CREATED` state requires its matching + object null. Staging `MISSING` or `HELD_FOR_DISPOSITION` requires the exact + non-null last staged generation and, after commit intent, that object equals + the intent's staging object. A final-destination `MISSING` or + `HELD_FOR_DISPOSITION` branch requires a commit intent and the exact + non-null destination object from it; before intent, destination state is + `NOT_CREATED`. Staging `NOT_CREATED` requires cleanup `NOT_REQUIRED`; + staging `MISSING` requires `CLEANED` with absence evidence; and staging + `HELD_FOR_DISPOSITION` requires the identically named cleanup state and + object. The destination matrix is likewise exact: `NOT_CREATED` requires + `destination_cleanup: NOT_REQUIRED`; `MISSING` requires `CLEANED` with + absence evidence; and `HELD_FOR_DISPOSITION` requires `FAILED` with + non-null evidence and a reason equal to the Event's reason. The + Materialization record stores + `destination_cleanup` byte-for-byte as its `cleanup`. +- In `quarantine.intent_recorded`, `expected_blob_sigil` and + `expected_size_bytes` are either both null or both non-null. They are + non-null exactly when replay of the owner supplies a complete authenticated + expected Blob-and-size pair, and then equal that pair byte-for-byte; neither + field may be synthesized from an unauthenticated partial observation. The + null pair is legal only when no complete authenticated pair exists. When + non-null, `expected_blob_sigil` equals both `source_object.blob_sigil` and + `destination_object.blob_sigil`, while `expected_size_bytes` equals both + `source_object.size_bytes` and `destination_object.size_bytes`; with a null + pair the intent destination's Blob Sigil is null. + `TRANSFER_ATTEMPT` selects an `SA-ID` owner and a `source_object` equal to + that Attempt's exact current staging object. `MATERIALIZATION` selects an + `SM-ID` owner and a source equal to that Materialization's exact current + destination-staging object. In both branches `destination_object` is the + fresh, distinct, coordinator-owned Quarantine identity and generation + covered by the Reservation; it cannot name the source, a pre-existing + object, or a pathname-derived substitute. +- `quarantine.recorded` and `quarantine.failed` repeat the intent's + `quarantine_id`, owner kind, and owner ID exactly and store `source_cleanup` + byte-for-byte in the Quarantine projection. `NOT_REQUIRED` proves the + intent-bound source generation is absent as a consequence of the atomic + move; `CLEANED` proves its exact conditional removal; and + `HELD_FOR_DISPOSITION` proves it remains present and inaccessible. + `PENDING` or `FAILED` cannot appear in either terminal Event. The referenced + owner terminal Event repeats the same cleanup object. Both Quarantine + terminal branches remove `QUARANTINE_MOVE`. +- `quarantine.recorded.destination_object` has the same backend ID, object + identity, locator, and generation as the intent destination byte-for-byte, + and its exact size also equals the intent destination size. Its Blob Sigil + is non-null, and its verification evidence authenticates that exact + destination identity and generation, Blob Sigil, and size. When the + expected pair is non-null, the terminal Blob Sigil and size equal it; when + the pair is null, this terminal verification is the first authenticated + complete pair and does not retroactively alter the intent's null Blob + field. The Event is invalid if verification covers another generation, + only a prefix, or bytes with a different size or Blob Sigil. +- In `quarantine.failed`, null `source_object` is legal only with + `source_cleanup: NOT_REQUIRED`; otherwise the source object equals + both the intent source and `source_cleanup.staging_object` byte-for-byte. + `destination_state: NOT_CREATED` alone has null `destination_object`; + `MISSING` and `HELD_FOR_DISPOSITION` carry the exact non-null intent + destination identity, generation, Blob Sigil, and size, with the latter + consuming its charged Quarantine usage. A failed branch carries no success + verification and therefore cannot adopt its destination as verified + Quarantine content. +- In `provenance.recorded`, `provenance` resolves the exact immutable + `artifact-provenance/1.0` record; its Ref ID and Sigil equal that record's + `provenance_id` and `record_sigil`. The payload's `blob_sigil` equals the + resolved record's `blob.blob_sigil`, and its `relation` equals the resolved + record's `relation`, all byte-for-byte. +- Every `retention.hold_set` is one of two closed Phase 3 branches and targets + a registered `REFERENCE_SET`. For an RFC-0012 execution ESM, its payload, + registered Set, registration Event, ESM plan, neutral policy, and Event ID + validate byte-for-byte, and `authorization_sigil` is exactly: + + ```text + Sigil(["execution-root-hold-set-authorization/1.0", + {manifest_id, manifest_sigil}, + {reference_set_id, reference_set_sigil, + registration_event: }, + {hold_id, hold_set_event_id}, + {policy_id, policy_sigil}]) + ``` + + For an RFC-0014 operational root, no root or activation Event exists yet. + The payload instead resolves and validates the already-durable + `patch-operational-root-plan/1.0`: its source control record, registered Set, + registration Event, preallocated Promotion Event/root/hold/Event IDs, + policy tuple, authorization kind, content-derived plan/root identities, and + self-Sigil all match. `authorization_sigil` uses RFC-0014's closed formula + over the plan pair, exact Set/registration Event, hold/Event IDs, and policy + pair; its subject is the plan ID. Admission must not look up or infer a + future Promotion Event or live OperationalRoot. An alternate plan, target + kind, arbitrary authorization Sigil, reused activation/hold slot, or third + Phase 3 hold-set branch is invalid. +- Every `retention.hold_released` resolves one exact `ACTIVE` hold and advances + only that projection to `RELEASED`. Its `release_authority` is the complete + closed union above; no reason text or bare Sigil is authority. + + In `EXECUTION_ROOT`, the `ControlRef` names + `execution-root-hold-release-authorization/1.0`, its `record_id` is exactly: + + ```text + "EHR-" + UPPER_HEX(SHA256(canonical_json( + ["execution-root-hold-release-authorization-id/1.0", hold_id]))) + ``` + + and it resolves the immutable RFC-0012 EHR whose + `release_authorization_sigil == authorization.record_sigil == + payload.authorization_sigil`. The EHR's complete Storage root, neutral + policy, and hold-set authorization equal this hold and its creation chain. + RFC-0012 replay through the exact Head in its basis validates the sole + activation/inactivation or activation absence: + + - `OWNER_TERMINAL` is legal only for `JOB_INPUT` or `ATTEMPT_INPUT`; reason + code is `EXECUTION_ROOT_OWNER_TERMINATED`, and trusted `clock.utc` is at + or after the named owner-terminal Event's `recorded_at`; + - `ORPHAN_ABORT` is legal only when the gate-held verified Head proves the + sole activation absent and the EHR permanently vetoes later activation; + reason code is `EXECUTION_ROOT_ORPHAN_ABORTED`; clock is trusted but + grants no release authority; and + - `OUTPUT_DEADLINE` is legal only for an `ATTEMPT_OUTPUT` schedule with + `EXACT` or `OVERFLOW_FAIL_CLOSED`; reason code is + `EXECUTION_HOLD_LIFETIME_EXPIRED`, the schedule's Job terminal Event and + activation Event match the EHR, and trusted `clock.utc` is at or after + the immutable `release_due_at`. + + For all three execution branches, `reason.evidence_sigils` is the sorted + unique union of the EHR Sigil, ESM Sigil, Reference Set Sigil, hold-set + Event Sigil, and every basis activation, terminal, and verified-Head Sigil + present in that branch. A missing, extra, or alternate evidence Sigil is + invalid. For `OUTPUT_DEADLINE`, RFC-0012's matching + `storage_root.hold_release_observed` additionally carries the complete + schedule, EHR ID/Sigil, and exact four-field Storage `EventRef`. + + In `PATCH_OPERATIONAL_ROOT`, `payload.authorization_sigil` equals the + branch's `authority_sigil`, reason code is + `OPERATIONAL_ROOT_TERMINATED`, and the installed + `benchwork.patch-operational-root-release/1.1` validator first resolves the + exact RootPlan and + `patch-operational-root-release-evidence/1.0` record, then replays the exact + RFC-0014 Promotion Journal through `inactivation_event` and this Storage + Journal through the evidence's pre-release prefix. The plan equals the + original hold-set plan and activation root; the release record equals the + authority's root, Set, hold/Event, activation/inactivation Events, + condition, terminal authority, policy, and prefixes byte-for-byte. The + validator enforces the closed canonical-completion, + `TERMINAL_GUARD | NO_GUARD_ALLOCATED | + NOT_APPLICABLE_JOURNAL_SUFFIX` mapping, the embedded patch-specific + abandonment disposition, trusted clock and checked retention formula, + healthy suffix integrity, and both exact empty relevant-intent sets. + In particular, `retention_completion.inactivation_recorded_at` equals the + replayed inactivation Event's `recorded_at` byte-for-byte; the Promotion + prefix ends exactly at that Event ID/sequence/Sigil; and an Attempt terminal + Event, its exact terminal guard Event when present, and the inactivation + Event occur in that order. The Storage prefix contains the exact hold-set + Event and, for a canonical branch, the exact + `canonical_reference.committed` Event while the hold still projects + `ACTIVE`. That committed Event resolves the same immutable Reference Intent + and Reference Set; its complete EventRef equals both + `release_evidence.canonical_completion.commit_event` and the replayed + `canonical.receipt-observed.canonical_commit_event` byte-for-byte. This + payload-carried backward link, not cross-journal timestamps or a later Head + lookup, proves that the canonical commit was durable before Promotion + inactivation. + For abandonment, the embedded and release-evidence guard completions are + byte-identical; their bound guard Ref equals the enclosing Record and + Recovery Intent guard Refs byte-for-byte, and its ID equals + `recovery.abandoned.guard_id`; the guard Event follows + `recovery.abandoned` and precedes Record construction. A + generic RFC-0013 disposition, bare Sigil, future-root lookup, uncertain + clock, open intent, branch substitution, or incomplete replay retains the + hold. + + `reason.evidence_sigils` is only an audit index and is exactly the sorted + unique set of the authority Sigil, release-evidence record Sigil, + operational-root Sigil, RootPlan Sigil, Reference Set Sigil, policy Sigil, + hold-set/activation/inactivation Event Sigils, terminal-authority record + Sigil, and validator Sigil. A missing or extra value is invalid, but even an + exact index never substitutes for resolving and replaying the release + record. The Event's `clock` equals the evidence's exact trusted + `ClockRef`. + + Retry resolves the same unique release Event rather than appending another. + No branch releases a different hold or authorizes deletion. +- A `gc.target_deletion_started.deletion_intent_id` is fresh and is repeated + byte-for-byte by its one terminal target Event. It creates `GC_DELETE`; that + intent is removed only by `gc.target_deleted` or `gc.target_failed`. +- A pre-start `disposition.failed` is legal only from `AUTHORIZED`, with + `execution_intent_id: null`, `reason.code: DISPOSITION_EXPIRED`, and an + authenticated clock proving the immutable authorization due. It changes + only `DISPOSITION` and `QUOTA`, settles the active outcome Reservation, + creates or removes no `OpenIntentProjection`, performs no backend or target + side effect, and leaves every target owner byte-for-byte unchanged. + Otherwise `disposition.started.execution_intent_id` is fresh, is repeated + non-null by its terminal Event, creates `DISPOSITION`, and is removed only + by `disposition.completed` or post-start `disposition.failed`. In both + failed branches, the disposition record, authorization Event, target kind, + target ID, target generation, and target object are identical; the + post-start branch additionally matches the started Event and open intent + byte-for-byte. + +The following is the complete quota-effect program for every event whose +revision row contains `QUOTA`. “Settle” always means exactly one `SETTLE` +effect for the named prior reservation. Its claims must cover the canonical +encoded event frame and every control record made durable for that event; +claims not consumed by the stated durable outcome are released. No other +event may create, settle, retain, or remove quota usage. + +| Event or branch | Exact quota effects | +| --- | --- | +| `storage.initialized` | One `INITIALIZE` for each of the twelve legal class/dimension pairs. The effect snapshots equal `payload.quota_snapshots` byte-for-byte, are ordered by class/dimension, and start with `used: 0`, `reserved: 0`, and `pressure_state: CLEAR`; each produces the distinct matching `quota-counter::` revision. Format and fixed system-reserve bytes are outside project quota. | +| `quota.pressure_entered` | One `PRESSURE` with `CLEAR -> PRESSURED`; its complete snapshot equals the payload snapshot and the replayed post-event counter. | +| `quota.pressure_cleared` | One `PRESSURE` with `PRESSURED -> CLEAR`; its complete snapshot equals the payload snapshot and the replayed post-event counter. | +| `transfer.prepared` | One `RESERVE` whose reservation equals the payload and attempt record, owner is `(TRANSFER_ATTEMPT, transfer_attempt_id)`, and purpose is `PAYLOAD_LIFECYCLE`. | +| `transfer.committed` | Settle the attempt reservation to `SETTLED`. A `NEW_REPLICA` consumes the exact `COMMITTED` byte/object claims for the selected durable Replica; `DEDUPLICATED_REPLICA` consumes none. Either branch additionally consumes exact `STAGING` claims only for a residual object in `HELD_FOR_DISPOSITION`, plus its used Journal/control-record claims. | +| `transfer.failed` or `transfer.cancelled` | Settle the attempt reservation to `SETTLED`. It consumes exact `STAGING` byte/object claims iff `staging_state` is `HELD_FOR_DISPOSITION`, consumes its used Journal/control-record claims, and releases every other claim. | +| `transfer.quarantined` | Settle the attempt reservation to `SETTLED`, consuming its used Journal/control-record claims. It additionally consumes the exact `STAGING` byte/object claims iff the referenced Quarantine Event's `source_cleanup` is `HELD_FOR_DISPOSITION`; otherwise it releases all payload claims. The independent Quarantine reservation owns only the verified destination bytes. | +| `materialization.prepared` | One `RESERVE` whose reservation equals the payload and materialization record, owner is `(MATERIALIZATION, materialization_id)`, and purpose is `PAYLOAD_LIFECYCLE`. | +| `materialization.committed` | Settle the materialization reservation to `RETAINED`, consuming the exact durable destination as `MATERIALIZATION` byte/object usage plus the event's Journal/control-record claims. It also consumes exact `STAGING` byte/object usage iff `residual_staging_cleanup` is `HELD_FOR_DISPOSITION`; otherwise the staging claims are released. Remaining claims are non-empty and `retained_for_event_types` is exactly `[materialization.cleanup_started]`; a later residual disposition obtains its own outcome Reservation and does not borrow this cleanup share. | +| `materialization.cleanup_started` | Settle the retained reservation to `RETAINED`, consuming the event's Journal/control-record claims. Remaining claims are non-empty and `retained_for_event_types` is exactly `[materialization.cleaned, materialization.cleanup_failed]` in enum order. | +| `materialization.cleaned` | Settle the retained reservation to `SETTLED`, consuming its final Journal/control-record claims, and add one `USAGE_REMOVED` per non-zero `MATERIALIZATION` destination dimension physically removed by this event. A separately retained staging residual remains owned by its exact disposition path and is not removed here. | +| `materialization.cleanup_failed` | Settle the retained reservation to `SETTLED`, consuming its final Journal/control-record claims and removing no destination usage. The failed exact destination remains charged as `MATERIALIZATION`; Phase 3 has no implicit cleanup retry or release. | +| `materialization.failed` | Settle the active reservation to `SETTLED`. It consumes exact `STAGING` and/or `MATERIALIZATION` byte/object claims for each object whose matching state is `HELD_FOR_DISPOSITION`, consumes its used Journal/control-record claims, and releases all other claims. | +| `materialization.quarantined` | Settle the active reservation to `SETTLED`, consuming its used Journal/control-record claims and exact `STAGING` source byte/object claims iff the referenced Quarantine Event's `source_cleanup` is `HELD_FOR_DISPOSITION`; otherwise it releases those source claims. The independent Quarantine reservation owns only the verified destination bytes. A final Materialization destination is never the source of this branch. | +| `quarantine.intent_recorded` | One `RESERVE` equal to the payload reservation, owner `(QUARANTINE, quarantine_id)`, purpose `QUARANTINE_MOVE`. | +| `quarantine.recorded` | Settle that reservation to `SETTLED`, consuming the exact durable destination as `QUARANTINE` byte/object usage and its used Journal/control-record claims. | +| `quarantine.failed` | Settle that reservation to `SETTLED`. It consumes exact `QUARANTINE` byte/object usage iff `destination_state` is `HELD_FOR_DISPOSITION`, consumes its used Journal/control-record claims, and releases every other claim. | +| `quarantine.reinspection_started` | One `RESERVE` equal to the payload reservation, owner `(QUARANTINE, reinspection_id)`, purpose `TERMINAL_OUTCOME`. | +| `quarantine.reinspection_completed` | Settle the matching reinspection reservation to `SETTLED`, consuming only its used Journal/control-record claims. A new Transfer has its own reservation. | +| `canonical_reference.intent_recorded` | One `RESERVE` equal to `lifecycle_reservation`, owner `(CANONICAL_REFERENCE, reference_intent_id)`, purpose `CANONICAL_PIN_LIFECYCLE`. | +| `canonical_reference.committed` | Settle the active intent reservation to `SETTLED`, consuming the commit Event's Journal/control-record claims and releasing every unused claim. A committed v1 pin has no outbound release Event and therefore retains no future-event Reservation. | +| `canonical_reference.released` | Settle only the active uncommitted-intent reservation to `SETTLED`, consuming its abort-release Journal/control-record claims and releasing all unused claims. | +| `gc.target_deletion_started` | One `RESERVE` equal to `outcome_reservation`, owner `(GC_TARGET, target_id)`, purpose `DELETION_OUTCOME`. | +| `gc.target_deleted` | Settle the target reservation to `SETTLED`, consuming its used Journal/control-record claims, and add one `USAGE_REMOVED` per non-zero `COMMITTED` dimension of the conditionally deleted Replica generation. | +| `gc.target_failed` | Settle the target reservation to `SETTLED`, consuming its used Journal/control-record claims, releasing unused claims, and removing no committed usage. | +| `disposition.authorized` | One `RESERVE` equal to `outcome_reservation`, owner `(DISPOSITION, disposition_id)`, purpose `DELETION_OUTCOME`. | +| `disposition.completed` | Settle the disposition reservation to `SETTLED`, consuming its used Journal/control-record claims, and add one `USAGE_REMOVED` per non-zero dimension of the exact usage selected by `target_kind`: `STAGING` and `MATERIALIZATION_STAGING` remove `STAGING`, `MATERIALIZATION_DESTINATION` removes `MATERIALIZATION`, and `QUARANTINE` removes `QUARANTINE`. | +| `disposition.failed` | In both the pre-start null-intent expiry branch and every post-start non-null-intent branch, settle the disposition reservation to `SETTLED`, consume its used Journal/control-record claims, release unused claims, and remove no target usage. | + +The capacity source for every Event is closed by the following 61-row table. +`SYSTEM(C)` means that the Event frame and every newly durable control record +referred to by that Event consume class `C` from `system_reserve_limits`. +`NEW(owner)` means the Reservation created by that same Event is validated and +physically withheld before the frame or any referred control record is +written. `ACTIVE(owner)` and `RETAINED(owner)` require the named replayed +Reservation and consume only its still-unconsumed `capacity_plan` counts and +claims. An existing immutable record merely resolved by an Event consumes no +new capacity. No Event frame, new control record, or evidence record may be +split across sources or fall back to another source. + +| Event type | Exact capacity source | +| --- | --- | +| `storage.initialized` | `SYSTEM(INITIALIZATION)` when `tail_recovery` is null; `SYSTEM(RECOVERY)` otherwise | +| `storage.activation_completed` | `SYSTEM(INITIALIZATION)` | +| `storage.epoch_started` | `SYSTEM(COORDINATOR_LIVENESS)` outside Recovery; `SYSTEM(RECOVERY)` inside Recovery | +| `storage.clock_uncertain` | `SYSTEM(CLOCK_PRESSURE)` | +| `storage.clock_restored` | `SYSTEM(CLOCK_PRESSURE)` | +| `storage.recovery_started` | frame and generic recovery records: `SYSTEM(RECOVERY)`; each operation-specific observation record: that open intent's `ACTIVE` or `RETAINED` owner | +| `storage.recovery_completed` | `SYSTEM(RECOVERY)` | +| `storage.message_rejected` | `SYSTEM(CLOCK_PRESSURE)` | +| `legacy_v1.protection_registered` | `SYSTEM(INITIALIZATION)` | +| `legacy_v1.protection_failed` | `SYSTEM(INITIALIZATION)` | +| `quota.pressure_entered` | `SYSTEM(CLOCK_PRESSURE)` | +| `quota.pressure_cleared` | `SYSTEM(CLOCK_PRESSURE)` | +| `transfer.prepared` | `NEW(TRANSFER_ATTEMPT)` | +| `transfer.streaming_started` | `ACTIVE(TRANSFER_ATTEMPT)` | +| `transfer.verification_started` | `ACTIVE(TRANSFER_ATTEMPT)` | +| `transfer.commit_intent_recorded` | `ACTIVE(TRANSFER_ATTEMPT)` | +| `transfer.committed` | `ACTIVE(TRANSFER_ATTEMPT)`, settled by this Event | +| `transfer.failed` | `ACTIVE(TRANSFER_ATTEMPT)`, settled by this Event | +| `transfer.cancelled` | `ACTIVE(TRANSFER_ATTEMPT)`, settled by this Event | +| `transfer.quarantined` | `ACTIVE(TRANSFER_ATTEMPT)`, settled by this Event | +| `replica.verification_started` | `SYSTEM(VERIFICATION_INCIDENT)` | +| `replica.verified` | `SYSTEM(VERIFICATION_INCIDENT)` | +| `replica.verification_expired` | `SYSTEM(VERIFICATION_INCIDENT)` | +| `replica.corrupt` | `SYSTEM(VERIFICATION_INCIDENT)` | +| `blob.incident_recorded` | `SYSTEM(VERIFICATION_INCIDENT)` | +| `materialization.prepared` | `NEW(MATERIALIZATION)` | +| `materialization.streaming_started` | `ACTIVE(MATERIALIZATION)` | +| `materialization.verification_started` | `ACTIVE(MATERIALIZATION)` | +| `materialization.commit_intent_recorded` | `ACTIVE(MATERIALIZATION)` | +| `materialization.committed` | `ACTIVE(MATERIALIZATION)`, retained by this Event | +| `materialization.cleanup_started` | `RETAINED(MATERIALIZATION)` | +| `materialization.cleaned` | `RETAINED(MATERIALIZATION)`, settled by this Event | +| `materialization.cleanup_failed` | `RETAINED(MATERIALIZATION)`, settled by this Event | +| `materialization.failed` | `ACTIVE(MATERIALIZATION)`, settled by this Event | +| `materialization.quarantined` | `ACTIVE(MATERIALIZATION)`, settled by this Event | +| `quarantine.intent_recorded` | `NEW(QUARANTINE)` | +| `quarantine.recorded` | `ACTIVE(QUARANTINE)`, settled by this Event | +| `quarantine.failed` | `ACTIVE(QUARANTINE)`, settled by this Event | +| `quarantine.reinspection_started` | `NEW(QUARANTINE)` for the reinspection identity | +| `quarantine.reinspection_completed` | `ACTIVE(QUARANTINE)` for the reinspection identity, settled by this Event | +| `provenance.recorded` | `SYSTEM(CATALOG_ADMINISTRATION)` | +| `retention.policy_registered` | `SYSTEM(RETENTION_REFERENCE)` | +| `retention.hold_set` | `SYSTEM(RETENTION_REFERENCE)` | +| `retention.hold_released` | `SYSTEM(RETENTION_REFERENCE)` | +| `reference_set.registered` | `SYSTEM(RETENTION_REFERENCE)` | +| `canonical_reference.intent_recorded` | `NEW(CANONICAL_REFERENCE)` | +| `canonical_reference.committed` | `ACTIVE(CANONICAL_REFERENCE)`, settled by this Event | +| `canonical_reference.released` | `ACTIVE(CANONICAL_REFERENCE)`, settled by this Event | +| `gc.plan_created` | `SYSTEM(GC_ADMINISTRATION)` | +| `gc.plan_authorized` | `SYSTEM(GC_ADMINISTRATION)` | +| `gc.execution_started` | `SYSTEM(GC_ADMINISTRATION)` | +| `gc.target_skipped` | `SYSTEM(GC_ADMINISTRATION)` | +| `gc.target_deletion_started` | `NEW(GC_TARGET)` | +| `gc.target_deleted` | `ACTIVE(GC_TARGET)`, settled by this Event | +| `gc.target_failed` | `ACTIVE(GC_TARGET)`, settled by this Event | +| `gc.completed` | `SYSTEM(GC_ADMINISTRATION)` | +| `gc.aborted` | `SYSTEM(GC_ADMINISTRATION)` | +| `disposition.authorized` | `NEW(DISPOSITION)` | +| `disposition.started` | `ACTIVE(DISPOSITION)` | +| `disposition.completed` | `ACTIVE(DISPOSITION)`, settled by this Event | +| `disposition.failed` | `ACTIVE(DISPOSITION)`, settled by this Event in either the pre-start or post-start branch | + +The exact `allowed_event_types` for an operation Reservation is the subset +named for its owner by this table, further narrowed at Reservation creation to +the paths reachable from the immutable request and policy; it never changes +after creation. In particular, the only owner-reserved +non-`QUOTA` intermediate Events are +`transfer.streaming_started`, `transfer.verification_started`, +`transfer.commit_intent_recorded`, `materialization.streaming_started`, +`materialization.verification_started`, +`materialization.commit_intent_recorded`, and `disposition.started`. +Every other non-`QUOTA` Event is assigned to the explicit `SYSTEM` class above +or consumes an already active/retained owner Reservation as a terminal +transition. A conformance check derives the enum set from this table and +requires exact equality with both the Event enum and Event payload table. + +For each `SystemReserveLimit c`, checked arithmetic defines: + +```text +class_journal_bytes(c) = + c.max_event_frame_count * c.max_event_frame_bytes +class_control_bytes(c) = + c.max_control_record_count * c.max_control_record_bytes +class_recovery_bytes(c) = + c.max_recovery_evidence_count * c.max_recovery_evidence_bytes + +required_system_journal_bytes = sum_c class_journal_bytes(c) +required_system_control_record_bytes = sum_c class_control_bytes(c) +required_system_recovery_bytes = sum_c class_recovery_bytes(c) +``` + +Count minima are also executable rather than operator guesses. For every +reachable State `s` under the profile's collection and concurrency bounds, +conformance enumerates each open system-funded workflow and takes the +worst-case remaining legal branch of its closed state machine: + +```text +terminal_event_min(c) = + max_s sum_{workflow w in s, class(w)=c} remaining_event_frames(w) +terminal_control_min(c) = + max_s sum_{workflow w in s, class(w)=c} remaining_control_records(w) +terminal_recovery_min(c) = + max_s sum_{workflow w in s, class(w)=c} remaining_evidence_records(w) +``` + +For `INITIALIZATION`, each minimum additionally includes the greater bootstrap +demand for `storage.initialized`, `storage.activation_completed`, and exactly +one legacy-protection outcome per bounded preflight entry. For `RECOVERY`, it +includes the complete tail-marker, original-suffix, Event-seed, +prepared-template, `max_tail_recovery_retries` retry-evidence, epoch, and +completion path. +Each installed count is at least its matching minimum. A profile whose counts +are positive but cannot escrow these maxima is rejected before its first +Event; it cannot defer that failure until a terminal frame is needed. + +Every class has positive frame count and frame size. `RECOVERY` additionally +has positive control-record and recovery-evidence counts; every class assigned +an Event that can create a control record has positive control-record count. +The three installed backend limits are strictly positive and respectively at +least the three computed totals. Any multiplication or sum overflow, zero +in a count required positive by the preceding rules, event-count exhaustion, +per-item bound violation, or installed limit below its computed requirement +rejects the backend profile before +initialization. System usage is a monotone replay fold by `(reserve_class, +kind, sequence-or-record-sigil)`; Doctor recomputes all three counts and byte +totals. Reaching a declared lifetime maximum closes caller mutation admission +before the next write and requires a new versioned backend profile with added +physical capacity; it never borrows project quota or another system class. + +For a system-funded Event that opens a nonterminal workflow—initialization, +Replica verification, a hold-release path, GC planning or execution, clock +Recovery, or Storage Recovery—admission also escrows that class's exact +worst-case remaining Event, control-record, and evidence counts from the closed +state machine and profile bounds. The replay fold tracks +`consumed + escrowed` per class even though this administrative ledger is +derived rather than a project quota counter. An unrelated system Event cannot +consume escrow. A matching terminal Event converts its actual lengths to +consumed and releases only unused escrow; Recovery preserves it. Thus the +positive system reserve guarantees all already opened administrative +workflows can reach a legal terminal or recoverable state, rather than merely +admitting their first frame. + +Tail marker, original suffix, recovery-event seed, prepared-frame template, +retry suffix, and generic recovery completion evidence always consume +`SYSTEM(RECOVERY)`. Evidence that classifies or terminalizes one particular +Transfer, Materialization, Quarantine, GC target, disposition, or canonical +intent consumes that owner's `max_recovery_evidence_count` and control-record +claim. The system Recovery frame may refer to those already durable +owner-charged records, but cannot pay for them. + +All object and byte amounts above come from the exact verified +`BackendObjectRef`, not a later listing. Journal and control-record amounts +come from their canonical durable byte lengths. The sum of `used + reserved` +for each legal counter must equal the deterministic fold of initialization +and all later effects; backend inventory is only a reconciliation claim. An +expired active operation is terminalized by its owning failure, cancellation, +or recovery event, which performs the mapped settlement. There is no generic +reservation-expiry event that could release capacity while its owner or bytes +remain live. A committed canonical pin has no expiry. + +Every type has one closed payload branch. A rejected message can be recorded +only after its envelope is safe and bounded enough to append; otherwise the +coordinator rejects it without copying untrusted content into the journal. +Adding, renaming, or changing the meaning of an event requires a new Schema +and replay version. + +### Exhaustive state machines + +Store transitions are: + +```text +NONE -> INITIALIZING -> ACTIVE + | | + v v + RECOVERING <------ + | | + v v + INITIALIZING ACTIVE +``` + +`storage.initialized` enters `INITIALIZING`; +`storage.activation_completed` alone enters `ACTIVE`. +`storage.recovery_started` enters `RECOVERING` from either nonterminal store +status and freezes that exact origin. `storage.recovery_completed` returns +only to the frozen origin, so recovery during migration cannot accidentally +activate an incompletely protected store. While already `RECOVERING`, a +process re-crash appends `storage.epoch_started` for the same +`active_recovery_id`, preserves the origin and unresolved intents, and resumes +that recovery; it never appends a second `storage.recovery_started`. +`storage.epoch_started` outside recovery has null active-recovery identity and +must have null `tail_recovery`; inside recovery its active identity is +non-null and `tail_recovery` is non-null exactly when that restart repaired an +interrupted final frame. It does not change `INITIALIZING` or `ACTIVE`. +`INTEGRITY_FAILURE` is a +fail-closed verifier result rather than an event appended to a chain that +cannot be trusted. + +The independent clock gate is `TRUSTED -> UNCERTAIN -> TRUSTED`, driven by +`storage.clock_uncertain` and `storage.clock_restored`. Clock events do not +change store status. While `UNCERTAIN`, the coordinator issues no new write +handle, finalizes no Transfer, expires no hold or authorization, releases no +reservation based on time, and performs no disposition or GC. It may retain +bytes, revoke access, run bounded verification, and record non-time-based +failure evidence. + +Transfer-attempt transitions are: + +```text +none -> PREPARED -> STREAMING -> VERIFYING -> COMMITTING -> COMMITTED + \ \ \ \ + +----------+------------+-------------+ + | + v + FAILED | CANCELLED | QUARANTINED +``` + +The corresponding events are the `transfer.*` events above. +`transfer.commit_intent_recorded` also creates a provisional Replica in +`COMMITTING`, with the intent's explicit provisional ID and null verification, +and creates the exact `TRANSFER_COMMIT` open-intent projection. +`transfer.committed` atomically makes the Transfer terminal. `NEW_REPLICA` +makes that provisional Replica `AVAILABLE` with the terminal verification and +selects the same ID. `DEDUPLICATED_REPLICA` verifies and selects a distinct +pre-existing `AVAILABLE` Replica without revising it, and makes only the +provisional Replica `ABANDONED`. Failure, cancellation, or quarantine after +intent also makes the provisional Replica `ABANDONED`; it is never selectable. +Every post-intent terminal branch removes the exact open intent. + +Replica transitions are: + +```text +none -> COMMITTING -> AVAILABLE -> STALE + | | | + v v v + ABANDONED VERIFYING <- STALE + | + +-> AVAILABLE | CORRUPT + +AVAILABLE | STALE | CORRUPT | DELETE_FAILED + \ / + -> DELETING -> DELETED | DELETE_FAILED +``` + +Only a verified Transfer can first enter `AVAILABLE`. Verification events +drive the `VERIFYING` branch. `replica.verification_expired` deterministically +moves an `AVAILABLE` Replica to `STALE` at its recorded due time; revalidation +uses `STALE -> VERIFYING -> AVAILABLE | CORRUPT`. Only an authorized GC target +can enter `DELETING`, and the `gc.target_*` event atomically updates both +target and Replica. A failed delete is ineligible until a later explicit +reconcile and new GC plan. + +Materialization transitions are: + +```text +none -> PREPARED -> STREAMING -> VERIFYING -> COMMITTING -> AVAILABLE + | | | | | + +-----------+------------+-------------+ v + | CLEANING + v | + FAILED | QUARANTINED CLEANED | CLEANUP_FAILED +``` + +A materialization that fails verification never becomes visible at its final +destination. `materialization.commit_intent_recorded` is the sole transition +into `COMMITTING` and is durable before any final-destination create or rename. +It also creates `MATERIALIZATION_COMMIT`. Cleanup failure remains distinct from +successful use. Before a terminal Event, every intent-owned residual staging +generation is proven absent, conditionally cleaned, or retained exactly as +`HELD_FOR_DISPOSITION`; terminalization removes the open intent. + +Quarantine bytes follow +`INTENT_RECORDED -> HELD -> INSPECTING -> HELD`, with +`INTENT_RECORDED -> FAILED` when exact recovery proves that isolation cannot +complete. Successful reinspection starts a new Transfer and does not change +the failed original Transfer. +`quarantine.intent_recorded` creates `INTENT_RECORDED` before any backend move +and binds the source staging generation, destination Quarantine identity and +generation, exact reserved bytes and objects, expected Sigil and size when +known, and recovery preconditions. `quarantine.recorded` enters `HELD` only +after the moved bytes and destination generation are durable and verified. +Authorized disposal follows +`HELD | FAILED | DISPOSAL_FAILED -> DISPOSING -> DISPOSED | +DISPOSAL_FAILED`. A new +disposition has its own +`AUTHORIZED -> FAILED` pre-start expiry branch or +`AUTHORIZED -> EXECUTING -> COMPLETED | FAILED` execution branch. +The original `quarantine.recorded` or `quarantine.failed` Event remains +resolvable through every later state. A `DISPOSAL_FAILED` state descended +from `quarantine.recorded` may be treated as retained isolation only when the +exact failed-disposition observation proves that same verified destination +generation still present and isolated. A `DISPOSAL_FAILED` state descended +from `quarantine.failed` remains terminal negative evidence and never gains a +success verification or eligible Blob/Replica status. + +A failed Transfer whose bytes cannot obtain a Quarantine reservation does not +create a Quarantine record and does not claim that bytes vanished. Its staging +projection enters `HELD_FOR_DISPOSITION`, keeps its exact staging generation +and quota charge, and blocks new admission until an exact authorized +disposition removes it or capacity becomes available for a new write-ahead +Quarantine intent. + +A GC plan follows +`DRAFT -> AUTHORIZED -> EXECUTING -> COMPLETED | PARTIAL | ABORTED`. Each +target independently follows +`PLANNED -> SKIPPED | DELETING -> DELETED | FAILED`. Target failure cannot be +rewritten as deletion, and a retry requires a new plan or an explicitly +versioned continuation bound to the unchanged plan and current target. + +Retention policies and Reference Sets are registered once and immutable. Holds +follow `NONE -> ACTIVE -> RELEASED`. Provenance is a one-shot immutable +record. Legacy protection follows `NONE -> PROTECTED | FAILED`; retry after +failure uses a new migration-attempt identity. Quota pressure follows +`CLEAR -> PRESSURED -> CLEAR`. `storage.message_rejected` records no entity +transition, while `blob.incident_recorded` changes only the derived Blob +availability to `INCIDENT`. + +Canonical-reference intents follow +`NONE -> OPEN -> COMMITTED` or `NONE -> OPEN -> RELEASED`. +`OPEN` and `COMMITTED` both pin their exact Blob set. `COMMITTED` has no +outbound transition in v1. `RELEASED` is legal only when the exact +`HEAD_SUPERSEDED_WITHOUT_BOUND_EVENT` authority proves that the conditional +candidate never committed and can no longer commit. An ambiguous intent +remains `OPEN` and is a GC root. `CANONICAL_REFERENCE_REMOVED` is not a v1 +enum or lifecycle branch; a future removal protocol requires a new version +with its own canonical Event family and Receipt rules. + +These transition sets and the events that drive them are exhaustive. Terminal +Transfer attempts, Materializations, GC targets, dispositions, released +holds, disposed Quarantine records, abandoned or deleted +Replicas, policies, Reference Sets, provenance records, and legacy-protection +attempts have no outbound lifecycle-state transition. Exact disposition +Events may revise only the retained residual-object subprojection of a terminal +Transfer Attempt or Materialization while leaving its terminal lifecycle state +unchanged. Restore, retry, repair, reinspection result, or repeated deletion +otherwise uses the new identity required by the relevant state machine and +cites the preceding record. A `DELETE_FAILED` Replica may enter `DELETING` +only through a new independently authorized GC target. + +Open intents are a deterministic projection, never a directory scan: + +| Creating Event | Exact branch and identity | Removing Event set | +| --- | --- | --- | +| `transfer.commit_intent_recorded` | `TRANSFER_COMMIT`, `intent_id == commit_intent.intent_id`, owner Attempt | `transfer.committed`, `transfer.failed`, `transfer.cancelled`, or `transfer.quarantined` | +| `materialization.commit_intent_recorded` | `MATERIALIZATION_COMMIT`, `intent_id == commit_intent.intent_id`, owner Materialization | `materialization.committed`, `materialization.failed`, or `materialization.quarantined` | +| `quarantine.intent_recorded` | `QUARANTINE_MOVE`, `intent_id == quarantine_id`, owner selected by `owner_kind` | `quarantine.recorded` or `quarantine.failed` | +| `gc.target_deletion_started` | `GC_DELETE`, `intent_id == deletion_intent_id`, owner GC plan | matching `gc.target_deleted` or `gc.target_failed` | +| `disposition.started` | `DISPOSITION`, `intent_id == execution_intent_id`, owner disposition | matching `disposition.completed` or `disposition.failed` | +| `canonical_reference.intent_recorded` | `CANONICAL_REFERENCE`, both IDs equal `reference_intent_id` | `canonical_reference.committed` or `canonical_reference.released` | + +Creation sets revision one and both event Sigils as required by +`OpenIntentProjection`. Removal requires an exact ID, owner, source Event, and +object/authorization match; an unknown or mismatched terminal ID is +`INTEGRITY_FAILURE`. `storage.recovery_started.open_intent_ids` is the complete +sorted set of these replayed IDs, and `storage.recovery_completed` can list an +ID as resolved only after its removing Event is durable. +A pre-start `disposition.failed` has a null execution-intent ID and therefore +does not match this table: it neither creates nor removes an open intent. + +### Append, replay, and recovery + +`journal.frames` is a concatenation of binary frames; it is not JSONL. Each +frame has exactly this byte layout: + +```text +offset size value +0 8 unsigned big-endian event-byte length N +8 N canonical UTF-8 JSON event bytes, with no BOM or newline +8+N 32 raw SHA-256 digest of those N event bytes +40+N 8 the same unsigned big-endian N +48+N 8 ASCII bytes "BWSEV1\r\n" +``` + +`N` is in `1..8388608`. The opening length, closing length, raw digest, fixed +marker, canonical JSON bytes, closed Event Schema, event Sigil, chain link, +sequence, and entity revisions must all validate. A frame boundary is +therefore mechanical; a newline, a valid JSON prefix, a filename, or a Head +claim cannot create a frame. + +Before acknowledging a mutation, the coordinator holds the exclusive journal +lock, verifies the complete committed chain and relevant revisions, evaluates +due fences and quotas, validates the event, and makes referenced immutable +material durable. It then writes the complete frame, fsyncs +`journal.frames`, atomically replaces and fsyncs the Head with the new +`committed_byte_length`, fsyncs the storage directory as required by the Host +profile, and only then exposes success or a handle. A backend commit, move, or +delete that can occur before its journal intent is forbidden. + +Replay starts at `storage.initialized`, requires contiguous sequence and event +Sigils, validates every exact Schema and relationship, applies only the state +machines above, recomputes quota counters and Blob availability, and validates +the result against `artifact-storage-state/1.0`. The state projection contains +the current epoch, journal last-event Sigil, all entity revisions and states, +legacy protections, policies, holds, typed reference sets, quota reservations, +GC and disposition state, and derived availability. The catalog is only a +projection of that state. + +The Head is a cache, not authority. A missing or stale Head may be rebuilt from +a chain consisting entirely of complete valid frames. A valid frame after a +stale Head is replayed; it is not discarded merely because Head replacement +did not occur. A conflicting Head, invalid middle frame, complete frame with a +bad digest or marker, unknown event, broken Sigil, illegal transition, revision +gap, impossible relationship, or missing referenced immutable payload puts +storage into `INTEGRITY_FAILURE`. It may run read-only Doctor and evidence +export but may not import, materialize, finalize, authorize disposition, or +delete. + +There is one narrowly defined interrupted-append case. EOF may occur after +one through seven bytes of a final prospective opening length, or after a +complete valid bounded opening length but before all event bytes, digest, +repeated length, and marker have arrived. Recovery may treat that suffix as +uncommitted only when a previously durable, fully valid Head points to a valid +frame boundary at or before the last complete frame, every complete +intervening frame validates, and no byte after the incomplete suffix can form +another frame. A complete opening length of zero or more than the bound is +corruption, not an interrupted append. + +While holding the journal lock, recovery first stores the exact suffix bytes +and its `ORIGINAL_INTERRUPTED_APPEND` evidence record under `recovery/`, then +atomically installs +`recovery/tail-recovery.json` as a valid +`artifact-storage-recovery-marker/1.0` with `phase: EVIDENCE_DURABLE`. The +marker binds that record and the exact `TailRecovery` fields. Only then may it +truncate the suffix to the recorded last-complete boundary, fsync the journal +and directory, and atomically replace the marker with +`phase: TAIL_TRUNCATED`. + +After replaying the complete prefix, recovery constructs exactly one +recovery-entry Event: + +- for an empty prefix with the valid installed `format.json`, empty Journal, + and zero Head, it reacquires the canonical-reference gate, repeats + initialization preflight, requires the project, backend, conformance, + quota-limit, and legacy plan to equal the installed format and current + Chronicle snapshot, and constructs `storage.initialized` with the exact + `TailRecovery` in its otherwise-null `tail_recovery` field; +- for `INITIALIZING` or `ACTIVE`, it constructs + `storage.recovery_started`; and +- for `RECOVERING`, it constructs `storage.epoch_started` bound to the + existing `active_recovery_id`. + +No other replayed prefix is legal. The empty-prefix branch enters +`INITIALIZING` directly and has no synthetic prior Recovery projection; +subsequent migration resumes under the ordinary initialization state machine. +The other two Events carry the exact `TailRecovery` as already defined. + +Recovery then generates one candidate `SE-` ID in memory and constructs the +canonical Event bytes containing that ID. ID allocation has no separately +durable allocator side effect: the ID becomes allocated only when the marker +replacement below commits it. Recovery stores and fsyncs those exact canonical +bytes plus a `RECOVERY_EVENT_SEED` evidence record, then atomically replaces +and fsyncs the marker as +`phase: RECOVERY_EVENT_ID_DURABLE`, binding the event ID, Event Sigil, and seed +record. It writes no frame-template byte before that marker is durable. + +From the marker-bound seed, Recovery constructs the one binary frame, stores +and fsyncs it with a `RECOVERY_FRAME_TEMPLATE` evidence record, and atomically +replaces the marker with `phase: RECOVERY_EVENT_PREPARED`, adding the frame +start, size, frame Sigil, and template record. It may then append only those +exact bytes. After the frame and matching Head are durable, it replaces the +marker with `phase: RECOVERY_EVENT_COMMITTED`, removes the marker, and fsyncs +`recovery/`. All evidence records and raw evidence bytes remain retained. + +Startup checks that one fixed marker path before ordinary replay. A crash in +`EVIDENCE_DURABLE` either completes the same exact truncation or, if the suffix +is already absent and the valid journal ends at the bound, advances to +`TAIL_TRUNCATED`. In `TAIL_TRUNCATED`, no Event identity or template is durable; +Recovery may construct one candidate only through the seed-and-marker +transaction above. An orphan seed left before marker replacement has no +authority and is retained as unreferenced Recovery evidence. A crash in +`RECOVERY_EVENT_ID_DURABLE` resolves the marker-bound seed, verifies its exact +Event ID and Sigil, and creates the same frame template before append; it never +allocates another ID or searches for an orphan seed. + +In `RECOVERY_EVENT_PREPARED`, the journal suffix at the bound must be exactly +empty, the complete prepared frame, or a strict byte prefix of that frame. An +empty suffix retries the exact frame. A complete valid frame rebuilds or +confirms the Head and advances to `RECOVERY_EVENT_COMMITTED`. For a strict +prefix, recovery first stores those exact bytes and a chained +`RECOVERY_FRAME_RETRY_SUFFIX` record, then advances the marker to +`RECOVERY_EVENT_RETRY_EVIDENCE_DURABLE`, atomically incrementing `retry_count` +and binding that record as the evidence-chain head; only that phase authorizes +truncation back to the prepared frame start. After journal and directory fsync +it returns the marker to `RECOVERY_EVENT_PREPARED`, preserving the counter and +chain head, and retries the same frame. A crash before or during that retry +truncation deterministically completes the same operation. A crash in +`RECOVERY_EVENT_COMMITTED` verifies the exact event and Head, then only removes +the marker. + +Any marker, evidence chain, prepared frame, Head, or journal mismatch enters +`INTEGRITY_FAILURE`; recovery never restarts the decision from directory +contents. A missing or conflicting old Head, an invalid length, digest, +repeated length, marker, non-prefix retry suffix, or complete conflicting +frame is not an interrupted append. Doctor never performs this repair. +Best-effort skipping, truncation of a complete unknown frame, truncation +without retained evidence, changing the prepared event on retry, and +reconstruction from directory names are forbidden. + +For a non-empty prefix, Recovery starts a higher coordinator epoch and, after +any proven interrupted append is resolved, appends the applicable +`storage.recovery_started` or `storage.epoch_started` Event defined above. It +resolves every open intent and side effect against exact backend generations, +then appends `storage.recovery_completed` before ordinary mutations resume. An +empty-prefix `storage.initialized` completes only tail repair and enters +`INITIALIZING`; it does not append `storage.recovery_completed`. No branch +infers ownership or completion from a path alone, and an unavailable or +ambiguous backend leaves an initialized store in `RECOVERING` without a new +mutable side effect. +A restart that replays an active Recovery first advances the epoch through the +one bound `storage.epoch_started`, fences any prior coordinator authority, and +continues the same open-intent set plus any newly discovered intent; it cannot +drop a prior intent or change the frozen resume status. + +### Trusted clock and expiry + +Every durable deadline is one authenticated UTC value: Reservation +`expires_at`, Verification `next_due_at`, retention-policy `retain_until`, +Blob `next_verification_due_at`, GC-plan `grace_ends_at`, GC authorization +`authorization_expires_at`, or disposition `expires_at`. Null is legal only +where that field's Schema explicitly permits it. Persisted monotonic ticks are +clock observations, not deadline authority and are never compared across a +process restart or monotonic-domain change. + +On process start, after each new due time, and after clock restoration, the +coordinator first verifies current UTC against the last trusted Journal time +and creates only an in-process anchor: + +```text +(trusted_utc_at_anchor, monotonic_at_anchor, durable_due_at) +remaining = durable_due_at - trusted_utc_at_anchor +``` + +`remaining <= 0` is already due. Otherwise the live timer becomes due at the +earlier of trusted UTC reaching `durable_due_at` or monotonic elapsed reaching +`remaining`. A later wall-clock adjustment cannot extend it. No process-local +anchor is serialized. `ReservationRef.created_clock` and +`remaining_micros_at_creation` preserve the creation observation only and +cannot re-establish future authority after restart. + +If UTC rollback, monotonic-domain loss, arithmetic overflow, excessive +divergence, or unauthenticated time prevents proof that a durable deadline +remains in the future, that deadline is conservatively due. The coordinator +first appends `storage.clock_uncertain`, closes the clock gate, enters or +continues Recovery, and applies due items in ascending +`(due_at, owner kind, owner ID)` order. “Due” revokes or rejects time-limited +authority: it terminalizes an operation through its owning Event, marks +verification stale, ends a GC or disposition authorization, and merely +triggers retention re-evaluation rather than authorizing deletion. +If a Disposition authorization becomes due while its state is still +`AUTHORIZED`, the owning terminal Event is the pre-start +`disposition.failed` branch with null execution-intent ID; it performs no +backend side effect and leaves the target projection unchanged. + +Restoration requires bounded evidence and a fresh process-local anchor, +appends `storage.clock_restored`, and durably resolves every item that cannot +be proven future before another caller mutation. It never restores an old GC, +disposition, Lease, or transfer authority. No caller timestamp, backend +timestamp, filesystem mtime, or object-store date can restore trust or order +an Event. + +## Storage records and lifecycle + +### Blob record + +`artifact-blob/1.0` is a closed replay projection with exactly these top-level +fields: `schema_version`, `blob_sigil`, `size_bytes`, nullable +`first_verified_at`, `availability`, `availability_as_of`, +`availability_basis_sigil`, `effective_policy_set_sigil`, nullable +`next_verification_due_at`, sorted +`known_replica_ids`, sorted `eligible_replica_ids`, sorted +`integrity_event_sigils`, bounded sorted +`media_type_observations`, bounded sorted sanitized +`filename_observations`, `revision`, and `record_sigil`. The record Sigil is +canonical JSON over every field except itself; Blob identity remains +`blob_sigil`, not the changing record Sigil. `first_verified_at` is null only +for an identity that has never had an eligible verified Replica. Descriptive +media-type or filename observations never participate in Blob identity. +Availability is a total function evaluated in this exact priority order: + +1. `INCIDENT` when any unresolved collision, catalog contradiction, or + integrity incident affects the Blob; +2. otherwise `UNAVAILABLE` when the eligible Replica count is zero; +3. otherwise `DEGRADED` when one or more eligible Replicas exist but their + count, backend distribution, integrity freshness, or another merged active + retention requirement is unmet; and +4. otherwise `AVAILABLE`. + +No Blob can satisfy more than one branch. A Replica is eligible only while it +is `AVAILABLE`, its generation and byte identity verify, and its explicit +verification deadline has not expired. State replay evaluates availability as +of the latest applicable direct or fan-out event at the verified State Head, +records that complete `EventRef` in `availability_as_of`, and resolves its +`recorded_at` only by verified Journal replay; it never stores a Timestamp in +that field or consults retrieval time. `availability_basis_sigil` covers the +exact sorted applicable +Replica revisions, integrity events, active policy records, holds, reference +roots, verification deadlines, and trusted clock event used by that +calculation. + +Blob `revision` advances only for an event that directly owns that Blob or one +of its Replica relationships. Project-, Program-, or Reference-Set-scoped +policy and hold events may change many derived Blob views; replay recomputes +those records and their `record_sigil`, `availability_basis_sigil`, and wrapper +`last_event_sigil` without fabricating thousands of `BLOB` revision +assignments. Such fan-out is deterministic from the policy/hold entity +revision and Storage Head and grants no entity-level compare-and-swap +authority. A caller that needs concurrency control compares the Storage Head +and basis Sigil, not the intrinsic Blob revision alone. + +Before any operation selects a Replica or changes storage state, the +coordinator evaluates due verification deadlines under the Journal lock and +appends `replica.verification_expired` before the requested operation. A +strictly read-only view may additionally report `currently_eligible: false` +when trusted current time is past a recorded deadline, but that view is not +the State projection or State Sigil and grants no authority. + +These states make no canonical or scientific claim. + +### Transfer lifecycle + +Every transfer has an immutable request identity and one or more unique attempt +identities. `artifact-transfer/1.0` is the immutable `ST-` request; +`artifact-transfer-attempt/1.0` is one `SA-` execution of that request. An +event, projection, API result, or test must use `transfer_id` only for the +request and `transfer_attempt_id` only for an attempt; neither identifier may +stand for both. The attempt's append-only projection follows: + +```text +PREPARED -> STREAMING -> VERIFYING -> COMMITTING -> COMMITTED + | | | | + +----------+-------------+-------------+ + v + FAILED | CANCELLED | QUARANTINED +``` + +`COMMITTED` is valid only when the complete logical bytes, size, expected +identity policy, backend commit, and required post-write verification all +pass. `FAILED`, `CANCELLED`, and `QUARANTINED` are terminal for that transfer +attempt. Retry creates a new attempt and retains the prior terminal record. + +The Attempt's `commit_intent` is null before +`transfer.commit_intent_recorded` and is the exact `TransferCommitIntent` +thereafter. `residual_staging_cleanup` is `PENDING` while an intent-owned +residual generation may still require classification; every terminal record +uses `NOT_REQUIRED`, `CLEANED`, or `HELD_FOR_DISPOSITION` with the field/null +matrix defined by `ResidualStagingCleanup`. `FAILED` is a nonterminal Recovery +observation and cannot appear in a terminal Attempt. No terminal record can +leave cleanup `PENDING`. + +The Attempt staging projection is also exact. `PREPARED` has +`staging_state: NOT_CREATED` and null `staging_object`; `STREAMING`, +`VERIFYING`, and `COMMITTING` have `PRESENT` and the exact non-null generation. +A terminal Event with cleanup `NOT_REQUIRED` or `CLEANED` has `MISSING` and +retains the exact historical `staging_object` unless its explicit pre-intent +failure branch is `NOT_CREATED`; cleanup `HELD_FOR_DISPOSITION` has the +identically named staging state and object. Only `COMMITTED` has a non-null +`selected_replica_id`, and only `QUARANTINED` has a non-null `quarantine_id`. +The former has null terminal reason; `FAILED`, `CANCELLED`, and `QUARANTINED` +have the exact non-null payload Reason. + +The request binds: + +- direction and operation purpose; +- source and destination classes; +- expected Sigil when one is pinned; +- maximum byte count, duration, and optional file-count limits; +- backend profile ID, version, and Sigil; +- authorization and idempotency identity; +- Job, Attempt, Lease, and public RFC-0012 fence tuple when execution is the + source or sink; secret Lease credentials and proofs are never journaled; +- required verification method; and +- provenance and retention policy references. + +Reuse of an idempotency key with any changed bound field is rejected. Replaying +an exactly matching committed request returns the existing outcome without +performing a second mutable write. + +The stored public fence tuple is exactly `(execution_journal_id, +executor_epoch, job_id, attempt_id, lease_id, fencing_generation)`. Every +side-effecting storage event also binds the execution-journal event Sigil at +which the coordinator verified that tuple, the then-current Job fence floor, +and whether a later tombstone already existed. A generation alone, or a tuple +without current journal evidence, is never sufficient. + +### Replica lifecycle + +A Replica becomes `AVAILABLE` only after a committed transfer identifies its +Blob and records backend object identity, immutable generation or version, +size, and verification evidence. Later checks append observations. A Replica +may become `VERIFYING`, `CORRUPT`, `DELETING`, `DELETED`, or `DELETE_FAILED`; +it never changes from one Blob identity to another. `QUARANTINED` is not a +Replica state: quarantined bytes have a separate Quarantine identity and can +become a Replica only through a new verified Transfer. + +A replacement or repaired copy is a new Replica with a fresh backend +generation whose transition is explicit and conditionally verified. Backend +overwrite of an `AVAILABLE` Replica is forbidden even when the claimed Sigil +is unchanged. + +### Materialization lifecycle + +A materialization record binds the source Blob and Replica verification, +destination class, normalized destination, Task and Attempt where applicable, +requested access mode, created time, and cleanup result. Materialization is +atomic from the consumer's perspective: the destination is absent until its +complete bytes have been verified and finalized. + +The record independently tracks consumer-destination cleanup in `cleanup` and +commit-time staging cleanup in `residual_staging_cleanup`; one cannot stand in +for the other. A committed destination may become visible with a residual +staging generation only when that residual is durably +`HELD_FOR_DISPOSITION`, inaccessible, and charged. Failed Materialization +staging and failed final destinations use the two exact materialization +disposition target kinds and remain charged until their completed Event. + +Before a final destination exists, and in `QUARANTINED`, `cleanup` is +`NOT_REQUIRED`. `materialization.committed` and +`materialization.cleanup_started` store `PENDING`; +`materialization.cleaned` stores `CLEANED` with the payload's cleanup-evidence +Sigil; and `materialization.cleanup_failed` stores `FAILED` with the payload's +cleanup-evidence Sigil and Reason. A `materialization.failed` projection uses +the exact `destination_cleanup` object and state/object matrix in its Event +branch. Thus `FAILED` or `CLEANUP_FAILED` plus `cleanup: FAILED` and the +intent-bound destination object is the durable held-destination condition that +`MATERIALIZATION_DESTINATION` may authorize; it is never inferred from a path. + +Sanctum inputs are read-only at the policy boundary. A writable Crucible copy +is a derived mutable workspace, not the source Replica. Hard-linking an +untrusted or writable destination to a content-addressed backend object is +forbidden because mutation through either name would corrupt the Blob. +Copy-on-write or reflink materialization is allowed only when conformance tests +prove that destination mutation cannot change stored bytes. + +## Trusted import + +"Trusted import" means that Benchwork establishes and retains the declared +identity, source binding, policy decision, and transfer evidence. It does not +mean that imported content is safe, authentic, licensed, scientifically valid, +or worthy of canonical acceptance. + +An import follows this sequence: + +1. Ward or another authorized control-plane caller submits a closed import + request with source class, bounds, expected identity policy, destination + backend, provenance, and idempotency identity. +2. The storage coordinator allocates a fresh transfer-scoped staging namespace. + The source cannot choose a final backend key. +3. The coordinator opens the source without following an unvalidated path, + streams bytes while enforcing byte and time limits, and computes SHA-256 and + byte count over the received logical bytes. +4. The coordinator closes the source, verifies completeness, compares any + expected Sigil and size, and records the computed result before commit. +5. It appends a recoverable commit intent, conditionally creates or reuses the + content-addressed backend object, and verifies the committed logical bytes. +6. Only then does it append `COMMITTED`, create or confirm the Replica, and + make the Blob eligible for later materialization or proposal submission. + +If the caller supplies an expected Sigil, exact match is mandatory. If no +prior Sigil exists, Benchwork may capture the bytes under the newly computed +identity only when the import policy permits `CAPTURE_NEW_IDENTITY`. Provenance +must say that the identity was first observed at ingest. Such a capture cannot +satisfy a Task input or other contract that pinned an identity before +execution. + +Local path import rejects paths outside the authorized scope, path traversal, +symlink or hard-link ambiguity, special files, source replacement during read, +and size changes. It must either hold a stable file handle and verify source +metadata before and after the read or copy from an isolation mechanism that +provides equivalent stability. A directory is rejected unless a declared +bundle builder handles it under the closed manifest rules. + +Worker output import uses an Attempt-scoped write handle. The Worker receives +neither backend credentials nor a final Blob key. Finalization requires the +current unexpired Lease and authenticated Worker-protocol authority. At A2, +the backend validates the complete public tuple, its highest accepted Executor +epoch, the matching Job fence floor, and absence of a higher tombstone on +every direct side-effecting operation. A backend that cannot do so may only +permit writes to Attempt-isolated staging that the trusted coordinator alone +can commit. Immediately before final commit, the coordinator replays the +execution journal and requires the same journal, epoch, Job, Attempt, Lease, +generation, active Lease state, fence floor, and tombstone status to remain +eligible. Lease credentials and proofs are ephemeral secrets and never enter a +Transfer, provenance record, or log. Late, fenced, over-limit, partial, or +policy-violating output is ineligible for the Job result. The coordinator +preserves its bytes in quarantine only after reserving the applicable bounded +quarantine capacity. If capacity is unavailable, it +retains the failed Transfer with +`staging_state: HELD_FOR_DISPOSITION`, does not expose the bytes, keeps the +exact staging reservation charged, and applies storage backpressure until a +separately authorized disposition or later Quarantine reservation resolves +the staging object. + +### Closed `ATTEMPT_OUTPUT` ingest binding + +An `artifact-transfer/1.0` request uses `purpose: ATTEMPT_OUTPUT` if and only +if the Executor preallocated it for one current RFC-0012 Lease-scoped output +handle. It becomes eligible for an execution output root only when one +accepted Result output selects that exact pair and the later +`execution-storage-root-manifest/1.0` entry resolves it. The following matrix +is closed; no other direction, descriptor, execution branch, fence, handle, +Blob, backend, or historical-transfer substitution is legal: + +| Surface | Exact value | Required equalities | +| --- | --- | --- | +| Direction and purpose | `direction: INGEST`, `purpose: ATTEMPT_OUTPUT` | Either value selects this entire matrix; it cannot be combined with any other row of the source/destination space. | +| Source | `ATTEMPT_OUTPUT {kind, execution, output_handle_id}` | `source.execution` equals the request's top-level `execution` byte-for-byte. | +| Destination and backend | `MANAGED_BACKEND {kind, backend}` | `destination.backend == transfer.backend` byte-for-byte. The Attempt staging object, commit-intent target object, terminal Event backend object, and selected Replica all use that `backend_id`; the selected Replica's complete `backend` equals the request backend. | +| Execution and public fence | `LEASED {kind, execution_journal_id, executor_epoch, job_id, attempt_id, lease_id, worker_id, worker_session_id, fence}` | The imported RFC-0012 execution identifiers equal the accepted Result's owner and current Lease. `fence.execution_journal_id`, epoch, Job, Attempt, Lease, and generation equal the Result `fence_tuple`'s `journal_id` and same-named fields; its additional execution-Event Sigil, Job fence floor, and tombstone status authenticate that exact current tuple. Finalization revalidates all of them. `ATTEMPT`, `NONE`, a prior Lease, or a merely equal fence Sigil is invalid. | +| Result staging reference | `ATTEMPT_OUTPUT {kind, storage_subject_id, output_handle_id, transfer_id, transfer_attempt_id}` | This is the exact closed branch in the accepted `execution-result/1.0` output. `source.output_handle_id` equals its opaque `output_handle_id`; `storage_subject_id` equals `Sigil(["execution-attempt-output-storage-subject-id/1.0", job_id, attempt_id, output_handle_id])` and the ESM entry's `storage_subject_id`. Its `transfer_id` and `transfer_attempt_id` equal the current `ST-ID` and `SA-ID`, and its canonical-JSON Sigil equals the ESM subject's `staging_reference_sigil`. A path, URL, backend locator, Replica ID, another Attempt's handle, or equality between the handle and derived subject is invalid. | +| Blob and bound | Non-null `expected_blob_sigil`; exact request byte bound | `expected_blob_sigil` equals the Result output and ESM subject `blob_sigil`; `bounds.max_bytes` equals that output's `byte_size`. The Transfer Attempt's computed Sigil and size, commit-intent `computed_blob`, terminal Event Blob and size, selected Replica, provenance Blob, ESM `claimed_blob`, and committed Blob record all equal that pair. | +| Current `TransferRef` | `transfer_id`, `transfer_attempt_id`, `request_record_sigil`, `attempt_record_sigil`, `terminal_event` | The IDs are the Result branch's current pair. The two record Sigils resolve this immutable request and its terminal Attempt record. `terminal_event` is the exact Storage Event that terminalized that Attempt and carries the same IDs; no field is reconstructed from a Replica or Blob. | +| Current provenance | One `artifact-provenance/1.0` record with `relation: CAPTURED` | Its Blob, source, destination, authorization, execution, backend, and `transfer` equal this request, committed Blob pair, and current `TransferRef`; `transformation` is `NONE`. For a committed output, `terminal_reason` is null and `verification_sigils` is the one-member array containing the terminal exact-generation verification evidence Sigil. Its ID and record Sigil are the pair carried by the ESM entry. | + +For a committed output, the physical outcome matrix is also exact: + +| `transfer.committed.outcome` | Selected Replica | Current Transfer and provenance | +| --- | --- | --- | +| `NEW_REPLICA` | `replica_id == provisional_replica_id`; its `created_by_transfer_attempt_id` equals the current `SA-ID`. | The ESM `TransferRef` and `CAPTURED` provenance use the current Result-bound `ST-ID`/`SA-ID` and this terminal Event. | +| `DEDUPLICATED_REPLICA` | `replica_id != provisional_replica_id`; the selected `AVAILABLE` Replica and its historical `created_by_transfer_attempt_id` remain unchanged, while only the provisional Replica becomes `ABANDONED`. | The ESM still carries a new current `TransferRef` and current `CAPTURED` provenance for the Result-bound `ST-ID`/`SA-ID`. The historical Replica creator, its request or Attempt Sigil, its terminal Event, and any older provenance record cannot populate or stand in for any current field. | + +An ESM `QUARANTINE` origin likewise carries the current Result-bound +`TransferRef` and current `CAPTURED` provenance for the attempted Blob; its +terminal reason and Quarantine owner resolve that same `SA-ID`. A +`NOT_STORED` origin carries the current failed or cancelled `TransferRef` when +one was started and carries null only when no Transfer was started; it cannot +borrow older provenance or a byte-equal Replica to claim storage success. + +Staging is the normal, incomplete state of every import and is not quarantine. +It has a Transfer owner, reservation, deadline, generation, and recoverable +state. Failed staging bytes may move into a fresh Quarantine generation only +after the coordinator reserves the exact byte and object bound and durably +appends `quarantine.intent_recorded`. The backend move then uses the exact +source and destination generations in that intent. After destination +durability and readback verify, `quarantine.recorded` changes the Quarantine +to `HELD`; only then may `transfer.quarantined` terminalize the attempt and +settle the staging reservation. It releases source claims only when +`source_cleanup` proves the source absent or cleaned; an exact residual remains +charged as `HELD_FOR_DISPOSITION`. Recovery resolves a crash after the intent +from those exact generations and never from a pathname guess. Staging paths +cannot be relabelled as quarantine merely to bypass quotas, and neither +staging nor quarantine can be selected as a Replica. + +At `quarantine.intent_recorded`, the projection's `source_cleanup` is +`PENDING` for the exact source object. `quarantine.recorded` or +`quarantine.failed` replaces it with the payload's legal terminal cleanup +object byte-for-byte. Reinspection and destination disposition preserve that +source-cleanup evidence. A later owner-staging disposition updates only the +owner's repeated residual subprojection; the Quarantine retains the terminal +move evidence that was true when it completed. +The Quarantine `source_object` is null exactly when terminal source cleanup is +`NOT_REQUIRED`; `PENDING`, `CLEANED`, and `HELD_FOR_DISPOSITION` retain the +intent's exact source reference. Its `destination_object` is null exactly for +a terminal `quarantine.failed` `NOT_CREATED` destination. An intent or failed +projection otherwise retains the intent destination reference exactly; a +recorded projection retains the terminal verified reference with the same +intent-bound backend identity, locator, generation, and size and its exact +verified Blob Sigil, including after disposal as tombstone identity. + +An existing `artifact/1.0` may be copied into managed storage only after the +coordinator verifies the Chronicle record, registration Receipt, root-contained +resolved location under the existing v1 rule, and exact location Sigil. This +creates operational +Blob, Replica, and provenance records. It does not migrate, replace, or update +the Artifact or permit removal of its recorded local file. + +## Trusted export and materialization + +An export selects source bytes by Blob Sigil and size, never by an unverified +mutable path or a human filename. The coordinator: + +1. authorizes the exact destination class and normalized destination; +2. selects an eligible Replica under the required backend and freshness + policy; +3. streams the complete logical bytes while recomputing SHA-256 and size; +4. writes into a fresh destination staging name with explicit bounds; +5. atomically finalizes with no-clobber or exact generation preconditions; +6. verifies the destination by readback or a conformance-approved end-to-end + checksum over the same logical bytes; and +7. records a closed export manifest and cleanup outcome. + +The export manifest binds the Blob Sigil and size, source Replica and +verification evidence, destination descriptor without credentials, transfer +request and attempt IDs, backend profiles, authorization, timestamps, +idempotency key, bytes observed, verification method, and result. + +A sink acknowledgement, TLS success, HTTP status, object-store ETag, file +existence, process exit code, or matching filename is insufficient by itself. +An ETag may count as content verification only when the exact backend profile +defines it as a cryptographic digest over the same logical bytes and the +conformance suite proves that behavior for the selected operation. + +Export never overwrites an unrelated destination. An existing destination may +be treated as an idempotent success only after its exact bytes and request +binding are verified. Otherwise the operation fails without changing the +destination and retains the conflict record. + +Materializing a Blob as a stable project-relative file can prepare a later +`artifact/1.0` registration. The materialization must use a durable independent +copy, preserve the exact Sigil, and pass destination readback before Athanor is +called. Artifact registration remains a separate explicit transition with an +`AR-` ID, Program, kind, producer, inputs, and Receipt. The storage coordinator +cannot call that transition merely because export succeeded. + +## Transfer integrity, atomicity, and recovery + +Every transfer computes an end-to-end digest and byte count at the Benchwork +trust boundary. Transport encryption and backend checksums are additive +controls, not substitutes for content identity. + +Chunked and resumable transfer is allowed only when the transfer contract +binds: + +- the immutable source Blob identity or a transfer-scoped source snapshot; +- exact offset and total size; +- verified prefix or chunk identities; +- backend upload or generation identity; +- the same request, authorization, Lease, and fencing context; and +- a resume proof that no accepted byte range changed. + +Out-of-order, overlapping, missing, duplicated with different bytes, or +unverifiable chunks fail the attempt. Final identity is always recomputed or +cryptographically combined under a separately specified algorithm over the +complete logical byte sequence; a list of individually valid chunks is not +automatically the Blob identity. + +Publication uses a recoverable intent: + +1. append a commit intent containing staging identity, computed Sigil and size, + backend profile, target key derivation, and preconditions; +2. conditionally finalize the backend object without overwriting an existing + generation; +3. verify the finalized logical bytes and durability evidence; and +4. append the committed Replica and Transfer records. + +That commit intent is also the sole rollback authority for conditional removal +of its exact never-published provisional target generation after the execution +fence becomes ineligible or verification fails. It never authorizes deletion +of a pre-existing generation or the selected DEDUP Replica. Absence or exact +rollback evidence is retained by the terminal Event; an ambiguous or failed +rollback leaves the intent open in Recovery. + +After a crash, recovery replays the Storage Journal. It does not infer success +from a filename or object key. For each unresolved intent it verifies the +staging and final objects, generation, Sigil, size, fencing eligibility, and +backend response. It may complete the exact pending commit, record an +idempotent pre-existing Replica, or quarantine the bytes. Ambiguity fails +closed. + +The recovery decision for every nonterminal Transfer attempt is exact: + +| Durable attempt state | Verified backend observation | Required recovery action | +| --- | --- | --- | +| `PREPARED`, `STREAMING`, or `VERIFYING` | no `transfer.commit_intent_recorded` exists | No final object could legally have been created. Append `transfer.failed` with `RECOVERY_INTERRUPTED_PRECOMMIT`; release an unused reservation, but retain any exact staging generation as `HELD_FOR_DISPOSITION` or move it only through a new durable Quarantine intent. | +| `COMMITTING` | intended final generation exists, independently verifies against the intent, and the execution fence remains eligible | Append `transfer.committed`, creating the intended Replica or selecting the exact already named verified Replica. | +| `COMMITTING` | final object is absent, the exact intended staging generation exists, and the execution fence remains eligible | Revalidate every intent precondition, perform the one conditional finalize, read back, and append `transfer.committed`. | +| `COMMITTING` | exact final generation and exact residual staging generation both exist, and the execution fence remains eligible | Conditionally clean only the intent-bound residual staging generation, verify that cleanup or retain its exact failure/held disposition, then append `transfer.committed` from the verified final generation with the closed `residual_staging_cleanup` outcome. | +| `COMMITTING` | the execution fence is now stale, tombstoned, or otherwise ineligible | Never append `transfer.committed` for this Attempt. Leave an already journaled pre-existing Replica unchanged. Prove the provisional target was never created or conditionally remove only its exact unpublished generation under the commit intent; if that cannot be authenticated, remain `RECOVERING`. Then isolate the exact staging generation through a new Quarantine intent or retain it `HELD_FOR_DISPOSITION`, and terminalize the Transfer as fenced and ineligible. | +| `COMMITTING` | neither exact final nor exact staging generation exists | Append `transfer.failed` with `RECOVERY_PAYLOAD_LOST`, retain the missing-generation evidence, and make no Replica available. | +| `COMMITTING` | an object exists with a different generation, size, Sigil, namespace identity, or ownership | Append the applicable incident and `transfer.failed`; quarantine only a separately identified exact generation through the Quarantine protocol. Never overwrite or adopt the conflicting object. | +| any nonterminal state | the backend observation, execution fence, or durability result is unavailable or ambiguous | Remain `RECOVERING`, retain the unresolved intent and reservation as roots, and perform no finalize, move, cleanup, or terminal transition. | + +Every `COMMITTING` row therefore replays the execution journal before +classification. A generation observed at the intended final key is not proof +that its side effect preceded a later fence, and recovery never backdates it. + +Materialization recovery is independently exhaustive: + +| Durable materialization state | Verified destination observation | Required recovery action | +| --- | --- | --- | +| `PREPARED`, `STREAMING`, or `VERIFYING` | no `materialization.commit_intent_recorded` exists | No final destination could legally have been created. Append `materialization.failed` with `RECOVERY_INTERRUPTED_PRECOMMIT`; retain an exact staging generation as `HELD_FOR_DISPOSITION` or move it only through a new Quarantine intent. | +| `COMMITTING` | the exact intended final generation exists, readback matches the source Blob, and the execution fence remains eligible | Append `materialization.committed` for that exact intent and generation; do not write again. | +| `COMMITTING` | final is absent, the exact staging generation exists, and the execution fence remains eligible | Revalidate the intent and destination no-clobber precondition, perform the one conditional finalize, read back, and append `materialization.committed`. | +| `COMMITTING` | exact final and exact residual staging generations both exist and the fence remains eligible | Conditionally clean only the exact residual staging generation first. Append `materialization.committed` only after recording `CLEANED`, or after proving the generation remains exact and inaccessible and recording `HELD_FOR_DISPOSITION`; store that complete result in the Event and Materialization. | +| `COMMITTING` | the fence is stale, tombstoned, or otherwise ineligible | Never append `materialization.committed`. Isolate the exact staging generation through a new Quarantine intent or retain it `HELD_FOR_DISPOSITION`; retain every exact final destination as `HELD_FOR_DISPOSITION` in the distinct destination field, then append the fenced `materialization.failed` branch. | +| `COMMITTING` | neither exact final nor exact staging generation exists | Append `materialization.failed` with `RECOVERY_PAYLOAD_LOST`, retain missing-generation evidence, and expose no destination handle. | +| `COMMITTING` | an object exists with a different generation, identity, size, Sigil, namespace, or ownership | Record the applicable incident and append `materialization.failed`; never overwrite, adopt, or expose the conflicting object. | +| any nonterminal state | destination, staging, fence, or durability observation is unavailable or ambiguous | Remain `RECOVERING`, retain the open intent and reservation, expose no destination handle, and perform no finalize, cleanup, or terminal transition. | + +The destination namespace stays inaccessible to its intended consumer until +`materialization.committed` and the matching Head are durable. Recovery checks +the current execution fence in every `COMMITTING` row. A filesystem name, +successful rename response, or matching bytes without the exact intent and +generation is not a materialization commit. + +Recovery of `quarantine.intent_recorded` uses the same closed observation +discipline. Exact destination present and verified permits +`quarantine.recorded`; exact source present with destination absent permits the +one intent-bound conditional move followed by readback. When both are present, +Recovery conditionally cleans only the exact residual source generation before +the terminal Event. It appends `quarantine.recorded` only with `CLEANED`, or +with `HELD_FOR_DISPOSITION` after proving the source remains exact, +inaccessible, and quota-owned; an unavailable cleanup result remains +`RECOVERING`. Neither object present produces +`quarantine.failed` with retained loss evidence. A generation, identity, or +byte mismatch records an incident and `quarantine.failed`; any still-present +exact generation remains inaccessible and quota-charged until a separately +authorized disposition. An unavailable or ambiguous observation remains +`RECOVERING`. No branch relabels a pathname, guesses ownership, or releases a +reservation without a durable event. + +Recovery of an open `GC_DELETE` is exhaustive: + +| Verified observation after `gc.target_deletion_started` | Required action | +| --- | --- | +| exact approved generation is present, authorization is provably future, and current root/policy revalidation still permits deletion | Retry only the same conditional exact-generation delete, prove the post-delete absence, and append `gc.target_deleted` with the same deletion-intent ID. | +| exact generation is present but authorization is due or current root/policy revalidation blocks deletion | Perform no delete; append `gc.target_failed` with `DISPOSITION_EXPIRED` or `GC_ROOT_CHANGED`, move the Replica to `DELETE_FAILED`, and retain the bytes. | +| exact generation is absent and exact-generation stat plus the durable started Event proves the approved target is the generation now absent | Append `gc.target_deleted` with bounded absence evidence. This records the authorized target's physical absence without asserting an unobserved backend acknowledgement. | +| a different generation, identity, size, Blob Sigil, namespace ancestor, or owner is present | Do not delete it; append the applicable incident and `gc.target_failed` with `GENERATION_MISMATCH` or `BACKEND_CONFLICT`. | +| exact-generation presence, absence, durability, root state, or authorization cannot be authenticated | Remain `RECOVERING`; retain `GC_DELETE` and its Reservation; append no target terminal Event. | + +Recovery of an open `DISPOSITION` is independently exhaustive: + +| Verified observation after `disposition.started` | Required action | +| --- | --- | +| exact authorized generation is present and authorization is provably future | Retry only the same conditional exact-generation delete, prove absence, and append `disposition.completed` with the same execution-intent ID. | +| exact authorized generation is present but authorization is due | Perform no delete; append `disposition.failed` with `DISPOSITION_EXPIRED`; retain the target and its usage charge. | +| exact authorized generation is absent and exact-generation stat plus the durable started Event proves that exact target is now absent | Append `disposition.completed` with bounded absence evidence and remove only the matching charged usage. | +| a different generation, identity, size, Sigil, namespace ancestor, or owner is present | Do not delete it; append the applicable incident and `disposition.failed` with `GENERATION_MISMATCH` or `BACKEND_CONFLICT`. | +| exact-generation presence, absence, durability, or authorization cannot be authenticated | Remain `RECOVERING`; retain `DISPOSITION` and its Reservation; append no terminal Event. | + +Both matrices use the Event-bound backend object, not listing or path +inference. The absent branches are legal because the started Event proves the +exact generation was revalidated immediately before deletion authority; they +still require fresh exact-generation absence evidence. A terminal Event +atomically removes the matching `OpenIntentProjection`, settles only its +Reservation, updates the owner projection, and records the deletion or failure +outcome. Recovery never substitutes a new intent ID, generation, target, or +authorization. +An `AUTHORIZED` Disposition whose authorization is due before +`disposition.started` is outside the open-intent matrix: Recovery appends only +the null-intent `disposition.failed` branch, settles its Reservation, and +performs no target observation, mutation, or open-intent operation. + +The backend must make a committed object visible atomically or keep the object +outside the readable Replica namespace until the coordinator records it. +Readers never observe staging objects as available Blobs. A backend whose +consistency model cannot satisfy that rule is ineligible for committed +Replica use under this profile. + +Cancellation and Lease loss stop new transfer activity. At A2, they revoke the +Worker's output handle and prevent finalization. An immutable identical Blob +that was already committed may remain as unreferenced operational storage, but +the fenced Attempt's provenance and result remain ineligible. A late attempt +cannot borrow a Replica committed by another attempt to fabricate eligible +completion. + +## Provenance + +Content identity answers "which bytes"; provenance answers "how Benchwork +observed, moved, or derived them." Provenance is append-only and is not folded +into Blob identity. + +`artifact-provenance/1.0` records, as applicable: + +- provenance event ID and relation type such as `CAPTURED`, `IMPORTED`, + `COPIED`, `DERIVED`, `EXPORTED`, `MATERIALIZED`, `VERIFIED`, or `DELETED`; +- Blob Sigil and size plus source and destination classes; +- source Artifact ID and registration Receipt, source Blob or Replica, or + sanitized external source descriptor; +- actor and control-plane authorization; +- Job, Attempt, Lease, public fence tuple and tombstone evidence, Worker, + Executor, and realized assurance claim when execution is involved; +- backend profile, transfer request and attempt, and verification evidence + Sigils; +- transformation contract, implementation version and Sigil, parameters, + input Blob identities, and runtime identity for derived bytes; +- observed, started, committed, verified, and terminal times; and +- policy violations, missing evidence, and quarantine or deletion reason. + +Credentials, signed query strings, bearer tokens, secret environment values, +and unredacted sensitive headers never enter provenance. A sanitized descriptor +must remain sufficient to distinguish the source class and stable object +identity without enabling access. + +Deduplication retains each provenance event even when no new physical bytes +are written. A transformation always produces a derived provenance edge and a +new output identity unless its exact output bytes happen to equal an existing +Blob; in that case the new derivation edge is still retained. + +Operational provenance cannot replace the canonical `artifact/1.0` +`producer_id` or `input_ids`. Matching a Job, Attempt, path, filename, or Blob +Sigil does not infer Program lineage. Athanor separately validates canonical +producer and input objects in the same Program. + +The following evidence levels remain distinct: + +| Evidence | What it establishes | What it does not establish | +| --- | --- | --- | +| `IDENTITY_COMPUTED` | Benchwork computed a Sigil and size for captured bytes | A prior party committed to that identity | +| `IDENTITY_MATCHED` | Captured bytes match a previously pinned Sigil and size | Source authenticity or scientific validity | +| `SOURCE_BOUND` | The transfer record binds the sanitized source and authorization | That the source statement is true | +| `TRANSFER_VERIFIED` | Source and destination logical bytes match end to end | Long-term availability or canonical acceptance | +| `CANONICALLY_REFERENCED` | Athanor accepted a transition that refers to the bytes under a defined contract | Safety, correctness, or indefinite retention | + +No Provider, Worker, Executor, storage adapter, or checksum service can issue +a scientific acceptance Receipt. + +## Storage backend contract + +An Artifact storage backend is a privileged operational adapter behind the +storage coordinator. It is not a Worker tool, canonical authority, general +filesystem API, or general object-store proxy. + +`artifact-storage-backend/1.0` declares a closed profile containing: + +- backend ID, implementation and protocol version, build or package Sigil, and + configuration Sigil with secrets excluded; +- namespace and tenant or project isolation semantics; +- maximum object size and supported transfer bounds; +- conditional-create, no-overwrite, immutable-generation, and atomic-visibility + behavior; +- read-after-write and list consistency; +- logical-byte readback and checksum capabilities; +- range and resume behavior; +- durability and fsync or remote-commit semantics; +- deletion preconditions and retention-lock behavior; +- supported encryption or compression transparency; +- fencing support for side-effecting sinks; +- reconciliation and corruption-reporting capabilities; and +- conformance-suite identity, version, Sigil, Host platform, and evidence + Sigil. + +Unknown profile fields or capability values fail closed. A mechanism name, +cloud brand, filesystem, container, encryption claim, or advertised checksum +does not establish conformance. + +The coordinator-facing interface is content-scoped and must support the +equivalent of: + +| Operation | Required boundary | +| --- | --- | +| `begin_stage` | Allocate a fresh bounded staging object under a transfer identity and optional fence. | +| `write_stage` | Write bounded bytes without selecting or overwriting a final key. | +| `finalize_if_absent` | Atomically bind verified staged bytes to a digest-derived key or return the exact existing generation. | +| `open_read` | Stream logical bytes from one exact Replica and generation. | +| `stat` | Return size, generation, state, and backend metadata without treating metadata as verified content. | +| `verify` | Provide readback required for the coordinator to validate the complete logical bytes. | +| `delete_if_generation` | Delete one exact generation only under its matching GC-delete Event, authorized disposition, or unpublished provisional-target rollback intent. Prefix, wildcard, and unresolved-path deletion are forbidden. | +| `reconcile` | Enumerate or prove managed objects for recovery without making them available before verification. | + +Backend keys are derived by trusted code from the verified Sigil and backend +namespace. Callers and Workers cannot provide arbitrary keys. Locators remain +opaque operational metadata and are never interpreted as proof of content. + +The adapter must return typed bounded results. Backend claims are untrusted +until the coordinator validates their Schema, request binding, generation, +size, and required readback. An adapter cannot append Chronicle, grant a Ward +approval, manufacture missing provenance, register an Artifact, or elevate a +Sanctum assurance claim. + +At A2, the Worker cannot access backend credentials, the Storage Journal, +catalog, quarantine, or committed namespace. The storage coordinator and +backend join the operational trusted computing base for any realized claim +that depends on their enforcement. A backend without per-write fencing may +accept only Attempt-isolated staging to which the Worker loses access; the +trusted coordinator validates the current fence before final commit. + +The `0.4` profile permits only the built-in local backend. Arbitrary executable +Storage adapters remain outside the public SDK until signature, supply-chain, +permission, isolation, and conformance policy are accepted. Phase 4 may add +pluggable remote adapters behind this interface; no particular remote provider +is required. + +## Conformance profiles and release boundary + +Conformance is profile-specific. Evidence for one profile cannot be relabelled +as evidence for another, and an implementation may advertise only the exact +profile and suite Sigil recorded in `format.json`, the Storage State, and its +backend profile. + +`LOCAL-PHASE3/1.0` is the required `0.4` profile. It covers only the built-in +single-project local backend under `.benchwork/storage/` and requires: + +- one fenced storage coordinator at a time and exclusive Journal locking; +- no-follow, project-confined path operations and conditional creation; +- same-filesystem atomic visibility, directory and file durability through + explicit fsync barriers, and crash recovery for every commit boundary; +- immutable Blob generations, exact-generation reads and deletes, and no + overwrite of an available Replica; +- bounded streaming SHA-256 and size verification on source and readback; +- quota reservation, pressure, staging, quarantine, manual-disposition, + 61-row capacity-source equality, and positive system-reserve formulas + defined by this RFC; +- Storage Journal replay, local reconciliation, Storage Doctor, GC dry-run, + and exact manually authorized GC execution for eligible committed Replicas; + and +- adversarial tests for crashes, path substitution, disk and inode + exhaustion, partial operations, stale locks, and generation races on the + exact Host filesystem and implementation build. + +It does not claim remote transport, multi-coordinator consensus, automatic +replication, remote durability, automatic GC, verified remote deletion, or +production disaster recovery. + +`PORTABLE-PHASE4/1.0` is a reserved later profile, not an optional checkbox on +the local suite. Enabling it requires a new accepted conformance package for +remote consistency, transport and credential isolation, immutable generation +and fencing semantics, reconciliation under partial network failure, +replication and repair, retention enforcement, automated GC, restore, and +backend-specific deletion evidence. Passing `LOCAL-PHASE3/1.0` cannot satisfy +any of those requirements. Unsupported interface methods fail closed; profile +negotiation cannot silently downgrade a requested guarantee. + +## Storage Doctor + +`bwork storage doctor` performs a read-only storage inspection and emits a +closed `artifact-storage-doctor-report/1.0`. The report binds the project, +Journal ID and last-event Sigil, Chronicle Head Sigil, storage format, backend +profile and Sigil, conformance profile and suite Sigil, coordinator epoch, +inspected backend generations, check results, applied limits and truncation, +overall status, and `report_sigil`. A report never grants authority or +changes availability by itself. + +`mode` is exactly `NORMAL` or `DEEP`; `overall_status` is exactly `HEALTHY`, +`DEGRADED`, `RECOVERY_REQUIRED`, `INTEGRITY_FAILURE`, or `INCOMPLETE`. +When the format and Journal chain verify, all nullable identity fields named +above are non-null and `journal_verification.status` is `PASS`. If format or +Journal authority cannot be established, `storage_journal_id`, Head Sigil, +storage-format identity, backend and conformance identities, and coordinator +epoch are each null unless independently verified; neither cached Head nor +State may populate them. `journal_verification` records the failure, +`state_verification.status` is `INCOMPLETE`, every replay-dependent check is +`INCOMPLETE`, `incomplete_reasons` is non-empty, and `overall_status` is +`INTEGRITY_FAILURE` or `INCOMPLETE`, never `HEALTHY`. A verified Chronicle +Head may remain non-null independently of Storage Journal failure. + +The normal mode validates the namespace and permissions, format, Journal +chain, Head and State caches, replay equivalence, closed Schemas and state +machines, quota-counter identities and Reservations, the exact 61-row +capacity-source classification, all system-reserve consumed and escrowed +counts/bytes against the installed formulas, legacy v1 protections, Reference +Sets and their relationship matrix, RFC-0014 Evidence Manifest/RootPlan/ +hold/activation chains, holds, open intents, GC root/proof +equalities, and exact backend generations named by records. A valid plan plus +active hold with its exact activation absent is reported as a conservative +previsibility orphan, never auto-released or treated as a live root. It checks +that no staged, quarantined, deleted, abandoned, or unjournaled generation is +selectable as an available Replica. It also validates any fixed recovery +marker and evidence; a pending valid marker reports `RECOVERY_REQUIRED`, while +a mismatch reports `INTEGRITY_FAILURE`. + +`bwork storage doctor --deep` additionally streams and rehashes every managed +available Replica, every legacy v1 compatibility anchor and protected managed +copy, and quarantined evidence when inspection policy permits. It reconciles +the bounded backend inventory against Journal ownership without trusting +directory names. The existing `bwork doctor --deep` invokes this storage check +when an active managed store exists while retaining all pre-existing +`artifact/1.0` path and Sigil checks. + +Doctor never truncates the Journal, rewrites a cache, repairs a Replica, +releases a hold, moves a legacy anchor, disposes of bytes, or executes GC. If +the Journal cannot be trusted, Doctor may still emit a bounded diagnostic +report over independently read evidence, explicitly marks replay-derived +fields unavailable, and leaves storage in read-only `INTEGRITY_FAILURE`. +Unknown Schemas, incomplete inventory caused by a declared bound, or an +unresolved contradiction cannot produce a healthy result. + +## Capacity, backpressure, and authorized disposition + +The Execution Specification, backend profile, and active retention policy +bind immutable limits for each Transfer and materialization, aggregate staging +bytes and objects, quarantine bytes and objects, committed bytes and objects, +open file descriptors, and concurrent streams, plus separate reserves for +Journal growth, immutable control-record bytes, and recovery evidence. A limit +change is a new versioned policy or execution decision, never an in-place +relaxation. + +Before issuing an output handle or beginning an import, export, or +materialization, the coordinator reserves the exact declared upper bound +against the relevant byte and object budgets. Insufficient capacity returns +typed `STORAGE_BACKPRESSURE`; no Job is launched and no write handle is +issued. `quota.pressure_entered` and `quota.pressure_cleared` journal the +bounded condition without repeatedly copying attacker-controlled requests. +An admitted operation may consume only its reservation. Growth past it fails +the attempt and cannot borrow quarantine capacity without a separate +reservation. + +An RFC-0014 protected-root operation similarly reserves, before its first +write, one RootPlan control record for every root; an Evidence Manifest for +every event/suffix root; every manifest Blob/control member and Reference Set; +the hold and activation Event frames; the release-evidence record and release +Event; and, for tail recovery, all bounded TailRecoveryIntent stage records. +No later phase may discover an unreserved member or use a partial manifest to +fit capacity. + +Every admitted Transfer, Materialization, Quarantine move, GC deletion, +disposition, and canonical-reference intent also reserves the profile-bounded +Journal and control-record bytes needed to reach one legal terminal or +recoverable state. A payload reservation that omits those lifecycle bytes is +invalid; backend side effects never begin on the hope that later record space +will become available. + +The backend profile additionally withholds fixed +`system_journal_reserve_bytes`, `system_control_record_reserve_bytes`, and +`system_recovery_reserve_bytes` from every project quota limit. They are +available only through the eight `SystemReserveClass` limits and exact 61-row +capacity-source table above. That table includes initialization and migration +policy, coordinator liveness, clock and pressure, legacy protection, +verification and incidents, provenance, policy, holds, Reference Sets, GC +planning and skips, and tail/Recovery records; there is no implicit +“administrative” fallback. They never appear as free or reserved project +quota, cannot be lent to payload operations, and have the strict positive +capacity formulas, count bounds, and monotone physical-byte fold defined +above. Exhausting any class closes mutation admission and requires a new +versioned profile plus operator capacity; it never authorizes truncation or +deletion. + +Journal and recovery reserve cannot be consumed by payload bytes. If the +coordinator cannot durably record a terminal outcome, it stops admitting +mutations and fails closed; it does not delete older records or bytes to make +space. Record retention and byte retention are distinct: Blob identities, +entity projections, Journal events, provenance, policies, failure evidence, +GC plans, dispositions, and tombstones remain even when an authorized policy +later removes their physical payload bytes. A retained record must expose +whether its bytes are `AVAILABLE`, `QUARANTINED`, `DISPOSED`, or +`HELD_FOR_DISPOSITION`. `NOT_CAPTURED` is permitted only when the coordinator +proved that no payload byte ever entered staging; it is never used for bytes +that may still exist. + +Phase 3 never garbage-collects an immutable object in `records/` or retained +recovery evidence. Before the referring Event, their admission is charged to +the exact owner or system capacity source assigned by the 61-row table; an +unassigned record is invalid. A future compaction format would require an +accepted migration RFC that preserves every content identity and replay proof. + +`artifact-storage-disposition/1.0` is the only Phase 3 operation that can +destroy uncommitted Transfer staging, Materialization staging, a failed or +expired Materialization destination, or quarantined bytes outside GC. Its +target kind selects exactly one owner projection: `STAGING` selects an +`SA-ID`, either materialization kind selects an `SM-ID`, and `QUARANTINE` +selects an `SQ-ID`. It is a closed, immutable authorization binding that exact +owner, backend generation, expected Sigil when known, exact size or upper +bound, reason, actor, policy and approval evidence, authorization time, expiry, +and idempotency identity. For either staging kind, the target object equals +the owner's replayed `HELD_FOR_DISPOSITION` residual object. For a +Materialization destination, it equals the intent destination selected by the +terminal `destination_state: HELD_FOR_DISPOSITION` or +`materialization.cleanup_failed` Event and current `cleanup: FAILED`. For +Quarantine it equals the retained destination in `HELD` or +`DISPOSAL_FAILED`, or in `FAILED` only when its terminal Event recorded +`destination_state: HELD_FOR_DISPOSITION`. Prefixes, wildcards, unresolved +paths, and post-approval target substitution are forbidden. The Journal records +authorization, start, completion or failure, and preserves the target record +and disposition tombstone. + +Disposition projection effects are exact: + +| Target kind | Started projection | Completed owner projection | Failed owner projection | +| --- | --- | --- | --- | +| `STAGING` | only Disposition becomes `EXECUTING`; the terminal Attempt remains unchanged | Attempt remains terminal, `staging_state` becomes `MISSING`, and matching `residual_staging_cleanup` becomes `CLEANED` with the deletion evidence | Attempt remains byte-for-byte unchanged | +| `MATERIALIZATION_STAGING` | only Disposition becomes `EXECUTING`; Materialization remains terminal | Materialization remains terminal and its matching `residual_staging_cleanup` becomes `CLEANED` with the deletion evidence | Materialization remains byte-for-byte unchanged | +| `MATERIALIZATION_DESTINATION` | only Disposition becomes `EXECUTING`; Materialization remains `FAILED` or `CLEANUP_FAILED` | lifecycle state remains terminal and `cleanup` becomes `CLEANED` with the deletion evidence | Materialization remains byte-for-byte unchanged | +| `QUARANTINE` | Quarantine enters `DISPOSING` | Quarantine enters `DISPOSED` | Quarantine enters `DISPOSAL_FAILED` | + +The completed Event retains the exact removed `BackendObjectRef` even where an +owner projection's nullable live-object field becomes null. A completed +materialization-target disposition never turns a failed Materialization into +success or exposes its destination. + +A disposition can never target an `AVAILABLE` Replica, canonical project +file, legacy v1 compatibility anchor, protected managed copy, Journal payload, +or active recovery material. Replica deletion remains the GC protocol. +Disposition is manual and explicit in Phase 3; quota pressure alone never +authorizes it. When a Quarantine reservation cannot be obtained, the failure +record says `HELD_FOR_DISPOSITION` and continues charging the exact staging +generation rather than pretending that an unrecorded deletion occurred. The +same rule retains and charges a Materialization staging or destination +generation until the matching materialization disposition branch removes it. + +## Retention and garbage collection + +Byte-retention policy applies to physical Replica, staging, and quarantine +payloads. It does not erase canonical events, logical Artifact records, Blob +identities, Storage Journal events, entity projections, transfer history, +provenance, policies, authorizations, failures, disposition records, GC +evidence, or deletion tombstones. Those identities and records remain +inspectable after bytes are removed. Conversely, retaining a Blob record does +not claim that its bytes remain available. + +`artifact-retention-policy/1.0` is immutable and content-identified. It binds +scope, its contract version, minimum Replica requirements, integrity-check +freshness, retain-until and grace rules, automatic-GC permission, authorization, +and registration time. Holds are separate journaled projections that cite the +policy. Quarantine remains governed by its explicit state and disposition +protocol. Changing policy creates a new record and does not rewrite prior +decisions. + +Every Blob projection binds the sorted set and Sigil of all active policies +whose closed scope matches it. The effective policy is their component-wise +strictest merge: + +- minimum Replica count is the maximum; +- required backend or failure-domain sets are the union; +- maximum integrity age and deletion grace are the minimum non-null duration; +- retain-until time is the maximum; +- every hold and canonical pin is additive; and +- deletion, disclosure, or quarantine release is permitted only when every + applicable policy permits it. + +An unknown scope, incomparable rule, overflow, or contradictory requirement +fails closed, protects the bytes, and makes availability `DEGRADED` unless an +integrity incident independently selects `INCIDENT`. Replay orders policies by +policy ID and Sigil, applies this fixed merge, and records the resulting +`effective_policy_set_sigil`; current directory or map order is never an +input. + +Protected roots include: + +- every managed Blob explicitly bound to a verified canonical Artifact or + other accepted canonical object under a defined retention rule; +- pinned research inputs, Result Bundles, reproduction material, and explicit + legal or preservation holds; +- active or recoverable Jobs, Attempts, Leases, transfers, exports, + materializations, and commit intents; +- every `OPEN` or `COMMITTED` canonical-reference intent; +- active or recoverable Patch Promotion and Recovery material represented by + a registered operational Reference Set and durable hold; +- material required by an unexpired retention policy; +- Replica repair or reconciliation work in progress; +- quarantined material until its separate quarantine policy and exact + disposition permit disposal; and +- every legacy v1 compatibility anchor, its protected managed copy, and the + protection evidence created during upgrade. + +A Promotion Journal, path, or checkpoint filename is not itself a storage +root. RFC-0014 must register the exact operational Reference Set and hold in +the Storage Journal before making checkpoint, intent, or recovery Blob +material visible; release requires its terminal/recovery Receipt and every +applicable retention policy. + +The RFC-0014 boundary is specifically the single-assignment Promotion +activation Event slot first, control record or Evidence Manifest second, +Reference Set/registration Event third, immutable RootPlan fourth, +`retention.hold_set` fifth, and the one exact planned Promotion activation +Event/root last. Slot reservation fixes Event ID/type/payload field but is not +an Event and creates no state or root. The RootPlan is a durable control +record, not a Storage root; the active hold is the protection before and after +activation. Storage admits the hold by validating the plan and its +preallocated identities, never by reading a future Event/root. A crash after +only slot reservation leaves no hold; a crash after a plan or hold but before +activation leaves the corresponding plan-only or conservative hold orphan. +RFC-0014 v1 provides no +absence-, age-, or disposition-based release for it: only the exact planned +activation may resume, otherwise Doctor reports the plan/active hold/absent +activation and the hold remains. The plan, manifest, Set, hold, and activation +are each single assignment and cannot be rebound or reused for a second root. + +The existing `artifact/1.0.location.uri` is not a managed Replica locator. +The storage subsystem must never move, replace, hard-link, or garbage-collect +that root-contained file. Importing it copies bytes and creates an explicit +operational binding; matching only its Sigil is not authority to change the +recorded path. This rule applies to ordinary v1 locations and to protected +historical locations under the now-reserved namespace. + +### Schema-aware reference closure + +GC reachability is a bounded, typed graph computation, not a scan for +digest-looking strings. Roots come only from a verified Chronicle snapshot, +the replayed Storage and execution journals, active retention policies and +holds, and legacy v1 protections. Each accepted Schema version has one closed, +versioned extractor that maps its typed fields to typed edges such as +`CANONICAL_BINDS_BLOB`, `JOB_REQUIRES_BLOB`, `TRANSFER_PINS_REPLICA`, +`BUNDLE_RETAINS_MEMBER`, or `HOLD_PROTECTS_BLOB`. Extractor code identity, +version, and Sigil form part of the conformance suite. + +Higher-layer validators register an immutable +`artifact-storage-reference-set/1.0`. Its top-level fields are exactly +`schema_version`, `reference_set_id`, `source`, `extractor`, `edges`, +`validation`, `registration_event_id`, `created_at`, and +`reference_set_sigil`, with the exact `ReferenceSource`, +`ReferenceExtractor`, `ReferenceValidation`, and field types defined above. +`registration_event_id` is allocated before the record is made durable and is +the `reference_set.registered` Event ID; the record does not contain that +Event's Sigil and therefore creates no hash cycle. + +Registration uses the deterministic identity and pending-candidate protocol +above. The first atomic candidate creation fixes `created_at`, the complete +canonical bytes, and deterministic Event ID before the Event append. A crash +between candidate creation, Event append, Head installation, set readback, and +higher-layer transition-request finalization always resolves and reuses that +same candidate and Event. It never allocates a second timestamp, ID, or +semantically duplicate set. + +Each `edges` member contains exactly `relationship`, `target_kind`, +`target_identity`, and `target_sigil`. Members are unique and sorted by that +four-field tuple. Its field types and the only legal +relationship/source/target combinations are exactly the closed matrix above. + +Every nested object is closed. `reference_set_sigil` is SHA-256 over canonical +JSON with that field omitted. Storage verifies the source record and its Sigil +through the named validator and never derives an edge merely by opening a +Blob. In particular, a Patch Bundle is an opaque Blob to storage; RFC-0014 or +its validator must register an explicit verified Reference Set for any base, +payload, rendering, or attachment whose bytes require transitive retention. + +For source Schema `patch-operational-evidence-manifest/1.0`, the only +installed profiles are `extractor_id` and `validator_id` +`benchwork.patch-operational-evidence`, both version `1.0`, with the exact +profile Sigils defined by RFC-0014. The manifest is finalized before +registration and binds one preallocated Promotion Journal/Event/type/payload +field plus one closed Event-evidence or Journal-suffix branch. Extraction +produces exactly one `HOLD_PROTECTS_BLOB` edge per sorted `blob_refs` member +and one `CONTROL_RETAINS_CONTROL` edge per sorted `control_records` member; +the edge identity/Sigil pairs equal those members and no extra edge exists. +The distinct `control_records` plus `blob_refs`, the resulting `edges`, and +the complete computed `validation.evidence_sigils` are separately capped at +4,096 members. The first two collections have equal cardinality. The +RFC-0014 source preflight rejects any overflow before Manifest finalization; +Storage independently recomputes and rejects it before registration. A +duplicate control `(schema_version, record_id)` with a different Sigil or a +duplicate Blob Sigil with a different size is an integrity conflict. +Every manifest `evidence_sigils` value resolves uniquely to exactly one such +Blob or control member. The validation evidence is the exact sorted unique +union of the manifest Sigil, its typed evidence/member Sigils, and, only for +the suffix branch, the two Head and classification Sigils; its +`source_validation_sigil` is RFC-0014's exact domain-separated formula over +the source pair, both profile tuples, sorted edges, and that evidence set. +A bare or multiply typed Sigil, unknown owner/role/Event combination, changed +member, null-branch manifest, or any future Set/Event/RootPlan/hold/Head/ +TailRecoveryIntent Sigil is invalid. Registration never reads a future +Promotion Event or OperationalRoot. + +The executable fixture set contains one positive case for every row and every +listed alternative target of the matrix, plus a rejection for every +relationship paired with each unlisted source or target kind, wrong identity +type, wrong target Sigil, wrong source Sigil, extra property, duplicate edge, +or non-canonical order. + +A missing extractor for a reachable Schema, an unknown Schema or relationship, +an absent required Reference Set, a mismatch between a source and its +Reference Set, or invalid extraction evidence aborts planning and protects the +affected material. The planner never guesses an edge from a filename, media +type, backend key, untyped JSON member, or Sigil-looking text. + +Closure follows typed edges transitively and detects cycles. The retention +policy binds maximum roots, nodes, edges, depth, aggregate control-record +bytes, and wall time. Reaching any bound, arithmetic overflow, unstable source, +or cycle-processing error aborts the plan with retained evidence; it cannot +use a partial traversal to conclude that a Blob is unreachable. A completed +GC plan binds the ordered root set, all Reference Set Sigils, extractor suite +Sigil, traversal bounds, visited-node and edge counts, cycle summary, and the +resulting reachable Blob and Replica sets. + +Reference Intent admission uses the same deterministic typed traversal with +the listed `reference_sets` as its exact roots. It resolves each root and every +reachable `REFERENCE_SET` target by both ID and Sigil, follows each verified +edge once in canonical tuple order, collects every reachable `BLOB` target, +and also collects the Blob identity of every reachable `REPLICA`. Cycles are +collapsed by the exact `(target_kind, target_identity, target_sigil)` node +key. The universal `4096` collection bound applies independently to root +sets, visited nodes, visited edges, traversal depth, and the resulting Blob +set; reaching a bound before proving completion, or encountering an unknown, +missing, mismatched, unstable, or invalid node or edge, rejects admission +before `canonical_reference.intent_recorded`. +The Reference Intent's `blob_sigils` must equal the resulting sorted unique +Blob set byte-for-byte and has length `0..4096`. An empty array is valid +exactly when the completed closure contains no Blob; it does not authorize a +placeholder Blob, an unrelated pin, or a partial traversal. Replay recomputes +this equality from the immutable Reference Sets rather than trusting the +stored array. + +The GC hash graph is exact. `H(x)` below is SHA-256 over canonical JSON bytes; +each tuple array is unique and lexicographically sorted by the fields shown: + +```text +hold_set_sigil = + H([[hold_id, target_kind, target_id, policy_id, + set_authorization_sigil, last_event_sigil], ...]) + +legacy_protection_set_sigil = + H([[protection_id, artifact_id, blob_sigil, protected_replica_id, + record_sigil, last_event_sigil], ...]) + +reference_intent_set_sigil = + H([[reference_intent_id, state, record_sigil, last_event_sigil], ...]) + +policy_set_sigil = + H([[policy_id, record_sigil, last_event_sigil], ...]) + +execution_root_set_sigil = + H(complete ExecutionRootSnapshot with root_set_sigil omitted) + +root_snapshot_sigil = H(complete GCRootSnapshot) +proof_sigil = H(complete ClosureProof with proof_sigil omitted) +``` + +The hold array contains active holds; the legacy array contains every +protected legacy entry; the reference-intent array contains every `OPEN` or +`COMMITTED` intent; and the policy array contains every policy applicable to +any root or target. `GCRootSnapshot.reference_set_sigils` is the sorted unique +array of every complete Reference Set traversable from those roots. Its +`execution_roots.root_set_sigil` is computed by the separate formula below. +Its `storage_event` is the verified Storage prefix immediately before +`gc.plan_created`, so it cannot refer to the plan Event itself. + +For an `artifact-gc-plan/1.0`, let `root_snapshot` and `closure_proof` denote +those exact nested fields and let `event` denote the exact +`gc.plan_created.payload` that refers to the plan. All of these equalities are +mandatory: + +```text +root_snapshot.hold_set_sigil == hold_set_sigil +root_snapshot.legacy_protection_set_sigil == legacy_protection_set_sigil +root_snapshot.reference_intent_set_sigil == reference_intent_set_sigil +root_snapshot.policy_set_sigil == policy_set_sigil +root_snapshot.execution_roots.root_set_sigil == execution_root_set_sigil +closure_proof.root_set_sigil == root_snapshot_sigil +closure_proof.extractor_suite_sigil == gc_plan.extractor_suite_sigil +closure_proof.bounds == gc_plan.bounds +closure_proof.proof_sigil == proof_sigil +event.root_snapshot_sigil == root_snapshot_sigil +event.closure_proof_sigil == proof_sigil +event.target_ids == [target.target_id for target in gc_plan.targets] +``` + +Targets are sorted and unique by `target_id`; their Blob, Replica, backend +generation, reason, and expected remaining Replica arrays are covered by the +GC plan's `record_sigil`. The Event's `gc_plan` Ref resolves that exact record. +A mismatched component-set Sigil, proof/root binding, bound, extractor, target +order, target identity, or self-Sigil is a Schema or replay failure and cannot +be authorized or revalidated. + +### Canonical-reference gate + +A Chronicle transition that would make a managed Blob reachable has a +cross-journal write-ahead protocol. `artifact-storage-reference-intent/1.0` +contains exactly `schema_version`, `reference_intent_id`, +`transition_request_id`, `transition_request_sigil`, `canonical_event_type`, +`expected_chronicle_head`, `reference_sets`, `blob_sigils`, `actor_id`, +`authorization_sigil`, `idempotency_key_sigil`, `requested_at`, and +`record_sigil`. `expected_chronicle_head` contains exactly +`schema_version: chronicle-head/1.1`, `event_count`, and +`terminal_receipt_sigil`. Each sorted unique `reference_sets` member contains +exactly `reference_set_id` and `reference_set_sigil`; `blob_sigils` is sorted +and unique, has length `0..4096`, and equals the exact Reference Set closure +defined above. The empty array is legal only for a completed closure with no +reachable Blob. `transition_request_sigil` identifies the complete closed +Athanor request independently of any not-yet-created Event or Receipt. Every +nested object is closed, and `record_sigil` covers canonical JSON with that +field omitted. + +Although the existing `chronicle-head/1.1` Schema permits an unbounded +non-negative integer, this protocol admits a canonical-reference operation +only when the replayed `event_count` is strictly less than `U63_MAX`. The +current Head therefore fits `ChronicleHeadRef`, and the one Event that the +candidate may commit has a representable resulting count. At +`event_count == U63_MAX`, or for any greater Chronicle Head accepted by the +older Schema, the coordinator fails closed before Reference Set registration, +Reference Intent creation, Reservation, or Chronicle submission. It preserves +existing state and requires a future versioned migration; it never wraps, +clamps, or serializes a wider count into this contract. + +`canonical_event_type` is exactly one of +`agent-result.accepted`, `patch.proposed`, `patch.validation.recorded`, +`patch.promotion.authorized`, `patch.promotion.rejected`, +`patch.promotion.outcome-recorded`, or +`patch.promotion.recovery-recorded`. A later canonical event family that +creates, changes, or removes managed Blob reachability must version this +contract rather than pass an unrecognized string. + +`canonical_reference.committed` is legal exactly once from `OPEN`. It resolves +the immutable Reference Intent and complete transition request by the +intent's ID/Sigil pair, then resolves one exact `chronicle-event/1.1` and +paired `receipt/1.1`. Validation requires: + +```text +event.type == intent.canonical_event_type +event.sequence == intent.expected_chronicle_head.event_count + 1 +event.previous_receipt_sigil + == intent.expected_chronicle_head.terminal_receipt_sigil + +chronicle_commit.event_id == event.event_id +chronicle_commit.event_body_sigil == event.event_body_sigil +chronicle_commit.receipt_id == event.receipt.receipt_id +chronicle_commit.receipt_sigil == event.receipt.receipt_sigil +chronicle_commit.head == { + schema_version: "chronicle-head/1.1", + event_count: intent.expected_chronicle_head.event_count + 1, + terminal_receipt_sigil: chronicle_commit.receipt_sigil +} +``` + +The Event Body Sigil and Receipt Sigil are recomputed. The ordinary RFC-0001 +Receipt equalities for Event ID, Event Body Sigil, previous Receipt Sigil, and +accepted/occurred time all hold. The event-family resolver validates the +complete request-to-candidate mapping, including transition-request ID and +Sigil, Reference Intent ID and record Sigil, Reference Set pairs, actor, +authorization, idempotency identity, and every family-specific payload field. +A different Event, Receipt, resulting Head, request, candidate payload, or +set projection is an integrity conflict. + +`canonical_reference.released` is legal exactly once from `OPEN`, with +`release_kind: ABORTED_BEFORE_CANONICAL_COMMIT`, +`chronicle_commit: null`, and the complete `CanonicalAbortAuthority`. Its +members equal the Reference Intent and request byte-for-byte, and: + +```text +absence_evidence_sigil = + Sigil(["artifact-storage-canonical-precommit-absence/1.0", + reference_intent_id, + reference_intent_record_sigil, + transition_request_id, + transition_request_sigil, + expected_chronicle_head, + verified_chronicle_head]) + +authority_sigil = + Sigil() +``` + +While holding the canonical-reference gate, complete Chronicle replay must +validate both Heads, prove +`verified_chronicle_head.event_count > +expected_chronicle_head.event_count`, and prove that the complete suffix after +the expected Head contains no Event that the event-family resolver binds to +this request or intent. The old conditional candidate can then no longer +commit. The release `reason.code` is `CHRONICLE_REFERENCE_CHANGED` and its +`evidence_sigils` is exactly the sorted unique set of the authority Sigil, +absence-evidence Sigil, Reference Intent record Sigil, and transition-request +Sigil. If the current Head is unchanged, any replay is unavailable or +ambiguous, or a bound Event exists, the intent remains `OPEN`. +`CANONICAL_REFERENCE_REMOVED` is forbidden in v1. + +The project has one outermost **canonical-reference gate**. Every Phase 3 +Athanor transition that creates, changes, or removes a managed Blob binding +must: + +1. acquire the gate, register any newly derived Reference Set, and verify + Chronicle, Storage Journal, candidate request, every Reference Set, + extractor, validation evidence, and Blob; +2. append `canonical_reference.intent_recorded` before the Chronicle + transaction, pinning the exact intent record, request, Reference Sets, and + exact closure-derived Blob set, including a legal empty set; +3. release the Storage Journal lock while retaining the gate, then ask Athanor + to commit the exact candidate conditional on `expected_chronicle_head`; +4. require the canonical event payload to bind `reference_intent_id`, + `record_sigil`, `transition_request_sigil`, and every Reference Set ID and + Sigil; and +5. after Athanor returns a verified Receipt, append + `canonical_reference.committed` with the canonical Event ID, Event Body + Sigil, Receipt ID and Sigil, and resulting Chronicle Head, then release the + gate. + +Reference Set registration alone is not a root and grants no canonical +authority. A caller deriving a set for this transaction retains the gate from +registration through intent append; if it cannot append the intent, it creates +no Chronicle Event and leaves the verified bytes as ordinary unreferenced +operational storage subject to their existing policy. + +Intent admission reserves Journal and control-record capacity for exactly one +terminal path—`canonical_reference.committed` or the aborted +`canonical_reference.released` branch—plus bounded recovery evidence. Either +terminal Event settles the Reservation; a committed v1 pin has no later +release reserve. Without the complete terminal-path reserve, the coordinator +does not append the intent or call Athanor. +The lifecycle Reservation is canonical-lifecycle capacity, not a lease: +`expires_at` and `remaining_micros_at_creation` are both null in the +`canonical_reference.intent_recorded` payload. The open intent's +`source_event` resolves that exact active Reservation and preserves it +byte-for-byte until one terminal path settles it. The committed pin itself is +the durable `COMMITTED` reference-intent projection and does not depend on a +retained quota Reservation. + +The raw Receipt is the ordinary outer Event Receipt and is not embedded in the +canonical event payload. A failed Athanor transaction does not erase the +intent. While holding the gate, recovery verifies the complete Chronicle. If +the exact bound event and Receipt exist, it appends the missing committed +Event. If the candidate is absent and the verified Head has advanced past the +expected Head, it may append `canonical_reference.released` only with the +exact `HEAD_SUPERSEDED_WITHOUT_BOUND_EVENT` proof above. If Chronicle is +unavailable, invalid, unchanged, or ambiguous, the intent remains `OPEN`, +pins all named Blobs, and blocks both GC and another transition using that +idempotency identity. + +A committed pin is permanent in Phase 3. Storage expiry, policy change, Job +termination, an absent file, or an unversioned removal claim never implies +release. A future canonical-removal Event family and Receipt protocol must +version this contract before a committed pin can have an outbound transition. + +The same outer gate linearizes execution roots. Before an RFC-0012 execution +event can first expose a managed Blob as a Job input, Attempt input, or Attempt +output, the Executor holds the gate, registers the exact +`OPERATIONAL_CONTROL_RECORD` Reference Set, and appends its durable Storage +hold. It then releases the Storage Journal lock, appends the execution event +that binds the Reference Set and hold, and only then releases the gate. The +final activation check re-resolves the hold as `ACTIVE` and proves that the +deterministic EHR for its hold ID does not exist. A crash before the execution +event leaves a conservative orphan hold. Because no execution Event activated +an owner, this is not an execution-recovery action and cannot fabricate an +execution release observation. Only when no Execution Recovery is active may +the Storage-side orphan reconciler, or the exact Start retry before a new +activation is considered, release it by durably creating the exact RFC-0012 +`ORPHAN_ABORT` EHR under the gate after verified Execution replay proves the +sole activation absent. That EHR is a permanent activation veto; a late Event +cannot expose the root. + +Removal uses the reverse safe order under the same gate: first append the +execution event that makes the root inactive, then durably create the exact +`OWNER_TERMINAL` or `OUTPUT_DEADLINE` EHR, then append +`retention.hold_released`. A crash leaves an extra hold, never a missing live +root. `JOB_INPUT` and `ATTEMPT_INPUT` use only their exact owner-terminal EHR; +`ATTEMPT_OUTPUT` uses only its immutable deadline EHR. A canonical Agent +Result transition establishes an independent additive pin and never releases +or shortens the execution hold. An output hold may remain overdue only while +trusted clock, execution replay, EHR, or Storage-prefix proof is unavailable +or ambiguous; this fail-closed retention does not move its scheduled +deadline. + +`ExecutionStorageRoot` has exactly the nine fields in its `$defs` row and is +byte-for-byte compatible with RFC-0012's +`execution-journal-event/1.0#/$defs/storage_root_binding`. Entries are sorted +by `(root_kind, job_id, attempt_id, storage_root_manifest_id, +reference_set_id, hold_id)`. `storage_root_manifest_id` is the exact +`ESM-` identity of one RFC-0012 +`execution-storage-root-manifest/1.0`, and +`storage_root_manifest_sigil` is that complete document's self-Sigil. The +nullable Attempt ID is null only for `JOB_INPUT`; it is non-null and equals +the owning Attempt for `ATTEMPT_INPUT` or `ATTEMPT_OUTPUT`. +`ExecutionRootSnapshot.root_set_sigil` is SHA-256 over the complete closed +snapshot with that field omitted. Its event count and nullable last-event +Sigil are the verified Execution Journal Head at the same replay point; zero +count requires null last-event Sigil. Every root's ESM, Reference Set, active +hold, and `hold_set_event` must replay exactly. In particular: + +1. the manifest pair resolves one complete + `execution-storage-root-manifest/1.0` whose `manifest_id`, + `manifest_sigil`, `root_kind`, `job_id`, and `attempt_id` equal the root + byte-for-byte; its single-assignment resolver rejects a second byte + sequence for the same ID; +2. the ESM has `protection_plan.kind: PLANNED`; its preallocated + `reference_set_registration_event_id`, `hold_id`, and + `hold_set_event_id` equal the registered Reference Set Event ID, this + root's `hold_id`, and `hold_set_event.event_id`, respectively, and its + policy ID and Sigil equal `SP-EXECUTION-ROOT-HOLD-V1` and its exact + initialized project policy cited by the hold; +3. the Reference Set resolves with exactly `reference_set_id` and + `reference_set_sigil`, and its special ESM-ID-derived + `registration_event_id` equals the ESM's precomputed registration Event ID; +4. the hold is `ACTIVE`, has `target_kind: REFERENCE_SET`, and has + `target_id == reference_set_id`; +5. `hold_set_event` is the exact `retention.hold_set` Event that created that + hold, and its `hold_id`, target kind, target ID, policy ID, and + authorization Sigil equal the replayed hold byte-for-byte and the exact + RFC-0012 post-registration formula; and +6. the set's source is exactly + `{kind: OPERATIONAL_CONTROL_RECORD, identity: + storage_root_manifest_id, schema_version: + execution-storage-root-manifest/1.0, sigil: + storage_root_manifest_sigil}`. Its installed extractor and validator equal + the profiles pinned by RFC-0012, and its sorted edges are the complete ESM + `blob_refs` closure: `JOB_INPUT` uses `JOB_REQUIRES_BLOB`, while + `ATTEMPT_INPUT` and `ATTEMPT_OUTPUT` use `CONTROL_RETAINS_BLOB`. Its + source-validation and evidence Sigils equal the exact RFC-0012 formulas + and exclude every future Set/Event/hold/release Sigil. + +Every edge has `target_kind: BLOB` and +`target_identity == target_sigil == manifest BlobRef.blob_sigil`; the edge +projection equals the manifest's sorted unique BlobRef projection exactly. +An active hold on a Blob, Replica, or different Reference Set cannot protect +this root. An incomplete edge set, mismatched source/owner, wrong +relationship, or valid but unrelated hold invalidates the snapshot and +protects conservatively. + +### Execution-owned output-hold deadline + +For an `ATTEMPT_OUTPUT` ESM, `protection_plan.hold_lifetime` is exactly +`OUTPUT_RETENTION {kind, maximum_duration_seconds}`. That duration is the +maximum lifetime of this one execution-owned `SH-ID` after the root's parent +Job becomes terminal. It is not an +`artifact-retention-policy/1.0.retain_until` minimum, does not rewrite that +policy, and does not bound any canonical pin, legal hold, preservation hold, +different execution hold, Quarantine protection, or open reference intent. + +While holding the canonical-reference gate, the Executor and Storage derive: + +```text +release_due_at = + checked_utc_add( + parent_job_terminal_event.recorded_at, + protection_plan.hold_lifetime.maximum_duration_seconds) +``` + +The Job terminal Event is the sole RFC-0012 inactivation event for this +`ATTEMPT_OUTPUT` root. Its exact Event identity, Event Sigil, and +`recorded_at`, plus the ESM ID/Sigil and hold ID, are the release +authorization evidence. Its closed RFC-0012 +`output_hold_release_schedules` entry repeats the Attempt, ESM, Reference Set, +hold, and hold-set Event, sets `root_inactivation_event_id` to that enclosing +Event ID, and repeats the bound duration. Checked addition that produces a +representable RFC 3339 UTC timestamp records that value with +`deadline_status: EXACT`. Overflow never wraps, saturates, or becomes an +indefinite duration: the schedule instead repeats the terminal +`recorded_at` and uses `OVERFLOW_FAIL_CLOSED`. The inactivated root is +therefore immediately due for this execution-hold release, while a +`CODE_MODIFICATION` Job with that schedule cannot be `SUCCEEDED`. + +Canonical acceptance, a replacement pin, and every other earlier narrowing +are invalid release bases in v1. Storage appends exactly one +`retention.hold_released` for this `hold_id` only when an `EXACT` +`release_due_at` becomes due, or immediately after inactivation for an +`OVERFLOW_FAIL_CLOSED` schedule whose due time is the terminal +`recorded_at`. Before that append the Executor creates or resolves the +deterministic `OUTPUT_DEADLINE` EHR. The Storage Event uses +`reason.code: EXECUTION_HOLD_LIFETIME_EXPIRED` and the schedule-bound +authorization and evidence defined in the Event rules above. RFC-0012 then +appends `storage_root.hold_release_observed` with that complete schedule, EHR +ID/Sigil, and exact Storage release `EventRef`. A zero duration takes this +reverse-order branch immediately after the Job terminal Event. Process restart +or Recovery never recomputes or moves an exact deadline: under the gate it +reuses an already committed EHR and Storage release or, once trusted time +proves the immutable deadline due, appends the missing release and +observation. Clock uncertainty, unavailable journal replay, or an ambiguous +prefix or EHR preserves the hold and blocks new time-authorized finalization, +disposition, and GC until the release preconditions can be proved. + +That Event advances only the named hold projection from `ACTIVE` to +`RELEASED`; its revision set remains exactly `HOLD`. It does not release, +shorten, supersede, or mutate the referenced retention policy, Reference Set, +canonical-reference intent, canonical Artifact pin, legal/preservation root, +Quarantine record, another `SH-ID`, or any Blob or Replica record. The +effective root set is then recomputed additively. Any surviving policy, +canonical root, legal/preservation hold, Quarantine protection, recovery +root, or different hold continues to block GC. Releasing this hold is never +byte-deletion authority and never proves that any Replica is eligible for +deletion. + +Lock order is always canonical-reference gate first. While holding it, a +coordinator may take and release the Storage Journal lock, Execution Journal +lock, or Chronicle lock, but never holds two journal locks simultaneously and +never tries to acquire the gate while holding any one. This ordering is part +of the local conformance suite. + +`LOCAL-PHASE3/1.0` realizes the gate as an exclusive Host-kernel lock on the +fixed coordinator-owned +`.benchwork/locks/canonical-reference.lock`, opened descriptor-relative with +no-follow validation. It never steals authority from a PID, timestamp, +hostname, or stale-looking file; kernel release on process death is followed +by intent recovery before a new holder proceeds. Failure to prove exclusive +ownership is `STORAGE_BACKPRESSURE` or +`INTEGRITY_FAILURE`, never an unlocked fallback. A multi-Host or remote gate +requires the later portable conformance profile. + +### Plan and execution + +Garbage collection is mark-and-sweep over an immutable root snapshot and +Storage Journal generation. Reference count alone is insufficient because +crashes, delayed canonical transitions, retention changes, and concurrent +imports can make it stale. GC follows: + +1. validate the retention policy and authorization; +2. capture and Sigil-bind the Chronicle Head, Storage Journal event, + `ExecutionRootSnapshot`, holds, legacy protections, Reference Sets, and + extractor-suite Sigil; +3. compute the complete bounded transitive closure described above; +4. create a deterministic dry-run plan with exact Replica IDs, Blob + identities, backend generations, reasons, closure proof, and expected + remaining availability; +5. retain that immutable plan for the policy grace period and obtain explicit + authorization for exactly its targets; +6. immediately before each deletion, acquire the canonical-reference gate, + replay current Chronicle and release its lock, replay the Execution Journal + and release its lock, verify every execution root's active Storage hold, + then acquire the exclusive Storage Journal lock, replay storage state, + resolve every reference intent, and recompute that target's reachability + from every current root through all of its current ancestor edges; +7. while retaining the gate, revalidate holds, legacy protections, active + transfers, policy, integrity freshness, Replica generation, minimum Replica + count, the exact backend object, and every namespace ancestor, and reserve + Journal capacity for the target's durable deletion or failure outcome; +8. append `gc.target_deletion_started` while retaining both locks, + conditionally delete only the approved generation, verify the bounded + backend outcome, append the target tombstone or failure, and only then + release the Storage Journal lock and gate; and +9. retain the plan, authorization, closure evidence, skips, and all failures. + +Target-specific revalidation is mandatory even when the Chronicle or Journal +Head has not changed; matching Heads are not proof that paths or backend +generations are unchanged. If either Head has changed, the coordinator +recomputes current typed reachability for the target and its ancestors rather +than trusting the old closure. A new root or edge, a removed or changed +Reference Set, an unknown ancestor, a traversal bound, changed hold, policy, +active transfer, integrity status, minimum count, or legacy protection skips +that target. It does not authorize a different target, and GC never broadens +the approved set. + +The gate remains held from the final current-root computation through the +durable deletion outcome, so no new canonical or execution reference can +commit between the reachability decision and physical deletion. Every `OPEN` +or `COMMITTED` canonical-reference intent and every active execution hold is a +root. Recovery ambiguity therefore protects bytes rather than permitting +deletion. This protocol, rather than Head equality or a second unlocked +lookup, closes both canonical-reference and execution-root GC +time-of-check/time-of-use races. + +For `LOCAL-PHASE3/1.0`, the exclusive Storage Journal lock also remains held +from final replay through the durable target outcome. Consequently no hold, +policy, transfer, materialization, disposition, Replica observation, or other +operational root can interleave after revalidation. Backend deletion has a +profile-bound timeout; timeout records `gc.target_failed` before either lock +is released. A later portable profile may replace the long-held local lock +only with a conformance-proven transactional fence that gives the same +linearization. + +For the local backend, deletion is anchored at an already opened trusted +directory descriptor for `.benchwork/storage/` and uses no-follow, +descriptor-relative operations or an equivalent proven primitive. Immediately +before deletion, it validates each ancestor component's expected type, +identity, device, inode or platform file identity, permissions, and generation, +then validates that the target is the expected regular immutable file with the +approved Replica ID, Blob Sigil, size, and generation. A symlink, mount, +rename, replacement, hard-link ambiguity, ownership change, or failed +ancestor check skips the target and records evidence. A remote backend must +provide equivalent namespace confinement and delete only with an exact +immutable-generation precondition. + +Normal GC must not remove the last eligible managed Replica of a +canonically-retained Blob. A destructive policy that intentionally permits +loss of the final copy requires a later accepted production contract; the +Phase 3 staging/quarantine disposition cannot authorize it. GC also never +classifies a failed or negative scientific Run as disposable merely because +its outcome is undesirable. + +The Phase 3 reference runtime performs no automatic GC. Its three physical +cleanup authorities are closed: a Transfer commit intent may roll back only +its exact unpublished provisional generation; an exact authorized disposition +may delete only one of its four uncommitted or quarantined target kinds; and an +exact manually authorized GC plan may delete only the eligible committed +Replica generations enumerated by that plan. Disposition and GC require +recovery to prove that each target is neither live nor protected, and Phase 3 +GC may never remove the last eligible Replica of a canonically retained Blob. +Committed Attempt outputs remain retained by default; failed and late output +records always remain, while their bytes follow the separately recorded +quarantine-retention outcome. Phase 4 must supply the +`PORTABLE-PHASE4/1.0` policy, authorization, race, crash, last-Replica, +replication, deletion, and restore conformance before automated GC is enabled. + +## Quarantine and corruption + +Quarantine is separate from staging and from the committed Replica namespace. +Staging is expected transient work owned by a live or recoverable Transfer; +quarantine is a terminal isolation decision with its own identity, evidence, +reservation, retention state, and authorization boundary. A pathname move +alone cannot change either state. + +Bytes enter quarantine when Benchwork observes at least: + +- partial, truncated, over-limit, or timeout-terminated transfer; +- expected-versus-computed Sigil or size mismatch; +- unknown digest algorithm or ambiguous encoding; +- path escape, symlink or hard-link ambiguity, special file, or unsafe bundle; +- stale or fenced Lease, duplicate completion conflict, or late output; +- backend generation, visibility, durability, or readback contradiction; +- corrupt known Replica, suspected collision, or catalog contradiction; +- malformed or missing required provenance; or +- policy, authorization, secret-handling, or cleanup violation. + +A quarantine record binds the original request, claimed and computed +identities where available, byte count, source, Job and Attempt context, +reason code, evidence Sigil, isolation location, access policy, and retention +decision. It also records whether bytes were retained and their exact backend +generation. It must not copy secrets into metadata. + +Quarantined bytes: + +- are never selected for materialization, export, deduplication, Replica repair, + result eligibility, or Artifact registration; +- are unreachable to Workers and ordinary storage readers; +- do not count toward Replica availability or retention minimums; +- remain bounded by quarantine storage quotas; and +- may be inspected or disposed of only through an explicit audited operation. + +Successful reinspection does not silently flip the original transfer to +success. It creates a new verified import or Replica record that cites the +quarantine evidence, while the original failure remains. Disposal removes only +the quarantined bytes and preserves the record and evidence identity. Quota +pressure does not authorize eviction: if a reservation cannot be obtained, +the coordinator leaves the exact staging generation +`HELD_FOR_DISPOSITION`, retains the failed-transfer and quota evidence, and +does not claim that Quarantine bytes exist. + +When a committed Replica fails verification, the coordinator marks that +Replica `CORRUPT`, stops serving it, and verifies another Replica before +failover. It may separately copy bounded forensic bytes into a new Quarantine +record, but the Replica itself never changes identity or becomes quarantined. +It never repairs in place by overwriting the corrupt generation. If no +eligible Replica remains, the Blob becomes `UNAVAILABLE` or `INCIDENT`, and +dependent work fails closed. + +Quarantine is an operational safety state, not a scientific conclusion. +Benchwork may propose an Issue or Deviation from its evidence, but only Athanor +can accept that separate transition. + +## Authorization and isolation + +Storage permissions are deny-by-default and appear explicitly in the Phase 3 +Capability, Task Capsule, Execution Specification, and Ward decision. A +filesystem read permission does not imply Blob import, backend read, export, +retention change, quarantine inspection, or deletion authority. + +The minimum distinct permissions are: + +- select a declared Blob as an immutable input; +- materialize that input into a named Task scope; +- write bounded Attempt output into isolated staging; +- finalize eligible output after Lease and policy validation; +- export a Blob to one declared destination class; +- inspect storage metadata or quarantine; +- set a retention hold; +- authorize one exact staging or quarantine disposition; +- execute one already authorized exact disposition; and +- execute a specific GC plan. + +MCP and Worker surfaces remain typed and content-scoped. They expose no +general backend key, arbitrary path, bucket, prefix, filesystem, or deletion +operation. The Worker cannot choose a storage namespace, override a Sigil, +disable verification, release quarantine, authorize disposition, change +retention, or invoke GC. + +At A1, the local runtime supervises cooperative output capture and removes +ambient credentials but does not claim containment of malicious code. At A2, +the complete committed store, Storage Journal, quarantine, backend credentials, +and `.benchwork/` tree are unreachable to the Worker. Only declared immutable +inputs and an Attempt-scoped bounded output handle cross the Circle. + +Storage authorization does not authorize external disclosure. An export to a +remote or external party requires the exact destination and disclosure policy +to be approved under the applicable Review or future transport contract. + +## Compatibility and migration + +This RFC does not reinterpret or rewrite an already accepted +`artifact/1.0`, its Schema identifier, event payload, projection, Receipt, or +Chronicle replay. It does make one deliberately versioned Alpha-era safety +tightening to admission: after the storage-format upgrade, new registrations +cannot use the reserved `.benchwork/storage/` namespace. The CLI, MCP, and +Doctor surfaces must report this as the new validator rule, not as a new +meaning for historical events. + +Outside that namespace, the accepted Athanor semantics remain: + +- `artifact_id` uses an `AR-` identifier and is unique; +- `program_id` identifies an existing Research Program; +- `kind` is non-empty; +- `location` contains exactly `uri` and `sigil`; +- `location.uri` resolves within the project and is readable at registration; +- `location.sigil` is SHA-256 over the exact file bytes; +- `producer_id` and every `input_id` identify existing objects in the same + Program; +- registration is immutable, sets `status: REGISTERED`, advances applicable + Working state, and carries the Chronicle time and Receipt; and +- later integrity inspection expects the recorded local bytes to remain + available and to match their Sigil. + +For every new registration, Athanor adds only two reserved-namespace checks to +the existing v1 resolution behavior: + +1. if the lexical URI can be expressed beneath the project root, normalize + that comparison path without following links and reject it when it equals + `.benchwork/storage` or has that directory as an ancestor; and +2. resolve the candidate exactly as the current v1 implementation does, + including its existing symlink-following and root-containment behavior, and + reject it when the resolved target is inside or aliases the reserved + namespace. + +An absolute URI or symlink alias that the existing implementation resolves +inside the project and outside the reserved namespace remains admissible. +This RFC does not introduce a general lexical-root-containment or no-follow +rule for ordinary v1 Artifact files. Only managed-store internals use the new +no-follow primitives. Reserved-path comparison follows the Host filesystem's +case and Unicode semantics and fails closed only when that reserved-boundary +decision is ambiguous. These checks happen before streaming content hashing or +Chronicle append. + +In particular, `artifact/1.0.location` is never reinterpreted as: + +- a Blob record, Replica ID, backend key, Replica set, cache pointer, mutable + URL, retention promise, or availability claim; +- the digest of a directory walk, metadata document, ciphertext, or storage + manifest instead of the referenced file bytes; or +- permission for a backend to move, rewrite, replace, or delete the recorded + project file. + +The existing Schema's reference shape does not override Athanor's exact-field +and project-path checks. A backend URI that current Athanor cannot resolve and +read as a project-local file cannot be placed into `artifact/1.0.location` to +simulate migration. A backend locator, URI, or object key is opaque operational +metadata even if it can be made to resemble a relative file path. To register +managed bytes under v1, a caller must explicitly materialize and verify a +durable independent file outside `.benchwork/storage/`, then invoke the v1 +registration with its ordinary producer and input lineage. + +Upgrade preflight is mandatory before `storage.activation_completed`: + +1. replay Chronicle and enumerate every already accepted v1 location; +2. normalize and resolve each location under the historical Host rules and + identify every lexical or physical alias of `.benchwork/storage/`; +3. verify the immutable Artifact record, Receipt, current exact bytes, and + recorded Sigil using bounded streaming; +4. after `storage.initialized`, reserve capacity and run the exact + `LEGACY_V1_PROTECTION` Transfer through staging, durable commit intent, + conditional finalize, and readback, without moving, linking, renaming, or + overwriting the recorded path; +5. make one closed `artifact-storage-legacy-protection/1.0` record durable, + binding the Artifact and Program, Receipt, a Sigil of the recorded URI, + lexical and resolved file identities, anchor observation, Blob, verified + managed Replica and Transfer, and `LegacyExclusion`, then append one + `legacy_v1.protection_registered` event whose `protection` Ref identifies + that exact record; and +6. activate storage only after replay proves that every discovered alias has + exactly one valid protection. + +The historical v1 path is a compatibility anchor, not a backend Replica. It +remains subject to the existing Deep Doctor byte check and is excluded from +backend key allocation, staging, reconciliation ownership, disposition, and +GC. Its protected managed copy is also a permanent GC root while v1 depends on +that anchor. Missing or changed bytes, an unreadable Receipt, path ambiguity, +insufficient migration capacity, or collision with required internal files +such as `format.json` or `journal.frames` emits or retains migration failure +evidence and blocks activation. The upgrader never rewrites the Chronicle +event, repairs the anchor from the copy, hides the conflict, or treats a +backend URI as the old file. + +Existing v1 locations outside the reserved namespace keep their prior +behavior. They may be explicitly imported into managed storage, but import +only copies and binds verified bytes operationally; it does not update their +recorded location. Deleting or losing every ordinary managed Replica never +deletes or rewrites an `artifact/1.0` record. A future canonical Artifact +contract that natively binds a Blob independent of a project path must use a +new Schema and event version with an accepted RFC, migration guidance, +fixtures, and replay coverage. + +Existing inline Artifact references in Runs, Workings, Evidence, and other +Phase 2 contracts retain their meanings. New storage fields are not smuggled +into their existing `uri`, `sigil`, or provenance fields. + +## Threat model + +### Assets + +The model protects: + +- the mapping from Blob Sigil to exact logical bytes; +- canonical Artifact records, Chronicle, Receipts, and `.benchwork/` state from + storage or Worker mutation; +- committed Replicas from partial publication, overwrite, corruption, and + mutable aliases; +- Host files, backend namespaces, credentials, quarantine, and unrelated + project material; +- complete transfer, failure, deletion, and provenance history; +- protected research material from premature or racing garbage collection; and +- the distinction between stored bytes, eligible Proposals, and accepted + scientific state. + +### Adversaries and failures + +Sources, imported bytes, repository files, archive content, Worker output, +Provider output, paths, filenames, media types, checksums supplied by callers, +and destination acknowledgements are untrusted. Backend responses are claims +until verified. The model covers malicious non-privileged Worker behavior only +at A2, consistent with RFC-0011. + +Required threat cases include: + +- forged Sigils, size lies, truncation, reordering, duplicated or missing + chunks, and corruption in transit or at rest; +- path traversal, absolute paths, symlink and hard-link substitution, special + files, case or Unicode collisions, and time-of-check/time-of-use replacement; +- archive traversal, decompression bombs, excessive files, and unsafe entry + types; +- staging publication, partial rename, stale generation, overwrite, eventual + consistency, and false durability acknowledgement; +- ETag or transport-security claims incorrectly treated as content identity; +- transfer retry, idempotency-key conflict, stale Lease, split-brain Worker, + late finalization, and duplicate completion; +- forged source, backend, Job, Attempt, assurance, provenance, deletion, or + verification evidence; +- backend credential theft, cross-project or cross-tenant reads, content-name + enumeration, and secrets copied into logs or provenance; +- disk, inode, memory, bandwidth, log, staging, or quarantine exhaustion; +- bit rot, missing Replica, wrong restore, and silent backend mutation; +- Journal truncation, forged Head, State cache, interrupted-tail marker or + recovery evidence, illegal event transition, open-intent ambiguity, and + storage activation with an unprotected legacy v1 alias; +- quota pressure used to trigger implicit eviction, record erasure, or + unauthorized quarantine disposal; +- GC reference-count races, incomplete or unbounded transitive closure, + unknown relationship Schema, stale root snapshots, changed ancestor edge, + active-transfer deletion, last-Replica deletion, symlink or ancestor + substitution, wildcard or prefix deletion, and partial delete; +- suspected digest collision or contradictory catalog records; and +- attempts to turn storage success directly into an Artifact, Run, Assessment, + Decision, Patch promotion, or Seal. + +### Trust limits + +SHA-256 content identity relies on collision and second-preimage resistance. +It detects accidental or malicious byte changes when Benchwork recomputes the +digest; it does not authenticate the source, prove scientific correctness, +scan for malware, establish license rights, or provide confidentiality. + +The built-in local backend, storage coordinator, Storage Journal verifier, Host +kernel, filesystem, and enforcement backend are part of the operational +trusted computing base for Phase 3 storage claims. This RFC does not protect +against their coordinated compromise, a Host administrator, a malicious +same-user process with direct access to their files, or physical storage +attack. A2 prevents the Worker from reaching those components but does not +protect against a compromised control plane. + +Remote transport confidentiality, malicious remote operators, verified +deletion, geographic durability, erasure coding, and key-management guarantees +are not claimed in Phase 3. Encryption at rest or in transit does not replace +end-to-end Blob verification. A remote backend may still withhold data or lie +about deletion; stronger attestation requires a later RFC. + +Content-addressed storage leaks equality to an observer of keys or access +patterns. Backend locators and access logs therefore require authorization and +must not be exposed to Workers or unauthorised clients. + +## Invariants + +- Athanor remains the only canonical transition authority. +- `artifact/1.0` retains its accepted logical meaning and local byte-verification + semantics; new Alpha registrations reject the reserved managed namespace. +- Every historical v1 alias under `.benchwork/storage/` has a verified + permanent protection before storage activation and is never a GC or + disposition target. +- A Blob is immutable exact bytes identified by a byte-level Sigil; a Replica + realizes exactly one Blob. +- Blob, Replica, Transfer, and backend existence has no scientific authority. +- Every committed transfer has end-to-end Sigil and size verification. +- Managed hashing and readback are bounded streaming operations over the + complete logical byte sequence. +- Staging and quarantine are never readable as eligible Replicas. +- Staging is live incomplete work; quarantine is a separately reserved, + terminal isolation record. Neither state is inferred from a path. +- Partial, mismatched, late, fenced, corrupt, or unverifiable bytes fail closed + and remain recorded. +- A Worker never receives committed-store, quarantine, journal, catalog, or + backend credential authority. +- No available Replica is overwritten in place. +- Retry and recovery are idempotent and preserve every transfer attempt. +- The hash-chained Storage Journal is authority; Head, State, directory names, + and backend listings are verified projections or claims. +- A complete Journal frame is never truncated or skipped; only a proven + EOF-interrupted final suffix is evidence-preserved and removed under the + exact recovery rule. +- An interrupted first Event recovers through the explicit empty-prefix + initialization branch; every later recovery-frame template has a + marker-bound Event ID, Sigil, and seed before template bytes exist. +- A Transfer commit intent names exactly one provisional Replica with null + verification. NEW makes it available; DEDUP or any post-intent failure + abandons it only with exact provisional-target absence evidence while + preserving the distinct selected Replica. +- Deduplication preserves all provenance and never merges logical lineage or + policy. +- Transformations create explicit derived provenance and are identified by + their output bytes. +- Storage operations never append Chronicle or automatically create canonical + objects. +- Retention and GC remove only exact physical Replicas under a current + authorized plan; they never erase canonical or operational history. +- Record identity and history outlive authorized byte disposal; quota pressure + never grants deletion authority. +- Every project quota counter is a deterministic fold of the closed + `QuotaEffect` program; reservations, retained future-event shares, + settlement, pressure, and physical usage removal are never inferred. +- All twelve quota counters have class-and-dimension entity identities. Every + one of the 61 Event types has exactly one owner or named system capacity + source, and every installed system class satisfies its positive count and + byte formulas. +- Transfer, Materialization, and Quarantine terminal Events classify every residual + generation as absent, cleaned, or exactly held and charged; every held + Transfer, Materialization, or Quarantine object has one legal disposition + target kind. +- GC uses closed Schema-aware Reference Sets and a complete bounded transitive + closure; unknown or exhausted traversal fails closed. +- Reference relationships obey the closed source/target matrix, and every GC + component-set, root snapshot, closure proof, target array, and Event binding + verifies its defined canonical preimage and equality. +- Every Phase 3 canonical Blob binding has a durable reference intent before + Chronicle commit; every open or committed intent is a GC root. +- Every Phase 3 execution Blob binding has its Storage Reference Set and hold + before its execution event becomes visible; removal commits the execution + transition before releasing the hold, under the same outer gate. +- GC revalidates every target's current ancestors and exact namespace path or + backend generation while holding the canonical-reference gate through its + durable deletion outcome. +- Every side-effect intent is one exact `OpenIntentProjection`; GC and + disposition Recovery use their complete exact-generation observation + matrices and never infer success from a path or unbound absence. +- Normal GC never removes the last eligible Replica of canonically retained + material. +- Storage treats Patch Bundles and every other structured payload as opaque + bytes; higher layers supply typed Reference Sets. +- Blob `availability_as_of` is always an `EventRef`, and every durable + deadline follows the conservative RFC-0012 restart rule: inability to prove + that it remains future makes it due and never extends authority. +- `LOCAL-PHASE3/1.0` evidence cannot be used to claim + `PORTABLE-PHASE4/1.0` conformance. +- Unknown algorithms, Schemas, backend profiles, controls, and evidence fail + closed. + +## Relationship to adjacent RFCs + +- RFC-0011 owns authority, assurance, Sanctum, Circle, Ward, Crucible, and the + operational-versus-canonical boundary. +- RFC-0012 owns Job, Attempt, Lease, the public fence tuple, secret Lease + credential, cancellation, terminal eligibility, and crash-recovery + identities consumed by storage transfers. Its execution events own root + visibility; this RFC owns the preceding Reference Set/hold and shared-gate + ordering that makes those roots safe for GC. +- RFC-0014 may encode a Patch Proposal and its validation evidence as Blobs, + but storage treats its Patch Bundle as an opaque Blob. RFC-0014 owns its + trusted extractor and supplies this RFC's typed Reference Set, while also + owning base identity, conflict handling, human promotion, and repository + mutation. +- RFC-0015 exposes typed Executor start, observe, cancel, and result operations. + It may return bounded Blob descriptors and transfer status, but never raw + backend credentials or general storage access. Its Agent Result acceptance + registers an exact Reference Set and uses this RFC's canonical-reference + gate. + +Those RFCs may refine record fields but may not weaken this RFC's identity, +atomic publication, quarantine, compatibility, provenance, or authority +boundaries without explicitly superseding it. + +## Alternatives + +- **Make every stored Blob a canonical Artifact.** Rejected because caches, + duplicate bytes, failed outputs, imports, and quarantined material do not + have scientific meaning or accepted Program lineage. +- **Treat `artifact/1.0.location.uri` as a backend locator.** Rejected because + current Athanor resolves and hashes a project-local file, and existing Deep + Doctor and replay behavior must remain unchanged. +- **Store Replica lists in Chronicle.** Rejected because health checks, + transfers, repair, replication, and deletion are operational churn rather + than canonical research transitions. +- **Trust Worker-provided hashes or backend ETags.** Rejected because both are + untrusted claims and may cover different bytes or encodings. +- **Write directly to the final content-addressed path.** Rejected because + readers could observe partial bytes and crashes could make a key look + committed. +- **Use mutable paths as content identity.** Rejected because paths and object + keys can be reused, redirected, or changed independently of bytes. +- **Use reference counts alone for GC.** Rejected because crash recovery, + concurrent roots, delayed canonical acceptance, and retention changes create + deletion races. +- **Hard-link materializations to stored Blobs.** Rejected because a writable + alias can corrupt the supposedly immutable backend object. +- **Automatically register successful Job outputs.** Rejected because + execution and storage success cannot replace Athanor validation or + researcher authority. + +## Non-goals + +- rewriting, automatically relocating, or replacing an accepted + `artifact/1.0`; the reserved-namespace admission check is the explicit + versioned Alpha safety tightening defined here; +- production-grade remote storage, replication, geo-durability, erasure + coding, or disaster recovery; +- selecting a cloud provider or requiring Slurm, Kubernetes, or cluster + storage; +- remote Worker transport or automatic Provider invocation; +- malware detection, content moderation, scientific validation, license + adjudication, or source authenticity; +- general secret brokering, encryption-key management, or confidential + computing; +- Patch application, merge, or promotion; +- automatic creation of Artifacts, Runs, Assessments, Decisions, or Seals; +- defining a canonical Dataset version contract; and +- allowing third-party executable Storage adapters in the `0.4` reference + runtime. + +## Acceptance tests + +Acceptance requires executable closed Schemas, positive and adversarial +fixtures, threat-model review, crash tests, and retained conformance evidence. +The combined suite must demonstrate: + +1. every already accepted `artifact/1.0` fixture and Chronicle replays + unchanged, and ordinary new locations outside the reserved namespace retain + their CLI, MCP, Working-transition, and Deep Doctor behavior; +2. `artifact/1.0.location` still contains exactly `uri` and `sigil`, resolves + to a project-local readable file at registration, is never accepted as a + Replica set or backend locator, and rejects lexical, resolved, symlink, + case, and normalization aliases of `.benchwork/storage/`; +3. upgrade preflight detects every historical v1 reserved-namespace alias, + verifies its Receipt and bytes by streaming, creates a managed copy and + closed legacy-protection record through the migration-only + `LEGACY_V1_PROTECTION` Transfer with a durable commit intent, without + rewriting the event or path, holds the fixed outer gate across the final + Chronicle replay and activation, and blocks on concurrent ungated writers, + missing bytes, collision, ambiguity, or insufficient capacity; +4. exact byte identity distinguishes newline, serialization, compression, and + other byte changes, while equal verified bytes deduplicate without merging + Artifacts, Programs, policies, or provenance; +5. file names, paths, media types, timestamps, backend keys, encryption + envelopes, and metadata do not affect Blob identity; +6. directory and bundle handling rejects traversal, absolute and duplicate + paths, case or normalization collisions, special files, excessive counts, + and aggregate-size violations; +7. import streams through bounded memory into isolated staging, enforces + overflow-safe bounds, computes SHA-256 and size, records commit intent, + creates the explicit null-verification provisional Replica, conditionally + finalizes, independently streams readback, and only then exposes an + `AVAILABLE` Replica; NEW selects that provisional ID, while DEDUP abandons + it with exact target-generation absence evidence and selects a distinct + already verified Replica without revising it, with Event backend object + and verification byte-for-byte equal to that selected Replica and bound to + its exact generation, Blob Sigil, and size; +8. capture without a prior Sigil is labelled `IDENTITY_COMPUTED` and cannot + satisfy a pre-pinned input, while an expected mismatch is quarantined; +9. local import detects source replacement, symlink and hard-link ambiguity, + special files, truncation, growth, and project-scope escape; +10. partial writes, disk exhaustion, crash before or after backend finalize, + and lost commit acknowledgement recover deterministically from the complete + commit intent and `OpenIntentProjection` without exposing staging, + fabricating success, or losing the provisional Replica transition; +11. retry preserves each transfer-attempt record, exact idempotent replay + returns the prior result, and conflicting idempotency-key reuse fails; +12. chunk corruption, loss, overlap, reordering, inconsistent resume state, + and total-size mismatch prevent commit even when transport security or + per-chunk checks pass; +13. export selects by verified Blob identity, uses no-clobber staging and + atomic finalize, verifies the destination logical bytes, records a closed + manifest, and rejects an unrelated existing destination; +14. TLS, process exit, file existence, backend metadata, and ordinary + object-store ETags cannot substitute for end-to-end verification; +15. materialization is exact and atomic, declared immutable inputs are + read-only, destination mutation cannot alter a stored Replica, and + hard-link aliasing is rejected; crashes before intent, after intent, + after conditional finalize, after readback, and after the terminal frame + follow the exhaustive materialization-recovery matrix without exposing an + unjournaled destination, and both-present recovery records `CLEANED` or + exact charged `HELD_FOR_DISPOSITION` before the terminal frame; the commit + Event's intent ID, destination object and identity, fence, expected Blob + and size, and verification satisfy every terminal-record equality above; +16. Worker output is bounded and Attempt-scoped; stale Lease, invalid fence, + cancellation, duplicate completion, policy violation, and late output + prevent finalization and result eligibility; +17. an A2 Worker cannot reach the committed namespace, quarantine, Storage + Journal, catalog, backend credentials, `.benchwork/`, or another Attempt's + staging; +18. backend profile mismatch, unsupported conditional operation, unknown + capability, false atomicity, stale generation, and inconsistent readback + fail closed; +19. the backend never overwrites an available Replica, and suspected + same-Sigil size or byte contradiction places the identity into incident + state; +20. corruption of one Replica removes it from selection, verifies another + Replica before failover, and reports the Blob unavailable when no eligible + Replica remains; +21. deduplicated import, copy, transformation, export, verification, deletion, + and restore each retain distinct provenance and never manufacture canonical + producer or input lineage; +22. provenance rejects credentials and secrets while retaining source class, + authorization, Job, Attempt, Lease, fence, backend, transfer, verification, + and transformation identities; +23. Blob commit, Replica repair, export, quarantine, and GC leave Chronicle, + Runs, Artifacts, Assessments, Decisions, and Seals unchanged until an + explicit Athanor transition succeeds; +24. GC computes a deterministic dry-run from Sigil-bound canonical and + operational roots and typed Reference Sets, observes a grace period, + acquires the canonical-reference gate, replays Chronicle and Execution + journals one at a time, verifies every execution Reference Set and hold, + and then acquires the exclusive Storage Journal lock for final + revalidation through deletion outcome; it revalidates every target, + ancestor edge, namespace ancestor, and generation, and cannot delete + active, held, newly referenced, legacy-protected, quarantined, or + minimum-Replica material; component-set preimages, root snapshot, + closure-proof self-Sigil, plan Ref, and exact ordered target IDs pass every + equality above and each single-field mismatch is rejected; +25. stale-root, concurrent-import, retention-change, backend-generation, + partial-delete, and restart races preserve evidence and never broaden a GC + plan; +26. deletion targets one exact Replica, leaves Blob identity, provenance, + transfer history, GC decision, and tombstone intact, and wildcard or prefix + deletion is unavailable; +27. the Phase 3 runtime has no normal path to delete the last eligible managed + Replica of canonically retained material and performs no automatic GC; +28. staging and quarantine have distinct identities and state machines; + quarantine is isolated and quota-bounded, cannot serve as a Replica or + deduplication source, and requires a new verified import for release or an + exact audited disposition; Quarantine terminal Events carry the closed + source-cleanup object, a retained source stays owner-charged, the expected + Blob and size are jointly null or non-null, and successful isolation + verifies the exact intent-bound destination generation, Blob Sigil, and + size; +29. restart recovery reconstructs storage state from the durable Storage + Journal's complete binary frames and verified backend generations rather + than paths, filenames, conversation, or Worker claims, derives exactly the + six open-intent branches and their create/remove mappings, and exercises + every row of the Transfer, Materialization, Quarantine, GC-delete, and + disposition recovery matrices, including the no-open-intent pre-start + Disposition expiry branch; +30. unknown storage Schemas, journal events, state transitions, Sigil + algorithms, provenance versions, reference relationships, retention + policies, backend profiles, or required evidence are rejected; and +31. the `0.4` local vertical slice retains all terminal Attempt output and + quarantine records by default, identifies its exact Host and backend + configuration, and makes no production Registry or remote durability + claim; +32. all Journal Event, Head, and State Schemas reject extra or malformed + fields; Event envelope, Reference Set, Reference Intent, cleanup, deadline, + and open-intent fields have the exact types, null matrices, and equality + constraints assigned by this RFC; every Event branch has exactly its + payload and entity revisions; legal Events replay to the identical State + Sigil, while a broken chain, revision gap, unknown Event, or illegal + transition enters read-only `INTEGRITY_FAILURE`; +33. crashes at every payload, intent, event, fsync, Head replacement, backend + commit, delete, disposition, and recovery boundary preserve a valid prefix + and resolve without path inference, hidden success, or truncation of a + complete frame; only a proven EOF-interrupted final suffix with a valid old + Head is evidence-preserved and removed, and crashes before or after each + recovery-marker phase—including an interrupted first + `storage.initialized`, `RECOVERY_EVENT_ID_DURABLE`, and every partial retry + of the prepared recovery-entry frame—resume the same durably allocated + Event ID, Sigil, seed, template, and decision; +34. storage treats a Patch Bundle and other structured Blobs as opaque bytes, + and only an explicit higher-layer Reference Set can retain their typed + children; +35. reference closure follows multiple levels, handles cycles + deterministically, and aborts without deletion on an unknown extractor, + missing or mismatched Reference Set, node, edge, depth, byte, or time + bound, integer overflow, or changing source; +36. a new reference on any ancestor, a changed target relationship, a symlink, + mount, rename, inode or generation substitution, hard-link ambiguity, or + failed no-follow check skips the exact GC target after authorization while + leaving other targets independently revalidated; +37. normal and deep Storage Doctor produce closed Sigil-bound reports; deep + mode streams all required Replica, legacy-anchor, protected-copy, and + permitted quarantine bytes, and neither mode repairs, deletes, releases, + truncates, or hides an incomplete check; +38. byte, object, inode, stream, staging, quarantine, committed, and Journal + counters initialize all twelve legal class/dimension pairs; every mapped + `RESERVE`, partial retained `SETTLE`, terminal `SETTLE`, `PRESSURE`, and + `USAGE_REMOVED` effect replays to identical counters before a Job or write + handle is issued, the twelve initialization revisions use twelve distinct + `quota-counter::` identities, and an admitted operation + cannot exceed, reclassify, or borrow beyond its claims; the 61-row + capacity-source table is mechanically equal to both Event sets, every + owner plan covers its maximum Event/control/recovery counts, and every + system class satisfies the strict positive capacity formulas; +39. quota exhaustion never silently deletes payloads or history: + failed staging remains exactly `HELD_FOR_DISPOSITION` with its reservation + charged until an authorized transition, while Blob identity, Transfer, + provenance, failure, disposition, GC, and tombstone records survive + authorized byte removal; materialization cleanup and canonical-pin + reservations retain exactly their future-event claims, Materialization and + Quarantine residuals remain charged to their exact classes and owners, and + expiry is resolved only by the owning terminal Event; +40. disposition requires an unexpired exact authorization and conditional + generation match, exercises all four target-kind owner projections and the + complete present/absent/mismatch/expired/ambiguous Recovery matrix, cannot + use prefixes or wildcards, and cannot target an available Replica, + canonical file, legacy anchor or copy, Journal, or recovery material; an + authorization that expires in `AUTHORIZED` takes the null-intent + `AUTHORIZED -> FAILED` branch with no open intent, backend side effect, or + target-owner revision, while every post-start failure repeats the non-null + execution-intent ID; +41. `LOCAL-PHASE3/1.0` passes its exact Host crash, fsync, path, quota, + reconciliation, Doctor, GC dry-run, and manually authorized GC execution + suite but cannot advertise `PORTABLE-PHASE4/1.0`, remote durability, + automated GC, or replication evidence; +42. backend URIs, keys, and digest-shaped paths remain opaque operational + locators and cannot be disguised as new v1 Artifact locations; an explicit + verified materialization outside `.benchwork/storage/` can use the + unchanged v1 path; and +43. provisional-target rollback, GC, and disposition can remove only their + exact authorized physical bytes, never operational identity or history, + and availability projections clearly distinguish retained records from + available bytes; +44. `ST-` request and `SA-` attempt identities are never interchanged; retry + keeps the immutable request, creates a new attempt, and preserves every + prior attempt; +45. every owned contract and nested `$defs` object has positive, negative, + additional-property, discriminator, bound, sorting, and canonical-Sigil + fixtures generated from the exact field sets in this RFC, including every + closed verification-method and provenance-relation value, + Replica-verification, cleanup, Recovery-Marker, deadline, and open-intent + state/null branch; +46. Reference Set registration verifies the exact source and extractor, + exercises every legal relationship/source/target row, rejects every + unlisted combination, identity/Sigil mismatch, unknown relationship or + target kind, and retains Patch Bundle members only through explicit typed + edges; its semantic content deterministically selects one RS-ID and the + generic RS-derived SE-ID, while an execution ESM uses only its + ESM-ID-derived special SE-ID and one fixed candidate; first creation + freezes `created_at` and complete bytes, and crashes before or after + candidate durability, Event append, Head installation, readback, or + higher-layer request finalization reuse that exact set/Event rather than + allocating a new time or identity; RFC-0014 Evidence Manifest fixtures + exercise all 19 owner/role/Event projections and the suffix branch, exact + fixed extractor/validator Sigils, typed Blob/control edges and validation + evidence, and reject a bare/multiply typed Sigil, missing/extra member, + null-branch manifest, changed activation tuple, future Sigil, or second + activation; exact 4,096 member/edge/validation-evidence boundaries pass, + while a 4,097th aggregate member, edge, validation Sigil, same control + identity with another Sigil, or same Blob Sigil with another size fails + before registration; +47. crashes before intent, after `canonical_reference.intent_recorded`, after + the Chronicle Event, after its Receipt, and before + `canonical_reference.committed` deterministically recover without a false + release or lost pin; the intent accepts `0..4096` exact closure-derived + Blob Sigils, including the legal empty closure, rejects every partial or + mismatched set, and its lifecycle Reservation is non-expiring while + `OPEN` and settles on either terminal path; committed fixtures verify the + exact Event sequence/type/body, Receipt ID/Sigil, prior Receipt, request, + set projection, and resulting Head; abort fixtures require an advanced + verified Head and complete bound-Event absence proof; deterministic RI-ID + and global + `(canonical_event_type, transition_request_id)` uniqueness make an exact + retry reuse one immutable record and one creation Event, while different + bytes, duplicate IDs, or duplicate creation Events fail closed; +48. GC and a concurrent Athanor transition are linearized by the + canonical-reference gate under every lock-order and crash schedule, with an + ambiguous `OPEN` intent always protecting its Blob set; execution input + and output visibility uses the same gate with Storage hold first and + execution event second, while removal reverses that order; every execution + root resolves the exact ESM-sourced Reference Set and complete typed Blob + edge closure, and its `ACTIVE` hold targets that same Reference Set and is + created by the exact bound and authorized `retention.hold_set` Event—an + unrelated active Blob, Replica, Reference-Set hold, or changed + authorization is rejected; activation rejects a pre-existing EHR; every + RFC-0014 hold-set admission resolves the already-durable single-assignment + RootPlan and never a future Event/root, exact activation reproduces its + Event/type/field/root identity, a second field in one Event slot or a + changed/reused plan/Set/hold/policy is rejected, and crashes after slot + reservation, plan, or hold distinguish no-hold, plan-only, and unreleased + conservative previsibility orphans; all three execution EHR bases and all + four RFC-0014 + release conditions resolve the exact RootPlan and replayable release + record, exercise canonical, terminal/no-guard/suffix, trusted-clock/ + retention, exact prefix/Event order, intent-absence, and embedded + patch-abandonment branches, reject an earlier inactivation time, missing + canonical commit, unrelated guard, reversed abandonment guard order, + generic disposition, or bare evidence index, and retain on ambiguous proof; + and +49. a committed canonical pin has no v1 outbound transition, while a + provably uncommitted request uses only + `ABORTED_BEFORE_CANONICAL_COMMIT` with + `HEAD_SUPERSEDED_WITHOUT_BOUND_EVENT`; an unchanged Head, existing bound + Event, wrong Receipt/Head, or any `CANONICAL_REFERENCE_REMOVED` branch is + rejected; and +50. UTC forward steps, rollback, slew, monotonic-domain loss, and restart + expire on the earlier trusted UTC or live process-local monotonic + condition, treat every deadline whose future cannot be proved as due, + never compare monotonic ticks across restart or extend authority, and close + the clock gate before any time-authorized finalization, release, + disposition, or GC when trust is uncertain; +51. a canonical-reference operation accepts a `chronicle-head/1.1` + `event_count` only below `U63_MAX`, permits the next Event while its + resulting count remains representable, and at or above that boundary + fails before Reference Set registration, intent, reservation, or Chronicle + side effects without wrapping, clamping, or losing existing state; +52. every `ExecutionStorageRoot` has exactly the nine printed fields, sorts + with its ESM identity, and resolves the same RFC-0012 ESM ID/Sigil, owner, + `PLANNED` protection plan, ESM-ID-derived registration ID, preallocated + hold Event ID, exact neutral policy tuple, Reference Set, active hold, + domain-separated hold-set authorization, fixed extractor/validator, + deterministic validation evidence, and complete typed Blob edge closure; + a missing manifest pair, second byte sequence or Set for one ESM-ID, generic + SE-ID substitution, owner mismatch, swapped planned Event, future-Sigil + evidence, valid but unrelated set or hold, or incomplete edge projection + is rejected; +53. `ATTEMPT_OUTPUT` fixtures exercise the exact `INGEST` request matrix, + accepted Result staging-reference branch, opaque handle and exact derived + subject mapping, current `LEASED` execution and public fence, exact + Blob/byte bound, backend, current `TransferRef`, and current `CAPTURED` + provenance for `NEW_REPLICA`, `DEDUPLICATED_REPLICA`, Quarantine, and + terminal-negative outcomes; every cross-field mismatch fails, and the + DEDUP fixture proves that a historical selected Replica's creator Attempt, + record Sigils, terminal Event, or provenance cannot replace the current + Result-bound `ST-ID`/`SA-ID` chain; and +54. execution-owned output-hold tests derive the checked `release_due_at` + from the exact parent Job terminal Event for zero and positive durations, + produce only the exact RFC-0012 schedule, map overflow to the terminal + time with `OVERFLOW_FAIL_CLOSED`, fail the `CODE_MODIFICATION` success + guard, and release that inactivated execution hold immediately; a + clock-uncertain schedule remains protected without recomputing its + deadline, canonical replacement and every earlier narrowing are rejected, + and at an exact or overflow-mapped expiry Storage resolves the one + `OUTPUT_DEADLINE` EHR and appends one + `EXECUTION_HOLD_LIFETIME_EXPIRED` release for the exact `SH-ID` with the + EHR-bound authorization, exact evidence, and one `HOLD` revision; retry + reuses its EHR and exact Storage `EventRef` in + `storage_root.hold_release_observed`; input owner-terminal and orphan-abort + fixtures exercise their distinct reason/evidence/activation rules, with no + execution observation fabricated for the never-activated orphan; policy + `retain_until`, canonical and legal/preservation roots, Quarantine, + different holds, Blob/Replica records, and surviving GC protection remain + unchanged, and the release alone never authorizes byte deletion. + +The acceptance report must map every test to the exact backend profile, +conformance-suite Sigil, Host platform, source revision, and retained evidence. diff --git a/docs/en/rfcs/RFC-0014-patch-promotion-protocol.md b/docs/en/rfcs/RFC-0014-patch-promotion-protocol.md new file mode 100644 index 0000000..be113ee --- /dev/null +++ b/docs/en/rfcs/RFC-0014-patch-promotion-protocol.md @@ -0,0 +1,4880 @@ +--- +title: "RFC-0014: Patch Promotion Protocol" +document_id: BW-RFC-0014 +version: 0.1 +status: draft +owner: unassigned +date: 2026-07-31 +language: en +canonical: true +--- + +# RFC-0014: Patch Promotion Protocol + +## Status + +This draft defines the Phase 3 protocol for exporting a bounded change from a +Crucible, validating it as an immutable Patch Proposal, and promoting that +exact proposal into a human-selected target through Host-native file and Git +tools. + +This RFC depends on RFC-0011's control-plane and execution-plane separation, +RFC-0012's terminal Attempt and fencing rules, and RFC-0013's content-addressed +Blob and Artifact contracts, plus RFC-0015's immutable Job Outcome, +`agent-result/2.0`, and shared MCP Registry contracts. It does not authorize +automatic patch application, merge, commit, push, or publication. Executable +Schemas and the reference adapter are required before this protocol can advance +from draft. + +## Problem + +A Worker may modify files in a mutable Crucible, but neither Job completion nor +the existence of a textual diff proves that the change: + +- was produced from the declared base; +- contains only files and entry types allowed by the Task; +- can be reproduced without fuzzy patch application; +- passed the declared validation policy; +- still applies to the researcher's current target; +- was explicitly selected by a human for that exact target; or +- was applied once, completely, and recoverably. + +The existing Phase 2 `code-modification-result/1.0` contract records a patch +string, changed files, tests, a validation summary, and residual risks. Those +fields are sufficient for a bounded Agent Result Proposal, but they do not bind +an immutable Crucible base, machine-verifiable preimages and postimages, +validation evidence, a promotion target, or a recoverable application +operation. Reinterpreting that contract as application authority would change +its accepted meaning and make a Provider-authored string capable of driving a +repository mutation. + +Patch handling also crosses two authority domains. Athanor and Chronicle own +accepted canonical research records. The interactive Host owns repository +reads, file edits, patch operations, shell, and Git through native tools. If +Athanor applied a patch, MCP exposed a Git proxy, or a Worker wrote directly to +the researcher's worktree, the Phase 2 Host boundary would be erased. If the +Host applied a patch without an identity-bound record, Benchwork could not +distinguish the intended change from a stale, duplicated, partial, or +conflicting mutation. + +## Decision + +Benchwork treats a patch as an immutable, content-identified Proposal and +treats promotion as a separately authorized, Host-native side effect. + +```text +pinned Crucible Base Identity + | + v + terminal eligible execution result + + retained terminal Crucible identity + | + v + Athanor accepts agent-result/2.0 -> Receipt + | + v +trusted derived export: stage -> verify -> commit Blobs + | + v + Reference Set candidate + Patch Proposal candidate + + expected Postimage Identity + | + v + acquire RFC-0013 gate -> register Reference Set + -> final request Sigil -> canonical-reference intent + | + v + Athanor accepts patch.proposed -> Receipt + | + v + isolated validation materialization + evidence + | + v + immutable promotion preview + | + v + explicit human authorization -> Chronicle Receipt + | + v + Host-native exact apply + postimage verification + | + v + Athanor accepts terminal promotion record + | + v + Chronicle Receipt +``` + +Validation never promotes a patch. Human authorization never makes a patch +scientifically correct. A promotion authorization Receipt permits one exact, +idempotent application operation; it is not evidence that the operation +completed. A terminal promotion Receipt proves that Athanor accepted the +declared identities, evidence, and outcome under this protocol. It does not +prove that an external Git remote, branch, pull request, or deployment exists. + +Patch derivation is post-terminal. An immutable Execution Job, +`execution-result/1.0`, accepted `agent-result/2.0`, or Job observation is +never reopened or backfilled with a Patch Proposal ID. The derived Proposal +points backward to exactly one accepted `agent-result/2.0` Receipt. A read +projection may later show that relationship without changing either source +record. + +Phase 3 adds these exact closed, versioned contracts: + +| Contract identifier | Purpose | +| --- | --- | +| `patch-base-identity-profile/1.0` | Complete-tree identity and path-semantics profile. | +| `patch-tree-manifest/1.0` | Canonical complete-tree manifest used by Base, Postimage, retained terminal-source, and Promotion Target identities. | +| `patch-base/1.0` | Immutable complete Base manifest identity and execution provenance. | +| `patch-bundle/1.0` | Primitive operations, explicit preimages and postimages, payload Blob references, and review renderings. | +| `patch-proposal/1.0` | Post-terminal derived Proposal bound to one accepted Agent Result Receipt. | +| `patch-validation-policy/1.0` | Required validation checks, evidence, assurance, and independence rules. | +| `patch-validation-evidence/1.0` | One immutable validation observation for the exact Proposal. | +| `patch-target-state-evidence/1.0` | Bounded verifier evidence for one Promotion Target identity. | +| `patch-promotion-adapter/1.0` | Closed Host-native application, deadline, guard, and recovery profile. | +| `patch-promotion-preview/1.0` | Immutable expiring human decision boundary for promotion or recovery. | +| `patch-promotion-authorization/1.0` | Canonical authorization bound to one exact Preview. | +| `patch-promotion-rejection/1.0` | Canonical explicit rejection bound to one exact Preview. | +| `patch-promotion-attempt/1.0` | One immutable allocation of a Host-native Promotion Attempt. | +| `patch-promotion-target-guard/1.0` | Target guard lifecycle, ownership, coordinator epoch, backend generation, fence floor, and expiry. | +| `patch-promotion-checkpoint/1.0` | Verified recoverable preimage checkpoint for one Attempt and affected-path set. | +| `patch-promotion-mutation-intent/1.0` | Write-ahead binding for all promotion side effects. | +| `patch-promotion-outcome/1.0` | One terminal Promotion Attempt outcome. | +| `patch-promotion-recovery-attempt/1.0` | One separately authorized attempt to recover a prior `PARTIAL` outcome. | +| `patch-promotion-recovery-intent/1.0` | Write-ahead binding for one exact recovery action. | +| `patch-promotion-recovery-record/1.0` | Terminal evidence for one Recovery Attempt. | +| `patch-promotion-journal-event/1.0` | Hash-chained operational event with closed type-specific payloads. | +| `patch-promotion-journal-head/1.0` | Replaceable cache for the verified Promotion Journal head. | +| `patch-promotion-state/1.0` | Deterministic replay projection for clock authority, Previews, decisions, guards, attempts, checkpoints, and recovery. | +| `patch-promotion-prepare-request/1.0` | Closed request for preparing a promotion or recovery Preview. | +| `patch-promotion-prepare-response/1.0` | Closed bounded response containing the exact Preview. | +| `patch-promotion-inspect-request/1.0` | Closed read request for one Proposal, Preview, decision, Promotion Attempt, or Recovery Attempt. | +| `patch-promotion-inspect-response/1.0` | Closed bounded and paginated inspection response. | +| `patch-promotion-authorize-request/1.0` | Closed `AUTHORIZE \| REJECT` human-decision request for one Preview. | +| `patch-promotion-authorize-response/1.0` | Closed response union containing the authorization or rejection Receipt and identity. | +| `patch-promotion-outcome-request/1.0` | Closed request for accepting one terminal Promotion or Recovery record. | +| `patch-promotion-outcome-response/1.0` | Closed response containing the accepted terminal Receipt. | +| `patch-operational-root-plan/1.0` | Immutable previsibility plan binding one future protection-bearing Promotion Event to its exact OperationalRoot identity. | +| `patch-operational-evidence-manifest/1.0` | Closed event-evidence or Journal-suffix source manifest with deterministic Storage extraction. | +| `patch-operational-root-release-evidence/1.0` | Replayable root, guard, canonical, retention, clock, and absence proof for one Storage hold release. | + +Each identifier is published at the corresponding +`https://benchwork.dev/schemas//` `$id` and conventional +`-.json` filename; for example, +`patch-promotion-journal-event/1.0` is +`patch-promotion-journal-event-1.0.json`. Aliases or differently versioned +filenames do not satisfy the contract. + +### Exact contract shapes + +The following notation is normative. `V` is the exact version string `x`; +`ID

` is an ASCII string of 3 through 128 bytes matching +`P-[A-Za-z0-9][A-Za-z0-9._-]*`; `StorageID

` is an exact RFC-0013 +operational ID: an ASCII string of 3 through 128 bytes matching +`P-[A-Za-z0-9][A-Za-z0-9._:-]*`; `StorageOpaque` is RFC-0013 `Opaque`: +printable UTF-8 of 1 through 256 bytes with no NUL or control character; +`OJID` is RFC-0015's exact 67-byte ASCII +`OJ-[A-F0-9]{64}` domain; `Sigil` is exactly the 71-byte ASCII form +`sha256:` followed by 64 lowercase hexadecimal SHA-256 characters; `Time` is +exactly RFC-0013 `Timestamp`: normalized UTC RFC 3339 ending in `Z`, with no +leap second and at most six fractional digits; `U64` is an integer in +`0..18446744073709551615`; `Text` is +well-formed UTF-8 of `0..N` bytes with no NUL or disallowed control character; +`Enum` is one listed ASCII value; and `Nullable` is exactly JSON +`null` or `T`, never omission. `Token` is exactly 43 ASCII bytes matching +`[A-Za-z0-9_-]{43}`, the unpadded base64url encoding of 32 bytes; `True` is +exactly the JSON boolean `true`; `Bool` is exactly a JSON boolean; and `U63` +is an integer in `0..9223372036854775807`; `PositiveU63` is `U63` in +`1..9223372036854775807`. `Ref` is the closed object +`{id: ID, sigil: Sigil}`; `BlobRef` is the closed object +`{sigil: Sigil, size_bytes: U63, media_type: Text<128>}`. Its `sigil` value +equals RFC-0013 `BlobRef.blob_sigil` and its `size_bytes` value equals +RFC-0013 `BlobRef.size_bytes`; this is an exact value mapping, not an identical +object shape, because local `media_type` is additional bounded observation +metadata. `ReceiptRef` is the +closed object `{receipt_id: ID, receipt_sigil: Sigil, +event_id: ID, event_body_sigil: Sigil}`; and `Rev` is the closed object +`{entity_type: Text<64>, entity_id: Text<128>, prior_revision: U64, +next_revision: U64}` with `next_revision = prior_revision + 1`. +`StorageEventRef`, `StoragePrefixRef`, `ReferenceSetRef`, `HeadRef`, +`ChronicleHeadRef`, and `HoldBinding` are exact closed `$defs` below rather +than instances of generic `Ref`. In particular, a replaceable Journal Head +has no synthetic object ID. + +`List` preserves order and may repeat. `Set` is sorted +strictly by the named key using unsigned UTF-8 byte order and contains no +duplicate key; `value` means the primitive value itself and a parenthesized +tuple means lexicographic comparison of its members. `Obj` and `Union` +name the exact closed local `$defs` published in this RFC. `Obj` is a +required object with exactly the named members. `Union` is exactly one +discriminated branch; overlapping validation is forbidden. Those definitions +may use only the primitives above and state every member, branch discriminator, +and bound. The shared hard +caps are `MAX_PATHS = 100000`, `MAX_BLOBS = 4096`, +`MAX_EVIDENCE = 4096`, `MAX_LOG_REFS = 256`, `MAX_PAGE = 256`, +`MAX_STATE_ENTITIES = 1000000`, `MAX_DIAGNOSTIC_BYTES = 65536`, and +`MAX_RECORD_BYTES = 8388608`. `MAX_ACTIVE_GUARDS = 4096` is a project-global +admission cap, not a per-page limit. No Schema may raise a cap through +configuration. The complete canonical JSON of every top-level contract, +including State, is at most `MAX_RECORD_BYTES`; admission uses the lower limit +implied by that byte cap and every field/cardinality cap. A field maximum never +permits construction of an oversized record, and a Journal append that would +make the next projected State oversized is rejected before visibility. + +`MAX_BLOBS` is also the aggregate cap on the sorted unique managed-Blob +closure of one Patch Bundle, derived Reference Set, operational hold, or +canonical transition request. Per-field maxima do not add: the retained +RFC-0012 source bundle, Base and Postimage manifests, the Patch Bundle Blob +itself, payloads, renderings, attachments, checkpoints, verifier evidence, and +other retained Blob members together must fit that one cap. Before any staging +reservation or write, Blob commit, +Reference Set registration, control-record creation, journal append, +canonical submission, or target side effect, Export, Prepare, and canonical +submission compute the complete candidate bytes and Sigils read-only, form +the exact distinct managed-Blob closure, and reject a 4,097th value. Later +steps must equal that preflight plan byte-for-byte and cannot discover or add +a Blob. This is the exact RFC-0013 Reference Set and reference-intent +collection bound; no sharding or implicit continuation is part of v1. +For a `patch-operational-evidence-manifest/1.0`, the distinct +`control_records` plus `blob_refs`, the extracted Reference Set `edges`, and +the computed `validation.evidence_sigils` are three separately bounded +collections and each has cardinality at most 4,096. The complete candidate +for all three is computed before Manifest finalization; exceeding any bound +fails before Manifest, Reference Set, plan, hold, or Promotion visibility. +The Event-specific `S(...)` union below is likewise a cross-field bound of at +most `MAX_EVIDENCE`: individual field maxima never authorize an aggregate +4,097th evidence Sigil. + +Every row below is the complete top-level field set. Every field is required; +conditional values use `Nullable`. Objects and all nested objects have +`additionalProperties: false`; arrays have exact `minItems`, `maxItems`, sort, +and uniqueness constraints shown by their type. Except for the two raw-token +request/response exceptions stated below, the final named `*_sigil` is +the `sha256:` prefix followed by lowercase hex SHA-256 over canonical JSON +with only that field omitted. Canonical JSON +rejects duplicate keys, non-integer numbers, non-finite values, invalid UTF-8, +and non-canonical escapes. + +| # | Contract | Exact required top-level fields and types | Self-Sigil | +| --- | --- | --- | --- | +| 1 | `patch-base-identity-profile/1.0` | `schema_version: V<1.0>`; `profile_id: ID`; `path_scope: Obj`; `path_semantics: Obj`; `entry_semantics: Obj`; `limits: Obj`; `protected_paths: Set,1..256,value>`; `profile_sigil: Sigil` | `profile_sigil` | +| 2 | `patch-base/1.0` | `schema_version: V<1.0>`; `base_id: ID`; `identity_profile: Ref`; `source_root_id: Text<128>`; `source_root_sigil: Sigil`; `scope: Obj`; `tree: Obj`; `manifest_blob: BlobRef`; `input_artifacts: Set,0..MAX_BLOBS,id>`; `execution: Obj`; `vcs: Nullable>`; `base_sigil: Sigil` | `base_sigil` | +| 3 | `patch-bundle/1.0` | `schema_version: V<1.0>`; `bundle_id: ID`; `base: Ref`; `postimage: Obj`; `operations: Set,1..MAX_PATHS,path_bytes>`; `payloads: Set`; `renderings: Set`; `attachments: Set`; `source: Obj`; `agent_result_receipt: ReceiptRef`; `limits: Obj`; `bundle_sigil: Sigil` | `bundle_sigil` | +| 4 | `patch-proposal/1.0` | `schema_version: V<1.0>`; `proposal_id: ID`; `agent_result: Obj`; `job_outcome: Obj`; `terminal_source: Obj`; `bundle: Ref`; `base: Ref`; `postimage: Obj`; `reference_set: Obj`; `control_bindings: Obj`; `execution_bindings: Obj`; `identity_profiles: Obj`; `scope: Obj`; `changed_paths: Set,1..MAX_PATHS,value>`; `summary: Text<16384>`; `claimed_intent: Text<16384>`; `residual_risks: List,0..256>`; `exporter: Obj`; `created_at: Time`; `promotion_eligibility: Enum`; `proposal_sigil: Sigil` | `proposal_sigil` | +| 5 | `patch-validation-policy/1.0` | `schema_version: V<1.0>`; `policy_id: ID`; `policy_version: Text<64>`; `required_checks: Set,1..MAX_EVIDENCE,check_id>`; `optional_checks: Set,0..MAX_EVIDENCE,check_id>`; `minimum_assurance: Obj`; `independence: Obj`; `environment_rules: List,0..256>`; `limits: Obj`; `policy_sigil: Sigil` | `policy_sigil` | +| 6 | `patch-validation-evidence/1.0` | `schema_version: V<1.0>`; `evidence_id: ID`; `proposal: Ref`; `bundle: Ref`; `base: Ref`; `postimage: Obj`; `policy: Ref`; `check: Obj`; `validator: Obj`; `execution: Obj`; `started_at: Time`; `terminal_at: Time`; `status: Enum`; `exit_result: Obj`; `observations: List,0..256>`; `logs: Set`; `result_artifacts: Set,0..MAX_EVIDENCE,id>`; `postimage_before: Obj`; `postimage_after: Obj`; `limitations: List,0..256>`; `residual_risks: List,0..256>`; `evidence_sigil: Sigil` | `evidence_sigil` | +| 7 | `patch-target-state-evidence/1.0` | `schema_version: V<1.0>`; `evidence_id: ID`; `target_id: ID`; `identity_profile: Ref`; `observed_identity: Obj`; `target_content_generation: Text<256>`; `root_identity: Obj`; `observed_at: Time`; `verifier: Obj`; `bounds: Obj`; `evidence_blobs: Set`; `evidence_sigil: Sigil` | `evidence_sigil` | +| 8 | `patch-promotion-adapter/1.0` | `schema_version: V<1.0>`; `adapter_id: ID`; `adapter_version: Text<64>`; `mode: Enum`; `platform_profile: Obj`; `deadline_policy: Obj`; `guard_backend: Obj`; `mutation_semantics: Obj`; `recovery_semantics: Obj`; `disabled_behaviors: Set,1..256,value>`; `limits: Obj`; `configuration_sigil: Sigil`; `adapter_sigil: Sigil` | `adapter_sigil`; `configuration_sigil` covers exactly `{platform_profile, deadline_policy, guard_backend, mutation_semantics, recovery_semantics, disabled_behaviors, limits}` | +| 9 | `patch-promotion-preview/1.0` | `schema_version: V<1.0>`; `preview_id: ID`; `kind: Enum`; `proposal: Ref`; `bundle: Ref`; `base: Ref`; `postimage: Obj`; `validation_policy: Ref`; `validation_evidence: Set,1..MAX_EVIDENCE,id>`; `target: Obj`; `target_state_evidence: Ref`; `target_content_generation: Text<256>`; `adapter: Ref`; `mode: Enum`; `deadline_policy_sigil: Sigil`; `affected_paths: Set,1..MAX_PATHS,value>`; `operation_sigil: Sigil`; `recovery: Nullable>`; `abandoned_lineage: Nullable>`; `residual_risks: List,0..256>`; `prepared_at: Time`; `expires_at: Time`; `idempotency_key_sigil: Sigil`; `confirmation_token_sigil: Sigil`; `preview_sigil: Sigil` | `preview_sigil`; raw token is not a field | +| 10 | `patch-promotion-authorization/1.0` | `schema_version: V<1.0>`; `authorization_id: ID`; `decision: Enum`; `preview: Ref`; `proposal: Ref`; `target_state_evidence: Obj`; `target_content_generation: Text<256>`; `affected_paths: Set,1..MAX_PATHS,value>`; `operation_sigil: Sigil`; `residual_risks: List,0..256>`; `target: Obj`; `base: Ref`; `postimage: Obj`; `validation: Obj`; `adapter: Ref`; `mode: Enum`; `recovery: Nullable>`; `abandoned_lineage: Nullable>`; `expires_at: Time`; `confirmation_token_sigil: Sigil`; `actor: Obj`; `host: Obj`; `decision_at: Time`; `clock_policy_sigil: Sigil`; `clock_evidence_sigil: Sigil`; `request_sigil: Sigil`; `idempotency_key_sigil: Sigil`; `authorization_sigil: Sigil` | `authorization_sigil`; the later canonical Event, not this candidate, binds the reference intent and set | +| 11 | `patch-promotion-rejection/1.0` | `schema_version: V<1.0>`; `rejection_id: ID`; `decision: Enum`; `preview: Ref`; `proposal: Ref`; `actor: Obj`; `host: Obj`; `reason: Text<4096>`; `decision_at: Time`; `clock_policy_sigil: Sigil`; `clock_evidence_sigil: Sigil`; `request_sigil: Sigil`; `idempotency_key_sigil: Sigil`; `rejection_sigil: Sigil` | `rejection_sigil`; the later canonical Event, not this candidate, binds the reference intent and set | +| 12 | `patch-promotion-attempt/1.0` | `schema_version: V<1.0>`; `attempt_id: ID`; `authorization: Ref`; `operation_sigil: Sigil`; `target: Obj`; `target_content_generation: Text<256>`; `adapter: Ref`; `mode: Enum`; `created_at: Time`; `state: Enum`; `revision: U64`; `attempt_sigil: Sigil` | `attempt_sigil` | +| 13 | `patch-promotion-target-guard/1.0` | `schema_version: V<1.0>`; `guard_id: ID`; `target_id: ID`; `owner_kind: Enum`; `owner_attempt_id: Text<128>`; `coordinator_id: ID`; `coordinator_epoch: U64`; `state: Enum`; `backend_generation: Nullable>`; `fencing_generation: U64`; `fence_floor: U64`; `target_content_generation: Text<256>`; `deadline_policy_sigil: Sigil`; `clock_anchor_evidence_sigil: Nullable`; `original_remaining_ns: Nullable`; `acquired_at: Nullable