Overview
Architecture Decision Records (ADRs)
This directory contains Architecture Decision Records for the Spectral project. ADRs document significant technical decisions, capturing the context, trade-offs, and alternatives so engineers — human and agent — understand why things are the way they are.
Format
Every ADR is a markdown file named NNN-short-title.md following _template.md in this directory. Copy the template when starting a new ADR; do not freestyle the structure. Uniform shape is what makes a growing corpus browsable.
Statuses
- Accepted (YYYY-MM-DD) — the decision is in effect.
- Accepted (supersession planned) — the decision is in effect but a replacement is planned.
- Accepted (partially superseded by ADR-NNN) — the decision is in effect; one or more sub-decisions have been replaced by a later ADR while the rest remain authoritative. Whole-ADR supersession is a different state — see “Retired” below.
- Rejected — the proposal was considered and declined. Rejected ADRs stay in the repository as history; deletion is rare and must be called out by the successor ADR.
- Retired (folded into ADR-NNN) — the ADR was wholly superseded; its file has been deleted from the filesystem; the historical context worth preserving lives as a bottom-anchored addendum on the superseding ADR. The index entry remains so the supersession trail stays browsable. Git history preserves the original file’s full text at the commit that retired it.
Discipline rule
Any decision that shapes more than one epic or more than one file deserves an ADR. This is a default, not a ceiling — write an ADR whenever the reasoning is worth preserving for a future reader who joins after the context has evaporated.
- Prefer one ADR per cohesive decision. If a decision bundles several related sub-decisions (as ADR-012 does with four dev-tooling choices), keep them together rather than splitting into separate ADRs that always have to be read as a set.
- Link ADRs from the Linear issue that prompted them and from the Codex page that documents the resulting system behavior. ADRs are why; Codex is what; Linear is when.
- Accepted ADRs are not edited while in force. Decision integrity is the point of the rule. The narrow exception: at a partial-supersession event, the superseded segment(s) of the prior ADR are removed (replaced with a one-line pointer to the superseder); the supersession’s documented rationale lives in the new ADR’s addendum. Other in-force decisions in the prior ADR remain unchanged. The intent is precision about what is currently authoritative — leaving superseded content in place produces the worst of both worlds, where a reader inside the prior ADR sees content that no longer governs.
- At full supersession, retire the prior ADR. Write the superseder; distill what a future reader of the new ADR genuinely needs to understand why we’re here, not there into a bottom-anchored addendum on the new ADR (reference to the prior ADR by name + brief bullets); delete the prior file; sweep live references in the repo and re-point them at the successor. Git history preserves the full archaeological record.
- Whole-ADR supersession only. A new ADR addressing one D# of a multi-D ADR is a new ADR that supersedes that specific D, not the whole prior ADR; the original’s other Ds remain authoritative and the original file stays. Reflect partial supersession in the prior ADR’s index status (see Statuses above) and remove the superseded segment from the prior ADR per the rule above (replace with a one-line pointer using the format:
**D# — superseded by [ADR-NNN](NNN-...md).** [one-sentence note].). - Discipline reminders for day-to-day work live in
AGENTS.mdat the repo root.
Adding a new ADR
- Pick the next unused number (
ls docs/decisions/and take the highest + 1; numbers skip over retired-and-deleted ADRs). - Copy
_template.mdtoNNN-short-title.mdand rename. - Fill in Context, Decision, Alternatives Considered, and Consequences.
- If this ADR supersedes a prior one: add a
Supersedes:line to the new ADR’s header; distill the prior ADR’s historical context worth preserving into a bottom-anchored addendum on the new ADR; delete the prior ADR file; sweep live references in the repo and re-point them at the new ADR; update the prior ADR’s index entry toRetired (folded into ADR-NNN). - Update the Index and the supersession graph below.
- Land the ADR in the same change as the code it governs when possible — the ADR and its first consumer ship together.
Index
| # | Title | Status |
|---|---|---|
| 001 | Three-package bounded-context architecture | Accepted — partially superseded by ADR-031 (supersedes ADR-011) |
| 002 | Supabase for Database, Auth, and Infrastructure Services | Retired (folded into ADR-046) |
| 003 | LiteLLM for Multi-Provider LLM Abstraction | Retired (folded into ADR-035) |
| 004 | SQLite In-Memory for API Test Database | Retired (folded into ADR-045) |
| 005 | Application Services over CQRS | Retired (folded into README addendum) |
| 006 | API Versioning Strategy and RESTful Conventions | Accepted (complemented by ADR-043) |
| 007 | LangGraph Agent Architecture | Accepted |
| 008 | Migrate Scan Pipeline LLM Provider to Pydantic AI | Accepted |
| 009 | Operations Dashboard — Section, Not a Separate App | Retired (folded into ADR-047) |
| 010 | psycopg3 + async DB migration | Retired (folded into ADR-041) |
| 011 | Spectral is one bounded context organized into feature modules | Retired (folded into ADR-001) |
| 012 | Dev tooling — Biome, git-cliff, tiered commit hooks, ruff TD rules | Accepted (mypy portion superseded by ADR-051) |
| 013 | packages/core boundary definition | Retired (folded into ADR-065) |
| 014 | EvaluationFramework as shared contractual type | Accepted |
| 015 | SystemCard protocol with authority and authority version | Accepted |
| 016 | Attribution metadata as opaque envelope in packages/core | Retired (folded into ADR-065) |
| 017 | Event-driven signal path from Spectral to Worlds | Accepted |
| 018 | Memory system redesign — three tiers, world signal path | Accepted (World Agent tier vocabulary partially superseded by ADR-058) |
| 019 | Complete rebuild of Spectral — net-new requirements | Retired (folded into README addendum) |
| 020 | Tournament redesign — consistent scoring metric | Accepted |
| 021 | CI redesign from scratch | Retired (folded into ADR-053) |
| 022 | Eval generation architecture | Accepted |
| 023 | Holdout strategy | Accepted |
| 024 | packages/core governance | Retired (folded into ADR-065) |
| 025 | System card authority basis | Accepted |
| 026 | World model version as unit of authority | Accepted |
| 027 | Eval corpus as internal world asset | Accepted |
| 028 | Statistically unique EvalSets per request | Accepted |
| 029 | US Individual Tax Preparation as the first world-model domain | Accepted |
| 030 | Authority version as metadata-only across the context boundary | Accepted |
| 031 | Single-library Python package with per-app leaf namespaces | Accepted (partially supersedes ADR-001; context renamed scanning→platform per ADR-041 D11) |
| 032 | Storage topology — single Supabase project, three application schemas | Accepted |
| 033 | Tenancy enforcement layering | Accepted (partially supersedes ADR-002) |
| 034 | Frontend data access via API proxy | Accepted (partially supersedes ADR-002) |
| 035 | LLM stack — pydantic-ai + in-process control plane | Accepted (partially supersedes ADR-003) |
| 036 | Observability stack — OTel substrate; three-stream LLM | Accepted |
| 037 | Secrets management — provisioning script | Accepted (D1/D2 superseded by ADR-046; D5 partially superseded by ADR-073; D9 superseded by ADR-073) |
| 038 | Embedding model + hybrid retrieval | Accepted |
| 039 | Supabase Auth confirmation + hardening | Accepted (confirms ADR-002 auth portion) |
| 040 | Baseline DR + backup posture | Accepted (D2/D7 partially superseded by ADR-072) |
| 041 | Connection pooling architecture | Accepted |
| 042 | Data retention — four-state lifecycle | Accepted |
| 043 | Spectral Agent conversation persistence | Accepted (complements ADR-006) |
| 044 | Event substrate — LISTEN/NOTIFY + outbox | Accepted (D5 partially superseded by ADR-063) |
| 045 | Test Supabase instance lifecycle | Accepted |
| 046 | Alpha hosting — Render PaaS | Accepted (supersedes ADR-037 D1+D2) |
| 047 | Operations as separate deployable | Accepted (supersedes ADR-009) |
| 048 | Deployment topology — six Render services + two Pages | Accepted (D2 inter-context inheritance superseded by ADR-063) |
| 049 | Container strategy | Accepted |
| 050 | TanStack Start + frontend SPA discipline | Accepted |
| 051 | Python type checker — ty primary, ruff ANN backfill | Accepted (supersedes ADR-012 mypy portion) |
| 052 | Edge / CDN / DNS — Cloudflare | Accepted |
| 053 | CD pipeline orchestration | Accepted |
| 054 | DLQ + retry semantics | Accepted |
| 055 | Curation → Worlds interface contract | Accepted (D3 grant superseded by ADR-063; D3 Option A vs B sub-decision resolved by ADR-064) |
| 056 | T3 Memory → Worlds routing | Accepted (D3 grant superseded by ADR-063; D3 Protocol-vs-projection sub-decision resolved by ADR-064 D3 broadened) |
| 057 | Failure cluster → Rule candidate signal | Accepted |
| 058 | World Agent memory + agent-memory primitives | Accepted (partially supersedes ADR-018 World Agent tier vocabulary) |
| 059 | Operations Agent memory storage | Accepted |
| 060 | Agent tool invocation + framework-layer composition | Accepted |
| 061 | LLM testing strategy | Accepted |
| 062 | CI secrets handling | Accepted |
| 063 | Inter-context access pattern — no SQL grants | Accepted (§2 default-mechanism framing refined by ADR-070) |
| 064 | Notification-shaped reads — event-driven local replica | Accepted (D3 broadened 2026-04-30 to cover T3 memory; partially supersedes ADR-055 D3 + ADR-056 D3) |
| 065 | Contract surface — admission rules and payload pattern | Accepted (D1 superseded by ADR-068; D4 default-mechanism framing partially superseded by ADR-070; supersedes ADR-013, ADR-016, ADR-024; partially supersedes specific D#s of ADR-014, ADR-015, ADR-017, ADR-020, ADR-044, ADR-055, ADR-056, ADR-057, ADR-060, ADR-063, ADR-064 — per-decision list in ADR-065 Consequences) |
| 066 | Snapshot-based contract testing with syrupy | Accepted |
| 067 | spectral.core killer-test re-audit cadence | Accepted (D1 + D2 superseded by ADR-069) |
| 068 | Pragmatic methods on kernel value types (clarifies ADR-065 D1) | Accepted (partially supersedes ADR-065 D1) |
| 069 | Trigger-driven spectral.core re-audit | Accepted (partially supersedes ADR-067 D1 + D2) |
| 070 | Inter-context mechanism selection — simplest-fit ladder | Accepted (partially supersedes ADR-065 D4 default-mechanism framing; refines ADR-063 §2 framing) |
| 071 | Flat application module placement | Accepted |
| 072 | Cloudflare R2 for backups and Terraform state | Accepted (partially supersedes ADR-040 D2 + D7) |
| 073 | Provisioning orchestrator — OpenTofu, setup.sh, 1Password, manual_step | Accepted (partially supersedes ADR-037 D5; supersedes ADR-037 D9) |
Supersession graph
Nodes show each ADR by number and status. Arrows point from the superseded ADR to its successor.
The graph shows only ADRs that participate in supersession relationships. ADRs with no incoming or outgoing supersession edge (004, 005, 006, 007, 008, 010, 014, 015, 017, 019–023, 025–030, 032, 036, 038, 041–043, 045, 049, 050, 052, 053, 054, 057, 059, 060, 061, 062, 066, 071) are listed in the index above and are not redrawn here for legibility.
The retired style marks whole-ADR supersession events where the prior ADR file has been deleted from the filesystem and its historical context lives as an addendum on the successor. The superseded style is reserved for older retirements that have not yet had their historical context distilled into the successor (a transient state — convert to retired when the addendum lands). Partial supersession is a different state: the prior ADR remains accepted with one or more sub-decisions replaced; styled accepted and labeled in the index status column.
Relationship to the SWMS decision log
ADRs 013–028 were originally migrated from planning/swms-decisions.md (legacy numbering 023–038) as part of the 0.3.0 rebuild. The earlier entries in that document (SWMS ADR-001 through ADR-021) described the internal design of the World Model System; those decisions are captured in narrative form in the Codex under system-design/world-model-system/ rather than as standalone ADRs, because they describe the subsystem’s domain logic rather than project-wide architectural commitments. SWMS ADR-022 (three-package monorepo) is represented by ADR-001 in this repo.
Cross-references inside ADRs 013–028 that mention “the World Model System Codex” point at those Codex pages for the fuller design context.
TA-N spike identifiers
References to “TA-1,” “TA-3,” “TA-5,” “TA-19,” etc. inside ADRs 032–065 are spike identifiers from the 0.3.0 Tech Arch Review (TAR), which ran approximately TA-1 through TA-27. Each spike’s disposition produced one of the ADRs in this set. The TA-N → ADR-NNN mapping is preserved in the corresponding Linear issues and in git commit history; it is not reproduced here. A future reader landing on a TA-N reference inside an ADR can treat it as the spike-era identifier for that ADR’s disposition.
Retired ADR addenda
The following ADRs were wholly retired as part of the 0.3.0 rebuild. Their historical context worth preserving for future readers is captured below.
ADR-005 — Application Services over CQRS
ADR-005 (Accepted 2026-03-29; retired during the 0.3.0 rebuild) reversed an earlier CQRS introduction (command/query Pydantic models, handler classes, dispatcher routing) on grounds that, at v0.2 scale, CQRS added 16+ trivial query handlers, ~5-hop start-scan paths, per-request handler-graph construction, and ~1200 lines of dispatcher infrastructure for behavior that fit naturally in service methods.
Why a future reader should know about ADR-005:
- The architecture style decision is now codified in Codex under
system-design/foundations/architecture.mdx(Clean Architecture layering with thin application services in<context>.application.*); the Codex page is normative. - The driver was proportionality at the size we are: CQRS earns its complexity at scales where the read/write paths genuinely diverge. At Spectral’s scale (v0.2 then; alpha now), thin application services with a Result-returning use case handler are the right shape.
- ADR-070’s simplest-fit ladder for inter-context mechanism selection inherits the same proportionality discipline: pick the simplest mechanism that meets the real need; escalate only when conditions warrant.
Git history at the commit retiring ADR-005 preserves the original text.
ADR-019 — Complete rebuild of Spectral — net-new requirements
ADR-019 (Accepted 2026-04-20; retired during the 0.3.0 rebuild) ratified the strategic decision to rebuild Spectral from scratch rather than incrementally migrate the v0.2 codebase: bounded-context introduction, memory-system redesign, evaluation-framework ownership transfer, tournament redesign, and CI overhaul collectively constituted a ground-up restructure, and incremental migration would have inherited known technical debt the new design rejects.
Why a future reader should know about ADR-019:
- The rebuild is the world-as-is per
AGENTS.md. There is no parallel v0.2 to migrate from; there is one codebase under active development against the 0.3.0 architecture. - The v0.2 codebase informed the rebuild as reference material — sound architectural patterns (Clean Architecture layering, Result type, pure functional verdict engine), domain vocabulary, and Codex docs — but no code was copied without explicit review.
- The rebuild rationale itself (one-time strategic call) does not need restatement in newer ADRs; subsequent ADRs reference the v0.2 → 0.3.0 boundary by name where it matters.
Git history at the commit retiring ADR-019 preserves the original text.