Commit Graph

250 Commits

Author SHA1 Message Date
Levi Neuwirth 694d135bfc Packet 2B apparatus: the neutral candidatekit and the check-5 oracle
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
2026-07-30 15:49:07 -04:00
Levi Neuwirth b622498c19 G3b packet 1 repair 2: the real-write grid tie, on a fixture that cannot pass vacuously
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
2026-07-30 13:49:39 -04:00
Levi Neuwirth b92023a032 G3b packet 1 repair: three defects the white-box tests could not see
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
2026-07-30 13:12:04 -04:00
Levi Neuwirth e64a4b7103 G3b packet 1: CreateMeasure reaches the wire, the mint, and the grid oracle
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
2026-07-30 12:19:32 -04:00
Levi Neuwirth 93deb18ed6 Record the check-5 asymmetry, and refuse to guess its size
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
2026-07-29 19:18:52 -04:00
Levi Neuwirth 6170015a8c P13-S17: restore G2b to the Binary Format revision history
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
2026-07-29 19:09:25 -04:00
Levi Neuwirth ffe313cd73 Packet 2A: the candidate-neutral text fixtures, emitter, and differential
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
2026-07-29 18:40:16 -04:00
Levi Neuwirth 565b0f8f79 Round 2 recipe: the precommitted stand-in, oracles, and differential
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
2026-07-29 18:39:52 -04:00
Levi Neuwirth 4b0abaf04b G3a undo repair: the tombstone branch was reachable all along
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
2026-07-29 18:21:45 -04:00
Levi Neuwirth 6c5e69f19d Genesis G3a: the four root-level entity mints
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
2026-07-29 14:07:35 -04:00
Levi Neuwirth 2892e7b2cd Give §1.1's ruling a normative footprint, and fix two impossible mutations
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
2026-07-29 11:39:54 -04:00
Levi Neuwirth ec25a5e0c1 Ratify §1.1 as disposition B, and file the gap it defers
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
2026-07-29 11:34:54 -04:00
Levi Neuwirth 012a2de16d G3a contract repairs: the authorship cycle, and four maps the pin promised without
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
2026-07-29 11:26:41 -04:00
Levi Neuwirth f1ce25f8f8 Ratify the G3 rulings and draft the G3a contract
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
2026-07-29 11:12:49 -04:00
Levi Neuwirth c10449a964 T4 round 1 record: both candidates PASS, and the two carry-forwards that outlive the verdict
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
2026-07-29 11:10:05 -04:00
Levi Neuwirth c20bc93247 T4 round 1: both candidates render the compound paths, and the oracle now refuses to be edited into agreement
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
2026-07-29 10:59:49 -04:00
Levi Neuwirth 25c473314b Sign t9 with a mutation that actually kills it
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
2026-07-29 10:18:57 -04:00
Levi Neuwirth 1a7df12b0c G2b: unconflate the corpus version domains, unscope t10, and sign the mutations
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
2026-07-29 10:09:49 -04:00
Levi Neuwirth d685b641cd G2b coverage repairs: the second stale rationale, and three guards that could not fail
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
2026-07-29 09:48:41 -04:00
Levi Neuwirth 13c3d2fd46 Genesis G2b: SetTuningContext, and the accept-set raise it pays for
Kind and tag 34, schema major 3, minor epoch 10. The last rung before G3, and
the one that closes P13-S13: the tuning context becomes operation-authored, so
it finally has a canonical carrier. The closure argument is the metadata
precedent, not the canonical base - the base embeds no graph values for any
field, and metadata has been durable purely through its operations since M2d.

The payload carries epiphany_core::TuningContextSettings, a five-field subset
of ScoreTuningContext, not the full graph type. ScoreTuningContext's codec
deliberately drops accidental_extensions, so a full-value payload would have
diverged between a live session, where accept stores the envelope as a value,
and the same document reloaded, where the field decodes empty. canonical_value!
could not have caught that: it compares bytes and never the originating value,
so a field that never reached the bytes is structurally invisible to it. The
subset makes the divergence unrepresentable instead of relying on a
normalization step nothing can enforce, and it costs no wire design - the
encoding is byte-identical to the existing five-field walk, which
tuning_context_settings_canonical_bytes_match_score_tuning_context asserts
directly. Reduction leaves accidental_extensions untouched.

SetTuningContext is the sole genesis payload born at major 3, because minimal
stamping is a function of each payload's value, so the accept-set raise is
charged to this one surface: OperationEnvelopeBlock 2 to 3. The doc comment
above it did not merely record the cap, it asserted that no operation payload
embeds the tuning context - a sentence this rung falsifies - so it is rewritten
rather than left beside a corrected constant.

Undo restores the seeded base settings, default or not, and the
never-authored versus authored-to-default distinction stays unobservable. An
earlier draft of the contract had that backwards; PLAN_GENESIS_OPS section 5
trap 5 withdrew it, and SetMetadata is the disproof.

Fixes two undefined references the interrupted run had not yet reached:
operation_catalog.tex referenced sec:evolution:major3, a label defined in
binary_format.tex, which LaTeX cannot resolve across documents. Replaced with
the sectionsc convention already used for every other cross-companion citation
in that file.

Gate: 1409 tests, clippy 0, fmt clean, conformance 8/8 including [7f], both
vector corpora regenerated, all four PDFs at 0 undefined references. The t5 and
t7 mutations were re-run independently and observed to fail as specified; the
remaining eight are not signed off, because the implementing run was stopped
before it reported them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-29 09:30:32 -04:00
Levi Neuwirth 039a8ddec8 G2b amendments: undo restores the seed, epoch 10 lands, and a real touch table
Documentation only. Ready for dispatch after these four repairs.

Replaces pin 5 and t7. The earlier draft required undo to distinguish "never
authored" from "authored to the default", which PLAN_GENESIS_OPS.md section 5
trap 5 explicitly withdrew - and SetMetadata is the disproof: Score::empty
seeds metadata exactly as it seeds tuning_context, and base ingest runs
metadata_chain.seed precisely so a value-restoring undo of the first
operational write restores the pre-operational state. Undo restores the seeded
base settings, default or not, and the distinction must stay UNOBSERVABLE -
code that can tell the two apart is a defect, not a safeguard. Predecessor::Base
vs ::Write earns its keep only for the bookkeeping families, where a base
predecessor returns a map key to absence; ScoreTuningContext is always-valued
like metadata and has no absent state to return to. t7 now asserts the two
cases are indistinguishable rather than that they differ.

Appends epoch 10 to the authoritative ladder with G2b as the event. It stays
monotonic and prefix-closed, and it is the first exercise of the ladder's own
growth path - G-minor's wildcard-free introduced_minor means kind 34 cannot
compile without one.

Adds the explicit non-goal the plan requires: G2b authorizes no pruning or
compaction of the canonical operation log. The rung sharpens that prohibition
rather than relaxing it, because after G2b pruning would discard authored
genesis state rather than merely re-derivable state.

Adds the touch table the gate already referred to but which did not exist,
derived from G2a's actual thirty-file footprint plus G1's one extra rather than
from recollection: the new core type and its export, full ops plumbing
including envdecode/migrate/v0/valuegen/fuzz/vectors, the bundle accept-set
raise and its falsified rationale, the four budgeted boundary crossings, text
projection with companion 0.11.0, all four .tex sources with all four PDFs, both
vector corpora, and P13-S13's closure.

Ratifications folded in: subset design, epiphany_core::TuningContextSettings,
epoch 10.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 21:27:17 -04:00
Levi Neuwirth 0a35697d8e T4 round 1: the precommitted fill oracle, and nothing has rendered against it
Pin 13 makes this sequencing a hard rule -- "points that must be ink" chosen
after seeing candidate output is not a test -- so this crate draws nothing and
depends on no rendering, tessellation or windowing crate. It derives the oracle
from the typed glyph outlines and writes it out as data. Candidates render
against it in a later packet.

Twenty-seven sample points across five glyphs, all derived programmatically by
even-odd point-in-path over the flattened PathCommand outline, never chosen by
eye, each at least eight device pixels clear of any outline edge so
antialiasing cannot explain a result. Every background point is proven to lie
inside a BOUNDED HOLE -- enclosed by the outer silhouette yet unfilled -- since
a point merely outside the glyph is passed trivially by a renderer that fills
holes solid. fClef instead carries one ink point inside each of its three
filled subpaths, tagged by subpath index so the oracle proves every component
is covered rather than three points that could all land in the bowl.

