fix(doctor): notice receipts destroyed after run.finished was audited - #264
fix(doctor): notice receipts destroyed after run.finished was audited#264ophiocus wants to merge 4 commits into
Conversation
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>
a49e3e3 to
7e6549c
Compare
|
Reviewed this end to end and pushed nothing — I could not find anything that warranted a commit. Verified on I also went looking for the obvious way this fix could be wrong — a legitimate deletion path that would make Worth adding that this restores a written decision rather than inventing a rule. The test earns its place: it drives a real run through Three things I looked at and deliberately did not act on: The test leaves an audited-but-missing run in the shared org, and The failure message reads
The unbounded row loads are real but pre-existing and unchanged by this branch, so they are tracked separately in #295 rather than held against you here. CI is green across all six checks, |
The failure message divided by checked, which counts only surviving receipts - null the only receipt in an org and the operator reads "1 audited-but-missing receipts found across 0 runs." The denominator now includes the missing runs themselves: found across N audited runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Thank you — a review that verifies the bug on On your three deliberate non-actions:
And thank you for routing the pre-existing unbounded loads to #295 instead of holding them against this branch — and for #262 this morning. From my side this stays good to merge. |
|
Closing this one — #289 just landed and it deleted the code this PR fixes. It was a big restructure, 600 files. These are all gone from main now:
The bug was real, though — I checked it on main before the merge. Forward-only verification can't see deletion, so nulling a receipt kept If you want to try it again on the new code, the closest equivalents look like Up to you. Thanks for the original catch, and to @Julian-Genuario for spotting it in #226. |
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 auditedrun.finisheddigest still has a receipt behind it — soUPDATE runs SET receipt = null(or deleting the row) just shrankcheckedwhileokstayedtrue, exactly as the issue demonstrated.missingReceiptRunIdsclass — kept separate frominvalidRunIdsandunauditedRunIdsso "receipt destroyed" and "never audited" remain distinguishable stories, per the issue's point that in current code this state can only be an anomaly.runIdsscope 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, confirmsok: true, then nulls the receipt — doctor goes red withmissingReceiptRunIds: [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.