diff --git a/website/BACKEND_DESIGN.md b/website/BACKEND_DESIGN.md index e344f47..032ab13 100644 --- a/website/BACKEND_DESIGN.md +++ b/website/BACKEND_DESIGN.md @@ -735,9 +735,16 @@ slashes from generated path keys — see *Regenerating the spec* in the website domain split makes that necessary. Scope: the manifest keeps `/api/**` and `/.well-known/**` from the public app plus everything on the -internal listener. The SPA catch-all, `/uploads` and `/brand` are filesystem-conditional static -mounts — not API contract, and including them would make the output depend on whether CI had built -the client. +internal listener. The SPA catch-all, `/uploads`, `/brand` and `/modules` are filesystem-conditional +static mounts — not API contract, and including them would make the output depend on whether CI had +built the client, or on which modules happened to be on the volume of the machine that generated it. + +`/modules//` is the last of those and the newest: an installed module's prebuilt client chunk, +served from the directory its `client.entry` sits in and never from the module root, behind the +module's own state guard (`503` when it failed to start, `404` when disabled) and with +`Cache-Control: no-cache`, because Vite's library build emits an unhashed `entry.js`. Anything else +under `/modules` is a `404` rather than the SPA shell. The full contract is +[`MODULE_API.md`](MODULE_API.md) §3.1. A third generated file, `server/routes.guards.json`, is a **review aid and not a contract**: per route, the middleware handler count plus the *named* middleware on its mount chain. It exists because a diff --git a/website/MODULE_API.md b/website/MODULE_API.md index cd35eba..f350220 100644 --- a/website/MODULE_API.md +++ b/website/MODULE_API.md @@ -85,7 +85,7 @@ rejected rather than ignored, so a typo is a loud failure and not a silently-ine | `version` | yes | Semver. Recorded in `installed_modules`; shown on failure. | | `coreApi` | yes | Semver range checked against `MODULE_API_VERSION` (§1.1). | | `server` | no | Entry point, relative to the module root. Absent ⇒ client-only module. | -| `client.entry` | no | Prebuilt ESM chunk, relative to the module root. Absent ⇒ server-only module. | +| `client.entry` | no | Prebuilt ESM chunk, relative to the module root, and **in a subdirectory** — the directory it sits in is what gets served (§3.1). Absent ⇒ server-only module; present-but-empty is rejected, since it claims a client half and delivers none. | | `schema` | no | Idempotent SQL fragment (§2.6). | | `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. | @@ -430,17 +430,50 @@ Exactly as `MODULE_SYSTEM.md` §2.6 resolved, and Phase 1's spike is what proves 1. Module CI builds `client/dist/entry.js` with Vite in **library mode**, `react`, `react-dom`, `react-dom/client` and `react-router-dom` declared **external**. -2. Core serves the module directory statically at `/modules//` — same-origin, so +2. Core serves the **directory the entry sits in** statically at `/modules//` — same-origin, so `script-src 'self'` (`config/csp.js:49`) admits it with no nonce and no inline. -3. `utils/htmlShell.js` injects `