59 KiB
Contract — P13-S16: the projection gets maintained
Status: DRAFT — UNBLOCKED 2026-08-09. NOT RATIFIED, and therefore NOT dispatchable.
P13-S27 landed and was accepted at 4df8e25, so pin 0's blocker is discharged: an
authority now defines the implementation's current reduction semantics
(epiphany_ops::CURRENT_REDUCTION_ALGORITHM_VERSION, currently 0), and
core_spec.tex:11614's requirement is met — a stale canonical base is refused with
CanonicalBaseRequiresRebuild on both the read and write paths.
What remains before dispatch is this contract's own ratification. It is complete and ratifiable as a plan and has not been through adversarial review rounds. Unblocked and dispatchable are different states, and conflating them is how the S27 contract came to be ratified after a single round and then have that ratification withdrawn — see its status block.
This rung's first act is bumping the authority to 1 (pin 12), because it
changes CreateStaffGroup's reduction verdict. That bump is the discipline S27
installed: no mechanism can detect a semantics change, so the bump is the entire
guarantee.
Draft amendment 1 — 2026-08-09, on ratification reconnaissance. NOT a review round.
Fourteen findings, thirteen blocking, against the draft before its first ratification round. Nine came from the reconnaissance pass, five from a second sweep of the same defect classes. The contract is a DRAFT, so these are edits, not amendments to frozen pins — the distinction S27's status block draws, applied here.
| # | Finding | Disposition |
|---|---|---|
| 1 | §2 omitted epiphany-ops/src/lib.rs, though the rung's defining act is bumping the authority there. The bump had no pin, no touch row, no gate and no report item — it lived only in pin 0's discharge note |
Pin 12 added, with touch row 7, gate 10, report item 2b |
| 2 | Invariant 21 is a TWO-crate change, not one. violating_score (generators.rs:498) matches GraphInvariant exhaustively — invariant 21 does not compile without an arm — and four all()-driven tests then require a real generator |
Touch row 8 added, with the consumer surface enumerated. This is the amendment's root cause; everything else is bookkeeping by comparison |
| 3 | S27's two tripwires fail at gate 1 with no touch row. roundtrip.rs test 10b panic!s by design once the authority moves; serialize.rs test 10a asserts the literal 0 and its own doc says it is expected to fail when S16 bumps |
Touch rows 9 and 10, plus gate 11 requiring they be updated and not silenced |
| 4 | requirement_labels.rs — the escapee CLAUDE.md names by name — was absent, and the contract never decided whether pin 6 or pin 10 mints a label. It touches both counted documents |
Pin 10a forces the decision; touch row 11 conditional; report item 2c. All three counters named, per S27's finding |
| 5 | Gates 2–3 pinned no toolchain, in a repo whose CI gates on 1.95.0 while this machine's default is 1.97.1 | Both pinned |
| 6 | Gate 3 formatted two crates of four — and rows 9–10 added the other two, so it would have reported clean over unformatted code | Expanded to ops, core, textproj, testkit |
| 7 | Gate 1 had no baseline and no ignored requirement | Baseline 1577 / 0 / 0, three delta buckets, ignored MUST be 0 |
| 8 | Gate 4 said "exactly §2" — the formulation S27's round 17 found unsatisfiable with a conditional row, and row 11 is now conditional | Subset both ways, with the unused row named in the report |
| 9 | M1, M2, M4 accepted "the test failed" as their whole signature | Each now names the behaviour the mutation produces. M3, M5, M6, M9 already met the standard |
| 10 | M7 covered two independently guarded doc blocks but reverted "one doc comment" — it could pass while the other guard stayed weak | Split into M7a and M7b, each quoting its own needle's non-match |
| 11 | Pin 8 identified four tests by INTERIOR line numbers (:16838 etc.), roughly a dozen lines into each body — anchored to nothing searchable |
All four named, with fn lines given second |
| 12 | t6/t7/t9 locators stale |
Re-derived to :16158, :16231, :16461, with the two-t6-families trap recorded |
| 13 | The bump falsifies live statements in CLAUDE.md and the handoff |
§4a added: explicitly not staged during execution — staging them would assert the rung had landed while it awaited acceptance — and required as a post-acceptance reconciliation the report must list as outstanding |
| 14 | shrink also takes GraphInvariant; whether it matches exhaustively was not established |
Report item 2d requires execution to determine it. Deliberately not guessed — guessing about an exhaustive match is what produced finding 2 |
Finding 2 is the one that changes the rung's shape. The others make an existing plan checkable; this one says the plan was scoped to the wrong number of files. An enum extension in a crate with a generated exhaustive consumer is never local, and nothing in the contract's own §0 inspection would have surfaced it — it appears at compile time, after execution begins.
Findings 5–8 are all defects S27 had already found and fixed in its own gate set. This contract predates those corrections and inherited none of them. A ratified contract's gate section is reusable knowledge, and the next contract drafted here should start from S27's §5 rather than from an older template.
The original status, retained: DRAFT — BLOCKED on P13-S27. Not executable as written. Pin 0 exposes that no authority defines the implementation's current reduction semantics, and prose saying old canonical bases "must be rebuilt" does not make them unusable.
core_spec.tex:11614's requirement stays unmet until S27 supplies a version authority and a rejection-or-rebuild path.
Rung type: canonical reduction-semantics change. This is stronger than
"behaviour change" and the first draft of this contract understated it. The same
operation set now reduces to a different canonical Score: a graph that reduces
cleanly today is refused, and a field that was never written is written.
Disposition A of spec/CONTRACT_GENESIS_G3A_ENTITIES.md §1.1, ratified
there as "the later maintenance/enforcement fix" and sequenced after G3b — which
has landed.
No schema major or minor moves and no wire byte changes — but the Operation Catalog's version does move (pin 10), and the reduction-semantics question is separate from the schema/wire version question. See pin 0.
No policy ruling is owed. Disposition A is already ratified. What follows is its implementation.
§0. What was verified before drafting
Read out of the working tree at f876836, not recalled. Every line number below
was confirmed by reading the line.
0.1 The refusal needs no new machinery — and this is the rung's biggest saving
Disposition A says CreateStaffGroup "MUST carry members: []; a non-empty
members is refused or normalized away at construction." An earlier reading of
this contract assumed refusal required a new PreconditionFailureReason — which
would have made S16 a schema-minor epoch event with a footprint in
binary_format.tex, PLAN_GMINOR_SCHEMA_MINOR.md, decode.rs, and the history
test. It does not.
reduce.rs:1236 already defines the shared helper, and its own doc comment
already covers this case:
The precondition no-op a structural create or delete returns when a container is non-empty where the operation requires it empty (a create carrying children, or a delete of a container with live children).
Three creates already call it for exactly this reason — create_region
(:4174), create_staff_instance (:4246), create_voice (:4310) — each
refusing a carried value that bears a separately-minted typed child.
create_staff_group is the sole outlier: it accepts carried members and
merely checks each is live (:4489–:4502).
So the refusal is the fourth instance of an established pattern, not a new
one. PreconditionFailureReason::ContainerNotEmpty stays at discriminant 10.
No new reason, no epoch, no wire change, no accept-set move.
0.2 Normalizing at construction or decode is excluded, on ratified grounds
Disposition A's "or normalized away" alternative must not be taken. The
committed decode vector at ops/src/vectors.rs:829 pins 130 literal bytes of a
CreateStaffGroup envelope whose payload carries members = [StaffId(…)], and
its contract is decode-then-re-encode injectivity. The text-projection golden at
textproj/src/vectors.rs:198–:201 carries the same value. Folding members
away at decode would break both, and violates req:binfmt:decode-vectors
(binary_format.tex:3404ff): "Canonical decode is injective: distinct byte
strings denote distinct values."
Refusal happens at reduction. Decode is untouched, and both artifacts survive unchanged. This is the same fork P13-S8 faces, resolved the same way, for the same ratified reason.
0.3 The authorship cycle makes empty-at-mint the only self-consistent rule
create_staff requires a carried group to be live (:4372–:4383);
create_staff_group requires each carried member to be live (:4489–:4502).
There is no ModifyStaff, ModifyStaffGroup, DeleteStaff, or
DeleteStaffGroup anywhere in OperationKind (payload.rs:170–:307;
confirmed normatively at operation_catalog.tex:1288 and :1553). With mints
only, no authoring order produces an agreeing pair.
Therefore the only authorable agreeing sequence is: mint the group empty, then
mint staves naming it. Requiring members: [] is not an arbitrary restriction —
it is the sole rule consistent with the operation surface that exists.
0.4 The re-carry hazard, and the base-ingest hazard hiding behind it
create_staff_group's idempotence check compares op.group against
self.staff_group_values (:4466–:4469). If maintenance wrote the appended
members into that map, a byte-identical re-carry of CreateStaffGroup(g, [])
would compare [] against [s] and return RecreateContentMismatch instead of
AlreadyApplied. Disposition A names this sub-pin explicitly.
The non-obvious half: base ingest (:1611–:1620) reseeds
staff_group_values from score.staff_groups — the maintained value. So even
if reduction keeps carried and derived apart in one session, a snapshot round
trip launders the derived value into the carried slot, and the same misverdict
returns after a reload. A test that never reloads cannot see it.
The resolution falls out of §0.1: once a non-empty carried members is refused,
the carried value is by construction always empty, so the base seed can
reconstruct it exactly rather than approximately (pin 4).
0.5 t8b is the obstacle, and it inverts rather than dies
t8b_both_permitted_stale_forms_hold (reduce.rs:16339, doc :16317–:16337)
asserts both stale forms hold, and its doc block names disposition A's two
maintenance rules as mutations that must make those assertions fail. The
test is a correctly-built detector for precisely this change.
It is therefore rewritten, not deleted: the same two authoring orders, with the
verdicts inverted — the missing order now yields agreement, the spurious order
now yields ContainerNotEmpty. Its doc block's mutation notes become the
rung's own mutation evidence, pointing the other way.
0.6 Invariant 21 has real work left after maintenance
Maintenance plus refusal does not make disagreement impossible:
- Undo.
reduce.rs:2967removes aStafffromscore.staveson undo but leaves its id in any live group'smembers. The reverse direction is guarded (:6736–:6744, which blocks undoing a group still named by a live staff); this direction is not. - Base ingest. A blob authored before this rung, or by another implementation, can carry a disagreeing pair straight in.
There are 20 invariants (invariants.rs:149, count guard :6064). 21 is free.
§1. Pins
PIN 0 IS DISCHARGED — read this before the pin. P13-S27 landed 2026-08-09.
Everything in pin 0 below is a dated record of the pre-S27 tree, and its three numbered requirements are superseded. Its claims that no constant names the current reduction semantics, that a conformingly-propagated stale base is accepted, that
ids.rs:288's catalog claim is false, and that this rung therefore cannot execute are now false.But one of pin 0's claims is STILL TRUE and S27 did not touch it: there is no mechanism to detect a reduction-semantics change. S27 enforces declared-version mismatches; it cannot tell that the semantics behind a version number changed. Do not read the discharge as closing that gap — see Enforcement is not detection at the end of this pin, which is why this rung's bump to
1is mandatory.The discharge, with each claim answered individually and the replacement requirements, is at the end of this pin. Read the pin as history; do not execute it.
Pin 0 — declare the reduction-semantics break; do not pretend it is containable.
core_spec.tex:14369–:14372 is normative: "Two replicas with different
ReductionAlgorithmVersion may produce different canonical states from the same
operation set; the active superblock declares the version under which the
bundle's canonical base was materialized." And :11614–:11617: "Snapshots
produced under an earlier algorithm version cannot be used as canonical bases
under a later one without rebuilding." This rung is exactly such a change.
The obvious disposition — bump the version — is not available, and the reason is subtler than an absence of machinery. The machinery exists; it is self-referential, so it cannot detect what it appears to guard.
ReductionAlgorithmVersion (bundle/src/ids.rs:291) is a wire field of the
bundle superblock (bytes 68..72, superblock.rs:20), and there is a
production writer path:
reduction_version_for(bundle.rs:989) sets a new superblock's version from the canonical base's own reported version, ordefault()(zero) when there is no base — its doc: "only a base records a reduction."open(bundle.rs:396–:399) rejects a bundle whose base's version disagrees with the superblock's.
So a document's declared reduction version is sourced from the base and then
checked against itself. Nothing anywhere compares either value against the
semantics the running implementation actually implements. A base reduced under
old semantics carries its own version forward, agrees with the superblock it
seeded, and is accepted. The check is not vacuous — it catches a corrupt or
tampered base whose version disagrees with its superblock. What it cannot catch
is a valid stale base: one whose version was conformingly propagated. So the
check necessarily passes for a conformingly propagated stale base, which is
exactly the case core_spec.tex:11614 exists to prevent.
(An earlier draft of this pin claimed "every construction site is an unrelated
hardcoded literal." That was false, and the error is instructive: the search
behind it looked for ReductionAlgorithmVersion( constructor calls, which by
construction cannot find a path that propagates an existing value without
constructing one. The instrument could not observe the thing it was used to
rule out.)
Two supporting facts stand: there is no constant or accessor naming the
implementation's current reduction semantics, and ids.rs:288–:289 states
that "the algorithm catalog itself lives in epiphany-ops" while nothing of the
kind exists there — a doc comment asserting a false fact about another crate,
which is P13-S26's pattern, second instance.
Scope of the claim, deliberately narrowed: this inspection establishes that the current implementation has no mechanism to detect a reduction-semantics change. It does not establish that no such change in the project's history was ever detectable; that would need a history audit this rung has not done, and the stronger sentence must not be written into the ledger.
Therefore this rung does NOT invent the missing machinery, which would be a scope explosion and a separate design — and, because it does not, this rung cannot execute. Pin 0 records the break; it does not discharge it. S16 waits on S27's disposition. What pin 0 still requires of the eventual rung:
- State the break in
spec/PASS13_CANDIDATES.md's P13-S16 row — which stays blocked on P13-S27, not resolved — and inoperation_catalog.tex's Revision History entry: canonical bases materialized before this rung must be rebuilt, not reused, becausecreate_staffnow writesStaffGroup.membersandcreate_staff_groupnow refuses inputs it previously accepted. - File P13-S27 — that the reduction-version machinery is
self-referential:
reduction_version_for(bundle.rs:989) sources a new superblock's version from the canonical base's own self-report, andopen(bundle.rs:396) checks only that the two agree, so the current implementation has no mechanism comparing either against the semantics it actually implements. Supporting: no constant or accessor names the current semantics, andids.rs:288's catalog claim is false. The stronger historical claim — that no such change has ever been detectable — is NOT established by this inspection and must not be written. This rung is the occasion, not the cause. - Assert nothing it cannot enforce. No test may claim stale bases are rejected; nothing rejects them. The break is recorded, not guarded, and the contract says so plainly rather than implying coverage.
PIN 0 IS DISCHARGED — P13-S27 LANDED 2026-08-09 (4df8e25)
Everything above in pin 0 is a dated record of the pre-S27 tree and MUST NOT be
executed as written. S27 built the machinery whose absence pin 0 documented, so the
pin's premises, its conclusion, and all three of its numbered requirements are
superseded. They are retained because the reasoning is what motivated S27, and because
operation_catalog.tex's rebuild note still has to be written.
Which of pin 0's factual claims are now FALSE, stated individually so none is left standing by implication:
| Pin 0 said | Now |
|---|---|
| "no constant or accessor naming the implementation's current reduction semantics" | epiphany_ops::CURRENT_REDUCTION_ALGORITHM_VERSION (currently 0), plus Bundle::capabilities() as the accessor |
"ids.rs:288–:289 … asserts a false fact about another crate" |
Made true by S27's pin 8 — the same doc comment now names the real location and mechanism |
| "a valid stale base is accepted — one whose version was conformingly propagated" | False now. A base whose declared version differs from the authority is refused with CanonicalBaseRequiresRebuild { base, current } on both the read path (open) and the write path (commit/commit_versioned) |
| "the current implementation has no mechanism to detect a reduction-semantics change" | STILL TRUE, and S27 did not change it. See the split immediately below — this row is the one an earlier draft of this supersession got wrong |
| "this rung cannot execute" | It can. This contract is UNBLOCKED — see the status block. It is still a DRAFT and needs ratification, which is a different bar |
ENFORCEMENT IS NOT DETECTION — the split S27 did not close
An earlier draft of this supersession declared pin 0's "no mechanism to detect a reduction-semantics change" false because mismatches are now rejected. That was wrong, and it contradicted requirement 3 thirty lines below it. The two are different claims and only one of them moved:
| Declared-version mismatch | Semantics change | |
|---|---|---|
| What it is | a base whose recorded reduction_algorithm_version differs from the running authority |
the implementation's canonical reduction verdicts changing, with or without a bump |
| After S27 | ENFORCED — refused with CanonicalBaseRequiresRebuild on read (open) and write (commit/commit_versioned) |
STILL UNDETECTABLE. Nothing compares the semantics the code implements against the number it declares |
| What guarantees it | the check, mechanically | a human remembering to bump. There is no backstop |
So S27's enforcement is conditional on the discipline, not a substitute for it. If
this rung changes CreateStaffGroup's verdict and the bump is missed, every base it
produces declares 0, matches an authority still reading 0, and passes every check
S27 installed — the enforcement fires correctly on a number that is itself wrong.
epiphany-ops's authority doc states this outright: no mechanism can detect a
semantics change; the discipline is the guarantee; there is no backstop.
This is precisely why pin 0's requirement 3 inverts into a mandatory bump rather than
dissolving. S16 is the first rung to change a canonical reduction verdict, so S16's
bump to 1 is the human-enforced half of the guarantee, and the only half that
applies to itself.
Pin 0's narrowing survives and still binds. This inspection does not establish that no reduction-semantics change in the project's history was ever detectable — S27 did not perform that history audit either, and the stronger sentence still must not be written into the ledger.
What replaces the three requirements:
- State the break in
operation_catalog.tex's Revision History exactly as written above — canonical bases materialized before this rung must be rebuilt, not reused. Unchanged. But the ledger half is superseded: the P13-S16 row is UNBLOCKED, not blocked on P13-S27 — see pin 11, amended the same day. File P13-S27— DISCHARGED. S27 has its own row, its own ratified contract, and its ownRESOLVED — IMPLEMENTED 2026-08-09 (pin 10)marker. Nothing here is left to file.- Assert nothing it cannot enforce — the principle stands; its application
inverts. Stale bases are now rejected, and S27 owns the tests that prove it. So
this rung must not re-assert S27's guarantee, and must not add a second detection
path. What it MUST do instead is bump
CURRENT_REDUCTION_ALGORITHM_VERSIONto1, because it changesCreateStaffGroup's reduction verdict. No mechanism can detect a missed bump — S27's authority doc is explicit that the discipline is the entire guarantee — so the bump is this rung's obligation and nothing will catch its absence.
Line-number citations in this contract predate S27 and are only PARTLY re-derived. S27 changed
bundle.rsby 795 lines, sobundle.rs:989,:396andids.rs:288above — and every otherbundle.rsreference in this document — remain stale as locators even where the claim about them is historical.Draft amendment 1 re-derived the ones that name executable targets:
t6:16154→**:16158,t7:16229→:16231,t9:16454→:16461**, and pin 8's four interior line numbers replaced by test names. The rest are not done and finishing them is ratification work.A trap the re-derivation found, recorded so the next pass does not fall into it:
reduce.rscontains twot6/t7families —t6_undo_restores_the_chain_...(:15577) andt6_g3a_referential_loops_...(:16158). A re-derivation that grepsfn t6and takes the first hit lands on the wrong test, and both hits are plausible in context. Match on the full_g3a_name, never the prefix.
Pin 1 — refuse a non-empty carried members, using the existing helper.
In create_staff_group (reduce.rs:4458), before the liveness loop, refuse a
carried non-empty members with container_not_empty(). Match the idiom and
comment style of :4174, :4246, :4310. Do not introduce a new
PreconditionFailureReason; ContainerNotEmpty stays at 10 and no schema
document moves.
The liveness loop (:4489–:4502) becomes unreachable for non-empty members
and must be deleted, not left dead — a precondition that cannot fire is the
kind of residue this pass keeps finding.
No behavioural test can prove that deletion. A retained dead loop passes M1
and every other assertion here, because refusal short-circuits before it. Pin 1
therefore requires a structural gate: read
crates/epiphany-ops/src/reduce.rs, slice the create_staff_group body
(brace-matched from its fn line to its closing brace, production source
only — never the test module), whitespace-normalize, and assert the slice
contains the empty-members refusal and does not contain any
TypedObjectId::Staff liveness check or TargetMissing construction.
Pin 1a — three existing tests assert what pin 1 removes; all three must be revised. Each currently passes by asserting the outgoing rule:
| Test | Line | Currently asserts | Under pin 1 |
|---|---|---|---|
t6 |
reduce.rs:16158 |
CreateStaffGroup.members naming a non-live target is TargetMissing, one of three referential loops asserted separately |
that arm becomes ContainerNotEmpty and stops testing a referential loop at all; the CreatePartDefinition.staves and CreateView.active_layers arms are untouched and must stay |
t7 |
reduce.rs:16231 |
those same preconditions are not enforced base-free | pin 1's refusal is not graph-gated — it is a property of the carried value — so this arm now refuses base-free too, inverting the claim for that arm only |
t9 |
reduce.rs:16461 |
a from-empty score passes check_invariants, and each skipped reducer check independently makes invariant 10 fire, on a fixture that already attempts a dangling reference in each of the four ops |
the dangling-member fixture can no longer enter the graph, so both the fixture and t9's own mutation set change |
t7's inversion is the subtle one and must be stated in its doc block: a
graph-aware precondition asks about the universe and cannot run base-free; an
empty-container precondition asks only about the carried value and therefore
runs everywhere. Different classes — conflating them is how a later reader would
wrongly "restore" the graph gate.
Each keeps its id and gains a doc note recording what it asserted before this rung and why the assertion inverted.
Pin 2 — maintain the projection in create_staff.
In create_staff (:4330), after the graph push at :4386 and only when
op.staff.group == Some(g), append the new staff's id to g's members in
self.graph's score.staff_groups. Under base-free reduction
(self.graph.is_none()) there is no graph to maintain and nothing is written —
matching how :4385 already guards the staff push itself.
Appending is set-union and order-independent per staff id; the append must be idempotent (never add an id already present), since convergence replays.
Pin 3 — carried and derived are kept apart, and staff_group_values holds the
carried value.
self.staff_group_values (:4507) MUST continue to store the value as
authored — which pin 1 guarantees is always empty-membered. Pin 2 writes the
maintained members only into self.graph. The re-carry comparator at
:4466 is not changed and must keep comparing against the carried value.
Pin 3a — a permanent named regression test, not only mutation M3. Add
t8c_recarry_compares_against_the_carried_members_not_the_derived: in one
session, CreateStaffGroup(g, []) → CreateStaff(s, group: Some(g)) →
re-carry CreateStaffGroup(g, []), asserting AlreadyApplied and that the
graph's g.members == [s] at that moment. A mutation demonstrates the hazard
once; only a test keeps it demonstrated.
Pin 4 — base ingest reseeds the carried value, not the derived one.
At :1619, seed staff_group_values with the group's value with members
emptied, not group.clone(). This is exact rather than lossy: pin 1 makes
empty the only authorable carried value, so the reconstruction is the carried
value. The comment at :1614–:1618 must be extended to say why, naming the
reload hazard of §0.4 — otherwise a later reader "fixes" it back to
group.clone() and reintroduces a defect that only appears after a snapshot.
Pin 4a — a permanent named regression test for the reload path. Add
t8d_recarry_after_reduction_onto_a_materialized_base_stays_idempotent: reduce
the pin-3a sequence, materialize the score, re-reduce onto that score as a
base, then re-carry CreateStaffGroup(g, []) and assert AlreadyApplied.
This is the only test that would exercise the base-seed path; pin 4 is
unguarded without it.
Pin 5 — close the undo hole in the unguarded direction.
The Staff undo retain arm (:2967) MUST also strip the removed staff's id
from every group's members, in both self.graph and — if any derived copy is
held — wherever else the projection lives. The StaffGroup arm (:2977) needs
no change: :6736's reference guard already blocks undoing a group a live staff
names.
Pin 6 — graph invariant 21, StaffGroupMembershipAgreement.
Flags both directions: a staff whose group names a group whose members omit
it, and a group listing a staff whose own group is not that group. Witnesses
must name both ids and the direction.
This is an append with a documentation footprint — invariants.rs:149,
all(), the count guard at :6064, and core_spec.tex's normative enumeration
(which currently ends at 20 and which P13-S26 has just established is already
under-describing invariant 10 — do not attempt to repair invariant 10's prose
here; that is S26's rung).
The count is not the test. all().len() == 21 passes with the dispatch arm
deleted — the project already knows this, which is why
m40_check_invariants_dispatches_invariant_20 exists
(invariants.rs:6045–:6066, whose own doc says "all().len() == 20 passes
even with the dispatch arm deleted, so this row must instead show a score
violating ONLY invariant 20 is actually flagged by the top-level
check_invariants entry point"). Pin 6 requires the same behavioural shape:
a score violating only invariant 21, flagged through check_invariants, with
the dispatch-arm deletion as its signing mutation. A count assertion may
accompany it and may not replace it.
Pin 6 is coupled to pin 5 and they split only together. M5 signs the undo
hole by requiring invariant 21 to observe the residue. If invariant 21 is
deferred to a later rung, then either pin 5 and M5 defer with it, or M5 must be
restated to assert the leftover member directly on the materialized score
rather than through check_invariants. Splitting pin 6 out while leaving M5 as
written would leave the undo repair unsigned.
Pin 7 — t8b inverts.
Rewrite t8b_both_permitted_stale_forms_hold as
t8b_the_projection_is_maintained_and_the_spurious_form_is_refused: same two
authoring orders, verdicts inverted. Its doc block must record that it
previously pinned the opposite, and why the change is the ratified disposition
rather than a regression. Deleting it is forbidden — the pairing of the two
orders is the coverage.
Pin 8 — the four G3a undo-repair tests are re-verified, not assumed. NAMED by draft
amendment 1. All four are in crates/epiphany-ops/src/reduce.rs:
| Test | fn at |
|---|---|
u2a_a_live_staff_naming_the_group_blocks_its_undo |
:16826 |
u2bf_a_the_staff_group_guard_holds_base_free |
:16983 |
u2tomb_a_a_tombstoned_referencing_staff_does_not_block_the_groups_undo |
:17125 |
u3a_minting_the_group_and_its_referencing_staff_in_one_transaction_undoes_whole |
:17333 |
This pin previously identified them ONLY as
:16838,:16992,:17138,:17345— and those are interior line numbers, roughly a dozen lines into each body, not thefnlines. An interior line number is worse than a stale one: it anchors to nothing a reader can search for, and it silently drifts with every edit to the function above it. Names are stable; line numbers are a convenience. Thefnline numbers above are given second and are already subject to the locator warning under pin 0.
They each construct the missing-member form,
which pin 2 now makes an agreeing form. They assert on undo effects rather than
on members, so they are expected to survive — expected, not known. Each
must be run and its verdict reported; any that changes is a finding.
Pin 12 — the authority bump. ADDED BY DRAFT AMENDMENT 1; it had no pin, no touch row and no gate.
Bump epiphany_ops::CURRENT_REDUCTION_ALGORITHM_VERSION 0 → 1 (touch row 7),
and add this rung's entry to the constant's own Bumps list in the same doc
comment. The list is the only record of why a version exists; a bump without its
entry leaves a number nobody can account for.
This is not optional and not a consequence — it is the obligation. This rung changes
CreateStaffGroup's reduction verdict, and core_spec.tex §"Canonical Document
Identity" makes such a change require a new version. No mechanism can detect a missed
bump (see Enforcement is not detection under pin 0), so nothing in the gate set will
catch its absence except the two tripwires in rows 9 and 10 — which is exactly why they
must be updated rather than silenced.
The bump is stated in pin 0's discharge as requirement 3, which is a discharge note and not a pin. So it had no numbered pin to execute, no touch row to stage, no gate to check and no report item to confirm — the four places this repo's discipline requires a mandated change to appear. It now has all four: pin 12, touch row 7, gate 10, report item 2b.
Pin 10a — whether pin 6 or pin 10 mints a \label{req:...} MUST be decided
explicitly, and stated in the report. ADDED BY DRAFT AMENDMENT 1.
This rung edits core_spec.tex (row 5, invariant 21's enumeration) and
operation_catalog.tex (row 4), and crates/epiphany-testkit/tests/requirement_labels.rs
counts requirements and labels in both. The two readings have different touch tables:
- If either mints a label, touch row 11 is used and all three counters move —
CORE_REQUIREMENT_COUNT,SUITE_REQUIREMENT_COUNT,SUITE_LABEL_COUNT. S27 found the hard way that naming only the first leaves two failing tests. - If neither does, row 11 is unused and the report says so.
CLAUDE.mdnames this file by name as a recurring escapee, it escaped the format-epoch rung's table, and S27 had to add it mid-execution. A file that must change but is not listed silently drops out of the commit, and the failure surfaces on someone else's branch. Carrying it conditionally costs nothing if unused.
Pin 9 — no byte artifact moves.
spec/vectors/decode_vectors.txt, ops/src/vectors.rs:829's literal-byte
vector, and textproj/src/vectors.rs:198's golden are all unchanged,
because refusal is at reduction and decode is untouched (§0.2). Confirm, do not
assume. No schema major or minor, no accept-set move, and no schema/binary-format
companion version bump. This is narrower than "no version bump": pin 10 does
require an Operation Catalog version bump and Revision History row, because the
catalog's normative text about these two operations changes.
Pin 10 — specification surfaces.
operation_catalog.tex §CreateStaff and §CreateStaffGroup currently state the
disposition-B stale-form semantics normatively (:1265–:1278, :1531–:1540)
and must be rewritten to the maintained rule, with a Revision History row and a
version bump. core_spec.tex's Staff.group / StaffGroup.members docs
(:5585–:5591, :4235–:4241) and the two Rust doc comments
(graph.rs:842–:847, :1643–:1649) likewise.
The two Rust doc comments are grep-asserted by
graph.rs:2136 and :2160 (needles at :2139, :2142, :2163, :2166,
:2148, :2172, :2176, :2180). Those guards must be updated in step, and
the updated needles must assert the new rule — a guard left asserting the old
words would fail loudly, but a guard weakened to a substring both rules share
would pass silently, which is the worse outcome.
Each new needle MUST be wording the disposition-B comments cannot satisfy.
Retaining only "sole authority" and "non-authoritative" is insufficient: both
phrases are true under B and under A, so a guard built from them alone passes
against the text it is meant to have replaced. Require phrases that are false
under B — for example "maintained from Staff.group" and "must agree" —
so that reverting either comment to its B wording fails the guard. M7 signs
exactly this.
Pin 11 — the ledger. AMENDED 2026-08-09: the mandated state changed when S27 landed.
This pin required the P13-S16 row to read "blocked on P13-S27". S27 landed and was accepted at
4df8e25, so that state is now false, and a pin mandating a false ledger state would put this contract in contradiction with the ledger it governs. As amended, the row must read: UNBLOCKED — S27 landed; this contract is DRAFT and needs ratification before dispatch. Everything else the pin requires recorded is unchanged.The second paragraph's instruction to file P13-S27 in the same edit is discharged — S27 has its own row, its own ratified contract, and its own
RESOLVED — IMPLEMENTED 2026-08-09 (pin 10)marker. It is no longer this rung's to file.
spec/PASS13_CANDIDATES.md's P13-S16 row → UNBLOCKED, S27 landed (was: blocked on
P13-S27), recording the
ContainerNotEmpty reuse (and that a new reason was considered and proved
unnecessary), the base-ingest hazard, the t8b inversion, the t6/t7/t9
revisions, and — per pin 0 — that canonical bases materialized before this rung
must be rebuilt rather than reused.
File P13-S27 in the same — DISCHARGED
2026-08-09; do not execute. S27 was filed, contracted, ratified, implemented and
landed at spec/PASS13_CANDIDATES.md edit4df8e25, and its row carries RESOLVED — IMPLEMENTED 2026-08-09 (pin 10).
The blocking relation this instruction existed to make visible from both ends is now a
resolved relation recorded at both ends. The reasoning below is retained as the
record of why S27 was filed, not as work to do.
It is a prerequisite discovered by this rung, not independent ledger cleanup, and the two rows must land together so the blocking relation is visible from either end. Its claim, at the scope §0's inspection supports: the reduction-version machinery is self-referential —
reduction_version_for(bundle.rs:989) sources a new superblock's version from the canonical base's own self-report, andopen(bundle.rs:396) checks only that the two agree — so the current implementation has no mechanism comparing either against the semantics it actually implements, andcore_spec.tex:11614's rebuild requirement is unenforced. Supporting: no constant or accessor names the current semantics, andids.rs:288's claim that the catalog lives inepiphany-opsis false — a second instance of P13-S26's pattern.(Kept verbatim as the filing that produced S27, not as a description of the tree. MOST of it is now false — but NOT all, and an earlier draft of this annotation said "every claim" and was itself wrong in the way this contract keeps having to correct. False now: the machinery is no longer self-referential —
opencompares the base's version against the injected authority, not only against the superblock it seeded;ids.rs:288was made true by S27's pin 8; andcore_spec.tex:11614is enforced for declared-version mismatches. STILL TRUE: "no mechanism comparing either against the semantics it actually implements" — S27 compares a declared number against a declared number. Nothing anywhere compares either against the semantics the code actually implements, and nothing can. See Enforcement is not detection under pin 0.)
Do not write the stronger historical claim ("no reduction-semantics change has ever been detectable"); §0 does not establish it.
The P13-S16 row does NOT move to RESOLVED in this edit. It records the
disposition-A plan, names this contract, and is marked blocked on P13-S27
UNBLOCKED — S27 landed 4df8e25 (corrected 2026-08-09). "Not RESOLVED" still
holds and is the part that matters here: this rung has not been implemented, and
unblocked, dispatchable and resolved are three different states.
§2. Touch table
| # | File | Change |
|---|---|---|
| 1 | crates/epiphany-ops/src/reduce.rs |
pins 1, 1a, 2, 3, 3a, 4, 4a, 5, 7, 8; and create_staff_group's own doc comment, which states the disposition-B rule |
| 1b | crates/epiphany-ops/src/payload.rs |
CreateStaffGroupOp's doc (:1789ff) states that graph-aware reduction preconditions every carried member resolves to a live Staff and that "the mint stores members exactly as given and neither maintains nor trusts it" — both clauses become false |
| 1c | crates/epiphany-ops/src/valuegen.rs |
staff_group's doc (:372–:375). Precision: the helper still carries and preserves supplied members exactly, and "never normalizes" stays true and MUST be preserved — the helper is unchanged. What becomes false is the framing that such a non-empty value is "the value a CreateStaffGroup mints" (it can no longer mint one), and the disposition-B attribution. Rewrite those two clauses only |
| 2 | crates/epiphany-core/src/invariants.rs |
pin 6 |
| 3 | crates/epiphany-core/src/graph.rs |
pin 10 (two doc comments + their two grep guards) |
| 4 | spec/operation_catalog.tex (+ .pdf) |
pin 10 |
| 5 | spec/core_spec.tex (+ .pdf) |
pins 6, 10 |
| 6 | spec/PASS13_CANDIDATES.md |
pin 11 |
| 7 | crates/epiphany-ops/src/lib.rs |
ADDED by draft amendment 1. Pin 12 — bump CURRENT_REDUCTION_ALGORITHM_VERSION 0 → 1 and add its entry to the constant's own Bumps list, which is in the same doc comment. The rung mandated by pin 0's discharge had no touch row, no pin and no gate |
| 8 | crates/epiphany-core/src/generators.rs |
ADDED by draft amendment 1 — the root cause below. violating_score (:498) matches GraphInvariant exhaustively, so invariant 21 does not compile without a new arm. Four all()-driven tests (:991, :1004, :1025, :1042) then consume it, so the arm must be a real generator, not a stub |
| 9 | crates/epiphany-testkit/src/roundtrip.rs |
ADDED by draft amendment 1. S27 test 10b (:894) reopens a literal-0 base under production_caps(); pin 12's bump makes that path return Err and hit an arm that panic!s by design |
| 10 | crates/epiphany-textproj/src/serialize.rs |
ADDED by draft amendment 1. S27 test 10a (:659) asserts the production writer supplies ReductionAlgorithmVersion(0). Its own doc (:655) says it is expected to fail when S16 bumps and that updating it is S16 stating the authority moved |
| 11 | crates/epiphany-testkit/tests/requirement_labels.rs |
ADDED by draft amendment 1 — CONDITIONAL, exactly as S27's row 12. Only if pin 6 or pin 10 mints a \label{req:...}. That decision MUST be made explicitly and stated in the report — see pin 10a. If it mints one, all three counters move, not one: CORE_REQUIREMENT_COUNT (:15), SUITE_REQUIREMENT_COUNT (:18), SUITE_LABEL_COUNT (:19). This file counts both core_spec.tex and operation_catalog.tex, and this rung touches both. If no label is minted, leave unmodified and say so |
Regenerate the two PDFs only after their sources reach final form.
Rows 9 and 10 are S27's tripwires firing as designed, not collateral damage
S27 wrote two assertions against the literal 0 precisely so they would move when
the authority moved, and serialize.rs:655 says so in as many words. Their failure is
this rung's signal that the bump took effect — the only signal it gets, since no
mechanism can detect a semantics change. They must be updated, not deleted or
#[ignore]d, and the report must quote both before and after.
Neither file was in any touch row, and both fail at gate 1. That is the exact shape of S27's own
gminor.rsfailure: a file the rung must change that no surface count reached, found by a gate rather than by the table. The allowlist catching it is the allowlist working — but only if the row exists before execution starts.
Row 8 is the rung's real shape change: an enum extension is a TWO-crate change
This contract treated invariant 21 as local to invariants.rs. It is not.
GraphInvariant has an exhaustive generated-consumer surface in epiphany-core:
violating_score(generators.rs:498) matches every variant — adding one is a compile error until its arm exists.- Four
all()-driven tests then call it andshrink(:991,:1004,:1025,:1042), so atodo!()or trivial arm fails them. Invariant 21 needs a fixture that genuinely violates it in both directions and survives shrinking. shrink(:932) takesGraphInvarianttoo. Whether it also matches exhaustively MUST be checked at execution and reported — this amendment did not establish it either way, and guessing is what produced this finding.
Consequence for the mutation plan and gate 6: invariant 21's generator is itself load-bearing, so M6 (deleting each arm) now has a second signature — the negative generator must still produce a violating score for the arm under test.
§3. Mutation plan
Applied, run, output recorded verbatim, restored by hand-editing back.
A FAILING TEST IS NOT AN OBSERVATION — draft amendment 1
M1, M2 and M4 accepted "the named test fails" as their whole signature. That signs nothing: a test fails for the mutation, for a typo, for an unrelated panic, or because the harness changed — and a compile error is not a test failure at all. The evidence a mutation owes is the BEHAVIOUR it changed, not the assertion it broke. Each mutation below must now name the wrong verdict, value or state the mutated build produces, and the report must quote it. (This is S27's round-15/16 lesson, which cost four defects there; M3, M5, M6 and M9 in this contract already met the standard, so the fix is uneven-by-design rather than uniform.)
M1 — the refusal fires. OBSERVATION TIGHTENED, draft amendment 1. Remove pin 1's
emptiness check. Required observation: a CreateStaffGroup carrying a non-empty
members is now applied instead of refused — report the resulting
OperationEffect and the minted StaffGroup.members value, showing the spurious
membership that reached the graph. The rewritten t8b spurious-order assertion failing
is the symptom; the applied mint is the observation.
M2 — the maintenance fires. OBSERVATION TIGHTENED, draft amendment 1. Remove pin 2's
append. Required observation: after a CreateStaff naming a live group,
StaffGroup.members is still empty — quote it, and quote invariant 21's verdict on
that state. The rewritten t8b missing-order assertion failing is the symptom.
M3 — the re-carry stays idempotent. Make pin 3 write maintained members into
staff_group_values; a byte-identical re-carry must degrade from
AlreadyApplied to RecreateContentMismatch. This mutation must be observed,
not reasoned about — it is the sub-pin disposition A named.
M4 — the base-ingest hazard is real. OBSERVATION TIGHTENED, draft amendment 1.
Restore :1619 to group.clone(). Required observation: the re-carry misverdict
itself — name what an ingested base's CreateStaffGroup re-carry now returns and what
it should have returned, in the shape M3 already uses (AlreadyApplied →
RecreateContentMismatch). "Pin 4a's test must fail" was the entire signature and does
not distinguish the hazard from any other breakage.
If the misverdict does NOT appear, pin 4 is unmotivated and that is a finding —
report it rather than keeping a guard nothing needs.
M3 and M4 are signed by pins 3a and 4a, not by manual demonstration. Run each mutation against its named test so the guard, not the transcript, is what survives the rung.
M8 — the dead loop is actually gone. Reinstate the member-liveness loop in
create_staff_group after pin 1's refusal, where it cannot fire. Every
behavioural assertion in this contract must still pass, and the pin-1 structural
gate must fail. This is the only signature available for a deletion that no
behaviour observes.
M9 — t7's inversion is real, not assumed. With pin 1 in place, run the
CreateStaffGroup arm of t7 base-free and confirm it refuses; then graph-gate
pin 1's refusal behind self.graph.is_some() and confirm that arm reverts to
applying. Signs that the empty-container precondition is deliberately
universe-independent.
M5 — the undo hole is closed. Remove pin 5's strip; undoing a CreateStaff
must leave a disagreeing pair that invariant 21 flags.
M6 — invariant 21 sees both directions. Delete each arm in turn; each deletion must leave a distinct disagreeing fixture unreported.
M7 — the doc guards discriminate. SPLIT IN TWO by draft amendment 1. Pin 10 covers
two independently guarded doc blocks — Staff.group and StaffGroup.members, each
with its own grep guard (touch row 3). Reverting "one doc comment" leaves the other
guard untested, and a run that reverts the stronger one passes while the weaker guard
is still weak. So:
- M7a —
Staff.group. Revert that block to its disposition-B wording. Required observation: quote the guard's own output showing its specific needle no longer matches, not merely that a test failed. - M7b —
StaffGroup.members. The same, independently, with its own output.
Both must be run and both outputs reported. A guard that passes against both wordings is weakened, not updated — and with one mutation covering two guards, that weakening is invisible.
§4. Gate
cargo test --workspace— full pass. Baseline is 1577 passed / 0 failed / 0 ignored across 42 suites (the post-S27 figure;CLAUDE.md's Green baseline is the single origin — if it disagrees with this line,CLAUDE.mdwins and that is a finding). The count will move (t8b renamed, new invariant tests, and rows 9–10's tripwire updates). Give the delta in buckets — net-new, converted-from-existing, tripwire-updated — and if they do not sum to the observed delta, that is a finding, not an arithmetic error to be papered over. Report the ignored count, which MUST be 0 — a "full pass" is satisfied by an#[ignore]d test that never runs.cargo +1.95.0 clippy --workspace --all-targets -- -D warnings→ clean. The toolchain is part of the gate: CI pins 1.95.0, this machine's defaultstableis 1.97.1, and the repo's CI comment records that 1.97 rejects a bare2.0that 1.95 accepts. A clippy result that does not say which toolchain produced it says nothing. Report the toolchain with the result.cargo +1.95.0 fmt -p epiphany-ops -p epiphany-core -p epiphany-textproj -p epiphany-testkit --check→ clean, on the same pinned toolchain. All four changed crates — corrected by draft amendment 1, which added rows 9 and 10 inepiphany-testkitandepiphany-textproj; the command named only two and would have left both unformatted while reporting clean.cargo fmt --allis forbidden — it reachesspikes/through path dependencies.git diff --cached --check→ clean after staging. The staged list is a SUBSET of §2, not an equality — corrected by draft amendment 1. "Exactly §2" is unsatisfiable here because row 11 is conditional: read literally it fails whenever a conditional row is correctly unused, or invites staging an unchanged file to satisfy it. Instead: every staged path must appear in §2, and every §2 row must be either staged or named in the report as unused, with its reason. Neither direction may be silent. (This is S27's round-17 correction; S16 carried the formulation S27 had already found unsatisfiable.) 4a. §2 carries no prohibitions, unlike S27's §2 — so gate 4 is the whole staging check here. If a later amendment adds an absence rule, this gate does not cover it: "appears in §2" and "is not forbidden by §2" are different questions.spec/vectors/decode_vectors.txtunmodified (pin 9). Confirm bygit status, not by inspection.- Invariant 21 is reached through
check_invariantson a score violating only it, in the shape ofm40_check_invariants_dispatches_invariant_20(invariants.rs:6045).all().len() == 21and thecore_spec.texenumeration ending at 21 are checked in addition, never instead. - The four pin-8 tests each run, each verdict reported.
- The pin-1 structural gate:
create_staff_group's production body contains the empty-members refusal and no member-liveness/TargetMissingpath. t8c(pin 3a) andt8d(pin 4a) both present and passing, by name.- Pin 12's bump landed, by value comparison — ADDED BY DRAFT AMENDMENT 1.
→ working treegrep -n "pub const CURRENT_REDUCTION_ALGORITHM_VERSION" crates/epiphany-ops/src/lib.rs git show HEAD:crates/epiphany-ops/src/lib.rs | grep -n "pub const CURRENT_REDUCTION_ALGORITHM_VERSION"= 1,HEAD= 0. Report both outputs, not the conclusion, and quote the newBumpslist entry verbatim. A bump without its entry fails this gate. - Rows 9 and 10's tripwires were UPDATED, not silenced. Quote both assertions
before and after. Neither may be deleted,
#[ignore]d, or weakened to accept any version — a tripwire that accepts both values is the tautology S27 existed to remove, rebuilt.
§4a. Landing obligation — files that must NOT be staged, and must be fixed after
ADDED BY DRAFT AMENDMENT 1. Pin 12's bump makes two live statements false, and both are in documents outside this rung's touch table on purpose:
| File | Statement the bump falsifies |
|---|---|
CLAUDE.md:106 |
"CURRENT_REDUCTION_ALGORITHM_VERSION — currently 0", and "P13-S16 is the first rung that must move it to 1" |
spec/HANDOFF_2026-08-07.md:25 |
the POST-S27 block's "currently 0" and the same "first rung" sentence |
DO NOT STAGE EITHER DURING EXECUTION. Both describe the state of the repository after a rung has landed, and this rung is not landed while it is staged and awaiting acceptance. Staging them would have the contract assert S16 had landed at the moment it was submitted for review — a claim about the future written as present fact, which is the defect class S27's amendments 4 through 7 were spent removing.
They are a POST-ACCEPTANCE obligation. Once the owner accepts and commits this rung, reconcile both in a separate commit, together with anything else the bump falsifies. The report MUST list them as outstanding, so acceptance is not mistaken for completion.
Pin 11 already covers
spec/PASS13_CANDIDATES.md, which is different and is staged: a ledger row records what a rung did, and is written by the rung.CLAUDE.mdand the handoff state what is true now, and only become true at acceptance. The distinction is whether the file's claim is dated or live — the rule amendment 6 settled for S27, applied here to decide staging rather than wording.
§5. Staging and boundary
Stage only §2's files, by explicit path. Never git add -A.
A concurrent session commits here. Re-check HEAD before staging and before
commit. Never git reset, git restore --staged, git checkout, or git stash.
Out of bounds — MUST NOT be read, written, or staged: the entire spikes/
tree, spec/PLAN_EDITOR_APP.md, spec/CONTRACT_EDITOR_*.md,
spec/ANALYSIS_GENESIS_PERSISTENCE.md, spec/ANALYSIS_TEXT_RUN_PRIMITIVES.md,
spec/DRAFT_T4_FIXTURE_RECIPE.md, crates/epiphany-render-svg/**,
crates/epiphany-glyphs/**, crates/epiphany-editor-gui/**,
crates/epiphany-testkit/benches/editor_pipeline.rs, the root Cargo.toml,
.claude/worktrees/.
Ratified contracts MUST NOT be edited, including
spec/CONTRACT_GENESIS_G3A_ENTITIES.md and …_G3A_UNDO_REPAIR.md.
Do not repair core_spec.tex's invariant-10 prose — that is P13-S26, and
its evidence at invariants.rs:69–:71 must stay intact.
The executing agent MUST NOT commit. Leave the work staged.
§6. Report requirements
- The nine mutations (M1–M9), each with verbatim failure output.
- The nine gate results, each with its command.
2b. Pin 12's bump and its
Bumpsentry, with gate 10's two outputs; and rows 9 and 10's tripwire updates, each quoted before and after, with gate 11's confirmation that neither was silenced. ADDED BY DRAFT AMENDMENT 1. 2c. Whether pin 6 or pin 10 minted a\label{req:...}(pin 10a), and therefore whether touch row 11 was used. If used, all three counters and their new values; if not, say so explicitly. ADDED BY DRAFT AMENDMENT 1. 2d. Whethershrink(generators.rs:932) matchesGraphInvariantexhaustively, and if so what its invariant-21 arm does. Draft amendment 1 did not establish this either way and deliberately did not guess. ADDED BY DRAFT AMENDMENT 1. 2a. REWRITTEN 2026-08-09 — it required the opposite of what is now correct. It read: "For pin 0: confirmation that nothing was added claiming to reject or detect stale canonical bases, and that the break is recorded only in prose." That was right while nothing rejected them. S27 now does, on both the read and write paths, so a report obeying the old text would confirm the absence of a guarantee that exists. As rewritten, the report must state:- that
CURRENT_REDUCTION_ALGORITHM_VERSIONwas bumped to1, with the bump's entry added to the constant's own "Bumps" list — this rung changesCreateStaffGroup's reduction verdict, and nothing can detect a missed bump; - that no second detection path was added. S27 owns the check and its tests; a rung that re-implements the guarantee it depends on has built a duplicate that can disagree with the original;
- that the rebuild break is recorded in
operation_catalog.tex's Revision History, which is unchanged from the original requirement.
- that
- The staged file list, and the test-count delta with its cause.
- The four pin-8 verdicts, and the
t6/t7/t9revisions with what each asserted before and after. - Anything contradicting this contract. A contract defect reported is worth more than a contract satisfied.