# Pass 13 — candidate ledger

Ambiguities and cross-cut inconsistencies found since Pass 12 closed, filed
per the house rule (a batch pass opens at ≥3 candidates; this file opened
when P13-D1/D2 joined P13-K1). Each entry names the owning DECISIONS record;
this file is the index, not the analysis.

## Batch 1 — CLOSED (2026-07-08)

All four candidates are resolved (worked down in order): P13-D3 and P13-K1 by
the user's ratified calls ("fix the mint only" / "reject the introduction"),
P13-D1 and P13-D2 as correctness fixes with convergence-locked /
execute-then-fix regressions.

| Id | One-line statement | Filed in | Status |
|---|---|---|---|
| P13-K1 | The K3 verdict for a system pitch introduced by a ModifyEvent replacement value differs across a snapshot cut (in-session `TargetMissing` vs post-snapshot `SystemDerivedContentImmutable`); really "may ModifyEvent introduce never-minted pitch ids" | `crates/epiphany-ops/DECISIONS.md` (Pass-12 G-pass code tranche) | **resolved** (Pass 13: reject the introduction — modify_event refuses a never-minted system-derived pitch, verdict now snapshot-cut-invariant; user "reject the introduction") |
| P13-D1 | Undo-driven event tombstones run graph-side re-anchor/cascade but never ledger-side `reanchor_for_tombstone`: structures leave the graph while staying `Live`, no `RepairRecord` — Ch6's same-step recording MUST is unmet for undo-driven tombstones (pre-existing class: slurs/spanners; repeats now too) | `crates/epiphany-ops/DECISIONS.md` (Schema major 2, Phase D) | **resolved** (Pass 13: `tombstone_undo_targets` runs the ledger re-anchor per event target; liveness guard; convergence-locked) |
| P13-D2 | Cue-cascade recursion re-anchors against the triggering event before its tombstone lands in `objects`: a structure anchored on {X, cue-of-X} can record `Reanchored{to: X}` then `CascadeDeleted` in one effect (contradictory repair trail; plausible by code trace, unexecuted) | `crates/epiphany-ops/DECISIONS.md` (Schema major 2, Phase D) | **resolved** (Pass 13: `delete_event` tombstones before the graph delete, matching `cascade_cue`/undo; repro executed then fixed) |
| P13-D3 | `CreateCrossCutting` validates only event endpoints (`CrossCuttingValue::endpoints()`), so a SPANNER anchored to a missing region/measure mints dangling past `anchor_target_exists`; and non-event referent tombstones (`DeleteRegion` under a region-anchored spanner/repeat) re-anchor nothing — "every referenced endpoint is live" is events-only as implemented | `crates/epiphany-ops/DECISIONS.md` (Phase D follow-up) | **resolved** (Pass 13: mint fixed via `anchor_object_refs`; non-event referent re-anchoring ratified events-only, user "fix the mint only") |

## Batch 2 — CLOSED (2026-07-09)

Three candidates accumulated while the Standard-tier solver track closed and the
notation-quality pass (stems, slurs) landed. All three were **parked** as they
were found, each in `crates/epiphany-layout-ir/DECISIONS.md`, and reaching three
reopened the pass per the house rule. None was a live incorrectness in shipped
output; each was a place where the code, the spec, and the data disagreed about
what is true.

**All three resolved**, worked down in order. Two grew when examined: P13-I1 was
filed as two elided fields and was three, the third (`diagnostics`) named nowhere
in core_spec; P13-I2's fix had to reach `editor-core` as well as the projection,
or a click on a bass staff would have resolved its pitch as treble. Zero golden
churn across all three. No open Pass-13 candidates remain; a future ≥3-candidate
batch reopens the pass.

| Id | One-line statement | Filed in | Status |
|---|---|---|---|
| P13-I1 | Chapter 7's `ConstrainedLayoutIR` listing elides **three** fields the code carries: `break_origins: Vec<BreakOrigin>` (named by `req:layoutir:break-origin-attribution`, its own shape unlisted), `catalog: GlyphCatalogIdentity` (its type specified, the field unlisted), and `diagnostics: Vec<LayoutDiagnostic>` — which appears **nowhere** in core_spec, though it is how the projection's honesty rule manifests: an unspellable pitch or an unbundled glyph is placed as a fallback *and recorded*, never silently guessed | `crates/epiphany-layout-ir/DECISIONS.md` ("the ConstrainedLayoutIR listing is still abridged") | **resolved** (Pass 13: listing gains all three fields; `BreakOrigin` and `LayoutDiagnostic` shapes added; new `req:layoutir:coverage-diagnostics` ratifies as-implemented that an unengravable object is recorded AND still placed — never guessed, never dropped) |
| P13-I2 | `Staff::default_clef` is never consulted: `to_constrained` takes the active clef from the staff instance's `clef_sequence` and falls back to `Clef::default()` (treble), so a bass-clef staff that declares its clef only on the `Staff` engraves as treble. The field is decorative in the projection — is it the fallback, or should it not exist? | `crates/epiphany-layout-ir/DECISIONS.md` ("`Staff::default_clef` is never consulted") | **resolved** (Pass 13: it IS the fallback. `StaffContent` carries it, `active_clef_or` resolves against it, and `editor-core`'s hit-test reads the same function — else a click on a bass staff would resolve its pitch as treble. Removal was rejected: the field is named for its purpose, is encoded on the wire, and dropping it is schema-major) |
| P13-I3 | `BRAVURA_METRICS`' `NOTEHEAD_ANCHORS` are hand-written, unconsumed, and doubly suspect: they name `stemUpNW`/`stemDownSE` — the corners a normal notehead's stems do *not* attach to, and a pair Bravura's `noteheadBlack` does not define — and their x of `1180` reads like 1.18 staff spaces written in thousandths rather than the table's `1/1024` units (1.18 sp = 1208). They enter only `metrics_hash`, so any correction moves the `GlyphCatalogIdentity` every conformance claim declares. The font is not vendored, so the values cannot be verified in-tree | `crates/epiphany-layout-ir/DECISIONS.md` ("the notehead stem anchors are unusable as written") | **resolved** (Pass 13: deleted. Unverifiable in-tree, unconsumed, and hand-derived where every neighbouring number is machine-extracted. `extract_bravura_outlines.py --anchors` now emits them from the pinned `bravura_metadata.json`, so the table regains them generated rather than remembered; the metadata's SHA-256 is deliberately unpinned and the script refuses until an operator with the font pins it. `GlyphCatalogIdentity` moves once, now, while no conformance claim declares the old one; user "delete them + teach the extractor") |

## Batch 3 — OPEN (2026-07-09)

Three candidates, so the pass reopens per the house rule. P13-S3 arrived as a
**live incorrectness** — a Push-4a follow-up audit found that the spelling-set
chain introduced to make `TransposeInterval` undoable had only one writer, so
undo erased an ordinary `RespellPitch` on either side of it. It is resolved.
S1, S2, and S3 are resolved. S5 and S6 were filed 2026-07-22 by verifying the
two Push-4a audit claims that `epiphany-core/DECISIONS.md` had carried as
**unverified** through two passes; both were real, and both were Chapter 4
defects standing in front of Push 4b. Both are now resolved, closing the spec
half of Push 4b.

**Filed 2026-07-22, second round.** S7 is a **bookkeeping repair**: the id was
minted in `spec/PLAN_PUSH4B_TUNING.md` ("new; file it"), ratified as that plan's
Ruling C, and implemented — but never entered this ledger, so an id existed
outside the index that is supposed to be the index. S8 and S9 are new. S9 is
the third instance of one pattern in a single day and, notably, **S4 is itself
an instance of it**, which is what moved it from incident to candidate.

**Filed 2026-07-23, third round.** S10–S12 are three underdetermined Chapter 4
types standing in front of Push 4b tranche 3 (the schema-major-3 wire freeze) —
the same shape as S5/S7 before tranche 1. Scoping the accidental subtree that
`accidental_extensions` drags onto the wire surfaced a raw `f64` that the byte
layer cannot encode, a leaf type referenced but never defined, and a version type
whose obvious representation orders SMuFL's real release history backwards. All
three are ratified; each freezes forever once tranche 3b lands, so they were
resolved before dispatch, not after.

S4, S8, and S9 remain open; S10–S12 are resolved on ratification; S13 is
**resolved by the genesis tranche**, not by this pass — it is one field of an
eight-field gap, and a tuning-specific fix would have been the wrong shape
(`spec/ANALYSIS_GENESIS_PERSISTENCE.md`). The pass stays open. (S13 was filed
2026-07-24, after S10–S12's wire freeze landed: it is the gap that freeze made
visible — a value with a wire form and no canonical carrier to reach it.)

**Filed 2026-07-31, fourth round — and two older rows corrected against the
tree rather than against memory.** S25 and S26 are new. Both came out of
reconnaissance run *before* drafting, not out of implementation, which is the
change in method worth recording: three read-only sweeps over S22, S16 and S8
were dispatched to establish current facts, and each returned something the
ledger had wrong. **S8's row carried a false mutation claim** — it said flipping
`is_none_or` to `is_some_and` killed no test; three tests kill it, executed and
confirmed. The original was reached by reading only `invariants.rs`'s own
tests, which assert `fires(...)` and survive the flip; the kill sites are in
another crate. **S16's row had drifted in every code citation**, some by
hundreds of lines, and never named the test that makes its fix expensive.
Neither row's *conclusion* changed. **S26 is a P13-S9 instance** and the
sharpest one yet: a doc comment that claims a specification repair which never
landed, where the claiming side is grep-guarded and the stale side is not.
The pattern S9 names — *fixing the loud one without reading the target converts
a caught defect into an uncaught one* — is why S26 forbids repairing its own
evidence in isolation.

| Id | One-line statement | Filed in | Status |
|---|---|---|---|
| P13-S1 | **169 of core_spec's 207 `requirement` blocks carry no `\label`**, so no conformance claim can cite them. Chapter 4 (`Tuning Systems and Pitch Spaces`) is 9/9 unlabeled and Chapter 11 (`Determinism Contract`) is 15/15, but the gap is universal, not local: `Semantic Operations` 24/27, `The Score Graph` 22/28, `Pitch` 10/13. The requirements *are* normative and *are* implemented; they simply cannot be named. Every `req:*` label the repo cites was added ad hoc by the pass that needed it | this file | **resolved** (all 207 core_spec requirements labelled, suite 277/277; and the pass found that labelling alone was insufficient — no document *numbered* its requirements, so a `\label` bound to the enclosing section and 61 of 207 shared a rendered number, one shared by six. All six documents now carry a real counter. Locked by `requirement_labels.rs`) |
| P13-S2 | `cmn-24` was declared in the built-in pitch-space table as CMN with quarter-tone accidentals while `PitchSpacePosition::Cmn.alteration`, transposition, and `CmnChromatic` were all specified in fixed semitones. The specification now denominates both CMN alteration sites and interval chromatic steps in the enclosing space's chromatic layer, with `cmn-24`'s map fixed explicitly. Until Push 4b provides structural registry resolution, the core fails closed outside provable built-in `cmn-12` instead of applying guessed 12-chromatic arithmetic | `spec/PLAN_P13S2_CMN24.md`; `crates/epiphany-core/DECISIONS.md` | **resolved** (schema and wire layout unchanged; `PitchSpaceUnavailable` maps to existing `PitchSpaceMismatch` 6; Operation Catalog 0.9.0; registry-backed generalization remains a Push 4b implementation blocker) |
| P13-S3 | The `engraved_spelling_chain` introduced with `TransposeInterval`'s undo had a **single writer**. `RespellPitch` mutates the same graph attachments but recorded only on `respell_chain`, so (a) undoing a transposed transaction after a prior respell restored the pitch and **erased the respell**, and (b) a respell landing canonically *after* the transaction was invisible to the chain, so a `StrictInverse` undo reported `Applied` and **wiped the newer authoring** instead of refusing as superseded. A `BestEffort` undo could also restore the pre-transpose pitch while leaving a spelling authored against the transposed one | `crates/epiphany-ops/DECISIONS.md` (P13-S3) | **resolved** (both operations now record on the shared key; pitch value + spelling set undo as one unit; new `req:opcat:spelling-set-chain`. The chain stays *physically* separate from `respell_chain`, which is `RespellPitch`'s LWW conflict state — folding transposes in would make a concurrent respell conflict with a transpose and move the canonical bytes of every existing history) |
| P13-S4 | **No labelled requirement governs vertical-band *heights*.** Pass 12 twice recorded a behavioural fix to the inter-staff solve — realizing an `InterStaffGap` band's declared `preferred_height` rather than a constructor default — and both times cited `req:layoutir:vertical-bands`, which never existed. The two real band requirements (`req:layoutir:primitive-band-ownership`, `req:layoutir:resolved-band-ownership`) govern *ownership*: which band a primitive belongs to, and that a resolved primitive retains it. Nothing states what a band's height means or that the solver must realize it, so the shipped behaviour is unspecified and the log invented a name for the gap | this file | **open** (found in P13-S1 review: an agent 'corrected' the dangling citations to the two ownership requirements, which replaced a visibly broken pointer with a silently wrong one) |
| P13-S5 | **The JI prime basis is specified at two lengths.** `req:pitch:ji-vector-basis` states that the built-in JI pitch spaces "order primes ascending **starting with 2**" and that `components.len()` MUST equal the basis size; the built-in pitch-space table calls `ji-5limit` "Two-dimensional (prime axes 3, 5)", `ji-7limit` three-dimensional and `ji-11limit` four-dimensional — each **exactly one short**, consistently, because the table is octave-reduced and the requirement is full-register. `req:tuning:builtin-tuning-catalog` makes the table normative, so a 5-limit vector is required to be both length 2 and length 3. The requirement's octave-reduction clause does not reconcile them: it *normalizes* the first component to a canonical range, it does not remove it | this file (verified 2026-07-22 against `core_spec.tex:1005-1008` and `:3496-3501`) | **resolved** (Push 4b Wave 1a, 5dcaa58: ratified **full register**. The three table rows gain prime 2 and become three-, four- and five-dimensional; `req:pitch:ji-vector-basis` is untouched, being the side that was already correct. A `JiVector` is an absolute *position*: without the prime-2 exponent, `ji-5limit` cannot distinguish C4 from C5) |
| P13-S6 | **No built-in tuning system's resolution is pinned to a versioned definition, and 14 of 20 have no definition at all.** `req:tuning:builtin-tuning-catalog` makes all 20 identifiers MUST-resolve with normative semantics, but only the six `tet-*` entries are actually specified — by `TuningResolution::EqualTemperament`'s structural rule. The other 14 are names: 3 meantone variants, `werckmeister-iii`/`iv`, `vallotti`, `kirnberger-ii`/`iii`, `young-ii`, `pythagorean` (the 3:2 ratio is named but not the fifth-chain construction or wolf placement), 3 `ji-static-5limit-*`, and `ji-adaptive-5limit`. `TuningResolution::Function` delegates them to a `TuningFunctionId`, which Chapter 10 lists as an **extension point**; no built-in is mapped to a function id and no function id is pinned. Against `req:tuning:tuning-resolution-determinism` — determinism MUST hold *across platforms* — two conforming implementations may each choose a different published Werckmeister III and both pass | `spec/DRAFT_P13S6_TEMPERAMENTS.md`; `spec/CONTRACT_P13S6_TEMPERAMENTS.md`; `spec/CONTRACT_P13S6_PROMOTION.md` | **resolved** (5e465a1: all 20 pinned by construction, not by cents table. The ten temperaments carry which fifths are tempered by what fraction of **which** comma, wolf placement where the name does not force it, exact ratios, and a closure sum that makes the section self-verifying. The static 5-limit systems are the lattice block $\{3^a5^b \mid a\in[-1,2], b\in[-1,1]\}$ — a 4×3 rectangle filling all twelve chromatic positions with nothing discarded — at three anchors, `req:tuning:ji-static-construction`. `ji-adaptive-5limit` is `"default-v1"`, the key-anchored static scale, with the anchor pinned to a staff and a moment (`req:tuning:adaptive-default-version`, `req:tuning:adaptive-anchor-derivation`). **Sourcing alone was insufficient**: a first draft produced two arithmetically impossible temperaments and one false ambiguity, every one properly cited, and only the closure invariant caught them — the contract gained a permanent "check the arithmetic, not just the source" section as a result) |
| P13-S7 | **`ScalePosition` referenced a score pitch-space registry that does not exist.** The listing comment said the space "References an entry in **the score's pitch-space registry**", and `req:pitch:default-pitch-space` said every score MUST **define** at least one pitch space and MAY define more. No such registry exists anywhere: `Score` has thirteen fields and none is one, and `ScoreTuningContext` carries ids plus accidental extensions — never space or system definitions. So the specification required an act (defining a pitch space) that the data model gives a score no way to perform | `spec/PLAN_PUSH4B_TUNING.md` (Ruling C); `spec/CONTRACT_PUSH4B_SPEC1.md` | **resolved** (Wave 1a, 5dcaa58: ratified **the catalog is closed**. Both "define"s become *select*, from the built-in catalog; the `ScalePosition` comment names the built-in catalog; score-local definition is deferred to a later schema major, because the Chapter 4 type surface has never had a consumer and `req:binfmt:frozen-layout` would freeze it permanently on first encode. **No struct gained a registry field — that was the point of the ruling.** Filed here retroactively: the id was minted in the plan, ratified, and implemented without ever entering this ledger) |
| P13-S8 | **Canonical state admits two encodings of one musical fact, and nothing normalizes them.** `TempoShape::Constant` (`core_spec.tex:2230`) says `end_tempo` MUST be `None` **or** equal to `start_tempo` — explicitly legalizing two spellings of an identical constant-tempo segment. `TempoSegment` encodes `end_tempo` positionally through `struct_codec!` (`codec.rs:1512`) and `TempoMap` is canonical state on `Score` (`graph.rs:1808`, `codec.rs:1519`), so the two forms produce different canonical bytes: **measured at 336 vs 363 for otherwise identical scores.** `invariants.rs:1399` merely permits both; no normalization runs anywhere. The project has ruled the other way everywhere else it looked — `req:determinism:unicode-canonicalization` applies NFC precisely so that "two texts whose NFC byte representations are equal are canonically equal", and `req:determinism:canonical-collection-order` pins iteration to a total order — but tempo has no equivalent, so two musically identical scores hash differently | this file (measured 2026-07-22 by encoding both forms) | **open.** **Corrected 2026-07-31; two of this row's own claims were false.** (1) **"Found sideways: the branch is untested"** — it is not. Flipping `is_none_or` to `is_some_and` at `invariants.rs:1399` was executed against the working tree at `a4d7847` and **three tests kill it**: `set_tempo_segment_materializes_in_score_and_region_scope` (`epiphany-ops/tests/graph_reduction.rs:3083`), `undo_restores_the_pre_transaction_writes_for_replaced_keys` (same file), and `convergence::tests::graph_sessions_converge_on_the_real_score` (`testkit/src/convergence.rs:224`, reporting the witness verbatim). The mutation was restored by hand. The original claim was reached by reading only `invariants.rs`'s own tests, which assert `fires(...)` and therefore survive the flip — the kill sites are in another crate. **The genuinely unconstructed spelling is the opposite one:** `Constant` + `Some(equal)` has **zero** construction sites in the workspace; the sole production constructor, `valuegen::tempo_segment` (`ops/src/valuegen.rs:569`), emits `None`. (2) **The row's framing that a normalization point may not exist** — tempo **is** op-authored: `OperationKind::SetTempoSegment`, wire and tag discriminant 26, reduced at `reduce.rs:5811`, with `PreconditionFailureReason::TempoMapMalformed` already gating shape well-formedness. **Ratification, restated — normalize-on-encode is NOT the default and should not be read as one.** Accepting two byte forms and folding one violates `req:binfmt:decode-vectors` (`binary_format.tex:3404`–`:3413`): *"A value decoded from an `accept` vector MUST re-encode to exactly that vector's bytes… Canonical decode is injective: distinct byte strings denote distinct values, which is what content-addressing rests on."* The ratified precedent for exactly this shape is `req:binfmt:compression-none-parameter` (`:1909`–`:1913`), which requires a decoder to **reject** rather than normalize, on the stated rationale that leniency made the codec non-injective. **A future repair must therefore either reject one spelling** (a stored-form invariant plus strict decode, the pattern `req:format:rationaltime-encoding` already uses for reduced rationals) **or explicitly retain both as distinct canonical values** and accept divergent hashes for identical music. **A third correction:** the permissive rule is **triplicated**, so a repair touching only the invariant leaves two behind — `invariants.rs:1399`, `reduce.rs:965` (`tempo_segment_shape_well_formed`), and `tempo.rs:452` (the wall-clock integrator). Note also the types live in `crates/epiphany-core/src/tempo.rs` (`:98`, `:109`, `:119`); `graph.rs` holds only `Score::tempo_map` (`:1808`) and the second, unmentioned reach into canonical state, `Region::local_tempo_map` (`:794`) |
| P13-S9 | **The citation checker enforces cited→defined, never cited→relevant.** `requirement_labels.rs` proves every `req:*` string in the repo resolves to a real label and nothing further, so a citation that resolves cleanly but does not support the sentence it is attached to is invisible to a fully green gate. Three instances in one day: a Push-4b spec agent justified "resolved layout is non-canonical" with `req:layoutir:staff-space-coordinates`, which mandates staff-space units and `f32` and says nothing about canonicality; a dispatch contract asserted "Chapter 10 defines `KeySignature`" when the Score Graph is Chapter 5 (caught only because the agent used `\ref{ch:graph}` and reported the discrepancy rather than hardcoding either number); and **P13-S4 is itself an instance** — an agent "corrected" two dangling citations to the ownership requirements, replacing a visibly broken pointer with a silently wrong one | this file | **open** (relevance is not mechanically checkable, so the ratification is *what discipline replaces a check*: e.g. requiring the quoted sentence alongside any newly-added citation so review compares text to text, or treating agent-authored citations as unverified until a human reads the cited requirement. Note the asymmetry that makes this dangerous — the broken-pointer case is loud and the wrong-pointer case is silent, so **fixing the loud one without reading the target converts a caught defect into an uncaught one**) |
| P13-S10 | **`PitchSpaceModification::Cents(f64)` puts a raw `f64` in canonical state, which the byte layer cannot encode.** `core_spec.tex:3112` declares `Cents(f64)`; `PitchSpaceModification` is reached from canonical score state through `AccidentalDefinition` → `ScoreAccidentalExtensions` → `ScoreTuningContext`. `req:determinism:canonical-floating-point` requires canonical stored floats to be finite IEEE 754 binary64, and the byte layer enforces it with no escape hatch: `serialize.rs:110` decodes floats *only* through `CanonicalF64::from_le_bytes → NonFiniteFloat`, so there is no `Codec for f64`. A raw `f64` is therefore not merely risky in canonical state — it is unencodable without inventing a new unvalidated codec. The same chapter already resolved this exact tension forty lines later: `EngravingBoundingBox` (`:3150`) carries `SpaceUnit` edges with a rationale (`:3191`) invoking the same requirement | this file (verified 2026-07-23 against `core_spec.tex:3112`, `:3150-3197`, and `crates/epiphany-determinism/src/serialize.rs:110`) | **resolved** (ratified: `Cents(CanonicalF64)`. A one-line spec correction, the only shape consistent with the existing byte layer — the identical maneuver Ruling D applied to the bounding box. Freezes in tranche 3b) |
| P13-S11 | **`AnchorPoint`, referenced by `AccidentalEngraving.anchor`, is defined nowhere.** `core_spec.tex:3166` names `pub anchor: AnchorPoint`; no struct or enum of that name exists in the spec, and `epiphany-core` (whose `Cargo.toml` has no `epiphany-layout-ir` dependency) cannot borrow any layout-ir type even if one shared the name — the same core-native requirement that forced `EngravingBoundingBox`. An undefined leaf frozen onto the wire is the `KeyContext`-shaped gap. Compounding it: the bounding box is documented "relative to the glyph's anchor point" (`:3160`), so freezing an anchor with no defined coordinate frame freezes a point with an undefined origin | this file (verified 2026-07-23; `AnchorPoint` appears once in `core_spec.tex`, defined nowhere; `epiphany-core/Cargo.toml` lists no layout-ir) | **resolved** (ratified: core-native `AnchorPoint { x: SpaceUnit, y: SpaceUnit }`, over the same `SpaceUnit` as `advance_width` and `EngravingBoundingBox`. **Plus one normative sentence pinning the frame**: x/y in canonical space units, y-up, relative to the glyph's coordinate origin — matching the repo's existing Bravura outline convention — so the anchor and the box it anchors share an unambiguous origin. Freezes in tranche 3b) |
| P13-S12 | **`SmuflVersion` is undefined, and its obvious representation orders SMuFL's real history backwards.** `SmuflVersionRequirement` (`core_spec.tex:3269`) carries `minimum`/`authored_against` of type `SmuflVersion`, which Chapter 4 references but does not define (it exists only as a **Chapter 7 / layout-ir** type, `glyph.rs:29`, with literal-minor encoding — see the resolution). Ordering is load-bearing (`SmuflVersionRequirement.minimum`, `:3271`, gates the fallback of `req:tuning:smufl-version-fallback`), and the type is dual-purpose — it also anchors Chapter 9's `GlyphCatalog::smufl_version()` / `GlyphCatalogIdentity` (`:10420`, `:10460`), so this freeze touches layout-conformance identity, not just tuning. The trap: SMuFL versions are decimal fractions — 1.12 (2015), 1.18, 1.20 (2016), 1.3 (2019), 1.4 (2021) — that succeed by fraction (0.12 < 0.18 < 0.20 < 0.30 < 0.40). A `{ major: u16, minor: u16 }` storing literal digits with derived `Ord` orders the minors 3 < 4 < 12 < 18 < 20, placing 1.3 and 1.4 **before** 1.12; old fonts declaring 1.18-era versions exist forever | this file (verified 2026-07-23 against `core_spec.tex:3269-3274` and SMuFL's published release history) | **resolved** (ratified shape `SmuflVersion { major: u16, minor_centi: u16 }`, **literal-minor storage rejected**: the minor is stored **fraction-normalized to hundredths** — 1.12→(1,12), 1.18→(1,18), 1.20→(1,20), 1.3→(1,30), 1.4→(1,40), rule normative, release table a note. Derived `Ord` is then correct across the whole real history and collapses the 1.2/1.20 ambiguity (both → (1,20)). **Correction on filing: the leaf is NOT undefined — it exists in `epiphany-layout-ir/src/glyph.rs:29` as `SmuflVersion { major, minor }` with LITERAL minor (`{1,4}`) and derived `Ord`, so the backwards-ordering bug is LIVE there today (1.3 < 1.12), and it is a direct field of `GlyphCatalogIdentity` — conformance identity.** `epiphany-core` cannot depend on layout-ir, and `SmuflVersionRequirement` is core, so the type MUST be defined in core and layout-ir must reuse it — a **unification**, not a fresh definition, which moves `GlyphCatalogIdentity` (`{1,4}`→`minor_centi 40`). Tranche 3a defines `core::SmuflVersion` for the tuning use and leaves layout-ir's alone (a bounded, core-invisible homonym since core can't import layout-ir's); tranche 3b performs the unification and the deliberate `GlyphCatalogIdentity` move. **Correction (2026-07-23, tranche 3b-ii): "with golden/vector regen" above was verified false before that tranche's dispatch.** No golden, baseline, or vector is pinned to the catalog identity — every assertion on `ResolvedLayoutIR::canonical_bytes()` is relative (stability, determinism, and a `metrics_hash[0] ^= 1` sensitivity check that never touches `smufl_version`), and the committed SVG/PNG goldens do not embed it. The move changes the catalog's emitted bytes (`encode_catalog`'s minor field: `04 00` → `28 00`) in *value*, with nothing to regenerate. The hundredths scale is blocking for 3b, free to adjust in 3a) |
| P13-S13 | **The score tuning context has no canonical persistence path: it can be saved, but never authored, replicated, or merged.** Schema major 3 (Push 4b tranche 3b-i) put `smufl` and `overrides` on the wire, but the only *persisted* carrier that embeds a `ScoreTuningContext` is the **acceleration snapshot**, which Chapter 8 makes explicitly non-canonical and regenerable and which the bundle may discard and rebuild at will. No **canonical** carrier embeds it at all: no operation authors it (`epiphany-ops` has no tuning-context payload anywhere — the vocabulary's only `tuning` references are the per-pitch `TuningReference::Inherit`), and `MaterializedState` carries effects, conflicts, anomalies, objects, spellings, breaks, page-breaks, and pending, but no tuning context. So a user who selects a tuning system or sets a per-voice override has authored something the format cannot durably represent, and `req:tuning:tuning-resolution-order`'s scopes 2–4 (`overrides`) are in practice unreachable from any document a replica could exchange | this file (found 2026-07-24 while scoping text-projection parity; verified by searching the whole `epiphany-ops` operation vocabulary and `MaterializedState`'s field list) | **CLOSED by genesis tranche G2b** (`spec/CONTRACT_GENESIS_G2B_TUNING.md`). `SetTuningContext` — kind/tag **34**, schema major **3**, minor epoch **10** — makes an operation author the tuning context, and the op log is canonical. **The closure argument is the `metadata` precedent, not the canonical base:** the base embeds no graph values for *any* field, including `metadata`, which has been op-authored since M2d and is durable purely through its operations. The rung paid the accept-set raise this entry predicted (`OperationEnvelopeBlock` 2→3), charged to this one surface because minimal stamping is a pure function of each payload's value and `ScoreTuningContext` is the only genesis payload born at v3. **The payload carries `epiphany_core::TuningContextSettings`, a five-field subset, not the full graph type** — `accidental_extensions` is staged off the wire, so a full-value payload would have diverged between a live session and the same document reloaded, and `canonical_value!` could not have caught it (it compares bytes, never the originating value). Reduction leaves that field untouched. *Original filing and disposition follow.* — `spec/RULING_GENESIS_PERSISTENCE.md` (ratified 2026-07-24, 011c68a) reverses Pass-12 K8 and absorbs genesis into the operation set, naming `SetTuningContext` in its §2 settings table. **This was not a tuning-specific defect and did not get a tuning-specific fix:** the operation arrives as one of nine surfaces in a single coordinated tranche, and the wire layout is untouched. **The disposition, and the evidence that produced it:** `spec/ANALYSIS_GENESIS_PERSISTENCE.md` maps the same gap across the whole `Score`: **eight fields have no operation that can produce them** — `canvas.layout_defaults`, `instruments`, `staff_groups`, `parts`, `tuning_context` (this entry, its field 10), `spelling_precedence`, `analysis_layers`, `views`, plus `identity` — and one more (`decomposition_attachments`) can only be pruned back, never authored. Independently re-verified against the working tree: each field's sole `reduce.rs` mention is a read-only base-seed read. Of the four dispositions weighed there (a canonical genesis block; closing the op-coverage gap; promoting the canonical base to carry graph values; scope-limiting), **the second was taken** — every mutable `Score` field becomes operation-authored — so S13 closes when that tranche lands and is tracked *there*, not here. **One cost this entry contributes as evidence:** the per-field operation route is not the free schema-minor it appears. Blocks stamp *minimally*, at the lowest major whose layouts decode them, and `bundle.rs`'s `max_supported_major` currently caps `OperationEnvelopeBlock` at **2** on the explicit ground that no operation payload embeds the tuning context. A `SetTuningContext`-style operation would make its blocks stamp v3, dragging a role accept-set raise along with the kind append — for one field of eight. That is a concrete instance of the analysis's closing constraint, that any option enumerating fields must be re-audited against its table whenever `Score` gains a field. **This cost is now paid deliberately rather than avoided — but NOT amortised, as first written.** The ruling's "one accept-set raise, spent once" implied a single batch landing every new kind at `OperationEnvelopeBlock` major 3, spreading the raise across nine surfaces. Re-derived 2026-07-28 against the working tree, that is wrong: minimal stamping is a pure function of each payload's value, so the other eight surfaces stamp at major 0, 2, or 2 and never reach the raised bound at all. `SetTuningContext` is the sole payload born at v3, so the raise is charged to **exactly this surface** after all — which is why `spec/PLAN_GENESIS_OPS.md` §4 splits G2 and isolates it in **G2b**. **And the closure argument is not the canonical base:** the base is role-bound to major 0 (`mis_stamped_canonical_base`) and is a `MaterializedState`, which embeds no graph values for *any* field — including `metadata`, op-authored since M2d and durable purely through its operations. S13 closes on that precedent: the op log is canonical, and G2b makes an operation author the tuning context. Consequence to carry forward: once G2b lands, pruning would discard *authored* genesis state rather than merely re-derivable state, so the standing prohibition on pruning (blocked on disposition C) gains real teeth. Note `bundle.rs` documents the cap of 2 *with the tuning-context rationale in prose*, so that comment must move with the cap. Note the wire layouts themselves are already frozen and correct under every disposition: this was never about how the tuning context encodes, only about which carrier embeds it. Not a regression — nothing ever worked; major 3 made the gap visible by giving the value a wire form and no way to reach it) |
| P13-S14 | **The chunk schema *minor* is a normative MUST that no writer has ever honoured, so every appended operation discriminant is unattributable to version skew.** `binary_format.tex:2330` requires a writer to raise the chunk schema minor when it emits any discriminant appended after the minor it otherwise declares, with a stated rationale: so that a decode failure on an unknown appended discriminant is attributable to a version skew rather than to corruption. `SchemaVersion::for_major` (`bundle/src/ids.rs:204`) maps a major to a fixed constant — `V0` is `{0, 1}`, not `{0, 0}` (`ids.rs:173`), and `V1`/`V2`/`V3` carry minor 0 — and, decisively, **takes only a major**, so no per-kind additive minor can reach it — and **both** writer-side staging paths derive nothing but the major (`testkit/src/bundle_harness.rs:25`, `textproj/src/serialize.rs:183`, each mapping `max(OperationEnvelope::schema_major)` through `for_major`). So every kind appended after the initial vocabulary has no additive-version record: **24–27** (Phase-3 first tranche), **28–29** (the schema-major-2 repeat revision), **30** (Push 4a `TransposeInterval`), and **31** (genesis G1 `CreateInstrument`). The requirement's own failure mode is exactly what the gap produces: a reader meeting kind 31 from a newer writer cannot distinguish "my vocabulary is stale" from "these bytes are damaged" | this file (found 2026-07-28 during G2a contract review; verified against the working tree — `for_major`'s signature admits no minor, and neither staging path computes one) | **RESOLVED at `ff9bd0f`** (the G-minor rung, 2026-07-28). Epoch ladder ratified as minors 2–9, one per additive event, complete against `spec/AUDIT_GMINOR_VOCABULARIES.md` and monotonic in the introducing commits' order. `introduced_minor()` landed on the five vocabularies with post-baseline variants, exhaustive and wildcard-free; envelope minor = max over emitted, block = max over envelopes, major and minor derived independently; baselines not normalised. **Two things the filing did not anticipate**, both from the audit that gated the rung: the scope was never just `OperationKind` (`OperationPayload` 3, `ReanchorReason` 6 and `PreconditionFailureReason` 10–15 also append), and the **manifest** reaches `OperationKindTag` through `edit_barriers` with no operation envelope in it — so the "no companion bump" note below is **superseded**: `TextDocument` carries the manifest `SchemaVersion` it cannot derive, and the companion moved 0.9.0 → 0.10.0. Op-block stamping did remain projection-invisible, exactly as scoped. Gate `[7f]` guards the manifest aggregate with an exact-equality oracle. *Original filing follows.* Ruled 2026-07-28: policy ratified 2026-07-28 as a **global additive epoch with content-minimal stamping** (`spec/PLAN_GMINOR_SCHEMA_MINOR.md` §4); the fix is its own rung, sequenced **after G2a and before G2b** — G2b appends kind 34, so running it first would either grow this sweep mid-flight or ship 34 with the very defect the rung retires. G2a appends kinds 32/33 and takes the violation from eight kinds to ten, knowingly — the debt is already eight deep, the +2 is immaterial against it, and one retroactive sweep over 24–33 is strictly less work than 24–31 now and 32–33 later. **What the rung owes:** a minor-assignment policy (which minor each past append belongs to — this is a retroactive judgement, not a derivation), a per-kind `schema_minor()` or equivalent, block minor = max over payloads, a `for_major` replacement that accepts one, and both staging paths. **Scoped 2026-07-28:** the minor IS in the chunk content-address preimage (`chunk_content_hash`, `chunk.rs:177`), so raising it moves every affected `ChunkId` and the manifest body naming it — real address churn, though **not** a `req:format:manifest-id` problem, since that promise is conditional on the same manifest body. It does **not** reach the text projection: op-block schemas are discarded during projection (`textproj/src/project.rs:424`), so no companion bump. And the problem is **not** per-`OperationKind`: an envelope also emits the outer `OperationPayload` discriminant (`ResolveEquivocation` = 3 carries no kind at all), so the gating work is an audit of every append-only vocabulary reachable from an affected payload. Note this is orthogonal to the *major* accept-set: `OperationEnvelopeBlock` stays at 2 through G2a and rises to 3 only at G2b |
| P13-S15 | **The `OperationKind` wire golden lock stops at discriminant 29, so kinds 30–39 — ten of them, every kind appended since the lock was written — have no byte-level lock at all.** `operation_kind_wire_discriminants_are_golden` (`crates/epiphany-ops/src/payload.rs:2189`) declares `let table: [(OperationKind, u8); 30]` (`:2233`), covering 0..=29 and asserting for each that `kind.discriminant()` has not moved *and* that the discriminant byte truly leads the canonical encoding. **Absent from that table: `TransposeInterval` (30, Push 4a), `CreateInstrument` (31, G1), `SetCanvasLayoutDefaults` (32) and `SetSpellingPrecedence` (33, G2a), `SetTuningContext` (34, G2b), `CreateStaffGroup` (35), `CreatePartDefinition` (36), `CreateAnalysisLayer` (37) and `CreateView` (38, G3a), and `CreateMeasure` (39, G3b).** Their wire discriminants are unlocked: a hand edit to `OperationKind::discriminant()`'s match — itself the site Push 4a got wrong — would move any of the ten silently, and the encoding-leads-with-the-byte assertion never runs for them. **The shape is the failure this project keeps rediscovering:** a hand-maintained parallel table that quietly stopped being extended, exactly like the four literal sites found at Push 4a and the six found during G2a — and the golden lock is the one guard specifically meant to catch that class, so its own staleness is the worst place for it. The gap widened from four kinds to ten while the entry sat open, which is itself the argument for closing it now that the genesis ladder has stopped appending | this file (found 2026-07-28 by the G-minor implementation while building s1's coverage; **scope corrected 2026-07-30**, after the genesis ladder closed at G3b — the original text scoped the repair to kinds 30–33 and an array length of `; 34`, both of which were accurate when written and are now six kinds short. Re-verified against the working tree at `4f141ce`: the length literal is still `30`, and no arm for 30–39 appears in the table) | **RESOLVED 2026-07-30** (its own rung, no rider). **Never a live incorrectness:** every one of the ten discriminants was correct throughout, and `binary_format.tex:1443`ff (kind table rows 30–39) and `:1548`–`:1552` (tag table rows 30–39) carry them normatively — the gap is the *absence of a guard*, not a wrong byte. **The repair, as landed:** `operation_kind_wire_discriminants_are_golden`'s table goes from 30 to **40** entries, locking kinds 30–39 individually — each row asserting both that `kind.discriminant()` has not moved and that the discriminant byte leads the canonical encoding. **No wire, schema-version, or specification change**: this adds a guard over assignments that were already normative. **Mutation evidence:** `OperationKind::discriminant()`'s `SetTuningContext` arm was edited 34 → 44 and the extended lock failed (*"wire discriminant for SetTuningContext moved — canonical encodings are append-only"*); with the same mutation still applied, restricting the loop to `&table[..30]` — the exact pre-repair coverage — made it **pass**, which is P13-S15 itself reproduced in the tree rather than argued for. Both edits restored by hand. **On the sibling:** the tag half is total by construction and needs no extension. `the_tag_vocabulary_is_complete` (`payload.rs:2652`) is derived, not hand-written — it computes `first_unknown_discriminant()` from `PAYLOAD_FREE`'s maximum and asserts the payload-free discriminants are exactly `0..unknown` minus `Registered`'s, dense and each decoding — so it already covers 30–39, and `operation_kind_tag_vocabulary!` makes a tag without a discriminant a compile error rather than a silent omission. **One residue, scoped out and stated rather than papered over:** density plus round-trip does not pin *which* tag holds which byte, so a permutation inside the dense range (swapping two assigned discriminants) survives both that test and `phase3_tag_discriminants_are_golden` (`payload.rs:2728`), which pins literals for 24–29 only. The same permutation on the *kind* side is caught by this rung's extended table. The tag-side gap is **not** part of this rung and is **filed as P13-S22** rather than left as a closing remark — a residue recorded only inside a resolved row is a residue that gets lost |
| P13-S16 | **`StaffGroup.members` and `Staff.group` may disagree in both directions, and nothing detects it.** The two fields encode the same relation twice. `Staff.group` (`core_spec.tex:5592`, `graph.rs:848`) names the group a staff belongs to; `StaffGroup.members` (`core_spec.tex:4242`, `graph.rs:1650`) lists the staves in a group. **The specification declares both and states neither an authority nor an agreement requirement.** Graph invariant 10 checks that each side *resolves* — a staff's group is declared (`invariants.rs:1188`), a group's members are declared (`invariants.rs:1196`) — and never that the two agree, so **both stale forms pass every check in the tree**: a *missing* member (`s.group == Some(g)` while `g.members` omits `s`) and a *spurious* member (`g.members` contains `s` while `s.group` is `None` or names a different group). Genesis G3a ruled `Staff.group` the sole authority and `StaffGroup.members` a non-authoritative denormalized projection (`spec/CONTRACT_GENESIS_G3A_ENTITIES.md` §1.1, disposition B, ratified 2026-07-29) — a normative semantic ruling that makes the disagreement *defined* rather than merely undetected, but does not make it *impossible*: G3a stores the projection without maintaining it | `spec/CONTRACT_GENESIS_G3A_ENTITIES.md` §1.1 (found 2026-07-29 during G3a contract review; the authorship cycle is what surfaced it — with mints only, no authoring order produces an agreeing pair, since `CreateStaff` requires its group live at `reduce.rs:4372` and `CreateStaffGroup` requires its members live) | **open.** Deliberately no code change in G3a. The fix is disposition **A**: maintain the projection under reduction — `create_staff` with `group: Some(g)` appends to `g.members` — plus a candidate **graph invariant 21** enforcing agreement in both directions (20 exist; `invariants.rs:149` and the count guard at `:6064` both move), and a decision on whether a re-carried `CreateStaffGroup` compares against its *carried* `members` (empty) or the current derived state. **Consumers must read `Staff.group`, never `StaffGroup.members`, for membership** until that lands. The G3b sequencing note is now discharged — G3b landed, and invariant 20 is no longer competing. **Locators corrected 2026-07-31**; every code citation in this row had drifted, some by hundreds of lines, and `spec/CONTRACT_GENESIS_G3A_ENTITIES.md` carries two of the same stale pointers (corrected separately, citation-only). **The concrete implementation cost, which this row did not state:** `t8b_both_permitted_stale_forms_hold` (`reduce.rs:16340`) **pins both stale forms as passing** — asserting `staff.group == Some(g)` while `g.members == vec![]`, and `g2.members == vec![staff2]` while `staff2.group == None` — and its own doc block (`:16317`–`:16339`) names disposition A's two maintenance rules as mutations that **must break** it. So the fix is precisely the mutation an existing test exists to detect: that test must be rewritten, not merely supplemented, and rewriting it is the rung's real cost. **Complete participant set**, none of it obvious from the two field declarations: `create_staff` (`reduce.rs:4330`) and `create_staff_group` (`:4458`); both byte-identical re-carry comparators (`:4334`, `:4466`), which under A must compare the **carried** value, never the derived one; both undo `retain` arms (`:2967`, `:2977`), where undoing a `CreateStaff` currently leaves its id in a live group's `members` with no guard — the reverse direction is guarded at `:6736` and this one is not; base ingest (`:1604`, `:1611`), which can seed a disagreeing pair from a blob authored before the rule; `valuegen::staff_group` (`valuegen.rs:376`), which documents that it never normalizes; and the four G3a undo-repair tests (`:16838`, `:16992`, `:17138`, `:17345`) that each construct the missing-member form. **No schema consequence:** `CreateStaffGroup` is schema major 0 and the accept-set does not move — but if A normalizes at *construction or decode* rather than at reduction, the pinned literal-byte vector at `ops/src/vectors.rs:829` and the text-projection golden at `textproj/src/vectors.rs:198` both break on re-encode injectivity. Reduction-time refusal leaves both untouched — the design fork is resolved that way in the contract. **Scoped 2026-07-31 as `spec/CONTRACT_P13S16_PROJECTION.md` (DRAFT), and immediately BLOCKED on P13-S27.** Two findings from that scoping changed the rung's size in opposite directions. **Cheaper than feared:** the refusal needs no new `PreconditionFailureReason` and no schema-minor epoch — `reduce.rs:1236`'s `container_not_empty()` helper already covers "a create carrying children" by its own doc, and three creates already call it for exactly this shape (`create_region` `:4174`, `create_staff_instance` `:4246`, `create_voice` `:4310`); `create_staff_group` is the sole outlier. **More expensive than feared:** this is a **canonical reduction-semantics change** — the same operation set now reduces to a different `Score` — so `core_spec.tex:11614` applies: canonical bases materialized beforehand cannot be reused without rebuilding. That requirement is currently **unenforceable** (P13-S27), so the rung may not execute until S27 has a disposition. Also found while scoping: base ingest (`reduce.rs:1619`) reseeds `staff_group_values` from the *maintained* value, so a snapshot round trip launders derived members into the carried slot and a re-carry misverdicts **only after a reload**; and `t6` (`:16154`), `t7` (`:16229`) and `t9` (`:16454`) each assert behaviour the refusal removes, alongside the already-known `t8b`. **Blocking chain, 2026-07-31: S16 → P13-S27 → P13-S28.** S16 does **not** become dispatchable when S27 lands; it additionally needs S27's pin-2a legacy-base disposition, which S28 owns |
| P13-S17 | **Binary Format revision history omitted genesis tranche G2b entirely, including the accept-set raise it performed.** `spec/binary_format.tex`'s Revision History chapter ran G2a 0.12.0 straight to G-minor 0.13.0 to G3a 0.14.0, with no row anywhere recording G2b (`spec/CONTRACT_GENESIS_G2B_TUNING.md`, kind/tag 34, `SetTuningContext`) — even though `OperationEnvelopeBlock`'s accept-set raise 2→3, the first accept-set move since genesis tranche G2a explicitly recorded staying at 2, reached the normative tables (the per-chunk-role accept-set prose at `binary_format.tex:2353`–`:2363`, which names the `OperationEnvelopeBlock` role's maximum as 3 "as of genesis tranche G2b", and the `OperationKind` minor-additive bullet at `:2395`–`:2418`, which records "genesis tranche G2b took 34 (`SetTuningContext`)") and never the history. G2b's own contract touch table row 27 required "version, Revision History row" among the four-document ritual, and the rung was signed off without it — the gate did not catch a documentation MUST because nothing tests the revision history | `spec/CONTRACT_GENESIS_G3A_UNDO_REPAIR.md` §0 (found 2026-07-29 during the G3a undo-repair contract review, verified against the working tree: `binary_format.tex:3599`, `:3628`, `:3643` before this rung's edit) | **RESOLVED in this same commit.** The chronology is restored: G2b lands as its own 0.14.0 row between G-minor and G3a, and G3a renumbers to 0.15.0 (`spec/CONTRACT_GENESIS_G3A_UNDO_REPAIR.md` Packet B, pins B2–B3). A new scoped guard in `epiphany-testkit` makes the omission recurrence-detectable — a distinct principal marker per standalone-row rung (G2a, G-minor, G2b, G3a — G1 is deliberately unguarded, having no standalone row of its own), strictly ordered, with G2b's content anchored within its own row segment — so the entry is filed and closed by the same packet that finds it, unlike **P13-S15** and **P13-S16**, which stay open because their fixes are sequenced to later rungs |
| P13-S18 | **Graph invariant 20's agreement and boundary-consistency checks are partial, but only three of the checker's nine non-success paths are the real gap — not the whole checker, as first filed and over-sized.** `check_measure_meter_consistency` (`invariants.rs:2662`) has nine non-success paths across its two clauses, enumerated and classified by both its own doc comment and `spec/CONTRACT_P13S18_MATRIX.md`'s 18-cell outcome matrix (agreement: A1-A4; boundary: B1-B5). A1 (`None` declared signature) is **inapplicable** — nothing to disagree with. A3/B3 (`Governing20::None`, an empty governing candidate set) are **vacuous** (pin 6c case 1). A2/B2 (an unresolving declared or governing signature) are **delegated** to invariant 10's own resolution check (`invariants.rs:1220`ff, its per-measure and instance-local-grid arms) — verified, not merely asserted: the matrix rung's mutations M7/M8 delete each arm in turn and show the SAME condition go unreported by the ENTIRE workspace suite, not merely by invariant 20. B1 (the first measure) is the already-filed pickup/anacrusis deferral, **P13-S19**. **Exactly three paths are genuine abstention: A4 and B4 (`Governing20::Indeterminate` — the comparable relation cannot place a candidate) and B5 (a decided governing selection with no computable musical delta — order without distance).** Also corrected: the earlier **"any `Measure` *end* anchor is incomparable"** claim was wrong only in its "any" — same-id, same-position `End`<->`End` IS comparable under c2 **when its offsets compare** (`measure20_offset_order`, `:2419`, returns `None` only for `Musical` against `WallClock`); distinct-id `End` anchors genuinely are incomparable (c3 restricts to `Start`+`Zero`) and do reach A4/B4/B5; and the `resolve_anchor` `Measure` arm citation (now `:503`-`:516`, formerly miscited as `:400`ff — see the companion line-number repair to `CONTRACT_GENESIS_G3B_MEASURE.md:206`) names the missing duration machinery, not invariant 20's own execution path, which never calls `resolve_anchor`. And the `WallClock`-anchored measure start `epiphany-ops::valuegen::measure` (`ops/src/valuegen.rs:447`) actually emits does **not** uniformly abstain both clauses: against `WallClock`-anchored meter changes (c5) agreement DECIDES and only the boundary clause abstains (B5, since `measure20_musical_delta` never returns a `WallClock` delta); against `Region`-anchored meter changes both abstain (A4/B4), because `WallClock` against `Region` has no comparability arm at all | `spec/CONTRACT_P13S18_MATRIX.md` pin 8 (ratified 2026-07-31, a diagnostic and bookkeeping rung: test coverage over `check_measure_meter_consistency`'s existing nine paths plus these three ledger repairs, no behaviour change) | **open, at its true size.** A4, B4 and B5 remain real, still gated on the deferred common-timeline machinery — but the capability that closes them is now filed as **P13-S23** (below), not P11-C5: P11-C5 is a narrower re-anchoring proximity metric this entry previously over-cited as the gate; P13-S23 is the broader "place any anchor pair on a common timeline and measure musical distance" capability that actually owns A4, B4 and B5 |
| P13-S19 | **A pickup's *successor*, not the pickup itself, is what `CreateMeasure` and invariant 20 refuse — pickup/anacrusis measures are authorable, but the rest of the instance becomes unauthorable once one exists.** Genesis tranche G3b (`spec/CONTRACT_GENESIS_G3B_MEASURE.md` pin 9) makes `create_measure`'s clauses 1 (ordering) and 3 (boundary distance) vacuous for a first measure, and invariant 20's boundary clause is symmetrically exempt at `i == 0` — both for lack of a predecessor. **The agreement clause (clause 2) is not predecessor-dependent and applies to a first measure like any other**: a pickup declaring a signature that disagrees with the governing grid is itself refused (`MeasureMeterMismatch`) and flagged; only a pickup declaring `None` or a matching signature clears it, and only when its other preconditions (a live parent `StaffInstance`, resolving referents) are satisfied too. **The real defect is one measure downstream**: both rules compare `delta(prev.start, m.start)` against the *governing signature's full* `measure_duration()`, when the true distance is `prev`'s own content duration — those coincide only for full measures. A pickup's successor is therefore refused (`MeasureMeterMismatch`) by `create_measure` and flagged by invariant 20, which is what actually makes the rest of the instance unauthorable once a pickup is minted. This applies to **any** partial measure, not only a first one: a mid-score partial measure enters successfully (nothing examines its own duration when it is created), while *its* successor is refused the identical way — the failure is always attributed to the measure after the partial one. Closing this needs a per-measure duration — the "partial measure" notion this entry already names — and that is deliberately **not** introduced by the correction that reworded this entry. See also **P13-S24**, the Chapter 3 derived-notation deferral that shares this same missing concept but touches neither invariant 20 nor `CreateMeasure` | `spec/CONTRACT_GENESIS_G3B_MEASURE.md` pin 9 (filed 2026-07-30 during the G3b contract's own drafting); corrected 2026-07-31 by `spec/CONTRACT_P13S19_PARTIAL.md`, which found the original wording true only of the pickup itself and, read as written, indistinguishable from "pickups work" | **open, deliberately.** A pickup/anacrusis authoring story — whatever shape it takes — is later schema-fill work, sequenced after the genesis ladder closes; it now depends on a per-measure duration concept, which does not yet exist |
| P13-S24 | **The Chapter 3 integer-grid metric splitter assumes the region origin falls on a barline, deferring anacrusis/pickup handling — a second, independent site carrying the same missing partial-measure-duration concept P13-S19 names for invariant 20 and `CreateMeasure`.** `core_spec.tex:2484` (Chapter 3, derived notation) states the assumption directly; the same deferral is mirrored at `crates/epiphany-core/DECISIONS.md:340` ("region origin assumed to be a barline (anacrusis/pickup deferred)"). **Not a duplicate of P13-S19**: it predates genesis tranche G3b, lives in the derived-notation splitter rather than the graph model, and does not touch invariant 20 or `CreateMeasure` at all — a pickup measure can now be *authored* (P13-S19's corrected scope) with no effect whatsoever on this splitter's own barline assumption. The two entries are filed separately because they own different subsystems' work — merging them would conflate a graph-model rung with a derived-notation one — and both wait on the same underlying capability: a per-measure partial-duration notion neither rung introduces. See also **P13-S19** | `core_spec.tex:2484` and `crates/epiphany-core/DECISIONS.md:340` (mirrored deferral sites; filed 2026-07-31 during `spec/CONTRACT_P13S19_PARTIAL.md`'s drafting, pin 6) | **open.** No code owed by this rung — filed so the deferral is discoverable on its own terms, independent of P13-S19, with which it shares a root cause but not a subsystem |
| P13-S20 | **`decode.rs`'s `precondition_reason` decoder stopped at discriminant 13, so `PreconditionFailureReason` 14 (`AcousticRealizationPinned`) and 15 (`TranspositionOutOfRange`), both live since Push 4a, encoded but could not decode.** A materialized effect carrying either reason failed canonical round-trip in production code, undetected because `epiphany-testkit`'s `precondition_failure_reason` generator (`generators.rs:417`, then `rng.below(14)`) never drew past 13 despite its doc comment claiming "every core and registered variant" | `spec/CONTRACT_GENESIS_G3B_MEASURE.md` §0 / touch-table row 12a (found 2026-07-30 during the G3b contract's own drafting, verified against the working tree: `decode.rs:205`ff ended at 13, `generators.rs:417` drew `below(14)`) | **RESOLVED in this rung** (genesis tranche G3b packet 1, `e64a4b7`). `decode.rs`'s `precondition_reason` now decodes discriminants 14 through 18 (the pre-existing 14/15 hole plus G3b's own 16–18), and `generators.rs`'s `precondition_failure_reason` now draws `below(19)` with arms for all of them — the generator's doc comment claim is true again, and the decode hole this rung found already latent in the tree, not introduced by it, is closed alongside G3b's own additions |
| P13-S21 | **The wire specification never documented `PreconditionFailureReason` 14 and 15, so the decoder hole P13-S20 repaired had no normative text to be measured against.** `binary_format.tex`'s bounded reason table (`\sectionsc{Bounded Enumerations}`) ran `\tablenums{13}` straight to G3b's `\tablenums{16}`, and Push 4a's own Revision History row recorded only `OperationKind` `\tablenums{30}` (`TransposeInterval`) while saying nothing about the two reasons it appended in the same epoch. `AcousticRealizationPinned` and `TranspositionOutOfRange` appeared **nowhere** in the document, though the Operation Catalog documented both at 0.8.0 and `effect.rs` has carried both since Push 4a. This is P13-S20's specification-side twin: the same two variants, the same silence, and the reason a decoder that stopped at 13 could sit in the tree unchallenged — a conforming implementer reading only the wire specification would have built exactly that decoder and been right | Found 2026-07-30 while verifying genesis tranche G3b packet 3b (`d58eee8`), whose touch-table row 28 scoped the reason table to discriminants 16–18 only; the executing agent correctly declined to widen scope and reported the gap instead | **RESOLVED in this rung** (the G3b pre-push repair). The bounded table gains `\tablenums{14}` and `\tablenums{15}` with their refusal conditions, and Push 4a's Revision History row is amended to record both appends in its own epoch — no version bump and no new history row, because this documents an assignment that has been normative since Push 4a rather than making one. `binary_format_history.rs` gains a bounded regression test requiring both names in the reason table **and** in Push 4a's own row segment, so neither the G3b row's prose nor the Operation Catalog's coverage can satisfy it |
| P13-S22 | **`OperationKindTag`'s normative tag-to-byte mapping is asserted for a minority of the vocabulary; the rest is defended only incidentally, by frozen byte artifacts that cannot say what broke.** P13-S15 closed the `OperationKind` half with one table covering 0..=39. The tag half has no equivalent. **Semantic locks — a named tag bound to a literal byte — exist only for:** 24–29 (`phase3_tag_discriminants_are_golden`, `payload.rs:2728`), 34 (one assertion inside a G2b reduction test, `reduce.rs:12744`), 35–38 (`t1_g3a_kinds_and_tags_are_35_to_38_in_both_spaces`, `reduce.rs:15931`), 39 (`payload.rs:2936`); **16** (`Registered`, whose corpus row is emitted under its *variant* name at `ops/src/vectors.rs:210` and whose committed literal leads with `0x10` at `spec/vectors/decode_vectors.txt:80`, so the drift comparison binds the association); and — incidentally — 1, spelled in a comment inside a golden blob (`barrier.rs:1058`, *"len 1, DeleteEvent (tag 1)"*). **Tags 0, 2–15, 17–23, and 30–33 have no semantic lock at all.** The derived tests do not supply one: `the_tag_vocabulary_is_complete` (`payload.rs:2652`) proves **completeness, density, and round-trip** — that the payload-free discriminants are exactly `0..unknown` with no gaps and each decodes — which constrains the *shape* of the assignment, not *which tag holds which byte*, and `operation_kind_tag_vocabulary!` guarantees coverage rather than value. **What actually catches a permutation is byte-level goldens that embed the tag by accident**, and they diagnose it as a blob mismatch: three probes, three catches, none of them naming a tag (below) | this file (found 2026-07-30 while closing P13-S15, whose kind-side extension made the asymmetry visible; **the entry was rewritten the same day after its own probes falsified its first draft.** That draft claimed a permutation was invisible and named 32↔33 as the demonstration. Three permutations were then executed against the working tree at `dcb28f0` and **all three failed**, so the claim was wrong and the severity is lower than filed: 32↔33 (`SetCanvasLayoutDefaults`/`SetSpellingPrecedence`) → caught by `the_committed_corpus_matches_the_generator` (`testkit/src/vectors.rs:225`); 1↔2 (`DeleteEvent`/`ModifyEvent`) → caught by `edit_barriers_blob_bytes_are_golden` (`layout-ir/src/barrier.rs:1061`); 2↔3 (`ModifyEvent`/`RespellPitch`) → caught by the corpus again. Each mutation was restored by hand and the suite returned to 1541/0) | **RESOLVED 2026-07-31** (its own rung, no rider). **Disposition A** — a hand-written literal tag→byte table in `crates/epiphany-ops/src/payload.rs` (`tag_wire_discriminants_are_golden`) — ruled 2026-07-31. Disposition B (giving the numbered corpus rows the variant-naming property) was considered and is **deferred to a separate ledger-only commit**; it is no part of this rung. **Corrected inventory, recounted from the tree at `17c1d67`:** semantic locks — a named variant bound to a literal byte — exist for **14 of 40** (1, 16, 24–29, 34, 35–38, 39); unlocked: **26** (0, 2–15, 17–23, 30–33). **Not a live incorrectness, and — confirmed — not an undefended one either:** every tag discriminant was correct throughout, and `binary_format.tex:1548`–`:1552` carries the mapping normatively. **The gap was intent and diagnosis, not exposure** — verified at all six incidental-catch sites (`spec/CONTRACT_P13S22_TAGLOCK.md` §0.4). **The corpus's tag coverage is deliberate, not accidental** — `ops/src/vectors.rs:201`–`:204` emits one row per tag straight from the vocabulary — **but those rows lock byte→byte, not variant→byte**: each is named `tag_{discriminant}` and carries `[discriminant]`, both derived from the value alone, so `tag_32` asserts that `0x20` round-trips — never that `SetCanvasLayoutDefaults` is 32. **The signing mutation, executed:** the **coordinated** 32↔33 permutation — swapping both the discriminant literals *and* the two declaration lines inside `operation_kind_tag_vocabulary!`, so `PAYLOAD_FREE` still emits ascending discriminants and every derived artifact stays byte-identical while the variant→byte association is reversed — run against the working tree at `17c1d67`: `cargo test --workspace` → **1558 passed, 0 failed**, byte-identical to the clean baseline. Two operations exchanged wire discriminants in complete silence. Restored by hand; `git status` verified clean. **The repair, as landed:** `tag_wire_discriminants_are_golden` (`payload.rs`), a hand-typed 40-row `[(OperationKindTag, u8); 40]` literal table transcribed independently from `operation_kind_tag_vocabulary!`'s invocation rather than derived from its output, so that moving a tag now fails by *name*. **Two wording repairs to this row's earlier drafts, both now corrected:** the table supersedes **one** fragment (`phase3_tag_discriminants_are_golden`, retired) — not "the six scattered fragments" as an earlier draft of this row claimed — and deliberately duplicates three more (`payload.rs:3086`, `reduce.rs:12744`, `reduce.rs:15941`) for the locality reason `spec/CONTRACT_P13S22_TAGLOCK.md` pin 5 states; and the mutation that signs the rung is the **coordinated** permutation above, not the literal-only inverse ("delete the proposed table and show that some permutation then passes") this row's original probe-design note proposed — that literal-only form is caught by `the_committed_corpus_matches_the_generator` and proves nothing. **Its own rung, not a rider** |
| P13-S23 | **No filed candidate owns "place any anchor pair on a common timeline and measure musical distance along it" — P13-S18 previously mis-cited a narrower capability as its gate.** Two disjoint deficiencies, both owned by this candidate. (1) **No ordering.** The pair is not comparable under any of `measure20_comparable_order`'s five shapes c1-c5 (`invariants.rs:2457`) at all — whether the failure is in the **referent** (distinct `Event` ids; distinct `Measure` ids outside c3's `Start`+`Zero` restriction), the **variant or selector** (`Event` against `Measure`, `Measure` against `Region`, differing `pos`/`edge`), or the **clock** (`Musical` against `WallClock`, including inside `measure20_offset_order`, `:2419`) — this is what invariant 20's A4 and B4 are made of. (2) **Ordering without a usable delta.** The pair IS comparable and still yields no musical distance: c3 supplies a vector index (an order, never a distance), and c5 compares two `WallClock`s, and `measure20_musical_delta` (`:2522`) never returns a `WallClock` delta (`:2527`) — this is what invariant 20's B5 is made of. Scoping this as merely "anchors of differing shapes" or "not directly comparable under c1-c5" would exclude B5 entirely — S5 (distinct-id `Measure` `Start`/`Zero`) is c3-comparable and S1 (`WallClock` measures, `WallClock` meter changes) is c5-comparable, and both still reach B5 — an earlier draft of this filing made exactly that narrower mistake. **Explicitly broader than P11-C5**: P11-C5 (`PASS11_WORKLIST.md:159`) is a re-anchoring proximity metric that resolves "when the graph-mutation phase tracks resolved positions", and covers narrowly the two-distinct-`Event`s case (`CONTRACT_GENESIS_G3B_MEASURE.md:223`, `effect.rs:139`-`:142`'s `PositionOutsideRegion` Reserved note); P13-S23 is the timeline itself, whatever positions get placed on it. Names its dependents: invariant 20's A4, B4 and B5, and `PositionOutsideRegion`'s Reserved status | `spec/CONTRACT_P13S18_MATRIX.md` pin 10 (filed 2026-07-31 during the same rung that corrected P13-S18's over-narrow P11-C5 citation) | **open.** No code owed by this rung. Closing it needs the deferred common-timeline/duration machinery — once a `Measure` end, a distinct-id `Measure`/`Event` referent, or an `Event` position on a wall-clock-placed region can be placed on a common timeline with a musical distance, invariant 20's A4/B4/B5 residue and `PositionOutsideRegion`'s Reserved status shrink together |
| P13-S25 | **The committed decode corpus's numbered tag rows lock byte→byte, not variant→byte — one row already has the property the other thirty-nine lack.** `ops/src/vectors.rs:206`–`:209` emits one row per tag as `format!("tag_{:02}", tag.discriminant())` carrying `[discriminant]`: **both the name and the payload derive from the value alone**, so `tag_32` asserts that `0x20` round-trips and never that `SetCanvasLayoutDefaults` is 32. The `Registered` row (`:210`–`:217`) is different — its name is the hard-coded string `"registered"` while its bytes are computed from the variant, so the frozen literal at `spec/vectors/decode_vectors.txt:80` binds the association. **Disposition B of P13-S22:** give the numbered rows the same property. It **does** catch the coordinated permutation — by exactly the `Registered` mechanism, with the committed text serving as the independent statement — and it propagates the property to every implementation that reads the cross-impl corpus, which an in-crate Rust test cannot do | `spec/CONTRACT_P13S22_TAGLOCK.md` (disposition B, considered and deferred during the 2026-07-31 ruling; filed rather than left as a closing remark, per the same discipline that moved P13-S22 out of P13-S15's resolved row) | **open. Complementary to P13-S22, not a replacement for it, and not a re-litigation of it.** P13-S22 landed disposition A (`tag_wire_discriminants_are_golden`, `payload.rs:2730`), which fails **by variant name inside the crate**. B cannot supply that: its failure is still *"spec/vectors/decode_vectors.txt is stale. Regenerate: …"* (`testkit/src/vectors.rs:224`) — the misleading diagnosis P13-S22 was filed about — even though the diff text would now name variants. **What B buys is cross-implementation reach; what it costs is churn in a committed artifact other implementations pin.** Both are wanted; neither substitutes for the other. Sequencing note: run B's own signing mutation as the coordinated permutation (literals *and* declaration lines), since the literal-only form is caught today by row ordering and proves nothing |
| P13-S26 | **A doc comment in shipped code claims a specification repair that never landed, and the claim is guarded on the code side and nowhere on the specification side.** `crates/epiphany-core/src/invariants.rs:69`–`:71` enumerates invariant 10's four reference classes and states that *“genesis tranche G3a repairs this prose to name what the check body already enforced”*. **It did not.** `core_spec.tex:6570`–`:6572`, the normative enumeration item 10, still reads only *“Every cross-cutting structure's references resolve to extant objects in the graph, except where explicit re-anchoring rules permit transient dangling states during edits”* — naming neither a staff's declared instrument, a staff's group, a staff group's members, a part's staves, a view's active layers, nor any of the meter/time-signature references the Rust doc lists and the check body enforces. The repair landed in the Rust doc comment only. **The asymmetry is the defect's sharp edge:** the Rust doc block is protected by a grep-assert, `t12_invariant_10_doc_comment_names_the_four_reference_classes` (`invariants.rs:4554`, needles at `:4562`–`:4566`), so the side that is *wrong about the other* is the side that is **locked**, while the side that is actually stale is unguarded | this file (found 2026-07-31 during P13-S16 reconnaissance, while verifying that row's invariant-10 citations; no ledger entry covered it) | **open.** **Not a live incorrectness** — the check body is correct and enforces every class; only the normative prose under-describes it, and only the doc comment lies about that. **A P13-S9 instance**, and filed deliberately as one: the loud form (a dangling citation) is caught by `requirement_labels.rs`, and this quiet form — a *true-sounding claim about another document's state* — is caught by nothing. **`invariants.rs:69`–`:71` MUST NOT be “corrected” on its own.** It is currently the only artifact in the tree pointing at the `core_spec.tex` gap; softening the Rust claim in isolation would make the specification defect invisible and convert a caught defect into an uncaught one — which is P13-S9's stated failure mode verbatim. **Repair both sides in one rung**, and consider whether the LaTeX enumeration deserves the grep-assert its Rust mirror already has |
| P13-S27 | **The reduction-algorithm-version machinery is self-referential, so the one check that would detect a canonical-semantics change necessarily passes.** `core_spec.tex:11614`–`:11617` is normative — *"Snapshots produced under an earlier algorithm version cannot be used as canonical bases under a later one without rebuilding"* — and `:14369`–`:14372` states that replicas at differing versions *"may produce different canonical states from the same operation set."* The machinery to enforce it appears to exist: `ReductionAlgorithmVersion` (`bundle/src/ids.rs:291`) is a superblock wire field (bytes `68..72`, `superblock.rs:20`); `reduction_version_for` (`bundle.rs:989`) sets a new superblock's value; and `open` (`bundle.rs:396`–`:399`) rejects a mismatch. **But the writer sources the value from the canonical base's own self-report** (mapping the base's `reduction_algorithm_version` through `unwrap_or_default()`), **and the reader compares it only against the superblock that value seeded.** Nothing compares either against the semantics the running implementation actually implements. **The check is not vacuous** — it catches a corrupt or tampered base whose version disagrees with its superblock — but it **necessarily passes for a conformingly propagated stale base**, which is precisely the case the requirement exists to prevent. Supporting: **no constant or accessor anywhere names the implementation's current reduction semantics**, and `ids.rs:288`–`:289` states that *"the algorithm catalog itself lives in `epiphany-ops`"* while nothing of the kind exists in that crate — **a second instance of P13-S26's pattern**, a doc comment asserting a false fact about another module | `spec/CONTRACT_P13S16_PROJECTION.md` pin 0 (found 2026-07-31 while scoping P13-S16, which is a canonical reduction-semantics change and therefore the first rung to need this guarantee; filed in the same ledger edit as the row it blocks) | **UNBLOCKED 2026-08-07 — the format-epoch rung landed; dispatchable, and still blocking P13-S16.** (Was: open, BLOCKED on P13-S28.) **Scoped 2026-07-31 as `spec/CONTRACT_P13S27_REDUCTION_AUTHORITY.md` (DRAFT, not dispatchable).** Rulings taken: a typed `BundleCapabilities` required at both `Bundle::open` and `Bundle::create` and carried on the `Bundle` — no default, so every caller states the semantics it implements — and outright rejection on mismatch via a new `CanonicalBaseRequiresRebuild` error, not read-only and not an integrity anomaly. Storing the capability keeps all 57 `commit` sites unchanged; only `open` (57 sites) and `create` (32) move. **The scoping also falsified this row's first reading that the writer path was test-only:** `epiphany-textproj`'s `serialize_document` (`serialize.rs:119`) and `project.rs:936` are production paths that copy a base's `reduction_algorithm_version` verbatim into a fresh `SnapshotRef`, which `commit_versioned` then stamps into the superblock (`bundle.rs:798`) — so production mints self-consistent stale documents **without ever calling `open`**, and the capability must govern writers too. **What blocks it:** contract pin 2a. Baseline authority `0` does not preserve the corpus (`serialize.rs:327` stamps `1` and round-trips it; `vectors.rs:353`/`:363` likewise), and once P13-S16 moves the authority to `1`, a pre-S27 base that happens to carry `1` is **indistinguishable from a legitimately rebuilt one** — a raw `u32` carries no provenance. Four dispositions are recorded there; `FORMAT_MINOR` as a provenance carrier was proposed and **rejected** (the header never changes after creation, `core_spec.tex:10799`, so a legacy bundle committing a freshly validated base keeps its old minor forever; and a minor change may only append append-safe discriminants, `:12258`, not alter acceptance semantics). The surviving requirement — provenance must ride a container property **old readers cannot silently accept** and **a later commit cannot inherit unchanged** — is a format-epoch design, filed as **P13-S28**. **Scope of the claim, deliberately narrow:** this establishes that the **current implementation** has no detection mechanism. It does **not** establish that no reduction-semantics change in the project's history was ever detectable — that needs a history audit not yet done, and the stronger sentence is deliberately not written here. **What closing it requires:** an authority naming the semantics this build implements, and a rejection-or-rebuild path when a base disagrees with it. Until then any rung changing canonical reduction semantics can record its break in prose but cannot make stale bases unusable — which is why P13-S16's contract is complete, ratifiable as a plan, and **not dispatchable**. **Method note:** an earlier draft of S16's pin 0 claimed no writer path existed at all. That was false, and the way it was false is the point — the search behind it looked for `ReductionAlgorithmVersion(` constructor calls, which cannot find a path that propagates an existing value without constructing one. The instrument could not observe the thing it was used to rule out. **UNBLOCKED 2026-08-07:** the format-epoch rung landed and its pin 8 **resolves pin 2a** — reduction-version authority is meaningful only in major-1 containers, so legacy bases are refused by container epoch and never by version arithmetic. The collision pin 2a identified never has to be adjudicated: a pre-S27 base carrying `1` and a rebuilt S16 base carrying `1` are indistinguishable as numbers but can never meet, because the former exists only in a major-0 container, refused at the epoch boundary before any version is compared. The `u32` never has to carry provenance because the container does. **S27 now additionally owes three inherited items** (both interim refusals converted to validation, M8's deferred laundering demonstration, pin 3c's two suspended conformance assertions), recorded in its contract as required tests. **~~RATIFIED 2026-08-07 after review round 1~~ — RATIFICATION WITHDRAWN 2026-08-07, see round 2 below. **RATIFIED 2026-08-08 on the repository owner's authority, after NINETEEN adversarial review rounds the last of which returned zero findings. PINS FROZEN — executed, not edited; a defect found during execution is reported, not patched in place. DISPATCHED for execution, with the work to be left STAGED and the execution report subject to INDEPENDENT REVIEW before completion is accepted, covering in particular M7's three observations and its control. Not settled by ratification: M7's authority/base leg is unverifiable until this rung is implemented, and every gate, test and mutation is specified while none has been run.** Prior status through the review: NOT RATIFIED, NOT DISPATCHABLE, pins NOT frozen, **awaiting the next independent review round. Which rounds have closed and the running tally live in the contract's own history table and are deliberately NOT restated here — this clause carried a count through two rounds and went stale in both, and the round list went stale the same way**; no execution work may begin.** Round 1 was run because this contract had reached "dispatchable" with **zero** ratification rounds on record, against the standing rule that contracts go through adversarial review before dispatch. Round 1 returned **nine findings, four blocking**, all now carried in the contract. **Correcting this row's own preceding clause:** the three inherited items were *not* all "recorded as required tests" — obligation 2, M8's laundering demonstration, appeared in **neither** the contract's test section nor its mutation plan, while that section's preamble claimed all of them were tests. It is now **M7**, and ruled a **mutation** rather than a capability restoration: the format rung's text refusal is permanent, `COMPANION_VERSION` stays 0.14.0, and the text-projection corpus keeps `canonical_bases` reach 0. The other blocking three: §0.4's `commit`-site count counted a same-named method in `epiphany-editor-core`, **a crate with no `epiphany-bundle` dependency at all** — the third instrument failure recorded in that one section; three independent stale list-counts (the test-section header, gate 1, and three report items) all naming figures the document had outgrown; and `testkit/tests/requirement_labels.rs` missing from the touch table while pin 9 may move `CORE_REQUIREMENT_COUNT` 213 → 214 — **the escapee `CLAUDE.md` names by name**, which also escaped the format-epoch rung. Non-blocking: locator drift since `381c498` (`bc06706` grew `bundle.rs` by 338 lines; pin 5's own `:396`–`:399` confirmed unmoved), pin 2a's corpus evidence superseded by the 2 → 0 rebuild, `Bundle::open(` 57 → **60**, gate 6a's scope widened to `epiphany-testkit`, and a missing **commit-side positive** test now added as test 8. **REVIEW ROUND 2, 2026-08-07, against the frozen contract: six further findings, four blocking — and round 1's ratification was therefore premature.** (1) The call-site correction had been applied to §0.4 only, leaving the "Rung type" paragraph at **57** and touch row 2 claiming `bundle.rs` has **35** opens — a figure that was never `bundle.rs` alone and is stale besides — which made the required reconciliation impossible. (2) §0.4 called `project.rs:936` a **production** bundle writer; `#[cfg(test)]` starts at `:630` and every `Bundle` call in that file is below it, so the writer-path correction stands on `serialize.rs` alone. (3) **M5 was unexecutable**: `serialize_document` refuses bases at `serialize.rs:151`, so its output is base-free, and pin 5 with test 4 require base-free bundles to open at *any* authority — split into **M5a**/**M5b**. (4) **M6's second half was unexecutable**: `open` rejects a stale base, `create` rejects a base-bearing manifest (`bundle.rs:234`), and `commit` validates what it emits, so no caller can hold an open `Bundle` with a stale *inherited* base — replaced by broadening rather than narrowing, with the unreachability itself reported as the stronger result. Non-blocking: pin 3a's justification (*"production code mints a self-consistent stale document"*) is **false in-tree** — zero production paths stage a base — so it now rests on guarding the public `commit_versioned` API; and `serialize.rs:157` is dead code orphaned by the `:151` guard, recorded and explicitly **not** repaired here. **Two of round 2's findings were introduced by round 1** — ruling M7's refusal permanent is what made M5 unexecutable, and test 8 was added without re-deriving M6 against the same reachability. **Method note: an amendment is a change to the system, not a patch to a line**; a round must re-derive every mutation against every ruling the previous round made. **Round 3 is warranted before dispatch — the defect rate has not fallen (9, then 6), and "dispatchable" is a claim requiring evidence of convergence rather than a status reached by running out of findings.** **REVIEW ROUND 3, 2026-08-07, INDEPENDENT, against `b842975`: six findings, four blocking — and every blocking finding was a defect in text rounds 1 and 2 wrote.** (1) **Pin 3a still carried the rationale round 2 retracted** — §0.4 states there is no in-tree production base writer while pin 3a still said "§0.4 shows production code minting a stale document", so the contract asserted a claim and its negation; **the third occurrence of fix-one-site-leave-the-others**. Rewritten onto the footing that survives: `commit`/`commit_versioned` are **public API** and guard out-of-tree callers, not an in-tree path. (2) **M5a had no observation mechanism** — pin 3 required the capability be *stored* and nothing exposed it; `Bundle` carries 17 public accessors and none for capabilities, so no `epiphany-textproj` test could inspect it. **`Bundle::capabilities()` is now pinned** — new scope, flagged for round 4. (3) **M5b could not fail**: if the supplied capability and the base version both derive from `CURRENT_REDUCTION_ALGORITHM_VERSION` — the natural implementation, since `roundtrip.rs:367` currently hardcodes `ReductionAlgorithmVersion(0)` — both operands move together and the comparison passes for every value. **This is §0.1's own tautology reproduced inside the mutation built to detect it.** The base version must now come from a source that does not track the authority (persisted artifact or deliberate literal), with both operands' provenance reported. (4) **M6's replacement named a scenario with no test** — test 6 stops at opening, so nothing asserted that an unrelated commit *succeeds*, and an implementation rejecting every post-base commit passed tests 2/5/6/8 while the broadening had nothing to break; **test 9 added**. Cleanup: touch row 7 listed `generators.rs` as "call sites, real authority" though it has **zero** `Bundle::open`/`create` calls and its `rng.range(0, 8)` versions are precisely the arbitrary wire values pin 3b assigns to *synthetic* capabilities — split to **row 7a**; and §7's call-site attribution credited round 1 alone where rounds 1 and 2 are both load-bearing. **The pattern is now legible and it is not about counts: three separate mutations were unrunnable in three different ways — M5a could not observe, M5b could not fail, M6 had nothing to break. §7 item 4a now requires, for every mutation, the named test it breaks and the provenance of each operand.** **Defect rate across three rounds: 9, 6, 6 — not converging.** The newest text (pin 3's accessor, M5a, M5b, test 9, row 7a) has had **zero** adversarial passes and was written by the same agent whose previous two attempts round 3 falsified. **REVIEW ROUND 4, 2026-08-07, INDEPENDENT, against `53292f6`: five findings, four blocking.** It accepted pin 3's `capabilities()` accessor as **bounded** — the first new text any round has passed — and found the M5 pair defective a third time. (1) **M5b cited the wrong value**: `roundtrip.rs:367` sits in `assert_score_serialization_stable` (`:332`) and versions an **acceleration snapshot**, not a canonical base, while `assert_reduction_serialization_stable` has **no base at all** because pin 3c suspended it — so the value round 3 warned the implementer not to touch was irrelevant to the authority check. **The tautology diagnosis stands; only its evidence was wrong.** (2) **The instrument was left unchosen** — round 3 said "the rung picks one" and offered two, one of which does not exist for the nominated crate, since `craft_image_with_base` is a private `fn` inside `epiphany-bundle`'s `#[cfg(test)]` module (`:1648`). **Now chosen: commit-then-reopen through public API only** — build with `synthetic_for_fixture(0)`, commit a base carrying the literal `0`, reopen those bytes under the real constant. (3) **No test could assert the error fields**: `assert_reduction_serialization_stable` returns `()` and reopens with `.expect` (`:292`), so a mismatch panics rather than yielding a matchable `CanonicalBaseRequiresRebuild { base, current }`. **Test 10b added.** (4) **M5a violated §7 item 4a, the rule round 3 added in the same edit** — it named no test, and its natural assertion compares the constant with itself and cannot fail. **Test 10a added, asserting against a deliberate literal.** **Round 3's error is the one to carry: it grepped `ReductionAlgorithmVersion`, saw a `roundtrip.rs` hit, and attributed it without resolving the enclosing item — the same shape as §0.4's `.commit(` miscount that round 1 had already recorded as a lesson. Recording a defect is not the same as not committing it.** Both literals in tests 10a/10b are **load-bearing as literals**; §7 item 4b now requires confirming neither was tidied into the constant, a failure mode invisible to the suite. **Defect rate: 9, 6, 6, 5 — still not converging after four rounds, and every blocking finding in rounds 3 and 4 was in text written to fix the previous round.** **REVIEW ROUND 5, 2026-08-08, INDEPENDENT, against `df9e528`: four findings, two blocking — the first round in which blocking findings fell below four.** (1) **The status history was numerically stale again** — "amended three times … fifteen findings so far, eight blocking" were the round-2 figures, left standing through rounds 3 and 4 **while the tables recording those very rounds sat directly below them**. This is the **fifth** count-staleness defect in five rounds, and it was in the one block the author edited every round. Replaced with a **table**, so a round appends a row rather than requiring a number to be found and re-derived. (2) **Test 10b could not make the two-field assertion M5b requires**: §3 said only "assert it opens", and under mutation that yields a bare `Err` or a panic — **a `#[test] -> Result` that returns `Err` asserts nothing about that error's fields**, so M5b's required observation had no home in the test M5b names. Both `Result` arms are now pinned, plus a third for the wrong-error case, so the mutation run produces a *verified* observation rather than a stack trace. Smaller: **M5b's "cannot be tidied" claim was false** — keeping `synthetic_for_fixture` while passing `CURRENT_REDUCTION_ALGORITHM_VERSION` as both its argument and the base version preserves the fixture and fully restores the tautology, so the structure does not protect itself and the real protection is §7 item 4b; round 4 asserted a structural guarantee that undercut the procedural check actually doing the work, **which is the same error as reasoning that a mutation would fail instead of running it**. And §3's preamble still said the tests were "in `epiphany-bundle`" after round 4 added two that **cannot** be, since `epiphany-bundle` must not depend on `epiphany-ops` and reaching the real authority is their entire purpose — corrected, with each test's touch-table home named. **Blocking findings by round: 4, 4, 4, 4, 2 — the first movement in four rounds and the first weak evidence of convergence, set against the fact that every round since the third has found blocking defects in text written to fix its predecessor.** **REVIEW ROUND 6, 2026-08-08, INDEPENDENT, against `03c85dd`: three findings, ALL THREE BLOCKING, and all three in text round 5 wrote.** (1) **The amendment tally went stale inside the block round 5 restructured to prevent exactly that** — round 5 turned the review totals into a table and left "amended five times … rounds 1–4" as prose immediately above it. The amendment count is now **the number of rows**, with no separate figure to go stale. (2) **§3's test-home correction was itself false**: round 5 wrote "tests 1–9 in `epiphany-bundle`", but **test 7 *is* `assert_reduction_serialization_stable`**, which the same section names as `testkit/src/roundtrip.rs`. Two wrong versions of that sentence, both written while fixing it; replaced with a per-crate table (1–6/8/9 bundle, 7 and 10b testkit, 10a textproj). (3) **§7 item 4b protected one operand where test 10b has two** — replacing **both** `synthetic_for_fixture(0)` **and** the committed base's `ReductionAlgorithmVersion(0)` with the constant **keeps the synthetic call in place** and fully restores the tautology, and **test 10b's `Err` arm never executes in the unmutated run**, so its literal cannot detect it. Item 4b now enumerates all **three** fixture operands individually and requires each quoted verbatim. **All three findings are one defect in different clothes: a fix applied to the site named rather than to every site the claim covers** — the sixth count-staleness defect in six rounds and the third range-correction that did not check its own range. **The mechanism that works is structural, not vigilant: the review totals stopped going stale when they became a table, the amendment count did not because it stayed prose, and item 4b stopped being under-specified when it became a table.** **Demonstrated a seventh time inside round 6's own amendment**, where the new table's Total row was first written "6 amendments" — a free-standing count, three paragraphs after the sentence declaring no such count exists, and already wrong at seven rows; caught before commit and replaced with "one amendment per row". **Prose invites a number and a table does not; the defence must be the shape of the artifact, not the attention of the editor.** **REVIEW ROUND 7, 2026-08-08, INDEPENDENT, against `c0d896c`: three findings, all blocking, and all three the same defect — a claim living in two places and fixed in one.** (1) **§7 item 4a was unsatisfiable**: it required every mutation to name "the test it breaks", while item 1 four paragraphs above states that **M4 is observed to *compile*** (no test is possible — that is why pin 3's prohibition is a review rule) and **M7's expected outcome is *success***. A report obeying 4a literally could not be written, and the honest response would have been to invent a test. 4a is now a table of what each of the eight mutations owes, with M4 and M7 carved out explicitly. (2) **Round 6's three-literal correction reached §7 and not §3** — §3 still said "**both** literals … tidying **either**", so the contract carried the fixed and the broken version of the same claim, reopening exactly the narrow-scope ambiguity round 6 existed to close. §3 no longer states the count at all; it points at item 4b. (3) **"Rounds 3, 4 and 5 were independent"** went stale the instant round 6 closed, sitting in prose beside the table whose own column records it. Deleted. **Three rounds, one lesson: round 5 fixed the review totals and not the amendment tally beside them, round 6 fixed item 4b and not §3's copy of the same rule, round 7 found the classification sentence duplicating the table's column. The defect is duplication, and every previous remedy was vigilance — "check the other sites too" — which has now failed three rounds running. The remedy adopted here is deletion, not diligence: where a claim had two homes, one is removed and replaced with a pointer. A copy that cannot drift is one that does not exist.** **Findings by round: 9, 6, 6, 5, 4, 3, 3 — flattened rather than still falling. Blocking: 4, 4, 4, 4, 2, 3, 3 — rounds 6 and 7 were both 100% blocking and 100% in the previous round's text. Seven consecutive rounds, no clean round yet. The deduplication is the first structural remedy for this particular defect and therefore the first with a reason to work, but it is untested.** **REVIEW ROUND 8, 2026-08-08, INDEPENDENT, against `9829ae3`: two findings, both blocking — and the first round to reach into a mutation's mechanics rather than its bookkeeping.** (1) **M7 did not describe a runnable observation.** It instructed execution to *construct* a base-bearing `TextDocument`, which **bypasses `parse_document` entirely**, so the parser refusal it ordered removed was irrelevant and the demonstration was not the **import** laundering it is named for; `project_text_document` is the **export** direction (`&TextDocument -> Result<String, _>`) and is not on the path at all, so "all three sides, since removing one leaves the others refusing and the document never reaches the writer" was simply false for it; and "byte-indistinguishable from one whose base was genuinely validated" named **no comparison artifact and no comparison method**, leaving the central claim a conclusion rather than an observation. Now: the input must be **text and must be parsed**; only the parser (`parse.rs:138`–`:147`) and serializer (`serialize.rs:151`) refusals are removed and restored; the comparison artifact is **test 10b's construction** with the same `FileUuid` and base bytes; and the comparison is a **field-by-field enumeration** of the `canonical_base` `SnapshotRef`, the superblock's reduction version and the header's major/epoch, **reported rather than concluded** — informative in both directions, since a field that *does* differ is a provenance signal nobody knew existed. (2) The round-7 deduplication was incomplete: the status block still carried "rounds 3 and 4 are closed" while declaring the history table the sole authority. **Finding 1 is the most substantive of any round, because every earlier one was about text agreeing with other text — this one is about whether the experiment runs at all, and it did not. M7 had been in the contract since round 1 and survived seven reviews, three of which specifically re-derived mutations, because reading it never required tracing what calls what. An observation stated in the right register can look complete for a long time; "indistinguishable" was a conclusion sitting inside the rung's own demonstration, which is the exact failure mode this rung exists to eliminate.** **Findings by round: 9, 6, 6, 5, 4, 3, 3, 2. Blocking: 4, 4, 4, 4, 2, 3, 3, 2. Eight rounds, none clean. While amending, the author caught a third instance unaided — §7 item 4a's M7 row still said "all three refusals" — which is weak evidence the deduplication rule is being applied rather than merely stated. The M7 rewrite is now the newest and least-reviewed material in the contract, and its predecessor survived seven rounds while being unrunnable.** **REVIEW ROUND 9, 2026-08-08, INDEPENDENT, against `01e76d1`: two findings, both blocking, both in M7's comparator — the text round 8 had just rewritten.** (1) **Test 10b is not the "genuinely validated" reference M7 nominated**: its write-side capability is `synthetic_for_fixture(0)` and only its *reopen* uses the real authority, so M7 would have compared one synthetic fixture against another with the validated half of the claim simply absent. **This is a collision between two of the contract's own designs, not a typo** — round 4 made 10b synthetic-on-write *deliberately* so M5b's operands would be provably independent, and that is exactly what disqualifies it here. **One artifact cannot be both independent of the real authority and committed under it.** M7 now builds its own reference in `epiphany-testkit`, committing a base under `caps` derived from the real constant so pin 3a validates it on the way in. (2) **The field enumeration could not support its conclusion**: it claimed "everything that could carry provenance" while omitting `FixedHeader.file_uuid` — **the field it required to match** — plus the superblock's `generation`, `manifest_offset`, `manifest_length` and `manifest_hash`, and the whole manifest outside `canonical_base`. Replaced with **whole-`image()` byte comparison**, any difference enumerated and classified as justified nondeterminism (normalize, stating why) or as a **provenance signal** (a finding, since the refusal may then be stronger than needed). **Finding 2 retires a technique rather than an instance: a hand-written list of "every field" is a claim about a struct's contents that is wrong the moment the struct changes, and this one was wrong the day it was written. Comparing the whole artifact cannot be incomplete — the tables-over-numbers lesson applied to the experiment instead of the prose.** Three further sites were caught by the author while amending: §7 item 6 still said "M7's three text refusals", surviving round 8's correction of that exact count in two other places; §7 item 4a's M7 row still named the superseded method; and round 8's own disposition cell stated it as current. **Findings by round: 9, 6, 6, 5, 4, 3, 3, 2, 2. Blocking: 4, 4, 4, 4, 2, 3, 3, 2, 2. Nine rounds, none clean. Rounds 8 and 9 both found defects in the immediately preceding round's rewrite of the same paragraph, so M7 has now been wrong in three distinct ways across three consecutive rounds — unrunnable, then wrong-artifact, then wrong-method. The comparator is on its third design and has never been executed.** **REVIEW ROUND 10, 2026-08-08, INDEPENDENT, against `0efd543`: one finding, blocking — the smallest round yet, and again in M7.** **The whole-image comparison had no complete construction alignment.** Round 9 named four things to align, but `serialize_document` also fixes `document_id`, `lineage_id`, `profile_declarations`, every extension's fields and preserved chunks, the envelope payloads, the **staging order** (base root → extension chunks → operation-envelope block), the manifest schema `major` and `epoch_max`, and every chunk ref, hash and offset derived from those. **So a byte difference would have had a third possible cause — "the reference was built differently" — which is neither permitted classification; the result would have been unclassifiable and the comparison meaningless. A result that cannot be classified is not an observation.** M7 is now a **round trip**: build `B` validated under the real authority, export it to text via `document_from_bundle` + the crate-private `render_text_document`, parse that text back, re-serialize as `A` with `B`'s `FileUuid`, and compare whole images. **Alignment is inherited rather than enumerated** — every input `serialize_document` reads is already `B`'s own, so no list can be incomplete and the setup-mismatch category is eliminated by construction rather than by care. It is also the realistic form of the threat: export a validated document to text, re-import it, and observe the re-imported container is indistinguishable from the original having validated only the base's number, never its provenance. **This was the third hand-enumerated "complete set" in this contract and the third wrong on the day it was written — "every field that could carry provenance" (round 8), "every field to align" (round 9), and round 9's list again. The single rule earned across rounds 5–10: where a claim requires completeness, do not enumerate, derive. Tables instead of counts, whole artifacts instead of field lists, one shared origin instead of an alignment list.** No refusal count is stated anywhere in M7 any more; three successive wordings each had a wrong one. **Findings by round: 9, 6, 6, 5, 4, 3, 3, 2, 2, 1. Blocking: 4, 4, 4, 4, 2, 3, 3, 2, 2, 1. Ten rounds, none clean, and three consecutive rounds have found one paragraph — M7 — defective in a new way each time: unrunnable, wrong artifact, wrong method, incomplete alignment. Findings are falling steadily and each of the last three has been narrower than the last, the first sustained convergence signal here. Against that: M7 has never been executed and each of its four designs looked correct when written — the open question for round 11 is whether the next defect is findable by reading at all, or whether M7 must be run against a scratch branch before further paper review can add anything.** **BOUNDED SCRATCH PROBE, 2026-08-08, authorised as an explicit narrow exception in the contract's status block and run on a discarded branch: it FALSIFIED round 10.** First, recorded as evidence in its own right: **M7 cannot be executed at all until S27 lands** — `BundleCapabilities` and `CURRENT_REDUCTION_ALGORITHM_VERSION` do not exist in the tree, being S27's own deliverables, and M7 step 1 needs a base committed under the real authority. M7 is a mutation *of this rung's implementation*, so it runs after the rung. The probe therefore tested the round-trip machinery M7 depends on, base-free — which removes **no** refusal, since both `project_text_document` and `serialize_document` gate on `canonical_base.is_some()`, leaving §1.2 untouched. **Result: the round trip is byte-preserving, but only from a fixed point, and round 10's comparison did not compare from one.** Round 10 compared `A` against a `B` built from the *input* document, which is valid only when that document is already a fixed point of `document_from_bundle ∘ serialize_document`. `minimal_document(42)` happens to be one — so the first probe **passed**, and would have been reported as success — while `minimal_document(99)` was not, and the **one-extension case diverged by 295 bytes from offset 352**. Rebuilt from the fixed point, all three cases are byte-identical (1641 / 1800 / 1894). **The non-idempotent field is `envelopes`, not extensions:** diagnosed field-by-field, `document_id`, `manifest_schema_version`, `lineage_id`, `profiles`, `canonical_base`, `blobs` and `extensions` — including every `TextChunk` payload — survive exactly, while `document_from_bundle` applies a **canonical envelope ordering** (its own test says so), so any other arrival order is not a fixed point and its operation-block bytes differ. **`project_text_document` → `parse_document` proved LOSSLESS** (`b_doc == d` in every case) — the text leg was never the problem; the defect was entirely in which artifact round 10 chose as reference. **What M7 must add, for round 11 to ratify rather than for the probe to assume: an explicit fixed-point normalisation and assertion before any byte comparison, because otherwise a mismatch is round 10's own unclassifiable "third category".** Probe hygiene: the comparison was **mutation-verified** — a different `FileUuid` for `A` produced 20 differing bytes at offsets 32–47 and 60–63, observed, then restored by hand-editing, incidentally confirming round 9's point that `FixedHeader.file_uuid` is byte-visible and round 8's enumeration had omitted it; one file touched, 142 insertions, all inside `#[cfg(test)]`; no refusal removed; no canonical base carried; diff captured, branch deleted. **The methodological result: four paper rounds refined this comparison and none found that it silently depended on an unstated precondition. One execution found it in minutes, via the case a reviewer would least likely hand-pick — a document with an extension. Had the probe stopped at the case round 10 implied, the contract would have been ratified on a comparison that fails for most documents.** **REVIEW ROUND 11, 2026-08-08, INDEPENDENT, against `39f2617` (post-probe): three findings, two blocking. It confirmed the probe contained and its fixed-point result decisive, and kept M7 BLOCKED.** (1) **M7 still lacked a distinct normalised reference.** Round 10 named one artifact where the comparison needs two: build **`B_raw`** under the real authority, then **iterate derive-and-reserialize until `B_fixed` is a byte-level fixed point**, **assert that property explicitly as a hard failure**, and **compare the imported artifact only with `B_fixed`, never with `B_raw`** — otherwise an envelope-order normalisation difference remains **indistinguishable from a provenance result**, and a comparison whose failure mode cannot be told from its success condition decides nothing. Steps 1a–1c added, including a bounded convergence loop (the probe saw one pass suffice for three documents, which is not proof that one pass always suffices) and a required report of the iteration count and whether `B_raw` was already fixed. (2) **The claim was stated more broadly than any observation supports.** M7 read as though every direct bundle is byte-identical to its re-imported form; it is not, and the probe measured 295 differing bytes proving so. The contract now scopes it: M7 proves **the text path carries no provenance marker *after normalisation***, and explicitly **not** that every direct bundle is byte-identical before it — the pre-normalisation differences are `document_from_bundle`'s canonical envelope ordering and have nothing to do with provenance. **Both sentences must appear in the rung's report.** **This finding has consequences beyond M7: its conclusion is the sole evidence for a permanent capability loss — the text refusal that moved `COMPANION_VERSION` to 0.14.0 and took the corpus's `canonical_bases` from 2 to 0 — so justifying a permanent refusal from a claim broader than the result obtained is the same error as concluding instead of observing, one level up: not a false observation, but a true one asked to carry more than it can.** (3) Clarification rather than defect: **the probe cannot pre-verify M7's authority/base leg**, which needs `BundleCapabilities`, `capabilities()` and pin 3a's validation — S27's own deliverables — so it remains an **execution requirement after S27 implementation**, with the probe standing as evidence for the prerequisite and explicitly **not** as a demonstration of laundering, since it carried no base. Recorded as a standing prerequisite table: the round-trip leg is settled, the authority leg is not pre-verifiable by any review or probe. **Findings by round: 9, 6, 6, 5, 4, 3, 3, 2, 2, 1, 3. Blocking: 4, 4, 4, 4, 2, 3, 3, 2, 2, 1, 2. Eleven rounds, none clean. Round 11 broke the falling trend, and did so because the probe supplied evidence that made a previously invisible defect findable — a reason to expect the next round to find more rather than less. The M7 comparator is on its fifth design: four were falsified by reading, the fifth by execution and then rebuilt on that evidence. It is the first with a measured result behind it and the first whose precondition is asserted rather than assumed, and it still cannot be executed end to end until S27 is implemented.** **REVIEW ROUND 12, 2026-08-08, INDEPENDENT, against `74dc994`: two findings, both blocking, and both the same defect — a requirement stated without the decision it requires, leaving execution to make a design choice silently.** (1) **The convergence loop was not actually bounded**: it demanded a bound and named no limit, so execution would have chosen when non-convergence becomes failure, changing what the experiment means. **Now pinned at one normalising step** — with `B₀ = B_raw` and `B₍ₙ₊₁₎ = serialize_document(document_from_bundle(Bₙ), uuid)`, compute at most `B₁` and `B₂`, permitted maximum `n = 1`, with a three-row outcome table (`B₁ == B₀` → already fixed; `B₁ != B₀` and `B₂ == B₁` → `n = 1`, the expected case; **`B₂ != B₁` → HARD FAILURE**, reporting all three image lengths and the first differing offset). **The bound is one step because it is a property, not a tolerance:** `document_from_bundle` canonicalises, so `serialize_document ∘ document_from_bundle` must reach its canonical form in a single application, and if it does not there is **no canonical form**, no principled reference artifact, and **M7 is invalid as a whole** — a finding about the projection rather than a signal to iterate further. A loop that runs until it happens to settle tests nothing; it reports how long it took. Raising the bound needs its own amendment and review round. (2) **M7's location was unchosen**: "in a crate that can reach the real constant" is true of two crates and decisive for neither, and `render_text_document` is **`pub(crate)` to `epiphany-textproj`** (`project.rs:595`), so `epiphany-testkit` could host M7 only via **an unpinned visibility change to another crate's public API**. **The harness is now pinned to `epiphany-textproj`**, which alone has both the renderer and (via its `epiphany-ops` dependency) the real constant — under **existing touch row 9**, no new row. **`render_text_document` stays `pub(crate)`:** handoff §1.3 records it as *the one intentional hole* in the text refusal, existing solely so a negative vector can carry the spelling it asserts is refused, and widening it to host a mutation that gets reverted would leave a permanently widened public surface behind — which is how a temporary harness becomes an API change nobody ratified. **Findings by round: 9, 6, 6, 5, 4, 3, 3, 2, 2, 1, 3, 2. Blocking: 4, 4, 4, 4, 2, 3, 3, 2, 2, 1, 2, 2. Twelve rounds, none clean. The last two rounds found the same kind of defect — a requirement that reads as a decision but is not one — so the next scan should hunt remaining instructions that name a constraint without naming its value. Everything M7 now specifies is pinned to a number, a crate or a named artifact, which is a checkable property a round can test directly.** **REVIEW ROUND 13, 2026-08-08, INDEPENDENT, against `bff9c9a`: one finding, blocking — and it inverted M7's result.** **M7 claimed the capability check "does not fire".** Pin 3a requires `commit`/`commit_versioned` to validate a **newly emitted** canonical base, which is exactly what **both** `B_raw` and the parsed `A` commit — so **the check fires on both paths and ACCEPTS**, because the raw version equals the real authority. **That acceptance is the laundering result:** the base is not slipped past an absent check, it is admitted by a check working correctly that cannot tell a coincidence from a rebuild. **As written, M7 was satisfiable by deleting pin 3a's writer check entirely** — yielding a passing M7 that demonstrated the exact opposite of its purpose. M7 now requires **three observations** (`A.image()` equals `B_fixed.image()`; pin 3a's validation ran and accepted on both commits; and the control) plus a **required control**: in the same run, same harness, repeat the import with a base version deliberately **not** equal to the real authority and observe the commit **REJECTED** with `CanonicalBaseRequiresRebuild`. **M7's removals are now explicitly limited to the text refusals — pin 3a is not among them and may not be weakened, being the thing under observation rather than an obstacle to it.** **This is a new failure shape worth naming: an observation satisfiable by the absence of the thing it observes.** M7's earlier defects were about being unrunnable or comparing the wrong artifacts; this one would have run, passed and reported success on a tree with the writer check removed. **"The check does not fire" cannot distinguish a check that accepts from a check that is not there, and only one of those is the finding.** **Findings by round: 9, 6, 6, 5, 4, 3, 3, 2, 2, 1, 3, 2, 1. Blocking: 4, 4, 4, 4, 2, 3, 3, 2, 2, 1, 2, 2, 1. Thirteen rounds, none clean. Round 13 is the narrowest since the probe, but it found a defect of a kind no earlier round had looked for — not "can this run?" or "does this compare the right things?" but "could this pass for the wrong reason?" — and that question has NOT been asked of M1–M6, M5a or M5b. Every mutation in §4 deserves the same check: what else, besides the intended defect, would make it pass?** **REVIEW ROUND 14, 2026-08-08, INDEPENDENT, against `f579172`: one finding, blocking — a contradiction round 13 created.** **The comparison method still said equal images "complete the observation and require nothing further"** — written in round 9, when byte equality *was* the whole of M7, and not swept when round 13 added the writer-check control. **The contract therefore simultaneously required the control and licensed omitting it, with the permissive sentence sitting earlier and reading as the summary.** Equality is now **necessary but not sufficient**: observation 1 of three, with the control still required, and that paragraph now specifies *how to compare*, never *what suffices*. **A second instance was found while amending, and round 14 reported none:** the "informative in both directions" note read *"if **every field matches**, the refusal is justified"* — the same sufficiency claim in different words, still carrying round 8's *"every field"* vocabulary that round 9 had replaced with whole-image comparison. **A search for "nothing further" or "sufficient" cannot reach a sentence that says "matches"** — the defect `CLAUDE.md` names, *searching one spelling and concluding about all sites*, met inside the fix for a sweep failure; neither the reviewer's search nor the author's first search found it, and a third pass on different terms did. **The round-13 lesson generalises further than round 13 stated: it is not only that a requirement must be swept to every site, but that the permissive statement usually reads *earlier* than the restrictive one, because requirements accumulate downward as a document is amended. A reader following the document in order stops at the first sentence that says "done". Where a later round narrows what suffices, the earlier summary is the site most likely to contradict it and least likely to be searched.** **Findings by round: 9, 6, 6, 5, 4, 3, 3, 2, 2, 1, 3, 2, 1, 1. Blocking: 4, 4, 4, 4, 2, 3, 3, 2, 2, 1, 2, 2, 1, 1. Fourteen rounds, none clean — but the last two are single-finding rounds and round 14's was created by round 13 rather than pre-existing, the narrowest the defect stream has been. Against that, round 13's question — what else, besides the intended defect, would make this pass? — has still not been asked of M1–M6, M5a or M5b, and round 14 did not ask it either. That scan remains outstanding and is the largest known unexamined surface.** **REVIEW ROUND 15, 2026-08-08, INDEPENDENT, against `fa483cf`: one finding, blocking — and it ran the scan rounds 13 and 14 left outstanding.** **M6 accepted "test 5 fails" and "test 9 fails" as its observations.** A test fails for **every** reason, not only the one under test, so an unrelated writer rejection satisfies both exactly as well as the intended cause — M6 could have reported success while demonstrating nothing about pin 3a's scope. Both halves now require the **mutated outcome itself**: after removing pin 3a, test 5's stale commit must be observed to **SUCCEED** and the bundle to reopen at the new generation with the stale base present; after broadening pin 3a, test 9's otherwise-unchanged commit — one that does not touch `canonical_base` — must be observed **rejected specifically by the broadened writer rule**, named in the report, not merely erroring. **The scan is now complete: M1–M5b survive it, M6 did not.** That the one remaining instance was in M6 — the mutation twice rewritten for unexecutability — is worth noting: **a mutation can be made runnable and still not be evidential.** **The principle, stated once so it need not be rediscovered: the evidence a mutation owes is the behaviour it changed, not the assertion it broke. A broken assertion is a symptom with many possible causes; the changed behaviour has one. Every mutation in §4 now names an outcome, not a failure.** **Findings by round: 9, 6, 6, 5, 4, 3, 3, 2, 2, 1, 3, 2, 1, 1, 1. Blocking: 4, 4, 4, 4, 2, 3, 3, 2, 2, 1, 2, 2, 1, 1, 1. Fifteen rounds, none returning zero — but what changed in the last three is the character of the findings, not only the count: round 13 found a defect of a kind never looked for, round 14 found a contradiction round 13 created, and round 15 found the last instance of round 13's kind with the scan reported complete across every mutation. The known unexamined surfaces are now enumerable, which they were not before: §4 is scanned and clean, and M7's authority/base leg remains unverifiable until S27 is implemented by construction, carried as an execution requirement rather than a gap in the document.** **ROUNDS 16–19, 2026-08-08, recorded together.** **Round 16 (independent, 4 findings, all blocking):** round 15 stated a rule covering every mutation and applied it only to M6 — **M1, M2, M3 and M5a still took a broken assertion as evidence**, and each now requires the mutated behaviour itself: the stale base observed *opening*; the corrupt fixture observed returning `CanonicalBaseRequiresRebuild`; the base-free fixture observed rejected by the wrongly widened check, with `base` named as the superblock's no-base default and **that synthetic source prohibited from shipped validation**; and `serialize_document`'s stored capability observed equal to the changed authority. Round 15's completeness claim is marked **FALSIFIED IN ROUND 16** at its original site. **Round 17 (authored-side sweep of §3 and §5, 10 findings, 5 blocking):** round 13's *could-this-pass-for-the-wrong-reason* question had never been asked of the tests or the gates, and both yielded immediately. **Gate 6's derive alternative could never match** — `grep` is line-oriented, so `[[:space:]]*` cannot cross the newline rustfmt puts between `#[derive(…, Default)]` and `pub struct BundleCapabilities`; the likelier violation returned 0 matches and the gate **passed**, while being the sole mechanical guard on the pin-3 prohibition M4 exists for because no test can catch it. **Gate 6a was vacuous under a rename** — pin 3b offered `synthetic_for_fixture` as an example, and the name is now pinned. **Gates 2 and 3 named no toolchain** in a repo whose CI records 1.95/1.97 lint divergence and whose default is 1.97.1; both are now `cargo +1.95.0`. **Gate 4's "staged list exactly §2" was unsatisfiable** with a conditional touch row, now subset-both-ways. **Tests 1, 6, 7, 8 and 9 could all pass on a base-free bundle**, since pin 5 makes base-free the permissive case and base-bearing fixtures are the awkward ones to build — test 1 degenerated into test 4. Also: tests 1/6 given distinct construction routes, test 4's caps asserted unequal, gate 1 requiring **0 ignored**, gate 7 given a method, gate 5 quoting all three dependency tables. **The unifying defect: a gate proving absence is only as strong as the string it searches for — a regex that cannot match, a name that was an example, a clause with no method, all reporting success while checking nothing. The remedy throughout is §4's: require an artifact quoted and read, not a pattern matched.** **Round 18 (independent, 2 findings, both blocking, both created by round 17):** the base-presence rule **demanded the opposite of what test 8 is for** — it grouped tests 8 and 9 as "the ones that commit", but test 8 *introduces* the base and must start `is_none()`, so the rule was either unsatisfiable or satisfiable by a fixture that made the test assert nothing; and **test 6's construction was self-contradictory**, assigned the commit path while required to arrive as its hand-built ancestor did (`bundle.rs:1866` calls `craft_image_with_base` at `:1869`). Fixed by a per-test state table and by **swapping the routes**, which makes the attribution true rather than deleting it. **Round 19 (independent): ZERO FINDINGS — the first clean round in nineteen**, confirming the per-test table, the route swap and the revised gate mechanics. **Running total: 65 findings, 47 blocking, across 19 rounds. A clean round is the criterion named at round 11 and the first evidence of convergence this contract has produced; it is not proof of correctness, and no round has re-derived the whole document. Still open after any ratification: M7's authority/base leg is unverifiable until S27 is implemented, those being S27's own deliverables, and every gate, test and mutation is specified but none has been run** |
| P13-S28 | **No container property distinguishes a document produced under a validated reduction authority from one produced before any authority existed — and the two candidates that look like they would, cannot.** P13-S27 installs an authority and validates it at read and write time, but cannot state what to do with a canonical base that predates the authority: a raw `ReductionAlgorithmVersion` is a bare `u32` (`bundle/src/ids.rs:291`) carrying no provenance, and the text-projection parser accepts an unbounded one from a document (`textproj/src/parse.rs:591`), so no numeric convention — including a deliberately high epoch — is safe from a hand-authored or third-party document declaring it. **`FORMAT_MINOR` does not work either, for two independent reasons:** the header *"never changes after the file is created"* (`core_spec.tex:10799`–`:10800`) and `commit_versioned` publishes only a superblock (`bundle.rs:791`), so a legacy bundle that commits a base S27 just validated keeps its old minor **permanently** — rejecting minor-≤1 bases would then reject a base the authority itself accepted, and accepting them leaves S16's `1` ambiguous; and `core_spec.tex:12258`–`:12262` limits a minor change to appending append-safe discriminants and calls it backward-compatible, whereas making a previously-valid base newly rejectable is a **semantic acceptance change**, with current readers ignoring minor entirely (`header.rs:119` gates on major alone) so the boundary would bind only readers that already comply. **The requirement that survives:** provenance MUST ride a container property that **old readers cannot silently accept** and that **a later commit cannot inherit unchanged** | `spec/CONTRACT_P13S27_REDUCTION_AUTHORITY.md` pin 2a (filed 2026-07-31; the disposition S27 cannot make from inside itself) | **IMPLEMENTED 2026-08-07 (`bc06706`, fix `be244df`). Was the critical path; both P13-S27 and P13-S16 were blocked on it.** **This rung must own all five, and none may be deferred into S27:** (1) an **old-reader rejection boundary** — pre-boundary readers must fail closed rather than silently open a document whose safety check they do not run; (2) **provenance that survives commits correctly**, i.e. is not inherited unchanged by a later generation and is not lost by one; (3) **legacy-base rebuild/repack behaviour**, stated for real artifacts rather than assumed away; (4) **every writer path, including text projection** — `serialize_document`, `project.rs`, and the committed `.txt` vectors, since a text document can declare any version; (5) **the exact format-version and compatibility consequences**, most plausibly a **major**-version boundary or a generation-scoped attestation paired with an incompatibility boundary. **Not a sub-pin of S27 and must not drift into it** — S27's pin 2a carries an explicit prohibition against being amended into a disposition without its own ratification round. **Scoped and RATIFIED 2026-07-31 as `spec/CONTRACT_FORMAT_EPOCH_MAJOR1.md`** after four adversarial review rounds — 11 pins, 11 tests, 11 mutations, 15 touch rows, 7 gate items. **This row is now a dependency record only; the work lives there and P13-S28 does not execute as a Pass 13 rung.** Rulings taken: the carrier is the **format major** (`FORMAT_MAJOR` 0 → 1, `FORMAT_MINOR` 1 → 0), decoded three ways through a named `FormatEpoch` rather than a bool, with **no** generation-scoped attestation in this epoch; legacy resolves to **hard rejection, not read-only**; and an eight-row epoch matrix in which a major-0 bundle with no base may open, one carrying a base is rejected, and one attempting to *add* a base is rejected and told to repack — **the non-inheritance rule that `FORMAT_MINOR` could not express**. All five things this row required the rung to own are pinned: old-reader boundary (pin 2), commit-surviving provenance (pin 3), legacy repack (pins 4, 5), every writer path including text projection (pins 3b, 6), and the exact format/compatibility consequences (pins 1, 7). **Three findings from the review rounds that changed the rung's shape**, none of them visible at filing: (1) **it cannot stamp major 1 before S27's writer enforcement exists**, so pin 3a temporarily refuses *both* boundaries — opening a major-1 bundle already carrying a base, and committing one into it — through a third, temporary `ReductionAuthorityUnavailable` error that must name P13-S27 and must **not** name repack; (2) **text projection launders provenance straight through the boundary** (`serialize_document` stages a carried base into a fresh bundle and `build_manifest` writes it), resolved as **symmetric document-level refusal** — projection, parsing and a new dedicated `SerializeError` variant, none of which existed to be "retained" — which forces `COMPANION_VERSION` 0.13.0 → **0.14.0** and rebuilds the committed corpus to **20 vectors, ten rejection classes, `canonical_bases` reach 2 → 0**, a real and stated capability loss; (3) **corruption precedence binds in both epochs** — a corrupt major-1 base must still fail as malformed, never as the *temporary* authority error a user would reasonably retry. **IMPLEMENTED 2026-08-07** — amended once before dispatch (pin 3c, touch rows 10/11, gate 8) after reconnaissance found pin 3a's refusals reaching a conformance criterion through a file the touch table did not carry. All 11 tests landed under their contract names, all 11 mutations run and observed, workspace green at 1569. **P13-S27 is unblocked and P13-S16 remains blocked on S27** — pin 8 resolved S27's open pin 2a (legacy bases are refused by container epoch, never by version arithmetic), and S27 additionally inherits three obligations recorded in its own contract: converting **both** interim refusals to validation, M8's deferred laundering demonstration, and pin 3c's two suspended conformance assertions. **Two touch-table gaps found during execution, both of the same shape** — a `.tex` requirement addition moves hardcoded counts in `testkit/tests/requirement_labels.rs`, and a companion-version bump moves a second normative version literal spelled `version~0.13.0` rather than `(0 13 0)`; neither file was in any touch table, and the second was caught only because `requirements_name_only_this_companion_version` exists. **A third gap was caught in review, after the rung was committed:** pin 3b's projection refusal had been implemented only on the **bundle** side (`document_from_bundle`), leaving the public `project_text_document` free to emit a `(canonical-base ...)` line for a directly constructed `TextDocument` — text the parser then rejects. A projector that can produce what the parser refuses is exactly the asymmetry pin 3b exists to close, and the refusal is unreachable through a `Bundle` during the interval anyway, so the *only* reachable half was the unguarded one. The public projector now returns `Result` and refuses; a crate-private `render_text_document` retains the base spelling for the one legitimate caller, the `canonical_base_present` negative vector. **The lesson is the rung's own recurring one:** a guard placed on the path that happened to be named, rather than on every path a caller can reach |
