Adds ADR 0013 documenting the durable Phase 4C decisions (permission registry, evaluator baselines, grant/scope semantics, platform-owner bootstrap, shared database pool, Origin enforcement, and what remains deferred), cross-links it from the authorization/README docs, and refreshes the pnpm doctor no-DATABASE_URL example output in README.md and repository-workflow.md to include the authorization migration.
10 KiB
Repository workflow
Prerequisites
- Node.js 24 LTS (
.node-versionand.nvmrcpin the reviewed patch) - pnpm 11.12.0
- Docker with the Compose plugin for local infrastructure
Install dependencies from the committed lockfile:
pnpm install --frozen-lockfile
Quality gates
pnpm format:check # formatting without mutation
pnpm lint # Biome recommended rules
pnpm boundaries # architectural directions, UI ownership, and cycles
pnpm modules:validate # installed module manifests, compatibility, and dependency graph
pnpm migrations:validate # offline Core + module migration inventory and exact checksums
pnpm identity:schema:validate # detects drift against the pinned Better Auth schema generator
pnpm migrations:plan # read-only comparison with PostgreSQL applied history
pnpm migrations:apply # locked, transactional application of the pending batch
pnpm doctor # build-first, read-only human readiness dashboard
pnpm typecheck # strict TypeScript
pnpm test # Vitest
pnpm test:migrations:integration # production runner against disposable PostgreSQL
pnpm build # Turborepo package build graph
pnpm verify # every code-only gate; never plan or apply
The lint, typecheck, and test commands run both repository-root checks and every matching
package-level Turborepo task. New packages add their own lint, typecheck, and test scripts;
the root verification command discovers them automatically.
pnpm modules:validate builds the workspace, then scans modules/* without a central registry. A
missing or empty modules directory is valid and reports zero installed modules. Installed modules
must produce the compiled JavaScript entrypoints declared by their static manifests. Optional
schema entrypoints are loaded and validated for table ownership, namespacing, duplicates, and
foreign-key isolation; this command does not connect to PostgreSQL or run migrations.
pnpm migrations:validate also builds first, completes static module discovery, and atomically
checks Core's own core/drizzle migration directory together with every declared module Drizzle
migration directory. It reports Core's migration first, followed by dependency-first module
inventories, with exact SHA-256 checksums, without importing module code, connecting to
PostgreSQL, or executing SQL. Modules with no migration history are valid.
pnpm identity:schema:validate regenerates Better Auth's canonical schema with the pinned local
auth CLI into a temporary, package-local directory, structurally compares every core model
(columns, types, nullability, defaults, foreign keys, and indexes) against the checked-in
core/src/identity/schema.ts, and always removes the temporary output. It requires no
DATABASE_URL. See Identity for the full upgrade workflow.
Developer Doctor
Run the complete local readiness dashboard with:
pnpm doctor
The root command follows the repository's build-first convention, so it works from a clean checkout
without preexisting ignored dist output. The build itself may create ignored compiled artifacts;
the diagnostics that follow are operationally read-only. Doctor never edits configuration, applies
migrations, creates the Core history table, acquires the migration apply lock, starts module server
entrypoints, provisions storage, or invokes Docker.
Doctor reports a fixed, deterministic set of checks:
- The active Node.js version against the repository's declared engine range.
- Static module discovery and dependency-first ordering.
- Schema discovery, including validated module and table counts. This imports compiled schema definitions, which the module contract requires to perform no configuration reads, I/O, or database connection.
- Offline migration discovery for Core's own identity migration and every module, with safe identities and consistently truncated checksums, Core listed first.
- PostgreSQL connectivity and read-only applied-history planning when
DATABASE_URLis configured. - Identity runtime configuration: skipped (not a failure) when no secret, base URL, or trusted
origins are set; otherwise validated with
createIdentityConfigand reported pass or failure. This check never constructs Better Auth or opens a database connection. - Object storage as skipped while Core has no production storage client or configuration contract.
Every check has a stable ID and one of four states: pass, warning, failure, or skipped. Independent
checks continue after a failure; a failed prerequisite causes only its dependent checks to be
skipped. Pending or approval-blocked migrations are warnings and are never applied. Invalid
history, checksum disagreement, unknown applied migrations, or a failed configured connection are
failures. If DATABASE_URL is missing, PostgreSQL is skipped and all static checks still run.
READY means every check passed. READY WITH WARNINGS means no check failed but at least one check
warned or was skipped. Both results exit zero. NOT READY means at least one check failed and exits
one. Meaning never depends on color, a TTY, timestamps, or interactive input.
For the current repository with no installed modules and no DATABASE_URL, the concise inventory
ends like this:
Modules
✓ Module discovery complete
0 modules installed
✓ Dependency graph valid
Dependency order is empty.
Data definitions
✓ Schemas validated
0 modules · 0 tables
✓ Migrations validated
2 migrations · checksums valid
core:0000_identity_core_tables · f8f2086ef06f
core:0001_authorization_core_tables · 830e1fbc26d0
PostgreSQL
○ Connection check skipped
DATABASE_URL is not configured.
○ Migration plan skipped
A validated migration inventory and configured PostgreSQL connection are required.
Identity
○ Identity configuration skipped
Core identity schema defines 4 tables (ekklesia_core_account, ekklesia_core_session, ekklesia_core_user, ekklesia_core_verification).
Identity runtime configuration (signing key, base URL, trusted origins) is not set.
Object storage
○ Check unavailable
Core storage integration has not been implemented yet.
Result: READY WITH WARNINGS
Doctor is a local, human-readable summary. pnpm verify, pnpm ci:verify, infrastructure smoke,
and migration smoke remain the authoritative automated evidence because they exercise formatting,
boundaries, types, tests, builds, containers, and live mutation/rollback behavior that Doctor does
not repeat. Core authorization and an explicit platform-owner bootstrap command are the next
architectural phase; Doctor does not diagnose authorization, the apps/api HTTP transport, runtime
module health, or production storage yet, and it never constructs an operational Better Auth
instance, starts the API, or claims sign-in works. See API for pnpm api:start and the
identity HTTP transport this repository now has.
Migration planning and application require DATABASE_URL. Keep it in the uncommitted .env
created from .env.example:
cp .env.example .env
pnpm migrations:plan
pnpm migrations:apply
Planning completes discovery and validation, reads Core's applied history, and prints a deterministic applied/pending/approval-blocked inventory. It is read-only and can become stale if another process applies migrations afterward. Applying is an explicit mutating operation: it acquires the bounded Core advisory lock, rereads history, checks every destructive approval, and executes the complete pending aggregate in one transaction. A later failure rolls back all schema and history changes from that invocation; migrations committed by earlier invocations remain.
The default command composition supplies no destructive approvals. There is no --force, wildcard,
or source-controlled approval escape hatch; deployments must later compose a trusted external
provider that supplies an exact module ID, migration identity, and checksum triple.
Migration-runner unit tests cover orchestration through its narrow database port. Run the separate live suite when changing migration behavior:
pnpm test:migrations:integration
It requires Docker with Compose, starts the pinned PostgreSQL image with a generated throwaway
credential and a Docker-assigned loopback port, creates an isolated database for each scenario,
and removes its containers, network, databases, and volume afterward. It exercises the production
adapter and real PostgreSQL transactions, history, advisory locks, approvals, and sanitized errors.
It remains outside the Docker-free pnpm verify command.
CI runs pnpm ci:verify, which adds Compose configuration validation to pnpm verify. Separate
infrastructure-smoke and migration-smoke jobs run the live service and migration suites. The
exact pull-request context to add to main branch protection after merge is
CI / migration-smoke (pull_request). The explicit ci:verify suffix avoids pnpm's built-in ci
install command.
Development infrastructure
Create an untracked local environment file before using Compose:
cp .env.example .env
pnpm infrastructure:up
Stop services or follow their logs with:
pnpm infrastructure:down
pnpm infrastructure:logs
Exercise both services with isolated, throwaway credentials and Docker-assigned host ports:
pnpm infrastructure:smoke
The smoke test verifies PostgreSQL password authentication and authenticated S3 list, put, get, head, and delete operations. It also proves invalid database passwords, anonymous object access, and invalid S3 signatures are rejected. Its containers, network, and volumes are removed on exit. When the smoke script itself runs in a Docker-based CI job, it temporarily joins that job container to the isolated Compose network and reaches object storage by its service name. Direct host runs continue to use Docker-assigned random ports published only on loopback.
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. Compose validation and live service
health therefore run separately from the code-only pnpm verify gate.