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
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>
The layout time axis was inert: TimeAxisModel carried bare Vec<SpringSlotId>,
project()/affected_slots() ignored their arguments (returning the first slot /
all slots), nothing populated it, and nothing consumed it.
Now it carries ordered SlotPlacement { time, slot } entries and has real
behavior:
- project(time) returns the slot covering a time (greatest placement at or
before it; first when the query precedes them all);
- affected_slots(range) returns the slots in a half-open time range;
- slots() lists them in time order;
- with_placements populates and sorts the axis from resolved spring slots.
The spacing stage (to_constrained) now populates each region's axis from its
spring slots and carries the populated axis on ConstrainedLayoutRegion, so the
axis is a real, consumed artifact. Tests cover project/affected_slots semantics
and that spacing produces a per-region axis whose project() is a genuine
function of the queried time. DECISIONS updated.
This completes item 6 (and the whole v0 follow-up list, items 1-6 / M1-M5).
(The slot times are still the prototype's wall-clock spacing columns; mapping a
metric region's measure/beat grid to musical times is the next layer.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
E-D (layout-ir, honest solver tier): add SolverTier::Stub (a non-conformance rung
below Minimal) and have StubSolver report it instead of falsely claiming the
Minimal conformance tier; the passthrough evaluates no constraints and computes
no quality metrics.
E-C (layout-ir, constraint/reference validation): ConstrainedLayoutIR::validate()
now also checks the LayoutConstraint vector — NoCollision/Align/PositionWithin
must name glyphs in the set, SystemBreakAt/PageBreakAt must name existing slots,
PositionWithin regions must be finite/non-negative — rejecting dangling
references instead of silently accepting them.
E-B (layout-ir, content-sensitive ScoreVersion): derive ScoreVersion from the
whole score's canonical bytes (Agent B's whole-score codec) rather than the
layout projection's object identities, so a pure content edit that changes no
identifier still changes the version — required for correct incremental-layout
cache invalidation.
D-A (bundle, extension-root preservation): Bundle::commit now enforces
preservation — after the builder closure runs, every prior extension declaration
it did not re-declare (by extension_id) is carried forward verbatim, so an
extension-unaware writer cannot silently orphan an unknown extension's
preserved_chunk_roots. An extension-aware writer that re-declares its id keeps
control.
Each fix has a regression test; per-crate DECISIONS updated. (Item-6 remainder:
D-B operation-block summaries next; E-A real time-axis deferred per request.)
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>
Implements Agent E per spec/QUICKSTART.md — the layout intermediate
representation (Chapter 7) and the constraint-solver interface (Chapter 9):
* Four IR stages: LogicalLayoutIR -> ConstrainedLayoutIR ->
ResolvedLayoutIR -> RenderIR (interface only), with the composite-object
taxonomy, spring slots/constraints, vertical-band model, pages/systems,
engraving decisions + overrides, and the incremental dependency/cache model.
* TimeAxisModel tagged enum (Metric/Proportional/Aleatoric/Registered).
* Provenance back-references with manifestation- and synthesis-aware ids
((source, region) and (source, kind, ordinal)), so multiply-manifested and
synthesized objects never collide.
* In-tree Bravura GlyphCatalog (Send+Sync, metrics + render-data interface),
MUSCFNTM-tagged metrics hash with anchors hashed as a name-keyed map.
* Edit-barrier types keyed on OperationKindTag, with precise EditContext /
EditOracle scope/condition evaluation.
* StubSolver: returns SolveStatus::Solved with the input geometry verbatim;
spec-compliant SolveReport, Minimal tier + all-worst (unmeasured) metric
vector (no false conformance claim); rejects ill-formed input.
* f32 staff-space IR coordinates, quantized to the 1/1024 grid only at
canonical ResolvedLayoutIR serialization (Appendix D); non-finite geometry
is rejected, not normalized. Canonical encoding is injective in glyph
identity, provenance, engraving decisions, and catalog identity.
Re-points Agent F's testkit layout harness from its in-tree stub to the real
crate (v0 acceptance criterion 6) and expands its generators to E's public
surface. Expands Agent C's OperationKindTag to the full normative variant set
so edit barriers can prohibit every operation class.
Workspace gates green: fmt, clippy -D warnings, 377 tests, doc tests, rustdoc
-D warnings, and the conformance suite at scale 1. Decisions and Pass 11
candidates recorded in crates/epiphany-layout-ir/DECISIONS.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>