feat: add the minimal operational module lifecycle #27

Merged
Zayin_Admin merged 1 commits from phase-7a/module-lifecycle into main 2026-08-23 02:02:14 +00:00
Owner

Ekklesia had no notion of a module being on or off: the accepted module set
was exactly "whatever discovery found on disk", and ModuleLifecycleStage
named nine states nothing produced or consumed. This replaces that vocabulary
with two axes that real code produces — persistent, administrator-owned
configuration (enabled/disabled) and per-boot runtime observation
(running/disabled/blocked) — and wires them through startup.

  • One Core table (ekklesia_core_module_state, migration 0004) holding only
    module_id and enabled. A module with no record is enabled by default, so
    deploying a module is still sufficient to run it and discovery never needs an
    initialization write. A record for an undeployed module is retained and
    ignored, so redeploying restores the decision.
  • resolveModuleEligibility is pure: a disabled module transitively blocks
    every module that requires it (module and capability edges alike) with a
    structured reason naming the provider; optional dependencies never block; and
    a missing, incompatible, or cyclic dependency stays discovery's concern.
  • startApi starts only the eligible set and builds the UI publication
    registry from that same list, so routes, artifacts, browser inventory, and
    Dashboard navigation all follow one authoritative set. A disabled module is
    never imported, so there is no per-request enablement check anywhere.
  • Startup stays all-or-nothing, deliberately; there is consequently no failed
    runtime state. pnpm modules:enable/modules:disable is the out-of-band
    recovery path for a module that breaks startup.
  • Changes take effect at the next start, and the read model reports
    configuration, runtime, and restartRequired separately rather than claiming
    a module stopped when it has not.
  • Migration participation and permission composition still span the deployed
    and discovered inventories respectively, unchanged.
  • New restricted platform permission core.modules.lifecycle.manage gates both
    reading and changing enablement, at a sibling prefix (/api/v1/module-lifecycle)
    that cannot collide with the module route namespace.
  • pnpm doctor gains a modules.lifecycle check so "why is this module
    unavailable?" is answerable without reading logs.

See ADR 0028.

Co-Authored-By: Claude Opus 5 [email protected]

Ekklesia had no notion of a module being on or off: the accepted module set was exactly "whatever discovery found on disk", and `ModuleLifecycleStage` named nine states nothing produced or consumed. This replaces that vocabulary with two axes that real code produces — persistent, administrator-owned configuration (`enabled`/`disabled`) and per-boot runtime observation (`running`/`disabled`/`blocked`) — and wires them through startup. - One Core table (`ekklesia_core_module_state`, migration 0004) holding only `module_id` and `enabled`. A module with no record is enabled by default, so deploying a module is still sufficient to run it and discovery never needs an initialization write. A record for an undeployed module is retained and ignored, so redeploying restores the decision. - `resolveModuleEligibility` is pure: a disabled module transitively blocks every module that *requires* it (module and capability edges alike) with a structured reason naming the provider; optional dependencies never block; and a missing, incompatible, or cyclic dependency stays discovery's concern. - `startApi` starts only the eligible set and builds the UI publication registry from that same list, so routes, artifacts, browser inventory, and Dashboard navigation all follow one authoritative set. A disabled module is never imported, so there is no per-request enablement check anywhere. - Startup stays all-or-nothing, deliberately; there is consequently no `failed` runtime state. `pnpm modules:enable`/`modules:disable` is the out-of-band recovery path for a module that breaks startup. - Changes take effect at the next start, and the read model reports configuration, runtime, and `restartRequired` separately rather than claiming a module stopped when it has not. - Migration participation and permission composition still span the deployed and discovered inventories respectively, unchanged. - New restricted platform permission `core.modules.lifecycle.manage` gates both reading and changing enablement, at a sibling prefix (`/api/v1/module-lifecycle`) that cannot collide with the module route namespace. - `pnpm doctor` gains a `modules.lifecycle` check so "why is this module unavailable?" is answerable without reading logs. See ADR 0028. Co-Authored-By: Claude Opus 5 <[email protected]>
Zayin_Admin added 1 commit 2026-08-23 02:00:13 +00:00
feat: add the minimal operational module lifecycle
CI / verify (pull_request) Successful in 35s
CI / infrastructure-smoke (pull_request) Successful in 13s
CI / migration-smoke (pull_request) Successful in 46s
7729c99e24
Ekklesia had no notion of a module being on or off: the accepted module set
was exactly "whatever discovery found on disk", and `ModuleLifecycleStage`
named nine states nothing produced or consumed. This replaces that vocabulary
with two axes that real code produces — persistent, administrator-owned
configuration (`enabled`/`disabled`) and per-boot runtime observation
(`running`/`disabled`/`blocked`) — and wires them through startup.

- One Core table (`ekklesia_core_module_state`, migration 0004) holding only
  `module_id` and `enabled`. A module with no record is enabled by default, so
  deploying a module is still sufficient to run it and discovery never needs an
  initialization write. A record for an undeployed module is retained and
  ignored, so redeploying restores the decision.
- `resolveModuleEligibility` is pure: a disabled module transitively blocks
  every module that *requires* it (module and capability edges alike) with a
  structured reason naming the provider; optional dependencies never block; and
  a missing, incompatible, or cyclic dependency stays discovery's concern.
- `startApi` starts only the eligible set and builds the UI publication
  registry from that same list, so routes, artifacts, browser inventory, and
  Dashboard navigation all follow one authoritative set. A disabled module is
  never imported, so there is no per-request enablement check anywhere.
- Startup stays all-or-nothing, deliberately; there is consequently no `failed`
  runtime state. `pnpm modules:enable`/`modules:disable` is the out-of-band
  recovery path for a module that breaks startup.
- Changes take effect at the next start, and the read model reports
  configuration, runtime, and `restartRequired` separately rather than claiming
  a module stopped when it has not.
- Migration participation and permission composition still span the deployed
  and discovered inventories respectively, unchanged.
- New restricted platform permission `core.modules.lifecycle.manage` gates both
  reading and changing enablement, at a sibling prefix (`/api/v1/module-lifecycle`)
  that cannot collide with the module route namespace.
- `pnpm doctor` gains a `modules.lifecycle` check so "why is this module
  unavailable?" is answerable without reading logs.

See ADR 0028.

Co-Authored-By: Claude Opus 5 <[email protected]>
Zayin_Admin merged commit fdaad58848 into main 2026-08-23 02:02:14 +00:00
Zayin_Admin deleted branch phase-7a/module-lifecycle 2026-08-23 02:02:14 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Zayin/Ekklesia#27