Commit Graph

13 Commits

Author SHA1 Message Date
Levi Neuwirth 567e8214a4 One tag vocabulary the compiler owns, and ratify the corpus in the companion
Two review findings after P4. Both real.

The tag-omission failure could recur. After the P4 fix there were STILL four
hand-maintained lists -- a test-local all_tags(), a barrier test spelling
0u8..=30, a fuzz corpus naming five tags, a vector corpus naming four -- plus a
malformed-bytes test asserting that 31 rejects. A future tag 31 added to
discriminant() and omitted from decode_canonical() would have left every one of
them green, and the malformed test would have LOCKED it, exactly as the 30
version did two commits ago.

operation_kind_tag_vocabulary! is now the single source. It generates
discriminant, from_discriminant, and OperationKindTag::PAYLOAD_FREE from one
list, and the generated discriminant match is exhaustive over the enum -- so a
variant added to the enum and not to the macro fails to COMPILE. Everything
downstream reads PAYLOAD_FREE: the decoder, the fuzz corpus (all 31 tags, not
five), the conformance vectors (an accept vector per tag: 65 vectors, not 37),
and the edit-barrier round-trip. Every "one past the vocabulary" constant is
computed, never spelled; a spelled constant is the trap that springs on whoever
appends the next tag.

Verified end to end with a hypothetical tag 31. Added to the enum alone: compile
error. Added to the enum and the macro: it compiles, decodes, and every derived
check passes because they read PAYLOAD_FREE -- while the committed corpus's
drift lock AND its now-stale "one past the vocabulary" reject vector both fail,
forcing the new vectors into the diff. There is no path where a new tag leaves
everything green.

Second finding: the corpus called itself normative while the spec said it was
deferred. Binary Format's "About This Companion" listed the cross-implementation
decoder test among things the document does not cover, and the Golden Anchor
Registry called it "the deferred conformance harness" whose literal-byte vectors
a future test "should add". Both now ratify it. New req:binfmt:decode-vectors
and a "The Decode Vector Corpus" section: a conforming decoder MUST accept every
accept vector for a surface it implements, MUST reject every reject vector, and
MUST re-encode an accepted value to exactly its bytes -- and accepting a reject
vector and then normalizing it IS accepting it. Binary Format 0.8.0 -> 0.9.0.
The wire-format fuzzer stays an implementation deliverable. The corpus header
now cites the requirement instead of asserting one.

Gate: fmt clean, clippy 0, 30 targets / 1024 passed / 0 failed, docs 0 under
-D warnings, conformance 8/8 with [7d] at 65 vectors, zero golden churn,
binary_format rebuilds with no undefined references.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 20:32:35 -04:00
Levi Neuwirth a41596d329 Push 5 / P4: the decode conformance corpus, and the tag it could not read back
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>
2026-07-09 20:16:15 -04:00
Levi Neuwirth 9596d1e824 Push 5 / P2: fuzz the ops decode surface, and find the guard's blind spot
The operation layer has exactly two byte-decode surfaces: MaterializedState and
OperationKindTag. Operation payloads have no decoder -- OperationKind is
encode-only -- so nothing here can yet accept a duplicate TransposeInterval
target. When such a decoder lands it inherits the wire table's seq-strictly-
increasing rule: reject a duplicate, never normalize it away.

No defect in the decoder. It already carries the Phase-1 hardening: a whole-state
re-encode-and-compare guard, and with_capacity(n.min(1024)) at every count site,
so the unbounded-allocation and soft-DoS classes P1 fixed in core do not apply.
2M adversarial inputs across four seeds, ~2s each, clean.

The finding is about the ARCHITECTURE, and it qualifies P1's recorded design
note that the guard "is complete-by-construction, it cannot miss a lenient
codec". It is complete only for fields the decoder NORMALIZES. The BTreeMaps
re-sort and de-duplicate, so a non-canonical encoding of them cannot survive a
round trip. But the guard is BLIND to order-preserving Vec fields: a reordered
anomalies or pending list re-encodes to exactly the bytes it came from, so the
guard sees identity and accepts. Only the per-site windows(2) checks reject
them, and the same holds for a conflict record's caused_by / affected_objects,
which ConflictRecord::encode_canonical writes verbatim.

Measured, not reasoned: removing both per-site Vec order checks leaves a 40K
injectivity sweep GREEN. An injectivity fuzzer structurally cannot see this
class -- it asserts bytes->value->bytes identity, which is exactly what a
missing order check preserves. Those checks were locked by nothing. They are now.

(effects is a Vec with no order check, correctly: its canonical order is
reduction order, which a decoder cannot recompute. Two orderings are two
different states, so injectivity is not at stake.)

Delivered: fuzz::run_decode_fuzz over both surfaces, returning a
DecodeFuzzCoverage the smoke tests assert on -- a decode fuzzer that never
reaches a decoder's accept path proves only the absence of a panic, and mine
initially accepted nothing worth speaking of. Plus one deterministic test per
layer: an out-of-order objects map (guard only), an out-of-order anomaly list
and a reordered pending list (per-site only). Each mutation-verified against the
exact check it locks, and the fuzzer confirmed blind to the per-site pair.

Corpus depth is now a property rather than luck. A fixed list of envelope-set
sizes reduces to states with no conflicts, anomalies, pending, or spellings --
the very branches holding every canonical-order check. Measured: 6 of 12 seeds
failed to produce all four. build_decode_corpus draws until covered and asserts.

Gate: fmt clean, clippy 0, 30 targets / 1007 passed / 0 failed, docs 0 under
-D warnings, conformance 8/8, zero golden churn.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 18:43:21 -04:00
Levi Neuwirth 2740a6c53c TransposeInterval: the faithful transpose, and the frozen one it replaces
Closes P12-K2. The reducer, the payload at wire discriminant 30, and the
editor authoring that emits it.

TransposeIntervalOp carries targets: CanonicalSet<PitchId> -- a set at the
type level, not a Vec plus a dedup() someone can forget. PitchId's Ord is its
canonical byte order, so a BTreeSet iterates in canonical order and cannot
hold a duplicate. Encoding it is the wire table's seq-strictly-increasing by
construction. The frozen Transpose keeps sorted_canonical and its multiset.

Reduction refuses atomically. Every mutable target is resolved before any is
written, so an untransposable one leaves the whole chord alone -- a chord
transposed except for one note is a different chord. Tombstoned and
SYSTEM_DERIVED targets are still skipped: a deleted pitch is not an
untransposable pitch, it is one the operation has nothing to say about. The
three refusals map to PitchSpaceMismatch (6, un-reserved -- detecting a
non-Cmn position reads a discriminant, never the tuning catalog its doc
claimed to need), AcousticRealizationPinned (14), TranspositionOutOfRange (15).

The refusal reads pitch values, which exist only under reduce_onto, so it is a
graph-aware-only precondition that passes base-free -- the convention
modify_identified_pitch's system-derived check already set. It writes nothing
base-free either, so both modes agree on objects, and on the effect log for
every operation whose targets are all transposable, which is all base-free
reduction can see.

Spelling propagates. Core Ch2 requires transposing operations to produce
Propagated attachments; Transpose produced none, so an authored spelling
survived a transposition still pinned to the notehead it was written against.
simplest_spelling on a Cmn position returns the authored letter verbatim, so
the attachment carries exactly what the interval's diatonic component decided:
a diminished sixth up from C4 records A-double-flat, not the enharmonic G.

Editor. transpose_selection now takes a TranspositionInterval; a scalar cannot
tell "up an octave" (7,12) from "C with twelve sharps" (0,12), which is
P12-K2 itself. The "+1 semitone" key became alter_selection(+-1). TransposeOp
is now unused in editor-core's lib, so the compiler enforces "never authored".

Tests, five mutations verified: the graph write removed; the refusal made
non-atomic (skip the offender, move the rest); spelling propagation dropped;
and -- for the freeze -- graph_transpose_pitch "helpfully" repaired to use the
real algebra, which the_frozen_transpose_keeps_its_saturating_alteration_
semantics correctly rejects. That test guards against rewriting history, not
against a bug.

The two old transpose tests were false locks, but the fix was not to rewrite
them as the design gate promised. What they assert -- skip-tombstoned,
skip-system-derived, refuse-missing -- are effect-log properties, correctly
checked base-free. The defect was one test's NAME: it claimed the live target
"shifts" and checked nothing of the kind. Renamed to say what it proves; the
shift itself is now locked by two graph-aware tests against reduce_onto.

fuzz::gen_payload gained arm 27, so below(27) became below(28) and the seeded
stream reshuffled; the canonical-base digest is re-pinned consciously, per
that test's own instruction and the Phase-D precedent. Nothing leaked --
canonical_bytes embeds effects, conflicts and anomalies, never payload values.
The frozen Transpose keeps fuzz arm 6 and its testkit corpus authoring: it
must reduce correctly forever, and a generator is now the only thing that will
ever produce one.

Gate: fmt clean, clippy 0, 30 targets / 982 passed / 0 failed, docs 0 under
-D warnings, conformance 8/8, zero golden churn.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:46:43 -04:00
Levi Neuwirth 9b5339f8f3 Schema major 2 Phase D: the repeat-authoring pair (code tranche)
CreateRepeatStructure / DeleteRepeatStructure land end-to-end,
review-hardened (five-dimension workflow review; 19 findings triaged —
12 verifier agents lost to a session limit were verified by hand).

epiphany-ops:
- CreateRepeatStructureOp { repeat } (lp(RepeatStructure), wire 28,
  BORN AT V2: schema_major always 2 — kind/voltas are unconditional
  fields) and DeleteRepeatStructureOp { repeat } (bare id, wire 29,
  major 0: minimal stamping over a major-0 layout; the discriminant a
  schema-minor append). OperationKindTag 28/29 name-verbatim with
  validating decode; golden tables grown to 30; migrate/v0 identity
  arms; lib re-exports.
- Reduction: set-union mint (live-id => AlreadyApplied without value
  comparison — the cross-cutting discipline; RecreateContentMismatch
  scope unchanged) with the ALL-anchor-sites-live precondition;
  delete-wins tombstone; the "Repeat structure / Anchor" rule-table
  row wired into BOTH the ledger (reanchor_for_tombstone arm, min-id
  survivor, cascade when dry) and the graph (every dead site rewrites
  to the same survivor), agreement asserted by tests incl. the
  DalSegno jump-target case.
- Undo: materialize_graph_tombstones gains RepeatStructure — and the
  pre-existing SPANNER GHOST fix (an undone spanner mint never left
  the graph), regression-locked.
- validate.rs: VoltaEndingsIllFormed advisory (non-empty, 1-based,
  strictly ascending; interactive-only).
- Canonical-base honesty: blake3 re-pinned for the corpus stream
  shift, its comment corrected (the seeded repeat creates all no-op),
  and the property covered surgically by
  the_canonical_base_embeds_no_repeat_values (two reductions
  differing only in repeat v2 content => byte-identical bases).

epiphany-core:
- RepeatStructure::anchor_sites()/anchor_sites_mut(): THE single
  site-set walk (start/end, jump targets, volta spans), consumed by
  reduction, the editor barrier seam, the invariant walk, and the
  cross-reference index — replacing five hand-rolled copies and
  fixing a SIXTH, silently-stale one (indexes.rs had never learned
  the Phase-B kind/volta anchors; regression-locked).
- RepeatStructure joins the CanonicalValue seam (strict per-value
  decode); decode-fuzz corpus gains valid_score_rich_with_repeats
  (DalSegno + voltas) CORPUS-LOCAL — shared render fixtures stay
  repeat-free until E1 (zero golden churn).

editor-core / gui / testkit / layout-ir:
- Barrier seam: repeat_event_refs + subjects_of arms +
  graph_structure_endpoints arm, gated end-to-end by
  repeat_authoring_kinds_derive_subjects_and_gate_on_barriers.
- Generators: operation_payload -> below(30); graph-session gen ->
  below(14); ops fuzz gen_payload -> below(27); layout_stub
  gen_operation_kind_tag unstaled 24..=29 (the Phase-3 tags were
  already missing); GUI name table.

Spec touch-ups (review findings): catalog says "strictly ascending";
the survivor tie-break is now explicit in the core rule-table row and
catalog (identifier-order minimum among multiple survivors;
proximity-aware four-key selection deferred, as for spanners).

