profileShare

rasmusjy / roundtable

Read-only snapshot

No repository description.

main default branch 181 files Expires Sep 13, 2026, 9:06 AM
schema.prisma 6,154 bytes
1 // Roundtable data model.
2 //
3 // Debates are persisted incrementally — each StageResult row is written the
4 // moment that stage completes, not at the end — so a client that disconnects
5 // mid-debate can reconnect and replay current state, and history/export read
6 // from the same rows.
7
8 generator client {
9 provider = "prisma-client-js"
10 // "native" for local dev; the debian target matches the node:20-slim runtime.
11 binaryTargets = ["native", "debian-openssl-3.0.x"]
12 }
13
14 datasource db {
15 provider = "postgresql"
16 url = env("DATABASE_URL")
17 }
18
19 // --- Auth.js (NextAuth) models -------------------------------------------
20
21 model User {
22 id String @id @default(cuid())
23 name String?
24 email String? @unique
25 emailVerified DateTime?
26 image String?
27 createdAt DateTime @default(now())
28
29 accounts Account[]
30 sessions Session[]
31 apiKey ApiKey?
32 debates Debate[]
33 presets Preset[]
34 }
35
36 model Account {
37 id String @id @default(cuid())
38 userId String
39 type String
40 provider String
41 providerAccountId String
42 refresh_token String? @db.Text
43 access_token String? @db.Text
44 expires_at Int?
45 token_type String?
46 scope String?
47 id_token String? @db.Text
48 session_state String?
49
50 user User @relation(fields: [userId], references: [id], onDelete: Cascade)
51
52 @@unique([provider, providerAccountId])
53 }
54
55 model Session {
56 id String @id @default(cuid())
57 sessionToken String @unique
58 userId String
59 expires DateTime
60 user User @relation(fields: [userId], references: [id], onDelete: Cascade)
61 }
62
63 model VerificationToken {
64 identifier String
65 token String @unique
66 expires DateTime
67
68 @@unique([identifier, token])
69 }
70
71 // --- BYOK: per-user encrypted OpenRouter key ------------------------------
72
73 model ApiKey {
74 id String @id @default(cuid())
75 userId String @unique
76 user User @relation(fields: [userId], references: [id], onDelete: Cascade)
77 // AES-256-GCM payload (v1.iv.tag.ciphertext). Never returned to the client.
78 encrypted String @db.Text
79 keyMask String
80 label String?
81 createdAt DateTime @default(now())
82 updatedAt DateTime @updatedAt
83 }
84
85 // --- Debates --------------------------------------------------------------
86
87 enum DebateStatus {
88 pending
89 running
90 completed
91 failed
92 aborted
93 }
94
95 enum StageType {
96 answer
97 critique
98 revision
99 convergence
100 synthesis
101 provenance
102 }
103
104 model Debate {
105 id String @id @default(cuid())
106 userId String?
107 user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
108
109 question String @db.Text
110 status DebateStatus @default(pending)
111 config Json
112 chairmanModel String
113 convergenceModel String
114 maxRounds Int
115 convergenceThreshold Int
116 temperature Float
117 promptVersion String
118
119 totalCostUsd Float @default(0)
120 promptTokens Int @default(0)
121 completionTokens Int @default(0)
122 roundsCompleted Int @default(0)
123 durationMs Int @default(0)
124 error String? @db.Text
125
126 // Demo fixtures are seeded and publicly replayable with no auth.
127 isDemo Boolean @default(false)
128 // Unlisted share link token for the public deliberation report.
129 shareToken String? @unique
130
131 createdAt DateTime @default(now())
132 updatedAt DateTime @updatedAt
133
134 participants Participant[]
135 stageResults StageResult[]
136 synthesis SynthesisResult?
137
138 @@index([userId, createdAt])
139 @@index([isDemo])
140 }
141
142 model Participant {
143 id String @id @default(cuid())
144 debateId String
145 debate Debate @relation(fields: [debateId], references: [id], onDelete: Cascade)
146 localId String // p0, p1, … (stable within a debate)
147 model String
148 displayName String
149 orderIndex Int
150
151 @@unique([debateId, localId])
152 }
153
154 model StageResult {
155 id String @id @default(cuid())
156 debateId String
157 debate Debate @relation(fields: [debateId], references: [id], onDelete: Cascade)
158 round Int
159 stage StageType
160 participantLocalId String? // null for convergence (assessor) and dropped-model failures keyed separately
161 model String
162 // Primary text: the answer / revised answer. Empty for critique/convergence.
163 content String @db.Text
164 // Structured payload: critique reviews, revision changelog, convergence score/disagreements.
165 data Json?
166 promptTokens Int @default(0)
167 completionTokens Int @default(0)
168 costUsd Float @default(0)
169 latencyMs Int @default(0)
170 error String? @db.Text
171 createdAt DateTime @default(now())
172
173 @@index([debateId, round, stage])
174 }
175
176 model SynthesisResult {
177 id String @id @default(cuid())
178 debateId String @unique
179 debate Debate @relation(fields: [debateId], references: [id], onDelete: Cascade)
180 model String
181 finalAnswer String @db.Text
182 // Dissent report: array of { topic, positions: [{ participantLocalId, model, position }] }
183 dissent Json
184 promptTokens Int @default(0)
185 completionTokens Int @default(0)
186 costUsd Float @default(0)
187 latencyMs Int @default(0)
188 createdAt DateTime @default(now())
189 }
190
191 // --- Saved councils -------------------------------------------------------
192
193 model Preset {
194 id String @id @default(cuid())
195 userId String
196 user User @relation(fields: [userId], references: [id], onDelete: Cascade)
197 name String
198 models String[]
199 chairmanModel String
200 convergenceModel String?
201 maxRounds Int @default(3)
202 convergenceThreshold Int @default(85)
203 temperature Float @default(0.7)
204 createdAt DateTime @default(now())
205 updatedAt DateTime @updatedAt
206
207 @@unique([userId, name])
208 }
209