all docsdocs/reference/system-architecture.md

System architecture

Reference · 2026-08-30. How Frankie's pieces fit, today and through the roadmap phases. Companions: ../ARCHITECTURE.md (repo structure), ../plans/database-design.md (schema), ../plans/card-data-ingestion.md (pipeline).

The system at a glance

                       ┌──────────────────────────────────────────────┐
   issuer pages,       │                INGESTION (Phase 0–1)         │
   terms PDFs,   ────▶ │  change detection → fetch → LLM extraction   │
   CFPB data           │  → validation → human review → publish()     │
                       └───────────────────┬──────────────────────────┘
                                           │ service role, one transaction
                                           ▼
                       ┌──────────────────────────────────────────────┐
                       │            SUPABASE (Postgres + Storage)     │
                       │  L1 evidence: sources, extractions, raw docs │
                       │  L2 truth:    versioned rows (terms, offers, │
                       │               rewards, benefits, partners)   │
                       │  L3 derived:  card_events                    │
                       │  RLS: public read on L2/L3, pipeline-only L1 │
                       └───────────────────┬──────────────────────────┘
                                           │ anon key, read-only
                                           ▼
                       ┌──────────────────────────────────────────────┐
                       │        WEB APP (@frankie/web on Vercel)      │
                       │  getDataSource() ──▶ FrankieDataSource       │
                       │    ├─ MockDataSource (@frankie/mocks) ← MVP  │
                       │    └─ SupabaseDataSource ← post-seed         │
                       │  lib/domain: pure stats + valuation          │
                       │  pages: dashboard · cards · compare ·        │
                       │         issuers · changes · analyst · admin  │
                       └──────────────────────────────────────────────┘
                                           │ later
                                           ▼
                          API product · AI analyst · consumer agent

One asset (the card graph), many consumers — the Channel 3 playbook (see ../PRD.md §Thesis).

Components

Web app — Next.js 16 App Router on Vercel, server components by default. All reads go through the repository interfaces in apps/web/src/lib/data; all business math is pure functions in lib/domain; all styling is tokens from theme.css. No app-side writes to the database exist or are planned — the app is a read model.

Database — Supabase Postgres, three layers (evidence → versioned truth → derived events) with the publish function as the only write path into layers 2/3. Schema in supabase/migrations/, seed generated from @frankie/mocks. Raw fetched documents live in Supabase Storage, hash-addressed, pointed at by sources.storage_path.

Ingestion pipeline (not yet built — Phase 0/1) — scripts in a future packages/pipeline, run by GitHub Actions cron: change detection on ~250 tracked issuer URLs → fetch + archive → LLM extraction to the schema contract → validation gates → human review queue → publish_extraction(). The pipeline holds the service key; it is the only component with write access.

Docs surface/admin renders docs/ from the repo at request time; the repo itself is the CMS.

Data flow contracts

  1. UI ↔ data: the FrankieDataSource interface. Swapping mock → Supabase changes one factory (lib/data/index.ts) and zero pages. The generated seed makes the two sources start identical, which is the adapter-equivalence test.
  2. Pipeline ↔ database: the extraction JSON contract (defined in Phase 0) + publish_extraction(). Nothing else may mutate layer 2/3 — "history is sacred" is enforced by grants, not convention.
  3. Database ↔ future API product: the same layer 2/3 tables exposed via PostgREST or a thin API app later; no separate data model.

Environments & deploys

ConcernTodayNotes
HostingVercel (apps/web, root dir setting)CI (lint/typecheck/build) on GitHub Actions
Domainfrankie.cardsmetadataBase set in layout
DataNEXT_PUBLIC_DATA_SOURCE=mockflip to supabase per env after seeding
Databasenone yetsupabase init + db reset locally; link + db push for cloud
Secretsnone in the appanon key is public by design; service key lives only in pipeline CI

Security model

  • Anon key + RLS read-only policies = the public app can never write.
  • Evidence/staging tables have no anon policies (deny by default).
  • Service key never ships to the browser or the web app runtime; it exists only in pipeline jobs.
  • Auth (Supabase Auth) arrives in PRD Phase 2 for gated features (exports, alerts); RLS policies already distinguish anon vs authenticated.

How the phases change this picture

  • Phase 0–1 (data foundation): pipeline + database go live; web flips DATA_SOURCE. No page changes.
  • Phase 3 (change tracking): the changes feed and history timelines start rendering real card_events from the publish path — same UI.
  • Phase 4 (AI analyst): a query-compilation layer (NL → structured filters over layer 2/3) behind the existing analyst page. Reads only.
  • API product: keyed read access to the same tables/views; rate limits and plans at the edge, not in the schema.