The status model distinguishes a satisfied disjoint-component result from a
failed hole result: a requirement enum, per-requirement required/satisfied
pairs, and one authoritative satisfied flag.

Mutations, each with kill evidence in the test module. A perturbed ink point is
rejected outside the glyph. Shrinking the clearance floor admits strictly more
points, so the filter is not a no-op. Every hole point sits inside a naive
outer-contour-only fill but outside the real whole-outline fill -- the positive
proof that inner-subpath handling is load-bearing, which stands in for a
fill-rule flip that CANNOT reclassify anything on correctly wound contours.
And filling only fClef's largest subpath misses both dot points, which is the
disjoint-component analogue and the reason that class exists.

Two proofs about fClef's topology, because one was not enough. Vertex-level
non-nesting cannot see two subpaths whose edges cross with no vertex of either
inside the other, so it is paired with pairwise bounding-box disjointness,
which rules out crossing, touching and containment together. The grid-based
corroboration is named for what it actually checks -- one finer 0.005 grid --
rather than claiming any resolution.

The oracle's adaptive flattening is authoritative for the recorded signed
areas; an earlier coarser fixed-step measurement is marked superseded where it
appears. The first four-glyph pass is kept in DECISIONS.md under an explicit
SUPERSEDED -- NEVER COMMITTED banner, because its two findings are what caused
the contract amendment, and deleting the discovery would erase the reason.

Verified: 12/12 tests; fmt clean; cargo doc clean; regeneration byte-identical
by SHA-256 from the crate directory. All 27 coordinates were additionally
cross-validated by an independent point-in-path implementation with zero
mismatches, and the new bounding-box assertion was mutation-killed against a
nested glyph to prove it is not vacuous.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
2026-07-28 21:25:07 -04:00
Levi Neuwirth b724e94faf Contract revision 6: round 1 was testing the wrong property on the wrong glyphs
Building round 1's oracle exposed two defects in the round as written, both
mine, and both invisible until something tried to satisfy it.

The glyph set was chosen by subpath count. I wrote that 19 of the 37 bundled
outlines have more than one subpath and named gClef, fClef, timeSig8 and
accidentalFlat as the useful ones -- conflating multi-subpath with has-a-hole.
fClef is the counter-example: its three subpaths are a bowl and two SOLID,
disjoint dots, nested in nothing. Measured across all 37 by point-in-path,
exactly twelve carry a bounded hole; fClef, cClef, barlineFinal and every
repeat glyph carry none.

Round 1 now runs five glyphs in two classes testing two different properties.
Hole checks -- gClef, timeSig8, accidentalFlat and noteheadHalf, which is new
and earns its place by being frequently repeated and semantically consequential
(a filled counter renders half notes as quarter notes, a notation error rather
than an artifact). Disjoint-component check -- fClef alone, no background
requirement, instead requiring one ink point inside EACH of its three filled
subpaths, tagged with its subpath index. That second class catches a
tessellator that keeps only the largest contour, which would pass every hole
check ever written. Hard failure is now stated as either: a bounded hole
painted as ink, or a required filled subpath omitted.

The oracle's status model is now specified rather than inferred from an
absence. fClef passing with zero background points is a SATISFIED result under
its own requirement class; recording it only as background_satisfied = false
would make a correct outcome indistinguishable from a failed one.

The second defect was the criterion itself. Ruling A said epaint does not
implement even-odd/nonzero fill for paths with holes -- framing criterion 1
around the fill RULE. Bravura's contours are correctly oppositely wound
(signed ring areas gClef [8.702, -0.691, -1.803, -0.509]; fClef
[2.534, 0.153, 0.148], all positive, the same fact from the other side), so
even-odd and nonzero AGREE on every bundled hole. The rule is not load-bearing;
preserving every filled contour and every bounded counter is. The criterion is
amended to compound-path / inner-subpath fill correctness, with a more accurate
reason for excluding raw egui shapes than the one it replaces: PathShape is a
single point loop documenting "Fill is only supported for convex polygons", so
it cannot express compound-fill or subtractive-hole semantics -- a Shape::Vec
can group loops, but grouping paints them, it does not subtract a counter from
its enclosing contour.

Signed areas are recorded from the oracle's adaptive flattening rather than an
earlier coarse fixed-step measurement, with the note that magnitudes are
flattening-dependent and the SIGNS are the claim.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
2026-07-28 21:24:47 -04:00
Levi Neuwirth 3af242d457 Draft the G2b contract: subset over normalization, and the raise this rung pays
Documentation only. Not dispatched - two items need ratification.

Resolves the open accidental_extensions pin as SUBSET, not normalization.
SetTuningContextOp carries a new type holding exactly the five wire-bearing
fields, and reduction leaves accidental_extensions untouched rather than
clearing it.

The deciding argument is that this costs no wire design at all:
ScoreTuningContext::enc already encodes exactly those five fields in exactly
that order, so the subset type's canonical encoding is byte-for-byte the
current one. It is a type-level narrowing, and canonical_value! still applies.
Normalization would instead make correctness depend on clearing a field at
every construction site, enforced by nothing the compiler or the codec can see
- canonical_value! compares bytes and never the originating value, so a field
that never reached the bytes is structurally invisible to it. That is the same
hand-maintained-discipline shape that went stale four times at Push 4a and six
times at G2a. Preserving is also semantically right: the operation carries no
information about the field, so clearing it would silently erase a live
registry extension the wire cannot even record.

Nothing in production populates accidental_extensions - every non-empty
reference in the tree is a test - and the text projection drops it too, so the
subset type ratifies the status quo rather than changing it.

Ten pins. The accept-set raise is charged here because minimal stamping is a
function of each payload's value and ScoreTuningContext is the only genesis
payload born at major 3. bundle.rs:56 does not merely document the cap, it
asserts that no operation payload embeds the tuning context - a sentence this
rung falsifies, so it must be rewritten rather than left beside a changed
number.

Awaiting ratification: the subset type's name, and epoch 10 for kind 34, which
is the first exercise of the G-minor ladder's own growth path. G-minor's
wildcard-free introduced_minor means kind 34 cannot compile without one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 21:15:07 -04:00
Levi Neuwirth b8601ec909 File P13-S15: the OperationKind golden lock stops at 29, and close P13-S14
Documentation only. No code change.

S15 records that operation_kind_wire_discriminants_are_golden declares
[(OperationKind, u8); 30] at payload.rs:1959, covering 0..=29 - so
TransposeInterval, CreateInstrument, SetCanvasLayoutDefaults and
SetSpellingPrecedence have no byte-level lock. Not a live incorrectness: all
four discriminants are currently correct and the .tex tables carry them
normatively. The gap is the absence of a guard.

Left open deliberately with no code change. The fix is mechanical, but a
golden-lock extension should land with its mutation evidence and nothing else
in the diff - and the mutation is to move one of the four and watch the
extended lock fail where it previously stayed green. The macro-guarded
OperationKindTag half is unaffected; this is the hand-written match, which is
the site Push 4a got wrong. Its sibling phase3_tag_discriminants_are_golden
wants the same check.

Closes S14 at ff9bd0f, and records the two things its filing did not
anticipate. The scope was never just OperationKind - OperationPayload,
ReanchorReason and PreconditionFailureReason all append - and the manifest
reaches OperationKindTag through edit_barriers with no envelope involved, which
supersedes the filing's "no companion bump" note. Op-block stamping did stay
projection-invisible as scoped; the bump came from the manifest attribute.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 21:10:17 -04:00
Levi Neuwirth ff9bd0fd06 G-minor: the chunk schema minor becomes a derived record
Implements the schema-minor MUST (binary_format.tex, Schema Versioning) that no
writer had ever honoured: a writer raises the chunk minor when it emits a
discriminant appended after the minor it otherwise declares, so an
unknown-discriminant decode failure is attributable to version skew rather than
corruption.

introduced_minor() lands on the five vocabularies with post-baseline variants -
OperationKind, OperationKindTag, OperationPayload, ReanchorReason, and
PreconditionFailureReason - each exhaustive with no wildcard arm, so a future
variant cannot compile without being assigned an epoch. The tag epochs live
inside operation_kind_tag_vocabulary! rather than beside it, because a sibling
match is the parallel list that macro exists to prevent. The sentinel is
Option<u16>, not 0, since 0 is a real baseline minor for V1-V3 and conflating
them would make the max read correctly only by accident.

An envelope's required minor is the max over every discriminant it actually
emits; a block's is the max over its envelopes; major and minor derive
independently. Baselines are not normalised - V0 keeps minor 1.

The manifest seam keeps epiphany-bundle opaque: no ops or layout-ir dependency,
and the aggregate version is supplied by the producer rather than derived, with
CommitContext carrying the previous one so unchanged barrier content preserves
it. The version rides the superblock slot that already exists; Manifest gains no
field, which would have been schema-major and would have defeated the rung.
bundle.rs's superblock check stays major-only - tightening it to full-version
equality is a conformance regression, and s11 locks that.

textproj carries the manifest SchemaVersion and never derives it, so
COMPANION_VERSION moves 0.9.0 to 0.10.0 with the corpus regenerated. Not because
of op-block stamping, which remains projection-invisible. A new normative
requirement records the carry-never-derive rule in the companion itself; its
rationale names layout-ir, which textproj genuinely lacks, rather than the
operation vocabulary, which it has.

Gate [7f] adds an independent oracle over decodable in-tree barrier fixtures,
requiring exact equality rather than >=. Equality is load-bearing: >= catches
under-stamping but not stale over-stamping after the sole maximum contributor is
removed. Undecodable blobs are reported not-checkable, never as a pass.

Also repairs binary_format.tex's stale claim that OperationKind and
OperationKindTag append at 30 with a history stopping at 29, while 30-33 are
taken and the normative tables already carry them.

Gate: 1399 tests, clippy 0, fmt clean, conformance 8/8 with [7f] at four
fixtures checked and one not-checkable, 102 decode vectors byte-identical
(verified, not assumed), 13 text-projection vectors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 21:04:34 -04:00
Levi Neuwirth 496dfd5640 T4 round 0: iced is eliminated, and the probe that cleared it proved nothing
Round 0 is the ladder's cheapest hard gate: for each candidate, a desk survey
and a demonstrated accessibility route -- a window exposing one node with a
role and a name, read back through the platform adapter. It is first precisely
so that a candidate with no accessibility story costs one round instead of
four.

C1 (egui/eframe 0.35.0) passes on a first-party route: egui-winit's accesskit
feature is literally dep:accesskit_winit, so the chain runs eframe ->
egui-winit -> accesskit_winit -> accesskit_unix and inherits that crate's
window-lifecycle handling. C2 (vello 0.9.0 + winit 0.30.13) passes on the
manual accesskit_winit route the contract names, wired into the same
ApplicationHandler that owns the vello renderer.

C3 (iced 0.14.0) FAILS, and the interesting part is that it first reported as
a pass. The probe registered a hand-built two-node tree through
accesskit_unix::Adapter, which takes no window handle at all -- only handlers
-- and registers with AT-SPI from process identity. It read back cleanly. It
also happened to label its button exactly as iced's own view() labelled a
button, so the transcript looked as though iced had produced it. Deleting iced
from that probe would produce the identical readback. Round 0 asks whether the
CANDIDATE exposes a route; a process-level side channel answers a different
question.

The verdict is recorded with dual attribution, because two distinct things
went wrong. The probe-design defect is the false positive above. The candidate
limitation -- which alone fails the round -- is that iced 0.14 ships no
accessibility integration anywhere (accesskit appears in no iced crate
manifest) and its stock runner hands application code neither the winit
ActiveEventLoop nor a pre-visibility Window, both of which every
accesskit_winit::Adapter constructor requires. That scoping matters and is
deliberate: iced_winit documents a conversion module for users implementing a
custom event loop, so a hand-built shell remains conceivable but unproven, and
would mean owning the shell. Upstream iced #552 is still open.

The evidence file keeps the verifier's factual READBACK: PASS beneath a
ROUND-0 RESULT: FAIL annotation rather than being rewritten. A corrected record
that erases the false positive teaches nobody why it was false.

Two findings carried forward. C1's frame node is unnamed -- its readback path
is application:'probe-egui' / frame:'' / button:'...' where C2 names its frame
-- which is non-disqualifying here but means a screen-reader user hears an
unnamed window, and round 3 must check it. And AT-SPI application registration
is gated behind two settings that are off by default; without both, probes
connect to the bus and enumerate zero applications, which would read as a
candidate failure rather than the environment absence it is.

The spike workspace lives outside the root workspace with its own lockfile;
the root gains one line, exclude = ["spikes"], and nothing else. Round 0's
probes depend on no epiphany crate, which is what makes this round independent
of the pinned source baseline the later rounds need.

Root gate unchanged: fmt clean, clippy -D warnings clean, 1371 tests passed 0
failed. Spike workspace gated separately: fmt clean, all probes build --locked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
2026-07-28 19:01:51 -04:00
Levi Neuwirth 0c8b8db1a5 Amend the G-minor contract: the [7f] oracle, and the PDFs the touch table missed
Documentation only. No implementation.

Rules pin 11 as (b) with (a) normative. textproj stays a preserving producer -
it carries the declared manifest version verbatim, never decodes barriers, and
the document author owns updating it when editing opaque barrier bytes.

On top of that, an independent testkit conformance oracle at gate [7f]. When
every barrier blob decodes it recomputes the exact aggregate and requires
equality, not >=: equality is what catches stale over-stamping after a
contributing barrier is removed, which >= cannot see. Both negative fixtures
are required - a tag-31 blob carrying baseline, and removal of the sole maximum
contributor. An undecodable blob is reported not-checkable and never as a pass,
with byte-and-version preservation left to s10/s13 rather than claimed here.
The gate must describe itself as validating known decodable in-tree artifacts,
not as evidence that textproj validates arbitrary edits.

Three new mutations. s16 is deliberately inverted: relaxing equality to >= makes
the over-stamp fixture go green, and that green is the evidence that equality is
load-bearing.

The touch table gains the testkit conformance files and the two tracked PDFs
whose .tex sources change. core_spec.pdf and operation_catalog.pdf stay
build-only for the undefined-references check; if either turns out byte-changed
that is a finding, since this contract changes neither source.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 18:59:00 -04:00
Levi Neuwirth f94102d839 Contract G-minor: ten pins, one open ruling, fourteen mutations
Documentation only. No implementation.

Drafts the implementation contract for the schema-minor rung against the
ratified epoch ladder and the vocabulary audit. Ten design pins covering the
derivation (envelope max over emitted, block max over envelopes, major and
minor derived independently), the five vocabularies that need introduced_minor,
the manifest seam, and the text-projection consequence.

Three pins exist to prevent specific wrong edits. The baseline sentinel must
not be 0, because 0 is a real baseline minor for V1-V3 and conflating them
makes the max read correctly by accident. OperationKindTag's epochs go inside
operation_kind_tag_vocabulary!, not beside it, because a sibling match is the
parallel list the macro exists to prevent. And bundle.rs:301 must stay a
major-only comparison - tightening it to the full version would be a
conformance regression, and it is the most tempting wrong edit in the packet.

One pin requires ratification before implementation: epiphany-textproj cannot
detect changed barrier bytes, having no prior state to diff and no layout-ir
dependency to decode with. Recommended disposition is a documented preserving
producer, with a testkit conformance check as the alternative. The two differ
by a test rather than a design.

Fourteen tests, each with the mutation that must kill it, including the
rejected highest-discriminant policy, the over-stamp after a contributing
barrier is removed, and the bundle.rs:301 tightening.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 18:50:45 -04:00
Levi Neuwirth 0def1563c8 Amend the G-minor plan: the seam ruling, the companion consequence, one wrong hash
Documentation only. No Rust, no .tex, no vectors, no implementation.

Corrects G2a's introducing commit to 7df5ca1 - where kinds/tags 32-33 enter
payload.rs. 55eff00 is the later review-fix and introduces no discriminant.
The hashes are the evidence for the ladder's monotonicity, so citing a
follow-up commit would make it unverifiable at exactly the rung a reader is
most likely to re-derive.

Records the ruled seam. epiphany-bundle stays opaque with no ops or layout-ir
dependency; producers supply the aggregate manifest SchemaVersion explicitly
and CommitContext exposes the previous one so unchanged barrier content
preserves it. That is new plumbing rather than a read-through: Manifest has no
schema-version field, it lives in the superblock, and it must not gain one -
a field addition is schema-major regardless of type, which would defeat the
rung. Changed barrier bytes require an aware producer that computes the exact
epoch or refuses, because blindly retaining the previous aggregate
over-stamps once a contributing barrier is removed.

Manifest::SCHEMA becomes a baseline constant rather than the universally
emitted version. Three sites silently select it, not two - bundle.rs:220,
bundle.rs:724, and the re-exported helper manifest_chunk_hash at bundle.rs:1298,
which is public API and so a signature change. The major-only check at
bundle.rs:301 stays as-is and stays correct.

Consequently G-minor does bump the text companion: 0.9.0 to 0.10.0 with corpus
regeneration, because TextDocument must carry the manifest SchemaVersion that
the projection cannot derive from deliberately opaque bytes. Not because of
op-block stamping, which remains projection-invisible - section 2's "no
companion bump" correction is still true about op blocks and now carries a
forward pointer so it does not read as the whole answer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 18:47:42 -04:00
Levi Neuwirth 2c7191482a Ratify the G-minor epoch ladder, and narrow the Manifest::SCHEMA rule
Documentation only. No Rust, no .tex, no vectors, no implementation.

Records the ratified schema-minor epochs (minors 2-9, one per additive event:
M2c, Push 3, Phase-3 tranche 1, Pass-12 G-pass, schema-major-2 repeat revision,
Push 4a, G1, G2a). These are schema-minor epochs, not companion semver.

The ladder is complete against spec/AUDIT_GMINOR_VOCABULARIES.md - every
post-baseline variant appears exactly once - and monotonic in the topological
order of the commits that introduced each variant, which is what makes a
declared minor prefix-closed. Verified against git rather than assumed,
including the two events sharing 2026-07-07.

Major baselines unchanged: V0 minor 1, V1-V3 minor 0. Baseline variants impose
no additive override; M2c's operation kinds stay baseline while its
PreconditionFailureReason append requires epoch 2.

Manifest::SCHEMA is narrowly superseded. Changed child ChunkRefs still never
raise the manifest minor, but emitted barrier tags do: a manifest naming a tag
in 24-33 takes that tag's epoch. Major stays 0. The derivation seam is left to
the implementation contract, with the case that must not be discovered late
written down - epiphany-bundle depends on neither ops nor layout-ir and
preserves barrier bytes verbatim, so a repack can carry a blob it cannot
decode, and "decode and inspect" does not cover it.

PLAN_GENESIS_OPS.md:257 no longer says "per-kind minor". That policy was
rejected; the paragraph now carries the ratified rule and records why.

Every gate in PLAN_GMINOR_SCHEMA_MINOR.md section 5 is discharged. The
implementation contract may now be drafted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 18:39:28 -04:00
Levi Neuwirth c90d662a0c Fold the vocabulary audit into the G-minor plan: the ladder is not ratifiable as written
Documentation only. No Rust, no .tex, no vectors, no implementation.

Records spec/AUDIT_GMINOR_VOCABULARIES.md (c63258d) as the governing
reachability inventory, superseding section 3's table where they differ. Section
3 stays as the reasoning that led there.

Section 4's tentative epoch ladder is marked incomplete rather than merely
unratified: it was drawn against OperationKind alone, and the audit placed
three families it never considered. OperationPayload 3 (Push 3) and
ReanchorReason 6 (Pass-12 G-pass) have no rung, and PreconditionFailureReason
10-15 spans four tranches of which M2c and the G-pass are unplaced.

Also records the distinction that makes the global epoch space and the
per-vocabulary baselines compatible: the epoch answers which revision
introduced a variant, the baseline answers whether it predated that
vocabulary's own lock, and only the second varies. M2c is the proof - baseline
for OperationKind, a genuine append for PreconditionFailureReason. So
introduced_minor is assigned per variant, per vocabulary.

The .tex correction at binary_format.tex:2373 is recorded as owed by the
implementation, not by this plan.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 18:29:59 -04:00
Levi Neuwirth c63258d32a Audit the G-minor reachable vocabularies: a ledger, a matrix, one stale sentence
Read-only packet. Walks each of the nine ChunkKind roles' payload encoders
transitively and records every discriminant vocabulary actually emitted,
with every variant classified baseline / post-baseline native / escape.

Two vocabularies outside the OperationKind/Tag/Payload trio have real native
appends: ReanchorReason (SameCanvasNearer = 6, Pass-12 G-pass) and
PreconditionFailureReason (10-15, four separate tranches). Fourteen further
escape-carrying enums checked and clean, recorded as evidence.

Two findings the plan's section 3 table did not name. The canonical base
reaches RepairKind, ReanchorReason, and SpellingNominal, and ReanchorReason
is one of only two vocabularies with an append. And the manifest reaches
OperationKindTag with no operation envelope in it at all, through
ExtensionDeclaration::edit_barriers -> EditBarrier::prohibited_operation_kinds
- the disposition-(c) crossing, realized in a role the plan never mentions.

binary_format.tex:2373 is recorded as stale narrative: it says OperationKind
and OperationKindTag "append at >= 30" and its history names only 24-29,
while 30-33 are taken. The normative tables at :1443 and :1526 are current
and authoritative; the .tex correction is deferred to the G-minor
implementation, which must touch that paragraph regardless.

CompressionAlgorithm is ruled out of scope for schema-minor epochs: it is
ChunkRef transport metadata, not a discriminant emitted by the payload
SchemaVersion governs, and it is deliberately excluded from chunk identity.
Recorded so it is not reopened.

The epoch space stays global, but baseline classification and introduction
events are per variant per vocabulary. The tentative ladder is incomplete -
Push 3, the Pass-12 G-pass, and M2c are unplaced - and must not be ratified
as written.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 18:27:35 -04:00
Levi Neuwirth 9dc48ecb70 Audit contract amendments: a possible gate, and four scoping repairs
The gate was unsatisfiable. The tree is already dirty with the editor track's
Cargo.toml and spikes/ subtree -- which the contract itself acknowledged two
sections earlier -- so "exactly one new file" could only have been faked or
argued around. It is now a before/after status delta whose sole permitted
difference is the new audit file, with anything else, a cargo-touched
Cargo.lock included, reported rather than cleaned up.

Scope widened to every reachable discriminant vocabulary, open or append-only
or expanded by ratified revision. binary_format:2386 permits minor-additive
appends to the value unions it describes as closed, so "closed" was the wrong
exclusion boundary; only ChunkKind is genuinely out.

Role completeness now has four dispositions rather than two, because opacity
is a property of a layer and not of bytes. Normatively typed bytes opaque only
to the immediate layer -- operation envelopes, manifest barrier blobs -- must
be crossed into and their producer encoders walked; treating op blocks as
opaque would drop the one vocabulary G-minor exists for. Producer-owned opaque
bytes such as ExtensionData get an explicit "no core derivation possible,
schema carried from the producer", since cannot-see-inside and nothing-inside
are different findings.

The escape checklist is now sixteen named entries and the count is deleted
rather than corrected: BarrierScope and BarrierCondition are separate enums,
and a prose pairing of them is exactly how the miscount happened.

Baseline enumeration is mandatory, in a separate vocabulary ledger classifying
every variant as baseline, post-baseline native, or escape/reserved. The matrix
references it instead of repeating large baseline sets, and a disagreement
between the two parts is a defect in the audit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 18:08:55 -04:00
Levi Neuwirth 6145efecd9 Contract the G-minor vocabulary audit: a matrix, read-only
The gating work before G-minor can be contracted. Deliverable is one file,
spec/AUDIT_GMINOR_VOCABULARIES.md, holding a reachability matrix keyed
chunk role -> encoded payload type -> discriminant vocabulary ->
post-baseline variants -> introduction event -> derivation site.

Method is pinned to walking the encoder rather than the type, because a field
can exist and never reach the wire -- ScoreTuningContext::accidental_extensions
is the standing proof -- and the matrix's purpose is to say what is emitted.

Inclusions cover nested additive variants at any depth and, the sharpest edge,
later native additions to enums that also carry a Registered escape: the escape
is out of scope while a new native variant on the same enum is not, and
binary_format names fifteen such carriers. ChunkKind is excluded as closed.

