Why it's built that way.
Architectural decisions, in plain language. Each ADR captures a real choice the team made — the context that forced it, the path picked, the cost we accept. Bottom of this section: a single "invariants" page listing the rules with no graceful failure mode.
How to read this section
Each ADR follows the same shape: Status · Context · Decision · Consequences · Alternatives. Read context first if you're a new contributor; jump to consequences if you're evaluating fit. The invariants page at the end is different — it's a flat list of hard rules that, if violated, break the system silently.
The decisions
Schema as code, not migrations
EnsureCreatedAsync() + inline ALTER TABLE in Program.cs. What we trade by skipping migration tooling.
Dictionary<string, object?> repositories
Why no typed DTOs. Why JSON-shaped responses. The cost we pay for the flexibility.
→ ADR-003Single API replica in production
What blocks horizontal scale-out. The work we'd need to do to lift this constraint.
→ ADR-004Headless paths auto-approve destructive ops
The risk we accept for scheduled jobs. The mitigations that keep it safe.
→ ADR-005Hash router, no react-router
Why the SPA owns its router in 123 lines. What we'd lose by adding a library.
→ ADR-0065-second Stop watchdog
The hard SLA on user-initiated stops. Why this number and not "best effort".
→ ADR-007JSONB envelope versioning
The _v wrapper pattern. Why we chose this over schema migrations.
MCP service is a separate process
Why a process boundary, not just a class boundary. What we gain.
→Hard rules
§11·Invariants & gotchas → lists the hard invariants — properties of the system that, if violated, break things in ways that are hard to debug. New contributors should skim this once before touching the agent loop or the data layer.