The documentation still described Phase 5D as in progress, Phase 6 as blocked, and a disposable-runner isolation proof as a prerequisite for opening the real pull request. All five passes and the independent whole-repository re-audit have landed, so those statements no longer match the accepted operating model. The README's current status now records Phase 5D as complete under the current trusted-contributor, not-yet-deployed threat model, with Phase 6 unblocked, and gains a pass 5 closure paragraph covering the re-audit, the repository-local supply-chain and dependency fixes, the accepted trusted-code-only CI boundary, and what remains required before untrusted CI or a public launch. The pass 1-4 paragraphs keep their technical content unchanged; only their tense changes, so each reads as what was outstanding at the close of that pass rather than as a current claim. `platform-architecture.md` no longer stops at pass 1 or says passes 2-5 are unfinished; it summarizes all five passes and defers the remaining operational limitations to the security document. `security.md` states the trusted-code-only operating rule explicitly and replaces "CI blockers before the real pull request is opened" with "Requirements before executing untrusted contributions", framing the five hardening items as prerequisites for relaxing that rule rather than as Phase 6 blockers, and naming an isolated hosted-runner service as an accepted alternative. The Gitea 1.24.2 warnings, the scope of `persist-credentials: false`, and the unverified runner topology and job-token authority are preserved verbatim; no external Gitea setting is claimed. The launch checklist is marked as gating a public launch, not development. Both Doctor examples are corrected to the real three-migration output, adding `core:0002_authorization_integrity · 634294731180`. The stale sentence calling Core authorization the next architectural phase is refreshed. ADRs 0017-0020 are untouched: their statements that later passes were outstanding are historical records made when each was accepted. Documentation only — no production code, test, workflow, dependency, migration, or schema change. Co-Authored-By: Claude Opus 5 <[email protected]>
159 lines
10 KiB
Markdown
159 lines
10 KiB
Markdown
# Platform architecture
|
|
|
|
Ekklesia is a long-lived platform from which churches can compose many applications and user
|
|
experiences. It begins as a modular monolith so contracts can stabilize before distribution is
|
|
introduced.
|
|
|
|
> **Trust boundary.** Installed modules are trusted compiled backend code, not sandboxed
|
|
> plugins: a module runs arbitrary JavaScript in the API process and shares its PostgreSQL
|
|
> role. The layering described below is a correctness and ownership boundary, not hostile-code
|
|
> isolation. See [Security model and deployment trust boundaries](../development/security.md).
|
|
|
|
## Dependency model
|
|
|
|
The arrows below mean "depends on." Presentation and composition depend on stable lower-level
|
|
contracts; infrastructure never learns individual feature names.
|
|
|
|
```text
|
|
Surface Builder configuration -> Surfaces -> public capability contracts
|
|
^
|
|
|
|
|
HTTP/API and client contracts
|
|
|
|
|
Feature Modules -> Module SDK / platform contracts <- Core implementations
|
|
```
|
|
|
|
The four ownership layers are:
|
|
|
|
1. **Core** orchestrates generic platform infrastructure: startup, identity, permissions,
|
|
configuration, database connectivity, storage, events, jobs, module discovery, and lifecycle.
|
|
2. **Modules** own backend domain behavior, data schemas, resources, actions, routes, events,
|
|
permissions, and jobs.
|
|
3. **Surfaces** own every presentation concern: components, renderers, layout, navigation,
|
|
accessibility, interaction, branding, and client state.
|
|
4. **Surface Builder** stores validated declarative composition. It selects capabilities and
|
|
surface-owned renderers but contains neither executable code nor module business logic.
|
|
|
|
Modules do not own UI. For example, Calendar data, actions, and permissions will live in
|
|
`modules/calendar`; Calendar renderers for a dashboard or public website will live in those
|
|
surfaces.
|
|
|
|
## Stable seams
|
|
|
|
- Modules use public SDK ports and injected Core services, never undocumented Core internals.
|
|
- Modules do not import another module's schema or private code. Integrations use public service
|
|
contracts, capabilities, events, or APIs.
|
|
- Surfaces consume API/client and capability contracts, never feature-module packages.
|
|
- Core discovers feature modules generically. It contains no feature-specific imports or
|
|
conditional branches.
|
|
- Deployable apps are thin composition roots and do not become domain-logic owners.
|
|
- Generated bootstrap artifacts may connect discovered entry points at the application edge;
|
|
they do not become manually maintained Core registries.
|
|
|
|
## Security invariants
|
|
|
|
- Authorization is enforced server-side.
|
|
- Secrets are isolated from public configuration and logs.
|
|
- Authentication tokens, private form content, prayer requests, and pastoral information are not
|
|
logged.
|
|
- Sensitive actions are designed for auditability without copying sensitive content into audit
|
|
records.
|
|
- Private objects use signed access and namespaced storage paths.
|
|
- Every network service starts from least-privilege and local-only development defaults.
|
|
|
|
## Current implementation status
|
|
|
|
Implemented now: the Phase 1 repository and infrastructure foundation plus public module contracts,
|
|
static versioned manifests, independent capability versions and dependency ranges, automatic
|
|
filesystem discovery, compiled server loading, runtime-definition verification, staged capability
|
|
registration, dependency-ordered startup and reverse shutdown, rollback, and a backend-only module
|
|
template. Optional compiled schema entrypoints now contribute validated, namespaced module-owned
|
|
Drizzle tables to a deterministic aggregate without connecting to a database. Optional module-owned
|
|
Drizzle migration directories now produce an atomic dependency-ordered offline inventory with
|
|
validated metadata, explicit destructive-change declarations, and exact SHA-256 checksums. Core can
|
|
now compare that inventory with its fixed PostgreSQL history table, produce a read-only plan, and
|
|
apply the complete pending batch under a session advisory lock and one transaction. Destructive
|
|
migrations require an exact externally supplied approval bound to module, identity, and checksum.
|
|
Hermetic integration coverage now exercises those contracts through the production adapter against
|
|
the pinned PostgreSQL container, including real DDL rollback and advisory-lock concurrency. The
|
|
Core-owned Doctor composes those existing services into a deterministic terminal readiness report:
|
|
static module, schema, and migration validation always run when their prerequisites succeed, while
|
|
PostgreSQL history planning runs only when configured. Schema inspection evaluates compiled schema
|
|
definition entrypoints, which the module contract requires to be side-effect-free; Doctor never
|
|
imports or starts module server entrypoints. It does not apply migrations or modify PostgreSQL, and
|
|
it honestly skips object storage because no production Core storage service exists yet.
|
|
|
|
Phase 4A added the Core identity foundation. Core owns a Better Auth-backed identity schema
|
|
(`ekklesia_core_user`, `ekklesia_core_session`, `ekklesia_core_account`,
|
|
`ekklesia_core_verification`) reachable only through Ekklesia-owned contracts and an explicit
|
|
Core-owned Better Auth construction boundary; modules never import Better Auth or receive a raw
|
|
database client. That schema ships as a first-class Core-owned migration source that reuses the
|
|
exact migration format, checksum rule, planner, and transactional runner Phase 3 already
|
|
established, ordered before module migrations in the same aggregate. See
|
|
[ADR 0010](decisions/0010-core-identity-and-better-auth-adoption.md).
|
|
|
|
Phase 4B adds identity HTTP transport: a minimal Hono-based `apps/api` composition root mounts
|
|
Better Auth at `/api/auth/*` and exposes one Ekklesia-owned route, `GET /api/v1/principal`, behind
|
|
Core's own Fetch-compatible identity runtime (`@ekklesia/core/identity`) — Core still does not
|
|
depend on Hono. Public email/password registration is the intended eventual product policy — every
|
|
account receives identical lowest-privilege baseline status, and signup order never grants
|
|
elevation — but is disabled by default as of Phase 5D pass 1, below. See
|
|
[ADR 0011](decisions/0011-identity-http-transport-and-public-enrollment.md) and
|
|
[the API guide](../development/api.md), and
|
|
[the identity development guide](../development/identity.md) for the underlying schema and
|
|
contracts. It is now possible to publicly register, sign in, resolve a principal, and sign out.
|
|
|
|
Phase 4C is complete: Core's authorization foundation — a schema, evaluator, and store for
|
|
platform owners, explicit grants, and relationship-derived grants, plus an immutable permission
|
|
registry — and an explicit, single-use, auditable platform-owner bootstrap command, plus reusable
|
|
Hono enforcement and an `/api/v1/grants` HTTP API in `apps/api`. See
|
|
[ADR 0013](decisions/0013-core-authorization-and-owner-bootstrap.md) and
|
|
[the authorization guide](../development/authorization.md).
|
|
|
|
Phase 5A wired module-declared permissions into that same authorization system — a version-3
|
|
manifest, one composed platform permission registry spanning Core's own declarations and every
|
|
installed module's, enforced through the identical middleware Core's own routes use. Phase 5B let a
|
|
module mount its own framework-neutral HTTP routes in production, under
|
|
`/api/v1/modules/<module-id>`, enforced and response-sanitized exactly like Core's own routes. Phase
|
|
5C added typed module database access (`context.services.database`) for a module's lifecycle hooks
|
|
and authorized HTTP handlers, over the platform's one existing PostgreSQL pool.
|
|
See [ADR 0014](decisions/0014-module-permission-declarations-and-platform-registry.md),
|
|
[ADR 0015](decisions/0015-static-module-http-routing-and-runtime-lifecycle.md),
|
|
[ADR 0016](decisions/0016-module-database-access.md), and the repository root
|
|
[README](../../README.md#current-status) for the complete, current status of every phase.
|
|
|
|
**Phase 5D is complete.** Five passes hardened the platform against an independent security audit of
|
|
the Phase 5C checkpoint, and an independent whole-repository re-audit closed the phase:
|
|
|
|
1. the public request perimeter — a server-owned client-address resolution boundary for the
|
|
authentication rate limiter, public sign-up disabled by default behind an explicit typed opt-in,
|
|
an enforced request-body ceiling on every public route (including a module route's own declared
|
|
contract), and finite Node header/request timeouts;
|
|
2. identity session resolution, and a mandatory certificate- **and** hostname-verified PostgreSQL
|
|
transport with no plaintext or `rejectUnauthorized: false` exception;
|
|
3. durable authorization history that survives user deletion, a one-time owner bootstrap that
|
|
deleting a user cannot reopen, and bounded, unambiguous explicit-grant management;
|
|
4. module route-overlap rejection at registration, finite startup rollback and shutdown deadlines,
|
|
and hardened migration command timeouts and SQL execution;
|
|
5. deployment and supply-chain closure plus the final re-audit.
|
|
|
|
See [ADR 0017](decisions/0017-public-request-perimeter-hardening.md),
|
|
[ADR 0018](decisions/0018-identity-session-resolution-and-database-transport.md),
|
|
[ADR 0019](decisions/0019-durable-authorization-history-and-grant-management.md), and
|
|
[ADR 0020](decisions/0020-module-runtime-and-migration-execution-hardening.md).
|
|
|
|
These guarantees hold under a **trusted-contributor, not-yet-deployed** threat model. The remaining
|
|
limitations are operational rather than architectural: CI is a trusted-code-only boundary whose
|
|
runner topology and job-token authority are unverified, and Ekklesia is not deployed, so the origin,
|
|
proxy, and throttling properties a public launch requires are unproven. Neither set of requirements
|
|
blocks Phase 6. Read
|
|
[Security model and deployment trust boundaries](../development/security.md) for what each boundary
|
|
does and does not protect against, and the repository root
|
|
[README](../../README.md#current-status) for the current status of every phase.
|
|
|
|
The module platform's backend foundations are now in place and Phase 6 Surface work is unblocked.
|
|
Target architecture not yet implemented includes production approval-provider
|
|
composition, physical storage-bucket provisioning, jobs/workflow automation, richer public resource
|
|
and action contracts, a Dashboard, a Builder, and reference feature modules; none of these are
|
|
scheduled by this document.
|