Skip to content

fix(core): make the canonical-encoding f64 round-trip bit-exact (local half of #130) - #176

Merged
jensholdgaard merged 5 commits into
mainfrom
fix/130-f64-canonical-roundtrip
Jun 11, 2026
Merged

fix(core): make the canonical-encoding f64 round-trip bit-exact (local half of #130)#176
jensholdgaard merged 5 commits into
mainfrom
fix/130-f64-canonical-roundtrip

Conversation

@jensholdgaard

@jensholdgaard jensholdgaard commented Jun 11, 2026

Copy link
Copy Markdown
Owner

What

Makes the Ourios canonical body encoding round-trip every finite f64 bit-exactly — the local half of #130 (the upstream opentelemetry-proto question is tracked there; this PR deliberately does not auto-close it).

Diagnosis correction (empirically verified)

#130 attributed the 1–2 ULP drift to a lossy with-serde double formatter. Probing the actual bytes relocated the bug:

  • The old encode path and plain serde_json emit byte-identical output for doubles (e.g. the repro value emits {"doubleValue":-1.5374084650425255e+99} on both) — and those digits are correct shortest-round-trip: "-1.5374084650425255e99".parse::<f64>() recovers the exact bits. with-serde has no custom double formatter.
  • The lossy half is serde_json's default float parser, which is approximate by design. Its float_roundtrip feature exists precisely for this; with it enabled, 200k random finite-f64 bit patterns round-trip with 0 drift, repro value included.

So instead of hand-rolling a canonical encoder (drafted, then discarded: it emitted byte-identical output to the derive and fixed nothing), the fix is the decode-side feature flag. The encode path and every stored byte shape are untouched — the #167 byte-shape test (encoder_emits_exact_canonical_bytes_per_variant) passes unchanged, as do all prior round-trip/determinism tests.

Changes

  • ourios-core/Cargo.toml: serde_json += float_roundtrip, with a comment marking it load-bearing for the §6.1 faithfulness guarantee (not an optimisation preference). proptest added as a dev-dependency (workspace-consistent "1").
  • otlp.rs module docs: explain that encode was already shortest-round-trip, decode requires float_roundtrip, and non-finite doubles are a separate pre-existing gap. Corrects an inaccurate doc claim that with-serde maps f64::NAN to a "NaN" string — empirically it emits {"doubleValue":null}.
  • Tests (ourios-core):
    • deterministic with-serde serializes f64 lossily in OTLP/JSON (~12% of arbitrary doubles drift 1–2 ULP) #130 regression: the repro value -1.5374084650425255e99, plus -0.0 (sign bit preserved — verified via to_bits), 0.0, f64::MAX, f64::MIN_POSITIVE, and the smallest subnormal round-trip bit-exactly;
    • proptest property finite_doubles_round_trip_bit_exact: arbitrary finite f64 from raw u64 bit patterns, round-tripped bit-exactly at top level and nested inside arrayValue / kvlistValue;
    • nonfinite_doubles_encode_to_the_null_shape: pins the empirically captured {"doubleValue":null} bytes for NaN / +Inf / -Inf so an upstream change can't silently drift stored bytes.
  • RFC 0001 §6.1: dated amendment (2026-06-11, mirroring the 2026-06-09 amendment style) + a double bullet in the encoding rule. RFC 0005 §3.3 defers to §6.1 and needed no edit.

Invariant / hazard statement (CLAUDE.md §4)

This touches hazard 7 territory (faithful reconstruction) via RFC 0001 §6.1's structured-body faithfulness: stored_bytes ↔ AnyValue, which lossy_flag = false rests on. The change strengthens that invariant (finite doubles now round-trip bit-exactly) without altering any stored byte: encode output is byte-identical before and after, so existing files and dedup behaviour are unaffected, and old stored bytes decode exactly under the new parser.

Known gap surfaced (pre-existing, out of scope, now pinned by a test): non-finite doubles (NaN, ±∞) encode to {"doubleValue":null} — bytes the decoder rejects, so the §6.1 faithfulness guarantee does not hold for them today and didn't before this PR. Flagged in the RFC amendment; likely warrants its own issue/OTel-spec question (proto3 JSON maps non-finite floats to the strings "NaN"/"Infinity"/"-Infinity", which with-serde does not implement for AnyValue).

Verification

  • cargo test --all-features — 607 passed; 0 failed; 24 ignored (whole workspace)
  • cargo fmt --all --check — clean
  • cargo clippy --all-targets --all-features -- -D warnings — clean
  • cargo doc --workspace --no-deps --all-features — clean
  • mdbook build — clean (pre-existing mdbook-mermaid version warning only)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Guaranteed bit-exact round-tripping for finite floating-point values in canonical JSON encoding/decoding; non-finite values (NaN, ±∞) are encoded as null.
  • Documentation

    • Updated canonical JSON specification and user-facing messages to clarify floating-point encoding, decoding, and behavior guarantees.
  • Tests / Dev tooling

    • Expanded unit and property tests to verify exact finite float round-trips and non-finite handling.

…local half of #130)

decode(encode(x)) on a DoubleValue drifted 1-2 ULP for ~12% of
arbitrary finite f64 through the Ourios canonical body encoding,
breaking RFC 0001 §6.1's stored_bytes <-> AnyValue faithfulness
(which lossy_flag = false rests on; cf. RFC 0005 §3.3).

Empirical probing relocated the bug from where #130 placed it: the
encoder (with-serde via serde_json) already emits shortest
round-trip digits — for the repro value -1.5374084650425255e99 the
emitted bytes parse back exactly with std's parser. The lossy half
is serde_json's *default float parser*, which is approximate by
design; its `float_roundtrip` feature exists precisely to make
parsing correctly rounded. Enabling it makes the round-trip
bit-exact for every finite f64 (verified over 200k random bit
patterns: 0 drift), so no hand-rolled encoder is needed and the
encode path — and every stored byte shape — is untouched.

- ourios-core: serde_json += `float_roundtrip`, documented as
  load-bearing, not an optimisation preference
- regression test pinning the #130 repro value and -0.0 sign-bit
  preservation, bit-for-bit via to_bits
- proptest property: arbitrary finite f64 (raw u64 bit patterns,
  subnormals and -0.0 included) round-trips bit-exactly at top
  level and nested inside array / kvlist
- pins the (empirically captured) non-finite encoding
  {"doubleValue":null} — NaN/±Inf bytes that do not decode back, a
  known pre-existing gap left out of scope
- RFC 0001 §6.1: dated amendment + a `double` bullet in the rule;
  the existing byte-shape test passes unchanged (encoder bytes are
  identical before and after)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fd186103-5598-4e09-83e6-c4a92ec41d8b

📥 Commits

Reviewing files that changed from the base of the PR and between 3261baf and 3e49759.

📒 Files selected for processing (1)
  • crates/ourios-core/src/otlp.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/ourios-core/src/otlp.rs

📝 Walkthrough

Walkthrough

The PR establishes a bit-exact round-trip guarantee for finite f64 values in Ourios-canonical JSON by enabling serde_json's float_roundtrip feature, amending RFC0001 with the canonical encoding contract, standardizing terminology from OTLP-canonical to Ourios-canonical throughout, and adding comprehensive regression tests covering finite and non-finite double behavior.

Changes

Canonical f64 round-trip guarantee

Layer / File(s) Summary
RFC canonical encoding amendment
docs/rfcs/0001-template-miner.md
RFC0001 updated with 2026-06-11 amendment: finite f64 values must round-trip bit-exactly; non-finite doubles (NaN, ±∞) encode as null and are out-of-scope for the guarantee; canonical encoding uses shortest-round-trip JSON formatting with correctly-rounded float parsing on decode.
Enable float_roundtrip feature
crates/ourios-core/Cargo.toml
Enables serde_json's float_roundtrip feature for correctly-rounded float parsing on decode and adds proptest = "1" to dev-dependencies for property-based round-trip validation.
Standardize terminology and documentation
crates/ourios-core/src/otlp.rs
Renamed OTLP-canonical to Ourios-canonical throughout docs and error messages; rewrote canonical encoding blocks with explicit f64 exactness section documenting bit-exact round-tripping for finite doubles and null-encoding for non-finite values; updated serializer docs to reflect the new f64 contract.
Float round-trip regression tests
crates/ourios-core/src/otlp.rs
Added double_av helper, deterministic tests for specific finite doubles (including ±0.0), pinned tests confirming non-finite doubles encode to {"doubleValue":null} and fail decode, and proptest validating arbitrary finite doubles round-trip exactly when nested in arrays/kvlists.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

Possibly related PRs

  • jensholdgaard/ourios#167: Overlaps in canonical JSON tests asserting byte-exact encoding/round-tripping of doubles; this PR tightens decode-side float rounding via float_roundtrip.
  • jensholdgaard/ourios#166: Related RFC/document work on Ourios canonical body encoding; this PR strengthens that contract with bit-exact finite f64 requirements and tests.
  • jensholdgaard/ourios#62: Introduced otlp::canonical helpers that these float-canonicalization rules and regressions apply to.

Poem

🐇 A hop through floats so fine,
Bits exact, now they align,
From NaN to zero's twin,
Round-trips guaranteed to win,
Ourios canonical—divine! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and specifically describes the main change: making canonical-encoding f64 round-trip bit-exactly, with reference to the related issue #130.
Description check ✅ Passed The description is comprehensive, covering all required sections: a detailed What/diagnosis, specific Changes across all modified files, verification steps, and RFC updates. All checklist items are addressed.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/130-f64-canonical-roundtrip

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@jensholdgaard
jensholdgaard requested a review from Copilot June 11, 2026 15:49
@jensholdgaard

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR strengthens Ourios’s “canonical body encoding” invariants by making finite f64 values round-trip bit-exactly through the canonical JSON encode/decode boundary (local half of #130). It does so by enabling serde_json’s correctly-rounded float parsing (float_roundtrip) while keeping the emitted byte shape unchanged, and it adds regression/property tests plus RFC/docs clarifications (including pinning the current non-finite f64null encoding behavior).

Changes:

  • Enable serde_json’s float_roundtrip feature in ourios-core (documented as load-bearing for RFC 0001 §6.1 faithfulness).
  • Update canonical encoding docs and RFC 0001 §6.1 to explicitly describe shortest-round-trip float emission + correctly-rounded parsing, and to note the known non-finite gap.
  • Add targeted regression tests and a proptest property to validate bit-exact finite-f64 round-trips, plus a test pinning the non-finite encode byte shape.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.

File Description
docs/rfcs/0001-template-miner.md RFC amendment documenting exact finite-f64 round-trip requirements and the non-finite null gap.
crates/ourios-core/src/otlp.rs Docs updated for f64 round-trip behavior; adds regression + property tests for bit-exact finite doubles and pins non-finite encoding.
crates/ourios-core/Cargo.toml Enables serde_json float_roundtrip; adds proptest dev-dependency with rationale.
Cargo.lock Locks new dev dependency (proptest).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/ourios-core/src/otlp.rs
Comment thread crates/ourios-core/src/otlp.rs
…ror text

The non-finite test now pins both halves of the gap (null shape emitted
AND decode rejects). Error Display + doc mentions say Ourios-canonical,
matching the post-#166 naming.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

crates/ourios-core/src/otlp.rs:120

  • The Body::Structured doc comment says the current miner still stores a Debug rendering of AnyValue as a placeholder, but ourios-miner::cluster::ingest_structured now canonical-encodes the AnyValue via ourios_core::otlp::canonical::encode_any_value and stores those JSON bytes. This doc is now incorrect and could mislead implementers of the exporter/reconstruction contract.
    /// The *current* miner implementation (in
    /// `ourios-miner::cluster::ingest_structured`) writes the
    /// `Debug` rendering of the decoded `AnyValue` (`format!(
    /// "{any_value:?}")`) as an interim placeholder — the
    /// canonicalisation PR replaces it before any wire-export

Comment thread crates/ourios-core/Cargo.toml Outdated
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated 1 comment.

Comment thread crates/ourios-core/src/otlp.rs Outdated
…ld placeholder

The Debug-rendering interim-placeholder claim and the deferred-to-
storage-layer claim were both stale (false post-#163/#166/#174);
reviewer-flagged, so fixed here rather than the planned follow-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.

Comment thread crates/ourios-core/src/otlp.rs Outdated
Comment thread crates/ourios-core/src/otlp.rs
…ology

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated no new comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants