Skip to Content
StandardsArchitecture Decisions

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:

  1. Separate privacy mapping document
  2. Database-level annotations
  3. 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:

  1. Elasticsearch / Algolia (external service)
  2. PostgreSQL full-text search with materialized views
  3. 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:

  1. Middleware-based per-route configuration
  2. Decorator/annotation pattern
  3. 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 limiting

Rationale

  • 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
Last updated on