← Specification catalog

Specification authority

spec-spine

Compile authored specs and inspect their authority and ownership graph.

Observed 2026-10-03 · Producer spec-spine 0.28.0

943504d6b82a9a6b40f7f3f82f39f89255aaa0d9 ↗

182 specifications. Verification and deployment qualification are unknown in this export.

000-spec-spine-bootstrap

Bootstrap spec system (markdown → compiled JSON authority ledger)

Foundational contract for spec-spine. Authored truth lives only in markdown (with YAML frontmatter); machine-consumable truth is compiler-emitted JSON only; the corpus compiles in full from day one; every artifact-producing step is a pure, deterministic function of (config, file contents); and a typed authority graph, not a flat pile of claims, governs who may change what. This spec defines what a spec IS. It is tier-1: its `unamendable` anchors are non-overridable, and it bootstraps the corpus by declaring authority it has held since before the graph existed (`origin.retroactive`).

Lifecycle approvedImplementation complete
001-compile-registry

Compile the spec corpus into a deterministic registry

The compile capability: read every specs/NNN-slug/spec.md, validate its frontmatter, and emit a deterministic spec-as-source registry.json plus a non-deterministic build-meta.json. Establishes the spec-spine-core engine scaffolding (content hashing, canonical JSON, markdown heading extraction) and the `spec-spine compile` CLI subcommand.

Lifecycle approvedImplementation complete
002-registry-query

Typed read-only query over the registry

The query capability: typed, read-only access over a loaded registry: list, show, status-report, and relationships; plus load_registry (which rejects an unknown MAJOR schema version). Establishes the query module and the `spec-spine registry` CLI subcommands; extends 001's public surface additively.

Lifecycle approvedImplementation complete
003-conformance-lint

Corpus conformance lint

The conformance lint: corpus well-formedness checks beyond compile's structural validation, emitted as L-codes with error/warning/info tiers. Error-tier always fails; warning-tier fails under --fail-on-warn; info-tier under --fail-on-info. Establishes the lint engine and the `spec-spine lint` CLI subcommand.

Lifecycle approvedImplementation complete
004-codebase-index

Codebase index and the file/section/symbol unit grammar

The codebase indexer (code-as-source view): discover compilation units via the configurable manifest namespace, link code to specs three ways, resolve the file/section/symbol unit grammar to physical (file, line-span) locations (tree-sitter for symbols, Rust + TypeScript in v1), emit a deterministic index.json with a content-hash staleness mechanism, and answer authorities(unit). Establishes the manifest/section/symbol resolution layer and the `spec-spine index` CLI subcommand.

Lifecycle approvedImplementation complete
005-coupling-gate

The coupling gate: join the two views and refuse drift

The PR-time coupling gate (spec-spine.md guardrail 2): cross-reference every changed code path against the authority graph and refuse the merge when code drifts from the spec that owns it. A pure function of (config, registry, index, diff, waiver); the CLI parses `git diff --no-color -U0 --no-renames base...head` (quotepath off, end-of-options) into a typed DiffInput so git stays out of core. Matches a diff hunk to its owning unit at file, section, and symbol granularity; is amends-aware with the FR-005 strict-expansion guard; transfers authority across supersedes; honors PR-body waivers and an additive bypass list. Establishes the couple engine and the `spec-spine couple` subcommand.

Lifecycle approvedImplementation complete
006-distribution

Distribution: npm binary shim + the release pipeline

How the spec-spine CLI reaches users who do not have a Rust toolchain. Two channels already existed but were unspecced: the tag-gated GitHub release pipeline (release.yml) that builds the five per-triple archives, and install.sh (curl | sh) that consumes them. This spec adopts both as governed territory and adds a third channel: an npm binary-distribution shim under npm/ so a TS/JS repo can `npm i -D spec-spine` and get the CLI with no Rust. The shim is a launcher, not a native addon: the main package's bin resolves a per-triple platform package (optionalDependencies, os/cpu-gated) and exec's the prebuilt binary, forwarding argv and exit code. No postinstall, no network at install, no archive extraction; it works under `npm ci --ignore-scripts` and offline. Version-locked to the binary release tag (npm 0.1.0 ships the v0.1.0 assets).

Lifecycle approvedImplementation complete
007-python-distribution

Distribution: uvx/PyPI wheel shim

The Python parallel of 006's npm shim: a uvx/PyPI channel so a Python or polyglot team can `uvx spec-spine ...` (or `uv tool install spec-spine`) and get the CLI with no Rust toolchain. Where npm uses a main launcher package + five os/cpu-gated platform packages, Python collapses the same idea into one PyPI project + five per-platform wheels (+ one sdist): the wheel platform tag IS the selector, and the prebuilt binary ships in the wheel's data/scripts dir so pip/uv place it directly on PATH. There is no Python in the run path, no postinstall, no network at install, and no archive extraction at install: it works offline and under --no-build-isolation. Unsupported hosts (musl/Alpine, win-arm64, 32-bit) match no wheel and fall to the sdist, whose only artifact is a `spec-spine` console entry point that names the host and points at `cargo install spec-spine-cli` -- exact parity with 006 §3.4. Wheels are assembled from the same release archives the build job already produces (no second Rust build), are byte-reproducible, and are version-locked to the tag.

Lifecycle approvedImplementation complete
008-coupling-floor-claim-precedence

Coupling gate: explicit unit claims take precedence over the bypass floor

Amends 005 §3.5: a path covered by an explicit unit claim (a file, section, or symbol declared in some spec's ownership-bearing edge and resolved into the index) is NEVER bypassed, even when it matches the hardcoded floor or an adopter's additive bypass list. Implicit path-level ownership (manifest floors, comment headers) keeps deferring to bypass. Closes the silent- weakening hole where an adopter who governs workflow YAML or specific docs by explicit claim would have those claims ignored because `.github/` and `docs/` sit on the immutable floor.

Lifecycle approvedImplementation complete
009-registry-query-projection-flags

Registry query: --ids-only and --nonzero-only projection flags

Two additive projection flags on the spec 002 query verbs: `registry list --ids-only` (newline-delimited spec ids; a JSON string array under --json) and `registry status-report --nonzero-only` (omit zero-count statuses). Both are load-bearing in adopter session-init protocols (OAP's /init and /setup read exactly these projections); neither changes any existing output shape when absent.

Lifecycle approvedImplementation complete
010-index-render-orphans

Index: implement the reserved render and orphans subcommands

Implements the two subcommands spec 004's CLI reserved but stubbed: `spec-spine index render` (a deterministic human-shaped markdown projection of the committed index.json -- package inventory, traceability, diagnostics) and `spec-spine index orphans` (the orphanedSpecs list, newline ids or a --json array). Both are pure read-side projections of the committed artifact; neither recomputes the index. Establishes the render module; extends the index CLI dispatch.

Lifecycle approvedImplementation complete
011-index-hash-slices

Index: named hash slices and `index check --slice <name>`

Named sub-hashes of the index's input set: an adopter declares glob groups under `[index.slices]`, the indexer emits a per-slice content hash into `build.sliceHashes` (additive index-schema MINOR), and `spec-spine index check --slice <name>` gates on exactly that slice -- fresh (0), stale (2), config/IO error (3). Generalizes the narrow high-value staleness gate OAP runs over its agent-config files (settings/MCP JSON) without hardcoding any adopter's file list into the core.

Lifecycle approvedImplementation complete
012-declared-extra-frontmatter-passthrough

Frontmatter: declared extra keys carry arbitrary YAML, verbatim

Widens the value domain of DECLARED extra-frontmatter keys (`config.frontmatter.extra_known_keys`) from scalars + string-lists to any JSON-representable YAML value (nested maps, mixed lists), carried verbatim into SpecRecord.extra_frontmatter under canonical-JSON normalization. Undeclared keys keep the existing scalar/string-list restriction and the V-007 cap -- the anti-bulk-YAML guard is untouched. Registry schema MINOR. This is the load-bearing half of the overlay contract: overlays cannot consume domain frontmatter the compiler drops.

Lifecycle approvedImplementation complete
013-edge-paths-grammar-sugar

Edge grammar: `paths:` list sugar on extends/refines items

Authoring sugar for the predecessor dialect: an `extends:` or `refines:` item may declare `paths: [<file>, ...]` instead of a single `unit:`; the compiler expands each path to a file unit at parse time, emitting N normalized single-unit edges into the registry. Consumers see exactly one edge shape -- `paths:` never reaches registry.json. `unit:` and `paths:` on the same item is malformed (V-002). Unblocks adopting a corpus authored in the OAP dialect (140+ extends-bearing specs) without a mass frontmatter migration.

Lifecycle approvedImplementation complete
014-establishes-wrapper-na-alias

Frontmatter sugar: `unit:`-wrapped establishes items and the `n/a` implementation alias

Two pieces of authoring sugar for the predecessor dialect, both normalized away at parse so nothing new reaches registry.json. (1) An authority unit may be written as a single-key `{ unit: <unit> }` wrapper -- a third 1:1 representation alongside the existing bare-string and tagged-map forms -- which the Unit deserializer unwraps to its inner unit. The corpus uses this on `establishes` items (one wrapper per item, `unit` the only field). (2) `implementation: n/a` is accepted as a deserialize-only alias for the canonical `n-a`, which stays the only spelling ever emitted. Neither changes the registry schema or any consumer; both unblock adopting a corpus authored in the OAP dialect (389 wrapped establishes items across ~97 specs; 5 specs using `n/a`) without a mass frontmatter migration.

Lifecycle approvedImplementation complete
015-short-id-resolution

Compile: resolve short spec ids on `depends_on` and `superseded_by`

Lets a `depends_on` or `superseded_by` reference name its target by the leading spec number alone (`109`) and have the compiler resolve it to the full id (`109-slug`) when exactly one spec matches that number, rewriting the reference before validation and emission. An exact id, an ambiguous number, or a number matching nothing is left unchanged, so V-008 (superseded_by must resolve) and V-010 (dangling depends_on) still fire on a genuinely broken reference. The resolution policy is the indexer's existing `resolve_id` (spec 004), applied at compile time so the registry and the index agree. Unblocks adopting a corpus that references specs by number (106 specs in the OAP dry-run) without a mass frontmatter migration; a no-op on any corpus that already uses full ids (such as this repo's own).

Lifecycle approvedImplementation complete
016-directory-crate-module-units

Authority units: resolve the reserved directory / crate / module kinds

Lifts the v1 unit-grammar limitation (`docs/design/00-architecture.md` §2.2, Q5): the three reserved kinds (`directory`, `crate`, and `module`) are now parsed and resolved alongside `file`/`section`/`symbol`. A `directory` unit resolves to its subtree (existence-checked, I-007 on miss); a `crate` unit resolves a manifest name against the discovered package inventory to that package's directory subtree (I-003 on miss, hyphen/underscore interchangeable); a `module` unit resolves a `::`-qualified Rust module path via a new module index; file-modules whole-file, top-level inline `mod` blocks to their span (I-008 on miss). The registry/index schemas are permissive on the unit payload, so this is an additive MINOR (`INDEX_SCHEMA_VERSION` 0.2.0 → 0.3.0) with no schema-file edit. Unblocks adopting the OAP corpus, which authors these three kinds across ~192 unit declarations (93 `directory`, 99 `crate`, 1 `module`) with no mass frontmatter migration.

Lifecycle approvedImplementation complete
017-constrains-discriminator-optional-unit

Constrains grammar: classification discriminator and optional unit

Widens the `constrains` item grammar to the two shapes the predecessor dialect authors: a **path-scoped** constraint (`unit:`, the canonical `invariant-freeze` over a file or schema) and a **spec-scoped** constraint (`target_specs:`, a sequencing/ordering plan over other specs with no code unit). Two changes: `unit` becomes optional, and an optional documentary discriminator is accepted under either spelling `flavor:` or `kind:` (synonyms, not gate-load-bearing). A new V-011 requires every item to scope at least one of `unit` / `target_specs`. A spec-scoped item contributes no resolved unit to the index. Unblocks the OAP corpus, which authors `{ flavor: invariant-freeze, unit }` (spec 130) and `{ kind: <plan>, target_specs }` (specs 093, 089), both rejected by the unit-required, discriminator-less v1 grammar.

Lifecycle approvedImplementation complete
018-structured-partial-supersedes

Supersedes grammar: structured items and partial (unit-scoped) transfer

Implements Option A from `docs/design/01-partial-supersession.md`: a `supersedes` item may be a bare predecessor id (full) or a structured `{ spec, scope, unit?, note?, rationale? }`. A full item (bare id or `scope: full`) normalizes to the bare-string form, so a full-only corpus emits a byte-identical registry; the wire is unchanged for every existing adopter; only a partial item serializes as an object. `scope: partial` with a `unit` transfers authority over that unit alone (additive, the predecessor keeps it and everything else), threaded through the index as a `supersedes` resolved unit so the gate already treats the successor as an owner of that unit's paths; `build_superseders` therefore contributes only **full** supersession to the whole-spec transfer. A partial item with no unit (a documentary lifecycle marker) transfers nothing. Short ids on the predecessor resolve like `depends_on` (spec 015). Unblocks the OAP corpus shapes 073 (`scope: full`), 108 (bare short id), 114 (`partial` + unit), and 199 (`partial` + note).

Lifecycle approvedImplementation complete
019-release-supply-chain-artifacts

Release supply-chain artifacts: per-target SBOM and build provenance

Every prebuilt binary spec-spine ships should carry a machine-readable bill of materials and a verifiable provenance trail. This spec adds, to the tag-gated release pipeline (006's release.yml), a per-target CycloneDX SBOM generated by syft for each of the five release archives, plus a SLSA build-provenance attestation over each archive. The SBOM names the dependency graph baked into that binary (read from the committed Cargo.lock, so it is as deterministic as the build); a fail-closed check refuses to ship an SBOM with zero components. SBOMs are attached to the GitHub Release as separate assets named to match their archive; the provenance attestation is recorded in GitHub's attestation store so any archive verifies with `gh attestation verify`. This is a port of OAP spec 117, scaled to spec-spine's single-CLI, five-triple release. The channels themselves (crates.io, npm, PyPI) are unchanged; this is purely additive supply-chain metadata on the binary release.

Lifecycle approvedImplementation complete
020-keypath-section-anchors

Bounded keypath section anchors for first-party structured config

Widens the section-anchor grammar (`sections.rs`, spec 004 §3.3) so a `{ kind: section, file, anchor }` unit can name a bounded mapping keypath inside a first-party structured-config file: a workflow YAML (`on`, `permissions`, `env`, `jobs.<name>`, `jobs.<name>.permissions`), a `Cargo.toml` table path (`workspace`, `package.metadata.oap`), or a `package.json` member (`scripts`, `dependencies`). The anchor field is already a free string (`Unit::Section`), so this is a permissive-payload additive change with no frontmatter or JSON-schema edit, mirroring spec 016. The motivating cases are ASI security boundaries: `permissions:` (GITHUB_TOKEN scope) and `on`/`on.merge_group` (the trigger and merge-queue contract). Section ownership of those keys means any token-scope escalation or trigger change forces the owning spec's review. A hard file-eligibility predicate keeps the grammar pointed at surfaces spec-spine has a first-party reason to parse and off foreign tool schemas (deny.toml, Helm values, k8s manifests), which their owners rev independently. This retires the `# region:`-in-structured-config escape hatch for the eligible surfaces: a structural keypath is computed from the document tree and cannot silently drift from the block it claims, the way a hand-placed region marker can.

Lifecycle approvedImplementation complete
021-ledger-seal

Ledger Seal (signed, reproducible attestation over the spec corpus)

spec-spine self-describes as a typed, hash-verifiable authority ledger over a markdown spec corpus. A hash-verifiable ledger is an attestation missing only two things: a signature and a verify. This spec completes that sentence. It adds a CorpusAttestation, a deterministic, pure-function payload over the inputs manifest, the registry hash, and the compile/lint (and optionally coupling) verdicts, plus a detached Ed25519 seal over its hash, and a two-mode verify-attestation (recompute, which any third party can run with no key, and signature, which is opt-in). The reproducible recompute mode is the property a run certificate can never have, because runs are side-effecting; this object is the signed, archival, independently-verifiable form of a verdict spec-spine already computes and then throws away into a CI log. It is deliberately NOT called a certificate: that word stays with run-provenance (signed testimony of a non-reproducible event). This is an attestation (recomputable truth).

Lifecycle approvedImplementation complete
022-index-sharding

Per-unit shard storage (conflict-free committed registry + index)

The two committed artifacts (the spec registry and the codebase index) were each emitted as one file behind one global content-hash line with id-sorted arrays. Because every spec or package PR rewrites that one hash line, two PRs touching different units collide textually, and GitHub's server-side merge (the web button and the merge queue's speculative build) does not run the spec-020 merge driver, so the conflict surfaces there and the merge queue ejects sibling PRs. This spec shards both artifacts by authority unit: per-spec registry shards under spec-registry/by-spec/<id>.json, per-spec traceability shards under codebase-index/by-spec/<id>.json, and per-package inventory shards under codebase-index/by-package/<slug>.json, each carrying its own input hash. The global view (validation, orphans, untraced code, the aggregate content hash) is computed ON READ from the shard set rather than committed, so there is no shared file to conflict on. Two PRs touching different specs then write disjoint files and never conflict, so the merge queue forms clean speculative stacks and auto-merge stops ejecting in-flight PRs. Staleness becomes per-shard (sharper than the broad hash), the committed shards stay present-on-clone, and a MAJOR schema bump (registry + index -> 1.0.0) marks the new on-disk shape. This generalizes spec 011 (index hash slices) from scoped staleness CHECKING to per-unit STORAGE, and it removes the textual-conflict cause spec 094's merge driver only papers over locally.

Lifecycle approvedImplementation complete
023-unresolved-unit-severity

Severity tiers for unresolved units: lifecycle- and edge-aware (warn, count, never skip)

Today the indexer hard-errors (I-003..I-009) on every declared unit that does not resolve, regardless of whether the edge is authoritative or whether the owning spec is even built yet. Two of those errors are not governance failures: an unresolved unit on a non-owning `references` edge is a stale provenance pointer, and an unresolved owning unit on a `draft` / `implementation: pending` spec is legitimate work-in-progress. This spec adds two orthogonal severity tiers over the same resolver, keyed on (a) edge authority and (b) owning-spec lifecycle, so those two cases become counted warnings (a fresh `W-001` / `W-002` band) instead of blocking errors, while an unresolved owning unit on an approved + implemented spec stays a hard error. The downgrade never skips: every such unit is still surfaced and counted, never silently dropped, so the authority-laundering / invisible-drift vector stays closed. The index never fabricates a code location for an unresolved path; authority remains a property of the registry relationship graph, which the coupling gate consults independently of index resolution. Additive: the `warnings` tier and the free-form diagnostic `code` already exist, so this is an `INDEX_SCHEMA_VERSION` MINOR (1.0.0 -> 1.1.0) with no schema-file edit and no registry change.

Lifecycle approvedImplementation complete
024-resolution-discovery-fixes

Indexer resolution + discovery fixes: foreign-YAML regions, Makefile tags, nested-workspace globs

Three mechanical correctness fixes surfaced by the OAP spec-217 Phase-0 dry run of 0.5.0, all in the indexer's resolution and discovery paths, none changing any schema or DTO shape. (1) Non-workflow YAML (Helm values, infra manifests) is dispatched to the CI-jobs parser, so its `# region:` markers never resolve and a declared section unit on one raises a spurious I-006; route foreign YAML to the region parser, which is exactly the whole-file / region ownership spec 020 §3.2 already promised foreign structured configs (retiring §4's deliberately-deferred D4). (2) A Makefile `## tag:` is silently overwritten when a second `## tag:` precedes any consuming target, so a tagged region is never emitted; make the pending tag deterministic so it is never silently lost. (3) Workspace-member globs declared in a NON-root workspace file (a nested `pnpm-workspace.yaml`) are resolved against the repo root instead of the declaration file's directory, so they match nothing; pre-join such globs with the declaration file's parent. A related lower-priority guard covers a `standalone_rust_workspaces` entry that ends in `Cargo.toml`. Pairs with spec 023 (the severity policy); these are the mechanical half of the 0.6.0 indexer work.

