epiphany/CONFORMANCE.md

131 lines
6.8 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.