Architecture#
Ticket Board is a self-hosted project tracker built for an unusual team shape: one human and an arbitrary number of AI bots. The architecture reflects that. Bots are first-class identities, every write is attributed, and the same domain is reachable over both REST and MCP. Everything else — the schema, the auth model, the event taxonomy — falls out of those three commitments.
This section is the operator-facing companion to the specs in spec/ and the ADRs in adr/. It documents what the system is, not how to install it. For install procedures, see the Getting started and Deployment sections.
What you'll find here#
- System overview — the request path, the topology, and the boring-tech choices that hold it together.
- Data model — entities, relationships, ID strategy, soft-delete policy.
- Auth and identity — sessions, bot tokens, the three carriers, and the project-allowlist authorization model.
- Observability — audit vs. activity, the event taxonomy, webhooks, health endpoints.
Tenets#
Three commitments drive every design decision below them. They are worth internalizing before reading further.
1. Single human, many bots#
The system is built for a workforce of one person and N AI agents acting on that person's behalf. v1 has one user, one workspace, and no role model. The human is the operator; bots are the workforce. Every domain entity carries a workspace ID so this can be revisited later (see ADR-0007), but v1 surfaces no workspace concept in the UI.
2. Audit-first#
Every write — domain mutation, auth event, token rotation, bot tool call — lands in a single append-only audit_event table. The user-visible "activity feed" is a filtered projection of that table, not a parallel write. This single source of truth makes delegation legible: an operator can ask "what did Bot-3 do yesterday?" and answer it with one query.
The activity feed and the audit log share a table, share an emitter, and share an index strategy. The split is a presentation concern, not a storage one. See ADR-0006.
3. MCP-first, REST-equivalent#
Bots interact through MCP (Model Context Protocol) but the surface is not bot-only. Every MCP tool maps to the same service function as the equivalent REST endpoint. There is no bot-only business logic and no human-only business logic — only one service layer that both transports call through. Authentication resolves to a CurrentActor once per request; everything downstream treats humans and bots uniformly.
How the pieces fit#
flowchart LR
H[Human] -->|HTTPS + cookie| Caddy
B[Bot] -->|MCP or X-API-Key| Caddy
Caddy --> FE[Next.js]
Caddy --> BE[FastAPI]
FE -->|JSON| BE
BE --> DB[(Postgres 16)]
BE -.->|HMAC-signed POST| WH[Webhook receivers]
One Caddy instance fronts the single-origin deployment (required for SameSite=Lax cookies). Next.js renders the UI. FastAPI carries both the REST router and the FastMCP mount in the same process. Postgres stores everything. Outbound webhooks fire in-process — no Redis, no Celery, no external queue.
For the full topology diagram, see System overview.
Reading order#
If you're coming in cold and want the architecture in one sitting:
- System overview — boundaries and request flow.
- Data model — what we store and why.
- Auth and identity — how requests acquire identity.
- Observability — what the system records and where you see it.
If you want the canonical specs (longer, more implementation-shaped), they live under spec/ in the repo. The ADRs under adr/ cover decisions that were genuinely contested.
Boring-tech posture#
A note on style. Ticket Board is deliberately small and deliberately conventional. Postgres handles everything we'd otherwise reach for Redis or Kafka for. The audit log is the event log; the projection is a view; webhooks fan out from a background task.
This is not because those tools are bad. It is because a single-operator homelab system pays the cost of complexity every day and benefits from it almost never. When v2 needs durable queues or partitioned audit, the migration shapes are documented in the relevant ADRs — but they aren't built today.