chore: establish Ekklesia platform foundation
CI / verify (push) Has been cancelled

This commit is contained in:
ekklesia
2026-07-12 20:28:19 -05:00
commit 192a6514cf
47 changed files with 2505 additions and 0 deletions
@@ -0,0 +1,34 @@
# 0001: Four-layer platform boundary
- Status: Accepted
- Date: 2026-07-12
## Context
Ekklesia must remain understandable and independently extensible after many backend capabilities
and user experiences have been added. A conventional feature monolith or UI-bearing plugin model
would couple business behavior to a particular presentation framework.
## Decision
Adopt four explicit ownership layers: Core orchestration, backend Modules, presentation Surfaces,
and declarative Surface Builder configuration. Begin as a modular monolith. Modules expose data
and behavior only; every renderer and UI concern belongs to a surface.
## Alternatives
- A conventional feature monolith was rejected because boundaries degrade as features grow.
- UI-bearing plugins were rejected because they bind capabilities to frameworks and visual design.
- Initial microservices were rejected because distribution adds failure modes before contracts
have been proven.
## Consequences
Stable contracts, SDK ports, generated composition, and automated boundary checks are required.
Some integrations take more deliberate service or event design, but modules and surfaces remain
independently replaceable.
## Migration implications
Future boundary violations must move behind public contracts before the affected package can be
treated as independently installable or upgradeable.
@@ -0,0 +1,36 @@
# 0002: pnpm workspace and strict TypeScript
- Status: Accepted
- Date: 2026-07-12
## Context
The platform needs atomic cross-package changes, reproducible installs, independent package
builds, and guardrails that do not force every surface onto one framework.
## Decision
Use a pnpm workspace with one committed lockfile and `workspace:` ranges for internal packages.
Use Turborepo for task ordering and local caching. Standardize production packages on strict
TypeScript and native ESM, with separate Node, browser, and React Native base configurations. Use
Biome and Vitest for the initial repository quality gates.
Node 24 LTS and pnpm 11.12 are pinned for Phase 1. Package exports are the supported cross-package
API; aliases may not expose another package's private `src` tree.
## Alternatives
Polyrepos would slow coordinated contract changes. npm and Yarn workspaces are viable but do not
match the selected pnpm workflow. A universal browser-oriented TypeScript configuration was
rejected because server and native packages have different resolution and library requirements.
## Consequences
Repository-wide changes are straightforward, while package manifests and automated checks must
prevent monorepo convenience from becoming hidden coupling. Remote cache and independent-release
automation are deferred until they solve an observed need.
## Migration implications
Packages can move to separate repositories later because consumers use package names and public
exports instead of filesystem aliases.
@@ -0,0 +1,36 @@
# 0003: Development infrastructure
- Status: Accepted
- Date: 2026-07-12
## Context
Developers need reproducible PostgreSQL and object-storage services without requiring host-level
database installation. Application source does not yet exist, so containerizing the whole
toolchain would add friction without value.
## Decision
Run PostgreSQL 18.4 and an S3-compatible object store in Docker Compose. Pin reviewed image
versions, enable health checks, use persistent named volumes, bind host ports to loopback, and use
explicitly development-only credentials. Run Node applications on the host during normal
development.
This Compose file is a development baseline, not the production deployment topology.
## Alternatives
Locally installed services make onboarding inconsistent. Containerizing editors and the complete
Node toolchain is unnecessary at this phase. Kubernetes is premature for a modular monolith
foundation.
## Consequences
Docker is required for infrastructure testing but not for code-only validation. Production
backups, high availability, secret injection, image digests, and deployment topology require a
later decision.
## Migration implications
The future Core storage service targets S3 behavior rather than a provider-specific client, so
development and production providers can change after contract testing.
@@ -0,0 +1,40 @@
# 0004: Replace unmaintained MinIO baseline
- Status: Accepted
- Date: 2026-07-12
## Context
The original Ekklesia brief selected MinIO as the default self-hosted S3-compatible service. The
upstream `minio/minio` repository was archived on April 25, 2026 and states that historical
community binary releases are no longer maintained. New church deployments may store highly
sensitive content, so beginning on an unmaintained security-sensitive service is unacceptable.
## Decision
Keep Ekklesia's future storage contract provider-neutral and use SeaweedFS 4.28 in the Phase 1
development Compose environment. SeaweedFS is actively maintained and offers an S3-compatible
endpoint and a small single-node development mode.
Before treating any provider as production-ready, add storage contract tests covering put, get,
head, delete, ranges, multipart uploads, metadata/content type, signed URLs, bucket and prefix
isolation, and denied access. A production provider decision remains separate.
## Alternatives
- Retaining the final MinIO community image was rejected because it no longer receives upstream
maintenance.
- Provider-specific Core APIs were rejected because they would couple every module to this choice.
- Deferring all object storage was rejected because a reproducible S3 development endpoint is part
of the repository foundation.
## Consequences
The implementation intentionally uses `S3_*` configuration rather than `MINIO_*` names. Some
SeaweedFS behavior may differ from AWS S3 or historical MinIO and must be captured by contract
tests before the storage abstraction is implemented.
## Migration implications
No product data exists in Phase 1. Later provider changes must follow an object-copy and integrity
verification plan while preserving stable Core storage contracts.
+15
View File
@@ -0,0 +1,15 @@
# Architecture decision records
ADRs capture durable choices that affect more than one package or phase.
- Filenames use a four-digit sequence followed by a short kebab-case title.
- Status is `Proposed`, `Accepted`, `Superseded`, or `Rejected`.
- Accepted ADRs are immutable except for status and cross-reference corrections.
- A changed decision receives a new ADR that links to and supersedes the old one.
## Index
- [0001: Four-layer platform boundary](0001-four-layer-platform-boundary.md) — Accepted
- [0002: pnpm workspace and strict TypeScript](0002-pnpm-workspace-and-typescript.md) — Accepted
- [0003: Development infrastructure](0003-development-infrastructure.md) — Accepted
- [0004: Replace unmaintained MinIO baseline](0004-replace-unmaintained-minio.md) — Accepted
@@ -0,0 +1,66 @@
# 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.
## Phase 1 status
Implemented now: monorepo/tooling, strict TypeScript policy, repository-boundary validation,
tests, base CI, PostgreSQL, and a development S3-compatible object store.
Target architecture, not yet implemented: Core runtime, module discovery and lifecycle, Drizzle
schema aggregation/migrations, Better Auth, permissions, storage service, Module SDK, Surface
SDK, capability/resource/action registries, API server, dashboard, Builder, and reference modules.
@@ -0,0 +1,40 @@
# Repository boundaries
These rules map platform ownership to filesystem and package boundaries. The executable check is
`pnpm boundaries`; CI must reject violations.
| Area | Owns | May depend on |
| --- | --- | --- |
| `apps/*` | Thin deployable composition roots | Public Core APIs, generated bootstrap, or a surface as required by the app |
| `core` | Feature-agnostic platform implementations | Public contracts and external infrastructure libraries |
| `modules/*` | Backend domain capability and data | Module SDK, public contracts, and external backend libraries |
| `surfaces/*` | UI, renderers, navigation, and interaction | Surface SDK, API/auth clients, public contracts, and design packages |
| `builders/*` | Declarative surface composition tooling | Builder/surface SDKs and public contracts |
| `packages/*` | Explicitly scoped SDKs, contracts, clients, design, testing, or tooling | Only dependencies appropriate to the package's declared level |
| `templates/module` | A small example backend module | The same dependencies allowed for any module |
## Mandatory rules
- Core never imports a feature module, surface, Builder implementation, or app.
- A module never imports Core implementations, another module, a surface, Builder code, or an app.
- A module cannot contain `.tsx`, `.jsx`, `.css`, `.scss`, `.less`, `.vue`, or `.svelte` source
and cannot depend on a UI framework. Modules expose data and behavior only.
- A surface never imports Core or a feature module. It uses public API/client contracts.
- Separate surfaces do not import each other's internals.
- Builder code composes contracts and surface renderer identifiers; it does not import module or
surface internals.
- Contract and SDK packages cannot depend upward on implementations.
- Workspace package consumers use declared `exports`, not private subpaths.
- Circular source and workspace-package dependencies are rejected.
- Cross-module data access uses services, events, capabilities, or opaque identifiers rather than
direct table imports and foreign keys.
The boundary test suite includes intentionally invalid fixture graphs. This proves that the guard
rejects forbidden dependency directions even before production packages fill every directory.
## Generated code
Generated artifacts are reproducible outputs with a named generator and validation step. A
generated module bootstrap may import discovered module entry points at an application
composition edge, but Core itself remains feature-agnostic. Generated files are never an excuse
for a hand-maintained central module registry.
+53
View File
@@ -0,0 +1,53 @@
# Repository workflow
## Prerequisites
- Node.js 24 LTS (`.node-version` and `.nvmrc` pin the reviewed patch)
- pnpm 11.12.0
- Docker with the Compose plugin for local infrastructure
Install dependencies from the committed lockfile:
```bash
pnpm install --frozen-lockfile
```
## Quality gates
```bash
pnpm format:check # formatting without mutation
pnpm lint # Biome recommended rules
pnpm boundaries # architectural directions, UI ownership, and cycles
pnpm typecheck # strict TypeScript
pnpm test # Vitest
pnpm build # Turborepo package build graph
pnpm verify # every code gate above
```
CI runs `pnpm ci`, which adds Compose configuration validation to `pnpm verify`.
## Development infrastructure
Create an untracked local environment file before using Compose:
```bash
cp .env.example .env
pnpm infrastructure:up
```
Stop services or follow their logs with:
```bash
pnpm infrastructure:down
pnpm infrastructure:logs
```
PostgreSQL and the S3 endpoint bind to `127.0.0.1` only. Compose uses persistent named volumes;
`infrastructure:down` preserves data. The S3 service creates the configured development bucket
idempotently at startup.
The checked-in credentials are safe only for local development. Deployed secrets must come from
an environment-appropriate secret provider and must not be committed.
Docker is not installed in every contributor environment. `pnpm infrastructure:config` and live
service health therefore run separately from the code-only `pnpm verify` gate.