docs(website): settle the module loader's trigger and its collision check
Records what phase 2 PR 2 decided against the two things MODULE_API.md left open for it. 7.6 is settled as an explicit modules.load(tierRouters) call in app.js rather than a require-time scan: the loader needs the tier routers handed to it for the 4.3 check, which a require-time side effect cannot receive, and a require's position enforces an ordering constraint invisibly. 4.1 and 4.3 gain the mechanics that fall out -- where the call must sit in app.js and why in both directions, that mounting is a second pass after validation, and that core's prefix ownership is probed on the live tier routers with express's layer.match() rather than declared in a table that was already stale in the spike. 2.7 records PR 2 as done. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -454,6 +454,24 @@ half needs rethinking before Phase 2 builds on it.
|
||||
finds (`MODULE_SYSTEM.md` §1.12). The database is not consulted. `MODULES_DIR` defaults to
|
||||
`<repo>/modules` and is overridable by env for tests and for the Docker volume mount.
|
||||
|
||||
**The trigger is one explicit call, and there is no lazy self-scan** (§7.6). `app.js` calls
|
||||
|
||||
```js
|
||||
modules.load({ public: publicRouter, admin: adminRouter, player: playerRouter })
|
||||
```
|
||||
|
||||
exactly once, and every accessor throws until it has run rather than answering with an empty list —
|
||||
"no modules installed" is a real state, and a caller must not be handed it by accident. Its position
|
||||
in `app.js` is load-bearing in both directions: **after** `app.use('/api', apiRouter)`, so every core
|
||||
prefix is already on the tier routers when §4.3 asks them what core owns and so first-match-wins
|
||||
means a module cannot shadow a core route; **before** the `/api` 404, so a module route reaches its
|
||||
handler instead of the catch-all.
|
||||
|
||||
**Mounting is a second pass** over the modules that survived validation, not part of the scan loop.
|
||||
Otherwise the first module's layers sit on the tier router while the second is being validated,
|
||||
indistinguishable from core's — the second module would be told it collided with *core*, naming the
|
||||
wrong culprit, and the module-versus-module check would be unreachable.
|
||||
|
||||
### 4.2 Order
|
||||
|
||||
Alphabetical by `id`, deterministically. There is no dependency resolution between modules (§2.0 of
|
||||
@@ -472,6 +490,14 @@ other order would imply a precedence that is not being computed.
|
||||
|
||||
A failure at any step is that module's failure and nobody else's.
|
||||
|
||||
**Step 3 asks the live tier routers, not a list.** Whether core owns a prefix is answered by probing
|
||||
the tier router's own stack with express's `layer.match()`, skipping root-mounted (`fast_slash`)
|
||||
layers — `public/index.js` ends with `use('/', siteRouter)` and `admin/index.js` with the dashboard
|
||||
router, and both match every path, so counting them would report every prefix as taken and no module
|
||||
could ever mount. A hardcoded prefix table was tried in the spike and was already one prefix stale
|
||||
when it was written; deriving it means the check cannot drift the first time core adds a capability
|
||||
router, and needs no second declaration of the mount table.
|
||||
|
||||
### 4.4 `startup_failed` is a state, not a crash
|
||||
|
||||
Per `MODULE_SYSTEM.md` §2.4, the loader try/catches the **entire** lifecycle — require, validation,
|
||||
@@ -690,10 +716,15 @@ They are recorded so nobody reads them as intended shape:
|
||||
3. **The module has no `swagger-fragment.json`** — §6.1a's obligation needs core's merge helper on
|
||||
the other side of it, which is Phase 2.
|
||||
|
||||
### 7.6 A finding for Phase 2's loader
|
||||
### 7.6 A finding for Phase 2's loader — **settled: an explicit `load()`**
|
||||
|
||||
`scan()` is lazy — requiring the loader does not run it. That is deliberate (app.js decides when
|
||||
`scan()` was lazy — requiring the loader did not run it. That was deliberate (app.js decides when
|
||||
modules are discovered) but it is a sharp edge: a caller that requires the loader and reads nothing
|
||||
gets an empty, *silent* module list. It cost one confusing test failure during the spike. Phase 2
|
||||
should either make the trigger explicit in the name or scan at require time and let app.js order the
|
||||
require.
|
||||
gets an empty, *silent* module list. It cost one confusing test failure during the spike.
|
||||
|
||||
**Phase 2 PR 2 made the trigger explicit** rather than scanning at require time. `app.js` calls
|
||||
`modules.load(tierRouters)` once, and `list()` throws until it has. Scanning on require was the
|
||||
alternative and was rejected for two reasons: the loader now needs the tier routers *handed to it*
|
||||
for the §4.3 collision check, which a require-time side effect cannot receive; and it would make the
|
||||
ordering constraint invisible, enforced by where a `require` sits rather than by an argument that is
|
||||
missing if it is wrong.
|
||||
|
||||
Reference in New Issue
Block a user