148 KiB
Contract — P13-S29: the violation tag stops multiplexing
STATUS: LANDED by this commit.
Ratified 2026-08-12 on the authority of the repository owner, the final whole-artifact review returning zero findings. Review-round records accumulate above §0.
DATED HISTORICAL RECORD — the eighteen review-round blocks above §0 (revisions A–R) are an account of what was found and decided between drafting and ratification on 2026-08-12. They state no current condition. The pins as ratified are §1's; §6's amendment 1 corrects two of §3's radius cells.
THE PINS ARE FROZEN. They may be executed, not edited. A defect found during execution is reported, not patched in place — if it needs a pin change, that is its own amendment with its own review round.
Owning candidate: P13-S29, filed by
spec/CONTRACT_P13S26_INVARIANT10_SURFACE.md pin 7.
Rung class: behaviour change. A public type is renamed, a public field
changes type, a targeted selector deliberately returns less than it does today,
a public Display changes for one arm, and a normative requirement is minted.
REVISION R — review round 18. Four blockers, one smaller, two cleanups. ALL ACCEPTED.
- [P1] Assertion 3's slice was ambiguous and destructive. The module header
is a 28-line
//!run with breaks at:2,:9,:13,:22; asserting the whole run equals a four-line paragraph would have deleted 24 lines of retained header. Fixed — insertion and boundaries are pinned. Placed after the title paragraph rather than prepended: prepending would make its first sentence the module's rustdoc summary line, displacing "The Chapter 5 graph invariants". - [P1] Assertions 4 and 5 did not have the boundaries they claimed — the
rustdocs are not "immediately preceding" their declarations; the derive
attribute sits between. Fixed — a three-step algorithm: declaration, its
derive line, then the
///block above that. - [P1] Pin 1a and the pinned prose disagreed. Pin 1a promises all three blocks state the two-arm split and that a requirement failure is not an invariant failure; the struct rustdoc stated neither and the enum rustdoc only the first. Fixed by strengthening the blocks, so pin 1a's claim is true of each individually, not merely of the three collectively.
- [P1] M20h, M20j and M20k were not applicable mutations — "append a sentence" names no sentence. Fixed — each pins its exact added line, as M20f, M20g and M20i already did.
- [P2] Revision Q corrected M20f's rationale but not the two live statements it contradicts. Fixed — both now say a call whose result is asserted would widen M3/M9's radii; an ignored one would not.
Two cleanups landed with them:
invariants.rs:8carries[`InvariantViolation`]in retained header prose. Gate 6 forbids that token in Rust code, so leaving it unpinned made gate 6 unsatisfiable. Pin 9 now owns its rename.- "Every assertion has an ADDITIVE discriminator" was false — M20i is a reordering. Now "non-deletion".
The round also noted my tally of 11 M20-rows was 12; the contract states no such count, which is why it did not stale.
REVISION Q — review round 17. Four blockers, one smaller. ALL ACCEPTED.
- [P1] M20f did not compile and did not demonstrate its stated radius. The
file imports only the three pinned names, and
valid_scorelives atepiphany_core::generators::valid_score(lib.rs:89,generators.rs:96). And its result was discarded, so even qualified it would not have put the test in M3's or M9's radius. Fixed — the call is fully qualified, and the rationale is corrected: M20f signs the file-content prohibition; the radius concern is why that prohibition exists, not what M20f exhibits. - [P1] Assertions 3–5 had no pinned expected text. Pin 1a states a requirement; a requirement is not an expected value, and an executor would have written the production string and copied it into the test — asserting nothing. Fixed — the module paragraph and both rustdocs are pinned as raw source.
- [P1] Assertions 1–5 had no extraction boundaries, and assertion 1 is
unsound without one:
#[derive(Clone, PartialEq, Eq, Debug)]occurs twice ininvariants.rs— on the violation type (:249) and onDeferredCheck(:286), which §0.5 deliberately retains — so a file-wide search survives M20. Fixed — every slice is symbolic and anchored to the declaration it precedes. - [P1] Equality was unsigned for assertions 2–4. M20g and M20h discriminated only 1 and 5; the rest were deletion-only, which a needle or subset implementation satisfies. Fixed — M20i, M20j, M20k. M20i reorders rather than adds: every trait this enum can usefully derive is already there, and a per-trait needle check passes on a reordering while equality fails.
- [P2] Finding 5 of round 16 reached the touch row but not pin 1b's premise.
Fixed — the pin now claims absence of a complete explicit three-name
observer, not absence of any: migrated
score_graph.rsdoes resolveViolationKindthrough the root.
REVISION P — review round 16. Three blockers, two smaller. ALL ACCEPTED.
- [P1] Pin 1b's no-call constraint was unobserved. Assertion 6 checked only
the import line, so the body could start calling
check_requirementwhile every mutation and gate behaved as expected — silently widening M3's and M9's radii, which is exactly what the type-level constraint exists to prevent. Fixed — assertion 6 is whole-file equality against the fenced source, and M20f adds a call while keeping the imports intact. - [P1]
production_source()is not reachable where pin 1b put its guard. It is a privatefnofmod g3a_tests(invariants.rs:4679), so a guard "beside pin 10's tests" cannot call it and the prescription was not literally implementable. Fixed — the guard is placed insidemod g3a_tests, and the two alternatives (a second slicing helper, widening that one's visibility) are named and declined as surface for no gain. - [P1] Assertions 1–5 were not fully signed. M20/M20a remove a trait;
M20b–d delete whole prose units — all of which a guard that merely searches
for
Eq,Hashor one phrase satisfies, while failing every promise the pin makes. Fixed — all six assertions are equalities, and M20g (adds a trait) and M20h (appends a sentence) are the discriminators: a subset or needle check accepts both; only equality rejects them. - [P2] Assertion 6 restated its import in the wrong register — an inline Markdown copy wrapped across source lines, against the one-line Rust in the fenced block. Fixed — the fenced source is its sole authority, and assertion 6 no longer respells it.
- [P2] Touch row 4c overstated exclusivity.
tests/score_graph.rsis an integration test too, and once its.invariantcomparisons migrate it must resolveViolationKindfrom the root,invariantsbeing private. Fixed — 4c is the unique explicit three-name observer, not the only observer of the re-export.
REVISION O — review round 15. Three blockers, three smaller. ALL ACCEPTED.
- [P1] M19 could not produce its promised failure.
mod invariantsis private (lib.rs:76), so deleting a name from the root re-export is an unresolved import — a compile error, which observes nothing and which gate 7 cannot record as a failing assertion. The mutation's claim that the function "remains public at its module path" was false for the same reason. Fixed — M19 is withdrawn; the re-export is compiler-observed, in the same class as pin 1's rename. What a mutation can reach is whether the test imports all three names, which is now assertion 6 of the source guard, signed by M20e. - [P1] Pin 1b's source guard was one-fifth signed. M20 exercised one derive
line; a guard implementing only that passed while omitting
ViolationKind's derives, the module header, and both rustdocs. Fixed — six independent assertions, signed by M20 and M20a–M20e. M20a removesHashrather thanOrd, becauseOrdwould not compile — and a compile error observes nothing. - [P1] Pin 1b named no locations, and the touch table did not cover it.
Fixed — the integration test is
crates/epiphany-core/tests/public_surface.rs(new touch row 4c), the source guard lands ininvariants.rs(row 1 updated), and the integration test is pinned type-level only: a function-item coercion and amatches!arm, calling nothing. Invokingcheck_requirementthere would silently widen M3's and M9's radii to include it. - [P2] The README propagation fix stopped one hop short again. Pin 9 still reproduced the cell inline while pin 9a was meant to be sole authority. Fixed — pin 9 defers.
- [P2] Pin 1b created new landing-time falsehoods — "no test or gate reads them" and "omitting the re-export leaves the suite green" become false when pin 1b lands. Fixed — scoped to the ratified input, as pin 13 now is.
- [P2] §3's observer inventory was stale, naming pins 3a, 10 and 11 only. Fixed — two classes: behavioural/classification (3a, 10, 11) and structural, reading declarations rather than running the checker (1b, 2, 8).
REVISION N — review round 14. Four blockers, three smaller. ALL ACCEPTED.
- [P1] M18 was named in pin 2 but absent from §3 — and I deleted it.
Revision M replaced the span from
M17·C1toM8to insert the measured radii, and M18 sat inside that span. Gate 7 covers §3's mutations, so a mutation named only in a pin is a mutation nothing runs. Restored, with that note in its cell. A span replacement is how I edit; this is the first time it silently removed a neighbour, and it did so in the round that was correcting radii. - [P1] M7d was not compile-complete.
violating_scorematches exhaustively onGraphInvariant(generators.rs:502), so a variant without an arm is a compile error, which observes nothing. Fixed — M7d now pins aviolating_scorearm returning an unchanged valid score, while still omitting the variant fromall(). - [P1] Parts of the public API had no observer. Pin 1a's derives and
rustdocs were prose, and touch row 2's root re-export could be omitted with
the suite green, since every pinned caller lives inside
invariants.rs. Fixed — pin 1b: an integration test importing the three names fromepiphany_core's root (a unit test cannot observe this — it reaches the module path regardless), plus a derives/rustdoc source guard. Signed by M19 and M20. - [P1] The invariant-side
Displayfixture was unpinned, and the choice decides which cell the test lands in — C1's, C2/C3's, M11's, or none. Fixed — pinned to a reversed aleatoric bound, acquired fromcheck_invariants; M11's cell now names the test. - [P2] §3 still said only M2a was measured. Fixed — four cells were: M2a and M17·C1–C3, and every one differed from what static reading predicted.
- [P2] The README oracle had a live duplicate. Pin 9 quoted the cell inline while claiming exactness; pin 9a holds the fenced source. Fixed — pin 9 now defers to pin 9a as sole authority.
- [P2] Pin 13 carried another landing-time falsehood — "the open S29 row exists today" becomes false once execution resolves it. Fixed — scoped to the ratified input.
REVISION M — review round 13. Five blockers. ALL ACCEPTED.
-
[P1] Revision L's two propagation fixes never reached the live pins. The README cell stayed an inline double-backtick construct, and pin 13 still said both additions are compared as "the whole normalised added record" — contradicting its own preceding paragraph. Fixed — pin 9a carries the cell as fenced raw source, and pin 13 states outright that the phrase described gate 15 only and was never true of gate 10.
-
[P1] The C4–C10 cleanliness claim was unsatisfiable for C6/C7. "Only its own requirement-arm violation" contradicts this contract's own finding that C6's fixture emits both. Fixed — the property is "no invariant-arm violation", which is what the pinned assertion always said and what the structural exclusion needs.
-
[P1] M17's radii were guesses, and all three were wrong. Measured with compiling proxies: C1 = 18 existing tests, spanning four
review_fix_testsmodules, the g3b matrix tests and — unguessed — four generator tests, sinceviolating_score(CrossCuttingRefsResolve)stops violating it.f4is not among them: its ghost-anchor is C2's, exactly as the round said. C2 = 1 (f4). C3 = 0 — nothing in the tree carries a dangling tempo end anchor. C2 and C3 now pin their replacement label, since a different one changes whichcheck_requirementretrieves the violation.Two measurement faults are recorded because each nearly became a result: a first proxy hit
E0501— theflagclosure holds a unique borrow ofout— and reported 0 failures for both C2 and C3, a compile error observing nothing, the trap this repository names and that these contracts cite. A second attempt usedcargo test'serror: test failedas a compile signal and misread a real 1-failure result as non-compiling. The third usedcargo build --testsanderror[, and only its numbers are pinned. -
[P1] M7b's radius was false.
every_invariant_has_a_negative_generatoriteratesall(), so replacing an entry means it visits the replacement twice and never asks for the removed variant. A test driven by the mutated list cannot detect an omission from that list. Fixed —graph_invariant_all_is_unchangedalone. -
[P1] Pin 8 froze
all(), not the enum. A fully implemented 22nd variant omitted fromall()leaves the canonical sequence untouched and every gate green — the same hole M18 closes forViolationKind, since a variant nothing enumerates is a variant nothing observes. Fixed — a second, independent declaration inventory read from theenum GraphInvariantsource, signed by M7d.
REVISION L — review round 12. Four behavioural blockers, two propagation defects. ALL ACCEPTED.
- [P1] C1–C3 were tested but never mutation-signed. §3 opened at C4, so an
implementation moving a tempo anchor — or the whole reference surface — to a
requirement arm was observed by tests nothing verified. Fixed —
M17·C1, M17·C2, M17·C3, each a complete reclassification with an
exhaustive radius. As predicted, C2 is the wide one: it reaches the mixed
fixture and the legacy ghost-anchor assertion in
f4_tempo_segment_structural_defects_fire. - [P1] Pin 8's freeze was only count-signed. M7 adds a 22nd entry, so a test
asserting
all().len() == 21satisfies it while leaving unobserved: the same variants reordered, one replaced or duplicated at length 21, and twonumber()arms swapped. Fixed — the test compares a canonical(variant, number)sequence, pinned in full, and M7a/M7b/M7c sign order, membership and mapping separately. - [P1] M2a's radius was unestablished for this rung's prospective fixtures.
The disposable run measured existing consumers; each new C4–C10 test also
carries a negative
check_invariantassertion, and any unrelated invariant tripped by a not-yet-built fixture would add it. Fixed structurally, not by estimate — a fourth pinned assertion on each of the seven requires the fixture's aggregate invariant arm to be empty, which removes the possibility rather than predicting it. - [P1] Pin 2's "exactly two arms" had no observer. A complete third arm with
a
Displayarm and no emitter compiles and leaves every gate green. Fixed —violation_kind_has_exactly_two_armscompares the enum's declaration to pin 2's by equality, and M18 is that third arm. - [P2] The README's "verbatim" cell was still an inline Markdown construct
whose own backticks forced outer delimiters, leaving two readings. Fixed —
fenced raw source, as revision K did for
APPEND. - [P2] Pin 13's final sentence still repeated the superseded method. Fixed — deleted; gate 10 reconstructs from removed-plus-added.
REVISION K — review round 11. Three blockers, two lifecycle/precision defects. ALL ACCEPTED.
- [P1]
APPENDwas pinned in the wrong source register. A Markdown blockquote's>prefixes are part of the raw text and survive whitespace-collapsing, while the ledger row has none — so the reconstruction could never match. Fixed —APPENDis a fenced raw-source block. - [P1] Pin 13's closing paragraph never received the ledger fix, still describing gate 10 as an added-lines check. Fixed — and the paragraph now says why the two gates differ: a new Revision History row genuinely is a run of added lines, while an append to a one-line table row rewrites that line, so only gate 15 can use the added-lines method.
- [P1] The README guard forbade digits, not restated counts. "all
twenty-one variants returned by
GraphInvariant::all()" carries no digit and recreates the defect exactly. Fixed — the invariant-description cell is pinned verbatim and compared by equality, which is the only form that rejects a spelled-out count. - [P2] "The real set is 20" conflicted with M2a's own cell, which also
carries
invariant_selector_discriminates_its_payload. Fixed — 20 is the measured pre-existing/legacy radius; the cell is those 20 plus the new test. - [P2] The
reduce.rscontingency was temporally impossible — re-verified "at execution", but a row added "before dispatch" cannot follow, since pin 12 dispatches at ratification. Fixed by doing the verification now:reduce.rscontains zero.invariantaccesses and bothGraphInvariantuses pass a variant as an argument. Re-verified at revision K, and if it turns out wrong during execution, execution stops and an amendment adds the row.
REVISION J — review round 10. Five blockers. ALL ACCEPTED.
-
[P1] M2a's derivation equated object identity with state identity. Tests mutate the score between assertions —
invariants.rs:3216removes the overlap before its negative;invariants.rs:4457installs the tempo map only after one — so a positive assertion proves nothing about the state the negative selector sees. Andm35was not one binding group at all: its negative useslone, its positive a separately built score (invariants.rs:4939). -
[P1] M2a's candidate surface was incomplete.
!fires(…)is not the surface; the mutation changes everycheck_invariantconsumer — direct zero-result assertions (invariants.rs:5158, the S2/S7/S8 matrix cells) and behavioural ones, notablygenerators.rs:953, whereshrinkuses the selector to choose candidates, plus test-scope consumers inreduce.rs:16586.Both fixed by measuring, as ruled. A disposable implementation —
check_invariantwith its payload filter removed — was run against the full workspace with--no-fail-fast. The measured pre-existing set is 20 tests, all ininvariants::g3b_measure20_tests; M2a's cell is those 20 plusinvariant_selector_discriminates_its_payload. Against revision I's derived 13: 3 in common, 10 false positives, 17 missed — the two sets are nearly disjoint. Static adjacency did not approximate this radius; it produced a different one. The tree was restored and re-verified at 43 suites / 1586 before continuing. -
[P1] The pinned Revision History row had its delimiter backwards. The existing final row already terminates with
\\immediately before\bottomrule(core_spec.tex:17031), so a block beginning with\\produces two separators before the new row and none after. Fixed — no leading delimiter, a terminating one, and the insertion point pinned. -
[P1] The ledger reconstruction could not hold. The S29 line ends with the table's terminal
|, so appending after the whole line writes outside the row. Fixed — the comparison strips the trailing delimiter from both sides and requires it restored on the added line. Gate 10's superseded added-lines-only instruction is replaced by the reconstruction. -
[P1] The README digit gate was unsatisfiable at row scope. The row's final cell necessarily reads
Ch. 5 §"Graph Invariants", so a digit scan over the line rejects the correct edit. Fixed — the numeral prohibition is scoped to the row's invariant-description cell.
REVISION I — review round 9. Three blockers, two smaller. ALL ACCEPTED.
- [P1] M2a had no precommitted failure oracle. A 19-test search surface with the set deferred left gate 7 nothing fixed to compare against. Derived and pinned: 13 named tests. Method, stated in §3: enumerate the 19 negative selector assertions, keep those where a positive assertion falls in the same binding group — the same score instance. Two directions of error are named rather than hidden (a rebinding through an unrecognised helper over-counts; a score violating an invariant no assertion names under-counts), and execution reconciles against the thirteen. This is the one cell derived by reading pre-existing tests rather than constructed from pinned ones, and it is flagged as such — M2a mutates code this rung creates, so it cannot be measured beforehand, only precommitted.
- [P1] Pin 13 promised exact comparisons and supplied semantic summaries.
Fixed — the Revision History row is pinned as verbatim LaTeX, and the
ledger append as verbatim text. The extraction boundaries are pinned too,
and the ledger's is the subtle one: the S29 entry is a single-line table
row, so an append shows as one removed and one added line and the added line
is not the append. The comparison is therefore
added == removed + " " + APPEND, which checks the whole logical record without pinning the pre-existing cell. - [P1] The README repair perpetuated the defect it found. Writing
21for19restates a count that has already staled twice, against the ruleinvariants.rs' own module header states — "no prose here restates it". Fixed — the README becomes symbolic, "all variants returned byGraphInvariant::all()", and gate 16 fails on a digit there. - [P2] M3 was not a complete mutation. Fixed — the predicate is
replaced by
matches!(v.kind, ViolationKind::Invariant(_)), matching every invariant variant and no label, which is what its radius assumed. - [P2] Gate numbering ran 14, 16, 15. Fixed — 1 through 16 in order, which matters because execution evidence is keyed by gate number.
REVISION H — review round 8. Five blockers. ALL ACCEPTED.
- [P1] The new selector tests were not propagated through the radii.
Fixed — M1·C5 and M1·C8 gain
requirement_selector_discriminates_its_payload(built from C5 and C8); M3 and M9 gain it; M11 gainsinvariant_selector_discriminates_its_payload, whose invariant-4 half is the very violation M11 retags. M2a was not "alone":m39_unresolvable_reference_is_invariant_10_onlyassertsfires(CrossCuttingRefsResolve)and!fires(MeasureMeterConsistency), soInvariant(_)breaks it, as it doesmatrix_b2_governing_signature_unresolving_delegated. The candidate surface is enumerated — 19 tests carry a negative selector assertion — and the exact set is measured at execution, since which of them fail depends on whether their score violates anything else. M3a's radius was right and its reason wrong: under M3a alone C6 and C7 still share a label, so M6 is irrelevant to it; what excludes every other test is that each presents one label. - [P1] The equality transition stopped before its consumers. Pin 3 still cited retired stems; M12pre–M13neutral named "positive 1–5"; M14a–c named stem assertions. Fixed. And M15a/M15b could no longer sign the structural assertion, since equality fails first on a mis-sliced block — so that assertion is retired as redundant and both are ordinary equality mutations.
- [P1] Pin 9's prose outcomes had no landing observer — every gate could
pass with all of them omitted. Fixed — gate 16, scoped per site. The
sweep also gained two more:
review_fix_tests_4's module doc calls its tempo subject "invariants", andcheck_invariants' public rustdoc never says it returns both arms — the one place a caller looks before relying on pin 4's comprehensiveness. The "six" tally is removed: the set grew in two consecutive rounds, and a count beside a table that can grow is the defect this family keeps finding. - [P1] Pin 13's gates observed tokens, not records. A truncated or misplaced addition carries the token and passes. Fixed — both gates slice the added record from the staged diff and compare it in normalised whole-record form against pin 13's text.
- [P1] The
Displaytest's acquisition path was unpinned, and the choice decides M3's radius. Fixed — acquisition is fromcheck_invariants, pinned, which keeps the test out of M3's radius as the table assumes.
REVISION G — review round 7. Five blockers. ALL ACCEPTED.
- [P1] S8 neutrality was bypassable by additive prose. Five required
sentences plus three forbidden stems all pass on
A \texttt{Constant} segment's \texttt{end\_tempo} \MUST{} be absent.— stem-free, and it resolves P13-S8. Fixed by taking the cleaner option: pin 3a asserts normalised exact equality against pin 3's pinned source. Pin 3 declares that block complete, so equality is the assertion that matches the pin, and it needs to anticipate nothing — no closed stem inventory can be trusted to anticipate the sentence someone will actually write. M14e is that sentence, and the demonstration. - [P1] Both selectors' payload discrimination was unsigned. Every fixture
presented one variant or one label, so implementations matching
Invariant(_)orRequirement(_)— ignoring the argument — satisfied every assertion. Fixed — two fixtures carrying two invariant variants and two requirement labels, with exact projections, signed by M2a and M3a. M2/M3 signed arm selection only. - [P1] The real-violation
Displayoracle was not independent. An expected string built fromviolation.witnessmoves with the actual when M10 restores the suffix, so the test would not fail. Fixed — an independent assertion that the witness contains noreq:comes first, and the wrapper equality is then built from the witness that assertion has already checked. - [P1] The prose migration stopped at the
/// 10.block. Five more live statements become false — the tempo, aleatoric and accidental headers, the migrated negative's message, andREADME.md:25, which exposes the old type and a count already stale at 19. Fixed — pin 9 covers all six, with touch rows 4a and 4b.DECISIONS.mdis not rewritten: it gains an explicit supersession note, because a decision record that is edited stops recording why the multiplexing was once defensible. - [P1] Both permanent history additions were authorized without pinned content, and their gates were satisfiable by baseline artifacts — the Revision History chapter and the open S29 row both exist today. Fixed — pin 13 pins distinguishing content for each, and gates 10 and 15 are rescoped to added lines in the staged diff. Third occurrence of the touch-row-without-a-pin class in this family, which is why it is pinned rather than tidied.
REVISION F — review round 6. Three blockers, one precision issue. ALL ACCEPTED.
- [P1] Pin 10's pair matrix was incomplete, and M10's radius contradicted
it. Only C6 and C7 had pinned witnesses; C4, C5, C8, C9 and C10 would have
been derived at execution. Fixed — the matrix is closed, and it
distinguishes two forms, because the checker does: C4–C7's witnesses are
fixed strings and are pinned as equalities; C8–C10's are
format!-built and carry ids, so what is pinned is the discriminating substring — enough to separate same-label siblings without pinningDebugoutput a fixture change would move. M10's radius gainsaccidental_incompatible_reports_tuning_requirement: restoring the suffix changes C10's witness, so every observer of that witness fails. - [P1] M6 described an impossible selector outcome. After C6 is mislabelled,
C7 still emits under
req:time:tempo-segment-orderfrom the same fixture, socheck_requirementfor that label is not empty. Fixed — M6 pins the real outcome: the expected C6 pair is absent while C7's pair remains, and both assertions fail on the pair. The old "returns nothing" rationale contradicted the very masking that motivated pairs in revision E. - [P1] M13post did not sign both closing sentences. Positive 4 was one literal holding two independent sentences, and M13post deleted the whole paragraph — so a guard implementing only the first sentence would still fail it and satisfy the plan, leaving the S8-neutrality sentence unobserved. Fixed — positives 4 and 5 are separate, with M13post and M13neutral deleting one sentence each.
- [P2] Pin 11b's assertion shape moved the observation count. Two predicates
as two assertions would make the module 5 observations and the inventory 26,
against gate 13's 25. Fixed — pinned as one combined assertion,
inventory unchanged at 25, and described as combined witness predicates
rather than an exact witness, since the full string carries
Debugids.
REVISION E — review round 5. Four blockers, two smaller. ALL ACCEPTED.
- [P1] C6 could be masked by C7, so M6 was not guaranteed to fail. C6's
natural fixture also overlaps, so after mislabelling C6 alone, C7 keeps
supplying
req:time:tempo-segment-orderand both label-only assertions still pass. Fixed — pin 10's requirement-arm template asserts(kind, witness)pairs, with the witnesses pinned verbatim from the checker:tempo segments are out of start orderandtempo segments overlap in musical time. The alternative isolated C6 fixture needs an end-before-start segment, a shape the checker should never see. - [P1] Pin 11b's inventory was still wrong. Three observations use
witness.contains(…); the fourth is afires()call observing selector non-emptiness. Fixed in §0.4, pin 11b and revision D's record. The "exact-witness assertion" also named no expected value — now two exact predicates: the witness ends withinterval algebraand contains noreq:. M10's radius gains the legacy test, which those predicates make it fail. - [P1] M16b risked proving the wrong assertion. The test asserts
accidental_extensions.is_empty()before the selector call; inserting the extension above that line trips the precondition first. Fixed — the mutation is pinned complete and ordered:let mut, the emptiness assertion kept in place, then theedo-31space and theCmnChromaticextension inserted after it. Both edits are named so incompatibility is not inferred. - [P1] Pin 3 pinned a complete source that the guard only half observed.
Deleting the opening
\MUST{}sentence or the closing neutrality paragraph passed pin 3a and every gate — and the second is the sentence that makes the requirement S8-neutral in prose. Fixed — four exact positives covering every sentence, with M12pre and M13post signing the two that were unguarded. - [P2] The stem guard did not specify case normalisation. "Canonically, …" escapes a case-sensitive search while M14a–c all still pass. Fixed — the slice is lowercased before scanning, and M14d uses an uppercase stem to sign the fold.
- [P2] M15a/M15b named an assertion but no test. Fixed — both name
tempo_segment_shape_requirement_states_its_clauses_and_stays_s8_neutral.
REVISION D — review round 4. Four blockers, three propagation errors. ALL ACCEPTED.
- [P1] Pin 11b contradicted pin 8a. All four accidental observations
identify the rule by
v.witness.contains("accidental-modification-compatibility")— the exact suffix pin 8a deletes. (Revision E: three of the four do; the fourth is afires()call.) A selector-only migration would make the positive fail and leave both negatives passing vacuously, since.all(|v| !v.witness.contains(…))is true of every violation once the suffix is gone. Fixed — pin 11b pins replacement predicates: negatives assertcheck_requirement(…).is_empty(); the positive asserts non-empty plus a label-free witness property. - [P1] M9 and gate 13 had the negative-test logic backwards. M9 makes
check_requirementreturn nothing, so both negatives pass vacuously and are not in its radius — I had counted them as failures, asserting the very vacuity the migration removes as though it were detection. And M1·C10 cannot prove them non-vacuous: their fixtures emit no violation to re-tag. Fixed — M16a and M16b make each negative's own fixture violate, and M3/M9's legacy set is stated as four test functions, not nine observations. - [P1] Pin 3a claimed exact literals and supplied none, and pin 3 never
pinned the requirement's source or placement, so M12/M13 were not literal
mutations. M15's premise was also false — the following requirement does
not carry the non-constant clause, and the later
end_tempooccurrence is in a rationale. Fixed — pin 3 pins the complete source and its placement; pin 3a's positives are exact normalised literals from it; and the boundary is observed structurally: the slice contains exactly one\label{and no inner\begin{requirement}. One assertion catches over-reach and under-reach alike, so M15a/M15b are ordinary failing mutations and the start boundary is covered too. - [P1] Gate 7 was unsatisfiable for M15, which demanded a verbatim failing assertion for a mutation whose success was another mutation ceasing to fail. Fixed by removing the passing control entirely — §3 now states this rung has no passing-outcome mutation, so gate 7 applies uniformly.
- [P2] §0.4 omitted
accidental_compatibility_testsfrom the measured blast radius; revision C's correction reached pin 11 and gate 13 and stopped before the section that claims to inventory the radius. Fixed. - [P2] The live status still read revision B. Fixed.
- [P2] §3 still said radii derive from pin 10's tests — repeating the exact omission revision C corrected. Fixed — pins 3a, 10 and 11.
REVISION C — review round 3. Four blockers, three smaller. ALL ACCEPTED.
- [P1] Pin 11's table totalled 22. The
f3_aleatoric_boundsrow already counted its invariant-4 assertion, so "nine further" double-counted it. Measured and fixed — eight further, in exactly the four tests the round named:f4_…offset_kind1,f5_…tempo_conversion2,f6_…namespaces3,f10_…member_notation2. Eight plus thef3invariant-4 gives the nine other-invariant assertions; 12 CCRR + 9 = 21. - [P1] The migration surface omitted
accidental_compatibility_tests— four selector-based observations in three tests (invariants.rs:4594–:4663). Fixed — pin 11b. The sharper half is the two negatives: they assert the selector returns empty, so under pin 5 they stay green and go vacuous, remaining green through any future accidental-compatibility regression. A migration repairing only the loudly-failing test leaves two tests silently weakened. - [P1] §3 derived radii from pin 10 alone, ignoring the legacy observers.
Fixed — M1 is derived per condition (including that C6/C7 fail their
pin-10 test alone, since the legacy fixture trips both and re-tagging one
leaves the assertion passing), M3 and M11 gain their legacy observers, and M9
is re-derived. M9 also still named the deleted
comprehensive_check_retains_both_arms— a live one-hop error from revision B, now removed and recorded as such. Pin 6a newly requires the architecture M9 assumed: both selectors are projections ofcheck_invariants. - [P1] Pin 3a was not a complete guard artifact. Fixed — test name,
exact slice endpoint (the first
\end{requirement}, becauseend_tempoappears later in Chapter 3 and a loose slice would pass on another requirement's prose), normalisation rule, exact positive literals, and a closed forbidden-stem inventory —canonic,normaliz,prefer— with M14a, M14b, M14c exercising each separately. M15 is a passing-outcome control that signs the slice endpoint itself. - [P2] Touch row 1 omitted pin 3a. Fixed.
- [P2] Revision B overclaimed M7's verification. Six of the seven exist
today;
graph_invariant_all_is_unchangedis prospective, pinned by pin 10. Fixed. - [P2] "No free-text arm" was literally inaccurate —
Requirementcarries a&'static str. Fixed — the rule is no unclassified fallback arm: every violation names either a numbered invariant or a real requirement label.
REVISION B — review round 2. Five blockers, two smaller. ALL ACCEPTED.
-
[P1] Pin 10's assertion template was impossible for C1–C3. It required every condition to be absent from the invariant selector and returned by
check_requirement— but C1–C3 deliberately stay invariant 10. Fixed — the template splits by arm, two assertions for the invariant arm and three for the requirement arm. -
[P1] Pin 11's inventory was false. Measured:
review_fix_tests_4holds 21fires()assertions, of which 12 targetCrossCuttingRefsResolveand 9 target other invariants; of the 12, 5 migrate and 7 stay. My "21CrossCuttingRefsResolveassertions" counted identifier occurrences over the wrong line range. Fixed — pin 11 carries the per-test table, gate 13 requires 21 preserved / 5 migrated / 16 unchanged, and §0.4 and revision A's record are swept.Two details only reading the tests reveals:
f4's fourth assertion is a ghost region, i.e. anchor existence, so it stays; and its out-of-order fixture —seg(2,3)thenseg(1,2)— trips both C6 and C7 in one assertion. -
[P1] M7 invented an implementation step and truncated its radius.
shrinkhas no per-invariantmatch, so no passthrough is needed to compile. Fixed — four pinned edits, and the radius is seven tests, becauseProbe22entersall()with a fixture that violates nothing. Six exist today and were verified;graph_invariant_all_is_unchangedis prospective, pinned by pin 10 of this contract. -
[P1] Pin 3a's clause guard was present but unsigned. M8 deletes the whole requirement, proving only that the slice anchor exists. Fixed — M12, M13 and M14, one per clause. M14 is the one that matters: it is the edit a well-meaning later reader would make, and it silently resolves P13-S8 inside a requirement minted specifically not to.
-
[P1] M2's radius named a test its pin left insensitive.
mixed_fixture_splits_by_armwas specified against the aggregate only, which wideningcheck_invariantdoes not change. Fixed — pin 10 requires it to exercise all three surfaces, M3's radius expands to match, and the duplicatecomprehensive_check_retains_both_armsis dropped rather than kept as a second observation of the same assertion. -
[P2] M6 claimed all three assertions detect it. Two do; the third is a positive control. Fixed.
-
[P2] Pin 1a attached the
BTreeSetrationale to the wrong type. It constrainsViolationKind: Ord, not the struct's derives. Fixed — the struct's retention is justified as public-API preservation, separately.
REVISION A — review round 1. Nine findings, eight blocking. ALL ACCEPTED.
- [P1] Coverage was by label, not by emitted condition. One fixture per
label can miss half of a shared label's surface — both tempo-shape
conditions, both order conditions, both aleatoric conditions. Fixed —
pin 10's matrix is per condition, and pin 11 pins the migration of
review_fix_tests_4's assertions, preserving reversed aleatoric bounds as invariant 4. (This entry said "21CrossCuttingRefsResolveassertions"; revision B measured 21fires()assertions of which 12 target that variant.) Without that pin, execution could satisfy the suite by deleting or softening the tests that fail under the new selector. - [P1] The mutation matrix was neither executable nor exhaustive. Radii were guessed per mutation rather than derived against a named test set; M7 would have stopped at a non-exhaustive-match compile error, which observes nothing; M8's radius was wrong. Fixed — §3 derives every radius from pin 10's named tests, by construction, and M7 pins a complete compilable variant.
- [P1] Gate 6 was unsatisfiable.
DeferredCheck.invariantis deliberately retained (§0.5), and this contract and the ledger quoteInvariantViolationhistorically. Fixed — the identifier check is scoped to Rust code, and the field migration toWellFormednessViolationconsumers, withDeferredCheck.invariantexplicitly allowed. - [P1] The contract's own lifecycle was unowned — touch row 9 permitted a status edit no pin required. Fixed — pin 12 and gate 14.
- [P1] Gate 12 contradicted itself, requiring the baseline to stay at
CLAUDE.md's figures while the rung adds tests. Fixed — gate 1 owns the new total; gate 12 owns the targetedrequirement_labelsrun with the temporary row absent and the label defined. - [P1] Pin 3's normative content was unobserved. M8 proves only that a block and label exist. Fixed — pin 3a adds a prose guard over the requirement's clauses.
- [P1] The public API surface was incomplete, and pin 4 overclaimed. Fixed — pin 1a pins derives and documentation; pin 4 now says it preserves rule coverage, not caller meaning, and lists what does change.
- [P1] The accidental witness already embeds its own label
(
invariants.rs:1594), so the requirementDisplayform would render it twice. Fixed — pin 8a strips the suffix, and theDisplaytest uses a real accidental violation, not a synthetic value. - [P2] Touch row 1 carried a pre-change grep count. "Five emission sites re-tagged" is false: one must stay invariant 10, and the tempo site splits by condition. Fixed — the row describes the transformation.
§0. What was verified before drafting
0.1 The defect, and why it is not internal
GraphInvariant::CrossCuttingRefsResolve is emitted from five sites in four
functions. Only check_cross_cutting_refs, plus the tempo map's two
segment-anchor conditions, are reference resolution.
check_invariant(score, which) is pub and re-exported from epiphany_core's
root, and impl Display for InvariantViolation renders:
invariant 10 (CrossCuttingRefsResolve) violated: non-constant tempo segment is missing its end_tempo
A Chapter 3 tempo rule, attributed in user-visible text to a Chapter 5 graph invariant, through a public API.
0.2 The classification is per emitted condition, and it is P13-S26's
S26 derived and ruled it; this rung implements it. Pin 10 carries the closed matrix; the summary is that anchor existence stays invariant 10 and the seven rider conditions move to four labels.
0.3 GraphInvariant does not move
core_spec.texstates "This enumeration contains exactly 21 invariants".- The enum is "numbered as in §Graph Invariants", so a variant with no enumeration entry has no number to return.
- S26 ruled invariant 10 stays normatively reference resolution.
Minting variants would also owe a negative generator and a shrink path each —
every_invariant_has_a_negative_generator and
every_invariant_shrinks_to_a_small_witness iterate all(). This rung adds no
variant, so neither obligation grows.
0.4 The blast radius, measured
- No wire reach.
GraphInvariant's only uses outsideepiphany-coreare two test-scope sites inreduce.rs(nearest#[cfg(test)]at:9663), and both name a variant, not the field. .invarianton the violation type: four consumers outsideinvariants.rs—tests/score_graph.rs×2,src/generators.rs×2.review_fix_tests_4(invariants.rs:4217–:4591) holds 21fires()assertions, all viacheck_invariant. 12 targetCrossCuttingRefsResolve; 9 target other invariants. Of the 12, 5 are riders that migrate and 7 are genuine invariant 10 that stay — the split is pin 11's table. Pin 11 owns all 21.accidental_compatibility_tests(invariants.rs:4594–:4663) holds 4 selector-based observations in 3 tests: three identify the rule by the witness suffix pin 8a deletes, and one is afires()call observing only selector non-emptiness — see pin 11b, which is why a selector-only migration is not enough.check_invariantshas ~10 consumer files. Pin 4 keeps it comprehensive.- Current derives:
#[derive(Clone, PartialEq, Eq, Debug)].
0.5 DeferredCheck is checked and excluded
It carries a GraphInvariant and renders "invariant N (…) deferred", so it
resembles the defect. It is not: its only producer,
deferred_region_overlaps, tags GraphInvariant::RegionExtents — a genuine
invariant, correctly attributed. Not renamed, not re-typed, and its
.invariant field is explicitly exempt from gate 6.
0.6 The tempo-shape rule, and P13-S8
Enforced today, verbatim:
TempoShape::Constant => seg.end_tempo.as_ref().is_none_or(|et| et == &seg.start_tempo)
TempoShape::Linear | TempoShape::Exponential | TempoShape::Curve => seg.end_tempo.is_some()
P13-S8 asks whether Constant + Some(equal) is canonical or normalizes to
None. Pin 3 states the compatibility enforced today and takes no position
on canonical form, so the label survives whichever spelling S8 ratifies.
0.7 One consequence for P13-S26's landed text
S26's pin 5 put a note in the /// 10. doc block saying the riders "are
reported under this same tag", naming P13-S29 as owner. This rung makes that
false, and pin 9 updates it. Not a violation of S26's frozen pin: pins govern
their own execution, not the tree forever.
§1. Pins
Pin 1 — the type becomes neutral
InvariantViolation → WellFormednessViolation; field invariant →
kind: ViolationKind. A type called InvariantViolation cannot honestly
carry a requirement failure, and the struct is public.
Pin 1a — derives and documentation are pinned
WellFormednessViolationretains#[derive(Clone, PartialEq, Eq, Debug)]exactly, as measured in §0.4. The rationale is public-API preservation: the struct is public, and a rung that renames a type has no business also narrowing what callers can do with it.ViolationKindderivesClone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug— matchingGraphInvariant's own derives.Ordis load-bearing independently:generators.rscollects violation kinds into aBTreeSet, which requires it. That collection constrains this enum, not the struct's derives; an earlier draft attached the rationale to the wrong type.- The module header and both types' rustdoc state the two-arm split and that a requirement failure is not an invariant failure.
Pin 2 — ViolationKind has exactly two arms
pub enum ViolationKind {
Invariant(GraphInvariant),
Requirement(&'static str),
}
No third arm, and no unclassified fallback arm. Requirement does carry
text — a &'static str label — so the rule is not "no text"; it is that every
violation names either a numbered invariant or a real requirement label, with
nothing in between. A condition with no label does not belong here, which is why
pin 3 mints one rather than adding an Other.
The two-arm rule has an observer, because otherwise it has none. A complete
third arm — variant, Display match arm, and no emitter — compiles and leaves
every other gate green, since nothing produces it. violation_kind_has_exactly_two_arms
therefore asserts the enum's shape: the source's enum ViolationKind block,
whitespace-collapsed, equals pin 2's declaration above. M18 adds a
complete, compiling third arm with a Display arm and no emitter, and must fail
it.
Pin 3 — req:time:tempo-segment-shape is minted, source and placement pinned
Placement: immediately after req:time:tempo-segment-order's
\end{requirement} in spec/core_spec.tex, before whatever follows it.
Source, verbatim — this is what execution writes:
\begin{requirement}
\label{req:time:tempo-segment-shape}
A tempo segment's \texttt{shape} and its \texttt{end\_tempo} \MUST{} be
compatible. If \texttt{shape} is \texttt{Constant} and \texttt{end\_tempo}
is present, it \MUST{} equal \texttt{start\_tempo}. If \texttt{shape} is
\texttt{Linear}, \texttt{Exponential} or \texttt{Curve}, \texttt{end\_tempo}
\MUST{} be present.
This requirement states the compatibility that is enforced. It does not
determine whether a constant segment records an \texttt{end\_tempo} at all.
\end{requirement}
It takes no position on canonical form. P13-S8 decides whether Constant +
Some(equal) is canonical or normalizes to None; this text is true under
either, and the closing sentence says so without settling the question. Pin 3a
observes this block by exact equality, so no phrase or stem inventory
governs its wording.
Moves requirement_labels.rs' three counts. Measured at execution, never
predicted.
Pin 3 also deletes the temporary allowlist row. Naming the label broke the
citation gate immediately; one DISCUSSED_NOT_CITED row was added as
prerequisite review scaffolding — not dispatch, licensing no other pin work —
on the owner's authorization. Pin 3 removes it when it mints the requirement,
because the row's claim (discussed, never cited) becomes false then. Removed by
hand if S29 is abandoned or the label changes.
Pin 3a — the requirement's clauses are guarded, and the guard is pinned
Test: tempo_segment_shape_requirement_states_its_clauses_and_stays_s8_neutral,
in invariants.rs beside pin 10's tests.
Slice. spec/core_spec.tex, whitespace-collapsed: from the
\begin{requirement} immediately preceding \label{req:time:tempo-segment-shape}
to the first \end{requirement} at or after that label.
No separate structural boundary assertion. Revision F added one — exactly one
\label{, no inner \begin{requirement} — to catch a mis-sliced block. Under
equality it is redundant: a slice extended past its \end{requirement} or
started at an earlier \begin{requirement} is unequal to pin 3's source and
fails on that. Retained as two assertions, M15a and M15b could no longer sign it
independently, since equality fails first. It is retired, and M15a/M15b are
ordinary equality mutations.
Content assertion — normalised exact equality against pin 3's pinned source. The slice, whitespace-collapsed, equals pin 3's block, whitespace-collapsed. Nothing weaker.
Why equality rather than required phrases plus a forbidden-stem list. Pin 3 declares the block complete source, so equality is the assertion that matches the pin. Every weaker form leaves an additive hole: a guard checking five required sentences and three forbidden stems passes on
A \texttt{Constant} segment's \texttt{end\_tempo} \MUST{} be absent.
which contains no canonic, normaliz or prefer — and resolves P13-S8
inside a requirement minted specifically not to. No closed stem inventory can
be trusted to anticipate the sentence someone will actually write. Equality needs
to anticipate nothing.
The cost is intended: any later edit to this requirement breaks the guard and forces deliberate review, which is what a frozen normative block should do.
(This replaces revision E's five positive literals and revision D's stem
inventory; both are subsumed. M12, M13, M13post, M13neutral and M14a–M14d all
now fail this one assertion, each with its own assert_eq! diagnostic naming
what differs.)
Pin 4 — check_invariants stays comprehensive
It returns both arms, so every broad caller keeps exactly the rule coverage it has today and no rule stops being checked anywhere.
It does not preserve caller meaning, and this pin does not claim to. What
changes for callers: the type name, the field name and type, the targeted
selector's results (pin 5), Debug output, and the rider arm's Display.
Pin 5 — check_invariant filters the Invariant arm only
Matches only ViolationKind::Invariant(which). This is the deliberate
behaviour change: check_invariant(score, CrossCuttingRefsResolve) returns
only genuine invariant-10 failures.
Pin 6 — check_requirement is added, symmetric
check_requirement(score, label: &str) -> Vec<WellFormednessViolation>, matching
only ViolationKind::Requirement(label). A rung that made invariant failures
selectable while leaving requirement failures reachable only by scanning the
comprehensive result would have moved the asymmetry, not removed it.
Pin 6a — both selectors are projections of check_invariants
check_invariant and check_requirement are filters over the comprehensive
result, not independent traversals. One traversal produces every violation;
each selector projects one arm out of it.
Pinned because §3's M9 depends on it. If the selectors traversed the score
independently, narrowing check_invariants would not reach them, and M9's radius
— the widest in the rung, and the measure of what pin 4 protects — would be
wrong. An architecture assumed by a mutation must be required by a pin.
Pin 7 — the two Display forms are exact
- Invariant arm:
invariant {n} ({variant:?}) violated: {witness}— unchanged. - Requirement arm:
requirement {label} violated: {witness}.
A requirement failure never renders the word invariant and never renders a
number.
Pin 8 — GraphInvariant is untouched, and the freeze is sequence-signed
No variant added or removed; number() unchanged; core_spec.tex's count claim
unchanged.
graph_invariant_all_is_unchanged compares GraphInvariant::all() to this
canonical (variant, number) sequence, in order, by equality:
EventVoiceBacklink=1 VoiceEventBacklink=2 VoiceEventsSortedNonOverlap=3
EventCoordinateModel=4 ContainmentTree=5 StaffInstanceResolves=6
RegionExtents=7 MeasureSingleInstance=8 AnchorOffsetModel=9
CrossCuttingRefsResolve=10 UniqueIdentifiers=11 PitchIdUnique=12
SpellingScopeResolves=13 DecompositionTargetResolves=14 DecompositionSum=15
TupletSum=16 TiePairing=17 VoiceOriginConsistent=18
BarlineGroupSameRegion=19 MeasureMeterConsistency=20 StaffGroupMembershipAgreement=21
A length check is not the freeze. M7 adds a 22nd entry, and a test asserting
only all().len() == 21 fails it — while leaving unobserved: the same variants
in a different order, one entry replaced or duplicated with the length
still 21, and two number() arms swapped. M7a, M7b and M7c sign those three
independently.
And all() is not the enum. A fully implemented 22nd variant that is
omitted from all() leaves the canonical sequence untouched and every gate
green — the same hole M18 closes for ViolationKind, since a variant nothing
enumerates is a variant nothing observes. The test therefore carries a second,
independent inventory: the enum GraphInvariant declaration is read from
source and its variant list, in order, must equal the canonical sequence's.
M7d declares a complete 22nd variant with a number() arm and no all()
entry, and must fail on that inventory and nothing else.
Pin 8a — the accidental witness stops embedding its label
check_accidental_modification_compatibility's witness ends
"… (req:tuning:accidental-modification-compatibility)". Under pin 7 the label
becomes the Display prefix, so the suffix is deleted — otherwise a real
violation renders the label twice.
Pin 10's Display test uses a real accidental violation, produced by the
checker, not a hand-built WellFormednessViolation. A synthetic value cannot
observe this, because the duplication lives in the witness the checker writes.
Pin 9 — consumers migrate, and every live statement made false is corrected
The four .invariant consumers migrate to .kind. The prose migration does not
stop at the /// 10. block. Every live statement made false is pinned below;
no count is stated, because the set grew in two consecutive rounds and a
tally beside a table that can grow is the defect this family keeps finding:
| Site | What becomes false |
|---|---|
invariants.rs:8, the module header |
"a typed [InvariantViolation] witness" — gate 6 forbids that token in Rust code, so pin 1b pins its replacement, [`WellFormednessViolation`] |
invariants.rs, the /// 10. doc block |
S26's rider note: the riders are no longer "reported under this same tag", and P13-S29 is no longer their pending owner. Rewritten to name the four labels |
invariants.rs:1421 region |
the tempo-map header says its conditions are "surfaced here under invariant 10" |
invariants.rs:1500 region |
the aleatoric header says dangling references "go under invariant 10" |
invariants.rs:1557 region |
the accidental header says it is "surfaced under an existing GraphInvariant tag rather than minting a new one" — it now carries its own requirement label. The same comment cites core_spec.tex:3120, a line locator already wrong before this rung; it is replaced by the symbolic req:tuning:accidental-modification-compatibility, which is what it meant |
invariants.rs:4624 |
the migrated negative's message calls it "the compatibility invariant" |
crates/epiphany-core/README.md:25 |
exposes InvariantViolation and "all 19 enumerated graph invariants" — the count was already stale (20 since G3b, 21 since S16). Its invariant-description cell is replaced with the text pin 9a pins; pin 9a is the sole authority and this row does not restate it |
review_fix_tests_4's module doc |
describes its subject as "tempo-map segment invariants" — after this rung they are requirements, not invariants |
check_invariants' public rustdoc |
does not say it returns both arms. Pin 4 makes comprehensiveness the property callers depend on; the doc must state it, since that is the one place a caller looks before relying on it |
crates/epiphany-core/DECISIONS.md:1061 records the multiplexing as current
policy, wiring the accidental check into check_invariants as "the
req:tuning:accidental-modification-compatibility invariant". It is not
rewritten. A decision record is a record of what was decided; it gains an
explicit supersession note naming P13-S29 and the date, leaving the original
decision legible. Rewriting it would destroy the reasoning that made the
multiplexing defensible at the time, which is exactly what a later reader needs
in order to understand why it was accepted and then reversed.
Pin 1b — the public surface has observers
As of the ratified input, pin 1a's derives and rustdocs are prose that no
test or gate reads, and touch row 2's root re-export has no complete explicit
observer: the migrated tests/score_graph.rs will resolve ViolationKind
through the root, so the re-export is not unobserved outright — but no test names
all three exported items, so dropping check_requirement alone would leave the
suite green. Scoped to the ratified input: once
this pin lands, both statements are false of the tree.
crates/epiphany-core/tests/public_surface.rs — a new integration test,
public_violation_surface_is_reexported, type-level only:
use epiphany_core::{check_requirement, ViolationKind, WellFormednessViolation};
#[test]
fn public_violation_surface_is_reexported() {
let _: fn(&epiphany_core::Score, &str) -> Vec<WellFormednessViolation> = check_requirement;
let _ = |k: &ViolationKind| matches!(k, ViolationKind::Requirement(_));
}
It calls nothing. A call whose result were asserted would put this test
in M3's and M9's radii; an ignored call would not, as M20f shows. The file stays
type-level so the question never arises — a function-item coercion and a
matches! arm observe the surface without producing a violation.
The re-export is compiler-observed, not mutation-observed. mod invariants
is private (lib.rs:76), so deleting a name from the root re-export is an
unresolved import — a compile error, which observes nothing and which gate 7
cannot record as a failing assertion. It is in the same class as pin 1's rename:
the compiler is the observer.
violation_types_declare_their_pinned_derives, placed inside
mod g3a_tests — not merely "beside pin 10's tests": production_source() is
a private fn of that module (invariants.rs:4679), so a guard anywhere else
cannot call it, and the alternatives (a second slicing helper, or widening that
one's visibility) add surface for no gain.
Six assertions, each an independent promise, each an EQUALITY, and each with a pinned extraction boundary.
| # | Slice — symbolic, never a file-wide search | Assertion |
|---|---|---|
| 1 | locate pub struct WellFormednessViolation {; take the line immediately preceding it |
equals #[derive(Clone, PartialEq, Eq, Debug)] |
| 2 | locate pub enum ViolationKind {; take the line immediately preceding it |
equals #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] |
| 3 | in the module header, from the pinned paragraph's first line through the next blank //! |
equals the module paragraph below |
| 4 | locate pub struct WellFormednessViolation {; require its preceding line to be assertion 1's derive; take the contiguous /// block immediately preceding that derive |
equals the struct rustdoc below |
| 5 | the same, for pub enum ViolationKind { and assertion 2's derive |
equals the enum rustdoc below |
| 6 | the whole of public_surface.rs |
equals the fenced source above |
Assertions 4 and 5 cannot say "immediately preceding the declaration": the
derive attribute sits between. The algorithm is therefore three steps —
declaration, then its derive line, then the /// block above that.
Assertion 1 is unsound without its boundary.
#[derive(Clone, PartialEq, Eq, Debug)] occurs twice in invariants.rs
today: on the violation type (:249) and on DeferredCheck (:286), which
§0.5 deliberately retains. A file-wide search survives M20, because removing
Eq from one leaves the other matching.
Assertion 3 replaces nothing. The module header is a 28-line //! run
with paragraph breaks at :2, :9, :13 and :22; asserting the whole run
equals a four-line paragraph would delete 24 lines of retained header. The
pinned paragraph is inserted after the title paragraph's blank //! — not
prepended: prepending would make its first sentence the module's rustdoc summary
line, displacing "The Chapter 5 graph invariants" — and the slice is bounded by
that paragraph's own first line and the next blank //!.
The three prose blocks are pinned here as raw source, not described. Pin 1a states a requirement; a requirement is not an expected value, and an executor would otherwise write the production string and copy it into the test, which asserts nothing. Each block states both of pin 1a's promises — the two-arm split, and that a requirement failure is not an invariant failure — so pin 1a's claim is true of each block individually, not merely of the three collectively.
Module paragraph:
//! Violations carry a two-armed [`ViolationKind`]: `Invariant` for the Chapter 5
//! graph invariants, `Requirement` for a normative requirement named by its
//! label. A requirement failure is not an invariant failure, and neither arm is
//! a fallback for the other.
WellFormednessViolation rustdoc:
/// A well-formedness failure: one [`ViolationKind`] — `Invariant` or
/// `Requirement` — and the witness identifying the smallest offending objects.
/// A requirement failure is not an invariant failure.
ViolationKind rustdoc:
/// What a [`WellFormednessViolation`] failed. `Invariant` names a numbered
/// Chapter 5 graph invariant; `Requirement` names a normative requirement by its
/// label. A requirement failure is not an invariant failure, and there is no
/// third arm and no unclassified fallback.
One retained header line must also be renamed, and pin 9 owns it:
invariants.rs:8 reads "a typed [InvariantViolation] witness". Gate 6
forbids that token in Rust code, so leaving it unpinned would make gate 6
unsatisfiable; it becomes [`WellFormednessViolation`], the rest of that
sentence unchanged.
Every assertion has a NON-DELETION discriminator, because deletion-only mutations are satisfied by needle and subset implementations: M20g (1), M20i (2), M20j (3), M20k (4), M20h (5), M20f (6). "Additive" would be inaccurate — M20i is a reordering. Revision P signed only 1 and 5 this way and left 2, 3 and 4 deletion-only, so a subset implementation of any of those three passed the whole plan.
Assertion 6 is the whole file, not its import line. An import-only check
leaves the body free to start calling check_requirement — and a call whose
result were asserted would widen M3's and M9's radii, which is the outcome the
type-level constraint exists to prevent. It is not restated here: the fenced Rust block above is its sole
authority, one line per line, and a second "exact" spelling wrapped into Markdown
prose would be a different string. M20f adds a call while keeping the
imports intact.
Pin 9a — the README's invariant-description cell, verbatim
Raw source, fenced so it has exactly one interpretation:
`check_invariants` over all variants returned by `GraphInvariant::all()`, with a typed `WellFormednessViolation` witness per check
Gate 16 compares that row's second cell to this, by equality after whitespace
collapse. Equality, not a numeral scan: a scan over the line rejects the
correct edit — the row's final cell necessarily reads Ch. 5 §"Graph Invariants"
— and a scan for digits passes on "all twenty-one variants", recreating the
defect without one.
Pin 10 — the closed per-condition matrix, and the tests
Coverage is per emitted condition, not per label. Each row gets a fixture and a named test. The assertion template differs by arm, because C1–C3 stay invariant 10 and the requirement template is impossible for them:
- C1–C3 (invariant arm), two assertions: the aggregate
check_invariantsresult carriesViolationKind::Invariant(CrossCuttingRefsResolve); andcheck_invariant(score, CrossCuttingRefsResolve)returns it. Every C4–C10 fixture violates no invariant at all — its aggregatecheck_invariantsresult carries no invariant-arm violation, and that is a pinned fourth assertion on each of the seven.
The property is "no invariant arm", not "only its own violation": C6's fixture emits both C6 and C7, as this contract establishes and as pairs exist to handle. A cleanliness claim of "only its own" would be unsatisfiable for that pair and for C7's.
This is what keeps those seven out of M2a's radius structurally rather than by
assumption. Each carries a negative check_invariant assertion; under M2a's
wildcard that assertion fails if the fixture emits any invariant violation.
An unrelated invariant tripped incidentally by a not-yet-built fixture would put
the test in M2a's cell — which the measured legacy set cannot anticipate, because
these fixtures do not exist yet. Requiring an empty invariant arm removes the
possibility instead of estimating it.
- C4–C10 (requirement arm), three assertions, each on a
(kind, witness)pair, never on the label alone: the aggregate contains(Requirement(label), witness)for that row; the same fixture yields nothing fromcheck_invariant(score, CrossCuttingRefsResolve); andcheck_requirement(score, label)returns that pair; and — the fourth — the aggregate's invariant arm is empty.
Pairs are required because three labels are shared by two conditions each, and
one fixture can trip both. C6's natural fixture — seg(2,3) then seg(1,2) —
is also overlapping, so it emits C6 and C7 under the same label. A
label-only assertion would still pass after C6 alone was mislabelled, because C7
keeps supplying the label. The pinned witnesses, verbatim from the checker:
The matrix is closed. C4–C7's witnesses are fixed strings in the checker and
are pinned exactly; C8–C10's are format!-built and carry ids, so what is pinned
is the discriminating substring — enough to tell same-label siblings apart,
without pinning Debug output a fixture change would move.
| Condition | Assertion on the witness | Form |
|---|---|---|
| C4 | constant tempo segment has end_tempo != start_tempo |
equals, fixed string |
| C5 | non-constant tempo segment is missing its end_tempo |
equals, fixed string |
| C6 | tempo segments are out of start order |
equals, fixed string |
| C7 | tempo segments overlap in musical time |
equals, fixed string |
| C8 | contains ordering references event |
contains, id-bearing |
| C9 | contains bounds key event |
contains, id-bearing |
| C10 | ends with interval algebra and contains no req: — one combined assertion |
combined predicate, id-bearing |
The alternative to pairs — an isolated C6 fixture — would need an end-before-start segment, which is unnatural and tests a shape the checker should never see.
| # | Emitted condition | Arm | Test |
|---|---|---|---|
| C1 | check_cross_cutting_refs — any condition |
invariant 10 | cross_cutting_refs_stay_invariant_ten |
| C2 | tempo segment start anchor existence |
invariant 10 | tempo_start_anchor_stays_invariant_ten |
| C3 | tempo segment end anchor existence |
invariant 10 | tempo_end_anchor_stays_invariant_ten |
| C4 | Constant with end_tempo ≠ start_tempo |
req:time:tempo-segment-shape |
tempo_constant_mismatch_reports_shape |
| C5 | non-constant missing end_tempo |
req:time:tempo-segment-shape |
tempo_nonconstant_missing_end_reports_shape |
| C6 | segment start ordering | req:time:tempo-segment-order |
tempo_out_of_order_reports_order |
| C7 | segment overlap | req:time:tempo-segment-order |
tempo_overlap_reports_order |
| C8 | aleatoric ordering event outside region |
req:time:aleatoric-reference-locality |
aleatoric_ordering_outside_region_reports_locality |
| C9 | aleatoric bounds key outside region |
req:time:aleatoric-reference-locality |
aleatoric_bounds_outside_region_reports_locality |
| C10 | accidental modification expressibility | req:tuning:accidental-modification-compatibility |
accidental_incompatible_reports_tuning_requirement |
| — | reversed aleatoric bounds | invariant 4, unchanged | reversed_aleatoric_bounds_stay_invariant_four |
C4 and C5 share a label and are separate conditions; so do C6/C7 and C8/C9. One fixture per label would leave one of each pair unexercised.
Whole-surface tests, in addition:
-
mixed_fixture_splits_by_arm— the fixture is pinned: one score carrying a dangling tempostartanchor (C2) and an out-of-region aleatoricorderingevent (C8). It exercises all three surfaces, not the aggregate alone, or it is insensitive to the selectors it exists to separate:- the aggregate carries both kinds;
check_invariant(score, CrossCuttingRefsResolve)returns the anchor only;check_requirement(score, "req:time:aleatoric-reference-locality")returns the rider only.
An aggregate-only form would leave M2 and M3 undetected here, which is why §3's radii name this test for both.
-
invariant_selector_discriminates_its_payload— a fixture violating two different invariants, C1's cross-cutting reference and a reversed aleatoric bound (invariant 4).check_invariant(score, CrossCuttingRefsResolve)returns only the invariant-10 violation;check_invariant(score, EventCoordinateModel)returns only the invariant-4 one. -
requirement_selector_discriminates_its_payload— a fixture violating two different requirement labels, C5's tempo shape and C8's aleatoric locality.check_requirementfor each label returns only that label's violation.Both exist because every other fixture in pin 10 presents its selector with a single variant or a single label. Against those, an implementation matching
ViolationKind::Invariant(_)orViolationKind::Requirement(_)— ignoring the payload entirely — satisfies every assertion. M2 and M3 sign arm selection; these two and M2a/M3a sign that the selector reads its argument. -
display_renders_each_arm_exactly— two assertions on the requirement side, in this order, because the second alone is circular:- independent: the violation's
witnesscontains noreq:; - then
assert_eq!(violation.to_string(), format!("requirement {} violated: {}", label, witness)), the wrapper built from the witness assertion 1 has already checked.
Assertion 2 alone cannot fail under M10: an expected string built from
violation.witnessmoves with the actual when the suffix returns. Assertion 1 is the independent oracle, and it is what makes M10 fail here. The invariant side's fixture is pinned: a reversed aleatoric bound, which emitsEventCoordinateModel— invariant 4 — acquired fromcheck_invariantslike the requirement side. The choice changes radii, so it cannot be the executor's: a C1 fixture would put this test in M17·C1's cell, a tempo-anchor fixture in M17·C2/C3's, and reversed bounds put it in M11's, which is where it now belongs and where M11's cell names it.The violation is acquired from
check_invariants, the aggregate — pinned, not incidental. Acquiring it throughcheck_requirementwould put this test in M3's radius; through the aggregate it is not. Leaving the path to the executor would leave M3's cell true or false depending on a choice no pin made. - independent: the violation's
-
graph_invariant_all_is_unchanged— the same 21, in order.comprehensive_check_retains_both_armsis not a separate test: it would duplicate the mixed fixture's first assertion exactly. Pin 4's coverage is observed by that assertion and by M9's radius, which reaches every aggregate assertion in the rung.
Pin 11 — every legacy observer is migrated, not deleted
Two modules observe the riders through the selector, and both are in scope.
11a — review_fix_tests_4 (invariants.rs:4217–:4591), 21 assertions
| Test | Assertions | Disposition |
|---|---|---|
f3_aleatoric_dag_referencing_absent_event_fires |
1 CCRR | migrates — C8 |
f3_aleatoric_bounds_key_absent_and_reversed_window_fire |
1 CCRR + 1 EventCoordinateModel |
the CCRR one migrates — C9; the invariant-4 one is untouched |
f4_tempo_segment_structural_defects_fire |
4 CCRR | 3 migrate — C5, C4, and one fixture tripping both C6 and C7 (seg(2,3) then seg(1,2) is out of order and overlapping); the 4th stays, a ghost-region anchor — C2 |
f8_structural_reference_resolution |
5 CCRR | all stay — genuine invariant 10 |
f9_dangling_decomposition_tuplet_reference_fires |
1 CCRR | stays — genuine invariant 10 |
f4_tempo_segment_offset_kind_is_checked_by_invariant_9 |
1 AnchorOffsetModel |
unchanged |
f5_overlapping_metric_regions_caught_via_tempo_conversion |
2 RegionExtents |
unchanged |
f6_time_signature_uniqueness_and_namespaces |
3 UniqueIdentifiers |
unchanged |
f10_tuplet_ratio_inconsistent_with_member_notation_fires |
2 TupletSum |
unchanged |
21 preserved: 12 CCRR (5 migrate, 7 stay) + 9 other-invariant (1 in the f3
row above, 8 in the four rows beneath it).
11b — accidental_compatibility_tests (invariants.rs:4594–:4663), 4 observations in 3 tests
A selector-only migration is insufficient here, and would leave the module
worse than it is now. Three of the four observations identify the rule
through v.witness.contains("accidental-modification-compatibility") — the
exact suffix pin 8a deletes; the fourth is a fires() call, which observes
only selector non-emptiness.
| Test | Observations | Selector-only migration |
|---|---|---|
cmn_chromatic_accidental_in_edo_31_fires |
2 — one fires(), one witness.contains |
Fails — the witness no longer contains the label |
cmn_chromatic_accidental_in_cmn_12_does_not_fire |
1 | Passes vacuously — `.all( |
a_score_with_no_accidental_extensions_never_fires_this_check |
1 | Passes vacuously, same predicate |
Pinned replacement predicates:
-
Both negatives:
check_requirement(&s, "req:tuning:accidental-modification-compatibility").is_empty(). The label moves from the witness text to the selector, which is where pin 2 put it. -
The positive: that same call is non-empty, and its witness is checked by one combined assertion — ends with
interval algebraand contains noreq:. One assertion, not two, so the module's observation count stays at 4 and pin 11c's inventory stays at 25; splitting it would make the inventory 26 and gate 13's figure wrong.These are combined witness predicates, not an exact witness: the full string carries
Debug-formatted ids that a fixture change would move. The label is now theDisplayprefix; asserting it inside the witness is what pin 8a removed.
The two negatives are the sharper half. A migration repairing only the loudly failing test leaves two tests green, weaker than before, and reporting nothing.
11c — the standing rule
All 25 observations preserved. Exactly 9 migrated (5 in 11a, 4 in 11b), and the 4 in 11b have their predicates replaced, not merely their selector. Exactly 16 unchanged. None deleted, none softened — the failure mode this pin prevents is an execution that makes the suite green by removing or hollowing the tests that caught the change.
Reversed aleatoric bounds keep asserting invariant 4. Correctly tagged before this rung and not a rider; sweeping them into the requirement arm would introduce the defect S29 removes.
Pin 12 — this contract's own lifecycle
- On ratification: the status block reads exactly
STATUS: RATIFIED; DISPATCHED., and the frozen-pins statement is added, word for word as P13-S16, S27 and S26 carry it. - On landing: exactly
STATUS: LANDED by this commit.— no hash; a commit cannot carry its own id, and if one is wanted it arrives by a later administrative amendment. - On landing, all review-round blocks above §0 are marked a dated historical record.
Touch row 9 authorizes these edits; this pin is what requires them.
Pin 13 — the two permanent history additions, with pinned content
Touch rows 5 and 8 license a core_spec.tex Revision History row and an S29
ledger append. Neither was required by any pin, and neither gate could observe
one: in the ratified input — the tree as it stands at ratification — the
Revision History chapter already exists and the S29 row is already present and
open, so a gate asserting their presence passes on the baseline artifact.
Scoped to the ratified input deliberately: after execution the S29 row is
resolved, so a clause saying it "exists today, open" would be false at landing. This is the
third occurrence of the touch-row-without-a-pin class in this rung's family
(S26's Revision History row, S26's own status block, and now these two), which is
why it is pinned rather than tidied.
Revision History row — pinned source, verbatim. Appended immediately before
\bottomrule in core_spec.tex's Revision History longtable:
\today & \sectionsc{Graph Invariants}, \sectionsc{Time and Duration} &
\textbf{P13-S29: the violation tag stops multiplexing.} Graph invariant~10
reported Chapter~3 and Chapter~4 failures under its own number, through a
public API. The violation type becomes \texttt{WellFormednessViolation}
carrying a two-armed \texttt{ViolationKind}: an invariant arm and a
requirement arm naming a \texttt{req:} label. Invariant~10 keeps only
reference resolution; tempo segment shape, tempo segment order, aleatoric
reference locality and accidental modification expressibility now report
under their own requirements. \sectionsc{Time and Duration} gains
Requirement~\ref{req:time:tempo-segment-shape}, stating the enforced
shape/\texttt{end\_tempo} compatibility without determining whether a
constant segment records an \texttt{end\_tempo} at all. The enumeration is
unchanged.
\\
The delimiter placement is pinned and was wrong in revision I. The existing
final row already terminates with \\ immediately before \bottomrule
(core_spec.tex:17031), so a block beginning with \\ yields two separators
before the new row and none after it. The new row carries no leading \\ and a
terminating one, and is inserted between that existing \\ and \bottomrule.
Extraction: the contiguous run of added lines in
git diff --cached -- spec/core_spec.tex that lies inside the Revision History
chapter. Whitespace-collapsed, it equals the block above, likewise collapsed.
S29 ledger append — pinned text, and an exact whole-line reconstruction. The
ledger's S29 entry is a single-line markdown table row, so an append shows in
the diff as one removed line and one added line; the added line is not the
append. APPEND is exactly the following, as raw source — a fenced block,
not a blockquote, because blockquote > prefixes are part of the raw text and
whitespace-collapsing does not remove them, while the ledger row contains none:
**RESOLVED 2026-08-11 by `spec/CONTRACT_P13S29_VIOLATION_KIND.md`, disposition (b) type-neutral.** `InvariantViolation` becomes `WellFormednessViolation` with `kind: ViolationKind`, whose two arms are `Invariant(GraphInvariant)` and `Requirement(&'static str)`. `check_invariants` stays **comprehensive** — every broad caller keeps its rule coverage — while `check_invariant` deliberately narrows to the invariant arm and a symmetric `check_requirement` is added. `req:time:tempo-segment-shape` is minted for the one rider that had no label, stating enforced compatibility without resolving P13-S8. **`GraphInvariant` did not move: 21 variants, unchanged.**
Extraction and comparison, exact — and the terminal delimiter is the trap.
The S29 line ends with the table's terminal |, so the append goes inside
the row, before that delimiter; concatenating after the whole line would write
outside the table. Let removed and added be the S29 row's removed and added
lines. Then, whitespace-collapsed, with strip(x) removing a trailing | and
surrounding space:
strip(added) == strip(removed) + " " + APPEND, and added ends with
|.
Revision I compared added == removed + " " + APPEND, which no correct edit can
satisfy. This compares the whole logical record without pinning the pre-existing
cell, and still rejects a truncated append — which carries the distinguishing
token and fails the equality.
Both gates observe the record, not a token — by different means, because the
two additions differ in shape. Neither is "the whole normalised added record":
that phrase described only gate 15 and was never true of gate 10. Gate 15 slices the added Revision History
row out of git diff --cached's additions and compares it, normalised, against
pin 13's block: a new table row is genuinely a run of added lines. Gate 10
cannot do that, because the S29 entry is one line and an append rewrites it —
so it uses the removed-plus-added reconstruction above, not an added-lines
check. Checking for req:time:tempo-segment-shape or
WellFormednessViolation anywhere in the additions passes on a truncated or
misplaced record — the token is present, the record is not. Both gates therefore
compare the whole logical record — gate 15 the added row, gate 10 the
reconstruction — and a shortened addition fails either.
§2. Touch table
| # | Path | Why |
|---|---|---|
| 1 | crates/epiphany-core/src/invariants.rs |
pins 1, 1a, 1b (its source guard lands here), 2, 3a (its guard lands here), 4, 5, 6, 6a, 7, 8, 8a, 9, 10, 11. The transformation, not a re-tag count: check_cross_cutting_refs' emission is unchanged; check_tempo_maps' single flag closure splits by condition into an invariant arm (C2, C3) and a requirement arm (C4–C7); check_aleatoric_models' two CrossCuttingRefsResolve pushes become requirement-arm (C8, C9) while its EventCoordinateModel push is untouched; check_accidental_modification_compatibility's becomes requirement-arm (C10) and loses its witness suffix |
| 2 | crates/epiphany-core/src/lib.rs |
re-exports: renamed type, ViolationKind, check_requirement |
| 3 | crates/epiphany-core/src/generators.rs |
.invariant → .kind (two sites) |
| 4 | crates/epiphany-core/tests/score_graph.rs |
.invariant → .kind (two sites) |
| 4a | crates/epiphany-core/README.md |
pin 9 — the exposed type name, and the already-stale invariant count |
| 4b | crates/epiphany-core/DECISIONS.md |
pin 9 — a supersession note; the original decision is not rewritten |
| 4c | crates/epiphany-core/tests/public_surface.rs |
pin 1b's integration test, new file — the only explicit three-name observer of touch row 2's root re-export. Not the only observer of that re-export: tests/score_graph.rs is an integration test too, and once its .invariant comparisons migrate it must resolve ViolationKind from the root, invariants being private |
| 5 | spec/core_spec.tex |
pin 3's requirement; Revision History row |
| 6 | spec/core_spec.pdf |
tracked build product of row 5 |
| 7 | crates/epiphany-testkit/tests/requirement_labels.rs |
pin 3's counts; pin 3's deletion of the temporary allowlist row |
| 8 | spec/PASS13_CANDIDATES.md |
S29 status append |
| 9 | spec/CONTRACT_P13S29_VIOLATION_KIND.md |
pin 12's transitions and historical marking |
| 10 | spec/EVIDENCE_P13S29_EXECUTION.md |
gate evidence destination |
Deliberately absent: epiphany-ops/src/reduce.rs — its two GraphInvariant
uses are test-scope and pass a variant as an argument, and reduce.rs
contains zero .invariant field accesses, so it compiles unchanged.
Re-verified before ratification, at revision K — not "at execution": pin 12
dispatches at ratification, so a row added "before dispatch" after an
execution-time discovery is temporally impossible. If this turns out wrong
during execution, execution stops and an amendment adds the row, like any
other frozen-pin defect.
§3. Mutation plan
Every guard is verified by re-introducing the defect and observing it. A compile error observed nothing. Restore by hand-editing, never with git.
Radii are derived against every observer this rung pins, in two classes. Behavioural and classification observers are pins 3a, 10 and 11; structural observers — reading declarations rather than running the checker — are pins 1b, 2 and 8. Not guessed, and not against pin 10 alone. The derivation is given per row so review can check it rather than take it. Deriving against pin 10 alone was revision C's finding 3; repeating the phrase would repeat the omission.
This rung has no passing-outcome mutation. Every mutation below must produce a failure, so gate 7 applies uniformly.
Four cells were measured, not derived, and the difference matters. Most radii
here follow from tests this contract pins. M2a's and M17·C1–C3's do not:
they reach pre-existing tests the contract did not write and whose fixtures it
does not control, so each was run against a disposable implementation and its
observed set pinned. Every one of the four differed from what static reading
predicted — M2a by 10 false positives and 17 omissions, M17·C1 by naming 7 where
18 fail and including a test (f4) that belongs to C2, M17·C2 and C3 by
predicting legacy observers that measurement put at 1 and 0.
Execution measured a fifth and sixth cell, M1·C6 and M1·C7; amendment 1 records them and authorizes their corrected radii. They are not among the four because they reach only tests this contract writes — the criterion that sent the other four to measurement was reaching tests the contract did not write, and that criterion was wrong. The operative property is whether a fixture is shared, not who authored the test.
Static derivation was attempted and failed. Revision I enumerated the 19
!fires(…) assertions and grouped them by score binding, yielding 13 tests.
Measured against a disposable implementation — check_invariant with its
payload filter removed, full workspace, --no-fail-fast — the pre-existing
set is 20, and against revision I's derived 13 the two are almost disjoint: 3 in
common, 10 false positives, 17 missed. M2a's full radius is those 20 plus
invariant_selector_discriminates_its_payload, the test this rung adds; 20 is
the legacy figure, not the cell.
Three reasons the static method could not work, each confirmed by the run:
- Binding identity is not state identity. Tests mutate the score between
assertions —
invariants.rs:3216removes the overlap before its negative assertion;invariants.rs:4457installs the tempo map only after one. A positive assertion proves nothing about the state the negative selector sees. m35was not one binding group at all — its negative useslonewhile its positive builds a separate score (invariants.rs:4939), contradicting the criterion outright.!fires(…)was never the whole surface. The mutation changes everycheck_invariantconsumer: direct zero-result assertions (invariants.rs:5158, the S2/S7/S8 matrix cells), and behavioural consumers —generators.rs:953, whereshrinkuses the selector to choose candidates — plus test-scope consumers inreduce.rs:16586.
The measured 20 are pinned in M2a's cell, alongside the new test. One caveat, stated because the
proxy is not the post-rung code: it was measured against today's single-arm type,
so it over-approximates for any test observing a rider violation through the
selector — after this rung those move to the requirement arm and Invariant(_)
will not return them. None of the 20 is a rider test (all are
g3b_measure20_tests, measure/meter territory), so the set is expected to hold;
execution confirms it and any difference is a finding.
| M | Mutation (complete, applicable) | Must fail — exhaustively, and why |
|---|---|---|
| M1·C4…C10 | Re-tag one rider emission back to ViolationKind::Invariant(CrossCuttingRefsResolve) — seven separate mutations, one per requirement condition |
That condition's pin-10 test, plus its legacy observer, plus the requirement discriminator where it uses that condition: requirement_selector_discriminates_its_payload is built from C5 and C8, so M1·C5 and M1·C8 also fail it. Derived per condition: C4, C5 also fail f4_tempo_segment_structural_defects_fire; C8 also fails f3_aleatoric_dag_referencing_absent_event_fires and mixed_fixture_splits_by_arm (C8 is in the pinned mixed fixture); C9 also fails f3_aleatoric_bounds_key_absent_and_reversed_window_fire; C10 also fails the migrated cmn_chromatic_accidental_in_edo_31_fires and display_renders_each_arm_exactly, whose requirement side uses a real accidental violation. C6 and C7 each fail both pin-10 order tests, and the legacy f4 assertion fails for neither. f4 holds one fixture tripping both conditions and asserts on the label, so the surviving sibling keeps supplying it — but pin 10's C6 and C7 tests stand in that same relation to each other: both are two_segments(seed, (2, 3), (1, 2)), which pin 10 establishes emits C6 and C7. Re-tagging either puts an Invariant violation into both fixtures, so the sibling's fourth assertion — the aggregate's invariant arm is empty — fails alongside the mutated condition's own pair assertion. Measured, not derived; the fixtures are shared by pin 10's deliberate choice and cannot be separated without the end-before-start segment pin 10 rejects. |
| M2 | check_invariant matches both arms |
All seven C4–C10 tests, via their absence assertion, and mixed_fixture_splits_by_arm, which pin 10 now requires to exercise both selectors and not only the aggregate. Not C1–C3, which assert presence and are unaffected by widening |
| M2a | check_invariant matches ViolationKind::Invariant(_), ignoring which — the wildcard replaces the payload match; the requirement arm is untouched |
invariant_selector_discriminates_its_payload, and the 20 existing tests measured below, all in invariants::g3b_measure20_tests: agreement_and_boundary_hold_together, m35_pickup_successor_boundary_flags_wrong_distance, m37_incomparable_abstains, m38_pickup_first_measure_boundary_clause_not_flagged, m39_unresolvable_reference_is_invariant_10_only, matrix_a1_none_time_signature_inapplicable, matrix_a3_vacuous_agreement, matrix_b2_governing_signature_unresolving_delegated, matrix_b3_vacuous_boundary, matrix_s1_wallclock_measures_wallclock_meter_changes, matrix_s2_agreement_a4, matrix_s2_boundary_b4, matrix_s4_measure_same_id_end_end_decides, matrix_s5_measure_distinct_ids_start_zero, matrix_s6_event_same_id_live_event_decides, matrix_s7_agreement_a4, matrix_s7_boundary_b4, matrix_s8_agreement_a4, matrix_s8_boundary_b4, matrix_s9_heterogeneous_measure_anchors |
| M3a | check_requirement matches ViolationKind::Requirement(_), ignoring label |
requirement_selector_discriminates_its_payload alone. Every other requirement-arm fixture presents its selector with one label, so a wildcard returns the same set as an exact match. Not M6's test either — but not for the reason an earlier draft gave: M6 is a separate mutation, so under M3a alone C6 and C7 still carry the same label, and one fixture with one label cannot distinguish wildcard from exact |
| M3 | check_requirement's predicate is replaced — not supplemented — by matches!(v.kind, ViolationKind::Invariant(_)), matching every invariant variant and no requirement label |
All seven C4–C10 tests via their retrieval assertion; mixed_fixture_splits_by_arm under pin 10's strengthened form; and every migrated positive legacy observer. Counted as test functions, not observations, the legacy set is exactly four: f3_aleatoric_dag_referencing_absent_event_fires, f3_aleatoric_bounds_key_absent_and_reversed_window_fire, f4_tempo_segment_structural_defects_fire, cmn_chromatic_accidental_in_edo_31_fires; and requirement_selector_discriminates_its_payload, whose exact projections both go empty. The two migrated negatives assert emptiness and pass under M3 — they are not in this radius |
| M4 | Render the requirement arm with the invariant form | display_renders_each_arm_exactly |
| M5 | Render the invariant arm with the requirement form | display_renders_each_arm_exactly |
| M6 | Give C6's emission the label req:time:tempo-segment-shape |
tempo_out_of_order_reports_order alone, via two of its three assertions, and the outcome is pinned precisely because the masking makes it counter-intuitive: check_requirement(score, "req:time:tempo-segment-order") is NOT empty — C7 still emits under that label from the same fixture — but the expected C6 pair (Requirement(order), "tempo segments are out of start order") is absent while C7's pair remains, so both the aggregate and the selector assertions fail on the pair. The third — targeted invariant-10 absence — still passes, the kind remaining a Requirement; it is a positive control here, not a detector. An earlier draft said the selector "returns nothing", contradicting the very masking that motivated pairs |
| M7 | Add a 22nd variant completely, and only these four edits: the GraphInvariant::Probe22 variant, a number() => 22 arm, an all() entry with its array length raised, and a violating_score arm returning an unmodified valid score. No shrink edit — shrink(score, inv) has no per-invariant match and needs none to compile |
Seven tests, because Probe22 enters all() with a fixture that violates nothing: graph_invariant_all_is_unchanged; m40_check_invariants_dispatches_invariant_20; every_invariant_has_a_negative_generator; every_invariant_shrinks_to_a_small_witness; shrink_is_idempotent; negative_generators_are_reasonably_targeted; full_invariant_sweep_via_public_api. An earlier draft named two and invented a shrink step |
| M7a | Order only: swap the all() entries for TupletSum and TiePairing, leaving membership and number() untouched |
graph_invariant_all_is_unchanged alone — length is 21 and membership identical, so nothing else notices |
| M7b | Membership only: replace all()'s PitchIdUnique entry with a second TupletSum, length still 21 |
graph_invariant_all_is_unchanged alone. Revision L also named every_invariant_has_a_negative_generator; that is false — it iterates all(), so it simply visits TupletSum twice and never asks for PitchIdUnique. A test driven by the mutated list cannot detect an omission from that list |
| M7c | number() only: swap the arms for BarlineGroupSameRegion and MeasureMeterConsistency (19 ⇄ 20), leaving all() untouched |
graph_invariant_all_is_unchanged alone — all() is identical and only the mapping moves |
| M7d | Declare a complete 22nd variant GraphInvariant::Probe22 with a number() => 22 arm and a violating_score arm returning an unchanged valid score, while omitting it from all() |
graph_invariant_all_is_unchanged's declaration inventory alone — all() is unchanged, so the sequence check passes and every generator test still enumerates 21. The violating_score arm is required for the mutation to compile at all: that function matches exhaustively on GraphInvariant (generators.rs:502), so a variant without an arm is a compile error, which observes nothing |
| M17·C1 | Re-tag check_cross_cutting_refs' emission to ViolationKind::Requirement("req:time:tempo-segment-order") |
Measured, 18 existing tests: full_invariant_sweep_via_public_api, every_invariant_has_a_negative_generator, every_invariant_shrinks_to_a_small_witness, negative_generators_are_reasonably_targeted, shrink_is_idempotent, m39_unresolvable_reference_is_invariant_10_only, matrix_b2_governing_signature_unresolving_delegated, inv10_resolves_annotation_layer_and_tuplet_parent, inv10_resolves_event_internal_references, inv10_resolves_graphic_object_references, inv10_flags_dangling_staff_instrument, inv10_flags_unresolved_time_signature_reference, f8_structural_reference_resolution, f9_dangling_decomposition_tuplet_reference_fires, inv10_flags_dangling_marker_lyric_and_gesture_refs, inv10_flags_dangling_repeat_kind_and_volta_anchors, inv10_flags_dangling_spanner_anchor, inv10_flags_dangling_sub_beam_event — plus the new cross_cutting_refs_stay_invariant_ten and invariant_selector_discriminates_its_payload. Revision L guessed 7, named f4, and missed the four generator tests entirely: violating_score(CrossCuttingRefsResolve) stops violating it, so the whole generator battery fails |
| M17·C2 | Re-tag the tempo segment start anchor condition to ViolationKind::Requirement("req:time:tempo-segment-order") — the label is pinned, since a different one changes which check_requirement call retrieves it |
Measured: exactly one existing test, f4_tempo_segment_structural_defects_fire — its ghost-region assertion, which is C2's and not C1's — plus the new tempo_start_anchor_stays_invariant_ten and mixed_fixture_splits_by_arm |
| M17·C3 | Re-tag the tempo segment end anchor condition to ViolationKind::Requirement("req:time:tempo-segment-order"), label pinned as above |
Measured: no existing test — nothing in the tree carries a dangling tempo end anchor. Its radius is the new tempo_end_anchor_stays_invariant_ten alone |
| M20 | Remove Eq from WellFormednessViolation's derives |
violation_types_declare_their_pinned_derives, assertion 1 alone. Eq is chosen because dropping it still compiles — BTreeSet needs Ord on ViolationKind, not Eq on the struct — so the guard is the only observer |
| M20a | Remove Hash from ViolationKind's derives |
that test, assertion 2 alone. Hash compiles without it; Ord would not, and a compile error observes nothing |
| M20b | Delete the two-arm sentence from the module header | that test, assertion 3 alone |
| M20c | Delete WellFormednessViolation's rustdoc |
that test, assertion 4 alone |
| M20d | Delete ViolationKind's rustdoc |
that test, assertion 5 alone |
| M20e | Remove ViolationKind from public_surface.rs's use line, dropping the matches! line with it so it still compiles |
that test, assertion 6 alone |
| M20f | Add to public_surface.rs, keeping all three imports: let _ = check_requirement(&epiphany_core::generators::valid_score(1), "req:time:tempo-segment-shape"); — fully qualified, because the file imports only the three pinned names and valid_score lives at epiphany_core::generators |
that test, assertion 6 alone. What it signs is the file-content prohibition, not a radius change: this call discards its result, so it would not by itself put the test in M3's or M9's radius. The prohibition exists because a call whose result were asserted would, and assertion 6 is what keeps the file type-level so that never arises |
| M20g | Add Hash to WellFormednessViolation's derives |
that test, assertion 1 alone — and it is the discriminator: a guard that searches for the pinned traits rather than comparing the line accepts an added one |
| M20h | Append to ViolationKind's rustdoc the line /// Both arms are exhaustive over what the checker reports. |
that test, assertion 5 alone |
| M20i | Reorder ViolationKind's derive list to #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
that test, assertion 2 alone. Reordering rather than adding, because every trait a simple field-less-payload enum can usefully derive is already there; a needle check for each trait passes on a reordering, and only equality fails |
| M20j | Append to the module header's pinned paragraph the line //! The arms are disjoint by construction. |
that test, assertion 3 alone |
| M20k | Append to WellFormednessViolation's rustdoc the line /// The witness is human-readable and not parsed. |
that test, assertion 4 alone |
| M18 | Add a complete, compiling third arm to ViolationKind: Deferred(GraphInvariant), a Display match arm rendering it, and no emitter anywhere |
violation_kind_has_exactly_two_arms alone — nothing emits it, so no classification, selector or Display test can observe it. That is precisely why the arm count needs its own guard, and why this row must exist in §3: gate 7 covers §3's mutations, so a mutation named only in a pin is a mutation nothing runs |
| M8 | Delete pin 3's requirement from core_spec.tex |
every_requirement_block_has_one_label, requirement_labels_follow_the_grammar, requirement_labels_are_unique_across_the_suite — the three count-bearing tests — and every_requirement_citation_is_defined, since this contract cites the label. Also tempo_constant_mismatch_reports_shape and tempo_nonconstant_missing_end_reports_shape? No — the Rust label string is independent of the .tex block. And pin 3a's prose guard, whose slice anchor disappears |
| M9 | Narrow check_invariants to the Invariant arm |
Every observer of a requirement-arm violation, because pin 6a makes both selectors projections of it: all seven C4–C10 tests; mixed_fixture_splits_by_arm; display_renders_each_arm_exactly, whose requirement side needs a real violation to exist; and the four migrated legacy test functions — f3_aleatoric_dag_referencing_absent_event_fires, f3_aleatoric_bounds_key_absent_and_reversed_window_fire, f4_tempo_segment_structural_defects_fire, cmn_chromatic_accidental_in_edo_31_fires; and requirement_selector_discriminates_its_payload. The two migrated negatives are NOT in this radius: M9 makes check_requirement return nothing, so their emptiness assertions pass vacuously. An earlier draft had this backwards and counted them as failures — the same vacuity the migration exists to remove, asserted as if it were detection. Widest radius in the rung, and the measure of what pin 4 protects. comprehensive_check_retains_both_arms is NOT in this radius: revision B deleted it, and naming it here was a live one-hop error, not a historical quotation |
| M10 | Restore the witness suffix " (req:tuning:accidental-modification-compatibility)" |
display_renders_each_arm_exactly — the requirement side, whose expected string is exact and whose fixture is a real violation — the migrated cmn_chromatic_accidental_in_edo_31_fires, whose pinned witness predicate forbids req:, and accidental_incompatible_reports_tuning_requirement, whose C10 pair assertion carries the same predicate. Earlier drafts named one, then two; restoring the suffix changes C10's witness, so every observer of that witness fails |
| M11 | Retag reversed aleatoric bounds from EventCoordinateModel to the locality requirement |
reversed_aleatoric_bounds_stay_invariant_four; display_renders_each_arm_exactly, whose invariant-side fixture is that violation; the legacy f3_aleatoric_bounds_key_absent_and_reversed_window_fire, whose invariant-4 assertion pin 11a leaves untouched; and invariant_selector_discriminates_its_payload, whose invariant-4 half is that very violation and disappears |
| M12pre | Delete pin 3's opening sentence — A tempo segment's \texttt{shape} and its \texttt{end\_tempo} \MUST{} be compatible. |
tempo_segment_shape_requirement_states_its_clauses_and_stays_s8_neutral, its equality |
| M12 | Replace clause 2 with If \texttt{shape} is \texttt{Constant}, \texttt{end\_tempo} \MUST{} equal \texttt{start\_tempo}. — dropping is present |
that test, its equality |
| M13 | Delete clause 3 — If \texttt{shape} is \texttt{Linear}, \texttt{Exponential} or \texttt{Curve}, \texttt{end\_tempo} \MUST{} be present. |
that test, its equality |
| M13post | Delete only This requirement states the compatibility that is enforced., keeping the sentence after it |
that test, its equality |
| M13neutral | Delete only It does not determine whether a constant segment records an \texttt{end\_tempo} at all., keeping the sentence before it |
that test, its equality. This is the sentence that keeps the requirement S8-neutral in prose. A mutation deleting the whole paragraph cannot sign it, because a guard checking only the first sentence would fail that too |
| M14a | Append to the block: A \texttt{Constant} segment's \texttt{end\_tempo} is canonically absent. |
that test, its equality |
| M14b | Append: A \texttt{Constant} segment's \texttt{end\_tempo} is normalized to absent. |
that test, its equality |
| M14c | Append: The preferred spelling omits \texttt{end\_tempo}. |
that test, its equality |
| M14d | Append: Canonically, a \texttt{Constant} segment omits \texttt{end\_tempo}. |
that test's equality |
| M14e | Append: A \texttt{Constant} segment's \texttt{end\_tempo} \MUST{} be absent. — stem-free, and it resolves P13-S8 |
that test's equality. This is the mutation the stem inventory could not catch, and the reason pin 3a asserts equality: it uses no forbidden stem, states no preference, and settles S8 outright |
| M15a | Extend pin 3a's slice end to the second \end{requirement} |
tempo_segment_shape_requirement_states_its_clauses_and_stays_s8_neutral, its equality — the slice is longer than pin 3's source |
| M15b | Start pin 3a's slice at the preceding \begin{requirement} |
the same test and assertion, from the other direction. Equality catches over- and under-reach alike; the separate structural assertion revision F added is retired as redundant |
| M16a | In cmn_chromatic_accidental_in_cmn_12_does_not_fire, change the fixture's space from cmn-12 to edo-31, making the accidental incompatible |
that test, migrated — its check_requirement(...).is_empty() becomes false. This is what proves the negative is non-vacuous; M1·C10 cannot, because this fixture emits no violation to re-tag |
| M16b | In a_score_with_no_accidental_extensions_never_fires_this_check, complete and ordered: change let s = valid_score(301); to let mut s = valid_score(301);; keep the existing assert!(s.tuning_context.accidental_extensions.is_empty()); where it is; then, after it and before the selector call, insert s.tuning_context.default_pitch_space = PitchSpaceId::new("edo-31"); and s.tuning_context.accidental_extensions.push(fixture_extensions("cmn-accidentals", PitchSpaceModification::CmnChromatic(1))); |
that test, migrated — its check_requirement(…).is_empty() becomes false. The ordering is pinned, not incidental: inserting the extension before the emptiness assertion trips that precondition first and proves nothing about selector non-vacuity. Both edits are needed for incompatibility: the space must be edo-31 and the accidental CmnChromatic, exactly as cmn_chromatic_accidental_in_edo_31_fires builds it |
§4. Gate
cargo test --workspace— theCLAUDE.mdbaseline plus this rung's net-new tests, 0 failed, 0 ignored. Gate 1 owns the new total.--no-fail-fastwhenever anything is failing.cargo +1.95.0 clippy --workspace --all-targets -- -D warnings— clean.cargo +1.95.0 fmt -p epiphany-core -p epiphany-testkit --check— clean. Never--all.- Every staged path is a §2 row; every §2 row staged or named unused.
git diff --cached --checkclean.- Identifier and field migration, scoped:
- no
InvariantViolationidentifier remains in Rust code (crates/**/*.rs). This contract, the ledger and the annex quote it historically and are out of scope; - no
.invariantfield access remains on aWellFormednessViolation.DeferredCheck.invariantis explicitly retained (§0.5) and must still be present. Both greps recorded verbatim.
- no
- Every mutation of §3 observed, with the failing assertion verbatim and the
complete
--no-fail-fastfailure set, compared against its derived cell; any mismatch is a finding. Evidence intospec/EVIDENCE_P13S29_EXECUTION.md. GraphInvariant::all()re-derived and confirmed at 21, andcore_spec.tex's count claim confirmed unchanged.cd spec && latexmk -xelatex -interaction=nonstopmode core_spec— re-run until "There were undefined references" clears;core_spec.pdfrebuilt.- S29's ledger append, verified by pin 13's removed-plus-added
reconstruction:
strip(added) == strip(removed) + " " + APPENDwith the terminal|restored onadded. Not an added-lines-only check — that instruction was superseded, and an added line here is the whole row, not the append. - The temporary allowlist row is ABSENT from the staged
requirement_labels.rs, and the two surviving rows are present and unchanged. A stale temporary row is inert once the label exists — nothing but this gate would catch it. cargo test -p epiphany-testkit --test requirement_labelspasses with the temporary row absent and the label defined. This, with gate 11, is what distinguishes the citation gate passes because the label is now defined from because it is still allowlisted — states indistinguishable from outside.- Pin 11's inventory recorded and confirmed, both modules: 25
observations preserved, exactly 9 migrated (5 in
review_fix_tests_4, 4 inaccidental_compatibility_tests), exactly 16 unchanged, none deleted and none softened. The two migrated negative accidental assertions are confirmed non-vacuous by M16a and M16b, each of which makes its own fixture violate. M1·C10 cannot do this: those fixtures emit no violation to re-tag. - Pin 12's lifecycle: the status block reads exactly
STATUS: LANDED by this commit.with no hash; the frozen-pins statement is present and shows no hunk in a zero-context staged diff, having been written at ratification; all review-round blocks above §0 are marked a dated historical record. - Read-checked, no machine observer: pin 3's requirement sits in Chapter 3
immediately after
req:time:tempo-segment-order's\end{requirement}; the added Revision History row is sliced from the staged diff and compared in normalised whole-record form against pin 13's text — every fact it pins, not just its distinguishing names; the/// 10.rider note names the four labels and no longer names P13-S29 as a pending owner. - Pin 9's prose outcomes, each read-checked against its own site — the
/// 10.rider note names the four labels and not P13-S29; the tempo, aleatoric and accidental headers no longer say their conditions are surfaced under invariant 10; the accidental header citesreq:tuning:accidental-modification-compatibilitysymbolically rather than the stalecore_spec.tex:3120; the migrated negative's message no longer says "compatibility invariant";review_fix_tests_4's module doc no longer calls its tempo subject an invariant;check_invariants' rustdoc states it returns both arms;README.md's invariant row: its description cell equals pin 9a's fenced source after whitespace collapse; andDECISIONS.mdcarries a supersession note with the original decision intact. Every one of these can be omitted with all other gates green.
§5. What ratification does NOT settle
- P13-S8's canonical-form ruling. Pin 3 states the enforced compatibility and nothing more; pin 3a guards that it says nothing more.
- Whether the riders should eventually leave
check_invariantsentirely (disposition (c)). Pin 4 keeps them for safety; a later rung may revisit it with a migration path for all ~10 callers. DeferredCheck's shape. §0.5 verified it is correctly attributed today. A future deferred check for a non-invariant rule inherits this rung's problem.- The recurring citation-gate collision — a contract proposing a label breaks
the gate the moment it is written, now four times. That belongs in
CLAUDE.mdas a standing note, on its own terms, not here.
§6. AMENDMENT 1 — §3's M1·C6 AND M1·C7 RADII, MID-EXECUTION
STATUS: LANDED by this commit.
DATED HISTORICAL RECORD — amendment 1 is ratified and executed. §6's finding, its defect class and its dispositions are an account of what was found and decided on 2026-08-12, and state no current condition.
Ratified 2026-08-12 on the authority of the repository owner, the final review round returning zero findings. The replacements are executed, not edited; a further defect is its own amendment with its own review round.
§3 was frozen and dispatched at cea21cd. Ratification authorizes and
freezes this amendment; execution applies the replacements. §6.9 pins the
landed form and the order of the remaining steps.
Scope — the complete write surface, all four parts. A description of the normative correction is not a description of what gets written, and an earlier draft of this head said "two radius cells and nothing else" while prescribing three further edits below it:
| Edit | Where | Touch row | |
|---|---|---|---|
| 1 | M1 row replacement | §3 (this file) | 9 |
| 2 | Preamble append | §3 (this file) | 9 |
| 3 | This amendment's own lifecycle transitions | §6 (this file) | 9 |
| 4 | Resolution/resumption append | spec/EVIDENCE_P13S29_EXECUTION.md |
10 |
"Two edits, both in §3" describes edits 1 and 2 — the normative §3 correction — and nothing else. Edits 3 and 4 are lifecycle and record.
One further act belongs to ratification rather than to execution: the
ratification commit stages spec/EVIDENCE_P13S29_EXECUTION.md in its
pre-amendment state, under the same touch row 10, to serve as §6.5c-bis's
oracle. It is listed here and not in the table because it is not an edit this
amendment makes — it is the act that gives the amendment something durable to be
checked against, and it must happen before edit 4.
What does not move: no pin, no test, no fixture, no behaviour, no other §3 cell. It does not create, retire or renumber a mutation, and it rewrites no existing evidence — §6.5d is the standing prohibition.
6.1 The defect
§3's M1 row closes:
C6 and C7 fail their pin-10 test alone: the legacy
f4assertion is one fixture tripping both, so re-tagging one leaves the other still reporting the same label and the legacy assertion still passes
Measured, the radius of each is two, not one. Transcripts: annex §5.3, §5.4.
| M | §3's cell | Measured |
|---|---|---|
| M1·C6 | tempo_out_of_order_reports_order |
that test and tempo_overlap_reports_order |
| M1·C7 | tempo_overlap_reports_order |
that test and tempo_out_of_order_reports_order |
6.2 Why — and what in the quoted sentence survives
The sentence's claim about f4 is correct and is retained. f4 is in
neither radius, for exactly the reason given: it holds one fixture tripping both
conditions and asserts on the label, which the surviving sibling keeps supplying.
Measurement confirms it.
What the sentence omits is that pin 10's own C6 and C7 tests stand in the same relation to each other. Pin 10 pins four assertions per row; the fourth is the aggregate's invariant arm is empty. Pin 10 also states, in terms:
C6's natural fixture —
seg(2,3)thenseg(1,2)— is also overlapping, so it emits C6 and C7 under the same label.
Both tests are built by two_segments(seed, (2, 3), (1, 2)), differing only in
the seed. Re-tagging either condition therefore places an Invariant
violation in both fixtures, and the sibling's fourth assertion fails on it.
The cell reasoned about the legacy observer and stopped there. Having established the shared-fixture property one clause earlier, it did not ask the same question of the tests this contract itself writes.
6.3 What must NOT change in response
- Not the fixtures. The only shape isolating C6 is an end-before-start segment, which pin 10 rejects as "unnatural and tests a shape the checker should never see."
- Not pin 10's fourth assertion. §3's M2a derivation depends on it: it is what keeps the seven C4–C10 tests out of M2a's radius structurally rather than by assumption.
- Not any other M1 cell. C4, C5, C8, C9 and C10 were measured in the same pass and matched exactly — 2, 3, 4, 2 and 3 respectively.
6.4 The correction is a strengthening, not a relaxation
The amendment adds no observer. Pin 10's tests already supplied both — its
own (kind, witness) pair assertion and the sibling's invariant-arm assertion
have detected each re-tagging since the moment those tests were written. What
changes is the accounted and required radius: §3 credited the mutation with
one observer and would have read the second as a mismatch, so the amendment
recognizes an observation the contract was under-counting.
That is a strengthening because the required radius is now two: a future implementation that silenced the sibling assertion would fail this gate, where under the dispatched cell it would have passed.
6.5 The replacements — edits 1, 2 and 4 of the scope table
Touch rows 9 and 10 already cover both files this amendment writes to
(CONTRACT_P13S29_VIOLATION_KIND.md, EVIDENCE_P13S29_EXECUTION.md). No touch
row is added.
6.5a — §3's M1 row
Replace the sentence quoted in §6.1 with:
C6 and C7 each fail both pin-10 order tests, and the legacy
f4assertion fails for neither.f4holds one fixture tripping both conditions and asserts on the label, so the surviving sibling keeps supplying it — but pin 10's C6 and C7 tests stand in that same relation to each other: both aretwo_segments(seed, (2, 3), (1, 2)), which pin 10 establishes emits C6 and C7. Re-tagging either puts anInvariantviolation into both fixtures, so the sibling's fourth assertion — the aggregate's invariant arm is empty — fails alongside the mutated condition's own pair assertion. Measured, not derived; the fixtures are shared by pin 10's deliberate choice and cannot be separated without the end-before-start segment pin 10 rejects.
6.5b — §3's preamble count
§3's preamble opens a paragraph with:
Four cells were measured, not derived, and the difference matters.
Six are now measured, and a count left standing beside what it counts is itself one of the ledger's recurring defects. Do not edit that sentence — it is an accurate statement about what was measured before ratification, and rewriting it would destroy a true historical claim to fix a staleness. Instead append to the end of that same paragraph:
Execution measured a fifth and sixth cell, M1·C6 and M1·C7; amendment 1 records them and authorizes their corrected radii. They are not among the four because they reach only tests this contract writes — the criterion that sent the other four to measurement was reaching tests the contract did not write, and that criterion was wrong. The operative property is whether a fixture is shared, not who authored the test.
This is deliberately an append, not a rewrite: the paragraph's subject is the pre-ratification derivation and its failures, and the amendment is a later event in the same record.
6.5c — the annex append, pinned as a source template
spec/EVIDENCE_P13S29_EXECUTION.md gains a new final section §8. It is
pinned as source, not described by its contents — a content description admits
a truncated or differently scoped record that still satisfies every "carries X"
clause, which is what an earlier draft of this subsection did. Exactly one
slot, {RATIFICATION_HASH}; everything else verbatim.
## §8. Amendment 1: resolution and resumption
Amendment 1 is ratified at `{RATIFICATION_HASH}` and landed by this commit. It
corrects two radius cells in the contract's §3 and changes no pin, test, fixture
or behaviour.
**§5 and §7 above are the dated execution record and are not edited.** They
state what was expected, what was observed, and why execution stopped. The
mismatch they record is the reason this amendment exists; reconciling them would
remove it.
### 8.1 The corrected radii
| M | Dispatched cell (§5) | Corrected cell, measured |
|---|---|---|
| M1·C6 | `tempo_out_of_order_reports_order` | that test **and** `tempo_overlap_reports_order` |
| M1·C7 | `tempo_overlap_reports_order` | that test **and** `tempo_out_of_order_reports_order` |
Both were observed before the halt. The transcripts in §5.3 and §5.4 stand as
the observation and **were not re-run**: the amendment corrects the expectation
they were compared against, not the observation.
### 8.2 Resumption
The mutation sequence resumes at **M2**. M1 is complete — C4, C5, C8, C9 and C10
matched their dispatched cells, and C6 and C7 match the corrected cells above.
Extraction endpoint — the same boundary rule as §6.5c-bis. §8 runs from its
## §8. heading to the last line before the next ## § heading, or EOF if
none. Normalize with §6.5c-bis's canonical serialization, then compare for
equality. Not "contains", not "mentions".
Later evidence sections are expressly permitted and do not disturb A4. M2 onward still needs its transcripts, and they land as §9 and beyond. Had §8 been defined as "heading to EOF", the very next evidence section would have invalidated A4 — a gate that the work it gates is guaranteed to break.
§5 is closed at M1 and must not be extended. It is digest-frozen by §6.5c-bis, so appending M2's rows to the expected-versus-observed matrix — the obvious place for them — would break A4(b). §9 continues the matrix under its own heading and carries M2 onward. This is a consequence of freezing an open section, and it is stated here because nothing else would warn the executor before the gate failed.
landed by this commit, never a landing hash — the annex lands in that
commit, and pin 12 already states why: a commit cannot carry its own id.
§5 and §7 are NOT edited. §5's table keeps its dispatched cells, its two ❌ marks and its "the phase is halted at M1" opening; §7 keeps the finding in the tense it was found in. §6.5c-bis pins the oracle.
6.5c-bis — the oracle for §5 and §7
"Byte-identical to their pre-amendment state" needs something durable to compare against, and the annex is untracked. Once §8 is appended there is nothing left to diff. Two measures, both required:
1. The ratification act commits the pre-amendment annex. Ratification stages
spec/EVIDENCE_P13S29_EXECUTION.md exactly as it stands at the halt — §1–§7,
no §8 — under touch row 10. That commit's blob is the named oracle, and
A4 is checked with git show <ratification-commit>:spec/EVIDENCE_P13S29_EXECUTION.md.
This is also what makes the amendment reviewable at all: an untracked evidence
file cannot be cited by a frozen document.
2. Digests, pinned here, as the oracle's oracle.
The canonical byte serialization, pinned exactly — a digest with an inferred serialization is a digest nobody else can reproduce. In order:
- Read the file as bytes and decode UTF-8. The annex is LF-only; a CRLF copy is out of scope and will not reproduce these hashes.
- Find section starts by
^## §\d+\.(multiline). A slice runs from its own heading — the heading line is included — to the byte before the next such heading, or to EOF for the last. - Split the slice into lines and right-strip each line (trailing spaces and tabs).
- Repeat while the last retained line is empty or is exactly
---: drop it. One loop, not three passes — a---and the blank lines around it are removed together however they interleave. - Join the retained lines with
\n, and emit NO terminal newline. - SHA-256 over the UTF-8 bytes of that string.
Step 5 is the one that actually needs pinning. An ordinary
"\n".join(lines) + "\n" — the natural way to write a file back out — yields
53b32278… for §5 and eb704854… for §7 instead of the pinned values. The
digests reproduce only because there is no terminal newline, and nothing in an
earlier draft said so.
Steps 2–3 use ^## §\d+\. and any line splitter that agrees with LF splitting;
verified that str.splitlines() and split("\n") produce identical digests for
both slices, the annex containing no form feed, vertical tab or U+2028.
| Slice | SHA-256 | Lines |
|---|---|---|
| annex §5 | 40ce82a70339159024c69dd5e280d8bd6846efbeb1eecd5b7971a2737826f74b |
134 |
| annex §7 | cdd9bfd91da5091174201b1bc16aea6a5f162cc5f6d8553f6d58d7c56aacccd8 |
76 |
The slice rule is what makes the §7 digest stable across the append. §7 currently ends at EOF and will end at §8's heading; the rule ends it at the last non-blank line either way, so appending §8 must not move either digest. A rule defined as "to EOF" would have made §7's digest change by construction and the gate unfalsifiable.
6.5d — the prohibition on rewriting evidence
An earlier draft's gate A4 would have rewritten §5's cells and removed the halt notice. That is prohibited, and the prohibition is the reason this subsection exists.
§5 and §7 are the dated record of what was expected, what was observed, and why execution stopped. Overwriting the expectation with the corrected one would leave an annex in which the observed radius matches the recorded cell everywhere — an annex that no longer contains the reason this amendment exists. The mismatch is the evidence.
The rule, stated generally: an execution record is appended to, never
reconciled. A correction is a later event in the record, not a revision of an
earlier one. This is the same discipline PASS13_CANDIDATES.md status cells
already carry — appended to, not rewritten — and the same reason §6.5b appends
to §3's preamble rather than editing its count.
6.6 The generalization this amendment explicitly declines to make
It would be wrong to conclude that same-label conditions always cross-talk. The same pass measured the other two shared-label pairs and found no cross-talk in either:
- C4/C5, sharing
req:time:tempo-segment-shape:Constant-with-mismatch and non-constant-missing-end_tempoare mutually exclusive shapes. - C8/C9, sharing
req:time:aleatoric-reference-locality: distinct fixtures, each aggregate carrying exactly one violation.
The property that matters is fixture identity, not label identity, and only C6/C7 share a fixture shape.
6.7 The defect class, for the ledger
The one-hop correction, the recurring shape: a fact established in one pin
is not carried into the section that depends on it — here within a single table
cell, which cites the shared-fixture property about f4 in one clause and
overlooks it about pin 10's own tests in the next.
§3's preamble already records that all four of its measured cells differed from static reading, and measured them because they reached tests the contract did not write. C6 and C7 reach tests the contract did write — which is why they were derived rather than measured. Authorship of a test is not knowledge of its fixture's reach.
6.8 Gate
-
A1. §3's M1 row carries §6.5a's replacement text, and §3's preamble paragraph carries §6.5b's appended sentences with its opening "Four cells were measured" sentence unedited. The superseded sentence survives in the file exactly once, inside §6.1's blockquote, where it is a dated quotation of what was corrected — not "absent from the file", which this amendment makes permanently unsatisfiable by quoting it. Checked by stripping blockquote markers and counting: one occurrence, and it is §6.1's.
-
A2. M1·C6 and M1·C7 each observed, each failing exactly
tempo_out_of_order_reports_orderandtempo_overlap_reports_order, with the failing assertions verbatim in the annex — and each restoring to44 / 1604 / 0 / 0. -
A3. Measured against the halt checkpoint, not against
cea21cd. The ratification commit predates every pin-10 test, so "no test differs fromcea21cd" is unsatisfiable by construction — the tests P13-S29 exists to add necessarily differ from it. The checkpoint is the working tree at the M1 halt:44 suites / 1604 passed / 0 failed / 0 ignored, annex §4. Against it: no implementation, test or fixture change is attributable to amendment 1, and no staged hunk incrates/belongs to it. Amendment 1's diff touches exactly two files,spec/CONTRACT_P13S29_VIOLATION_KIND.mdandspec/EVIDENCE_P13S29_EXECUTION.md. -
A4. Two checks, both mechanical:
(a) The append. The annex's §8, normalized per §6.5c, equals §6.5c's template with
{RATIFICATION_HASH}filled — including the literallanded by this commit. Whole-section equality, not presence of topics.(b) The preserved record. Annex §5 and §7, sliced and normalized per §6.5c-bis, hash to
40ce82a7…f74bandcdd9bfd9…ccd8, and are byte-identical to the same slices ingit show <ratification-commit>:spec/EVIDENCE_P13S29_EXECUTION.md. Halt notice and ❌ marks intact. This check is also what observes the ratification act: if the ratification commit does not contain the pre-amendment annex, thegit showdoes not resolve and A4(b) cannot pass.A4 observes an append; it must never observe a reconciliation (§6.5d).
-
A5. §6's status block carries §6.9's exact ratified form, and — at landing — its exact landed form and the historical marking.
The contract's own top status has two permitted values, and A5 accepts whichever the landing commit makes true:
Landing Contract's top status must read Amendment 1 lands alone, contract still mid-flight STATUS: RATIFIED; DISPATCHED.— unchangedAmendment 1 lands in the same commit as P13-S29 pin 12's exact landed transition, STATUS: LANDED by this commit.An earlier draft required the first unconditionally, which rejected compliance with pin 12 in the same-commit case §6.9 explicitly permits — a gate contradicting a lifecycle the same amendment allows. What A5 forbids in both branches is unchanged: no pin-12 transition attributable to amendment 1 itself. Without A5 the scope table's edit 3 would be a prescribed edit no gate observes.
6.9 This amendment's own lifecycle
Mirrors pin 12's form for the contract, with the transitions an amendment needs and a contract does not.
-
On ratification: §6's status block reads exactly
STATUS: RATIFIED; FROZEN. Execution of P13-S29 resumes at M2.and the frozen-amendment statement is added:
The replacements are executed, not edited; a further defect is its own amendment with its own review round.
This adapts P13-S26's formula and does not reproduce it word for word. The precedent reads "The amended pins are executed, not edited; a further defect is its own amendment with its own review round." (
CONTRACT_P13S26_INVARIANT10_SURFACE.md:1656). Amendment 1 amends no pin — it replaces two radius cells — so quoting the precedent exactly would assert something false about this amendment. The second clause is verbatim; the first substitutes replacements for amended pins. An earlier draft claimed exact reuse while paraphrasing; the claim, not the wording, was the defect. -
Ratification resumes execution, and Amendment 1's own execution comes first. The order is pinned:
- Ratify — §6's status block takes the form above; the pre-amendment annex is committed as §6.5c-bis's oracle.
- Apply edits 1, 2 and 4 — the M1 row replacement, the §3 preamble append, and the annex §8 append.
- Pass A1–A5.
- Resume the mutation sequence at M2.
"Ratification resumes execution at M2" must not be read as skipping steps 2 and 3: the amendment has its own execution, and M2 follows it.
The resumption point is M2 — not M1. M1·C6 and M1·C7 were already observed (annex §5.3, §5.4); the amendment corrects the expectation they were compared against, not the observation. The tree is unchanged between those runs and this amendment — A3's checkpoint is what establishes that — so A2 is discharged by the existing dated transcripts and requires no re-run. Re-running would produce a second, identical transcript and a record implying the first was doubted.
-
On landing: exactly
STATUS: LANDED by this commit.— no hash; a commit cannot carry its own id.
-
On landing, §6's review-round record is marked a dated historical record, in the form P13-S26 §9 uses: §6's findings and dispositions are an account of what was found and decided, and state no current condition.
-
P13-S29's own §0-and-above review blocks are NOT marked historical by this amendment. Pin 12 marks them on the contract's landing, which has not happened — execution is mid-flight. Marking them here would date a record whose subject is still live.
This amendment does not alter pin 12. The contract's own status block still
reads STATUS: RATIFIED; DISPATCHED. and transitions to LANDED on the
contract's landing commit, which may be the same commit that lands this
amendment or a later one.
§7. AMENDMENT 2 — PIN 13'S APPEND DATE, POST-LANDING AND ADMINISTRATIVE
STATUS: RATIFIED; FROZEN.
Ratified 2026-08-12 on the authority of the repository owner. The correction is appended, not edited; a further defect is its own amendment with its own review round.
7.0 Scope — the complete write surface, in two commits
The normative correction is one appended sentence in the ledger. The write surface is larger than the correction, and an earlier draft of this section said "one appended sentence … nothing else" while §7.6 below prescribed three further edits. Both statements cannot hold; the surface is pinned here.
| Commit | Files | Contents | |
|---|---|---|---|
| 1 | Ratification | spec/CONTRACT_P13S29_VIOLATION_KIND.md only |
§7's status takes its ratified form and gains the frozen statement (§7.6). The ledger correction stays UNSTAGED. |
| 2 | Execution / landing | that file and spec/PASS13_CANDIDATES.md |
the ledger append (§7.3); §7's landed status; §7's historical marker |
Then B1–B5.
What does not move, in either commit: no pin, no test, no fixture, no
mutation, no radius, no behaviour, no .tex, no .pdf, nothing under crates/.
It does not reopen amendment 1, and it changes no lifecycle transition belonging
to P13-S29 or to amendment 1 — both are landed at 52fcf37.
7.1 The defect
Pin 13's APPEND opens:
RESOLVED 2026-08-11 by
spec/CONTRACT_P13S29_VIOLATION_KIND.md, disposition (b) type-neutral.
Actual resolution and landing were both 2026-08-12. The contract's own
ratification line says so (§0: "Ratified 2026-08-12"), as does amendment 1's.
The pinned date is the date the append text was drafted, and pin 13 froze it
before the dates diverged.
This is not a new discovery. Gate 10 rejected the artifact over exactly this character at landing, and annex §11.1 recorded it and flagged it for the owner rather than settling it. That is the gate working: a one-character deviation from a pinned source was made visible instead of absorbed.
7.2 What is preserved, and why — append-only, not "history would change"
Pin 13 is NOT edited. Its APPEND keeps 2026-08-11.
Annex §11.1 is NOT edited. It records the deviation in the tense it was found.
The original ledger sentence is NOT edited, and the reason is the ledger's own
convention: a status cell is appended to, never rewritten. It records what was
said when it was said, and a correction is a later entry rather than a revision of
an earlier one. CLAUDE.md:12 states the rule — "Status cells are appended
to, not rewritten, so an opener can lag the truth by several rungs" — and this row
is a status cell. Attributed to CLAUDE.md, not to PASS13_CANDIDATES.md: an
earlier draft said the ledger states the rule of itself, and it does not. The
convention is imposed on the ledger by the standing rules, which is a stronger
place for it to live and a different citation.
An earlier draft of this subsection gave a false reason — that rewriting the
sentence "would make a discharged gate's record false about the tree it
examined." That is wrong. 52fcf37 is immutable. Gate 10's observation was
made against that commit's content and remains true of it no matter what any later
commit says; nothing written now can reach backwards and falsify it. Recorded
rather than quietly replaced, because the false reason is the more persuasive one
and would otherwise be re-derived.
7.3 The correction, pinned verbatim
Appended inside the S29 row, immediately before its terminal |, after
**GraphInvariant did not move: 21 variants, unchanged.**:
**CORRECTION, appended 2026-08-12:** the *resolved* date above reads 2026-08-11 because that is the date pin 13 pinned its append text during drafting. **Actual resolution and landing were both 2026-08-12** — ratified `cea21cd`, amendment 1 at `29ef3af`/`e7bd12c`, landed `52fcf37`. The original sentence stands because this ledger's status cells are appended to, never rewritten; pin 13 pins it verbatim and gate 10 verified the artifact against it at landing.
7.4 Gate 10 is discharged and is NOT re-run
After this append, gate 10's reconstruction no longer holds of the working tree,
and that is correct rather than a regression. Gate 10 is a landing gate: it
observed the append at 52fcf37, where
strip(added) == strip(removed) + " " + APPEND was true of that commit, and it
has no standing form and no CI observer.
Stated because the alternative is a trap: an executor who re-ran gate 10 after this amendment would read its failure as a defect and "repair" it by deleting the correction — restoring a date everyone now knows is wrong. A reconstruction gate is valid only against the commit that performed the append.
7.5 Gate
RC is the ratification commit and LC the landing commit. Every oracle
below is read from a commit, never from the working copy — an oracle taken
from the working copy is not an oracle: an executor who edits the template and
the artifact together satisfies it, which is exactly the hole an earlier draft of
B1 left open.
-
B1 — exact append-only reconstruction of the S29 row, both oracles from
RC.ORACLE= the single line beginning| P13-S29ingit show RC:spec/PASS13_CANDIDATES.md;CORRECTION= the fenced text of §7.3 ingit show RC:spec/CONTRACT_P13S29_VIOLATION_KIND.md.
Let
trim(x)remove trailing whitespace, then one trailing|, then trailing whitespace again. Require all of:git diff RC LC -U0 -- spec/PASS13_CANDIDATES.mdyields exactly one removed and exactly one added line, both beginning| P13-S29;- the added row equals
trim(ORACLE) + " " + CORRECTION + " |"; CORRECTIONoccurs exactly once inspec/PASS13_CANDIDATES.md— the file is named because "the whole file" was ambiguous between the ledger and the contract, and the contract necessarily contains it too, in §7.3.
A presence check admits a duplicated correction, a truncated one, an edit to a neighbouring row, or a rewritten prefix. Equality against
RC's row admits none of those. -
B2 — the frozen bytes, in three parts, each with its command.
-
The annex is untouched:
git show 52fcf37:spec/EVIDENCE_P13S29_EXECUTION.mdequals the working file, byte for byte. This observes annex §11.1 without slicing it. -
Everything before §7 is untouched: let
BLOB = git show 52fcf37:spec/CONTRACT_P13S29_VIOLATION_KIND.md. The working file's firstlen(BLOB)bytes equalBLOB, and the remainder begins\n---\n\n## §7.. This observes pin 13'sAPPEND, the contract's top status, §6's status and historical marker, and §3 as amendment 1 left it in one comparison, so §7.6 needs no separate "not touched" clause that could drift out of step with it. -
§7's ratified body is untouched by landing.
git diff RC LC -U0 -- spec/CONTRACT_P13S29_VIOLATION_KIND.mdcontains exactly: one removed lineSTATUS: RATIFIED; FROZEN.; one added lineSTATUS: LANDED by this commit.; and four added lines for the marker — one empty line followed by §7.6's historical marker block, equal to it line for line. No other added or removed line.The empty line is counted, not overlooked: a markdown blockquote needs a blank line before it, so the insertion is four lines and a gate demanding three would be unsatisfiable by the edit it prescribes.
Without this, B2's prefix protects only what precedes §7 and B5 checks selected lines inside it — leaving the ratified §7 body, including §7.3's template and the whole gate, editable during landing.
-
-
B3 — write surface, read from the commits rather than the index. "The staged set" is not durable once the commit exists; a path set relative to the commit's parent is.
git show --name-only --format= RCis exactlyspec/CONTRACT_P13S29_VIOLATION_KIND.md;git show --name-only --format= LCis exactly that file andspec/PASS13_CANDIDATES.md;- neither names any path under
crates/, any.pdf, or any.tex.
-
B4 — baseline unchanged:
44 suites / 1606 passed / 0 failed / 0 ignored. Asserted rather than assumed: the ledger is not compiled, butrequirement_labels.rsscansspec/, so a ledger edit is not automatically inert. -
B5 — §7's lifecycle, complete rather than sampled.
- At
RC: §7's status line is exactlySTATUS: RATIFIED; FROZEN., and §7.6's frozen statement block is present, equal to its pinned source line for line. - At
LC: §7's status line is exactlySTATUS: LANDED by this commit.— no hash — the complete historical marker block of §7.6 is present and equal to its pinned source line for line, not by its first line, and the frozen statement is byte-identical to its form atRC. A landing that swapped the status and dropped the frozen statement would pass a first-line check; ratification freezes that statement, and landing must carry it. - The contract's top status and §6's status are unchanged — B2's prefix equality establishes this; B5 names where the check lives rather than restating it.
- At
7.6 This amendment's own lifecycle
On ratification, §7's status block reads exactly STATUS: RATIFIED; FROZEN.
followed by a blank line and this frozen statement block, pinned as source:
Ratified 2026-08-12 on the authority of the repository owner. The correction is
appended, not edited; a further defect is its own amendment with its own review
round.
"Appended, not edited" — not "the replacement is executed, not edited". This amendment appends a correction; it replaces nothing, and the standing formula's noun would name an act it does not perform. Amendment 1 did replace two radius cells and used the replacement wording correctly; the formula is per-amendment, not boilerplate.
On landing, §7's status line becomes exactly STATUS: LANDED by this commit.
— no hash — the frozen statement block above is left in place, and this
historical marker block is inserted after it, separated by one empty line,
pinned as source:
> **DATED HISTORICAL RECORD — amendment 2 is ratified and executed. §7's
> correction, its rationale and its gate are an account of what was found and
> decided on 2026-08-12, and state no current condition.**
Nothing else in §7 changes between RC and LC, and B2's third part is
what observes that — a status swap and this marker's lines, and no other line.
P13-S29's and amendment 1's status blocks are NOT touched, and B2's prefix equality is what observes that — both lie before §7.