docs(website): demo mode core built — DM15-DM17, MODULE_API 1.18.0
- DEMO_MODE.md: DM15 (onDemoSeed), DM16 (POST /auth/register locked), DM17 (sessions route locked outright on a demo account). It also covers the settings keys that reach a Tier A lock, how demo accounts are recognised, failing closed, the published logins, the toast, the seed's fixture shape and $env, and the fact that there is no seeded team (Teams come from the rig's clans). The §3.2 example drops the config-file POST that DM14 opened. Step 1 is marked built. - MODULE_API.md: 1.18.0. demoLocks in module.json, ctx.demoMode, and api.onDemoSeed, which must upsert because it runs on every seed. - BACKEND_DESIGN.md: §5.1 demo mode, and demo_state in the settings table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -26,13 +26,30 @@ 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.17.0'
|
||||
const MODULE_API_VERSION = '1.18.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.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.
|
||||
|
||||
- **`demoLocks` in `module.json`** (§2.1): the module's own write routes that the demo refuses. Core
|
||||
answers a matching request `403 { message, code: 'DEMO_LOCKED' }` before the module's handler runs,
|
||||
exactly as it does for its own locked routes. Writes only, under the module's own mounts.
|
||||
- **`ctx.demoMode`** (§2.3): `true` on the demo. It is for the locks a route list cannot express,
|
||||
which the module enforces in its own handlers: a console command refused by its first word, or a
|
||||
config file refused by the plugin that owns it. A refusal should answer with the same body.
|
||||
- **`api.onDemoSeed(fn)`** (§2.4): the demo's seed hands the module its section of the fixture, so the
|
||||
module writes its own rows (DM15).
|
||||
- A module that **declares `demoLocks`** needs `coreApi` `^1.18.0`, because an older core rejects the
|
||||
unknown key. A module that only reads `ctx.demoMode` or registers `onDemoSeed` should declare it too:
|
||||
an older core has no `onDemoSeed` on `api`, and its `ctx.demoMode` is `undefined`. Module-uo's
|
||||
`^1.10.0` still resolves and uses none of them.
|
||||
|
||||
**1.17.0 — a module may take part in linking a sign-in identity** (`website/STEAM_SIGNIN.md` §4; Rust
|
||||
`modules/rust/PLAN_REDESIGNS.md` §15, D392 and D396). Three optional additions and no removal, so
|
||||
minor. Linking stays core's: a module can read what core verified, refuse a link, and hear one, and
|
||||
@@ -521,6 +538,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.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.
|
||||
|
||||
**1.17.0 — identity links** (`website/STEAM_SIGNIN.md` §4; Rust PLAN_REDESIGNS §15, D392, D396).
|
||||
`ctx.identities.list`, and `api.onIdentityLinking` (refuse-only, fails closed) and
|
||||
`api.onIdentityLinked`; three optional additions, so minor; see §1.1. Module-uo's `^1.10.0` still
|
||||
@@ -788,6 +809,7 @@ rejected rather than ignored, so a typo is a loud failure and not a silently-ine
|
||||
| `mounts` | no | Declared prefixes per tier (§2.3). Declaration is the contract; the loader compares it against what the module actually registers and rejects a mismatch. |
|
||||
| `extensions` | no | Core extension slots this module mounts into (§2.4). |
|
||||
| `capabilities` | no | Opaque strings published by `GET /api/v1/public/modules` (§2.9), for clients (the SPA, the Android app) to feature-detect against. Published only while the module is `started`. |
|
||||
| `demoLocks` | no | *(1.18.0)* `[{ method, path }]`: the module's write routes the public demo refuses (`website/DEMO_MODE.md` §3.2). `method` is `POST`, `PUT`, `PATCH` or `DELETE`; `path` is relative to `/api/v1`, in the routers' own `:param` syntax with an optional trailing `/*`, and must sit under one of the module's declared `mounts`. Validated on every boot; read only with `DEMO_MODE=true`, where core answers a match `403 DEMO_LOCKED` before the module's handler runs. A module declaring it needs `coreApi` `^1.18.0`, since an older core rejects the unknown key. |
|
||||
|
||||
### 2.2 The entry point
|
||||
|
||||
@@ -838,6 +860,7 @@ module-uo does not need is on the list.
|
||||
| `ctx.events.expired` | `({ kind, ref }) => void`, fire-and-forget, owner bound | `events/cleanup` `expireResource` | `module-rust`'s ingest of `world.expired` (1.11.0) |
|
||||
| `ctx.inbox.push` | `(userId, item) => void`, fire-and-forget | the in-app channel (live since Phase 7) | `module-uo` reaches both through `server/core.js` (1.7.0) |
|
||||
| `ctx.identities.list` | `(userId, provider) => Promise<[{ provider, subject, displayName, avatarUrl, linkedAt }]>`, read only | `model/userIdentities` | Steam sign-in (1.17.0) |
|
||||
| `ctx.demoMode` | boolean, read once when `ctx` is built | `DEMO_MODE=true` (`demo/mode.js`) | Module-Rust's console-command deny list and config-editor rules (1.18.0) |
|
||||
|
||||
**`ctx.events.emit(triggerId, envelope)`** fires an event the module DECLARED with
|
||||
`api.registerEventTriggers` (§2.4). It is the push half of the engagement seam
|
||||
@@ -967,6 +990,7 @@ api.onBoot(async (ctx) => {})
|
||||
api.onShutdown(async () => {})
|
||||
api.onIdentityLinking(async ({ userId, provider, subject }) => ({ ok: true })) // 1.17.0
|
||||
api.onIdentityLinked(async ({ userId, provider, subject, displayName }) => {}) // 1.17.0
|
||||
api.onDemoSeed(async (data, { accounts }) => {}) // 1.18.0
|
||||
```
|
||||
|
||||
**Every call STAGES; nothing is committed until the module as a whole is known good.** A claim's
|
||||
@@ -1460,6 +1484,25 @@ api.onIdentityLinked(async ({ userId, provider, subject, displayName }) => { /*
|
||||
identity already linked to another account is refused by core before any module is asked). There is
|
||||
no unlink hook (D393).
|
||||
|
||||
**`onDemoSeed(fn)`** (1.18.0) — the module's part of the public demo's seed (`DEMO_MODE.md` §7.3,
|
||||
DM15). `npm run seed:demo` loads the installed modules the way the server does (but never calls
|
||||
`onBoot`) and calls `fn(data, { accounts })`, where `data` is the fixture's `modules.<id>` object with
|
||||
every `"$env:NAME"` already replaced, and `accounts` maps each demo username to its user id. The module
|
||||
writes its own rows through its own model; core never writes a module's tables.
|
||||
|
||||
```js
|
||||
api.onDemoSeed(async ({ servers = [], links = [] }, { accounts }) => {
|
||||
for (const s of servers) await serversModel.upsert({ ...s, tokenEnc: ctx.secretBox.encrypt(s.sidecarToken) })
|
||||
for (const l of links) await linksModel.upsert({ userId: accounts[l.username], steamId: l.steamId })
|
||||
})
|
||||
```
|
||||
|
||||
- **Upsert, never insert.** The seed may run more than once on one database. Core seeds content only
|
||||
on the first run, but it calls every module hook on every run.
|
||||
- Called only with `DEMO_MODE=true`, only for a module whose fixture section exists, and only in the
|
||||
seed's own process. A fixture section for a module that registered no hook fails the seed. So does a
|
||||
hook that throws.
|
||||
|
||||
### 2.5 Lifecycle
|
||||
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user