Erasure and restore-replay runbook
Covers executing a GDPR erasure (ADR-0014) and re-applying erasures after a backup restore. The erasure model is anonymization-first: value-bearing rows survive with their person linkage irreversibly severed; only pure-identity rows are deleted.
Executing an erasure
Section titled “Executing an erasure”Erasure is performed by executeErasure from @emergent/db (src/erasure/execute.ts),
which runs the full plan from buildErasurePlan() in one transaction:
- Collect identity bindings (including the active
supabase-authsubjects for post-transaction auth deletion), launch-scoped LRS credential ids, NRPS context snapshot ids, and audit targets (every(table_name, record_id)belonging to the subject) — before any mutation detaches them. - Delete via path:
submission_artifact(learner-created content) through the attempt join. - Scrub via path:
feedback_entrybodies on the subject’s attempts. - Sever: null
person_id(and scrub declared payload columns) on retained rows — memberships, attempts, sessions, telemetry, competency evidence, awards, package progress. - Scrub:
activity_event, NRPS member/context snapshots, and everychange_audit_entrycovering collected audit targets. - LRS: delete statements by IFI (actor / object / context-agent / context-group
relations), canonical agents, agent documents; revoke launch-scoped credentials.
IFI keys are derived automatically (
src/erasure/ifi.ts): the platform convention keyaccount:<EMERGENT_XAPI_ACTOR_HOME_PAGE>::<person id>(plus the built-in default home page), every actor recorded on the person’s launch credentials, and any caller-supplied extras for identifiers minted outside those paths. - Root delete: the
personrow — FK cascades remove profile, bindings, role grants, affiliations, launch sessions, snapshots; FK set-nulls clear actor columns. - Receipt: insert into
erasure_receipt—subject_scope_hashissha256(person uuid);affected_countsrecords per-table row counts. The receipt contains no personal data.
After the transaction, executeErasure returns two caller duties:
storageCleanup: thesubmission_artifactURIs and LRS attachment/agent-document storage references whose rows were just deleted. The operator (or wrapping endpoint) must purge these from object storage to complete the erasure.authSubjectsToDelete: the Supabase auth user ids that were actively bound to the person (identity_binding, providersupabase-auth), collected before the root delete. The operator (or wrapping endpoint) must delete each via the Supabase admin API (DELETE /admin/users/:id) — otherwise the subject’s email survives inauth.users. Expired bindings are deliberately excluded: their auth user may have since been re-bound to a different person.
Both are deliberately excluded from the receipt because storage paths and auth subjects can embed or resolve to personal identifiers.
Controller/processor note (ADR-0014): in org deployments the org is controller — requests route through it; for independent learners the platform is controller.
Restore-replay procedure
Section titled “Restore-replay procedure”Backups are handled “beyond use”: they expire on normal rotation and are never rewritten. The compliance obligation is that restores re-apply erasures:
-
Restore the backup into the target environment.
-
Re-apply all migrations newer than the backup.
-
Replay erasures executed after the backup’s snapshot time:
SELECT subject_scope_hash, legal_basisFROM erasure_receiptWHERE executed_at > :backup_snapshot_time;For each receipt, find the restored subject by hashing candidate person ids:
SELECT id FROM personWHERE encode(sha256(id::text::bytea), 'hex') = :subject_scope_hash;If a person row matches, run
executeErasureagain for that person id with the receipt’slegal_basisandrequestSource = 'restore-replay'. A non-match means the original erasure predates the data in this backup — nothing to do. -
Re-run the replay query until no receipts match restored persons.
-
Record the replay in the restore’s change log (date, backup id, receipts applied).
Because the live erasure_receipt table is part of every backup taken after an
erasure, receipts are themselves restored; replay only needs receipts issued between
the backup snapshot and the present, plus any receipts whose scope hash still matches
a person (defense in depth: replaying an already-applied receipt is a no-op).
Sync replicas
Section titled “Sync replicas”No device action is required (ADR-0014): severed rows stop matching person-scoped row filters and drop from learner replicas on next sync; severed rows are no longer owned, so stale client writes are rejected by RLS. A never-reconnecting device is a device-security concern, not an erasure concern.
Out of scope, by decision
Section titled “Out of scope, by decision”- Archival media are never rewritten (beyond-use doctrine; ICO/CNIL).
- Wallet/verifier copies of exported credentials: revocation/status-list update is the Art. 17(2) “reasonable step” (ADR-0015).
- Research Projections retain de-identified data under Art. 89 (ADR-0008).