The trap most likely to corrupt the matrix: the spec's "append at >= N"
phrasing names the next free slot, not the baseline boundary. OperationPayload
is documented as appending at >= 4 while discriminant 3 is itself an append,
and OperationKind at >= 30 while 24-29 were appends too. Post-baseline
membership must come from revision history, not from those numbers.

Epoch numbers are explicitly not ratified here; column 5 records the
introduction event. Completeness is enforced by requiring a per-role
accounting including roles that produce no rows, and negative results recorded
for every escape-carrying enum -- an unlisted role being indistinguishable
from an overlooked one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 18:02:32 -04:00
Levi Neuwirth 82ac636982 Revise the G-minor scope: the recommended policy could not work
All five findings verified against the tree; the recommended policy is
rejected and replaced with the ratified one.

Policy (b), minor = highest discriminant emitted, cannot represent an
operation block. An envelope also emits the outer OperationPayload
discriminant, and ResolveEquivocation is appended at 3 while carrying no
OperationKind at all -- so the ambiguity is inside one role and one block, not
between roles. Generalizing to "highest from any vocabulary" is worse: an old
kind 23 would numerically mask a new payload 3. binary_format itself enumerates
four independent minor-additive vocabularies. The rung's gating work is
therefore an audit of every append-only discriminant reachable from each
affected payload, which the first draft never scoped.

Ratified instead: a global additive epoch with content-minimal stamping, an
envelope's minor being the max across outer payload, primitive kind, and every
nested additive variant actually emitted. The maintenance objection is
answered by co-locating introduced_minor with each discriminant in an
exhaustive match with no wildcard, so an unassigned variant cannot compile --
the operation_kind_tag_vocabulary! reasoning. Per-major counters are rejected
too: mixed blocks do not compose after max_major.

Two of my conclusions were wrong. Op-block minors do not reach the text
projection -- block schemas are discarded there, and all seven accepted-corpus
schema forms belong to extension chunks or canonical bases -- so no companion
bump. I had also miscounted them as six by grepping lines rather than
occurrences. And the manifest-ID promise is not threatened: it is conditional
on the same manifest body, and a changed ChunkRef is a different body. Real
address churn, not a broken guarantee.

"Is the canonical base exempt" was the wrong binary question. It never emits
the op-kind discriminant, so it holds its minor until MaterializedState's own
bytes emit a later-added variant. Manifest::SCHEMA stays put; existing bundles
need no migration. Construction-site count corrected 62 -> 66.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 17:59:56 -04:00
Levi Neuwirth 42a8cc6dc5 T4 pin 7: the fixture recipe is approved, and the spike tree opens with it
Pin 7 of the spike contract holds that staff-by-measure dimensions do not pin a
workload, and the workload is what gets tessellated. This is that recipe: one
region, one staff instance per staff, a fixed seven-event bar of six eighths
plus a quarter, and integral per-measure quotas for rests, accidentals, ledger
notes and slurs. Nothing is sampled. The generator still takes a seed, but its
only role is replica minting as in the testkit fixtures -- content is fixed by
construction, which is strictly stronger than a fixed seed over a free
selection algorithm.

Three review rounds, and nearly every finding was the same mistake in a new
place: a claim about the pipeline inferred from an adjacent fact instead of
read from the code that emits.

Ties are not curves. The Curve doc says slurs engrave to one "and ties and
other span curves will follow" -- future tense; constrained.rs has a Slur arm
and no Tie arm, so ties trace as zero-extent anchors. The casting lines first
cited only transform curves produced upstream. Ties are now zero for the
primary ladder, and the decisive reason is not the miscount: every structural
anchor is emitted at the default x, which is the clef column, so casting files
all of them into the first system regardless of musical position. At the top
rung that would pile 2,400 anchors into the most likely damage target and
distort the per-system rebuild timing the whole spike turns on.

The census counts the parser's output, not the source string. H and V each
become a PathCommand::LineTo, so counting M/L/C/Z tokens undercounts exactly
the commands a tessellator consumes -- a sharp is 71, not 57. The census is now
defined as render_data(name).outline.len(), which measures the API candidates
actually see and removes the whole error class.

Ledger lines are emitted per staff step, not per off-staff note. A note one
space outside the staff emits none at all; the first ledger sits two steps out.
So off-staff pitches are pinned by absolute step rather than by "ledger
position", which would have produced about half the ledger ink it implied.

Also corrected: an all-eighths bar cannot yield a quarter rest (rest_glyph maps
value to glyph exactly), 4/4 uses timeSig4 which is single-subpath rather than
the multi-subpath coverage first claimed, barlines are glyphs and not strokes,
and the accidental cadence is one fixed cycle with bounded edge imbalance --
not equal thirds, since no rung's measure count divides by three.

Two-voice engraving is NOT unproven -- rs4_two_voice_counterpoint passes
against the real engraver. The narrower gap is collision handling, rest
displacement and deliberate stem-direction separation, and the ladder stays at
one voice as an isolation choice rather than out of doubt.

The remaining gate is separate and still open: no candidate code exists until
the generator emits its per-rung census, glyph histogram, SHA-256 of each
rung's canonical layout bytes, and the F4 engrave preflight -- and that output
is approved. Recipe approval is not generator approval.

Docs only. No Cargo manifest yet, so the root workspace is untouched; the
exclude line lands with the generator.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
2026-07-28 17:50:12 -04:00
Levi Neuwirth a229a977a2 Scope G-minor: the schema minor is content-addressed, and that changes the rung
The scoping question was whether the chunk header's minor reaches a pinned
corpus. It does, and worse: chunk_content_hash pushes SchemaVersion's canonical
bytes -- major AND minor -- into the preimage, so raising a minor changes the
ChunkId and propagates to the manifest that names it. Not a semantic break,
since readers gate on the major only, but a content-address-moving change
landing on the structure req:format:manifest-id makes a two-writer promise
about.

The text projection also projects the minor as a document surface, and the
committed corpus carries six literal (schema 0 1) occurrences, so op-block
minors rising means another companion bump on the G1/G2a precedent.

The policy is the real work and is left for ratification: one minor per tranche
(a retroactive judgement needing a hand-maintained table -- a seventh such site
on a track whose defining lesson is that they go stale), minor as the highest
discriminant emitted (derivable, self-describing, nothing to maintain, but
redefines the term and is per-vocabulary), or a per-major append counter. I
recommend the second and say why the recommendation is not a default.

Four further questions logged, including whether the canonical base is exempt:
its major is role-pinned to 0 and its byte-identity test pins the payload, not
the header, so a base whose minor rises keeps its payload and moves its id.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 17:49:29 -04:00
Levi Neuwirth 55eff00778 G2a review fixes: a false deviation, and two tests that proved less than claimed
Four findings, all confirmed against the tree.

The accept-set "deviation" was not real, and I propagated it. bundle.rs:1322
has asserted max_supported_major(OperationEnvelopeBlock) == 2 since before
this packet, so the gate was always satisfiable; the privacy observation about
the symbol not being re-exported is true but irrelevant, because the assertion
never needed to live outside the crate. I checked the premise and not the
conclusion, then reported it as verified. No re-export is needed here or by
G-minor. The test now also asserts what the contract actually asked for -- that
a staged block carrying either kind stamps major 0, exercising the writer-side
derivation rather than the bare kind.

s3 asserted effects only, though the contract required chain growth too. A
mutant returning Applied while skipping WriteChain::record for an unchanged
value passed it, and the damage would surface only later as an undo restoring
the base instead of reporting supersession. The two identical writes now sit in
different transactions and a strict undo of the first must report the second as
superseding; mutation (b) kills exactly that half while the effects half stays
green.

s9 claimed no mutation was needed because the test is itself a reject-path
exercise. That reasoning was wrong: being a reject-path test does not show the
rejection is caused by the mislabeling rather than something incidental. The
mutation -- a parse arm that silently accepts a mismatched shape -- was both
performable and killing.

core_spec grouped SetCanvasLayoutDefaults and CreateInstrument as leaves with a
single layout. True only of the first. Instrument has distinct major-0/1/2
layouts and stamps major 2 unconditionally because its major-2 appends are
mandatory, so it is not major 1 for the opposite reason. Split.

All ten contract mutations now have observed kill evidence: s1, s2, s4, s8 and
s10's row-28 half were run here alongside the four recorded earlier. s8 killed
only the two pinned literal-byte vectors while 217 round-trip tests stayed
green -- the 3b-i property, demonstrated rather than asserted.

Gate: fmt clean, clippy 0, 1371 passed / 0 failed, conformance 8/8 and 9/9,
labels 6/6, core_spec PDF 0 undefined refs, goldens byte-identical,
epiphany-bundle diff empty.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 17:37:43 -04:00
Levi Neuwirth 61890a046f Editor T4 opens: the toolkit spike gets a contract that cannot be argued with
Ruling A grants the architecture -- direct vector canvas over ResolvedLayoutIR,
viewport-culled, per-system damage -- and deliberately leaves the toolkit open
for this spike to decide under six recorded criteria. All three prerequisites
are discharged (W1 dd33b34, W2 24f8c80, W3 f639919), so the tranche opens.

The contract's problem is not what to build but how to keep the answer honest.
Six criteria across three candidate stacks is unbounded work with a dozen
places for the verdict to be decided by whoever writes the report. So the
structure is an elimination ladder, cheapest disqualifier first, with every
deciding number pinned before it is measured: the fixture ladder F1-F4 and its
musical-content recipe, the deciding rung, the equivalence band, the tie-break
and its three operationally defined axes, and the oracles -- committed, with
their commit recorded, before any candidate renders against them.

Five review rounds shaped it, and most of what they caught was mine.

Measurement. Timed work must be completed GPU work, not submission, or a
candidate that returns after enqueueing looks fast. One common configuration
across all candidates: offscreen, 1920x1080, identical MSAA and format,
--release --locked, both adapters with the integrated one deciding. Freehand
latency runs from scheduled event injection to completion of the first frame
containing that event, backlog included; timing from renderer pickup would let
a candidate buffer the trace, render late, drop nothing, and report fast update
work while lagging visibly.

Damage. The first draft compared raw PrimitiveIndices, which are positions in
flat arrays -- so a pitch change that preserves primitive count moves geometry
while every vector stays identical, and an inserted accidental renumbers
indices so untouched systems look changed. It is now fingerprints over
dereferenced primitives, unowned included. And byte equality alone lets a full
redraw masquerade as per-system damage, so the oracle is paired with a reuse
assertion over the scene cache, at every engraving-valid rung, before the
ranking round rather than inside it.

Outcomes. A check that ran and got the wrong answer is FAIL, not NOT RUN;
conflating them let a broken implementation buy an escalation instead of
recording negative evidence. Capability and timing are separate cells.
Eligibility is a disqualifying set tracked apart from criterion cells, so a
user waiver actually reaches the tie-break instead of leaving a decision with
no effect. Both no-winner paths are total: disqualification, and ranking-loss
wipeout when nobody renders the deciding rung.

Maintenance inputs are reproducible rather than selectable: upstreams pinned
per candidate before any figure is fetched, composites taking the worse value
per axis, maintainer identity machine-observable, the issue snapshot archived,
and unanswered issues contributing infinity rather than their current age --
which would otherwise score twenty ignored issues opened yesterday as
excellent responsiveness.

What the spike is not: the app. No command registry, no editing, no document
layer, no goldens. Ruling D creates epiphany-editor-app fresh after this
verdict. And it does not reopen Ruling A -- a finding that no candidate can
implement the granted architecture escalates as an amendment request and does
not silently promote the SVG path back.

Docs only; no code, no crate, no gate movement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
2026-07-28 17:26:23 -04:00
Levi Neuwirth 7df5ca17ce Genesis G2a: two settings setters, and the vocabulary's documentation catches up
SetCanvasLayoutDefaults and SetSpellingPrecedence, kinds and tags 32/33, on the
SetMetadata LWW pattern: advisory last-writer-wins, no conflict, no idempotence
short-circuit, seeded from the base so a value-restoring undo of the first
operational write reaches the pre-operational value. Both carried types already
had Codec impls, so the packet designs no wire layout -- canonical_value!
delegation and one push_lp_bytes each, inheriting strict canonical-form
rejection rather than writing it.

Both stamp schema major 0 unconditionally, staying in the catch-all arm. The
op-block accept-set is untouched at 2; epiphany-bundle has an empty diff. The
raise to 3 belongs to G2b, and the chunk schema minor -- a MUST no writer has
ever honoured -- belongs to G-minor, filed as P13-S14 and deliberately not
fixed here.

Most of this commit is documentation the vocabulary had outgrown. G1 shipped
five normative falsehoods because its contract declared the wire surfaces out
of scope, and Push 4a left TransposeInterval out of both of core_spec's
normative listings. So binary_format's payload-layout and tag tables now reach
33 rather than 30, its snapshot-only bullet records that it is empty and why,
two spelled-out payload counts move from eight to nine, and core_spec's
listings gain all four missing kinds. The Pass-12 K8 doctrine is narrowed
rather than reversed: the root and canvas remain structural givens no operation
mints, and only the claim that the score's contents arrive outside the
operation set is superseded.

Two generators were stale by two tranches and fed corpora other suites treat as
exhaustive. layout_stub's is now derived from PAYLOAD_FREE with an explicit
Registered append instead of a hand-written match, so future built-ins follow
structurally; s10 asserts PAYLOAD_FREE union {Registered} so deleting that
append cannot pass.

Companion 0.8.0 -> 0.9.0, binary format 0.11.0 -> 0.12.0, catalog 0.9.0 ->
0.10.0 (covering G1 retroactively). 102 decode vectors, regenerated.

Coordinator verification: 1371 passed / 0 failed, clippy 0 warnings, fmt clean,
conformance 8/8 and 9/9, requirement_labels 6/6, four PDFs at 0 undefined
references, goldens byte-identical, accept-set confirmed still 2. Mutations
s3/s5/s7/s10 re-run independently and each observed to kill its test.

Two findings beyond the contract. max_supported_major is not reachable outside
epiphany-bundle -- mod bundle is private and the symbol is absent from its
pub use -- so the gate's "assert it in code" was unsatisfiable without touching
a crate the contract forbids; verified by reading the unedited source instead.
And core_spec carries two P12-K8 references, not one; the contract named only
the first, so the second is annotated here for consistency.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 17:07:46 -04:00
Levi Neuwirth 4a4988ce88 The latency wall is document-lifetime, not one sitting
Review found the mitigation attached to the last landing reading the wrong
code path. EditorSession::open does start with an empty applied log, but that
is the score-only probe constructor. The savable-document path is Ruling B's:
reopen is full replay, stored envelopes load as a committed partition, and
materialization reduces committed plus session operations together. Nothing
resets the depth this bench varies until the checkpoint and pruning machinery
assigned to T4b can write a new canonical_base.

So consequence (e) is withdrawn rather than corrected in place. The ~4,500-edit
wall is a budget on a document's whole accumulated history, and there is no
session reset to lean on -- neither as reassurance about the number nor as
support for the sequencing argument, which rests on paint dominance and does
not need it. T4b's trigger is correspondingly firmer than it read yesterday.

Also: two comments still described the gated core portion as reduce plus
engrave, from before envelope construction was added as a third stage. The
sum they document has included it since the last landing.

Verified in an isolated worktree at HEAD rather than in the working tree,
which still carries the genesis tranche's in-flight work: fmt clean, clippy 0
with and without golden-gate, gate OK with every verdict unchanged (the edits
are documentation only).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
2026-07-28 16:27:59 -04:00
Levi Neuwirth 986c9cc5a1 The edit-latency bench measures the log a session actually writes
Review found the first version generating every envelope with an empty causal
context. Production gives only the root that shape: each later edit carries
active_prior_context(), the head's context extended by the head, so it covers
the whole active prefix. Reduction orders on those edges, so a context-free log
exercises a different algorithm -- and a cheaper one. Counters were wrong the
same way: EditorSession mints at authored.len(), so the root is counter zero,
which is also what extend_context recognises as the start of a contiguous run.

Remeasured on a session-shaped log, reduce is roughly three times its former
self at depth ten thousand -- 54 ms, not 17 -- and the wall moves from about ten
thousand edits to between three and five thousand. That is the number T4b is
sequenced against, so the first table would have mis-sequenced it. Depths three
and five thousand now bracket the crossing; sampling only decades hid it.

Two findings survive the correction and one is weakened. Reduce is still the
only depth-scaling stage, and is superlinear at about n^1.4 -- which does not
contradict the reduction bench's subquadratic result at fifty thousand
envelopes, because that log is generated across three replicas with a different
causal shape, and two logs of equal length are not equal work. Engrave is still
flat, at 260 to 327 microseconds, and is still the larger half of the core's
portion at shallow depth, so criterion 2's "uninformative while reduction
dominates" holds only past roughly depth five hundred. But "render dominates at
realistic depths" is now bounded: paint leads by four and a half times at depth
one hundred, is level by one thousand, and is left behind after. T4 before T4b
still stands -- the canvas removes what dominates a session's first thousand-odd
edits -- but the two are no longer comfortably separated.

The gate now includes envelope construction, which the requirement names first
and the first version silently dropped. It is forty nanoseconds and never moves
a verdict; a gate that omits a named component is a proxy for the requirement
rather than the requirement.

The 98% claim is replaced by both figures with their denominators named: what a
direct-IR canvas avoids is 83% of the full measured per-edit pipeline, and 99.8%
of the render path alone. The unqualified number was supported by neither.

One row changed marking for a reason worth recording. Depth four thousand passes
clean at 12.99 ms, but that is 78% of budget, and a load-contaminated run
measured it at 22.77 ms -- above the five thousand row, which is impossible
clean. A Pass row that fails whenever the machine is busy teaches people to
ignore the gate, so the last gated Pass is three thousand and four thousand's
clean number is kept as data in the table instead.

Also recorded: depth is per session, not per document. EditorSession::open
starts with an empty applied log, so reopening resets it and the reduced score
becomes the new pristine base. That is what keeps a four-figure wall from being
catastrophic -- though note entry mints one operation per note, so it is
reachable in a sitting.

Verified in an isolated worktree at HEAD rather than in the working tree, which
currently carries the genesis tranche's in-flight G2 work: fmt clean, clippy 0
with and without golden-gate, workspace tests green, gate OK across all five
rows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
2026-07-28 16:03:27 -04:00
Levi Neuwirth 4043627898 Contract G2a round five: the sixth hand-maintained site, and locking Registered
The literal-site count said four and named three plus two generator bounds. The
omitted one is OperationKind::discriminant() -- the hand-written match that
Push 4a got wrong in the first place, and the one the contract elsewhere notes
the tag macro does not guard. All six are now enumerated, with the count after
row 29's derivation stated as five so the next tranche inherits an accurate
number rather than this one's.

s10's layout-stub half asserted kinds 30-33, which would have survived deleting
the required Registered append -- PAYLOAD_FREE excludes it by design and the
generator's doc promises every variant, so the gap between them is exactly
where the bug would live. It now asserts PAYLOAD_FREE union {Registered}, with
removal of Registered as the mutation. Future built-ins follow structurally
with no test change; the one payload-bearing variant that cannot stays locked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 15:47:17 -04:00
Levi Neuwirth 2b86e8033a Contract G2a round four: three generator sites, and a grep I read and ignored
All three confirmed. Two of them contain SetMetadata and appeared in the very
grep the touch table claimed to be derived from, so the derivation claim is
struck rather than repaired: the evidence was on screen and went unused, which
is worse than not having looked.

generators.rs:647 and layout_stub.rs:951 are both already stale by two
tranches -- neither emits TransposeInterval or CreateInstrument -- and both
feed corpora other suites treat as exhaustive, so a missing kind is untested
everywhere downstream while every suite stays green. layout_stub's doc comment
claims every variant while its bound omits two. Its fix is structural rather
than additive: derive the built-ins from PAYLOAD_FREE and append Registered,
which payload.rs:1991 already does. New test s10 asserts both generators emit
all four appended kinds.

textproj_kind.rs:597 is a fourth hand-maintained literal count.

The pattern is worth naming: operation_kind_tag_vocabulary! exists because
Push 4a added TransposeInterval to a hand-written match and nothing else, and
its own doc says four hand-maintained lists stayed green. The macro made the
compile-enforced half safe, and these three lists went stale at that same
append anyway, because nothing forces an rng bound or a literal count to move.
Hence: prefer deriving over extending wherever a list can be derived, and
report the sites that cannot be.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 15:43:18 -04:00
Levi Neuwirth 5eac1c9bc7 Contract G2a round three: Push-4a listing debt, and the ladder order
TransposeInterval is missing from both Core normative listings, which carry the
older Transpose and never gained its successor at kind 30. So the vocabulary
has drifted from its normative listings for two tranches, not one, and both
listings need four additions rather than two. The contract's grep list gains
TransposeInterval, and names the signature worth hunting: a spelled-out count
that disagrees with the enumeration beside it.

for_major does not return {major, 0} unconditionally -- V0 is {0, 1}, and only
V1 through V3 carry minor 0. Corrected in the contract, the plan, and P13-S14.
The finding is unchanged: the function accepts only a major, so no per-kind
additive minor can reach it.

Ladder order is now explicit as G2a -> G-minor -> G2b, with the reason. The
sweep is scoped to kinds 24-33, which is what exists once G2a lands; running
G2b first appends kind 34 and would either grow the sweep mid-flight or ship 34
carrying the defect the rung exists to retire.

Core is five live-text edits plus one historical annotation, not five edits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 15:29:32 -04:00
Levi Neuwirth 47fb4266c4 Ruling A criterion 2 stops being an assumption
The staged interactive-edit latency bench: reduce / engrave / scene-build /
paint measured separately, gating the core's portion against
req:perf:single-system-edit-latency's 16.7 ms frame. Criterion 2 asserts that a
toolkit verdict is uninformative while reduction dominates, and that sentence
had never been measured; the sequencing question it governs -- T4's spike now,
or T4b's incrementality first -- was resting on it.

The stage split is not invented here. It is the seam EditorSession::materialize
already walks, read off its private render_score and reproduced stage for
stage, so the bench measures the pipeline rather than a model of it. Only reduce
and engrave are gated: the requirement bounds "the core's portion" and says in
its own words that edit-to-pixel latency is a product-layer obligation, so
charging the SVG serializer and resvg against a core budget would be a category
error. They are measured and printed because the ruling asks for the stages
separately, and because today's is the path Ruling A demotes -- the number is
the baseline a canvas must beat, not a budget to defend.

Four findings, in the order they matter. Reduce is the only stage that scales
with log depth, near-linearly, and it breaks the frame at roughly ten thousand
edits -- 17.26 ms against 16.7, a three percent miss, so an order of magnitude
rather than a threshold. Engrave is flat and small at ~280 microseconds, and at
shallow depth it is the larger half of the core's portion, which qualifies
criterion 2 rather than confirming it: reduction does not dominate until about
depth five hundred. Paint is the largest single cost at every realistic depth --
2.12 ms at depth one hundred is four and a half times the entire core portion.
And scene-build is 3.5 microseconds of IR work plus about 130 of SVG
serialization, which the no-feature run separates: a canvas consuming the IR
directly skips some ninety-eight percent of today's per-edit cost, none of it in
the core.

The sequencing answer is therefore that T4 before T4b stands, for the opposite
reason to the one assumed. The dominant cost at the depths real sessions reach
is the render path Ruling A already demoted, not reduction. T4b's trigger is a
session ten thousand edits deep, and the bench now watches for it as the one
Xfail row.

Two things the bench had to survive being wrong about, both mine. The depth-1000
row was drafted Xfail on the assumption Fact 8 would already bite; it passes
with eightfold margin, the gate's XPASS notice said so, and the row is promoted
here rather than left stale -- which is the whole point of that mechanism. And
the first edit log alternated transposition direction per operation, which is
degenerate when the pitch-list length is even: every edit to a given pitch
pushed the same way, drifting it twenty-five semitones by depth 1000 and would
have been two hundred and fifty by depth 10000. That inflated engrave by a
factor of two and paint by nearly three -- a score-content change wearing a
log-depth costume. Alternating per pass instead bounds drift to one semitone.
The residual content effect is documented rather than hidden: paint is
non-monotonic in depth because pass-count parity decides how many accidentals
the score carries, and reading its dip at depth 10000 as a scaling win would be
a mistake.

Stated limitation: the testkit's largest fixture is three staves by ten
measures, so the engrave and scene-build columns are lower bounds and this
cannot prove the budget holds on the hundred-page orchestral score the
requirement contemplates. It shows where the time goes at the scale we can
build, and a row that misses at this size misses by more at a real one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
2026-07-28 15:04:36 -04:00
Levi Neuwirth c39e58a755 Contract G2a round two: the schema-minor debt, and a test that could not fail
All four findings verified against the tree before fixing; all four hold.

The schema-minor MUST (binary_format.tex:2330) has never been implemented.
SchemaVersion::for_major accepts no minor and returns {major, 0}, and both
staging paths derive only the major, so kinds 24-27, 28-29, 30, and 31 already
carry no additive-version record -- and the requirement's own rationale is
exactly what the gap defeats: a reader meeting an appended discriminant cannot
tell a stale vocabulary from damaged bytes. Filed as P13-S14 and ruled a
separate rung after G2a, sweeping 24-33 in one retroactive pass rather than
blocking G2a on a debt eight kinds deep or paying for two partial sweeps. G2a
now says explicitly that it extends the violation by two, knowingly, and
forbids working around the absence.

s7 could not fail. WorkingSnapshot::restore reassigns the whole graph
independently of every write chain, so omitting a chain leaves stale history
while the field still rolls back -- the prescribed assertion passed under its
own mutation. Two framings of this test were wrong; the third asserts against
a later undo's predecessor, and the contract now requires the mutation be run
rather than reasoned about. s8's mutation was impossible as written: each
payload has one field, so there are no adjacent fields to swap. Swapping
discriminants 32/33 in both halves is the self-consistent mutation that leaves
round-trips green and kills correctly-named literal vectors.

The normative repair surface doubles: eleven sites across four documents, five
of them G1 debt. Core's normative OperationKind and OperationKindTag listings
are missing CreateInstrument as well as both new kinds; the catalog's
value-restoration family list is normative for undo and omitting a family is a
silent semantic gap; two spelled-out payload counts move. Since two independent
reviews each found sites the other missed, the list is a floor and the contract
now prescribes grepping the load-bearing phrases.

Also: core_spec said "two edits" and prescribed more, and the split-cost
accounting counted only Text Projection when G2b repeats the Binary Format and
Operation Catalog work too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 14:48:10 -04:00
Levi Neuwirth f639919ee1 Editor T4-pre W3: the text run carries both its string and its ink
The last T4 prerequisite, and the one that had to be a ruling rather than a
packet: a canvas, an exporter, a hit test, and an accessibility tree must agree
about text, and what they agree on is decided by where the shaper runs relative
to the canonical boundary.

The census reframes the tranche. Three of the five categories the plan names --
lyrics, chord symbols, rehearsal marks -- carry no text in the model at all:
LyricLine holds only event references, ChordSymbol and Marker only an anchor.
They are blocked on a core-track schema major, not on this decision. What the
primitive does gate is the text the model already has: score metadata,
instrument and staff names, and text-line spanners. That is a smaller v1 than
the plan implied and a real one, and bidi and fallback are exercised through
synthetic fixtures that need no model work.

The ruling is a fourth resolved primitive carrying the source string and the
canonical shaped result together. The alternative that discards the string
renders a title as anonymous outlines and is unreadable to a screen reader; the
alternative that discards the shaped result lets two consumers draw the same
bytes differently, which contradicts the definition of canonical_bytes as the
rendering fingerprint. Both halves stay, and the apparent trade between
deterministic geometry and accessibility turns out not to exist.

Two drafts were wrong in opposite directions and the errors are recorded rather
than quietly fixed, because each came from asserting a constraint instead of
reading the requirement that governs it. Draft 1 held that shaping before the
canonical boundary poisons cross-implementation byte equality -- but layout
determinism is byte-equal only within one implementation at a fixed version;
across implementations it is reference-suite thresholds, and the spec says so in
both the determinism table and req:solver:cross-implementation-conformance.
Draft 1 had imported the score layer's guarantee into the layout layer, where
the spec deliberately weakens it. Revision 2 then over-corrected, banning host
fonts outright on the grounds that an OS font update breaks fixed-version
stability -- but that requirement defines identical inputs to include font
metrics referenced by version and content hash, so an updated font is a changed
input. The rule that survives is narrower than either: no ambient or unresolved
lookup, and a host face may participate only once resolved to an exact
content-hashed asset every consumer can obtain.

The identity is specified rather than gestured at, because bytes that do not
determine ink are worse than bytes that admit they don't. A face is pinned by a
hash over the font file, not its metrics -- GlyphCatalogIdentity's metrics_hash
covers bounding boxes, advances and anchors, which pins spacing and not shape --
together with face index, variation coordinates and synthetic weight/slant.
Segments carry font-internal glyph ids, source ranges, direction, script,
language and em size; glyph offsets have alignment already applied, so a
consumer places by origin alone; positions quantize on the same 1/1024 grid as
every other primitive. The cluster map indexes UTF-8 byte offsets with caret
stops at grapheme boundaries carrying bidi affinity, and the Unicode
segmentation version is always part of the identity -- otherwise two
implementations could agree on every pixel and still differ inside the
fingerprint, where no visual test would ever see it.

One consequence lands on the exporter: SVG cannot honour "no consumer reshapes"
with <text>, which carries characters and lets the viewer's shaper choose the
glyphs, so a ligature or positional form silently draws something the layout did
not resolve. Conformant text export emits explicit glyphs as paths through the
same face, reusing the mode render-svg already has for music.

The reservation is re-ordered to follow shaping rather than precede it -- with a
canonical shaper in the pipeline, reserved_box becomes a solver policy over
measured bounds, not an estimate of them. Paint-time re-spacing stays forbidden.

Two findings for the core track, named so their absence is a decision. Score
text authored through operations is not NFC-validated: the envelope's NFC-
checked string reader covers only directly encoded strings such as transaction
labels, while SetMetadataOp, CreateStaffOp, CreateInstrumentOp and the cross-
cutting values embed the core codec's bytes, which preserve non-NFC strings by
design. And the .tex amendment adding the primitive changes the layout
fingerprint but needs no bundle or wire schema-major move, following strokes and
curves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSX4zSLgKvtiXaPjnMqLGz
2026-07-28 14:38:06 -04:00
Levi Neuwirth 4a4a766d4b Contract G2a review fixes: the normative surfaces, and a G1 debt
All four findings verified independently against the tree before fixing; all
four hold, and the first is worse than filed.

P1 (normative wire docs) is not a G2a omission but a G1 regression. 3b09595
touched operation_catalog.tex and text_projection.tex and no other .tex, so it
shipped four falsehoods: binary_format's payload-layout and tag tables stop at
kind 30 while 31 is assigned in code, and req:binfmt:kind-discriminants says
each row pins the payload's byte layout; binary_format:2432 still asserts there
is no CreateInstrument; core_spec:12186 still says Canvas.layout_defaults and
Instrument.range reach only the non-canonical snapshot; and the catalog gained
a CreateInstrument section with no version bump against its own convention.
G2a now repairs all four and adds its own two surfaces, with explicit version
and changelog dispositions for binary_format (0.11.0 -> 0.12.0), the catalog
(0.9.0 -> 0.10.0, covering G1 retroactively), and two narrow core_spec edits.

The core_spec:5114 amendment is pinned rather than left to a subagent. The
K8 paragraph has a load-bearing half the ruling does not touch -- root and
canvas remain structural givens no operation mints -- and only the
outside-the-operation-set clause is superseded.

P1 (source) adds ops/src/lib.rs re-exports and the textproj parse.rs literal
HEADER fixture, whose tracking test is a tripwire that fires by design.

P2: s8 and s9 gain the mutations they lacked, and s2's was fabricated --
set_metadata contains no comparison to reverse, it records and overwrites
unconditionally, so the mutation is now setter-local first-write-wins. s7's
framing was backwards: WorkingSnapshot is transaction rollback, so omitting a
chain retains a failed transaction's write rather than losing history, and the
wrong framing would have sent the test hunting the wrong symptom.

Records the G2b holdout: ScoreTuningContext's Codec deliberately drops
accidental_extensions while OperationSet::accept stores envelopes as objects,
so a naive full-value SetTuningContext diverges between a live session and the
same document reloaded -- and canonical_value! cannot see it, because its
check compares bytes and never the originating value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjsEnYhm1gPpf6ii2iFxFV
2026-07-28 14:35:53 -04:00