feat(account): deletion = legal retention, not erasure (PR3) #164
Reference in New Issue
Block a user
Delete Branch "feature/email-relay-pr3"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
PR3 — Account deletion = legal retention, not erasure
Deletion deactivates + anonymises live surfaces + retains a legal dossier — a legal
obligation (illegal content / law-enforcement) that overrides GDPR-style erasure. All
decisions owner-locked (2026-07-03).
Model (owner Q1=A, Q2=B)
retained_identities(append-only): every credential detachment —unlink, email change, delete — writes a row (
reason) before the live identity isremoved, so the dossier keeps the full timeline while the
(kind, external_id)frees fora new account to reuse. No soft-delete → no auth footgun.
internal/accountdelete.AnonymizeAndTombstone): journal + remove thecredentials → tombstone (
accounts.deleted_at) → scrub livedisplay_name→[Deleted](unspoofable — the editable-name rule forbids brackets) with the real namekept in
deleted_display_name→ anonymise seat snapshots → drop the account's ownfriendships/blocks/invitations/friend-codes/drafts/pending-codes. Chat, feedback and
complaints are kept (the tombstone keeps their no-cascade FKs valid).
(no human opponent; children cascade) → tombstone → revoke sessions.
purpose=delete, no deeplink — a stray click must not delete)for an email account; else a typed DELETE phrase (anti-impulse).
accounts.last_login_at/last_login_ipstamped on the cold-load profilefetch, throttled to once an hour.
and a deleted account's feedback thread + dossier PII; chat and the tombstone stay.
Surface
retained_identities+ 4 nullableaccountscolumns) — jetregen (accounts + retained_identities only). No contour wipe (expand-contract).
/user/delete/{request,confirm}; gatewayaccount.delete.{request,confirm}opsAccountDeleteConfirm/AccountDeleteRequestResult; branded ru/en delete email.AccountDeletedscreen("Учётная запись удалена") + Close (telegramClose/vkClose; web = none); client/transport/
mock/codec; ru/en i18n.
journal) + an operator Delete-user action (same orchestration).
Tests
(journal + tombstone + credential reuse), all-robot drop vs human keep, step-up (code +
no-email), dossier readers, TTL reaper boundary + feedback/PII purge.
Locally verified: Go build/vet/gofmt/unit, gateway, full integration package, codegen
idempotent, UI check/test/build, e2e (social/smoke/onboarding). PR4 (Grafana + admin
alerts) follows.
Step-up: email accounts confirm with a mailed code (purpose=delete, no deeplink — ConfirmByToken refuses a delete token so a stray click can't delete); platform-only accounts type a fixed phrase (anti-impulse). Endpoints /user/delete/{request,confirm}; the confirm orchestration resigns active games, drops all-robot games, tombstones + anonymizes the account (freeing its creds), and revokes its sessions — the tombstone is the point of no return, the rest best-effort. Gateway account.delete.{request,confirm} ops + fbs AccountDeleteConfirm/AccountDeleteRequestResult + branded ru/en delete email. Integration tests cover the step-up (code + no-email) and the orchestration pieces.Profile gains a Delete-account control (durable accounts) opening a step-up dialog: a mailed code for an email account, or the typed DELETE phrase for a platform-only one. On success the app swaps to a terminal AccountDeleted screen ('Учётная запись удалена') with a Close that closes the host Mini App (telegramClose / vkClose; web = no close). Wires deleteRequest/deleteConfirm through client/transport/mock/codec; ru/en i18n; codec wire test + Chromium/WebKit e2e.