datasource db { provider = "postgresql" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" } // ─── Auth & Billing ──────────────────────────────────────────── model User { id String @id @default(cuid()) email String @unique passwordHash String name String? emailVerified Boolean @default(false) demoSeeded Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt subscription Subscription? apiKeys ApiKey[] traces Trace[] passwordResetTokens PasswordResetToken[] emailVerificationTokens EmailVerificationToken[] @@index([email]) } model PasswordResetToken { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) token String @unique // SHA-256 hash of the raw token expiresAt DateTime used Boolean @default(false) createdAt DateTime @default(now()) @@index([token]) @@index([userId]) } model EmailVerificationToken { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) token String @unique // SHA-256 hash of the raw token expiresAt DateTime used Boolean @default(false) createdAt DateTime @default(now()) @@index([token]) @@index([userId]) } model ApiKey { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) name String @default("Default") keyHash String @unique // SHA-256 hash of the actual key keyPrefix String // First 8 chars for display: "al_xxxx..." lastUsedAt DateTime? revoked Boolean @default(false) createdAt DateTime @default(now()) @@index([keyHash]) @@index([userId]) } model Subscription { id String @id @default(cuid()) userId String @unique user User @relation(fields: [userId], references: [id], onDelete: Cascade) tier SubscriptionTier @default(FREE) stripeCustomerId String? @unique stripeSubscriptionId String? @unique stripePriceId String? currentPeriodStart DateTime? currentPeriodEnd DateTime? // Usage tracking for the current billing period sessionsUsed Int @default(0) sessionsLimit Int @default(20) // Free tier: 20/day, paid: per month status SubscriptionStatus @default(ACTIVE) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([stripeCustomerId]) @@index([stripeSubscriptionId]) } enum SubscriptionTier { FREE // 20 sessions/day STARTER // $5/mo — 1,000 sessions/mo PRO // $20/mo — 100,000 sessions/mo } enum SubscriptionStatus { ACTIVE PAST_DUE CANCELED UNPAID } // ─── Observability ───────────────────────────────────────────── model Trace { id String @id @default(cuid()) sessionId String? name String status TraceStatus @default(RUNNING) tags String[] @default([]) metadata Json? isDemo Boolean @default(false) userId String? user User? @relation(fields: [userId], references: [id], onDelete: SetNull) totalCost Float? totalTokens Int? totalDuration Int? startedAt DateTime @default(now()) endedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt decisionPoints DecisionPoint[] spans Span[] events Event[] @@index([sessionId]) @@index([status]) @@index([createdAt]) @@index([name]) @@index([userId]) } model DecisionPoint { id String @id @default(cuid()) traceId String trace Trace @relation(fields: [traceId], references: [id], onDelete: Cascade) type DecisionType reasoning String? chosen Json alternatives Json[] contextSnapshot Json? durationMs Int? costUsd Float? parentSpanId String? span Span? @relation(fields: [parentSpanId], references: [id]) timestamp DateTime @default(now()) @@index([traceId]) @@index([type]) @@index([timestamp]) } model Span { id String @id @default(cuid()) traceId String trace Trace @relation(fields: [traceId], references: [id], onDelete: Cascade) parentSpanId String? parentSpan Span? @relation("SpanTree", fields: [parentSpanId], references: [id]) childSpans Span[] @relation("SpanTree") name String type SpanType input Json? output Json? tokenCount Int? costUsd Float? durationMs Int? status SpanStatus @default(RUNNING) statusMessage String? startedAt DateTime @default(now()) endedAt DateTime? metadata Json? decisionPoints DecisionPoint[] @@index([traceId]) @@index([parentSpanId]) @@index([type]) @@index([startedAt]) } model Event { id String @id @default(cuid()) traceId String trace Trace @relation(fields: [traceId], references: [id], onDelete: Cascade) spanId String? type EventType name String metadata Json? timestamp DateTime @default(now()) @@index([traceId]) @@index([type]) @@index([timestamp]) } enum TraceStatus { RUNNING COMPLETED ERROR } enum DecisionType { TOOL_SELECTION ROUTING RETRY ESCALATION MEMORY_RETRIEVAL PLANNING CUSTOM } enum SpanType { LLM_CALL TOOL_CALL MEMORY_OP CHAIN AGENT CUSTOM } enum SpanStatus { RUNNING COMPLETED ERROR } enum EventType { ERROR RETRY FALLBACK CONTEXT_OVERFLOW USER_FEEDBACK CUSTOM }