# 04 — DATA MODEL (Prisma) Generated 2026-07-21 · prod arm64-a8595e555c2d · regenerated from live code > snapshot 2026-07-21 · regenerated live from `prisma/schema.prisma` (3092 lines, **130 models**). Postgres + pgvector. Grouped by domain; key fields, relations, and `@@unique`/`@@index` shown. Security-relevant constraints called out. Datasource: PostgreSQL. `DocumentChunk.embedding Unsupported("vector(1536)")` + `fts Unsupported("tsvector")` (pgvector; written/read via raw SQL in `src/lib/rag/vectorStore.ts`). Money columns use `Decimal(18,6)`. --- ## Identity / auth / audit | Model | Key fields | Constraints | |---|---|---| | **User** | email `@unique`, password (bcrypt), emailVerified, isSuspended/suspendedAt/Reason/By, ageAffirmed*, timezone/timezoneSource, displayName/location*/preferredChannel/greetingMode (PROFILE epic; `Source` + `UpdatedAt` pattern), agentEmail/agentEmailVerified/agentEmailPending/agentEmailVerifyTokenHash (SHA-256, never raw), twoFactorEnabled/twoFactorSecret/recoveryCodes | `email @unique`; `@@index([agentEmailVerifyTokenHash])` | | **UserSession** | token `@unique`, userAgent, ipAddress, lastActiveAt, revokedAt | server-side session records (`createUserSession`/`ensureSessionActive`) | | **AuthOAuthAccount** | provider, providerAccountId, email | `@@unique([provider, providerAccountId])` | | **LinkingToken** | messenger self-link tokens | `@@unique([provider, providerId])` | | **AuditLog** | action, targetType/targetId, ipAddress, metadata | `@@index([userId, createdAt])`, `@@index([action])` | | **EmailPreference / EmailIngestToken** | notification prefs; inbound email ingest tokens | | ## Notebooks / sources / RAG | Model | Key fields | Constraints | |---|---|---| | **Notebook** | userId, teamId, title, isPublic | `@@index([userId, createdAt])` | | **Source** | notebookId, corpusId, sha256, status | `@@index([sha256])`, `@@index([corpusId])` | | **Document / DocumentChunk** | Document = metadata/stats; **DocumentChunk** = `embedding vector(1536)`, `fts tsvector`, offsets + anchorId/startLine/endLine/startParagraph/endParagraph (nullable pre-migration `20260505000000_document_chunk_anchors`, read paths NULL-tolerant) | | | **Corpus** | per-notebook corpus routing | | | **SharedNotebook / PublicNotebook** | publicToken, slug, takenDown | `@@index([publicToken])`, `@@index([slug])` | | **NoteComment / NotebookActivity** | threaded comments (parentId/rootId), activity feed | | | **Note / Message** | note tree (parentId/rootId), chat messages | | | **Entity / DocumentEntity / EntityMergeDecision** | entity-resolution graph (NER) | `Entity @@unique([notebookId, key])`, `DocumentEntity @@unique([sourceId, name])` | | **GlobalTopicStats** | topic → aggregate | `@@unique([topic])` | ## Embed / widget / guest | Model | Key fields | Constraints | |---|---|---| | **EmbedConfig** | publicToken, origin allowlist | `@@index([publicToken, origin, pagePath])` | | **EmbedShareLink / EmbedConversationSession / EmbedConversationMessage** | share links, sessions (per client key), messages | `EmbedConversationSession @@unique([configId, origin, pagePath, clientSessionKeyHash])`; `Message @@unique([sessionId, sequence])`; `EmbedConversationLogArchive @@unique([embedConfigId, clientKeyHash])` | | **EmbedLead** | captured leads, retentionUntil | `@@index([userId, status, createdAt])` | | **GuestConnectBundle** | ownerUserId, status, expiresAt | connect/[slug] backing | ## Billing / economy / ledger (money-sensitive) | Model | Key fields | Constraints — **security-relevant** | |---|---|---| | **Subscription** | stripeCustomerId, tier | `@@index([stripeCustomerId])` | | **UsageRecord** | userId, month, counters | **`@@unique([userId, month])`** (quota row) | | **UserMetrics / DailyMetrics** | per-user + per-day metrics | `DailyMetrics @@unique([userId, date])` | | **Transaction** | type (earn/spend), currency (coins/gems), amount, source, **reference** | **`@@unique([userId, reference])`** — idempotency guard (SEC-ECONOMY-IDEMPOTENCY-UNIQUE): stops earns being infinitely replayable | | **CurrencyBalance / UserXP / UserProgress / UserStreak** | gamification balances/XP | | | **UserBalance** | tenantId, userId (NULL = pooled), cash/subscriptionGrant/promo/reserved `Decimal(18,6)`, `version` (optimistic lock) | **`@@unique([tenantId, userId])`** — one wallet per (tenant,user); hybrid seat-fee + shared team pool (billing decision #9) | | **LedgerEntry** | balanceId, kind, amountUsd (signed), balanceBucket, **idempotencyKey `@unique`** | append-only signed money ledger; `idempotencyKey @unique` REQUIRED so replays never double-settle | | **BalanceHold** | callId, amountUsd, expiresAt, status | pre-auth reservation (hold/tick/settle) | | **TokenUsage** | channel, operation (stt/tts/llm/embedding), tokens, audioSeconds, model, costUsd | per-turn LLM/voice cost accounting | | **ProviderRate** | provider, country, serviceType rate card | `@@unique([provider, country, serviceType])` | | **ShopItem / Purchase** | key `@unique`, price/currency/tier | shop | ## Games / gamification / social | Model | Constraints | |---|---| | **Achievement / UserAchievement / Character / CharacterInstance** | `UserAchievement @@unique([userId, achievementId])`, `CharacterInstance @@unique([userId, characterId])` | | **Deck / DeckCard / DeckSession / Duel / GameEvent / ArcadeRun / BossFightHistory** | game surfaces; `DeckSession` stored client-supplied xp (SEC-0281) | | **Bounty / BountySolution** | `BountySolution @@unique([bountyId, solverId])` | | **ReferralCode / ReferralClick** | growth/referral | | **SocialIntegration / SocialPost** | `SocialIntegration @@unique([userId, platform])` | | **Podcast / PodcastLike / PodcastPlay** | `Like/Play @@unique([podcastId, userId])` (SEC-0279) | | **Show* (Attendance/Question/Rating/Subscription) / StudioRoom*** | live-show + studio-room; `@@unique([showId, userId])` family | ## Teams / collaboration | Model | Constraints | |---|---| | **Team / TeamMember** | `TeamMember @@unique([teamId, userId])` (RBAC seat) | | **TeamQuest / TeamQuestContribution** | team quests (contribute gate SEC-0280) | ## Telephony / voice / meeting rooms (Codex zone) | Model | Constraints | |---|---| | **TelephonyEndpoint** | `@@unique([provider, extension])` | | **VoiceSession / VoiceTurn / VoiceArtifact / TelephonyEventLog** | `VoiceSession @@unique([provider, providerCallId])`, `VoiceTurn @@unique([sessionId, turnIndex])` | | **MeetingRoom / MeetingParticipant / MeetingLeg / MeetingTurn / MeetingEvent / MeetingArtifact** | `MeetingParticipant @@unique([roomId, userId])`, `MeetingTurn @@unique([roomId, turnIndex])` | | **ChannelConfig / MessageLog / MessengerGuardEvent** | cross-channel config + guard event log | | **WebhookDedupe** | **`@@unique([provider, providerEventId])`** — idempotent webhook processing (update_id / MessageSid) | | **TokenUsage / ProviderRate** | (see billing) | | **PstnDIDNumber / PstnDIDKyc** | `PstnDIDKyc @@unique([userId, country])` | | **OutboundCallConfirmation / MeBridgeRequest/Session/UserMode** | me-bridge + outbound consent | ## Video pipeline | Model | Notes | |---|---| | **VideoAsset / VideoPipelineRun / VideoArtifact / VideoEvent** | video→doc pipeline; run status/index | | **VideoByokCredential / Policy / CredentialEvent / QuotaLedger** | bring-your-own-key video creds + quota ledger | ## Agent / autonomy / system | Model | Notes | |---|---| | **UserAgent / AgentTask / AutonomyRun / AgentList / AgentListItem / CliRequest** | agent tasks, autonomy runs, lists | | **UserSettings / UserIntegration / SystemSetting / RuntimeConfig** | settings + runtime config; `UserIntegration` per-provider | | **ShortLink** | `s/[token]` resolver | | **AbuseReport / DmcaNotice / TakedownAction** | legal/abuse pipeline | | **AnalyticsOutbox** | analytics event outbox | | **CouncilRun / VerificationRun / VerificationClaim / VerificationEvidence / FactcheckPolicy** | factcheck/verify (parked zone) | | **VisualRun / VisualArtifact / VisualArtifactLink** | visualize mode | --- ### Idempotency / anti-abuse constraints to know - `Transaction @@unique([userId, reference])` — economy earn replay guard. - `LedgerEntry.idempotencyKey @unique` — money ledger double-settle guard. - `WebhookDedupe @@unique([provider, providerEventId])` — inbound webhook replay guard. - `UsageRecord @@unique([userId, month])`, `UserBalance @@unique([tenantId, userId])` — quota/wallet uniqueness. - `PodcastLike/Play @@unique([podcastId, userId])`, `UserAchievement`, `CharacterInstance` — one-per-user guards.