Architecture overview
Launchpad V2 is a normalized-ingestion + governed-reporting platform with separate paid-media, commerce, attribution, configuration, and delivery planes.
Subsystems
Core planes
1. Ingestion (Inngest)
- Shared durable functions execute connector-specific fetch stages and write to the connector's analytics/configuration paths.
- Paid facts use deterministic event identity plus the ClickHouse ordered grain/version contract. Connector-specific ledgers have their own identity rules.
- Concurrency is function-specific: some paths serialize globally while others key by binding or client. Read each Inngest function's configuration before changing it.
2. Analytics, commerce, and attribution (ClickHouse)
- User-facing tenant reads resolve server-side scope before querying. Background, audit, and internal paths must carry an explicit verified tenant scope appropriate to their caller.
- Uses the relevant maintained latest-state view or ledger path;
mv_ad_daily_rollupis only one paid-acquisition surface. - Hierarchy queries follow the 6-level order (see Data model).
3. Configuration and credentials (Supabase)
- Owns tenants, users/RBAC, brands, bindings, report settings, and secret pointers.
- Uses RLS plus server-side scope resolution; ClickHouse
client_idmaps to the client slug.
4. Creative QC and operator workflows
- Registry of checks:
caption,qc_score,field_presence,compliance_v1,lander_congruency. - One durable Inngest fn + per-check plug-in files.
- Writes to
creative_check_resultsin ClickHouse;ad_check_statusupsert in Supabase drives the Review Queue.
Hard rules
These are the "if you break this, everything downstream breaks" rules. Use the available tests and audits for the path you change; not every rule has a universal automated guard.
- Every user-facing marketing entrypoint resolves server-side scope. Background/audit callers must supply and test an explicit tenant boundary. Cross-client leakage is a security incident.
- Rollups are derived, never written.
mv_ad_daily_rollupis a plain VIEW computed at read time. Never write to a rollup, and never re-introduce an accumulating materialized view over aReplacingMergeTreesource — that bug inflated production spend 8× once. event_idis deterministic. Don't include timestamps or nonces in the hash inputs.- Hierarchy is 6 levels in fixed order. UI code that special-cases level order will drift and eventually leak.
- Source definitions stay explicit. Ad account/source is one reporting dimension, while platform, CRM, attribution, and commerce metrics retain their own provenance and denominators.
Key files by concept
| Concept | File | Notes |
|---|---|---|
| Tenant scoping | src/lib/auth-scope.ts | resolveQueryScope() — the fail-closed chokepoint |
| RBAC | src/lib/rbac.ts | Roles, permissions, grants. Single source of truth |
| Nav gating | src/lib/nav.ts | Permission-driven menu, derived from rbac.ts |
| ClickHouse queries | src/lib/marketing/queries.ts, src/lib/health-queries.ts | Every tenant query carries a client_id filter |
| Ad-platform fetchers | src/lib/marketing/platform-fetch.ts, src/lib/marketing/platforms/ | See that folder's guide (AGENTS.md) |
| Check runner | src/inngest/creative-check.ts | The one QC runner, driven by src/lib/checks/registry.ts |
| Background jobs | src/inngest/ | Registry is the functions array in functions.ts |
| Auth gate | src/proxy.ts | Next.js 16's renamed middleware.ts, Node runtime. Layout guards remain the primary gate |
| Validated env access | src/lib/env.ts | Primary Zod schema; direct runtime/build reads also exist and must be inventoried |
Fact table naming
The ClickHouse paid fact table is marketing_paid_acquisition (a ReplacingMergeTree). mv_ad_daily_rollup is a plain view over the maintained latest-state path and stores nothing. See ClickHouse rollups.
Continue to Data model for the schema-level view.