The Standard-tier spacing floor warned on short healthy scores: three reference-suite entries measured spacing_distortion 0.36-0.41, above the 0.32 warning floor (0.8 x 0.40), a spurious diagnostic (every score still passed Minimal). Root cause: the CV folded in the clef-to-first-note lead advance, which is sized by notational-furniture width, not by rhythm. Unlike P12-I11 (an engrave-side fix, no spec change), the defect here lived in the metric's own normative definition, so the honest fix is a catalog change - the mirror of I11: correct the measurement rather than relax the threshold. Quality Metric Catalog 0.1.0 -> 0.2.0: spacing_distortion is scoped to the system's rhythmic columns (spring slots bearing a notehead or rest). The clef / key-signature / time-signature lead and barlines contribute no column, so a note-to-note advance spans them. quality::census now builds its spacing columns only from a precomputed rhythmic-slot set (is_rhythmic: a notehead*/rest* glyph anywhere in the slot); the CV and the >= 3-column contributing-unit rule are otherwise unchanged. Results: RS-3/5/6 drop to 0.2188 / 0.0819 / 0.0856 (all below the floor), and the axis stays honest on real irregularity (RS-3 keeps 0.2188 from a mid-line accidental). RS-2/RS-4 go vacuous-0.0 (their systems carry < 3 rhythmic columns - honestly "too little to measure," cleaner than the old furniture noise). RS-1 0.1341 -> 0.1967 (cross-barline note advances). The 1.0 anchor, orientation, range, tier thresholds, and the eight other axes are unchanged. This is measurement-only: the resolved layout, canonical bytes, render goldens, and ENGRAVER_VERSION are all untouched - only the reported spacing_distortion value moves. The duration-aware optical-spacing open question (deviation from duration-proportional spacing) stays open; it needs the pipeline's deferred duration-aware preferred widths. - catalog: spacing_distortion requirement (req:qmc:spacing) redefined over rhythmic columns, rationale + open-question note updated, version 0.2.0, revision-history row; PDF rebuilt clean (0 undefined refs). - engrave: quality::census rhythmic-column filter + is_rhythmic; module and spacing_raw docs; the floor-column contrast test re-pointed from b-flat's spacing (no longer warns) to RS-1's casting-off (still between the Standard 0.28 and Minimal 0.72 floors); new short_scores_do_not_trip_the_standard_ spacing_floor locks the fix. - QMC version breadcrumbs bumped to 0.2.0 (engrave + layout-ir quality.rs, both DECISIONS.md, testkit RS-1 comment); reference suite companion unchanged (cites the catalog by name, no pinned values). - process trail: PASS12_BATCH I12 struck; PASS12_RATIFICATION_LOG I12 section (Version movements: QMC 0.1.0 -> 0.2.0); engrave DECISIONS quality decision 8 + item 7 + candidate. 861 workspace tests pass; clippy -D warnings, fmt --check, rustdoc -D warnings, and the catalog PDF build all clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd |
||
|---|---|---|
| .. | ||
| benches | ||
| 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).
Performance benches (Chapter 10 budgets, worklist F1)
benches/ holds the criterion benches for the spec's measurable Chapter 10
budgets (see DECISIONS.md F0 for why they live in this crate, F1 for every
call made). Criterion measures; the budget gate (src/budget.rs) asserts:
each bench's main() ends by re-timing every budget row and exiting nonzero if
a Pass-marked row misses its threshold. Known-pending rows are marked
Xfail(reason) in the bench source next to the numeric budget — a miss is
reported and tolerated, and a pass prints a loud promotion notice so stale
markings cannot linger. This is the "F surfaces, K fixes" handshake, and its
inaugural round has completed: the bench documented the reducer's O(n²)
canonical_reduction_order failure at scale, and Agent K's subquadratic
rewrite (see epiphany-ops/DECISIONS.md) flipped the xfail row to Pass.
| row | budget (spec Chapter 10) | expectation |
|---|---|---|
reduction/1000 |
> 10,000 envelopes/s, cold | Pass (~674K env/s measured) |
reduction/10000 |
> 10,000 envelopes/s, cold | Pass (~257K env/s measured) |
reduction/50000 |
> 10,000 envelopes/s, cold | Pass (~87K env/s measured; promoted from Xfail by Agent K's reducer fix — was ~1.7K env/s) |
bundle/typical_edit_commit |
≤ 50 ms (append + manifest + superblock flip, fsync'd) | Pass (~15 ms) |
bundle/open_bootstrap_read |
≤ 200 ms (manifest + bootstrap chunks) | Pass (moderate-corpus stand-in) |
# Full run (includes the 50K cold-reduction point, ~0.6 s per iteration):
cargo bench -p epiphany-testkit
# The reduced CI shape: smaller sampling, 50K point skipped (PR CI runs this):
EPIPHANY_BENCH_QUICK=1 cargo bench -p epiphany-testkit
The gate is a calibrated median over a few iterations, deliberately not the
spec's p99-over-1000-iterations conformance methodology (that is the reference
suite's job; the deviation is documented in src/budget.rs).
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.