From e5c0a0c64ff361169ba464080e6782fd44618c54 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Tue, 1 Sep 2026 08:16:51 -0500 Subject: [PATCH] docs(engagement): Phase 12 as built -- the 12a/12b split and three findings Records Phase 12 against runicgateway.com#26 (12a, mergeable) and #27 (12b, a draft held for the cutover window). The split is the org lead's decision, and building the phase sharpened the reason the timing note only gestured at: checkFacts.mjs fetches from `main`, so the moment platform.json says protocol 5 the repo is red for the whole edge window -- and three of its values cannot be written today at all, because bundle.tag/sidecar/overlay name an artefact that does not exist until Phase 13 republishes it. Phase 13's order gains the fill-in step, written to be mechanical rather than remembered: the check names its own expected values, so it is "run checkFacts and copy the says column", plus the two pages that quote the bundle to re-read first. Two of the phase's own statements were already stale. moduleApi is 1.9.0, not 1.7.0 (Phase 11 moved it twice after that sentence was written), and Phase 7 had already fixed the capabilities.mjs "Web" claim. Phase 13's step 3 corrected to 1.9.0 as well. Three findings, none of them in the phase's text: 1. /privacy had been publishing a false sentence since Phase 8 -- app-content claimed nothing is cached offline, and Phase 8 shipped a DataStore snapshot of the inbox. That row is an INPUT to the generated Play Data Safety answers, so it is a store review matter rather than a doc nit. 2. Nothing in the engagement schema has a retention sweep. cooldowns' prune() has no caller, finished outbox rows are never deleted, and the send log and suppression list grow forever. Recorded rather than fixed -- a sweep is a website change and outside this phase. 3. The one page whose whole subject is the protocol number was the one page that hardcoded it, against platform.json's own rule. Plus the trap for whoever does this again: MDX does not evaluate an expression inside backticks. AI-assisted: written with Claude Code. Co-Authored-By: Claude --- website/ENGAGEMENT.md | 71 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/website/ENGAGEMENT.md b/website/ENGAGEMENT.md index 4e6b036..d378d9e 100644 --- a/website/ENGAGEMENT.md +++ b/website/ENGAGEMENT.md @@ -1387,7 +1387,7 @@ change is not complete until `docs/` reflects it" — is the floor; this table i | **10** Protocol bump | `link/INTEGRATION.md` §Housing **and §Market** (tables + examples) + the new `account.login.result` row · `link/PLAN.md` §5/§7 · **a `link/v5.md`** — the bump now carries three enrichments across three subsystems, which is what earned v3 and v4 their own design docs | `servuo-plugins/overlay.toml` · `servuo-plugins/patches/README.md` — `vendor.sale` stays in the tier and the docs must say what that costs · **`runicgateway.com`**: `platform.json.protocol` → 5, `bundle.*`, `architecture/protocol-versions.mdx` | | **11a** module-uo triggers | `modules/uo/API.md` — **the full trigger catalogue, its audiences and its ceilings**, not one entry · `modules/uo/README.md` · `website/ENGAGEMENT.md` §8.6 kept true as rows ship · `website/MODULE_API.md` §1.1 (**1.8.0**) and the ceiling vocabulary wherever it is enumerated · `BACKEND_DESIGN.md` — the `news.post` publish path now runs through the engine | `module-uo/README.md` | | **11b** Seeded rules + templates | `website/ENGAGEMENT.md` this phase as built · a release note naming **the seeded-disabled `news.post` rule as an upgrade step** (decision 5) — without it a deployment loses news push silently | **`runicgateway.com`**: `capabilities.mjs` and the notifications page — "one rule" and "the whole catalogue" are different marketing claims | -| **12** Public site | — | **`runicgateway.com`**, in full — see the phase | +| **12** Public site | `website/ENGAGEMENT.md` this phase as built, incl. the 12a/12b split and Phase 13's fill-in step | **`runicgateway.com`**, in full — see the phase. **Two PRs**: 12a (#26) mergeable now, 12b (#27) a draft held for the cutover window | | **13** Cutover | `README.md` index rows · every doc's status line | `.profile/README.md` if this is a headline capability | **One thing this table is protecting against.** `runicgateway.com` appears in eight rows, and it is the @@ -3646,6 +3646,65 @@ post-cutover `main` of every source repo**; `grep -ri "gmail" src/` returns only references; `/privacy` and `PLAY_DATA_SAFETY.md` regenerate from the amended inventory with no manual edit; the Notifications capability line is true of the shipped system. +#### As built (2026-09-01) — split 12a / 12b, because the facts cannot be green and correct at once + +**The org lead's decision: split the phase.** The timing note above says Phase 12's work is "written +and reviewed before the cutover and merged inside the same window", and building it made the reason +sharper than the sentence. `checkFacts.mjs` fetches from `main`, so the moment `platform.json` says +`protocol: 5` the repo is red — and it stays red for the whole `edge` window. Worse, **three of its +values cannot be written at all today**: `bundle.tag` / `bundle.sidecar` / `bundle.overlay` name an +artefact that does not exist until Phase 13 has landed `link` + `servuo-plugins` on `main` and CI has +republished the bundle. So: + +- **12a — `runicgateway.com#26`, mergeable on `edge` today.** The `Engagement rules` page, the + Troubleshooting symptom that points at it, the cross-links, and the `/privacy` + Play Data Safety + inventory. Every check green. +- **12b — `runicgateway.com#27`, a DRAFT, deliberately red.** `platform.json` `protocol` → 5 and + `moduleApi` → 1.9.0, plus the protocol page. Held for the Phase 13 window; the fill-in step is in + Phase 13's order below. + +**Two of this section's own statements were already stale**, both because Phase 11 moved after it was +written: `moduleApi` is **1.9.0**, not 1.7.0 (1.8.0 for the `admin` ceiling, 1.9.0 for the module seed +API), and the `capabilities.mjs` "Web" claim was already corrected in Phase 7's rg.com#24. The Gmail +sweep is likewise already done — `grep -ri gmail src/` returns only the migration note Phase 1 wrote +and the historical reference in Troubleshooting, which is what the acceptance line asks for. + +**The page the phase actually owed.** "New docs pages … the engagement rules screen, the template +editor, and per-channel notification preferences" — the second and third landed in Phases 5b and 7. The +first had not, and it was the largest hole on the site: there was a page for *where* a message goes and +a page for *what it says*, and nothing for **what makes one get sent**, which is the entire admin +surface this workstream built. One page — Rules, Audiences, the trigger catalog and the send log — +between the two it joins up. Suppressions stays in Troubleshooting (Phase 9) and Templates keeps its +own page (Phase 5b). + +Two things it exists to state, because both are things a reader gets wrong by reasoning: + +- **Every rule ships disabled, including the ones a module brings.** "Installed" is not "on". +- **The ceiling is a tree, not a ladder** (§7.2's lattice). The ladder reading says a staff-only event + could obviously also go to one person; "one person" for `uo.cheat.detected` is *the player it was + detected on*. + +#### Three findings, none of them in the phase's own text + +**1. `/privacy` had been publishing a false sentence since Phase 8.** The `app-content` row claimed +"Nothing is cached for offline use and nothing is duplicated anywhere else", and Phase 8 shipped a +DataStore snapshot of the inbox. That row is an *input to the generated Play Data Safety answers*, so +it is a store-review matter and not a doc nit. The snapshot now has its own row and its own Play +mapping (Messages / Other in-app messages; not collected by us, stored on the device), and the old +claim is narrowed rather than softened. + +**2. Nothing in the engagement schema has a retention sweep.** `engagementCooldowns.db.prune()` exists +and **has no caller**; a finished `engagement_outbox` row is never deleted; the send log and the +suppression list grow forever. The `deploy-engagement` privacy row therefore says "kept until the +operator removes them; nothing here expires on its own", which is the true answer rather than the one +a reader assumes. **Recorded, not fixed** — a sweep is a `website` change and outside this phase. + +**3. The one page whose whole subject is the protocol number was the one page that hardcoded it.** +`platform.json`'s header says no version number is ever hardcoded in prose; `protocol-versions.mdx` +wrote `4` three times. It now imports the JSON like every other page that quotes a fact. Note for +whoever does this again: **MDX does not evaluate an expression inside backticks**, so the interpolation +has to sit outside the code span or the braces reach the published page. + --- ### Phase 13 — The cutover: `edge` → `main`, in order @@ -3658,7 +3717,7 @@ v4 overlay is a shard that cannot pair, and the installer refuses it by design. 2. **`servuo-plugins`** and **`link`** together — the emitter and `overlay.toml` and `PROTOCOL_VERSION` are one protocol bump with three declaration sites (CLAUDE.md). Then CI publishes the paired bundle. -3. **`website`** — core: the transport abstraction, the trigger registry, `MODULE_API_VERSION` 1.7.0, +3. **`website`** — core: the transport abstraction, the trigger registry, `MODULE_API_VERSION` **1.9.0**, the engine, the templates, the in-app channel, the Teams migration. 4. **`module-uo`** — its `coreApi` range and its triggers, after the core it declares against. 5. **`Integration-kit`** — `ci/core-ref.json` to the new website `main` sha. Its equality check is red @@ -3666,6 +3725,14 @@ v4 overlay is a shard that cannot pair, and the installer refuses it by design. chapters. 6. **`android-app`** — the in-app inbox. First real CI run (§6.0a). 7. **`runicgateway.com`** — last, because every fact it fetches has to be true on `main` first. + Two merges, in this order: **12a (#26)** into `edge` at any time, then `edge` → `main`, then + **12b (#27)** with its three unknowable values filled in. Filling them in is mechanical, because + the check names them: run `GITEA_TOKEN= node scripts/checkFacts.mjs` and copy what it prints + in the "says" column — `bundle.tag`, `bundle.sidecar`, `bundle.overlay` from + `installer bundles:current.json`, `releases.Module-uo` if the cutover cut a new module release, and + `verifiedOn` as the date it was run. Then re-read the two pages that quote the bundle + (`getting-started/connect-a-game-server`, `administration/the-shard-connection`) before merging — + §12's own rule is that a value is never edited to make the check pass. 8. **`.profile/README.md`** — only if this is a headline capability. **Acceptance:** a clean install from `main` alone stands the whole stack up — installer pairs a v5 -- 2.49.1