Commit Graph

151 Commits

Author SHA1 Message Date
Levi Neuwirth 357e8b9eeb Push 4a design gate: pin the transpose algebra, freeze the old operation
An audit reopened P12-K2, whose Pass-12 pin promised the repair would be "a
payload schema-major landing with the Chapter 4 tuning catalog". Both halves
were wrong, and the operation was more broken than the pin admitted.

Measured through EditorSession, not inferred. On a C4: +12 yields alteration
12 (six double-sharps, not C5); +128 clamps to 127 and still reports Applied;
targets [p, p] transposes twice; a non-Cmn position is silently untouched.
transpose(1000) then transpose(-1000) lands on -128, so the operation is not
invertible. Nothing downstream is at fault -- prepass::accidental_ids renders
alteration 12 faithfully. The defect is entirely in what Transpose means.

The false coupling is why this looked big. Pitch has orthogonal scale_position
and acoustic fields. Transposition adds an interval to a scale position;
tuning decides what frequency a scale position sounds at. Adding a fifth to C4
needs no tuning catalog. The same weld had spread: PitchSpaceMismatch was
"Reserved: requires the Chapter 4 tuning catalog" (it reads a discriminant),
and TranspositionInterval was "ADVISORY until the Chapter 4 tuning catalog
pins interval algebra". Push 4 splits: 4a is the algebra and needs no catalog;
4b is the catalog, which has its own blockers (cmn-24 is in the pitch-space
table but cannot exist while Cmn.alteration is i8 semitones).

Ratified by the user:

- New kind, freeze the old. An operation is history; a corrected reduction
  rule would rewrite every score that used one. Transpose (disc 9) keeps its
  exact semantics, now written as normative replay semantics rather than as
  apologies. TransposeInterval takes disc 30. This is cheap: appending a kind
  at >= 30 is a schema MINOR, and the payload's constituents are all major-0
  layouts, so it stamps major 0. No major 3, no migration.

- Diatonic + chromatic interval, reusing TranspositionInterval -- which
  already existed in graph.rs at major 2 for Instrument.transposition, already
  codec'd, byte-for-byte the required pair. Minting an Interval beside it
  would have been a second normative listing of one type, the drift P13-I1
  just closed. Declared once now, in Chapter 2; Chapter 5 references it.

- Atomic refusal. Non-Cmn, AbsoluteHz, or an out-of-range result refuses the
  whole operation. Never saturate, never partially apply. Tombstoned and
  SYSTEM_DERIVED targets are still skipped: a deleted pitch is not an
  untransposable pitch, it is one the operation has nothing to say about.

targets becomes CanonicalSet<PitchId> at the type level, not a Vec plus a
dedup() someone can forget (PitchId's Ord is its canonical byte order). This
was never a convergence bug -- every replica replaying [p, p] double-
transposes identically -- but a canonicalization one. It is free today because
no operation-payload decoder exists yet; once one lands in Push 5, dedup
normalization would change the meaning of stored operations. Push 4a blocks
Push 5, and that is why.

Spec: req:pitch:transposition (algebra + the three refusals), and four
req:opcat:transpose-* requirements. Operation Catalog 0.7.0 -> 0.8.0; Binary
Format 0.6.0 -> 0.7.0 (disc 30, and a seq-strictly-increasing notation whose
decoder must reject a duplicate rather than normalize it away).

This commit is the design gate: the spec now declares MUSTs the code does not
yet satisfy -- editor-core still authors Transpose, and TransposeInterval does
not exist. The implementation follows in this push.

Also recorded: the two existing transpose tests are false locks. Gutting
graph_transpose_pitch leaves both green -- they call base-free reduce(), where
graph is None and the function never runs, and assert only OperationEffect.
Only editor-core's undo_and_redo_a_transpose, three crates away, catches it.

Gate: clippy 0, 30 targets / 964 passed / 0 failed, docs 0 under -D warnings,
conformance 8/8, all three spec documents build with no undefined references.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:25:27 -04:00
Levi Neuwirth beb61fca2f P13-I3: delete the hand-written notehead anchors; teach the extractor
BRAVURA_METRICS' NOTEHEAD_ANCHORS were hand-derived where every neighbouring
number in the table is machine-extracted from the SHA-pinned font -- the same
mistake as inferring band ownership downstream instead of reading it from the
source that had it. And they were wrong under any reading: they named
stemUpNW/stemDownSE, the corners a normal notehead's stems do not attach to and a
pair Bravura's noteheadBlack does not define, with an x of 1180 that reads as 1.18
staff spaces written in thousandths rather than the table's 1/1024 units (1.18 sp
= 1208). Nothing consumed them.

Deleted rather than corrected. The font is not vendored, so the true values cannot
be verified in-tree, and shipping data we cannot stand behind into a hash every
conformance claim declares is worse than shipping none. Verified empirically
before deciding, not reasoned about: changing the anchors breaks nothing in-tree
-- 30/30 targets, zero golden churn, no pinned literal hash -- so
GlyphCatalogIdentity moves once, now, while no claim declares the old one.

extract_bravura_outlines.py gains --anchors, emitting them from the pinned
bravura_metadata.json (anchors live in the SMuFL metadata, not the glyf bounds,
which is why the outline extraction never covered them). SMuFL anchors are points,
so they round to nearest -- unlike a bbox, which rounds outward so the metric box
contains the ink. The metadata's SHA-256 is deliberately left UNPINNED and
verify() now refuses an unpinned source, printing the digest to paste: the script
cannot regenerate anchors until an operator with the font pins it in a reviewable
commit, which is the discipline the other two sources already have.

The test guarding the anchors proved nothing. anchors_participate_in_the_hash
compared noteheadBlack (anchored) against noteheadWhole (not) and asserted they
hash differently -- but their advance and bbox differ too, so it would have passed
with the anchors ignored entirely. It now varies the anchors while holding every
other field fixed (presence, one coordinate, one name), against synthetic metrics
through a factored-out metrics_hash_of.

Pass-13 Batch 2 CLOSED. All three candidates resolved; two grew when examined.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 14:53:47 -04:00
Levi Neuwirth afb487c24d P13-I2: Staff::default_clef is the fallback, not decoration
to_constrained took the active clef from the staff instance's clef_sequence and
fell back to Clef::default() -- treble. A staff that declared its clef only on the
Staff, with no ClefChange, therefore drew a treble clef and placed every note
against it. The field was decorative in the projection.

It is the fallback. StaffContent now carries default_clef -- the clef belongs to
the Staff, the sequence to the StaffInstance, and resolving "the clef at time t"
needs both -- and active_clef_or(clefs, at, default) resolves against it.
active_clef remains as that with the treble default, for callers with no staff to
hand, so the public API is intact.

The part worth pausing on: epiphany-editor-core reads the same function for
hit-test pitch resolution. Fixing only the projection would have left a click on a
bass staff resolving its pitch as treble -- the engraved clef and the editor
disagreeing about what note is where. Both now go through active_clef_or.

Removal was rejected: the field is named for its purpose, is encoded on the wire,
and dropping it would be schema-major.

Zero golden churn: every fixture and generator declares treble, which is also
Clef::default(), so nothing that exists today moves. Locked by
a_staff_declaring_only_a_default_clef_engraves_in_it and mutation-verified by
restoring the Clef::default() fallback. Scoping that test by provenance was
necessary -- valid_score_rich has three staves and only one was re-clefed, so the
first assertion I wrote ("no gClef anywhere") failed against a correct fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 14:47:42 -04:00
Levi Neuwirth b7dab0fd44 Pass 13 reopens; P13-I1: complete the ConstrainedLayoutIR listing
Three parked candidates accumulated while the Standard-tier solver track closed
and the notation-quality pass landed, which is the threshold at which the house
rule opens a batch pass. PASS13_CANDIDATES.md reopens as Batch 2 with P13-I1
(this), P13-I2 (Staff::default_clef never consulted), P13-I3 (the notehead stem
anchors).

P13-I1 was filed as "two elided fields". Checking before writing turned up a
third, and it is the one that matters: `diagnostics: Vec<LayoutDiagnostic>`
appears NOWHERE in core_spec, though it is how the projection's honesty rule
manifests. So Chapter 7's listing gains break_origins, diagnostics, and catalog;
BreakOrigin and LayoutDiagnostic gain their shapes.

And it gains req:layoutir:coverage-diagnostics, ratified as implemented: an
object the projection cannot engrave faithfully -- a pitch with no resolved
spelling, a glyph the bound catalog does not carry -- MUST be recorded as a
LayoutDiagnostic AND still placed, as a fallback notehead or a zero-extent traced
anchor. It MUST NOT silently substitute a plausible shape, and MUST NOT drop the
object. Both halves matter: dropping it breaks the round-trip surjection, so a
hit-test can no longer find what the author wrote; guessing produces a score that
looks engraved and is wrong, with nothing in the IR to say so.

Locked by an_unengravable_object_is_recorded_and_still_placed, which drives a
percussion clef (no bundled glyph) and asserts all three halves: the diagnostic
names the object, no glyph stands in for it, and a zero-extent anchor keeps its
provenance addressable.

Spec rebuilds clean, no undefined references, new label registered. No code
change beyond the test; zero golden churn.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 14:42:32 -04:00
Levi Neuwirth 86635e43e1 Two-sided inter-staff renegotiation, and the cascade defect it uncovered
ENGRAVER_VERSION 11 -> 12. The inter-staff solve now closes a slack pair as well
as opening a crowded one, realizing the InterStaffGap band's declared height
exactly. SYSTEM_STAFF_PITCH is demoted from a floor to an initial arrangement the
solve fully renegotiates. This is what vertical_density_penalty was reporting: an
un-pressured multi-staff system sat at 0.739, honest sprawl against the declared
gap, because the axis is symmetric and the solve only ever expanded.

The band's height had no agreed meaning, so pin it: it is an INK CLEARANCE -- the
separation between the two staves' outermost content, exactly the unit
req:qmc:vertical measures. preferred 2.0 -> 5.0, min 1.0 -> 2.0. The old 2.0 was
a placeholder reconciled with nothing: neither the 8.0 staff-box gap the fixed
pitch of 12 produces, nor the ~6.4 ink clearance it leaves for plain content.
Realizing it would have crushed a relaxed system to a pitch of ~7.6. At 5.0 plain
ledgered content settles near a pitch of 10.6.

Making the solve two-sided immediately exposed a CASCADE DEFECT latent since v11.
The recurrence subtracted the upper staff's shift from the measured gap and then
added it back through the accumulator, so every pair below the first was
over-separated by exactly the shift above it. Both staves move; the relation is
shift_lower = shift_upper + target - (upper_lo - lower_hi), the UNSHIFTED gap.
three_staff_close_content's lower pair realized 21.06 against a declared 4.0. It
was invisible on two-staff fixtures (shift_upper = 0) and invisible to
inter_staff_shifts_cascade_down_three_staves, which asserted only s2 > s1 -- true
under both the correct and the double-counting recurrence.

What caught it was the metric measuring realized clearance back from the BAKED
output instead of the solve's own extents. Reading back solver intent would have
reported 0 and shipped the over-separation again. That design choice was made one
commit earlier for exactly this reason; the catalog rationale now recommends it to
any conforming implementation.

Once the solve realizes each declared clearance exactly, every inter-staff unit is
0 on a healthy solve -- the axis becomes a solver self-check, and its MEAN can no
longer distinguish "measured every realization" from "measured one". So
vertical_raw is split into vertical_units and the regressions assert the unit SET.
Four mutations verified: the double-counting recurrence, expand-only, the
glyph-members band filter, and first-system-only measurement each fail a named test.

No normative change, no version move: QMC formula, units, anchors, thresholds all
untouched; only its non-normative rationale is refreshed. Churn is the two
multi-staff engrave goldens: two_staff grew by exactly 3.0 (the target change, no
cascade); three_staff SHRANK by 9.06 -- the same +3 per pair, less the 17.06 of
over-separation the defect was adding. Single-staff and every stub golden are
byte-stable.

The 5.0 was the user's call. 4.0 ("one staff height") was chosen first and
withdrawn once its true consequence -- pitch 9.57, not the 11.04 an arithmetic slip
of mine had projected -- was measured rather than inferred. The slip: deriving
plain-content ink clearance from an aggregate metric by assuming two contributing
units when it had three.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 13:32:37 -04:00
Levi Neuwirth 5b016f33b7 Two more glyph-members assumptions in the vertical-density axis (QMC 0.3.0)
Review follow-up on 43215c7. Both findings were right, and the second falsified a
comment I wrote in that very commit.

1. Region staff bands were still identified by glyph `members`. vertical_raw
   measured content over all primitives but decided WHICH staff bands belong to a
   region by glyph membership -- reintroducing the assumption the change exists to
   shed. A staff band is allowed to own no glyphs: to_constrained emits one per
   staff of the region regardless, and a percussion-clef staff (no bundled glyph,
   so it engraves to a traced anchor stroke) with no notes owns only staff-line
   strokes. Membership now comes from content presence in one of the region's
   systems, which identifies the band exactly -- a staff band is per-(staff,
   region), so its content can land nowhere else.

2. Only the first realizing system was measured, justified by a comment claiming
   rigid system translation makes every realization agree. The inter-staff solve
   had just falsified that: it sizes each system's gaps from that system's own
   content. req:qmc:vertical now counts ONE UNIT PER REALIZATION, matching how
   realized inter-system gaps were already counted. That is a contributing-unit
   change, so unlike 43215c7's clarification the catalog moves: QMC 0.2.0 -> 0.3.0
   (the P12-I12 precedent). Raw formula, anchor, orientation, thresholds unchanged.

Two new fixtures, because an unexercised fix is what I criticised last round:
percussion_placeholder_staff (a valid, invariant-clean score whose lower band owns
zero glyphs and six strokes) and two_staff_wrapping_pressure (one region, two
systems, staff-line gap 15.93 where it collides and 7.87 where it is slack). Both
mutation-verified: the members filter scores 4.8e-7, first-system-only scores
1.3e-7 -- each ~0 where the corrected axis reports real deviation.

