From ae3750043a7a682cc93916feeda1f68046438ac6 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Thu, 29 Oct 2026 23:00:32 -0500 Subject: [PATCH] docs(account): record step 3 (module hooks, kit re-pin) as built MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ACCOUNT_DELETION.md: status line, and §6 step 3 marked built with what went beyond §4.4 (Module-uo queues before calling, also retries on a live server.hello, clears pending rows on a webId-less login, lists them via GET /admin/uo-link/config). modules/uo/SCHEMA.md: the new shard_pending_unlinks table, and the table count corrected (it was already stale: 33 tables, 32 shard_* plus uo_link_config). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY --- modules/uo/SCHEMA.md | 32 +++++++++++++++++++++++++++++++- website/ACCOUNT_DELETION.md | 21 +++++++++++++++++++-- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/modules/uo/SCHEMA.md b/modules/uo/SCHEMA.md index 2cd7d37..bcfa486 100644 --- a/modules/uo/SCHEMA.md +++ b/modules/uo/SCHEMA.md @@ -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-.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 — diff --git a/website/ACCOUNT_DELETION.md b/website/ACCOUNT_DELETION.md index dc9be0a..e0d7030 100644 --- a/website/ACCOUNT_DELETION.md +++ b/website/ACCOUNT_DELETION.md @@ -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: