Add a single platform-owned request-body policy (hono/body-limit) with small, named limits for Better Auth and Core's grant-mutation endpoint, and a new required maxBodyBytes field on the executable module route contract so every module route makes a conscious no-body-or-bounded-body choice, capped at a platform ceiling. Every oversized request gets one sanitized 413 contract and never reaches business logic, authorization, or a handler. Also configure finite Node header/request/keep-alive timeouts, including the non-obvious connectionsCheckingInterval needed for them to take effect promptly, so a slow-header or slow-body connection cannot hold a socket indefinitely. Co-Authored-By: Claude Sonnet 5 <[email protected]>
137 lines
8.9 KiB
Markdown
137 lines
8.9 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.
|
||
|
||
## 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. This
|
||
branch completes Phase 5C: 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 pass 1 of 5 hardens the public request perimeter against an independent security audit of
|
||
the Phase 5C checkpoint: a server-owned client-address resolution boundary (direct mode by default,
|
||
explicit trusted-proxy mode otherwise) replaces trusting a caller-controlled forwarded header for
|
||
the authentication rate limiter; public sign-up is disabled by default behind an explicit typed
|
||
opt-in; every public route — Better Auth, Core's grant mutation, and every module route via a new
|
||
required `maxBodyBytes` field on the executable route contract — has an explicit, enforced
|
||
request-body ceiling with one sanitized `413` contract; and the Node HTTP server has finite
|
||
header/request timeouts. See
|
||
[ADR 0017](decisions/0017-public-request-perimeter-hardening.md). This pass does not add
|
||
distributed rate limiting or durable signup-abuse control, and Phase 5D itself is not complete —
|
||
passes 2–5 and a final independent re-audit remain before Phase 6 may begin.
|
||
|
||
The project continues finishing the module platform's backend foundations before Phase 6 Surface
|
||
work begins. 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.
|