Repository navigation
spec-guards: ring-096 and specs/numeric/formats.t27 DRIFTED -- both sides disagree with the gf16 SSOT on f32 #6887
Description
What is wrong
spec-guards is red on master and on every pull request that touches specs/** or rings/**. The failing step is python3 tools/check_ring_spec_drift.py, which exits 1 on one pair:
DRIFTED ring-096-rust specs/numeric/formats.t27
hand fns 7, spec fns 6, shared 6, differing 5:
f32_to_gf16, f32_to_ternary, gf16_to_f32, quantize_value, ternary_to_f32
Example: https://github.com/gHashTag/t27/actions/runs/37473887427/job/112304143768
Cause
Both files were written in the same commit (ea15cd5, #1399) and never agreed:
| function | spec (t27c gen-rust) |
ring-096 (hand Rust) | numeric SSOT specs/numeric/gf16.t27 |
|---|---|---|---|
gf16_to_f32 |
(x: u16) -> u16 (spec wrote -> gf16) |
(x: u16) -> f64 |
gf16_decode_to_f32(GF16) f32 |
f32_to_gf16 |
(a: f32) -> u16 |
(a: f64) -> u16 |
gf16_encode_f32(value: f32) GF16 |
f32_to_ternary |
(x: f32) -> Trit |
(x: f64) -> Trit |
-- |
ternary_to_f32 |
(t: Trit) -> u16 (-> gf16) |
(t: Trit) -> f64 |
-- |
quantize_value |
(x: f32, fmt: Format) -> u16 (-> gf16) |
(x: f64, fmt: Format) -> f64 |
-- |
format_bytes |
(fmt: Format) -> usize |
same | -- |
The drift was invisible until 2026-09-17: the spec declared its functions as fn sig;, which does not parse, so gen-rust emitted nothing and the pair read UNRELATED. #4248 made the declarations fn sig { }, the spec started generating, format_bytes matched and the other five did not, and the pair became DRIFTED.
Both sides are wrong, against the SSOT (law L6):
- Spec: a decoder named
gf16_to_f32returninggf16(which lowers tou16) contradictsgf16_decode_to_f32 ... f32ingf16.t27. Same forternary_to_f32and forquantize_value, whose own tests read the result back as a float near 1.5. - Ring: the hand crate uses
f64where the spec and the SSOT both sayf32, while its header claims it "mirrors specs/numeric/formats.t27 byte-for-byte".
Fix (one PR)
specs/numeric/formats.t27: the three return types becomef32; the tests and invariants that wrapped those results ingf16.to_f64(...)compare thef32directly. Two false claims in the same tests are corrected rather than carried:0x3C00is not 1.0 under bias 31 (it is0x3E00; the ring's test already says so), andternary_to_f32_is_inverselisted -0.5 and 0.5, which quantize to 0 under the strict threshold. Resealed.rings/ring-096-rust/src/lib.rs: the public API moves tof32. The f64 arithmetic stays as private helpers; narrowing at the boundary is exact because every GF16 value is representable in f32 and f32 -> f64 is exact. All 42 crate tests pass (rustc 1.99 on the t27c Railway lab).specs/numeric/formats.t27:ExpShift/SignShiftchange fromu5/u4tou8, matchingEXP_SHIFT/SIGN_SHIFTingf16.t27.gen-rustpassedu5/u4through verbatim, so the generated Rust did not compile and the differential harness could not even be built. Resealed again.
Editing the existing Rust in rings/ring-096-rust/src/lib.rs is an exception to "Only t27"; this issue carries the label owner-approved-foreign under the owner's standing rule of 2026-10-06, and the path is listed in tools/policy/foreign-exceptions.txt for this issue only.
Known follow-up, not hidden (measured)
After the fix the pair is CONVERGED, so the differential step runs it. On the lab it prints covered 2 of 6, REFUSED 4 (Trit/Format have no producer) and exits 2. That is an honest "could not test": the spec functions have no bodies (unimplemented!()). Tracked in #6893. On master that step is not reached anyway, because tools/published_figures.py --check fails earlier in the same job with 8 drifted figures, independent of this pair.
Refs #6488
Activity
seal(numeric/formats): reseal after the f32 return types (Refs #6887)
fix(numeric/formats): shift constants are u8, as in the gf16 SSOT (Refs
seal(numeric/formats): reseal after the u8 shift constants (Refs #6887)
numeric/formats: implement the codec bodies; ring-096 encodes denormals
feat(t27b): std.mem.eql/indexOf on byte slices and byte-array pointers (
fix(codegen): gen-rust enum order and gen-zig float-call cast (#6941) (…