diff --git a/Repo-docs-MODULE-ARCHITECTURE.-.md b/Repo-docs-MODULE-ARCHITECTURE.-.md index f4d73b4..33fab33 100644 --- a/Repo-docs-MODULE-ARCHITECTURE.-.md +++ b/Repo-docs-MODULE-ARCHITECTURE.-.md @@ -1,4 +1,4 @@ - + > Mirrored from `/mnt/DATA/git/govoplan-core/docs/MODULE_ARCHITECTURE.md`. > Origin: `repository`. @@ -334,6 +334,101 @@ Any future exception is extraction debt and must be temporary, documented in the script with a reason, and removed when a capability/API/event contract replaces it. +## Module Lifecycle + +Core exposes the installed module catalog through the admin API and WebUI. The +current lifecycle model separates four states: + +- installed: the Python/WebUI package is available to the process +- active: the module is present in the running platform registry +- desired: the module should be active on the next server startup +- planned package change: an operator-reviewed package install/uninstall item + saved in system settings but not executed by the running server + +The admin module manager can change the desired enabled set and apply it to the +running server. It always keeps `tenancy`, `access`, and `admin` enabled when +saving through the admin UI, and it adds required module dependencies before +saving the desired state. On startup, core always keeps the minimum +authenticated platform set `tenancy`/`access` enabled and keeps `admin` enabled +when the operator configuration includes it. Unknown saved module ids are +ignored when the matching package is no longer installed. The core app factory, +devserver, development bootstrap, background worker registry, and migration +metadata plan all read the saved desired state from `system_settings` before +building their module registry. + +Hot enable/disable is a core design principle for every module: + +- Core keeps one mutable active `PlatformRegistry` object and swaps its manifest + set through the module lifecycle manager. Modules must read module presence, + optional integrations, permissions, role templates, capabilities, navigation, + and frontend contributions from that registry instead of caching sibling + module availability. +- Core validates install state and dependency closure before activation. +- Core applies configured module migrations before activation. Deactivation + never drops tables or data. +- Core mounts module routers once and guards them by active module state. A + deactivated module's routes remain mounted internally but return a disabled + module response until the module is active again. +- Module route factories must be side-effect-light and idempotent. They may + configure module runtime references, but they must not start workers, + schedulers, or irreversible external subscriptions. Use lifecycle hooks for + those resources. +- Core refreshes the active registry before frontend metadata is returned from + `/api/v1/platform/modules`; the WebUI shell refetches this metadata after + module changes so navigation, routes, and UI capabilities update without a + page reload. +- Modules can provide `on_activate` and `on_deactivate` hooks for worker, + scheduler, cache, or external subscription lifecycle. These hooks must be + idempotent and must not mutate another module directly. +- Package install/uninstall remains a deployment/operator action unless the + runtime provides a trusted package installer and rollback path. The admin UI + can activate/deactivate installed packages; it must not remove package files + or attempt dependency-manager operations in-process. + +The package install-plan API records operator intent only: + +- `GET /api/v1/admin/system/modules/install-plan` reads the saved plan and + renders shell commands for the operator. +- `PUT /api/v1/admin/system/modules/install-plan` saves planned install or + uninstall rows. Install rows must use tagged package or git references, not + local `file:`/workspace paths. +- `DELETE /api/v1/admin/system/modules/install-plan` clears the plan. +- `govoplan-module-install-plan --format shell` or + `python -m govoplan_core.commands.module_install_plan --format shell` renders + the same commands from a server shell. + +The running server intentionally reports `install_uninstall_supported=false` +and `package_mutation_supported=false`. This is by design: mutating the +interpreter environment, npm dependency graph, frontend bundle, migrations, and +worker process set from inside the same running app is not safe without a +separate trusted installer, rollback model, and bundle loader. + +## Maintenance Mode + +Maintenance mode is the required operating state for package install/uninstall +and other disruptive system maintenance. + +Core stores maintenance mode in `system_settings.settings.maintenance_mode`. +The public platform status endpoint exposes only the flag and message so the +WebUI can show a clear login-screen notice. Login remains reachable during +maintenance so an operator can sign in. + +Authenticated API access is enforced at the access-principal boundary. When +maintenance mode is enabled, authenticated requests require the system scope +`system:maintenance:access`; otherwise the API returns `503 Service +Unavailable` with a maintenance-mode detail payload. The protected +`system_owner` role grants this through `system:*`. A dedicated +`maintenance_operator` role exists for accounts that should be able to access +the system during maintenance without receiving broad write permissions. + +Changing the maintenance-mode flag requires both system settings write access +and `system:maintenance:access`, so an administrator cannot accidentally enable +a mode they cannot use. + +The first implementation is a platform access gate. It does not replace +database backups, process supervision, migration checks, or external load +balancer maintenance pages. + ## Build And Verification Backend verification from core: