generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" } enum Role { PRODUCT_OWNER SCRUM_MASTER DEVELOPER ADMIN } enum StoryStatus { OPEN IN_SPRINT DONE } enum PbiStatus { READY BLOCKED DONE } enum ClaudeJobStatus { QUEUED CLAIMED RUNNING DONE FAILED CANCELLED SKIPPED } enum VerifyResult { ALIGNED PARTIAL EMPTY DIVERGENT } enum VerifyRequired { ALIGNED ALIGNED_OR_PARTIAL ANY } enum TaskStatus { TO_DO IN_PROGRESS REVIEW DONE } enum LogType { IMPLEMENTATION_PLAN TEST_RESULT COMMIT } enum TestStatus { PASSED FAILED } enum SprintStatus { ACTIVE COMPLETED } enum IdeaStatus { DRAFT GRILLING GRILL_FAILED GRILLED PLANNING PLAN_FAILED PLAN_READY PLANNED } enum ClaudeJobKind { TASK_IMPLEMENTATION IDEA_GRILL IDEA_MAKE_PLAN PLAN_CHAT } enum IdeaLogType { DECISION NOTE GRILL_RESULT PLAN_RESULT STATUS_CHANGE JOB_EVENT } enum UserQuestionStatus { pending answered } model User { id String @id @default(cuid()) username String @unique email String? @unique password_hash String is_demo Boolean @default(false) bio String? @db.VarChar(160) bio_detail String? @db.VarChar(2000) must_reset_password Boolean @default(false) avatar_data Bytes? active_product_id String? active_product Product? @relation("UserActiveProduct", fields: [active_product_id], references: [id], onDelete: SetNull) idea_code_counter Int @default(0) min_quota_pct Int @default(20) created_at DateTime @default(now()) updated_at DateTime @updatedAt roles UserRole[] api_tokens ApiToken[] products Product[] todos Todo[] ideas Idea[] product_members ProductMember[] assigned_stories Story[] @relation("StoryAssignee") login_pairings LoginPairing[] asked_questions ClaudeQuestion[] @relation("ClaudeQuestionAsker") answered_questions ClaudeQuestion[] @relation("ClaudeQuestionAnswerer") claude_jobs ClaudeJob[] claude_workers ClaudeWorker[] @@index([active_product_id]) @@map("users") } model UserRole { id String @id @default(cuid()) user User @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id String role Role @@unique([user_id, role]) @@map("user_roles") } model ApiToken { id String @id @default(cuid()) user User @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id String token_hash String @unique label String? created_at DateTime @default(now()) revoked_at DateTime? claimed_jobs ClaudeJob[] claude_worker ClaudeWorker? @@index([token_hash]) @@map("api_tokens") } model Product { id String @id @default(cuid()) user User @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id String name String code String? @db.VarChar(30) description String? repo_url String? definition_of_done String auto_pr Boolean @default(false) archived Boolean @default(false) created_at DateTime @default(now()) updated_at DateTime @updatedAt pbis Pbi[] sprints Sprint[] stories Story[] tasks Task[] todos Todo[] members ProductMember[] active_for_users User[] @relation("UserActiveProduct") claude_questions ClaudeQuestion[] claude_jobs ClaudeJob[] ideas Idea[] idea_products IdeaProduct[] @@unique([user_id, name]) @@unique([user_id, code]) @@index([user_id, archived]) @@map("products") } model Pbi { id String @id @default(cuid()) product Product @relation(fields: [product_id], references: [id], onDelete: Cascade) product_id String code String @db.VarChar(30) title String description String? priority Int sort_order Float status PbiStatus @default(READY) pr_url String? pr_merged_at DateTime? created_at DateTime @default(now()) updated_at DateTime @updatedAt stories Story[] idea Idea? @@unique([product_id, code]) @@index([product_id, priority, sort_order]) @@index([product_id, status]) @@map("pbis") } model Story { id String @id @default(cuid()) pbi Pbi @relation(fields: [pbi_id], references: [id], onDelete: Cascade) pbi_id String product Product @relation(fields: [product_id], references: [id]) product_id String sprint Sprint? @relation(fields: [sprint_id], references: [id]) sprint_id String? assignee User? @relation("StoryAssignee", fields: [assignee_id], references: [id], onDelete: SetNull) assignee_id String? code String @db.VarChar(30) title String description String? acceptance_criteria String? priority Int sort_order Float status StoryStatus @default(OPEN) created_at DateTime @default(now()) updated_at DateTime @updatedAt logs StoryLog[] tasks Task[] claude_questions ClaudeQuestion[] @@unique([product_id, code]) @@index([pbi_id, priority, sort_order]) @@index([sprint_id, sort_order]) @@index([product_id, status]) @@index([sprint_id, assignee_id]) @@map("stories") } model StoryLog { id String @id @default(cuid()) story Story @relation(fields: [story_id], references: [id], onDelete: Cascade) story_id String type LogType content String status TestStatus? commit_hash String? commit_message String? metadata Json? created_at DateTime @default(now()) @@index([story_id, created_at]) @@map("story_logs") } model Sprint { id String @id @default(cuid()) product Product @relation(fields: [product_id], references: [id], onDelete: Cascade) product_id String sprint_goal String status SprintStatus @default(ACTIVE) start_date DateTime? @db.Date end_date DateTime? @db.Date created_at DateTime @default(now()) completed_at DateTime? stories Story[] tasks Task[] @@index([product_id, status]) @@map("sprints") } model Task { id String @id @default(cuid()) story Story @relation(fields: [story_id], references: [id], onDelete: Cascade) story_id String product Product @relation(fields: [product_id], references: [id], onDelete: Cascade) product_id String sprint Sprint? @relation(fields: [sprint_id], references: [id]) sprint_id String? code String @db.VarChar(30) title String description String? implementation_plan String? priority Int sort_order Float status TaskStatus @default(TO_DO) verify_only Boolean @default(false) verify_required VerifyRequired @default(ALIGNED_OR_PARTIAL) // Override product.repo_url for branch/worktree/push purposes. Set when // a task targets a different repo than its parent product (e.g. an // MCP-server task tracked under the main product's PBI). Falls back to // product.repo_url when null. repo_url String? created_at DateTime @default(now()) updated_at DateTime @updatedAt claude_questions ClaudeQuestion[] claude_jobs ClaudeJob[] @@unique([product_id, code]) @@index([story_id, priority, sort_order]) @@index([sprint_id, status]) @@index([product_id]) @@map("tasks") } model ClaudeJob { id String @id @default(cuid()) user User @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id String product Product @relation(fields: [product_id], references: [id], onDelete: Cascade) product_id String task Task? @relation(fields: [task_id], references: [id], onDelete: Cascade) task_id String? idea Idea? @relation(fields: [idea_id], references: [id], onDelete: Cascade) idea_id String? kind ClaudeJobKind @default(TASK_IMPLEMENTATION) status ClaudeJobStatus @default(QUEUED) claimed_by_token ApiToken? @relation(fields: [claimed_by_token_id], references: [id], onDelete: SetNull) claimed_by_token_id String? claimed_at DateTime? started_at DateTime? finished_at DateTime? pushed_at DateTime? verify_result VerifyResult? model_id String? input_tokens Int? output_tokens Int? cache_read_tokens Int? cache_write_tokens Int? plan_snapshot String? branch String? pr_url String? summary String? error String? retry_count Int @default(0) created_at DateTime @default(now()) updated_at DateTime @updatedAt @@index([user_id, status]) @@index([task_id, status]) @@index([idea_id, status]) @@index([status, claimed_at]) @@index([status, finished_at]) @@map("claude_jobs") } model ModelPrice { id String @id @default(cuid()) model_id String @unique input_price_per_1m Decimal @db.Decimal(12, 6) output_price_per_1m Decimal @db.Decimal(12, 6) cache_read_price_per_1m Decimal @db.Decimal(12, 6) cache_write_price_per_1m Decimal @db.Decimal(12, 6) currency String @default("USD") created_at DateTime @default(now()) updated_at DateTime @updatedAt @@map("model_prices") } model ClaudeWorker { id String @id @default(cuid()) user User @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id String token ApiToken @relation(fields: [token_id], references: [id], onDelete: Cascade) token_id String product_id String? started_at DateTime @default(now()) last_seen_at DateTime @default(now()) last_quota_pct Int? last_quota_check_at DateTime? @@unique([token_id]) @@index([user_id, last_seen_at]) @@map("claude_workers") } model ProductMember { id String @id @default(cuid()) product Product @relation(fields: [product_id], references: [id], onDelete: Cascade) product_id String user User @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id String created_at DateTime @default(now()) @@unique([product_id, user_id]) @@index([user_id]) @@map("product_members") } model Todo { id String @id @default(cuid()) user User @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id String product Product? @relation(fields: [product_id], references: [id], onDelete: SetNull) product_id String? title String description String? @db.VarChar(2000) done Boolean @default(false) archived Boolean @default(false) created_at DateTime @default(now()) updated_at DateTime @updatedAt @@index([user_id, done, archived]) @@index([user_id, product_id]) @@map("todos") } model Idea { id String @id @default(cuid()) user User @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id String product Product? @relation(fields: [product_id], references: [id], onDelete: SetNull) product_id String? code String @db.VarChar(30) title String description String? @db.VarChar(4000) grill_md String? @db.Text plan_md String? @db.Text pbi Pbi? @relation(fields: [pbi_id], references: [id], onDelete: SetNull) pbi_id String? @unique status IdeaStatus @default(DRAFT) archived Boolean @default(false) created_at DateTime @default(now()) updated_at DateTime @updatedAt questions ClaudeQuestion[] jobs ClaudeJob[] logs IdeaLog[] user_questions UserQuestion[] secondary_products IdeaProduct[] @@unique([user_id, code]) @@index([user_id, archived, status]) @@index([user_id, product_id]) @@map("ideas") } model IdeaProduct { id String @id @default(cuid()) idea_id String product_id String created_at DateTime @default(now()) idea Idea @relation(fields: [idea_id], references: [id], onDelete: Cascade) product Product @relation(fields: [product_id], references: [id], onDelete: Cascade) @@unique([idea_id, product_id]) @@index([product_id]) @@map("idea_products") } model IdeaLog { id String @id @default(cuid()) idea Idea @relation(fields: [idea_id], references: [id], onDelete: Cascade) idea_id String type IdeaLogType content String @db.Text metadata Json? created_at DateTime @default(now()) @@index([idea_id, created_at]) @@map("idea_logs") } model UserQuestion { id String @id @default(cuid()) idea_id String user_id String question String @db.Text answer String? @db.Text status UserQuestionStatus @default(pending) created_at DateTime @default(now()) updated_at DateTime @updatedAt idea Idea @relation(fields: [idea_id], references: [id], onDelete: Cascade) @@index([idea_id, status]) @@index([user_id]) @@map("user_questions") } model LoginPairing { id String @id @default(cuid()) secret_hash String desktop_token_hash String status String user_id String? user User? @relation(fields: [user_id], references: [id], onDelete: SetNull) desktop_ua String? @db.VarChar(255) desktop_ip String? @db.VarChar(45) created_at DateTime @default(now()) expires_at DateTime approved_at DateTime? consumed_at DateTime? @@index([expires_at]) @@index([status, expires_at]) @@map("login_pairings") } model ClaudeQuestion { id String @id @default(cuid()) story Story? @relation(fields: [story_id], references: [id], onDelete: Cascade) story_id String? task Task? @relation(fields: [task_id], references: [id], onDelete: SetNull) task_id String? idea Idea? @relation(fields: [idea_id], references: [id], onDelete: Cascade) idea_id String? product Product @relation(fields: [product_id], references: [id], onDelete: Cascade) product_id String // gedenormaliseerd uit story.product_id voor SSE-filter asker User @relation("ClaudeQuestionAsker", fields: [asked_by], references: [id]) asked_by String // user_id van token-houder (= Claude-token) question String @db.Text options Json? // string[] voor multi-choice; null voor free-text status String // 'open' | 'answered' | 'cancelled' | 'expired' answer String? @db.Text answerer User? @relation("ClaudeQuestionAnswerer", fields: [answered_by], references: [id]) answered_by String? answered_at DateTime? created_at DateTime @default(now()) expires_at DateTime // ingesteld door MCP-tool, default now() + 24h @@index([story_id, status]) @@index([idea_id, status]) @@index([product_id, status]) @@index([status, expires_at]) @@map("claude_questions") }