Lifecycle approvedImplementation complete
025-symbol-resolution-feature-gate

Feature-gate tree-sitter symbol/module resolution for dependency-free embedding

Adds a default-enabled `symbol-resolution` cargo feature to `spec-spine-core` that gates the tree-sitter dependency tree (tree-sitter + the Rust/TypeScript grammars) and the symbol/module resolution code. The CLI and `spec-spine index` keep default features, so adopter and self-governance behavior is byte-for-byte unchanged. Library consumers that read only the committed shards (`load_committed_registry` / `load_committed_index`) can build with `default-features = false` to drop tree-sitter entirely, which removes a hard blocker: tree-sitter declares `links = "tree-sitter"`, so a host workspace that pins a different tree-sitter (e.g. an analysis crate on `^0.26` while this crate pins `=0.25.10`) cannot otherwise resolve a single lockfile, and adding `spec-spine-core` anywhere in that workspace fails resolution before compilation. The seam is minimal: the `SymbolIndex` / `ModuleIndex` types and their `resolve` lookups are plain `BTreeMap` wrappers (no tree-sitter) and stay compiled, so the index resolver, its readers, and `compile()` build with the feature off; only the `build_*` functions and their parse helpers are gated. No DTO, JSON Schema, or `INDEX_SCHEMA_VERSION` change: the on-disk index shape is identical. Filed off the OAP spec-217 engine-swap work, which is blocked workspace-wide by exactly this `links` clash across its eight planned read-path consumers.

Lifecycle approvedImplementation complete
026-references-provenance-derived-at

References provenance: optional derived_at timestamp

Adds an optional `derived_at` field to the `Provenance` struct carried on a `references` edge: `{ kind, ref, derived_at? }`. `derived_at` is a generic, optional ISO-8601 timestamp recording when the reference was derived, preserved verbatim with no format validation in the type (an adopter that wants format enforcement adds a lint). The field is additive and optional, so a corpus that declares none is unaffected and `deny_unknown_fields` is preserved (the field becomes known, not a hole in the schema). The registry format gains an emittable field, so `REGISTRY_SCHEMA_VERSION` bumps an additive minor 1.0.0 -> 1.1.0 (the permissive shard schema file is unchanged; the bump only restamps the `specVersion` carried by each committed registry shard, leaving every `shardHash` and `record` body byte-identical, because a shard hash is taken over the `spec.md` source bytes). Filed off the OAP spec-217 engine-swap, whose decomposition synthesizer (OAP spec 165) emits this shape because OAP spec 161 FR-007 requires `provenance.derived_at`; the published library rejected it at parse with `V-002: unknown field derived_at`, producing zero shards. This is the second small, broadly-useful additive concession from the 217 swap (the first was the spec-027 symbol-resolution feature gate, shipped as 0.7.0).

Lifecycle approvedImplementation complete
027-cargo-workflow-dependency-waiver

Cargo and workflow dependabot bumps self-clear: freshness projection + auto-waiver

Amends 004 §3.5 and 005 §3.5, extending the paired 2026-06-11 npm dependency-only mechanism to the cargo and GitHub Actions ecosystems so a dependabot-class bump self-clears without a human waiver. Freshness half: Cargo.toml folds into the index content hash as a governance projection (its dependency tables stripped), mirroring npm, so a version bump leaves the committed shards fresh. Coupling half: the mechanical auto-waiver recognizes a Cargo.toml whose only change is dependency version specifiers, and a `.github/workflows/*.yml` whose only change is the `@ref` of `uses:` action references. Every non-version edit (a new or removed dependency, a feature / git / path change, a `run:` / `with:` edit, an added step or table, an unpinned action, a spec-metadata edit) still refuses the waiver, fail-closed. A claimed workflow file (007/008/019's release.yml) and a crate whose direct dependencies dependabot bumps are the live acceptance tests.

Lifecycle approvedImplementation complete
028-registry-freshness-check

Registry freshness: `spec-spine compile --check`

The committed spec-registry shard tree had no freshness gate. `index check` (spec 004) covers only the codebase-index tree, and `compile` is a validation gate that rewrites the shards without ever asserting the committed copies matched, so editing a spec.md and forgetting to recompile lands stale shardHash values with CI green (this is what PR #61 did to specs 016 and 021). This spec adds `spec-spine compile --check`: a non-writing gate that compiles in memory and compares the result byte-for-byte against the committed shards, exiting 2 on staleness exactly as `index check` does. It closes the registry/index asymmetry with a first-class command rather than a repo-local git tree check, so every adopter that commits its registry gets the same guarantee spec-spine gets.

Lifecycle approvedImplementation complete
029-ownership-coverage

Ownership coverage: `spec-spine index coverage` and the `C-002` ratchet

spec-spine refuses drift but has never required coverage. The coupling gate skips a changed path that no spec claims ("not a coupling concern"), and the only coverage-shaped signal, `traceability.untracedCode`, is package-granular and read by nothing, so a crate with one governed file and two hundred ungoverned ones reports as traced. "This application is fully specified" is therefore not a state the tool can assert. This spec adds two additive pieces that share one definition of ownership. `spec-spine index coverage` reports, per source file, whether a spec *specifically* claims it (a resolved unit or a comment header), whether only a package's manifest floor covers it, or whether nothing does; `--fail-on-untraced` turns that into the whole-tree assertion. `[coupling] require_ownership` (default false) makes the gate ask the same question of every changed source file and refuse a floor-only or unowned one as `C-002`, the PR-time ratchet. Coverage is a read verb over the tree and the committed ledger, like `index check`: nothing new is committed, no schema version moves, and the report predicts the gate exactly because both call one classifier over one universe.

Lifecycle approvedImplementation complete
030-dependency-cycle-refusal

Dependency cycle refusal: the `V-014` corpus-wide check on `depends_on`

`depends_on` is the one edge whose graph shape spec-spine never checked. A dangling target is a `V-010` warning, but a cycle passes: two specs that depend on each other compile with zero warnings and lint clean at every severity, so a corpus can be green and structurally unexecutable. This spec adds `V-014`, an error-tier, corpus-wide check that reports a cycle in the resolved `depends_on` graph and names the path. It is the sibling of `V-010` and lives beside it in `compile.rs`, the first step of the gate chain and the step `compile --check` verifies, so a cycle fails the build at the earliest point and no new verb is introduced. `V-014` joins `CROSS_SPEC_CODES`, so it is never stored in a registry shard and is recomputed from the assembled records on read; both the fresh-compile and committed-shard paths reach one detector, so the two views cannot disagree. Scheduling semantics stay out: readiness, contract pinning and downstream invalidation depend on merge history and a run journal, which are not a pure function of the corpus and therefore not spec-spine's to compute.

Lifecycle approvedImplementation complete
031-references-non-owning-paths

`references` seeds no implementing path: closing the C-001 ownership leak

The contract says `references` is the only non-owning edge and that the coupling gate ignores it. The indexer did not implement that. It seeded a spec's `implementingPaths` from every resolved unit location, owning or not, so a file a spec merely cited became a file it claimed. Every consumer reads that field as a claim: `couple.rs` treats a listed path as whole-file ownership, the indexer's own `owners_of_unit` does the same, and `orphaned_specs` and coverage read a non-empty list as "this spec claims something". One wrong seed made all of them wrong together. The observable defect is that a spec could not edit its own `spec.md` without a waiver once any other spec referenced it: on this corpus, changing `specs/094-one-gate-and-the-boundaries-it-holds/spec.md` was refused as `C-001` with `022-index-sharding`, which only cites it, named as the sole owner. This spec filters the seed by the `ownership` flag the loop already holds, at the single source rather than at each consumer. The citation is not lost: it stays in `resolvedUnits` with `ownership: false`, so provenance is preserved and only the ownership view changes. No DTO or schema shape changes.

Lifecycle approvedImplementation complete
032-stdout-closed-reader

A closed reader is not an error: stdout writes stop panicking the CLI

Exit codes are a stable contract: 0 ok, 1 validation failure, 2 stale, 3 I/O. The CLI could exit 101. `println!` unwraps its write, so a reader that stops early made the process panic: `spec-spine registry list --json | head` printed a Rust backtrace and exited 101, and under `set -o pipefail` that failed the surrounding script. Piping into `head`, `less` or `grep -q` is ordinary use, not an error condition. This spec routes every CLI stdout write through one helper that classifies the write instead of unwrapping it: a closed reader ends the process 0, because the consumer got what it asked for, and a genuine I/O failure reports and exits 3, which is the code the contract already assigns to I/O. Stderr keeps `eprintln!`: diagnostics are small, and a broken stderr has nowhere left to report itself. The fix is not `unsafe` SIGPIPE restoration, which `forbid(unsafe_code)` rules out workspace-wide, and not a panic hook, which would match on a message string the standard library is free to change.

Lifecycle approvedImplementation complete
033-configured-corpus-root

The coupling gate honors `layout.specs_dir`

`layout.specs_dir` is configuration, and every other consumer reads it: `compile` scans it, `index` builds each spec's `spec.md` path from it, and `init` scaffolds into it. The coupling gate did not. Two functions in `couple.rs` hardcoded the literal `specs/`, and both are load-bearing. The primary-owner heuristic asked whether `specs/<id>/spec.md` was in the diff, so for an adopter whose corpus lives anywhere else the answer was permanently no: no edit to an owning spec could clear `C-001`, and every governed change was refused with the owner named and no reachable way to satisfy it, short of a waiver on every PR. The amends-awareness parser had the same literal, so amendment expansion silently never fired. Both now take the configured root, and the builder is shared with the indexer (`spec_md_rel`, promoted to `pub(crate)`) so a path constructor and its parser cannot drift apart again. A trailing slash on the configured value is trimmed, making `specs` and `specs/` name one corpus root instead of the second yielding `specs//<id>/spec.md`. No DTO, schema, or config shape changes; a default-layout repo is byte-identical.

Lifecycle approvedImplementation complete
034-machine-readable-verdicts

Machine-readable verdicts: `--json` on the adjudicating verbs

The read verbs speak JSON; the verbs that render a verdict do not. `registry` (four subcommands), `index orphans` and `index coverage` have `--json`, while `couple`, `lint`, `compile --check`, `index check`, `attest` and `verify-attestation` emit prose to stdout and nothing else. A programmatic consumer of the gate chain is therefore forced to string-match sentences like `index is fresh` and to infer the reasons for a refusal from formatted text, which is precisely the ad-hoc parsing constitution §II forbids and the governed-artifact-reads rule was written against; the CLI currently leaves no alternative for exactly the commands whose output is a decision. The reports already exist: every one of these verbs is backed by a facade function returning a typed structure, and only the CLI surface is missing. This spec adds `--json` to each of them, behind one versioned envelope (`schemaVersion`, `verb`, `ok`, `exitCode`, and either `report` or `error`) whose `report` member is byte-identical to the corresponding facade output, so a consumer parses one header shape across the whole chain and one payload shape per verb, never two spellings of the same verdict. `--json` changes what is written, never what is decided: the exit code for every outcome is unchanged, and a failure is reported inside the envelope rather than as bare prose on stderr, so the machine consumer's happy path and error path have the same shape.

Lifecycle approvedImplementation complete
035-registry-plan-ready-set

`registry plan`: the ready set in dependency order

`depends_on` records the order specs must be built in, spec 030 guarantees that graph is acyclic, and `implementation` records how far each one has got, but nothing joins the three: there is no query that answers "which specs can be worked on now, and in what order". Every consumer that needs it (a contributor picking up the corpus, a release note, an autonomous driver taking one spec per session) recomputes it by reading frontmatter by hand, which is both tedious and exactly the ad-hoc traversal the typed read verbs exist to replace. This spec adds `registry plan`: a read-only projection that partitions the corpus into a `ready` set (nothing unfinished blocks it) emitted in deterministic topological order, and a `blocked` set, each entry naming the unfinished dependencies that block it, so the output explains itself rather than merely listing. It is a scheduling projection and nothing more: `implementation` is a self-declared field that no gate currently verifies, so `plan` treats it as a hint about intent and never as evidence about code, and this spec deliberately does not let a scheduling answer imply that anything is done.

Lifecycle approvedImplementation complete
036-declared-state-dir

`layout.state_dir`: a declared, ungoverned tool-state root

A repo governed by spec-spine increasingly hosts tooling that keeps state inside it: a build daemon's journal, a local database, captured transcripts, attestation bundles. There is nowhere agreed to put any of it. Every such tool invents a directory, and every invented directory is then a surprise to the gates: `couple` sees changed files no spec claims and refuses the merge under `require_ownership`, `index coverage` counts them as untraced debt, and the resolver walks them. The adopter's only recourse is to keep appending private paths to `bypass_prefixes` and `resolver_exclusions`, one list per concern, none of which states what the directory *is*. This spec adds `layout.state_dir`: a single declared root, unset by default, that spec-spine agrees to know about and refuses to govern. Declared means the gates recognize it (bypassed by `couple`, excluded from coverage classification and from the resolver scan, and never hashed); ungoverned means spec-spine never reads its contents, never writes into it, and refuses a spec that tries to claim a unit inside it, which `lint` reports as `L-006`, a contradiction rather than a precedence question resolved silently in either direction. Unset by default, so no existing repo changes behavior.

Lifecycle approvedImplementation complete
037-amendment-authoring

An amendment is declared once, in the amending spec

The corpus has always authored amendments one way: the `amends` edge is declared in the amending spec's frontmatter, and the amended spec's `spec.md` is left alone. Spec 023 amends 004 without editing it; spec 033 amends 005 without editing it. The rule is enforced by nothing, stated by nothing, and visible only as a pattern in the history, so a reader looking at a diff cannot tell a deliberate convention from an omission. That is not hypothetical: the review of PR #89 asked six separate times for spec 030 to be edited to record that 038 had amended it, withdrew the request once on its own analysis, then raised it again, because nothing in the corpus says which answer is right. This spec writes it down, in the one-page contract where a reader checking the edge vocabulary will find it. It adds no mechanism, because the two mechanisms already exist and work: the gate's amends-awareness (005 §3.3) widens the owner set so editing the amender clears a violation on the amended spec's territory, and `registry relationships` reports `amended_by (incoming)` so the amendment is discoverable from the amended spec's side without that spec being touched.

Lifecycle approvedImplementation complete
038-completion-held-to-claims

`implementation: complete` defeats draft leniency

`index.rs::in_flight` is `status == "draft" || implementation == Pending`, and spec 023 uses it to downgrade an unresolved owning unit from a blocking `I-0xx` error to a counted `W-001` warning. The `Complete` arm of `implementation` is never consulted, so `status: draft` alone buys the leniency: a spec that states in its own frontmatter that its work is finished may claim units that do not exist, and the indexer counts a warning instead of refusing. That window is not an edge case in this corpus, it is the normal one. A spec is filed as `draft` with `implementation: complete` when its code lands (spec 033 did exactly that at 46474a3) and stays that way until a later ratification PR flips it to `approved`, so every spec passes through a period where its strongest claim about code is the one claim nothing checks. This spec makes `implementation: complete` decisive: a spec asserting completion is never in flight, whatever its `status`, and its unresolved owning units are hard errors. `status` and `implementation` are orthogonal axes (design ratification versus code existence), and where they disagree the more specific claim about code wins. It verifies existence, not behavior, and 3.3 is explicit that no gate can do more than that.

Lifecycle approvedImplementation complete
039-per-spec-attestation

`attest --spec <id>`: a signed record scoped to one spec

Spec 021 attests the corpus: one payload, one hash, one optional Ed25519 seal over the whole ledger. The unit of work in a governed build is one spec, and a corpus-scoped attestation cannot answer "was this spec's territory sound when its work was declared done" without being reduced by hand. This spec adds `attest --spec <id>`, emitting a `SpecAttestation` scoped to a single spec: its own source hash, the resolved location and content hash of every owning unit it claims, and the verdicts restricted to it. It reuses 021's split exactly, the payload pure and reproducible with the wall clock and the signer identity in the detached seal, so the attested fact stays a function of `(config, file contents)` while the act of attesting is dated and attributed. It stays on-demand and gitignored like 021's, deliberately: the design note wanted a committed per-spec bundle, and committing one would restale on every edit to any claimed unit and would need its own freshness gate, which buys churn rather than assurance. A consumer that wants durable evidence receives the bundle; it does not need the repository to store it. One constraint is load-bearing and stated normatively: no lint rule may consume a `SpecAttestation`, because the payload records the lint verdict and a lint that read it would grade itself.

Lifecycle approvedImplementation complete
040-governance-document-gaps

Three governance statements the first adopter could not act on

hqgit is the first repository outside this one to adopt spec-spine, and what its author had to write by hand is a measurement of what the governance documents fail to supply. Three findings. First, the constitution's own Amendment clause names a mechanism that cannot be written: it says the constitution may be amended by "an ordinary spec that `amends` it", but `amends` resolves to spec ids and the constitution is not a spec, so the sentence has no implementation, and hqgit copied it verbatim into a corpus where it is equally unwritable. Spec 037 4 named this as an open question and deliberately did not answer it; this spec answers it. Second, spec 000 3 files `depends_on` and `implementation` under "optional descriptive keys", which was true when it was written and is now false: 033 refuses a cycle in one, and 038 and 041 make the other decide scheduling and diagnostic severity. hqgit had to write its own "lifecycle as scheduling" section because tier 1 reads as though the field were inert. Third, constitution V says pre-existing code is evidence but never says what to write about code adopted with defects; hqgit supplied the missing half. The scaffold learns all three, because the scaffolded constitution is four bullets with no tier statement, no normative hierarchy and no amendment clause, and the evidence is that the first adopter did not use it.

Lifecycle approvedImplementation complete
041-in-progress-is-in-flight

`implementation: in-progress` is in flight

Spec 023 grants an in-flight spec leniency: an unresolved owning unit is a counted `W-001` warning rather than a blocking error, because work under way may legitimately claim territory that does not exist yet. Its predicate names `status: draft` and `implementation: pending` and does not name `in-progress`, so a spec whose implementation is openly underway gets hard errors for every file it has not written. Spec 038 3.1 wrote that cell down and 4 called it very likely a defect, then left it alone on the grounds that one spec should not both tighten and loosen the same predicate. This is the spec 4 deferred. `in-progress` and `pending` are the same claim about the filesystem, that the work is not finished, and only one of them buys the leniency designed for exactly that state. The asymmetry has no argument behind it: 025 never discussed `in-progress`, which is what makes the omission look accidental rather than chosen. It bites a corpus that ratifies a design before building it, which lives in `approved` + `in-progress` for the whole build; the rahi corpus is in that state today.

Lifecycle approvedImplementation complete
042-absent-implementation-defers-to-status

An absent `implementation` key takes its answer from `status`

`implementation` is optional, and two verbs read its absence in opposite directions. `registry plan` (spec 035) reads an absent key as `pending` and offers the spec as ready; the index's in-flight predicate (specs 038 and 041) reads `approved` + absent as settled and holds the spec to its claims. The one spec that routinely lacks the key is the bootstrap spec `spec-spine init` scaffolds, so every scaffolded corpus reports its bootstrap spec as the single ready item in an otherwise finished corpus, forever; one adopter does today, and this repository did until its own spec 000 was patched by hand. This spec fixes the rule rather than the instance: an absent key takes its answer from `status`. On a `draft` it reads as `pending`, as 038 intended; on anything ratified it reads as settled, as the gate already concludes. The scaffold's bootstrap spec declares `implementation: n-a` and the spec template states the key instead of commenting it out, so the absent case becomes rare as well as consistent.

Lifecycle approvedImplementation complete
043-verify-declared-acceptance

`spec-spine verify <id>`: run a spec's declared acceptance

A spec's `## Verification` section states what would make its claims true, and nothing runs it. Three adopters independently wrote the same 78-line `scripts/verify-spec.sh` to close that gap, and spec 093 added a fourth copy to this repository, so the kit now ships the workaround it exists to retire. Spec 040 4 named this "the most substantial thing the adoption invented and the one most worth having" and deferred filing it. This is that filing. `spec-spine verify <id>` reads the spec's `## Verification` section, runs each line of its `verify:cli` fences from the repository root in order, and stops at the first failure. Two things make it a spec-spine verb rather than a script: the parse becomes a typed, tested read of authored markdown that the library owns, and the outcome becomes a spec 034 verdict envelope instead of a sentence a caller must string-match. The engine never spawns a process. Core returns a `VerifyPlan`, the commands it found and the fence tags it declined, and the CLI executes it, which is the same seam `couple` already uses for `git`. Two behaviours diverge deliberately from the ported script, both to keep an older contract intact: a missing spec exits 1, not the script's 2, because 2 means stale; and a failing command exits 1 with its own code carried in the payload, rather than propagating a code the exit contract does not define. `not-declared` stays an honest zero and is distinguishable from a pass in the payload, never by parsing prose.

Lifecycle approvedImplementation complete
044-index-diagnostics-reach-a-gate

Index diagnostics reach a gate

Spec 023 downgrades an unresolved unit to a counted `W-001` (owning unit on an in-flight spec) or `W-002` (non-owning `references` unit) instead of failing the index, because work under way legitimately claims territory that does not exist yet. That downgrade is right and this spec does not touch it. What 025 never gave the warning is a reader. The codes are deliberately absent from `BLOCKING_CODES`, `lint` counts a different set entirely (`L-001`..`L-006`), and `index check` prints one word, `fresh`. The only way to see them is to run the *writing* command, which mutates the tree, or to read `index render`'s markdown, which is the ad-hoc parsing constitution II forbids. So a corpus accumulates them without limit while every gate stays green: aicortex carries 248 that nothing reports. The asymmetry is exact, because error-tier diagnostics are gated: `I-003`..`I-009` mark their shard stale and `index check` exits 2. This spec gives the warning tier the same reachability without giving it the same force. `index check` reports the counts it already holds, `--fail-on-unresolved` turns them into exit 1 for a corpus past the specify-first stage, and `index diagnostics` lists them as typed output. All three read the committed shards and recompute nothing, so the answer always describes the ledger the corpus compiled to.

Lifecycle approvedImplementation complete
045-couple-names-the-crossing

The coupling gate names the crossing

The coupling gate has always named the specs that own a drifted path, and has never named the mechanism for crossing into their territory. Its resolution footer offers two doors: edit an owning spec, or waive. For an author who has legitimately touched a unit somebody else owns, both are shut. Editing another spec to clear a refusal is the illegitimate edit the corpus rules forbid, and the waiver is a human instrument that no adopter has ever used and that an unattended session is forbidden on every path. The third door, an `extends` edge declared in the author's own spec, is the corpus's actual answer and the gate never mentions it. This spec makes the refusal name it: three doors in author order, and when the diff edits exactly one spec.md, the concrete `extends` block to paste into that file. It also promotes the owner set from prose into an optional `owners` field on the violation, so an orchestrator reading `couple --json` learns the owner as data instead of by regexing English. No committed artifact and no schema version moves.

Lifecycle approvedImplementation complete
046-depends-on-ordinal-monotonicity

A dependency points backward, and the corpus can say so

Three adopters independently wrote the same ninety-line `scripts/spec-dag.sh` to assert one thing the tool does not: that a `depends_on` entry names a lower ordinal than the spec declaring it. Spec 030 already ships the harder half of that graph check, refusing a cycle at compile time, and ordinal monotonicity is the cheap total order that makes a cycle impossible by construction rather than detectable after the fact. This spec adds `L-007`, an opt-in conformance lint gated on a new `[lint] require_ordinal_monotonic_depends_on` knob, defaulting off so no existing corpus changes verdict. It is deliberately a lint and not a `V-` code: a forward dependency is a corpus convention an adopter may reasonably not hold, not a structural defect that makes the registry unreadable.

Lifecycle approvedImplementation complete
047-effective-config-is-a-governed-read

The effective configuration is a governed read

`spec-spine.toml` is only half the configuration a coupling decision is made from. The other half is `DEFAULT_BYPASS_PREFIXES`, a thirteen-entry constant compiled into the binary, additive to the adopter's list and impossible to read from outside the process. claude-observatory, which judges other repositories, hand-parses each target's TOML and documents in its spec 015 D-3 that the built-in floor is simply unknowable to it. That is a consumer reimplementing half a contract and knowing it has the other half wrong. This spec adds `spec-spine config show [--json]`: the effective configuration, every default resolved, with the bypass floor rendered as the merged list the gate actually matches against and each entry attributed to the built-in floor or to the adopter's file. It reads and reports; it decides nothing.

Lifecycle approvedImplementation complete
048-the-ledger-answers-what-consumers-rebuild

The ledger answers what consumers rebuild by hand

Two facts the ledger computes and then keeps to itself, each of which an adopter has reimplemented. Ownership of a path is resolved inside `couple.rs::owners_for_path`, a private function, so a consumer that wants to know which spec governs a file rebuilds a path-to-spec map from raw edges and gets the subtree, supersession and comment-header rules wrong. A spec's content hash is written into every registry shard as `shardHash` and is absent from every read verb, so claude-observatory reimplemented `hash.rs` normalization in TypeScript in order to pin against it. This spec adds `spec-spine index owner <path>` and puts `contentHash` on `registry show --json`'s output object. Neither touches a committed artifact, a DTO, or a schema version: both facts already exist and are merely unreachable.

Lifecycle approvedImplementation complete
049-compile-one-spec

Validate one draft without a temporary repository

Writing a spec means validating it before it lands, and the tool offers no way to do that. `compile` writes the whole shard tree, which an author with an unfinished draft must not do; `compile --check` refuses the whole corpus because the draft has no committed shard, reporting the author's work in progress as staleness. So two adopters wrote down a `mktemp -d` ritual: copy the corpus, copy the draft in, compile there, read the violations, delete the copy. It has a documented gotcha, because a `references` edge into `docs/` does not resolve in a corpus copy that has no `docs/`. This spec adds `compile --spec <id>`: validate one spec against the committed registry, report only that spec's violations, and write nothing.

Lifecycle approvedImplementation complete
050-claimed-but-unwitnessed

A claim no hash witnesses

A `file` unit carries no span, and only span-backing source files are folded into a per-spec index shard hash. So a spec can claim a file, `index check` can report fresh, `index coverage` can report it claimed, and the file's contents can be rewritten from end to end with no gate noticing. This is not hypothetical in this repository: `AGENTS.md` is claimed by three specs, and appending a line to it leaves both `index check` and `compile --check` reporting fresh. Twenty-four claimed paths here are in that state, and spec 021 signs an attestation over a ledger that never hashed any of them. This spec adds `L-008`, a warning naming each claimed path that contributes to no content hash, and one line of `index check` output reporting the count. It does not fold claimed files into the hash: that would change what staleness means, and the honest first move is to say how large the gap is.

Lifecycle approvedImplementation complete
051-retroactive-adoption-shape

The defects heading has one spelling

Constitution V requires code adopted from outside the corpus to be specced as found, with the behavior the adopting spec would not have chosen recorded under a `## Known defects` heading. It does not say what counts as that heading, and the first consumer to check mechanically got it wrong: claude-observatory's `defects.ts` matches the string `## Known defects` exactly and therefore rejects the numbered headings of the very specs it cites as precedent. This spec settles the spelling as an anchor rather than a string: any heading whose computed slug is or ends with `known-defects`, at any level, under the same slug rule the indexer already uses for section units. It adds `L-009`, an info-tier lint for a near-miss spelling, and it declines to lint `origin.retroactive` into implying the heading, because spec 040 established that the two are orthogonal.

Lifecycle approvedImplementation complete
052-read-verbs-on-a-code-free-corpus

Two read verbs that mislead a specify-first corpus

Three of the four governed repositories are specify-first: the whole corpus is ratified before a line of code exists, and specs live at approved plus pending for months. Two read verbs give that corpus an answer that is technically correct and practically a lie. `index orphans` reports sixty-four of hqgit's sixty-eight specs, because an orphan is a spec claiming nothing that resolves, and a spec whose code is not written yet claims nothing that resolves; the list is the corpus, so it says nothing. `index coverage --fail-on-untraced` on a tree with no discovered package enumerates zero source files, computes zero untraced, and exits 0 from inside a CI step named "the whole-tree ownership assertion". This spec makes the first partition by the in-flight predicate the index already computes, and makes the second refuse an empty universe instead of passing it.

Lifecycle approvedImplementation complete
053-plan-answers-the-whole-question

The plan answers the whole question, and names one pick

`registry plan` prints spec ids and a count. Every consumer needs more than that, so every consumer adds it back: adopters kept about forty lines of Python to join the ready ids against titles and to explain why each blocked spec is blocked, and the corpus's own `/next` skill has to make a second call to turn an id into something a human can read. The data is all there. `Plan` already carries each blocked spec's blockers with the state of each, and the registry it was computed from carries every title. This spec makes the prose form render what the structure already holds, and adds `--next`, the single pick that the one-spec-per-session loop actually asks for. The JSON shape gains title fields and is otherwise unchanged.

Lifecycle approvedImplementation complete
054-the-scaffold-ships-what-adopters-wrote

The scaffold ships what every adopter wrote by hand

Four scaffold defects the audit measured, all in one generator. `init` writes no `.gitignore`, so every adopter independently learns that `.derived/**/build-meta.json` carries a wall clock and dirties the tree, and spec 036's `state_dir` has the same missing half: the live failure it fixed was a permanently dirty tree, not a classification. The scaffolded `spec-spine.toml` emits five knobs; adopters needed thirteen, and aicortex annotated each of its own with the diagnostic code that knob drives, which is the template to copy. And `CONSTITUTION_TEMPLATE` in `scaffold.rs` is still the two-bullet stub spec 040 complained about, while the real thirty-four-line document sits in `standards/spec/templates/constitution-template.md`: adopters get the stub, and two of them deleted it. This spec fixes all four in `scaffold_init`, which stays a pure function of `Config` performing no IO.

Lifecycle approvedImplementation complete
055-a-version-pin-the-cli-can-check

A version pin the CLI can check

A repository is governed by whichever spec-spine binary happens to be on the path, and nothing anywhere says which one it should be. rahi states 0.11.0 in three files that must agree by hand. All four governed repositories are two to four releases behind the fixes their own experience motivated, and rahi cannot see spec 041, which was written for rahi's exact state, because it is pinned below it. This spec adds `[meta] required_version`, a semver requirement the CLI checks on every run, refusing with a message that names both the requirement and the running version. The schema versions already tell a loader whether it can read an artifact; this tells an operator whether they are running the tool the corpus was governed by, which is a different question and currently has no answer at all.

Lifecycle approvedImplementation complete
056-the-contract-records-the-lifecycle-table

The contract records the lifecycle table and the extra keys

`status` and `implementation` are the two keys every adopter reasons about daily, and the contract mentions `status` once, in a list of required frontmatter, and `implementation` not at all. The mechanical consequences are spread across four specs: 041 makes a `complete` claim checkable, 044 defines the in-flight window, 045 says an absent key takes its answer from `status`, and 038 reads both to build the ready set. Every specify-first adopter wrote the resulting table into its own contract by hand, and hqgit had to invent a section called "Lifecycle as scheduling" because ours has none. This spec adds that section and an "Extra keys" section telling adopters to document their `extra_known_keys`, and claims both as section units of the contract.

Lifecycle approvedImplementation complete
057-the-docs-name-what-adopters-derived

The docs name what adopters derived by experiment

Three documentation gaps the audit measured, each with a named victim. There is no page for specify-first adoption, which is how three of the four governed repositories work, so each of them independently derived the guarded Makefile, the CI manifest probe, `implementation: n-a` for record specs, and the fact that two hundred and forty-eight W-001 warnings is the defined state rather than a backlog. Two interactions are documented nowhere and were found by experiment: a path can be on the coupling bypass floor and simultaneously a hashed input, and a trailing-slash directory unit in `establishes` satisfies ownership recursively. And spec 034 changed `attest`'s stdout, which claude-observatory regexes in two places, with no migration note. This spec writes the page and the three paragraphs.

Lifecycle approvedImplementation complete
058-the-shipped-default-hashes-what-it-names

The shipped default hashes what it names

`IndexConfig::default().extra_hashed_inputs` ships as `["standards/**", ".github/workflows/**"]`. In the glob crate `dir/**` enumerates directories, and the hasher keeps only entries that are files, so the shipped default matches nothing: every adopter who has not overridden it folds zero extra files into the content hash and is told nothing. Spec 050 found the form in this repository's own config and fixed it there; spec 054 found it in the default behind it, emitted the key at its real value with a comment naming the trap, and deferred the fix to its own spec on the grounds that changing it restales every adopter's committed index. This is that spec. The default becomes `["standards/**/*", ".github/workflows/**/*"]`, the working form 054's comment already tells adopters to write, so an adopter who followed that advice sees no change and one who did not gets a one-time restale and a governance surface that is finally in the ledger. A regression test pins the property that was never asserted: that the shipped default matches files.

Lifecycle approvedImplementation complete
059-a-malformed-id-is-refused-not-a-panic

A malformed id is refused, not a panic

`detect_duplicates` reads a spec id's V-004 prefix as `&id[..id.len().min(3)]`, a byte slice. An id whose fourth byte falls inside a multi-byte character panics the process instead of diagnosing it, so `a日本-spec` exits 101 with a backtrace where the corpus should have been refused at exit 1 by V-012, the code that exists to name exactly that malformation. Spec 046 §4 found the defect, declined to fix it from inside a lint change, and asked for this spec. The fix reads the prefix the way spec 001 §3.2 already describes it, as a numeric prefix (`NNN`), using the character-safe leading-digit-run rule spec 046 established in `lint.rs`.

Lifecycle approvedImplementation complete
060-a-workflow-bump-is-not-a-governed-change

A workflow bump is not a governed change

A GitHub Actions workflow enters the index content hash as raw bytes, so a Dependabot `uses:` bump moves the global-inputs scalar and stales every shard in the repository. The bot can neither re-index nor add a waiver to its own PR body, so the PR walls on exit 2 and needs a maintainer to push a re-index commit. Spec 027 built the coupling half of this for workflows and read the freshness half as already fine; it was not, and spec 058 made that visible by fixing the default glob, then deferred the real fix as a hashing change with a migration of its own. This is that spec. A workflow folds as its governance projection, the parsed document with the pinned ref of each `uses:` reference removed and the action path kept, so a version or SHA bump is invisible to the ledger while a changed action, a `run:` or `with:` edit, an added step or an unpin all still stale it. The projection and 027's waiver classifier are required to state one rule, and the upgrade restales every workflow-bearing repository once.

Lifecycle approvedImplementation complete
061-shipped-is-not-the-same-as-working

Shipped is not the same as working

Two adopters installed spec-spine on the same day and each found a shipped artifact that does not do what it says. A glob form that matches no files reached one by hand and the other through a pre-069 scaffold, where the fix to the default can never arrive and 058's migration note explicitly does not look. `init --with-kit` writes the merge-driver hooks without an executable bit and without the binding that makes them fire. It installs a script the tool absorbed two releases ago, and it will write a second spec at an ordinal the corpus already uses. The session protocol calls a version read a precondition and never schedules it, while the skill beside it still prescribes the stderr ritual that protocol retired. Every one of these shipped for the same reason: the acceptance asserted that a thing exists rather than that it works. This spec closes them together and adds the refusal that would have caught the first one in an adopter's own config.

Lifecycle approvedImplementation complete
062-one-name-one-freshness-verb

One name for the protocol, one verb for freshness

The session protocol is ambiguous in two places and both are collisions with its own neighbours. Its entry point is named `/init`, which is also a Claude Code built-in meaning the opposite thing (one-time, repository-level, and it writes) and also `spec-spine init`, which means a third thing again. And it asks one question, whether the committed state is current, in two spellings: `compile --check` is a flag and `index check` is a subcommand, so the protocol, the hook, the skills and CI each have to spell both. This spec renames the skill to `/prime` with no alias, and adds `spec-spine check`, an additive top-level verb that runs both freshness reads, reports each tree separately, and composes one exit code. Nothing is removed: both primitives keep their flags, their tests and their contracts, and CI may still call one when it regenerated only one tree.

Lifecycle approvedImplementation complete
063-planned-territory-is-declared-not-inferred

Planned territory is declared, not inferred

A spec cannot say what it intends to own. Ownership is only ever inferred from a unit resolving on disk, so a corpus that opts into spec 044's `--fail-on-unresolved` gate forecloses declaring territory before writing it: every claim must arrive with its file. The cost is that `registry plan` and `index coverage` are blind to planned ownership, a draft's risk understates the work, and two specs can plan the same file with no edge to collide on until one of them ships. This spec adds an optional `planned` flag to the unit payload. A planned unit is a declared state rather than an unresolved one, so it produces no `W-001` and the gate keeps refusing every claim that is merely broken. The flag is bound to the lifecycle field that ends it: a spec at `implementation: complete` carrying one is refused, and a planned unit that has resolved is reported so the flag cannot be left on.

Lifecycle approvedImplementation complete
064-compile-warnings-reach-the-gate

Compile warnings reach the gate

`V-010`, a `depends_on` naming a spec that does not exist, is the only warning-tier code the compiler emits, and no verb in the gate chain can be told to refuse it. `lint --fail-on-warn` cannot reach it: lint's codes are disjoint from compile's by design, and `lint()` keeps only the registry from the compile it runs. So a dangling dependency compiles clean, lints clean at every severity, and reaches the default branch unread. This spec adds `--fail-on-warn` to `compile` and forwards it through `check` into the compile half, the shape spec 044's `--fail-on-unresolved` already uses for the index half. The flag gates the CLI's exit code only; `validation.passed` keeps the meaning spec 001 fixed for it, and `V-010` keeps its warning tier, because a corpus that files specs forward must be able to name a dependency it has not written yet. Zero `V-010`s exist today: the gap is structural, and this spec closes it before it costs something.

Lifecycle approvedImplementation complete
065-a-dead-glob-is-dead-in-both-tables

A dead glob is dead in both tables

`L-010` refuses a pattern ending in `/**` in `[index] extra_hashed_inputs`, where it enumerates directories and can contribute no bytes. `[index.slices]` carries pattern lists with the same documented semantics and the same file-only walk, and the lint is silent about it. An adopter audited on 2026-09-09 carried seventeen dead patterns, nine in the table the lint reads and eight in the table it does not. A fix pass corrected all nine and six of the eight; the two survivors reached an already-reviewed pull request and were caught by a second human reading, since no tool could report them. Both pointed at directories that do not exist yet, so they would have stayed silently empty on the day those directories arrived. This spec extends the rule to the second table, and requires the slice message to speak about the slice's own hash rather than repeating a sentence about `contentHash` that is false for slices.

Lifecycle approvedImplementation complete
066-an-attestation-covers-the-territory-it-claims

An attestation covers the territory it claims

`attest_spec` hashes a unit by opening each resolved location with `read_to_string`. Several unit spellings resolve to a directory rather than to a file, so the read fails with `Is a directory` and the verb exits 3: fourteen of this corpus's eighty-three specs cannot be attested at all, and the failure arrives as an I/O error rather than as a verdict. The dominant spelling is not the obvious one: thirteen of the fourteen are plain `file` units whose path is a subtree (`npm/`, `.claude/skills/`), which spec 000 4.2 defines as resolving identically to an explicit `directory` unit. Spec 039 3.1 already states what should happen, that `units` carries "the content hash of what it resolved to" for every owning unit, without qualifying by kind, so this spec changes nothing 042 requires and supplies the implementation that sentence never had. A directory-resolved location is walked instead of opened: every file beneath it, pruned by `index.resolver_exclusions` and `layout.state_dir` through the indexer's existing walker, folded into the same path-sorted `hash::content_hash` construction the file units already use. The walk is deliberately not extension-filtered, because the coverage universe's source extensions would hash zero files for a markdown-only directory such as `.claude/agents/` and emit a confident hash over nothing. A directory that walks to no files at all hashes its own path as a single piece, so an empty claim cannot attest as SHA-256 of the empty input, a constant every empty claim would otherwise share. No previously emitted payload changes value, because every spec this reaches could not produce one.

Lifecycle approvedImplementation complete
067-a-short-id-names-the-same-spec-at-every-verb

A short id names the same spec at every verb

Six arguments take a spec id, and they disagree about what one is. `verify` and `compile --spec` accept the short form spec 015 defines; `registry show`, `registry relationships` and `attest --spec` refuse it at exit 1; and `verify-attestation --spec` fails at exit 3 reading a file named after the raw argument. Specs 043 3.2 and 056 3.1 each state the cross-verb rule as already true elsewhere, and nothing enforces it. The policy itself exists in four private copies, two strict ones for arguments and two lenient ones for frontmatter references, and the two strict ones test the exact case by joining the argument onto a path, so `verify ../specs/<id>` resolves and `compile --spec ./<id>` reports a false `V-001` on a valid spec. This spec puts the policy in one module that every argument and both reference resolvers call. Each verb resolves against the ids it already reads, and every value derived from the id after resolution uses the resolved id, which is what keeps `relationships` from computing incoming edges against the raw argument and `attest` from naming its output file after it. A census over the CLI's argument tree fails when a seventh id argument appears outside the matrix. It amends 016 2, whose mirror becomes a shared call. The implementation changes no committed shard: the lenient adapters return what the mirrors returned.

Lifecycle approvedImplementation complete
068-a-verifier-checks-the-bytes-it-was-given

A verifier checks the bytes it was given

`verify-attestation` decides on a different object from the one it was handed. It deserializes the attestation without refusing members it does not know, and it checks the seal against a hash of its own re-serialization of what it parsed, never against the file's bytes. Measured at 75181a5: a sealed attestation with an injected `"prCouple": {"ok": true}` verifies as `match` and `valid` at exit 0, in both the corpus and the per-spec scope, and so does a reformatted copy. Separately, the corpus recompute never compares `schemaVersion`, so an attestation claiming `9.0.0` recomputes as `match` at exit 0, while the per-spec path reports an unnamed content mismatch. Spec 021 AC-4 already requires a single tampered payload byte to fail the signature, and the attestation module documents that loaders reject an unknown MAJOR, so this spec extends rather than amends: the verifier reads the file once, both modes decide on those bytes, every attestation and seal DTO refuses unknown members at exit 3, an unknown schema MAJOR is refused at exit 3 in the loaders' own words, and the corpus recompute compares the version. Nothing `attest` emits changes, and every attestation and seal it has ever written still verifies, provided the bytes were kept.

Lifecycle approvedImplementation complete
069-the-committed-index-is-compared-not-trusted

The committed index is compared, not trusted

`index check` does not compare the committed index with what the corpus indexes to. It reads each committed shard's own mapping, derives from that mapping which span files to hash, and compares the resulting hash with the shard's `shardHash` field. The mapping body itself (which units resolved where, which paths implement which spec, the ownership flags) is never compared with a fresh resolution, and for a `file`, `directory` or `crate` unit, which carries no span, nothing in the hash constrains it. Measured on a scratch repository: rewriting a committed shard so its spec owns nothing, with `shardHash` left alone, leaves `index check` and `check` reporting fresh, and `couple` then derives ownership from the edited body and passes a change to code the spec owned, without its `spec.md`. `.derived/` is in the bypass floor, so the edit itself trips nothing. AGENTS.md tells every session that a fresh `check` means the committed shards are exactly what the corpus compiles to; for the index that is not what is checked. Spec 028 3.1 recorded the weaker comparison as a cost trade; a full re-index of this repository takes 0.04 s against 0.03 s for the check. This spec makes `index check` compare bytes and set membership exactly as `compile --check` does, so every reader of the committed index (the coupling gate, `index owner`, coverage, the projections) reads a body equal to the recompute. It amends 024 5, whose bounded trade let a sibling-caused resolution flip go unreported.

Lifecycle approvedImplementation complete
070-an-authority-snapshot-says-what-it-read

An authority snapshot says what it read

A consumer that binds spec-spine's authority results to a revision needs five separate outputs today and still cannot answer four questions from them: whether the committed ledger matched the corpus (attestations recompute and never read the committed shards, and `check --json` reports booleans without the digests it compared), which configuration and governance files were read, what every spec's territory hashed to, and how much source no spec claims. This spec adds a third attestation scope, `attest --snapshot`, emitting an `AuthoritySnapshot`: the tool and schema versions, a digest of `spec-spine.toml`, the existing corpus hashes, the committed registry and index trees with a digest each and whether each matches the recompute, the governance inputs by path and digest, the gate verdicts, the ownership counts, the exclusions in force, and each spec's lifecycle with a framed digest of its resolved territory (plus, for joining records issued earlier, the hash of the `SpecAttestation` spec 039 would emit for it, or a stated reason where that construction has no answer). New members use a framed digest that binds normalized text rather than exact bytes, a contract 3.3 states explicitly and distinguishes from spec 068's, because the existing fold does not frame content and two trees can share one hash; existing members and payloads keep their constructions. The payload is pure, on demand, gitignored, sealable and verified under spec 068's rules. It commits to no git revision: the consumer binds the tree.

Lifecycle approvedImplementation complete
071-a-change-is-classified-under-the-bases-rules

A change is classified under the base's rules

The coupling gate clears C-001 when any owning spec's `spec.md` is in the diff, and reads the owners from the candidate's own index. Measured on a scratch repository, a candidate that deletes a command from its own `## Verification` block while changing the code that command checked passes `couple` at exit 0, and so does a candidate that files a new draft spec with an `extends` edge on another spec's unit and rewrites that unit. Both are changes a reviewer must weigh under the rules that held before the change, and nothing reports them as such. Design note 02 G7 says no refusal should be asserted before it is designed, because the naive rule refuses legitimate refactors. This spec therefore adds a report, not a gate: `spec-spine delta --base B --head H` classifies every changed path into implementation, requirement, verification, authority, lifecycle, constitutional, policy, derived, bypassed, unowned or unknown, computed under the merge base's own configuration and index so a candidate cannot reclassify its change by editing `spec-spine.toml`. It records the base and head plan digests for every spec whose declared acceptance changed, the owner sets on both sides of every path whose ownership moved, and a `priorPolicy` block naming the classes a consumer must judge under the base's policy. It interprets no prose, infers no moves, and decides nothing.

Lifecycle approvedImplementation complete
072-two-ready-specs-can-collide

Two ready specs can collide

`registry plan` answers "what can be worked on now", and a scheduler fans that answer out. Readiness is computed from `depends_on` alone, so two specs with no edge between them are both offered even when they claim the same files. The corpus has the case standing today: drafts 084 and 085 both extend `crates/spec-spine-cli/src/verify_attestation.rs`, `attest.rs` and `lib.rs`, neither depends on the other, and `plan` lists both as ready with no way to say so. This spec adds the overlap report: for every pair on the ready set, the units both claim. It is a report and never a refusal, and it is a lower bound rather than a safety verdict: disjoint territory does not prove independence, because a shared lockfile, a regenerated shard or a consumed API are collisions no frontmatter declares. `plan` keeps exiting 0.

Lifecycle approvedImplementation complete
073-a-mode-only-or-binary-change-is-a-change

A mode-only or binary change is a change

`couple` builds its `DiffInput` by parsing `git diff --no-color -U0 --no-renames`, and registers a path only when it meets a `---` or `+++` header. Git prints neither for a mode-only change (`old mode` / `new mode`) nor for a binary change (`Binary files a/x and b/x differ`), so both arrive at the gate as no change at all: an owned binary can change without its spec and `C-001` stays silent, and a mode flip on an unclaimed source file is invisible to `C-002`. Spec 071 met the same blindness while building `delta`, worked around it with a `--name-only` helper, and recorded in D-9 that `couple` is spec 005's territory. This spec unions the name list into the parsed diff: a path git reports and the hunk parser did not register enters `DiffInput` as a whole-file change with no spans, deleted when the status letter says so, and is then judged by the same clearance rules as every other path.

Lifecycle approvedImplementation complete
074-a-governed-read-names-its-version

A governed read names its version

`docs/api.md` states that every JSON this tool emits is pretty-printed with sorted keys, and spec 034 gave every verdict verb a versioned envelope. The read verbs got neither. Measured at 0.19.0: thirteen read documents across ten verbs, of which twelve carry no version member at all and seven emit keys in struct declaration order, because they serialize with `serde_json::to_string_pretty` while every committed artifact goes through the canonical writer. A consumer therefore pins a document whose shape it cannot dispatch on and whose key order is an implementation detail of a Rust struct. This spec routes every read document through one emitter that sorts keys and stamps a new `READ_SCHEMA_VERSION`, and decides note 04's D7: the version is an additive member on an object document, and the documents that are not objects today become objects so they can carry one. Four documents across three verbs change shape breakingly, and two of the four are contracts approved specs state, so this spec carries `amends` edges to 010 and 060 rather than editing them.

Lifecycle approvedImplementation complete
075-a-claim-below-the-header-window-is-not-silent

A claim below the header window is not silent

A `// Spec: specs/<id>/spec.md` comment header claims the file it sits in, and `index.rs::scan_comment_headers` looks for it in `content.lines().take(16)`. That bound is claim eligibility, and no spec, standard or adopter-facing document records it: a header on line 17 claims nothing, the file then reads as unclaimed, and under `[coupling] require_ownership` a change to it is a `C-002` refusal for a reason nothing states. Two neighbouring failures are equally silent: a header whose reference names no spec in the corpus, and the `//!` doc-comment form, which the scanner deliberately does not accept. This spec declares the window (16, unchanged) and the recognizer exactly as it ships, and makes `index coverage` report the near misses, so a file that tried to claim itself and failed is distinguishable from a file that never tried. The recognizer is looser than the documented form in three ways, and 3.2 records them rather than correcting them: declaring a rule is not the place to change it.

Lifecycle approvedImplementation complete
076-a-stray-shard-is-orphaned-at-the-verbs

A stray shard is orphaned at the verbs

Spec 069 §3.1 says `orphaned` covers "a stray file in either shard directory", and `compare_shard_dir` classifies by name without parsing, so the library holds to it. The verbs do not. `check_report` and `check_freshness_json` compute the freshness verdict and then call `diagnostics::committed_counts(config, repo_root)?`, which parses every file in the shard directories; an unparseable stray makes that `?` throw the computed verdict away, and `index check` and `check` exit 3 with a parse error where `couple` and `index coverage` exit 2 and name the orphan. Measured at 0.19.0, and the same at 0.18.0, so it is not a regression and not a bypass. 069's acceptance asserted the classification on the library function only, which is exactly how it passed. This spec makes the tally best-effort and the verdict authoritative, and asserts it at the verbs.

Lifecycle approvedImplementation complete
077-one-hash-one-construction-one-name

One hash, one construction, one name

`registry show` prints `contentHash: <h> (sha256 of this spec.md)`, and approved spec 048 §3.4 says the same thing in prose: "`contentHash` is SHA-256 over that spec's `spec.md` alone". It is neither. The value is the shard hash, which is SHA-256 over the repo-relative POSIX path, a NUL, and the normalized bytes, so for `specs/069-.../spec.md` the printed value is `a4a0098235e9...` while the digest of the file's bytes is `ae0144ca7e4a...`, the value `attest --spec` reports as `specSourceHash`. 048 §3.3 exists for consumers who reimplement the normalization to pin against the ledger, and one adopter does exactly that, so a gloss naming the wrong construction is the whole defect. This spec amends 048 §3.4 to state the construction the code has always used, makes the printed line say it, and adds the test that pins both digests so the two cannot drift again.

Lifecycle approvedImplementation complete
078-governed-scope-is-declared-not-inferred

Governed scope is declared, not inferred

The ownership ratchet sees a file only if all four conjuncts of `coverage.rs::in_coverage_universe` hold: the extension is in `SOURCE_EXTS`, no path component is a resolver exclusion, the file lies inside a discovered package, and no bypass prefix matches. Measured on 2026-09-15: this repository tracks 572 files and the ratchet can see 89. Nineteen tracked files carry a `SOURCE_EXTS` extension and are invisible on the package conjunct alone, and eleven of those nineteen are files `[index] extra_hashed_inputs` already names, so a change to one restales all 101 shards while `C-002` cannot ask who owns it. Seven of the nineteen already carry a valid `// Spec:` claim header that nothing ever reads. The two lists disagree about what this repository governs. This spec adds an opt-in declared scope: named path patterns that join the coverage universe whatever their extension and wherever they sit, with the tracked-file enumeration done by the CLI and passed in, because the core has no git. Empty by default, so no adopter's verdict changes on upgrade. The claim scanner is untouched: a scoped file is claimed from spec frontmatter, so those seven headers stay inert and this spec says so rather than implying otherwise.

Lifecycle approvedImplementation complete
079-a-blocking-claim-is-not-a-stale-shard

A blocking claim is not a stale shard

`check_index_freshness` folds two different refusals into one verdict: a committed shard whose bytes moved, and a spec whose claimed unit does not resolve. Both arrive as `Freshness::Stale`, so `check` prints "codebase-index: STALE (run `spec-spine index`)" and `index check` prints "index is STALE (run `spec-spine index` to refresh)" for a corpus whose shards are byte-exact. The named remedy provably does not work: reproduced at 0.19.0, `index` exits 0, writes the shard, and `check` still exits 2, because re-indexing cannot create a file a spec claims. An adopter documenting exit 2 as staleness follows that advice indefinitely. This spec keeps the refusal and the exit code exactly as they are and changes what the two freshness verbs say: a blocking resolution diagnostic is reported as its own class, names the owning spec and unit, and says regeneration will not clear it. The mixed case reports both and says which half regeneration addresses.

Lifecycle approvedImplementation complete
080-an-unresolved-claim-is-not-stale

An unresolved claim exits as a validation failure

Spec 079 stopped `check` calling an unresolved claim "stale" and stopped it prescribing `spec-spine index`, a remedy that provably does not work. It deliberately left the exit code alone, because 069 3.1 pins "the exit code is unchanged: 2 when anything drifted" and adopters branch on it. The result is a verb whose prose says "this is not staleness" while its exit code says staleness, and exit 2 is the code an adopter's CLAUDE.md maps to "run spec-spine index". This spec moves an unresolved claim to exit 1, the validation code, by an `amends` edge on 086. A spec claiming a unit that does not resolve is a corpus that does not describe its tree, which is a validation failure and not a ledger that has fallen behind. No message changes: 098 already made every report line correct.

Lifecycle approvedImplementation complete
081-coupling-sees-the-change-being-committed

The coupling gate can see the change being committed

`couple --base X --head HEAD` builds a `git diff X...HEAD`, so every path in the working tree and the index is invisible to it. The documented pre-commit coupling check therefore cannot see the change it is being run to judge: on a branch whose work is not yet committed it reports `0 path(s) checked, no drift` and exits 0, which reads as a pass. Spec 094 §4 placed the defect out of scope and said it "is filed separately"; spec 073 §4 placed it out of scope and pointed back at 090. Nothing filed it, and the phrase in 094 §4 has been untrue since it was written. This spec files it: `--include-uncommitted` unions the committed range with `git diff HEAD`, so the gate judges what a commit would contain. The library stays pure and still shells out to nothing; the CLI does the git work, as it already does for the range.

Lifecycle approvedImplementation complete
082-an-amended-acceptance-is-the-one-that-runs

An amended acceptance is the one that runs

Spec 074's `## Verification` line 13 asserts `d["next"]["id"]` on `registry plan --next`, which is a claim about the corpus rather than about the code: it holds only while something is ready to build. The corpus finished, `next` became `null`, and `spec-spine verify 093` has been red ever since, failing an assertion that contradicts 074 §3.3's own requirement that an absent answer be a present `null` member. The line is wrong and 093 is approved, so correcting it is an amendment. Spec 037 forbids editing the amended file and `verify` executes that file, so until now an amendment could not reach an acceptance block at all. This spec gives it a route: a spec that `amends` another may declare that its own `## Verification` block replaces the amended spec's, and `verify` runs the replacement and says whose it ran.

Lifecycle approvedImplementation complete
083-an-amendment-carries-the-acceptance-it-replaces

An amendment carries the acceptance it replaces

Spec 080 amended spec 079 so that an unresolved claim exits 1 rather than 2, and migrated the four assertions that live in Rust. Spec 079's own `## Verification` block was left as it was ratified, and five of its assertions still require the pre-amendment code, so `spec-spine verify 098` has been red since 101 merged. A sixth line is red for an unrelated reason: 079's AC-9 compares two `settings.json` files against a fixed commit to prove 098 did not edit them, which specs 093 and 104 then legitimately did. Spec 082 built the route an amendment needs to reach an acceptance block and landed after 101, naming the audit of the remaining blocks as its own work. This is that work for 098: this spec declares 079's acceptance replaced and carries the corrected block, without editing 079's file.

Lifecycle approvedImplementation complete
084-an-acceptance-outlives-the-output-it-was-written-against

An acceptance outlives the output it was written against

Spec 044's `## Verification` block has three assertions that no longer hold, and none of the three describes a defect. It compares the whole of `index check`'s stdout against one line, which spec 050 legitimately added a second line to; it takes `len()` of `index diagnostics --json` as a bare array, which spec 074 legitimately wrapped in an `items` envelope; and it pins `VERDICT_SCHEMA_VERSION` at the literal `0.2.0`, which spec 071 legitimately moved to `0.4.0`. The third is the sharpest: 044 3.6 rules that the envelope version MUST NOT move for a payload addition, and a literal pin cannot assert that, because it also fails for every move the rule permits. None of the three amending specs declared `amends_verification`, and all three predate the spec that built it. This spec declares 044's acceptance replaced and carries the corrected block, without editing 044's file.

Lifecycle approvedImplementation complete
085-a-version-pin-is-not-a-contract

A version pin is not a contract

Spec 049's `## Verification` block pins the verdict envelope at the literal `0.3.0`, which spec 071 legitimately moved to `0.4.0`, so `spec-spine verify 056` has been red since 088 merged. The pin was never what 056 required: 056 3.4 states that `--spec` emits the spec 034 envelope with a `verb` token distinguishing it from `compile --check`, and records the bump it performed as a fact about that bump, not as a standing ceiling. A literal pin cannot assert either half, because it goes red for every move the rule permits and stays green for a verb token that collides. This spec declares 049's acceptance replaced and carries the corrected block, without editing 049's file. It is the first of the four follow-ons spec 084 4 named.

Lifecycle approvedImplementation complete
086-an-exact-key-set-refuses-what-the-rule-allows

An exact key set refuses what the rule allows

Spec 052's `## Verification` block asserts that `index orphans --json` has exactly the keys `orphaned` and `inFlight`, and spec 074 legitimately added a `schemaVersion` member to every governed read, so `spec-spine verify 059` has been red since 093 merged. 052 3.1 requires the two named arrays to be present and always emitted; it never required them to be the only members, and an equality over the key set asserts a closed document the rule does not state. The same block is silent about the two decisions 059 actually recorded for that verb: that `--json` emits both arrays even when empty, and that the prose form stays silent on a corpus with nothing to report. This spec declares 052's acceptance replaced and carries the corrected block, without editing 052's file. It is the second of the four follow-ons spec 084 4 named.

Lifecycle approvedImplementation complete
087-the-answer-is-a-member-not-the-document

The answer is a member, not the document

Spec 053's `## Verification` block compares the whole of `registry plan --next --json` against one spec object, and spec 074 moved that object under a `next` member so the empty ready set could be a value rather than a missing document, so `spec-spine verify 060` has been red since 093 merged. 093 declared an `amends` edge to 060 for exactly this change and carried the replacement text for 053's rule; what it could not reach was 053's acceptance, because spec 037 forbids editing the amended file and the block lives inside it. The same block leaves 053 3.2's strongest requirement unasserted: that `--next` is a projection of `plan` and does not reimplement selection, which a one-element fixture cannot show. This spec declares 053's acceptance replaced and carries the corrected block, without editing 053's file. It is the third of the four follow-ons spec 084 4 named.

Lifecycle approvedImplementation complete
088-the-template-teaches-the-whole-grammar

The template teaches the whole grammar

`standards/spec/templates/spec-template.md` is the file `/spec` copies to start every spec, and it is the only place in the corpus where the frontmatter grammar is written down for an author. It has fallen behind the parser. Spec 082 added `amends_verification` and the template never gained it, a gap five specs have now recorded in their own section 4 with nobody able to close it, because no spec claims the file. Measured against `KNOWN_KEYS`, four more keys are missing for the same reason, and the `extends` example omits `nature`, which 96 of the 111 specs in this corpus write. This spec claims the template, completes it, and gives it an acceptance that reads the parser's own key list, so the next key added to the grammar cannot reach `main` without the document that teaches it.

Lifecycle approvedImplementation complete
089-nothing-reruns-a-merged-acceptance

Nothing reruns a merged acceptance

A `## Verification` block is run once, by the session that writes it, and then never again. `verify` is outside the gate chain on purpose, so a block that a later approved spec legitimately invalidates goes red silently and stays red: specs 084 through 110 each exist to repair one such block, and each was found by hand. Spec 083 §4 named the remedy, "a periodic sweep a maintainer runs, not a gate step", and left naming its home as its own work. This spec is that home. It establishes `scripts/verify-sweep.sh`, a maintainer-invoked sweep that runs the whole corpus's declared acceptance against one already-merged revision in an isolated worktree, accounts for every spec under five outcomes of which only two are success, and closes the 000-047 population as a ledger that can only shrink and that no future spec can enter.

Lifecycle approvedImplementation complete
090-the-verdict-is-the-only-thing-on-stdout

The verdict is the only thing on stdout

Spec 034 3.1 requires that under `--json` a verb write exactly one JSON object to stdout, and `verify --json` does not: it spawns each acceptance command with stdout inherited, so every byte the command prints lands on the parent's stdout ahead of the envelope. The failure is quiet in the worst way, because the envelope is still emitted and still correct and the exit code is still right, so a consumer sees a successful run whose document will not parse. Measured on `5f95613`, a one-command block printing a single line makes `json.load` of the whole stdout fail at character 0 while the process exits 0. `verify` is the verb an orchestrator most needs to read programmatically, which is the third argument spec 043 1.2 used to justify building it, so the one verb that had to be machine-readable is the one that is not. The correction keeps the child's bytes and moves them: under `--json` both child streams are streamed to the parent's stderr as they arrive, and the command and exit transcript spec 043 3.5 requires joins them there, so stdout carries the envelope and nothing else on the report path and on the error path alike. Nothing is captured in memory, no field joins the verdict schema, and without the flag every byte goes where it goes today.

Lifecycle approvedImplementation complete
091-an-unclassified-review-failure-blocks-the-merge

An unclassified review failure blocks the merge

The AI review job classifies a failed reviewer invocation into two outcomes, and its fall-through is a pass. On 2026-09-20 the provider returned "Your organization does not have access to Claude. Please login again or contact your administrator." and the job matched it against a token allowlist (`authentication_error`, `permission_error`, `unauthorized`, `forbidden`, and three more), found no match, set `review_status=api_failure`, exited 0, and `ci-gate` went green on PR #267 with no review. The refusal was prose and the allowlist reads tokens, so an access refusal took the transient-outage branch. Two further holes sit beside it: `review_status=ok` is set from exit status alone, so a reviewer that exits 0 having written nothing publishes an empty review the gate reads as reviewed, and the transient notice swallows a failed `gh pr comment` with a warning, so the one artifact that makes a skip visible can be absent while the gate still passes. This spec states the classification policy the job must implement: success and failure are separated by exit status before any text is read, refusal signals outrank transient signals inside a failed invocation, transient signals are recognized only in the contextual forms their emitting layer produces, and everything else blocks. It is defended by a test that executes the workflow's own script rather than a copy of its patterns, so inverting a blocking branch is caught even when its regexes are untouched.

Lifecycle approvedImplementation complete
092-the-engine-ships-governance-not-an-environment

The engine ships governance, not an environment

spec-spine has been shipping two products. One is a governance engine: a compiler, an indexer, a classifier, a coupling gate, a verifier. The other is a development environment: twenty-nine files of skills, agents, rules, hooks, a Makefile and a CI workflow, embedded into the binary as a 2,707-line generated module and written out by a public `init --with-kit`, plus two further generated projections of the same instructions for other agent runtimes. The second product is now Statecraft's: the Statecraft CLI is the sole distributor and initializer of the managed development environment, and it places the compiled governance artifacts at `.statecraft/derived/`. This spec removes the public initialization command, the kit, the embedded kit, the agent-tree projections and the parity machinery that held them equal; narrows the retained library scaffold to governance starter content and nothing else; relocates this repository's own committed derived trees; and records, per predecessor, what the removal does to the claims and the acceptance those specs left behind.

Lifecycle approvedImplementation complete
093-the-harness-this-repository-runs

The harness this repository runs

Sixteen specs governed one tree. `.claude/` is four hooks, ten skills, four agents and four rules, and the requirements on them were spread over specs 093, 047, 048, 051, 063, 068, 071, 072, 078, 080, 081, 082, 099, 104, 110 and 116, each written when that tree was a product this repository distributed. Spec 092 removed the distribution; what is left is one tree that one repository reads, described in sixteen places, most of them half about copies that no longer exist. This spec is that description, in one place: what the hooks may do, what the push gate refuses, how every hook reads a verdict, what the ten skills are and what each must contain, what the four rules say, and who owns the protocol. Every surviving requirement of the seventeen is carried here; what is dropped is named in §6 with the reason, and in every case the reason is that its subject was a copy spec 092 deleted.

Lifecycle approvedImplementation complete
094-one-gate-and-the-boundaries-it-holds

One gate, and the boundaries it holds

The governed loop has one definition and three boundaries where it is enforced: the commit, the pull request, and the merge. Five specs described that, each written against a copy the repository distributed. 020 put the merge driver in `.githooks/`; 064 wrote the composite gate as `kit/Makefile` and the workflow as `kit/govern.yml`; 089 fixed the guard that conflated a skip with a failure; 090 added the commit-boundary hook; 114 gave the target its controls and made the workflow call it instead of restating it. Spec 092 removed the copies and moved the gate to the repository root. This spec is the gate, in one place: the target and its controls, the two CI legs that call it, the commit-boundary hook that refuses without repairing, and the merge driver that resolves a same-shard conflict by regenerating rather than by leaving markers.

Lifecycle approvedImplementation complete
095-the-corpus-describes-what-exists

The corpus describes what exists

Spec 092 removed the kit and obeyed every standing rule while doing it: it withdrew claims, declared amendments, replaced acceptance, and edited no predecessor's prose. The result is a corpus where 102 unit claims were withdrawn from 29 specs, 29 specs no longer run their own acceptance because one block stands for all of them, two blocks pass only because the files they assert about are gone, and 41 documents describe artifacts that no longer exist while reading as live instruction. That is the cost of the rules, paid once, and the owner authorized a one-time exception to settle it. This spec is the record of that exception: what was deleted, what was merged into what, how the ordinals were made contiguous again, and what the rules go back to being the moment it is done.

Lifecycle approvedImplementation complete
096-compaction-is-a-verb-not-a-session

Compaction is a verb, not a session

Spec 095 removed 27 specs and renumbered 93, which meant rewriting about six thousand references by hand. Seven distinct defects were introduced doing it, every one a rewrite rule that matched more or less than its author meant, and every one silent: the corpus compiled, the gate passed, and the citations pointed at the wrong documents. Four were caught only because spec 089 built a sweep that runs every acceptance block, and the fifth was caught by the sweep finding a sixth. A corpus whose ids are the tool's own vocabulary should not need a session to renumber itself. This spec adds `spec-spine compact`: a plan in, a rewritten corpus out as data, idempotent, refusing every ambiguity the five defects came from, and reporting what it changed rather than leaving a six-thousand-line diff to read.

Lifecycle approvedImplementation complete
097-a-path-leaves-the-corpus-the-way-a-spec-does

A path leaves the corpus the way a spec does

Spec 096 mechanizes one retirement: a spec id leaving the corpus. On 2026-09-20 a different one was done by hand, `.claude/rules/` leaving this repository for a section of `AGENTS.md`, and it touched 81 references in 46 files, three frontmatter units, one hashed-input glob, one hook path pattern, six acceptance lines, three Rust tests and 122 derived shards. None of that is reachable by `compact`: its three reference forms are all spelled `NNN-slug`, and a path is spelled six other ways. This spec adds the second half of the same verb, a `retire` plan naming paths rather than specs, with the reference grammar a path actually has and, more importantly, the exclusions: eleven of those 81 occurrences had to survive, because a sentence recording that a path USED to exist is not a citation of it.

Lifecycle approvedImplementation complete
098-a-citation-the-renumber-could-not-see

A citation the renumber could not see

Spec 096 made compaction a verb and recorded seven defects the hand renumber produced. Two of them, the wrapped citation and the multi-ordinal citation, were measured after 096 was filed and were never repaired: `compact` refuses a plan naming a spec the corpus no longer has, so it prevents the next renumber and not the last one. Re-measuring on 2026-09-21 against the collapse commit found the debt is not 41 occurrences but 638, in three further classes nobody had looked for: 90 spec documents are still titled with another spec's ordinal, and 474 citations are written as a bare ordinal (`084 §3.1`, `050's`) which none of 096's three forms matches. This spec repairs all 638, adds the two forms and the file-selection fix that would have prevented them, and makes a title heading self-checking with a new lint code so this class cannot come back silently.

Lifecycle approvedImplementation complete
099-a-merged-acceptance-is-asked-again

A merged acceptance is asked again

`verify` is outside the gate chain because it executes what a spec declares and a pull request is a stranger's code. Spec 089 built the sweep that re-asks the corpus's acceptance and made it maintainer-invoked, which leaves the window it was built to close open between runs: spec 092's block was red on `main` from #277 until #278 with every required check green, and no light turned. This spec runs the sweep where the trust boundary already allows it, on the default branch after a merge and nightly, scoped after a merge to the specs that own a changed path. It amends spec 089's "never CI" to "never on untrusted input", which is the rule that clause was written to hold.

Lifecycle approvedImplementation complete
100-a-deleted-path-is-judged-where-it-lived

A deleted path is judged where it lived

The coupling gate resolves a deleted path's owners from the head index, where the claim the same change withdrew no longer exists, so a correct removal is refused as C-001 against a package floor that has nothing to say about it. A deleted path is judged against the snapshot that precedes the segment its deletion belongs to, reconstructed from that snapshot's own source bytes, and a snapshot the gate cannot reconstruct is an explicit non-success rather than a head-only pass.

Lifecycle approvedImplementation complete
101-readiness-is-scheduling-not-approval

Readiness is scheduling, not approval

`registry plan` partitions by `implementation` and `depends_on` and never by `status`, so a draft appears on the ready set by design. The layering is deliberate and stays; what is missing is a governed sentence saying so, which a consumer reading the document directly has no way to infer.

Lifecycle approvedImplementation complete
102-a-ready-spec-carries-its-status

A ready spec carries its status

`ReadySpec` carries `id` and `title`, so a consumer reading `registry plan` directly cannot apply an approval rule without a second query per entry. An additive `status` field is a read-schema MINOR that lets a direct consumer see what `/next` sees. An enhancement with a named cost, not a defect fix.

Lifecycle approvedImplementation complete
103-a-verifier-fixture-is-a-published-artifact

A verifier fixture is a published artifact

The tamper and cross-version cases an independent verifier must reproduce exist only as commands inside spec 068's acceptance block and as a measured table in docs/authority-evidence.md. A verifier outside this repository cannot run either. This emits them as a named, versioned fixture set: the bytes, the corpus each payload is about, and the outcome each case must reach, all measured against the shipped verifier rather than asserted.

Lifecycle approvedImplementation complete
104-a-producer-is-tested-as-published

A producer is tested as published

The published spec-spine-core 0.21.0 emits AGENTS.md and a `.claude/rules/` tree; the current source under the same version string emits neither. Both states passed a green workspace suite, because "the workspace is green" and "the artifact a consumer installs behaves this way" are different claims and only the first was ever tested. The producer contract is asserted against the packaged crate, through its public facade, from outside the workspace, and that check gates a release.

Lifecycle approvedImplementation complete
105-governed-scope-is-enabled-here

Governed scope is enabled here

Spec 078 added `[coverage] governed_scope` so a repository can bring its governance files under the ownership ratchet, and the repository that wrote it never turned it on. The measured cost is four unclaimed paths. They are claimed, the scope is declared over exactly the files this repository already folds into its content hash, and what stays exempt is stated rather than implied.

Lifecycle approvedImplementation complete
106-obligations-are-declared-constraints

Obligations are declared constraints

A spec's normative content is prose, so nothing downstream can name a single requirement, cite it, or tell whether the sentence it cited has changed. This adds an optional `obligations` frontmatter key: three kinds, ids stable within a spec, an anchor validated against the spec's own headings, explicit inputs for a verification, and withdrawal in place rather than deletion. The registry gains a digest for every section of every spec, beside the spec's full content hash, and a read resolves a qualified `<spec-id>#<obligation-id>` reference. No gate reads any of it.

Lifecycle approvedImplementation complete
107-a-context-closure-is-declared

A context closure is declared

A piece of work has no declared answer to "what must I have read before I act on this", and no way to tell later whether that context changed. A ContextClosure is that answer as data: the specs, sections and obligations a task is answerable to, named by the task's owner. spec-spine resolves a closure request against the committed ledger and returns every member with its identity and one order-independent digest over all of them, so "the context this work was done against has changed" is one comparison. The closure lives in the consumer's record; spec-spine only resolves it, and no gate reads one.

Lifecycle approvedImplementation complete
108-a-work-scope-is-declared

A work scope is declared

Spec 072 reports that two ready specs claim overlapping territory, at spec granularity. What was left of WorkScope is the path-level half: one piece of work declares which paths it expects to change alone, which it expects to change alongside named other specs, and which it only reads. A scope is a document the consumer holds, like a context closure (spec 107). spec-spine evaluates it against the committed ownership index, reporting undeclared crossings, unowned paths and wrong sharing declarations, and compares two scopes for conflicting intentions. Both answers are reads. Nothing here locks, reserves, excludes or permits anything, and no gate reads a scope.

Lifecycle approvedImplementation complete
109-impact-and-conflict-are-declared

Impact and conflict are declared

A spec can say which specs it extends and amends. It cannot say which requirements it is expected to affect, or which requirements it knowingly conflicts with. Both are declared in frontmatter against spec 106's qualified obligation ids, validated at compile, carried in the registry, and answered by one read that inverts the declarations so the affected side can see them. Nothing gates on either, and nothing computes an impact.

Lifecycle approvedImplementation complete
110-an-interface-reference-is-digest-pinned

An interface reference is digest-pinned

A spec can cite another repository's spec only in prose, so the citation cannot go stale visibly. This declares a cross-corpus interface reference pinned to the cited spec's content hash, and optionally to the digests of the sections it relies on, and adds one verifier that recomputes those digests from a corpus the caller supplies locally. A moved interface becomes a named, exit-coded fact. Discovery, fetching and trust in the exporter stay with the caller; nothing is ever pinned from what was first observed.

Lifecycle approvedImplementation complete
111-a-move-is-a-reviewed-mapping

A move is a reviewed mapping

The gate passes `--no-renames`, so a move arrives as a deletion and an addition and each half is judged where it is true. That is correct and it is not the whole story: nothing records that the two halves are one act. The spec performing a move declares it in its own frontmatter as authored, reviewed data. The compiler validates the declaration's shape, the lint checks its paths against the tree, and a lookup derived from every declaration follows declared chains, reporting ambiguity and cycles instead of guessing through them. A move is informational: it transfers no ownership, authorizes no deletion, infers no similarity and changes no verdict.

Lifecycle approvedImplementation complete
112-typed-overlays-ride-the-existing-seam

Typed overlays ride the existing seam

Four proposals (effect contracts, budgets, dependency rationale, contract adapters) all want to attach typed data to a spec's units without changing what a unit is. The corpus already has the seam: declared extra frontmatter (spec 012), carried into the registry as `extraFrontmatter`. This states the contract an overlay keeps on that seam, pins what the engine does and does not promise about it (canonical values, not original bytes; a hash scope that is the declaring spec's, not every shard), and asserts it with several independent overlays in one corpus. No new unit kind, no engine enforcement, and no domain schemas.

Lifecycle approvedImplementation complete
113-a-waiver-has-a-declared-lifecycle

A waiver has a declared lifecycle

A `Spec-Drift-Waiver:` line clears every refusal in the pull request it appears in, with no scope, no expiry and no record of what it excused. A waiver may now declare a path scope, an expiry, an ancestry and a use limit on lines of its own. The gate evaluates each declared check over inputs the caller supplies (an as-of date, an ancestry answer, a prior use count) and reports it as satisfied, failed or not evaluated, never guessing a missing input. A failed check makes the waiver clear nothing; a scoped waiver clears only its paths; and the verdict says which waiver cleared which violation. Nothing is counted, consumed, stored or authorized by the engine, and an undeclared waiver behaves exactly as before.

Lifecycle approvedImplementation complete
114-authoring-adapters-and-intent-are-separated

Authoring adapters and intent are separated

The feature register carried A10 (authoring-tool adapters) and B23 (an intent declaration) as one item. They belong to different owners: A10 is environment delivery, which is Statecraft's since spec 092, and B23 is a corpus-side declaration, which is spec-spine's. This separates them, records what an adapter author can build against, and specifies the smallest optional structured intent a spec can carry: a standing goal and its non-goals, validated for shape, transported into the registry, and read by no gate. The prose stays authoritative; attempt-specific planning stays with the consumer.

Lifecycle approvedImplementation complete
115-bindings-are-designed-not-shipped

Bindings are designed, not shipped

`docs/bindings-plan.md` designs napi, pyo3 and cgo bindings and says no binding code belongs in this repository. That mandate lived only in a document header. This records it in the corpus as a policy, and states the obligations the JSON facade keeps so that a binding remains buildable elsewhere, each as it measurably holds today. It builds nothing, endorses no external binding, and is reviewable rather than enforced. Its implementation is `n-a`: there is no binding to implement, and this spec is the decision not to.

Lifecycle approvedImplementation n-a
116-a-deferred-contract-claims-nothing

A deferred contract claims nothing

`L-001` warns when a spec declares no ownership edge, and exempts a superseded, retired or retroactive one because each is making a correct statement by claiming nothing. A spec deliberately not scheduled (`implementation: deferred`) is in the same position and is not exempt, so the one shape the corpus contract leaves a deferred contract, no territory, makes `lint --fail-on-warn` red. The exemption is extended to `deferred`, covers the declaration and never a claim, and re-arms the moment the spec is scheduled.

Lifecycle approvedImplementation complete
117-the-gate-resolves-the-binary-it-documents

The gate resolves the binary it documents

The root Makefile's header documents a binary-resolution order preferring the binary this checkout builds, and its assignment implements none of it: `SPEC_SPINE ?= spec-spine` is the PATH fallback and nothing else, so `make gate` on a machine with an installed CLI governs with whatever that CLI is. Measured here at three releases behind the checkout. The assignment is corrected to the order the header states, an unusable explicit override is refused rather than silently replaced, and the resolved binary and its version are announced before the chain runs.

Lifecycle approvedImplementation complete
118-the-renumber-is-history-not-a-standing-rule

The renumber is history, not a standing rule

Spec 095's acceptance asserts that the live corpus's ordinals are contiguous, `o == list(range(len(o)))`. Its requirement (3.3) is narrower: the one-time renumber left the survivors contiguous. Its own D-2 says reserved gaps are deliberate. Merging spec 117 while 105 to 116 stay reserved made the block red with no defect in the corpus. The repository owner ruled that the renumber is a historical transformation and not a perpetual contiguity rule (docs/release-candidate-0.22.0.md 10.1). This spec carries 095's block in full through `amends_verification`. The historical contiguity is read from the map that records it, and the live corpus is held to identity rules that survive growth, with fixture negatives showing each can still refuse. 095's requirements and block are not edited; its file gains only the superseded- acceptance note spec 082 3.4 requires.

Lifecycle approvedImplementation complete
119-a-release-verdict-judges-what-was-built

A release verdict judges what was built

The whole-corpus sweep (spec 089) runs every declared acceptance block and exits 1 when any fails. This repository files a spec's acceptance before the spec is built and writes it to fail first, so any filed-but-unbuilt draft keeps the sweep red. "Sweep green" as a release pre-flight is then unreachable for as long as the backlog is honest. The sweep had no lifecycle predicate, and the only ways to get a green run were to build unrequested features or to hide the failures. This spec adds a release verdict next to the corpus verdict. It is judged over the specs whose implementation is built, with pending work run, listed with its real outcome and never counted as success or exemption, and `--release` puts it in the exit code. The corpus verdict and its exit code are unchanged. It also moves the default run directory out of the macOS temporary tree, whose daily purge is the inferred cause of a run in which 26 blocks failed that had not, and makes that directory new per run, so a rerun cannot clear the evidence of the run before it.

Lifecycle approvedImplementation complete
120-a-scoped-sweep-judges-the-binary-the-blocks-name

A scoped sweep judges the binary the blocks name

The `Acceptance` workflow gives the sweep a binary built outside the sweep's worktree through `SPEC_SPINE_BIN`. Most acceptance blocks call `./target/release/spec-spine` relative to the worktree, and several do not build it first. In a whole-corpus run an earlier block's `cargo build --release` creates the file, so they pass by ordering. The push leg sweeps only the specs a merge touched, and when none of those builds, every block fails at exit 127, `not found`. That is how the ratification merge at `da47632b` and the merge at `ff79c68a` both went red with no acceptance failing. The workflow now lets the sweep build the binary from the revision inside its worktree, spec 089 3.8's default, so a block judges the binary it names.

Lifecycle approvedImplementation complete
121-the-acceptance-workflow-judges-what-was-built

The Acceptance workflow judges what was built

The nightly `Acceptance` leg exits on the sweep's corpus verdict, so it is red for as long as any draft is filed ahead of its build, which is this repository's normal state. A signal that is always red cannot tell a reader that an implemented spec's acceptance just broke. Spec 119 built the release verdict and left the workflow's choice open. This spec takes it: both legs exit on the release verdict, the full selected corpus still runs, and a new report step puts both verdicts, every release failure and every pending block's real outcome into the run's summary and annotations. A missing, unreadable or inconsistent report fails the job on its own.

Lifecycle approvedImplementation complete
122-a-commit-refuses-an-unresolved-merge

A commit refuses an unresolved merge or an unformatted Rust change

The commit-boundary hook (spec 094) refuses a stale or invalid derived tree and nothing else, so a merge whose conflict was "resolved" by staging the markers committed cleanly, and so did a Rust change whose formatting check had failed earlier in the same command chain. Both happened on this repository's expansion branches. The hook now also refuses unmerged index entries, conflict markers on the lines a commit adds (git's own detector, with git's own attribute as the only opt-out), and a staged Rust change that fails `cargo fmt --all --check`. It still writes and stages nothing.

Lifecycle approvedImplementation complete
123-a-startup-verdict-names-its-reader

A startup verdict names its reader

Three session starts on 2026-09-23 reported "spec registry: INVALID; codebase index: STALE" on a corpus that was valid and fresh. The reader was the repository's own `target/release/spec-spine`, built from an earlier revision and left in place while the checkout fast-forwarded past the frontmatter grammar it knows, so an incompatible reader's refusal was printed as a finding about the corpus. The hooks that turn `check` into a message now name the executable they selected, and when that executable is the in-tree build and is older than the source it is built from, they say the verdict is an earlier revision's reading and keep what it said. They still read only: nothing builds, regenerates or installs.

Lifecycle approvedImplementation complete
124-the-expansion-line-carries-its-own-version

The expansion line carries its own version

The frozen 0.22.0 candidate (`f9fa6a8f`) and `main` both answered `spec-spine 0.22.0` while emitting different registry, read and verdict schemas, so the version string could not say which engine a verdict came from, and an older reader judged a newer corpus invalid instead of refusing it. `main` carries the version the next release will be cut at (0.23.0 when filed, 0.24.0 since D-3, 0.25.0 since D-4, 0.26.0 since D-5, 0.27.0 since D-6, 0.28.0 since D-7), in all three package manifests; this repository's version floor moves with it, so a reader older than the corpus's grammar refuses by name (exit 3); the release runbook makes both a standing step; and a script records a binary's identity from evidence (path, digest, revision, whether it is that checkout's current build, and the schema axes it emits) rather than from `--version`. The frozen candidate is not recut.

Lifecycle approvedImplementation complete
125-a-forwarded-line-arrives-whole

A forwarded line arrives whole

`verify` forwards a command's stdout and stderr to its own stderr from two threads, one write per read. A read ends wherever the pipe's contents did, which is mid-line whenever the command writes faster than the drain, so a line of one stream could be spliced by a chunk of the other. Spec 090's own flood test counts intact lines and caught it intermittently: the post-merge acceptance sweep at `0bf9ff78` failed twice, in two different specs' runs of the same suite, with 8191 of 8192 lines intact. Each drain now delivers whole lines only, holding a partial line until its newline arrives or a bound is reached. Order between the streams is still not promised.

Lifecycle approvedImplementation complete
126-a-derived-file-stays-in-its-directory

A derived file stays in its directory

`compile` and `index` name each per-spec shard `<id>.json` after the spec's frontmatter `id`, and `attest --spec` names its attestation the same way. None of the three checked that the id was a file name before joining it to a directory. A corpus declaring `id: "../../../package"` made `compile` overwrite the repository's `package.json` with shard JSON, an absolute id wrote wherever it pointed, and `index` and `attest --spec` did the same while exiting 0. `V-012` reported the id, but after the write rather than instead of it. These are verbs documented as safe to run against a stranger's branch. Every file name derived from a spec id is now checked before anything is written, pruned or created: a name that is not one plain file name refuses the whole write with exit 3 and leaves the tree as it was.

Lifecycle approvedImplementation complete
127-a-derived-file-is-where-its-path-says

A derived file is where its path says

Spec 126 made every file name derived from a spec id one plain name, so an id can no longer walk a write out of its directory. Two ways past that remained. A symbolic link committed inside the derived tree (git records it like any file) made `compile`, `index` and `attest` write through it and prune `*.json` files wherever it pointed, all with exit 0. And a name that passes 126's lexical rule can still name a Windows device (`CON.json`, `nul.tar.json`), which is not a file at all there. Every path below the repository root that these verbs write, create, remove or prune is now checked for a link before anything moves, a device name is refused on every platform, and a refusal leaves the tree exactly as it was. A repository reached through a link still works.

Lifecycle approvedImplementation complete
128-a-derived-tree-stays-in-its-repository

A derived tree stays in its repository

`[layout] derived_dir` is read from the committed `spec-spine.toml`, and nothing checked where it pointed. `derived_dir = "../outside"`, an absolute path, or `x/../../outside` made `compile`, `index` and `attest` write their shards outside the repository and prune `*.json` files there, all with exit 0, on `main` and on spec 127's build. Spec 127 checks every component below the repository root for links, and stops where a path leaves the root (its D-4), so it did not reach this. A `derived_dir` that is absolute, carries a `..` segment, or uses a Windows path form is now refused when the configuration is loaded, before any verb reads or writes, and the checked writer refuses any path that is not wholly below its root. A repository reached through a link still works.

Lifecycle approvedImplementation complete
129-a-json-config-obeys-the-loaders-rules

A configuration passed as JSON obeys the loader's rules

`load_config` refuses a `derived_dir` that leaves the repository (128), a `state_dir` that is the repository root or overlaps a governed root (036), and a malformed `[index.slices]` entry (011). The JSON facade deserialized its configuration with serde alone and ran none of those rules, at all 22 entries that take one, so a binding could hand the engine a configuration no `spec-spine.toml` can hold. `scaffold_init_json`, the entry Statecraft links, returned `Ok` with a `spec-spine.toml` that every verb then refuses with exit 3, and it wrote each value between literal quotes, so a `"` broke the file and a `\` changed the value. The rules now live in one public function, `validate_config`, which the loader and every facade entry call, and the scaffold refuses what the loader refuses and escapes what it writes. Checks that need the filesystem or the running binary stay where they are.

Lifecycle approvedImplementation complete
130-a-planned-claim-ends-at-completion

A planned claim ends at completion

Spec 063's `planned: true` lets a spec claim territory it has not written, so a corpus gating on `--fail-on-unresolved` can ratify before it builds. 063 bound the flag to completion only through the lint (`L-011`): a spec at `implementation: complete` whose planned unit is absent passed `check`, `index check` and both with `--fail-on-unresolved`, exit 0, because the index dropped the diagnostic for a planned unit at every lifecycle. A consumer whose hooks run `check` alone never saw the contradiction. The flag now holds a claim open only until the spec says `complete`; after that the absent unit classifies like any settled claim, the natural `I-0xx` error, and `check` exits 1. `index coverage` also stops listing a planned unit that has resolved as "not yet written". Nothing changes for an unmarked claim or for a spec whose work is not complete.

Lifecycle approvedImplementation complete
131-a-scaffold-can-pin-its-producer

A scaffold can pin its producer

Statecraft writes the `spec-spine.toml` that `scaffold_init_json` returns byte for byte, and that file pins nothing: the `[meta]` header and the key are both commented out (055 §3.4), and the example is a caret range. A repository Statecraft initializes is therefore judged by whatever binary a hook finds. This spec adds an opt-in scaffold option, `pinExactVersion`, that emits an active `[meta]` table with `required_version = "=<this producer's version>"`, through a new facade entry `scaffold_init_opts_json(config_json, options_json)` and its Rust form `scaffold_init_opts`. Without the option the output is byte-identical to `scaffold_init_json`'s, measured against 0.25.0 for Statecraft's configuration and the default. The configuration is validated exactly as 129 requires, and an unknown option is refused rather than silently producing an unpinned file.

Lifecycle approvedImplementation complete
132-one-exit-contract-for-the-family

One exit contract for the family

spec-spine and the Statecraft CLI answered in two exit vocabularies. spec-spine spent 2 on staleness and 3 on everything that went wrong, from a malformed `spec-spine.toml` to a failed read to a refused write, so a consumer could not tell "you asked for something this tool will not do" from "the disk failed", and a containment refusal was reported as I/O. This spec adopts the family contract the two tools share: 0 ok, 1 finding (validation, drift, stale, unresolved, not found, authored content that does not parse), 2 refused (an invalid configuration, a version pin not met, a containment refusal: nothing done), 3 usage, 4 failed (I/O, internal, a tool-produced artifact that fails its schema). `Error` gains `Refused`, `Usage` and `Internal`, and every call site is classified. The `--json` envelope becomes the family envelope, version 1.0.0: `schemaVersion`, `tool`, `verb`, `outcome`, `exitCode`, `summary`, then `report` or `error`, with `ok` removed and `error.kind` a closed set of ten tokens. It is a deliberate breaking change, accepted by the owner, taken once so the consumer migrates once.

Lifecycle approvedImplementation complete
133-owner-delegation-is-declared

Owner delegation is declared

What an agent may decide in this repository without asking was spread across the owner's session instructions and never written where every agent reads. `AGENTS.md` gains an "Owner delegation" section that states it: agents decide, record and report reversible choices inside an adopted direction, relocation-only changes, test and evidence design, repository hygiene, and unambiguous corrections of internal inconsistencies; ratification, waivers, publication, spending, trust roots and signing, changes to the gate, check suite or acceptance authority, anything visible outside the repository or affecting another adopter, and deleting anything remote stay with the owner. Every handoff ends with one decision table. The section widens no authority the standing rules withhold.

Lifecycle approvedImplementation complete
134-the-suite-runs-on-windows

The suite runs on Windows

spec-spine ships a Windows binary and proves its shard trees byte-identical on Windows, but CI only ever built there: the workspace test suite ran on Linux alone, so a Windows behavior difference could ship with every check green. A `test (windows)` job now runs `cargo test --workspace` on `windows-latest` as a check of its own, required through `ci-gate`. A test that cannot run on Windows is gated or quarantined in its source with a tracked finding, never skipped by the workflow, and Windows results are reported apart from build results.

Lifecycle approvedImplementation complete
135-the-toolchain-floor-and-dependencies-are-checked

The toolchain floor and the dependencies are checked

The workspace declares `rust-version = "1.85"` and nothing ever built it there; the code uses let-chains, stable only since 1.88, so the declaration was false. Dependencies were never checked for advisories, licenses or sources, and the dev tree carried two open RustSec advisories through `jsonschema`'s HTTP resolver, which no test uses. CI now builds and tests at the declared `rust-version`, read from `Cargo.toml`, and runs `cargo deny check` against a committed `deny.toml`; both are required through `ci-gate`. `jsonschema` drops its default features, which removes the whole HTTP and TLS dev tree and both advisories, and the declared `rust-version` is corrected to the measured floor. `CONTRIBUTING.md` states the loop.

Lifecycle approvedImplementation complete
136-the-legacy-ledger-is-paid-registry-and-grammar

The legacy ledger is paid: registry and grammar

10 specs filed before `verify` existed (001, 002, 009, 012, 013, 014, 015, 017, 018, 026) sit on the legacy ledger in `scripts/verify-sweep.sh` with no executable acceptance, although the tests that hold their behavior exist and pass. This spec carries that acceptance for them under `amends_verification`, the route spec 132 used, and deletes their ledger lines in the same change. Every command names its tests exactly and asserts the exact pass count, so a renamed or deleted test turns the block red instead of passing on an empty filter. The area is the registry compiler, its query surface, and the frontmatter and edge grammar.

Lifecycle approvedImplementation complete
137-the-legacy-ledger-is-paid-index-and-lint

The legacy ledger is paid: index, lint and resolution

10 specs filed before `verify` existed (003, 004, 010, 011, 016, 020, 022, 023, 024, 025) sit on the legacy ledger in `scripts/verify-sweep.sh` with no executable acceptance, although the tests that hold their behavior exist and pass. This spec carries that acceptance for them under `amends_verification`, the route spec 132 used, and deletes their ledger lines in the same change. Every command names its tests exactly and asserts the exact pass count, so a renamed or deleted test turns the block red instead of passing on an empty filter. The area is the codebase index, lint, section and symbol resolution, and sharding.

Lifecycle approvedImplementation complete
138-the-legacy-ledger-is-paid-coupling-and-freshness

The legacy ledger is paid: coupling, coverage and freshness

10 specs filed before `verify` existed (005, 008, 027, 028, 029, 030, 031, 033, 035, 036) sit on the legacy ledger in `scripts/verify-sweep.sh` with no executable acceptance, although the tests that hold their behavior exist and pass. This spec carries that acceptance for them under `amends_verification`, the route spec 132 used, and deletes their ledger lines in the same change. Every command names its tests exactly and asserts the exact pass count, so a renamed or deleted test turns the block red instead of passing on an empty filter. The area is the coupling gate, ownership coverage, freshness and scheduling.

Lifecycle approvedImplementation complete
139-the-legacy-ledger-is-paid-verdicts-and-lifecycle

The legacy ledger is paid: verdicts, attestation and lifecycle

9 specs filed before `verify` existed (007, 021, 032, 034, 038, 039, 040, 041, 042) sit on the legacy ledger in `scripts/verify-sweep.sh` with no executable acceptance, although the tests that hold their behavior exist and pass. This spec carries that acceptance for them under `amends_verification`, the route spec 132 used, and deletes their ledger lines in the same change. Every command names its tests exactly and asserts the exact pass count, so a renamed or deleted test turns the block red instead of passing on an empty filter. The area is verdicts, attestation, lifecycle leniency, governance documents and the PyPI shim.

Lifecycle approvedImplementation complete
140-a-prose-only-example-stays-prose-only

A prose-only example stays prose-only

Spec 043's acceptance, and one of its tests, use spec 041 as the corpus's standing example of a spec whose acceptance is prose only: `verify 041` must report `not-declared`. Spec 139 carries 041's acceptance, which makes that example false while the rule it illustrates still holds. This spec carries 043's block unchanged except that the example is 037, which stays prose-only by design (139 3.3), and moves the test's example the same way.

Lifecycle approvedImplementation complete
141-a-governance-edit-rewrites-one-file

A governance edit rewrites one file

Every index shard's `shardHash` folds one global scalar over `spec-spine.toml` and every `[index] extra_hashed_inputs` file, so a one-line edit to any root document restamps every shard. Measured on this repository at `153d35df`: an `AGENTS.md` edit rewrites 139 shards, and two pull requests editing two different root documents conflict on all 139. An adopter's merge queue pays that on every such PR. This spec moves the global inputs out of the shard hashes into one committed sidecar, `codebase-index/inputs.json`, with one entry per input file, compared byte for byte like a shard. A governance edit then rewrites that one file, and two edits to different documents change lines far enough apart to merge. The aggregate index content hash folds the sidecar, so an attestation still covers every authored input.

Lifecycle approvedImplementation complete
142-a-relocation-is-proven

A relocation is proven

Splitting an approved spec had no honest path. A partial `supersedes` added the new spec as an owner of a unit and left the old one owning it too, so an edit to either spec cleared a change to the code. Moving a section of text out of an approved spec read as a requirement change, whatever the text said, because a section's digest is keyed by its spec's path. `amends_sections` accepted any string, and nothing said whether `amends` grants code ownership. This spec makes a partial `supersedes` an exclusive hand-off of its unit; adds a `relocates` declaration that `delta` proves against the merge base, classing a proven move `relocation` instead of `requirement`; validates `amends_sections` against the amended specs' headings (L-017); warns when two live specs establish one unit (L-018); and states the rule that `amends` changes what a spec requires and never who owns its code.

Lifecycle approvedImplementation complete
143-line-endings-do-not-stale-the-ledger

Line endings do not stale the ledger

Every emitted derived file is canonical JSON with LF endings, and the freshness reads compare the committed bytes with the recompute exactly (spec 086). Git on Windows with `core.autocrlf=true` and no `.gitattributes` rule for the derived tree checks those files out with CRLF, so every shard read as `modified`: a false staleness that regenerating cannot cure, because the next checkout converts again. Spec 134 quarantined three tests for it as WF-7. This spec makes the comparison read a committed file whose only difference is CRLF for LF as the same text, in the registry, the index and the inputs sidecar, keeps every other byte significant, and lifts the quarantine so the Windows test job proves it.

Lifecycle approvedImplementation complete
144-a-repository-path-is-one-type

A repository path is one type

Paths the engine joins onto the repository root were checked key by key, or not at all. `derived_dir` refused `/`, `..`, `\` and `:` (spec 128); `specs_dir` and `standards_dir` accepted anything, so a corpus could be read from outside the repository; a compact plan refused a leading slash (WF-8) and let the drive-relative `C:foo` through; no key refused a Windows device-name segment; and a link inside the repository could point any governed read outside it. This spec gives one rule for a repository-relative path, applies it to every layout root and to compact plan paths, and refuses a run whose tree holds a link that resolves outside the repository, exit 2, while a repository reached through a link keeps working.

Lifecycle approvedImplementation complete
145-an-unresolved-claim-is-not-called-stale

An unresolved claim is not called stale

Spec 079 taught `check` and `index check` to tell a stale shard from an unresolved claim, because the two have different remedies and regenerating cures only the first. The other verbs that read the committed index (`index coverage`, `index owner`, `couple`, `scope`, and `delta` at the merge base) kept the folded verdict and still said "index is stale" about a claim that resolves to nothing, naming a `blocking-diagnostics` shard and implying the wrong fix. This spec gives them one guard that keeps the two apart: a committed shard whose bytes moved is staleness, as before; an unresolved claim with byte-exact shards is a validation finding that names the claim and says regenerating does not clear it. The exit code stays 1; under `--json` the error kind becomes `validation`, with the claim in `violations`.

Lifecycle approvedImplementation complete
146-carried-acceptance-follows-139-and-144

Carried acceptance follows 139 and 144

The pre-release sweep of `e440b714` failed two approved specs' acceptance, both invalidated by specs merged this cycle rather than by a defect. Spec 046's block lints this corpus from a scratch root whose `specs` is a link to this repository's `specs/`, a link that resolves outside the scratch root, which spec 144 now refuses (exit 2). Spec 089's block asserts that the built-in legacy ledger reports spec 011 `exempt`, and spec 137 retired 011's line when it gave 011 an executable acceptance. This spec carries both blocks under `amends_verification`, unchanged except that 046's scratch root copies the corpus instead of linking it, and 089 asks the ledger about 019, which stays exempt by design (139 3.3).

Lifecycle approvedImplementation complete
147-a-link-above-the-derived-root-is-checked-on-read

A link above the derived root is checked on read

Spec 144's link walk skips every symbolic link at or above the derived and state roots and leaves them to spec 127, whose refusal fires only on write. A read-only verb therefore reads through such a link unchecked: with `.statecraft` a link to a directory outside the repository, `check` reads the committed derived tree and a governed file stored beside it from outside and reports the repository fresh, exit 0. This spec checks those links on read with 144's own rule, while still not descending into either root.

Lifecycle approvedImplementation complete
148-the-link-rule-is-tested-on-windows

The link rule is tested on Windows

Spec 144's rule that no governed read leaves the repository through a link is tested only on Unix: both link tests in `tests/repo_path.rs` are `#[cfg(unix)]`, and 144 §4 puts Windows links and junctions out of scope. Windows is a shipped platform with a required `test (windows)` job (spec 134), and it has two link kinds 144's walk must see: symbolic links and directory junctions. This spec adds a Windows test for both, and records what a runner without the symbolic-link privilege does.

Lifecycle approvedImplementation complete
149-the-installer-is-tested

The installer is tested

Spec 006 is one of the four specs the pre-release sweep still reports `exempt`, because `install.sh`, the tag-gated release pipeline and npm publication "run only in a release" (139 3.3). Two of its three channels do not: `install.sh` is a shell script whose every decision (platform, tag, checksum, attestation policy, install directory) can be exercised offline against a local fixture release, and the npm shim already has an offline unit test and smoke test. This spec adds an offline test of `install.sh`, gives 006 an executable acceptance that runs it and the npm shim's tests, and removes 006's line from the legacy ledger. The release pipeline's publication steps stay unexercised by it, and it says so.

Lifecycle approvedImplementation complete
150-a-change-runs-the-acceptance-it-can-break

A change runs the acceptance it can break

Specs 144 and 137 each broke an approved spec's acceptance (046 and 089), and nothing noticed until the pre-release sweep of `e440b714`, after both had merged; spec 146 repaired them. The sweep runs only on a merged revision (specs 089, 099), and no pre-merge step runs any acceptance other than the changed spec's own. This spec adds a pre-merge acceptance run: a selector that names every spec whose acceptance a change can break, and a step that runs those blocks on the pull request's head before it merges, with the counts recorded in the pull request. Measured on 136 to 146, a selector keyed on references alone would not have been narrower than the corpus, and would have caught 046 only through its generic `lint` line, so the selector falls back to the whole corpus when engine source changes.

