Skip to main content
KaireonAI persists all platform state in 105 Prisma models declared in the platform schema. This page indexes every model by domain, names the underlying PostgreSQL table (via @@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 a tenantId 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

The schema file is 2,333 lines long; jump to a model with the editor’s go-to-symbol command on the model name.

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 a tenantId 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 nullable deletedAt 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

The 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: varcharVARCHAR(n) (default 255), textTEXT, integer/bigint/smallint, numeric/decimalNUMERIC(p,s), float/realREAL, doubleDOUBLE PRECISION, boolean, date, timestamp, timestamptz, jsonJSONB, 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 the manual-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 against ds_{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_history by month, which is why /recommend writes use prisma.$executeRaw instead of prisma.createMany({ skipDuplicates }). The Prisma model does not declare partitioning.
  • Soft-delete is not Prisma-enforced. Models with deletedAt rely on every API route to filter where: { 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 @@map directives. Their Postgres table names default to the model name as written rather than snake_case — verify via \dt in psql.