docs(website): demo mode step 2 built — DM18, MODULE_API 1.19.0

DM18 (org lead, 2026-10-24): core's event save never asked a module
about a step's params, so the console-command deny list's save-time
refusal is a new core hook, demoRefuses(params), asked at create, save
and publish on the demo. MODULE_API 1.19.0.

- DEMO_MODE.md: §3.4 says where the save-time check lives; DM18 row;
  step 2 marked built (website#224, Module-Rust#62) with the module's
  bridge recognition and fail-closed rules; the Rust fixture section.
- MODULE_API.md: 1.19.0 in §1.1, the changelog, the registerEventActions
  signature and the action rules.
- BACKEND_DESIGN.md §5.1: event steps on the demo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-10-26 03:41:26 -05:00
parent de7775eaab
commit 1a769f1e6c
3 changed files with 53 additions and 8 deletions

View File

@@ -1,7 +1,8 @@
# Demo mode — `demo.runicgateway.com`
**Status:** design **fully decided** 2026-10-23 (DM1-DM17, §9). §10 step 1 (core, `MODULE_API`
1.18.0) is **built**; steps 2-6 are not.
**Status:** design **fully decided** 2026-10-23 (DM1-DM18, §9). §10 step 1 (core, `MODULE_API`
1.18.0) is **built**, and so is step 2 (the module, with its core hook `MODULE_API` 1.19.0, DM18);
steps 3-6 are not.
**Issue:** `RunicGateway/website#222`. **Why now:** Google Play (`../android/PLAN.md` item 9,
M8) needs a public HTTPS site with working sign-ins for its reviewers and for the 12 testers who
must be opted in for 14 days before the app can apply for production.
@@ -177,9 +178,13 @@ for.
denied command named;
- when the step **runs**, so an event saved before the list changed is still refused.
The module learns that it runs on the demo from a new read-only `ctx.demoMode` boolean. That is part
of the same `MODULE_API` minor bump as `demoLocks` (§3.2). The list lives in the module with tests,
and a command added later is a module change.
Saving an event is core's route, and core never asked a module about a step's params when it saved.
So the save-time check is a core hook (DM18, `MODULE_API` **1.19.0**): an event action may declare
`demoRefuses(params)`, which returns a sentence or `null`. With `DEMO_MODE` on, core asks it for every
step when a definition is created, saved or published, and the first sentence refuses the write
`403 DEMO_LOCKED` with that sentence as the message. A hook that throws refuses too. The run-time check
reads a new read-only `ctx.demoMode` boolean, part of the same `MODULE_API` minor bump as `demoLocks`
(§3.2, 1.18.0). The list lives in the module with tests, and a command added later is a module change.
Commands that only change the world or the players (`server.writecfg`, `entity.*`, `env.*`,
`weather.*`, `say`, `kick`, `ban`, `oxide.grant`) stay open. The baseline restore puts all of them
@@ -324,6 +329,8 @@ org lead created the subuser and added the secret on 2026-10-23.
UTC, and the published logins.
The fixture is JSON: `{ accounts, settings?, content?: { posts, wiki, pages, events }, modules? }`.
The Rust module's section is `{ servers: [{ id, name, sidecarBaseUrl, sidecarToken, protocol?,
enabled?, sortOrder? }], links: [{ username, steamId, name?, serverId? }] }`.
Any string `"$env:NAME"` in it is replaced with that variable from the host's `.env`, so the
sidecar token is never in the repo. An unset variable fails the run instead of seeding an empty
credential. The script checks the fixture before writing anything. It must name exactly the four
@@ -414,6 +421,7 @@ down.
| DM15 | (build) How does the seed create rows in a module's tables (the Rust server link, `demo-player`'s Steam link)? | Through a new optional module hook, **`api.onDemoSeed(data, { accounts })`**, in the same `MODULE_API` minor as `demoLocks`. The module writes its own rows with its own model; core never writes a module's tables (§7.3) |
| DM16 | (build) Settings are open, so `player_registration` can be switched on. Lock registration? | Yes: **`POST /auth/register`** is a Tier A lock. Invites stay open (§3.3) |
| DM17 | (build) The server cannot tell which mobile session is the caller's own. What does the other-sessions lock do? | **`DELETE /auth/me/sessions/:id` is locked outright on a demo account**; `POST /auth/mobile/logout` stays open (§3.3) |
| DM18 | (build, step 2) Saving an event is core's route, and core never asks a module about a step's params. How is a denied command refused at save? | **A new core hook:** an event action's optional `demoRefuses(params)`, asked at create, save and publish on the demo, refusing `403 DEMO_LOCKED` (§3.4). `MODULE_API` 1.19.0; Module-Rust then needs `^1.19.0` |
DNS and HTTPS for `demo.runicgateway.com` come from an external reverse proxy, which the org lead is
setting up. The app sits behind it on plain HTTP; core already trusts one proxy hop for
@@ -442,7 +450,15 @@ through that proxy.
naming a real route fails the build.
2. **Module** (`Module-Rust`): `demoLocks` in `module.json`; behind `ctx.demoMode`, the console-command
deny list (§3.4) and the config-editor rules (§3.5); and `protocol` in the release's install
manifest (§7.5). Each with tests. One PR.
manifest (§7.5). Each with tests. One PR. The save-time half of the deny list needed a core hook
first (DM18): `demoRefuses` in `website`, `MODULE_API` 1.19.0, its own PR ahead of the module's.
**Built 2026-10-26:** core `website#224`, module `Module-Rust#62` (`coreApi` `^1.19.0`). The
module's rules are in `server/demo.js`, and its seed hook is `server/demoSeed.js`, which reads
`modules.rust.servers` and `modules.rust.links`. A bridge path is recognised after
canonicalising it (dropping `.` segments, trimming, lower-casing), so `./RunicGateway.json`
counts. The plugin name `RunicGateway` is a floor beside the catalogue's `self`, so a catalogue
that cannot be read never unlocks the file. A config that does not parse is withheld whole on the
demo, because its secrets cannot be found to redact.
3. **Kit** (`Integration-kit`): a paragraph on `demoLocks` and `ctx.demoMode`, and the
`core-ref.json` re-pin. One PR.
4. **App** (`Android-app`): the `DEMO_LOCKED` error kind and the banner on Home. One PR.