docs(website): settle what core publishes about an installed module
Phase 2, PR 6 of MODULE_SYSTEM.md 2.7 — GET /api/v1/public/modules. MODULE_API.md gains 2.9, the normative shape: four fields (id, name, version, capabilities) in scan order, and what is deliberately absent. Only started modules appear, so a disabled or failed one is absent exactly as 4.4 already leaves its routes and nav; no state, failure_stage or failure_reason reaches an anonymous caller; no client chunk URL, because htmlShell hands the browser the tag; an empty array is a real answer, but the 7.6 guard is a 500 and never []. Also records why it owns the /modules prefix rather than sitting in the root-mounted site router, where the loader's collision probe could not see it. 6.7 amends MODULE_SYSTEM 2.6 step 4, which said the SPA reads the endpoint "to learn what to load" — step 3 of the same list had already answered loading a different way, and 3.1.3 is the normative version. The endpoint feature-detects. 2.6 step 4 now says so, and Part 6's intro no longer claims a fixed count of amendments. MODULE_SYSTEM.md 2.7 records PR 6 and restates the phase exit criterion honestly: no EXISTING URL moves, and PR 6 is the single deliberate addition in Phase 2. Status line updated — the design is in implementation, not unimplemented. BACKEND_DESIGN.md documents the route in the /public table and the router in the folder map, and the published api-route-inventory.json mirror is refreshed to 228 public routes (the prose count was stale at 226). Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -90,7 +90,7 @@ rejected rather than ignored, so a typo is a loud failure and not a silently-ine
|
||||
| `purge` | no | Destructive teardown (§2.6). Required if `schema` is present. |
|
||||
| `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`, for clients (the SPA, the Android app) to feature-detect against. |
|
||||
| `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`. |
|
||||
|
||||
### 2.2 The entry point
|
||||
|
||||
@@ -384,6 +384,42 @@ fragments of started modules into `/api/docs.json`; the full reasoning and the c
|
||||
§6.1a. In short: fully-qualified paths, namespaced schema keys, module CI fails if a registered
|
||||
route has no path in the fragment, and core wins every key collision.
|
||||
|
||||
### 2.9 What core publishes about a module
|
||||
|
||||
`GET /api/v1/public/modules` — anonymous, database-free, never site-mode gated.
|
||||
|
||||
```json
|
||||
{ "modules": [ { "id": "uo", "name": "Ultima Online", "version": "1.0.0",
|
||||
"capabilities": ["shard", "atlas", "market"] } ] }
|
||||
```
|
||||
|
||||
Four fields, in the loader's scan order (§4.2). What is *not* there is the design:
|
||||
|
||||
- **Only `started` modules appear.** The endpoint answers what this backend is serving. A module that
|
||||
is `disabled` or `startup_failed` is **absent**, which is the same answer §4.4 already gives for its
|
||||
routes and its nav — a client renders a site without that capability rather than one advertising a
|
||||
capability that 503s. `installed` and `registered` are likewise absent: neither is serving yet.
|
||||
- **No `state`, no `failure_stage`, no `failure_reason`.** Where a module broke and how far it got is
|
||||
operator detail for the admin Modules screen. An anonymous visitor is not told that something is
|
||||
broken, and the message — which is an exception string from inside core — never leaves the server.
|
||||
- **No `client` chunk URL.** `utils/htmlShell.js` injects a `<script type="module">` per started
|
||||
module (§3.1.3), so the browser is handed the tag rather than a URL to fetch. This endpoint is for
|
||||
**feature detection**, not for loading. `MODULE_SYSTEM.md` §2.6 step 4 predates that resolution and
|
||||
is amended to match (§6.7).
|
||||
- **An empty `modules` array is a real answer** — a core with nothing installed. The one thing that is
|
||||
not an answer is the §7.6 guard: reading the list before `modules.load()` ran is a **500**, never
|
||||
`[]`, because a caller cannot tell an empty list from a mis-ordered boot.
|
||||
|
||||
`capabilities` are opaque to core: it never interprets one, and two modules may declare the same
|
||||
string. A client must treat an unknown capability as absent and must not infer a route from one — the
|
||||
mount prefixes are `module.json`'s business (§2.3), not the capability list's.
|
||||
|
||||
The endpoint owns the `/modules` prefix on the public tier, which is *why* it is a router of its own
|
||||
rather than a fifth singleton beside `/settings` and `/version`. The loader's collision probe reads
|
||||
the live tier stack and skips root-mounted layers (a `use('/', …)` matches every path), so a route
|
||||
declared inside the root-mounted site router would be invisible to it — a real `use('/modules', …)`
|
||||
layer is what makes "no module may claim `/modules`" enforced rather than merely intended.
|
||||
|
||||
---
|
||||
|
||||
## Part 3 — The client contract
|
||||
@@ -687,8 +723,8 @@ diff the whole workstream is allowed.
|
||||
|
||||
## Part 6 — Amendments to MODULE_SYSTEM.md
|
||||
|
||||
Four things the survey found that the design of record gets wrong or does not cover. The first
|
||||
needs a decision.
|
||||
Things the survey found that the design of record gets wrong or does not cover, plus what
|
||||
implementation has since amended. The first needed a decision and has one.
|
||||
|
||||
### 6.1 OpenAPI generation does not survive a dynamic loader — **settled: fragment merge**
|
||||
|
||||
@@ -815,6 +851,21 @@ stays reproducible on any machine regardless of what is installed.
|
||||
from. A module needs no equivalent — it ships a prebuilt `swagger-fragment.json`, because core never
|
||||
has its sources to analyse.
|
||||
|
||||
### 6.7 `/api/v1/public/modules` is not the client's load trigger
|
||||
|
||||
`MODULE_SYSTEM.md` §2.6 step 4 says "the SPA reads `/api/v1/public/modules` to learn what to load,
|
||||
then registers routes, nav and its feature provider". Step 3 of the same list resolved the loading
|
||||
question a different way, and §3.1.3 here is the normative version: `htmlShell.js` injects a
|
||||
`<script type="module" src="/modules/<id>/entry.js">` per **started** module, so the browser is handed
|
||||
the tag by the document and never fetches a URL the endpoint told it about. Nothing waits on an API
|
||||
round trip to start loading, which is also why the tag can be in `<head>`.
|
||||
|
||||
What the endpoint is for is **feature detection**: capabilities for the SPA and the Android app, which
|
||||
has no chunk to load at all. §2.9 is the shape. The two statements were only ever in tension because
|
||||
§2.6 was written before the CSP constraint forced the injected-tag design; step 4 should read "the SPA
|
||||
reads `/api/v1/public/modules` to feature-detect", and the registration it describes happens when the
|
||||
injected chunk executes and calls `window.__rg.registry` (§3.3).
|
||||
|
||||
---
|
||||
|
||||
## Part 7 — What the spike proved
|
||||
|
||||
Reference in New Issue
Block a user