From 79a2507d47daa2bb915855fd800314d1472ccd66 Mon Sep 17 00:00:00 2001 From: Levi Neuwirth Date: Fri, 31 Jul 2026 21:03:29 -0400 Subject: [PATCH] Contract for P13-S27, tracked as DRAFT and blocked MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reduction-version check is a tautology: reduction_version_for seeds a superblock from the canonical base's self-report, and open compares the base against that superblock. Both operands descend from the same source. It catches a corrupt base disagreeing with its superblock, and necessarily passes for a conformingly propagated stale one. Rulings: a typed BundleCapabilities required at both constructors and carried on the Bundle, with no default; outright rejection on mismatch via CanonicalBaseRequiresRebuild, not read-only and not an anomaly. Storing the capability keeps all 57 commit sites unchanged; only open (57) and create (32) move. Pin 2a stays an open question. Baseline zero does not preserve the corpus, and after S16 moves the authority to one, a pre-S27 base that happens to carry one is indistinguishable from a legitimately rebuilt S16 base. A raw u32 has no provenance. Four candidate dispositions are recorded, including FORMAT_MINOR as a carrier, which is rejected here for two independent reasons: the header never changes after creation (core_spec.tex:10799) so a legacy bundle that commits a validated base keeps its old minor forever, and a minor change may only append append-safe discriminants (core_spec.tex:12258), not alter acceptance semantics — while current readers ignore minor entirely, so the boundary would bind only readers that already comply. What survives: provenance must be carried by a container property old readers cannot silently accept and a later commit cannot inherit unchanged. That is a format-epoch design, filed next as P13-S28. Three claims of mine died while scoping this, all recorded in the contract: a constructor-grep that could not see a propagated value, a head -14 truncation behind a universal negative, and reading `format_major != FORMAT_MAJOR` for the half that supported the proposal. A contract about unobservable defects should carry its own. Docs only. Not dispatchable. S16 and S27 both remain blocked. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV --- spec/CONTRACT_P13S27_REDUCTION_AUTHORITY.md | 505 ++++++++++++++++++++ 1 file changed, 505 insertions(+) create mode 100644 spec/CONTRACT_P13S27_REDUCTION_AUTHORITY.md diff --git a/spec/CONTRACT_P13S27_REDUCTION_AUTHORITY.md b/spec/CONTRACT_P13S27_REDUCTION_AUTHORITY.md new file mode 100644 index 0000000..46d301c --- /dev/null +++ b/spec/CONTRACT_P13S27_REDUCTION_AUTHORITY.md @@ -0,0 +1,505 @@ +# Contract — P13-S27: the reduction version gets an outside witness + +**Status:** DRAFT — **BLOCKED on P13-S28** (the format-epoch rung). Pins 1 and +3–10 are settled, internally consistent, and ratifiable as a plan. **Pin 2a is +an open question this contract states rather than answers**, and it is not +answerable inside this rung: the provenance carrier it needs is a format-epoch +design, which P13-S28 owns. + +**This is bounded analysis, not an abandoned implementation plan.** It may not be +dispatched. Pin 2a MUST NOT be treated as a sub-pin that drifts into this rung's +scope — see its own prohibition. + +**Rung type:** **capability + API change.** No wire bytes move and no schema +major or minor changes — `BundleError` has no discriminant and no encoder +(`bundle/src/error.rs`), so a new variant is a pure Rust API change. What does +change is `Bundle::open`'s **and `Bundle::create`'s** signatures, at 57 and 32 +call sites. + +**Does NOT by itself unblock P13-S16.** This rung installs the authority and +validates both read and write paths. S16 additionally requires the **legacy-base +disposition of pin 2a**, which is an open question this contract states rather +than answers. + +**Rulings already made (2026-07-31), not re-opened here:** + +- **Fork 1 → B, typed injected capability.** A required capability carrying the + current reduction version, with **no bundle-local default**: every caller + consciously supplies the semantics it implements. Broadened from + `OpenCapabilities` to **`BundleCapabilities`** in this revision, because it now + governs creation and commit as well as opening. +- **Fork 2 → outright rejection.** A dedicated error, not read-only and not an + integrity anomaly. + +--- + +## §0. What was verified before drafting + +Read out of the working tree at `381c498`. Every line number confirmed by +reading the line. + +### 0.1 The defect is a tautology, not an absence + +`reduction_version_for` (`bundle.rs:989`) sets a new superblock's version from +**the canonical base's own self-report**, `unwrap_or_default()` when there is no +base. `open` (`bundle.rs:396`–`:399`) then rejects a bundle whose base's version +**disagrees with that superblock's**. Both operands descend from the same +source, so for any conformingly-written document the comparison is a tautology. + +**It is not vacuous** — it catches a corrupt or tampered base whose version +disagrees with its superblock, and that behaviour is preserved by pin 6. What it +cannot catch is a *valid stale base*: one whose version was conformingly +propagated from an earlier implementation. That is exactly the case +`core_spec.tex:11614`–`:11617` exists to prevent. + +### 0.2 The project has this pattern twice, and both instances are the wrong shape here + +- `SUPPORTED_PROFILE_MAJOR` (`bundle.rs:87`) → `profile_is_understood` + (`:939`) → read-only + `IntegrityAnomaly::UnsupportedProfile` (`:389`). +- `IntegrityAnomaly::UnsupportedCanonicalChunkMajor { schema_major }` + (`error.rs:253`ff). + +Both are **bundle-crate constants**, and `open(store: S)` (`bundle.rs:301`) +takes no capability parameter. Fork 1's ruling deliberately departs from this +precedent, because a profile major and a schema major are *visible in the +container* while reduction semantics are not: a bundle-crate constant would be a +number the container crate cannot verify and a rung must remember to bump — +the hand-maintained-parallel-table shape that produced P13-S15 and the four +Push-4a literal sites. + +### 0.3 The layering forces the split, and makes `ids.rs:288` repairable + +`epiphany-bundle`'s only workspace dependency is `epiphany-determinism`. +It cannot read a constant in `epiphany-ops`. So `ids.rs:288`–`:289`'s claim that +*"the algorithm catalog itself lives in `epiphany-ops`"* is not merely false — +as written it is **unimplementable from where the check must run**. + +Fork 1's ruling makes it true: the authoritative number lives in +`epiphany-ops`, and the composing layer wraps it. **`epiphany-ops` must NOT gain +a dependency on `epiphany-bundle`** merely to use the wrapper type — hence a +plain `u32` in `epiphany-ops` and the `ReductionAlgorithmVersion` wrapper +constructed at the composition boundary. + +### 0.4 The surface, and a correction to this contract's own first draft + +**The writer path is production, not test-only. An earlier draft of this +section asserted the opposite and was wrong.** + +`epiphany-textproj` serializes a `TextDocument` into a bundle in production +code: `serialize_document` (`serialize.rs:119`) creates a bundle and commits a +manifest built by `build_manifest` (`serialize.rs:212`), which **copies +`base.reduction_algorithm_version` verbatim** into a fresh `SnapshotRef` +(`:219`–`:222`). `commit_versioned` then stamps the superblock from that same +carried value via `reduction_version_for` (`bundle.rs:798`). `project.rs:936` is +a **second** production write path of the same shape. + +**So production code mints a self-consistent stale document without ever calling +`open`.** An authority enforced only at read time would leave that entirely +open, which is the gap this revision closes (pin 3a, pin 4a). + +> **Method note, recorded because it is this rung's own subject matter.** The +> false claim came from `grep … canonical_base | head -14`. The `textproj` hits +> were below the cut. A universal negative — "no production code *anywhere*" — +> was asserted from deliberately truncated evidence. This is the second +> instrument failure in this rung: the first searched for +> `ReductionAlgorithmVersion(` constructor calls and so could not see a path +> that *propagates* a value without constructing one. Both are the defect S27 +> exists to fix, committed while scoping it: **an observation that cannot +> support the claim drawn from it.** + +**Reader surface — 57 `Bundle::open(` sites across 10 files:** + +| Crate | Sites | +|---|---| +| `epiphany-bundle` (`bundle.rs` 20, `fuzz.rs` 15) | 35 | +| `epiphany-testkit` (`bundle_harness.rs` 11, `roundtrip.rs` 4, `benches/bundle.rs` 2, `tests/bundle_reopen.rs` 1) | 18 | +| `epiphany-textproj` (`serialize.rs`, `project.rs`) | 2 | +| `epiphany-bundle/tests/` | 2 | + +**Writer surface — 32 `Bundle::create(` sites:** `bundle.rs` 11, +`testkit/roundtrip.rs` 6, `testkit/bundle_harness.rs` 6, `textproj/project.rs` 2, +`bundle/tests/crash_recovery.rs` 2, `textproj/serialize.rs` 1, +`testkit/tests/bundle_reopen.rs` 1, `testkit/benches/bundle.rs` 1, +`bundle/tests/manifest_selection.rs` 1, `bundle/fuzz.rs` 1. + +**`commit` has 57 sites** (including 2 in `epiphany-editor-core`) — pin 3's +design keeps every one of them unchanged. + +**Which capability each site supplies is NOT decided by crate dependency.** +`epiphany-testkit` and `epiphany-textproj` depend on `epiphany-ops`, but that +does not mean every call site there should pass the real constant. Format and +container fixtures that deliberately exercise arbitrary wire values — e.g. +`generators.rs:1628`/`:1651`'s `rng.range(0, 8)`, and the committed +text-projection vectors — MUST pass an **explicitly named synthetic capability +matching the fixture**, so the test keeps testing what it was written to test. +Only **production composition paths** wrap +`CURRENT_REDUCTION_ALGORITHM_VERSION`. Pin 3b makes this a naming rule rather +than a judgement call. + +--- + +## §1. Pins + +**Pin 1 — the authority is a plain `u32` in `epiphany-ops`, with its discipline +beside it.** + +```rust +pub const CURRENT_REDUCTION_ALGORITHM_VERSION: u32 = 0; +``` + +**`epiphany-ops` MUST NOT gain a dependency on `epiphany-bundle`.** The constant +is a bare `u32`; the `ReductionAlgorithmVersion` wrapper is constructed by the +composing layer (pin 3). + +Its doc comment carries the **bump discipline**, in the shape of +`PLAN_GMINOR_SCHEMA_MINOR.md`'s epoch rules: *any change to a canonical +reduction verdict or to canonical reduced state MUST bump this constant and +record the change here*, with a dated list of bumps. State plainly that **no +mechanism can detect a semantics change** — a golden test over reduction outputs +can prompt the question, never answer it — so the discipline is the guarantee. + +**Pin 2 — the initial value is 0, and this is a decision, not a placeholder.** +Bundles written to date carry `0` when they have no base, and bases self-report +whatever they were stamped with. Starting the constant at anything but `0` would +make every existing base-bearing document fail to open **without any semantics +having changed** — the rung would manufacture the breakage it exists to detect. +The first real bump is **P13-S16's**, to `1`. + +The doc comment must say this, so `0` is not later read as "unset." + +**Pin 2a — the legacy-base disposition is OPEN, and S16 is blocked on it.** +Baseline `0` does **not** preserve every current base-bearing document, and this +contract's first draft claimed it would. Verified: `serialize.rs:327`–`:331` +builds a canonical base stamped `ReductionAlgorithmVersion(1)` and +`serialize_and_reopen` (`:347`) round-trips it; `vectors.rs:353`/`:363` are +committed text-projection vectors carrying the same; `generators.rs:1628`/`:1651` +emit `rng.range(0, 8)`. Injecting the real authority `0` at every site would +reject part of the present corpus. + +Pin 3b handles the *fixtures* — they take named synthetic capabilities. It does +**not** handle the real problem: + +> **After S16 moves the authority to `1`, a pre-S27 base that happens to carry +> `1` is indistinguishable from a legitimately rebuilt S16 base. A raw `u32` +> carries no provenance, so no check can tell a conforming new base from a +> coincidental legacy one.** + +`0` may remain the baseline, but this rung MUST settle **one** of: + +- **(i) Normalize before S16** — a migration that rebuilds or re-stamps every + pre-authority nonzero base, so the space below the first real bump is empty by + construction. Requires locating every such artifact, including committed + vectors. +- **(ii) Choose a non-colliding epoch** — start the authority at a value no + pre-authority artifact can hold (the observed range is `0..8`, so e.g. `1000`), + making legacy values structurally distinguishable, and define what an + implementation does on encountering one. +- **(iii) Carry provenance** — a wider or tagged authority type. The largest + change; it makes `ReductionAlgorithmVersion`'s wire meaning richer and is + likely a schema question, which the other two are not. + +- **(iv) `FORMAT_MINOR` as a provenance carrier — PROPOSED AND REJECTED + 2026-07-31.** Recorded here so it is not re-proposed. The idea was to bump + `FORMAT_MINOR` (`header.rs:42`) so pre-S27 bundles are structurally + identifiable, since `decode` gates on **major** only (`:119`). Two independent + objections, either one fatal: + + 1. **The header is immutable and commit cannot touch it.** + `core_spec.tex:10799`–`:10800`: *"The header never changes after the file is + created."* `commit_versioned` (`bundle.rs:791`) computes and publishes only + a new **superblock**. So: open a legacy minor-1 bundle with no base; under + S27 commit a base that writer validation just accepted; the header is still + minor 1. If minor ≤ 1 bases are rejected, a base S27 itself validated + becomes permanently unusable. If they are accepted, S16 still cannot tell an + old arbitrary `1` from a real S16 `1`. **Both branches fail**, which is the + whole question 2a exists to settle. + 2. **A minor bump may not mean this.** `core_spec.tex:12258`–`:12262`: minor + changes are backward-compatible and *"MUST only append discriminants to the + append-safe"* set. Making a previously-valid canonical base newly rejectable + is a **semantic acceptance change**, not an append. And because current + readers ignore minor entirely, they would open minor-2 bundles and skip the + check — an enforcement boundary that binds only the readers that already + comply. + + The proposal also mis-stated its own corpus result: after the bump, + `serialize_document` writes a **minor-2** header, so its base-`1` fixture must + fail writer validation under real authority `0`. A synthetic capability + (pin 3b) can preserve that as a *format* test, but it cannot make the fixture + evidence about a legacy minor-1 bundle — the two are different documents. + +**The insight worth keeping from (iv), stated so a later rung can use it:** + +> Provenance MUST be carried by a container property that **old readers cannot +> silently accept** and that **cannot be inherited unchanged by a later commit**. +> `FORMAT_MINOR` fails both halves; any candidate carrier must be checked against +> both before it is proposed. + +That points at a real format-epoch design — most plausibly a **major**-version +boundary with explicit legacy-read and rebuild handling, or a +generation-scoped attestation paired with an incompatibility boundary for old +readers. Either is a **format-design rung of its own**, with an honest +rebuild/repack policy for legacy bases; neither is a clause this contract can +absorb. + +**Until a disposition is ratified AND tested, P13-S16 does not become +dispatchable merely because S27 lands.** This pin deliberately remains an open +question. It is not to be amended into a disposition without its own ratification +round. + +**Pin 3 — `BundleCapabilities`, required at both constructors, carried on the +`Bundle`.** + +```rust +pub struct BundleCapabilities { + pub current_reduction_version: ReductionAlgorithmVersion, +} +``` + +**It MUST NOT implement `Default`, and MUST NOT expose a bundle-local +constant.** Both would let a caller use a bundle without stating what semantics +it implements, which is the defect this rung exists to remove. + +`Bundle::open(store, caps)` **and `Bundle::create(store, …, caps)`** both take +it; the `Bundle` **stores it**. `commit` and `commit_versioned` then validate +against `self`'s copy and **their 57 call sites are unchanged** — the capability +is a property of the session, not of each call. Naming it +`BundleCapabilities` rather than `OpenCapabilities` follows from its now +governing creation and commit. + +The struct is a struct rather than a bare parameter so later capabilities append +without another signature break — say so in its doc, and do not add speculative +fields now. + +**Pin 3a — the writer is validated, not only the reader.** §0.4 shows production +code minting a stale document without ever calling `open`. Therefore +`commit`/`commit_versioned` MUST reject a manifest whose **newly emitted or +replaced** canonical base carries a version differing from +`self.caps.current_reduction_version`, with the same error as pin 4. + +"Newly emitted or replaced" is the operative scope: a commit that does not touch +`canonical_base` MUST NOT be refused merely because an inherited base is stale — +that document could not have been opened in the first place, and refusing here +would make an unrelated commit the site of the diagnosis. + +**Pin 3b — synthetic capabilities are named, so the choice is not a judgement +call.** Provide a clearly-named constructor for fixture use, e.g. +`BundleCapabilities::synthetic_for_fixture(v: u32)`, whose doc states it is for +format and container fixtures deliberately exercising arbitrary wire values and +**must never appear in a production composition path**. Production paths wrap +`epiphany_ops::CURRENT_REDUCTION_ALGORITHM_VERSION`. + +Every call site converted by this rung uses one or the other **explicitly**; +none may take a value that merely happens to be in scope. + +**Pin 4 — the mismatch is a hard error.** + +```rust +BundleError::CanonicalBaseRequiresRebuild { + base: ReductionAlgorithmVersion, + current: ReductionAlgorithmVersion, +} +``` + +A `BundleError`, **not** an `IntegrityAnomaly`, and **not** a read-only +degradation. Read-only is wrong on the merits: a stale base is not a +restricted-but-correct view, it is **the wrong materialization**, and exposing it +read-only would serve incorrect canonical state confidently. + +Its doc must record why `open` cannot recover: **`open` cannot rebuild**, and +drop-and-replay is unsound once pruning exists (`core_spec.tex:12207`, `:14701` +— pruning is specified and **not implemented**; no `prune` appears anywhere in +`epiphany-bundle`). A higher-level rebuild path may be authorized later **only +where full pre-base history is demonstrably available**; this rung authorizes +none. + +**Pin 5 — the check runs where the current check runs, and only for a base.** +In `open`, at `bundle.rs:396`. A bundle with **no canonical base is openable +regardless** of `caps.current_reduction_version` — there is no reduced state to +be stale. Order the two checks so the corrupt case (pin 6) is distinguishable. + +**Pin 6 — the existing corrupt-disagreement failure is preserved, distinctly.** +The current base-vs-superblock disagreement check must keep returning its +existing malformed-bundle `DecodeError`. It is **not** replaced by pin 4 and +**not** merged with it: they detect different things — tampering versus valid +staleness — and collapsing them would lose the distinction §0.1 rests on. + +**Pin 7 — `reduction_version_for` does not change, and the reasoning is +recorded.** +It keeps sourcing from the base's self-report — but its justification now rests +on **both** validation points, and the first draft's single-sided reasoning was +insufficient: + +- **read-time (pin 5):** an opened bundle has proved `base == current`; +- **write-time (pin 3a):** a newly emitted or replaced base has proved the same. + +Only together do these make propagation equal to propagating the current +version. A doc line MUST state both, naming pin 5 and pin 3a — otherwise a later +reader sees an unguarded self-report and either "fixes" it or trusts it in a +context where neither check has run. §0.4's correction is exactly what happens +when that reasoning is done one-sided. + +**Pin 8 — `ids.rs:288`–`:289` becomes true.** +Repair the catalog claim to name the real location and the real mechanism: the +authoritative version lives in `epiphany-ops` as a plain `u32`; the wrapper type +is constructed at the composition boundary; `epiphany-bundle` deliberately does +not depend on `epiphany-ops`. This is the rung that earns the sentence. + +**Pin 9 — normative specification text.** +`core_spec.tex` Chapter 8 §"Canonical Document Identity" currently states the +rebuild requirement (`:11614`) with no error surface. Add the rejection +behaviour normatively, and a Revision History row. There is **no** existing +"requires rebuild" error language in either document — verified — so this is new +prose, not an amendment. + +**Pin 10 — the ledger.** +`spec/PASS13_CANDIDATES.md`: P13-S27 → RESOLVED, recording both rulings, the +baseline-0 decision, the writer-path correction of §0.4, and **whichever +legacy-base disposition pin 2a settles on**. + +**P13-S16's row does NOT become dispatchable on this rung alone.** It moves from +"blocked on P13-S27" to "blocked on the pin-2a legacy disposition" unless 2a is +ratified and tested within this rung, in which case it opens and its row records +that its first act is bumping the authority past the baseline. + +--- + +## §2. Touch table + +| # | File | Change | +|---|---|---| +| 1 | `crates/epiphany-ops/src/lib.rs` (or a new `reduction.rs`) | pins 1, 2 | +| 2 | `crates/epiphany-bundle/src/bundle.rs` | pins 3, 3a, 3b, 5, 6, 7 + 35 in-crate `open` sites + 11 in-crate `create` sites | +| 3 | `crates/epiphany-bundle/src/error.rs` | pin 4 | +| 4 | `crates/epiphany-bundle/src/ids.rs` | pin 8 | +| 5 | `crates/epiphany-bundle/src/fuzz.rs` | call sites | +| 6 | `crates/epiphany-bundle/tests/{crash_recovery,manifest_selection}.rs` | call sites | +| 7 | `crates/epiphany-testkit/src/{bundle_harness,roundtrip,generators}.rs` | call sites, real authority | +| 8 | `crates/epiphany-testkit/tests/bundle_reopen.rs`, `benches/bundle.rs` | call sites | +| 9 | `crates/epiphany-textproj/src/{serialize,project}.rs` | call sites, real authority | +| 10 | `spec/core_spec.tex` (+ `.pdf`) | pin 9 | +| 11 | `spec/PASS13_CANDIDATES.md` | pin 10 | + +--- + +## §3. Required tests (pin 4's ruling names all four) + +Named, permanent, in `epiphany-bundle`: + +1. **`open_succeeds_when_base_and_current_reduction_versions_match`** +2. **`open_rejects_a_valid_stale_canonical_base`** — base and superblock agree + with each other, `caps.current` differs. Must return + `CanonicalBaseRequiresRebuild { base, current }` with **both fields + asserted**, not merely the variant. +3. **`a_corrupt_base_superblock_disagreement_still_fails_as_malformed`** — the + pin-6 path, asserted to be the *existing* malformed error and **not** + `CanonicalBaseRequiresRebuild`. +4. **`a_bundle_with_no_canonical_base_opens_at_any_reduction_version`** — run at + two different `caps` values. +5. **`committing_a_stale_canonical_base_fails_and_leaves_the_prior_generation_reopenable`** + — the pin-3a writer test, and the one this contract's first draft omitted + entirely. Create a bundle, commit a good generation, then attempt a commit + whose newly emitted canonical base carries a version differing from `caps`. + Assert **both**: the commit fails with `CanonicalBaseRequiresRebuild`, **and** + the bundle reopens at the prior active generation with its earlier content + intact. A writer check that corrupts the document while refusing is worse than + no check. + +Tests 2 and 3 must be **paired in review**: each asserts the other's error is +*not* produced. A test that only checks its own variant cannot show the two +paths are distinguishable, which is the whole point of pin 6. + +--- + +## §4. Mutation plan + +Applied, **run**, output recorded verbatim, restored **by hand-editing back**. + +**M1 — the new check fires.** Delete pin 5's comparison; test 2 must fail. + +**M2 — the paths are distinguishable.** Make pin 4's error subsume pin 6's +(return `CanonicalBaseRequiresRebuild` for the corrupt case too); test 3 must +fail. Signs that §0.1's distinction is real in the code, not only in prose. + +**M3 — the no-base exemption is deliberate.** Make the check run when +`canonical_base` is `None`; test 4 must fail. + +**M4 — the capability is genuinely required.** Add `impl Default for +BundleCapabilities` and a call site using it. **This must be observed to +compile**, then reverted — it demonstrates what pin 3 forbids and why the +prohibition needs to be a review rule, since no test can catch a `Default` that +callers then use. Report it as a *prohibition recorded*, not a guard. + +**M5 — the authority is the one consulted, and by a production path.** Change +`CURRENT_REDUCTION_ALGORITHM_VERSION` to a different value. The failing test +MUST be one exercising a **production composition path** — `textproj`'s +`serialize_document` / `project` round trip — **not** a fixture using +`synthetic_for_fixture`, which by design would not move. Report which test +failed; if only fixture tests fail, pin 3b has been applied backwards and that +is a finding. + +**M6 — the writer check fires.** Remove pin 3a's commit-side validation; test 5 +must fail. Then narrow it to refuse *any* stale inherited base rather than only a +newly emitted or replaced one, and confirm an unrelated commit on an +already-open bundle starts failing — signing that pin 3a's scope is deliberate. + +--- + +## §5. Gate + +1. `cargo test --workspace` — full pass; report the new total and the delta with + its cause (four tests added). +2. `cargo clippy --workspace --all-targets -- -D warnings` → clean. +3. `cargo fmt -p epiphany-ops -p epiphany-bundle -p epiphany-testkit -p + epiphany-textproj --check` → clean. **`cargo fmt --all` is forbidden.** +4. `git diff --cached --check` clean after staging; staged list exactly §2. +5. **`epiphany-ops` has NOT gained an `epiphany-bundle` dependency** — check + `crates/epiphany-ops/Cargo.toml` directly. +6. **`BundleCapabilities` implements no `Default`**, by exact query with an + expected count of **zero**: + `grep -rnE "impl +Default +for +BundleCapabilities|derive\\([^)]*\\bDefault\\b[^)]*\\)[[:space:]]*(pub )?struct BundleCapabilities" crates/epiphany-bundle/src/` + → **0 matches.** Run it against production source only; report the count, not + a verdict. +6a. **No production composition path uses the fixture constructor:** + `grep -rn "synthetic_for_fixture" crates/epiphany-textproj/src/` shows matches + **only** inside `#[cfg(test)]` modules. Report each match with its enclosing + item. +7. `spec/vectors/decode_vectors.txt` unmodified; no schema major/minor moved. + +--- + +## §6. Staging and boundary + +Stage only §2's files, by explicit path. **Never `git add -A`.** + +**A concurrent session commits here.** Re-check `HEAD` before staging and before +commit. **Never** `git reset`, `git restore --staged`, `git checkout`, `git +stash`. + +**Out of bounds — MUST NOT be read, written, or staged:** the entire `spikes/` +tree, `spec/PLAN_EDITOR_APP.md`, `spec/CONTRACT_EDITOR_*.md`, +`spec/ANALYSIS_GENESIS_PERSISTENCE.md`, `spec/ANALYSIS_TEXT_RUN_PRIMITIVES.md`, +`spec/DRAFT_T4_FIXTURE_RECIPE.md`, `crates/epiphany-render-svg/**`, +`crates/epiphany-glyphs/**`, `crates/epiphany-editor-gui/**`, +`crates/epiphany-testkit/benches/editor_pipeline.rs`, the root `Cargo.toml`, +`.claude/worktrees/`. + +**Do not implement any part of P13-S16.** This rung unblocks it; it does not +begin it. No `create_staff`, `create_staff_group`, or invariant change. + +**Do not bump `CURRENT_REDUCTION_ALGORITHM_VERSION` past 0** — pin 2. The bump +to 1 belongs to S16. + +**The executing agent MUST NOT commit.** Leave the work staged. + +--- + +## §7. Report requirements + +1. The five mutations, each with verbatim output (M4 as a recorded prohibition). +2. The seven gate results, each with its command. +3. The staged file list and the test-count delta with its cause. +4. The four required tests by name, each passing, with tests 2 and 3 shown to + produce *different* errors. +5. A count of call sites updated per crate, against §0.4's table — any + discrepancy is a finding. +6. Anything contradicting this contract.