The first version answered "what is the state of the work" but not
"what actually has to move", which is the question a machine change
asks. Audited it instead of assuming.
Most of the risk turned out to be already retired: spikes/ is tracked
(106 files), the editor-t4 worktree is clean with zero uncommitted and
zero untracked, there are no stashes, and every commit on every local
branch is already on origin. There is no unpushed work on this machine.
What a clone still will not give you, now enumerated: the gitignored
root HANDOFF.md (18KB, not regenerable, deliberately kept out of git),
.claude/settings.local.json, and — outside the repository entirely —
the 21-file agent memory directory both threads have been accumulating
track notes in.
One real exposure found: the branch ref editor-t4-packet2b exists here
and not on origin, which has only refs/heads/main. Its commits are
reachable through the f33673d merge so nothing is lost, but the pointer
is local and should be pushed before this machine is wiped.
§4 is restructured into before-wiping, on-arrival, and then-work, since
two of those steps have to happen while this machine still exists.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
spec/HANDOFF_2026-08-07.md: state of the spec / Pass-13 / format-epoch
thread at be244df, the live constraint that no bundle may carry a
canonical base until P13-S27 lands, the S28 -> S27 -> S16 chain with
S27's three inherited obligations, the working agreements that are not
derivable from the code, and the environment notes the other machine
needs (xelatex not pdflatex, the 1.95.0/1.85 toolchain pins, the
cargo fmt --all trap and why its --check form is safe).
Section 2 covers the parallel editor/T4 thread and is explicitly
bounded: those files were out of bounds for this session all along, so
it records only what shared git history shows plus leads to verify, and
says plainly that it is not a substitute for that session's own handoff.
It does name the one place the threads can collide — the canonical-base
interval — which neither side can see from its own side.
Ledger: P13-S27 and P13-S28 both still opened with "open" while their
resolutions sat further down the cell. S27 is UNBLOCKED and
dispatchable; S28 is IMPLEMENTED. The cells are appended to rather than
rewritten, so an opener can lag the truth by several rungs; the handoff
records that as a reading hazard.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
document_from_bundle refused a base-bearing bundle, but the public
project_text_document did not. A caller holding a directly constructed
TextDocument could therefore emit a (canonical-base ...) line that
parse_document then rejects — a projector able to produce what the
parser refuses, which is precisely the asymmetry pin 3b exists to close
and which req:textproj:roundtrip's second equation quantifies over.
The guard had been placed on the path the pin happened to name rather
than on every path a caller can reach, and the unguarded one was the
only reachable half: no live Bundle can carry a canonical base during
the S28 -> P13-S27 interval, so the bundle-side refusal cannot fire
today, while the document-side path is one public call away. The new
corpus vector proved the hole existed rather than closing it — it is
built by projecting a base-bearing document.
project_text_document now returns Result and refuses. A crate-private
render_text_document keeps the unchecked formatter for its one
legitimate caller, the canonical_base_present negative vector: a
negative vector still has to contain the spelling it asserts is
refused, and producing those bytes is not the same as permitting them.
Every other vector goes through the checked projector.
projecting_a_base_bearing_text_document_is_refused locks both halves —
that the public projector refuses, and that the private renderer still
emits the section, since the reject vector silently stops carrying its
spelling otherwise. Mutation-verified: removing the refusal fails that
test and nothing else. Restored by hand.
The corpus is byte-identical, so no vector regenerated. Workspace green
at 1570.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
FORMAT_MAJOR becomes 1 and FORMAT_MINOR restarts at 0. The decoder
stops being exact-major-only: it classifies three ways through a named
FormatEpoch carried on FixedHeader, so major 0 is decoded deliberately
as legacy rather than refused. Old readers already fail closed on an
unknown major, so that half needed no mechanism — which is why the major
is the right carrier, and why the header's immutability, fatal to
FORMAT_MINOR as a provenance field, is what makes it sound as an epoch
field.
The matrix: a major-0 bundle with no base may open; one carrying a base
is refused; one attempting to add a base is refused and told to repack.
That last row is the non-inheritance rule. Three errors, none of which
degrades to read-only: two permanent legacy/repack errors, and
ReductionAuthorityUnavailable, which is temporary, names P13-S27, and
must not say repack — a major-1 container is already the right epoch.
Until P13-S27 lands, both major-1 base boundaries are closed: opening a
major-1 bundle that already carries a base, and committing one into it.
Neither may be left open while the epoch asserts a validation that never
ran.
Text projection cannot mint a base. serialize_document staged a carried
base into a fresh bundle and build_manifest wrote it, so an old or
hand-authored document could be laundered straight through the boundary.
All three sides now refuse: projection, parsing, and a new dedicated
SerializeError variant — none of which existed to be "retained".
COMPANION_VERSION moves to 0.14.0 and the corpus is rebuilt to 20
vectors and ten rejection classes, with canonical_bases reach dropping
2 -> 0. That is a real capability loss and is recorded as one.
Corruption keeps precedence in both epochs: a corrupt major-1 base fails
as malformed, never as the temporary authority error a user would
reasonably retry.
All 11 mutations were run and observed, not reasoned about. M4 is the
signing one — with the legacy commit refusal removed, a legacy container
gains a base in place, which is exactly the counterexample that killed
FORMAT_MINOR. M11 confirms the third error is distinct while test 3
stays green, proving the mutation stayed inside the major-1 branches. M7
fails on both epoch halves. M8 falls through to
SerializeError::Bundle(ReductionAuthorityUnavailable), confirming the
text layer's own refusal is what the test asserts.
Two touch-table gaps surfaced during execution, both the same shape: a
.tex requirement addition moves hardcoded counts in
requirement_labels.rs, and the companion bump moves a second normative
version literal spelled version~0.13.0 rather than (0 13 0). Neither
file was in any touch table; the second was caught only because a test
exists for exactly that failure.
P13-S27 is unblocked — its pin 2a is resolved from outside, as its own
prohibition required — and inherits three obligations: both interim
refusals converted to validation, M8's deferred laundering
demonstration, and pin 3c's two suspended conformance assertions.
P13-S16 remains blocked on S27.
Workspace green at 1569.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
Reconnaissance before dispatch found that pin 3a's refusals close every
path to a base-bearing container, which the pins never said out loud.
create already rejects a base-bearing manifest, row 3 refuses committing
one into major 0 and row 6i into major 1, and rows 2 and 5i refuse
opening one in either epoch. For the whole S28 -> S27 interval no bundle
anywhere may carry a canonical base.
That reaches criterion 4's bookkeeping-projection counterpart,
roundtrip::assert_reduction_serialization_stable, which commits the
canonical state as the canonical base and reopens it. Both halves are
refused. Its file was not in the touch table, and staging is limited to
that table, so gate 1 could not have passed and the fix could not have
been staged.
The first reading of the finding said the assertion could only be
suspended. That was too strong and was checked before the pin was
written: read_chunk hash-verifies any ChunkRef, so the serialize -> load
-> decode -> reserialize cycle does not depend on the snapshot being the
canonical base. The harness keeps the cycle and loses exactly two
canonical-base-specific assertions — verify_canonical_chunks's base
branch with its hash cross-check, and the reopened manifest carrying the
base. Both are recorded as owed back by P13-S27.
Re-homing the snapshot to acceleration_snapshots is forbidden: that
field appears nowhere in bundle.rs, so the reference would verify
nothing while looking like preserved coverage. Pin 3c names it as the
tempting wrong repair, and names one more trap — the existing
mis-stamped-base test asserts read-only for a base fault on the
data-model-major axis, which must not be harmonized toward pin 4's three
errors, none of which may degrade to read-only.
Adds touch rows 10 and 11 (testkit roundtrip harness, bundle bench) and
gate 8, which classifies every surviving base-declaring site as a
crafted-image fixture, a refusal test, or the text corpus — a live
commit path among them is a failure.
Forward amendment of a ratified contract, with its own review round.
Documentation only. Still not implemented.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
P13-S28 asked for a container property old readers cannot silently
accept and a later commit cannot inherit unchanged. The format major is
that property. FORMAT_MAJOR moves 0 -> 1, FORMAT_MINOR resets to 0, and
the decoder stops being exact-major-only: it classifies three ways
through a named FormatEpoch, deliberately decoding major 0 as legacy.
Old readers already fail closed on an unknown major, so that half needs
no new mechanism — which is why the major is the right carrier and the
header's immutability, fatal to FORMAT_MINOR as a provenance field, is
exactly what makes it sound as an epoch field.
The eight-row matrix carries the rule: a major-0 bundle with no base may
open, one carrying a base is rejected, and one attempting to add a base
is rejected and told to repack. That last row is the non-inheritance
rule. Legacy resolves to hard rejection, never read-only — a
pre-authority base is not a restricted-but-correct view.
Three things the review rounds found, none visible at filing:
It cannot stamp major 1 before S27's writer enforcement exists. Pin 3a
therefore closes both boundaries temporarily — opening a major-1 bundle
already carrying a base, and committing one into it — through a third,
temporary error that names P13-S27 and must not name repack, since a
major-1 container is already the right epoch.
Text projection launders provenance straight through the boundary:
serialize_document stages a carried base into a fresh bundle and
build_manifest writes it. Resolved as symmetric document-level refusal —
projection, parsing, and a new dedicated SerializeError variant. None of
the three existed to be "retained"; an earlier draft claimed otherwise
and was wrong. This forces COMPANION_VERSION to 0.14.0 and rebuilds the
committed corpus to 20 vectors and ten rejection classes, with
canonical_bases reach dropping 2 -> 0. That is a real capability loss
and is stated as one.
Corruption precedence binds in both epochs. A corrupt major-1 base must
still fail as malformed, never as the temporary authority error a user
would reasonably retry on a container that is in fact tampered with.
11 pins, 11 tests, 11 mutations, 15 touch rows, 7 gate items. S27's
contract is a mandatory touch: pin 8 resolves its open pin 2a — legacy
bases are refused by container epoch, never by version arithmetic.
Documentation only. Not implemented, not dispatched. S27 and S16 stay
blocked until this rung lands.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
S27 installs a reduction authority but cannot say what to do with a
base that predates it. A raw ReductionAlgorithmVersion is a bare u32
with no provenance, and the text-projection parser accepts an unbounded
one from a document, so no numeric convention — including a high epoch —
is safe from a hand-authored file declaring it.
FORMAT_MINOR fails too. The header never changes after creation and
commit publishes only a superblock, so a legacy bundle that commits a
freshly validated base keeps its old minor forever: rejecting minor-<=1
bases rejects one the authority just accepted, and accepting them leaves
S16's version ambiguous. Separately, a minor change may only append
append-safe discriminants, and current readers ignore minor entirely, so
the boundary would bind only readers that already comply.
What survives is the requirement: provenance must ride a container
property old readers cannot silently accept and a later commit cannot
inherit unchanged. S28 owns that, plus the old-reader rejection
boundary, legacy rebuild/repack behaviour, every writer path including
text projection, and the exact format-version consequences.
S16 and S27 both blocked on it; the chain is recorded in all three rows.
Ledger only.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
The reduction-version check is a tautology: reduction_version_for seeds
a superblock from the canonical base's self-report, and open compares
the base against that superblock. Both operands descend from the same
source. It catches a corrupt base disagreeing with its superblock, and
necessarily passes for a conformingly propagated stale one.
Rulings: a typed BundleCapabilities required at both constructors and
carried on the Bundle, with no default; outright rejection on mismatch
via CanonicalBaseRequiresRebuild, not read-only and not an anomaly.
Storing the capability keeps all 57 commit sites unchanged; only open
(57) and create (32) move.
Pin 2a stays an open question. Baseline zero does not preserve the
corpus, and after S16 moves the authority to one, a pre-S27 base that
happens to carry one is indistinguishable from a legitimately rebuilt
S16 base. A raw u32 has no provenance. Four candidate dispositions are
recorded, including FORMAT_MINOR as a carrier, which is rejected here
for two independent reasons: the header never changes after creation
(core_spec.tex:10799) so a legacy bundle that commits a validated base
keeps its old minor forever, and a minor change may only append
append-safe discriminants (core_spec.tex:12258), not alter acceptance
semantics — while current readers ignore minor entirely, so the
boundary would bind only readers that already comply.
What survives: provenance must be carried by a container property old
readers cannot silently accept and a later commit cannot inherit
unchanged. That is a format-epoch design, filed next as P13-S28.
Three claims of mine died while scoping this, all recorded in the
contract: a constructor-grep that could not see a propagated value, a
head -14 truncation behind a universal negative, and reading
`format_major != FORMAT_MAJOR` for the half that supported the
proposal. A contract about unobservable defects should carry its own.
Docs only. Not dispatchable. S16 and S27 both remain blocked.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
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
The ratified reasoning is untouched — every changed token is a bare line
number. What the contract argues, pins, and disposes of is unchanged;
only its pointers move, so a reader following them lands on the code the
sentences describe.
Ten, not the two first found: reduce.rs:4119 and :4117 both name
CreateStaff's group precondition, now :4372; invariants.rs:1126/:1135
name invariant 10's two resolution arms, now :1188/:1196; graph.rs:819
and :1614 name the two fields, now :848 and :1650, cited twice each.
core_spec.tex:5578 appears twice and needed two different corrections —
at the prose it quotes the doc line "Visual grouping: which staff group",
now :5585; in the pin-4b table it parallels :4231's struct declaration,
so it wants `pub struct Staff {`, now :5564. A single substitution would
have been wrong in one of the two places.
Forward, not by amendment: the contract is ratified and pushed, and its
hash is cited by the ledger and the genesis plan.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
S8 carried a false mutation claim. It said flipping `is_none_or` to
`is_some_and` at the constant-tempo check killed no test; three tests
kill it — two in graph_reduction, one in convergence reporting the
witness verbatim. Executed and restored by hand. The original was
reached by reading only invariants.rs's own tests, which assert
`fires(...)` and survive the flip; the kill sites live in another crate.
The genuinely unconstructed spelling is the opposite one: `Constant`
with `Some(equal)` has no construction site anywhere.
S8's ratification also stops presenting normalize-on-encode as the
default. Folding one of two accepted byte forms violates
req:binfmt:decode-vectors' injectivity rule, and
req:binfmt:compression-none-parameter is the ratified precedent for
refusing exactly that leniency. A repair must reject one spelling or
keep both as distinct canonical values.
S16 had drifted in every code citation, some by hundreds of lines, and
never named what makes its fix expensive: t8b_both_permitted_stale_forms_hold
pins both stale forms as passing and documents disposition A's rules as
mutations that must break it. The fix is the mutation an existing test
exists to detect.
S25 files disposition B of S22 — corpus rows named for their variant.
Complementary, not a replacement: it reaches other implementations, but
its failure still reads as corpus staleness.
S26 files a doc comment claiming a core_spec repair that never landed.
A P13-S9 instance, and the sharpest: the side that is wrong about the
other is the grep-guarded side. Its evidence must not be repaired alone.
Locators only in this commit; no code, no spec, no contract.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
§3 offered "delete the new table's row loop (or the whole new test)" as
interchangeable forms of the paired control. They are not. Deleting the
test drops the suite to 1557, contradicting the 1558/0 result required
in the same sentence; only loop-deletion holds the test count fixed so
the passing count is comparable to the baseline.
Found by the executing agent, which took the loop reading — the only one
satisfying both conditions — and reported the tension rather than
silently picking. Corrected here as a forward commit: 6e4e455 already
sits on the contract, and the evidence chain reads better with the
defect and its repair both visible.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
`operation_kind_tag_vocabulary!` generates `discriminant()`,
`from_discriminant()`, `catalog_name()`, `introduced_minor()` and
`PAYLOAD_FREE` from one list, and `PAYLOAD_FREE` carries variants only.
Every derived test therefore obtains a tag and its byte from the same
invocation and asserts `$disc == $disc`. It cannot disagree with the
macro, because it is the macro.
The kind side never had this problem: `OperationKind::discriminant()` is
a hand-written match, so two independent statements exist and
`operation_kind_wire_discriminants_are_golden` asserts they agree. This
adds the tag side's second statement — `tag_wire_discriminants_are_golden`,
a hand-typed 40-row literal table transcribed by reading the macro
invocation rather than derived from its output.
Coverage is computed, association is not: a 40-long array does not prove
forty distinct tags, so the table's totality over the vocabulary is
asserted separately, and the comment says why that is not circular.
Payload-free tags assert the whole canonical byte vector — which also
proves the length-1 property the retired test had and the kind-side
idiom lacks — while `Registered`, the one tag carrying a payload,
asserts `[0]` alone.
Retires `phase3_tag_discriminants_are_golden`, whose whole subject was
tag→byte for 24–29. Keeps the three assertions at payload.rs:3086,
reduce.rs:12744 and reduce.rs:15941: the table duplicates their tag→byte
subclaim but not the kind-and-tag pairings that contain them, nor G3b's
local mutation evidence.
Signed by the coordinated 32↔33 permutation — swapping the discriminant
literals *and* the declaration lines, so `PAYLOAD_FREE` still emits
ascending discriminants and every derived artifact stays byte-identical.
Before: 1558/0, silent. After: fails naming SetCanvasLayoutDefaults.
No wire, schema, specification or corpus change. Suite 1558/0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
The tag space has one statement of its own discriminants: the
`operation_kind_tag_vocabulary!` macro. `PAYLOAD_FREE` carries variants
only, so every derived test obtains the pair from that same invocation
and asserts `$disc == $disc`. The kind side is safe for a reason the tag
side lacks — `OperationKind::discriminant()` is a hand-written match, so
two independent statements exist and the golden lock asserts they agree.
Disposition A: a hand-written literal table supplies the second witness.
The signing mutation, executed at 17c1d67: swapping tags 32 and 33 in
both the discriminant literals and the declaration lines leaves
`PAYLOAD_FREE` emitting ascending discriminants, so every derived
artifact stays byte-identical. Two operations exchanged wire
discriminants and the workspace suite returned 1558/0 — unchanged from
baseline. The three probes already on record swapped literals only,
which reorders the corpus and gets caught; they proved a lock exists,
not that one is missing.
Retires one test, not six: the table duplicates the tag→byte subclaim of
the three retained assertions but does not subsume the kind-and-tag
pairings that contain them, nor G3b's local mutation evidence.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
Filed as a deferral -- pickups unmodelled -- it is closer to a live defect, and
the tree already held the proof. m35 placed a first measure at offset 0 and its
successor half a whole note later under a whole-note signature and asserted
invariant 20 fires. That is a pickup. The test has been labelled "wrong distance"
since packet 2. create_measure applies the same rule as a refusal, now observed
end to end rather than cited: the successor comes back NoOp with
MeasureMeterMismatch. Authoring a pickup does not leave it unmodelled; it makes
the rest of the instance unauthorable.
Both refusals carry the same reason code, so the fixture is the only thing
separating them. Pickup and successor both declare None, which keeps clause 2
from running on either side and makes the observed refusal provably clause 3's.
The pickup's own mint is asserted Applied before the successor's NoOp, because a
fixture whose operations never execute produces a non-Applied result
indistinguishable from a refusal.
The exemption is narrower than every document said. A first measure escapes only
the predecessor-dependent checks -- invariant 20's boundary clause, and
create_measure's clauses 1 and 3 -- plus agreement when it declares None or a
matching signature, and only when its other preconditions hold. It can still be
refused for a dead parent or an unresolving anchor referent, and invariant 10 can
still flag it. Seven surfaces carried the loose form; one had hardened into
falsehood, claiming all three clauses are vacuous for a first measure when
clause 2 has no predecessor dependency at all.
core/DECISIONS.md is deliberately untouched. It already said "never flagged by
the boundary clause" -- the one site that drew the distinction correctly -- and
an earlier contract draft listed it as defective by matching the phrase without
reading its qualifier. The corrected ops entry now quotes that qualifier, and a
positive gate check protects it.
A mid-score partial enters successfully and its successor fails, so the scope is
boundaries following any partial measure, not partial measures. The root cause is
a missing quantity rather than a missing exemption: both rules compare the
start-to-start distance against the governing signature's full measure_duration
when it actually equals the predecessor's own content duration. Introducing that
quantity is a semantic rung; this one stops at its edge, with both function
bodies byte-identical.
P13-S24 is filed for the Chapter 3 splitter deferral, which shares the missing
partial-duration concept and is otherwise independent.
Executed against spec/CONTRACT_P13S19_PARTIAL.md, four mutations.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
P13-S19 is filed as a deferral. It is closer to a live defect, and the tree
already contains the proof: m35_boundary_flags_wrong_distance places a first
measure at offset 0 and its successor half a whole note later, under a signature
whose measure_duration is a whole, and asserts invariant 20 fires. That is a
pickup, and the test has been labelled "wrong distance" since packet 2.
create_measure applies the same rule as a refusal. So authoring a pickup does
not merely leave it unmodelled -- it makes the rest of the instance unauthorable.
The rung corrects seven surfaces, three of them normative, one of them
outright false: ops/DECISIONS.md says all three CreateMeasure clauses are
vacuous for a first measure, and the agreement clause has no predecessor
dependency at all. It observes the reducer refusal rather than citing it, files
the Chapter 3 splitter deferral as its own candidate, and introduces no
partial-measure notion -- that is a semantic rung and pin 9 stops this one at
its edge.
Three over-readings committed while drafting are recorded inside the contract
rather than smoothed away, because text that is locally true and globally
misleading is the rung's own subject. It claimed a pickup is "neither refused
nor flagged" (the agreement clause runs on a first measure); it claimed that
again more weakly after the first fix (referential preconditions and invariant
10 still apply); and it listed core/DECISIONS.md as an understated surface by
matching "never flagged" without reading the qualifier that makes it correct.
That file is now explicitly out of bounds, with the reason, so a later reader
comparing it against the corrected ops entry does not "fix" the one site that
already draws the distinction.
The absence gate replaces a grep that could not fail. Two of its seven phrases
straddle a newline, so a line-oriented search would have returned nothing before
the correction as well as after; the gate normalizes whitespace, and all seven
anchors were confirmed present at 339269b so it fails today and can pass only
once the work lands.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
Invariant 20 has nine non-success paths, not the three P13-S18 recorded. Only
three are abstentions: agreement-Indeterminate, boundary-Indeterminate, and an
incomputable boundary delta. Two are delegated to invariant 10, two are vacuous,
one is inapplicable, one is P13-S19's pickup deferral. The entry had been
counting all of it as gap.
Delegation is proved, not asserted. Deleting invariant 10's per-measure arm
leaves the condition unreported by the entire workspace suite except by the two
tests that name it; the same holds for the instance-local-grid arm. A delegation
nobody discharges would have been an abstention with a better name.
Every abstention cell carries a paired positive control, because silence is the
same observation for all nine paths. Each test asserts zero violations on the
fixture that takes the claimed path, then changes only that path's dependency
and asserts the clause decides with the expected witness. The control has to
observe the clause the cell names: S8's first version restored the governing
search by moving prev, which broke prev<->x comparability and left the boundary
silent for a second reason, signing the cell by inference. Moving the grid edge
instead keeps both measures c4-comparable and the boundary clause itself fires.
S2 has no such option -- a WallClock delta is never computable -- so its control
legitimately observes prev's agreement, and that exception is S2's alone.
Three shapes claimed a clause pair no single measure exhibited: m0 carried a
resolving signature at index 0 and m1 carried None, so the pair was really
A4+B1 on one measure and A1+B4 on the other. A boolean over the whole invariant
cannot see that, which is how it survived the first pass.
No behaviour change. check_measure_meter_consistency's executable body is
byte-identical to f33673d at 4871 bytes, verified by brace-matching from the
signature rather than a sentinel; every red observation came from fixture data or
from invariant 10, never from invariant 20's own logic.
P11-C5 was never this residue's gate -- it is a re-anchoring proximity metric.
P13-S23 is filed for the real dependency: placing anchor pairs on a common
timeline and measuring musical distance wherever c1-c5 do not already yield
both. It owns two disjoint deficiencies, since c3 and c5 order without
supplying any delta.
Executed against spec/CONTRACT_P13S18_MATRIX.md, 18 cells and 10 mutations.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
Filed ahead of execution so the contract is under version control before any
agent touches it. This session already learned the other way round: the G3b
contract sat untracked past a "clean" gate, because git diff --check cannot see
a file that never reached the index, which is why this contract's own section 5
mandates git diff --cached --check instead.
The rung it scopes is diagnostic. Invariant 20 has nine non-success paths, not
the three P13-S18 records; only three are abstentions. Two are delegated to
invariant 10, two are vacuous, one is inapplicable, one is P13-S19. The rung
builds an 18-cell outcome matrix, classifies every path, corrects the ledger and
two drifted citations in the G3b contract, and files P13-S23 for the capability
everything defers to. No behaviour change: the gate requires
check_measure_meter_consistency's executable body to stay byte-identical, and a
finding that suggests changing which graphs violate invariant 20 stops the rung
rather than being implemented in it.
Six review rounds are recorded in the pins as corrections, not smoothed away.
Successive drafts scoped the residue at five paths then nine; called the
Measure-end claim false, then misattributed, before landing on conditionally
false; conflated two distinct WallClock shapes whose clauses fail differently;
and scoped P13-S23 twice too narrowly, the second time excluding B5 -- comparable
pairs that yield no delta -- which is a third of what it is filed to own.
Baseline 1541/0 confirmed at f33673d. The commits since cc49533 are spikes/-only,
a separate workspace, so the root count is unmoved and the section 0 readings
stand.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
Five commits, all confined to spikes/editor-toolkit/. Built in a worktree
so the shared crates/ tree could stay mid-edit for the genesis track
without turning the spike gate red under review.
The pin-13 apparatus (694d135) and its three schema amendments were each
committed and user-reviewed before either candidate existed, so neither
candidate could shape what it would be measured against. e2979df lands
both candidates from one commit for the same reason.
C1 (egui + lyon) and C2 (vello) land together, from one commit, because
scoring either against an apparatus the other had already moved would not
be a comparison. Both were built against the pin-13 oracle frozen at
694d135 and its three schema amendments, all committed before either
candidate existed.
Both pass: Pass / Pass / NOT RUN / Pass / Pass, 80/80 hit-test probes each,
five live out-of-process AT-SPI readbacks each, no bus-unreachable
evidence claimed. Check 3 is NOT RUN by the 1.2 ruling, so both criterion
cells are NOT RUN and both candidates are eligible -- check 3 is not
disqualifying, and the checks that are (2 and 5) both pass.
Neither candidate re-shapes. Each takes glyph ids and offsets from the
resolved data and draws those glyphs at those positions; outline
extraction and path conversion are candidate-owned, the staff-to-device
transform is shared because re-implementing it would inject a divergence
check 1 is not measuring. D1 is 0 on all ten rasters, worst D2 0.14%
against a 2% tolerance, worst D4 1.22%. No tolerance was touched.
Round 1's binaries are byte-identical. Both candidates added Round 2 entry
points beside them rather than editing frozen evidence.
The cost measurement, which is why the packet exists:
ReportPart C1 C2
TextRendering 651 342
HitTestResolution 228 254
AccessibilityTreeConstruction 64 109
AccessibilityIntegrationWiring 62 199
FixtureAndReportPlumbing 1845 1925
Product-side accessibility -- tree construction plus integration wiring --
is 126 for C1 against 308 for C2: 2.4x, 182 lines. C1 inherits eframe's
AccessKit path and writes a single 62-line file to reach it; C2 inherits
nothing from vello and writes 199 lines of adapter lifecycle, event loop,
and bridge setup. C1 also writes *less* semantic-node code, 64 against 109.
That figure survived four reattributions, and the earlier ones were wrong
in ways worth recording so the next measurement is not made the same way:
- The first reading, 190 vs 30, was backwards. C1's tree row counted a
file that also held cosmetic glyph rendering; C2's excluded wiring that
belonged in it. Both errors pushed the same direction, which is exactly
why the number looked like a clean story.
- The second attribution put verifier subprocess orchestration under
AccessibilityIntegrationWiring, so C1's row grew 692 -> 1011 -> 1066
across two review rounds while measuring nothing but how much review its
harness attracted. That machinery is spike apparatus, and it is
FixtureAndReportPlumbing now.
- The mapping was disjoint before it was exhaustive: a 37-line file sat in
no part at all. Both candidates now assert every source file is claimed
by exactly one part and fail naming any that is not.
- C2 serialized two rows for one part while C1 serialized one. My own
comparison script summed them silently, which is how it survived a
review; it now refuses to aggregate and fails instead.
Three caveats belong with the number rather than under it. By non-comment
code the same comparison reads 55 vs 176, a 3.2x ratio -- same direction,
larger gap, because C1's files are proportionally more documentation; the
committed reports encode whole-file maintenance surface, so that is the
official figure and this is the sensitivity check. The dependency delta
points the other way: C1 carried accesskit at the Round 1 baseline and C2
carried none, yet both pull the same 16-17 AccessKit/AT-SPI/zbus crates
once a live tree exists, so inheritance saved code and not dependencies.
And roughly 1,900 lines per candidate is spike-only plumbing -- verifier
orchestration, evidence handling, report assembly -- that no real editor
would carry.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
Adding Registered = 16 made the inventory six fragments -- 24-29, 34, 35-38, 39,
16, and 1 -- but the repair sentence still said "superseding the five scattered
fragments rather than adding a sixth". The count was right when written and went
stale in the same commit that lengthened the list it counts, which is the entry's
own subject matter arriving one row early.
Ledger-only. No code, wire, or specification change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
Registered joins the semantic-lock inventory. Its corpus row is emitted under the
variant name at ops/src/vectors.rs:210 and its committed literal leads with 0x10
at spec/vectors/decode_vectors.txt:80, so the drift comparison binds the
association. The uncovered set narrows to 0, 2-15, 17-23, and 30-33.
Verifying that turned up a false clause of my own. The entry said the corpus's
tag coverage is unchosen and unmaintained. It is neither: vectors.rs:201-204
emits one row per tag straight from the vocabulary and states the reason -- a
hand-picked subset is how TransposeInterval shipped encoding to a byte its own
decoder rejected.
But the rows lock byte-to-byte, not variant-to-byte. Each is named
tag_{discriminant} and carries [discriminant], both derived from the value alone,
so tag_32 asserts that 0x20 round-trips and never that SetCanvasLayoutDefaults is
32. All forty rows are identical under a permutation; what moves is their order,
since PAYLOAD_FREE is declaration order. That is why the 32<->33 probe failed,
established from the committed file's ascending tag_NN rows rather than inferred.
Which sharpens the entry rather than weakening it: Registered's row has exactly
the property the numbered rows lack, because it is named for its variant. The gap
is that one row's discipline is not the vocabulary's.
Ledger-only. No code, wire, or specification change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
Three citations in P13-S15 had drifted when the golden-lock comment was added:
the table (:2220 -> :2233), the_tag_vocabulary_is_complete (:2569 -> :2652), and
phase3_tag_discriminants_are_golden (:2645 -> :2728). The tag residue moves out
of P13-S15's closing sentence and into an open row of its own -- a residue
recorded only inside a resolved entry is a residue that gets lost.
P13-S22 was drafted claiming a tag permutation is invisible, with 32<->33 as the
demonstration. Running it falsified the claim, so the entry says what was
observed instead. Three permutations, three catches: 32<->33 and 2<->3 by the
frozen decode-vector corpus, 1<->2 by layout-ir's edit-barrier golden blob. Each
restored by hand; suite back to 1541/0 with payload.rs byte-identical to dcb28f0.
So the gap is narrower and different from the draft. Semantic tag-to-byte locks
cover 24-29, 34, 35-38, 39, and -- incidentally, in a blob comment -- 1. Tags 0,
2-23, and 30-33 have none. What defends them is byte-level goldens that embed the
tag by accident and report a permutation as corpus drift or a moved blob, never
as a moved wire discriminant. The coverage is real but unchosen and unmaintained
as coverage, which is the hand-maintained-table failure mode wearing a costume.
The entry carries a probe-design note, because the obvious next probes are now
known to fail and a probe that fails proves the lock exists rather than that it
is missing.
Ledger-only. No code, wire, or specification change. No DECISIONS.md entry: this
is test-coverage bookkeeping, not a semantic ruling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
The OperationKind wire golden lock stopped at discriminant 29 while ten kinds
were appended past it -- TransposeInterval (30, Push 4a) through CreateMeasure
(39, G3b). Every one sat with no byte-level lock, and this is the one guard
written to catch exactly that class of stale hand-maintained table, so its own
staleness was the worst place for it. The table goes to 40 entries and locks
30-39 individually, each row asserting both that discriminant() has not moved
and that the byte leads the canonical encoding.
The mutation is the finding reproduced rather than argued for. Editing
discriminant()'s SetTuningContext arm 34 -> 44 fails the extended lock; with
that same mutation still applied, restricting the loop to &table[..30] -- the
exact pre-repair coverage -- passes. That is P13-S15, executed.
The sibling tag half needs no extension and did not get one.
the_tag_vocabulary_is_complete is derived, not hand-written: it computes the
bound from PAYLOAD_FREE's maximum instead of spelling it, so it already covers
30-39, and the vocabulary macro makes a tag without a discriminant a compile
error. One residue is stated in the ledger rather than papered over: density
plus round-trip does not pin which tag holds which byte, so a permutation
inside the dense range survives both tag tests. The same permutation on the
kind side is now caught. Closing the tag-side permutation gap is a separate
question and is not part of this rung.
No wire, schema-version, or specification change: this adds a guard over
assignments that were already normative. P13-S18 and P13-S19 remain open by
design.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
PreconditionFailureReason 14 (AcousticRealizationPinned) and 15
(TranspositionOutOfRange) entered the vocabulary at Push 4a and reached this
document at neither place that owed them. The bounded enumeration ran 13 straight
to G3b's 16, and Push 4a's own history row recorded only OperationKind 30 while
saying nothing about the two reasons it appended in the same epoch. The Operation
Catalog documented both at its 0.8.0 and effect.rs has carried both throughout;
only the wire specification was silent.
This is P13-S20's specification-side twin, and it is why that decoder could stop
at 13 unchallenged: an implementer reading only the wire specification would have
built exactly that decoder and been right. No version bump and no new history
row -- this records an assignment normative since Push 4a rather than making one.
The regression test checks both sites, each bounded to its own longtable row,
because either alone is satisfiable by the wrong thing: the G3b row and the
enumeration both discuss PreconditionFailureReason at length, and an unbounded
search would go green the moment any row mentioned the names. Each half was
observed red alone while the other stayed green. The Push 4a row is located by a
version-free separator marker, per the file's standing rule against encoding a
document version number anywhere in it.
The epoch-12 evidence hash is e64a4b7, not this rung's parent. G3b landed across
six commits, and the chain records introducing commits -- the commit where
kind/tag 39 enters payload.rs and reasons 16-18 enter effect.rs -- exactly the
distinction the 2026-07-28 correction draws between 7df5ca1 and 55eff00 for G2a.
d58eee8 completes the rung and introduces no discriminant; both are named, with
their roles stated, in the plan and in the contract's pin 15.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
Kind 39 CreateMeasure and precondition reasons 16-18 reach the Binary Format's
kind table, tag table, payload layout, and reason table; graph invariant 20
reaches the core specification's enumeration, which now says twenty in all three
places it counts. Both normative listings in that document gain CreateMeasure --
earlier drafts of the contract named only the invariant, and prose fails silently.
Every version surface moves in pairs. Binary Format 0.15.0 -> 0.16.0, Operation
Catalog 0.12.0 -> 0.13.0, each with a changelog entry beside the title bump. The
Text Projection companion needed only the changelog: packet 1 bumped its header to
0.13.0 and stopped there, leaving the document claiming a version its own history
did not record. That was live from e64a4b7 until now, and no gate could see it.
Two public hooks exist that would otherwise look like leaks. epiphany-ops depends
on epiphany-core and never the reverse, so invariant 20 implements pin 6/6b's
comparable relation and musical delta a second time over the graph alone. Both
DECISIONS records name the divergence hazard that forces the duplication, name the
cross-crate agreement test as the hooks' only sanctioned use, and say so from each
side.
The monotonicity evidence chain gains only vocabulary-introducing events -- G2b
13c3d2f, G3a 6c5e69f, G3b -- and excludes G-minor and P13-S17 with the reason
stated: neither introduced an additive variant. The 2026-07-29 tie between G2b and
G3a is broken by ancestry, not timestamp.
P13-S18 (invariant 20's abstention residue) and P13-S19 (the pickup deferral) are
filed open by design. P13-S20 is recorded RESOLVED.
The genesis ladder G1 -> G2a -> G-minor -> G2b -> G3a -> G3b is CLOSED.
Executed against spec/CONTRACT_GENESIS_G3B_MEASURE.md rows 14a and 26-36,
mutation M71, which is now the contract's own guard: deleting the G3b Revision
History row fails the history test even though "genesis tranche G3b" still appears
twice in neighbouring prose.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
Appends to 775d7ba, which is left untouched.
That amendment put the non-empty check in a constructor and in Deserialize,
and left `Inherited { provider: String }` public. So the struct-literal path
bypassed both -- and the very first caller took it: C1's adapter rows were
written as `IntegrationOwnership::Inherited { provider: "...".to_string() }`,
never touching the constructor that was supposed to be guarding them. A
checked constructor sitting beside a public field is a suggestion. The field
is the API.
Provider is now a newtype whose inner String is private, so every route in
-- Provider::new, Deserialize -- runs the same check, and an empty provider
cannot be constructed on any path outside this module. The compiler proved
it during the change: converting the field turned my own struct-literal
test sites into type errors, which is exactly the class of site that had
been slipping through. I also compiled a deliberate bypass from outside the
module to confirm it is rejected rather than assume it:
error[E0308]: mismatched types
provider: String::new(),
^^^^^^^^^^^^^ expected `Provider`, found `String`
and reverted that probe byte-identically.
Two new tests beyond the constructor and wire cases already present. One
pins the serialization consequence a type error cannot express: every
Provider that exists has been checked, so no serialized row can carry an
empty one. The other round-trips a whole adapter row and asserts the
provider survives intact, since the shape a real report carries this in is
the row, not the enum alone.
C1's two construction sites are converted to the checked constructor. No
report values change -- both candidates already named real providers.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
Appends to d76287c, which is left untouched.
That amendment justified IntegrationOwnership::Inherited's provider field
as a claim "a reader can check against the dependency graph rather than
take on trust" -- and then accepted "" and " " for it, in Rust and
through serde alike. An unnamed provider is not a weaker claim of
inheritance. It is the same claim with its evidence removed, and it renders
as Inherited in every table it reaches.
Fixed the way this crate already fixed the identical hole in CheckOutcome's
reason strings: a checked constructor, plus a hand-written Deserialize over
a private wire shadow so the JSON path runs the same check. A hand-edited
report is exactly where an unnamed provider would arrive from, so guarding
only the Rust constructor would have guarded the path nobody takes.
Seven tests. Six fail when the check is reverted to `if false`, verified by
doing it and restoring: the constructor on empty and on whitespace-only,
the deserialize path on both, the same empty provider nested inside a whole
adapter row (the shape a real report carries it in, not just the enum
alone), and the accepting case for a real provider. CandidateOwned keeps
deserializing unchanged.
No candidate report changes: both candidates already name real providers,
so this closes a hole rather than correcting a value.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
Append-only amendment to 694d135. The oracle, the verifier, the scoring
rule, the PASS results and eligibility are all untouched; this changes only
what a cost row is able to say.
The first pair of real candidate reports proved the schema could not carry
what Packet 2B was chartered to record. Both candidates reached AT-SPI
through AccessKit -- C1 via the integration bundled with eframe, C2 via
wiring it wrote by hand -- and they reported that as opposite rows:
C1: NotBuilt accesskit-0.24 "no separate AccessKit-native readback was built"
C2: Implemented accesskit-0.24 "the in-process tree this binary constructs by hand"
C1's row was false as written. The AccessKit path was present and
exercised; every one of its five check-5 readbacks travelled it. NotBuilt
means uncovered scope, and that scope was covered.
Relabelling it Implemented would have fixed the falsehood and still lost
the measurement, because "inherited or candidate-owned?" would have
survived only as prose in notes -- which is precisely how the two
candidates diverged in the first place. So AdapterStatus::Implemented now
requires a typed integration_ownership: Inherited { provider } or
CandidateOwned. Inherited names its provider so the claim can be checked
against the dependency graph instead of taken on trust. NotBuilt keeps no
ownership field at all: a platform reached by any route is Implemented.
Inheriting an integration is not inheriting the semantics drawn on top of
it. A candidate that inherits a bridge still writes the accessible nodes
for whatever it painted itself, and that work is counted under
ReportPart::AccessibilityTreeConstruction, not here -- which is what makes
the two rows comparable rather than a single "has accessibility" flag.
Five tests, each of which fails if its guard is removed: the two-candidate
comparison the old schema could not express, Inherited carrying a
checkable provider, an Implemented row missing integration_ownership
refused at deserialize, NotBuilt refusing an ownership field, and a
round trip that must not alter a row.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
Undoing a CreateMeasure removes it from the owning StaffInstance, reached
through measure_values' carried parent rather than a search. The strand guard
covers seven surface classes, not the two earlier drafts assumed: spanner,
repeat, another measure's start, meter change, system break, page break, and
tempo segment -- the last at both its start and its optional end anchor.
It cannot use the structures index, which is event-only by construction and
would make the guard born green; rewriting it that way breaks fifteen tests.
RepeatStructure is not a CrossCuttingValue variant, so the modify chain
structurally cannot hold one and a repeat_values ledger carries it instead --
seven sites, no delete site. Deleted repeats stay in the map, matching the
ratified retention discipline and making the tombstoned-referencer row
reachable at all.
Owner rules are per surface. Six classes always have one; tempo segments split
by key, since a score-level segment has no owning object to test for liveness.
The five WriteChain surfaces are restoration-aware in both directions, and the
shared path now normalizes a value that does not reference the target to
absent. That is behaviour-preserving -- the guard only ever asks whether a
value names this target -- and it is what lets the two directions fail
independently: spanner chains have no explicit-removal form at all, and a
removed break is still Some((anchor, false)).
Also closes a hole that predates G3b: a minted TimeSignature still named by a
live Measure.time_signature now blocks its own undo.
Executed against spec/CONTRACT_GENESIS_G3B_MEASURE.md pin 10, mutations M48-M63.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
Pin 13 requires an oracle committed and reviewed before any candidate is
scored against it. Both halves land here, before C1 or C2 exists, so
neither candidate can shape what it will be measured by.
round2-candidatekit is the only code the two candidates share. It loads
and validates Packet 2A's fixtures, probe table, reference rasters and
regions; it renders nothing, resolves no hit test, and builds no
accessibility node -- a dependency deny-list test reads its own manifest
at test time to keep it that way, including the dotted-subtable and
target-cfg forms a line scanner would otherwise miss. Check 3 is pinned
NOT RUN by the recipe's 1.2 ruling: a report claiming Pass or Fail for it
is refused, and the supplementary F-D field is one the scoring function
never reads, so it cannot reach the criterion cell by construction rather
than by convention. A check-5 NOT RUN now requires typed evidence that the
platform bus was unreachable, because Round 0 already proved it reachable
for both candidates -- "we did not build it" is scope, and scope is not
environmental.
round2-a11y-oracle derives every byte string a live AT-SPI readback must
compare against, from the digest-validated fixtures alone: the expected
name three ways, the at-spi2 role sets, a precommitted alternative form
per prohibited outcome, the visual-order form, and the per-segment source
atoms. The fixtures digest is unchanged at acc13c0d, so Packet 2A stays
frozen.
verify.py gains a check-5 mode whose classifier is a pure function, unit
tested without a bus, and which fails closed on platform, digest, fixture
completeness, the name/hex/length triple, atom concatenation, and any
string that would classify as two different outcomes.
The scoring policy, settled over eight review rounds and worth stating
because most of it was won by finding the opposite in the code first:
- An accepted-role exact match wins from anywhere in the forest. It used
to be first-match, so tree order decided a disqualifying check.
- Composition is scoped to one run subtree and outranks any per-node
diagnosis. Application-wide concatenation both passed a run half-drawn
as a canvas and failed a correct split run because the window carried
a label.
- Contributor order stays significant, since 8.1 requires logical order;
only non-contributor order is required not to matter.
- name-empty means an attempted static-text exposure without a name.
absent-from-tree means a drawing-only or unrelated tree, and nothing
else -- it is the outcome 8.3 says this check will most likely catch,
and it was nearly unreachable while gated on role presence.
- A byte-exact PASS outranks an alternative-form match. F-C's
"drops unresolved codepoints" form is its own first segment, so a
legitimate per-run split was being failed on a check that eliminates.
That collision is structural for any two-segment fixture with one
unresolved segment, not an F-C accident.
Recorded as a finding, not fixed: the recipe's claim that visual-order
assembly diverges for F-D "and only there" is false -- F-B diverges too,
and any non-palindromic RTL run of two or more graphemes will. F-D's real
distinguishing property is an interior RTL run. The same stale wording is
compiled into fixtures.json and covered by its frozen digest, so
correcting it would move acc13c0d out from under Packet 2A.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
Invariant 20 checks measure/meter agreement and boundary consistency, and
nothing else: invariant 10 already checks that a measure's signature reference
resolves, so 20 never re-checks it. A None signature avoids only the agreement
clause -- inherited meter still governs distance. Where agreement or delta is
not computable the invariant ABSTAINS rather than failing closed, the deliberate
opposite of create_measure, because base-ingested data may predate the rule.
Pickup first measures are exempt.
epiphany-ops depends on epiphany-core and never the reverse, so invariant 20
cannot reuse packet 1's reducer predicates and implements the comparable
relation and delta a second time over the graph alone. Two implementations of
one normative relation is a divergence hazard, so a cross-crate agreement test
drives a table of anchor pairs through both and a one-sided perturbation signs
it.
SetMetricGrid and SetTimeSignature now refuse writes that would break either
clause for a live measure, and every check precedes the mint -- set_time_signature
previously minted its carried signature before writing the chain, so a refusal
appended afterward would leak a TimeSignature from a non-transactional operation
with no undo to reclaim it.
Undo restoration safety is evaluated in aggregate, because individually-unsafe
restorations can be jointly safe and the reverse. StrictInverse conflicts on the
whole set; BestEffort applies the maximal safe subset under a documented
canonical-order greedy. These are the first callers to pass overlapping
overrides into the grid oracle, so they meet packet 1's tie-break for real.
Both aggregate paths are signed end-to-end as well as by unit test: the measures
are created after the transaction commits, so nothing disagrees on the forward
path and only the restoration of the older grid conflicts. Deleting either call
site was previously invisible to the whole suite.
Executed against spec/CONTRACT_GENESIS_G3B_MEASURE.md, mutations M34-M47.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
The aggregate tie test added in b92023a never ran its own transaction. Envelope
counters jumped 3 to 10 to 11 to 12 to 20 while asserting seen_r1 continuity
across the gaps, so compute_pending's missing-vector-predecessor rule held both
transaction members and the undo permanently pending. The value it compared
against was the leftover pre-transaction baseline, not a restored one. It went
red under mutation only through its synthetic prediction half.
The tie-break was also too narrow. undo_transaction records the whole-grid and
per-key restorations under the same undo env.id, so after the undo commits both
chains carry Recency::Write with an identical stamp tuple. A prospective-only
tie-break left that real tie resolving to the whole grid while the graph held
the per-key value -- reachable today, since create_measure queries the oracle
with no overrides. Per-key now governs on any equal non-Base recency;
Base/Base still keeps whole-grid, because there the per-key chain has no write
and the overlay would delete the whole grid's entry.
The rebuilt fixture runs a contiguous 14-op sequence with the full prerequisite
chain and pre-mints every signature a SetMetricGrid names, since set_metric_grid
rejects a grid referencing an undeclared one. It asserts every op is exactly
Applied, that the effect count equals the accepted envelope count, and that no
conflicts or anomalies exist -- so a dropped op, a failed prerequisite, a
conflicted member or a skipped undo fails loudly instead of passing quietly.
Two proofs, deliberately distinct: the prospective tie via the aggregate
prediction, and the real-write tie via a CreateMeasure after the undo. Only the
second can see the widened rule.
Executed against spec/CONTRACT_GENESIS_G3B_MEASURE.md pins 6c, 7, 9.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
Base-free c3 ordering was unsound three ways: it never checked that the two
measures share a StaffInstance, never checked they are live, and ordered by
OperationId, whose Ord is (replica, counter) authoring identity rather than
canonical reduction order. It now reads the parent from measure_values,
requires both Live, and compares the minters' stamp reduction tuples -- the
same tuple chain_recency uses. The old test discarded the parent, so it
encoded the bug.
An empty effective grid refused instead of abstaining. Pin 6c case 1 makes a
missing active signature vacuous for agreement and abstaining for boundary
distance, but agreement grouped GoverningElement::None with a disagreeing
Unique, and the boundary clause collapsed None into Indeterminate and then
failed closed. None and Indeterminate are now separate outcomes. The
white-box selector test passed throughout, so the new coverage is end-to-end.
Simultaneous prospective whole-grid and per-key restorations had no tie-break:
Recency::Prospective is a unit variant, so both compared equal and the strict
> handed it to the whole grid -- while real restoration records the meter
change after the whole grid under the same undo operation. The per-key write
now governs on equal recency, scoped to the prospective family only; a plain
>= would delete the whole grid's entry when both sides are Base.
The aggregate test asserts the oracle against the materialized post-undo
graph, not against itself.
Executed against spec/CONTRACT_GENESIS_G3B_MEASURE.md pins 6, 6c, 7, 9.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
Kind and tag 39 at schema-minor epoch 12, in both unaligned discriminant
spaces. Measure is schema major 0 -- struct_codec! is a plain walk and
TimeAnchor's Codec has no version branching -- so schema_major() gains no arm
and OperationEnvelopeBlock stays at 3.
CreateMeasure is a nested-container mint following CreateStaffInstance, not
G3a's root-level shape: it carries the owning StaffInstanceId beside the
value, and measure_values carries that parent because Measure has no
back-pointer and the graph-removal arm will need it. Append-only, with
referential preconditions on the parent, the signature, and every non-wall-clock
start referent.
The comparable relation is five exact shapes with an identical boundary
selector; ordering across Start/End is unsound while measure length is
unresolved, so it stays unverifiable. Boundary distance needs a musical delta,
which only same-referent same-selector Musical offsets supply. The effective-grid
oracle reconstructs inheritance from metric_grid_chain and meter_change_chain by
write recency -- not by always overlaying per-key on whole-grid -- folds in
prospective overrides, and runs identically in both reduction modes, so an
instance_grid ledger keeps base-free reduction honest.
Three precondition reasons at 16-18. Repairs a live bug found in review: the
materialized-effect decoder stopped at 13, so reasons 14 and 15 already encoded
without decoding, and the generator drawing below(14) could not see them.
Executed against spec/CONTRACT_GENESIS_G3B_MEASURE.md, mutations M1-M33,
M64-M66, M67-M70. text_projection.tex moved into this packet: four tests read
it live, so the companion bump cannot be split from the grammar.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
Packet 2A closed with an open question its criterion cannot answer. Check 5
(accessibility) is disqualifying and scored PASS/FAIL, and the recipe pins it
per platform so it does not encode one candidate's stack — but a neutral
criterion does not make the cost neutral. Two candidates can both read PASS
having paid different prices, and the cell has nowhere to say so.
Ruled 2026-07-29: check 5 stays candidate-neutral PASS/FAIL and is not
adjusted for who had further to walk; Packet 2B measures the cost on a
separate row; the final ruling weighs that cost without retroactively
relabelling a PASS.
My first draft of this entry stated the asymmetry as "C1 inherits an
accessibility tree, C2 must build one", and that overstates both sides. C1
inherits AccessKit and its platform bridge, but NOT correct semantic nodes for
custom-rendered text — a score canvas draws glyphs egui knows nothing about,
so the nodes carrying the source string, role and bounds are C1's to create
either way. Inheriting the bridge is not inheriting the semantics. C2 inherits
no accessibility integration from vello, but is free to integrate AccessKit or
another bridge, so it does not face a from-scratch stack; what it owns is the
additional integration and wiring. The delta is real, narrower than
"has it / doesn't have it", and concentrated in wiring rather than in the
semantic node construction both candidates must do regardless.
So 2B reports observed facts at that granularity — inherited vs candidate-owned
per part of the path, dependencies added over the Round 1 baseline, which
platform adapters are actually implemented (an adapter not built is scope not
covered, and is reported as such rather than absorbed into a PASS), the
integration each candidate writes itself, and the resulting maintenance
surface. It does not presume the magnitude: writing down a guess at the gap
and then confirming it is the same failure as choosing a tolerance after
seeing a candidate's output.
The general rule, for later rungs: an eligibility gate answers "may this
candidate proceed", not "what will this candidate cost". Merging the two into
one cell loses the second, and the second is the one the project lives with
after the ruling.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
The Revision History chapter ran G2a straight to G-minor to G3a, with no row
for genesis tranche G2b anywhere -- so the accept-set raise
OperationEnvelopeBlock 2 to 3, the first accept-set move since G2a explicitly
recorded staying at 2, reached the normative tables and never the history.
G2b's own contract required that row; 13c3d2f edited 99 lines of
binary_format.tex and added none of it.
The stack is unpublished, so the chronology is restored rather than patched:
G2b lands as its own row between G-minor and G3a, G3a renumbers up, and the
PDF is regenerated.
The new epiphany-testkit guard makes recurrence detectable. Bare name-presence
would not have: with the G2b row deleted, "G2b" still occurs inside the chapter
in G3a's prose, so a substring guard would have been born green. The guard
requires a principal marker -- the rung name preceded by the row's separator --
strictly ordered across the four standalone-row rungs G2a, G-minor, G2b, G3a,
with G2b's content anchored inside its own row segment so G3a's row cannot
satisfy it. G1 is deliberately unguarded: it has no standalone row, being
recorded retroactively inside G2a's. No document version number appears in the
test, in its comments, or in this message.
Executed against spec/CONTRACT_GENESIS_G3A_UNDO_REPAIR.md Packet B.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
Four crates, none of which knows anything about a candidate. This is the
apparatus Round 2 will judge with, built and mutation-tested before either
candidate exists — the role Round 1's oracle tranche played.
round2-textkit — the W3 3E stand-in. Resolves two declared faces from an
explicit path list (pin 9), shapes and itemizes five fixtures with
rustybuzz/unicode-bidi/unicode-segmentation, asserts all five W3 section 5
invariants, and emits fixtures.json + an 80-probe hit-test table (none
dropped).
round2-svgref — pin 10's explicit-glyph SVG emitter. Never emits <text>, and
asserts it: a <text> element would let the viewer's shaper pick glyphs and
reintroduce exactly the re-shaping this round exists to forbid.
round2-diff — the bounded visual differential, D1-D4, zero dependencies.
round2-reference — composes the three into reference rasters and D4 regions,
and executes the text mutations M4/M5/M6 against the real frozen fixtures.
Defects found by testing rather than by reading, and what each cost:
* The stand-in had its own quantizer. `(v*1024).round()/1024` is
ties-away-from-zero; QuantizedCoord is ties-to-even (Appendix D). W3
section 5 says text quantization "is not a second convention", and naming
the divergence in a doc comment is not the same as being allowed to take
it. Now routed through QuantizedCoord, with four half-grid tests at +/-0.5
and +/-2.5 units chosen because the conventions DISAGREE there, plus one
recording a tie where they agree. is_on_grid claimed exactness while
accepting anything within 1e-6; it is now an exact round-trip.
* The validator accepted any self-consistent file. Named checks say nothing
about individual glyph ids or quantized offsets — tens of thousands of
numbers a candidate will be scored against. A whole-artifact SHA-256
closes it; four tests fail without it.
* stable_id: u128 did not survive JSON. Measured,
82875741697311382809239399464544864365 came back as 8.287574169731139e+37.
A provenance id that changes when a tool reads and rewrites a file is not
an identity. Now a decimal string (W3-F5).
* SpikeProvenance was lossy while claiming to mirror every 3E field: a Debug
rendering for `source`, a length for `dependencies`. It lost nothing
measurable only because these fixtures' dependency lists are empty, which
is an accident of the fixtures. Now canonical_bytes under W3's own field
names (W3-F6).
* A cross-crate string-slice: the reference composer extracted <path>
fragments by searching for another crate's markers. It worked, and a
formatting change there would have broken it silently. Now an API call, so
a rename is a build failure. Verified byte-identical across the refactor.
* Two harnesses printed their own bad news and exited 0.
M4/M5/M6 measured: M4 kills D1 at 221 px (D4 blind at 1.20%); M5 kills D4 at
13.28%/13.19% on the two composed-acute regions; M6's emitter refusal fires
on U+05D0 before any raster, and forced past it D4 reads 95.37/71.26/34.69%.
F-D is labelled SUPPLEMENTARY in code, in fixtures.json, in the summary, and
in every generator's output, with check 3 NOT RUN — enforced by the
validator, because a ruling recorded only in prose loses to whichever record
the reader opens.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
Packet 2A's governing artifact, under CONTRACT_EDITOR_T4_SPIKE pin 13 —
written and reviewed before any candidate can consume it, because a
tolerance chosen after seeing a candidate's output is not a tolerance.
Ruling A permits "geometry/scene equivalence plus a bounded visual
differential under a controlled backend, NOT pixel equality". That phrase
had never been given a number. Section 10 gives it four rules; section 11
lists the mutations that must kill, and every one is now executed by a
binary that exits non-zero when a required kill does not happen.
Three things here were corrected by measurement, not by review:
* The em size. Revision 1 pinned 64 px, which puts Pagella's stems at
5.4 device px — inside D1's structural blind spot of 2*EDGE_BAND_PX+1 = 5
px. D1 would have reported `pass` on every stem in the fixture set. The em
size is now derived FROM the blind spot: 128 px, stems 10.8 px.
* M4. Assigned to D4 by analogy with a dropped glyph; executing it showed
the analogy is false. An `ff` ligature and two `f` glyphs carry nearly the
same ink (0.07% of image mass, 1.20% in-region against a 2% tolerance), so
a mass rule is the wrong instrument for a shape substitution. D1 sees it
at 221 px outside the band. D4 keeps its threshold: tightening it to 1.20%
would leave 1.3x over M10's measured 0.785%, and a threshold that close to
a known-good AA variant is not a tolerance. D1 and D4 are complementary
and neither is sufficient.
* The origin. The 1/1024 invariant caught this recipe's own stated constant:
1.6 * 1024 = 1638.4 is not representable. Now 1638/1024.
Section 8 pins the check-5 accessibility oracle per platform rather than in
one toolkit's vocabulary — naming only AccessKit would have favoured C1, and
a criterion that encodes one candidate's stack is not a criterion.
Section 1.2 records the 2026-07-29 ruling: check 3 is NOT RUN (no
Arabic-capable face; pin 9), F-D is a separate Supplementary row that must
not upgrade it, and eligibility is unaffected because check 3 is not
disqualifying.
Section 12 routes six findings back to the W3 amendment, three of them found
by building against 3E rather than by reading it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
Undoing a transaction that minted a StaffGroup, PartDefinition,
AnalysisLayer, View, or Instrument tombstoned the object in the ledger and
left its value live in the Score. DECISIONS.md claimed ObjectState::Tombstoned
was unreachable through the public operation API; UndoTransaction over the
minting transaction reaches it with no delete operation required, and did so
for CreateStaff and CreateInstrument before G3a existed.
Adds five graph-removal arms to materialize_graph_tombstones and three
inbound-reference guards to undo_strand_block: StaffGroup blocked by a live
Staff.group, AnalysisLayer by a live ViewDefinition.active_layers, Instrument
by a live Staff.instrument. The guards read the carried-value maps rather than
the graph and are deliberately ungated, so base-free undo cannot strand a
reference the ledger can see; they require the referencer to be Live, and
exempt referencers minted in the same transaction. No restorations lookup:
none of those three fields has a modify operation, so there is no write chain.
Twenty-two mutation-signed rows across six families — removal, live-referencer
refusal, base-free guard behaviour, tombstoned-referencer permission,
same-transaction teardown, and objects-outranks-retained-map on re-create.
Executed against spec/CONTRACT_GENESIS_G3A_UNDO_REPAIR.md Packet A.
Records G3a's commit and corrects the kind/tag pair count to fifteen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
CreateStaffGroup (35), CreatePartDefinition (36), CreateAnalysisLayer (37),
CreateView (38) -- all schema major 0, all schema-minor epoch 11, on
CreateStaff's set-union mint pattern with byte-identical re-carry
idempotence. Completes the four remaining root-level Score entity vectors.
No epiphany-bundle change of any kind: schema_major() gained NO arm (all
four fall through the catch-all _ => 0), so the op-block accept-set stays at
3 where G2b left it. The OperationKind golden lock stays at [(OperationKind,
u8); 30] -- kinds 35-38 remain outside it by design, per P13-S15's own rung.
Closes the satisfiability half of the from-empty defect: every G3 object
kind previously became Live only through base ingest, so CreateStaff's group
precondition was unsatisfiable from empty. It does NOT make a consistent
group authorable -- §1.1 disposition B rules Staff.group the sole authority
and StaffGroup.members a non-authoritative projection G3a stores without
maintaining, with both stale forms permitted. That ruling now reaches all
four normative sites: both field doc comments (neither field was documented
before), core_spec.tex, and both the new CreateStaffGroup and the existing
CreateStaff sections of operation_catalog.tex.
Carried-value maps for all four families thread through seven sites each,
including the base seed that fails silently -- a re-carry test reducing only
from empty cannot see a missing seed, which is why t5b reduces onto a
populated base.
Verified independently, not relayed. Full gate re-run: 1429 tests / 0
failed, clippy 0, fmt clean, git diff --check clean. Two mutations the agent
had not observed were run here and observed: reverting COMPANION_VERSION to
0.11.0 kills t11 (the agent had only reasoned this), and skipping the
staff_group_values base seed kills t5b. Both reversed by editing back, with
the baseline green.
Two findings recorded rather than silently absorbed:
- valuegen::analysis_layer's name format was accidentally exactly 16 bytes,
the same width as AnalysisLayerId's encoding, which made a field-swap
mutation on the two-field AnalysisLayer struct byte-invisible. Shortened
and documented -- a fixture that makes a mutation undetectable is the same
failure class as a guard that cannot fail.
- binary_format.tex has NO Revision History row for G2b. G2b's own contract
required one (touch row 27: "version, Revision History row"); it changed
the payload-layout row, tag row, accept-set text and §2373 history but
never bumped the version or added the row. The document went 0.12.0 (G2a)
-> 0.13.0 (G-minor) -> 0.14.0 (G3a) with the accept-set raise unrecorded.
G3a adds its own row without retroactively backfilling G2b's.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
1. The ruling had no normative/API surface. §1.1 assigns authority, but Pin 3
still said "nothing else in core" and the touch table listed graph.rs under
"Not touched" -- leaving the contract and P13-S16 as the only carriers of
supposedly normative meaning while core_spec.tex remained ambiguous. A
normative ruling that lives only in a contract is not normative.
Added pin 4b requiring all four sites, and narrowed pin 3 to the codec
surface ("no other CODE change in core"):
- graph.rs:819 Staff.group -- sole authority. Note this field has NO doc
comment today; it must be written, not amended.
- graph.rs:1614 StaffGroup.members -- non-authoritative projection, must
not be read for membership, may be stale in both directions. Also
currently undocumented.
- core_spec.tex at the Staff/StaffGroup declarations -- the same rule
normatively. This is the document that declared both fields without
ranking them, so it is where the ambiguity actually lives.
- operation_catalog.tex -- explicit stale-form semantics in the new
CreateStaffGroup section AND the existing CreateStaff section (:1104).
CreateStaff needs it because it is the operation that creates the
missing form, and its section currently promises nothing.
Each cross-references P13-S16. graph.rs moves into the touch table
(documentation only; no type changes anywhere in core). Added t14 to guard
the two field doc comments, sliced per-field on t12's discipline since a
file-wide grep cannot fail here.
2. t8b's two mutations were assigned to the reducer that runs FIRST in each
order, so neither could reach the state its assertion names:
- Missing form (CreateStaffGroup then CreateStaff): create_staff_group
cannot append a staff that does not exist yet. Mutation moved to
create_staff -- append the newly minted staff to g.members.
- Spurious form (CreateStaff then CreateStaffGroup): create_staff has no
later group to repair. Mutation moved to create_staff_group -- reject or
normalize away the non-empty carried members.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
Ruling, ratified 2026-07-29:
- Staff.group is the SOLE authority for membership.
- StaffGroup.members is a non-authoritative denormalized projection.
- G3a stores it but neither maintains nor trusts it.
- BOTH stale forms are permitted, equally: a missing member (s.group ==
Some(g) while g.members omits s) and a spurious member (g.members contains
s while s.group is None or names a different group). The earlier draft
named only the first, which left the spurious form reading as a bug rather
than a permitted state.
Withdraws the earlier draft's argument for B. "B adds no semantics while A
does" was wrong: B assigns authority to a field the specification left
unranked, which IS a semantic change. What B defers is enforcement, not
meaning. The honest advantage is narrower -- B adds semantics without adding
machinery, leaving the mint a mint.
Files P13-S16, which makes the contract's "filed gap" claim true; it was
false when written, since no such entry existed. The entry records both
stale forms, the disposition-A fix, candidate invariant 21, the re-carry
comparison question A must answer, and the standing instruction that
consumers read Staff.group and never StaffGroup.members for membership.
Remaining repairs:
- PLAN_GENESIS_OPS.md still claimed G3a "closes the staff-group half".
Narrowed to satisfiability, matching the contract.
- Boundary accounting normalized to six crossings across both documents: one
exhaustive-match site plus five literal/prose sentinels. The two classes
are counted together but named apart because they fail differently -- the
match site refuses to compile, while every sentinel stays green while
meaning something narrower than it says.
- t4 now runs four independent mutations, one per struct_codec! layout;
these are four separate layouts and one mutation signs one of them.
Collapsing is permitted only if the implementation consolidates them
behind a shared mechanism, and must be stated if it does.
- t8 split: t8 asserts satisfiability only; new t8b pins BOTH asymmetric
authoring orders as states the ruling permits, with a mutation each. A
test pinning one order leaves the other free to change silently.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
Five review findings, all confirmed against the tree before repair.
1. StaffGroup/Staff authorship cycle. CreateStaff requires its group live
(reduce.rs:4117); CreateStaffGroup requires its members live. With mints
only, NO authoring order yields a bidirectionally consistent group, and the
inconsistency is unrepairable in-packet -- there is no ModifyStaffGroup and
deletes are deferred. Invariant 10 checks resolution in both directions
independently (invariants.rs:1126, :1135) and agreement in neither; the
spec declares both fields (core_spec.tex:4231, :5578) without saying which
is authoritative. Added as §1.1, UNRESOLVED, with two dispositions and a
recommendation -- the shape G2b's accidental_extensions pin took. t8's
claim is narrowed accordingly: what closes is that CreateStaff's group
precondition becomes satisfiable, not that a consistent group becomes
authorable. The unqualified "defect closes" framing was wrong.
2. Pin 4 promised byte-identical re-carry without the machinery that makes it
work. Comparing "the same value" requires retaining it, and the object map
holds only Live/Tombstoned. Exactly three carried-value maps exist
(reduce.rs:999-1007), each threaded through seven sites; G3a needs four
more, so 28 touch points the first draft omitted entirely. Added pin 4a
with the seven sites enumerated. Site 4, base seeding, is the one that
fails silently: a re-carry test that only reduces from empty cannot see a
missing seed at all. Added t5b (base re-carry) and t13 (each of the four
seeds killed separately), modelled on G1's documented instrument_values
precedent at reduce.rs:13264.
3. The companion bump was stale. The tree is at 0.11.0 (G2b bumped it,
textproj/src/lib.rs:47); the contract repeated G2b's 0.10.0 -> 0.11.0.
Corrected to 0.11.0 -> 0.12.0 at both sites, with the negative vector
rejecting 0.11.0.
4. Mutation table repairs:
- t4's mutation was impossible. A create op's encode_canonical is a single
push_lp_bytes line over one carried value, so there is nothing in the op
to reorder. Moved to the carried type's struct_codec! declaration
(codec.rs:1790), which is where the layout actually lives and is
self-consistent across both halves -- exactly trap 4's shape.
- t6 omitted PartDefinition.staves. Now three independent mutations, one
per referential loop; an uncovered loop is a loop that deletes green.
- t9 moved fixture and production together, which proves only that a bad
score fails a checker. Fixture now constant and already dangling; only
production mutates, one skipped check at a time.
- t12 could not fail: grepping the whole file matches the implementation
body, which contains the identifiers the doc comment is meant to gain.
Now slices the invariant-10 doc block and searches only that.
5. Touch table missed testkit/src/layout_stub.rs:1373, whose 30..=34 range
restates the coverage claim in prose. Added there and to the plan's trap 6,
now five boundary-crossing literals rather than four.
Editor authorization recorded as granted narrowly: barriers.rs for the four
exhaustive subjects_of arms only, barrier.rs for the invalid-tag literal,
comment, and assertions 35 -> 39 only. No other change in either crate.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
Folds the three open PLAN_GENESIS_OPS.md §6 rulings, ratified 2026-07-29:
1. Deletes: mints only, all five deferred out of G3. The former "live
dependents -> container-not-empty" sentence is superseded outright --
ContainerNotEmpty concerns owned children (effect.rs:156), while the G3
hazard is dangling inbound references from independently-live objects,
which would need a new typed reason and its own epoch.
2. decomposition_attachments: derived, not authored.
3. Measure.time_signature: resolution-plus-agreement. An optional explicit
declaration at the measure start, neither an override of the metric grid
nor a cache of it. Invariant 20 (G3b) covers agreement and boundary
consistency only, not reference resolution.
G3 splits: G3a (four root-level mints, kinds/tags 35-38, epoch 11, all
schema major 0) and G3b (CreateMeasure, kind/tag 39, epoch 12, carrying
invariant 20 and a new precondition reason at discriminant 16).
Correction of record: an earlier scoping claimed invariant 10 "covers
cross-cutting refs, not this". False -- read off the variant's doc comment
rather than the check body. Invariant 10 already resolves a staff's group, a
group's members, a part's staves, a view's active layers, and measure and
grid time-signature references (invariants.rs:1122-1156, :1180-1212), tested
at :3596. Its doc comment names none of them, so G3a owes a prose
reconciliation and no enum entry.
Also corrects five drifted citations that a touch table exists to prevent:
reduce.rs 3850->4075 and 2342->2559, barriers.rs 437->313, barrier.rs
1105->1156, text_projection_grammar.rs 307->315; and "five siblings" ->
"four siblings" now that CreateMeasure is its own packet.
Verified against the tree, not assumed: all four carried types are schema
major 0 (no versioned walk exists; both decode_v0_score and the live walk
read them through plain Codec::dec), TypedObjectId already carries all five
variants, and struct_codec! generates TextValue as well as Codec -- so G3a
moves no wire bound, appends no typed id, and needs no textvalue_graph.rs
work. It does close a live defect: every G3 object kind becomes Live only
through base ingest, so CreateStaff's group precondition is currently
unsatisfiable under from-empty reduction.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
The Round 1 result and the evidence it rests on: C1 and C2 each reproduce
all 27 precommitted sample points on both required adapters, 108 samples
in total, every one exactly opaque black or opaque white. Neither
candidate is eliminated at this rung.
Anchored to three SHAs, per pin 12 -- root baseline 0a35697, oracle
0a35697, candidate harness c20bc93. The authoritative run was made from a
detached worktree at the baseline with the harness extracted into it, so
the glyph outlines came from frozen epiphany-glyphs and not from a working
tree the parallel genesis-ops track has been editing throughout.
Two findings are recorded because they matter after this round is over.
A blank target is a silent pass shape and it actually happened here: C1
failed all 15 ink points while passing all 12 background points, which is
the signature of nothing being drawn rather than of a fill bug. Had this
round tested only background points, or only "renders without erroring",
a blank target would have passed it. Every later round needs at least one
assertion that can only succeed if ink was deposited.
And nominal 8x AA is not the same mechanism on both sides. Pin 4 asks for
an identical sample count and gets one, but C1's 8x is a hardware
multisample attachment while C2's is vello's compute AA into a
single-sample texture. Round 1 is indifferent; Round 4 is not, since AA is
in the deciding latency numbers, so the report prints the mechanism beside
the number rather than leaving "8 == 8" to imply parity.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
The shared harness plus the two candidate binaries for Round 1 of the
toolkit spike. C1 tessellates each Bravura outline with lyon and draws it
through egui's own paint pipeline; C2 builds one kurbo BezPath and one
Scene::fill. Both classify their readback against the frozen oracle from
0a35697 and report per-point.
Three things this had to get right, none of which is the rendering:
A candidate must not be able to pass by drawing nothing. C1 initially
failed all fifteen ink points while passing all twelve background points,
which is the signature of a blank target, not of a fill bug: the mesh
named TextureId::default() and egui-wgpu silently skips primitives whose
texture id is unregistered. It now registers a 1x1 opaque-white texture.
The harness must not substitute for a broken readback. A short buffer
used to yield (0,0,0,0), whose luma is 0, so every ink point would pass.
Buffer length, coordinate range, and sample opacity are now hard errors
rather than clamps or defaults.
The oracle must not be editable into agreement. deny_unknown_fields
catches structural drift, but semantic drift is the dangerous kind, and
a validator that checks the oracle against its own other fields accepts
any self-consistent file. OracleFile::validate now checks against
literals restated in the harness: the exact five-glyph roster with its
requirement mapping, subpath counts and point counts, the 27-point
census, 1920x1080, the 8 px clearance floor and every sample's clearance,
and the requirement-specific status flags. Thirteen mutations were run
against it -- deleting a glyph, renaming one, duplicating one, dropping a
point, retargeting every glyph together, lowering the floor, relaxing
spacing -- and all thirteen are rejected.
AA is nominally 8x on both, as pin 4 requires, but the mechanisms differ:
C1 uses a hardware multisample attachment (which is why it must request
TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, since 8x on Rgba8Unorm is off
the WebGPU baseline) while C2 uses vello's compute AA into a
sample_count:1 storage texture. The run report prints the mechanism beside
the number so no later round reads "8 == 8" as parity. Round 1 is
indifferent to the difference; Round 4's timings will not be.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
t9 attributed its mutation to t2 in both the contract row and the test comment.
The sweep disproved that attribution: t9 stayed green under t2's schema_major
change, because the from-empty spine reaches a note whatever the block stamps.
A mutation borrowed from another test signs nothing unless it is observed to
kill this one.
Signed with the dispatch-arm bypass instead:
OperationKind::SetTuningContext(_) => OperationEffect::Applied
which skips tuning reduction without making the match non-exhaustive - so the
mutation isolates the reduction rather than being absorbed by a compile error.
Observed exactly as predicted: the spine operations stayed applied, the note
stayed reachable, and the test failed on the authored tuning-context
assertion, with the seeded default 440.0 surviving in place of the authored
441.0. Reversed by editing back; baseline green.
Both attribution sites now name that mutation and record why the previous one
was wrong, since a wrong mutation attribution is worse than a missing one: it
reads as signed.
Gate: 1410 tests, clippy 0, fmt clean, git diff --check clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
The corpus fixture stamped the manifest at {0,10} because SetTuningContext
needs an operation block at {3,10}. Those are separate domains: the document
line carries the manifest's aggregate SchemaVersion and projection discards the
block's schema by design. With no edit barriers the manifest stays baseline V0,
so the fixture was locking an over-stamped manifest into the corpus while
appearing to prove the operation epoch - the exact inference text_projection.tex
tells readers not to make. The block's stamp is proven where it lives, by the
staged-and-reopened roundtrip test.
t10 still read only bundle.rs. Correcting ids.rs and adding a prose
cross-reference did not make the pair travel together; sharing one guard does.
It now iterates both sources. Two incidental discoveries while extending it:
include_str! pulls in the test's own text, so both the needle and the assertion
message must avoid the phrase they search for - which is why the original split
its needles with concat().
Four stale comments: manifest.rs's barrier-tag range 24-33, payload.rs's "ten
events"/"thirty-four payloads", and the 30..=33 ranges in generators.rs and
layout_stub.rs.
Mutation sweep, each run and observed:
- t1 kind space 34->35, and tag space 34->35, separately. Both kill t1.
- t2/t3 schema_major 3->0: kills t3 and the staged/reopened test ({0,10} vs
{3,10}).
- t6 predecessor restore dropped: kills t6 (442 vs 441).
- t8 kind epoch 10->9 and tag epoch 10->9, separately. Both kill t8; the tag
mutation additionally kills s1's tag table, the kind mutation does not,
which is why both tables needed the entry.
- t10 stale claim reinjected into bundle.rs and into ids.rs, separately. Each
kills the guard, naming the offending file.
t5 and t7 were signed earlier; t4 by the cap-to-2 run.
Gate: 1410 tests, clippy 0, fmt clean, git diff --check clean, 14 textproj
vectors, 105 decode vectors.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
Closes four coverage gaps found in review. No behavioral change to the reducer,
codec, or wire.
ids.rs carried the same falsehood bundle.rs did - major 3 is snapshot-only, no
operation payload embeds the tuning context, the op-block accept-set is
untouched. t10 grepped only bundle.rs, so it could not see it. Rewritten, with
a pointer to its sibling so the pair stays together.
The text-projection corpus covered no set-tuning-context document: every
accepted document built DeleteRegion envelopes, and G2b only regenerated
headers to 0.11.0. Adds a real one, carried at minor 10. The typed all-kind
round-trip proves the production parses; only a committed vector proves the
emitted text is stable across implementations, which is what that corpus is
for.
Fixing it exposed a latent fragility worth more than the fix: the negative
vectors bound their source documents by positional index into the accept list,
so inserting a document silently repointed every reject builder at the wrong
text. Converted to by-name binding, which is what made the insertion safe.
The generator guard stopped at kind 33 - the very test written because
TransposeInterval and CreateInstrument had gone missing from every corpus while
downstream suites stayed green. It now records and asserts SetTuningContext, so
removing or rerouting the new arm cannot leave it green.
t3 and t4 did not test block behavior. t3 computed max(schema_major()) over
bare OperationKinds without building a block; t4 asserted only the ceiling
constant. Neither would notice a mis-derived stamp or a gate that rejected the
block it now admits. Adds one integration test that stages a real envelope
through stage_operation_block and reopens the bundle, asserting {3, 10} and
read-write. Verified by mutation: reverting the accept-set to 2 fails it.
Also three stale counts, each the hand-maintained-parallel-list shape: the
textproj production count said 31 and had been stale since the original
text-projection commit (now 35), the G-minor epoch table omitted kind 34, and
the corpus count was pinned at 13.
Gate: 1410 tests, clippy 0, fmt clean, conformance 8/8, 14 textproj vectors,
105 decode vectors, git diff --check clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV