From cfd1800347eb400ae606ea5d9b609382cdb430c1 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Wed, 12 Aug 2026 18:03:34 -0500 Subject: [PATCH] docs(modules): record the cutover, and retire what it makes untrue The module system reached `main` on 2026-08-12 (website#150) and `edge` was deleted. Several present-tense claims across two plans became false at that merge; this is the pass that closes them. MODULE_SYSTEM.md's status banner was the loudest: it still announced "in implementation - Phase 2's core scaffolding is landing, PRs 1-7 of 9 done", three phases stale. It now says SHIPPED and gives the shape of what landed. The substantive addition is 2.9's "as landed" section: the four decisions that settled the cutover (none of them a code question), and what the merge does to a LIVE deployment - no DROP TABLE anywhere, one guarded and already-verified column drop in 228 changed files, no newly-required env var, and a declared module starting on the same first boot rather than waiting for an operator click. That paragraph is the one a future cutover will want, and none of it was written down anywhere before. Phase 5 is marked COMPLETE in the phase list (2.11.1 already recorded slice 3 closing it), decision 11 carries its landing date, and 2.11.1's "the pin is one of the things the cutover has to revisit" is resolved in place - the sha did not move, only the branch label, so the coreApi equality assertion stays armed. The Android note is the one that changes meaning rather than tense. It said the `gameAccountSignup` fix "must land before that cutover"; the org lead released that gate instead, so the note now records a LIVE regression - the shipped app hides game-account creation from every user - rather than a scheduling item. An accepted break and a forgotten one differ only by whether someone wrote it down. Co-Authored-By: Claude --- android/PLAN.md | 13 +++++++ website/MODULE_SYSTEM.md | 84 ++++++++++++++++++++++++++++++++++------ 2 files changed, 86 insertions(+), 11 deletions(-) diff --git a/android/PLAN.md b/android/PLAN.md index 3247618..483996e 100644 --- a/android/PLAN.md +++ b/android/PLAN.md @@ -110,6 +110,19 @@ spec-aligned (as recorded for M1); 17 new JVM unit tests cover the account + pla > Deferred rather than coordinated into the slice (org lead, 2026-08-11): the website work lands on > `edge` and reaches `main` as one cutover, so there is room. **It must land before that cutover** — > after it, a shipped app quietly loses a feature. +> +> **The gate was released and the cutover went first (org lead, 2026-08-12.)** The module-system +> cutover merged as [website#150](https://gitea.whitlocktech.com/RunicGateway/website/pulls/150) +> with this unfixed, deliberately: the app needs a broader pass than this one field, so holding a +> finished workstream for it bought delay and nothing else. +> +> **So this is no longer owed-but-harmless — it is live.** Against `main`, `/public/settings` no +> longer carries `gameAccountSignup`, `PublicDto.kt:80` reads its `false` default, and +> `CharactersViewModel.kt:72` hides game-account creation from every user of the shipped app. Nothing +> crashes and nothing logs; the button is simply not there. The fix is unchanged — one DTO field, one +> ViewModel line and two tests, reading the flag from `/public/shard/features`, which the app already +> calls — and it is now a **bug fix against a live regression** rather than a scheduling item, to be +> picked up with whatever else that broader app pass covers. ✅ **M5 — design pass** (2026-07-20, `RunicGateway/Android-app#10`): the shard-website theme applied across every screen, restyling the working M1–M4 UI with **no architecture, data-flow, endpoint, or DTO diff --git a/website/MODULE_SYSTEM.md b/website/MODULE_SYSTEM.md index 7460a49..45bbfc8 100644 --- a/website/MODULE_SYSTEM.md +++ b/website/MODULE_SYSTEM.md @@ -1,9 +1,17 @@ # The Module System — design of record -**Status:** approved design, **in implementation** — Phase 2's core scaffolding is landing on the -website `edge` branch, PRs 1–7 of 9 done (§2.7 tracks what each settled). Every decision in Part 3 -has been settled with the org lead; Part 1 records what was verified against the working trees on -2026-08-10, including the places the original draft was wrong. +**Status: SHIPPED.** All six phases are built and the `edge` → `main` cutover merged on +**2026-08-12** ([website#150](https://gitea.whitlocktech.com/RunicGateway/website/pulls/150)), so +core on `main` is game-agnostic: **166 public + 2 internal** routes with no game in them, and the 72 +UO routes arriving from [`RunicGateway/Module-uo`](https://gitea.whitlocktech.com/RunicGateway/Module-uo). +The contract is at `MODULE_API_VERSION` **1.5.0**. §2.7 tracks what each phase settled, §2.9 records +the cutover itself. + +This document stays in the present tense about the *design* and the past tense about the *build*. +Where it says "will", read it as the plan speaking; the "as landed" notes under each phase are what +actually happened, and several of them record the plan being wrong. Every decision in Part 3 was +settled with the org lead; Part 1 records what was verified against the working trees on 2026-08-10, +including the places the original draft was wrong. **The normative contract is [`MODULE_API.md`](MODULE_API.md)** (Phase 1). This document decides what the module system *is*; that one decides exactly what a module may call. Where the two differ, that @@ -1103,8 +1111,13 @@ either repo reached the branch. Both regression tests were shown to fail before **A known break, accepted:** the shipped Android app reads `gameAccountSignup` off `/public/settings` (`PublicDto.kt:80`). The field defaults to `false`, so nothing crashes — the app silently stops offering game-account creation until it reads the module's `/public/shard/features` -instead. Out of scope for this slice, recorded in the Android plan, and it lands well before this -workstream's cutover reaches `main`. +instead. Out of scope for this slice and recorded in the Android plan. + +*(Amended at the cutover, 2026-08-12: this said the fix "lands well before this workstream's cutover +reaches `main`", and it did not. The org lead released that gate rather than holding the cutover — +the app needs a broader pass than this one field, so waiting bought nothing but delay. The break is +therefore **live** from the cutover, not hypothetical, and the OWED note in `../android/PLAN.md` +carries it.)* ##### What the browser found this time @@ -1218,9 +1231,11 @@ Slice 1 moved the `UPDATE` to module-uo and left the `INSERT` behind. The two fi together — core's schema is replayed **in full** before any module fragment (§2.6) — so the marker existed before the guard ever read it and the one-shot could never fire. An install carrying a protocol-2 row would have stayed pinned at 2 against a v3 sidecar: every REST call 409, the WS closed -on `ws.hello`, which is precisely what the migration exists to prevent. Latent rather than live, and -only because `edge` has not cut over: it bites an install that first boots a post-slice-1 build while -already holding a `uo_link_config` row. +on `ws.hello`, which is precisely what the migration exists to prevent. Latent rather than live when +it was found, and only because `edge` had not cut over yet: it bites an install that first boots a +post-slice-1 build while already holding a `uo_link_config` row — which is exactly what every +existing instance did at the 2026-08-12 cutover, so the fix landing in this slice rather than after +it is the reason there was nothing to notice. Verified against a real MariaDB, all three states: a fresh database writes both rows and has nothing to migrate; a database with `protocol = 2` and no marker is moved to 3 and marked; and a database @@ -1404,7 +1419,7 @@ all being debugged at once. Its shape, the six decisions it turned on, the per-s acceptance table with its results are in §2.7.2 below — all four criteria met, each against the real published `module-uo` release rather than a fixture. -**Phase 5 — The Integration Kit. STARTED 2026-08-12.** `RunicGateway/Integration-kit`, the +**Phase 5 — The Integration Kit. COMPLETE 2026-08-12**, in four slices. `RunicGateway/Integration-kit`, the instruction book for building a module for a game that is not UO — the website module, the sidecar and why it is mandatory, and the game-side plugin that feeds it, on top of a `template/` module that really builds. Scaffolded when Phase 2 lands, written against Phase 3's extraction, started once @@ -1921,6 +1936,46 @@ The fix is one line — `branches: [main, edge]` — and it must land on `websit first module PR, not alongside it. `build-images.yml` is untouched: it triggers on push to `main`, so images are published and production rolls at the cutover and at no point before it, which is correct. +#### The cutover, as landed — 2026-08-12 + +[website#150](https://gitea.whitlocktech.com/RunicGateway/website/pulls/150), 52 commits, `main` a +strict ancestor of `edge` so there was nothing to resolve. The branch was deleted afterwards, as the +protocol v3 and installer cutovers did before it: `edge` is cut fresh the next time it is needed, and +a branch left alive is one that collects work by habit. + +Four decisions settled it, and the shape of them is worth keeping because none was a code question: + +1. **The Android parity gate was released, not met.** `../android/PLAN.md` said the + `gameAccountSignup` fix "must land before that cutover"; it had not, and the org lead's call was + that the app needs a broader pass than that one field, so holding the cutover bought delay and + nothing else. The break is live and recorded in both plans rather than quietly dropped — that is + the whole difference between an accepted break and a forgotten one. +2. **The UO module was declared before the merge, not installed after it.** UOMysticmoon's tenant + template gained `MODULES=uo@0.3.0=…` ([website#149](https://gitea.whitlocktech.com/RunicGateway/website/pulls/149)), + so the compose host resolves its own set at boot (§2.7.2 decision 4). The admin-panel path would + have worked equally well and left the site game-less between the image roll and someone clicking + install — for an instance with a shard to be down for, that gap is the entire objection. +3. **The docs status pass rode with the cutover**, this section included, so no document claimed a + branch that no longer exists. +4. **`Integration-kit`'s pinned core ref moved off `edge`** (§2.11.1), which is the one thing outside + this repo that the cutover was always going to invalidate. + +**What the merge does to a live deployment**, checked against the diff rather than reasoned about, +because this is the only merge in the workstream that rolls production: + +- **No `DROP TABLE` anywhere.** Core stops *creating* the 27 `shard_*` tables and never drops them; + the module's fragment is `CREATE TABLE IF NOT EXISTS`, so an existing instance keeps its shard data + and its encrypted `uo_link_config` row. The extraction is invisible to the data. +- **One destructive statement in 228 changed files** — phase 2 PR 4's `announce_jobs` column drop, + guarded on `information_schema`, idempotent, and verified against the live `uomm-db` when it was + written. +- **No newly-required environment variable**, so nothing fails to boot for want of configuration. +- A declared module **starts on that same first boot**: a fresh `installed_modules` row lands + `installed`, and `lifecycle.boot()` skips only a row that is explicitly `disabled`. No operator + click stands between the deploy and a working shard. + +The one thing a cutover PR cannot prove is the deploy, which the org lead ran. + ### 2.10 Process obligations Every server-side PR runs `npm run swagger`, `npm run routes:manifest` (the diff is reviewed, not @@ -2008,6 +2063,13 @@ holds the whole website workstream on `edge` until one cutover, and `main` has n the things the cutover has to revisit — recorded here because a pinned ref that quietly points at an abandoned branch is exactly the failure this kit is supposed to be immune to. +*(Resolved at the cutover, 2026-08-12. The constraint expired exactly as written: `edge` merged to +`main` and was deleted, so `ci/core-ref.json` moved to `main` at the merge commit. The **sha did not +change** and neither did `MODULE_API_VERSION` — the pinned commit is an ancestor of `main`, so the +`coreApi` equality assertion stays armed at 1.5.0 and no chapter needed re-reading. Only the branch +label was ever wrong, which is the cheap kind of stale and precisely why the pin names a branch +alongside the sha instead of trusting the sha to explain itself.)* + ##### Six decisions, settled by the org lead 1. **The kit ships a buildable `template/`, not prose alone.** A minimal module that really compiles @@ -2160,7 +2222,7 @@ check in this repo can see. | 8 | SPA pages namespaced: `/uo/*`, `/admin/uo/*`, `/player/uo/*` | §2.8 | | 9 | Clean break — no redirects, no nav-override migration; site is not public yet | §2.8 | | 10 | Client half loads as a prebuilt ESM chunk with React shared via a core global | §2.6 | -| 11 | Website work lands on `edge` and reaches `main` as one cutover at the end | §2.9 | +| 11 | Website work lands on `edge` and reaches `main` as one cutover at the end — **done 2026-08-12**, website#150, `edge` deleted | §2.9 | | 12 | The module repo is `RunicGateway/Module-uo`; the module id is `uo` | §2.3 | | 13 | `RunicGateway/Integration-kit` is the module-builder's instruction book — module + sidecar + game plugin, teaching only, never re-specifying a contract | §2.11 | | 14 | Phase 3 extracts **server-first, then client**, sliced by feature; `module-uo` merges before `website` in each pair | §2.7.1 |