29 KiB
epiphany-testkit — Decisions
Agent F's crate. The v0 decisions live in spec/QUICKSTART.md and the per-crate
DECISIONS files; this file records the Phase 2 calls F makes as its mandate
broadens (spec/PHASE2_QUICKSTART.md, spec/PHASE2_F_WEEK0_WORKLIST.md).
F0 — Where the new harnesses, benches, and the integration runner live
Worklist item F0: "a 1-day call that everything else lands inside of." Decide before writing F1/F3/F4/F5.
Decision: keep the examples + library-module pattern; do not adopt xtask;
put benches/ in this crate; per-agent harnesses are library modules asserted
by tests/ integration tests; the end-to-end integration harness is a tests/
integration test over a library integration module.
Concretely:
-
No
xtask, notests/reference-suite/. The QUICKSTART topology named both (HANDOFF §7-F), but their function is already met by per-crateexamples/*fuzzers andepiphany-testkit/examples/conformance_suite.rs, which is the orchestration layer and the soak entry point. Addingxtaskwould introduce a second, parallel orchestration surface for no present gain; the workspace has one cross-cutting crate (this one) andcargoalready drives every gate. Revisit only if cross-crate orchestration outgrows a singleexamples/runner (e.g. multi-binary pipelines that need a build graph). -
Per-agent harnesses are library modules in
src/, alongside the v0 harnesses (roundtrip,convergence,equivocation,bundle_harness,negative,layout_stub). Each new agent gets one module exposingrun_all()-style entry points plus the granular asserts:- H → [
crate::prepass_harness] (spelling/decomposition determinism, eligibility-taxonomy coverage,RespellPitchprecedence, non-vacuity), driving the corpus in [crate::corpus]. Live now (H landed). - I →
engrave_harness(hard-constraint validation against declared IR constraints + class-specific collision rules, provenance survival, SVG XML-validity, golden machine-acceptance snapshot). Skeleton when I starts. - K →
migration_harness(deterministic + equivalence-preserving v0→v1 migration, payload-schema completeness). Skeleton when K starts. - J →
wire_harness(cross-impl decoder, canonicalization tests) + the wire-format fuzzer as anexamples/soak target. Skeleton when J starts.
- H → [
-
Each harness is asserted by a
tests/integration test (the same shape astests/acceptance.rs) so it is a discretecargo testtarget and a discrete CI job, and is also exercised at scale byexamples/conformance_suite.rsfor the nightly soak. The H harness lands astests/prepass.rs+ a[prepass-harness]stage in the conformance suite. -
benches/lives in this crate (not per-crate). The Chapter-10 budgets are workspace-level, and the marquee bench (the reducer's — at the timeO(n²)—canonical_reduction_orderat 10K+ envelopes, worklist F1) drivesepiphany-opsthrough the testkit's envelope generators — exactly what this crate already does. A bench that lived inepiphany-opscould not reuse the generators without a dev-dependency cycle. Usescriterion; thresholds are written in the bench, with known-pending scale points markedxfailper F1. -
The end-to-end integration harness (F5) is a
tests/integration test over a libraryintegrationmodule with documented stub swap-points, so real H/I/K/J stages replace stubs in place as they land, and the byte-identity assertion is wired from day one (trivially true on stubs, meaningful once stages are real).
Why a library module + integration test, not a bare integration test: the
harness logic (asserts, fingerprinting, the corpus) must be callable from both
the unit-budget tests/ target and the at-scale examples/conformance_suite.
Bare tests/ code is not importable across targets; library modules are. This
mirrors how v0's convergence/roundtrip modules are shared between
tests/acceptance.rs and the conformance example.
Unblocks: F1 (benches), F3 (corpus + taxonomy harness — done), F4 (per-agent harness skeletons — H done), F5 (integration skeleton).
F1 — The Chapter 10 budget benches (benches/reduction.rs, benches/bundle.rs)
Worklist item F1: a criterion bench asserting Chapter-10 budgets at documented
scale points, known-pending points marked xfail with the numeric budget written
in the bench. Home per F0: this crate's benches/.
Criterion version: 0.5 (locks to 0.5.1), default features off plus
cargo_bench_support. The 0.5 line's MSRV (1.70) fits the workspace's pinned
rust-version = "1.77"; criterion 0.6+ requires 1.80. Disabling default
features drops the plotting stack (plotters) and rayon — dead weight for
budget gates — while cargo_bench_support keeps plain cargo bench as the
entry point. Two transitive pins in Cargo.lock keep the tree MSRV-clean,
because the resolver (v2) is not MSRV-aware: clap 4.5.53 (MSRV 1.74; 4.6
requires 1.85) and half 2.4.1 (MSRV 1.70; 2.5+ requires 1.81). Re-check those
two if cargo update touches the criterion tree.
The gate mechanism. Criterion measures but never asserts, so each bench is
harness = false and its main() runs criterion's measurements first, then
the budget gate in [crate::budget] — a library module (the F0 pattern: shared
logic lives in src/, and it deliberately uses no criterion types, so the
library itself takes no new dependency). Every budget row carries an
Expectation written in the bench source next to its numeric threshold:
Pass— must hold today; a miss exits nonzero (the CI tripwire).Xfail(reason)— documented known-pending miss; the reason names the defect and owner. A miss prints an expected-failure line; a pass prints an XPASS promotion notice, so a stale marking is loud in both directions.
Skipped rows (the 50K point in quick mode) print an explicit skip line, never
silence. Under cargo test --benches (criterion test mode, detected by the
absence of the --bench flag cargo bench passes) the measurements run once
and the gate is skipped — the gate belongs to cargo bench.
Sampling calibration (a documented deviation from Chapter 10). The
conformance methodology — p99 over ≥1000 iterations on the reference hardware
profile — is the reference suite's job, not this gate's: 1000 iterations of the
(pre-fix ~29 s) 50K cold reduction was not a CI shape. The gate takes the
median of a small per-row iteration count (reduction: 9/3/1 iterations at
1K/10K/50K; bundle: 40 commit / 25 read; cut to 5/2/skip and 12/8 under
EPIPHANY_BENCH_QUICK=1),
cold (no in-gate warm-up — the marquee budget is an explicitly cold rate).
Criterion uses flat sampling with sample_size(10) for the reduction group;
the 50K point is gate-only (calibrated when a single iteration took ~29 s and
criterion's ≥10-sample loop would have blown the wall-clock budget; ~0.58 s
post-fix). Full cargo bench -p epiphany-testkit stays under ~2 minutes of
measurement; quick mode under ~15 seconds.
Measured on the dev profile (2026-07, post-K-fix), the table's ground truth:
| row | budget | measured | verdict |
|---|---|---|---|
reduction/1000 |
> 10,000 env/s cold | ~674,000 env/s (~1.5 ms) | Pass |
reduction/10000 |
> 10,000 env/s cold | ~257,000 env/s (~39 ms) | Pass |
reduction/50000 |
> 10,000 env/s cold | ~87,000 env/s (~0.58 s) | Pass (promoted from Xfail) |
bundle/typical_edit_commit |
≤ 50 ms | ~14.7 ms (real fsync) | Pass |
bundle/open_bootstrap_read |
≤ 200 ms | ~63 µs (moderate corpus) | Pass |
As F1 first measured it (2026-07, pre-fix), the O(n²)
canonical_reduction_order indegree construction was unambiguous in the curve
(~155K / ~12.5K / ~1.7K env/s at 1K/10K/50K — 10x envelopes cost ~120x time),
sinking the 50K row (~29 s per cold reduce, the documented xfail) and leaving
10K only ~25% over budget. Agent K's subquadratic rewrite
(threshold/frontier readiness, byte-identical order — see
epiphany-ops/DECISIONS.md) triggered the gate's XPASS promotion notice, and
the 50K row was flipped to Pass in the same change (F surfaces, K fixes,
round-tripped). Any future red row is a fresh regression: fix the reducer, do
not re-mark rows xfail without a written decision here.
Two honesty notes on the bundle rows. (1) They run on the build target's
filesystem, not std::env::temp_dir(): /tmp is commonly tmpfs, where fsync
is a near-no-op — measured, the commit row is ~54 µs on tmpfs vs ~14.7 ms on a
real NVMe filesystem, a 270x difference that would have made the 50 ms budget
vacuous. (2) The read budget is spec'd against "a 100-page orchestral score";
no such corpus generator exists yet, so the row is a moderate-corpus (1K
envelopes + canonical-base snapshot) stand-in with margin to spare, and says so
in its docs.
Scope, and the budget rows deliberately not built yet. F1 covers the three
budgets that are measurable against shipped subsystems: the reduction rate and
the two file-format budgets. The remaining Chapter 10 rows are blocked on
unimplemented or in-flight subsystems and become future benches in this same
benches/ + gate shape: the interactive keystroke→frame budgets (blocked on
Agent I's engrave/render pipeline maturing past the visible slice), cold
solving of a 100-page score within 2 s p99 and the incremental-propagation
bound (blocked on the real Chapter 9 solver and a 100-page corpus generator —
the latter also unblocks the honest read-row corpus), and the memory ceilings
(blocked on the same corpus). CI runs the gates in the conformance job with
EPIPHANY_BENCH_QUICK=1 (50K skipped) and in the nightly soak job in full.
F3 — The representative score corpus + eligibility-taxonomy harness (Agent H)
Worklist item F3 — "the most underbuilt dependency; unblocks H entirely."
The corpus lives in [crate::corpus] as ≥20 deterministic, check_invariants-clean
fixtures tagged by tier (common / edge / torture) and by the event-kind
eligibility taxonomy of PHASE2_QUICKSTART §H. Rather than re-deriving the
taxonomy, the harness runs Agent H's own epiphany_core::derive_annotations and
reads its [epiphany_core::TaxonomyReport], then (a) independently recounts
events by kind and cross-checks H's counts (so a miscount is caught, not
trusted), and (b) aggregates per-bucket counts across the corpus and asserts every
taxonomy bucket is non-empty or explicitly deferred. The corpus also re-uses the
existing positive generators (valid_score, valid_score_rich,
ten_measure_single_staff) as fixtures so F's taxonomy harness runs over the same
graphs Agents H and I develop and render against.
Deferred buckets (documented, not required non-empty by the clean corpus):
none — every bucket including decomposition_skipped_nonmusical (zero-duration
grace) and decomposition_ungriddable (off-grid / sub-sixty-fourth torture
cases) is exercised by a dedicated fixture, so the honest-classification paths are
all proven reachable. If a future invariant change makes a bucket unreachable by
clean input, move it to corpus::DEFERRED_BUCKETS with a written reason rather
than dropping the assertion.
F4 (H) — The H merge gate
[crate::prepass_harness] is H's merge gate (worklist F4). It asserts H's stated
PHASE2_QUICKSTART acceptance criterion over the corpus: every eligible
IdentifiedPitch carries a non-trivial spelling (verified by pitch-class
correctness, which the old constant-C4 stub fails); every eligible determinate
metric duration carries a Decomposition whose components reconstruct the
duration (invariant 15); ineligible cases are classified and counted; derivation
is deterministic across runs (asserted by structural equality and a canonical
textual fingerprint, since DerivedAnnotations deliberately has no codec);
RespellPitch-style authored overrides take precedence; and the derivation stays
deterministic when run on materialized scores in the criterion-5 pipeline.
Non-vacuity guard (the F discipline — the gate must go red if H were stubbed):
across the corpus the harness requires multiple distinct spelled nominals and at
least one accidental (a constant-C4 stub yields one nominal, zero accidentals),
at least one multi-component (tied) decomposition and multiple distinct note
values (an empty/no-op decomposition map yields neither), and per-pitch
pitch-class correctness (the stub mis-spells the first non-C pitch).
Pass 12 batch tracker
Per F's mandate, the Pass 12 batch is tracked in spec/PASS12_BATCH.md. It opens
once ≥3 ambiguities accumulate (same rule as v0 → Pass 11). Agent H's landing
contributed five candidates (P12-H1…P12-H5, recorded in
crates/epiphany-core/DECISIONS.md), which crosses the threshold, so the batch is
open. F does not resolve these; F collects them.
F5 — The Reference Suite harness (src/reference_suite.rs, 2026-07)
The Reference Suite companion (v0.1.0) charters the v0.1 entry set — six
scores named by reference-implementation builder and seed — and its
non-normative Harness Binding chapter says the executable binding "is
delivered with the reference implementation." This module is that binding,
in the F0 shape: a library module holding the machinery
(entries/evaluate_minimal/table), asserted by
tests/reference_suite.rs with one test per entry so a failure names its
entry, plus pins for the entry-set shape, the declared A4 default geometry
(the companion's solve-configuration requirement — asserted against
Engraver::default().geometry()), and RS-2's builder identity
(corpus gen_valid_score_rich ≡ generators::valid_score_rich(0xF302),
byte-for-byte).
Solver-parametric on purpose. The library module takes
&dyn ConstraintSolver; the integration test supplies the real Engraver.
This keeps epiphany-engrave a dev-only dependency (the library dependency
graph is unchanged, mirroring the multi-system click test) while the harness
itself stays reusable against any solver claiming Minimal.
The four-condition pass rule, with the F1 Pass/Xfail discipline on
condition 4. evaluate_minimal asserts the companion's per-entry rule
exactly: hard-constraint satisfaction (renderable, non-partial,
satisfied_hard_constraints, nothing unsatisfied); internal determinism
(byte-identical ResolvedLayoutIR canonical bytes and bitwise-identical
metric vectors across repeated solves); a well-formed, accurate report
(Minimal tier claim, every axis a finite [0,1] value, never the unmeasured
placeholder); and every axis at or below the Quality Metric Catalog's
Minimal-column threshold. The first real measurement (2026-07) found exactly
one miss: RS-1's casting_off_quality = 1.0 — greedy first-fit leaves a
two-measure stub last system (glyph spans ~78.6/18.8 staff spaces, width CV
0.61 ≥ the 0.5 anchor), the exact failure the axis exists to catch, on a
layout that is byte-locked by the render goldens. Waiving it silently would
fake conformance; failing the workspace would misreport a ratified-spec
tension as a code bug. So condition 4 carries the budget harness's (F1)
discipline: the miss is a documented minimal_xfail row asserted to still
miss — if the layout or the catalog changes and RS-1 comes within
threshold, the harness fails demanding promotion (remove the row), exactly
like an F1 XPASS. The row's resolution is spec-side and tracked in
epiphany-engrave/DECISIONS.md's Pass-12 candidates (casting-off balance
pass with golden regeneration, or a QMC anchor/threshold minor revision —
the catalog's own threshold-tuning open question anticipated this).
Eligibility tiers vs. solver tiers. The corpus Tier
(Common/Edge/Torture) is Agent H's eligibility taxonomy; the suite's tiers
(Minimal/Standard) are Chapter 9 conformance tiers. The companion carries the
same caution; the harness resolves corpus entries by name string only and
never reads the corpus tier.
The Text Projection grammar checker (tests/text_projection_grammar.rs)
Text Projection 0.3.0 claimed a "machine-checked" grammar. It was checked by a throwaway script, so the claim was true of one run and of nothing durable. This test file is the durable form; 0.4.0 retracts the earlier claim in its own revision history. It is the same evidence gap the P2–P4 decode work kept finding: a green gate proves nothing about coverage until the harness states its own reach.
Six tests, and every one was mutation-verified — the mutation was applied to
the .tex, the anchor asserted present before substitution (a str.replace
that matches nothing looks exactly like a passing test), and the named test
observed to fail:
| Mutation | Killed by |
|---|---|
delete the transpose-interval production |
the_kind_productions_are_the_operation_vocabulary |
| reference an undefined nonterminal | every_nonterminal_is_defined_and_reachable |
let unescaped admit U+005C (the 0.3.0 bug) |
the_escape_grammar_agrees_with_the_escape_requirement |
drop the derived-ordering citations |
the_derived_ordering_requirement_is_cited_where_it_applies |
spell the escape introducer "\\" |
the_escape_grammar_agrees_with_the_escape_requirement |
drop the \t escape alternative |
the_escape_grammar_agrees_with_the_escape_requirement |
restore Ligatures={TeX} on the mono font |
the_mono_font_does_not_substitute_glyphs_in_the_grammar |
The vocabularies are derived, never transcribed. The 31 operation-kind
productions come from OperationKindTag::PAYLOAD_FREE plus Registered, mapped
to Catalog section names through an exhaustive match — so a kind added to
the enum and not to the grammar fails to compile, then fails the test. The chunk
productions come from ChunkKind::from_discriminant. This is the
operation_kind_tag_vocabulary! lesson applied to prose: a hand-maintained list
parallel to an enum is a latent false lock.
Every locator finds its production by name, not by column. Writing the
checker exposed four bugs in the checker, three of them of this kind: a
column-anchored needle ("projection ::=") that a reflow broke — a checker
that cannot find the grammar silently checks nothing; a defined() that missed
three productions whose left-hand side sat on the line above their ::=, which
would have hidden undefined nonterminals; and a per-line <...> stripper
that leaked 0022 and 005 out of multi-line prose spans, where they read as
nonterminals. Nonterminal tokens must now begin [a-z], per symbol itself.
Two rendering rules are load-bearing, not cosmetic. A document that specifies a text syntax must not misprint that syntax.
- The escape introducer and the string delimiter are written as codepoints
(
U+005C,U+0022). A quoted terminal"\\"reads as two backslashes, making every escape three characters where the requirement says two. This is the audited 0.3.0 defect in a second disguise; it was written, caught by review, and is now asserted against. - The mono font must not enable TeX ligatures.
tligrewrites"as a right curly quote and--as an en dash, sostring ::= '"' schar* '"'rendered as’”’ schar* ’”’.core_spec.texalready omittedLigatures={TeX}from its\setmonofont, which makes this the house convention rather than a new one; the three companions that still enable it have no listing content the feature would touch.
0.5.0: what starting the implementation found
The grammar could not derive an ordinary pitched note. value had no
alternative for a sequence, though req:textproj:value-projection clause 5
required one. Adding it exposed why it had been missing: a sequence whose first
element is a fieldless variant is shape-identical to a struct, and () is both
the empty sequence and the absent option. Both collisions are reachable from the
first pitched note in any score — PitchedEvent carries articulations and
ornaments (sequences over the zero-field ArticulationMark / OrnamentMark)
beside an optional DynamicMark.
The collision is irreducible without new syntax, and new syntax buys nothing.
req:textproj:strict-parse already obliges a parser to reject "a duplicate in a
set-typed field" — which it cannot do without knowing the field is set-typed. A
parser consults the schema either way. Spending (seq …) or […] to remove a
shape ambiguity, while leaving the schema dependency that syntax was meant to
remove, lengthens every line and simplifies no tool. User ratified
schema-directed (req:textproj:schema-directed), so value collapses to
"(" value* ")" or a leaf — all that shape can honestly say — and the
requirement assigns meaning by expected type.
Three consequences, now stated: a struct with no fields is the bare symbol, as a
fieldless variant is; a byte string is not a sequence (where the binary form
writes a length-prefixed run of bytes — an opaque extension payload, a
SoundConfiguration — the projection writes a byte string, never a list of
integers); and the grammar's repetitions carry a notation rule, adjacent elements
separated by exactly one space, without which "(transpose (" bytes* ") " spelled
two targets as one undelimited run of hex.
The checker gained value_admits_a_bare_list_and_claims_no_shape_it_cannot_distinguish,
mutation-verified two ways: restoring the 0.4.0 value production, and stripping
the schema-directed citations. A grammar that claims to distinguish a struct
from a sequence would be lying, so the test asserts the symbol-headed alternative
is absent.
T2 W3 (2026-07-23) — Promoting the T1a visual goldens to gate [9], behind golden-gate
spec/CONTRACT_EDITOR_T2_SELECTION.md §W3. T1a
(crates/epiphany-editor-gui/src/goldens.rs, spec/CONTRACT_EDITOR_T1A_GOLDENS.md)
landed pixel-level golden tests in epiphany-editor-gui but left this crate's
conformance_suite frozen at 8/8 — the harness existed but nothing counted it.
This tranche adds gate 9, re-deriving the three T1a golden states headlessly
inside examples/conformance_suite.rs and comparing against the same committed
baselines, entirely behind a new golden-gate feature.
Why a feature, not an unconditional addition. The MSRV check job
(.github/workflows/ci.yml, the check job) runs cargo test --workspace --exclude epiphany-editor-gui --all-targets at the pinned floor (1.85) —
--all-targets builds every example in this crate, conformance_suite
included, with default features. resvg (and the transitive raster stack it
pulls: usvg, tiny-skia, font/image decoding crates) is only ever exercised
by epiphany-editor-gui, which is deliberately excluded from that job because
one of its own dependencies (image, via eframe) declares a newer MSRV than
the workspace floor. Making resvg reachable from epiphany-testkit
unconditionally would silently re-impose that same floor on a crate the MSRV
job is not supposed to skip. The fix is Cargo's standard shape for "a
dependency only some callers need": resvg (and epiphany-render-svg, not
previously a dependency of this crate at all) go into [dependencies] with
optional = true — dev-dependencies cannot be optional, which is why
example-only code still needs a [dependencies] entry — gated by a
golden-gate feature that nothing enables by default. cargo tree -p epiphany-testkit -e normal shows zero resvg nodes without the feature and
exactly one with it (--features golden-gate).
epiphany-editor-core and epiphany-engrave needed no such treatment: T1a's
multisystem_click.rs test and the Reference Suite harness (F5, above) already
made both unconditional dev-dependencies of this crate, carrying no
resvg-class MSRV risk of their own, so gate 9's EditorSession /
Engraver::default() calls simply reuse them as-is.
Keeping the no-feature output byte-identical was the binding constraint,
stated in the contract as a hard requirement (the MSRV job's --all-targets
build must see zero difference). The suite's gate-count denominator moved from
a literal 8 in every eprintln! to a TOTAL_GATES const — 8 without
golden-gate, 9 with it — so every existing print line renders
byte-for-byte what it always has when the feature is off, and gate 9's own
line plus the renumbered final [9/9] ok: line appear only when it is on.
Verified directly: the pre-tranche example source, rebuilt and run at SCALE=0,
produces output diffed byte-for-byte against the post-tranche no-feature build
of the same invocation — diff reports no difference.
Gate 9's substance mirrors T1a's comparator, not its full machinery. It
re-renders ten_measure_single_staff(0) as opened, the same fixture after the
scripted pencil insert (the click point re-derived by literally copying
goldens.rs::scripted_insert_target's algorithm — last system by lowest
bounding_box.origin.y, rightmost Pitch-sourced notehead in that system's
vertical band, +0.5 staff spaces right / +2.0 up — since this crate cannot
call a #[cfg(test)]-private function in a different crate's binary), and
ten_measure_with_slurs(0) as opened, rasterizing each exactly as
epiphany-editor-gui/src/main.rs::rasterize_pixmap does (the algorithm
copied for the same cross-crate-privacy reason: usvg parse, dimensions
ceiling to whole pixels, opaque white background, resvg::render). Unlike
goldens.rs's own comparator, gate 9 is compare-only: no
EPIPHANY_BLESS_GOLDENS path, no actual.png/expected.png/diff.png
artifact writing. A mismatch's panic message points at cargo test -p epiphany-editor-gui goldens — the crate that owns the baselines, the bless
mechanism, and the reviewable artifacts — as the place to diagnose, rather
than duplicating that machinery here. G3 (undo-equals-G1) is deliberately not
re-derived as a fourth comparison: it reuses G1's own baseline file in T1a,
so covering G1 already covers what G3 would additionally lock at the pixel
level for this gate's purposes.
The cross-crate baseline path is a documented coupling, not an accident.
The three committed PNGs live in crates/epiphany-editor-gui/goldens/, one
level up and over from this crate's own directory. Gate 9 resolves that path
from this crate's CARGO_MANIFEST_DIR (the only directory env! can see at
compile time) as {CARGO_MANIFEST_DIR}/../epiphany-editor-gui/goldens/. A
rename or relocation of either crate directory, or of
epiphany-editor-gui/goldens/, breaks this path — silently succeeding at
cargo check (it is a runtime path, not a compile-time one) but failing gate
9 loudly at run time with a clear "no golden baseline at ..." panic (mutation
m6 below), never a silent skip. This crate's own Cargo.toml comment on the
epiphany-render-svg/resvg dependencies and the golden_gate module doc in
examples/conformance_suite.rs carry the same note; epiphany-editor-gui is
outside this packet's blast radius (spec/CONTRACT_EDITOR_T2_SELECTION.md
§W3), so its own DECISIONS.md — which already documents the baseline
directory from the T1a side — is left untouched here.
Mutation evidence (independently reproduced against gate 9's real run, not just described):
- m5 — tamper one decoded pixel before comparison. Rendering G1, XOR-ing
byte 0 of its pixmap with
0xFF, then comparing that tampered pixmap against the committed baseline made gate 9 die with (actual panic, captured verbatim):gate 9: ten_measure_open decoded RGBA differs from the committed baseline at .../epiphany-editor-gui/goldens/ten_measure_open.png (1 of 1119968 bytes differ); diagnose with \cargo test -p epiphany-editor-gui goldens`. (The pixel comparison is a manualif actual.data() != expected.data() { panic!(...) }reporting a differing-byte count, not a bareassert_eq!— the latter was tried first and dumps both full byte slices, ~1.1 MB each, into the panic message on a real score raster; the differing-byte count is the useful signal, and the visual artifacts to inspect already live inepiphany-editor-gui's own comparator.) Restored by reversing the exact same byte-flip line (notgit checkout`); the suite returns to 9/9. - m6 — point the baselines path at a nonexistent directory. Appending
-DOES-NOT-EXISTtobaseline_path's directory component made thefs::readcall fail, and the gate panicked immediately with (captured verbatim):gate 9: no golden baseline at .../epiphany-editor-gui/goldens-DOES-NOT-EXIST/ten_measure_open.png (No such file or directory (os error 2)) — \epiphany-editor-gui/goldens/` must contain the committed T1a baselines for this gate to compare against; diagnose with `cargo test -p epiphany-editor-gui goldens`` — a loud, named failure, never a silent skip or pass. Restored by reversing the substitution; the suite returns to 9/9.
Baselines pin the raster stack (inherited from T1a, restated here because
this crate now also depends on it). Any Cargo.lock movement of
resvg/usvg/tiny-skia/png is a golden-review event — for both crates
now, not just epiphany-editor-gui — never a silent re-bless. Gate 9 has no
bless mechanism of its own by design (above), so a deliberate raster-stack
bump that changes pixels must be reviewed and re-blessed through
epiphany-editor-gui's own EPIPHANY_BLESS_GOLDENS=1 path; gate 9 will then
pass again against the newly-blessed baselines with no changes of its own
needed.