Skip to content

Factor detail: JSON-normalize before hash/store; seal-check rescore lines - #107

Merged
AzielEliab merged 3 commits into
mainfrom
fix/factor-detail-normalize
Oct 11, 2026
Merged

AzielEliab merged 3 commits into
mainfrom
fix/factor-detail-normalize

Conversation

@AzielEliab

Copy link
Copy Markdown
Owner

Off main d7f6a1d (#106 follow-up).

  1. Normalize before hashing: factorDetail() now returns JSON.parse(JSON.stringify(raw)), so the hashed value and the stored triad_json bytes are identical. An undefined nested checker field can no longer cause a false 'not covered'.
  2. Rescore lines: each line is checked like SUBMIT. Its RESCORE ledger entry (matched by rescore_id) must match the verified checkpoint, and its factor_detail_sha256 must equal the sha256 of that rescore's stored factor_detail. The line then ends '(covered by seal)'. Otherwise it ends '(stored data, not covered)'. A SUBMIT hash never seals a rescore line. The JSON gains rescore_factor_changes[].id and .sealed.

No new D1 queries: the RESCORE rows already come back in the indexed receipt_id read. Hash-linked and sealed, not signed.

Tests: 304/304 (normalized round trip, rescore covered, rescore edited after the write, SUBMIT hash not sealing a rescore). Not merged, not deployed.

Copy link
Copy Markdown
Owner Author

Updated at 192a4b7 for AZBot's fail-open blocker:

  • Rescore before/after/delta values now come only from the hashed factor_detail objects (factors[k].value). The unhashed top-level triad_json.factors is ignored. If either side has no factor_detail, the line reads 'per-factor detail not stored for this receipt'.
  • A line is covered only if both its own RESCORE detail and the previous detail (SUBMIT, or the prior RESCORE by its rescore_id) are sealed.
  • New tests:
    • editing only triad_json.factors leaves the shown numbers unchanged;
    • edited factor_detail gives not covered;
    • an unsealed previous snapshot gives not covered even when the line's own detail is sealed;
    • both sealed gives covered.
  • 308/308 pass. Not merged, not deployed.

…mps; reasoning seal via SUBMIT content_sha256

Copy link
Copy Markdown
Owner Author

Updated at fd00762 for AZBot's ordering blocker:

  • Rescore lines are built by ledgerRescoreLines from this receipt's verified SUBMIT/RESCORE rows, in ledger sequence order. receipt_rescores is used only to look up stored detail by rescore_id, so created_utc and row order have no effect.
  • Each line pairs with the next-lower SUBMIT/RESCORE row for this receipt_id, so nothing can sit between them. A line is covered only if both rows are in the verified chain and both factor_detail_sha256 values match. A stored rescore with no ledger row is listed as not covered.
  • Labels now use the hashed ledger timestamp_utc and seq. The reason comes from the RESCORE payload.
  • Self-review found one more unhashed input shown above the seal: summary/explanation. They now carry 'Reasoning seal' plus reasoning_sealed, which is true only when the stored receipt row recomputes to content_sha256 in the verified SUBMIT row. I checked live receipt e8be822c: stored, SUBMIT and recomputed hashes all match (cfb7695f…).
  • Tests:
    • swapped created_utc keeps the original order with correct deltas;
    • reversed history order;
    • a missing stored middle row (it and the next line not covered);
    • a ghost row with no ledger entry;
    • an intervening RESCORE;
    • labels taken from ledger timestamps;
    • reasoning covered vs edited.
  • 307/307 pass. Not merged, not deployed.

@AzielEliab
AzielEliab merged commit 892cfcb into main Oct 11, 2026
2 checks passed
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.

1 participant