@@map), and links each model back to the API or operator surface that owns it.
What it indexes
The platform schema is the single source of truth for every persisted entity. Every API route, cron job, MCP tool, and worker reads or writes one or more of these models. The Tenant model anchors multi-tenancy — most other models carry atenantId column and an index on it.
This page exists because operators and contributors need a one-screen overview before they grep for symbols. For per-API request and response shapes, follow the per-domain links in the Reference tables below; this page only documents the persistence layer.
Quick start
How it works
Prisma 7 lifecycle
Every model in the schema maps to one PostgreSQL table via the@@map("table_name") directive. The Prisma client generator emits a typed client into a gitignored generated directory; that path is regenerated by npm run build (which runs prisma generate && next build) and by the postinstall hook on npm install.
The datasource block in the schema declares only the provider — provider = "postgresql" (no url). The connection URL lives in the Prisma config file and is read from DATABASE_URL. This split is required by Prisma 7; setting url directly inside the schema errors out with The datasource property 'url' is no longer supported in schema files.
Multi-tenant isolation
Every tenant-scoped model carries atenantId String column and at least one composite index that begins with tenantId. API routes enforce isolation by adding where: { tenantId } on every query through the shared tenant-resolution helper. The Tenant table itself (tenants) holds tenant-level settings — settings (JSON), aiAnalyzerSettings (JSON), isPlayground (boolean) — that gate features per tenant.
A handful of models — User, audit log rows, Account, Session, the email-verification token table, and the per-tenant region tag — either hold cross-tenant rows or scope by userId instead. Each is called out in its row below.
Soft delete vs hard delete
Models with operator-visible “archive” semantics carry a nullabledeletedAt DateTime? column and an index on [tenantId, deletedAt]. API routes filter out soft-deleted rows by default; the audit log captures the soft-delete event. Models with deletedAt include Category, SubCategory, Channel, Placement, FlowRoute, Offer, Creative, OutcomeType, QualificationRule, ContactPolicy, DecisionFlow, TriggerRule, GuardrailRule, RankingProfile, and SummaryDefinition.
Other tables — the suppression ledger, the transactional outbox, the dead-letter store, pipeline run records, decision traces, and the interaction history fact table — are hard-deleted by retention crons per the per-tenant retention policy data class.
IR-native pipelines
Pipeline.irVersion is String NOT NULL DEFAULT "1.0" — every pipeline is IR-native after the legacy ETL editor was deleted on 2026-04-28. The full DAG lives in the pipeline IR version row’s ir JSONB column. The legacy per-node and per-edge tables were removed in the same change; the underlying pipeline_nodes and pipeline_edges Postgres tables were dropped via the 04_drop_legacy_pipeline_tables.sql manual SQL script.
Reference
Each subsection groups models by domain. Tables list the model name, the underlying Postgres table (@@map), the most operationally relevant fields, primary relations, and a one-line purpose. Schema line numbers below are valid against the schema as of 2026-04-30.
Decisioning core
The studio entities that make up an offer catalog and the four-stage decisioning pipeline (Eligibility / Fit Filters / Match Scoring / Ranking).Customer & interactions
Customer-scoped rows that drive the decisioning loop and the analytics fact tables.Algorithm models
ML model registry, versioning, governance, and per-scope adaptive learning state.Audit & compliance
Tamper-evident logs, retention, consent, and DSAR machinery.Governance
Approval workflows, four-eyes governance, and operator-driven change control.Operator & infra
Tables consumed by operators, cron jobs, and out-of-band workers.AI & imports
Conversation ledger, AI recommendations, and the AI document import pipeline (V1).Journeys
Multi-step customer journey orchestration — entry conditions, step definitions, and per-customer enrollment state.Flow & pipeline
Data ingestion, schema management, and pipeline runtime.Content
Reusable content templates, CMS-sync content items, and template inheritance.Reports
Scheduled report templates, schedules, and execution runs.Behavioral metrics & summaries
User-defined metrics evaluated against the interaction stream.Configuration
Prisma 7 datasource split
url field is illegal inside the schema file under Prisma 7. The connection URL is read from DATABASE_URL by the Prisma config and passed to the generated client.
ds_* tables created outside the schema
Data-schema rows do not declare their column shape inside the Prisma schema. Each row holds metadata; the actual ds_{name} table is created at runtime by the platform’s DDL helper via CREATE TABLE statements when the schema is published. Adding a schema field issues ALTER TABLE ds_{name} ADD COLUMN.
This split exists because the column shape is per-tenant and dynamic — it cannot be declared statically in a global schema. Operators viewing \d ds_* in psql will see real Postgres tables that are not represented in the Prisma schema.
Primary key & id column. When no schema field is marked as the primary key, the DDL helper adds an auto id BIGSERIAL PRIMARY KEY. If exactly one field is marked primary, that column becomes the PK and the auto id is skipped. If two or more fields are marked primary, they form a composite key emitted as a single table-level PRIMARY KEY (col_a, col_b, …) constraint (Postgres rejects multiple per-column PRIMARY KEY clauses). Every ds_* table also carries created_at / updated_at timestamp columns.
Row-level security. If a ds_* table has a tenantId (or tenant_id) column, the DDL helper enables Postgres RLS on it at creation time — an additive isolation layer on top of the application’s where: { tenantId } scoping. RLS enablement is best-effort: a failure is logged but does not fail table creation.
Type mapping. Each schema field’s abstract dataType maps to a concrete Postgres type: varchar → VARCHAR(n) (default 255), text → TEXT, integer/bigint/smallint, numeric/decimal → NUMERIC(p,s), float/real → REAL, double → DOUBLE PRECISION, boolean, date, timestamp, timestamptz, json → JSONB, uuid, and anything unrecognized falls back to TEXT.
Table naming. Schemas created through the Schemas API/UI land at ds_{name} (the name lowercased, non-alphanumerics replaced with _). The built-in seed/default schemas instead use a tenant-short prefix — ds_{tenantShort}_{name}, where tenantShort is the tenant id with hyphens stripped, first 8 chars — so both forms can coexist depending on how the schema was created.
Manual SQL for partitioning and migrations
Schema changes that Prisma cannot express are stored in themanual-sql directory under the Prisma folder:
Run these in numerical order against any database that pre-dates them. Production databases run them through CI; local databases need a manual
psql -f or prisma db execute --file.
Honest limits
ds_*tables are not in the Prisma schema. The dynamic per-tenant entity tables created from data-schema rows live outside the schema file. There is no way to type-check them through Prisma — runtime queries againstds_{name}go through raw SQL or generated query builders.- Interaction history is partitioned in production. The model declares one logical table; production deployments range-partition
interaction_historyby month, which is why/recommendwrites useprisma.$executeRawinstead ofprisma.createMany({ skipDuplicates }). The Prisma model does not declare partitioning. - Soft-delete is not Prisma-enforced. Models with
deletedAtrely on every API route to filterwhere: { deletedAt: null }. There is no global Prisma middleware enforcing this — a raw query or a route that forgets the filter will return soft-deleted rows. - Cross-offer constraints have no UI surface yet. The model is wired into Lagrangian ranking in both realtime and batch decisioning paths via the cross-offer ranking helper, but rows must be inserted directly via SQL or a future admin API — there is no Studio UI for editing them today.
- Some operator-internal tables have no public API surface. The transactional outbox, dead-letter store, channel-delivery ledger, export checkpoints, ML job results, AI-import token ledger, AI-import skip digest, policy snapshots, approval-request stages, custom-role assignments, NextAuth verification tokens, WebAuthn credentials, the per-tenant region tag, model-adaptation rows, and customer-engagement-health rollups are read/written by internal code paths only — there is no
/api/v1/{model}REST surface for them. - Several alternate storage backends are referenced but not declared here. ScyllaDB / DynamoDB / OpenSearch backends mentioned in Scaling are write paths inside platform adapters; their row shapes are not declared in the Prisma schema. Treat the Prisma schema as the authoritative shape for the canonical Postgres path only.
- AI import models and content models lack
@@mapdirectives. Their Postgres table names default to the model name as written rather than snake_case — verify via\dtin psql.
Related
- Schemas API — the data-schema + schema-field REST surface that creates the dynamic
ds_*tables. - Audit Logs API — query the tamper-evident audit chain.
- Decision Traces API — query decision-trace rows.
- Decisioning Gates — the rule-engine surface backed by qualification rules.
- Contact Policies API — the contact-policy surface (root rule + scope rows).
- Frequency Caps — operator semantics for the frequency-cap model.
- Operations Runbook — operator view of the outbox, dead-letter store, and channel-delivery ledger.
- Architecture Overview — system-level view above this index.
- Prisma 7 Schema Reference — upstream Prisma docs.