docs(website): the module API contract, validated by the atlas spike (phase 1) #124

Merged
whitlocktech merged 3 commits from docs/module-api into main 2026-08-10 11:16:48 +00:00
Member

Phase 1 of the module system — the API contract, plus the throwaway spike that validates it.

What's here

website/MODULE_API.md — the normative contract between core and an installed module. Every member is derived from what the UO code actually imports today, re-read against the working tree; nothing speculative.

  • Part 1 versioning (MODULE_API_VERSION, and what is deliberately not contract)
  • Part 2 the server contract — module.json, the entry point, ctx, the register* calls, lifecycle, schema fragments, the OpenAPI fragment
  • Part 3 the client contract — window.__rg, the registry, the curated UI kit, the request primitive, the Vite library build
  • Part 4 the loader's obligations, including the two kinds of startup_failed
  • Part 5 enforcement (three CI checks)
  • Part 6 four places the survey contradicted MODULE_SYSTEM.md
  • Part 7 what the spike proved

The spike

website branch spike/module-atlas, cut from edge and deliberately never merged — it is evidence, not implementation. It carries the six public spawn-atlas routes into modules/uo/.

All three exit criteria met:

Criterion Result
No internal-file imports pass — module's only non-builtin requires are ctx.express/ctx.validator; the built chunk has zero bare specifiers and bundles no React
Zero-line routes.manifest.json diff pass — manifest and guards byte-identical
Chunk loads under enforced CSP pass — renders under script-src 'self', zero violation reports

729 core tests and 81 module tests pass. Verified end to end against a real database: schema fragment replays after core's, onBoot runs the atlas refresh, the six API URLs answer unchanged.

Decisions taken in this PR

Both approved by the org lead on 2026-08-10:

  1. OpenAPI: fragment merge (§6.1). Modules ship a swagger-fragment.json; core merges started modules' fragments into /api/docs.json and always wins a key collision. Needed because swagger-autogen statically parses app.js and cannot follow a dynamic loader — unlike routeManifest.js, which walks the live Express stack. The spike confirmed this empirically: regenerating the spec silently deleted all 361 lines of atlas paths with Swagger-autogen: Success, while the manifest kept all six in the same run.
  2. A curated, closed UI kit on window.__rg (§3.4). The atlas pages import five core modules beyond React, so a module either reaches into core's tree or ships copies that drift. Adding to the kit is a minor version bump; changing a member's props is a major one.

What the spike changed in the contract

  • ctx.express / ctx.validator — a module lives outside server/, so Node never reaches server/node_modules and require('express') fails outright. This is the server-side twin of the one-React rule that §2.6 only had for the client.
  • window.__rg.jsxRuntime — so a module builds with the automatic JSX runtime its tooling already assumes.

Also corrected

27 UO tables, not 25. Six atlas routes, not five. MODULE_SYSTEM.md gains a pointer to the contract and the corrected count; README.md gains index rows for both docs.


  • AI-assisted: written with Claude Code (Claude Opus 5)

🤖 Generated with Claude Code

