One iteration of an interactive edit, wired end to end so a missing connection
between the crates surfaces here, not in a GUI: render a score to its RenderIR,
resolve a click on a notehead to its graph pitch through the hit-test map, apply a
real operation (sharpen -- a +1-chromatic Transpose) by reducing it onto the score
graph, re-render, and confirm the selection survives the relayout.
- run_edit_loop_with<S: ConstraintSolver>(base, solver) returns an EditLoopReport
(selected_pitch, selection, graph_changed, selection_preserved, render_changed);
run_edit_loop is the stub wrapper. render_with returns None for a
diagnostic-only (non-renderable) solver report, so the loop never hit-tests or
edits a layout the caller must not render.
- The click resolves like a GUI's: aim at each real notehead's centre (a notehead
glyph, not synthesized, pitch-backed) and select whatever the topmost hit there
is, taking the first aim whose topmost hit is a pitch-backed glyph -- faithful
to a click and robust to an occluding unison/chord notehead.
- Selection survival rests on the MUSCLOID layout id, a function of the pitch's
identity (PitchId), not its content: the sharpen changes the pitch's value but
not its id, so its layout object keeps the same stable id and the cursor does
not jump off the edited note. The prepass re-runs inside to_logical, so the
edited pitch is re-spelled and its accidental reflects the new value (why the
re-render reliably differs).
Tests drive the loop on valid_score_rich (one fixture and across 48 seeds, every
seed required), refuse a diagnostic-only solver layout, and -- in epiphany-engrave,
via its existing testkit dev-dep -- run it through the real Engraver across 16
seeds, proving the selection survives even as the Engraver re-spaces every glyph.
Full gate green: build, fmt, clippy, conformance scale 1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| examples | ||
| src | ||
| tests | ||
| Cargo.toml | ||
| DECISIONS.md | ||
| README.md | ||
README.md
epiphany-testkit
Agent F's crate per spec/QUICKSTART.md: the
cross-cutting conformance testkit. It is the architecture's tripwire — the suite
that proves the other crates work end to end and that runs in CI (see
.github/workflows/ci.yml).
It provides:
- Deterministic property-test generators for the public types of A
(
epiphany-determinism), B (epiphany-core), C (epiphany-ops), D (epiphany-bundle), and E (epiphany-layout-ir). Agent B's score-graph generators/shrinkers are re-exported asgenerators::graph. - The canonical round-trip harness (
roundtrip) — v0 acceptance criterion 4 (typed values + bundle container; the bookkeepingMaterializedStateround-trip is retained asassert_reduction_serialization_stable). - The CRDT convergence harness (
convergence) — criteria 1 and 5. Criterion 1 proper is real-Score convergence throughreduce_onto(run_graph_convergence); the byte-canonical bookkeeping-projection convergence (assert_convergence) backs criterion 5. - The equivocation harness (
equivocation) — criterion 3. - The crash-recovery harness (
bundle_harness) — Agent D's gate, criterion 2. - The manifest-selection harness (
bundle_harness). - The layout round-trip harness (
layout_stub) — criterion 6. - The audit regression guards (
negative) — one guard per defect the Agent C framework audit surfaced (the M1 fixes), so a regression trips this suite directly.
All harnesses are real
The QUICKSTART charters Agent F to "build against A and stubs for the others."
All five implementation crates — A, B, C, D, and now E (epiphany-layout-ir) —
have shipped, so every harness drives the real crate.
| Harness | Backend | Status |
|---|---|---|
roundtrip (criterion 4) |
A + B + C + D, real | real |
bundle_harness (criterion 2, manifest selection) |
D, real | real |
convergence (criteria 1, 5) |
C (epiphany-ops), real |
real |
equivocation (criterion 3) |
C (epiphany-ops), real |
real |
layout_stub (criterion 6) |
E (epiphany-layout-ir), real |
real |
For criteria 1, 3, and 5 the testkit drives the real
epiphany_ops::OperationSet / canonical_reduction_order / reduce and also
re-exports Agent C's own authoritative gates
(convergence::ops_reduction_determinism_fuzz,
equivocation::ops_equivocation_fuzz). The layout_stub module — once a
faithful in-tree stub of Chapters 7 & 9 — now re-exports the real
epiphany-layout-ir IR types and stub solver behind the same round_trip
signature; the provenance-preservation contract is implemented and tested inside
that crate. (The "stub" in the module name now refers to the spec-sanctioned
stub constraint solver, not to a stubbed crate.)
Criterion 1: real-Score vs. reducer-bookkeeping convergence
Criterion 1 proper (convergence::run_graph_convergence, the acceptance
criterion_1_convergence test) is real-Score convergence: a real ~50-bar,
two-voice base epiphany_core::Score is edited by two replicas through
OperationSet::reduce_onto, and the entire materialized graph — arena, voices,
tombstones, cross-cutting, and the bookkeeping state — must be identical
under every delivery order, pass check_invariants, and genuinely grow both
edited voices (non-vacuity). The session targets the base's actual voice ids
(generators::graph_edit_session), so it exercises the integration point, not a
synthetic id space.
The earlier, narrower gate is retained and honestly renamed
(reducer_bookkeeping_convergence): it converges the byte-canonical
bookkeeping projection (OperationSet::reduce →
MaterializedState::canonical_bytes) — the Chapter 6 §6.3 ledger (effects,
conflicts, anomalies, tombstones, spellings, pending), not the full musical
graph. It still backs criterion 5 and proves causal-first ordering
(convergence::assert_causal_order_respected,
run_authoritative_reduction_gate). The bookkeeping two-staff scenario remains
instantiated — a real ~50-bar (TWO_STAFF_BARS) session whose staves are
asserted populated by generators::assert_two_staff_populated, not just modeled.
Criterion 4: what is and isn't tested
Criterion 4 has three tiers — two asserted now, one pending item 5:
-
Real decode round-trips (these catch decoder / canonicalization defects): the generic
CanonicalEncode/CanonicalDecodeproperty swept across every typed identifier, bothRationalTimearms, and everyTypedObjectIddiscriminant; the bundleManifest(encode → decode → encodefixpoint, with a rich generator exercising snapshots, blobs, extensions, varied profiles, retention, and the optional roots); theFixedHeader; and theSuperblockslot encoding. Crucially, the decoders are shown to validate: corrupting a manifest or header makesdecodereject it (assert_manifest_decode_rejects_corruption,assert_header_decode_rejects_corruption). -
A reducer-bookkeeping serialization tier (
reducer_bookkeeping_serialization, viaassert_reduction_serialization_stable): a realOperationSetis reduced to itsMaterializedState::canonical_bytes()— the canonical bookkeeping state, not the whole musicalScore— which is stored as aSnapshotchunk referenced by the manifest'scanonical_base, survives the bundle's content-addressed store (hash-verified on reopen), decodes throughMaterializedState::decode_canonical, compares structurally with the original reduction, and re-serializes byte-identically. The decoder validates nested tags, lengths, primitive values, canonical form, and trailing bytes. Musical sensitivity is proven two ways:assert_content_mutation_changes_serialization(a cloned operation set with identical ids/stamps/causal contexts but one changed payload reduces to different bytes — the rebuttal to an id-only serializer) andassert_distinct_scores_serialize_differently. The materialized realScoreitself is shown reproducible today (full_score_materialization_is_reproducible, structural equality across delivery orders) — the determinism precondition a byte codec depends on. -
The full-
Scorebyte round-trip (criterion_4_full_score_byte_roundtrip, viaassert_score_serialization_stable): item 5's whole-score codec (epiphany_core::Score::canonical_bytes/decode_canonical) has landed, so a real ~50-barScore— materialized through Agent C'sreduce_onto— nowencode → decode → re-encodes byte-identically through a real bundle snapshot (hash-verified on reopen), with the decodedScorestructurally equal to the original. This is the whole musical graph (arena, voices, regions, cross-cutting, tombstones), not the bookkeeping projection.
Decisions (per QUICKSTART "Make each one once and document it")
- No platform entropy in the harness. Appendix D §"Randomness" forbids
platform entropy in canonical state; the testkit holds itself to the stronger
rule that no platform entropy enters the harness at all. Everything draws
from
rng::Rng(a wrapper over Agent A's vendored SplitMix64, with unbiased bounded draws and an overflow-safe full-rangerange), so every failure reproduces from its seed. - Drive the real crate once it ships; stub only what hasn't landed. Earlier
in development
epiphany-ops(C) andepiphany-layout-ir(E) were in-flight and their harnesses ran against faithful in-tree stubs; now that both have shipped, every harness drives the real crate and re-exports its gates.
Flagged for a future spec pass (Pass 11 candidates)
Per the QUICKSTART, implementation-discovered gaps are batched, not improvised:
- Whole-graph (
epiphany_core::Score) wire format — landed (item 5). A direct canonical byte codec for the coreScorenow exists (epiphany_core::Score::canonical_bytes/decode_canonical), andcriterion_4_full_score_byte_roundtripexercises it on a realreduce_ontomaterialization through a bundle snapshot. The prototype byte form predates the Binary Format companion specification and is to be reconciled with it (seeepiphany-core/DECISIONS.md, P11-4). - Layout harness re-pointed.
epiphany-layout-irhas landed, solayout_stubnow drives the real IR types behind the sameround_tripsignature (done). IR coordinates are f32 staff spaces, quantized only when serializing canonicalResolvedLayoutIR(Appendix D); see that crate'sDECISIONS.mdfor the remaining layout-specific Pass 11 candidates (theOperationKindTagvariant set and the layout-object id derivation).
Running
# Unit + acceptance tests (a meaningful slice, under the cargo test timeout):
cargo test -p epiphany-testkit
# The full conformance suite at scale, outside the test timeout (includes Agent
# A's 1,000,000-iteration determinism gate and Agent C's reduction/equivocation
# gates):
cargo run --release -p epiphany-testkit --example conformance_suite # scale 1
cargo run --release -p epiphany-testkit --example conformance_suite 10 # soak
cargo run --release -p epiphany-testkit --example conformance_suite 0 # smoke
The six v0 acceptance criteria are asserted in tests/acceptance.rs, one test
per architecture layer.