feat: one generic image, with installed modules in installation-owned storage (ADR 0048)
CI / verify (pull_request) Failing after 47s
CI / infrastructure-smoke (pull_request) Skipped
CI / migration-smoke (pull_request) Skipped
CI / image-build (pull_request) Skipped
CI / deploy-smoke (same-origin) (pull_request) Skipped
CI / deploy-smoke (split) (pull_request) Skipped

The production image now contains zero feature modules and is the same for every
installation. Installed modules live in module storage that one Core resolver
selects from EKKLESIA_MODULES_DIR: the checkout's modules/ when unset, an
absolute path that must exist when set (/var/lib/ekklesia/modules in the image).
A relative, blank, missing, or unreadable configured directory fails closed with
a sanitized error that never contains the value.

- Host configuration reads EKKLESIA_MODULES_DIR. The API, migration plan, apply
  and validate, module validation, module enablement, and Doctor all resolve it
  the same way; the test-only API override still takes precedence.
- Doctor receives Core's migrations directory explicitly instead of deriving it
  from the modules directory's parent.
- The migration runner retains and reports the applied history of a module that
  is not installed, and plans and applies Core's and present modules' migrations
  around it. Core's history and every installed module's history stay strict: a
  missing or changed applied migration, or a downgrade, still fails. No down
  migrations, no data deletion.
- deploy/Dockerfile copies no module and installs nothing for the module
  template; .dockerignore excludes modules/ and the build refuses a context that
  contains it; the runtime has an empty root-owned /var/lib/ekklesia/modules and
  sets EKKLESIA_MODULES_DIR.
- deploy/compose.yaml bind-mounts EKKLESIA_MODULES_HOST_DIR read-only into api
  and migrate, with create_host_path disabled.
- scripts/check-image.sh proves the image holds no modules and that a populated
  modules/ directory leaves its runtime file system identical; CI runs it.
- The split deploy smoke now uses the same generic image: it boots with no
  modules, installs a self-contained test module packaged from the template
  (test-only), migrates and restarts, removes it (Core runs, planning reports the
  retained history, data survives), and restores it with its data.

Docs: deploy README (module storage, install/update/remove/restore, the runtime
modules target), .env.example, API configuration, module development guide,
testing, workflow, security, and the architecture overview.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ekklesia
2026-10-07 08:38:14 -05:00
co-authored by Claude Opus 5.5
parent 4d28a82937
commit e36b70d8d4
50 changed files with 2275 additions and 224 deletions
+12
View File
@@ -35,6 +35,18 @@ concurrency and release, exact destructive approvals, and sanitization against t
adapter. A Core-owned `pnpm doctor` dashboard combines the existing static discovery and read-only
migration-planning services into one concise readiness report.
**Ekklesia ships one generic image, and installed modules live outside it
([ADR 0048](docs/architecture/decisions/0048-generic-image-and-installed-module-storage.md)).** The
production image built from [`deploy/Dockerfile`](deploy/Dockerfile) contains zero feature modules
and is the same for every installation. Installed modules live in installation-owned module storage,
selected by `EKKLESIA_MODULES_DIR` (`/var/lib/ekklesia/modules` in the image, the checkout's
`modules/` when unset) and mounted read-only by the reference deployment. Each installed module is a
self-contained directory carrying its own production dependencies. Installing, updating, or removing
a module needs only a controlled restart and, when it has migrations, an explicit plan and apply.
Nothing is hot loaded. Removing a module's code keeps its data and its stored state; its applied
migration history is retained and reported, and blocks no other migration. Purge, a package format,
signing, and any installer or marketplace do not exist yet.
Phase 4A added Core-owned identity foundations: a Core-owned Better Auth database schema
(`ekklesia_core_user`, `ekklesia_core_session`, `ekklesia_core_account`,
`ekklesia_core_verification`), a first-class Core-owned migration source that coexists with