33 KiB
Contract — P13-S27: the reduction version gets an outside witness
Status: DRAFT — UNBLOCKED and dispatchable as of 2026-08-07; AMENDED
2026-08-07 (pin 10), before dispatch and before ratification. (Was: BLOCKED on
the format-epoch rung, spec/CONTRACT_FORMAT_EPOCH_MAJOR1.md, which at the time
was ratified and in implementation but had not yet landed.) That rung landed at
bc06706, with its pin-3b follow-up at be244df. Pins 1 and 3–10 are settled,
internally consistent, and ratifiable as a plan.
The pin 10 amendment, recorded here so it is not read as the original: pin 10 made P13-S16's opening conditional on pin 2a being "ratified and tested within this rung" — a condition that the only route pin 2a permitted, resolution from outside, could never satisfy. Ratification is now sourced correctly. The tested half is not waived: it is carried by this rung's inherited obligations rather than dropped. See pin 10.
Pin 2a is RESOLVED as of 2026-08-07 — from outside this rung, by that contract's pin 8, exactly as its prohibition required. Legacy bases are refused by container epoch, never by version arithmetic; see the resolution block under pin 2a. This contract additionally inherits three obligations from that rung (the two interim refusals it must convert to validation, M8's deferred laundering demonstration, and pin 3c's two suspended conformance assertions) — recorded in the same place.
It became dispatchable when the format rung landed, not before — and that has now happened. This is no longer bounded analysis. Pin 2a's original prohibition stands for the record: it was never amended into a disposition from inside this contract, and the resolution above came from outside, which is exactly what the prohibition required.
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.
Now DOES unblock P13-S16, once it lands. This rung installs the authority and validates both read and write paths; S16's remaining precondition was pin 2a's legacy-base disposition, and that is resolved. The chain is therefore format-epoch rung → P13-S27 → P13-S16, with no open question left in it.
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
OpenCapabilitiestoBundleCapabilitiesin 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:253ff).
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. Thetextprojhits 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 forReductionAlgorithmVersion(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.
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 carry1is indistinguishable from a legitimately rebuilt S16 base. A rawu32carries 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_MINORas a provenance carrier — PROPOSED AND REJECTED 2026-07-31. Recorded here so it is not re-proposed. The idea was to bumpFORMAT_MINOR(header.rs:42) so pre-S27 bundles are structurally identifiable, sincedecodegates on major only (:119). Two independent objections, either one fatal:- 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 arbitrary1from a real S161. Both branches fail, which is the whole question 2a exists to settle. - 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_documentwrites a minor-2 header, so its base-1fixture must fail writer validation under real authority0. 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 header is immutable and commit cannot touch it.
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_MINORfails 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 2a — RESOLVED 2026-08-07 by the format-epoch rung.
The ratification round this pin demanded is the one the format-epoch contract
had: spec/CONTRACT_FORMAT_EPOCH_MAJOR1.md, ratified after four adversarial
review rounds, whose pin 8 exists to resolve this pin from outside it. The
open analysis above is retained verbatim as the reasoning that produced the
answer, not superseded prose.
The disposition is none of (i), (ii) or (iii): it is the container epoch.
Reduction-version authority is meaningful only in major-1 containers. Legacy bases are refused by container epoch, never by version arithmetic.
That is why the collision this pin identified never has to be adjudicated. A
pre-S27 base carrying 1 and a legitimately rebuilt S16 base carrying 1 are
indeed indistinguishable as numbers — and they never meet, because the
pre-S27 base can only exist in a major-0 container, which is refused at the
epoch boundary before any version is compared. The u32 never has to carry
provenance, because the container already does.
Each rejected option, and why the epoch beats it: (i) normalizing the corpus
would have to find every artifact, and a missed one is silently wrong forever;
(ii) a non-colliding epoch value is a convention a hand-authored document can
simply declare — parse.rs:591 accepts an unbounded u32; (iii) widening
the type makes the wire meaning richer and buys nothing the container property
does not already give. All three try to make a number carry provenance. The
epoch makes the file carry it.
S27's own baseline stays 0 (pin 2 is unchanged). What changes is that the
question "what about a base older than the authority?" is no longer S27's to
answer.
Inherited from the format-epoch rung — obligations S27 must discharge
The format rung lands before this one and closes two things temporarily, naming S27 as what reopens them. Both are owed work here, not optional:
-
The interim refusals become real validation. The format rung's pin 3a temporarily refuses both major-1 base boundaries — opening a major-1 container that already carries a base, and committing a base into one — through a third, temporary error (
ReductionAuthorityUnavailable) that is distinct from its two legacy/repack errors. S27 replaces both branches with capability validation. Replacing only one leaves a hole exactly where the format rung's own review found one. -
The deferred laundering demonstration (format-rung M8). That rung could not demonstrate the text-import laundering path end to end, because pin 3a refuses every major-1 base commit categorically, so the "a base-bearing text document really does serialize into a major-1 container" observation is unreachable there. Under S27 a base commit succeeds or fails on its version, so the demonstration becomes performable and is owed: with pin 3b's text refusal removed, show that a base-bearing document whose raw version happens to match the current authority serializes into a major-1 container indistinguishable from a validated one. That is the false provenance the text refusal exists to prevent, and it has never been observed — only reasoned about.
-
Two conformance assertions come back (format-rung pin 3c). Criterion 4's bookkeeping-projection counterpart,
assert_reduction_serialization_stable(testkit/src/roundtrip.rs:241), keeps its serialize → load → decode → reserialize cycle through the interval but loses exactly two canonical-base-specific assertions:verify_canonical_chunks's base branch (bundle.rs:613–:621, including thebase.hash != base.root.hashcross-check), and the reopened manifest actually carrying the base (roundtrip.rs:293–:297). S27 restores both, since a base-bearing container becomes constructible again the moment validation replaces refusal. The harness carries a marker naming this contract at the suspension point.
Pin 3 — BundleCapabilities, required at both constructors, carried on the
Bundle.
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.
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. AMENDED 2026-08-07, before dispatch, on a finding from
the machine-move review.
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 — which, as of 2026-08-07, is the
container epoch, settled from outside this rung.
The original clause, retained for the record: "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."
Why it is amended. Pin 2a was ratified — through the format-epoch contract's four adversarial rounds — but outside this rung, which is exactly what pin 2a's own prohibition demanded: it forbade being amended into a disposition from inside this contract. So the clause's literal condition ("within this rung") could never be satisfied by the only route pin 2a permitted, and read literally pin 10 would have the ledger record S16 as still blocked on a disposition that is settled. The condition was written when an inside resolution still looked possible.
As amended: P13-S16's row becomes dispatchable when this rung lands. Pin 2a's disposition is settled, so the row moves from "blocked on P13-S27" straight to open, and records that its first act is bumping the authority past the baseline.
The "tested" half is not waived by this amendment. Ratification came from outside; testing did not, and cannot — no other rung exercises this authority. It is discharged by this rung's own inherited obligations, in particular converting both interim refusals to real capability validation. The ledger row may not record S16 as open until those land with this rung; ratification of 2a alone does not open it.
§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:
open_succeeds_when_base_and_current_reduction_versions_matchopen_rejects_a_valid_stale_canonical_base— base and superblock agree with each other,caps.currentdiffers. Must returnCanonicalBaseRequiresRebuild { base, current }with both fields asserted, not merely the variant.a_corrupt_base_superblock_disagreement_still_fails_as_malformed— the pin-6 path, asserted to be the existing malformed error and notCanonicalBaseRequiresRebuild.a_bundle_with_no_canonical_base_opens_at_any_reduction_version— run at two differentcapsvalues.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 fromcaps. Assert both: the commit fails withCanonicalBaseRequiresRebuild, 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.
Added 2026-08-07 with pin 2a's resolution — the inherited obligations, stated as tests so they cannot be discharged by prose:
a_major_1_bundle_carrying_a_base_opens_when_the_authority_matches— the read-side half of the format rung's pin 3a, converted from temporary refusal to real validation. Its sibling is test 2, which is the same path when the authority disagrees. Both branches must exist; the format rung's own review found a draft that closed only one.- The two restored conformance assertions (format-rung pin 3c), in
assert_reduction_serialization_stable(testkit/src/roundtrip.rs:241):verify_canonical_chunkscovering the base again, and the reopened manifest carrying it. Restoring them means deleting the suspension marker that names this contract — if the marker is still in the tree when this rung reports, the restoration did not happen.
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.
Tests 6 and 2 stand in the same relation to each other.
§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
cargo test --workspace— full pass; report the new total and the delta with its cause (four tests added).cargo clippy --workspace --all-targets -- -D warnings→ clean.cargo fmt -p epiphany-ops -p epiphany-bundle -p epiphany-testkit -p epiphany-textproj --check→ clean.cargo fmt --allis forbidden.git diff --cached --checkclean after staging; staged list exactly §2.epiphany-opshas NOT gained anepiphany-bundledependency — checkcrates/epiphany-ops/Cargo.tomldirectly.BundleCapabilitiesimplements noDefault, 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.spec/vectors/decode_vectors.txtunmodified; 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
- The five mutations, each with verbatim output (M4 as a recorded prohibition).
- The seven gate results, each with its command.
- The staged file list and the test-count delta with its cause.
- The four required tests by name, each passing, with tests 2 and 3 shown to produce different errors.
- A count of call sites updated per crate, against §0.4's table — any discrepancy is a finding.
- Anything contradicting this contract.