diff --git a/crates/epiphany-ops/src/payload.rs b/crates/epiphany-ops/src/payload.rs index d90e622..c099aa1 100644 --- a/crates/epiphany-ops/src/payload.rs +++ b/crates/epiphany-ops/src/payload.rs @@ -2198,12 +2198,14 @@ mod tests { // that class of stale hand-maintained table. Extended to 40 entries and // closed 2026-07-30. `OperationKind::discriminant()` is a hand-written // match, so a discriminant can be edited there without touching the - // enum; the table below is what makes that edit fail. The sibling tag - // half needs no such extension: `the_tag_vocabulary_is_complete` is - // derived from `OperationKindTag::PAYLOAD_FREE` with a computed bound - // rather than a spelled one, so it already covers every tag including - // 30..=39. Adding a kind here is not optional bookkeeping — a kind - // appended without a row rediscovers P13-S15. + // enum; the table below is what makes that edit fail. The tag half is + // not exempt from an equivalent guard: its derived tests (among them + // `the_tag_vocabulary_is_complete`) prove the vocabulary's + // completeness, density and round-trip — never which byte a tag + // holds — and `tag_wire_discriminants_are_golden` (this file) is what + // pins that mapping (`spec/CONTRACT_P13S22_TAGLOCK.md`). Adding a kind + // here is not optional bookkeeping — a kind appended without a row + // rediscovers P13-S15. use crate::valuegen; use epiphany_core::{MusicalDuration, MusicalPosition, TimeSignatureId}; @@ -2725,20 +2727,168 @@ mod tests { } #[test] - fn phase3_tag_discriminants_are_golden() { - // GOLDEN LOCK (Phase-3 first tranche): appended past the ratified - // 0..=23; the values below never change. - for (tag, expected) in [ - (OperationKindTag::InsertStaff, 24u8), + fn tag_wire_discriminants_are_golden() { + // GOLDEN LOCK, tag side (`spec/CONTRACT_P13S22_TAGLOCK.md`, + // disposition A). `operation_kind_tag_vocabulary!` (`:695`ff) + // generates `discriminant()`, `from_discriminant()`, `PAYLOAD_FREE` + // and friends from ONE list, so every existing derived test that + // pairs a tag with its byte via + // `PAYLOAD_FREE.iter().map(|t| (t, t.discriminant()))` asserts + // `$disc == $disc` — it IS the macro, and cannot disagree with it. + // The table below is hand-typed, transcribed independently by + // reading the macro invocation (`:754`–`:793`) and + // `REGISTERED_TAG_DISCRIMINANT` (`:679`), so it is the tag side's + // first genuinely second statement of the mapping. + // + // FORBIDDEN ABSOLUTELY: deriving any row, the length, or the + // ordering from `PAYLOAD_FREE`, `discriminant()`, + // `from_discriminant()`, or any other macro output — the same + // warning appears at `:2638`–`:2639`, + // `testkit/tests/text_projection_grammar.rs:312`–`:314`, and + // `layout-ir/src/barrier.rs:1171`–`:1172`. A table so derived is + // born green. + // + // Coverage vs. association, and why the split below is not + // circular: the block asserting the table's tag set and byte set + // match the vocabulary is legitimately COMPUTED — a 40-long array + // does not by itself prove forty distinct tags, since duplicate + // rows would satisfy the length. That block proves the table is + // TOTAL over the vocabulary; it says NOTHING about which tag holds + // which byte, which only the literal rows below state. + // + // Redundancy is intended: this table duplicates the tag→byte + // subclaim already made by `payload.rs:3086`, `reduce.rs:12744` and + // `reduce.rs:15941`. Those three stay — each is the byte-half of a + // paired kind-and-tag statement in one place + // (`…kind_and_tag_discriminant_are_39`, `…kind_and_tag_are_both_34`, + // `…in_both_spaces`) — do not "consolidate" them into this table and + // break that locality. This table replaces only + // `phase3_tag_discriminants_are_golden` (retired), whose entire + // subject was tag→byte for 24–29 and nothing else. + let table: [(OperationKindTag, u8); 40] = [ + (OperationKindTag::InsertEvent, 0), + (OperationKindTag::DeleteEvent, 1), + (OperationKindTag::ModifyEvent, 2), + (OperationKindTag::RespellPitch, 3), + (OperationKindTag::Transpose, 4), + (OperationKindTag::CreateCrossCutting, 5), + (OperationKindTag::DeleteCrossCutting, 6), + (OperationKindTag::ModifyCrossCutting, 7), + (OperationKindTag::ChangeRegionTimeModel, 8), + (OperationKindTag::InsertRegion, 9), + (OperationKindTag::DeleteRegion, 10), + (OperationKindTag::InsertStaffInstance, 11), + (OperationKindTag::DeleteStaffInstance, 12), + (OperationKindTag::SetUserSystemBreak, 13), + (OperationKindTag::SetUserPageBreak, 14), + (OperationKindTag::DeclareTransaction, 15), + ( + OperationKindTag::Registered(OperationKindRegistryId( + 0x0102_0304_0506_0708_090A_0B0C_0D0E_0F10, + )), + 16, + ), + (OperationKindTag::InsertIdentifiedPitch, 17), + (OperationKindTag::DeleteIdentifiedPitch, 18), + (OperationKindTag::ModifyIdentifiedPitch, 19), + (OperationKindTag::CreateVoice, 20), + (OperationKindTag::DeleteVoice, 21), + (OperationKindTag::SetMetadata, 22), + (OperationKindTag::SetMetricGrid, 23), + (OperationKindTag::InsertStaff, 24), (OperationKindTag::SetTimeSignature, 25), (OperationKindTag::SetTempoSegment, 26), (OperationKindTag::SetStaffLayout, 27), - // Schema-major-2 revision (repeat authoring). (OperationKindTag::CreateRepeatStructure, 28), (OperationKindTag::DeleteRepeatStructure, 29), - ] { - assert_eq!(tag.discriminant(), expected); - assert_eq!(tag.to_canonical_bytes(), vec![expected]); + (OperationKindTag::TransposeInterval, 30), + (OperationKindTag::CreateInstrument, 31), + (OperationKindTag::SetCanvasLayoutDefaults, 32), + (OperationKindTag::SetSpellingPrecedence, 33), + (OperationKindTag::SetTuningContext, 34), + (OperationKindTag::CreateStaffGroup, 35), + (OperationKindTag::CreatePartDefinition, 36), + (OperationKindTag::CreateAnalysisLayer, 37), + (OperationKindTag::CreateView, 38), + (OperationKindTag::CreateMeasure, 39), + ]; + + // --- Coverage (derived; see the header comment above) --- + { + let mut expected: Vec = OperationKindTag::PAYLOAD_FREE.to_vec(); + expected.push(OperationKindTag::Registered(OperationKindRegistryId(0))); + assert_eq!( + expected.len(), + 40, + "sanity: the vocabulary itself is 40 wide" + ); + + // Tag-set equality with `Registered`'s payload id normalized + // away: only its *presence* is a vocabulary fact, the id is + // per-message data the table's own row (above) does not pin. + let normalize = |t: &OperationKindTag| match t { + OperationKindTag::Registered(_) => { + OperationKindTag::Registered(OperationKindRegistryId(0)) + } + other => *other, + }; + let table_tags: std::collections::BTreeSet = + table.iter().map(|(t, _)| normalize(t)).collect(); + let expected_tags: std::collections::BTreeSet = + expected.iter().map(normalize).collect(); + assert_eq!( + table_tags, expected_tags, + "the table's tag set must equal PAYLOAD_FREE union {{Registered}}, each exactly once" + ); + assert_eq!( + table_tags.len(), + 40, + "duplicate rows would satisfy the length without covering the vocabulary" + ); + + let byte_set: std::collections::BTreeSet = table.iter().map(|(_, b)| *b).collect(); + assert_eq!( + byte_set, + (0u8..40).collect::>(), + "the table's byte set must be exactly 0..=39, no gaps, no repeats" + ); + } + + // --- Association: the payload-free / `Registered` asymmetry --- + // `Registered` (byte 16) carries a 16-byte registry id, so its + // canonical encoding is 17 bytes; asserting the WHOLE vector against + // `vec![16]` would fail on encoding length, not on the mapping this + // test exists to pin, so it is checked by `[0]` only. Every other + // (payload-free) tag's encoding is exactly one byte, and asserting + // the whole vector — not just `[0]` — is what also proves that + // length-1 property: the property `phase3_tag_discriminants_are_golden` + // had and the kind-side idiom (`[0]` only) does not. `Registered` is + // the sole non-payload-free tag in this table. + for (tag, expected) in &table { + assert_eq!( + tag.discriminant(), + *expected, + "wire discriminant for {:?} moved — canonical encodings are append-only", + tag, + ); + match tag { + OperationKindTag::Registered(_) => { + assert_eq!( + tag.to_canonical_bytes()[0], + *expected, + "wire discriminant for {:?} moved — canonical encodings are append-only", + tag, + ); + } + _ => { + assert_eq!( + tag.to_canonical_bytes(), + vec![*expected], + "wire discriminant for {:?} moved — canonical encodings are append-only", + tag, + ); + } + } } } diff --git a/spec/PASS13_CANDIDATES.md b/spec/PASS13_CANDIDATES.md index 99e4f4a..6582a7e 100644 --- a/spec/PASS13_CANDIDATES.md +++ b/spec/PASS13_CANDIDATES.md @@ -100,5 +100,5 @@ visible — a value with a wire form and no canonical carrier to reach it.) | 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) | **open.** **Not a live incorrectness, and — corrected — not an undefended one either:** every tag discriminant is currently correct, `binary_format.tex:1548`–`:1552` carries the mapping normatively, and no probe escaped. **The gap is intent and diagnosis, not exposure.** A permutation fails as *"the committed corpus does not match the generator"* or *"blob bytes are not golden"* — artifacts that regenerate, and whose failure reads as corpus drift rather than as a moved wire discriminant. **The corpus's tag coverage is deliberate, not accidental** — `ops/src/vectors.rs:201`–`:204` emits one row per tag straight from the vocabulary and names the reason: *"A hand-picked subset is how `TransposeInterval` shipped encoding to a byte its own decoder rejected: the corpus never named it."* **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. Every one of the 40 rows is identical under a permutation; what actually moves is their **order**, since `PAYLOAD_FREE` is declaration order. That is why the 32↔33 probe failed, and it is exactly the property `Registered`'s row has and the numbered rows do not — its row is named for the variant. **The gap is that one row's discipline is not the vocabulary's.** **Likely shape of the repair:** one table over the whole tag vocabulary asserting tag → byte directly, superseding the six scattered fragments rather than adding a seventh — a seventh band is how this got here — so that moving a tag fails by *name*. **Probe design note for whoever takes this:** do not reuse 32↔33 or 1↔2; both are now known to be caught, and a probe that fails proves the lock exists, not that it is missing. The mutation that would sign this rung is the **inverse**: delete the proposed table and show that some permutation then passes, or show the surviving failure names no tag. **Its own rung, not a rider** | +| 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 |