Lifecycle approvedImplementation complete
151-carried-acceptance-tests-what-it-names

Carried acceptance tests what it names

The 0.27.0 cross-reference of 136 to 146 found three acceptance plans that could pass without testing what their spec now says. 018's plan (carried by 136) never runs 142's amendment of it: its coupling tests build their index by hand, so they pass whether or not the index hands a partially superseded unit over. 144's link-rule line accepts any positive test count, so a link test that stops running still passes. 145's binary line drives only `index coverage`, while six other verbs read the same guard. This spec carries all three plans under `amends_verification`: 018's section gains the hand-off, run through `index` and the shipped binary; 144's line names its three tests; 145's block gains one fixture driven through all seven verbs. Each new line fails under a mutation the carried line it tightens passes.

Lifecycle approvedImplementation complete
152-a-refusal-says-what-it-is-in-every-form

A refusal says what it is in every form

Three consumer-visible inconsistencies survived 0.27.0, each documented in its handoff with a workaround. `check --json` and `index check --json` still report an unresolved claim as `fresh: false` with a `stale shard` line, where the human output and every other verb say validation, not staleness (spec 145). `index coverage`, `index owner`, `scope evaluate` and `registry show` print nothing on stdout under `--json` when they refuse, so a consumer gets an empty document where the verdict verbs give an error envelope. And a compact plan path refused for a `:` is told to drop `.`, `..` and a leading slash, none of which it has. This spec makes the index half of the JSON freshness report answer drift alone and carry the claims separately, gives every `--json` verb an error envelope on failure, and makes the compact refusal name the rule the path broke.

Lifecycle approvedImplementation complete
153-a-test-count-is-exact

A test count is exact

Thirty-six live acceptance lines, in seventeen blocks that eighty specs' plans resolve to, accept `test result: ok. [1-9][0-9]* passed`: they pass while any one test in the target runs, so deleting, renaming, ignoring or platform-gating most of a test file still passes. This spec states the rule (no live plan accepts a loose count) and asserts it corpus-wide through `verify <id> --plan`. It holds no block itself: each of the seventeen is tightened by its own spec, in the form spec 151 gave 144, so no single spec becomes the acceptance of eighty. This spec is built last, when its rule holds.

Lifecycle draftImplementation complete
154-the-pre-merge-sweep-is-a-skill-step

The pre-merge sweep is a skill step, not a gate step

Spec 150 put the pre-merge `verify-sweep.sh --affected-by` run into the `/ship` and `/shepherd` skills, as its 3.2 requires. Spec 089's acceptance, carried for 046 and 089 by spec 146, asserts that no skill mentions `verify-sweep` at all, so 046, 089 and 146 fail from 150's merge on. 150's own sweep did not show it: those three failed earlier in the same block, on the stale 006 ledger entry. This spec carries 146's block under `amends_verification`, unchanged except that the one line asserts what 089 meant and 150 keeps: every mention of the sweep in a skill is the pre-merge `--affected-by` step, and the gate list names no sweep.

Lifecycle approvedImplementation complete
155-selected-content-accessor

Select bounded content from one repository snapshot

Adds a deterministic, read-only selected-content request and response over one explicitly identified repository snapshot. Callers may select a whole spec, spec section, obligation, owned source unit, explicit file, explicit directory member, symbol, module, or deterministically supported test, with bounded projections, stable spans and digests, canonical ordering, continuation, explicit omissions, and no execution or authority effect.

Lifecycle approvedImplementation complete
156-statecraft-profile-10-governs-this-repository

Statecraft Profile 11 governs this repository

Statecraft is the adopted owner of the managed development environment, but this repository still carries its pre-adoption CI workflow, AI review and instruction surface. Profile 11 is adopted at one recorded identity, with its exact spec-spine pin and managed authority files, while the Windows, MSRV, cargo-deny, no-default-features, self-governance and determinism checks particular to this engine remain required behind the one ci-gate status.

Lifecycle approvedImplementation complete
157-the-affected-sweep-runs-in-the-merge-queue

The affected-acceptance sweep runs in the merge queue

Spec 150 runs the affected-acceptance sweep in the session that drives a pull request, on its head, and records the counts in the pull request body (D-1). Any engine change selects the whole corpus, so each remediation round costs the session about 45 minutes and a fresh body record, and two pull requests swept at once share one machine. 150 D-1 named the alternative and left it to a later spec: run the sweep in CI on `merge_group`, which sweeps only a change a maintainer has queued. This spec takes it. The sweep becomes a required merge-queue check, sharded across a job matrix so a whole-corpus run is not one sequential job, and the session step and the body record retire. The job belongs to the Statecraft profile, so every governed repository gets it; this repository declares it until the profile carries it.

Lifecycle draftImplementation complete
158-the-affected-selection-is-a-governed-read

The affected selection is a governed read

Spec 150's selector, which names every spec whose acceptance a change can break, lives in an embedded Python block of `scripts/verify-sweep.sh`, a script only this repository carries. Spec 157 moves the pre-merge sweep into the merge queue across every Statecraft-governed repository, and the Statecraft owner decided (2026-09-27) that spec-spine owns selection: the profile's sweep job calls a spec-spine read and shards the plan it returns. This spec adds that read, `verify --affected-by <base> --plan --json`: the CLI turns the Git range into changed paths, the core selects as a pure function of the configuration, the corpus and those paths, and the answer names each selected spec, the rule that selected it and its effective plan. The whole-corpus trigger becomes configuration rather than this repository's layout, and `verify-sweep.sh` calls the read instead of keeping a second selector.

Lifecycle draftImplementation pending
168-ratification-has-two-paths

Ratification has two paths, both an owner approval

This repository builds a spec while it is still a draft and ratifies it in a second, hand-authored pull request. The owner has chosen ratify-then-build instead, with ratification as a triggerable action gated on an owner-only deployment environment, and a second path that ratifies at merge when a pull request lands a spec at draft and complete. Both paths flip only `status`, bind the approval to the exact bytes shown, and leave ratification owner-only.

Lifecycle draftImplementation pending
170-a-consumer-is-served-answers-not-access

A consumer is served answers, not access

Fixes how spec-spine serves a programmatic consumer, the Statecraft CLI first. There are two tiers, split by whose pinned version decides the answer rather than by who is asking: a judging tier (the CLI's JSON envelopes and the family exit contract, answered by the binary the judged repository's own pin admits) and a producer tier (pure library functions whose output the caller owns). No surface is shaped for, gated to, or named after one consumer. A consumer's depth comes from a declared consumer contract instead: the verbs, schema axes, envelope members and exit codes it relies on, asserted by this repository's own suite, plus one public `capabilities` verb that states what a binary supports so no consumer has to probe `--help`.

Lifecycle draftImplementation pending
171-exact-test-counts-for-082

Exact test counts for 082

Spec 082's acceptance block carries one line that accepts `test result: ok. [1-9][0-9]* passed`, which passes while any one test in the target runs. This spec holds 082's block, carries it byte-identical, and replaces each loose line with spec 151's form: the tests the line ran, named with `--exact`, and `N passed; 0 failed`. One of spec 153's per-block tightening specs (153 D-5).

Lifecycle approvedImplementation complete
172-exact-test-counts-for-084

Exact test counts for 084

Spec 084's acceptance block carries one line that accepts `test result: ok. [1-9][0-9]* passed`, which passes while any one test in the target runs. This spec holds 084's block, carries it byte-identical, and replaces each loose line with spec 151's form: the tests the line ran, named with `--exact`, and `N passed; 0 failed`. One of spec 153's per-block tightening specs (153 D-5).

Lifecycle approvedImplementation complete
173-exact-test-counts-for-085

Exact test counts for 085

Spec 085's acceptance block carries one line that accepts `test result: ok. [1-9][0-9]* passed`, which passes while any one test in the target runs. This spec holds 085's block, carries it byte-identical, and replaces each loose line with spec 151's form: the tests the line ran, named with `--exact`, and `N passed; 0 failed`. One of spec 153's per-block tightening specs (153 D-5).

Lifecycle approvedImplementation complete
174-exact-test-counts-for-086

Exact test counts for 086

Spec 086's acceptance block carries one line that accepts `test result: ok. [1-9][0-9]* passed`, which passes while any one test in the target runs. This spec holds 086's block, carries it byte-identical, and replaces each loose line with spec 151's form: the tests the line ran, named with `--exact`, and `N passed; 0 failed`. One of spec 153's per-block tightening specs (153 D-5).

Lifecycle approvedImplementation complete
175-exact-test-counts-for-102

Exact test counts for 102

Spec 102's acceptance block carries one line that accepts `test result: ok. [1-9][0-9]* passed`, which passes while any one test in the target runs. This spec holds 102's block, carries it byte-identical, and replaces each loose line with spec 151's form: the tests the line ran, named with `--exact`, and `N passed; 0 failed`. One of spec 153's per-block tightening specs (153 D-5).

Lifecycle approvedImplementation complete
176-exact-test-counts-for-093

Exact test counts for 093

Spec 093's acceptance block carries three lines that accept `test result: ok. [1-9][0-9]* passed`, which pass while any one test in the target runs. This spec holds 093's block, carries it byte-identical, and replaces each loose line with spec 151's form: the tests the line ran, named with `--exact`, and `N passed; 0 failed`. One of spec 153's per-block tightening specs (153 D-5).

Lifecycle approvedImplementation complete
177-exact-test-counts-for-096

Exact test counts for 096

Spec 096's acceptance block carries eight lines that accept `test result: ok. [1-9][0-9]* passed`, which pass while any one test in the target runs. This spec holds 096's block, carries it byte-identical, and replaces each loose line with spec 151's form: the tests the line ran, named with `--exact`, and `N passed; 0 failed`. One of spec 153's per-block tightening specs (153 D-5).

Lifecycle approvedImplementation complete
178-exact-test-counts-for-098

Exact test counts for 098

Spec 098's acceptance block carries two lines that accept `test result: ok. [1-9][0-9]* passed`, which pass while any one test in the target runs. This spec holds 098's block, carries it byte-identical, and replaces each loose line with spec 151's form: the tests the line ran, named with `--exact`, and `N passed; 0 failed`. One of spec 153's per-block tightening specs (153 D-5).

Lifecycle approvedImplementation complete
179-exact-test-counts-for-137

Exact test counts for 137

Spec 137's acceptance block carries three lines that accept `test result: ok. [1-9][0-9]* passed`, which pass while any one test in the target runs. This spec holds 137's block, carries it byte-identical, and replaces each loose line with spec 151's form: the tests the line ran, named with `--exact`, and `N passed; 0 failed`. One of spec 153's per-block tightening specs (153 D-5).

Lifecycle approvedImplementation complete
180-exact-test-counts-for-138

Exact test counts for 138

Spec 138's acceptance block carries two lines that accept `test result: ok. [1-9][0-9]* passed`, which pass while any one test in the target runs. This spec holds 138's block, carries it byte-identical, and replaces each loose line with spec 151's form: the tests the line ran, named with `--exact`, and `N passed; 0 failed`. One of spec 153's per-block tightening specs (153 D-5).

Lifecycle approvedImplementation complete
181-exact-test-counts-for-149

Exact test counts for 149

Spec 149's acceptance block carries one line that accepts `test result: ok. [1-9][0-9]* passed`, which passes while any one test in the target runs. This spec holds 149's block, carries it byte-identical, and replaces each loose line with spec 151's form: the tests the line ran, named with `--exact`, and `N passed; 0 failed`. One of spec 153's per-block tightening specs (153 D-5).

Lifecycle approvedImplementation complete
182-exact-test-counts-for-151

Exact test counts for 151

Spec 151's acceptance block carries one line that accepts `test result: ok. [1-9][0-9]* passed`, which passes while any one test in the target runs. This spec holds 151's block, carries it byte-identical, and replaces each loose line with spec 151's form: the tests the line ran, named with `--exact`, and `N passed; 0 failed`. One of spec 153's per-block tightening specs (153 D-5).

Lifecycle approvedImplementation complete
183-exact-test-counts-for-088

Exact test counts for 088

Spec 088's acceptance block carries one line that accepts `test result: ok. [1-9][0-9]* passed`, which passes while any one test in the target runs. This spec holds 088's block, carries it byte-identical, and replaces each loose line with spec 151's form: the tests the line ran, named with `--exact`, and `N passed; 0 failed`. One of spec 153's per-block tightening specs (153 D-5).

Lifecycle approvedImplementation complete
184-exact-test-counts-for-092

Exact test counts for 092

Spec 092's acceptance block carries four lines that accept `test result: ok. [1-9][0-9]* passed`, which pass while any one test in the target runs. This spec holds 092's block, carries it byte-identical, and replaces each loose line with spec 151's form: the tests the line ran, named with `--exact`, and `N passed; 0 failed`. One of spec 153's per-block tightening specs (153 D-5).

Lifecycle approvedImplementation complete
185-exact-test-counts-for-097

Exact test counts for 097

Spec 097's acceptance block carries one line that accepts `test result: ok. [1-9][0-9]* passed`, which passes while any one test in the target runs. This spec holds 097's block, carries it byte-identical, and replaces each loose line with spec 151's form: the tests the line ran, named with `--exact`, and `N passed; 0 failed`. One of spec 153's per-block tightening specs (153 D-5).

Lifecycle approvedImplementation complete
186-exact-test-counts-for-099

Exact test counts for 099

Spec 099's acceptance block carries one line that accepts `test result: ok. [1-9][0-9]* passed`, which passes while any one test in the target runs. This spec holds 099's block, carries it byte-identical, and replaces each loose line with spec 151's form: the tests the line ran, named with `--exact`, and `N passed; 0 failed`. One of spec 153's per-block tightening specs (153 D-5).

Lifecycle approvedImplementation complete
187-exact-test-counts-for-156

Exact test counts for 156

Spec 156's acceptance block carries four lines that accept `test result: ok. [1-9][0-9]* passed`, which pass while any one test in the target runs. This spec holds 156's block, carries it byte-identical, and replaces each loose line with spec 151's form: the tests the line ran, named with `--exact`, and `N passed; 0 failed`. One of spec 153's per-block tightening specs (153 D-5).

Lifecycle approvedImplementation complete
188-a-repository-pin-selects-the-engine

A repository's pin selects the engine that runs

Adds a launcher: a small executable, separate from the engine, that a user or a consumer invokes as `spec-spine`. It finds the repository, reads the engine pin from `spec-spine.toml` with a tolerant reader, resolves an installed engine of exactly that release, verifies its digest, and executes it with the original arguments, working directory, streams and exit status. Engines are stored once per user, content addressed, and a repository may instead declare a local tool directory. A committed `spec-spine.lock`, which no engine reads, records each target's engine digest. Acquisition of a missing engine happens only when the user or CI enabled it, never because a repository asked. A consumer asks `launcher resolve --json` and executes the absolute path it returns. The launcher never judges a repository.

Lifecycle draftImplementation complete
189-agent-attribution-is-refused

Agent attribution is refused in commits and pull requests

Pull request 417 reached a green ci-gate with four commits whose author and committer were an agent identity, an agent co-author trailer on every commit, and an agent footer, attribution marker and project-thread link in its body. The authored-content checker refused only the em dash and one session-link form, and nothing read commit identity. The checker now refuses agent-session links and agent attribution in every text it already reads, and refuses an agent author or committer on every commit the governance step judges. The project settings stop Claude Code from writing the attribution in the first place.

Lifecycle draftImplementation complete
190-the-gate-requires-the-queue-sweep

The carried gate line names the queue sweep

Spec 157 added `affected-acceptance` to `ci-gate`'s `needs:` in `statecraft-ci.yml`, as its section 3.1 requires. Spec 187's carried block, which is the plan of 156, 091, 094, 124, 134 and 135, still asserts the six-job list byte for byte, so all seven plans fail at the same command on `main` since 157 merged, and every queued change that selects them is refused. This spec holds 187's block, carries it byte-identical, and replaces that one line with the seven-job list 157 made true.

Lifecycle draftImplementation complete
191-profile-13-keeps-the-engine-local

Profile 13 keeps the exact engine local

Adopts Statecraft revision 13 without narrowing the engine-specific checks or retiring the repository-owned merge-queue acceptance sweep.

Lifecycle draftImplementation complete