==> read the decisions
ADR-0015: The platform's code is organised by domain
Status: Accepted (2026-10-14). Implemented 2026-10-15.
Context
Section titled “Context”The API is one file per concern (session.ts, models.ts, reconcile.ts, settings.ts, ...)
sharing one database handle, with routes, rules and SQL interleaved in each. At about 1,700 lines it
still reads, but every rule worth a test -- when a session is valid, which door may call which model,
what a reconcile converges to -- is reachable only through HTTP and a database.
Decision
Section titled “Decision”-
Six bounded contexts, each a directory under
api/src/:identity User; sign-in, password changeaccess Session, ApiToken, Principal (a user plus the door they came through), CSRF,Invite; resolve the caller, issue and revoke credentials, last seenpreferences Settings and its values (ThemeId, Mode, Lang); the resolution laddertenancy Tenant (= user, ADR-0001), TenantDatabase, Release, Project, ServiceHost;the registry and forwarding to the user's hostmodels Model, ModelGrant, UsageRecord; proxying, gating, usage and per-request metricsprovisioning Roster (desired state) -> reconcile users, grants, projects, roles, databases -
Four layers inside each, and dependencies point inward only:
domain.ts types and rules, pure: no I/O, no framework; this is where the tests areservice.ts use cases (login, createInvite, revokeSession, recordUsage, reconcile)repo.ts Postgres, through Bun.sqlhttp.ts Hono routes -
A small kernel holds what every context needs: the database pool, configuration, typed errors, logging (ADR-0018), and the assembly of the app.
-
Contexts talk through service functions, never through each other's tables.
-
DDD-lite, deliberately: no aggregates framework, no event bus, no CQRS. The point is where a rule lives and that it can be tested without a server; the ceremony would outweigh a codebase this size.
Consequences
Section titled “Consequences”- Each rule worth a test is a pure function with
bun testbeside it. - The layering is mechanical enough for lint to check (no
repo.tsimport fromdomain.ts). - Shared code that both the platform and user services need still lives in the shared library; a context is the platform's own.