fix(receipts): tolerate legacy float attention_score so a stale receipt cannot brick boot (#350)#369
Open
gnanirahulnutakki wants to merge 1 commit into
Open
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
…pt cannot brick boot (#350) `CostTuple.attention_score` drifted from an `f64` share (`0.0..=1.0`) to a milli-attention `u64`, but nothing migrated receipts written by older builds. Such a receipt persists the field as a JSON float; the plain derived `Deserialize` rejects it, and boot-time receipt-chain reconciliation (`decode_body` -> `serde_json`) aborts the whole load. A single old-format line therefore locked users out of both `ardur chat` and `ardur-server` for that data dir, with no migration and no recovery path. Add a migration-tolerant field deserializer: an integer passes through as milli-attention; a legacy `0.0..=1.0` float is mapped losslessly onto `0..=1000` (`share * MILLI_ATTENTION_PER_UNIT`, rounded); negative / non-finite still errors, so tolerance is scoped to the legacy shape. The migration is representational only — a receipt's JWS signature and `parent_hash` linkage are verified over its original on-disk bytes, never over the decoded value, so no trust check is weakened. No non-JSON (bincode/postcard) serializer exists in-tree, so `deserialize_any` is safe. Tests: core-types unit coverage for the float->milli mapping, integer pass-through, and rejection of malformed values; a new fused-runtime boot test seeds a genuinely ES256-signed legacy float receipt and asserts the real `.build()` reconciliation path succeeds, the value migrates to 500, and `verify_persisted_chain_with_jwks` still authenticates. Checkpoint: architect/sessions/issue-350-receipt-legacy-float/journal.md Signed-off-by: GR <gnanirn@gmail.com>
gnanirahulnutakki
force-pushed
the
fix/350-receipt-legacy-float-2026-07-22
branch
from
July 23, 2026 18:29
6c8a5cd to
11067cb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #350.
Root cause (verified against a real receipt)
CostTuple.attention_scoredrifted from anf64share (0.0..=1.0) to a milli-attentionu64(crates/core-types/src/cost.rs), but nothing migrated receipts written by older builds. A real receipt on disk (~/.ardur/receipts/chain.jsonl) base64url-decodes to:attention_scoreis a JSON float. The plain derivedDeserializeon au64field rejects it, sodecode_body(crates/fused-runtime/src/receipts.rs:145) fails withpayload json: invalid type: floating point 0.0, expected u64.parse_receipt_linesonly tolerates a torn trailing line, so a well-formed old-format line aborts the entire chain load during boot reconciliation (builder.rs::build→load_persisted_chain→verify_persisted_chain_with_jwks). A single legacy receipt therefore bricks bothardur chatandardur-serverfor that data dir, with no migration and no recovery path (ardur doctorstill reportsokbecause it does not reconcile receipts).Fix
Field-level migration-tolerant deserializer for
attention_score(#[serde(deserialize_with = ...)]):0.0..=1.0share) is mapped losslessly onto0..=1000(share * MILLI_ATTENTION_PER_UNIT, rounded) — the mapping the module docs already document;The migration is representational only. A receipt's JWS signature and each
parent_hashare computed over the original compact-JWS bytes, never over the decoded value, so coercing the float changes no signature or hash-chain outcome — the trust checks are preserved, not weakened. No non-JSON (bincode/postcard/ciborium) serializer exists in-tree, sodeserialize_anyis safe.This deliberately does not add the broader "quarantine a mid-chain corrupt line and continue" path the issue also mentions: skipping a mid-chain line breaks
parent_hashlinkage and needs its own design (noted as follow-up). This change removes the actual brick — the legacy float — without that risk.Tests
crates/core-types:0.0/0.5/1.0→0/500/1000, integer pass-through, negative rejected.crates/fused-runtime/tests/legacy_receipt_migration.rs(new): seeds a genuinely ES256-signed legacy float receipt (signed by the runtime's own key via PKCS#8 re-import) and drives the real.build()boot path — asserts boot succeeds, the value migrates to500, andverify_persisted_chain_with_jwksstill authenticates over the original bytes.Verification
"attention_score":0.0(float).ardur-core-types,ardur-receipt,ardur-cost-gate,ardur-fused-runtime, andcargo test -p ardur-e2e-tests.cargo fmt --checkclean;cargo clippy(core-types + fused-runtime, all targets) clean.No
Cargo.lockchange is required (p256, added as a fused-runtime dev-dependency for the test, is already locked viaardur-receipt).