What the per-realization count exposes is not comfortable, and is recorded rather
than smoothed over: two_staff_wrapping_pressure now scores 0.739. Its pressured
system solves to the declared gap exactly; its slack system sits at ~5 staff
spaces against a preferred 2.0. The axis is symmetric -- a gap wider than
preferred is sprawl exactly as a narrower one is crowding -- and this solve only
expands, never compresses. The deferral "compressing an OVER-wide fixed gap toward
preferred ... rarely wanted" is promoted to measurably wrong. Named, not fixed:
compression is a layout change (golden churn, ENGRAVER_VERSION move), not a
measurement one.

Adjacent finding, parked: Staff::default_clef is never consulted -- to_constrained
takes the active clef from the instance's clef_sequence and falls back to
Clef::default() (treble), so a staff declaring its clef only on Staff engraves as
treble. Verified (no layout-ir consumer reads the field). Filed in layout-ir
DECISIONS with the ConstrainedLayoutIR listing gap, pending a >=3-candidate batch.

Measurement-only: no layout change, ENGRAVER_VERSION stays 11, zero golden churn.
Gate green; QMC PDF rebuilds clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 11:39:28 -04:00
Levi Neuwirth 43215c7d67 The inter-staff gap band becomes a height model; fix a non-conforming metric
Two items the inter-staff solve deferred turned out to be one thing -- and it was
not the "metric-vs-solver tension" I filed it as. The catalog was right; the
engraver was non-conforming.

req:qmc:vertical has always defined the realized inter-staff gap as the separation
"between the adjacent CONTENT EXTENTS the band separates". vertical_raw measured
the separation between the two bands' glyph `members`, because until primitive
band ownership (efaebb9) a band listed no strokes or curves to own. A staff's
outermost ink is usually not a glyph. On two_staff_close_content the solve cleared
the declared 2.0 gap exactly, while the glyph-ink gap was 5.06 -- so the axis
reported |5.06-2|/2 = 1.53, saturated to 1.0, and fired a Standard-tier floor
warning on a correct layout. The metric was charging the solver for the ledger and
slur ink it had made room for. Axis now reads 2.7e-7; the warning is gone.

Two design calls worth naming:

  - The geometry is read back from the BAKED output, not from the solve's own
    staff_ext. Reading back solver intent would make the axis circular and blind
    to exactly the bug class that bit twice this week; now a shift the bake fails
    to apply to some primitive class surfaces as a real deviation. CastLayout
    gained stroke_system/curve_system for it -- a stroke carries no spring slot,
    so system_of_slot cannot answer for it.
  - The solve now targets the preferred_height of the InterStaffGap band
    to_constrained emitted for that staff pair, not VerticalBand::inter_staff_gap's
    default. That is what makes the band a height model rather than a constant:
    solve and metric agree by construction, not by both calling one constructor.

NO version move. Formula, contributing units, anchor, and normalization are
unchanged -- only a wrong measurement was. This is the P12-I11 precedent
(engrave-side resolution), not P12-I12 (which redefined spacing_distortion's unit
and did move the catalog). The catalog gains a clarification of what "content
extent" means, since before band ownership that reading was arguably
unimplementable, which is why the defect survived. Its stale rationale (still
claiming the vertical spring solve is deferred) is refreshed, and the axis's
inter-system half is recorded as a genuine trade-off against page_fill_efficiency
rather than a defect, so it is not re-filed as a bug.

Measurement-only: no layout change, ENGRAVER_VERSION stays 11, zero golden churn.
Mutation-verified: reverting vertical_raw to glyph-only measurement scores 1 and
fails the new assertion. Gate green, QMC PDF rebuilds clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 11:16:31 -04:00
Levi Neuwirth ab411f4cae Reconcile a process claim: park the listing gap, do not fake a Pass-13 row
ced4b72's ratification log said the ConstrainedLayoutIR listing gap was "tracked
as a Pass-13 candidate", but PASS13_CANDIDATES.md declares that batch CLOSED with
no open candidates -- and the house rule opens a pass only at >=3 candidates. The
claim pointed at a ledger that says the opposite.

Filed where the rule wants it instead: layout-ir DECISIONS records the two fields
the listing still elides (break_origins, catalog), why neither blocks an
implementer (both are governed by requirement text elsewhere), and that it is
PARKED awaiting company rather than reopening a pass on its own. The log now says
that too.

No code, no spec text.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 11:02:20 -04:00
Levi Neuwirth ced4b7255e Rescope the band-ownership MUST to the constrained stage; ratify it
Review follow-up on fc411ea. Finding 1 was correct and was mine to own: I wrote
"every primitive -- glyph, stroke, and curve alike -- MUST declare a
VerticalBandId" and placed it in the ResolvedLayoutIR section, where the adjacent
ResolvedGlyph listing has no such field. Under a literal read the implementation
was non-conforming against a requirement I had just added.

The rule belongs to the stage that has the obligation. req:layoutir:primitive-
band-ownership moves to Ch7 §ConstrainedLayoutIR §Vertical Bands and is scoped to
"every primitive the projection presents to the solver". A new companion,
req:layoutir:resolved-band-ownership, says what survives: a resolved Stroke/Curve
MUST retain its band (casting-off and the inter-staff solve both relocate them
and must attribute them), while a ResolvedGlyph carries none -- its ownership is
consumed during the solve and already baked into its resolved position, so the
field would be dead weight in a rendering fingerprint. That requirement also
carries the non-canonical clause: canonical_bytes omits vertical_band even from
the primitives that do retain it.

Finding 2: the ConstrainedLayoutIR listing gains strokes/curves. They have been
in the code since staff lines and were never listed -- so an implementation
following Chapter 7 could not have supplied non-glyph ownership to the solver at
all, which makes this the listing gap that actually blocked the new MUST.

Finding 3: the ratification trail now records the tranche -- a revision-history
row and a PASS12 log section naming both requirements, the three listing
corrections, the two commits, and the fact that adopting it churned no golden
(the declared owner agrees with the inferred one across the corpus, which is what
licensed the swap).

The log also records a pre-existing listing gap I found while checking my own
citations: ConstrainedLayoutIR still elides break_origins and catalog. I had
first written that neither is named anywhere in core_spec, then verified and
found both types ARE specified (req:layoutir:break-origin-attribution; Ch7 §Glyph
Catalog Identity) -- only the struct listing is incomplete, and unlike
strokes/curves neither gap blocks an implementation. Left as a Pass-13 candidate
rather than widened into this tranche.

Spec rebuilds clean: no LaTeX errors, no undefined references, both new labels
registered. Workspace 30/30, conformance 8/8; no Rust changed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 11:00:09 -04:00
Levi Neuwirth fc411ea7ac Ratify primitive band ownership in Chapter 7; scope canonical_bytes' doc
Review follow-up. Both findings were about documentation lagging a contract the
code now enforces, and the first is the one that matters: band ownership is the
solver-facing correctness contract, so it belongs in the spec, not only in a
crate's DECISIONS.md.

Chapter 7's ratified Stroke/Curve listings gain `vertical_band`, and a new
requirement (req:layoutir:primitive-band-ownership) states the rule as a MUST:
every primitive declares its owning band, the projection MUST name a band that
exists, and a vertical solver MUST take ownership from that declaration and MUST
NOT infer it from geometry. The rationale is recorded because the requirement
looks like a convenience until you see why it isn't: a stem shares its column
with the staff above, and a slur's endpoints are lifted clear of its own staff
into the zone where the nearest notehead belongs to the neighbour. A solver that
guesses from proximity tears such primitives off their notes when it renegotiates
the staff gaps -- which is exactly what happened, twice. The requirement also
records that only glyphs are band *members*, and that the reference is
non-canonical attribution metadata.

Stroke's stale "the vertical-band model does not contain" gloss is dropped.

canonical_bytes' doc claimed to encode "the full resolved layout". It encodes the
rendering fingerprint and deliberately excludes attribution metadata. Stating
that precisely required checking rather than paraphrasing: ResolvedGlyph drops
its band on the way out of the constrained stage, while Stroke and Curve -- whose
types are shared with that stage -- carry vertical_band through but never encode
it. Two layouts differing only in band ownership draw identically and hash alike.

Spec rebuilds clean: no LaTeX errors, no undefined references, new label
registered. Gate green, no golden churn.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 10:50:10 -04:00
Levi Neuwirth 430214d619 Pass 13 — P13-D2: tombstone before the graph delete, closing the cue-cascade trail
delete_event tombstoned the event in objects AFTER materialize_graph_delete —
but that graph pass cascades a cue among the event's referents, running
reanchor_for_tombstone over the cue's referents while the source event is still
Live. A slur bridging {X, cue-of-X} therefore re-anchored onto X
(Reanchored{to: X}) and then cascade-deleted when X's tombstone landed a line
later: a contradictory same-effect trail (candidate was "plausible by code
trace, unexecuted" — now executed; reverting the fix reproduces exactly that
two-record trail).

Fix: tombstone the event in objects BEFORE the graph delete, matching the
conventions cascade_cue and tombstone_undo_targets already follow (both
tombstone before their graph delete — which is why the undo path never had this
bug). The bridging slur now sees X already dead during the cue cascade and
cascades once.

Regression: deleting_a_cue_source_does_not_leave_a_contradictory_repair_for_a_
bridging_slur_p13_d2 (built on a cue-bearing base with a bridging slur; asserts
exactly one CascadeDeleted, no Reanchored). Verified to fail without the fix.
940 tests, convergence/conformance green.

PASS13_CANDIDATES.md: P13-D2 resolved — batch CLOSED (all four candidates done).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 18:25:37 -04:00
Levi Neuwirth 79c8e1da2e Pass 13 — P13-D1: run the ledger re-anchor for undo-driven event tombstones
An undo of an event-minting transaction tombstoned the events graph-side
(materialize_graph_tombstones → materialize_graph_delete re-anchors/cascades a
structure whose anchor died) but never ran the ledger-side
reanchor_for_tombstone — so a structure orphaned by the undo left the graph
while staying Live in objects, with no RepairRecord. Ch6's same-step
RepairRecord MUST was unmet for undo-driven tombstones (slurs/spanners/repeats).

Fix: tombstone_undo_targets now captures each event target's voice (before the
graph half clears voice_occupancy) and runs reanchor_for_tombstone per event
target after the graph half. The orphaned structure now cascades or re-anchors
in objects with a same-step RepairRecord, agreeing with the already-updated
graph — both use the same min-survivor rule, so they converge on existence and
target. reanchor_for_tombstone gains a liveness guard (skip a non-Live
structure) so the undo's own tombstoned mints — whose stale structures-index
entries linger — aren't re-processed into duplicate repairs; the direct-delete
path already drops tombstoned structures from the index, so the guard is a
no-op there.

canonical_bytes embeds both objects and the effect log, so this corrects the
reduced state (an inconsistency never previously exercised — no existing test
broke). Regression: undo_orphaning_a_pre_existing_slur_cascades_it_in_the_
ledger_p13_d1 (cascade + recorded repair + order-independent convergence).
939 tests, convergence/equivocation/conformance green. PASS13: P13-D1 resolved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 18:17:51 -04:00
Levi Neuwirth 382aff23a2 Pass 13 — P13-K1: reject a ModifyEvent introducing a never-minted system pitch
The K3 verdict for a system pitch introduced by a ModifyEvent replacement
(never minted — the collision pre-walk excludes ModifyEvent) differed across a
snapshot cut: in-session it slipped through (system_mints had no entry, so the
P12-K3 identity check saw nothing), yet post-snapshot it read
SystemDerivedContentImmutable once the pitch was re-seeded as a system mint.

Per the user's call ("reject the introduction"): modify_event now refuses a
replacement carrying a never-minted SYSTEM-DERIVED pitch id (replica
SYSTEM_DERIVED, not Live in objects) with TargetMissing, before the identity
check. The verdict no longer depends on the registry — the pitch is not live in
objects in either frame — so both refuse identically, closing the asymmetry.

Scoped to the system namespace, where the asymmetry lives (only system pitches
re-seed as system mints): a user-replica pitch carries no namespace claim and
has no snapshot asymmetry, so ModifyEvent may still introduce user pitch
content (the concurrent-modify tests rely on it).

Regression: a_modify_event_introducing_a_never_minted_system_pitch_is_refused_
p13_k1. 938 tests, convergence/conformance green. PASS13: P13-K1 resolved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 18:08:48 -04:00
Levi Neuwirth cd998142f5 Pass 13 — P13-D3: reject spanners anchored to a missing region/measure
A spanner anchored to a missing region/measure minted dangling: the
CreateCrossCutting mint checked only CrossCuttingValue::endpoints() (events),
so a region/measure TimeAnchor with no live target slipped past — the exact
sibling of the repeat mint gap fixed in Phase D.

Per the user's call ("fix the mint only"): CrossCuttingValue::anchor_object_refs()
returns the full anchor object set (events + a spanner's measure/region anchors;
wall-clock references nothing), and create_cross_cutting's liveness precondition
now checks it, so such a spanner is refused (TargetMissing) rather than minted
dangling. Deterministic across both reduction modes (the base seed registers
regions/measures in objects). endpoints() stays event-only — it feeds the
re-anchoring referent index, and non-event referent re-anchoring stays deferred,
ratified events-only (the spanner discipline).

Regression: create_cross_cutting_spanner_preconditions_region_measure_anchors
(missing region → refused; live measure/region → mints, invariant-clean).
937 tests, convergence/equivocation/conformance green. PASS13_CANDIDATES.md:
P13-D3 resolved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 18:01:08 -04:00
Levi Neuwirth e6308371d5 Push 3 slur review fix: measure slur_shape on whole slurs, not split fragments
An adversarial review of the slur-quality tranche found a real interaction bug
between curve splitting (commit 5869691) and slur_shape measurement (7d61271):
slur_shape_raw iterated cast.curves — the per-system SUB-CUBICS of a
break-spanning slur — and measured each fragment as a unit. A slur that is
ideally shaped as a whole (ρ ≈ 0.16, in-band) splits into sub-arcs whose
diagonal chords each read flatter (ρ below 0.08), so the whole slur earned a
spurious "too flat" penalty (confirmed ~0.088) and was double-counted —
contradicting the catalog's "a tier that draws the ideal shallow arc measures 0"
property.

Fix: measure the WHOLE slur curves of the constrained input.curves (one unit per
drawn slur — the engraver's arc-proportion decision), not the cast fragments.
Casting's horizontal re-spacing and system-splitting are spacing/rendering
concerns, not shape ones. The Quality Metric Catalog contributing-units
definition is clarified: the unit is the whole slur, measured once even when
split across a break.

Regression: a break-spanning in-band slur splits (≥2 segments) yet measures 0.
Still measurement-only (no ENGRAVER_VERSION bump, no golden churn, RS suite
unaffected). 936 tests, conformance 8/8.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 17:56:11 -04:00
Levi Neuwirth 449dd2ef8e Push 3 ratify: extend the slur-rendering requirement + refresh slur_shape
The three slur refinements (dashed rendering, curve splitting, slur_shape
measured) lifted into the spec — Chapter 7 / Quality Metric Catalog only,
non-canonical, so no wire form and no companion-version move.

core_spec Ch7: req:layoutir:slur-curve extended — an authored non-Solid
SpanStyle line renders faithfully (the LineStyle rides the Curve, whose listing
gains the field); a slur spanning a system break splits into per-system
sub-curves (de Casteljau; first segment keeps the slur's provenance, the rest
synthesized continuations). Only the curvature-computing algorithm stays
forward-referenced out now (dash rendering no longer deferred).

quality_metric_catalog: the §slur_shape rationale and the notated-but-unrendered
open question refreshed to record slurs now render and are measured; the formula
(req:qmc:slur) is unchanged, so no catalog version move.

Revision-history row (Push 3); a Push-3 tranche in PASS12_RATIFICATION_LOG.md;
layout-ir DECISIONS cross-ref extended. Both specs build clean (latexmk
-xelatex, 0 undefined refs). No code change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 17:33:11 -04:00
Levi Neuwirth 693150e43e Schema major 2 Phase F: ratify repeat + slur rendering into Chapter 7
The schema-major-2 push's rendering consumers (E1 repeat barlines/volta
brackets, E2 slur cubic-béziers) lifted into core spec Chapter 7 — layout-IR
only, non-canonical, so no wire form and no companion-version bump (Operation
Catalog stays 0.7.0, Binary Format 0.6.0), mirroring the schema-major-1 Phase-F
engrave-ratify-only precedent.

Three req:layoutir:* requirements added at §ResolvedLayoutIR:
- resolved-primitives: ResolvedLayoutIR carries non-glyph line Strokes (staff
  lines, stems, barlines, volta brackets — present since staff lines, never
  previously ratified) and cubic-bézier Curves (slurs, E2); each provenance-
  traced (the hit-test basis), re-spaced like a glyph, non-canonical. The
  struct listing gains strokes/curves and the Stroke/Curve shapes; the RenderIR
  provenance requirement widened from "originating ResolvedGlyph" to
  "ResolvedGlyph, Stroke, or Curve".
- repeat-render (E1): a barline-drawing RepeatStructure renders a repeat barline
  at each resolved boundary, each Volta a bracket with ending numbers;
  unresolvable boundaries draw no ink (honest placement); jump-kind marks and
  cross-region repeats deferred.
- slur-curve (E2): a Slur renders as a cubic bézier honoring CurvatureOverride;
  an endpoint not on a single staff of one region draws no curve; a non-Solid
  line style MUST be surfaced (diagnostic), not silently rendered solid.

