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> |
||
|---|---|---|
| .. | ||
| examples | ||
| src | ||
| tests | ||
| Cargo.toml | ||
| DECISIONS.md | ||
| README.md | ||
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
- A single reduction-order function.
canonical_reduction_orderperforms deterministic causal topological ordering, using the intrinsic stamp tuple(physical, logical, replica, counter)only among ready operations. - Order-independent equivocation. A duplicate
OperationIdwith different canonical bytes transitions its slot toEquivocatedregardless of which envelope arrived first (Pass 10). Equivocated slots contribute nothing to reduction; dependents are held pending. - Content-derived facts.
ConflictIdandIntegrityAnomalyIdare 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. - Byte-identical materialized state.
MaterializedState::canonical_bytesserializes the effect log, conflict registry, anomaly register, object existence, spellings, and LWW fields in their normative orders. - Real graph materialization.
OperationSet::reduce_onto(&base_score)returnsGraphMaterialization { 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 ~60–80-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.