Trail: ops + core DECISIONS entries; spec/PASS13_CANDIDATES.md opens
the Pass-13 ledger at three candidates (P13-K1; P13-D1 undo-driven
tombstones bypass ledger re-anchoring — pre-existing class; P13-D2
cue-cascade repair-trail ordering).

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
2026-07-07 20:06:18 -04:00
Levi Neuwirth 0316160395 Phase 3 tranche 1: casting-off, K1 schema-fill, value-restoring undo
The chosen Phase-3 attack, run as two parallel waves. 829 workspace
tests pass; clippy -D warnings, fmt, and rustdoc clean; all three
spec documents build with zero undefined references.

Casting-off (epiphany-engrave/src/casting.rs, wired into the
Engraver):
- Greedy first-fit system breaking per region at measure-start
  barline columns; a measure that would overflow the content width
  starts a new system. Hard SystemBreakAt/PageBreakAt always
  honoured; soft breaks honoured unless pathological (skipped with
  the spec's warning + IrOverride-recorded decision).
- Vertical stacking from real content extents with the inter-system
  gap read from the vertical band model; page overflow starts the
  next page. World frame: pages stacked vertically, coordinates
  baked into glyphs/strokes, so the SVG renderer, hit-testing, and
  the GUI viewport are unchanged.
- Real ResolvedPage/ResolvedSystem trees (1-based page numbers,
  content bounding boxes, staves from staff-line segments, measures
  from barline columns); every chosen break appends an
  EngravingDecision with MUSCLOID EngravedBreak provenance,
  UserOverride-attributed via the new ConstrainedLayoutIR
  break_origins sidecar; staff lines split per system with
  synthesized continuation provenance.
- Break-constraint evaluation flips: satisfied iff the layout breaks
  at the slot. The two single-system tests invert deliberately
  (a hard break is now honoured; a user break is honoured and
  attributed instead of warned). Geometric constraints evaluate in
  the pre-casting spaced frame (documented).
- Page geometry is engraver-side PageGeometry (A4 portrait at an
  8 mm staff: page 105 x 148.5 staff spaces, margins 7.5, content
  90 x 133.5; arithmetic documented) — Canvas.layout_defaults has no
  graph home and is a schema-major addition (P12-I7).
  ENGRAVER_VERSION = 2. Goldens regenerated: ten_measure_single_staff
  engraves as 2 systems (viewBox 84x20.6, was 103x11);
  valid_score_rich as 3 systems; stub goldens byte-identical.

K1 schema-fill (Operation Catalog 0.4.0 -> 0.5.0, ratified first;
wire discriminants strictly appended):
- CreateStaff (24 / tag InsertStaff 24): set-union mint of a global
  Staff; CreateStaffInstance now preconditions that its referenced
  staff is live.
- SetTimeSignature (25): value-carrying meter-change LWW keyed by
  (region, resolved position); the carried TimeSignature mints
  set-union; StructuralFieldCollision on meter_sequence.
- SetTempoSegment (26): LWW keyed by (scope, resolved start) over
  the score or region tempo map; a write that would malform the map
  refuses with the appended PreconditionFailureReason 11
  (TempoMapMalformed).
- SetStaffLayout (27): LWW advisory over the staff instance's three
  inline layout fields.
- Create score/canvas remain deliberately unavailable slots: the
  root and canvas are inline singletons with no addressable object
  model (P12-K8), not force-designed.

Value-restoring undo (P11-C8 narrowed; catalog §UndoTransaction
rewritten and per-primitive undo notes updated):
- Canonical-order write chains (base-seeded) across all eleven LWW
  families. StrictInverse restores each written key to its
  chain-predecessor value iff the transaction's write is still the
  key's last writer, else refuses the whole undo with a
  TransactionConflict naming the superseder; BestEffort restores the
  still-last keys. Clean compensations are Applied; only minted-
  object tombstone repairs ride AppliedWithRepair (no new repair
  vocabulary). Mixed mint+overwrite transactions compose; strand
  guards refuse tombstoning mints still referenced by live
  non-members.
- Undo-of-undo pinned and tested: restorations are chain writes, so
  undoing the undo's transaction restores the undone value, and a
  second undo of the same transaction conflicts (absence-restores
  repeat idempotently — documented asymmetry, P12-K11).
- Permutation invariance pinned across five delivery orders; the
  convergence generators gain the new ops and a tx-then-undo flow.
- Still deferred in normative text: delete resurrection (needs a
  system-derived tag outside the ratified closed set), Transpose
  inversion (P12-K2), Cascade dependent closure.

Trackers: Binary Format companion 0.1.0 -> 0.2.0 (appended wire/tag
tables, PreconditionFailureReason 11, payload layouts, history row —
a schema-minor evolution under its own rules); nine new Pass-12 rows
(C5, K8-K11, I7-I10); core-spec OperationKind listing gains the four
kinds; revision-history rows in core spec and companion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
2026-07-02 21:55:26 -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 1e4ab24779 Agent K M2d (Group 4): score-settings operations
Fourth broad-K0 subsystem group — three value-typed LWW field-overwrite ops, one
per settings cluster (additive: OperationKind variants 21-23; framework frozen):

- SetMetadata { metadata: ScoreMetadata } — overwrite the score-singleton
  metadata (title/composer/copyright); concurrent differing ⇒
  StructuralFieldCollision.
- SetMetricGrid { region, grid: Option<MetricGrid> } — overwrite a region's
  default metric grid (LWW keyed by region; concurrent differing ⇒ collision).
- SetUserPageBreak { region, anchor, present } — the page-break sibling of
  SetUserSystemBreak: a canonical LWW advisory.

- core: expose ScoreMetadata + MetricGrid via CanonicalValue (no new byte layout
  — they already have whole-score Codec impls).
- SetMetadata / SetMetricGrid mirror the modify ops: the resolved value lives in
  the graph (reduce_onto), with new last_metadata / last_metric_grid LWW working
  state for concurrent-differing detection; MaterializedState records only the
  effect and conflict.
- SetUserPageBreak mirrors SetUserSystemBreak's canonical advisory: a new
  MaterializedState.page_breaks map (encode + decode added, parallel to breaks),
  plus the graph's region user_page_breaks.

Migration: v1-native (no lossy v0 predecessor) -> project/migrate by identity;
the round-trip identity test now covers all four M2 groups.

Coverage:
- testkit operation_payload + ops fuzz gen_payload emit the three kinds, so the
  convergence / determinism / migration-equivalence and MaterializedState
  decode-roundtrip gates exercise them (incl. page_breaks) at scale.
- reduce_onto graph tests: the three settings materialize in graph and ledger
  (metadata title, region default metric grid, region user page break +
  MaterializedState.page_breaks), invariant-clean; plus a concurrent-differing
  SetMetadata conflict test.

Gates: build/fmt/clippy -D warnings clean; cargo test --workspace green (519);
conformance_suite scale 1 passes. Catalog sections + DECISIONS for the M2 groups
land in M2e per the staged plan. The unrelated Agent-I working tree is left
uncommitted; this commit stages only core/ops/testkit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:26:31 -04:00
Levi Neuwirth a207077cd7 Agent K M2c (Group 3): structural container CRUD operations
Third broad-K0 subsystem group — six new value-typed ops for the score-graph
containers, reusing M1's disciplines (additive: OperationKind variants 15-20,
new apply arms + reduction methods; framework frozen). Per the project lead's
call, container deletes are EMPTY-ONLY (no cascade): a precondition NoOp unless
the container has no live children, so the caller deletes contents first.

