GBBackend Atlas
Internal technical document · 2026-08-14

GTM Bud backend atlas

Architecture boundary

Convex remains the durable data and control plane. This atlas maps the current system and its principal debt.

A Python execution tier is a later learning question. It is not a proposal to replace Convex.

Snapshot

Production scale

Origin: Stable production snapshot
Commit: 9fc01b8234d467b38baa19ee222143c9084993cd
Date: 2026-08-14

421,574Nonblank production runtime lines2,208 files
269,366Convex backend lines
102,690UI and routes lines
25,996SvelteKit server lines
9,343SvelteKit API lines
14,179Shared web runtime lines
211Application tables
512Indexes
73Recurring jobs
73Production API route modules
1,471Direct Convex function registrations
33Schema files8,318 nonblank lines
100Registered migration definitions
96Migration or backfill modules
242ADRs
32Grills
744Fix records

Runtime line counts exclude blank lines, Markdown, tests, fixtures, generated files, build output, dependencies, and this atlas. Counts include production TypeScript, JavaScript, and Svelte source.

01

Runtime and trust boundaries

The browser uses Convex directly for reactive product data. Railway performs server work and provider operations.

Current runtime

Requests, durable state, providers, and telemetry

flowchart LR
  B["Browser
End User and Admin End User"] CF["Cloudflare
edge delivery and request proxy"] SK["SvelteKit Node on Railway
SSR, API routes, provider gateway, compute"] subgraph CX["Convex: durable data and control plane"] Q["Queries
reactive reads"] M["Mutations
transactional state changes"] A["Actions
external work coordination"] SCH["Scheduler
recurring and delayed work"] DB[("Database
domain state, claims, intents, history")] Q --> DB M --> DB A --> Q A --> M SCH --> A SCH --> M end PG["Provider gateways
authentication, timeouts, request IDs"] WH["Webhook ingress
provider events and confirmations"] U["Unipile
LinkedIn and email delivery"] L["LinkdAPI
LinkedIn sourcing"] LM["LeadMagic
contact enrichment"] OR["OpenRouter
model access"] S["Stripe
billing events"] R["Resend
transactional email"] PH["PostHog
diagnostic telemetry and product analytics"] B -->|"page and API request"| CF --> SK B -->|"direct subscription and command"| Q B --> M SK -->|"server client"| Q SK --> M A -->|"call SvelteKit gateway"| SK SK --> PG PG <--> U PG <--> L PG <--> LM PG <--> OR PG <--> S PG <--> R U --> WH S --> WH R --> WH WH --> SK -->|"validate and reconcile"| M SK --> PH A --> PH M --> PH classDef client fill:#17233a,stroke:#63a4ff,color:#eef5ff classDef durable fill:#102f2a,stroke:#4fd1a1,color:#effff9 classDef gateway fill:#30271a,stroke:#e5ad52,color:#fff6e7 classDef provider fill:#29203b,stroke:#ae82ed,color:#f8efff classDef telemetry fill:#342025,stroke:#ec7184,color:#fff0f2 class B,CF client class Q,M,A,SCH,DB durable class SK,PG,WH gateway class U,L,LM,OR,S,R provider class PH telemetry
02

Sourcing to Send

This path must find good Leads and use the available Account volume. It contains the highest execution and recovery pressure.

03

Durable application data

The database receives debt slowly and releases it slowly. Names, types, indexes, and compatibility paths need explicit ownership.

Stable schema

211 tables contain 512 named indexes. The 33 schema files contain 8,318 nonblank lines.

Compatibility surface

The schema has 2,910 top-level field slots. Of these fields, 1,548 are directly optional.

Optional fields represent 53.2 percent of the top-level field slots.

Migration inventory

Stable registers 100 migration definitions. The production tree has 96 migration or backfill modules.

Branch drift

The checked-out alpha branch has 173 tables and 419 indexes. It has 38 fewer tables than Stable.

Use Stable for production scale. Use alpha only for local code analysis.

Stable schema

Application table families and named indexes

flowchart TB
  ROOT[("Stable Convex application schema
211 tables · 512 named indexes
2,910 top-level field slots")] PRODUCT["Product and delivery state
98 tables"] SOURCE["Sourcing and economics
93 tables"] OPS["Platform and operations
20 tables"] P1["v2 execution, delivery, reporting
51 tables · 107 indexes"] P2["Campaign design and messaging
14 · 33"] P3["Leads, enrichment, scrapes
13 · 49"] P4["Users, Accounts, access
6 · 19"] P5["Customer email and notifications
6 · 13"] P6["Lifecycle automation
4 · 10"] P7["UTM attribution
4 · 8"] S1["Sourcing and quality
48 tables · 114 indexes"] S2["Supply planning and generation
33 · 76"] S3["Provider cost and billing
12 · 34"] O1["Platform configuration and infrastructure
6 tables · 4 indexes"] O2["Operational alerts
6 · 15"] O3["Legacy Outreach execution
5 · 23"] O4["Observability and audit
3 · 7"] ROOT --> PRODUCT ROOT --> SOURCE ROOT --> OPS PRODUCT --> P1 PRODUCT --> P2 PRODUCT --> P3 PRODUCT --> P4 PRODUCT --> P5 PRODUCT --> P6 PRODUCT --> P7 SOURCE --> S1 SOURCE --> S2 SOURCE --> S3 OPS --> O1 OPS --> O2 OPS --> O3 OPS --> O4 classDef root fill:#102f2a,stroke:#4fd1a1,color:#effff9 classDef group fill:#30271a,stroke:#e5ad52,color:#fff6e7 classDef family fill:#17233a,stroke:#63a4ff,color:#eef5ff class ROOT root class PRODUCT,SOURCE,OPS group class P1,P2,P3,P4,P5,P6,P7,S1,S2,S3,O1,O2,O3,O4 family
Schema change

Widen, migrate, and narrow

flowchart LR
  W["Widen
accept old and new shapes"] C["Compatibility
write both shapes and read either shape"] B["Backfill
move existing documents in bounded batches"] V{"Verify
check coverage, invariants, and read behavior"} CUT["Cutover
read and write only the new shape"] N["Narrow
remove old fields, indexes, and compatibility code"] HOLD["Hold and repair
keep compatibility active"] W --> C --> B --> V V -->|"verified"| CUT --> N V -->|"gap found"| HOLD --> B classDef phase fill:#17233a,stroke:#63a4ff,color:#eef5ff classDef decision fill:#30271a,stroke:#e5ad52,color:#fff6e7 classDef recovery fill:#3a1e25,stroke:#f17487,color:#fff0f2 class W,C,B,CUT,N phase class V decision class HOLD recovery

Table count is not debt count

A table can reduce cost and coupling when it has one stable purpose. Enrichment sibling rows prevent large Lead reads during narrow operations.

Send indexes prevent full scans on dispatch paths. Projections keep read models small. Immutable Supply Plan data separates compiled intent from mutable progress.

Debt starts when ownership, lifetime, invariants, or migration paths are unclear. A high table count is evidence for review, not proof of debt.

Largest Stable table contracts

Field counts include direct top-level fields. They exclude nested object fields and Convex system fields.

407 appSettings395 optional · 0 indexes81 unipileAccounts70 optional · 4 indexes80 campaigns73 optional · 10 indexes64 supplyPlans29 optional · 8 indexes64 leadItems60 optional · 25 indexes49 sourcingAttempts37 optional · 13 indexes

Architecture record pressure signals

ADR-0008v2Config is the canonical Campaign Config. The legacy stepData shape remains stored.

ADR-0029steps[] is authoritative. The stored channels field remains for compatibility.

ADR-0038v2ConvProjection protects dashboard reads. It also requires projection writers and repair.

ADR-S0138The Plan Space Definition becomes authoritative. Older manifest and payload tables remain declared.

Walker migration grillSome v1 fields remain because live replay can still read historical shapes.

ADR-S0099Provider verification is required before retry. The current orphan-intent path does not implement this verification.

04

Code and decision inventory

Large modules show concentration. Document counts show how much operating knowledge supports the runtime.

Largest Stable runtime modules

  1. web/src/convex/outreach/v2/leadGenExecutor.ts 7,324 nonblank lines
  2. web/src/convex/appSettings.ts 4,010 nonblank lines
  3. web/src/convex/lib/posthogEvents.ts 3,438 nonblank lines
  4. web/src/lib/admin/controlBoardCatalog.ts 3,388 nonblank lines
  5. web/src/convex/outreach/v2/dashboardQueries.ts 3,253 nonblank lines

These counts use the same Stable snapshot and nonblank-line rule as the production KPI.

Architecture record

ADRs
242
Grills
32
Fix records
744

ADRs record selected trade-offs. Grills preserve operator decisions. Fix records preserve defect evidence and correction history.

Later learning question

Can a Python execution tier reduce long-running execution complexity while Convex keeps durable state and control?

Answer this question with one bounded workflow and measured operational evidence. Do not make it the current atlas conclusion.

Expanded diagram