epiphany/spec/PASS13_CANDIDATES.md

62 KiB
Raw Blame History

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. S10S12 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; S10S12 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 S10S12'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.)

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:1510) and TempoMap is canonical state on Score (graph.rs:1685, codec.rs:1517), so the two forms produce different canonical bytes: measured at 336 vs 363 for otherwise identical scores. invariants.rs:1335 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 (ratification: normalize on encode to one form — None is the smaller and the one every generator already emits — or state that both are canonical and accept divergent hashes for identical music. Found sideways: the branch is also untested, since flipping is_none_or to is_some_and at invariants.rs:1335 kills no test, so nothing in the suite constructs a Constant segment with end_tempo: None)
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 AccidentalDefinitionScoreAccidentalExtensionsScoreTuningContext. 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 0028 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 24 (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 typeaccidental_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 themcanvas.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: 2427 (Phase-3 first tranche), 2829 (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 29, 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 1015 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 2433 is strictly less work than 2431 now and 3233 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 3039 — 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 3033 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 3039 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:1443ff (kind table rows 3039) and :1548:1552 (tag table rows 3039) 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 3039 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 3039, 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 2429 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:5578, graph.rs:819) names the group a staff belongs to; StaffGroup.members (core_spec.tex:4231, graph.rs:1614) 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:1126), a group's members are declared (:1135) — 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:4117 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, 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. Sequence it after G3b so an invariant append is not competing with G3b's invariant 20
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 B2B3). 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:1220ff, 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 :400ff — 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:205ff 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 1618), 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 1618 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: 2429 (phase3_tag_discriminants_are_golden, payload.rs:2728), 34 (one assertion inside a G2b reduction test, reduce.rs:12744), 3538 (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, 215, 1723, and 3033 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, 2429, 34, 3538, 39); unlocked: 26 (0, 215, 1723, 3033). 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 accidentalops/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 --workspace1558 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 WallClocks, 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-Events 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