Verifiable asset deletion: listRoomAssets, deleteReferencesNow, plugin deleteNow - #57
Open
aldrinjenson wants to merge 1 commit into
Open
Verifiable asset deletion: listRoomAssets, deleteReferencesNow, plugin deleteNow#57aldrinjenson wants to merge 1 commit into
aldrinjenson wants to merge 1 commit into
Conversation
…lugin deleteNow
deleteReferences is tuned for compaction: plugin deletes are scheduled without
being awaited and the rows are removed regardless. That is the right trade-off
there, but it means a failed object delete leaves data behind with nothing
pointing at it, which makes permanent deletion unverifiable.
retrieveDoc({references: true}) has a related gap: a reference is only reported
once the plugin retrieve for it succeeded, so a temporarily unreadable object
hides its row -- and with it the assetId needed to ever delete that object.
Adds three additive pieces, leaving all existing behaviour untouched:
- persistence.listRoomAssets(room) decodes every asset from the row columns
without calling the plugins, so the list cannot be short.
- persistence.deleteReferencesNow(references) deletes objects first, awaited,
and removes rows only once all are confirmed gone, rejecting otherwise.
- PersistencePlugin.deleteNow is an optional immediate, awaited delete,
implemented for the S3 plugin. delete is unchanged.
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.
Adds three additive pieces that make it possible to delete a document's persisted data and prove it is gone. No existing behaviour changes — 234 insertions, 0 deletions.
Why
deleteReferencesis tuned for its caller, compaction (store(new row)→deleteReferences(old rows)), and its current semantics are right there: scheduling the plugin deletes without awaiting them keeps a slow object store off the compaction critical path, the 10s delay protects readers still holding the previoust, and always removing the rows avoids leaking them on the hot path.But that combination means a failed object delete removes the only rows naming the object. Since
store()offloads unconditionally forbranch === 'main', effectively every byte lives in object storage and the row holds a stub — so deleting rows deletes pointers, not data. For a data-retention requirement, "deleted" has to mean the objects are provably gone.There's a related gap in enumeration:
retrieveDoc(room, { references: true })only reports a reference once the plugin retrieve for it succeeded (retrieved && references?.push(...)), so a temporarily unreadable object hides its row — and with it theassetIdneeded to ever delete that object. A transient storage error strands a row permanently. That's a sensible degradation for reads, but it silently under-reports for deletion or inventory.What's here
persistence.listRoomAssets(room)— every asset for a room, decoded from the row columns, no plugin calls. Complete by construction, so deletion can be verified and retried.persistence.deleteReferencesNow(references)— objects first and awaited, rows only once all are confirmed gone; rejects otherwise.deleteReferenceskeeps its exact current behaviour.PersistencePlugin.deleteNow(assetId, assetInfo)— optional immediate, awaited delete. Implemented for the S3 plugin;deleteuntouched. Optional on the interface, so existing plugins keep working.Composed:
Notes
deleteReferencesNowfails closed: it rejects if no plugin claims an offloaded asset, rather than skipping it. Plugins other than S3 need adeleteNowbefore they can be used with it — happy to add Azure/GCS if you'd like them in scope.API.md, including a callout that deleting rows doesn't stop a document coming back (store()is an insert, so a later compaction of stream residue re-creates it) and that writers should be disconnected first —recheckAuth(room, { forceDisconnect: true })covers the client half nicely.tests/storage.tests.js, following the existing conventions there.Testing
npm run lint(standard + tsc) passes. I could not run the integration suite locally — it needs Valkey/Postgres/MinIO viacompose.yamland I don't have Docker on this machine — so the two new tests are unverified against real infra; please run them. I did exercise both new methods against stubbedsqland plugins to check the decode path, the ordering (objects before rows), that rows survive a rejectingdeleteNow, and that an unclaimed asset rejects rather than being skipped.Happy to adjust naming, split this up, or fold it into a higher-level
deleteDoc(room)if you'd prefer that shape — see the linked issue for the fuller picture.