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.
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]>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Ekklesia had no notion of a module being on or off: the accepted module set
was exactly "whatever discovery found on disk", and
ModuleLifecycleStagenamed 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.ekklesia_core_module_state, migration 0004) holding onlymodule_idandenabled. A module with no record is enabled by default, sodeploying 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.
resolveModuleEligibilityis pure: a disabled module transitively blocksevery 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.
startApistarts only the eligible set and builds the UI publicationregistry 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.
failedruntime state.
pnpm modules:enable/modules:disableis the out-of-bandrecovery path for a module that breaks startup.
configuration, runtime, and
restartRequiredseparately rather than claiminga module stopped when it has not.
and discovered inventories respectively, unchanged.
core.modules.lifecycle.managegates bothreading and changing enablement, at a sibling prefix (
/api/v1/module-lifecycle)that cannot collide with the module route namespace.
pnpm doctorgains amodules.lifecyclecheck so "why is this moduleunavailable?" is answerable without reading logs.
See ADR 0028.
Co-Authored-By: Claude Opus 5 [email protected]