docs(website): demo mode step 2 built — DM18, MODULE_API 1.19.0 #380
@@ -1828,6 +1828,10 @@ it.
|
||||
`teams_voice_*`, `demo_*`).
|
||||
|
||||
A condition that cannot decide refuses.
|
||||
- **Event steps** (`MODULE_API` 1.19.0, DM18). `POST /admin/events`, `PUT /admin/events/:id` and
|
||||
`POST /admin/events/:id/publish` ask each step's action `demoRefuses(params)`, when it declares one.
|
||||
A sentence back refuses the write `403 { code: "DEMO_LOCKED", message, errors }` with that sentence; a
|
||||
hook that throws refuses too.
|
||||
- **Uploads.** Any request with a `multipart/*` content type is refused app-wide, before a router or
|
||||
multer sees it.
|
||||
- **Search engines.** Every response carries `X-Robots-Tag: noindex, nofollow`. `/robots.txt` is
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -26,13 +26,27 @@ here extends the contract first, in this file, before the module is written agai
|
||||
Core exports a single integer-major semver string from `server/src/modules/version.js`:
|
||||
|
||||
```js
|
||||
const MODULE_API_VERSION = '1.18.0'
|
||||
const MODULE_API_VERSION = '1.19.0'
|
||||
```
|
||||
|
||||
The client half carries the same number (`client/src/modules/version.js`) and a test asserts the two
|
||||
agree. Duplicated rather than fetched because the value has to be on `window.__rg` before the first
|
||||
module chunk evaluates, which is earlier than any network round trip could answer.
|
||||
|
||||
**1.19.0 — the public demo refuses a step at save** (`website/DEMO_MODE.md` §3.4, DM18). One optional
|
||||
member on an event action, so minor.
|
||||
|
||||
- **`demoRefuses(params)`** (§2.4, `registerEventActions`): a function of a step's checked params that
|
||||
returns a sentence, or `null` to let the step through. Asked only with `DEMO_MODE` on, for every step
|
||||
when a definition is created, saved or published. The first sentence refuses the whole write
|
||||
`403 { code: 'DEMO_LOCKED', message: <the sentence>, errors: [<the sentence>] }`, so core's one
|
||||
refusal toast shows it. A hook that throws refuses too, with core's own sentence, because a lock that
|
||||
cannot decide fails closed.
|
||||
- The hook covers the **save**. The module still refuses in `perform()` (behind `ctx.demoMode`),
|
||||
because a step can be saved before the module's list grows.
|
||||
- A module that declares it needs `coreApi` `^1.19.0`: an older core rejects nothing but never calls
|
||||
it, so the save would go through. Module-uo's `^1.10.0` still resolves and declares none.
|
||||
|
||||
**1.18.0 — the public demo** (`website/DEMO_MODE.md` §3.2, §3.4, §7.3; website#222). Three optional
|
||||
additions and no removal, so minor. With `DEMO_MODE` unset, none of them changes anything a module sees,
|
||||
except that `demoLocks` is validated.
|
||||
@@ -538,6 +552,10 @@ branches lacks — so it is the only place `permits` is true between two values
|
||||
between `admin` and `owner`, `members` or `subscribers`. `permits`, `meet` and `meetAll` are otherwise
|
||||
unchanged, and so is every rule about composition narrowing rather than widening.
|
||||
|
||||
**1.19.0 — a step refused at save on the demo** (`website/DEMO_MODE.md` §3.4, DM18). An event action's
|
||||
optional `demoRefuses(params)`, asked at create, save and publish with `DEMO_MODE` on; one optional
|
||||
member, so minor; see §1.1. Module-uo's `^1.10.0` still resolves.
|
||||
|
||||
**1.18.0 — the public demo** (`website/DEMO_MODE.md` §3.2, §3.4, §7.3). `demoLocks` in `module.json`,
|
||||
`ctx.demoMode` and `api.onDemoSeed`; three optional additions, so minor; see §1.1. Module-uo's
|
||||
`^1.10.0` still resolves.
|
||||
@@ -981,7 +999,7 @@ api.registerSlashCommands([{ name, description, options, access, handler }]) //
|
||||
api.registerEventTriggers([{ id, label, kind, subjectKey, audience, ceiling, version, variables }]) // 1.7.0
|
||||
api.registerAudiences([{ id, label, params, ceiling, resolve }]) // 1.7.0
|
||||
api.registerEngagementSeeds({ templates, ruleGroups }) // 1.9.0
|
||||
api.registerEventActions([{ id, label, risk, reversible, cost, params, perform, revert, reconcile, progress }]) // 1.10.0; progress 1.12.0; a param's duration 1.13.0; a param's multiple + max 1.14.0; a failure's retryAfterMs 1.15.0; a dry run's detail.warnings 1.16.0
|
||||
api.registerEventActions([{ id, label, risk, reversible, cost, params, perform, revert, reconcile, progress, demoRefuses }]) // 1.10.0; progress 1.12.0; a param's duration 1.13.0; a param's multiple + max 1.14.0; a failure's retryAfterMs 1.15.0; a dry run's detail.warnings 1.16.0; demoRefuses 1.19.0
|
||||
api.registerEventBudgets([{ id, label, unit }]) // 1.10.0
|
||||
api.registerEventLeases([{ id, label, type, min, max, values, example, target, maxDurationMs, read, apply, restore, inForce }]) // 1.10.0; example 1.13.0
|
||||
api.registerEventOptionSources([{ id, label, searchable, resolve }]) // 1.10.0; a row's note 1.13.0
|
||||
@@ -1396,6 +1414,13 @@ rather than implementation and belong here:
|
||||
would hide what the line describes answers `null` for that reader; a public page must never say
|
||||
what the module's own pages would hide. Core gives it 2 s and drops a late answer. It is a read: it
|
||||
must change nothing, and it may be called every few seconds for as long as a run is live.
|
||||
- **`demoRefuses(params)` is the demo's save-time lock, and it is not the only one** (1.19.0). Asked
|
||||
only with `DEMO_MODE` on, at create, save and publish, with the step's checked params; a sentence
|
||||
refuses the write `403 DEMO_LOCKED` and is shown as written, so phrase it for the person saving
|
||||
(`"quit" is turned off on the demo site.`). It is a pure check: no database, no game, nothing changed.
|
||||
A throw refuses. Because a definition can predate the list it is checked against, the module checks
|
||||
again in `perform()` behind `ctx.demoMode`. It never leaves the process: the catalog strips it with
|
||||
the other callables.
|
||||
- **A template is a shortcut past the editor, never a second way to make an event** (1.13.0). What
|
||||
`build()` returns is validated as a spec and opened as an unsaved draft; the author saves it
|
||||
through the same route, with the same checks, as one they typed. So a template cannot make a
|
||||
|
||||
Reference in New Issue
Block a user