Commit Graph

5 Commits

Author SHA1 Message Date
Levi Neuwirth 03758bbae0 Schema major 1 Phase D1: Instrument.range + Region flag (snapshot side)
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
2026-07-06 11:14:52 -04:00
Levi Neuwirth e199fabe3f Schema major 1 Phase C: Canvas.layout_defaults + real migrate-on-read
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
2026-07-05 20:37:36 -04:00
Levi Neuwirth 92aaccf7e2 Pushes 1+3: fix the MUST-level violations, wire the types-only machinery
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
2026-07-02 17:10:50 -04:00
Levi Neuwirth 7a94814ba3 Cross-seam review fixes: respell→pre-pass visibility, profile enforcement, canonical fingerprint, catalog reconciliation
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>
2026-06-25 18:52:34 -04:00
Levi Neuwirth 732660988d Phase 2: spelling + decomposition pre-passes (Agent H) with F merge gate
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
2026-06-23 16:33:39 -04:00