epiphany/CONFORMANCE.md

174 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Determinism Conformance Statement
This is the published conformance statement required by the core
specification's Determinism Contract (`core_spec` Appendix D,
§"Conformance Statement", `sec:det:conformance`). It covers the reference
implementation in this repository — the `epiphany-*` workspace crates — as of
the tree that carries this file. Every claim below is anchored by a test named
in the Canonical Byte-Layout Reference (Appendix E) or cited inline.
## Platform and floating-point library
The reference build and CI platform is Linux on x86-64, compiled with stable
Rust (MSRV pinned in `Cargo.toml`, `rust-version = "1.77"`), default target
options — no `fast-math`-class flags anywhere in the workspace.
Canonical state contains no *computed* floating-point values. Every float that
enters canonical state is a stored `CanonicalF64` (finite by construction,
`-0.0` canonicalized) serialized as its 8 little-endian IEEE 754 bytes
(`epiphany-determinism/src/float.rs`); no canonical algorithm derives new
float values into canonical state.
**Transcendental functions.** The only transcendentals in the workspace are
`f64::ln`/`f64::exp` from the platform's `std`/libm, used exclusively by tempo
integration (`epiphany-core/src/tempo.rs`, speed-linear and exponential
segments). Per the contract's required disposition, those conversion outputs
are declared **advisory and non-canonical**: musical time is the exact
rational, wall-clock time is exact integer nanoseconds, and no tempo-derived
float is stored in canonical chunks or hashed into canonical identity. If a
future feature promotes tempo-derived values into canonical state, adopting a
documented portable math library (or quantizing at the canonical boundary) is
a precondition, not an afterthought.
Content hashing is BLAKE3 (the `blake3` crate, workspace-pinned), which is
bit-exact by specification on all platforms.
## Parallel execution strategy
None. Every canonical algorithm — reduction, materialization, pre-passes,
encoding, hashing, layout solving — is single-threaded by construction; the
workspace contains no `rayon`, `std::thread::spawn`, or other concurrency in
any canonical path. The implementation *is* the single-threaded baseline, so
equivalence to that baseline holds by identity. Any future parallel execution
must demonstrate byte-identical output against this baseline before it ships.
## `-0.0` canonicalization and NaN/infinity rejection
`CanonicalF64::new` rejects NaN and ±infinity at runtime in **all build
profiles** (not just debug assertions) and canonicalizes `-0.0` to `+0.0`
before storage, so both are unrepresentable in canonical state. Decoding
treats non-finite float bytes as corruption (typed error, never silent
acceptance), and hash preimages accept only `CanonicalF64` values. Locked by
the unit tests in `epiphany-determinism/src/float.rs` and
`src/serialize.rs`.
## Rounding
Round-to-nearest-ties-to-even is used at every canonical quantization
boundary: `QuantizedCoord` (1/1024 staff space) quantizes via
`round_ties_even` and rejects NaN/infinity/overflow rather than saturating
(`epiphany-determinism/src/coord.rs`), and `ResolvedLayoutIR` quantizes its
f32 working coordinates through the same type at canonical emission.
Declared deviation, inside the advisory surface only: the non-canonical
wall-clock conversion in `tempo.rs` rounds nanoseconds with `f64::round`
(half-away-from-zero). It shares the tempo-integration surface declared
non-canonical above; it must be converted to ties-to-even if that surface is
ever promoted.
## Canonical iteration orders
All canonically-serialized collections iterate in the contract's orders
(§`sec:det:ordering`):
- Generic containers are `BTreeMap`/`BTreeSet` or explicitly sorted through
`CanonicalMap`/`CanonicalSet`/`sort_canonical`, whose element types must
implement the `CanonicalByteOrder` marker — a type whose `Ord` does not
match its canonical byte order is rejected at compile time
(`epiphany-determinism/src/order.rs`).
- Operation envelopes reduce in the canonical order: causal (Kahn topological
over DVV coverage), then the HLC tuple
(`epiphany-ops/src/reduce.rs::canonical_reduction_order`), property-tested
for permutation invariance at 1,000 envelopes × 10 orders plus a
10,000-set fuzz gate.
- Conflicts serialize ascending by `ConflictId`; integrity anomalies ascending
by `IntegrityAnomalyId`; chunk references by `(kind, hash, offset)`;
extension declarations by `(ExtensionId, SemVer)` as numeric tuples —
each rejected (not normalized) on decode when out of order.
`HashMap`/`HashSet` appear only in non-canonical lookup indexes, caches, and
diagnostics, or are projected through a sort before any canonical output.
## NFC normalization
Unicode normalization uses the `unicode-normalization` crate
(workspace-pinned, `0.1.x`). Catalog identifiers NFC-normalize at
construction (`epiphany-core/src/pitch.rs`); envelope string fields
NFC-normalize before hashing (`epiphany-ops/src/encode.rs`, test
`nfc_normalizes_before_hashing`); system-derived pitch identity NFC-normalizes
at the derivation boundary. Free-text fields are raw UTF-8 by the ratified
codec convention (`req:format:codec-conventions`) and are not folded.
## Declared open-question algorithms
Per §"Open Algorithm Hooks", every open-question area is either
profile-declared with a versioned identifier or errors rather than
substituting a vendor heuristic:
- **Spelling**: `SpellingAlgorithmId` `"default"` — a Temperley-style
line-of-fifths preference algorithm, v1 (`epiphany-core/src/prepass.rs`).
The identifier is ratified normative (Pass 12,
`req:pitch:spelling-algorithm`). A profile requesting any other id errors;
nothing is silently substituted.
- **Notational decomposition**: `DecompositionAlgorithmId` `"default"` — the
integer-grid metric splitter, v1, with its scope bounds ratified as
normative bounds of the versioned algorithm (Pass 12,
`req:time:decomposition-algorithm`: single governing meter, `MAX_DOTS = 1`,
tuplet-nesting deferred; a wider algorithm is a version bump).
Same no-substitution rule.
- Both pre-pass outputs are **canonical derived annotations**: deterministic
functions of `(materialized graph, profile, algorithm id)` recomputed on
materialization, never stored canonical state — so an algorithm version
change deterministically invalidates derived output without state
migration.
- **Tempo curves**: `TempoShape::Curve` integration is unimplemented and
declared as such — conversion returns `CurveIntegrationUnsupported`
rather than a wrong or vendor-specific answer. The linear/exponential
segment integration and the `wallclock_to_musical` inverse (deterministic
continued-fraction with documented iteration/denominator bounds and a
typed `TempoIntegration`-class tolerance) are advisory, per the
floating-point declaration above.
## The conformance suite's gate count
`cargo run -p epiphany-testkit --example conformance_suite` runs the numbered
gates described above (and several supplementary lettered stages) end to end,
printing `[N/TOTAL] ...` as each gate starts and `[TOTAL/TOTAL] ok: full
conformance suite passed` on a clean run; CI's `conformance` job
(`.github/workflows/ci.yml:146-176`) runs it on every push and pull request.
**Gate 9 — visual golden conformance, behind the `golden-gate` feature.**
`epiphany-testkit`'s default build (`TOTAL = 8`) covers determinism,
round-trip, crash recovery, equivocation, convergence, reduction, layout
round-trip, and the cross-implementation/Text-Projection decode vectors — no
rendering. Gate 9 (`TOTAL = 9` with the feature enabled) additionally
re-derives the T1a visual golden states
(`spec/CONTRACT_EDITOR_T1A_GOLDENS.md`) headlessly — `EditorSession::open`
over the real engraver and renderer, rasterized with `resvg` exactly as
`epiphany-editor-gui`'s demo binary does — and compares the decoded RGBA
pixels against the baselines committed at
`crates/epiphany-editor-gui/goldens/*.png`, dimensions first. It is
compare-only: no bless mechanism, no artifact writing; a mismatch's panic
names `cargo test -p epiphany-editor-gui goldens` as the crate that owns
that machinery.
**Why the gate is feature-gated rather than always on.** `epiphany-testkit`
is a workspace member the MSRV `check` job (`.github/workflows/ci.yml`, pinned
to Rust 1.85) builds with `--all-targets`, `conformance_suite` included — and
that job deliberately excludes `epiphany-editor-gui`, because one of its own
dependencies (`image`, pulled in via `eframe`) declares a newer MSRV than the
workspace floor. `resvg` — the crate gate 9 needs to rasterize SVG the same
way the demo GUI does — carries that same raster-stack dependency weight.
Making it reachable unconditionally from `epiphany-testkit` would silently
re-impose that floor on a crate the MSRV job is meant to keep covering. So
`resvg` (and `epiphany-render-svg`, needed to produce the SVG in the first
place) are `optional = true` dependencies of `epiphany-testkit`, gated by a
`golden-gate` Cargo feature nothing enables by default; only the
`conformance` job's suite invocation passes `--features golden-gate`
(`.github/workflows/ci.yml:165`). Without the feature, `cargo tree -p
epiphany-testkit -e normal` shows no `resvg` node in the dependency graph;
with `--features golden-gate`, it shows exactly one. Full rationale, the
mutation evidence, and the cross-crate baseline-path coupling are recorded in
`crates/epiphany-testkit/DECISIONS.md` (T2 W3) and
`spec/CONTRACT_EDITOR_T2_SELECTION.md` §W3.