The page Google Play is given, built in runicgateway.com#45, with the org lead's two answers (D85 links, D86 cannot-sign-in goes to the operator). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
395 lines
27 KiB
Markdown
395 lines
27 KiB
Markdown
# Account deletion — a member deletes their own account, on the website and in the app
|
|
|
|
**Status:** plan of record, 2026-10-29, with every question answered (AD4-AD13). **§6 step 2
|
|
(core) built 2026-10-29** and merged as website#232 (`7bd93b8`), `MODULE_API` 1.20.0. **Step 3
|
|
(modules and kit) built 2026-10-30**; steps 4-6 are next. Each phase in §6 stops for the org lead before the next one starts.
|
|
|
|
**Why now.** Google Play's Data safety form asks whether the app lets people create an account, and
|
|
an app that does must let them delete it, both **in the app** and at a **web address** given to Play
|
|
(`android/PLAN.md` M8, step 2). The app starts accounts in two places: the sign-in screen's link to
|
|
the deployment's sign-up page, and in-app game-account creation where a game server offers it
|
|
(`PlayerShardApi.createAccount`). The platform had no self-service deletion: only an admin could
|
|
delete an account. The org lead chose to build it (AD1) and to do it **before** report, block and
|
|
hide (`USER_SAFETY.md`), so it takes the next `MODULE_API` minor.
|
|
|
|
## 1. What a member can do
|
|
|
|
| Where | What they see | What happens |
|
|
|---|---|---|
|
|
| Website, *Account* (`PlayerAccount.jsx`) | a *Delete my account* section at the bottom: what goes, what stays (§4.1), and a confirm step | the account is deleted at once (AD4), the member is signed out everywhere, and the site says so |
|
|
| App, *Account* (`AccountScreen.kt`) | the same section and the same confirm step | the same, and the app clears what it holds on the phone, as a sign-out does |
|
|
| runicgateway.com `/delete-account/` | how to do it on the website or in the app, what is deleted and what is kept, and who to ask when you cannot sign in | nothing; it is the page whose address Play is given (AD2) |
|
|
|
|
The confirm step asks for the account's password, and for a two-factor code when the account has
|
|
two-factor sign-in on. It also asks the member to type their username, so a mis-tap on a phone
|
|
cannot delete an account. Signing in with Google, Discord or Steam is a convenience on top of an
|
|
account that already has its own password (AD7), so the password is always the proof.
|
|
|
|
Every member can delete themself, staff included. The one refusal is the **last admin**, as the
|
|
admin route already refuses. Then a deployment would be left with no one who can run it.
|
|
|
|
## 2. Decisions (org lead, 2026-10-29)
|
|
|
|
- **AD1 — Build self-service deletion.** The org lead chose this over an explanation page alone,
|
|
and over removing the app's account-creation links. A member deletes their account on the
|
|
website and in the app, and that removes the account and its game links.
|
|
- **AD2 — runicgateway.com hosts the deletion page Play is given.** We publish the app, so
|
|
Play needs one address from us. Every deployment runs its own site, so that page explains the
|
|
steps rather than performing them.
|
|
- **AD3 — Before report, block and hide.** It takes `MODULE_API` **1.20.0**, and
|
|
`USER_SAFETY.md`'s `reportTargets` moves to 1.21.0.
|
|
|
|
The org lead's answers to the six questions in §5, the same day:
|
|
|
|
- **AD4 — Delete now** (Q1). There is no grace period. The typed username and the password are
|
|
the safeguard.
|
|
- **AD5 — What they wrote stays, attributed to "[deleted player]"** (Q2). For members, every
|
|
username snapshot of a deleted account reads "[deleted player]": their threads, posts and
|
|
uploads, and the records in gap 2. That label replaces today's "[deleted account]".
|
|
- **AD6 — Staff keep the name and the IP** (Q3). The snapshots and the activity log's IP
|
|
addresses stay in the database, and the staff views keep showing them, marked as a deleted
|
|
account, for moderation. So AD5 is a rule about what members see, not a change to the stored
|
|
rows.
|
|
- **AD7 — The password is the proof** (Q4). SSO is a convenience. A member must already have a
|
|
proper account before they can link a sign-in provider, so every account has its own password,
|
|
and the confirm step always asks for it. An account that somehow has no password is told to set
|
|
one first, on the same Account page.
|
|
- **AD8 — A UO game account stays on the shard** (Q5). Only the link goes. The shard's staff
|
|
handle the shard account like any other. There is no protocol change.
|
|
- **AD9 — A deleted staff account's username is never available again** (Q6). It can never be
|
|
registered, or taken by a username change. A player's username is free at once.
|
|
|
|
Asked after the plan was first written ("will deletion call the unlink function for that
|
|
player?"), the same day:
|
|
|
|
- **AD10 — Deletion unlinks through each module's own unlink, before the row goes.** A database
|
|
cascade is not an unlink. For UO, the link lives on the shard (gap 8), so dropping the site's
|
|
mirror would leave the shard attributing the game account to a user who no longer exists. The
|
|
hook is therefore `onUserDeleting` and runs before the delete. Each module calls the unlink it
|
|
already has, for every link the person holds, so the game sees exactly what a player's own
|
|
unlink produces.
|
|
- **AD11 — An unlink that cannot finish never holds up the deletion.**
|
|
- A shard that is offline (503) gets a pending unlink in the module, retried when the bridge
|
|
reconnects.
|
|
- A **protected staff account**, which the shard refuses to unlink (403), is logged and shown to
|
|
admins as *still linked on the shard*, for the shard's staff to clear by hand.
|
|
|
|
Asked while building core (§6 step 2), the same day:
|
|
|
|
- **AD12 — A wrong password or code is 400, not 401.** §4.1 first said 401. In the app, a 401 on a
|
|
signed-in call goes to `TokenAuthenticator`, which spends the refresh token and replays the request
|
|
with the same body: one typo would make two password attempts, both counted by the login backoff, and
|
|
a failed refresh would sign the member out mid-deletion. Every other self-service password check
|
|
already answers 400, so deletion does too, with `code: 'wrong_password'` or `'wrong_code'`.
|
|
- **AD13 — A deleted account drops off a Team's *Forum guests* list.** Its grant row stays (the id is
|
|
set NULL) and the staff grant ledger still shows it with the name, marked deleted. A grant is matched
|
|
by user id, so a deleted account cannot use it and is not a guest.
|
|
|
|
## 3. What exists today (read 2026-10-29, website `main` 222bcb0)
|
|
|
|
- **The admin route.** `DELETE /api/v1/admin/users/:id` refuses your own account and the last admin,
|
|
then `users.remove(id)`, a plain `DELETE`. Everything else is the schema's foreign keys.
|
|
- **What the foreign keys already do.** Of the 59 keys that reference `users(id)`:
|
|
- **CASCADE** removes what belongs to the account alone: linked sign-in identities, mobile
|
|
refresh tokens and SSO codes, revoked sessions, trusted devices, recovery codes, password
|
|
resets, email verifications, push devices, notification subscriptions and channel prefs,
|
|
in-app notifications, and the engagement outbox, cooldowns and digest state.
|
|
- **SET NULL** detaches what the member made from them: news posts, wiki edits, forum threads,
|
|
posts and uploads, reports filed, appeals, Team activity, event participation, engagement
|
|
send logs, and the activity log.
|
|
- Modules: Module-uo's `shard_account_links`, and Module-Rust's `rust_account_links`,
|
|
`rust_perm_group_members`, `rust_perm_grants`, `rust_perm_run_grants` and
|
|
`rust_permgroup_members`, all **CASCADE**. Module-Rust's grants and group memberships keyed
|
|
straight to a Steam id (`rust_perm_steam_grants`, `rust_permgroup_steam_members`) have no
|
|
site account behind them, and stay. Staff gave them to a player in the game, not to a member
|
|
of the site. **A cascade is not an unlink, though** (gap 8, AD10).
|
|
- **Sessions die at once.** `requireAuth` re-reads the user on every request and answers 401 when
|
|
the row is gone (`session.middleware.js`). So a deleted member's web cookie or mobile access
|
|
token stops working on its next use, without waiting for it to expire.
|
|
- **Rust permissions are withdrawn in the game by themselves.** `permSync.js` compares a digest of
|
|
the desired set with what each server last confirmed. When the grant rows cascade away, the digest
|
|
changes, and the next tick pushes the set with the withdrawn grants.
|
|
- **Teams follow on the next reconcile.** `team_members.user_id` is SET NULL. The roster belongs to
|
|
the module and re-syncs every 15 minutes (`TEAMS.md`).
|
|
|
|
**The gaps:**
|
|
1. There is no self-service route, and no screen on the web or in the app.
|
|
2. **Their username survives in snapshot columns.** The Teams tables store a username next to the
|
|
user id, deliberately, so a record still names someone after the account is gone.
|
|
`teamForum.model.js` shows "[deleted account]" only when the snapshot is empty too. There are
|
|
two kinds:
|
|
- **What they wrote:** `team_forum_threads.created_username`,
|
|
`team_forum_posts.author_username`, `team_forum_uploads.uploader_username`.
|
|
- **Records of what was done:**
|
|
- `content_reports.reporter_username` and `handled_username`
|
|
- `team_forum_moderation.actor_username`
|
|
- `team_forum_grants.username`, `granted_username` and `revoked_username`
|
|
- `team_leader_overrides.actor_username`
|
|
- `team_moderation_requests.requested_username` and `decided_username`
|
|
3. **The activity log keeps IP addresses.** Rows for the deleted member keep `ip`, and `detail`
|
|
can carry a username, with only `user_id` cleared. They stay, for staff (AD6).
|
|
4. **A module cannot learn that a user was deleted.** If a module keeps user data without a
|
|
foreign key, or has a side effect in the game, it has no hook. The Module API has
|
|
`onIdentityLinked`, but nothing for deletion.
|
|
5. **A game account the app created stays on the game server.** For UO the link cascades, but the
|
|
shard account and its characters remain, which is intended (AD8).
|
|
6. The demo's four accounts (`demo/mode.js`) must not be deletable.
|
|
7. **Nothing stops a deleted name being reused.** Registration and username changes only check
|
|
the live `users` table (AD9).
|
|
8. **A cascade skips the modules' unlink, and for UO that leaves the shard linked.**
|
|
- **UO:** `shard_account_links` is only a mirror. The shard keeps the site user id on the game
|
|
account (`INTEGRATION.md`: "permanently tagged with your `websiteUserId`"), and every
|
|
`mob.login` reports it as `webId`. That is why the staff unlink in `usersShard.controller.js`
|
|
calls the sidecar's `DELETE /link/{account}` first and drops the mirror after. A cascade
|
|
alone would leave the shard, and the sidecar's own link mirror, attributing the account to a
|
|
deleted user forever, with every login still carrying that `webId`.
|
|
- **Rust:** the site's `rust_account_links` is the only record; the plugin keeps no link state.
|
|
The cascade gets the same result as `links.unlinkOwned`, apart from its
|
|
`rust.account.unlink` log entry and its prompt Teams re-sync. Calling the real function keeps
|
|
both.
|
|
|
|
## 4. Design
|
|
|
|
### 4.1 Core (`website`)
|
|
|
|
- **One service, two routes.** A new `accountDeletion.deleteAccount(userId, { req, by })` holds
|
|
the whole procedure. The admin route's `users.remove(id)` call is replaced by it, so a deletion
|
|
by staff and a deletion by the member do the same thing.
|
|
- **The member's route.** `DELETE /api/v1/auth/me/account`, behind `requireAuth` and
|
|
`accountChangeLimiter`.
|
|
- Body: `{ username, password, code?, recoveryCode? }`.
|
|
- The typed `username` must match the account, ignoring case.
|
|
- The password is always checked (AD7).
|
|
- A two-factor code or a recovery code is checked when two-factor sign-in is on.
|
|
- Refusals, each with a `code`:
|
|
- 400 `username_mismatch` for a username that doesn't match
|
|
- 400 `wrong_password`, `code_required` or `wrong_code` (AD12: 400, never 401)
|
|
- 409 `password_required` for an account with no password: the screen points at the
|
|
Account page's set-a-password section, which already exists for that case
|
|
- 409 `last_admin` for the last admin
|
|
- the demo's own refusal for a demo account (`demo/mode.js`, the same lock other breaking
|
|
settings use)
|
|
- `#swagger.*` annotations; the spec is regenerated.
|
|
- **The procedure, in order:**
|
|
1. **Before the delete**, read what is needed afterwards: the email address (for the notice
|
|
below), the username and the role. Then run every module's `onUserDeleting` hook (below), in
|
|
turn, each in its own `try`. That is where game links are unlinked (AD10).
|
|
2. **In one transaction:**
|
|
- when the role is staff (anything but `player`), add the username to a new
|
|
`reserved_usernames` table (AD9)
|
|
- delete the `users` row, so the foreign keys do the rest. The snapshot columns, the
|
|
content and the activity log are **not** touched (AD5, AD6).
|
|
3. **After commit**, fire-and-forget, each in its own `try`:
|
|
- `teams.reconcile({ reason: 'account deleted' })`
|
|
- an activity-log entry, `user.delete` (by staff) or `user.self_delete`, naming the id and
|
|
never the username
|
|
- an email to the address read in step 1, when there was one and mail is configured:
|
|
"your account on <site> was deleted on <date>". This is the only notice a person gets
|
|
if someone else used their unlocked phone, and it says who to contact.
|
|
- **Nothing can stop a deletion once it starts.** A module hook that throws, or that takes too
|
|
long, is logged, and the deletion goes ahead, because Play requires the request to be
|
|
honoured. Core gives each hook a time limit (10 seconds) so a hung game server cannot hold a
|
|
member's request open; whatever the module had not finished is its pending work (AD11).
|
|
- **Who sees the name (AD5, AD6).** A snapshot whose id column is now NULL belongs to a deleted
|
|
account; nothing else clears those ids. Rendering follows the viewer:
|
|
- **Members**, Team leaders included, and signed-out visitors see `[deleted player]`. That
|
|
covers forum threads, posts and uploads, and any record a member can read. The constant in
|
|
`teamForum.model.js` and `contentReports.model.js` changes from `[deleted account]`, and the
|
|
"id NULL" case now wins over a present snapshot.
|
|
- **Staff** (admin, editor, moderator) see the stored username with a *deleted account*
|
|
marker, in the Reports queue, the forum's moderation views, the user and activity screens,
|
|
and Team moderation requests. The activity log keeps its IP addresses.
|
|
- The rule lives in one helper, `displayAuthor(row, viewer)`, which every model that renders a
|
|
snapshot uses, and its tests cover each table in gap 2.
|
|
- **`reserved_usernames`** (AD9) holds `username_norm` (the primary key, lowercased), `reason`
|
|
(`deleted_staff`) and `reserved_at`. Registration, an invite's acceptance, an admin creating an
|
|
account, and a username change all refuse a reserved name, with the same message as a taken
|
|
one. There is no release path, because the org lead said "completely unavailable". A player's
|
|
username is not added and is free at once.
|
|
- **`MODULE_API` 1.20.0: `api.onUserDeleting(async ({ userId, deletedBy }) => {})`**, where
|
|
`deletedBy` is `'self' | 'staff'`.
|
|
- It runs **before** the row goes, while the module can still read the person's links (AD10).
|
|
Its job is to undo in the game, and anywhere else the module holds state, what the site's
|
|
rows are about to stop saying: unlink each game link through the module's own unlink, and
|
|
clean up data kept without a foreign key.
|
|
- It cannot veto. Throwing, or running past core's time limit, is logged, and the deletion
|
|
goes ahead (AD11). A module that cannot finish keeps its own record of what is left, and
|
|
retries.
|
|
- It is the same staging and validation as `onIdentityLinking`.
|
|
- `MODULE_API.md` §2.4 and §2.5 are amended. The Integration Kit's `coreApi` check goes red
|
|
until it is re-pinned, which is how the kit is meant to notice.
|
|
- **What goes and what stays** is written once in `BACKEND_DESIGN.md`, and the screens and the
|
|
runicgateway.com page say the same in plain words:
|
|
- **Goes:** the account, password, email address, two-factor secret and recovery codes; every
|
|
session, trusted device and push registration; linked Google, Discord, OIDC and Steam sign-ins;
|
|
game-account links and, in Rust, the permissions and groups the site gave those Steam ids;
|
|
notification settings and the inbox.
|
|
- **Stays, shown to other members as "[deleted player]":** what you wrote where others read it
|
|
(AD5), and reports you filed and appeals you made, as moderation records.
|
|
- **Stays, and staff can still see who it was:** the same records with your username, and the
|
|
activity log with its IP addresses, for moderation (AD6). Kept until the operator removes
|
|
them.
|
|
- **Stays, detached:** event results (the score and the in-game name, no site account), the
|
|
delivery log's hashes and the suppression list (`collection.mjs` already says so).
|
|
- **If you were staff:** your username can never be used again (AD9).
|
|
- **Docs:** `BACKEND_DESIGN.md` (the route, the procedure, `reserved_usernames`, the
|
|
display rule, the list above), `TEAMS.md` (the forum's deleted-author rule), `MODULE_API.md`,
|
|
OpenAPI.
|
|
|
|
### 4.2 Website client
|
|
|
|
- **`PlayerAccount.jsx`** gets a *Delete my account* section, last on the page and visually set
|
|
apart. It holds the "what goes, what stays" text and a button that opens a confirm dialog with
|
|
the username, password and (if needed) code fields. *Delete* stays disabled until the username
|
|
matches.
|
|
- **On success**, the client clears its session state and lands on the home page with a notice:
|
|
"Your account has been deleted."
|
|
- **On the demo** the section shows, and its button is disabled with the demo's usual locked
|
|
wording, so a reviewer can see the feature exists.
|
|
|
|
### 4.3 Android app (`edge`)
|
|
|
|
- **`AccountScreen.kt`** gets a *Delete my account* row that opens a new screen with the same text
|
|
and fields, and `MeApi.deleteAccount(body)`.
|
|
- **On success** the app does what a sign-out does, without the logout call, because the server
|
|
has already dropped everything. It clears the tokens, the trusted-device token, the push
|
|
registration and the inbox snapshot, then returns to sign-in with "Your account has been
|
|
deleted."
|
|
- **On the demo** the server refuses, and `demoLockInterceptor` shows the usual locked message.
|
|
The row is still visible, which is what Play's reviewer needs to find.
|
|
|
|
### 4.4 Modules
|
|
|
|
- **Module-Rust (`edge`):**
|
|
- `onUserDeleting` calls `links.unlinkOwned(steamId, userId)` for each Steam id the person
|
|
holds, the same call the player's own unlink makes. That keeps its `rust.account.unlink`
|
|
entry (with `detail.reason: 'account deleted'`) and its Teams re-sync.
|
|
- It then marks the permission sync dirty, so withdrawn grants reach the game on the next tick
|
|
rather than waiting for the digest check.
|
|
- It never fails on the game: the link is the site's own record and the plugin keeps none, so
|
|
there is nothing pending.
|
|
- Core-ref pin.
|
|
- **Module-uo:**
|
|
- `onUserDeleting` calls the staff unlink's path for each game account in
|
|
`shard_account_links`: `uoLinkClient.unlinkAccount({ actor, account })`, where `actor` is
|
|
`web:account-deletion`, then `shardLinks.removeByAccount(account)` on success. That clears
|
|
the shard's `WebsiteUserId` tag, so attribution stops at once. The shard account and its
|
|
characters stay (AD8).
|
|
- **Shard offline** (503, or no sidecar configured): the account goes into a new
|
|
`shard_pending_unlinks` table (`account`, `queued_at`, `attempts`, `last_error`), and the
|
|
mirror row goes with the cascade. The module retries the queue at boot and whenever the
|
|
bridge reconnects, until the shard answers 200 or 404 (404 means the shard has nothing to
|
|
clear).
|
|
- **Protected staff account** (403): the shard refuses by design, so retrying will not help.
|
|
The account goes into the same table marked `refused`. Admin → Shard shows it as *still
|
|
linked on the shard* until shard staff clear it by hand, and the module drops the row once
|
|
the shard stops reporting that `webId`.
|
|
- Core-ref pin.
|
|
- **Integration Kit:** the re-pin, and a paragraph in the module chapter. `onUserDeleting` is
|
|
where a module unlinks anything a game holds for the person, through its own unlink, and cleans
|
|
up data kept with no foreign key. It can't stop the deletion, so a module that talks to a game
|
|
needs a retry for when the game is away.
|
|
|
|
### 4.5 runicgateway.com
|
|
|
|
- **`/delete-account/`**, the page Play is given (AD2). It covers:
|
|
- the app's name and ours
|
|
- the steps on the website and in the app
|
|
- what is deleted and what is kept, and for how long: the moderation records that stay, and
|
|
that other members see "[deleted player]"
|
|
- that we hold no account, because each community runs its own site
|
|
- what to do if you cannot sign in: ask that community's operator, who can delete the account
|
|
from the admin panel
|
|
- **The data, written once.** The page's facts become `deployment` rows in `collection.mjs`, so
|
|
`/privacy` and the page agree. `PLAY_DATA_SAFETY.md`'s account-deletion bullet then names the URL
|
|
and answers "yes" to "can users request deletion".
|
|
|
|
## 5. The questions, as they were asked and answered (org lead, 2026-10-29)
|
|
|
|
| | Asked, with the example given | Answer |
|
|
|---|---|---|
|
|
| Q1 | Delete now, or lock for 14 days and let a sign-in cancel it? (a player taps *Delete* at 2 a.m. after a bad raid) | **Delete now** (AD4) |
|
|
| Q2 | Keep what they wrote, anonymously, or delete it? (a forum guide with 40 replies) | **Keep it; every username snapshot reads "[deleted player]"** (AD5) |
|
|
| Q3 | Keep or clear the activity log's IPs and the moderation snapshots? (a banned player returns under a new name) | **Keep the name and the IP on the admin side, for moderation** (AD6) |
|
|
| Q4 | How does an account with no password confirm? (a Discord-only player) | **Every account already has a password; SSO is a convenience on top of a proper account** (AD7) |
|
|
| Q5 | What happens to a UO game account the app created? (`Thorne`, with three characters) | **Leave it on the shard** (AD8) |
|
|
| Q6 | Can a deleted username be taken at once? (a moderator `Ravenna` impersonated the next day) | **Staff names are completely unavailable** (AD9) |
|
|
|
|
## 6. Build order
|
|
|
|
1. **This plan** (docs), with the answers (AD4-AD9).
|
|
2. **Core** (`website`): §4.1 and §4.2, `MODULE_API` 1.20.0, tests (the service's procedure, the
|
|
route's refusals, the demo lock, a module hook that throws, `displayAuthor` per table, a
|
|
reserved staff name), OpenAPI, `BACKEND_DESIGN.md`, `TEAMS.md`, `MODULE_API.md`. One PR.
|
|
**Built 2026-10-29.** Beyond the list: the section is on the staff Account page
|
|
(`AccountAdmin.jsx`) as well as the player one, since staff delete themselves too; the staff views
|
|
(Reports queue, Teams forum log, moderation requests, leader overrides, the forum itself) mark a
|
|
deleted account's stored name "(deleted account)"; a `transaction()` helper in `utils/db.js`. Walked
|
|
locally against a throwaway database: refusals, deletion on the web and over a bearer with 2FA, the
|
|
other device's session dying on its next request, staff names refused at admin create and at a
|
|
rename, a player name free at once, the admin route, the last admin refused, and the web screens
|
|
for a player and an admin. The forum's "[deleted player]" rendering and the demo lock are covered
|
|
by tests only; the walk in step 6 sees them with a game module and the demo flag on.
|
|
3. **Module-Rust** (`edge`) and **Module-uo**: §4.4's hooks plus their core-ref pins, one PR
|
|
each. Module-uo's PR adds `shard_pending_unlinks`, the retry, and the *still linked on the
|
|
shard* list. **Integration Kit**: the re-pin and the paragraph, one PR.
|
|
**Built 2026-10-30**, all three pinned to `7bd93b8` with `coreApi` `^1.20.0`:
|
|
- **Module-Rust** (`edge`): `links.unlinkForDeletion`, as §4.4 says.
|
|
- **Module-uo**: `utils/accountDeletion.js`. Beyond §4.4:
|
|
- every account is queued *before* the first sidecar call, because one call can outlast the
|
|
hook's 10 s;
|
|
- the retry also runs on every live `server.hello` (the plugin reconnecting to a sidecar that
|
|
stayed up);
|
|
- a `pending` row clears on a live login without the `webId`, as a `refused` one does;
|
|
- *still linked on the shard* is `pendingUnlinks` on `GET /admin/uo-link/config` and a section
|
|
on Admin → Shard, so there is no new route.
|
|
|
|
The table is in [`modules/uo/SCHEMA.md`](../modules/uo/SCHEMA.md).
|
|
- **Integration Kit**: chapter 2 also gained the demo's two unlisted refusals (multipart and
|
|
`data:` URIs), which it had never taught.
|
|
- **Checked:** each repo's tests, and both modules' frozen-manifest job reproduced against
|
|
`7bd93b8`.
|
|
- **Not walked:** the live deletions are step 6.
|
|
4. **Android** (`edge`): §4.3. One PR.
|
|
**Built 2026-10-30.** *Delete my account* is the last thing on *My account*, under a red rule,
|
|
and opens its own screen with the website's words. Beyond §4.3:
|
|
- **The refusals are read from the body.** The call reads the raw response, as the
|
|
trusted-device cap does, so each `code` becomes its own sentence (the web's wording).
|
|
- **The trust token goes too.** A sign-out keeps it for the next sign-in. A deleted account
|
|
has no next sign-in, so the token is dropped.
|
|
- **The notice** is the shell's snackbar, the one the demo's refusals use. The app opens
|
|
sign-in with Home beneath it.
|
|
- **Not touched:** the opt-in to push, which belongs to the phone, not the account. It
|
|
survives, as it does a sign-out.
|
|
|
|
Checked on the `s22_ultra` emulator against a local core at `7bd93b8`, with two throwaway
|
|
players. A wrong password showed "Your password is incorrect." The server logged one 400
|
|
and no replayed call, so AD12 holds in the app. Each player was deleted, one with an
|
|
authenticator code. The app opened sign-in with the notice, and back led to a signed-out
|
|
Home. The demo refusal and the no-password and last-admin refusals are covered by tests
|
|
only; step 6 walks them.
|
|
5. **runicgateway.com**: §4.5, plus `PLAY_DATA_SAFETY.md` regenerated. One PR.
|
|
**Built 2026-10-30.** The facts are the `deploy-account-deletion` row in `collection.mjs`, in the
|
|
website's words, with how long each kept thing is kept, which Play asks and the screens don't
|
|
say. The row also lists the delivery log's hashes and the suppression list, which §4.1's list
|
|
has and the screens leave out. The app rows that a deletion touches say so, and the Data Safety
|
|
notes answer "yes" and name the URL. Two answers from the org lead (runicgateway.com `PLAN.md`):
|
|
- **D85:** the page is linked from the footer's legal bar, `/privacy` and `/app/`.
|
|
- **D86:** someone who cannot sign in is told to try "Forgot your password?" first, then their
|
|
community's operator. They are never pointed at us, and the page says we cannot do it.
|
|
6. **A walk**, using a throwaway account on each surface:
|
|
- delete on the website, and in the app on the emulator
|
|
- the other device's session dies on its next request
|
|
- a Rust-linked account's grants leave the rig on the next sync, with a
|
|
`rust.account.unlink` entry
|
|
- a UO-linked account (the local ServUO with the uo-link sidecar): the shard's link is cleared,
|
|
and the next login carries no `webId`. With the sidecar stopped, the unlink waits in
|
|
`shard_pending_unlinks` and clears when the sidecar comes back
|
|
- a forum post shows "[deleted player]" to a member and the old username, marked deleted, to
|
|
staff
|
|
- the deleted staff username is refused at registration and at a username change
|
|
- the demo refuses
|
|
- the last admin is refused
|
|
7. Then `USER_SAFETY.md` (report, block and hide, now 1.21.0), then `android/PLAN.md` M8's
|
|
cutover.
|