Independent review of 5d2db93 returned one blocking finding: gate 8's new
method named a boundary that made the gate impossible to pass or honestly
fail.
Revision H said to quote create_staff_group's production body "to the
#[cfg(test)] boundary." But create_staff_group begins at reduce.rs:4458,
create_part_definition begins at :4515 and carries its own
PreconditionFailureReason::TargetMissing at :4554, and the next #[cfg(test)]
is at :9576. So the literal read spans roughly 5,000 lines and always contains
the very path the gate says must be absent, while any shorter read violates
the stated boundary.
Gate 8 now quotes exactly pin 1's slice: create_staff_group's body,
brace-matched from its fn line to its closing brace, production source only.
Pin 1 had the boundary right the whole time -- "slice the create_staff_group
body (brace-matched from its fn line to its closing brace, production source
only)". Revision H invented a second, looser boundary instead of citing the
pin. That is revision A's failure in a new place: importing a
plausible-sounding rule rather than re-deriving from the source that owns it.
There it was an S27 conclusion; here a boundary borrowed from a different kind
of check. The #[cfg(test)] boundary is the right instrument for "is this call
site production or test", which is what §0.4 used it for; it is the wrong
instrument for "where does this function end."
The rule: where a pin already defines the artifact, the gate CITES the pin, it
does not redescribe it. A redescription is a second definition, and two
definitions of one artifact are a contradiction waiting for someone to read
the looser one.
Worth noting the failure kind. This was not a gate that passes when it should
fail -- revision H's usual shape -- but a gate with no passing state at all.
It would have surfaced at execution, with an agent forced to choose between
obeying the boundary and obeying the requirement, and whichever it chose would
have been reported as a pass.
Still DRAFT, still unratified. No implementation work has begun.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps1szk2mSfgp4Cz21eVH9x
Independent review of 09d8439 returned one blocking finding. A sweep found a
second of the same family.
1. Gate 12's four-line aggregate could pass with one surface missing. Both
definitions can exist (2 lines) while check_invariants calls
check_staff_names_absent_group TWICE and check_group_lists_unowned_staff
NEVER (2 lines). Four lines total, the stated "fewer than four fails" rule
reports success, and M6b has no call site to delete.
Replaced by four independent grep -c checks, each required to be exactly 1
-- so a count ABOVE 1 now fails too, catching the doubled dispatch
directly. Plus quoted context, because a count is not a mapping: each
definition with its enclosing impl GraphIndex<'_> header, proving it is a
method on the type check_invariants builds; each dispatch with the pub fn
check_invariants header, proving the call is in the dispatcher M6 edits and
not in a test or a second dispatcher.
This is the contract's oldest defect class in new clothes. Revisions A-C
removed counts that had gone stale; this one was never right -- an
aggregate can be satisfied by the wrong distribution of the same total.
Where a gate must establish a mapping, it cannot count. It has to check
each element on its own, which is the structural sibling of the rule this
document already carries: where a claim requires completeness, do not
enumerate, derive.
2. Sweep: gate 8 asserted an absence with no method. "contains the
empty-members refusal and no member-liveness/TargetMissing path" named no
command, and a TargetMissing path can be spelled without either literal, so
any grep would prove only that a chosen string is gone. Method pinned:
quote create_staff_group's production body in full to the #[cfg(test)]
boundary and read it, explicitly not a grep -- S27's gate-6a lesson and the
reason its gate 6c quotes a definition rather than searching for it. M8
signs exactly this gate, so a vacuous gate 8 leaves M8's deletion
unobserved.
Both findings are gates that report success without observing what they
claim. One counted instead of pairing; the other asserted an absence with
nothing able to establish it. A structural gate needs a method, and the method
must distinguish the passing case from every failing one -- not merely from
the most obvious failing one.
Still DRAFT, still unratified. No implementation work has begun.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps1szk2mSfgp4Cz21eVH9x
Independent review of 2818ced returned one blocking finding: M6 was
unexecutable against a conforming implementation.
M6 assumed two independently removable arms, but pin 6 and pin 6a specify
behaviour only. A single shared comparison -- one walk emitting a violation
whichever way Staff.group and StaffGroup.members disagree -- satisfies m41,
m41b, the generator test and gate 6, and leaves nothing for M6 to delete one
at a time. Deleting the shared check disables both directions, so M6's
required "one test fails while the sibling passes" observation cannot be
produced at all. M6 was executable only against one implementation style,
which nothing required.
Pin 6b added: the mutation surface is pinned as two GraphIndex methods
following the crate's existing idiom --
fn check_staff_names_absent_group(&self, out: &mut Vec<InvariantViolation>)
fn check_group_lists_unowned_staff(&self, out: &mut Vec<InvariantViolation>)
both emitting StaffGroupMembershipAgreement violations, both dispatched from
check_invariants. The names are pinned because gate 12 greps for them and M6
deletes them by name -- the same reason S27 had to pin synthetic_for_fixture
after finding its gate searched for a name offered only as an example.
M6 is now M6a and M6b, each deleting one named call site. Gate 12 added:
structural, proving both definitions and both call sites exist -- four quoted
lines -- BEFORE M6 is attempted, so the surface's absence is a finding rather
than a mid-run discovery. Fewer than four lines is a pin 6b violation.
This is precedent, not invention: check_invariants (invariants.rs:257-:282)
already dispatches 23 check_* methods for 20 invariants, so more than one
method per invariant is the crate's existing shape. A shared helper both
methods call is explicitly permitted -- the deletable call site is what M6
needs, not a duplicated walk.
The class is one S27 hit twice, its M5 and M6 both rewritten after review
found no runnable observation behind them. The tell is identical: a mutation
phrased as an edit to a structure the pins never required. Behaviour pins
constrain outcomes; a mutation deletes code. Where a mutation is the
signature, the structure it deletes must itself be pinned -- otherwise the
contract is satisfiable in a shape that makes its own evidence unobtainable.
Also fixed before commit: gate 12 was first inserted before gate 11, leaving
the sequence 10, 12, 11. Moved, and a stray double blank line collapsed.
Still DRAFT, still unratified. No implementation work has begun.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps1szk2mSfgp4Cz21eVH9x
Independent review of abe2c35 returned one blocking finding, and it is both
prior revisions' lessons colliding.
The shrink leg had no observable direction or exactness guarantee. Row 8
requires the S->G fixture to survive shrinking, but:
- the named generator test asserted only on the raw violating_score(...);
- the shrunk score was checked solely by
every_invariant_shrinks_to_a_small_witness (generators.rs:1003), whose
!check_invariant(&small, inv).is_empty() is membership in ONE
GraphInvariant variant -- and both directions of invariant 21 are the same
variant, so a shrunk witness that flipped to G->S-only passes it. Because it
calls check_invariant (singular) rather than check_invariants, a shrunk
witness that gained an unrelated second defect passes too;
- §6 item 2d still said "quote the shrunk witness," which a passing test
cannot emit.
So a shrunk witness that changed direction, or acquired a second defect while
retaining invariant 21, satisfied every assertion and every gate.
Fixed: the named test now asserts the same three properties TWICE -- on the
raw fixture and again on shrink(&that, StaffGroupMembershipAgreement): exactly
one violation and it is StaffGroupMembershipAgreement, the witness naming the
S->G ids, and G->S asserted satisfied. Gate 6 requires both legs. Item 2d
rewritten to revision E's source-assertion-plus-pass-verdict model. Item 2e
extended to both legs. Row 8's description of the existing shrink tests now
says plainly that they establish only that SOMETHING still fires, and that
direction and exactness after shrinking come solely from the named test's
shrunk leg.
Two lessons collided here. Revision E established that a gate must name
evidence its artifact produces and fixed gate 6 -- stopping one hop short of
item 2d, which is the fix-propagation failure revisions A-D kept recording.
And the underlying gap is revision D's: a requirement, "survives shrinking,"
with nothing able to fail it.
The generalisable rule now recorded: shrink is a TRANSFORMATION, and a
transformation's output needs the same guarantees asserted of its input.
Requiring a fixture to "survive" a transformation establishes only that
something survived. Every property the input was pinned for must be
re-asserted on the output, or the transformation is free to change what the
fixture proves.
Still DRAFT, still unratified. No implementation work has begun.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps1szk2mSfgp4Cz21eVH9x
Independent review of cae1d32 returned two blocking findings and one stale
rationale. Both blocking findings are in requirements revision D itself wrote,
and both are revision D's own closing lesson turned back on it.
1. Row 8's new generator test had no name, so nothing consumed it. Gate 6
named only m41 and m41b; §6 item 2d asks for shrink evidence. Omitting the
test entirely would still compile, satisfy all four all() loops, and pass
every named gate.
Named invariant_21_negative_generator_breaks_staff_to_group_only, with its
three assertions spelled out: exactly one violation and it is
StaffGroupMembershipAgreement, the witness naming the S->G ids, and the
G->S direction asserted satisfied. Added to gate 6 and to new §6 item 2e.
This is revision D's own lesson unapplied to itself: it closed by
distinguishing a rule with no consumer from a rule with no observer, then
wrote a requirement with neither. An unnamed artifact cannot be gated,
because every gate here names what it checks.
2. Gate 6 demanded runtime evidence the prescribed tests cannot emit. It said
to quote check_invariants' full return and witness ids, but these are
assert!-style tests in m40's shape and cargo test prints ok, not local
values. Obeying it literally would need unpinned --nocapture
instrumentation added purely to produce a report, or source inference
presented as observation.
Evidence model now chosen explicitly: quote the SOURCE assertions plus the
pass verdict. A passing exact-set assertion IS the observation -- the
assertion text says what was checked, the verdict says it held. This
follows S27's gate 6c, which quotes a struct definition rather than
grepping for it: a quoted source construct is read, not inferred. It also
adds no code to epiphany-core written solely for a report.
The requirement was right and the mechanism was borrowed from gates that
run commands and read stdout. A gate must name evidence the prescribed
artifact actually produces; otherwise execution improvises, and improvised
instrumentation is unpinned scope arriving through the report.
3. Gate 4's rationale still said row 11 is "conditional," which revision D
changed to decided-unused. A fourth site under pin 10a said "carrying it
conditionally costs nothing" -- found by sweep. Both updated. The subset
rule itself is unaffected; only its rationale needed the current term.
Still DRAFT, still unratified. No implementation work has begun.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps1szk2mSfgp4Cz21eVH9x
Independent review of 25473a1 returned two blocking findings. A sweep
escalated one of them into a third. Both reported findings are the same
failure in different clothes: a requirement stated with nothing able to fail
it.
1. Pin 10a still deferred the label decision to execution -- twice reworded,
never decided. The facts were readable in the .tex source the whole time:
core_spec.tex:6529-:6648 is ONE requirement box carrying the single label
req:graph:score-graph-invariants, with exactly 20 \items inside it.
Invariant 21 is a 21st \item within that box, so pin 6 mints nothing. Pin
10 rewrites prose plus a Revision History row and version bump, so it mints
nothing either.
DECIDED: neither document mints a label; touch row 11 is UNUSED and must
not be staged; no counter moves. If execution finds otherwise that is a
finding against this contract, not a keyboard decision. The counter table
is retained for that case and for the next rung. Row 11 is kept rather than
deleted because CLAUDE.md names the file as a recurring escapee -- a row
reading "deliberately unused, and why" survives review; an absent row looks
like an oversight.
"Decide and report" reads like rigour and is its opposite: it makes the
staged set and counter expectations depend on a choice made at the
keyboard, so the touch table can be wrong in either direction and the
report will agree with whatever happened.
2. Pin 6a required each fixture to violate its own direction only, and nothing
could observe that. The prescribed model, m40, asserts only
check_invariants(&s).iter().any(...) -- any() cannot see a second unrelated
defect -- and gate 6 checked the target verdict and the opposite direction
but never the absence of invariants 1-20. So a fixture carrying an
unrelated second defect satisfied every stated gate and mutation outcome.
Each m41/m41b must now assert the EXACT violation set: exactly one
violation, StaffGroupMembershipAgreement, witness naming that direction's
staff and group ids, opposite direction asserted satisfied. Gate 6 reports
check_invariants' full return for both.
Borrowing a test's shape imports its blind spots along with its virtue. m40
was cited for its dispatch property, which is real and still applies;
nothing about invariant 20 ever turned on exactness.
3. Sweep: the same blind spot covers touch row 8's generator, and worse.
negative_generators_are_reasonably_targeted bounds kinds:
BTreeSet<GraphInvariant> at <= 3, but both directions of invariant 21 are
the SAME variant -- they collapse to one element, so no existing test can
observe direction at all; the other three all() loops assert only
!is_empty(). Row 8 now requires a dedicated permanent test that the
generator violates S->G and not G->S.
Also fixed: revision D's block was first inserted inside revision C's,
orphaning C's closing paragraphs after it. Reordered, and the cross-revision
pattern paragraph moved to the end and extended.
That paragraph now carries rev D's converse: the fix-every-site rule asks who
READS a corrected rule; rev D asks what OBSERVES each requirement. Findings 2
and 3 were invisible to every sweep that looks for restated text, because
nothing was restated. A rule with no consumer goes stale; a rule with no
observer was never enforced at all.
Still DRAFT, still unratified. No implementation work has begun.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps1szk2mSfgp4Cz21eVH9x
Independent review of 0a5b936 returned two blocking findings and one factual
error. The first two are one issue: invariant 21 mandates two directions, and
only one -- unspecified -- had durable evidence.
1. Neither direction had permanent named coverage. Pin 6 asked for "a score
violating only invariant 21" (singular), gate 6 asked for one score, the
generator carries one, and M6 observed both directions only while mutated.
A mutation is reverted, so the restored suite could ship with one branch
untested.
Pin 6a added: two permanent, direction-isolated tests --
m41_check_invariants_dispatches_invariant_21_staff_names_absent_group (S->G)
and m41b_check_invariants_dispatches_invariant_21_group_lists_unowned_staff
(G->S) -- each required to SATISFY the direction it does not break. Gate 6
requires both verdicts. M6 now breaks those exact tests, one each, and
requires the sibling to still pass; the surviving test passing is what
proves the arms independent rather than one arm catching everything.
This contract already stated the rule and did not apply it to itself: pin
3a says "a mutation demonstrates the hazard once; only a test keeps it
demonstrated." M6 was carrying both directions on mutation alone, three
sections below that sentence.
2. "One named direction" delegated a design decision to execution. Either
choice changes the generated witness and the shrink evidence, so reporting
it afterward is not specifying it. Pinned to S->G in touch row 8, with the
reason: smallest corruption of valid_score (drop the staff id from
group.members, leave staff.group intact), matching every other arm's
doctrine, and the exact shape pin 2's append failing produces -- which is
what M2 observes.
Three fixtures now have three distinct purposes: row 8's generator (S->G,
survives shrink), pin 6a's two direction-isolated tests, and M6 as their
signature. None stands in for another.
3. §6's revision-B history said §4 has "twelve entries -- 1-11 plus 4a," a
false identity. The item numbered 4a. was a scope note with no command and
no output -- it could not be "a gate result" -- and it collided with §4a,
the landing-obligation section. Demoted out of the gate numbering into gate
4's body. §4 now has eleven gates, 1-11, and §4a is unambiguous.
Still DRAFT, still unratified. No implementation work has begun.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps1szk2mSfgp4Cz21eVH9x
Independent review of 3096c54 returned three blocking findings, all in
revision A's text, and each is a rule revision A had just corrected surviving
one step downstream of where it was fixed.
1. §6 item 2c still said "all three counters and their new values" -- the rule
pin 10a corrected in the same revision. Third site of one false claim: the
pin, then touch row 11 (caught by rev A's sweep), then the report item that
READS the pin. Now points at pin 10a's table and requires naming which
document minted the label.
2. §6 still demanded "the nine gate results" while §4 carries twelve entries
(1-11 plus 4a). Revision A removed the identical tally from item 1 for
mutations and left its neighbour on the next line standing. Count removed,
§4 named as origin, 11a-e identified as subchecks of one gate rather than
five separate results.
3. Touch row 8 still required violating_score's fixture to violate "both
directions" while M6 requires direction-isolated fixtures -- incompatible
evidence models in one contract. A both-direction generator remains
reported after either M6 arm is deleted, so it cannot sign that arm's
absence. Row 8 now specifies one named direction plus shrink survival; pin
6/M6 own two separate isolated fixtures. violating_score returns one Score
per variant and could not have carried both regardless.
Found by sweeping and fixed with them: gate 7 and §6 item 4 both said "the
four pin-8 tests." Correct today, but the same construction -- a count
restated away from its origin, and inconsistent with the two tallies just
removed. Pin 8's table named instead.
The pattern across revisions A and B is sharper than any single finding: a
correction propagates one hop and stops. Rev A fixed pin 10a and left touch
row 11; the sweep caught row 11 and stopped before §6's consumer. Rev A
removed item 1's mutation tally and left item 2's gate tally on the next line.
The fix-every-site rule is not satisfied by fixing the site and its obvious
neighbour -- it requires asking who READS the corrected rule.
Noted, not changed: §6's items run 2, 2b, 2c, 2d, 2a because 2a predates the
new items. Cosmetic, and renumbering would break pin 12's and gate 10's
references to item 2b.
Still DRAFT, still unratified. No implementation work has begun.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps1szk2mSfgp4Cz21eVH9x
Independent review of d06e2f7 returned six findings, five blocking, all in
draft amendment 1's own text. A sweep found a seventh: the same false claim at
a second site.
1. Report item 2d asked execution to decide a static fact the draft could
read. shrink (generators.rs:932) does NOT match GraphInvariant -- it calls
check_invariant(score, inv). The unverified claim was made twice, in item
2d and in touch row 8. Both corrected. Item 2d replaced with the real
obligation: invariant 21's fixture must survive shrinking (:1025), since
shrink asserts on entry that its input still violates the target.
2. Pin 10a's "all three counters move if either document mints a label" is
false. CORE_REQUIREMENT_COUNT is asserted only against core_spec.tex
(requirement_labels.rs:259); a label in operation_catalog.tex moves the two
suite counters only. Replaced with a per-document table.
Sweep finding: touch row 11 carried the same false sentence and was left
standing while pin 10a was fixed -- the fix-one-site defect, committed
inside the edit that fixed the other site. Row 11 now points at pin 10a
rather than restating it.
3. Gate 11 permitted the exact tautology it exists to prevent. "Updated, not
silenced" does not forbid replacing the literals with
CURRENT_REDUCTION_ALGORITHM_VERSION -- the tidiest-looking update, after
which both operands move together and M5a/M5b are vacuous. Rewritten as
11a-e requiring independent literal 1 values, never the constant, each
quoted. S27 round 3 caught this substitution and roundtrip.rs:882 forbids
it by name.
4. Gate 11 omitted roundtrip.rs:947, test 10b's mutation-only Err arm. Left at
0, M5b aborts on the base comparison before reaching the two-field panic
that is its required observation -- failing at the wrong assertion while
observing nothing. Added as 11d, with 11e for the literal-preservation
comments whose reasoning is what stops the next rung making substitution 3.
5. §6 demanded "the nine mutations (M1-M9)" while M7's split makes ten
executions. Count removed; §3 is the single origin.
6. Pin 12 said no gate catches a missed bump except the tripwires, written in
the same amendment that added gate 10, which compares the value against
HEAD directly. Split: gate 10 guards this bump, 11a-e guard the wiring,
only the general future case stays undetectable.
Tightening folded in with finding 1: M6's two fixtures must each violate ONE
direction only. A fixture disagreeing in both is still reported after either
arm is deleted, so the mutation appears to fail correctly while signing
nothing. The same trap applies to touch row 8's generator, whose all()-driven
consumers only ask whether 21 is reported.
Findings 1, 2, 3 and 6 share one root cause: an S27 conclusion applied without
re-derivation. S27's "name all three counters," its "no mechanism can detect a
semantics change," and its literal-independence rule are true statements about
S27; two are false or incomplete here and one was dropped where it was needed.
A ratified contract is reusable as a source of questions, not of answers.
Still DRAFT, still unratified. No implementation work has begun.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps1szk2mSfgp4Cz21eVH9x
Fourteen findings, thirteen blocking, against the draft BEFORE its first
ratification round. Nine from the recon pass, five from a second sweep of the
same defect classes. The contract is a DRAFT, so these are edits to an
unratified plan, not amendments to frozen pins.
Root cause, and the one that changes the rung's shape: invariant 21 is a
TWO-crate change. violating_score (epiphany-core/src/generators.rs:498)
matches GraphInvariant exhaustively, so the new variant does not compile
without an arm, and four all()-driven tests (:991, :1004, :1025, :1042) then
require a real generator rather than a stub. The contract had treated the enum
extension as local to invariants.rs. Nothing in its §0 inspection would have
surfaced this -- it appears at compile time, after execution begins.
Touch table gained five rows:
- epiphany-ops/src/lib.rs -- the bump and its Bumps entry. The rung's defining
act had no pin, no touch row, no gate and no report item; it existed only as
a note inside pin 0's discharge. Now pin 12, touch row 7, gate 10, item 2b.
- epiphany-core/src/generators.rs -- the root cause above.
- epiphany-testkit/src/roundtrip.rs and epiphany-textproj/src/serialize.rs --
S27's two tripwires. Test 10b panics by design once the authority moves;
test 10a asserts the literal 0 and its own doc says it is expected to fail
when S16 bumps. Both fail gate 1 with no row to fix them in -- the same
shape as S27's own gminor.rs failure. Gate 11 requires they be updated, not
silenced: a tripwire accepting both values rebuilds the tautology S27
existed to remove.
- requirement_labels.rs -- conditional, the escapee CLAUDE.md names by name.
Pin 10a now forces an explicit decision on whether pin 6 or pin 10 mints a
label; this rung touches both counted documents, and all three counters move
if either does.
Gates: 2 and 3 now pin +1.95.0 (CI gates on it; this machine defaults to
1.97.1). Gate 3 formatted two crates of four and would have reported clean
over the two the new rows added. Gate 1 gained the 1577/0/0 baseline, three
delta buckets, and a 0-ignored requirement. Gate 4's "exactly §2" was the
formulation S27's round 17 found unsatisfiable with a conditional row.
Mutations: M1, M2 and M4 accepted "the named test fails" as their whole
signature; each now names the behaviour the mutated build produces -- the
applied spurious mint, the still-empty members, the re-carry misverdict. M3,
M5, M6 and M9 already met the standard. M7 covered two independently guarded
doc blocks while reverting one, so it could pass with the other guard still
weak; split into M7a and M7b, each quoting its own needle's non-match.
Locators: pin 8's four tests were identified by INTERIOR line numbers, a dozen
lines into each body, anchored to nothing searchable -- now named. t6/t7/t9
re-derived to :16158/:16231/:16461, with the trap recorded that reduce.rs has
two t6/t7 families and grepping "fn t6" lands on the wrong one.
New §4a, landing obligation: the bump falsifies live statements in
CLAUDE.md:106 and the handoff's POST-S27 block. Both are explicitly NOT staged
during execution -- staging them would assert the rung had landed while it
awaited acceptance -- and are required as post-acceptance reconciliation the
report must list as outstanding. Pin 11's ledger row is different and is
staged: a ledger records what a rung did; those two state what is true now.
Report item 2d requires execution to determine whether shrink() also matches
GraphInvariant exhaustively. Deliberately not guessed -- guessing about an
exhaustive match is what produced the root-cause finding.
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. The
next contract drafted here should start from S27's §5.
No implementation work has begun. Still DRAFT, still unratified.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps1szk2mSfgp4Cz21eVH9x
The supersession declared pin 0's "no mechanism to detect a
reduction-semantics change" false because version mismatches are now
rejected. That conflates two different claims, and it contradicted this same
contract's requirement 3 thirty lines below, which says no mechanism can
detect a missed bump. CLAUDE.md, the handoff, and epiphany-ops's own
authority doc all state the correct version.
Only one of the two claims moved:
- Declared-version mismatch -- a base whose recorded
reduction_algorithm_version differs from the running authority -- is now
ENFORCED, refused with CanonicalBaseRequiresRebuild on read and write.
- A semantics change is STILL UNDETECTABLE. Nothing compares the semantics
the code implements against the number it declares, and nothing can.
So S27's enforcement is conditional on the bump 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. That is exactly why pin 0's requirement 3
inverts into a mandatory bump rather than dissolving: S16's bump to 1 is the
human-enforced half of the guarantee, and the only half that applies to
itself.
Changes:
- The falsified-claims table splits the row in two: the accepted-stale-base
consequence is false now; the no-detection claim is marked STILL TRUE.
- New "Enforcement is not detection" section states the split as a table so
the two cannot collapse into each other again.
- The discharge marker before pin 0 no longer lists no-detection among the
falsified claims and warns not to read the discharge as closing that gap.
- Pin 11's retained blockquote had the same defect: it was annotated "every
claim in this quoted block is now false," but "no mechanism comparing
either against the semantics it actually implements" is still true -- S27
compares a declared number against a declared number. Annotation now
separates what is false from what stands.
Documentation only.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps1szk2mSfgp4Cz21eVH9x
Follow-up review found the previous reconciliation half-done in two places.
Documentation only; the accepted S27 code is untouched.
spec/HANDOFF_2026-08-07.md -- it declared CLAUDE.md the sole origin for the
green baseline and then stated the number twice anyway, once in its own update
table and once in §3.5. Both replaced by pointers; the figure now exists in
exactly one file. The §1.2 marker also said everything following it was
pre-S27 while two post-S27 notes sat inside that section. The notes are
deleted and their substance folded up into the top block's table rows -- the
permanent acceleration_snapshots warning onto the conformance row, the
"may not degrade to read-only" carry-over onto the errors row -- so §1.2 is
now purely a dated record. The marker is also scoped to §1.2 rather than
"everything after," which would have wrongly claimed §1.4 onward.
spec/CONTRACT_P13S16_PROJECTION.md -- pin 0 still carried live pre-S27
instructions: no authority exists, no mechanism detects a stale base,
ids.rs:288's catalog claim is false, this rung cannot execute, the ledger
stays blocked on P13-S27, and file S27. A status-and-ledger update did not
reach any of them.
- Pin 0 now carries a discharge marker BEFORE the pin, not only after it, so a
top-down reader meets the correction before the false claims.
- The discharge answers each falsified claim individually in a table, and
replaces all three numbered requirements. Pin 0's deliberate narrowing --
that no history audit was done, so the stronger "never detectable" claim
must not be written -- survives and still binds.
- Requirement 3 inverts rather than disappears: stale bases are now rejected
and S27 owns those tests, so this rung must not add a second detection path;
what it must do instead is bump CURRENT_REDUCTION_ALGORITHM_VERSION to 1.
- Pin 11's live "File P13-S27 in the same edit" instruction is struck inline,
not only in its preamble, and the retained reasoning is properly blockquoted
with every claim in it marked false.
- §6 report requirement 2a required confirmation that nothing was added
detecting stale bases -- the opposite of what is now correct. Rewritten to
require the version bump with its Bumps-list entry, no second detection
path, and the unchanged operation_catalog.tex rebuild note.
- "The P13-S16 row is marked blocked on P13-S27" corrected; "does NOT move to
RESOLVED" is retained, since it is still true and is the distinction that
matters -- unblocked, dispatchable and resolved are three different states.
Line-number citations throughout the S16 contract predate S27's 795-line
bundle.rs change and are NOT re-derived here; that is recorded as part of
ratification rather than silently patched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps1szk2mSfgp4Cz21eVH9x
P13-S27 landed at 4df8e25. Four documents carried live statements that the
landing invalidated, and they had to move together: updating the ledger alone
would have left the active S16 contract contradicting it.
CLAUDE.md
- Track head: P13-S27 LANDED; P13-S16 unblocked, contract still DRAFT.
- The T1b/S27 collision is resolved. T1b is NOT thereby free -- it stays
blocked on Ruling B blocker (ii), versioned decode. A future
epiphany-bundle rung re-creates the collision on its own terms.
- Green baseline 1570 -> 1577, and marked as the single origin for the count.
- "One live constraint" rewritten: the blanket no-canonical-base prohibition
is lifted, replaced by the authority check (accepted when
reduction_algorithm_version equals CURRENT_REDUCTION_ALGORITHM_VERSION,
currently 0; CanonicalBaseRequiresRebuild on both read and write paths;
legacy epoch still refuses outright), plus the bump discipline and the
synthetic_for_fixture / production_caps split.
spec/HANDOFF_2026-08-07.md -- a dated snapshot, so it keeps its text and gains
a POST-S27 UPDATE block at the top that is the single place the new state is
given. Each invalidated site now points there instead of restating:
§1.2 constraint (dated record), the suspended conformance wiring (restored),
ReductionAuthorityUnavailable (deleted, replaced), §1.4 chain state, §2.6 and
§4.3 collision, §4.3 items 7 and 9. The three 1570 repetitions are replaced by
a pointer to CLAUDE.md -- a figure kept in four places goes stale in three.
spec/CONTRACT_P13S16_PROJECTION.md
- Status: DRAFT, UNBLOCKED 2026-08-09, NOT RATIFIED and therefore NOT
dispatchable. Pin 0's blocker is discharged; core_spec.tex:11614 is met.
Original status retained verbatim.
- Pin 11 amended: it mandated a ledger state of "blocked on P13-S27," which is
now false -- a pin requiring a false ledger state would put the contract in
contradiction with the ledger it governs. Its instruction to file S27 in the
same edit is discharged.
spec/PASS13_CANDIDATES.md -- appended to both cells, per the append-only
convention.
- S16: unblocked, with the "additionally needs pin-2a's disposition" sentence
explicitly superseded (settled from outside S27 by the format rung's pin 8);
unblocked is not dispatchable; first act is bumping the authority to 1.
- S27: accepted and landed, with the gate figures, and a correction to pin
10's own wording -- it said S16 becomes "dispatchable," but by this repo's
definition S16 is unblocked, not dispatchable. Same unblocked/dispatchable
conflation S27's round 1 committed.
Documentation only: no .rs or .toml touched, so the gates re-run against the
landed tree stand unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ps1szk2mSfgp4Cz21eVH9x
Scoping disposition A found two things that move the rung's size in
opposite directions.
Cheaper: the refusal needs no new PreconditionFailureReason and no
schema-minor epoch. reduce.rs:1236's container_not_empty() helper
already covers "a create carrying children" by its own doc, and three
creates already call it for exactly this shape. create_staff_group is
the sole outlier.
More expensive: this is a canonical reduction-semantics change, not
merely a behaviour change. The same operation set now reduces to a
different Score, so core_spec.tex:11614 applies — canonical bases
materialized beforehand cannot be reused without rebuilding. That
requirement is currently unenforceable, so the contract is complete and
ratifiable as a plan but explicitly not dispatchable.
S27 is why. The version machinery is self-referential:
reduction_version_for sources a new superblock's value from the
canonical base's own self-report, and open compares it only against the
superblock that value seeded. The check is not vacuous — it catches a
corrupt base disagreeing with its superblock — but it necessarily
passes for a conformingly propagated stale base, which is the case the
requirement exists to prevent.
An earlier draft of pin 0 claimed no writer path existed at all. That
was false, and the way it was false is recorded in both the contract and
the S27 row: the search behind it looked for constructor calls, which
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.
Docs only. No code, no spec sources, no implementation authorized.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV