fix(platform): re-check legal holds per erasure pass and fix the receipt - #3178
Open
Israeltheminer wants to merge 1 commit into
Open
fix(platform): re-check legal holds per erasure pass and fix the receipt#3178Israeltheminer wants to merge 1 commit into
Israeltheminer wants to merge 1 commit into
Conversation
Two ways an erasure receipt could claim more than happened. The legal hold was read once, before the cascade, and never again. The passes are not one transaction, and two of them fan out per-thread lineage purges and per-document blob and corpus deletes, so a hold placed mid-cascade was ignored for everything after it. 0.4 re-read holds inside all 19 of its arms and its helper names the reason: FRCP 37(e) spoliation. The re-check now runs before every pass, and an unreadable hold table skips the pass rather than running it — a table you cannot read is not evidence that nothing is held. A held-off pass now lands the receipt 'partial' and says which, tracked separately from a failed one so an operator can tell a hold from a fault. Before, only a thrown pass did, so a hold that stopped the cascade halfway still reported 'done' — and that receipt is the subject's Art 19 confirmation. The Full breakdown panel also rendered blank for every recent request: a pass records a plain count, and the renderer skipped anything that was not an object, so every entry was dropped before the empty-category branch. It reads both shapes now, because receipts written earlier still carry the older one. Both rules are extracted so they are testable at all — the status decision out of the cascade, the entry fold out of the drawer. Refs #3142.
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.
Two ways an erasure receipt could claim more than happened, and the panel meant to show the detail rendered blank.
Replaces #3144, which was stacked on #3122's pre-merge branch. Refs #3142.
Why
The legal hold was read once and never again.
processErasuregates on it before the cascade, then runs the passes as separate statements — only the finalize is in a transaction. Two of them fan out per-thread lineage purges and per-document blob and corpus deletes, so the window is real, and a hold placed during it was ignored for every remaining pass.0.4 re-read holds inside all 19 of its arms, and the helper's docstring names why: "a hold placed AFTER scheduling and BEFORE the per-table mutation runs must still win… a custodian-hold race could let GDPR erasure delete data the hold was meant to preserve — FRCP 37(e) spoliation."
A hold that stopped the cascade halfway still reported
done. Only a thrown pass setpartial. Under Art 19 that receipt is the subject's confirmation, sodonehas to mean every category was reached.The Full breakdown panel rendered blank for every recent request. A pass records a plain count; the renderer did
if (typeof value !== 'object' || value === null) continue;, so every entry was dropped before the empty-category branch. The result was an empty list with not even the "no data in N other categories" line. The headline counts still rendered, so it looked populated.What changed
The hold is re-read before each pass. An unreadable hold table skips the pass rather than running it.
A held-off pass is tracked separately from a failed one. Both make the receipt
partial, and the error line names which, so an operator can tell a hold from a fault.The breakdown fold reads both shapes — receipts written before the backend change still carry the object form, so dropping it would break the panel in the other direction.
Both rules are extracted so they are testable at all: the status decision out of the cascade, the entry fold out of the drawer. The bug was in pure logic buried in a component.
What is not covered
The per-pass re-check itself has no automated test. Proving it needs a hold to appear between two passes, and every way I could arrange that was timing-dependent — a flaky test on a spoliation guard is worse than none. What is pinned instead is the rule the re-check feeds: a held-off pass makes the receipt
partialand says so. If someone reverts the re-check, that test stays green. Naming the gap rather than implying coverage.Tests
12 assertions across the two extracted units.
The fold mutation is the original bug, so seeing that assertion red is seeing the defect reproduced.
Re-verified after #3122 and #3143 merged: both mutations still turn red on the merged base.
Gate:
typecheck,oxlint --type-aware,oxfmt --check,lint:sastgreen; branched fromorigin/mainat899fcc08a.