epiphany/spec/CONTRACT_P13S18_MATRIX.md

362 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Contract — P13-S18: the invariant-20 outcome matrix
**Status:** RATIFIED.
**Rung type:** diagnostic and bookkeeping. **No behaviour change.** No graph that
violates invariant 20 today may stop violating it, and no graph that passes today
may start violating it. If the work suggests otherwise, **stop** (§1 pin 6).
---
## §0. What was verified before drafting
Every claim below was read out of the working tree at `cc49533`, not recalled,
and re-confirmed unaffected at `f33673d` (see §4).
1. **`check_measure_meter_consistency` has nine non-success paths**, not the five
an earlier scoping note claimed. Enumerated with line numbers in pin 1.
2. **Only three are abstentions.** The other six are one inapplicability, two
delegations, two vacuities, and one separately-filed deferral (pin 2).
3. **Invariant 10 really does own the two delegated paths.**
`invariants.rs:1220`ff checks that time-signature references resolve *"at
every level a `MeterChange` can appear"* and its loops cover **per-measure**
(`si.measures`, emitting `"measure {:?} time signature {:?} is not
declared"`), **instance-local grids** (`si.local_metric_grid`), the
**region-default grid**, and the **metric time model's own meter sequence**.
4. **P13-S18's "`Measure` *end* anchor" claim is wrong only in its "any", and
two successive scoping notes each overcorrected it.** The first called the
claim simply false; the second called it merely misattributed. The precise
position has three parts, all read out of `measure20_comparable_order`:
- **Same id, same position — `End`↔`End` is comparable *when its offsets
compare*.** c2 requires `ia == ib && pa == pb` and then delegates to
`measure20_offset_order`, which returns `None` for `Musical`↔`WallClock`
(`:2428``:2429`). So "any `Measure` end anchor" is false as written, but
the counter-example is conditional and must be stated that way.
- **Distinct ids — `End` anchors remain incomparable**, because c3 returns
`None` unless `*pa == MeasurePosition::Start` with both offsets `Zero`. A
vector index orders measure *reference points*, not arbitrary points near
them. Distinct-id `End` anchors therefore genuinely do reach **A4, B4 and
B5**, and are a real residue shape.
- **The resolver citation describes the missing duration machinery, not
invariant 20's execution path.** `resolve_anchor`'s `Measure` arm
(`invariants.rs:503``:516`) returns `None` for any `position !=
MeasurePosition::Start` because a measure's length "needs the deferred
decomposition/tempo machinery" — a true statement about *why* the duration
is unavailable. **Invariant 20 never calls it: zero occurrences in
`:2636``:2725`.** The citation explains the underlying gap; it does not
describe how invariant 20 reaches its abstentions.
The `invariants.rs:400` line number is separately stale (§0.7).
5. **P11-C5 is not P13-S18's gate.** `PASS11_WORKLIST.md:159` defines it as the
*"nearest surviving anchor" stand-in* — a re-anchoring proximity metric that
*"resolves when the graph-mutation phase tracks resolved positions"*. The G3b
contract cites it correctly but **narrowly**, for the two-distinct-`Event`s
case, via `PositionOutsideRegion`'s Reserved note (`effect.rs:139``:142`).
The capability P13-S18 waits on — placing on a common timeline **any pair
c1c5 cannot order, and any pair it orders without yielding a usable musical
delta** — is strictly broader and is owned by **no filed candidate**. Pin 10
is the authoritative scoping; this item must not restate it more narrowly.
6. **Step 0 is per measure reference.** `measure20_governing_by_anchor` is
called once per measure with that measure's own `start`, and
`measure20_comparable_order` is a property of the *pair*. An incomparable
meter change therefore disables agreement for every measure whose start is
incomparable **to it** — not for every measure in the instance, when the
instance's measures carry heterogeneous anchor shapes. An earlier scoping
note said "every measure in that instance"; that was wrong and pin 7 locks
the correction behaviourally rather than in prose.
7. **The G3b contract's `invariants.rs:400`ff citation (`:206`) has drifted.**
The resolver it describes now begins at `invariants.rs:466`, with the
`Measure` arm at `:503``:516`. The paragraph's own narrower claim — that
cross-boundary comparison stays unverifiable without the boundary's
duration — remains **correct** and is not touched (pin 9).
---
## §1. Pins
**Pin 1 — the nine paths, and their sites.** These are exhaustive over
`check_measure_meter_consistency` (`invariants.rs:2636`). Any tenth found during
execution is a finding, reported before proceeding.
| Id | Clause | Path | Site |
|---|---|---|---|
| A1 | agreement | `m.time_signature` is `None` | `:2661` |
| A2 | agreement | declared signature does not resolve | `:2662` |
| A3 | agreement | `Governing20::None` | `:2675` |
| A4 | agreement | `Governing20::Indeterminate` | `:2677` |
| B1 | boundary | first measure (`i == 0`) | `:2683` |
| B2 | boundary | governing signature does not resolve | `:2689``:2693` |
| B3 | boundary | `Governing20::None` | `:2717` |
| B4 | boundary | `Governing20::Indeterminate` | `:2719` |
| B5 | boundary | musical delta not computable | `:2713` |
**Pin 2 — the classification, ratified.** Six labels. Every path takes exactly
one.
| Id | Class | Why |
|---|---|---|
| A1 | **inapplicable** | no declared signature can disagree with anything; pin 9b makes `None` avoid *only* this clause, never the boundary clause |
| A2 | **delegated** | invariant 10, per-measure arm (§0.3) |
| A3 | **vacuous** | no governing signature exists to disagree with (pin 6c case 1) |
| A4 | **genuine abstention** | the relation cannot place a candidate |
| B1 | **P13-S19** | the pickup/anacrusis deferral, already filed |
| B2 | **delegated** | invariant 10, grid-level arms (§0.3) |
| B3 | **vacuous** | pin 6c case 1 |
| B4 | **genuine abstention** | as A4 |
| B5 | **genuine abstention** | order without distance |
**Exactly three genuine abstentions: A4, B4, B5.** This is the whole point of
the rung — P13-S18 currently presents all nine as one undifferentiated residue,
which both overstates the gap and hides which part of it is real.
**Pin 3 — delegation must be verified, not asserted.** A2 and B2 are classed
`delegated` only if some *other* invariant actually reports the condition. The
matrix must show, for each, that invariant 10 emits a violation on the same
graph — and M7/M8 must show that removing invariant 10's arm makes the condition
go **unreported by the whole suite**, not merely by invariant 20. A delegation
nobody discharges is an abstention wearing a better name.
**Pin 4 — the outcome matrix.** Rows are representative anchor shapes, columns
are the two clauses, each cell names the path id that fired and its class.
Minimum shapes:
| Shape | Description |
|---|---|
| S1 | `WallClock` start, `WallClock`-anchored meter changes |
| S2 | `WallClock` start, `Region`-anchored meter changes |
| S3 | `Region` same id, same edge, `Musical` offsets — fully decidable |
| S4 | `Measure` **same id, `pos: End` on both sides**, `Musical` offsets (c2) |
| S5 | `Measure` distinct ids, `Start`, `Zero` (c3) |
| S6 | `Event` same id, **live** event, `Musical` offsets (c1) |
| S7 | `Event` distinct ids, otherwise identical to S6 |
| S8 | matching referent, **differing** `pos`/`edge` selector |
| S9 | one instance, **heterogeneous** measure anchors (pin 7) |
**S6 and S7 are a positive/negative pair and neither may be dropped.**
`Measure.start` and `MeterChange.anchor` are unrestricted `TimeAnchor`s, so
`Event`-anchored measures are valid rather than hypothetical. S6 proves such a
measure genuinely reaches c1. S7 changes **only the referent identity** and
isolates the distinct-`Event` fall-through — which is precisely the case the
retained P11-C5 citation at `CONTRACT_GENESIS_G3B_MEASURE.md:223` exists for.
Both use live events and `Musical` offsets.
**S4 is the row that falsifies "any `End`", and it must do so by observation.**
It is deliberately an `End`↔`End` pair on the **same** measure id with `Musical`
offsets — c2's exact shape — and its meter changes carry the same anchor form,
so both clauses reach a decision. It is therefore `D`/`D` and, per the rule
below, deliberately wrong. S5 supplies the contrast on distinct ids. A contract
that only *stated* "same-id `End` is fine" would be repeating the mistake this
pin exists to correct.
**Every cell holds either a path id (A1B5) or `D`.** A fully comparable shape
takes *none* of the nine paths — it decides — and a cell left blank because
"nothing fired" is indistinguishable from a cell nobody looked at. **Every `D`
fixture must be deliberately wrong**, so that the emitted violation is what
proves the clause decided. S3 and S6 are `D` in both columns; an S6 that emits
nothing has demonstrated nothing.
Every cell must be **observed**, never derived. A cell filled in by reading the
match arms and reasoning about which one wins is unsigned.
**Pin 5 — the `WallClock` finding, and the split an earlier draft got wrong.**
`valuegen::measure` (`ops/src/valuegen.rs:447`) anchors starts to
`TimeAnchor::WallClock`. An earlier draft of this contract said that shape makes
**both** clauses abstain. It does not, and the difference is the meter changes'
anchors, not the measures':
- **S1 — `WallClock` measures, `WallClock` meter changes.** The anchors are
mutually comparable under c5, so step 0 passes and a unique maximum is found:
**agreement decides.** But `measure20_musical_delta` never returns a
`WallClock` delta (`:2527`, `AnchorOffset::WallClock(_) => None`), so the
boundary clause takes **B5**.
- **S2 — `WallClock` measures, `Region` meter changes.** `comparable_order`
falls through to `_ => None`, so step 0 returns `Indeterminate`: agreement
takes **A4** and the boundary clause takes **B4***not* B5, because the
`Governing20` match short-circuits before the delta is ever computed.
**A `WallClock` start does not by itself disable both clauses.** The report must
state the split, must **not** claim `WallClock` is the dominant shape in real
scores (measure starts are author-supplied), and may claim only that it is the
shape this repository's fixture generator emits.
**Pin 6 — the hard stop.** No widening of `measure20_comparable_order` or
`measure20_musical_delta`. No new violation. No change to which graphs violate
invariant 20. The G3b contract holds that defining a new comparability relation
*"is a specification question this rung has no authority over"*, and that is
unchanged. **If the classification work suggests a behaviour change — including
"A3 should really be a violation" or "B2 should be reported here rather than by
10" — stop, report, and let it be split into a separately ratified semantic
rung.** Do not implement it here, and do not soften a class label to avoid
raising it.
**Pin 7 — step-0 scope, locked behaviourally.** S9 must contain one instance
whose measures carry heterogeneous anchor shapes, such that **one measure's
agreement clause abstains via A4 while another measure's agreement clause in the
same instance reaches a decision**. Prose asserting this is not enough; the row
is what forbids the "one incomparable change disables the whole instance"
overstatement from reappearing.
**Pin 8 — the P13-S18 correction.** Five defects, all of them mine:
1. three abstention shapes → **nine paths**, classified;
2. **correct**, not delete, the "`Measure` *end* anchor" claim, to all three
parts of §0.4: same-id `End`↔`End` is comparable under c2 **when its offsets
compare** — `measure20_offset_order` rejects `Musical`↔`WallClock` — so
"any" is false but conditionally so; distinct-id `End` anchors **are**
incomparable and do reach A4/B4/B5; and the resolver citation names the
missing duration machinery rather than invariant 20's execution path;
3. re-gate: not P11-C5, but the broader capability filed as P13-S23 (pin 10);
4. name A2/B2 as delegated to invariant 10 with its line, so the entry stops
counting them as gap;
5. state the S1 finding.
The entry stays **open** — A4, B4 and B5 remain real — but open at its true size.
**Pin 9 — the G3b contract corrections, two of them and both narrow.**
1. **`:347`** says the residue's closure needs "P11-C5 resolved positions". That
is the overbroad claim. Correct it to name P13-S23 as the owner of the
general case.
2. **`:206`** cites `invariants.rs:400`ff for the prototype anchor resolver. The
resolver now begins at `:466` with its `Measure` arm at `:503``:516`.
**Correct the line number only.** The paragraph's claim — that cross-boundary
comparison stays unverifiable without the boundary's own duration — is
correct, is narrower than P13-S18's use of it, and must survive verbatim.
**`:223`'s citation is correct and stays.** It cites P11-C5 for the
two-distinct-`Event`s case specifically, which is genuinely what
`PositionOutsideRegion`'s Reserved note (`effect.rs:139``:142`) covers.
**The contract is RATIFIED and its pins are not reopened**; both edits are
citation repairs, and neither changes a ratified claim.
**Pin 10 — P13-S23.** File the unowned capability: *placing anchor pairs on a
common timeline and measuring musical distance along it, wherever **c1c5 do not
already yield both**.* Two disjoint deficiencies, and the candidate owns both:
1. **No ordering.** The pair is not comparable at all. The failure may be in the
**referent** (distinct `Event` ids, distinct `Measure` ids outside c3's
`Start`+`Zero` restriction), the **variant or selector** (`Event`↔`Measure`,
`Measure`↔`Region`, differing `pos`/`edge`), or the **clock**
(`Musical`↔`WallClock`, including inside `measure20_offset_order`). This is
what 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, which is an order
and never a distance; **c5** compares two `WallClock`s, and
`measure20_musical_delta` never returns a `WallClock` delta (`:2527`). This
is what **B5** is made of.
**Scoping this as "not directly comparable under c1c5" would have excluded B5
entirely** — S5 is c3-comparable and S1 is c5-comparable, and both reach B5 — so
the candidate would have disowned a third of the residue it is filed to own. An
earlier draft also said "anchors of differing shapes", which additionally
excluded the same-variant cases (distinct `Event`s, distinct `Measure`s).
Explicitly **broader than P11-C5**, with the distinction stated: P11-C5 is a
re-anchoring proximity metric that waits on resolved positions; P13-S23 is the
timeline itself. Name its dependents: invariant 20's A4/B4/B5, and
`PositionOutsideRegion`'s Reserved status. Filed **open**; no code owed.
---
## §2. Touch table
| # | File | Change |
|---|---|---|
| 1 | `crates/epiphany-core/src/invariants.rs` | **tests only**, plus a doc-comment classification on `check_measure_meter_consistency` naming each of the nine paths and its class. The doc comment is not normative and changes no behaviour. **No production logic edit of any kind.** |
| 2 | `spec/PASS13_CANDIDATES.md` | P13-S18 corrected per pin 8; **P13-S23** filed per pin 10 |
| 3 | `spec/CONTRACT_GENESIS_G3B_MEASURE.md` | `:347` (overbroad P11-C5 claim) and `:206` (stale resolver line number), per pin 9. `:223` untouched |
| 4 | this contract | DRAFT → RATIFIED |
**No `.tex` change and no PDF.** The classification describes existing
behaviour; it makes no normative claim. **If the classification turns out to
contradict `core_spec.tex`'s invariant-20 text, that is a finding — stop and
report it** (pin 6), because a spec correction is a semantic rung.
**No `DECISIONS.md` entry.** Consistent with the P13-S15 ruling: test coverage
and ledger bookkeeping, not a semantic ruling.
---
## §3. Mutation plan
Every mutation is a specific edit to a specific site, each **observed** red and
restored by hand-editing back. Never `git checkout`, never `git stash`.
| M | Edit | Row it signs |
|---|---|---|
| M1 | make A4's graph comparable (swap the incomparable meter change's anchor to the measure's own shape) | A4 is an abstention, not a pass: the agreement violation **appears** |
| M2 | as M1 for B4 | B4 is an abstention |
| M3 | swap S5's two measure starts to `Region` same-id `Musical` offsets | B5 is an abstention: the delta becomes computable and the boundary violation appears |
| M4 | give A1's measure a resolving `time_signature` that disagrees | A1 is inapplicable, not concealing: the violation appears |
| M5 | make **S2**'s meter-change anchors `WallClock`, matching its measures | pin 5's split: S2's cell pair moves **A4/B4 → D/B5** — agreement stops abstaining and decides, and the boundary clause moves from indeterminate selection to incomputable delta. Writing this as "A4/B4 → B5" is wrong: A4 is an agreement path and cannot appear in the boundary column. **S2 is the target, not S1** — S1's agreement clause already decides, so no mutation of S1 could show it begin to |
| M6 | in S9, give the second measure the first's incomparable anchor shape | pin 7: its agreement clause stops deciding — and **only** then |
| M7 | delete invariant 10's per-measure time-signature arm (`:1250`ff) | A2 is delegated: the condition becomes **unreported by the whole suite** |
| M8 | delete invariant 10's instance-local-grid arm | B2 is delegated, same standard |
| M9 | make B1's measure a non-first measure | B1 is the P13-S19 deferral, not a silent pass |
| M10 | remove B3's emptying filter so a governing signature exists and disagrees | B3 is vacuity, not concealment |
**Ten mutations.** Report the observed count; any deviation is a finding.
**Anti-traps, all earned in this session.** A mutation that does not compile
signs nothing. A mutation in an operation that runs *before* the asserted state
cannot reach it. A guard written against an index that structurally cannot hold
the referent is born green. Transaction-block members reduce atomically at the
*first* member's canonical position. Envelope counter gaps make operations
permanently pending, which makes fixtures silently vacuous. **And the one this
rung is most exposed to: a matrix cell that agrees with the prediction is the
easiest place in the world to stop looking.** Each cell must fail loudly if the
path it names is not the path taken — assert the *outcome*, and construct the
graph so no other path could have produced it.
---
## §4. Gate
- `cargo test --workspace` — baseline **1541 / 0**, confirmed at `f33673d`
(the commits between `cc49533` and it are `spikes/`-only, a separate
workspace, so the root count is unmoved); delta explained.
- `cargo clippy --workspace --all-targets` — zero warnings.
- `cargo fmt -p epiphany-core -- --check` — clean. **Never `cargo fmt --all`**;
it crosses into the `spikes/` workspace through path dependencies.
- All **10** mutations observed red and hand-restored.
- **A diff check that `check_measure_meter_consistency`'s executable body is
byte-identical to `cc49533`** — the doc comment may grow; not one line of
logic may move. This is the rung's central claim and must be mechanically
demonstrated, not asserted.
- `git diff --cached --check` clean; nothing from `spikes/` or `.claude/`
staged.
## §5. Whitespace and staging
1. Stage the touch-table files explicitly. **Never `git add -A`.**
2. `git diff --cached --check` — catches staged and formerly-untracked files.
3. Commit.
4. `git diff --check <parent>..HEAD -- crates/ spec/` — path-scoped.
**A concurrent session commits to this repository.** Re-check `HEAD` before
committing, and never run `git reset`, `git restore --staged`, `git checkout`,
or `git stash` against the shared index.
## §6. Boundary — unchanged and absolute
MUST NOT be read, written, or staged: `spec/PLAN_EDITOR_APP.md`,
`spec/CONTRACT_EDITOR_*.md`, `spec/ANALYSIS_GENESIS_PERSISTENCE.md`,
`spec/ANALYSIS_TEXT_RUN_PRIMITIVES.md`, `spec/DRAFT_T4_FIXTURE_RECIPE.md`,
`crates/epiphany-editor-gui/goldens/*.png`, `crates/epiphany-render-svg/**`,
`crates/epiphany-glyphs/**`,
`crates/epiphany-testkit/benches/editor_pipeline.rs`, the entire `spikes/`
tree, the root `Cargo.toml` change, `.claude/worktrees/`.
## §7. Report requirements
- The **full matrix — nine shapes × two clauses, 18 cells** — every cell
observed, each holding a path id or `D`, with the test name that observed it.
- All **10** mutations with their observed failing test names.
- Explicit confirmation that `check_measure_meter_consistency`'s logic is
byte-identical, with the command that showed it.
- Explicit confirmation that **no new violation** is emitted anywhere, and that
the workspace count moved only by the tests added.
- For A2 and B2: the invariant-10 violation text observed, and the M7/M8 result
showing the condition otherwise goes unreported.
- **Anything the contract did not anticipate** — a tenth path, a cell that
contradicts pin 2, or anything suggesting a behaviour change. Report it and
**stop**; do not implement it.