193 lines
12 KiB
Markdown
193 lines
12 KiB
Markdown
# 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."*).
|
||
|
||
## 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
|
||
|
||
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).
|
||
|
||
## 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.
|