This commit is contained in:
@@ -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.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user