Contract for P13-S27, tracked as DRAFT and blocked

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
This commit is contained in:
Levi Neuwirth 2026-07-31 21:03:29 -04:00
parent 381c49849e
commit 79a2507d47
1 changed files with 505 additions and 0 deletions

View File

@ -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
310 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.