feat(modules): the filesystem module loader (phase 2, PR 2) #129
Reference in New Issue
Block a user
No description provided.
Delete Branch "feat/modules-loader"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Phase 2 PR 2 of
docs/website/MODULE_SYSTEM.md§2.7. Docs half: RunicGateway/docs#127.Adds
server/src/modules/{loader,semver,version}.js— the synchronous scan ofMODULES_DIR, manifest validation, prefix and table-name collision rejection, per-module try/catch across the load path, and the mount into the three tier routers behind theMODULE_API.md§4.5 dispatch guard.The two decisions the contract left open
1. The load trigger is one explicit call, not a lazy scan (API §7.6).
app.jscallsmodules.load({ public, admin, player })once; every accessor throws until it has run, because "no modules installed" is a real answer a caller must not be handed by accident — the spike's silent empty list cost one confusing test failure. Scanning at require time was the alternative and was rejected twice over: the loader now needs the tier routers handed to it, which a require-time side effect cannot receive, and arequire's position enforces an ordering constraint invisibly.2. Core's prefix ownership is asked of the live tier routers, via express's own
layer.match(), skipping root-mounted (fast_slash) layers — the spike hardcoded ~24 prefixes and the table was already stale when 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.One thing found while building it
Mounting is a second pass, after every module is validated. Mounting inside the scan loop puts the first module's layers on the tier router while the second is still being validated, indistinguishable from core's: the second module claiming a taken prefix was told it collided with core, and the module-versus-module check below it was unreachable. Caught by a test that expected the other message.
Scope
registerExtension/registerNotificationStreams/registerAnnounceLegandonBoot/onShutdownthrownot available until phase 2 PR 4/5rather than no-op — an accepting stub would let a module believe it had registered something and fail silently at the far end. No schema replay (PR 3), no boot dispatch orinstalled_modulesreconcile (PR 5); until then a record's state is in memory only, moved by the one narrowsetState(id, state, reason)those two PRs will call.Verification
routes.manifest.jsondiff is zero lines, 229 routes — the Phase 2 exit criterion.routes.guards.jsonlikewise.swagger-output.jsonregenerates byte-identical; the new require and call inapp.jsdo not disturb swagger-autogen's static analysis (the §6.1 trap).Phase 2 PR 2 of docs/website/MODULE_SYSTEM.md 2.7. Adds server/src/modules/{loader,semver,version}.js: the synchronous scan of MODULES_DIR, manifest validation, prefix and table-name collision rejection, per-module try/catch and the mount into the three tier routers behind the MODULE_API.md 4.5 dispatch guard. Two decisions the contract left open, both now written up there: - The load trigger is one explicit modules.load(tierRouters) call in app.js, not a lazy scan (API 7.6). Accessors throw until it has run, because "no modules installed" is a real answer a caller must not be handed by accident. - Whether core owns a prefix is asked of the live tier routers via express's own layer.match(), skipping root-mounted layers, rather than a hardcoded table -- the spike's was already stale when written (API 4.3). Mounting is a second pass after every module is validated. Doing it inside the scan loop makes the first module's layers indistinguishable from core's, so the second module claiming a taken prefix is told it collided with core and the module-versus-module check is unreachable. registerExtension/NotificationStreams/AnnounceLeg and onBoot/onShutdown throw "not available until phase 2 PR 4/5" rather than no-op; an accepting stub would let a module believe it had registered something. No schema replay, no boot dispatch, no installed_modules reconcile -- those are PRs 3 and 5, and until PR 5 a record's state is in memory only. No module ships on the volume, so nothing an operator or client can see changes: 842 tests pass, routes.manifest.json is unchanged at 229 routes and swagger-output.json regenerates byte-identical. Co-Authored-By: Claude <noreply@anthropic.com>