epiphany/crates/epiphany-editor-core
Levi Neuwirth 655cf8ef5b TransposeInterval becomes undoable; the frozen Transpose stays un-undoable
Audit finding 3, reproduced and fixed. Neither transpose recorded into
pitch_modify_chain, so UndoTransaction(StrictInverse) over a transaction
containing one reduced to NoOp(TargetMissing) and left the pitch shifted.
EditorSession::undo works only because it re-materializes from a truncated log,
which is a different mechanism entirely.

The behaviour gap was pre-existing: the frozen Transpose does the same, and the
pre-Push-4a catalog said so honestly ("an inverse-interval undo is a Phase-3
refinement, P11-C8"). What was new was my claim, written into the catalog for
both kinds, that the write chain handled it. It did not.

Per the ratified call, the fix is asymmetric, and the asymmetry is the point.
TransposeInterval now records its pitch write and its spelling rewrites, so
value-restoring undo returns both. Transpose does NOT start recording: that
would not change its own reduction rule, but it would change what a stored
{Transpose, UndoTransaction} history replays to -- from "the pitch stays
shifted" to "the pitch returns" -- and that is a change in what an existing
document means, which is the one thing the freeze forbids. The old operation is
therefore permanently un-undoable, which is one more reason never to author it.
the_frozen_transpose_is_not_undoable_and_that_is_frozen_too pins that, and was
verified by the inverse mutation: making it undoable fails the test.

Restoring the pitch alone would leave a notehead spelled for a pitch that is no
longer there, so a new chain -- transposed_spelling_chain, keyed by pitch,
holding the engraved-layer explicit attachment SET -- restores the moved
authored attachments and removes the propagated one together. It is deliberately
not respell_chain: RespellPitch owns that chain, its last write is the LWW
working state its concurrent-differing conflict detection reads, and folding
transposes into it would make a concurrent respell conflict with a transpose and
would move the canonical bytes of every existing history.

Base-free reduction records nothing, because it writes nothing: the fuzz
corpus's canonical-base digest is unmoved.

Three mutations verified: the pitch-value record removed, the spelling-set
record removed (both undo tests fail), and the frozen Transpose "helpfully" made
undoable. The second of those first appeared to survive -- my harness restored
the file between the two tests, so the second ran unmutated. A mutation that
no-ops looks exactly like a test that passes; that trap, recorded after Push 4a,
was hit again here in a new form.

Gate: fmt clean, clippy 0, 30 targets / 993 passed / 0 failed, docs 0 under
-D warnings, conformance 8/8, zero golden churn.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 16:45:57 -04:00
..
src TransposeInterval becomes undoable; the frozen Transpose stays un-undoable 2026-07-09 16:45:57 -04:00
Cargo.toml epiphany-editor-core: a headless editor API + a conformance UI-seam gate 2026-06-27 16:47:34 -04:00
DECISIONS.md Pass 12 G-pass: ratify the full batch (28 rows) — spec tranche 2026-07-07 10:23:30 -04:00