feat(modules): publish the installed-module list at /api/v1/public/modules #133
Reference in New Issue
Block a user
No description provided.
Delete Branch "feature/module-public-endpoint"
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 6 of
docs/website/MODULE_SYSTEM.md§2.7 — the first module-system URL a client can see. The SPA and the Android app feature-detect against thecapabilitiesa module declares. Docs half: docs#131 (MODULE_API.md§2.9 is the normative shape).What the payload does not contain is most of the design
startedmodules only. Adisabledorstartup_failedmodule is absent — exactly as §4.4 already leaves its routes and its nav absent — so a client renders a site without that capability rather than advertising one that 503s.state,failure_stageorfailure_reason. Where a module broke belongs to the admin Modules screen, and the reason is an exception string from inside core. Not anonymous-visitor business.clientchunk URL.htmlShellinjects a<script type="module">per started module (API §3.1.3), so the browser is handed the tag rather than a URL to fetch. This endpoint feature-detects; it does not load.MODULE_SYSTEM.md§2.6 step 4 is amended to match (API §6.7).siteModegate and no database — the same class as/public/statusand/public/version, so a client can still feature-detect while the site is in maintenance.Why it is a router of its own
/modulesis a capability router, not a fifth singleton insite.router.js, and that part is load-bearing. The loader's prefix-collision probe (ownedByCore) reads the live tier stack and skips root-mounted layers, because ause('/', …)matches every path — so a route declared inside the root-mounted site router is invisible to it. Mountinguse('/modules', …)is what makes "no module may ever claim/modules" a rule the loader enforces rather than a convention a reviewer has to remember.Verification
state, no stage, no reason, no extra key, and a failed module does not hide the ones that started. The suite runs with the pool pointed at a dead port, so "this endpoint never touches a database" is proved by the tests not having to stub a query.routes.manifest.jsongains exactly the one route, 229 → 230 (228 public + 2 internal).routes.guards.jsonrecords it with an emptygateslist, which is itself the assertion that it is ungated.npm run swaggerregenerated: the operation plus thePublicModules/PublicModuleschemas, nothing else moved.Phase 2's exit criterion is restated in docs#131 to say what it always meant: no existing URL moves. PR 6 is the single deliberate addition in the phase.
Decisions
All four settled with the org lead before implementation; all four the recommended option.
{ modules: [...] },startedonly, no state field/modulescapability routerhtmlShellinjects the tagsiteMode, DB-free🤖 Generated with Claude Code
https://claude.ai/code/session_018ocYxQWk3EhZe5gWRJXFU8
Phase 2, PR 6 of docs/website/MODULE_SYSTEM.md 2.7 — the first module-system URL a client can see. The SPA and the Android app feature-detect against the capabilities a module declares; the shape is settled in MODULE_API.md 2.9. Four decisions, and what is absent from the payload is most of the design: * started modules only. A module that is disabled or failed to load is ABSENT, exactly as 4.4 already leaves its routes and its nav absent, so a client renders a site without that capability rather than advertising one that 503s. * no state, failure_stage or failure_reason. Where a module broke belongs to the admin Modules screen, and the reason is an exception string from inside core — not anonymous-visitor business. * no client chunk URL. htmlShell injects a script tag per started module (3.1.3), so the browser is handed the tag rather than a URL to fetch. This endpoint feature-detects; it does not load. MODULE_SYSTEM 2.6 step 4 is amended to match (API 6.7). * no siteMode gate and no database — the same class as /public/status and /public/version, so a client can still feature-detect during maintenance. It is a capability router of its own rather than a fifth singleton in site.router.js, and that is load-bearing: the loader's prefix-collision probe reads the live tier stack and skips root-mounted layers, because a use('/', ...) matches every path. A route inside the root-mounted site router would be invisible to it — mounting use('/modules', ...) is what makes "no module may claim /modules" a rule the loader enforces. 910 tests pass (+9, every one on the boundary — what must NOT appear). routes.manifest.json gains exactly the one route and routes.guards.json records it with an empty gates list, which is itself the assertion that it is ungated. Co-Authored-By: Claude <noreply@anthropic.com>