You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Deletion is not supported anywhere in the W3DS sync path. A user can delete a post, a message, a chat or a binding, and that deletion reaches no other platform — and in most cases never leaves the platform's own database. A delete has to be passed down five hops: platform database → web3-adapter → eVault → AaaS → every other platform holding a copy. Every hop is either missing or actively does the wrong thing, so fixing any one of them changes nothing user-visible.
Scope of this issue is all five hops, at record level. Decommissioning an eVault or removing a registry entry (identity-level erasure) is out of scope and should be tracked separately.
1. A platform's local delete never reaches the adapter. Platform services delete through TypeORM's repository.delete(id) and repository.softDelete(id) — query-builder forms that do not fire entity subscribers. The web3adapter watchers only hook afterRemove, and no subscriber implements afterSoftRemove. Only 7 call sites in the whole monorepo use remove()/softRemove(); every other delete is invisible to sync from the moment it happens.
2. The adapter cannot express a delete.EVaultClient (infrastructure/web3-adapter/src/evault/evault.ts) exposes storeMetaEnvelope, updateMetaEnvelopeById, uploadFile, fetchMetaEnvelope and storeReference — there is no delete method. adapter.ts has no delete branch. So on the rare path where afterRemove does fire, handleChange maps the removed entity and upserts a snapshot of it into the eVault — the opposite of the intent, and worse than doing nothing. mappingDb.deleteMapping() exists but is never called, so the local↔global id mapping leaks for every deleted record.
3. eVault deletes emit no awareness packet.removeMetaEnvelope (graphql-server.ts:642) and legacy deleteMetaEnvelope (:1643) append an operation log but never call notifyAwareness. Every other mutation fans out (create :427, update :575, bulk create :804, binding documents :980/:1090, uploadFile, legacy store/update). A delete made directly against a vault — which is what the eID wallet does when unbinding a social or personal binding — is invisible to every platform.
4. Receivers ignore the operation type. The AaaS packet type already declares operation?: "create" | "update" | "delete" (services/awareness-service/api/src/types.ts:10), stores it on Packet, and passes it to subscribers (DeliveryEngine.ts:177). All 10 platform WebhookControllers contain zero references to operation; every inbound packet is treated as an upsert.
5. There is no vocabulary for "deleted". eVault deletion is a hard DETACH DELETE (db.service.ts:549) with no tombstone, and only 7 of 42 ontology schemas carry an isArchived flag. A receiver that wanted to honour a delete has nothing to write, and no way to distinguish "deleted" from "never seen" or "not authorised".
Adjacent gaps that fall out of the same missing path:
Orphaned blobs.uploadFile writes an object plus a File meta-envelope. Deleting the meta-envelope never calls storage.deleteObject — that is only used on the upload-rollback path (graphql-server.ts:1531), so the blob stays in object storage forever.
No cascade semantics. Deleting a chat, group or post leaves undefined what happens to the messages, comments, votes and signatures that reference it.
infrastructure/web3-adapter/src/evault/evault.ts — client with no delete method
infrastructure/web3-adapter/src/db/mapping.db.ts:140 — deleteMapping, zero callers
platforms/*/api/src/web3adapter/watchers/subscriber.ts — afterRemove only
services/awareness-service/api/src/types.ts:10, DeliveryEngine.ts:177 — operation already carried end to end
Acceptance Criteria
The eVault emits an awareness packet with operation: "delete" for removeMetaEnvelope and legacy deleteMetaEnvelope, matching how create and update already fan out.
EVaultClient gains a delete method, and the adapter has a delete path that calls it instead of upserting the removed entity.
A local delete on a platform reliably reaches the adapter — either by moving services onto remove()/softRemove(), adding afterSoftRemove hooks, or making deletes explicit in the service layer rather than relying on subscribers.
deleteMapping is called when a record is deleted, so the id mapping does not leak.
Platform webhook controllers branch on operation; a delete packet removes or archives the local row instead of upserting it.
Receiving a delete for an unknown record is a no-op, not an error — deletes may arrive before or without the corresponding create.
Deletes do not ping-pong: honouring an inbound delete must not emit an outbound delete back to the origin (the same guarantee the requestingPlatform check gives create/update).
A delete of a File meta-envelope also removes the backing object from storage, or an agreed GC path does.
Cascade behaviour is specified per ontology: deleting a parent (chat, group, post) states what happens to its children.
ACL is enforced on the propagated delete — a platform cannot cause a deletion it lacks the DELETE bit for.
Documented: how a platform should represent a deleted record, and what a consumer may assume from the absence of a record.
Design decisions to settle
Tombstone or hard delete. A tombstone makes propagation, "deleted vs never existed", and late-joining consumers straightforward, but retains a record of the deleted thing — which cuts against the erasure story. A hard delete plus a log-backed delete packet keeps nothing behind, but a consumer that was offline during the delete has no way to catch up. Shared with eVault: expose creation/update/deletion metadata on MetaEnvelope in GraphQL responses #1129; decide once for both.
Soft or hard on the receiving side. Should an inbound delete hard-delete the local row, or set isArchived? Only 7 of 42 ontologies have such a flag today, so either it is generalised or receivers hard-delete.
Desired Output (may vary)
A user who deletes something in one platform sees it disappear from the eVault and from every other platform that holds a copy — and a platform receiving a delete has a defined way to represent it. Deletion becomes a first-class operation in the sync path, not a gap that silently resurrects data.
Description
Deletion is not supported anywhere in the W3DS sync path. A user can delete a post, a message, a chat or a binding, and that deletion reaches no other platform — and in most cases never leaves the platform's own database. A delete has to be passed down five hops: platform database → web3-adapter → eVault → AaaS → every other platform holding a copy. Every hop is either missing or actively does the wrong thing, so fixing any one of them changes nothing user-visible.
Scope of this issue is all five hops, at record level. Decommissioning an eVault or removing a registry entry (identity-level erasure) is out of scope and should be tracked separately.
1. A platform's local delete never reaches the adapter. Platform services delete through TypeORM's
repository.delete(id)andrepository.softDelete(id)— query-builder forms that do not fire entity subscribers. The web3adapter watchers only hookafterRemove, and no subscriber implementsafterSoftRemove. Only 7 call sites in the whole monorepo useremove()/softRemove(); every other delete is invisible to sync from the moment it happens.2. The adapter cannot express a delete.
EVaultClient(infrastructure/web3-adapter/src/evault/evault.ts) exposesstoreMetaEnvelope,updateMetaEnvelopeById,uploadFile,fetchMetaEnvelopeandstoreReference— there is no delete method.adapter.tshas no delete branch. So on the rare path whereafterRemovedoes fire,handleChangemaps the removed entity and upserts a snapshot of it into the eVault — the opposite of the intent, and worse than doing nothing.mappingDb.deleteMapping()exists but is never called, so the local↔global id mapping leaks for every deleted record.3. eVault deletes emit no awareness packet.
removeMetaEnvelope(graphql-server.ts:642) and legacydeleteMetaEnvelope(:1643) append an operation log but never callnotifyAwareness. Every other mutation fans out (create:427, update:575, bulk create:804, binding documents:980/:1090,uploadFile, legacy store/update). A delete made directly against a vault — which is what the eID wallet does when unbinding a social or personal binding — is invisible to every platform.4. Receivers ignore the operation type. The AaaS packet type already declares
operation?: "create" | "update" | "delete"(services/awareness-service/api/src/types.ts:10), stores it onPacket, and passes it to subscribers (DeliveryEngine.ts:177). All 10 platformWebhookControllers contain zero references tooperation; every inbound packet is treated as an upsert.5. There is no vocabulary for "deleted". eVault deletion is a hard
DETACH DELETE(db.service.ts:549) with no tombstone, and only 7 of 42 ontology schemas carry anisArchivedflag. A receiver that wanted to honour a delete has nothing to write, and no way to distinguish "deleted" from "never seen" or "not authorised".Adjacent gaps that fall out of the same missing path:
uploadFilewrites an object plus aFilemeta-envelope. Deleting the meta-envelope never callsstorage.deleteObject— that is only used on the upload-rollback path (graphql-server.ts:1531), so the blob stays in object storage forever.Reference
MetaEnvelope) — the tombstone-vs-hard-delete decision below is shared with that issue and should be settled once for both.infrastructure/evault-core/src/core/protocol/graphql-server.ts:642,:1643— delete resolvers, nonotifyAwarenessinfrastructure/evault-core/src/core/db/db.service.ts:549—DETACH DELETEinfrastructure/web3-adapter/src/evault/evault.ts— client with no delete methodinfrastructure/web3-adapter/src/db/mapping.db.ts:140—deleteMapping, zero callersplatforms/*/api/src/web3adapter/watchers/subscriber.ts—afterRemoveonlyservices/awareness-service/api/src/types.ts:10,DeliveryEngine.ts:177—operationalready carried end to endAcceptance Criteria
operation: "delete"forremoveMetaEnvelopeand legacydeleteMetaEnvelope, matching how create and update already fan out.EVaultClientgains a delete method, and the adapter has a delete path that calls it instead of upserting the removed entity.remove()/softRemove(), addingafterSoftRemovehooks, or making deletes explicit in the service layer rather than relying on subscribers.deleteMappingis called when a record is deleted, so the id mapping does not leak.operation; adeletepacket removes or archives the local row instead of upserting it.requestingPlatformcheck gives create/update).Filemeta-envelope also removes the backing object from storage, or an agreed GC path does.DELETEbit for.Design decisions to settle
isArchived? Only 7 of 42 ontologies have such a flag today, so either it is generalised or receivers hard-delete.Desired Output (may vary)
A user who deletes something in one platform sees it disappear from the eVault and from every other platform that holds a copy — and a platform receiving a delete has a defined way to represent it. Deletion becomes a first-class operation in the sync path, not a gap that silently resurrects data.