docs: account deletion = legal retention, not erasure
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 18s
CI / ui (pull_request) Successful in 1m5s
CI / conformance (pull_request) Successful in 8s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m43s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 18s
CI / ui (pull_request) Successful in 1m5s
CI / conformance (pull_request) Successful in 8s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m43s
FUNCTIONAL (+ru): the deletion user story — anonymised live surfaces ([Deleted]), freed sign-in methods, a two-year-retained dossier, the code/phrase step-up, game forfeit + all-robot drop, fresh account on reopen. ARCHITECTURE: the retention model (retained_identities journal on every detach, tombstone + [Deleted] sentinel, drop all-robot games, purpose=delete step-up, cold-load last-login, two-year TTL reaper).
This commit is contained in:
@@ -273,6 +273,25 @@ arrive from a platform rather than completing a mandatory registration).
|
||||
new address already confirmed by **another** account is refused **without disclosure**
|
||||
(a neutral "check the address or contact support") and **never merged** — the anti-
|
||||
enumeration check is only reachable by someone who controls the new mailbox.
|
||||
- **Deletion is legal retention, not erasure** (`internal/accountdelete`). The account row
|
||||
survives as a **tombstone** (`accounts.deleted_at`) — its chat/complaint foreign keys
|
||||
have no cascade, so a hard delete is impossible. `AnonymizeAndTombstone` **journals**
|
||||
every live identity into an append-only **`retained_identities`** log (the legal dossier)
|
||||
then removes it, freeing the `(kind, external_id)` for a new account to reuse; it scrubs
|
||||
the live `display_name` → **`[Deleted]`** (an unspoofable sentinel — the editable-name
|
||||
rule forbids brackets) while snapshotting the real name into `deleted_display_name`,
|
||||
anonymises the game-seat snapshots, and drops the account's own friendships / blocks /
|
||||
invitations / friend-codes / drafts / pending-codes. **Chat, feedback and complaints are
|
||||
kept.** The orchestration a layer up resigns the account's active games (so opponents are
|
||||
not stranded), **drops its all-robot games** (no human opponent; children cascade), and
|
||||
revokes its sessions. Every credential detachment — **unlink, email change and delete** —
|
||||
writes a `retained_identities` row (`reason`), so the dossier keeps the full timeline.
|
||||
Step-up is a mailed code (`purpose=delete`, no deeplink — a stray click must not delete;
|
||||
`ConfirmByToken` refuses a delete token) for an email account, else a typed phrase.
|
||||
`last_login_at` / `last_login_ip` are stamped on the cold-load profile fetch (throttled
|
||||
to once an hour). A **two-year TTL reaper** (from the event) purges the whole dossier —
|
||||
the journal, plus a deleted account's feedback thread and dossier PII — while the chat and
|
||||
the tombstone row stay.
|
||||
- **Merge** retires the account that owns the linked identity into the **current**
|
||||
account, in a single transaction (`internal/accountmerge`): statistics summed
|
||||
(counters incl. moves/hints added, max points kept, and the per-variant best moves
|
||||
|
||||
Reference in New Issue
Block a user