Skip to content

fix(doctor): notice receipts destroyed after run.finished was audited - #264

Open
ophiocus wants to merge 2 commits into
theam:mainfrom
ophiocus:fix/receipt-integrity-missing
Open

fix(doctor): notice receipts destroyed after run.finished was audited#264
ophiocus wants to merge 2 commits into
theam:mainfrom
ophiocus:fix/receipt-integrity-missing

Conversation

@ophiocus

@ophiocus ophiocus commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Closes #226 — credit to @Julian-Genuario for spotting the missing reverse question. Claimed in-thread with right of way offered; the courtesy window has passed, so here is the fix in the shape the claim promised.

The gap, closed

verifyStoredReceipts() proved every surviving receipt against the audit chain but never asked whether an audited run.finished digest still has a receipt behind it — so UPDATE runs SET receipt = null (or deleting the row) just shrank checked while ok stayed true, exactly as the issue demonstrated.

  • The reverse pass now runs over the audited digests: any id with an audited digest and no stored receipt lands in a new, distinct missingReceiptRunIds class — kept separate from invalidRunIds and unauditedRunIds so "receipt destroyed" and "never audited" remain distinguishable stories, per the issue's point that in current code this state can only be an anomaly.
  • The runIds scope is respected (asking about other runs stays clean), and the doctor's failure line now reports all three counts.

The regression bites

The test finishes a real run through /internal/runs/:id/result, confirms ok: true, then nulls the receipt — doctor goes red with missingReceiptRunIds: [run.id] — and finally deletes the run row entirely, which trips the same wire because the audit trail outlives it. The "absence must fail loudly" shape, same family as the truncation disclosure above it in the suite.

sandbox.test.ts: 40 tests, 0 failures; tsc clean.

ophiocus and others added 2 commits September 1, 2026 11:20
verifyStoredReceipts only walked runs that still carry a receipt, so
nulling one - or deleting the run row - silently shrank checked while ok
stayed true (theam#226). The reverse question is now asked: every audited
run.finished digest must still have a receipt behind it, reported as a
distinct missingReceiptRunIds class so receipt-destroyed and
never-audited stay distinguishable, with the runIds scope respected and
the doctor message extended. The regression finishes a real run, nulls
its receipt, and watches the doctor go red; deleting the row entirely
trips the same wire.

Closes theam#226

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

receipt_integrity never notices a receipt that disappears after run.finished was audited

1 participant