docs(website): record the demo-mode decisions DM1-DM10

The org lead's answers of 2026-10-23: demoLocks in module.json, settings
open, the rig open but reset each cycle through Pterodactyl, content open
with uploads locked hard, the Oxide rig as the demo's, a stand-in Steam
link, every 2 hours, files in website/, latest versions, and every lock at
the server route level.

Adds the rig reset (§7.1-7.2): the site's first sync imports what is on
the rig (D198, D244), so the rig is restored from a baseline archive
before the site relinks, and that first import is the reset. Adds the
hard upload rule (any multipart body refused). Three follow-ups: F1
console-command steps, F2 "latest" across the bundle, F3 reading of DM2.

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-23 17:57:07 -05:00
parent c714906bbd
commit c4a1a64941
2 changed files with 186 additions and 99 deletions

View File

@@ -37,7 +37,7 @@ sidecar as a service, and hands you the values the website needs.
| [SHARD_VISIBILITY.md](website/SHARD_VISIBILITY.md) | Who sees which shard data — the admin-configurable audience framework | | [SHARD_VISIBILITY.md](website/SHARD_VISIBILITY.md) | Who sees which shard data — the admin-configurable audience framework |
| [TRUSTED_DEVICES_MFA.md](website/TRUSTED_DEVICES_MFA.md) | TOTP two-factor, trusted devices and recovery codes | | [TRUSTED_DEVICES_MFA.md](website/TRUSTED_DEVICES_MFA.md) | TOTP two-factor, trusted devices and recovery codes |
| [STEAM_SIGNIN.md](website/STEAM_SIGNIN.md) | **Plan, built 2026-10-19** — Sign in with Steam as a built-in provider beside Google and Discord (OpenID 2.0), and a Steam id for modules | | [STEAM_SIGNIN.md](website/STEAM_SIGNIN.md) | **Plan, built 2026-10-19** — Sign in with Steam as a built-in provider beside Google and Discord (OpenID 2.0), and a Steam id for modules |
| [DEMO_MODE.md](website/DEMO_MODE.md) | **Design, awaiting answers** — the public `demo.runicgateway.com`: one flag that locks destructive actions, static logins, and a wipe every couple of hours (website#222) | | [DEMO_MODE.md](website/DEMO_MODE.md) | **Design, decided 2026-10-23 (three follow-ups open)** — the public `demo.runicgateway.com`: one flag that locks destructive actions, static logins, and a wipe every couple of hours (website#222) |
| [MODERATION_APPEALS.md](website/MODERATION_APPEALS.md) | Moderation actions, content reports and the appeals flow | | [MODERATION_APPEALS.md](website/MODERATION_APPEALS.md) | Moderation actions, content reports and the appeals flow |
| [SPAWN_ATLAS.md](website/SPAWN_ATLAS.md) | The bestiary / spawn atlas: what the shard contains, parsed from the shard's own ServUO files — served over the bridge since Protocol 8, so no shared filesystem | | [SPAWN_ATLAS.md](website/SPAWN_ATLAS.md) | The bestiary / spawn atlas: what the shard contains, parsed from the shard's own ServUO files — served over the bridge since Protocol 8, so no shared filesystem |
| [CLILOCS.md](website/CLILOCS.md) | UO's id → name table, so items have names. The shard decompresses and serves it over the bridge; the desktop conversion it replaced is gone | | [CLILOCS.md](website/CLILOCS.md) | UO's id → name table, so items have names. The shard decompresses and serves it over the bridge; the desktop conversion it replaced is gone |

View File

@@ -1,6 +1,7 @@
# Demo mode — `demo.runicgateway.com` # Demo mode — `demo.runicgateway.com`
**Status:** design, waiting on the org lead's answers to §9. Nothing is built. **Status:** design. The org lead answered §9 Q1-Q9 on 2026-10-23 (recorded as decisions DM1-DM10
in §9); three follow-up questions in §9.2 remain. Nothing is built.
**Issue:** `RunicGateway/website#222`. **Why now:** Google Play (`../android/PLAN.md` item 9, **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 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. must be opted in for 14 days before the app can apply for production.
@@ -51,20 +52,43 @@ app maps the code to a new error kind with the same wording; today it would show
Reads are never locked. Every page can be opened and every form filled in; only the save is Reads are never locked. Every page can be opened and every form filled in; only the save is
refused. That keeps the demo useful as a tour of the admin panel. refused. That keeps the demo useful as a tour of the admin panel.
Module routes need their own entries, because they are mounted by core but belong to the module. **The server is the lock, never the UI (DM10).** Every lock is enforced at the route, so a request
§9 Q1 asks how the module declares them. sent with `curl`, an old app build, or a hand-edited page is refused exactly like a click. The UI may
explain a lock (the toast, the banner) but never hides or disables a control as the means of
locking it, and no client-side check is relied on.
### 3.2 What is locked Module routes are mounted by core but belong to the module, so the module declares its own locked
routes in `module.json` (DM1, §3.2).
Paths are relative to `/api/v1`. Tier A breaks the site for the next visitor and is always locked. Tier B reaches the real game ### 3.2 The module's declaration
server, and Tier C is content anyone can see; both are §9 questions.
`module.json` gains an optional `demoLocks` list, read by core when `DEMO_MODE` is on:
```json
"demoLocks": [
{ "method": "PUT", "path": "/admin/rust/servers/:id" },
{ "method": "DELETE", "path": "/admin/rust/servers/:id" },
{ "method": "POST", "path": "/admin/rust/config/:serverId/file" }
]
```
Paths use the same `:param` syntax as the module's routers and must sit under the module's own
mounts, which the loader checks the same way it checks `mounts`. A module without the field is
unaffected. It is a new manifest field, so `MODULE_API_VERSION` gets a **minor** bump,
`MODULE_API.md` documents it, and the Integration Kit's module chapter gains a paragraph (its
`ci/core-ref.json` pin then moves, as for any API bump).
### 3.3 What is locked
Paths are relative to `/api/v1`. **Tier A** would break the site for the next visitor and is always
locked. **Uploads** are locked hard. **Tier B** (the rig) and **Tier C** (content) stay **open**: the
wipe puts them back (§7).
**Tier A — always locked (core)** **Tier A — always locked (core)**
| Area | Routes | What a visitor could otherwise do | | Area | Routes | What a visitor could otherwise do |
| --- | --- | --- | | --- | --- | --- |
| Site mode | `PUT /admin/site-mode` | Put the site into maintenance; every non-admin is locked out | | Site mode | `PUT /admin/site-mode` | Put the site into maintenance; every non-admin is locked out |
| Settings & branding | `PUT /admin/settings`, `POST /admin/settings/brand-asset/:slot`, `DELETE /admin/settings/:key` | Rename or rebrand the site, swap the logo |
| Modules | every write under `/admin/modules` (install, sources, restart, enable, disable, purge, delete) | Disable or purge the Rust module; restart the server | | Modules | every write under `/admin/modules` (install, sources, restart, enable, disable, purge, delete) | Disable or purge the Rust module; restart the server |
| SSO providers | writes under `/admin/auth/providers` | Add a sign-in provider pointing anywhere | | SSO providers | writes under `/admin/auth/providers` | Add a sign-in provider pointing anywhere |
| Email | `PUT /admin/email/config`, `POST /admin/email/test`, `POST /admin/email/disconnect` | Connect a mail account; send mail from the site | | Email | `PUT /admin/email/config`, `POST /admin/email/test`, `POST /admin/email/disconnect` | Connect a mail account; send mail from the site |
@@ -82,13 +106,33 @@ server, and Tier C is content anyone can see; both are §9 questions.
| The link to the rig | `PUT`/`DELETE /admin/rust/servers/:id` | Repoint or delete the sidecar link; the site goes dark | | The link to the rig | `PUT`/`DELETE /admin/rust/servers/:id` | Repoint or delete the sidecar link; the site goes dark |
| Plugin config files | `POST /admin/rust/config/:serverId/file` | Writes a plugin's config file on the rig's disk | | Plugin config files | `POST /admin/rust/config/:serverId/file` | Writes a plugin's config file on the rig's disk |
**Tier B — reaches the rig (§9 Q3).** Permission writes (policy, grant, revoke, groups, members, **Settings stay open (DM2).** Theme, navigation and the other `PUT /admin/settings` values can be
drift actions, sync), RunicNPC writes (factions, profiles, placements, push, respawn), economics and changed and come back at the wipe. Brand images are uploads, so they fall under the upload lock.
permission grants to a linked player, console-command steps and every other world action an event
can run, and map fetch/render.
**Tier C — content anyone can see (§9 Q4).** Posts, wiki, pages, events, teams and team forums, **Uploads — locked hard (DM4)**
moderation actions, invites, uploads, and the public contact form.
| Route | What it stores |
| --- | --- |
| `POST /admin/uploads` | The media library |
| `POST /admin/posts/upload` | Images in posts |
| `POST /admin/settings/brand-asset/:slot` | Logo, hero, favicon |
| `POST /player/teams/:slug/forum/uploads` | Images in team forum posts |
"Hard" means a second rule beside the list: with `DEMO_MODE` on, core refuses **any** request whose
body is `multipart/form-data`, before a router sees it. An upload route added later is locked
without anyone remembering to list it, and a module cannot open one.
**Tier B — the rig: open, reset by the wipe (DM3).** Permission writes (policy, grant, revoke,
groups, members, drift actions, sync), RunicNPC writes (factions, profiles, placements, push,
respawn), economics and permission grants to a linked player, the world actions events run, and map
fetch/render all work. The rig starts every cycle from a **demo default set** of permissions, NPCs
and events, and the wipe restores it (§7.1). Console-command steps are the exception that needs a
rule of their own (§9.2 F1).
**Tier C — content: open (DM4).** Posts, wiki, pages, events, teams and team forums, moderation
actions and invites work. The contact form is locked (`POST /public/contact`), because it sends mail
to a person. The demo is served with `X-Robots-Tag: noindex` and a matching `robots.txt`, so nothing
a visitor writes reaches a search engine.
## 4. Static logins ## 4. Static logins
@@ -96,7 +140,7 @@ moderation actions, invites, uploads, and the public contact form.
| --- | --- | --- | | --- | --- | --- |
| `demo-admin` | admin | The admin panel, read in full; writes per §3 | | `demo-admin` | admin | The admin panel, read in full; writes per §3 |
| `demo-moderator` | moderator | Moderation queue, appeals | | `demo-moderator` | moderator | Moderation queue, appeals |
| `demo-player` | player | Self-service; a linked Steam account so the Rust player screens have data (§9 Q6) | | `demo-player` | player | Self-service; linked to a stand-in Steam ID that has played on the rig, so the Rust player screens have data (DM6) |
| `play-review` | admin | Google's reviewer. Same powers as `demo-admin`, but a separate account, so a reviewer is never signed out because someone else revoked a session on the shared one | | `play-review` | admin | Google's reviewer. Same powers as `demo-admin`, but a separate account, so a reviewer is never signed out because someone else revoked a session on the shared one |
Usernames and passwords are published on the demo's sign-in page and on runicgateway.com `/beta`. Usernames and passwords are published on the demo's sign-in page and on runicgateway.com `/beta`.
@@ -111,7 +155,7 @@ address with (§5), and the static logins cover every role.
| Email | Not configured. Registration closed, password reset locked, digest and engagement mail never send | | Email | Not configured. Registration closed, password reset locked, digest and engagement mail never send |
| Push (ntfy) | Not deployed. The app's notification settings still save; nothing is delivered | | Push (ntfy) | Not deployed. The app's notification settings still save; nothing is delivered |
| Discord bot | Not deployed | | Discord bot | Not deployed |
| The rig | Per §9 Q3 | | The rig | Open, and restored to the demo default set at every wipe (§7.1) |
## 6. The banner ## 6. The banner
@@ -122,115 +166,158 @@ needs to know the schedule.
## 7. The wipe ## 7. The wipe
A scheduled workflow on the `demo-site` runner (§9 Q7 for the cadence) runs: A scheduled workflow on the `demo-site` runner runs **every 2 hours on the hour, UTC** (DM7). It
resets the rig first and the site second, and the order matters.
1. `docker compose down -v` — drops the database **and** the uploads volume, so nothing a visitor ### 7.1 Why the rig goes first
made survives.
2. `docker compose pull && docker compose up -d` — the app boots, creates the schema, and installs The site's first sync with a server **imports what it finds there**. A new site's first permission
the Rust module from `MODULES=rust@<version>=<manifest>` in `.env`. read adopts every grant and group on the rig (D198), and RunicNPC profiles are adopted before the
first push (D244). So wiping only the site would bring back every change a visitor made on the rig,
now owned by the fresh site. The rig has to be back at the demo default set **before** the new site
first talks to it. Then that first import *is* the reset: the site adopts the default set, and the
default set never has to be described twice.
### 7.2 The rig: restore a baseline through Pterodactyl (DM3)
The demo rig is the existing `rust-oxide` Pterodactyl server (DM5). On it sits one archive,
`demo-baseline.tar.gz`, holding the demo default set:
- `oxide/data/` — permissions and groups, RunicNPC profiles and placements, Economics balances, the
bridge plugin's own data;
- `oxide/config/` — plugin configs, so even a config that changed some other way comes back;
- `server/<identity>/` — the world save and `cfg/`, so anything an event built or a console command
wrote with `server.writecfg` comes back too.
`oxide/plugins/` is **not** in the baseline. Plugin versions are the deployment's business (§9.2
F2), not the wipe's.
The workflow uses the Pterodactyl **client** API (files and power):
1. `POST …/power {"signal":"kill"}` — no save, since the world is about to be replaced.
2. `POST …/files/delete` the three directories.
3. `POST …/files/decompress` `demo-baseline.tar.gz` at the server root.
4. `POST …/power {"signal":"start"}`. Rust takes a few minutes to boot; the site does not wait.
**Capturing the baseline** is a second, hand-run workflow (`workflow_dispatch` only): someone sets up
the default set through the demo site (the permission groups, a handful of NPCs, events), then runs
it. It saves the world (`server.save` over the console), stops the server, `POST …/files/compress`es
the three directories into `demo-baseline.tar.gz`, and starts the server again. Re-run it whenever
the default set should change.
**The key.** Pterodactyl client keys belong to a user and reach every server that user can see. The
demo gets its own Pterodactyl **subuser**, with access to `rust-oxide` only and the file and power
permissions only, and that subuser's client key is the `demo-site` workflow's secret
(`PTERODACTYL_CLIENT_KEY`). The panel URL and server identifier are plain variables.
### 7.3 The site
1. `docker compose down -v` — drops the database **and** the uploads volume.
2. `docker compose pull && docker compose up -d` — the app boots on the latest `main` image, creates
the schema, and installs the Rust module from `MODULES=rust@<version>=<manifest>` (DM9, §9.2 F2).
3. Wait for the app's healthcheck. 3. Wait for the app's healthcheck.
4. `docker compose exec app npm run seed:demo` — one core script that, from a fixture file in the 4. `docker compose exec app npm run seed:demo` — one core script that, from a fixture file in the
deploy directory: deploy directory:
- creates the four accounts (§4); - creates the four accounts (§4) and `demo-player`'s stand-in Steam link (DM6);
- creates the Rust server row with the rig's sidecar URL and token taken from `.env`, so the - creates the Rust server row with the rig's sidecar URL and token taken from `.env`, so the
link survives every wipe without being stored in the fixture; link survives every wipe without being stored in the fixture;
- writes the seeded content: a few news posts, wiki pages, an upcoming event and a team, enough - writes the seeded site content: news posts, wiki pages, the demo events and a team;
for every screen to have something on it;
- stamps `resetsAt` for the banner. - stamps `resetsAt` for the banner.
5. Smoke check: `GET /api/v1/public/status` answers, and the Rust server reports a live connection. 5. The module's sync connects when the rig finishes booting, and its first import adopts the
baseline's permissions and NPCs (§7.1).
6. Smoke check, after a bounded wait: `GET /api/v1/public/status` answers, and the Rust server
reports a live connection. A rig that has not come back fails the run, and the site still
serves with the server shown offline.
A wipe takes the site down for about a minute. Signed-in visitors, including app users, are signed ### 7.4 What a visitor sees
out: their sessions and refresh tokens were in the database.
The same workflow runs on a push that changes the deploy directory and on `workflow_dispatch`, so a The site is down for about a minute; the rig for a few. Signed-in visitors, including app users,
redeploy is the same path as a wipe. are signed out, because their sessions and refresh tokens were in the database.
The same workflow runs on `workflow_dispatch` and on a push to `main` that changes `deploy/demo/`,
so a redeploy is the same path as a wipe.
## 8. Where it lives ## 8. Where it lives
| Piece | Repo | | Piece | Repo |
| --- | --- | | --- | --- |
| `DEMO_MODE` lock, banner field, `seed:demo` script | `website` (core) | | `DEMO_MODE` lock, banner field, `seed:demo` script | `website` (core) |
| The Rust module's own lock entries | `Module-Rust` (§9 Q1) | | The Rust module's `demoLocks`, and the console-command rule (§9.2 F1) | `Module-Rust` |
| `DEMO_LOCKED` error kind and the banner in the app | `Android-app` | | `DEMO_LOCKED` error kind and the banner in the app | `Android-app` |
| `docker-compose.yml`, `.env.example`, seed fixture, wipe workflow | §9 Q8 | | `deploy/demo/` (compose file, `.env.example`, seed fixture) and `.gitea/workflows/demo-wipe.yml` + `demo-baseline.yml` | `website` (DM8) |
| `.env` (JWT secret, encryption key, the rig's sidecar token) | On the `demo-site` host only, never in a repo | | `.env` (JWT secret, encryption key, the rig's sidecar token) | On the `demo-site` host only, never in a repo |
| `PTERODACTYL_CLIENT_KEY` (the demo subuser's key) | A Gitea Actions secret on `website` |
| `demo-baseline.tar.gz` | On the rig, at the Pterodactyl server root |
## 9. Questions for the org lead ## 9. Decisions and open questions
**Q1 — How does the Rust module declare its locked routes?** ### 9.1 Decisions (org lead, 2026-10-23)
Example: the demo is up and `demo-admin` opens Rust → Servers → Edit and changes the sidecar URL.
Something has to know that route is locked.
- **(a) Recommended:** `module.json` gains an optional `demoLocks` list of method + path, read by
core's lock. A module with no list is unaffected. It is a new manifest field, so `MODULE_API`
gets a minor bump and the Integration Kit documents it.
- (b) Core hard-codes the Rust module's paths. No module change, but core then knows one module's
routes, which the module system was built to avoid.
**Q2 — Is the Tier A list (§3.2) complete, and is anything on it you want left open?** | # | Question | Decision |
Example: should `demo-admin` be able to change the site's theme colours, which are under Settings? | --- | --- | --- |
It is harmless and resets at the wipe, but it is also the first thing a visitor would deface. | DM1 | How the module declares its locked routes | A `demoLocks` list in `module.json`; `MODULE_API` minor bump (§3.2) |
| DM2 | Is anything on Tier A to stay open? | Settings stay open (theme, navigation and the rest of `PUT /admin/settings`); the rest of Tier A stays locked (§3.3). See F3 |
| DM3 | What may a visitor do to the rig? | Permissions, NPCs and events start from a **demo default set**, can be changed through the site, and are put back each cycle by a CI script that restores the rig through Pterodactyl (§7.2) |
| DM4 | May visitors publish content? | Yes. **Uploads are locked hard** (§3.3); the contact form is locked; the site is `noindex` |
| DM5 | Which rig? | The existing Oxide rig becomes the demo's. A new Oxide server is spun up for testing when a walk needs one |
| DM6 | A Steam link for `demo-player`? | Yes, a stand-in Steam ID that has played on the rig |
| DM7 | Cadence | Every 2 hours |
| DM8 | Where the files live | `website/deploy/demo/` and `website/.gitea/workflows/` |
| DM9 | Version | Latest, at every wipe |
| DM10 | (the org lead, unprompted) | **Every lock is at the server route level, never the UI level** (§3.1) |
**Q3 — What may a visitor do to the rig?** DNS and HTTPS for `demo.runicgateway.com` come from an external reverse proxy, which the org lead is
Example: `demo-admin` grants `demo-player` a kit permission, or starts an event whose step runs setting up. The app sits behind it on plain HTTP; core already trusts one proxy hop for
`server.time 0` on the Oxide rig. The website wipe does not undo either; the rig keeps them. `X-Forwarded-*` by default (`TRUST_PROXY`), and the deploy PR checks secure cookies and client IPs
- **(a) Recommended:** lock everything that writes to the rig (all of Tier B). Visitors see the through that proxy.
live map, feed, players and every admin screen, but saves that would reach the rig are refused.
- (b) Allow a short list of harmless, self-healing actions — for example respawning a RunicNPC or
rendering the map — and lock the rest.
- (c) Allow everything and also reset the rig in the wipe. Much larger: a rig wipe means
permissions, NPC placements and plugin state, and a Rust server restart.
**Q4 — May visitors publish content?** ### 9.2 Follow-up questions
Example: someone signs in as `demo-admin` and posts a news item with an offensive image or a
phishing link. It is public on `demo.runicgateway.com` until the next wipe.
- **(a) Recommended:** allow posts, wiki, pages, events and teams, so the demo shows the real
editors. Lock **uploads** (no hosting of arbitrary files on our domain) and the **contact form**.
Serve the demo with `noindex`, so nothing a visitor writes reaches search engines.
- (b) Lock all publishing as well; the editors open but every save is refused.
- (c) Allow uploads too, with the wipe as the only cleanup.
**Q5 — Which rig does the demo use, and what happens to our walks on it?** **F1 — Console-command steps on the demo.**
Example: next week a walk needs the Oxide rig while the demo is linked to it. The Rust module's Example: `demo-admin` adds a console-command step with `quit` to an event and starts it. The Rust
site-owned permission sync means two sites linked to one rig fight over its permissions. server stops and stays down until the next wipe, up to two hours, for every visitor. Other commands
- **(a) Recommended:** the Oxide rig becomes the demo's rig; walks use the Carbon rig. do the same more quietly: `oxide.unload RunicGateway` cuts the site off from the rig, and
- (b) A third rig for the demo. `kickall` and `ban` affect anyone playing. The command box is free text with no allowlist today
- (c) Share it, with permission sync off on the demo. (Rust §16).
- **(a) Recommended:** on the demo, the module accepts a command step or command reward only if its
command is on a short allowlist, for example `say`, `env.time`, `weather.*` and `server.time`.
The module checks it when the event is saved **and** when the step runs, so it is a server-side
rule (DM10). Everything else in events stays open.
- (b) Refuse command steps and command rewards entirely on the demo.
- (c) Leave them open; the wipe is the cleanup, and the rig may be down until it.
**Q6 — Does `demo-player` have a linked Steam account?** **F2 — "Latest" across the website, the module and the rig.**
Example: the player's Rust screens (stats, linked accounts, economics) are empty without one. Example: Module-Rust ships a release that needs protocol 15 while the rig's bridge plugin still
- **(a) Recommended:** the seed links a stand-in Steam ID that has played on the rig, so the screens speaks 14. At the next wipe, the demo pulls the new module, and the site refuses the rig until
have data. It is not anyone's real account. someone updates the plugins.
- (b) Leave it unlinked, and show the empty states. - **(a) Recommended:** the demo follows the latest **Rust bundle**, the protocol-checked set of
module, sidecar and plugins that CI already publishes. When the bundle changes, the wipe also
puts the bundle's plugins and sidecar onto the rig through the same Pterodactyl file API. The
website image always follows `main`.
- (b) Latest module at every wipe, and the rig's plugins updated by hand when a release needs it.
**Q7 — How often does it wipe?** **F3 — DM2, read correctly?**
- **(a) Recommended:** every 2 hours, on the hour (00:00, 02:00, … UTC). Each wipe signs everyone "Stay open" is read as: the settings (theme, colours, navigation, site texts) stay open, and every
out, which a reviewer in the middle of a review may notice. other Tier A row stays locked, including site mode, modules, SSO, email, Discord, the demo
- (b) Every 6 hours, which leaves visitor changes up for longer. accounts' credentials and 2FA, and the rig link. Is that right?
**Q8 — Where do the deployment files live?** **For the org lead to set up (not questions):** a Pterodactyl subuser with access to `rust-oxide`
- **(a) Recommended:** `website/deploy/demo/` (compose file, `.env.example`, seed fixture) and only (file and power permissions), and its client key as the `PTERODACTYL_CLIENT_KEY` secret on
`website/.gitea/workflows/demo-wipe.yml`. The seed script ships in the image, so its fixture sits `website` (§7.2). Also confirm that the `demo-site` host can reach the Pterodactyl panel and the
next to it. rig's sidecar over the network.
- (b) A new repo, `RunicGateway/demo-site`, which keeps the website repo free of one deployment's
files.
**Q9 — Which version does the demo run?** ## 10. Build order, once §9.2 is answered
- **(a) Recommended:** whatever `website` `main` and the latest Rust module release are at each
wipe, so the demo always shows the current release.
- (b) Pinned versions in `.env`, moved by hand.
**Not asked, assumed:** the host's reverse proxy and DNS for `demo.runicgateway.com`, and that the 1. **Core** (`website`): the `DEMO_MODE` lock (deny list, the `multipart/form-data` rule, the
`demo-site` host can reach the Oxide rig's sidecar over the network. These are for the org lead to demo-account and other-session checks), `demoLocks` loading, the banner field, `seed:demo`,
confirm, not to decide. `noindex`, the web banner and toast, `MODULE_API` minor bump, and the OpenAPI note that a route
may answer `DEMO_LOCKED`. Tests for every lock. One PR, plus `MODULE_API.md` and this doc.
## 10. Build order, once §9 is answered 2. **Module** (`Module-Rust`): `demoLocks` in `module.json`, and the F1 rule with its tests. One PR.
3. **Kit** (`Integration-kit`): the `demoLocks` paragraph and the `core-ref.json` re-pin. One PR.
1. **Core** (`website`): the `DEMO_MODE` lock and its tests, the banner field, `seed:demo`, and the 4. **App** (`Android-app`): the `DEMO_LOCKED` error kind and the banner on Home. One PR.
OpenAPI note that a route may answer `DEMO_LOCKED`. One PR. 5. **Deployment** (`website`): `deploy/demo/`, `demo-wipe.yml` and `demo-baseline.yml`; the first
2. **Module** (`Module-Rust`): its lock entries (Q1), plus a kit note if the manifest changes. One PR. deploy on `demo-site`; setting up the default set and capturing the baseline; then a walk of
3. **Clients**: the web banner and toast (in the core PR), and the `DEMO_LOCKED` error kind and every static login, every Tier A lock and the upload lock, in the browser and the app, plus
banner in `Android-app`. One PR. one full wipe cycle that restores a changed permission, NPC and event. One PR.
4. **Deployment**: compose, fixture and wipe workflow (Q8); first deploy on `demo-site`; a walk of 6. **Play**: the sign-in details in the Play Console point at `play-review`;
every static login and every Tier A lock, in the browser and the app. One PR.
5. **Play**: the sign-in details in the Play Console point at `play-review`;
runicgateway.com `/beta` publishes the logins. runicgateway.com `/beta` publishes the logins.