# epiphany-ops — decisions and Pass 11 candidates This file records (a) the implementation decisions the QUICKSTART asked each agent to make once and document, and (b) the ambiguities discovered while building `epiphany-ops`, batched as **Pass 11 candidates** for the spec rather than improvised in code (QUICKSTART, Process notes: *"Ambiguities go into a batch, not into code … Don't open Pass 11 until you have at least three such items batched."*). > **RATIFIED (Pass 11, 2026-06-21).** The ops-layer Pass 11 candidates have been > ratified into `core_spec.tex` — see `spec/PASS11_RATIFICATION_LOG.md`. > Highlights: C2 adopted (`IntegrityAnomalyId` = `derive_system_id(MUSCSANM,…)`, > with `MUSCSANM` promoted to a reserved built-in tag); C3 decided > (field-collision tags the *winner* `Conflicted`); C4 adopted + lifted to spec > (the >2-way, partial-overlap promotion generalization); C7 fixed (zero-based > DVV floor made normative); C9 decided (`TransactionCategory`/`ObjectKind` core > vocabularies pinned); C10 decided (added `ResolutionAction::Dismiss` so > `Dismissed` is reachable). C1/C5/C6/C8 stay deferred to their tracks. ## Implementation decisions (QUICKSTART "Decisions you'll need to make") 1. **Replica ID entropy / event-arena / chunk store** — N/A to this crate (Agents A, B, D). `epiphany-ops` consumes Agent B's identifier family and never mints graph identifiers itself, except the deterministic system-derived ones (promoted voices via Agent B's `derive_promoted_voice_id`, and the content-derived `ConflictId` / `IntegrityAnomalyId`). 2. **Async or sync — sync only.** No async traits anywhere (decision 4). 3. **MSRV — workspace 1.77** (decision 5). No exotic features. `unsafe` forbidden crate-wide (`#![forbid(unsafe_code)]`). 4. **Canonical iteration is structural.** Every collection that feeds canonical output is a `BTreeMap`/`BTreeSet` or a vector put into the normative order before encoding (Appendix D §"Ordered Iteration"). The determinism fuzzer is the tripwire: it reduces each random set in several acceptance orders and asserts byte-identical materialized state, which would fail the moment a `HashMap` iteration order leaked in. ## Scope boundary: framework in full, representative operations Chapter 6 specifies the *operation framework* and a *representative selection* of operations; the full catalog of ~60–80 primitives is an **explicit open question** (§6.11, marked `\openquestion`) deferred to the Operation Catalog companion. This crate mirrors that division exactly: - **Implemented in full (the framework):** operation identity/stamps, the HLC and its monotonicity rule, DVV causal context, the order-independent `OperationSlot` model and acceptance pipeline, the canonical reduction order, the four-phase lifecycle, effects with the typed `PreconditionFailureReason`, conflict records with content-derived ids, the conflict registry, integrity anomalies kept separate from conflicts, transactions with the descriptor-precedence rule, re-anchoring, LWW discipline, validation modes, and forward undo. - **Representative (the §6.10 set):** `InsertEvent`, `DeleteEvent`, `RespellPitch`, `CreateCrossCutting`, `ChangeRegionTimeModel`, `SetUserSystemBreak`, `DeclareTransaction`, plus `ResolveConflict` and `UndoTransaction`. Together they exercise every reduction *discipline* the chapter defines (position-keyed insert + voice promotion; delete-wins + tombstone + re-anchor; field-overwrite + conflict; set-union; structural migration; LWW; atomic transactions). The remaining catalog kinds are an additive future change behind the existing `OperationKind` enum. `MaterializedState` is the canonical bookkeeping Chapter 6 owns — the effect log, conflict registry, anomaly register, object existence/tombstones, spellings, and LWW fields. M2 adds `OperationSet::reduce_onto(&Score)`, which seeds those indices from a canonical base and returns the corresponding Agent B graph. Insert/delete, voice promotion, supported reference-level cross-cutting values, system breaks, migration checks, transaction rollback, and undo mutate that graph. The base-free `reduce()` remains the operation-set convergence API. ## Pass 11 candidates (ambiguities for the spec, not resolved in code) ### P11-C1 — operation payload schemas are deferred; we carry identifiers + fingerprints > **RESOLVED (Phase 2, Agent K — Operation Catalog, M1).** The representative > payloads are now **value-typed**: `InsertEventOp { staff_instance, event: > Event }`, `RespellPitchOp { pitch, spelling: PitchSpelling }`, > `CreateCrossCuttingOp { structure: CrossCuttingValue }`, > `ChangeRegionTimeModelOp { …, new_time_model: RegionTimeModel }`, > `SetUserSystemBreakOp { …, anchor: TimeAnchor }`, and > `TupletCompensation::ReplaceWithRest { rest: Rest }`. They serialize by framing > each value's `epiphany_core::CanonicalValue` bytes behind a `u32` length prefix > — the ratified byte-convention baseline (Pass 11 item 1.8, > `req:format:codec-conventions`), introducing no new layout (the K↔J seam; see > `epiphany-core/DECISIONS.md`). Graph-aware reduction now materializes the > **real** event/structure rather than the C4 placeholder described below. > `reduce_onto`'s reduction rules are unchanged — only the field read-sites moved > onto the value. The v0 identifier-only shapes are frozen in `src/v0.rs` as the > migration regression guard, and `src/migrate.rs` lifts a v0 envelope to v1 > deterministically and equivalence-preservingly (`migrate_v0_envelope(v0, > context: &Score)`; `epiphany-testkit::migration` is the merge gate). The full > K0 set + the literal wire layout (Binary Format companion, Agent J) follow; the > remainder of this entry is the historical v0 rationale. See `P12-K1` below and > `spec/operation_catalog.tex`. Chapter 6's payload structs embed rich graph values (`InsertEventOp { event: Event }`, `RespellPitchOp { new_spelling: PitchSpelling }`, …), but the *canonical wire encoding* of those graph value types is itself deferred to the Binary Format companion (Agent B's P11-4: `epiphany-core` canonically encodes only identifiers and the scalar time types). An `OperationEnvelope` must be hashable **today** — the `EnvelopeHash` and slot equivocation both need canonical bytes — so this crate's payloads carry the reduction-relevant *identifiers and canonical scalar coordinates*, plus a `ContentHash` fingerprint where the reduction needs only equality (a respelling). Graph-aware reduction materializes this projection as deterministic C4 pitches (or a rest when no pitch ids are present); those placeholders do not claim to recover musical values absent from the payload. **For the spec:** pin the payload schemas (the Operation Catalog companion) and the canonical encoding (the Binary Format companion); when they land, the structs regain their full value fields without changing the reduction. The trigger will be a failing cross-crate round-trip test, per the QUICKSTART process notes. ### P11-C2 — `IntegrityAnomalyId` derivation is unspecified Chapter 5 gives `IntegrityAnomaly` an `IntegrityAnomalyId` but does not pin how it is derived. Because anomalies are deterministic facts of reduction, the id must be content-derived (two replicas must agree). This crate derives it as `derive_system_id(MUSCSANM, kind.canonical_bytes())` in the `SYSTEM_DERIVED` namespace — the same discipline Chapter 5 uses for system identifiers, with a new `MUSCSANM` extension system tag. **For the spec:** confirm the derivation (and whether a built-in `MUSCS…` tag should be reserved for anomalies, as `MUSCSVCE`/ `MUSCSPCH` are for voices/pitches). ### P11-C3 — which participant's *effect* carries `Conflicted` in a field collision For concurrent differing `RespellPitch`es, the spec pins the **conflict record** (kind `StructuralFieldCollision`, with `winner`/`loser` and the loser's spelling) and says the later-in-canonical-order operation wins and materializes. It does not pin which participant's `OperationEffect` is tagged `Conflicted`. This crate tags the **winner** (the later op, which materializes and whose processing created the record) `Conflicted`, and leaves the earlier op's already-recorded `Applied` effect in place. The outcome is order-independent because canonical order is fixed. **For the spec:** pin the per-operation effect tag for a field collision (and whether the superseded loser should retroactively read `NoOp{SupersededByLaterOperation}`). ### P11-C4 — voice-promotion derivation inputs and the >2-collision generalization Invariant 18's promoted-voice derivation takes *(staff instance, original voice, winning op, losing op)*. M2 expanded `VoiceOrigin::SystemPromoted` to retain both operation ids, so Agent B verifies the exact Agent C derivation. This crate resolves promotion in an **order-independent pre-pass**: bucket inserts by voice, walk them by `OperationId`, keep a non-overlapping set in the original voice, and promote each concurrent overlapping loser to `derive_promoted_voice_id(staff_instance, voice, winner, loser)`. This applies the InsertEvent invariant to partial interval overlaps as well as identical start positions. The op carries its `staff_instance` explicitly (a full reducer recovers it from the voice's container). One open point remains for the spec: define the >2-way collision case — the spec describes a pairwise rule; this crate uses the first lower-id overlapping operation retained in the original voice as the winner for each promotion. ### P11-C5 — "nearest surviving anchor" needs resolved positions The re-anchoring total order (Chapter 6 §6.5) ranks surviving candidates by containment proximity, then absolute time distance, then direction, then id. The prototype does not yet track resolved positions/time per object, so `nearest_survivor` uses a deterministic **stand-in**: the lexicographically- smallest surviving endpoint. The *structure* of the rule table (Tie → cascade-delete, Comment → orphan, Beam → truncate, Slur/Spanner → reanchor-or- cascade) is implemented faithfully; only the metric "nearest" is approximated. **For the spec:** no change needed — this resolves once the graph mutation phase tracks positions; recorded so the approximation is explicit. ### P11-C6 — time-model compatibility is computed when a graph is available `ChangeRegionTimeModel` retains a `declared_incompatible` list for base-free reduction. Graph-aware reduction additionally derives incompatibilities from every event's actual coordinate variants and mapping coverage, refusing any migration that would violate Agent B's coordinate discipline. Concurrent same-region migrations conflict; causally-later migrations are reevaluated against the first migration's graph. **For the spec:** the rich migration payload still belongs to the Operation Catalog. ### P11-C7 — DVV contiguous ranges use the zero-based operation-counter floor The DVV's contiguous `vector[r] = n` asserts predecessors `(r, 0..=n)` exist, matching the operation-id and causal-context documentation. Reduction finds the first absent id in every asserted range and holds the dependent pending; dots and vector coverage of known equivocated/excluded ids remain direct blocking signals, with transitive propagation to dependents. The range check walks known ids rather than expanding `0..=n`, so a sparse context with a very high counter does not cause proportional work. **For the spec:** explicitly retain the zero-based per-replica counter floor in the normative DVV definition. ### P11-C8 — forward undo is modeled via minted-object compensation The spec defines undo as a forward compensating edit (StrictInverse / BestEffort / Cascade) computed against the current materialized state. Without the full graph-mutation phase, this crate models the compensation as tombstoning the objects the target transaction *minted*: StrictInverse conflicts if any such object was already tombstoned/modified; BestEffort tombstones the survivors; Cascade is treated as StrictInverse over the same set (dependent-closure undo is deferred with the rest of the catalog). Graph-aware reduction also removes those event, pitch, promoted-voice, and supported cross-cutting mints from the live graph and records graph tombstones. **For the spec:** this is faithful to the "content-equivalence to pre-target state" definition for insert-shaped transactions; the inverse of every catalog primitive is the Operation Catalog's job. ### P11-C9 — local minimal enums for open vocabularies `TransactionCategory` (spec: open, "used by UIs and analytics") and `ObjectKind` (used by `SystemIdentifierCollision`) are given **minimal core enums with a `Registered(…)` escape**: `TransactionCategory ∈ {NoteEntry, Structural, Layout, Import, Registered}`, and `ObjectKind ∈ {Voice, Pitch, Registered}` (only the kinds the spec actually derives into the system namespace). **For the spec:** ratify or extend these sets. ### P11-C10 — `ResolveConflict` Dismissed has no distinct payload The spec distinguishes `Resolved` from `Dismissed` resolution states but provides a single `ResolveConflictPayload { target, action }`. This crate maps every applied resolve to `Resolved { action }`; `Dismissed` is reachable as a state but is not authored by a representative op. **For the spec:** define how a `ResolveConflict` selects Dismissed (a distinct action, or a separate payload). *Phase 2 update: the Operation Catalog (Ch. ResolveConflict) records that `ResolutionAction::Dismiss` is the action that selects `Dismissed`; resolved.* ## Pass 12 candidates (Agent K — Operation Catalog) ### P12-K1 — a v0 `RespellPitch` fingerprint is not invertible to a spelling The v0 `RespellPitchOp` carried a `ContentHash` *fingerprint* of the new spelling, not the `PitchSpelling`. The v0→v1 migration (`src/migrate.rs`) must reconstruct the value, but a fingerprint cannot be inverted without a side table. The migration recovers the spelling from the score-graph **context** — an explicit per-pitch spelling attachment (`SpellingScope::Pitch` + `SpellingDirective::Explicit`) whose canonical bytes hash to the fingerprint — and, when the context lacks it, returns `MigrationError::Irreversible` (the bundle opens read-only, per the QUICKSTART migration contract). This is the one representative payload that is not self-contained under migration. **For Pass 12:** confirm the read-only fallback is the intended long-term disposition (vs. requiring a richer v0 corpus that preserves spelling pre-images). Recorded in `spec/PASS12_BATCH.md` and `spec/operation_catalog.tex` (§RespellPitch, §Migration). ## Provisional canonical encoding (mirrors Agent B's P11-4) The composite Chapter 6 types use a concrete, reversible canonical byte form (little-endian integers; `u32` length prefixes on every variable-length part; NFC + length-prefixed text; single-byte discriminants) so that envelope hashing, conflict-id derivation, and the materialized-state bytes are testable now. This is deterministic and unambiguous but **provisional**: when the Binary Format companion lands, reconcile `encode.rs` and the per-type `CanonicalEncode` impls with it. A failing cross-crate round-trip test is the trigger.