docs(website): close phase 3 — slice 5, the fragment obligation, and the rust dry run
Records the slice that closes the extraction, and adds the two documents phase 3 owed: docs/modules/uo/ and the module-rust dry run. **The slice found an obligation neither repo had built.** MODULE_API.md §2.8 and §6.1a settle the OpenAPI fragment in detail — a module ships one, core merges the fragments of started modules into /api/docs.json. Neither half existed, so the 72 URLs module-uo serves were in no spec at all. §2.8 and §6.1a now record what was built, including the four things settled while building it: the filename is fixed rather than declared, a module namespaces what it DEFINES and references core's shared schemas by core's name, the generator derives its prefixes from the module's own register() call, and swagger-autogen's diagnostics have to be captured because it reports a broken annotation and then prints Success. **§5.3 gains the design decision the frozen manifest actually made:** it is a SUBTRACTION, not a prefix filter. Generating the manifest without the module and then with it answers "what does the module serve" AND "did core lose anything", and the second is the one §1.2 promises to the shipped Android app. A module that shadowed a core route cannot appear as an addition anywhere. **BACKEND_DESIGN.md §4.0.1** is new: /api/docs.json is assembled per request, the two generated artifacts are core's alone, and the route count was still 228. **docs/modules/** is new, per §2.10 (module documentation aggregates here, not in module repos): docs/modules/uo/README.md orients a reader on what module-uo serves, owns and needs from an operator, and links out to the feature docs that already existed rather than restating them. **docs/modules/rust-dryrun.md** is phase 3's fourth acceptance criterion. A written, deliberately unimplemented module for Rust — chosen because it wipes monthly, runs several servers rather than one shard, identifies by Steam, and ships RCON so there is no sidecar to write. The contract generalises: same manifest, same seven registration calls, same schema rules, and six of the UI kit's seven members wanted by a game with nothing in common with the one the kit was curated from. It found one real gap — **a module cannot register an identity provider**, and "Sign in with Steam" is what a Rust community expects. Recorded as the first candidate for a future MODULE_API_VERSION bump rather than bolted on: an identity provider participates in session creation, and §2.7's link-only SSO policy has to survive it. Also: website-README.md refreshed from the repo (it was several changes stale), and three settled decisions added (18-20). Pairs with Module-uo#6 and website#141. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -731,8 +731,15 @@ are authoritative, and they answer different questions:
|
||||
|
||||
| Artifact | Source of truth for | Generated by |
|
||||
|---|---|---|
|
||||
| `server/routes.manifest.json` — mirrored as [api-route-inventory.json](./api-route-inventory.json) | **What URLs exist.** 228 public routes + 2 on the internal listener, sorted, method + path only. | `npm run routes:manifest`, by walking the live Express stack |
|
||||
| `server/swagger/swagger-output.json` — served at `/api/docs` | **What each route means.** Parameters, bodies, response codes, security. | `npm run swagger`, from `#swagger.*` annotations |
|
||||
| `server/routes.manifest.json` — mirrored as [api-route-inventory.json](./api-route-inventory.json) | **What URLs CORE serves.** 158 public routes + 2 on the internal listener, sorted, method + path only. | `npm run routes:manifest`, by walking the live Express stack |
|
||||
| `server/swagger/swagger-output.json` — merged into `/api/docs` | **What each core route means.** Parameters, bodies, response codes, security. | `npm run swagger`, from `#swagger.*` annotations |
|
||||
|
||||
Both are **core's**. An installed module's routes are in neither: they are in that module's own
|
||||
frozen manifest and its `swagger-fragment.json`, in its own repo, and core merges the fragment into
|
||||
`/api/docs.json` at request time (§4.0.1). So on a running instance the served document describes
|
||||
more than the committed one does, which is the intended arrangement rather than a drift —
|
||||
`swagger-output.json` has to regenerate identically on any machine, whatever happens to be
|
||||
installed on it.
|
||||
|
||||
The split is deliberate: Swagger is annotation-derived, so an unannotated route is invisible in it and
|
||||
it churns whenever a description is reworded — it documents *intent*. The manifest is introspection-
|
||||
@@ -764,6 +771,34 @@ authenticated endpoints silently. Names are a hint only — `requireRole(...)` r
|
||||
arrow and cannot be observed — but a *missing* `requireAuth` is unambiguous, and the server test suite
|
||||
asserts every `/admin/**` and `/player/**` route still carries it.
|
||||
|
||||
#### 4.0.1 `/api/docs.json` is assembled per request
|
||||
|
||||
`GET /api/docs.json` and the Swagger UI at `/api/docs` do not serve `swagger-output.json` directly.
|
||||
`swagger/docsSpec.js` merges the `swagger-fragment.json` of every **started** module over it first,
|
||||
cached on the module loader's state version and rebuilt when a module's state moves.
|
||||
|
||||
It exists because swagger-autogen is static analysis: it parses `src/app.js` as text and follows the
|
||||
literal `app.use(…)` chain, which reaches neither an installed module (required by a filesystem loop,
|
||||
from a volume that had nothing on it when the image was built) nor an extension slot (whose router is
|
||||
created empty by `declareSlot()` and filled later). Slots are handled at generation time by
|
||||
`swagger/slotSpecs.js` and are therefore *in* the committed file; modules cannot be, because core
|
||||
never has their sources.
|
||||
|
||||
Three rules, all from [`MODULE_API.md`](MODULE_API.md) §6.1a:
|
||||
|
||||
- **`started` only.** A `registered`, `disabled` or `startup_failed` module's paths are absent —
|
||||
documenting a route that answers 503 or 404 sends a client somewhere it cannot go.
|
||||
- **Core wins every key collision**, in all three merged sections (`paths`, `tags`,
|
||||
`components.schemas`); the collision is logged and the module's version dropped. This is what makes
|
||||
the naming rule work: a module namespaces the schemas it *defines* (`UoShardStatus`) and references
|
||||
core's shared ones (`Error`, `ValidationError`) by core's name, and both resolve in the merged
|
||||
document.
|
||||
- **A bad fragment costs that module its paths and nothing else.** Missing, unreadable or not JSON is
|
||||
logged and skipped; `/api/docs.json` still answers with everything else.
|
||||
|
||||
The committed spec is never mutated — it is a `require()`d JSON module, so an in-place merge would be
|
||||
permanent for the life of the process *and* cumulative across rebuilds.
|
||||
|
||||
### /auth (auth/index.js → the capability routers in §2)
|
||||
|
||||
No group gate — `/auth` is where an anonymous caller becomes authenticated. The authenticated parts
|
||||
|
||||
Reference in New Issue
Block a user