feat: purge cached bytes when their upload is deleted - #154
Conversation
Deleting an upload now removes its cached plaintext from the handling instance's disk before the delete API returns. Drop becomes the purge primitive: it returns an actionable error, unlinks even unindexed files (failed-boot-scan leftovers), and a failed unlink fails the delete with the row intact — the API never reports a deletion while a cached copy remains readable. Both promote sites (read-through and upload seeding) re-verify the upload row after promoting and take the bytes back out if a concurrent delete removed it, closing the delete/late-promote race. Purged counter added to the cache stats line; the failed-unlink warning is rate-limited to once a minute per store. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Panel review — no material blockerReviewed head: Consensus: 6/6 substantive reviewers found no material merge blocker. The core delete/purge mechanics are sound for the current public-only cache:
Panel dissent / non-blocking tighteningA deterministic adversarial reproducer confirmed one narrower case: if a late promotion appears after the strict pre-purge, the row deletion commits, and the post-commit However, this is weaker than the PR body's literal claims that the API reports success only once plaintext is gone and that no cache entry outlives its row. Please either:
This becomes load-bearing before any private-content caching is considered. Separate inherited defectNot introduced by #154: Verification
No approval or merge action was taken; this is an advisory panel review. |
…al 404 path Panel follow-up: the post-commit purge and promote-side guard are race cleanup, not a disk-level guarantee — if that final unlink fails, the leftover bytes are unreachable (the row gate 404s before the cache is consulted) and fall to eviction. Say so in the handler comment and the deployment guide, and add a regression test that orphaned cache bytes behind a deleted row are never served. Durable purge retry arrives with the fleet purge log (V2-873). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Thanks — both follow-ups are handled in Non-blocking tightening → option 1 taken, with the option-2 linkage recorded:
Inherited Affected suites re-run green under |
What
The download cache holds decrypted plaintext on the instance's disk. Deleting an upload destroys the record (and, for private uploads, the DataMap — the only thing that makes the network copy recoverable), but until now a cached copy was only ever removed by later eviction. This PR makes the delete honest on the handling instance, at two strengths:
The cache remains public-only, so today this is hygiene plus the recorded shred foundation; it becomes load-bearing only if private-content caching is built (V2-873, deliberately not part of this PR).
How
Store.Drop(key) erroris now the purge primitive (the binding contract from the feat: download cache disk budget — LRU eviction sweeper with uploads-pause precedence #150 panel re-review): unlink-first inside the promotion critical section, returns the unlink error instead of silently retaining the entry, removes on-disk bytes even when the key is not indexed (a failed boot scan leaves files present but unindexed), and an absent file counts as success.DeleteUploadpurges before the row delete — a failed purge fails the request with the row intact — and purges again (best-effort, logged) after the row is gone, covering both key derivations (local DataMap and network address, which differ).purgedcounter in the cache stats line (deferred here from feat: download cache observability — lifecycle counters, stats emission, deployment guide #152) and the failed-unlink warning is rate-limited to once a minute per store (feat: download cache disk budget — LRU eviction sweeper with uploads-pause precedence #150 panel rider — it previously could fire per victim per sweep tick).purged.Tests
All
-race, full suite green:🤖 Generated with Claude Code