spec/vectors/decode_vectors.txt -- 37 committed byte strings across five
surfaces, each with its normative accept/reject verdict. The reference
implementation's fuzzers prove its own decoders self-consistent, which says
nothing about whether a foreign decoder agrees with the format. This is what one
is checked against. Gated in the conformance suite as [7d], and drift-locked:
the committed file must equal vectors::render(), so a wire-format change lands
in the diff.
It found a real defect on its first run. OperationKindTag::TransposeInterval
encoded to [30] and its own decoder REJECTED it -- Push 4a added the variant to
discriminant() and never to decode_canonical. OperationKindTag is what edit
barriers persist, so a barrier prohibiting TransposeInterval could be written and
never read back. Silent data loss on reopen.
Four things should have caught it. None did, and two made it worse:
The round-trip test enumerated DISCRIMINANTS -- (0u8..30).map(decode_canonical)
-- starting from bytes the decoder already knew, so it structurally could not
notice a variant the decoder was missing. It now enumerates VARIANTS from one
all_tags() list, with a completeness check in both directions.
The distinctness test's hand-written variant list omitted it too. Same list now.
operation_kind_tag_decode_rejects_malformed_bytes asserted that tag 30 is
REJECTED, and layout-ir's decode_rejects_unknown_discriminants asserted the
same at the barrier surface. Both were locking the bug in place and made it
look deliberate. Both now name 31, and a new barrier test round-trips a barrier
prohibiting every tag -- the persistence surface where this actually bites.
The P2 decode fuzzer fed valid corpus bytes to the tag decoder and tallied the
failure as a REJECTION, like any garbage input. It never asserted that an
unmutated corpus entry decodes. Both fuzzers now do, as a pre-pass.
The harness had the same disease as the code. `check` collapsed "rejected" with
"accepted but does not re-encode", so a decoder that silently normalizes
non-canonical bytes PASSED the reject vectors it was written to catch. Verified:
removing the whole-state guard, and restoring the lenient compression codec, both
left the corpus green. `check` now returns Ok(injective) for accept and Err for
reject and never conflates them -- silently normalizing non-canonical bytes IS
accepting them. With that fixed, all four defect mutations fail the corpus, each
naming its class.
The corpus pins one vector per class this repo has shipped a bug in:
non-canonical-map-order (a guard catches it; no per-site check exists),
non-canonical-vec-order (only a per-site check catches it; a guard is blind),
lenient-sub-codec (a guard masked it in the manifest; the index had none), plus
trailing-bytes, truncated, unknown-discriminant, count-exceeds-remaining. A test
fails if one goes missing.
Gate: fmt clean, clippy 0, 30 targets / 1024 passed / 0 failed, docs 0 under
-D warnings, conformance 8/8 (now including [7d]), zero golden churn.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Packages the proven editing-loop vertical slice as the API a GUI calls -- no UI,
no rendering backend of its own (it produces a RenderIR). EditorSession owns:
- selection state (Selection { source, layout_object }): click(point) selects the
topmost hit, select(id) restores a selection, selection()/clear_selection();
- render/hit-test query: render() and hit_test() for the GUI to draw and resolve
clicks/drags;
- operation minting -- the ergonomics gap the harness exposed, closed before UI
depends on it: the caller passes an OperationKind to apply() (or an intent like
transpose_selection(+1)) and the session assembles the OperationEnvelope (id,
author, stamp, causal context). A GUI never hand-rolls envelope bookkeeping;
- apply/re-render -- ATOMIC: a minted op the reducer rejects (e.g. a reserved
replica identity) returns Err(RejectedOperation), not a silent no-op, and a
diagnostic-only layout returns Err(NotRenderable); on any error nothing mutates,
operation counter included (the candidate id is committed only on success);
- selection preservation: the selection is re-resolved against the new layout,
kept when its layout object survives and cleared when it is gone.
The session is solver-agnostic (Box<dyn ConstraintSolver>), so a GUI plugs in the
Engraver, the stub, or any conformant solver. EditorError implements Display/Error.
epiphany-ops now re-exports AcceptOutcome (accept()'s return type, previously
unreachable) so a caller can inspect a rejection.
Also wires the edit-loop harness into the conformance suite as the [7c] UI-seam
gate: over both fixtures (ten_measure_single_staff and valid_score_rich) every seed
must drive a click->sharpen->re-render cycle whose selection survives the relayout
-- the contract a GUI's correctness rests on.
Full gate green: build, fmt, clippy, 606 tests, conformance scale 1 (incl. [7c]).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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
Chapter 8's OperationEnvelopeBlock carries dvv_summary/min_stamp/max_stamp so a
reader can select or skip a block by causal frontier / stamp range without
decoding it. These are semantic (ops-computed); the bundle carries them opaquely.
Bundle (Agent D):
- OperationBlockSummary { dvv_summary: FrontierBytes, min_stamp, max_stamp } and
Manifest.operation_block_summaries: BTreeMap<ChunkId, OperationBlockSummary>,
keyed by the block's chunk id, encoded/decoded in canonical (ChunkId-ascending)
order and accessible via Manifest::operation_block_summary. Optional and
non-canonical; preserved across reopen by the manifest round-trip.
- Round-trip + selectability test.
Testkit (Agent F, the C/D integration point):
- roundtrip::operation_block_summary computes the summary from envelopes using
ops (causal frontier + min/max OperationStamp canonical bytes).
- assert_operation_block_summary_survives_storage commits a real operation block
+ its summary, reopens, and selects the summary by block id without decoding
the payload. Wired into acceptance + the conformance suite.
bundle DECISIONS updated (summary metadata now carried, not omitted); fixed a
stale "pending item 5" doc on criterion 4 (the whole-score codec has landed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements a total, reversible canonical byte form for the entire
epiphany_core::Score graph, unblocking the byte-level full-Score serialization
gate that M3 had to defer.
epiphany-core/src/codec.rs:
- Score::canonical_bytes() / Score::decode_canonical() with a validating
ScoreDecodeError and a forward-only Reader cursor.
- A local Codec trait with generic combinators (Option/Vec/BTreeSet/BTreeMap/
tuple) and macros (struct_codec!/cstyle_enum_codec!/unit_codec!/
catalog_id_codec!) so encode and decode stay symmetric across ~110 types
spanning graph.rs, event.rs, pitch.rs, time.rs, tempo.rs.
- Uniform form: LE integers, one discriminant byte per tagged union, u32
counts/length-prefixes, every variable-width leaf length-prefixed, raw UTF-8
for free text (so decode(encode(x)) == x for any valid score; catalog ids are
already NFC). EventArena round-trips via iter_canonical + insert.
- Two pub(crate) accessors added for the codec: EventOrderingDAG::edges_ref,
SpellingPrecedence::order_ref.
- Tests: generator-score corpus (valid_score + valid_score_rich), exotic
event/pitch variants the generators omit, distinctness, and decoder
rejection of trailing/truncated/empty bytes.
epiphany-testkit:
- roundtrip::assert_score_serialization_stable: encode the real Score, store it
as a bundle Snapshot, reopen + hash-verify, decode to an equal Score, and
assert a byte-identical re-encode.
- convergence::materialized_score builds a real ~50-bar reduce_onto
materialization for the gate.
- criterion_4_full_score_byte_roundtrip flips from #[ignore] to a live gate;
wired into the conformance suite. Docs (lib.rs, README, core DECISIONS P11-4)
updated to reflect the landed codec.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The audit flagged Agent F's criterion 1 (convergence) and criterion 4
(serialization) as testing the reducer-bookkeeping projection
(MaterializedState) while claiming to be full-Score gates. This makes them
honest, per item 4 of the v0 follow-up plan.
Criterion 1 — real-Score convergence:
- generators::graph_edit_session builds a real ~50-bar, two-voice edit
session targeting a base Score's actual voices (so it survives reduce_onto,
which rejects unknown voices).
- convergence::{assert_graph_convergence, run_graph_convergence} reduce that
session onto a real epiphany_core::Score via OperationSet::reduce_onto and
assert the entire GraphMaterialization (graph + bookkeeping) is identical
across delivery orders, passes check_invariants, and genuinely grows both
voices (non-vacuity).
- acceptance criterion_1_convergence now drives this; the former bookkeeping
convergence is retained and renamed reducer_bookkeeping_convergence.
Criterion 4 — honest serialization tiers:
- criterion_4_canonical_serialization_stability keeps the real typed/manifest
round-trips; the MaterializedState round-trip is split out as
reducer_bookkeeping_serialization.
- full_score_materialization_is_reproducible asserts the materialized Score is
reproducible across orders (the determinism precondition for a byte codec),
achievable without the codec.
- criterion_4_full_score_byte_roundtrip is #[ignore]'d pending item 5's
whole-score codec (visible as ignored, never falsely green).
Negative regression guards (src/negative.rs): one guard per audited M1 defect
(inverted causal/HLC order, missing predecessor via vector, HLC 100/200/50
quarantine-from-0, tx rollback of member conflicts, causally-ordered
same-position non-promotion, partial-duration overlap), driven through the real
epiphany_ops API with explicit negative controls. Wired into acceptance and the
conformance suite. Crate/README docs updated to match.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>