Phase 1 of the [module system](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/MODULE_SYSTEM.md) — the API contract, plus the throwaway spike that validates it. ## What's here **`website/MODULE_API.md`** — the normative contract between core and an installed module. Every member is derived from what the UO code actually imports today, re-read against the working tree; nothing speculative. - **Part 1** versioning (`MODULE_API_VERSION`, and what is deliberately *not* contract) - **Part 2** the server contract — `module.json`, the entry point, `ctx`, the `register*` calls, lifecycle, schema fragments, the OpenAPI fragment - **Part 3** the client contract — `window.__rg`, the registry, the curated UI kit, the request primitive, the Vite library build - **Part 4** the loader's obligations, including the two kinds of `startup_failed` - **Part 5** enforcement (three CI checks) - **Part 6** four places the survey contradicted `MODULE_SYSTEM.md` - **Part 7** what the spike proved ## The spike `website` branch [`spike/module-atlas`](https://gitea.whitlocktech.com/RunicGateway/website/src/branch/spike/module-atlas), cut from `edge` and **deliberately never merged** — it is evidence, not implementation. It carries the six public spawn-atlas routes into `modules/uo/`. All three exit criteria met: | Criterion | Result | | --- | --- | | No internal-file imports | **pass** — module's only non-builtin requires are `ctx.express`/`ctx.validator`; the built chunk has zero bare specifiers and bundles no React | | Zero-line `routes.manifest.json` diff | **pass** — manifest *and* guards byte-identical | | Chunk loads under enforced CSP | **pass** — renders under `script-src 'self'`, zero violation reports | 729 core tests and 81 module tests pass. Verified end to end against a real database: schema fragment replays after core's, `onBoot` runs the atlas refresh, the six API URLs answer unchanged. ## Decisions taken in this PR Both approved by the org lead on 2026-08-10: 1. **OpenAPI: fragment merge** (§6.1). Modules ship a `swagger-fragment.json`; core merges started modules' fragments into `/api/docs.json` and always wins a key collision. Needed because `swagger-autogen` statically parses `app.js` and cannot follow a dynamic loader — unlike `routeManifest.js`, which walks the live Express stack. **The spike confirmed this empirically:** regenerating the spec silently deleted all 361 lines of atlas paths with `Swagger-autogen: Success`, while the manifest kept all six in the same run. 2. **A curated, closed UI kit** on `window.__rg` (§3.4). The atlas pages import five core modules beyond React, so a module either reaches into core's tree or ships copies that drift. Adding to the kit is a minor version bump; changing a member's props is a major one. ## What the spike changed in the contract - **`ctx.express` / `ctx.validator`** — a module lives outside `server/`, so Node never reaches `server/node_modules` and `require('express')` fails outright. This is the server-side twin of the one-React rule that §2.6 only had for the client. - **`window.__rg.jsxRuntime`** — so a module builds with the automatic JSX runtime its tooling already assumes. ## Also corrected 27 UO tables, not 25. Six atlas routes, not five. `MODULE_SYSTEM.md` gains a pointer to the contract and the corrected count; `README.md` gains index rows for both docs. --- - [x] AI-assisted: written with Claude Code (Claude Opus 5) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
wtclaude added 3 commits 2026-08-10 10:30:40 +00:00
The normative contract between core and an installed module: the `ctx` handed
to a module's entry point, the `register*` calls, the client-side registry and
shared-dependency global, the schema-fragment rules, and the loader's
validation and failure obligations. Every member is derived from what the UO
code actually imports today, re-read against the working tree.

Part 6 records four places the survey contradicted MODULE_SYSTEM.md:

  • OpenAPI generation is STATIC analysis (swagger-autogen parses app.js as
    text), unlike routeManifest.js which walks the live Express stack. A
    filesystem-scanning loader is invisible to it, so module routes would be
    silently absent from swagger-output.json. Three options, one recommended;
    needs a decision before Phase 2.
  • The client contract is much larger than §2.1 implies — the atlas pages
    import five core modules beyond React, so the plan needs a curated UI kit
    and a request primitive on window.__rg.
  • shardVisibility is module-owned and the atlas depends on it, so the spike
    carries it; two copies coexist during the spike by design.
  • Two counts corrected: 27 UO tables (not 25), 6 atlas routes (not 5).

MODULE_SYSTEM.md gains a pointer to the contract and the corrected table count.

Co-Authored-By: Claude <noreply@anthropic.com>
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>
Part 7 of MODULE_API.md: the three exit criteria and their results, the two
contract changes the spike forced (ctx.express/ctx.validator and
window.__rg.jsxRuntime), the empirical confirmation of the OpenAPI split, the
loader's tested failure guarantees, and the three artifacts in the branch that
are consequences of stopping at six routes rather than intended shape.

Phase 1 is complete: docs/website/MODULE_API.md is written and the spike met
every exit criterion on website branch spike/module-atlas (cut from edge, never
merged).

Co-Authored-By: Claude <noreply@anthropic.com>
whitlocktech approved these changes 2026-08-10 11:16:41 +00:00
whitlocktech merged commit 27bfdc9152 into main 2026-08-10 11:16:48 +00:00
whitlocktech deleted branch docs/module-api 2026-08-10 11:16:49 +00:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RunicGateway/docs#124
No description provided.