The nine type bodies fill to the ratified Ch5 shapes and the wire moves
to the Binary Format §Schema Major 2 layouts, review-hardened (high-
effort review; 8 findings, 7 fixed + 1 deferral sharpened).
epiphany-core:
- 19 new leaf types in graph.rs (SlurKind/CurveDirection/
CurvatureOverride/SpanStyle/LineStyle/SpaceUnit/SubBeam/
BeamGeometryOverride/SpannerKind+5 payload types/RepeatKind/Volta/
StaffBracketKind/MetadataEntry/MetadataValue/Timestamp/
SoundConfiguration/TranspositionInterval/UnpitchedMember) with the
ratified discriminants (cstyle_enum_codec! reused; tagged unions
hand-written); nine struct fills appended in wire order.
- The frozen wire forms generalized to a SHARED sub-codec layer
(enc_/dec_*_v1, v0==v1 for every type major 2 changed; vec framing
through enc_/dec_vec_v1) used by the new decode_v1_score/
encode_v1_score AND the rerouted v0 pair — major 2 touched types the
v0 walk had treated as unchanged (metadata, staves, cross_cutting,
staff instances transitively). Strict-canonical guards on every
versioned path; decode_canonical_versioned dispatches {0,1,2} with
composed default-fill migration.
- Invariants extended to the new reference-bearing fields (REVIEW
FIX): Beam.sub_beams events, RepeatKind DaCapo/DalSegno anchors,
Volta spans now covered by CrossCuttingRefsResolve + the anchor
model walk, with negative tests.
- Tests: v1 migration size-anchor (v1 omits exactly the appended
default bytes — the frozen encoder cannot drift), a non-default
round-trip covering every new field and every SpannerKind/RepeatKind
wire arm, fuzzer corpus gains genuine-v1 forms + the major-2 seam
with enforced must-decode-Ok on unmutated frozen forms.
epiphany-ops + epiphany-bundle (Phase C's semantic core, landed here
deliberately — the live codec flip makes CrossCutting/Staff/Metadata
payload bytes v2 immediately; shimming nine transitively-embedded
types the major-1 D1 way was throwaway):
- Minimal-stamping OperationKind::schema_major per the ratified rule
(CrossCutting/CreateStaff/SetMetadata always 2; CreateRegion 2 iff a
carried instance bears Some(staff_lines_override) else 1;
CreateStaffInstance/SetStaffLayout 2 iff Some else 0), unit-locked.
- Bundle op-block accept-set [0,2]; SchemaVersion::V2;
beyond-accept-set tests moved to major 3; testkit V2
stamp-derivation test.
- the_canonical_base_is_byte_identical_across_data_model_majors:
pinned blake3 of a seeded reduction — the companion's SHOULD that
the canonical base never moves across data-model majors.
- The op-payload migrate-on-read deferral restated precisely in
DECISIONS (no consumer byte-reconstructs op payloads today; the
first one must bring per-type frozen payload decoders).
Zero golden churn (fixtures deliberately carry v2 defaults).
Instrument::new consolidates the sweep's default fills. 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
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
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
The full-Score snapshot side of the three schema-major-1 data-model fields,
on a struct-level frozen v0 decoder. Snapshot-only: the CreateRegion op
payload stays byte-v0 (D2 owns the op-block migration + read-only).
Data model (epiphany-core):
- PitchRange { lowest, highest: Pitch } in pitch.rs; contains() is frame-guarded
(decides only within a shared PitchSpaceId) and treats a reversed range as
undecidable (None), not "everything out of range".
- Instrument.range: Option<PitchRange>; Region.permits_spanning_slurs: bool.
Codec:
- struct_codec! for PitchRange; Instrument -> {id,name,range}; Region appends
permits_spanning_slurs.
- Replaced the Phase-C byte-splice with a struct-level decode_v0_score: a
hand-written 19-field Score walk using dec_canvas_v0/dec_region_v0/
dec_instruments_v0 for the two changed fields (nested in Vecs) and the current
Codec for the other 17. Removed the now-unused Reader::pos().
- Region.canonical_bytes_v0() (+ enc_region_v0) is the frozen v0 op-payload
surface: CreateRegionOp embeds it so the op-envelope block stays byte-v0.
Advisory preconditions (epiphany-ops/validate.rs):
- PitchOutsideInstrumentRange: pitch-in-range via voice->instance->staff->
instrument (honoring instrument_override); "if any"/indeterminate-frame pass.
- Slur-spanning suppressed only when BOTH endpoint regions permit (AND
semantics; documented pending spec ratification of which region governs).
Tests: the three frozen-decoder fixtures (non-default v1 round-trip; a mirror
v0 encoder synthesizing genuine v0 bytes that migrate default-filling all three
fields, anchored by an independent byte-length check; the nested-Vec multi-
region case); the byte-v0 CreateRegion payload; the frame-aware/reversed-range
contains(); and 5 advisory tests. ~29 construction sites updated. Full gate
green (workspace tests, clippy -D warnings, fmt, rustdoc -D warnings).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
The first real data-model field of the schema-major-1 bump, and the point
where the dispatch seam flips from the Phase-B identity to a genuine
migration. Zero golden churn: CanvasLayoutDefaults::default() equals the
engraver's existing A4/8mm default, so no resolved geometry moves.
- Data model (core/graph.rs): Canvas gains `layout_defaults:
CanvasLayoutDefaults`, with new core geometry primitives CanvasSize /
CanvasMargins (staff-space CanonicalF64, A4/8mm Default -- core has no
geometry types of its own and must not depend on layout-ir). Exported from
the crate root; ~7 Canvas construction sites updated with
`..Default::default()`.
- Codec (core/codec.rs): struct_codec! for the three new types; Canvas v1
encodes `regions` then `layout_defaults`.
- The frozen migrate-on-read (decode_v0_score): a byte-level splice. v0 Score
bytes are the v1 layout minus Canvas.layout_defaults; Canvas is Score field 2
and its v0 layout was just `regions`. Read the v0 prefix (metadata,
canvas.regions) to find the split, insert the default CanvasLayoutDefaults
encoding, then decode the resulting v1 bytes. Total and default-filling (no
score context), frozen by value. A `pos()` accessor was added to Reader for
the splice.
- Test (v0_score_migrates_by_default_filling_layout_defaults): derives REAL v0
bytes by stripping the field from a v1 encoding, then migrates them back and
checks the original score is reconstructed with the default refilled. A wrong
splice offset corrupts the bytes and fails the decode, so it guards the
frozen v0 assumptions.
Deferred: routing the migrate through a bundle acceleration_snapshots slot (a
Phase-B Finding-3 item). The migrate logic is proven by the core unit test and
the bundle read path by the canonical-base roundtrip; combining them hits
cross-crate friction (constructing v0 bytes needs core-internal Reader, which
testkit can't reach and core-below-bundle can't stage) for marginal coverage.
The forward scenario (v1 reader migrating a v0 bundle's major-0 acceleration
snapshot) needs no new bundle code -- the v0 snapshot passes the exact gate.
863 workspace tests pass; clippy -D warnings, fmt --check, rustdoc -D warnings
all clean; no render/snapshot goldens changed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
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
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>
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