Commit Graph

8 Commits

Author SHA1 Message Date
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 47a581a4de Agent I-0: real clef + key-signature data in the core graph
Prerequisite for the visible-slice engraving milestones (I-1+): give the
score graph the clef/key data the engrave pipeline needs for clef-relative
staff positioning and key-signature rendering, replacing the Chapter-7
TimeAnchor-only placeholders.

epiphany-core:
- ClefShape { G, F, C, Percussion } — each family's reference pitch (G4 / F3 /
  middle C4) pins the staff-position mapping.
- Clef { shape, line, octave_shift } — the SMuFL family, the staff line its
  reference pitch sits on (1 = bottom line), and an octave transposition; with
  treble/bass/alto/tenor constructors and a treble Default. Generalizes to any
  C-clef line and octave-transposing clefs.
- KeySignature — circle-of-fifths position, validated -7..=7: a private field
  behind KeySignature::new (Option) + fifths(), and a custom Codec that rejects
  an out-of-range count on decode (Reconstruct, as PowerOfTwo / Tempo do).
- ClefChange now carries clef: Clef; KeySignatureChange carries key: KeySignature.
- Codec (cstyle_enum_codec / struct_codec / the validating KeySignature impl),
  lib.rs re-exports, a round-trip test over every shape x line x octave and every
  fifths value, and an out-of-range decode-rejection regression test.

Zero blast radius: nothing constructed these with data (clef/key sequences are
always empty) and invariants only read .anchor, so existing Score bytes, hashes,
and goldens are unchanged. Fixtures get populated and the engrave pipeline reads
the data in I-1.

Gates: build/fmt/clippy -D warnings clean; cargo test --workspace green;
conformance_suite scale 1 passes. Stages only epiphany-core; the pre-existing
Agent-I working tree (engrave/layout-ir/render-svg + .gitignore) stays unstaged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 10:31:16 -04:00
Levi Neuwirth d93baac3ba Agent K M2d review follow-up: harden the Group-4 score-settings ops
Address the five-finding review of M2d (1e4ab24) plus the two-finding
follow-up review; all fixes are code/test/doc only, no spec change needed
(the catalog/core-spec already classify metadata as advisory LWW).

- SetMetadata is now a true advisory LWW: it silently last-writer-wins in
  canonical order and records no conflict, matching operation_catalog
  §set-user-system-break. Dropped the StructuralFieldCollision, the
  `last_metadata` working slot, and the `env` parameter; rewrote the
  conflict test as `concurrent_differing_set_metadata_is_advisory_lww`
  (no conflict, state stays clean, permutation-independent resolution).
- SetMetricGrid / SetUserPageBreak / SetUserSystemBreak share a new
  `layout_region_slot` precondition backed by a `staff_based_regions`
  index: the target must be live and staff-based (FreeGraphic regions have
  neither a metric-grid nor a break slot). The index is read from base-free
  state, so reduce() and reduce_onto() reach the same verdict for missing,
  tombstoned, and FreeGraphic regions.
- SetMetricGrid now rejects a grid whose meter_sequence names an undeclared
  time signature, rather than installing an invariant-violating grid.
- User breaks materialize under the canonical LWW key: `apply_break_lww`
  drops any existing anchor resolving to the same position before adding,
  so the graph break list stays in lockstep with the resolved-position
  ledger map (shared `resolved_anchor_position`). Applied to page and
  system breaks alike.
- Coverage: SetMetadata/SetMetricGrid/CreateVoice/DeleteVoice added to the
  tag-distinctness test; layout_stub `gen_operation_kind_tag` extended to
  every normative tag; the MaterializedState decode test populates
  page_breaks; four direct regression tests pin each fixed bug; the stale
  SetMetadata/score_metadata doc comments now say advisory LWW.

Gates: build/fmt/clippy -D warnings clean; cargo test --workspace green
(533); conformance_suite scale 1 passes. Stages only core/ops/testkit; the
unrelated Agent-I working tree is left untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 21:45:51 -04:00
Levi Neuwirth b2f2e204a7 Pass 11: ratify provisional byte choices into normative spec text
Spec-revision pass (architecture unchanged) converting the v0
implementation's provisional, golden-locked choices into ratified
core_spec.tex text, so durable byte layouts are fixed before the
next-phase build-outs. Worklist: spec/PASS11_WORKLIST.md; per-item
dispositions: spec/PASS11_RATIFICATION_LOG.md.

Adopt-and-pin (bytes): TypedObjectId 16-bit BE discriminant table
0..=27 (added the 5 variants the code carried); promoted-voice
(MUSCSVCE), synthetic-pitch (MUSCSPCH, tuning always in identity),
and integrity-anomaly (MUSCSANM, now a reserved built-in tag)
derivations; ChunkKind/ProfileId/CompressionAlgorithm discriminants;
ManifestId preimage (manifest_id excluded); RationalTime/scalar
layouts + the codec convention baseline the Binary Format companion
inherits.

Decide-and-pin: tempo Linear interpolates speed (not bpm);
StructuralFieldCollision tags the winner Conflicted; lifted the
>2-way / partial-overlap voice-promotion rule to normative; pinned
TransactionCategory and ObjectKind core vocabularies; added
ResolutionAction::Dismiss so the Dismissed state is reachable by an
authored op; pinned the (non-canonical) LayoutObjectId derivation
(MUSCLOID).

Fixes: blob hashing is bare MUSCBLOB||payload (deleted the
contradictory "identically to chunks" phrasing); equal-generation
superblock rule (DivergentSameGeneration); defined ProfileConstraints
with the required RetentionPolicy + first-declared precedence; made
the DVV zero-based floor normative; reconciled the invariant count to
19 and named the three construction-time MUSTs — TupletRatio now
rejects degenerate ratios at construction (zero term or
actual==notated), enforced by a checked constructor + codec decode
validation.

Code changes carry regression tests; byte-layout golden tests now
cite their ratified requirements. Workspace green: cargo test, clippy
-D warnings, fmt; spec builds (lualatex, 253pp). Per-crate DECISIONS
files annotated with the ratification status.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011giSRaHCFCGm1Z2SWv6JHt
2026-06-21 22:30:06 -04:00
Levi Neuwirth 306871ea29 Finish Agent B (item 5): tolerance/overflow, region-overlap honesty, id lock-down
Closes the three remaining item-5 sub-parts found in the audit.

② Typed tolerance + overflow (tempo.rs, invariants.rs):
- Replace the ad-hoc `f64::EPSILON` speed-degeneracy guards with a named
  TempoIntegration `Tolerance` (relative, non-finite-safe), per Appendix D
  "no ad-hoc epsilons"; aligns code with the module's own claim.
- Guard the continued-fraction convergent recurrence with checked i128 ops
  (break on overflow) and bound the residual-fraction stop by 1/max_den
  instead of f64::EPSILON, so a pathological input can't silently wrap.
- Endpoints::of: wall-clock event end uses checked_add -> Endpoints::Unknown
  on overflow, not saturating_add (which could mask an ordering violation).
- Regression tests: equal-endpoint linear segment uses the constant limit;
  extreme inversion inputs don't overflow.

① Region-overlap honesty (invariants.rs):
- Unresolvable region-overlap checks (symbolic anchors + shared staff extent)
  were silently treated as valid. Add DeferredCheck + deferred_checks() to
  surface them explicitly; check_invariants stays sound (no false positives).
- Test proves an undecidable overlap is reported as deferred, not passed, and
  that a wall-clock-resolvable disjoint pair is neither violation nor deferred.

③ Identifier-derivation lock-down (graph.rs, pitch.rs, ids.rs):
- Golden-bytes tests pin derive_promoted_voice_id (MUSCSVCE 64-byte preimage),
  derive_system_pitch_id (MUSCSPCH input layout), and the TypedObjectId
  discriminant table + Registered layout, so an accidental layout change is
  caught (the derivations were concrete but unlocked).
- canonical_pitch_bytes NFC-normalizes strings at the derivation boundary,
  making the documented NFC guarantee explicit (no-op for the already-NFC
  catalog ids).
- DECISIONS P11-1/3/6 updated to record the pinned-and-locked layouts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 18:48:03 -04:00
Levi Neuwirth f5aaa96b11 Land whole-score codec (item 5) and flip the M3 full-Score gate green
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>
2026-06-21 18:15:07 -04:00
Levi Neuwirth e9c4bad7a6 Land M1 + M2 (Agent C): framework edge fixes and real-Score graph integration
M1 — fix Agent C framework defects, tests-first:
- causal ordering: topologically order DVV edges instead of assuming HLC
  alone implies causal order (false for adversarial remote envelopes);
  HLC only breaks ties among ready operations.
- anomaly cutoff: quarantine from the earliest counter participating in
  any violating HLC pair (suffix-minima), e.g. [100,200,50] quarantines
  from counter 0, not counter 1.
- pending detection: DVV contiguous ranges use the zero-based per-replica
  counter floor; first absent id in any asserted range holds the dependent
  pending (vector coverage, not only dots).
- transaction snapshots: rollback removes member-generated conflicts.
- edge tests in concurrent_reduction.rs for all six audited cases.

M2 — reduce onto Agent B's real score graph:
- OperationSet::reduce_onto(&Score) -> GraphMaterialization { state, score }
  mutates the real arena, voices, regions, tombstones, indexes, and
  cross-cutting structures; base-free reduce() retained.
- VoiceOrigin::SystemPromoted now carries { winning_operation,
  losing_operation, original_voice }; spec and Invariant 18 updated.
- graph-aware migration, forward undo, system breaks, promotion pre-pass.
- tests/graph_reduction.rs: 11 tests asserting check_invariants is clean,
  plus a 64-seed order-independence sweep.

Pass-11 spec decisions resolved (blocking subset): DVV floor (P11-C7),
HLC-vs-causality, promoted-voice derivation inputs (P11-C4 / core P11-3).
Payload/Score canonical encoding remain deferred to the companion docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 16:37:51 -04:00
Levi Neuwirth a2e9ec32f6 A B C D F 2026-06-19 12:42:31 -04:00