epiphany/crates/epiphany-core/src
Levi Neuwirth 23521b4bcf P13-S3: the engraved spelling set is a shared undo key, not the transpose's
My design note was half right, and the audit caught the other half. Keeping
engraved_spelling_chain physically separate from respell_chain was correct --
respell_chain is RespellPitch's LWW working state, read by its
concurrent-differing conflict detection, and folding transposes into it would
make a concurrent respell conflict with a transpose and move the canonical
bytes of every existing history. What that did NOT license was letting one
operation own the key.

RespellPitch mutates the same graph attachments and recorded nowhere on the
attachment chain, so a chain with a single writer was wrong both ways. Both
reproduced:

  respell -> [tx: transpose] -> StrictInverse undo
      Applied; pitch restored to C4; the UserChosen(C) attachment ERASED.
      The respell was an operation, not part of the base, so the transpose's
      chain had never seen it and its predecessor was absence.

  [tx: transpose] -> respell -> StrictInverse undo
      Applied; the newer UserChosen(D) authoring WIPED. The respell was
      invisible to the chain, so it never registered as a superseding writer,
      contradicting the catalog's rule that a later canonical writer supersedes
      a strict undo.

And BestEffort could restore the pre-transpose pitch while leaving a spelling
authored against the transposed one attached to it.

Fix: every writer of the attachments records on the attachment chain
(record_engraved_spellings), and a pitch's value and its engraved spelling set
undo as one unit -- if either half is superseded, neither is restored.
StrictInverse already refuses on any supersession, so the coupling only bites
for BestEffort. Two physical chains, two responsibilities: respell_chain owns
the ledger spelling and the LWW verdict, engraved_spelling_chain owns the graph
attachments.

Recording is gated on graph presence, so base-free reduction is byte-unchanged
and the seeded corpus's canonical-base digest does not move.

Four tests, two mutations verified: removing the respell's record fails all
three undo tests; removing the coupling fails the best-effort one with the
pitch back at C4 and its spelling still at C-sharp. The fourth test locks
convergence -- both permutations of a concurrent respell/transpose reduce to
identical canonical bytes.

Spec: new req:opcat:spelling-set-chain. Batch 3 of the Pass-13 ledger reopens
(P13-S1, S2 open; S3 resolved here).

Also: PitchSpelling::transposed's doc said B-sharp 3 becomes F-double-sharp 3.
The code, spec, and tests all correctly produce F-double-sharp 4 -- B to F
carries the octave. Doc only.

Gate: fmt clean, clippy 0, 30 targets / 998 passed / 0 failed, docs 0 under
-D warnings, conformance 8/8, zero golden churn, canonical-base digest unmoved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 17:29:05 -04:00
..
prepass Schema major 2 Phase B: snapshot side + honest stamps (data-model fills) 2026-07-07 16:30:07 -04:00
codec.rs Pin the interval algebra on the type that already carried it 2026-07-09 15:29:44 -04:00
event.rs Land M1 + M2 (Agent C): framework edge fixes and real-Score graph integration 2026-06-21 16:37:51 -04:00
fuzz.rs Schema major 2 Phase D: the repeat-authoring pair (code tranche) 2026-07-07 20:06:18 -04:00
generators.rs Schema major 2 Phase B: snapshot side + honest stamps (data-model fills) 2026-07-07 16:30:07 -04:00
graph.rs TransposeInterval: the faithful transpose, and the frozen one it replaces 2026-07-09 15:46:43 -04:00
ids.rs Pass 11: ratify provisional byte choices into normative spec text 2026-06-21 22:30:06 -04:00
indexes.rs Schema major 2 Phase D: the repeat-authoring pair (code tranche) 2026-07-07 20:06:18 -04:00
invariants.rs Schema major 2 Phase D: the repeat-authoring pair (code tranche) 2026-07-07 20:06:18 -04:00
lib.rs Pin the interval algebra on the type that already carried it 2026-07-09 15:29:44 -04:00
pitch.rs P13-S3: the engraved spelling set is a shared undo key, not the transpose's 2026-07-09 17:29:05 -04:00
prepass.rs A transposition moves the authored spelling; recording Propagated is not enough 2026-07-09 16:40:06 -04:00
tempo.rs Push 4: Binary Format companion, F1 benches, subquadratic reduction order 2026-07-02 19:02:07 -04:00
time.rs Pass 11: ratify provisional byte choices into normative spec text 2026-06-21 22:30:06 -04:00