docs(website): settle where the module state machine lives and how boot treats it
Phase 2 PR 1 of the module system records two things 2.4 left open: the states are stored in one `state` column rather than a policy flag beside a runtime one, and every boot recomputes the outcome states while leaving `disabled` alone. That second rule is the one with consequences worth writing down -- a startup_failed module is retried on every restart, so an operator who fixes the cause needs no admin-panel visit; a running module can never display a stale failure reason; and disabling, the one operator decision rather than outcome, survives restarts. Also states what the row does NOT decide: the loader scans the filesystem before the database is reachable, so the URL surface is a property of the volume, which is what keeps routes.manifest.json generatable against a dead database. BACKEND_DESIGN.md 3 gains the installed_modules columns alongside the other tables. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -665,6 +665,34 @@ JSON. `resolveMany()` returns only ids that resolved to something displayable
|
||||
`~1_val~` are stripped, since the bridge sends the id and never the property packet that carries the
|
||||
arguments — and it never throws, because a cliloc lookup is decoration on a character sheet.
|
||||
|
||||
### installed_modules — what is installed, and what happened to it (module system)
|
||||
|
||||
One row per installed module, keyed by the `id` from its `module.json` — the same id that names its
|
||||
directory on the modules volume and its URL segment.
|
||||
|
||||
| Column | Shape |
|
||||
|---|---|
|
||||
| `id` | VARCHAR(32) PK — the module id |
|
||||
| `name`, `version` | the manifest's label and semver, for the admin Modules screen |
|
||||
| `state` | ENUM `installed` / `enabled` / `disabled` / `started` / `startup_failed` |
|
||||
| `failure_stage`, `failure_reason` | the stage a failure happened at (`manifest`, `core_api`, `mounts`, `extensions`, `schema`, `require`, `register`, `boot`) and its recorded reason |
|
||||
| `source`, `sha256` | the release the bundle came from and the digest verified before unpacking; both NULL for a directory placed on the volume by hand |
|
||||
| `installed_at`, `started_at`, `updated_at` | `started_at` is the last **successful** start |
|
||||
|
||||
**This table never decides which routes exist.** The module loader scans the filesystem at require
|
||||
time, before the database is reachable, so the URL surface is a property of the volume — which is what
|
||||
lets `routes.manifest.json` be generated against a dead database. A disabled module stays mounted and
|
||||
is guarded; the row decides whether it *answers*, not whether it is there.
|
||||
|
||||
**Every boot resets each non-disabled row to `enabled`** and clears its recorded failure, then the load
|
||||
writes that boot's outcome. So a `startup_failed` module is retried on the next restart (an operator
|
||||
who fixes the cause needs no admin-panel visit), a running module can never display a stale reason,
|
||||
and `disabled` — the one operator *decision* rather than outcome — survives untouched. A re-install or
|
||||
upgrade refreshes the metadata and leaves `state` alone.
|
||||
|
||||
Design of record: [`MODULE_SYSTEM.md`](MODULE_SYSTEM.md) §2.4; the loader's obligations are
|
||||
[`MODULE_API.md`](MODULE_API.md) Part 4.
|
||||
|
||||
---
|
||||
|
||||
## 4. API contract
|
||||
|
||||
@@ -337,6 +337,41 @@ marks that one module `startup_failed`, records the reason, and the site comes u
|
||||
routes and nav absent. `startup_failed` is recoverable from the admin panel — disable, retry, or roll
|
||||
back to the previous version — with no shell access to the box.
|
||||
|
||||
**Where the states live.** One `installed_modules` row per module, keyed by its id, with the machine
|
||||
held in a single `state` column carrying all five values — the shape this section already describes,
|
||||
rather than a policy flag beside a runtime one. The table also carries `name`/`version` for the admin
|
||||
screen, `failure_stage` + `failure_reason` for [`MODULE_API.md`](MODULE_API.md) §4.4's recorded
|
||||
reason, `source` + `sha256` for the
|
||||
install provenance of §2.5 below (both null for a directory placed on the volume by hand, which stays
|
||||
supported), and `installed_at` / `started_at` / `updated_at`. Full column list in
|
||||
[`BACKEND_DESIGN.md`](BACKEND_DESIGN.md) §3.
|
||||
|
||||
**The row is a record of what happened, never the source of truth for what is mounted.** The loader
|
||||
scans the filesystem at require time, before the database is reachable (API §4.1), so the URL surface
|
||||
is a property of the volume and not of a row here. What the row decides is whether a mounted module
|
||||
*answers* (`disabled` ⇒ its guard 404s, API §4.5) and what the admin panel shows after a failure.
|
||||
This is also why
|
||||
`routes.manifest.json` can be generated against a dead database.
|
||||
|
||||
**`disabled` is the only state a boot leaves alone.** Every boot resets each non-disabled row to
|
||||
`enabled`, clearing any recorded failure, and the load that follows writes this boot's outcome —
|
||||
`started` or `startup_failed`. Three consequences, all deliberate:
|
||||
|
||||
- **A `startup_failed` module is retried on every restart.** An operator who fixes the underlying
|
||||
cause — a truncated file, a missing dependency, a database that was not up yet — gets the module
|
||||
back by restarting, with no admin-panel visit. The cost is that a deterministically broken module
|
||||
re-records its failure each boot, which is the honest thing for it to do.
|
||||
- **A stale reason can never be shown against a running module**, because every non-failing
|
||||
transition clears the failure columns.
|
||||
- **Disabling is an operator decision, not an outcome**, so it survives restarts untouched — and a
|
||||
module the operator switched off is neither started nor re-recorded as failed if it happens to be
|
||||
broken. `installed` is likewise transient: it is the gap between an install writing the row and the
|
||||
restart that resolves it.
|
||||
|
||||
A re-install or an upgrade refreshes `name`/`version`/provenance and deliberately leaves `state`
|
||||
alone: upgrading an enabled module must not silently switch it off, and re-installing a disabled one
|
||||
must not silently switch it on.
|
||||
|
||||
### 2.5 Install, uninstall, purge
|
||||
|
||||
Modules live on a **mounted volume**, not in the image — the same treatment `uploads` already gets in
|
||||
@@ -418,6 +453,10 @@ too (API §7.2).
|
||||
Exit criterion: `routes.manifest.json` diff is zero lines and every existing test passes. If Phase 2
|
||||
changes one URL, it is wrong.
|
||||
|
||||
**Progress: PR 1 done** — `installed_modules` and the state machine, with the stored shape and the
|
||||
boot rules settled in §2.4 above. No loader, no routes, no boot wiring yet, so it changes nothing an
|
||||
operator or a client can see.
|
||||
|
||||
**Phase 3 — Extract `module-uo`.** Moves out of `website/`: the 8 model directories and their 25
|
||||
tables; the nine UO `utils/` files plus `newsGump.js`; the 13 router/controller files;
|
||||
`scripts/importSpawnAtlas.js` and `db/spawnAtlas.art.json`; `usersShard.controller.js` **minus
|
||||
|
||||
Reference in New Issue
Block a user