6.8 KiB
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/BTreeSetor explicitly sorted throughCanonicalMap/CanonicalSet/sort_canonical, whose element types must implement theCanonicalByteOrdermarker — a type whoseOrddoes 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 byIntegrityAnomalyId; 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::Curveintegration is unimplemented and declared as such — conversion returnsCurveIntegrationUnsupportedrather than a wrong or vendor-specific answer. The linear/exponential segment integration and thewallclock_to_musicalinverse (deterministic continued-fraction with documented iteration/denominator bounds and a typedTempoIntegration-class tolerance) are advisory, per the floating-point declaration above.