docs(account): record step 3 (module hooks, kit re-pin) as built #394

Merged
whitlocktech merged 1 commits from docs/account-deletion-modules into main 2026-10-29 23:46:38 -05:00
2 changed files with 50 additions and 3 deletions

View File

@@ -1,6 +1,6 @@
# module-uo — the tables it owns
The 27 tables `module-uo` creates and owns, and the reasoning behind their shapes. **26 `shard_*`
The 33 tables `module-uo` creates and owns, and the reasoning behind their shapes. **32 `shard_*`
plus `uo_link_config`**, all created by the idempotent `server/db/schema.sql` fragment core replays
after its own schema on every boot, and all dropped by `server/db/purge.sql` when an operator purges
the module's data.
@@ -263,6 +263,36 @@ same way — `uo-static-3922-h33-<sha8>.png`. A key the shard has no art for wri
an empty row would make it "held" and it would never be asked again, including after the operator
patches in the graphic that was missing.
## shard_pending_unlinks — what an account deletion left linked on the shard (MODULE_API 1.20.0)
When a website account is deleted, core asks the module first (`api.onUserDeleting`,
[`ACCOUNT_DELETION.md`](../../website/ACCOUNT_DELETION.md) §4.4). `shard_account_links` is only a
mirror: the shard keeps the site user id on the game account and reports it as `webId` on every
`mob.login`, so the module unlinks each game account on the shard, through the staff unlink's
`DELETE /link/{account}` (actor `web:account-deletion`), before the users row goes. This table is
what that unlink could not finish.
| Column | Meaning |
|---|---|
| `account` | PK, the game account |
| `web_user_id` | the deleted user's id, which the shard may still report |
| `state` | `pending` (the shard was away) or `refused` (a protected staff account, 403) |
| `queued_at`, `attempts`, `last_attempt_at`, `last_error` | the retry's own record |
Four rules that are load-bearing:
- **Every linked account is written here BEFORE the first call to the shard**, and dropped when the
shard answers 200 or 404 (404: nothing to clear). Core gives the hook 10 s and one sidecar call can
take 12, so a hook core stops waiting for still leaves everything it had not finished queued.
- **No foreign key, on purpose.** Every row outlives the user it names; that is the point of it.
The mirror row in `shard_account_links` goes with the users cascade either way.
- **`pending` is retried; `refused` never is.** The retry runs when the WebSocket opens (which is
also the boot path, since boot never calls the shard) and on every live `server.hello`. A refused
row waits for the shard's staff to clear the tag by hand; Admin → Shard lists both kinds as *Still
linked on the shard*, from `GET /admin/uo-link/config`'s `pendingUnlinks`.
- **A live `mob.login` on the account with no `webId`, or another one, clears the row**, whoever
cleared the tag. A replayed login never does: one from before the link carries no `webId` either.
## shard_clilocs / shard_cliloc_meta — UO's localization table (Protocol 3.0)
Items on the wire carry a `LabelNumber`, not a name. The bridge has always sent it —

View File

@@ -1,8 +1,8 @@
# 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** as website `feature/account-deletion`, `MODULE_API` 1.20.0; steps 3-6 are
next. Each phase in §6 stops for the org lead before the next one starts.
(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
@@ -333,6 +333,23 @@ Asked while building core (§6 step 2), the same day:
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.
5. **runicgateway.com**: §4.5, plus `PLAY_DATA_SAFETY.md` regenerated. One PR.
6. **A walk**, using a throwaway account on each surface: