epiphany/crates/epiphany-ops
Levi Neuwirth 357e8b9eeb Push 4a design gate: pin the transpose algebra, freeze the old operation
An audit reopened P12-K2, whose Pass-12 pin promised the repair would be "a
payload schema-major landing with the Chapter 4 tuning catalog". Both halves
were wrong, and the operation was more broken than the pin admitted.

Measured through EditorSession, not inferred. On a C4: +12 yields alteration
12 (six double-sharps, not C5); +128 clamps to 127 and still reports Applied;
targets [p, p] transposes twice; a non-Cmn position is silently untouched.
transpose(1000) then transpose(-1000) lands on -128, so the operation is not
invertible. Nothing downstream is at fault -- prepass::accidental_ids renders
alteration 12 faithfully. The defect is entirely in what Transpose means.

The false coupling is why this looked big. Pitch has orthogonal scale_position
and acoustic fields. Transposition adds an interval to a scale position;
tuning decides what frequency a scale position sounds at. Adding a fifth to C4
needs no tuning catalog. The same weld had spread: PitchSpaceMismatch was
"Reserved: requires the Chapter 4 tuning catalog" (it reads a discriminant),
and TranspositionInterval was "ADVISORY until the Chapter 4 tuning catalog
pins interval algebra". Push 4 splits: 4a is the algebra and needs no catalog;
4b is the catalog, which has its own blockers (cmn-24 is in the pitch-space
table but cannot exist while Cmn.alteration is i8 semitones).

Ratified by the user:

- New kind, freeze the old. An operation is history; a corrected reduction
  rule would rewrite every score that used one. Transpose (disc 9) keeps its
  exact semantics, now written as normative replay semantics rather than as
  apologies. TransposeInterval takes disc 30. This is cheap: appending a kind
  at >= 30 is a schema MINOR, and the payload's constituents are all major-0
  layouts, so it stamps major 0. No major 3, no migration.

- Diatonic + chromatic interval, reusing TranspositionInterval -- which
  already existed in graph.rs at major 2 for Instrument.transposition, already
  codec'd, byte-for-byte the required pair. Minting an Interval beside it
  would have been a second normative listing of one type, the drift P13-I1
  just closed. Declared once now, in Chapter 2; Chapter 5 references it.

- Atomic refusal. Non-Cmn, AbsoluteHz, or an out-of-range result refuses the
  whole operation. Never saturate, never partially apply. 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.

targets becomes CanonicalSet<PitchId> at the type level, not a Vec plus a
dedup() someone can forget (PitchId's Ord is its canonical byte order). This
was never a convergence bug -- every replica replaying [p, p] double-
transposes identically -- but a canonicalization one. It is free today because
no operation-payload decoder exists yet; once one lands in Push 5, dedup
normalization would change the meaning of stored operations. Push 4a blocks
Push 5, and that is why.

Spec: req:pitch:transposition (algebra + the three refusals), and four
req:opcat:transpose-* requirements. Operation Catalog 0.7.0 -> 0.8.0; Binary
Format 0.6.0 -> 0.7.0 (disc 30, and a seq-strictly-increasing notation whose
decoder must reject a duplicate rather than normalize it away).

This commit is the design gate: the spec now declares MUSTs the code does not
yet satisfy -- editor-core still authors Transpose, and TransposeInterval does
not exist. The implementation follows in this push.

Also recorded: the two existing transpose tests are false locks. Gutting
graph_transpose_pitch leaves both green -- they call base-free reduce(), where
graph is None and the function never runs, and assert only OperationEffect.
Only editor-core's undo_and_redo_a_transpose, three crates away, catches it.

Gate: clippy 0, 30 targets / 964 passed / 0 failed, docs 0 under -D warnings,
conformance 8/8, all three spec documents build with no undefined references.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:25:27 -04:00
..
examples A B C D F 2026-06-19 12:42:31 -04:00
src Push 3 audit follow-up: modify anchor validation, spaced slur_shape, stale docs 2026-07-08 18:54:39 -04:00
tests Schema major 2 Phase B: snapshot side + honest stamps (data-model fills) 2026-07-07 16:30:07 -04:00
Cargo.toml A B C D F 2026-06-19 12:42:31 -04:00
DECISIONS.md Push 4a design gate: pin the transpose algebra, freeze the old operation 2026-07-09 15:25:27 -04:00
README.md Land M1 + M2 (Agent C): framework edge fixes and real-Score graph integration 2026-06-21 16:37:51 -04:00

README.md

epiphany-ops

The Epiphany concurrent semantics: the operations through which the score graph becomes a live model, and the deterministic reduction by which a set of operations becomes a materialized score state. Implements the normative requirements of Chapter 6 (Semantic Operations and Concurrent Reduction) of the core specification (spec/core_spec.pdf). This is Agent C's crate per spec/QUICKSTART.md, building on Agent A's epiphany-determinism and Agent B's epiphany-core.

A score's state is defined by the set of operations committed to it. Any materialized graph is a deterministic reduction of that set; caches, snapshots, and partial reductions are acceleration structures, never the source of truth. — Chapter 6, Design Principles

The thesis in one paragraph

The replicated operation set is a grow-only CRDT: replicas accumulate envelopes and converge on the same set. The materialized graph is not a CRDT — it is the deterministic reduction of that set in a single canonical order (causal-first, then the HLC tuple). Any permutation of the same envelopes reduces to byte-identical materialized state. That property is the determinism heart of the architecture, and the reduction fuzzer is its tripwire.

What's here

Area Items Spec
Stamps HybridLogicalClock, OperationStamp, the reduction & monotonicity tuples Ch. 6 §"Operation Identity and Stamps"
Causal context CausalContext (dotted version vector), covers, the missing-predecessor signal Ch. 6 §6.2
Payloads OperationKind, the discriminator-only OperationKindTag, OperationPayload, the §6.10 representative ops Ch. 6 §"Operation Envelopes", §6.10
Envelopes OperationEnvelope, EnvelopeHash (MUSCENVH), well_formed (incl. stamp.id == id) Ch. 6 §6.4
Slots OperationSlot::{Single, Equivocated}, the order-independent (Pass-10) transitions Ch. 6 §6.5
Anomalies AnomalousReplicaSegment, IntegrityAnomaly/Kind, the HLC-monotonicity detector Ch. 6 §6.6; Ch. 5 §"System-Derived Counter Collisions"
Effects OperationEffect, NoOpReason, the typed PreconditionFailureReason, RepairRecord/RepairKind Ch. 6 §6.3.2, §6.5
Conflicts ConflictRecord, ConflictKind, content-derived ConflictId (derive_conflict_id), the registry, resolution Ch. 6 §6.4
Transactions / undo TransactionDescriptor with the causal-prior-descriptor rule, UndoTransactionPayload / UndoPolicy Ch. 6 §6.6, §6.8
Operation set OperationSet: accept pipeline (well-formedness → slot → causal), grow-only Ch. 6 §"Envelope Acceptance"
Reduction canonical_reduction_order (single function), MaterializedState, the reduction driver Ch. 6 §6.3

The determinism this crate enforces

  1. A single reduction-order function. canonical_reduction_order performs deterministic causal topological ordering, using the intrinsic stamp tuple (physical, logical, replica, counter) only among ready operations.
  2. Order-independent equivocation. A duplicate OperationId with different canonical bytes transitions its slot to Equivocated regardless of which envelope arrived first (Pass 10). Equivocated slots contribute nothing to reduction; dependents are held pending.
  3. Content-derived facts. ConflictId and IntegrityAnomalyId are derived from content, so two replicas reducing the same set agree on every conflict and anomaly id — the conflict registry and anomaly register are deterministic materialized facts, not local bookkeeping.
  4. Byte-identical materialized state. MaterializedState::canonical_bytes serializes the effect log, conflict registry, anomaly register, object existence, spellings, and LWW fields in their normative orders.
  5. Real graph materialization. OperationSet::reduce_onto(&base_score) returns GraphMaterialization { state, score }. The graph is mutated in the same canonical order and compares by canonical event identity, independent of arena storage order.

Hand-off gates

Run the gate harnesses (QUICKSTART, Agent C):

cargo test -p epiphany-ops
cargo run --release -p epiphany-ops --example fuzz_reduction          # 10k iters, seed 0
cargo run --release -p epiphany-ops --example fuzz_reduction 100000 7 # soak, seed 7
  • Reduction determinism — every randomized envelope set reduces to byte-identical materialized state under any acceptance order (v0 acceptance criteria 1 and 5).
  • Equivocation order-independence — every duplicate-id-with-different-bytes scenario equivocates regardless of arrival order (v0 acceptance criterion 3).

The integration tests (tests/concurrent_reduction.rs) exercise these plus transaction atomicity, descriptor precedence, anomaly exclusion, and forward undo through the public API.

Scope and decisions

Chapter 6 specifies the framework and a representative selection of operations; the full ~6080-primitive catalog is an explicit open question (§6.11) deferred to the Operation Catalog companion. This crate implements the framework in full and the representative operations, which is sufficient to exercise every reduction discipline. The representative operations can also reduce onto an epiphany_core::Score: insert/delete, voice promotion, supported cross-cutting structures, system breaks, migration checks, transaction rollback, and undo mutate the real graph while preserving Agent B's invariants. reduce() remains the base-free CRDT/bookkeeping API; reduce_onto() is the graph-aware editing path. See DECISIONS.md for remaining payload boundaries.

Per QUICKSTART "Don't do these": undo is the spec's forward compensating operation, never inverse-based; unsafe is forbidden; everything is sync.