docs(website): settle the OpenAPI fragment merge and the curated UI kit
Both approved by the org lead 2026-08-10. §6.1 moves from open question to decision A with the per-side obligations spelled out in a new §6.1a: modules ship a swagger-fragment.json with fully-qualified paths and namespaced schema keys, core merges started modules' fragments into /api/docs.json at request time and always wins a key collision, and swagger-output.json stays exactly what core's own routes generate. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -251,6 +251,13 @@ them leaves an operator with orphaned data and no supported way to remove it.
|
|||||||
- Call `process.exit`, install signal handlers, or start a listener.
|
- Call `process.exit`, install signal handlers, or start a listener.
|
||||||
- Write outside `ctx.paths.moduleRoot` and the upload directory.
|
- Write outside `ctx.paths.moduleRoot` and the upload directory.
|
||||||
|
|
||||||
|
### 2.8 The OpenAPI fragment
|
||||||
|
|
||||||
|
Every module that registers routes ships `swagger-fragment.json` in its bundle root. Core merges the
|
||||||
|
fragments of started modules into `/api/docs.json`; the full reasoning and the collision rules are
|
||||||
|
§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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Part 3 — The client contract
|
## Part 3 — The client contract
|
||||||
@@ -347,7 +354,8 @@ no-op, because no core nav item carries a `feature` today.
|
|||||||
|
|
||||||
### 3.4 `ui` — the shared component kit
|
### 3.4 `ui` — the shared component kit
|
||||||
|
|
||||||
**This is the largest addition Phase 1 makes to the plan, and it is not optional** (§6.2). The atlas
|
**This is the largest addition Phase 1 makes to the plan, and it is not optional** (§6.2; approved
|
||||||
|
2026-08-10). The atlas
|
||||||
pages alone import five core modules that are not React and not the router: `PublicLayout`,
|
pages alone import five core modules that are not React and not the router: `PublicLayout`,
|
||||||
`PageHeader`, `Loading` / `ErrorState` / `EmptyState`, and `useAsync`. Without a shared kit a module
|
`PageHeader`, `Loading` / `ErrorState` / `EmptyState`, and `useAsync`. Without a shared kit a module
|
||||||
either reaches into core's tree (violating the zero-import rule) or ships its own copies, which
|
either reaches into core's tree (violating the zero-import rule) or ships its own copies, which
|
||||||
@@ -504,7 +512,7 @@ diff the whole workstream is allowed.
|
|||||||
Four things the survey found that the design of record gets wrong or does not cover. The first
|
Four things the survey found that the design of record gets wrong or does not cover. The first
|
||||||
needs a decision.
|
needs a decision.
|
||||||
|
|
||||||
### 6.1 OpenAPI generation does not survive a dynamic loader — **open question**
|
### 6.1 OpenAPI generation does not survive a dynamic loader — **settled: fragment merge**
|
||||||
|
|
||||||
`MODULE_SYSTEM.md` §1.12 treats `scripts/routeManifest.js` and `swagger/swagger.js` as the same
|
`MODULE_SYSTEM.md` §1.12 treats `scripts/routeManifest.js` and `swagger/swagger.js` as the same
|
||||||
problem, because both require `app.js` with no database. They are not the same problem.
|
problem, because both require `app.js` with no database. They are not the same problem.
|
||||||
@@ -522,12 +530,30 @@ spec*. Three ways out:
|
|||||||
|
|
||||||
| Option | How | Cost |
|
| Option | How | Cost |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| **A. Fragment merge** *(recommended)* | Module CI runs swagger-autogen against its own `server/index.js` and ships `swagger-fragment.json` in the bundle. Core deep-merges the fragments of started modules into `/api/docs.json` at request time. | one merge helper in core (~40 lines); the module owns its own spec, which matches "one repo, one bundle" |
|
| **A. Fragment merge** ✅ | Module CI runs swagger-autogen against its own `server/index.js` and ships `swagger-fragment.json` in the bundle. Core deep-merges the fragments of started modules into `/api/docs.json` at request time. | one merge helper in core (~40 lines); the module owns its own spec, which matches "one repo, one bundle" |
|
||||||
| B. Glob the modules dir | Core's `swagger.js` adds `modules/*/server/index.js` to `routes` when present. | core's *committed* spec then depends on which modules the developer had checked out — a spec that differs per machine |
|
| B. Glob the modules dir | Core's `swagger.js` adds `modules/*/server/index.js` to `routes` when present. | core's *committed* spec then depends on which modules the developer had checked out — a spec that differs per machine |
|
||||||
| C. Hand-write module paths into core's spec | — | a second source of truth; drifts on the first module release |
|
| C. Hand-write module paths into core's spec | — | a second source of truth; drifts on the first module release |
|
||||||
|
|
||||||
A is the only one that keeps the spec correct on an operator's box, where core is a prebuilt image
|
**Decided 2026-08-10: A.** It is the only one that keeps the spec correct on an operator's box,
|
||||||
and the module arrived afterwards. **This needs sign-off before Phase 2 item 2.**
|
where core is a prebuilt image and the module arrived afterwards. The obligation this puts on a
|
||||||
|
module is §2.8; the one it puts on core is Phase 2 item 2.
|
||||||
|
|
||||||
|
### 6.1a The obligation, on each side
|
||||||
|
|
||||||
|
**Module:** a `swagger-fragment.json` in the bundle root, generated by its own CI with the same
|
||||||
|
`swagger-autogen` tooling pointed at its own entry point, carrying only `paths`, `tags` and
|
||||||
|
`components.schemas`. Its paths must be fully qualified (`/api/v1/public/atlas/creatures`), because
|
||||||
|
the module knows its own mount prefixes and core does not re-derive them. Its `components.schemas`
|
||||||
|
keys are namespaced (`UoAtlasCreature`, not `AtlasCreature`) so two modules cannot collide in the
|
||||||
|
merged spec. CI fails the module build if a route it registers has no path in its fragment — the
|
||||||
|
per-module form of "never ship a route that isn't in the spec".
|
||||||
|
|
||||||
|
**Core:** `/api/docs.json` merges the fragments of **started** modules over its own committed spec at
|
||||||
|
request time (cached, invalidated on a module state change). Merge is shallow-per-section and
|
||||||
|
**core always wins a key collision** — a module cannot redefine a core path, tag or schema by
|
||||||
|
shipping one with the same name; the collision is logged and the module's version dropped.
|
||||||
|
`swagger-output.json` itself stays exactly what core's own routes generate, so `npm run swagger`
|
||||||
|
remains reproducible on any machine regardless of what is installed.
|
||||||
|
|
||||||
### 6.2 The client contract is much larger than §2.1 says
|
### 6.2 The client contract is much larger than §2.1 says
|
||||||
|
|
||||||
|
|||||||
@@ -508,6 +508,8 @@ Conventional Commits, the AI-disclosure trailer, branches cut from an up-to-date
|
|||||||
| 3 | Core declares an extension slot on `/admin/users/:id`; all six URLs preserved | §1.9 |
|
| 3 | Core declares an extension slot on `/admin/users/:id`; all six URLs preserved | §1.9 |
|
||||||
| 4 | Phase 1 spike targets `/api/v1/public/atlas/*` | §2.7 |
|
| 4 | Phase 1 spike targets `/api/v1/public/atlas/*` | §2.7 |
|
||||||
| 4a | The contract lives in [`MODULE_API.md`](MODULE_API.md); it is normative where the two differ | §2.7 |
|
| 4a | The contract lives in [`MODULE_API.md`](MODULE_API.md); it is normative where the two differ | §2.7 |
|
||||||
|
| 4b | Modules ship an OpenAPI **fragment**; core merges started modules' fragments into `/api/docs.json` | API §6.1 |
|
||||||
|
| 4c | Core exposes a **curated, closed** UI kit + request primitive on `window.__rg`, versioned by `MODULE_API_VERSION` | API §3.4 |
|
||||||
| 5 | Install surfaces: admin panel and the Docker environment; never a build step | §2.5 |
|
| 5 | Install surfaces: admin panel and the Docker environment; never a build step | §2.5 |
|
||||||
| 6 | One repo, one bundle — server and client halves version together | §2.3 |
|
| 6 | One repo, one bundle — server and client halves version together | §2.3 |
|
||||||
| 7 | Android app is a separate plan; core owes it `/api/v1/public/modules` | §2.5, §2.7 |
|
| 7 | Android app is a separate plan; core owes it `/api/v1/public/modules` | §2.5, §2.7 |
|
||||||
|
|||||||
Reference in New Issue
Block a user