Architecture Decision Records
Architecture Decision Records (ADRs) document the why behind key technical decisions. They capture context, options considered, and consequences — so future developers understand not just what was built, but the reasoning that shaped it.
Why ADRs?
- Institutional memory — decisions don’t live in someone’s head
- Onboarding accelerator — new developers understand the reasoning quickly
- Reversal insurance — if context changes, we can revisit with full information
- Debate settler — “why don’t we just…?” has a documented answer
ADR-0004: PII Classification in Schema Definitions
Status: Accepted Date: 2024 Category: Data Privacy
Context
We need to track which fields contain personally identifiable information (PII) for GDPR and CCPA compliance. Options:
- Separate privacy mapping document
- Database-level annotations
- Co-located schema extensions (chosen)
Decision
Co-locate privacy metadata alongside field definitions in the schema. In LinkML schemas, this takes the form of annotations; in JSON Schema contexts, the x-pii extension:
{
"email": {
"type": "string",
"format": "email",
"x-pii": {
"level": "sensitive",
"category": "email",
"retention": "2 years",
"encryption": "at-rest",
"gdprField": true,
"ccpaField": true
}
}
}Rationale
- Co-location — privacy requirements live with the field definition, not in a separate document that drifts out of sync
- Machine-readable — automated compliance scanning, encryption enforcement, and retention policies
- Developer-friendly — privacy is visible during schema authoring, not an afterthought
- Extensible — new categories and fields can be added without changing the schema format
Consequences
- Every schema author must consider PII classification for every field
- Code generation can enforce encryption and retention policies automatically
- Compliance audits can be automated by scanning schema files
ADR-0005: Cross-Table Search Implementation
Status: Accepted Date: 2024 Category: Search
Context
Users need to search across multiple tables (users, invoices, organizations) from a single search bar. Options:
- Elasticsearch / Algolia (external service)
- PostgreSQL full-text search with materialized views
- Client-side aggregation with Supabase (chosen)
Decision
Use PostgreSQL text search with trigram matching (pg_trgm), executed in parallel across tables via Supabase client.
const results = await Promise.all([
supabase.from('users').select('*').textSearch('name', query),
supabase.from('invoices').select('*').textSearch('description', query),
supabase.from('organizations').select('*').textSearch('name', query),
]);Rationale
- No additional infrastructure — uses existing Supabase/PostgreSQL
- Parallelizable — queries run concurrently across tables
- Good enough — works well for datasets under 100K records per table
- Migration path — can switch to Elasticsearch/Algolia later if scale demands it
Consequences
- Performance degrades with very large tables (>100K rows)
- No fuzzy matching beyond trigrams
- Search relevance ranking is basic compared to dedicated search services
- Clear upgrade path to Algolia/Meilisearch when needed
ADR-0006: API Security Classification
Status: Accepted Date: 2024 Category: Security
Context
We need a consistent way to enforce different security levels across API endpoints. Options:
- Middleware-based per-route configuration
- Decorator/annotation pattern
- Directory structure enforcement (chosen)
Decision
Three tiers of API security, enforced by directory structure:
src/app/api/
├── internal/ # Service role key required
├── v1/ # Authenticated user session
└── public/ # No auth, aggressive rate limitingRationale
- Visible at a glance — the security level is encoded in the file path
- Hard to bypass — you can’t accidentally create a public endpoint in the wrong directory
- Middleware simplification — security middleware reads the path prefix to determine the tier
- Code review friendly — reviewers immediately see which tier a new endpoint belongs to
Consequences
- Moving an endpoint between tiers requires moving the file
- Three separate middleware configurations to maintain
- Rate limiting configuration is per-tier, not per-endpoint (can be overridden)
- New developers must understand the tier system before creating endpoints
ADR Template
Use this template when creating new ADRs:
## ADR-NNNN: [Title]
**Status:** Proposed | Accepted | Deprecated | Superseded by ADR-NNNN
**Date:** YYYY-MM-DD
**Category:** Architecture | Security | Data | Performance | DX
### Context
What is the issue we're seeing that motivates this decision?
### Decision
What is the change that we're proposing?
### Rationale
Why is this the best option? What alternatives were considered?
### Consequences
What becomes easier or harder because of this change?
What are the tradeoffs?Naming Convention
ADRs are numbered sequentially: ADR-0001, ADR-0002, etc. Numbers are never reused — if an ADR is deprecated, it keeps its number and a new ADR supersedes it.
When to Write an ADR
- Choosing between competing technologies or approaches
- Establishing a pattern that the whole team will follow
- Making a decision that’s expensive to reverse
- When someone asks “why did we do it this way?” more than once