The curvature-computing algorithm and the RenderIR encoding stay forward-
referenced out. Ch5 §Slurs / §Repeat Structures and the Ch7 Forward References
gain labels for the cross-references. Revision-history row + a schema-major-2
rendering tranche in PASS12_RATIFICATION_LOG.md; layout-ir DECISIONS carry the
Phase-F cross-refs. Spec builds clean (latexmk -xelatex, 0 undefined refs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 16:23:12 -04:00
Levi Neuwirth 58eef41c7e Phase D follow-up: anchor-site liveness and containment, all object kinds
User-review findings on 9b5339f — the Phase-D site-set unification was
incomplete in exactly two consumers that still collapsed anchor_sites()
to events:

- High: the mint precondition validated only TimeAnchor::Event targets,
  so CreateRepeatStructure with start naming a missing REGION (or a
  volta span a missing MEASURE) minted a dangling repeat straight past
  CrossCuttingRefsResolve. Fixed: anchor_object_refs (events + measures
  + regions; wall-clock references nothing) drives the precondition —
  deterministic across reduce()/reduce_onto(), since the base seed
  registers regions and measures in `objects`. Regression covers a
  missing region in start (base-free), a ghost measure inside a volta
  span (graph-aware), and the positive measure/region-anchored mint
  with invariants green. The referent INDEX stays event-only by design
  (the rule table repairs event tombstones — the spanner discipline).

- Medium: editor barrier containment derived only from event locations,
  so a repeat anchored solely to a protected region carried a default
  context and bypassed a region-scoped barrier. Fixed: repeat_context
  walks all anchor objects in anchor_sites order — event/measure sites
  bind (region, staff instance) via event_location/measure_location, a
  bare region anchor binds the region — used by both Create and Delete
  subject arms. Regression: a region-scoped barrier fires for a
  region-anchored repeat create and stays quiet for another region.

P13-D3 filed (spec/PASS13_CANDIDATES.md + ops DECISIONS): the SPANNER
family has the same mint-time shape (CrossCuttingValue::endpoints() is
events-only while anchor_target_exists checks all three kinds) plus the
non-event-referent-tombstone gap — pre-existing,
ratified-as-implemented; a catalog-semantics decision, not a Phase-D
fix.

Full gate: fmt, clippy -D warnings, rustdoc -D warnings, 30 workspace
suites, conformance scale 1 (8/8).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-07 20:18:29 -04:00
Levi Neuwirth 9b5339f8f3 Schema major 2 Phase D: the repeat-authoring pair (code tranche)
CreateRepeatStructure / DeleteRepeatStructure land end-to-end,
review-hardened (five-dimension workflow review; 19 findings triaged —
12 verifier agents lost to a session limit were verified by hand).

epiphany-ops:
- CreateRepeatStructureOp { repeat } (lp(RepeatStructure), wire 28,
  BORN AT V2: schema_major always 2 — kind/voltas are unconditional
  fields) and DeleteRepeatStructureOp { repeat } (bare id, wire 29,
  major 0: minimal stamping over a major-0 layout; the discriminant a
  schema-minor append). OperationKindTag 28/29 name-verbatim with
  validating decode; golden tables grown to 30; migrate/v0 identity
  arms; lib re-exports.
- Reduction: set-union mint (live-id => AlreadyApplied without value
  comparison — the cross-cutting discipline; RecreateContentMismatch
  scope unchanged) with the ALL-anchor-sites-live precondition;
  delete-wins tombstone; the "Repeat structure / Anchor" rule-table
  row wired into BOTH the ledger (reanchor_for_tombstone arm, min-id
  survivor, cascade when dry) and the graph (every dead site rewrites
  to the same survivor), agreement asserted by tests incl. the
  DalSegno jump-target case.
- Undo: materialize_graph_tombstones gains RepeatStructure — and the
  pre-existing SPANNER GHOST fix (an undone spanner mint never left
  the graph), regression-locked.
- validate.rs: VoltaEndingsIllFormed advisory (non-empty, 1-based,
  strictly ascending; interactive-only).
- Canonical-base honesty: blake3 re-pinned for the corpus stream
  shift, its comment corrected (the seeded repeat creates all no-op),
  and the property covered surgically by
  the_canonical_base_embeds_no_repeat_values (two reductions
  differing only in repeat v2 content => byte-identical bases).

epiphany-core:
- RepeatStructure::anchor_sites()/anchor_sites_mut(): THE single
  site-set walk (start/end, jump targets, volta spans), consumed by
  reduction, the editor barrier seam, the invariant walk, and the
  cross-reference index — replacing five hand-rolled copies and
  fixing a SIXTH, silently-stale one (indexes.rs had never learned
  the Phase-B kind/volta anchors; regression-locked).
- RepeatStructure joins the CanonicalValue seam (strict per-value
  decode); decode-fuzz corpus gains valid_score_rich_with_repeats
  (DalSegno + voltas) CORPUS-LOCAL — shared render fixtures stay
  repeat-free until E1 (zero golden churn).

editor-core / gui / testkit / layout-ir:
- Barrier seam: repeat_event_refs + subjects_of arms +
  graph_structure_endpoints arm, gated end-to-end by
  repeat_authoring_kinds_derive_subjects_and_gate_on_barriers.
- Generators: operation_payload -> below(30); graph-session gen ->
  below(14); ops fuzz gen_payload -> below(27); layout_stub
  gen_operation_kind_tag unstaled 24..=29 (the Phase-3 tags were
  already missing); GUI name table.

Spec touch-ups (review findings): catalog says "strictly ascending";
the survivor tie-break is now explicit in the core rule-table row and
catalog (identifier-order minimum among multiple survivors;
proximity-aware four-key selection deferred, as for spanners).

Trail: ops + core DECISIONS entries; spec/PASS13_CANDIDATES.md opens
the Pass-13 ledger at three candidates (P13-K1; P13-D1 undo-driven
tombstones bypass ledger re-anchoring — pre-existing class; P13-D2
cue-cascade repair-trail ordering).

Full gate: fmt, clippy -D warnings, rustdoc -D warnings, 30 workspace
suites, conformance scale 1 (8/8).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-07 20:06:18 -04:00
Levi Neuwirth b816144a43 Schema major 2 Phase D: ratify the repeat-authoring pair (spec tranche)
CreateRepeatStructure / DeleteRepeatStructure enter the ratified
operation set — the dedicated pair for RepeatStructure (repeats live in
the cross-cutting registry but are deliberately NOT CrossCuttingValue
wire kinds; the reconciliation note follows Ch6's CrossCuttingStructure
listing).

operation_catalog (0.6.0 -> 0.7.0):
- New K0 §"Repeat Structures": the full six-part schema for the pair.
  Set-union mint with the ALL-anchors-live precondition (start/end, the
  kind's jump targets, each volta's span — the mint must leave the graph
  satisfying reference-resolution invariants; dead anchor =>
  TargetMissing no-op); live-id re-create reads AlreadyApplied without
  value comparison (cross-cutting discipline; RecreateContentMismatch
  scope unchanged); delete-wins tombstone; create-undo tombstones the
  mint, delete-undo does not restore (P11-C8); volta well-formedness
  stays advisory.
- K1 gains the "Added in the schema-major-2 revision" entry (net-new
  primitives, never drafted as slots).

binary_format (0.5.0 -> 0.6.0):
- OperationKind wire table appends 28 (lp(RepeatStructure)) / 29 (bare
  identifier); OperationKindTag 28/29, name-verbatim projection;
  requirement bounds move past 29.
- Honest per-op stamping ratified: the CREATE is born at v2 (kind/voltas
  are unconditional fields — no lower-major payload layout exists, so
  every block carrying one stamps major 2 under minimal stamping); the
  DELETE's bare-id payload is a major-0 layout, so minimal stamping
  gives its blocks major 0 — the kind discriminant itself being a
  schema-minor vocabulary append (mechanism claim only; the stamp always
  follows minimal stamping, as discriminant 24's always-2 CreateStaff
  shows).
- §Schema Major 2 restructured: RepeatStructure moves from the
  snapshot-only bullet to the canonical operation layer (eight embedding
  payloads now); the CanonicalValue seam grows to twenty-three
  (RepeatStructure joins — CreateRepeatStructure embeds it per
  req:catalog:value-encoding).

core_spec:
- Ch6 OperationKind + Ch8 OperationKindTag listings gain the variants;
  the CrossCuttingStructure/wire-vocabulary reconciliation note; the
  re-anchoring rule table gains the "Repeat structure / Anchor" row
  (re-anchor to the nearest surviving anchor across EVERY
  event-referencing anchor site; cascade-delete only when none
  survives — the spanner discipline); Ch8's schema-versioning paragraph
  now enumerates eight embedding payloads with the born-at-v2/major-0
  split; revision-history row.

Review-hardened: five-dimension workflow review, 22 findings, 9
confirmed + 1 recovered from a failed verifier (an API-error casualty,
assessed by hand) — all fixed (the load-bearing ones: Ch8's stale
seven-payload enumeration; the mis-attributed "Phase-3 precedent" gloss
that would have taught a false stamping rule; the pair filed under
"Snapshot-only"; the CanonicalValue seam omission). All three documents
compile clean, zero undefined references.

Code tranche follows this commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-07 17:31:51 -04:00
Levi Neuwirth 7c9d1c40aa Schema major 2 Phase A: ratify the v2 data-model wire form (spec only)
Push 2's spec-first phase. core_spec (schema-major-2 revision): defines
the nineteen leaf types Ch5's ratified shapes referenced but never
pinned (SlurKind, CurveDirection, CurvatureOverride, the shared
SpanStyle record consolidating the identical SlurStyle/TieStyle/
SpannerStyle triplet, SubBeam, BeamGeometryOverride, SpannerKind's five
payload types + a leading Generic migration default, Volta,
MetadataValue, Timestamp, SoundConfiguration, TranspositionInterval
(structural, advisory until Ch4 per the P12-K2 discipline),
UnpitchedMember + member-resolution semantics (first-match, no-match
tolerated; StaffPosition vertical convention explicitly deferred),
SpaceUnit, LineStyle, StaffBracketKind); ratifies the visible-slice
clef/key content model into Ch5 (Clef/ClefShape/KeySignature/
ClefChange/KeySignatureChange) and retires the ClefId sketch;
strictly-authored metadata timestamps (req:graph:metadata-timestamps);
metadata `additional` = ordered list, duplicates permitted; Ch8 gains
the major-2 paragraph; Ch6 OperationKind listing gains its seven
missing variants (staleness fix).

binary_format 0.4.0 -> 0.5.0: new "Schema Major 2" section — per-
payload-type major assignment (snapshot-only Instrument/
RepeatStructure; SEVEN canonical embedding payloads incl. CreateRegion
transitively and SetStaffLayout's direct staff_lines_override field);
the MINIMAL-STAMPING rule (a block stamps the lowest major whose
layouts decode its bytes — deterministic, per-payload table given;
extends the major-1 no-restamp principle); append-after-prior-major
wire layouts with field-order divergences called out; new leaf layouts
with framing pinned (SubBeam EventIds = framed leaves; UnpitchedMember
fields = bare primitives; SpaceUnit = framed 12); total default-fill
v1->v2 migration table (complete StaffLineConfiguration defaults at
all four embed sites; RepeatKind default SimpleRepeat{count:2});
accept-set {0,1,2} with per-role staging note; CanonicalValue seam
list corrected 18 -> 22 (staleness fix).

Two review rounds (8 + 9 findings, all fixed pre-commit — the
stamping-rule determinism contradiction, the CreateRegion transitive
embedding, SubBeam leaf framing, and the SetStaffLayout direct-field
site being the load-bearing ones). Both PDFs build clean, zero
undefined references. No code changes; Phase B (snapshot side) next.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-07 15:05:48 -04:00
Levi Neuwirth 93f3dfde93 G-pass follow-up: unsupported pre-pass algorithm ids error
Post-commit review finding: req:pitch:spelling-algorithm /
req:time:decomposition-algorithm ratified MUST-error for unregistered
algorithm ids, but derive_annotations kept the pre-ratification
derive-nothing-under-honest-profile behavior (and a test locked it).
The spec text stands; the code moves: derive_annotations returns
Result<DerivedAnnotations, PrePassError>, rejecting unregistered ids
up front — a silently-empty derivation is indistinguishable from a
legitimately empty score, and would silently disagree with an
implementation that does support the requested id. All production
callers use the default profile (.expect); the stale lock test is
rewritten as unknown_algorithm_ids_error; PrePassError re-exported.

Full gate green: fmt, clippy -D warnings, rustdoc -D warnings, 30
workspace suites, conformance scale 1 (8/8).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-07 11:32:05 -04:00
Levi Neuwirth e4edea6a3e Pass 12 G-pass: code tranche (K3/K9/C4/H7) + review fixes
The decided code work behind aff1809, review-hardened (high-effort
multi-agent review; 3 correctness findings fixed pre-commit, all
regression-locked).

epiphany-ops:
- K3: SystemDerivedContentImmutable (12) — modify_event /
  modify_identified_pitch refuse intrinsic-content rewrites of
  SYSTEM_DERIVED pitches, checked against the system_mints registry
  (base-seeded + op mints, so reduce()/reduce_onto() agree).
  REVIEW FIX: Transpose now *skips* system-derived targets like
  tombstoned ones (an in-place alteration shift desynchronized content
  from the derivation registry -> checkpoint-dependent verdicts); an
  all-system transpose reduces as the K3 no-op. Catalog Transpose
  updated.
- K9: RecreateContentMismatch (13) replaces the TargetMissing misnomer
  at both value-retaining re-create sites (CreateStaff, carried
  TimeSignature). Catalog corrected: container creates are plain
  set-union (no value comparison), not K9 sites.
- C4: ReanchorReason::SameCanvasNearer (6); rank-4 recording routes
  through rank_reason. REVIEW FIX: an *unestablished* rank 4
  (unresolvable placement fallthrough in containment_rank) keeps the
  honest ExplicitFallback — selection order unchanged.
- Filed P13-K1 (pre-existing checkpoint asymmetry for ModifyEvent-
  introduced system pitches) in DECISIONS rather than improvising.

epiphany-core (H7, req:pitch:authored-uninferred):
- Authored attachments surface for inference-ineligible targets in
  both pre-passes (best_authored_spelling/_decomposition refactor,
  candidates-first liveness via the existing Score::live_pitch_ids);
  two new taxonomy buckets enter the derivation fingerprint. The old
  test locking the pre-decision mirror is flipped to the ratified
  behavior.

epiphany-testkit (review fixes):
- prepass_harness: spelling-unavailable pitches may carry *authored*
  surfacings (inferred still forbidden); decomposition reconstruction
  invariants scoped to algorithm output (authored entries are
  invariant-15's graph-level jurisdiction); accounting identity gains
  the authored-uninferred bucket.
- generators: the three appended discriminants (12/13/6) now generate.

Full gate green: fmt, clippy -D warnings, rustdoc -D warnings, 30
workspace suites, conformance scale 1 (8/8). Wire goldens + decode
bounds for the appended discriminants; 12 new regression tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-07 11:21:22 -04:00
Levi Neuwirth aff1809421 Pass 12 G-pass: ratify the full batch (28 rows) — spec tranche
The G-ratification pass over the accumulated PASS12_BATCH backlog
(worklist: spec/PASS12_WORKLIST.md; dispositions:
PASS12_RATIFICATION_LOG "G-pass tranche"). Four project-lead
decisions: K12 slur permission = AND, H7 authored-uninferred
annotations SURFACE, K4 ResolveConflict = no supersede, K8 genesis
outside the operation set. Three named deferrals: H2 (narrowed;
spelling-v2 + notation refinement), K2 (tuning catalog), K5 (Profile
Conformance companion).

core_spec: spelling + decomposition move to profile-declared with
ratified v1 defaults (req:pitch:spelling-algorithm,
req:time:decomposition-algorithm — two open-question boxes closed);
authored-uninferred surfacing (req:pitch:authored-uninferred);
decomposition precedence pinned FIXED; system-derived content
immutability (K3); genesis note (K8); slur AND advisory (K12);
re-anchoring table C1/C2/C3 + SameCanvasNearer variant (C4); barrier
matching + unsafe-tombstone semantics (req:format:barrier-matching,
req:format:unsafe-tombstone); solver kind-strength /
sub-conformant-report / Minimal-floor requirements (I4/I5/I6); stale
OperationKindTag listing gains the eleven appended tags.

operation_catalog 0.5.0 -> 0.6.0: K1 migration fallback long-term;
K4 no-supersede; K6 edge semantics (single-pass, quarantine excluded,
pending governs); K5 deferral pinned; K8 slots RETIRED; K10 reuse
blessed; K11 asymmetry normative; K12 AND; K2 prototype pin;
K3/K9 preconditions.

binary_format 0.3.0 -> 0.4.0: appended vocab discriminants
SystemDerivedContentImmutable(12), RecreateContentMismatch(13),
SameCanvasNearer(6); E5 tombstone-encoding open question.

All three PDFs rebuilt clean, zero undefined references. Batch rows
struck 28 -> 0 (tracker CLOSED); CONFORMANCE.md caveat dropped;
DECISIONS cross-refs in core/ops/layout-ir/editor-core (new file).

Code tranche (H7 surfacing, K3/K9/C4 discriminants) follows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-07 10:23:30 -04:00
Levi Neuwirth 7045a13a18 Schema major 1 Phase F: ratify engrave I8/I9/I10 + process trail
Spec/process only, no code. Closes the schema-major-1 track (Phases A-F): the
two data-model batch rows parked behind the frozen-layout rule (P12-I7, P12-K7)
landed via Phases C/D1, and the three engrave/layout-ir dispositions the
implementation already made are ratified into the core spec.

- core_spec: three normative requirements ratifying the implemented behavior --
  break-constraint satisfaction (req:layoutir:break-satisfaction, I8: a
  SystemBreakAt/PageBreakAt is satisfied iff the final ResolvedLayoutIR starts a
  system/page at that slot), break-override attribution via a
  ConstrainedLayoutIR.break_origins sidecar declining to widen the constraint
  record (req:layoutir:break-origin-attribution, I9), and system-continuation
  synthesis Registered(SYSTEM_CONTINUATION_SYNTHESIS) with an (original, ordinal)
  instance key (req:layoutir:continuation-synthesis, I10). Revision-history row.
  PDF rebuilt (latexmk -xelatex, 0 undefined refs).
- PASS12_RATIFICATION_LOG: schema-major-1 tranche (I7/K7 landed + I8/I9/I10
  adopt), with the open cross-region-slur item flagged.
- PASS12_BATCH: struck I7/K7/I8/I9/I10; added P12-K12 (which region governs a
  cross-region slur's spanning permission -- implemented as conservative AND).
- engrave/layout-ir DECISIONS: ratification cross-refs; the P12-I7 note reworded
  so deferred Phase C' does not read as landed.
- .gitignore: spec/*.xdv (xelatex intermediate; the tracked PDF is committed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-06 17:40:38 -04:00
Levi Neuwirth f4a2f1fdf0 Schema major 1 Phase A: ratify the v1 wire form + migration (spec only)
The first phase of the first-ever binary-format schema-major bump (v0 -> v1),
the machinery-first minimal major. Spec-only: ratifies the contract that
Phases B-F build against; no code changes.

core_spec.tex: defines the two referenced-but-undefined types that were the
P12-I7 / P12-K7 gaps -- CanvasLayoutDefaults (with core geometry primitives
CanvasSize/CanvasMargins in staff spaces via CanonicalF64, A4/8mm default,
since core has no geometry types and must not depend on layout-ir) and
PitchRange (advisory pitch compass used by Instrument.range and
IndeterminacyHints) -- and adds Region.permits_spanning_slurs (default false).
Records schema major 1 as the first data-model expansion major and tightens
the minor-version rule (a field add, even Option, is major; minor = append
discriminants to the companion's append-safe vocabularies only).

binary_format.tex -> 0.3.0: the full "Schema Major 1" section --
  - Where the changed fields reach: Canvas.layout_defaults and
    Instrument.range are snapshot-only (no CreateCanvas/CreateInstrument op),
    but Region.permits_spanning_slurs also reaches the CANONICAL CreateRegion
    operation payload (CreateRegion embeds the full Region). The canonical-base
    MaterializedState embeds none of these and stays major 0, byte-identical.
  - Cross-major reader rules: discard-and-regenerate non-canonical chunks;
    parse-or-read-only for canonical ones, so a major-0 reader opens a bundle
    carrying v1 CreateRegion ops read-only.
  - Accept-set gate [min,max] (rejects majors outside the set); per-payload-
    type major assignment; the changed v1 value layouts (the wire form ratifies
    the reduced reference-code layout, not the fuller data model); the total
    default-filling v0->v1 migration table (including the CreateRegion payload).
  - Length-prefix unification NARROWED to the resolved-layout (its own
    non-canonical LayoutCache): the barrier/extension blobs stay regime (b) u64
    because they ride the canonical manifest, which stays major 0.

Two review passes hardened this checkpoint. The first caught that Region is a
canonical operation payload (not cache-only, as the architecture analysis had
assumed) -- user chose to embrace it and build the canonical op-payload
migration. It also surfaced the barrier-blobs-in-manifest constraint that
narrows the unification. The second refined the minor-version delegation, the
accept-set outside-[min,max] semantics, and stale "no defined type" text in
the reference-suite / quality-metric companions and the engrave DECISIONS.

P12-I7 moved to IN PROGRESS (spec type defined here; code graph home lands in
Phase C). Both companions and the engrave DECISIONS reworded accordingly. All
four affected PDFs rebuild clean (0 undefined references).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-05 18:05:38 -04:00
Levi Neuwirth 9d28dd6615 Clear P12-I12: scope spacing_distortion to rhythmic columns (QMC 0.2.0)
The Standard-tier spacing floor warned on short healthy scores: three
reference-suite entries measured spacing_distortion 0.36-0.41, above the
0.32 warning floor (0.8 x 0.40), a spurious diagnostic (every score still
passed Minimal). Root cause: the CV folded in the clef-to-first-note lead
advance, which is sized by notational-furniture width, not by rhythm.

Unlike P12-I11 (an engrave-side fix, no spec change), the defect here lived
in the metric's own normative definition, so the honest fix is a catalog
change - the mirror of I11: correct the measurement rather than relax the
threshold.

Quality Metric Catalog 0.1.0 -> 0.2.0: spacing_distortion is scoped to the
system's rhythmic columns (spring slots bearing a notehead or rest). The
clef / key-signature / time-signature lead and barlines contribute no
column, so a note-to-note advance spans them. quality::census now builds its
spacing columns only from a precomputed rhythmic-slot set (is_rhythmic: a
notehead*/rest* glyph anywhere in the slot); the CV and the >= 3-column
contributing-unit rule are otherwise unchanged.

Results: RS-3/5/6 drop to 0.2188 / 0.0819 / 0.0856 (all below the floor),
and the axis stays honest on real irregularity (RS-3 keeps 0.2188 from a
mid-line accidental). RS-2/RS-4 go vacuous-0.0 (their systems carry < 3
rhythmic columns - honestly "too little to measure," cleaner than the old
furniture noise). RS-1 0.1341 -> 0.1967 (cross-barline note advances).

The 1.0 anchor, orientation, range, tier thresholds, and the eight other
axes are unchanged. This is measurement-only: the resolved layout, canonical
bytes, render goldens, and ENGRAVER_VERSION are all untouched - only the
reported spacing_distortion value moves. The duration-aware optical-spacing
open question (deviation from duration-proportional spacing) stays open; it
needs the pipeline's deferred duration-aware preferred widths.

- catalog: spacing_distortion requirement (req:qmc:spacing) redefined over
  rhythmic columns, rationale + open-question note updated, version 0.2.0,
  revision-history row; PDF rebuilt clean (0 undefined refs).
- engrave: quality::census rhythmic-column filter + is_rhythmic; module and
  spacing_raw docs; the floor-column contrast test re-pointed from b-flat's
  spacing (no longer warns) to RS-1's casting-off (still between the Standard
  0.28 and Minimal 0.72 floors); new short_scores_do_not_trip_the_standard_
  spacing_floor locks the fix.
- QMC version breadcrumbs bumped to 0.2.0 (engrave + layout-ir quality.rs,
  both DECISIONS.md, testkit RS-1 comment); reference suite companion
  unchanged (cites the catalog by name, no pinned values).
- process trail: PASS12_BATCH I12 struck; PASS12_RATIFICATION_LOG I12
  section (Version movements: QMC 0.1.0 -> 0.2.0); engrave DECISIONS quality
  decision 8 + item 7 + candidate.

861 workspace tests pass; clippy -D warnings, fmt --check, rustdoc
-D warnings, and the catalog PDF build all clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-05 13:57:10 -04:00
Levi Neuwirth bc9fb27d36 Clear P12-I11: casting-off widow-rebalance evens the stub last system
RS-1 honestly failed the Minimal casting_off_quality threshold under the
reference engraver: greedy first-fit left a two-measure stub last system
(width CV 0.6145 -> clamped 1.0 > 0.90). Cleared the honest way — an
engrave-side balance pass, no Quality Metric Catalog or core-spec change.

Casting-off gains a second phase, a widow rebalance
(casting::rebalance_widows, run between the greedy walk and vertical
stacking): it moves whole trailing measures from a region's penultimate
system into its final one, choosing the shift that minimizes the larger of
the two distribution penalties the catalog defines for the break family —
the width imbalance (casting_off_quality, the CV of the region's system
widths) and the non-final break penalty (system_break_penalty, the mean of
|W-w|/W over non-final systems). distribution_cost computes each raw by the
same formula as quality.rs's casting_off_raw / system_break_raw (mean not
worst, abs not clamp), so the rebalance optimizes the values the metric
census will report. The two axes pull against each other, so their min-max
lands on a 6/4 split for RS-1 (casting_off 1.0 -> 0.4463, system_break
0.254 -> 0.677, every axis <= 0.90) — with comfortable margin, over the
fragile full-balance 5/5 (system_break 0.889, a hair under 0.90).

Scope is tight: only a region's last boundary moves, and only when greedy
placed it (an Automatic boundary with no break requirement or page force
pinned to its slot); a user/IR-anchored or page-forced boundary is never
disturbed, the penultimate system keeps >= 1 measure, the final never grows
past its predecessor, and the system count is unchanged — so every
break-count and page-assignment invariant (and all break-constraint tests)
hold untouched.

The casting_off 0.5 anchor and the 0.90 Minimal column were vindicated, not
relaxed: the engraver improved, no anchor rescale / threshold loosening /
RS-1 override. Core spec Chapter 9's "Minimal makes no optimality claim"
already permits the heuristic, so nothing normative changed (no .tex/PDF
rebuild). P12-I12 (the Standard-tier spacing floor on short scores) stays
open.

- engrave: rebalance_widows + distribution_cost + two-phase module docs;
  ENGRAVER_VERSION 2 -> 3 (a wrapping score's baked geometry differs from
  pure greedy); three new casting tests (even-split preference, the
  mean-not-worst break penalty for 3+ systems, and the resolved final
  system); the_wrapping_fixture_is_measured_honestly re-pinned to the 6/4
  values (both axes floor-warn under Standard, status untouched).
- testkit: RS-1 minimal_xfail row removed (promoted to a plain Pass); the
  suite ships no xfail rows.
- render-svg: ten_measure.engrave.{svg,snapshot} goldens regenerated
  (view_box width 83.99 -> 64.95; still two systems).
- process trail: PASS12_BATCH I11 struck; PASS12_RATIFICATION_LOG
  "no spec change" section; engrave DECISIONS casting-off decision 9 +
  quality item 7 + candidate promoted.

860 workspace tests pass; clippy -D warnings, fmt --check, rustdoc
-D warnings all clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-03 19:09:51 -04:00
Levi Neuwirth b9a9921d50 Phase 3 tranche 2: quality conformance companions, real metrics, multi-system click fix
857 workspace tests pass; clippy -D warnings, fmt, and rustdoc clean;
both new companions build with zero undefined references.

Quality Metric Catalog v0.1.0 (spec/quality_metric_catalog.tex, new):
- Formal definitions for all nine normative quality metrics, each with
  a raw measurement over resolved geometry and a clamped-linear
  normalization n = min(1, raw/R_worst) with pinned anchors.
- The vacuous-geometry rule (a metric over absent geometry evaluates
  to 0.0; the notated-but-unrendered honesty edge is an open
  question), all-1.0 default tie-breaking weights, and the per-tier
  threshold table — Minimal's uniform 0.90 deliberately fails the
  all-worst placeholder, forcing real measurement.
- Pins QualityMetricKind (referenced but never defined by the core
  spec) and the registered SolverProfile catalog (Draft selects the
  Minimal threshold column; Standard/Publication select Standard).
- QualityFloorApproached fires at 0.8x the applicable threshold and
  is status-neutral by requirement.

Reference Suite v0.1.0 (spec/reference_suite.tex, new):
- Six entries referenced by deterministic builder + seed (RS-1
  ten_measure_single_staff, RS-2 valid_score_rich, RS-3..6 corpus
  fixtures), each with the declared A4-at-8mm-staff solve geometry
  (Canvas.layout_defaults has no graph home yet, P12-I7).
- All entries required at Minimal; the same set is the pre-declared
  Standard bar (no implementation claims Standard yet). Fixed-
  expectation tests deliberately unused in v0.1.

Real metrics in the engraver (engrave/src/quality.rs, new;
layout-ir/src/quality.rs = the catalog constants transcribed):
- QualityMetricVector::unmeasured() replaced with computed values:
  collision sweep with the catalog's same-slot-cluster and stroke
  exclusions, per-system spacing CV, vertical gap deviations,
  system-break slack, page fill, casting-off width CV, symbol
  density; slur/beam vacuously 0.0 (no drawn geometry exists).
- Bit-identical across repeated solves (tested); floor warnings never
  change solve status; malformed inputs keep unmeasured(). The two
  all-worst test pins now assert real values; the StubSolver's
  unmeasured() stays (Stub genuinely computes nothing).

Reference-suite harness (testkit reference_suite module + tests):
- Each RS entry asserts the four-condition Minimal pass (hard
  constraints, byte/bit determinism, well-formed Minimal report,
  every axis within threshold) under the F1 Pass/Xfail discipline,
  with the measured table printed per run.
- HONEST FINDING, day one: RS-1 fails Minimal casting-off (measured
  1.0 vs 0.90) — greedy first-fit leaves a two-measure stub last
  system (width CV 0.6145). Encoded as an asserted Xfail row (fails
  on XPASS) and filed as P12-I11 (engrave balance pass, or catalog
  revision). P12-I12: the Standard spacing floor warns on short
  scores with wide lead measures.

Multi-system click-to-insert fix (editor-core):
- Casting-off exposed two inversion breaks: position_anchors fed a
  non-monotonic cross-system anchor list into a monotonic inverter
  (system-2 clicks resolved to system-1 times), and
  nearest_manifestation found only system 1's staff-line segment
  (system-2 clicks got system-1 pitch geometry).
- Fixed with a containing-system lookup over the resolved pages tree
  (containment, else nearest by vertical distance), per-system staff
  resolution, and per-system anchor filtering; degenerate-geometry
  fallback preserves the flat path, so all 84 pre-existing
  editor-core tests pass unmodified.
- Five regression tests through the real Engraver over the wrapped
  ten-measure fixture, each shown to fail without the fix; testkit
  gains dev-only dependencies on editor-core and engrave.

Trackers: P12-I11/I12 filed; DECISIONS entries in engrave, layout-ir,
and testkit; Phase-3 memory updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-03 10:39:05 -04:00
Levi Neuwirth 0316160395 Phase 3 tranche 1: casting-off, K1 schema-fill, value-restoring undo
The chosen Phase-3 attack, run as two parallel waves. 829 workspace
tests pass; clippy -D warnings, fmt, and rustdoc clean; all three
spec documents build with zero undefined references.

Casting-off (epiphany-engrave/src/casting.rs, wired into the
Engraver):
- Greedy first-fit system breaking per region at measure-start
  barline columns; a measure that would overflow the content width
  starts a new system. Hard SystemBreakAt/PageBreakAt always
  honoured; soft breaks honoured unless pathological (skipped with
  the spec's warning + IrOverride-recorded decision).
- Vertical stacking from real content extents with the inter-system
  gap read from the vertical band model; page overflow starts the
  next page. World frame: pages stacked vertically, coordinates
  baked into glyphs/strokes, so the SVG renderer, hit-testing, and
  the GUI viewport are unchanged.
- Real ResolvedPage/ResolvedSystem trees (1-based page numbers,
  content bounding boxes, staves from staff-line segments, measures
  from barline columns); every chosen break appends an
  EngravingDecision with MUSCLOID EngravedBreak provenance,
  UserOverride-attributed via the new ConstrainedLayoutIR
  break_origins sidecar; staff lines split per system with
  synthesized continuation provenance.
- Break-constraint evaluation flips: satisfied iff the layout breaks
  at the slot. The two single-system tests invert deliberately
  (a hard break is now honoured; a user break is honoured and
  attributed instead of warned). Geometric constraints evaluate in
  the pre-casting spaced frame (documented).
- Page geometry is engraver-side PageGeometry (A4 portrait at an
  8 mm staff: page 105 x 148.5 staff spaces, margins 7.5, content
  90 x 133.5; arithmetic documented) — Canvas.layout_defaults has no
  graph home and is a schema-major addition (P12-I7).
  ENGRAVER_VERSION = 2. Goldens regenerated: ten_measure_single_staff
  engraves as 2 systems (viewBox 84x20.6, was 103x11);
  valid_score_rich as 3 systems; stub goldens byte-identical.

K1 schema-fill (Operation Catalog 0.4.0 -> 0.5.0, ratified first;
wire discriminants strictly appended):
- CreateStaff (24 / tag InsertStaff 24): set-union mint of a global
  Staff; CreateStaffInstance now preconditions that its referenced
  staff is live.
- SetTimeSignature (25): value-carrying meter-change LWW keyed by
  (region, resolved position); the carried TimeSignature mints
  set-union; StructuralFieldCollision on meter_sequence.
- SetTempoSegment (26): LWW keyed by (scope, resolved start) over
  the score or region tempo map; a write that would malform the map
  refuses with the appended PreconditionFailureReason 11
  (TempoMapMalformed).
- SetStaffLayout (27): LWW advisory over the staff instance's three
  inline layout fields.
- Create score/canvas remain deliberately unavailable slots: the
  root and canvas are inline singletons with no addressable object
  model (P12-K8), not force-designed.

Value-restoring undo (P11-C8 narrowed; catalog §UndoTransaction
rewritten and per-primitive undo notes updated):
- Canonical-order write chains (base-seeded) across all eleven LWW
  families. StrictInverse restores each written key to its
  chain-predecessor value iff the transaction's write is still the
  key's last writer, else refuses the whole undo with a
  TransactionConflict naming the superseder; BestEffort restores the
  still-last keys. Clean compensations are Applied; only minted-
  object tombstone repairs ride AppliedWithRepair (no new repair
  vocabulary). Mixed mint+overwrite transactions compose; strand
  guards refuse tombstoning mints still referenced by live
  non-members.
- Undo-of-undo pinned and tested: restorations are chain writes, so
  undoing the undo's transaction restores the undone value, and a
  second undo of the same transaction conflicts (absence-restores
  repeat idempotently — documented asymmetry, P12-K11).
- Permutation invariance pinned across five delivery orders; the
  convergence generators gain the new ops and a tx-then-undo flow.
- Still deferred in normative text: delete resurrection (needs a
  system-derived tag outside the ratified closed set), Transpose
  inversion (P12-K2), Cascade dependent closure.

Trackers: Binary Format companion 0.1.0 -> 0.2.0 (appended wire/tag
tables, PreconditionFailureReason 11, payload layouts, history row —
a schema-minor evolution under its own rules); nine new Pass-12 rows
(C5, K8-K11, I7-I10); core-spec OperationKind listing gains the four
kinds; revision-history rows in core spec and companion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-02 21:55:26 -04:00
Levi Neuwirth 3e91a8302a Push 4: Binary Format companion, F1 benches, subquadratic reduction order
The audit's fourth push: the biggest outstanding Phase-2 item plus the
performance gate. 793 workspace tests pass; clippy -D warnings, fmt,
and rustdoc (deny-warnings) clean; all three spec documents build with
zero undefined references.

Binary Format companion (spec/binary_format.tex, v0.1.0 — Agent J's
deliverable, 43 pages):
- Twelve chapters transcribed from the golden-locked implementation:
  encoding conventions (the three prefix/endianness regimes, a
  normative no-varint rule, reject-never-normalize decode discipline),
  identifiers imported from the core spec's Canonical Byte-Layout
  Reference, primitive value encodings, the whole-Score positional
  codec ratified as the schema-major-0 wire form, operation wire
  forms (envelope field order with the normative id-leads property,
  the OperationPayload 0..=3 and OperationKind 0..=23 tables,
  effects/conflict/anomaly/MaterializedState vocabulary), the bundle
  physical layout (64-byte header, 256-byte superblock, chunk
  preimages and framing, ChunkRef, manifest body order), the
  operation-index payload, and the extension-blob/edit-barrier byte
  forms.
- Ratifies P12-D1 (req:binfmt:opindex), P12-E1 (req:binfmt:ext-blobs),
  P12-E2 (req:binfmt:condition-depth, MAX_CONDITION_DEPTH = 64
  normative), and P12-E3 (req:binfmt:object-kind-open) — batch rows
  struck through; discharges the provisional-codec notes in core
  (P11-4), ops, and bundle (P11-D2/D4/D5) DECISIONS with ratification
  cross-references.
- Pins the frozen-layout schema-evolution keystone: within schema
  major 0 every positional struct layout is frozen; a field-set change
  is a schema-major change with migration — formally grounding the
  data-model-expansion staging decision. Open questions kept honest
  in-document: SnapshotId derivation, index-refresh threshold, u64/u32
  prefix unification at the next major.
- Not yet delivered from J's charter: the cross-implementation decoder
  test and the wire-format fuzzer (follow-up harnesses).

F1 benches (crates/epiphany-testkit/benches/, per the F0 decision):
- criterion 0.5.1 (workspace dev-dependency; MSRV 1.77 respected with
  documented transitive pins: clap 4.5.53, half 2.4.1).
- reduction bench at 1K/10K/50K envelopes with the Chapter-10 budget
  (>10,000 envelopes/second cold) written in the bench as a Pass/Xfail
  gate; bundle benches for the typical-edit commit (<=50 ms; measured
  ~14.7 ms on real disk after catching that tmpfs neuters fsync) and
  the open/bootstrap read (<=200 ms; measured ~60 us).
- CI: quick budget gates in the conformance job, full gates nightly.

Subquadratic canonical_reduction_order (the F-surfaces/K-fixes
handshake, closing K's 10K-envelope acceptance gate):
- The bench documented the failure (50K at ~1.7K env/s, a 29 s cold
  reduction; two O(n^2) loops); the fix replaces pair enumeration with
  threshold/frontier readiness per replica plus explicit-dot dependent
  lists and a stamp-tuple binary heap — O((n + sum(context)) log n),
  never materializing covered pairs.
- Byte-identical order: same edge relation, same ready predicate, same
  total order; the old implementation is retained as a test-only
  oracle with element-for-element order-equality property tests over
  fuzz sets, adversarial sets, and directed shapes (2,000-envelope
  full-coverage chains, dot cycles, duplicate-id stamp ties),
  mutation-tested for sensitivity.
- Measured: 1K 155K->674K env/s, 10K 12.5K->257K, 50K 1.7K->87K; all
  three scale points now pass and the 50K row is promoted from Xfail.

Also: fixed nine rustdoc private/unresolved intra-doc links that had
accumulated across the pushes (the CI deny-doc-warnings job would have
failed on them).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-02 19:02:07 -04:00
Levi Neuwirth 92aaccf7e2 Pushes 1+3: fix the MUST-level violations, wire the types-only machinery
Two audit pushes whose code edits interleave line-by-line in the same
files (reduce.rs, bundle.rs, the DECISIONS logs), committed together so
the tree at every commit builds. Gate: 784 workspace tests pass, clippy
-D warnings clean, fmt clean.

Push 1 — the true MUST violations, all fixed:
- bundle: zstd read support on both read paths, output bounded by the
  declared uncompressed_length, typed decompression errors, explicit
  CompressedManifest rejection (zstd 0.13 workspace dep; write path
  stays uncompressed per the Phase-3 deferral).
- ops: system-derived counter collision check — mint registry seeded
  from the base graph, canonical-order pre-walk, halt via the new
  PendingReason::HaltedBySystemCollision (discriminant 4, additive)
  with transaction-atomicity and causal-dependent closure; neither
  input set occupies a collided counter. canonical_pitch_bytes made
  pub in core for the MUSCSPCH preimage.
- ops: Transpose skips tombstoned targets per the catalog; missing
  targets still refuse the whole operation.
- ops: marker re-anchoring recorded as a RepairRecord in the
  triggering operation's effect; ResolveConflict meta-conflicts name
  both resolvers; base-free pitch-id freshness; reserved effect
  vocabulary annotated.
- core: decomposition pre-pass honors authored attachments
  (resolve_decomposition, spec-default precedence); inversion
  tolerance typed as a TempoIntegration-class Tolerance.
- CONFORMANCE.md: the determinism conformance statement required by
  Appendix D — all seven declarations.

Push 3 — wiring the types-only machinery:
- layout-ir/engrave: to_constrained emits real constraints (successive
  notehead no-collision chains, per-glyph region containment, soft
  user-break constraints); ConstraintStrength{Required, Preferred}
  with strength-by-rule; Preferred violations surface as warnings, not
  failures; StubSolver reworked honest-but-renderable. SVG goldens
  byte-identical; snapshot constraint counts regenerated (0->90/15).
- layout-ir: to_logical projects user system/page breaks as anchored
  EngravingOverrides with paired UserOverride-sourced decisions
  (OverrideKind::SystemBreak/PageBreak carry TimeAnchor, ratified in
  the spec alongside).
- layout-ir/ops/editor-core: edit-barrier bridge — decode mirrors for
  the whole barrier tree (reject-never-normalize, NFC revalidation,
  MAX_CONDITION_DEPTH = 64), golden-locked blob codec for the
  ExtensionDeclaration fields, a barrier gate in apply and
  apply_transaction backed by a Score oracle and real containment
  contexts, and apply_unsafe recording the crossed extensions in
  extensions_requiring_tombstone() for the next bundle write.
- ops: ResolveEquivocation meta-operation per the newly ratified
  catalog entry — payload discriminant 3 (appended), set-level
  earliest-resolve-governs promotion, ResolveConflict-mirrored
  meta-conflicts, permutation-invariance fuzz; the missing golden
  locks on the OperationKind/OperationPayload wire tables added.
- ops/editor-core: validation modes — ValidationMode + a non-canonical
  advisory layer (validate.rs), an authoring gate before minting, and
  reduction pinned as replay mode by construction (canonical bytes
  untouched).
- bundle: the operation index (opindex.rs) — provisional golden-locked
  payload, binary-search locate, staleness defined as full-ChunkRef
  set equality against operation_roots, and the reject-and-rebuild
  discipline (a defective index is never bundle corruption).
- ops: re-anchoring rule table completed — the four-key "nearest"
  ordering computed from base-free ledger indices; markers re-anchor
  to the nearest live event in the same staff instance (replacing the
  Push-1 region-start stand-in); cue-source cascade; graphic-gesture
  Events/Range/Free rows; comment and analytical-annotation orphaning.
  Zero appended discriminants.

Spec enablers ratified with Push 3: catalog §ResolveEquivocation
(0.3.0 -> 0.4.0) and anchored break overrides; 16 new Pass-12 rows
filed (C1-C4, K5-K7, I4-I6, D1, E1-E5). The data-model payload
expansion (SlurKind, beam geometry, voltas, instrument bodies,
metadata) is deliberately staged to the Binary Format companion — the
positional graph codec has no value-level versioning, so filling those
structs is a schema-major break that should land once, with J.

Also carries the pre-existing editor-track increment: the atomic
tuplet overwrite (CascadeDeleteTuplets prunes decomposition
attachments naming the cascaded tuplet).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-02 17:10:50 -04:00
Levi Neuwirth 80e3fed99c spec: Pass 12 tranche 1 — align spec text with ratified dispositions
The 2026-07 compliance audit's spec-side batch: every place where
normative text trailed a disposition the project had already made (the
ratified Operation Catalog, a crate DECISIONS entry, or the
PHASE2_QUICKSTART model the implementation follows). No byte-layout or
architecture changes.

- Re-anchoring table: the Slur/Spanner rows now state the catalog's
  surviving-endpoint semantics (re-anchor while >=1 endpoint survives,
  cascade-delete only when none does).
- RewriteTuplets adopts the catalog's id-only v1 payload (TupletRewrite
  removed); graph-aware reduction MUST refuse the variant rather than
  fabricate rewritten values.
- Operation-envelope blocks are pure envelope vectors (a content-
  addressed chunk cannot embed its own id); per-block summaries
  (dvv_summary/min_stamp/max_stamp) relocated to the manifest's
  operation_block_summaries map; the order-blocks-by-min_stamp SHOULD
  dropped (the canonical encoding sorts chunk refs by encoded form).
- Ch2+Ch3 pre-passes respecified as canonical derived annotations —
  deterministic functions of (graph, profile, versioned algorithm id),
  recomputed on materialization, never stored; the incremental-re-run
  MUST demoted to MAY with unobservability required.
- Spelling precedence ties break by canonical attachment order (the
  creation-timestamp tie-break deleted; attachments carry none).
- ConflictRegistry moved off the Score root into canonical materialized
  state, matching Chapter 6.
- SolverTier gains the non-conformance Stub variant.
- The drafted ModifyEvent M2e text landed in the catalog (0.2.0 ->
  0.3.0): metric placement changes materialize behind a voice-occupancy
  placement precondition.

Dispositions logged in spec/PASS12_RATIFICATION_LOG.md; the Pass 12
batch itself stays open (no P12-H/K rows resolved). Also carries the
audit-filed batch rows P12-H6/H7/K3/K4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-02 17:04:14 -04:00
Levi Neuwirth 3ef1a8abd9 Pass-12 batch: mark the resolved I-series rows done
A status sweep: the I-series Pass-12 candidates are all closed, but the tracker
and two crate DECISIONS.md files still described them as open.

  - PASS12_BATCH.md: P12-I1 (structural-placeholder pipeline) is resolved by I-1
    -- to_constrained now builds real notation and the Engraver re-spaces it;
    P12-I3 (BRAVURA_METRICS approximations) is resolved by I-4a -- metrics
    re-extracted from the same pinned 1.392 font, containment-tested. Both rows
    struck through and marked done.
  - render-svg/DECISIONS.md: fixed the stale P12-I2 bullet, which still said the
    MUSCLOID derivation was unwired and the determinism crate exposed no tag (a
    miss from the P12-I2 commit); now marked resolved.
  - engrave/DECISIONS.md: its P12 section described P12-I1/I3 as open and omitted
    I2; rewritten so all three read resolved.

The batch's top-level Status stays OPEN -- the H-series and K-series candidates
remain. Docs-only; no code change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 12:53:36 -04:00
Levi Neuwirth e2d389330d P12-I2: wire the ratified MUSCLOID layout-object id derivation
The spec's domain-tag registry reserves a non-canonical MUSCLOID layout tag for
LayoutObjectId derivation (req:layoutir:object-id-derivation, Pass-11 item 2.6),
but it was never realized in code: layout-ir minted provisional, untagged ids and
synthesized objects borrowed MUSCCONF. This wires the ratified derivation.

  - epiphany-determinism: add the reserved built-in DomainTag::LAYOUT_OBJECT_ID
    (`MUSCLOID`), non-canonical/layout-namespace like FONT_METRICS (following the
    SYSTEM_ANOMALY Pass-11 precedent of adding a reserved tag). The tag-enumeration
    tests now derive from BUILTINS so they cannot drift; the spelling is locked and
    from_bytes resolves it as a non-system builtin.
  - layout-ir provenance.rs: all three LayoutObjectId derivations route through
    MUSCLOID exactly per the requirement -- single keyed on source.canonical_bytes(),
    multiply-manifested on (source, region), synthesized on (source, synthesis_kind,
    instance_key); synthesized no longer borrows MUSCCONF. A reference-lock test
    pins the derivation and proves it is genuinely domain-separated; another asserts
    the three keying schemes do not collide (safe by the discriminant-led,
    fixed-width canonical_bytes).
  - layout-ir engraving.rs: EngravingDecisionId borrowed MUSCCONF for the same
    reason; moved it onto MUSCLOID too, keeping its `engraving-decision` prefix so it
    cannot alias a layout-object id within the namespace.

Layout ids are non-canonical (never document state, in no content hash), so this
changed id *values* but no durable or interchanged artifact: the only golden churn
is the data-prov hex in the four render goldens (every changed line is a data-prov;
geometry/structure byte-identical).

Spec/status sync: core_spec.tex descriptive notes (the requirement tail, the
domain-tag registry row, the registry intro, and the revision-history entry) now
say the reference implementation wires MUSCLOID as of P12-I2; MUSCLOID is moved out
of the "deferred to the companions" (not-ratified/provisional) list and given a
non-canonical anchor paragraph after the reference-implementation-locks table.
PASS12_BATCH.md marks P12-I2 resolved; PASS11_RATIFICATION_LOG.md keeps the
historical row with a "superseded by P12-I2" note; layout-ir/DECISIONS.md updates
the ratified-block note, the id bullet, and the open candidate (now RESOLVED).

Full gate green: build, fmt, clippy, 587 tests, conformance scale 1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 12:46:19 -04:00
Levi Neuwirth dfdbc625b1 Agent K M2e review follow-up: tighten catalog accuracy + container creates
Address the two-pass review of the M2e catalog expansion (ca07c28). All gates
green: cargo test --workspace 535/0, conformance_suite scale 1, fmt + clippy
-D warnings clean, catalog PDF rebuilt (no undefined refs).

Pass 1 (doc-vs-implementation accuracy):
- Undo semantics narrowed to the prototype minted-object model (the catalog's
  own UndoTransaction model + P11-C8): the new K0 sections' minting members
  (insert/create) keep tombstone-the-mint undo; the non-minting ops (modify /
  transpose / deletes / settings) now state they synthesize no inverse, rather
  than promising rich restore/reintroduction the reducer does not implement.
- Spanner migration corrected: Tie/Slur/Beam reconstruct self-containedly while
  a Spanner remains read-only/unmigratable until the v0 projection carries its
  TimeAnchors (a Phase-3/Pass-12 extension); the "joins in M2" claim is removed,
  in both the CreateCrossCutting section and the migration chapter.
- The reduce()/reduce_onto() agreement claim (DECISIONS + the staff_based_regions
  comment) narrowed to regions represented in reducer state: op-created/deleted
  regions agree, but reduce_onto additionally seeds base regions a base-free
  reduce() never sees.

Pass 2 (empty-container enforcement, made real and complete):
- create_region / create_staff_instance / create_voice reject (ContainerNotEmpty)
  a carried value bearing ANY typed child object — not just the structural
  hierarchy. A region: no staff instances, barline-alignment groups, or graphic
  objects; a staff instance: no voices or measures; a voice: no events. Each is a
  distinct TypedObjectId the reducer mints separately, so a carried child would
  otherwise materialize an unminted object into the graph (a graph/ledger
  faithfulness gap). ClefChange/KeySignatureChange/metric-grid carry no
  TypedObjectId and are values, so they are correctly not gated. The check reads
  the carried value only, so reduce() and reduce_onto() agree.
- Catalog §Structural Containers states the precondition as "no typed child
  object" with the per-container enumeration, matching enforcement exactly.

Coverage: new graph_reduction tests create_rejects_a_non_empty_carried_container
(hierarchy children) and create_rejects_carried_non_hierarchy_children (barline
group / graphic object / measure). DECISIONS M2c gains the create-emptiness bullet.

Stages only ops + spec; the unrelated Agent-I working tree is left untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 09:52:13 -04:00
Levi Neuwirth ca07c28c82 Agent K M2e: catalog expansion + DECISIONS for the M2 broad-K0 groups
The documentation milestone deferred through M2a–M2d. Documentation only —
no Rust changes; the d93baac code gates (cargo test --workspace 533/0,
conformance_suite scale 1) are unchanged.

operation_catalog (v0.1.0 -> v0.2.0):
- Chapter K0 gains full six-part schema sections for every M2-implemented op:
  ModifyEvent; Identified-Pitch Operations (insert/delete/modify, with the
  note<->rest equivalence stated normatively); Transpose; DeleteCrossCutting;
  ModifyCrossCutting; Structural Containers (region/staff-instance/voice
  set-union mint + empty-only delete); Score Settings (advisory metadata,
  structural metric grid with the staff-based + live-time-signature
  preconditions, advisory page break under the resolved-position LWW key).
- Chapter K1 cleanup: the implemented groups now cross-reference their K0
  sections rather than sit in "MUST reject"; the stale Phase-3 listing of
  SetMetadata / SetMetricGrid / page-break advisory (implemented in M2d) is
  removed, and the remaining slots are split to the genuinely-unimplemented
  finer metric ops (time signature / tempo segment) and non-break layout.
- Intro, conformance-profile, and version strings updated for the expansion.
- PDF rebuilt with xelatex (18 pp, no undefined refs); .xdv removed, only
  tex+pdf tracked.

epiphany-ops/DECISIONS.md: new M2c (Group 3 — empty-only structural-container
delete; live-child indices; staff-extent maintenance) and M2d (Group 4 — the
per-op disciplines as review-hardened in d93baac: advisory metadata, metric
grid with both preconditions, resolved-position break key) entries, and a note
that the dedicated 10K-envelope reducer micro-bench (criterion 5) is Agent F's
worklist F1 — the M2 value-typed ops are already exercised at 10K*scale by the
conformance reduction-determinism / convergence gates.

The unrelated Agent-I working tree is left untouched; this commit stages only
spec/ + ops DECISIONS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 22:21:32 -04:00
Levi Neuwirth 7a94814ba3 Cross-seam review fixes: respell→pre-pass visibility, profile enforcement, canonical fingerprint, catalog reconciliation
Addresses four findings spanning the H (pre-pass) and K (reduction) seams plus
the Operation Catalog.

1. [High] A reduced RespellPitch is now visible to the pre-pass. The reducer
   stored overrides only in MaterializedState.spellings, but Agent H's
   derive_annotations resolves authored spellings from score.spelling_attachments
   — so a real respelling accepted by reduce_onto was lost before annotation
   derivation, violating manual-override precedence. respell_pitch now upserts a
   user-chosen explicit SpellingAttachment into the materialized graph
   (materialize_respell / graph_respell_pitch); DeleteIdentifiedPitch drops that
   attachment (graph_delete_pitch) so none dangles (it does NOT tombstone the
   pitch — the event survives a pitch delete and a later ModifyEvent may reuse
   the id, which would make it both live and tombstoned). New testkit gate
   assert_reduced_respell_is_honored reduces a real RespellPitch and proves
   derive_annotations honors it as Authored(UserChosen); wired into run_all.

2. [Medium] PrePassProfile algorithm ids are now enforced, not just recorded.
   derive_annotations ran the default logic and labeled the result with the
   requested algorithm. It now runs each pre-pass only when its requested id is
   the implemented "default"; an unknown/future id yields no annotations for that
   pre-pass (the requested id stays in the result profile), so a future algorithm
   can no longer silently alias the default in a derivation cache. Test:
   unknown_algorithm_ids_are_not_honored.

3. [Medium/Low] The determinism gate now fingerprints canonical bytes, not Debug.
   DerivedAnnotations gains canonical_fingerprint(): embedded graph values
   (PitchSpelling, DecompositionAttachment, SpellingSourceKind — the latter two
   added to the CanonicalValue surface) use their ratified bytes; counts/ids are
   little-endian, length-framed. The pre-pass harness fingerprints with it. A
   discrimination check confirms it is not a degenerate constant.

4. [Low] operation_catalog.tex K1 chapter reconciled with the implemented M2
   work: the now-dispatched ops (event/pitch leaf-field, cross-cutting CRUD,
   structural container CRUD) are listed as implemented-since-M2 (available under
   the Phase-2 profile), and the "MUST reject" scope is narrowed to the genuinely
   deferred slots (create score/canvas/staff, set metadata, metric-grid/time-sig/
   tempo, layout/page-break). PDF rebuilt clean (0 undefined refs).

Gates: build/fmt/clippy -D warnings clean; cargo test --workspace green (criterion
1 + the pre-pass and convergence gates); conformance scale 1 passes. The unrelated
Agent-I working tree is left uncommitted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 18:52:34 -04:00
Levi Neuwirth c47f4b5cec Agent K M2a review follow-up: graph-materialization fixes for the leaf-field ops
From the M2a review (no bug in the bookkeeping reduction; the gap was that the
Group-1 ops' *graph* materialization — reduce_onto — was unexercised by the
gates, which hid two invalid-graph edges). Fixes are graph-materialization only;
the bookkeeping projection, and therefore convergence/determinism, is unchanged.

- DeleteIdentifiedPitch of a single-pitch note's last pitch left an empty
  (Chapter-5-invalid) PitchedEvent via EventArena::get_mut (which bypasses
  insert's well-formedness guard). It now degrades the note to a Rest of the
  same id/voice/position/duration; InsertIdentifiedPitch into a rest is the dual
  (rest -> one-pitch note), keeping the graph consistent with the bookkeeping
  that mints/tombstones the pitch object either way.
- ModifyEvent now skips placement-changing (move) and malformed-empty pitched
  replacements in the graph rather than corrupting invariant 3
  (VoiceEventsSortedNonOverlap) via get_mut; voice re-sort stays deferred and the
  LWW bookkeeping still records the modify.

Coverage: graph_edit_session (criterion 1, reduce_onto + check_invariants) now
emits all five Group-1 kinds, so the real-Score gate exercises their graph
mutations at scale; plus two targeted reduce_onto regression tests (note->rest,
rest->note) in tests/graph_reduction.rs.

Docs: DECISIONS.md records the note<->rest equivalence and the ModifyEvent
placement deferral (catalog section prose routed to M2e); Transpose / P12-K2 note
the i8-saturation caveat; valuegen::pitch_value_nth no longer implies spelling()
is injective.

Gates: build/fmt/clippy -D warnings clean; cargo test --workspace green (ops
graph_reduction 13, ops lib 51); conformance_suite scale 1 passes. The unrelated
Agent-I working tree is left untouched; this commit stages only ops/testkit/spec.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 18:25:48 -04:00
Levi Neuwirth 1658fd18f3 Agent K M2a (Group 1): event & pitch leaf-field operations
First broad-K0 subsystem group — five new value-typed ops reusing M1's proven
disciplines (additive: new OperationKind variants 8–12, new apply arms +
reduction methods; framework frozen):

- ModifyEvent { event: Event } — field-overwrite LWW by EventId; concurrent
  differing ⇒ StructuralFieldCollision.
- Transpose { targets, chromatic_steps } — order-dependent; pitch ids preserved;
  canonical footprint = effect-log entry; reduce_onto applies a minimal CMN
  alteration shift (rich interval algebra deferred — P12-K2).
- InsertIdentifiedPitch / DeleteIdentifiedPitch — pitch-within-event mint /
  delete-wins tombstone.
- ModifyIdentifiedPitch { pitch, value: Pitch } — field-overwrite LWW (the pitch
  VALUE, distinct from RespellPitch's spelling-only overwrite).

Design (honesty rule): the modify/transpose ops record effect + conflict
canonically — the resolved values live in the graph (reduce_onto), since
MaterializedState is bookkeeping, not a second graph; respell stays special
because spelling is a bookkeeping-owned annotation. LWW diff uses new
`last_event_modify`/`last_pitch_modify` working maps (synced through
WorkingSnapshot/snapshot/restore).

- core: expose Pitch + IdentifiedPitch via CanonicalValue (no new layout).
- The five kinds are v1-native (no lossy v0 predecessor): project/migrate them by
  identity; only the original kinds reconstruct from a lossy v0 form.
- Generators (testkit operation_payload, ops fuzz gen_payload) now emit the new
  kinds, so the convergence / determinism / migration-equivalence gates exercise
  them at scale; plus targeted migrate identity + reduce LWW/mint/delete tests.

Gates: build/fmt/clippy -D warnings clean; cargo test --workspace green (ops lib
51 tests); conformance_suite scale 1 passes. Catalog sections + DECISIONS for
these ops land in M2e per the staged plan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:56:01 -04:00
Levi Neuwirth 339b1e475b Agent K M1 review follow-up: correct two doc-accuracy findings
From the M1 review (no correctness bugs found; these are accuracy corrections to
shipped artifacts, not new feature work):

- migration gate: the `v1 == migrated` assertion's comment overclaimed a
  universal inverse. It is round-trip self-consistency over the representative
  corpus (which is built from the same valuegen helpers the migration
  reconstructs values with); the spec-level property is the reduction-equivalence
  asserted alongside it. The ReplaceWithRest rest-voice is the known
  non-invertible field, recovered from the deleted event's placement at reduction.
- operation_catalog §CreateCrossCutting: document that v0→v1 migration covers the
  event-anchored Tie/Slur/Beam; a Spanner (anchor-based) cannot be reconstructed
  from the v0 event-reference and is reported unmigratable (read-only) under M1,
  so the catalog no longer silently implies it round-trips.

Review findings deferred to M2 (per project lead): migrate.rs unit tests for the
untested reconstruction branches, and the valuegen::spelling() mod-7 domain
cleanup. Gates unchanged and green (comment + spec-text only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:06:22 -04:00
Levi Neuwirth 4556ede9f0 Phase 2 (Agent K) M1: value-typed operation payloads + Operation Catalog scaffold
Foundation milestone for Track B's Operation Catalog: shift epiphany-ops from
the v0 identifier-only payload projections to durable value-typed payloads, and
scaffold the companion spec. Scope: the representative §6.10 set (7 primitives +
2 meta-ops); the slice-driven K0 expansion follows.

Core (the K↔J seam):
- epiphany-core exposes a public `CanonicalValue` trait (canonical_bytes /
  decode_canonical) delegating to the existing private `Codec` machinery, for
  Event/Rest/PitchSpelling/Tie/Slur/Beam/Spanner/RegionTimeModel/TimeAnchor.
  No new byte layout — a value's bytes equal what the whole-score codec emits,
  so all goldens / criterion 4 stay byte-identical.

Ops (value-typed payloads, frozen reduction rules):
- InsertEventOp{staff_instance,event:Event}, RespellPitchOp{pitch,spelling:
  PitchSpelling}, CreateCrossCuttingOp{structure:CrossCuttingValue},
  ChangeRegionTimeModelOp{...,new_time_model:RegionTimeModel},
  SetUserSystemBreakOp{...,anchor:TimeAnchor}, ReplaceWithRest{rest:Rest}.
  Payloads frame each value's CanonicalValue bytes behind a u32 length prefix.
- reduce.rs: read-sites only moved onto the value (rules, conflicts, ordering,
  promotion, re-anchoring, undo, transactions unchanged); reduce_onto now
  materializes the real event/structure instead of the C4 placeholder.
  MaterializedState.spellings now stores PitchSpelling (encode + decode updated).
- v0.rs: frozen identifier-only shapes (migration regression guard).
- migrate.rs: migrate_v0_envelope(v0, &Score) + project_v1_to_v0 + MigrationError;
  deterministic and equivalence-preserving. Respell spelling recovered from the
  score context; irreversible case is P12-K1.
- valuegen.rs: shared value-type builders (reused by fuzz, migration, tests,
  testkit). Resolves P11-C1; P11-C10 Dismiss recorded.

Testkit (Agent F merge gate):
- migration.rs: reduce(v1)==reduce(migrate(project(v1))) byte-identical, plus
  migration determinism and a non-vacuity guard; wired into acceptance.rs as
  agent_k_migration_equivalence_gate. Generators/harnesses build v1 payloads.

Spec:
- spec/operation_catalog.{tex,pdf}: new companion (independent semver) — framework
  + per-primitive template, the 7+2 representative primitives, the v0→v1 migration
  contract, and K1 framework slots for the remaining K0 primitives. Builds clean.
- PASS12_BATCH.md: P12-K1 (respell fingerprint irreversibility).

Gates: cargo build/fmt/clippy -D warnings clean; cargo test --workspace green
(incl. criteria 1/4/5/6 and the new K gate); conformance_suite scale 1 passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 11:52:02 -04:00
Levi Neuwirth ac7c076e73 Phase 2 (Agent I): visible-slice scaffold — Bravura SVG renderer vs stub
Lands the renderer-against-stub slice of Agent I's visible engraving work
(spec/PHASE2_QUICKSTART.md). Two new crates; prerequisites (G Pass 11, H
spelling/decomposition) are in place. Real engraving + Minimal-tier solver
follow next phase.

epiphany-render-svg (the deliverable this phase):
- Renders a ResolvedLayoutIR to well-formed SVG 1.1, drawing each glyph as a
  GENUINE Bravura SMuFL outline <path>. Outlines are extracted reproducibly
  from the official OFL Bravura.otf by a committed generator
  (tools/extract_bravura_outlines.py, OFL.txt); the font is not vendored, only
  the generated Rust (src/outlines_generated.rs). Staff-space/y-up coords with
  one global y-flip wrapper; viewBox in staff spaces, px scale on the root.
- Non-overreach: every element traces to a ResolvedGlyph (data-prov) or a
  declared wrapper; a glyph lacking an outline is surfaced as a diagnostic and
  drawn as a fallback rect, never silently dropped.
- Hand-rolled xml::check_well_formed (no XML dep); acceptance tests cross-check
  with system xmllint when present.
- examples/render_fixture.rs demo (fixture name -> SVG stdout, --solver=stub|real).
- Golden-locked machine acceptance snapshot + full-SVG golden for
  ten_measure_single_staff and valid_score_rich; deterministic output.

epiphany-engrave (honest scaffold):
- Engraver: a deterministic horizontal-spacing pass (first axis of the planned
  two-pass spring layout). Reports SolverTier::Stub — NOT Minimal — until it
  evaluates the declared hard constraints, guarded by a regression test. The
  demo's --solver=real exercises it end to end.

Honesty notes (recorded as Pass-12 candidates P12-I1..I3 in spec/PASS12_BATCH.md
and the crates' DECISIONS.md): the v0 to_logical/to_constrained pipeline is a
structural placeholder (arbitrary glyph per object, y=0), so stub output is not
yet recognizable notation and the QUICKSTART human visual gate is a next-phase
gate; MUSCLOID layout-id derivation stays unwired; bundled BRAVURA_METRICS are
approximations that disagree with the real outlines.

Gates: cargo fmt + clippy -D warnings clean; cargo test --workspace 504 passed,
0 failed, 0 ignored (criterion 6 layout round-trip still green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:01:40 -04:00
Levi Neuwirth 732660988d Phase 2: spelling + decomposition pre-passes (Agent H) with F merge gate
Land the two real pre-passes as canonical *derived annotations* (pure
functions of the materialized Score + profile, recomputed on
materialization, never serialized into canonical Score bytes), exposed via
`derive_annotations`:

- Spelling: a Temperley-style line-of-fifths centre-of-gravity preference
  rule (key-free, deterministic), preserving authored CMN letters and only
  inferring spelling for chromatic/integer input. `resolve_spelling`
  layers authored overrides above the inferred default (the RespellPitch
  precedence rule). `spell` now takes `&Pitch` and delegates to
  `simplest_spelling`.
- Decomposition: metric greedy-aligned splitting on a 1/4096 integer grid
  (barline + dyadic-boundary ties), with exact sounding->notated tuplet
  conversion before gridding. Components reconstruct the event duration
  (invariant 15).
- A per-event-kind eligibility `TaxonomyReport` so "ineligible" is always
  explicit and counted, never silently absent.

Test infrastructure (Agent F): a 29-fixture representative corpus +
taxonomy harness (corpus.rs), the H spelling/decomposition merge gate
(prepass_harness.rs), a discrete `tests/prepass.rs` CI target, conformance
stage [7b], a dedicated CI job, and the Pass-12 batch tracker.

Review hardening folded in (nine findings):
- Guard `decompose_metric` against a zero-length measure (was a
  divide-by-zero panic; now reported ungriddable).
- Resolve spelling-override priority via `Reverse` instead of negation
  (was an i32::MIN overflow).
- Verify spelling *register* (octave), not just pitch class, in the gate.
- Close the decomposition under-emission gap: the unusual-outcome
  taxonomy buckets are an exact per-fixture whitelist (classify_corpus
  step 5b).
- Generalize `accidental_ids` to a glyph stack so authored extreme
  alterations (triple-sharp+) reconstruct exactly instead of being clamped.
- Per-fixture spread checks in the non-vacuity tripwire and broad-bucket
  coverage, so no single rich fixture can carry a signal (partial-stub
  resistance); added a `mixed_rhythm` fixture for margin.
- Pin the integer-grid note-value math to the canonical rational helpers
  via an exhaustive test; cross-reference comments.
- Replace the O(n^2) tuplet innermost-resolution scan with an id index.

fmt + clippy -D warnings clean; 199 tests pass; conformance suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011giSRaHCFCGm1Z2SWv6JHt
2026-06-23 16:33:39 -04:00
Levi Neuwirth 83bc202ff6 Pass 11 follow-up: disambiguate 'reserved built-in tags' in Appendix E
The Canonical Byte-Layout Reference used the phrase 'reserved built-in
tags' in two scopes: the system-derived-identifier section called it the
closed set of three (MUSCSVCE/MUSCSPCH/MUSCSANM), while the domain-tag
registry calls eleven tags 'reserved built-in'. The source section
(req:graph:system-derived) qualifies the three as tags 'for
system-derived identifiers'; the consolidation had dropped that
qualifier, leaving the single-import appendix internally ambiguous for
the Binary Format companion author.

- Restore the scope qualifier and cross-reference the full registry,
  noting the other reserved tags feed plain hashing preimages, not the
  system-derived counter.
- Tighten the 'canonical' gloss: canonical tags produce identifiers and
  content hashes that are part of the interoperable, durably persisted
  form (not loosely 'document state', which misreads the storage-layer
  chunk/manifest/blob tags).

Spec-text only; rebuilds clean (0 undefined refs, 261 pages, no new
overfull boxes). No code or byte-layout change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 14:35:30 -04:00
Levi Neuwirth dffac4c744 Pass 11: consolidated byte-layout appendix + pin ObjectKind/ResolutionAction discriminants
Adds Appendix E (Canonical Byte-Layout Reference) — the single
byte-convention table the Binary Format companion imports — and closes
two pins (ObjectKind, ResolutionAction discriminant bytes) that were
golden-locked in code but absent from spec text. Audit follow-up:
completed the domain-tag registry (added canonical MUSCCONF/MUSCENVH and
non-canonical MUSCFNTM) and corrected golden-lock wording for the two
non-literal-byte anchors (BlobId, RationalTime).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 13:16:20 -04:00
Levi Neuwirth 0d8ec61a3c Pass 11 audit follow-up: honest LayoutObjectId status + doc/test gaps
Independent audit of b2f2e20 / a7adbdc. The canonical, document-state byte
layouts were already correct and golden-locked; this closes the one real
spec-vs-code gap (in the non-canonical layout namespace) and the smaller
doc/test gaps around it.

LayoutObjectId (item 2.6): the spec normatively stated the id "is derived ...
with MUSCLOID" and the ratification log / layout DECISIONS header called it
"pinned" -- but no code uses MUSCLOID (stable_layout_id and
manifestation_layout_id are untagged; synthesized_layout_id borrows MUSCCONF),
and MUSCLOID cannot even be constructed: DomainTag::from_bytes accepts only
built-ins or MUSCS-prefixed tags, so wiring it requires changing the frozen
determinism crate (out of scope for a spec pass, and the spec itself says
MUSCLOID is not a canonical system tag). Kept MUSCLOID as the pinned Track-A
target and made every artifact honest that the v0 code is provisional:
  - spec: "is derived" -> "MUST be derived" (forward contract) + a note that
    the prototype mints provisional ids; changelog "pinned" -> "specified ...
    as the Track-A target".
  - records: ratification-log line 2.6 and layout DECISIONS header/body now
    state spec-pinned-but-code-provisional; stale "Pass 11 candidate 3"
    pointer now cites the ratified requirement.
  - provenance.rs comments name the MUSCLOID target and label the current
    derivation provisional.

Other fixes:
  - epiphany-core/DECISIONS.md: the Tuplet bullet still claimed degenerate
    ratios are caught by runtime invariant 16 "since a Tuplet is a plain
    struct" -- stale after the construction-time TupletRatio change. Rewritten.
  - codec.rs: added degenerate_tuplet_ratio_is_rejected_on_decode, guarding
    the TupletRatio::dec re-validation branch that no test exercised (the
    constructor was tested, the decode path was not).
  - spec: integrity-anomaly snippet kind.canonical_bytes() ->
    to_canonical_bytes() (the actual method).
  - ids.rs / reduce.rs: clarifying comments (ManifestId's intentional,
    golden-locked document_id/generation duplication; compute_promotions
    bucketing by voice == (staff_instance, original_voice) via Invariant 5).

Verification: cargo test --workspace (434 pass, +1), clippy --all-targets clean
(0 warnings), fmt clean; spec rebuilds (lualatex, 0 undefined refs, 254pp).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 10:02:05 -04:00
Levi Neuwirth a7adbdc5a1 Pass 11 follow-up: golden-lock the ratified discriminant tables; fix three spec-text inaccuracies
Audit follow-up to b2f2e20. The ratification was byte-faithful, but the
audit found a gap between the protection the worklist claims ("a
golden-bytes test already locks every byte-layout item") and the
protection actually in place for several newly-normative tables, plus
three small inaccuracies in the ratified spec text.

Golden locks (close the round-trip-vs-golden gap):
- ChunkKind: chunk_kind_discriminants_are_golden pins the literal 0..=8.
  ChunkKind::canonical_bytes() is in the chunk hash preimage, so the
  prior round-trip-only test would let a coordinated renumbering silently
  change every chunk content address while passing.
- CompressionAlgorithm: compression_algorithm_encoding_is_golden pins the
  exact bytes (None -> [0,0], not a bare tag).
- ProfileId (load-bearing superblock field): profile_id_discriminants_are_golden
  pins the u32 discriminants and the fixed 20-byte encoding.
- ResolutionAction / TransactionCategory / ObjectKind: *_discriminants_are_golden
  pin the canonical discriminants (ObjectKind feeds the anomaly id;
  ResolutionAction/TransactionCategory feed operation content hashes).
- IntegrityAnomalyId: integrity_anomaly_id_byte_form_is_locked golden-locks
  the MUSCSANM-derived id (cross-replica agreement is a conformance
  property; it previously had no byte-form golden).

Spec-text fixes (core_spec.tex):
- CompressionAlgorithm: "None = 0 (no payload)" was wrong; the code writes
  a fixed two bytes (discriminant + always-present parameter byte). Text
  now states the fixed-width framing.
- ProfileId: "a single discriminant followed by any variant payload" was
  wrong; it is a u32-LE discriminant + a fixed 16-byte registry id (zero
  unless Custom), 20 bytes total. Text now matches the only encoding.
- TupletRatio listing showed `pub` fields (freely constructible by struct
  literal), contradicting req:time:tuplet-ratio-construction. Listing now
  shows private fields + the checked `new`/`actual()`/`notated()`, matching
  the code.

Test honesty:
- testkit resolution_action generator now emits Dismiss (rng.below(6)); it
  previously skipped the variant, leaving the Dismiss path unfuzzed.

Verification: cargo test --workspace (433 pass, +7), clippy -D warnings
clean, fmt clean; spec rebuilds (lualatex/latexmk, 0 undefined refs, 254pp).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 09:22:55 -04:00
Levi Neuwirth b2f2e204a7 Pass 11: ratify provisional byte choices into normative spec text
Spec-revision pass (architecture unchanged) converting the v0
implementation's provisional, golden-locked choices into ratified
core_spec.tex text, so durable byte layouts are fixed before the
next-phase build-outs. Worklist: spec/PASS11_WORKLIST.md; per-item
dispositions: spec/PASS11_RATIFICATION_LOG.md.

Adopt-and-pin (bytes): TypedObjectId 16-bit BE discriminant table
0..=27 (added the 5 variants the code carried); promoted-voice
(MUSCSVCE), synthetic-pitch (MUSCSPCH, tuning always in identity),
and integrity-anomaly (MUSCSANM, now a reserved built-in tag)
derivations; ChunkKind/ProfileId/CompressionAlgorithm discriminants;
ManifestId preimage (manifest_id excluded); RationalTime/scalar
layouts + the codec convention baseline the Binary Format companion
inherits.

Decide-and-pin: tempo Linear interpolates speed (not bpm);
StructuralFieldCollision tags the winner Conflicted; lifted the
>2-way / partial-overlap voice-promotion rule to normative; pinned
TransactionCategory and ObjectKind core vocabularies; added
ResolutionAction::Dismiss so the Dismissed state is reachable by an
authored op; pinned the (non-canonical) LayoutObjectId derivation
(MUSCLOID).

Fixes: blob hashing is bare MUSCBLOB||payload (deleted the
contradictory "identically to chunks" phrasing); equal-generation
superblock rule (DivergentSameGeneration); defined ProfileConstraints
with the required RetentionPolicy + first-declared precedence; made
the DVV zero-based floor normative; reconciled the invariant count to
19 and named the three construction-time MUSTs — TupletRatio now
rejects degenerate ratios at construction (zero term or
actual==notated), enforced by a checked constructor + codec decode
validation.

Code changes carry regression tests; byte-layout golden tests now
cite their ratified requirements. Workspace green: cargo test, clippy
-D warnings, fmt; spec builds (lualatex, 253pp). Per-crate DECISIONS
files annotated with the ratification status.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011giSRaHCFCGm1Z2SWv6JHt
2026-06-21 22:30:06 -04:00
Levi Neuwirth e9c4bad7a6 Land M1 + M2 (Agent C): framework edge fixes and real-Score graph integration
M1 — fix Agent C framework defects, tests-first:
- causal ordering: topologically order DVV edges instead of assuming HLC
  alone implies causal order (false for adversarial remote envelopes);
  HLC only breaks ties among ready operations.
- anomaly cutoff: quarantine from the earliest counter participating in
  any violating HLC pair (suffix-minima), e.g. [100,200,50] quarantines
  from counter 0, not counter 1.
- pending detection: DVV contiguous ranges use the zero-based per-replica
  counter floor; first absent id in any asserted range holds the dependent
  pending (vector coverage, not only dots).
- transaction snapshots: rollback removes member-generated conflicts.
- edge tests in concurrent_reduction.rs for all six audited cases.

M2 — reduce onto Agent B's real score graph:
- OperationSet::reduce_onto(&Score) -> GraphMaterialization { state, score }
  mutates the real arena, voices, regions, tombstones, indexes, and
  cross-cutting structures; base-free reduce() retained.
- VoiceOrigin::SystemPromoted now carries { winning_operation,
  losing_operation, original_voice }; spec and Invariant 18 updated.
- graph-aware migration, forward undo, system breaks, promotion pre-pass.
- tests/graph_reduction.rs: 11 tests asserting check_invariants is clean,
  plus a 64-seed order-independence sweep.

Pass-11 spec decisions resolved (blocking subset): DVV floor (P11-C7),
HLC-vs-causality, promoted-voice derivation inputs (P11-C4 / core P11-3).
Payload/Score canonical encoding remain deferred to the companion docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 16:37:51 -04:00
Levi Neuwirth a2e9ec32f6 A B C D F 2026-06-19 12:42:31 -04:00