Build
Documentation for people changing the platform, rather than running it. If you are setting the product up, start at Get started instead.
What is here
| Page | Answers |
|---|---|
| Add a connector | How do I teach the platform to read a system it does not support yet? |
| Keeping the docs true | How does this guide avoid drifting away from the code? |
| Model-facing encoding | How is tool output encoded for the investigator, and what must not change? |
The shape of the repository
One monorepo, split into applications that run and packages they share.
| Workspace | Responsibility |
|---|---|
apps/api |
Hono control plane: managed Slack Socket Mode, tenant/connector/surface config, queue producer, WebSocket hub. |
apps/dashboard |
React + Vite responder dashboard. |
apps/dev-tunnels |
Local-development supervisor for loopback-only kubectl port-forward tunnels to Prometheus and Grafana. |
apps/surface-worker |
Consumes the hub fan-out stream and posts to external surfaces. surface_deliveries is the durable outbox; every post threads under the incident binding. |
apps/triage-worker |
Queue consumer and Triage Engine host (Claude / OpenAI). |
packages/agent-tools |
Triage tool implementations and the per-engine binding layer. |
packages/alerts |
openIncidentWorkspace, the sole atomic incident opener, plus the Slack routeToIncident adapter and platform subject identity. |
packages/connectors |
Compile-time connector catalog, provider modules, stable connector ports, and registries. |
packages/contracts |
Shared client/server wire contracts (WebSocket ingress cap and error-frame shape). Pure TypeScript, no runtime dependencies. |
packages/db |
Drizzle schema (tables, indexes, RLS policies), generated migrations, role/grant/FORCE-RLS bootstrap, SecretStore, Embedder. |
packages/hub |
Canonical per-incident conversation: Postgres log plus Valkey pub/sub. |
packages/notifications |
Durable recipient inbox, lifecycle templates, and optional best-effort SMTP delivery. |
packages/platform-settings |
Typed platform-global settings backed by Postgres with bounded Valkey caching. |
packages/queue |
Valkey Streams work queue with Postgres as the durable source of truth, plus the reconciler. |
packages/slo |
Error-budget read model: objective evaluation, budget and burn computation, and status views. |
packages/surfaces |
Slack API clients and the outbound surface registry. |
packages/topology |
Service-graph queries, including the blast-radius CTE. |
That table is generated from the workspace tree, so it cannot fall behind a rename.
Getting it running
bun install
bun run dev:setup # containers, health checks, migrations
bun run dev # every application's watcher
dev:setup brings up PostgreSQL and Valkey and applies migrations. dev runs the watchers.
Install locally covers the same ground in more detail,
including what to do when a long-lived local database has fallen behind.
What has to pass
Both CI providers run the same scripts, so there is one list, not two.
| Gate | What it protects |
|---|---|
bun run typecheck --concurrency=2 |
TypeScript strict, per package, Turbo-cached with bounded CI memory use. |
bun run typecheck:scripts |
TypeScript strict over the repository scripts, which sit outside the workspace task graph. |
bun run lint |
oxlint, the public JSDoc contract gate, and prettier --check. |
bun run docs:gen --check |
Every generated documentation block matches the source it is derived from. |
bun run docs:lint |
Documentation prose rules: no em dashes, no tracker or decision-record references, no source paths. |
bun run check:live-test-scripts |
Bundles manually invoked live-test entrypoints so stale imports fail before operators need them. |
bun run db:check |
Drizzle migration-history consistency. Reads the journal, needs no database. |
bun run check:connector-architecture |
Connector module boundaries and the production module size cap. |
bun run check:test-layout |
Every test sits in a __tests__ directory beside its source area. |
bun run test:slack-socket-compat |
Slack Socket Mode heartbeat compatibility, run under Bun. |
bun run test:ci:backend |
Every suite outside the dashboard, over ephemeral Testcontainers Postgres and Valkey, pinned to the real Node the pipeline installs rather than whichever runtime resolves first. The only lane that starts a container stack, so a second lane copying it would boot and migrate twice. Fails unless the files it ran are exactly the files on disk for the lane. Includes the RLS isolation tests, which block merge. |
bun run test:ci:ui |
The dashboard suites, on the real Node the pipeline installs. The lane reaches no Postgres and no Valkey and starts no container, and the same file-set check enforces that boundary from both sides. |
bun run build |
Every workspace builds. |
uv lock --check |
The documentation Python lockfile matches pyproject.toml. |
uv run --locked --group docs mkdocs build --strict |
Strict documentation build: broken links, stale navigation, or a missing snippet fail here. |
bun run ci:container |
Production image builds and passes its smoke test. |
Two of those deserve a note before you hit them for the first time.
Tests need Docker, not a running stack. The suite boots its own PostgreSQL and Valkey through Testcontainers, migrates once, and tears them down. It never touches your development database, and it must never be pointed at shared infrastructure.
Tenant isolation tests block merge. Every domain table carries a tenant, and the database enforces it rather than the application. A change that reaches domain data without that scoping is a cross-tenant leak, not a style problem.
The rules that are not negotiable
These hold everywhere, and a change that breaks one is rejected regardless of what it enables.
| Rule | Why |
|---|---|
| Every domain table is tenant-scoped, enforced by the database | A gap is a cross-tenant data leak |
| PostgreSQL is the durable source of truth, queues are delivery | Work survives a queue that loses a message |
| One model provider per deployment, no cross-provider fallback | Silent failover means silently different behaviour |
| Connectors are read-only, without exception | The product promises it on every page |
| No new datastore without a recorded decision | PostgreSQL and Valkey are the only two |
| Secrets are encrypted at rest and never returned | Credentials are write-only, everywhere |