Architecture overview
Where code lives, what depends on what, and where a change belongs
Concourse is a pnpm and Turborepo workspace. apps/ holds executable workspaces, including the independent operator CLI.
packages/ holds things that are shared.
Read this page to find where a change belongs. Read Module shape for the rules once you are there.
Source validation uses combined checks. Turbo retains compiler passes where generated files or alternate configurations require them.
Deployables
Each deployable app has a vercel.json. The Applications page links each
app guide.
| Workspace | What it serves |
|---|---|
apps/concourse | The application and the system administration interface |
apps/docs.concourse | This documentation site |
apps/docs.tredis | The TREDIS 6 User Manual |
apps/midot | Michigan Department of Transportation tools |
apps/us-counties | A map for selecting U.S. counties |
The dependency graph
The graph shows the runtime dependencies in each workspace package.json, and the entry points
that each workspace imports. It leaves out development-only edges, such as the tooling packages
@concourse/oxlint-config, @concourse/typescript-config, and @concourse/vitest-config. They
carry no runtime meaning.
apps/concourse ──> api, sdk, services, auth, db, rsdb/runtime, contracts, email,
platform, ui, tailwind-config
apps/cli ──> sdk, auth, db, rsdb/local-generation, contracts, email, platform
apps/midot ──> api/midot, sdk/midot, services/rsdb/mi-property-value,
services/rsdb/tredis-global-dynamic, rsdb/mi-property-value,
rsdb/tredis-global-dynamic, rsdb/runtime, contracts, email
apps/us-counties ──> ui, tailwind-config
apps/docs.concourse ──> docs, ui
apps/docs.tredis ──> docs, ui
api ──> services, auth, contracts
services ──> db, rsdb/mi-property-value, rsdb/tredis-global-dynamic, contracts, email
auth ──> db, email, platform, contracts
db ──> contracts, platform
rsdb ──> contracts
sdk ──> contracts
docs ──> ui
contracts, email, platform, ui, and tailwind-config depend on no workspace.Two things to notice.
The graph is layered and acyclic. Nothing imports upward, and .dependency-cruiser.ts enforces
that through pnpm lint:boundaries. contracts is the browser-safe leaf that every layer shares.
It imports no other workspace. services owns
shared Project and Application operations. Isolated Services entry points can also own behavior for
a registered RSDB database. api exposes shared operations and Auth's remaining domain operations
over HTTP. sdk uses the shared contracts.
The web app uses the SDK for organizations, members, invitations, SSO configuration, account security, administration, Projects, and Applications. Rackspace verification uses app-local server actions. Server calls use the API handler in the same process. Identity flows and page-access gates stay on Auth. See ADR 0005.
rsdb depends only on contracts. It imports no other workspace, even though db owns stored
verification reports and Services owns database-specific behavior. That is
the single most confusing thing about this repository, so it has its own section below.
Ports and adapters
The workspace uses ports and adapters, also called Hexagonal architecture. ADR 0017 defines the terms and the dependency rule: source dependencies point inward. This table gives the role of each workspace.
| Role | Workspaces |
|---|---|
| Core | services, and the domain modules of auth: access decisions, policy, and sessions |
| Shared kernel | contracts: browser-safe schemas and the HttpApi declarations |
| Driving adapters | api, sdk, apps/*/app/, apps/*/lib/**/framework/, the MIDOT server routes, and apps/cli |
| Driven adapters | db, rsdb, email, platform, and the Better Auth adapter of auth |
| Composition roots | apps/concourse/lib/runtime.ts, apps/midot/lib/runtime.ts, and the CLI runtime modules |
| Outside the model | ui, docs, tailwind-config, the tooling packages, apps/us-counties, and the two documentation sites |
@concourse/db/repositories holds the application repositories. rsdb holds the typed sessions
and the repositories for each Rackspace database, one entry point per database. A core module asks for a repository port, and the composition root
provides its Drizzle adapter.
Current exceptions
None. pnpm lint:boundaries enforces the dependency rule, and no storage code is outside a driven
adapter. Each MIDOT sign-in route that changes state makes one MidotSignIn call. GET /api/session
makes no core call: it only decodes the session cookie, as the session middleware does for every
protected route.
Rackspace storage and access
packages/db, packages/rsdb, and database-specific Services answer different questions. The two
database packages define types whose names start with Rackspace, but they share no symbol.
| Question | Owner |
|---|---|
| What did we record about a Rackspace schema and its verification report? | packages/db |
| What is a Rackspace schema, and does the deployed database still match it? | packages/rsdb |
| What does a property value project operation mean? | packages/services |
packages/db owns application storage: one report row and the audit events for each verification
run. packages/rsdb owns MySQL access, the committed schema snapshots, the registry, and what a
verification result means. Its private factory turns reviewed database bindings into concrete typed
capabilities; it does not publish a generic application SQL reader. Database-specific Services can
own legacy application behavior while RSDB retains generated schemas, Drizzle sessions, the
repository queries, per-database transactions, shared pool lifecycles, and cleanup. These external writes do not use the
SQLite audit path. A transaction does not span RSDB databases.
A third body of Rackspace code lives in apps/concourse/lib/rackspace/, and it belongs there. It
orchestrates a run: the report row in the application database, audit rows under the administrator's
identity, and the admin pages. The browser advances through the immutable registry snapshot one
database at a time and saves each result before continuing. A failed result stops the run. The
application stores no step ledger or lease.
rsdb stays independent of application persistence and orchestration. The Application context records the decision.
Effect backend composition uses one model across backend packages. Domain operations compose Effects,
repositories hide Drizzle, and application Layers own configuration and scoped resources. Promise
execution stays at framework and vendor boundaries. React and Next.js code keeps ordinary TypeScript
control flow. Package ownership remains unchanged. The
shared HTTP declaration drives API handlers and both SDK
entries. @concourse/sdk returns Promise methods; @concourse/sdk/effect serves Effect programs.
The Effect toolchain decision scopes its rules to
Effect-owned code. Mixed apps put framework adapters in framework/ folders. Effect-owned
modules use Effect's platform services. Reviewed Node-only operations live in .node.ts host
adapters with static node: imports.
@concourse/platform defines the shared HMAC boundary. Effect supplies the other portable platform
services. Executable roots choose Node adapters; domain Layers stay open to replacement.
Shared contracts
Browser-safe schemas and types live in @concourse/contracts, the leaf that db, auth, and the
app depend on. Persistence derives its column enums from them. Auth owns identity and organization permissions.
Services own Project and Application permissions and transitions. @concourse/contracts/property-value
owns the canonical legacy project shape, while its Services entry point owns behavior. The
verification result schema also lives here, so rsdb and the app read results with the same rules.
See ADR 0004.
@concourse/contracts/rackspace combines database results with execution identity and deployment policy.
It uses the verification invariants of the same entry point. Historical report validation remains independent of
the current Registry.
Where does my change go?
| Change | Destination |
|---|---|
| A page, a route, a form, a Server Action | apps/concourse/app/ |
| Application Layer composition (repositories, adapters, domain Layers) | apps/concourse/lib/runtime.ts |
| Shared server runtime lifetime and the request SDK client | apps/concourse/lib/server/runtime.ts and lib/server/framework/client.ts |
| Request-bound reads and navigation refusals | Domain framework/reads.ts modules and lib/reads/framework/execution.ts |
| Browser identity client | apps/concourse/lib/framework/client.ts |
| Application write completion and affected-view refresh | apps/concourse/lib/applications/framework/actions.ts |
| A shared application table, relation, or migration | packages/db. It owns the shared application schema. See ADR 0001. |
| Identity, sessions, organization access | packages/auth. It owns access decisions. See ADR 0002. |
| A Project or Application operation | packages/services. See ADR 0005. |
| Legacy behavior for a registered RSDB database | An isolated packages/services entry backed by typed packages/rsdb access. See ADR 0011. |
| An HTTP route of the API | packages/api, one file per resource under lib/routes/ |
| How another program calls the API | packages/sdk |
| A schema that crosses a package boundary | packages/contracts. See ADR 0004. |
| A shared technical capability missing from Effect | packages/platform; runtime adapters belong in its host entry point. |
| A shared component | packages/ui |
| Shared documentation UI | packages/docs |
| A documentation page | docs/concourse/ |
| A rule that applies everywhere | docs/concourse/development/standards/ |
| Michigan Digital Guidelines components | Direct upstream imports in apps/midot |
If a change needs a new package, read when a package is justified first. The answer is usually no.
The server entry point creates one database connection and composes Auth, Services, and the API handler.
It creates one SDK client per render with the incoming credentials and audit request facts.
Domain read modules cache protected reads only within that render. lib/reads/framework/execution.ts maps Auth and API refusals.
The browser entry point exposes Better Auth for identity. Browser domain interactions use Server Actions that call the server SDK client.
These composition modules do not own domain permissions.
Application catalog and memberships
Persistence stores the application catalog and organization memberships. Services own their reads, administrator writes, and effective modes. The API serves these operations. Concourse presents catalog management and organization app lists on the Apps tab. Application PRD records the model. Midot enforcement remains outside this implementation.
The app's Application write module calls the SDK and refreshes affected views after success. The shared form-action helper returns its final Notice. Form adapters parse input and select complete operations. They do not reconstruct refresh paths. This keeps completion behavior in one place without moving permissions, audit writes, or lifecycle decisions out of Services and Persistence. The action tests cover refresh and Notice outcomes. The browser lifecycle covers catalog status, Saved mode, and Effective mode.
Projects
Persistence owns Projects and their explicit memberships. Services combine organization roles, grants, and archive status to decide Project access, built on the roles Auth owns. Organization landing pages show accessible Projects. System administrators use a separate support entry without broadening ordinary organization access.
PRD-0003 defines shared identity and access behavior. The Persistence guide owns tenant-safe grant storage. The Services guide owns support access and query scoping.
The ADR index lists the decisions behind this structure.
The Rackspace security page owns the shared-account risk.
The performance standard owns cache and feedback rules.
The MIDOT guide owns its upstream Michigan component and Node function boundaries.
Each context keeps its own vocabulary in CONTEXT-MAP.md.
Operator runtime
apps/cli owns database maintenance, bootstrap, local Maildev, Rackspace generation, SDK examples,
and deployment tooling. The Concourse app owns Production build migrations; the CLI checks the
resulting deployment. See ADR 0013.
Each command family composes only the public package capabilities it needs.
The CLI and applications own independent environment files. Their dependencies connect code and
Turbo task hashes, not dotenv values. See runtime-local configuration.
Verification ownership
The command reference records explicit integration selection and cache ownership. Browser setup prepares fixtures. The verification graph selects the database suites separately.