- CreateRegion / DeleteRegion, CreateStaffInstance / DeleteStaffInstance,
  CreateVoice / DeleteVoice. Creates are value-typed mints of an empty container
  (set-union creation); deletes are delete-wins tombstones gated on emptiness.
- core: expose Region / StaffInstance / Voice via CanonicalValue (no new byte
  layout — they already have whole-score Codec impls), with round-trip coverage.
- New PreconditionFailureReason::ContainerNotEmpty (additive discriminant 10;
  encode + decode), reported when an empty-only delete hits a non-empty container.

Reduction (reduce.rs):
- Two child-existence indices, region_instances and instance_voices, drive the
  emptiness checks (a voice's events are read from voice_occupancy), so the
  ledger projection and the graph agree on every delete result. Populated by
  seed_from_graph, the create ops, and implicit voice creation in insert_event.
- CreateStaffInstance / DeleteStaffInstance maintain the region's staff_extent so
  it lists exactly the manifested staves (Chapter 5 RegionExtents); valuegen's
  fresh region uses a far-future wall-clock extent so it never overlaps an
  existing region in both time and staff.

Migration: v1-native (no lossy v0 predecessor) -> project/migrate by identity;
group1_and_group2_kinds_round_trip_by_identity extended to cover Group 3.

Coverage:
- testkit operation_payload + ops fuzz gen_payload emit the six kinds, so the
  convergence / determinism / migration-equivalence gates exercise the
  bookkeeping projection at scale.
- A reduce_onto graph test materializes a region -> staff instance -> voice
  subtree (invariant-clean), asserts the empty-only delete refuses a non-empty
  container with ContainerNotEmpty, and verifies an ordered teardown clears the
  subtree from both graph and ledger.

Gates: build/fmt/clippy -D warnings clean; cargo test --workspace green (criterion
1 green with the new container objects in the corpus); conformance_suite scale 1
passes. Catalog sections + DECISIONS for these ops land in M2e per the staged
plan. The unrelated Agent-I working tree is left uncommitted; this commit stages
only core/ops/testkit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 18:10:15 -04:00
Levi Neuwirth f62f5d4276 Agent K M2b (Group 2): cross-cutting CRUD operations
Second broad-K0 subsystem group — two new value-typed ops reusing M1's proven
disciplines (additive: OperationKind variants 13-14, new apply arms + reduction
methods; framework frozen):

- DeleteCrossCutting { structure: TypedObjectId } — delete-wins tombstone of a
  cross-cutting structure (idempotent concurrent deletes; guarded to the
  Tie/Slur/Beam/Spanner kinds). Drops the transient endpoint/LWW indices so a
  later event-tombstone re-anchoring pass never re-processes the deleted
  structure.
- ModifyCrossCutting { structure: CrossCuttingValue } — LWW field-overwrite by
  the structure's id; concurrent differing => StructuralFieldCollision. Mirrors
  modify_event (resolved value lives in the graph, not MaterializedState);
  re-derives endpoints from the new value, and mirrors CreateCrossCutting's
  beam->=2 / endpoints-live preconditions.

Graph materialization (reduce_onto): graph_delete_cross_cutting removes the
structure by id; graph_modify_cross_cutting replaces it in place by id, across
all four kinds (Slur/Tie/Beam/Spanner). New last_cross_cutting_modify LWW map,
synced through WorkingSnapshot/snapshot/restore.

Migration: v1-native (no lossy v0 predecessor) -> project/migrate by identity;
group1_and_group2_kinds_round_trip_by_identity extended to cover them.

Coverage:
- testkit operation_payload + ops fuzz gen_payload now emit both kinds, so the
  convergence / determinism / migration-equivalence gates exercise the
  bookkeeping projection at scale.
- Targeted reduce_onto graph tests (tests/graph_reduction.rs) cover every kind
  arm of graph_delete/graph_modify_cross_cutting (Slur/Tie/Beam/Spanner) plus
  the beam->=2 reject branch of modify, with check_invariants; plus two
  bookkeeping unit tests (delete tombstones; concurrent differing modify
  conflicts).

Not wired into graph_edit_session (criterion 1): doing so requires creating
structures in the session, which exposes a pre-existing M1 reanchor/graph-delete
divergence (a slur whose endpoint event is deleted is re-anchored in bookkeeping
but removed from the graph). That is a separate DeleteEvent fix; the targeted
reduce_onto tests above give the M2b graph paths guaranteed coverage meanwhile.

Gates: build/fmt/clippy -D warnings clean; cargo test --workspace green (ops lib
53, ops graph_reduction 18); conformance_suite scale 1 passes. Catalog sections +
DECISIONS for these ops land in M2e per the staged plan. The unrelated Agent-I
working tree is left uncommitted; this commit stages only ops/testkit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 14:31:24 -04:00
Levi Neuwirth 1658fd18f3 Agent K M2a (Group 1): event & pitch leaf-field operations
First broad-K0 subsystem group — five new value-typed ops reusing M1's proven
disciplines (additive: new OperationKind variants 8–12, new apply arms +
reduction methods; framework frozen):

- ModifyEvent { event: Event } — field-overwrite LWW by EventId; concurrent
  differing ⇒ StructuralFieldCollision.
- Transpose { targets, chromatic_steps } — order-dependent; pitch ids preserved;
  canonical footprint = effect-log entry; reduce_onto applies a minimal CMN
  alteration shift (rich interval algebra deferred — P12-K2).
- InsertIdentifiedPitch / DeleteIdentifiedPitch — pitch-within-event mint /
  delete-wins tombstone.
- ModifyIdentifiedPitch { pitch, value: Pitch } — field-overwrite LWW (the pitch
  VALUE, distinct from RespellPitch's spelling-only overwrite).

Design (honesty rule): the modify/transpose ops record effect + conflict
canonically — the resolved values live in the graph (reduce_onto), since
MaterializedState is bookkeeping, not a second graph; respell stays special
because spelling is a bookkeeping-owned annotation. LWW diff uses new
`last_event_modify`/`last_pitch_modify` working maps (synced through
WorkingSnapshot/snapshot/restore).

- core: expose Pitch + IdentifiedPitch via CanonicalValue (no new layout).
- The five kinds are v1-native (no lossy v0 predecessor): project/migrate them by
  identity; only the original kinds reconstruct from a lossy v0 form.
- Generators (testkit operation_payload, ops fuzz gen_payload) now emit the new
  kinds, so the convergence / determinism / migration-equivalence gates exercise
  them at scale; plus targeted migrate identity + reduce LWW/mint/delete tests.

Gates: build/fmt/clippy -D warnings clean; cargo test --workspace green (ops lib
51 tests); conformance_suite scale 1 passes. Catalog sections + DECISIONS for
these ops land in M2e per the staged plan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:56:01 -04:00
Levi Neuwirth 4556ede9f0 Phase 2 (Agent K) M1: value-typed operation payloads + Operation Catalog scaffold
Foundation milestone for Track B's Operation Catalog: shift epiphany-ops from
the v0 identifier-only payload projections to durable value-typed payloads, and
scaffold the companion spec. Scope: the representative §6.10 set (7 primitives +
2 meta-ops); the slice-driven K0 expansion follows.

Core (the K↔J seam):
- epiphany-core exposes a public `CanonicalValue` trait (canonical_bytes /
  decode_canonical) delegating to the existing private `Codec` machinery, for
  Event/Rest/PitchSpelling/Tie/Slur/Beam/Spanner/RegionTimeModel/TimeAnchor.
  No new byte layout — a value's bytes equal what the whole-score codec emits,
  so all goldens / criterion 4 stay byte-identical.

Ops (value-typed payloads, frozen reduction rules):
- InsertEventOp{staff_instance,event:Event}, RespellPitchOp{pitch,spelling:
  PitchSpelling}, CreateCrossCuttingOp{structure:CrossCuttingValue},
  ChangeRegionTimeModelOp{...,new_time_model:RegionTimeModel},
  SetUserSystemBreakOp{...,anchor:TimeAnchor}, ReplaceWithRest{rest:Rest}.
  Payloads frame each value's CanonicalValue bytes behind a u32 length prefix.
- reduce.rs: read-sites only moved onto the value (rules, conflicts, ordering,
  promotion, re-anchoring, undo, transactions unchanged); reduce_onto now
  materializes the real event/structure instead of the C4 placeholder.
  MaterializedState.spellings now stores PitchSpelling (encode + decode updated).
- v0.rs: frozen identifier-only shapes (migration regression guard).
- migrate.rs: migrate_v0_envelope(v0, &Score) + project_v1_to_v0 + MigrationError;
  deterministic and equivalence-preserving. Respell spelling recovered from the
  score context; irreversible case is P12-K1.
- valuegen.rs: shared value-type builders (reused by fuzz, migration, tests,
  testkit). Resolves P11-C1; P11-C10 Dismiss recorded.

Testkit (Agent F merge gate):
- migration.rs: reduce(v1)==reduce(migrate(project(v1))) byte-identical, plus
  migration determinism and a non-vacuity guard; wired into acceptance.rs as
  agent_k_migration_equivalence_gate. Generators/harnesses build v1 payloads.

Spec:
- spec/operation_catalog.{tex,pdf}: new companion (independent semver) — framework
  + per-primitive template, the 7+2 representative primitives, the v0→v1 migration
  contract, and K1 framework slots for the remaining K0 primitives. Builds clean.
- PASS12_BATCH.md: P12-K1 (respell fingerprint irreversibility).

Gates: cargo build/fmt/clippy -D warnings clean; cargo test --workspace green
(incl. criteria 1/4/5/6 and the new K gate); conformance_suite scale 1 passes.

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