@trycompai/db 2.0.1 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/schema.prisma +2984 -0
- package/package.json +2 -2
|
@@ -0,0 +1,2984 @@
|
|
|
1
|
+
generator client {
|
|
2
|
+
provider = "prisma-client"
|
|
3
|
+
output = "../src/generated/prisma"
|
|
4
|
+
previewFeatures = ["postgresqlExtensions"]
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
datasource db {
|
|
8
|
+
provider = "postgresql"
|
|
9
|
+
extensions = [pgcrypto]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// ===== attachments.prisma =====
|
|
13
|
+
model Attachment {
|
|
14
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('att'::text)"))
|
|
15
|
+
name String
|
|
16
|
+
url String
|
|
17
|
+
type AttachmentType
|
|
18
|
+
entityId String
|
|
19
|
+
entityType AttachmentEntityType
|
|
20
|
+
|
|
21
|
+
// Dates
|
|
22
|
+
createdAt DateTime @default(now())
|
|
23
|
+
updatedAt DateTime @updatedAt
|
|
24
|
+
|
|
25
|
+
// Relationships
|
|
26
|
+
organizationId String
|
|
27
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
28
|
+
comment Comment? @relation(fields: [commentId], references: [id])
|
|
29
|
+
commentId String?
|
|
30
|
+
|
|
31
|
+
@@index([entityId, entityType])
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
enum AttachmentEntityType {
|
|
35
|
+
task
|
|
36
|
+
vendor
|
|
37
|
+
risk
|
|
38
|
+
comment
|
|
39
|
+
trust_nda
|
|
40
|
+
task_item
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
enum AttachmentType {
|
|
44
|
+
image
|
|
45
|
+
video
|
|
46
|
+
audio
|
|
47
|
+
document
|
|
48
|
+
other
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ===== auth.prisma =====
|
|
52
|
+
model User {
|
|
53
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('usr'::text)"))
|
|
54
|
+
name String
|
|
55
|
+
email String
|
|
56
|
+
emailVerified Boolean
|
|
57
|
+
image String?
|
|
58
|
+
createdAt DateTime @default(now())
|
|
59
|
+
updatedAt DateTime @updatedAt
|
|
60
|
+
lastLogin DateTime?
|
|
61
|
+
emailNotificationsUnsubscribed Boolean @default(false)
|
|
62
|
+
emailPreferences Json? @default("{\"policyNotifications\":true,\"taskReminders\":true,\"weeklyTaskDigest\":true,\"unassignedItemsNotifications\":true}")
|
|
63
|
+
role String? @default("user")
|
|
64
|
+
banned Boolean?
|
|
65
|
+
banReason String?
|
|
66
|
+
banExpires DateTime?
|
|
67
|
+
isPlatformAdmin Boolean @default(false)
|
|
68
|
+
|
|
69
|
+
accounts Account[]
|
|
70
|
+
auditLog AuditLog[]
|
|
71
|
+
integrationResults IntegrationResult[]
|
|
72
|
+
invitations Invitation[]
|
|
73
|
+
members Member[]
|
|
74
|
+
sessions Session[]
|
|
75
|
+
fleetPolicyResults FleetPolicyResult[]
|
|
76
|
+
evidenceSubmissions EvidenceSubmission[] @relation("EvidenceSubmitter")
|
|
77
|
+
evidenceReviews EvidenceSubmission[] @relation("EvidenceReviewer")
|
|
78
|
+
adminFindings Finding[] @relation("AdminFindingCreator")
|
|
79
|
+
timelinePhaseCompletions TimelinePhase[]
|
|
80
|
+
lockedTimelineInstances TimelineInstance[] @relation("TimelineInstanceLockedBy")
|
|
81
|
+
unlockedTimelineInstances TimelineInstance[] @relation("TimelineInstanceUnlockedBy")
|
|
82
|
+
publishedFrameworkVersions FrameworkVersion[] @relation("FrameworkVersionPublisher")
|
|
83
|
+
|
|
84
|
+
@@unique([email])
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
model EmployeeTrainingVideoCompletion {
|
|
88
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('evc'::text)"))
|
|
89
|
+
completedAt DateTime?
|
|
90
|
+
videoId String
|
|
91
|
+
|
|
92
|
+
memberId String
|
|
93
|
+
member Member @relation(fields: [memberId], references: [id], onDelete: Cascade)
|
|
94
|
+
|
|
95
|
+
@@unique([memberId, videoId])
|
|
96
|
+
@@index([memberId])
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
model Session {
|
|
100
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('ses'::text)"))
|
|
101
|
+
expiresAt DateTime
|
|
102
|
+
token String
|
|
103
|
+
createdAt DateTime @default(now())
|
|
104
|
+
updatedAt DateTime @updatedAt
|
|
105
|
+
ipAddress String?
|
|
106
|
+
userAgent String?
|
|
107
|
+
userId String
|
|
108
|
+
activeOrganizationId String?
|
|
109
|
+
impersonatedBy String?
|
|
110
|
+
|
|
111
|
+
deviceAgent Boolean @default(false)
|
|
112
|
+
deviceAgentFor Device[] @relation("DeviceAgentSession")
|
|
113
|
+
|
|
114
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
115
|
+
|
|
116
|
+
@@unique([token])
|
|
117
|
+
@@index([userId])
|
|
118
|
+
@@index([deviceAgent])
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
model Account {
|
|
122
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('acc'::text)"))
|
|
123
|
+
accountId String
|
|
124
|
+
providerId String
|
|
125
|
+
userId String
|
|
126
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
127
|
+
accessToken String?
|
|
128
|
+
refreshToken String?
|
|
129
|
+
idToken String?
|
|
130
|
+
accessTokenExpiresAt DateTime?
|
|
131
|
+
refreshTokenExpiresAt DateTime?
|
|
132
|
+
scope String?
|
|
133
|
+
password String?
|
|
134
|
+
createdAt DateTime
|
|
135
|
+
updatedAt DateTime
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
model Verification {
|
|
139
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('ver'::text)"))
|
|
140
|
+
identifier String
|
|
141
|
+
value String
|
|
142
|
+
expiresAt DateTime
|
|
143
|
+
createdAt DateTime @default(now())
|
|
144
|
+
updatedAt DateTime @updatedAt
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// JWT Plugin - Required by Better Auth JWT plugin
|
|
148
|
+
// https://www.better-auth.com/docs/plugins/jwt
|
|
149
|
+
model Jwks {
|
|
150
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('jwk'::text)"))
|
|
151
|
+
publicKey String
|
|
152
|
+
privateKey String
|
|
153
|
+
createdAt DateTime @default(now())
|
|
154
|
+
expiresAt DateTime?
|
|
155
|
+
|
|
156
|
+
@@map("jwks")
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
model Member {
|
|
160
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('mem'::text)"))
|
|
161
|
+
organizationId String
|
|
162
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
163
|
+
userId String
|
|
164
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
165
|
+
role String // Purposefully a string, since BetterAuth doesn't support enums this way
|
|
166
|
+
createdAt DateTime @default(now())
|
|
167
|
+
|
|
168
|
+
department Departments @default(none)
|
|
169
|
+
jobTitle String?
|
|
170
|
+
isActive Boolean @default(true)
|
|
171
|
+
deactivated Boolean @default(false)
|
|
172
|
+
externalUserId String?
|
|
173
|
+
externalUserSource String?
|
|
174
|
+
employeeTrainingVideoCompletion EmployeeTrainingVideoCompletion[]
|
|
175
|
+
fleetDmLabelId Int?
|
|
176
|
+
|
|
177
|
+
assignedPolicies Policy[] @relation("PolicyAssignee") // Policies where this member is an assignee
|
|
178
|
+
approvedPolicies Policy[] @relation("PolicyApprover") // Policies where this member is an approver
|
|
179
|
+
approvedSOADocuments SOADocument[] @relation("SOADocumentApprover") // SOA documents where this member is an approver
|
|
180
|
+
risks Risk[]
|
|
181
|
+
tasks Task[]
|
|
182
|
+
vendors Vendor[]
|
|
183
|
+
comments Comment[]
|
|
184
|
+
auditLogs AuditLog[]
|
|
185
|
+
reviewedAccessRequests TrustAccessRequest[] @relation("TrustAccessRequestReviewer")
|
|
186
|
+
issuedGrants TrustAccessGrant[] @relation("IssuedGrants")
|
|
187
|
+
revokedGrants TrustAccessGrant[] @relation("RevokedGrants")
|
|
188
|
+
createdTaskItems TaskItem[] @relation("TaskItemCreator")
|
|
189
|
+
updatedTaskItems TaskItem[] @relation("TaskItemUpdater")
|
|
190
|
+
assignedTaskItems TaskItem[] @relation("TaskItemAssignee")
|
|
191
|
+
createdFindings Finding[] @relation("FindingCreatedBy")
|
|
192
|
+
subjectFindings Finding[] @relation("FindingSubject")
|
|
193
|
+
publishedPolicyVersions PolicyVersion[] @relation("PolicyVersionPublisher")
|
|
194
|
+
performedFrameworkSyncOperations FrameworkSyncOperation[] @relation("FrameworkSyncOperationPerformer")
|
|
195
|
+
approvedTasks Task[] @relation("TaskApprover")
|
|
196
|
+
devices Device[]
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
model Invitation {
|
|
200
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('inv'::text)"))
|
|
201
|
+
organizationId String
|
|
202
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
203
|
+
email String
|
|
204
|
+
role String // Purposefully a string, since BetterAuth doesn't support enums this way
|
|
205
|
+
status String
|
|
206
|
+
expiresAt DateTime
|
|
207
|
+
inviterId String
|
|
208
|
+
user User @relation(fields: [inviterId], references: [id], onDelete: Cascade)
|
|
209
|
+
createdAt DateTime @default(now())
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// This is only for the app to consume, shouldn't be enforced by DB
|
|
213
|
+
// Otherwise it won't work with Better Auth, as per https://www.better-auth.com/docs/plugins/organization#access-control
|
|
214
|
+
enum Role {
|
|
215
|
+
owner
|
|
216
|
+
admin
|
|
217
|
+
auditor
|
|
218
|
+
employee
|
|
219
|
+
contractor
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Custom roles for dynamic access control
|
|
223
|
+
// This table stores organization-specific custom roles created via better-auth
|
|
224
|
+
// See: https://www.better-auth.com/docs/plugins/organization#dynamic-access-control
|
|
225
|
+
model OrganizationRole {
|
|
226
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('rol'::text)"))
|
|
227
|
+
name String
|
|
228
|
+
permissions String @db.Text // Stored as serialized JSON string for better-auth compatibility
|
|
229
|
+
obligations String @default("{}") @db.Text // JSON: { compliance?: boolean }
|
|
230
|
+
organizationId String
|
|
231
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
232
|
+
createdAt DateTime @default(now())
|
|
233
|
+
updatedAt DateTime @updatedAt
|
|
234
|
+
|
|
235
|
+
@@unique([organizationId, name])
|
|
236
|
+
@@map("organization_role")
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
enum PolicyStatus {
|
|
240
|
+
draft
|
|
241
|
+
published
|
|
242
|
+
needs_review
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ===== automation-run.prisma =====
|
|
246
|
+
model EvidenceAutomationRun {
|
|
247
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('ear'::text)"))
|
|
248
|
+
createdAt DateTime @default(now())
|
|
249
|
+
updatedAt DateTime @updatedAt
|
|
250
|
+
|
|
251
|
+
// Relations
|
|
252
|
+
evidenceAutomationId String
|
|
253
|
+
evidenceAutomation EvidenceAutomation @relation(fields: [evidenceAutomationId], references: [id], onDelete: Cascade)
|
|
254
|
+
|
|
255
|
+
// Run details
|
|
256
|
+
status EvidenceAutomationRunStatus @default(pending)
|
|
257
|
+
startedAt DateTime?
|
|
258
|
+
completedAt DateTime?
|
|
259
|
+
|
|
260
|
+
// Results
|
|
261
|
+
success Boolean?
|
|
262
|
+
error String?
|
|
263
|
+
logs Json?
|
|
264
|
+
output Json?
|
|
265
|
+
|
|
266
|
+
// Evaluation
|
|
267
|
+
evaluationStatus EvidenceAutomationEvaluationStatus?
|
|
268
|
+
evaluationReason String?
|
|
269
|
+
|
|
270
|
+
// Metadata
|
|
271
|
+
triggeredBy EvidenceAutomationTrigger @default(scheduled)
|
|
272
|
+
runDuration Int? // in milliseconds
|
|
273
|
+
version Int? // Version number that was executed (null = draft)
|
|
274
|
+
task Task? @relation(fields: [taskId], references: [id])
|
|
275
|
+
taskId String?
|
|
276
|
+
|
|
277
|
+
@@index([evidenceAutomationId])
|
|
278
|
+
@@index([status])
|
|
279
|
+
@@index([createdAt])
|
|
280
|
+
@@index([version])
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
enum EvidenceAutomationRunStatus {
|
|
284
|
+
pending
|
|
285
|
+
running
|
|
286
|
+
completed
|
|
287
|
+
failed
|
|
288
|
+
cancelled
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
enum EvidenceAutomationTrigger {
|
|
292
|
+
manual
|
|
293
|
+
scheduled
|
|
294
|
+
api
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
enum EvidenceAutomationEvaluationStatus {
|
|
298
|
+
pass
|
|
299
|
+
fail
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ===== automation-version.prisma =====
|
|
303
|
+
model EvidenceAutomationVersion {
|
|
304
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('eav'::text)"))
|
|
305
|
+
createdAt DateTime @default(now())
|
|
306
|
+
updatedAt DateTime @updatedAt
|
|
307
|
+
|
|
308
|
+
// Relations
|
|
309
|
+
evidenceAutomationId String
|
|
310
|
+
evidenceAutomation EvidenceAutomation @relation(fields: [evidenceAutomationId], references: [id], onDelete: Cascade)
|
|
311
|
+
|
|
312
|
+
// Version details
|
|
313
|
+
version Int // Sequential version number (1, 2, 3...)
|
|
314
|
+
scriptKey String // S3 key for this version's script
|
|
315
|
+
publishedBy String? // User ID who published
|
|
316
|
+
changelog String? // Optional description of changes
|
|
317
|
+
|
|
318
|
+
@@unique([evidenceAutomationId, version])
|
|
319
|
+
@@index([evidenceAutomationId])
|
|
320
|
+
@@index([createdAt])
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ===== automation.prisma =====
|
|
324
|
+
model EvidenceAutomation {
|
|
325
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('aut'::text)"))
|
|
326
|
+
name String
|
|
327
|
+
description String?
|
|
328
|
+
createdAt DateTime @default(now())
|
|
329
|
+
isEnabled Boolean @default(false)
|
|
330
|
+
scheduleFrequency TaskFrequency @default(daily)
|
|
331
|
+
lastRunAt DateTime?
|
|
332
|
+
|
|
333
|
+
chatHistory String?
|
|
334
|
+
evaluationCriteria String?
|
|
335
|
+
|
|
336
|
+
taskId String
|
|
337
|
+
task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)
|
|
338
|
+
|
|
339
|
+
// Relations
|
|
340
|
+
runs EvidenceAutomationRun[]
|
|
341
|
+
versions EvidenceAutomationVersion[]
|
|
342
|
+
|
|
343
|
+
@@index([taskId])
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ===== browserbase-context.prisma =====
|
|
347
|
+
/// Stores Browserbase context IDs for browser-based automation
|
|
348
|
+
/// One context per organization - shared like a normal browser
|
|
349
|
+
model BrowserbaseContext {
|
|
350
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('bbc'::text)"))
|
|
351
|
+
|
|
352
|
+
/// Organization that owns this browser context
|
|
353
|
+
organizationId String @unique
|
|
354
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
355
|
+
|
|
356
|
+
/// Browserbase context ID from their API
|
|
357
|
+
contextId String
|
|
358
|
+
|
|
359
|
+
createdAt DateTime @default(now())
|
|
360
|
+
updatedAt DateTime @updatedAt
|
|
361
|
+
|
|
362
|
+
@@index([organizationId])
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/// Browser automation configuration linked to a task
|
|
366
|
+
model BrowserAutomation {
|
|
367
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('bau'::text)"))
|
|
368
|
+
name String
|
|
369
|
+
description String?
|
|
370
|
+
|
|
371
|
+
/// Task this automation belongs to
|
|
372
|
+
taskId String
|
|
373
|
+
task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)
|
|
374
|
+
|
|
375
|
+
/// Starting URL for the automation
|
|
376
|
+
targetUrl String
|
|
377
|
+
|
|
378
|
+
/// Natural language instruction for the AI agent
|
|
379
|
+
instruction String
|
|
380
|
+
|
|
381
|
+
/// Optional natural-language criteria used to evaluate whether the
|
|
382
|
+
/// automation's outcome satisfies the auditor's requirement.
|
|
383
|
+
/// When null, runs don't produce a pass/fail verdict — only a screenshot.
|
|
384
|
+
evaluationCriteria String?
|
|
385
|
+
|
|
386
|
+
/// Whether automation is enabled for scheduled runs
|
|
387
|
+
isEnabled Boolean @default(false)
|
|
388
|
+
scheduleFrequency TaskFrequency @default(daily)
|
|
389
|
+
lastRunAt DateTime?
|
|
390
|
+
|
|
391
|
+
createdAt DateTime @default(now())
|
|
392
|
+
updatedAt DateTime @updatedAt
|
|
393
|
+
|
|
394
|
+
runs BrowserAutomationRun[]
|
|
395
|
+
|
|
396
|
+
@@index([taskId])
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/// Records of browser automation executions
|
|
400
|
+
model BrowserAutomationRun {
|
|
401
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('bar'::text)"))
|
|
402
|
+
|
|
403
|
+
/// Parent automation
|
|
404
|
+
automationId String
|
|
405
|
+
automation BrowserAutomation @relation(fields: [automationId], references: [id], onDelete: Cascade)
|
|
406
|
+
|
|
407
|
+
/// Execution status
|
|
408
|
+
status BrowserAutomationRunStatus @default(pending)
|
|
409
|
+
|
|
410
|
+
/// Timestamps
|
|
411
|
+
startedAt DateTime?
|
|
412
|
+
completedAt DateTime?
|
|
413
|
+
|
|
414
|
+
/// Duration in milliseconds
|
|
415
|
+
durationMs Int?
|
|
416
|
+
|
|
417
|
+
/// Screenshot URL in S3 (if successful)
|
|
418
|
+
screenshotUrl String?
|
|
419
|
+
|
|
420
|
+
/// Evaluation result - whether the automation fulfilled the task requirements
|
|
421
|
+
evaluationStatus BrowserAutomationEvaluationStatus?
|
|
422
|
+
|
|
423
|
+
/// AI explanation of why it passed or failed
|
|
424
|
+
evaluationReason String?
|
|
425
|
+
|
|
426
|
+
/// Error message (if failed)
|
|
427
|
+
error String?
|
|
428
|
+
|
|
429
|
+
createdAt DateTime @default(now())
|
|
430
|
+
|
|
431
|
+
@@index([automationId])
|
|
432
|
+
@@index([status])
|
|
433
|
+
@@index([createdAt])
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
enum BrowserAutomationEvaluationStatus {
|
|
437
|
+
pass
|
|
438
|
+
fail
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
enum BrowserAutomationRunStatus {
|
|
442
|
+
pending
|
|
443
|
+
running
|
|
444
|
+
completed
|
|
445
|
+
failed
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ===== comment.prisma =====
|
|
449
|
+
model Comment {
|
|
450
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('cmt'::text)"))
|
|
451
|
+
content String
|
|
452
|
+
entityId String
|
|
453
|
+
entityType CommentEntityType
|
|
454
|
+
|
|
455
|
+
// Dates
|
|
456
|
+
createdAt DateTime @default(now())
|
|
457
|
+
|
|
458
|
+
// Relationships
|
|
459
|
+
authorId String
|
|
460
|
+
author Member @relation(fields: [authorId], references: [id])
|
|
461
|
+
organizationId String
|
|
462
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
463
|
+
|
|
464
|
+
// Relation to Attachments
|
|
465
|
+
attachments Attachment[]
|
|
466
|
+
|
|
467
|
+
@@index([entityId])
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
enum CommentEntityType {
|
|
471
|
+
task
|
|
472
|
+
vendor
|
|
473
|
+
risk
|
|
474
|
+
policy
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// ===== context.prisma =====
|
|
478
|
+
model Context {
|
|
479
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('ctx'::text)"))
|
|
480
|
+
organizationId String
|
|
481
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
482
|
+
|
|
483
|
+
question String
|
|
484
|
+
answer String
|
|
485
|
+
|
|
486
|
+
tags String[]
|
|
487
|
+
|
|
488
|
+
createdAt DateTime @default(now())
|
|
489
|
+
updatedAt DateTime @updatedAt
|
|
490
|
+
|
|
491
|
+
@@index([organizationId])
|
|
492
|
+
@@index([question])
|
|
493
|
+
@@index([answer])
|
|
494
|
+
@@index([tags])
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// ===== control-document-type.prisma =====
|
|
498
|
+
model ControlDocumentType {
|
|
499
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('cdt'::text)"))
|
|
500
|
+
controlId String
|
|
501
|
+
control Control @relation(fields: [controlId], references: [id], onDelete: Cascade)
|
|
502
|
+
formType EvidenceFormType
|
|
503
|
+
|
|
504
|
+
@@unique([controlId, formType])
|
|
505
|
+
@@index([controlId])
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// ===== control.prisma =====
|
|
509
|
+
model Control {
|
|
510
|
+
// Metadata
|
|
511
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('ctl'::text)"))
|
|
512
|
+
name String
|
|
513
|
+
description String
|
|
514
|
+
|
|
515
|
+
// Review dates
|
|
516
|
+
lastReviewDate DateTime?
|
|
517
|
+
nextReviewDate DateTime?
|
|
518
|
+
|
|
519
|
+
// Sync-driven archive (set by FrameworkSyncOperation when the control
|
|
520
|
+
// template is removed from the framework's latest version and no other
|
|
521
|
+
// framework instance in the org still references it). Distinct from user
|
|
522
|
+
// archive (no user archive exists on Control today).
|
|
523
|
+
archivedAt DateTime?
|
|
524
|
+
|
|
525
|
+
// Relationships
|
|
526
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
527
|
+
organizationId String
|
|
528
|
+
requirementsMapped RequirementMap[]
|
|
529
|
+
tasks Task[]
|
|
530
|
+
policies Policy[]
|
|
531
|
+
controlTemplateId String?
|
|
532
|
+
controlTemplate FrameworkEditorControlTemplate? @relation(fields: [controlTemplateId], references: [id])
|
|
533
|
+
controlDocumentTypes ControlDocumentType[]
|
|
534
|
+
|
|
535
|
+
@@index([organizationId])
|
|
536
|
+
@@index([organizationId, archivedAt])
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// ===== custom-framework.prisma =====
|
|
540
|
+
model CustomFramework {
|
|
541
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('cfrm'::text)"))
|
|
542
|
+
name String
|
|
543
|
+
description String
|
|
544
|
+
version String @default("1.0.0")
|
|
545
|
+
|
|
546
|
+
organizationId String
|
|
547
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
548
|
+
|
|
549
|
+
requirements CustomRequirement[]
|
|
550
|
+
instances FrameworkInstance[]
|
|
551
|
+
|
|
552
|
+
createdAt DateTime @default(now())
|
|
553
|
+
updatedAt DateTime @default(now()) @updatedAt
|
|
554
|
+
|
|
555
|
+
@@unique([id, organizationId])
|
|
556
|
+
@@index([organizationId])
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
model CustomRequirement {
|
|
560
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('creq'::text)"))
|
|
561
|
+
name String
|
|
562
|
+
description String
|
|
563
|
+
identifier String
|
|
564
|
+
|
|
565
|
+
organizationId String
|
|
566
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
567
|
+
customFrameworkId String
|
|
568
|
+
// Composite FK onto (id, organizationId) so tenant consistency with the
|
|
569
|
+
// referenced CustomFramework is enforced at the DB level.
|
|
570
|
+
customFramework CustomFramework @relation(fields: [customFrameworkId, organizationId], references: [id, organizationId], onDelete: Cascade)
|
|
571
|
+
|
|
572
|
+
requirementMaps RequirementMap[]
|
|
573
|
+
|
|
574
|
+
createdAt DateTime @default(now())
|
|
575
|
+
updatedAt DateTime @default(now()) @updatedAt
|
|
576
|
+
|
|
577
|
+
@@unique([customFrameworkId, identifier])
|
|
578
|
+
@@index([organizationId])
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// ===== device.prisma =====
|
|
582
|
+
model Device {
|
|
583
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('dev'::text)"))
|
|
584
|
+
name String
|
|
585
|
+
hostname String
|
|
586
|
+
platform DevicePlatform
|
|
587
|
+
osVersion String
|
|
588
|
+
serialNumber String?
|
|
589
|
+
hardwareModel String?
|
|
590
|
+
|
|
591
|
+
memberId String
|
|
592
|
+
member Member @relation(fields: [memberId], references: [id], onDelete: Cascade)
|
|
593
|
+
organizationId String
|
|
594
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
595
|
+
|
|
596
|
+
agentSessionId String?
|
|
597
|
+
agentSession Session? @relation("DeviceAgentSession", fields: [agentSessionId], references: [id], onDelete: SetNull)
|
|
598
|
+
|
|
599
|
+
isCompliant Boolean @default(false)
|
|
600
|
+
diskEncryptionEnabled Boolean @default(false)
|
|
601
|
+
antivirusEnabled Boolean @default(false)
|
|
602
|
+
passwordPolicySet Boolean @default(false)
|
|
603
|
+
screenLockEnabled Boolean @default(false)
|
|
604
|
+
checkDetails Json?
|
|
605
|
+
|
|
606
|
+
lastCheckIn DateTime?
|
|
607
|
+
agentVersion String?
|
|
608
|
+
installedAt DateTime @default(now())
|
|
609
|
+
updatedAt DateTime @updatedAt
|
|
610
|
+
|
|
611
|
+
findings Finding[]
|
|
612
|
+
|
|
613
|
+
@@unique([serialNumber, organizationId])
|
|
614
|
+
@@index([memberId])
|
|
615
|
+
@@index([organizationId])
|
|
616
|
+
@@index([isCompliant])
|
|
617
|
+
@@index([agentSessionId])
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
enum DevicePlatform {
|
|
621
|
+
macos
|
|
622
|
+
windows
|
|
623
|
+
linux
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// ===== dynamic-integration.prisma =====
|
|
627
|
+
// ===== Dynamic Integration Platform =====
|
|
628
|
+
// Stores integration manifests and declarative check definitions in the database
|
|
629
|
+
// Enables adding new integrations without code changes or deployments
|
|
630
|
+
|
|
631
|
+
/// Stores a full integration manifest as JSON — replaces hand-written TypeScript manifests
|
|
632
|
+
model DynamicIntegration {
|
|
633
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('din'::text)"))
|
|
634
|
+
/// Unique slug (e.g., "azure-devops", "office-365")
|
|
635
|
+
slug String @unique
|
|
636
|
+
/// Display name
|
|
637
|
+
name String
|
|
638
|
+
/// Short description for catalog
|
|
639
|
+
description String
|
|
640
|
+
/// Category for grouping
|
|
641
|
+
category String
|
|
642
|
+
/// Logo URL
|
|
643
|
+
logoUrl String
|
|
644
|
+
/// URL to documentation
|
|
645
|
+
docsUrl String?
|
|
646
|
+
|
|
647
|
+
/// API base URL for ctx.fetch
|
|
648
|
+
baseUrl String?
|
|
649
|
+
/// Default headers (JSON object)
|
|
650
|
+
defaultHeaders Json?
|
|
651
|
+
|
|
652
|
+
/// Auth strategy config (JSON — matches AuthStrategy type: oauth2/api_key/basic/jwt/custom)
|
|
653
|
+
authConfig Json
|
|
654
|
+
|
|
655
|
+
/// Capabilities JSON array (default ["checks"])
|
|
656
|
+
capabilities Json @default("[\"checks\"]")
|
|
657
|
+
|
|
658
|
+
/// Whether multiple connections per org are allowed
|
|
659
|
+
supportsMultipleConnections Boolean @default(false)
|
|
660
|
+
|
|
661
|
+
/// Declarative sync definition (JSON — DSL steps that produce employee list)
|
|
662
|
+
/// When present and capabilities includes 'sync', enables employee sync
|
|
663
|
+
syncDefinition Json?
|
|
664
|
+
|
|
665
|
+
/// Services metadata (JSON array of { id, name, description, enabledByDefault?, implemented? })
|
|
666
|
+
services Json?
|
|
667
|
+
|
|
668
|
+
/// Whether this dynamic integration is active
|
|
669
|
+
isActive Boolean @default(true)
|
|
670
|
+
|
|
671
|
+
createdAt DateTime @default(now())
|
|
672
|
+
updatedAt DateTime @updatedAt
|
|
673
|
+
|
|
674
|
+
checks DynamicCheck[]
|
|
675
|
+
|
|
676
|
+
@@index([slug])
|
|
677
|
+
@@index([category])
|
|
678
|
+
@@index([isActive])
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/// Stores a declarative check definition — DSL JSON replaces hand-written run() functions
|
|
682
|
+
model DynamicCheck {
|
|
683
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('dck'::text)"))
|
|
684
|
+
|
|
685
|
+
/// Parent integration
|
|
686
|
+
integrationId String
|
|
687
|
+
integration DynamicIntegration @relation(fields: [integrationId], references: [id], onDelete: Cascade)
|
|
688
|
+
|
|
689
|
+
/// Unique slug within integration (e.g., "mfa_enabled")
|
|
690
|
+
checkSlug String
|
|
691
|
+
|
|
692
|
+
/// Human-readable name
|
|
693
|
+
name String
|
|
694
|
+
/// Description of what this check does
|
|
695
|
+
description String
|
|
696
|
+
|
|
697
|
+
/// Task template ID for auto-completion (references TASK_TEMPLATES)
|
|
698
|
+
taskMapping String?
|
|
699
|
+
|
|
700
|
+
/// Default severity for findings
|
|
701
|
+
defaultSeverity String @default("medium")
|
|
702
|
+
|
|
703
|
+
/// Service ID this check belongs to (groups checks under a service)
|
|
704
|
+
service String?
|
|
705
|
+
|
|
706
|
+
/// Declarative DSL definition (JSON — the step-by-step instructions)
|
|
707
|
+
definition Json
|
|
708
|
+
|
|
709
|
+
/// Check-level variables (JSON array of CheckVariable)
|
|
710
|
+
variables Json @default("[]")
|
|
711
|
+
|
|
712
|
+
/// Whether this check is enabled
|
|
713
|
+
isEnabled Boolean @default(true)
|
|
714
|
+
|
|
715
|
+
/// Display order
|
|
716
|
+
sortOrder Int @default(0)
|
|
717
|
+
|
|
718
|
+
createdAt DateTime @default(now())
|
|
719
|
+
updatedAt DateTime @updatedAt
|
|
720
|
+
|
|
721
|
+
@@unique([integrationId, checkSlug])
|
|
722
|
+
@@index([integrationId])
|
|
723
|
+
@@index([isEnabled])
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// ===== evidence-submission.prisma =====
|
|
727
|
+
model EvidenceSubmission {
|
|
728
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('evs'::text)"))
|
|
729
|
+
organizationId String
|
|
730
|
+
formType EvidenceFormType
|
|
731
|
+
submittedById String?
|
|
732
|
+
submittedAt DateTime @default(now())
|
|
733
|
+
data Json
|
|
734
|
+
status String @default("pending")
|
|
735
|
+
reviewedById String?
|
|
736
|
+
reviewedAt DateTime?
|
|
737
|
+
reviewReason String?
|
|
738
|
+
createdAt DateTime @default(now())
|
|
739
|
+
updatedAt DateTime @updatedAt
|
|
740
|
+
|
|
741
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
742
|
+
submittedBy User? @relation("EvidenceSubmitter", fields: [submittedById], references: [id], onDelete: SetNull)
|
|
743
|
+
reviewedBy User? @relation("EvidenceReviewer", fields: [reviewedById], references: [id], onDelete: SetNull)
|
|
744
|
+
findings Finding[]
|
|
745
|
+
|
|
746
|
+
@@index([organizationId, formType, submittedAt])
|
|
747
|
+
@@index([organizationId, formType])
|
|
748
|
+
@@index([submittedById, status])
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// ===== finding.prisma =====
|
|
752
|
+
enum FindingType {
|
|
753
|
+
soc2
|
|
754
|
+
iso27001
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
enum FindingStatus {
|
|
758
|
+
open
|
|
759
|
+
ready_for_review
|
|
760
|
+
needs_revision
|
|
761
|
+
closed
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
enum FindingSeverity {
|
|
765
|
+
low
|
|
766
|
+
medium
|
|
767
|
+
high
|
|
768
|
+
critical
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
enum FindingArea {
|
|
772
|
+
people
|
|
773
|
+
documents
|
|
774
|
+
compliance
|
|
775
|
+
// "General" buckets for cases where an auditor wants to log something like
|
|
776
|
+
// "no risks are tracked" or "the policy for X is missing" without picking a
|
|
777
|
+
// specific Risk/Vendor/Policy row.
|
|
778
|
+
risks
|
|
779
|
+
vendors
|
|
780
|
+
policies
|
|
781
|
+
// Legacy/unclassified bucket for rows backfilled from the old FindingScope
|
|
782
|
+
// enum (see migration 20260419120000). Not surfaced as a primary target
|
|
783
|
+
// option in the Create Finding sheet.
|
|
784
|
+
other
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
model FindingTemplate {
|
|
788
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('fnd_t'::text)"))
|
|
789
|
+
category String // e.g., "evidence_issue", "further_evidence", "task_specific", "na_incorrect"
|
|
790
|
+
title String // Short title
|
|
791
|
+
content String // Full message template
|
|
792
|
+
order Int @default(0)
|
|
793
|
+
createdAt DateTime @default(now())
|
|
794
|
+
updatedAt DateTime @updatedAt
|
|
795
|
+
|
|
796
|
+
findings Finding[]
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
model Finding {
|
|
800
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('fnd'::text)"))
|
|
801
|
+
type FindingType @default(soc2)
|
|
802
|
+
status FindingStatus @default(open)
|
|
803
|
+
severity FindingSeverity @default(medium)
|
|
804
|
+
content String // Custom message or copied from template
|
|
805
|
+
revisionNote String? // Auditor's note when requesting revision
|
|
806
|
+
area FindingArea? // Used when the finding is not tied to a specific item
|
|
807
|
+
|
|
808
|
+
createdAt DateTime @default(now())
|
|
809
|
+
updatedAt DateTime @updatedAt
|
|
810
|
+
|
|
811
|
+
// Target relationships — exactly one target link should be set (task, evidenceSubmission,
|
|
812
|
+
// evidenceFormType, policy, vendor, risk, member, device) OR the `area` field for non-item findings.
|
|
813
|
+
taskId String?
|
|
814
|
+
task Task? @relation(fields: [taskId], references: [id], onDelete: Cascade)
|
|
815
|
+
evidenceSubmissionId String?
|
|
816
|
+
evidenceSubmission EvidenceSubmission? @relation(fields: [evidenceSubmissionId], references: [id], onDelete: Cascade)
|
|
817
|
+
evidenceFormType EvidenceFormType?
|
|
818
|
+
policyId String?
|
|
819
|
+
policy Policy? @relation(fields: [policyId], references: [id], onDelete: Cascade)
|
|
820
|
+
vendorId String?
|
|
821
|
+
vendor Vendor? @relation(fields: [vendorId], references: [id], onDelete: Cascade)
|
|
822
|
+
riskId String?
|
|
823
|
+
risk Risk? @relation(fields: [riskId], references: [id], onDelete: Cascade)
|
|
824
|
+
memberId String?
|
|
825
|
+
member Member? @relation("FindingSubject", fields: [memberId], references: [id], onDelete: Cascade)
|
|
826
|
+
deviceId String?
|
|
827
|
+
device Device? @relation(fields: [deviceId], references: [id], onDelete: Cascade)
|
|
828
|
+
|
|
829
|
+
// Metadata
|
|
830
|
+
templateId String?
|
|
831
|
+
template FindingTemplate? @relation(fields: [templateId], references: [id])
|
|
832
|
+
createdById String?
|
|
833
|
+
createdBy Member? @relation("FindingCreatedBy", fields: [createdById], references: [id])
|
|
834
|
+
createdByAdminId String?
|
|
835
|
+
createdByAdmin User? @relation("AdminFindingCreator", fields: [createdByAdminId], references: [id])
|
|
836
|
+
organizationId String
|
|
837
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
838
|
+
|
|
839
|
+
@@index([taskId])
|
|
840
|
+
@@index([evidenceSubmissionId])
|
|
841
|
+
@@index([evidenceFormType])
|
|
842
|
+
@@index([policyId])
|
|
843
|
+
@@index([vendorId])
|
|
844
|
+
@@index([riskId])
|
|
845
|
+
@@index([memberId])
|
|
846
|
+
@@index([deviceId])
|
|
847
|
+
@@index([organizationId, status])
|
|
848
|
+
@@index([organizationId, severity])
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// ===== fleet-policy-result.prisma =====
|
|
852
|
+
model FleetPolicyResult {
|
|
853
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('fpr'::text)"))
|
|
854
|
+
userId String
|
|
855
|
+
organizationId String
|
|
856
|
+
fleetPolicyId Int
|
|
857
|
+
fleetPolicyName String
|
|
858
|
+
fleetPolicyResponse String
|
|
859
|
+
attachments String[] @default([])
|
|
860
|
+
createdAt DateTime @default(now())
|
|
861
|
+
updatedAt DateTime @updatedAt
|
|
862
|
+
|
|
863
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
864
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
865
|
+
|
|
866
|
+
@@index([userId])
|
|
867
|
+
@@index([organizationId])
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// ===== framework-editor.prisma =====
|
|
871
|
+
// --- Data for Framework Editor ---
|
|
872
|
+
model FrameworkEditorVideo {
|
|
873
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('frk_vi'::text)"))
|
|
874
|
+
title String
|
|
875
|
+
description String
|
|
876
|
+
youtubeId String
|
|
877
|
+
url String
|
|
878
|
+
|
|
879
|
+
// Dates
|
|
880
|
+
createdAt DateTime @default(now())
|
|
881
|
+
updatedAt DateTime @default(now()) @updatedAt
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
model FrameworkEditorFramework {
|
|
885
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('frk'::text)"))
|
|
886
|
+
name String // e.g., "soc2", "iso27001"
|
|
887
|
+
version String
|
|
888
|
+
description String
|
|
889
|
+
visible Boolean @default(false)
|
|
890
|
+
|
|
891
|
+
requirements FrameworkEditorRequirement[]
|
|
892
|
+
frameworkInstances FrameworkInstance[]
|
|
893
|
+
soaConfigurations SOAFrameworkConfiguration[] // Multiple SOA config versions per framework
|
|
894
|
+
soaDocuments SOADocument[] // SOA documents from organizations
|
|
895
|
+
timelineTemplates TimelineTemplate[]
|
|
896
|
+
versions FrameworkVersion[]
|
|
897
|
+
|
|
898
|
+
// Dates
|
|
899
|
+
createdAt DateTime @default(now())
|
|
900
|
+
updatedAt DateTime @default(now()) @updatedAt
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
model FrameworkEditorRequirement {
|
|
904
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('frk_rq'::text)"))
|
|
905
|
+
frameworkId String
|
|
906
|
+
framework FrameworkEditorFramework @relation(fields: [frameworkId], references: [id])
|
|
907
|
+
|
|
908
|
+
name String // Original requirement ID within that framework, e.g., "Privacy"
|
|
909
|
+
identifier String @default("") // Unique identifier for the requirement, e.g., "cc1-1"
|
|
910
|
+
description String
|
|
911
|
+
|
|
912
|
+
controlTemplates FrameworkEditorControlTemplate[]
|
|
913
|
+
requirementMaps RequirementMap[]
|
|
914
|
+
|
|
915
|
+
// Dates
|
|
916
|
+
createdAt DateTime @default(now())
|
|
917
|
+
updatedAt DateTime @default(now()) @updatedAt
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
model FrameworkEditorPolicyTemplate {
|
|
921
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('frk_pt'::text)"))
|
|
922
|
+
name String
|
|
923
|
+
description String
|
|
924
|
+
frequency Frequency // Using the enum from shared.prisma
|
|
925
|
+
department Departments // Using the enum from shared.prisma
|
|
926
|
+
content Json
|
|
927
|
+
|
|
928
|
+
controlTemplates FrameworkEditorControlTemplate[]
|
|
929
|
+
|
|
930
|
+
// Dates
|
|
931
|
+
createdAt DateTime @default(now())
|
|
932
|
+
updatedAt DateTime @default(now()) @updatedAt
|
|
933
|
+
|
|
934
|
+
// Instances
|
|
935
|
+
policies Policy[]
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
model FrameworkEditorTaskTemplate {
|
|
939
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('frk_tt'::text)"))
|
|
940
|
+
name String
|
|
941
|
+
description String
|
|
942
|
+
frequency Frequency // Using the enum from shared.prisma
|
|
943
|
+
department Departments // Using the enum from shared.prisma
|
|
944
|
+
automationStatus TaskAutomationStatus @default(AUTOMATED)
|
|
945
|
+
|
|
946
|
+
controlTemplates FrameworkEditorControlTemplate[]
|
|
947
|
+
|
|
948
|
+
// Dates
|
|
949
|
+
createdAt DateTime @default(now())
|
|
950
|
+
updatedAt DateTime @default(now()) @updatedAt
|
|
951
|
+
|
|
952
|
+
// Instances
|
|
953
|
+
tasks Task[]
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
model FrameworkEditorControlTemplate {
|
|
957
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('frk_ct'::text)"))
|
|
958
|
+
name String
|
|
959
|
+
description String
|
|
960
|
+
|
|
961
|
+
policyTemplates FrameworkEditorPolicyTemplate[]
|
|
962
|
+
requirements FrameworkEditorRequirement[]
|
|
963
|
+
taskTemplates FrameworkEditorTaskTemplate[]
|
|
964
|
+
documentTypes EvidenceFormType[]
|
|
965
|
+
|
|
966
|
+
// Dates
|
|
967
|
+
createdAt DateTime @default(now())
|
|
968
|
+
updatedAt DateTime @default(now()) @updatedAt
|
|
969
|
+
|
|
970
|
+
// Instances
|
|
971
|
+
controls Control[]
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// ===== framework-sync-operation.prisma =====
|
|
975
|
+
enum FrameworkSyncOperationKind {
|
|
976
|
+
SYNC
|
|
977
|
+
ROLLBACK
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
model FrameworkSyncOperation {
|
|
981
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('fso'::text)"))
|
|
982
|
+
frameworkInstanceId String
|
|
983
|
+
frameworkInstance FrameworkInstance @relation(fields: [frameworkInstanceId], references: [id], onDelete: Cascade)
|
|
984
|
+
|
|
985
|
+
fromVersionId String
|
|
986
|
+
fromVersion FrameworkVersion @relation("FrameworkSyncOperationFromVersion", fields: [fromVersionId], references: [id])
|
|
987
|
+
toVersionId String
|
|
988
|
+
toVersion FrameworkVersion @relation("FrameworkSyncOperationToVersion", fields: [toVersionId], references: [id])
|
|
989
|
+
|
|
990
|
+
kind FrameworkSyncOperationKind
|
|
991
|
+
|
|
992
|
+
performedAt DateTime @default(now())
|
|
993
|
+
performedById String?
|
|
994
|
+
performedBy Member? @relation("FrameworkSyncOperationPerformer", fields: [performedById], references: [id], onDelete: SetNull)
|
|
995
|
+
|
|
996
|
+
// Only set when kind = SYNC
|
|
997
|
+
rollbackExpiresAt DateTime?
|
|
998
|
+
|
|
999
|
+
// Self-reference: when this sync was reversed, points at the rollback op.
|
|
1000
|
+
rolledBackByOperationId String? @unique
|
|
1001
|
+
rolledBackByOperation FrameworkSyncOperation? @relation("FrameworkSyncOperationRollback", fields: [rolledBackByOperationId], references: [id])
|
|
1002
|
+
rolledBackOperation FrameworkSyncOperation? @relation("FrameworkSyncOperationRollback")
|
|
1003
|
+
|
|
1004
|
+
undoPayload Json // structured per undo-payload.types.ts
|
|
1005
|
+
summary Json // counts for audit log / UI
|
|
1006
|
+
|
|
1007
|
+
@@index([frameworkInstanceId, performedAt])
|
|
1008
|
+
@@index([frameworkInstanceId, kind])
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// ===== framework-version.prisma =====
|
|
1012
|
+
model FrameworkVersion {
|
|
1013
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('fvr'::text)"))
|
|
1014
|
+
frameworkId String
|
|
1015
|
+
framework FrameworkEditorFramework @relation(fields: [frameworkId], references: [id], onDelete: Cascade)
|
|
1016
|
+
|
|
1017
|
+
version String // semver-ish, e.g., "1.0.0", "2.1.0"
|
|
1018
|
+
publishedAt DateTime @default(now())
|
|
1019
|
+
publishedById String?
|
|
1020
|
+
publishedBy User? @relation("FrameworkVersionPublisher", fields: [publishedById], references: [id], onDelete: SetNull)
|
|
1021
|
+
|
|
1022
|
+
releaseNotes String? // markdown
|
|
1023
|
+
|
|
1024
|
+
// Full snapshot of all templates at publish time (see manifest.types.ts).
|
|
1025
|
+
// Immutable once published.
|
|
1026
|
+
manifest Json
|
|
1027
|
+
|
|
1028
|
+
frameworkInstances FrameworkInstance[] @relation("FrameworkInstanceCurrentVersion")
|
|
1029
|
+
syncOperationsFrom FrameworkSyncOperation[] @relation("FrameworkSyncOperationFromVersion")
|
|
1030
|
+
syncOperationsTo FrameworkSyncOperation[] @relation("FrameworkSyncOperationToVersion")
|
|
1031
|
+
|
|
1032
|
+
@@unique([frameworkId, version])
|
|
1033
|
+
@@index([frameworkId, publishedAt])
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// ===== framework.prisma =====
|
|
1037
|
+
model FrameworkInstance {
|
|
1038
|
+
// Metadata
|
|
1039
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('frm'::text)"))
|
|
1040
|
+
organizationId String
|
|
1041
|
+
|
|
1042
|
+
// Exactly one of frameworkId / customFrameworkId is set (enforced by DB CHECK constraint).
|
|
1043
|
+
frameworkId String?
|
|
1044
|
+
framework FrameworkEditorFramework? @relation(fields: [frameworkId], references: [id], onDelete: Cascade)
|
|
1045
|
+
|
|
1046
|
+
customFrameworkId String?
|
|
1047
|
+
// Composite FK onto (id, organizationId) so an FI can only reference a
|
|
1048
|
+
// CustomFramework in its own org. Enforced at the DB level.
|
|
1049
|
+
customFramework CustomFramework? @relation(fields: [customFrameworkId, organizationId], references: [id, organizationId], onDelete: Cascade)
|
|
1050
|
+
|
|
1051
|
+
currentVersionId String?
|
|
1052
|
+
currentVersion FrameworkVersion? @relation("FrameworkInstanceCurrentVersion", fields: [currentVersionId], references: [id], onDelete: Restrict)
|
|
1053
|
+
|
|
1054
|
+
// Relationships
|
|
1055
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
1056
|
+
requirementsMapped RequirementMap[]
|
|
1057
|
+
timelineInstances TimelineInstance[]
|
|
1058
|
+
syncOperations FrameworkSyncOperation[]
|
|
1059
|
+
|
|
1060
|
+
@@unique([organizationId, frameworkId])
|
|
1061
|
+
@@unique([organizationId, customFrameworkId])
|
|
1062
|
+
@@index([customFrameworkId])
|
|
1063
|
+
@@index([currentVersionId])
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// ===== integration-platform.prisma =====
|
|
1067
|
+
// ===== Integration Platform =====
|
|
1068
|
+
// New integration platform models for scalable, config-driven integrations
|
|
1069
|
+
|
|
1070
|
+
/// Stores metadata about available integration providers (synced from code manifests)
|
|
1071
|
+
model IntegrationProvider {
|
|
1072
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('prv'::text)"))
|
|
1073
|
+
/// Unique slug matching manifest ID (e.g., "github", "slack")
|
|
1074
|
+
slug String @unique
|
|
1075
|
+
/// Display name
|
|
1076
|
+
name String
|
|
1077
|
+
/// Category for grouping
|
|
1078
|
+
category String
|
|
1079
|
+
/// Hash of manifest for detecting changes
|
|
1080
|
+
manifestHash String?
|
|
1081
|
+
/// Capabilities JSON array
|
|
1082
|
+
capabilities Json @default("[]")
|
|
1083
|
+
/// Whether provider is active
|
|
1084
|
+
isActive Boolean @default(true)
|
|
1085
|
+
|
|
1086
|
+
createdAt DateTime @default(now())
|
|
1087
|
+
updatedAt DateTime @updatedAt
|
|
1088
|
+
|
|
1089
|
+
connections IntegrationConnection[]
|
|
1090
|
+
|
|
1091
|
+
@@index([slug])
|
|
1092
|
+
@@index([category])
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/// Represents an organization's connection to an integration provider
|
|
1096
|
+
model IntegrationConnection {
|
|
1097
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('icn'::text)"))
|
|
1098
|
+
|
|
1099
|
+
/// Reference to the provider
|
|
1100
|
+
providerId String
|
|
1101
|
+
provider IntegrationProvider @relation(fields: [providerId], references: [id], onDelete: Cascade)
|
|
1102
|
+
|
|
1103
|
+
/// Organization that owns this connection
|
|
1104
|
+
organizationId String
|
|
1105
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
1106
|
+
|
|
1107
|
+
/// Connection status
|
|
1108
|
+
status IntegrationConnectionStatus @default(pending)
|
|
1109
|
+
|
|
1110
|
+
/// Auth strategy used (oauth2, api_key, basic, jwt, custom)
|
|
1111
|
+
authStrategy String
|
|
1112
|
+
|
|
1113
|
+
/// Reference to active credential version
|
|
1114
|
+
activeCredentialVersionId String?
|
|
1115
|
+
|
|
1116
|
+
/// Last successful sync timestamp
|
|
1117
|
+
lastSyncAt DateTime?
|
|
1118
|
+
|
|
1119
|
+
/// Next scheduled sync timestamp
|
|
1120
|
+
nextSyncAt DateTime?
|
|
1121
|
+
|
|
1122
|
+
/// Custom sync cadence (cron expression), null = use default
|
|
1123
|
+
syncCadence String?
|
|
1124
|
+
|
|
1125
|
+
/// Additional metadata (e.g., connected account info)
|
|
1126
|
+
metadata Json?
|
|
1127
|
+
|
|
1128
|
+
/// User-configured variables for checks (collected after OAuth)
|
|
1129
|
+
variables Json?
|
|
1130
|
+
|
|
1131
|
+
/// Error message if status is error
|
|
1132
|
+
errorMessage String?
|
|
1133
|
+
|
|
1134
|
+
createdAt DateTime @default(now())
|
|
1135
|
+
updatedAt DateTime @updatedAt
|
|
1136
|
+
|
|
1137
|
+
credentialVersions IntegrationCredentialVersion[]
|
|
1138
|
+
runs IntegrationRun[]
|
|
1139
|
+
findings IntegrationPlatformFinding[]
|
|
1140
|
+
checkRuns IntegrationCheckRun[]
|
|
1141
|
+
syncLogs IntegrationSyncLog[]
|
|
1142
|
+
remediationActions RemediationAction[]
|
|
1143
|
+
remediationBatches RemediationBatch[]
|
|
1144
|
+
|
|
1145
|
+
@@index([organizationId])
|
|
1146
|
+
@@index([providerId])
|
|
1147
|
+
@@index([providerId, organizationId])
|
|
1148
|
+
@@index([status])
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
enum IntegrationConnectionStatus {
|
|
1152
|
+
pending // Awaiting credential setup
|
|
1153
|
+
active // Connected and operational
|
|
1154
|
+
error // Connection has errors
|
|
1155
|
+
paused // Manually paused by user
|
|
1156
|
+
disconnected // User disconnected
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
/// Stores encrypted credentials with versioning for audit trail
|
|
1160
|
+
model IntegrationCredentialVersion {
|
|
1161
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('icv'::text)"))
|
|
1162
|
+
|
|
1163
|
+
/// Parent connection
|
|
1164
|
+
connectionId String
|
|
1165
|
+
connection IntegrationConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
|
|
1166
|
+
|
|
1167
|
+
/// Encrypted credential payload (JSON with encrypted fields)
|
|
1168
|
+
encryptedPayload Json
|
|
1169
|
+
|
|
1170
|
+
/// Version number (auto-increment per connection)
|
|
1171
|
+
version Int
|
|
1172
|
+
|
|
1173
|
+
/// Token expiration (for OAuth tokens)
|
|
1174
|
+
expiresAt DateTime?
|
|
1175
|
+
|
|
1176
|
+
/// When this version was rotated/replaced
|
|
1177
|
+
rotatedAt DateTime?
|
|
1178
|
+
|
|
1179
|
+
createdAt DateTime @default(now())
|
|
1180
|
+
|
|
1181
|
+
@@unique([connectionId, version])
|
|
1182
|
+
@@index([connectionId])
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
/// Records each sync/job execution for audit and debugging
|
|
1186
|
+
model IntegrationRun {
|
|
1187
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('irn'::text)"))
|
|
1188
|
+
|
|
1189
|
+
/// Parent connection
|
|
1190
|
+
connectionId String
|
|
1191
|
+
connection IntegrationConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
|
|
1192
|
+
|
|
1193
|
+
/// Type of job
|
|
1194
|
+
jobType IntegrationRunJobType
|
|
1195
|
+
|
|
1196
|
+
/// Execution status
|
|
1197
|
+
status IntegrationRunStatus @default(pending)
|
|
1198
|
+
|
|
1199
|
+
/// Timestamps
|
|
1200
|
+
startedAt DateTime?
|
|
1201
|
+
completedAt DateTime?
|
|
1202
|
+
|
|
1203
|
+
/// Duration in milliseconds
|
|
1204
|
+
durationMs Int?
|
|
1205
|
+
|
|
1206
|
+
/// Number of findings from this run
|
|
1207
|
+
findingsCount Int @default(0)
|
|
1208
|
+
|
|
1209
|
+
/// Error details if failed
|
|
1210
|
+
error Json?
|
|
1211
|
+
|
|
1212
|
+
/// Additional metadata (trigger source, cursor, etc.)
|
|
1213
|
+
metadata Json?
|
|
1214
|
+
|
|
1215
|
+
createdAt DateTime @default(now())
|
|
1216
|
+
|
|
1217
|
+
findings IntegrationPlatformFinding[]
|
|
1218
|
+
|
|
1219
|
+
@@index([connectionId])
|
|
1220
|
+
@@index([status])
|
|
1221
|
+
@@index([createdAt])
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
enum IntegrationRunJobType {
|
|
1225
|
+
full_sync
|
|
1226
|
+
delta_sync
|
|
1227
|
+
webhook
|
|
1228
|
+
manual
|
|
1229
|
+
test_connection
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
enum IntegrationRunStatus {
|
|
1233
|
+
pending
|
|
1234
|
+
running
|
|
1235
|
+
success
|
|
1236
|
+
failed
|
|
1237
|
+
cancelled
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
/// Stores findings/results from integration syncs
|
|
1241
|
+
model IntegrationPlatformFinding {
|
|
1242
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('ipf'::text)"))
|
|
1243
|
+
|
|
1244
|
+
/// Parent run (optional - webhooks may not have runs)
|
|
1245
|
+
runId String?
|
|
1246
|
+
run IntegrationRun? @relation(fields: [runId], references: [id], onDelete: SetNull)
|
|
1247
|
+
|
|
1248
|
+
/// Parent connection
|
|
1249
|
+
connectionId String
|
|
1250
|
+
connection IntegrationConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
|
|
1251
|
+
|
|
1252
|
+
/// Resource classification
|
|
1253
|
+
resourceType String
|
|
1254
|
+
resourceId String
|
|
1255
|
+
|
|
1256
|
+
/// Finding details
|
|
1257
|
+
title String
|
|
1258
|
+
description String?
|
|
1259
|
+
|
|
1260
|
+
/// Severity level
|
|
1261
|
+
severity IntegrationFindingSeverity @default(info)
|
|
1262
|
+
|
|
1263
|
+
/// Finding status
|
|
1264
|
+
status IntegrationFindingStatus @default(open)
|
|
1265
|
+
|
|
1266
|
+
/// Remediation guidance
|
|
1267
|
+
remediation String?
|
|
1268
|
+
|
|
1269
|
+
/// Raw payload from provider
|
|
1270
|
+
rawPayload Json?
|
|
1271
|
+
|
|
1272
|
+
createdAt DateTime @default(now())
|
|
1273
|
+
updatedAt DateTime @updatedAt
|
|
1274
|
+
|
|
1275
|
+
@@index([connectionId])
|
|
1276
|
+
@@index([runId])
|
|
1277
|
+
@@index([resourceType, resourceId])
|
|
1278
|
+
@@index([severity])
|
|
1279
|
+
@@index([status])
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
enum IntegrationFindingSeverity {
|
|
1283
|
+
info
|
|
1284
|
+
low
|
|
1285
|
+
medium
|
|
1286
|
+
high
|
|
1287
|
+
critical
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
enum IntegrationFindingStatus {
|
|
1291
|
+
open
|
|
1292
|
+
resolved
|
|
1293
|
+
ignored
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
/// Stores OAuth state for CSRF protection during OAuth flow
|
|
1297
|
+
model IntegrationOAuthState {
|
|
1298
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('ios'::text)"))
|
|
1299
|
+
|
|
1300
|
+
/// Random state parameter
|
|
1301
|
+
state String @unique
|
|
1302
|
+
|
|
1303
|
+
/// Provider slug
|
|
1304
|
+
providerSlug String
|
|
1305
|
+
|
|
1306
|
+
/// Organization initiating the OAuth
|
|
1307
|
+
organizationId String
|
|
1308
|
+
|
|
1309
|
+
/// User initiating the OAuth
|
|
1310
|
+
userId String
|
|
1311
|
+
|
|
1312
|
+
/// PKCE code verifier (if using PKCE)
|
|
1313
|
+
codeVerifier String?
|
|
1314
|
+
|
|
1315
|
+
/// Redirect URL after OAuth completes
|
|
1316
|
+
redirectUrl String?
|
|
1317
|
+
|
|
1318
|
+
/// Expiration timestamp
|
|
1319
|
+
expiresAt DateTime
|
|
1320
|
+
|
|
1321
|
+
createdAt DateTime @default(now())
|
|
1322
|
+
|
|
1323
|
+
@@index([state])
|
|
1324
|
+
@@index([expiresAt])
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
/// Stores organization-level OAuth app credentials
|
|
1328
|
+
/// Allows orgs (especially self-hosters) to use their own OAuth apps
|
|
1329
|
+
model IntegrationOAuthApp {
|
|
1330
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('ioa'::text)"))
|
|
1331
|
+
|
|
1332
|
+
/// Provider slug (e.g., "github", "slack")
|
|
1333
|
+
providerSlug String
|
|
1334
|
+
|
|
1335
|
+
/// Organization that owns this OAuth app config
|
|
1336
|
+
organizationId String
|
|
1337
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
1338
|
+
|
|
1339
|
+
/// Encrypted client ID
|
|
1340
|
+
encryptedClientId Json
|
|
1341
|
+
|
|
1342
|
+
/// Encrypted client secret
|
|
1343
|
+
encryptedClientSecret Json
|
|
1344
|
+
|
|
1345
|
+
/// Optional: custom scopes (overrides manifest defaults)
|
|
1346
|
+
customScopes String[]
|
|
1347
|
+
|
|
1348
|
+
/// Provider-specific settings (e.g., Rippling app name for authorize URL)
|
|
1349
|
+
/// Stored as JSON: { "appName": "compai533c" }
|
|
1350
|
+
customSettings Json?
|
|
1351
|
+
|
|
1352
|
+
/// Whether this config is active
|
|
1353
|
+
isActive Boolean @default(true)
|
|
1354
|
+
|
|
1355
|
+
createdAt DateTime @default(now())
|
|
1356
|
+
updatedAt DateTime @updatedAt
|
|
1357
|
+
|
|
1358
|
+
@@unique([providerSlug, organizationId])
|
|
1359
|
+
@@index([organizationId])
|
|
1360
|
+
@@index([providerSlug])
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
/// Records check runs linked to tasks for compliance verification
|
|
1364
|
+
model IntegrationCheckRun {
|
|
1365
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('icr'::text)"))
|
|
1366
|
+
|
|
1367
|
+
/// Parent connection
|
|
1368
|
+
connectionId String
|
|
1369
|
+
connection IntegrationConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
|
|
1370
|
+
|
|
1371
|
+
/// Task being verified (optional - checks can run without a task)
|
|
1372
|
+
taskId String?
|
|
1373
|
+
task Task? @relation(fields: [taskId], references: [id], onDelete: SetNull)
|
|
1374
|
+
|
|
1375
|
+
/// Check ID from the manifest
|
|
1376
|
+
checkId String
|
|
1377
|
+
|
|
1378
|
+
/// Check name (denormalized for display)
|
|
1379
|
+
checkName String
|
|
1380
|
+
|
|
1381
|
+
/// Execution status
|
|
1382
|
+
status IntegrationRunStatus @default(pending)
|
|
1383
|
+
|
|
1384
|
+
/// Timestamps
|
|
1385
|
+
startedAt DateTime?
|
|
1386
|
+
completedAt DateTime?
|
|
1387
|
+
|
|
1388
|
+
/// Duration in milliseconds
|
|
1389
|
+
durationMs Int?
|
|
1390
|
+
|
|
1391
|
+
/// Summary counts
|
|
1392
|
+
totalChecked Int @default(0)
|
|
1393
|
+
passedCount Int @default(0)
|
|
1394
|
+
failedCount Int @default(0)
|
|
1395
|
+
|
|
1396
|
+
/// Error message if failed
|
|
1397
|
+
errorMessage String?
|
|
1398
|
+
|
|
1399
|
+
/// Full execution logs (JSON array)
|
|
1400
|
+
logs Json?
|
|
1401
|
+
|
|
1402
|
+
createdAt DateTime @default(now())
|
|
1403
|
+
|
|
1404
|
+
/// Results from this check run
|
|
1405
|
+
results IntegrationCheckResult[]
|
|
1406
|
+
|
|
1407
|
+
@@index([connectionId])
|
|
1408
|
+
@@index([taskId])
|
|
1409
|
+
@@index([checkId])
|
|
1410
|
+
@@index([status])
|
|
1411
|
+
@@index([createdAt])
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
/// Stores individual results (pass/fail) from check runs
|
|
1415
|
+
model IntegrationCheckResult {
|
|
1416
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('icx'::text)"))
|
|
1417
|
+
|
|
1418
|
+
/// Parent check run
|
|
1419
|
+
checkRunId String
|
|
1420
|
+
checkRun IntegrationCheckRun @relation(fields: [checkRunId], references: [id], onDelete: Cascade)
|
|
1421
|
+
|
|
1422
|
+
/// Whether this result is a pass or fail
|
|
1423
|
+
passed Boolean
|
|
1424
|
+
|
|
1425
|
+
/// Resource classification
|
|
1426
|
+
resourceType String
|
|
1427
|
+
resourceId String
|
|
1428
|
+
|
|
1429
|
+
/// Result details
|
|
1430
|
+
title String
|
|
1431
|
+
description String?
|
|
1432
|
+
|
|
1433
|
+
/// Severity (for failures)
|
|
1434
|
+
severity IntegrationFindingSeverity?
|
|
1435
|
+
|
|
1436
|
+
/// Remediation guidance (for failures)
|
|
1437
|
+
remediation String?
|
|
1438
|
+
|
|
1439
|
+
/// Evidence/proof (JSON - API response data)
|
|
1440
|
+
evidence Json?
|
|
1441
|
+
|
|
1442
|
+
/// When this evidence was collected
|
|
1443
|
+
collectedAt DateTime @default(now())
|
|
1444
|
+
|
|
1445
|
+
remediationActions RemediationAction[]
|
|
1446
|
+
|
|
1447
|
+
@@index([checkRunId])
|
|
1448
|
+
@@index([passed])
|
|
1449
|
+
@@index([resourceType, resourceId])
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
/// Stores platform-wide OAuth app credentials
|
|
1453
|
+
/// Used by platform operators to provide default OAuth apps for all users
|
|
1454
|
+
model IntegrationPlatformCredential {
|
|
1455
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('ipc'::text)"))
|
|
1456
|
+
|
|
1457
|
+
/// Provider slug (e.g., "github", "slack") - unique per platform
|
|
1458
|
+
providerSlug String @unique
|
|
1459
|
+
|
|
1460
|
+
/// Encrypted client ID
|
|
1461
|
+
encryptedClientId Json
|
|
1462
|
+
|
|
1463
|
+
/// Encrypted client secret
|
|
1464
|
+
encryptedClientSecret Json
|
|
1465
|
+
|
|
1466
|
+
/// Masked display hint for client ID (computed at write time)
|
|
1467
|
+
clientIdHint String?
|
|
1468
|
+
|
|
1469
|
+
/// Masked display hint for client secret (computed at write time)
|
|
1470
|
+
clientSecretHint String?
|
|
1471
|
+
|
|
1472
|
+
/// Optional: custom scopes (overrides manifest defaults)
|
|
1473
|
+
customScopes String[]
|
|
1474
|
+
|
|
1475
|
+
/// Provider-specific settings (e.g., Rippling app name for authorize URL)
|
|
1476
|
+
/// Stored as JSON: { "appName": "compai533c" }
|
|
1477
|
+
customSettings Json?
|
|
1478
|
+
|
|
1479
|
+
/// Whether this credential is active
|
|
1480
|
+
isActive Boolean @default(true)
|
|
1481
|
+
|
|
1482
|
+
/// Who created this credential
|
|
1483
|
+
createdById String?
|
|
1484
|
+
|
|
1485
|
+
/// Who last updated this credential
|
|
1486
|
+
updatedById String?
|
|
1487
|
+
|
|
1488
|
+
createdAt DateTime @default(now())
|
|
1489
|
+
updatedAt DateTime @updatedAt
|
|
1490
|
+
|
|
1491
|
+
@@index([providerSlug])
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
// ===== integration-sync-log.prisma =====
|
|
1495
|
+
// ===== Integration Sync Log =====
|
|
1496
|
+
// Generic audit trail for integration sync operations (employee sync, role discovery, etc.)
|
|
1497
|
+
|
|
1498
|
+
model IntegrationSyncLog {
|
|
1499
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('isl'::text)"))
|
|
1500
|
+
connectionId String
|
|
1501
|
+
connection IntegrationConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
|
|
1502
|
+
organizationId String
|
|
1503
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
1504
|
+
|
|
1505
|
+
/// Provider slug (e.g., "google-workspace", "rippling", "jumpcloud")
|
|
1506
|
+
provider String
|
|
1507
|
+
/// Event type (e.g., "employee_sync", "role_discovery", "role_mapping_save")
|
|
1508
|
+
eventType String
|
|
1509
|
+
/// Execution status
|
|
1510
|
+
status IntegrationSyncLogStatus @default(pending)
|
|
1511
|
+
/// When the operation started executing
|
|
1512
|
+
startedAt DateTime?
|
|
1513
|
+
/// When the operation completed (success or failure)
|
|
1514
|
+
completedAt DateTime?
|
|
1515
|
+
/// Duration in milliseconds
|
|
1516
|
+
durationMs Int?
|
|
1517
|
+
/// Flexible result payload (e.g., { imported, deactivated, reactivated, skipped, errors })
|
|
1518
|
+
result Json?
|
|
1519
|
+
/// Error message if failed
|
|
1520
|
+
error String?
|
|
1521
|
+
/// How the sync was triggered: "manual", "scheduled", "api"
|
|
1522
|
+
triggeredBy String?
|
|
1523
|
+
/// User who triggered the sync (null for automated/cron)
|
|
1524
|
+
userId String?
|
|
1525
|
+
|
|
1526
|
+
createdAt DateTime @default(now())
|
|
1527
|
+
|
|
1528
|
+
@@index([connectionId])
|
|
1529
|
+
@@index([organizationId])
|
|
1530
|
+
@@index([provider])
|
|
1531
|
+
@@index([createdAt])
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
enum IntegrationSyncLogStatus {
|
|
1535
|
+
pending
|
|
1536
|
+
running
|
|
1537
|
+
success
|
|
1538
|
+
failed
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
// ===== integration.prisma =====
|
|
1542
|
+
model Integration {
|
|
1543
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('int'::text)"))
|
|
1544
|
+
name String
|
|
1545
|
+
integrationId String
|
|
1546
|
+
settings Json
|
|
1547
|
+
userSettings Json
|
|
1548
|
+
organizationId String
|
|
1549
|
+
lastRunAt DateTime?
|
|
1550
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
1551
|
+
results IntegrationResult[]
|
|
1552
|
+
|
|
1553
|
+
@@index([organizationId])
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
model IntegrationResult {
|
|
1557
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('itr'::text)"))
|
|
1558
|
+
title String?
|
|
1559
|
+
description String?
|
|
1560
|
+
remediation String?
|
|
1561
|
+
status String?
|
|
1562
|
+
severity String?
|
|
1563
|
+
resultDetails Json?
|
|
1564
|
+
completedAt DateTime? @default(now())
|
|
1565
|
+
integrationId String
|
|
1566
|
+
organizationId String
|
|
1567
|
+
assignedUserId String?
|
|
1568
|
+
|
|
1569
|
+
assignedUser User? @relation(fields: [assignedUserId], references: [id], onDelete: Cascade)
|
|
1570
|
+
integration Integration @relation(fields: [integrationId], references: [id], onDelete: Cascade)
|
|
1571
|
+
|
|
1572
|
+
@@index([integrationId])
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
// ===== knowledge-base-document.prisma =====
|
|
1576
|
+
model KnowledgeBaseDocument {
|
|
1577
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('kbd'::text)"))
|
|
1578
|
+
name String // Original filename
|
|
1579
|
+
description String? // Optional user description/notes
|
|
1580
|
+
s3Key String // S3 storage key (e.g., "org123/knowledge-base-documents/timestamp-file.pdf")
|
|
1581
|
+
fileType String // MIME type (e.g., "application/pdf")
|
|
1582
|
+
fileSize Int // File size in bytes
|
|
1583
|
+
processingStatus KnowledgeBaseDocumentProcessingStatus @default(pending) // Track indexing status
|
|
1584
|
+
processedAt DateTime? // When indexing completed
|
|
1585
|
+
triggerRunId String? // Trigger.dev run ID for tracking processing progress
|
|
1586
|
+
|
|
1587
|
+
// Dates
|
|
1588
|
+
createdAt DateTime @default(now())
|
|
1589
|
+
updatedAt DateTime @updatedAt
|
|
1590
|
+
|
|
1591
|
+
// Relationships
|
|
1592
|
+
organizationId String
|
|
1593
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
1594
|
+
|
|
1595
|
+
@@index([organizationId])
|
|
1596
|
+
@@index([organizationId, processingStatus])
|
|
1597
|
+
@@index([s3Key])
|
|
1598
|
+
@@index([triggerRunId])
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
enum KnowledgeBaseDocumentProcessingStatus {
|
|
1602
|
+
pending // Uploaded but not yet processed/indexed
|
|
1603
|
+
processing // Currently being processed/indexed
|
|
1604
|
+
completed // Successfully indexed in vector database
|
|
1605
|
+
failed // Processing failed
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
// ===== notification-policy.prisma =====
|
|
1609
|
+
model RoleNotificationSetting {
|
|
1610
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('rns'::text)"))
|
|
1611
|
+
organizationId String
|
|
1612
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
1613
|
+
role String // "owner", "admin", "auditor", "employee", "contractor", or custom role name
|
|
1614
|
+
|
|
1615
|
+
policyNotifications Boolean @default(true)
|
|
1616
|
+
taskReminders Boolean @default(true)
|
|
1617
|
+
taskAssignments Boolean @default(true)
|
|
1618
|
+
taskMentions Boolean @default(true)
|
|
1619
|
+
weeklyTaskDigest Boolean @default(true)
|
|
1620
|
+
findingNotifications Boolean @default(true)
|
|
1621
|
+
|
|
1622
|
+
createdAt DateTime @default(now())
|
|
1623
|
+
updatedAt DateTime @updatedAt
|
|
1624
|
+
|
|
1625
|
+
@@unique([organizationId, role])
|
|
1626
|
+
@@map("role_notification_setting")
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
// ===== onboarding.prisma =====
|
|
1630
|
+
model Onboarding {
|
|
1631
|
+
organizationId String @id
|
|
1632
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
1633
|
+
policies Boolean @default(false)
|
|
1634
|
+
employees Boolean @default(false)
|
|
1635
|
+
vendors Boolean @default(false)
|
|
1636
|
+
integrations Boolean @default(false)
|
|
1637
|
+
risk Boolean @default(false)
|
|
1638
|
+
team Boolean @default(false)
|
|
1639
|
+
tasks Boolean @default(false)
|
|
1640
|
+
callBooked Boolean @default(false)
|
|
1641
|
+
companyBookingDetails Json?
|
|
1642
|
+
companyDetails Json?
|
|
1643
|
+
triggerJobId String?
|
|
1644
|
+
triggerJobCompleted Boolean @default(false)
|
|
1645
|
+
|
|
1646
|
+
@@index([organizationId])
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// ===== org-chart.prisma =====
|
|
1650
|
+
model OrganizationChart {
|
|
1651
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('och'::text)"))
|
|
1652
|
+
organizationId String @unique
|
|
1653
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
1654
|
+
name String @default("Organization Chart")
|
|
1655
|
+
type String @default("interactive") // "interactive" or "uploaded"
|
|
1656
|
+
nodes Json @default("[]")
|
|
1657
|
+
edges Json @default("[]")
|
|
1658
|
+
uploadedImageUrl String? // S3 key when type="uploaded"
|
|
1659
|
+
createdAt DateTime @default(now())
|
|
1660
|
+
updatedAt DateTime @updatedAt
|
|
1661
|
+
|
|
1662
|
+
@@index([organizationId])
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
// ===== organization-billing.prisma =====
|
|
1666
|
+
model OrganizationBilling {
|
|
1667
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('obil'::text)"))
|
|
1668
|
+
organizationId String @unique @map("organization_id")
|
|
1669
|
+
stripeCustomerId String @map("stripe_customer_id")
|
|
1670
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
1671
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
1672
|
+
|
|
1673
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
1674
|
+
pentestSubscription PentestSubscription?
|
|
1675
|
+
|
|
1676
|
+
@@map("organization_billing")
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
// ===== organization.prisma =====
|
|
1680
|
+
model Organization {
|
|
1681
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('org'::text)"))
|
|
1682
|
+
name String
|
|
1683
|
+
slug String @unique @default(dbgenerated("generate_prefixed_cuid('slug'::text)"))
|
|
1684
|
+
logo String?
|
|
1685
|
+
createdAt DateTime @default(now())
|
|
1686
|
+
metadata String?
|
|
1687
|
+
onboarding Onboarding?
|
|
1688
|
+
website String?
|
|
1689
|
+
onboardingCompleted Boolean @default(false)
|
|
1690
|
+
hasAccess Boolean @default(false)
|
|
1691
|
+
advancedModeEnabled Boolean @default(false)
|
|
1692
|
+
evidenceApprovalEnabled Boolean @default(false)
|
|
1693
|
+
deviceAgentStepEnabled Boolean @default(true)
|
|
1694
|
+
securityTrainingStepEnabled Boolean @default(true)
|
|
1695
|
+
whistleblowerReportEnabled Boolean @default(true)
|
|
1696
|
+
accessRequestFormEnabled Boolean @default(true)
|
|
1697
|
+
|
|
1698
|
+
// FleetDM
|
|
1699
|
+
fleetDmLabelId Int?
|
|
1700
|
+
isFleetSetupCompleted Boolean @default(false)
|
|
1701
|
+
|
|
1702
|
+
// Employee sync provider (e.g., 'google-workspace', 'rippling')
|
|
1703
|
+
// When set, the scheduled sync will only use this provider
|
|
1704
|
+
employeeSyncProvider String?
|
|
1705
|
+
|
|
1706
|
+
apiKeys ApiKey[]
|
|
1707
|
+
auditLog AuditLog[]
|
|
1708
|
+
controls Control[]
|
|
1709
|
+
frameworkInstances FrameworkInstance[]
|
|
1710
|
+
integrations Integration[]
|
|
1711
|
+
invitations Invitation[]
|
|
1712
|
+
members Member[]
|
|
1713
|
+
policy Policy[]
|
|
1714
|
+
risk Risk[]
|
|
1715
|
+
vendors Vendor[]
|
|
1716
|
+
tasks Task[]
|
|
1717
|
+
taskItems TaskItem[]
|
|
1718
|
+
comments Comment[]
|
|
1719
|
+
attachments Attachment[]
|
|
1720
|
+
evidenceSubmissions EvidenceSubmission[]
|
|
1721
|
+
trust Trust[]
|
|
1722
|
+
context Context[]
|
|
1723
|
+
secrets Secret[]
|
|
1724
|
+
securityPenetrationTestRuns SecurityPenetrationTestRun[]
|
|
1725
|
+
trustAccessRequests TrustAccessRequest[]
|
|
1726
|
+
trustNdaAgreements TrustNDAAgreement[]
|
|
1727
|
+
trustDocuments TrustDocument[]
|
|
1728
|
+
trustResources TrustResource[] @relation("OrganizationTrustResources")
|
|
1729
|
+
trustCustomLinks TrustCustomLink[]
|
|
1730
|
+
knowledgeBaseDocuments KnowledgeBaseDocument[]
|
|
1731
|
+
questionnaires Questionnaire[]
|
|
1732
|
+
securityQuestionnaireManualAnswers SecurityQuestionnaireManualAnswer[]
|
|
1733
|
+
soaDocuments SOADocument[]
|
|
1734
|
+
primaryColor String?
|
|
1735
|
+
trustPortalFaqs Json? // Array of { question: string, answer: string, order: number }
|
|
1736
|
+
|
|
1737
|
+
// Integration Platform
|
|
1738
|
+
integrationConnections IntegrationConnection[]
|
|
1739
|
+
integrationOAuthApps IntegrationOAuthApp[]
|
|
1740
|
+
integrationSyncLogs IntegrationSyncLog[]
|
|
1741
|
+
|
|
1742
|
+
// Pentest Subscription
|
|
1743
|
+
pentestSubscription PentestSubscription?
|
|
1744
|
+
billing OrganizationBilling?
|
|
1745
|
+
|
|
1746
|
+
// Browser Automation
|
|
1747
|
+
browserbaseContext BrowserbaseContext?
|
|
1748
|
+
fleetPolicyResults FleetPolicyResult[]
|
|
1749
|
+
|
|
1750
|
+
// Findings
|
|
1751
|
+
findings Finding[]
|
|
1752
|
+
|
|
1753
|
+
// Device Agent
|
|
1754
|
+
devices Device[]
|
|
1755
|
+
|
|
1756
|
+
// Org Chart
|
|
1757
|
+
organizationChart OrganizationChart?
|
|
1758
|
+
|
|
1759
|
+
// RBAC
|
|
1760
|
+
organizationRoles OrganizationRole[]
|
|
1761
|
+
roleNotificationSettings RoleNotificationSetting[]
|
|
1762
|
+
|
|
1763
|
+
// Timeline
|
|
1764
|
+
timelineInstances TimelineInstance[]
|
|
1765
|
+
|
|
1766
|
+
// Custom frameworks / requirements authored by this org
|
|
1767
|
+
customFrameworks CustomFramework[]
|
|
1768
|
+
customRequirements CustomRequirement[]
|
|
1769
|
+
|
|
1770
|
+
@@index([slug])
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
// ===== pentest-subscription.prisma =====
|
|
1774
|
+
model PentestSubscription {
|
|
1775
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('psub'::text)"))
|
|
1776
|
+
organizationId String @unique @map("organization_id")
|
|
1777
|
+
organizationBillingId String @unique @map("organization_billing_id")
|
|
1778
|
+
stripeSubscriptionId String @map("stripe_subscription_id")
|
|
1779
|
+
stripePriceId String @map("stripe_price_id")
|
|
1780
|
+
stripeOveragePriceId String? @map("stripe_overage_price_id")
|
|
1781
|
+
status String @default("active") // active | cancelled | past_due
|
|
1782
|
+
includedRunsPerPeriod Int @default(3) @map("included_runs_per_period")
|
|
1783
|
+
currentPeriodStart DateTime @map("current_period_start")
|
|
1784
|
+
currentPeriodEnd DateTime @map("current_period_end")
|
|
1785
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
1786
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
1787
|
+
|
|
1788
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
1789
|
+
organizationBilling OrganizationBilling @relation(fields: [organizationBillingId], references: [id])
|
|
1790
|
+
|
|
1791
|
+
@@index([organizationId])
|
|
1792
|
+
@@map("pentest_subscriptions")
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
// ===== policy.prisma =====
|
|
1796
|
+
enum PolicyDisplayFormat {
|
|
1797
|
+
EDITOR
|
|
1798
|
+
PDF
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
enum PolicyVisibility {
|
|
1802
|
+
ALL // Visible to everyone in organization
|
|
1803
|
+
DEPARTMENT // Only visible to specified departments
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
model Policy {
|
|
1807
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('pol'::text)"))
|
|
1808
|
+
name String
|
|
1809
|
+
description String?
|
|
1810
|
+
status PolicyStatus @default(draft)
|
|
1811
|
+
content Json[]
|
|
1812
|
+
draftContent Json[] @default([])
|
|
1813
|
+
frequency Frequency?
|
|
1814
|
+
department Departments?
|
|
1815
|
+
isRequiredToSign Boolean @default(true)
|
|
1816
|
+
signedBy String[] @default([])
|
|
1817
|
+
reviewDate DateTime?
|
|
1818
|
+
isArchived Boolean @default(false)
|
|
1819
|
+
displayFormat PolicyDisplayFormat @default(EDITOR)
|
|
1820
|
+
pdfUrl String?
|
|
1821
|
+
|
|
1822
|
+
// Visibility settings (for department-specific policies)
|
|
1823
|
+
visibility PolicyVisibility @default(ALL)
|
|
1824
|
+
visibleToDepartments Departments[] @default([])
|
|
1825
|
+
|
|
1826
|
+
// Dates
|
|
1827
|
+
createdAt DateTime @default(now())
|
|
1828
|
+
updatedAt DateTime @updatedAt
|
|
1829
|
+
lastArchivedAt DateTime?
|
|
1830
|
+
lastPublishedAt DateTime?
|
|
1831
|
+
|
|
1832
|
+
// Sync-driven archive. Distinct from `isArchived` (which is user-initiated
|
|
1833
|
+
// archiving of a published policy). UI should hide a policy if EITHER is
|
|
1834
|
+
// set: `WHERE isArchived = false AND archivedAt IS NULL`. Rollback restores
|
|
1835
|
+
// only `archivedAt`, never touches `isArchived`.
|
|
1836
|
+
archivedAt DateTime?
|
|
1837
|
+
|
|
1838
|
+
// Relationships
|
|
1839
|
+
organizationId String
|
|
1840
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
1841
|
+
assigneeId String?
|
|
1842
|
+
assignee Member? @relation("PolicyAssignee", fields: [assigneeId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
|
1843
|
+
approverId String?
|
|
1844
|
+
approver Member? @relation("PolicyApprover", fields: [approverId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
|
1845
|
+
policyTemplateId String?
|
|
1846
|
+
policyTemplate FrameworkEditorPolicyTemplate? @relation(fields: [policyTemplateId], references: [id])
|
|
1847
|
+
controls Control[]
|
|
1848
|
+
currentVersionId String? @unique
|
|
1849
|
+
currentVersion PolicyVersion? @relation("PolicyCurrentVersion", fields: [currentVersionId], references: [id])
|
|
1850
|
+
pendingVersionId String?
|
|
1851
|
+
versions PolicyVersion[] @relation("PolicyVersions")
|
|
1852
|
+
findings Finding[]
|
|
1853
|
+
|
|
1854
|
+
@@index([organizationId])
|
|
1855
|
+
@@index([organizationId, archivedAt])
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
model PolicyVersion {
|
|
1859
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('pv'::text)"))
|
|
1860
|
+
createdAt DateTime @default(now())
|
|
1861
|
+
updatedAt DateTime @updatedAt
|
|
1862
|
+
|
|
1863
|
+
// Relations
|
|
1864
|
+
policyId String
|
|
1865
|
+
policy Policy @relation("PolicyVersions", fields: [policyId], references: [id], onDelete: Cascade)
|
|
1866
|
+
currentForPolicy Policy? @relation("PolicyCurrentVersion")
|
|
1867
|
+
|
|
1868
|
+
// Version details
|
|
1869
|
+
version Int
|
|
1870
|
+
content Json[]
|
|
1871
|
+
pdfUrl String?
|
|
1872
|
+
publishedById String?
|
|
1873
|
+
publishedBy Member? @relation("PolicyVersionPublisher", fields: [publishedById], references: [id], onDelete: SetNull)
|
|
1874
|
+
changelog String?
|
|
1875
|
+
|
|
1876
|
+
@@unique([policyId, version])
|
|
1877
|
+
@@index([policyId])
|
|
1878
|
+
@@index([createdAt])
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
// ===== questionnaire.prisma =====
|
|
1882
|
+
model Questionnaire {
|
|
1883
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('qst'::text)"))
|
|
1884
|
+
filename String // Original filename
|
|
1885
|
+
s3Key String // S3 storage key for the uploaded file
|
|
1886
|
+
fileType String // MIME type (e.g., "application/pdf")
|
|
1887
|
+
fileSize Int // File size in bytes
|
|
1888
|
+
status QuestionnaireStatus @default(parsing) // Parsing status
|
|
1889
|
+
parsedAt DateTime? // When parsing completed
|
|
1890
|
+
totalQuestions Int @default(0) // Total number of questions parsed
|
|
1891
|
+
answeredQuestions Int @default(0) // Number of questions with answers
|
|
1892
|
+
source String @default("internal") // Source of the questionnaire: 'internal' (from app) or 'external' (from trust portal)
|
|
1893
|
+
|
|
1894
|
+
// Dates
|
|
1895
|
+
createdAt DateTime @default(now())
|
|
1896
|
+
updatedAt DateTime @updatedAt
|
|
1897
|
+
|
|
1898
|
+
// Relationships
|
|
1899
|
+
organizationId String
|
|
1900
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
1901
|
+
questions QuestionnaireQuestionAnswer[]
|
|
1902
|
+
manualAnswers SecurityQuestionnaireManualAnswer[] // Manual answers saved from this questionnaire
|
|
1903
|
+
|
|
1904
|
+
@@index([organizationId])
|
|
1905
|
+
@@index([organizationId, createdAt])
|
|
1906
|
+
@@index([status])
|
|
1907
|
+
@@index([source])
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
model QuestionnaireQuestionAnswer {
|
|
1911
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('qqa'::text)"))
|
|
1912
|
+
question String // The question text
|
|
1913
|
+
answer String? // The answer (nullable if not provided in file or not generated yet)
|
|
1914
|
+
status QuestionnaireAnswerStatus @default(untouched) // Answer status
|
|
1915
|
+
questionIndex Int // Order/index of the question in the questionnaire
|
|
1916
|
+
sources Json? // Sources used for generated answers (array of source objects)
|
|
1917
|
+
generatedAt DateTime? // When answer was generated (if status is generated)
|
|
1918
|
+
updatedBy String? // User ID who last updated the answer (if manual)
|
|
1919
|
+
|
|
1920
|
+
// Dates
|
|
1921
|
+
createdAt DateTime @default(now())
|
|
1922
|
+
updatedAt DateTime @updatedAt
|
|
1923
|
+
|
|
1924
|
+
// Relationships
|
|
1925
|
+
questionnaireId String
|
|
1926
|
+
questionnaire Questionnaire @relation(fields: [questionnaireId], references: [id], onDelete: Cascade)
|
|
1927
|
+
|
|
1928
|
+
@@index([questionnaireId])
|
|
1929
|
+
@@index([questionnaireId, questionIndex])
|
|
1930
|
+
@@index([status])
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
enum QuestionnaireStatus {
|
|
1934
|
+
parsing // Currently being parsed
|
|
1935
|
+
completed // Successfully parsed
|
|
1936
|
+
failed // Parsing failed
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
enum QuestionnaireAnswerStatus {
|
|
1940
|
+
untouched // No answer yet (empty or not generated)
|
|
1941
|
+
generated // AI generated answer
|
|
1942
|
+
manual // Manually written/edited by user
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
// ===== remediation-action.prisma =====
|
|
1946
|
+
model RemediationAction {
|
|
1947
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('rma'::text)"))
|
|
1948
|
+
checkResultId String
|
|
1949
|
+
connectionId String
|
|
1950
|
+
organizationId String
|
|
1951
|
+
initiatedById String
|
|
1952
|
+
remediationKey String
|
|
1953
|
+
resourceId String
|
|
1954
|
+
resourceType String
|
|
1955
|
+
previousState Json
|
|
1956
|
+
appliedState Json
|
|
1957
|
+
status String @default("pending")
|
|
1958
|
+
riskLevel String?
|
|
1959
|
+
acknowledgmentText String?
|
|
1960
|
+
acknowledgedAt DateTime?
|
|
1961
|
+
errorMessage String?
|
|
1962
|
+
executedAt DateTime?
|
|
1963
|
+
rolledBackAt DateTime?
|
|
1964
|
+
createdAt DateTime @default(now())
|
|
1965
|
+
updatedAt DateTime @updatedAt
|
|
1966
|
+
|
|
1967
|
+
checkResult IntegrationCheckResult @relation(fields: [checkResultId], references: [id], onDelete: Cascade)
|
|
1968
|
+
connection IntegrationConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
|
|
1969
|
+
|
|
1970
|
+
@@index([connectionId])
|
|
1971
|
+
@@index([organizationId])
|
|
1972
|
+
@@index([checkResultId])
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
// ===== remediation-batch.prisma =====
|
|
1976
|
+
model RemediationBatch {
|
|
1977
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('rmb'::text)"))
|
|
1978
|
+
connectionId String
|
|
1979
|
+
organizationId String
|
|
1980
|
+
initiatedById String
|
|
1981
|
+
triggerRunId String?
|
|
1982
|
+
status String @default("pending") // pending, running, done, cancelled
|
|
1983
|
+
findings Json @default("[]") // Array of { id, key, title, status, error? }
|
|
1984
|
+
fixed Int @default(0)
|
|
1985
|
+
skipped Int @default(0)
|
|
1986
|
+
failed Int @default(0)
|
|
1987
|
+
createdAt DateTime @default(now())
|
|
1988
|
+
updatedAt DateTime @updatedAt
|
|
1989
|
+
|
|
1990
|
+
connection IntegrationConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
|
|
1991
|
+
|
|
1992
|
+
@@index([connectionId])
|
|
1993
|
+
@@index([organizationId])
|
|
1994
|
+
@@index([status])
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
// ===== requirement.prisma =====
|
|
1998
|
+
model RequirementMap {
|
|
1999
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('req'::text)"))
|
|
2000
|
+
|
|
2001
|
+
// Exactly one of requirementId / customRequirementId is set (enforced by DB CHECK constraint).
|
|
2002
|
+
requirementId String?
|
|
2003
|
+
requirement FrameworkEditorRequirement? @relation(fields: [requirementId], references: [id], onDelete: Cascade)
|
|
2004
|
+
|
|
2005
|
+
customRequirementId String?
|
|
2006
|
+
customRequirement CustomRequirement? @relation(fields: [customRequirementId], references: [id], onDelete: Cascade)
|
|
2007
|
+
|
|
2008
|
+
controlId String
|
|
2009
|
+
control Control @relation(fields: [controlId], references: [id], onDelete: Cascade)
|
|
2010
|
+
|
|
2011
|
+
frameworkInstanceId String
|
|
2012
|
+
frameworkInstance FrameworkInstance @relation(fields: [frameworkInstanceId], references: [id], onDelete: Cascade)
|
|
2013
|
+
|
|
2014
|
+
// Sync-driven archive — edges are framework-scoped, so these always archive
|
|
2015
|
+
// cleanly when the mapping disappears from the framework's latest version.
|
|
2016
|
+
archivedAt DateTime?
|
|
2017
|
+
|
|
2018
|
+
@@unique([controlId, frameworkInstanceId, requirementId])
|
|
2019
|
+
@@unique([controlId, frameworkInstanceId, customRequirementId])
|
|
2020
|
+
@@index([requirementId, frameworkInstanceId])
|
|
2021
|
+
@@index([customRequirementId, frameworkInstanceId])
|
|
2022
|
+
@@index([frameworkInstanceId, archivedAt])
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
// ===== risk.prisma =====
|
|
2026
|
+
model Risk {
|
|
2027
|
+
// Metadata
|
|
2028
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('rsk'::text)"))
|
|
2029
|
+
title String
|
|
2030
|
+
description String
|
|
2031
|
+
category RiskCategory
|
|
2032
|
+
department Departments?
|
|
2033
|
+
status RiskStatus @default(open)
|
|
2034
|
+
likelihood Likelihood @default(very_unlikely)
|
|
2035
|
+
impact Impact @default(insignificant)
|
|
2036
|
+
residualLikelihood Likelihood @default(very_unlikely)
|
|
2037
|
+
residualImpact Impact @default(insignificant)
|
|
2038
|
+
treatmentStrategyDescription String?
|
|
2039
|
+
treatmentStrategy RiskTreatmentType @default(accept)
|
|
2040
|
+
|
|
2041
|
+
// Dates
|
|
2042
|
+
createdAt DateTime @default(now())
|
|
2043
|
+
updatedAt DateTime @updatedAt
|
|
2044
|
+
|
|
2045
|
+
// Relationships
|
|
2046
|
+
organizationId String
|
|
2047
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
2048
|
+
assigneeId String?
|
|
2049
|
+
assignee Member? @relation(fields: [assigneeId], references: [id])
|
|
2050
|
+
tasks Task[]
|
|
2051
|
+
findings Finding[]
|
|
2052
|
+
|
|
2053
|
+
@@index([organizationId])
|
|
2054
|
+
@@index([category])
|
|
2055
|
+
@@index([status])
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
enum RiskTreatmentType {
|
|
2059
|
+
accept
|
|
2060
|
+
avoid
|
|
2061
|
+
mitigate
|
|
2062
|
+
transfer
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
enum RiskCategory {
|
|
2066
|
+
customer
|
|
2067
|
+
fraud
|
|
2068
|
+
governance
|
|
2069
|
+
operations
|
|
2070
|
+
other
|
|
2071
|
+
people
|
|
2072
|
+
regulatory
|
|
2073
|
+
reporting
|
|
2074
|
+
resilience
|
|
2075
|
+
technology
|
|
2076
|
+
vendor_management
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
enum RiskStatus {
|
|
2080
|
+
open
|
|
2081
|
+
pending
|
|
2082
|
+
closed
|
|
2083
|
+
archived
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
// ===== secret.prisma =====
|
|
2087
|
+
model Secret {
|
|
2088
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('sec'::text)"))
|
|
2089
|
+
organizationId String @map("organization_id")
|
|
2090
|
+
name String
|
|
2091
|
+
value String @db.Text // Encrypted value
|
|
2092
|
+
description String? @db.Text
|
|
2093
|
+
category String? // e.g., "api", "webhook", "database", etc.
|
|
2094
|
+
lastUsedAt DateTime? @map("last_used_at")
|
|
2095
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
2096
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
2097
|
+
|
|
2098
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
2099
|
+
|
|
2100
|
+
@@unique([organizationId, name])
|
|
2101
|
+
@@map("secrets")
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
// ===== security-penetration-test-run.prisma =====
|
|
2105
|
+
model SecurityPenetrationTestRun {
|
|
2106
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('ptr'::text)"))
|
|
2107
|
+
organizationId String @map("organization_id")
|
|
2108
|
+
providerRunId String @map("provider_run_id")
|
|
2109
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
2110
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
2111
|
+
|
|
2112
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
2113
|
+
|
|
2114
|
+
@@unique([providerRunId])
|
|
2115
|
+
@@index([organizationId])
|
|
2116
|
+
@@map("security_penetration_test_runs")
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
// ===== security-questionnaire-manual-answer.prisma =====
|
|
2120
|
+
model SecurityQuestionnaireManualAnswer {
|
|
2121
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('sqma'::text)"))
|
|
2122
|
+
question String // The question text
|
|
2123
|
+
answer String // The answer text (required for saved answers)
|
|
2124
|
+
tags String[] @default([]) // Optional tags for categorization
|
|
2125
|
+
|
|
2126
|
+
// Optional reference to original questionnaire (for tracking)
|
|
2127
|
+
sourceQuestionnaireId String?
|
|
2128
|
+
sourceQuestionnaire Questionnaire? @relation(fields: [sourceQuestionnaireId], references: [id], onDelete: SetNull)
|
|
2129
|
+
|
|
2130
|
+
// User who created/updated this answer
|
|
2131
|
+
createdBy String? // User ID
|
|
2132
|
+
updatedBy String? // User ID
|
|
2133
|
+
|
|
2134
|
+
// Dates
|
|
2135
|
+
createdAt DateTime @default(now())
|
|
2136
|
+
updatedAt DateTime @updatedAt
|
|
2137
|
+
|
|
2138
|
+
// Relationships
|
|
2139
|
+
organizationId String
|
|
2140
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
2141
|
+
|
|
2142
|
+
@@unique([organizationId, question]) // Prevent duplicate questions per organization
|
|
2143
|
+
@@index([organizationId])
|
|
2144
|
+
@@index([organizationId, question])
|
|
2145
|
+
@@index([tags])
|
|
2146
|
+
@@index([createdAt])
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
// ===== shared.prisma =====
|
|
2150
|
+
model ApiKey {
|
|
2151
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('apk'::text)"))
|
|
2152
|
+
name String
|
|
2153
|
+
key String @unique
|
|
2154
|
+
keyPrefix String?
|
|
2155
|
+
salt String?
|
|
2156
|
+
createdAt DateTime @default(now())
|
|
2157
|
+
expiresAt DateTime?
|
|
2158
|
+
lastUsedAt DateTime?
|
|
2159
|
+
isActive Boolean @default(true)
|
|
2160
|
+
scopes String[] @default([])
|
|
2161
|
+
|
|
2162
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
2163
|
+
organizationId String
|
|
2164
|
+
|
|
2165
|
+
@@index([organizationId])
|
|
2166
|
+
@@index([key])
|
|
2167
|
+
@@index([keyPrefix])
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
model AuditLog {
|
|
2171
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('aud'::text)"))
|
|
2172
|
+
timestamp DateTime @default(now())
|
|
2173
|
+
organizationId String
|
|
2174
|
+
userId String
|
|
2175
|
+
memberId String?
|
|
2176
|
+
data Json
|
|
2177
|
+
description String?
|
|
2178
|
+
entityId String?
|
|
2179
|
+
entityType AuditLogEntityType?
|
|
2180
|
+
|
|
2181
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
2182
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
2183
|
+
member Member? @relation(fields: [memberId], references: [id], onDelete: Cascade)
|
|
2184
|
+
|
|
2185
|
+
@@index([userId])
|
|
2186
|
+
@@index([organizationId])
|
|
2187
|
+
@@index([memberId])
|
|
2188
|
+
@@index([entityType])
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
enum AuditLogEntityType {
|
|
2192
|
+
organization
|
|
2193
|
+
framework
|
|
2194
|
+
requirement
|
|
2195
|
+
control
|
|
2196
|
+
policy
|
|
2197
|
+
task
|
|
2198
|
+
people
|
|
2199
|
+
risk
|
|
2200
|
+
vendor
|
|
2201
|
+
tests
|
|
2202
|
+
integration
|
|
2203
|
+
trust
|
|
2204
|
+
finding
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
enum EvidenceFormType {
|
|
2208
|
+
board_meeting @map("board-meeting")
|
|
2209
|
+
it_leadership_meeting @map("it-leadership-meeting")
|
|
2210
|
+
risk_committee_meeting @map("risk-committee-meeting")
|
|
2211
|
+
meeting
|
|
2212
|
+
access_request @map("access-request")
|
|
2213
|
+
whistleblower_report @map("whistleblower-report")
|
|
2214
|
+
penetration_test @map("penetration-test")
|
|
2215
|
+
rbac_matrix @map("rbac-matrix")
|
|
2216
|
+
infrastructure_inventory @map("infrastructure-inventory")
|
|
2217
|
+
employee_performance_evaluation @map("employee-performance-evaluation")
|
|
2218
|
+
network_diagram @map("network-diagram")
|
|
2219
|
+
tabletop_exercise @map("tabletop-exercise")
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
model GlobalVendors {
|
|
2223
|
+
website String @id @unique
|
|
2224
|
+
company_name String?
|
|
2225
|
+
legal_name String?
|
|
2226
|
+
company_description String?
|
|
2227
|
+
company_hq_address String?
|
|
2228
|
+
privacy_policy_url String?
|
|
2229
|
+
terms_of_service_url String?
|
|
2230
|
+
service_level_agreement_url String?
|
|
2231
|
+
security_page_url String?
|
|
2232
|
+
trust_page_url String?
|
|
2233
|
+
security_certifications String[]
|
|
2234
|
+
subprocessors String[]
|
|
2235
|
+
type_of_company String?
|
|
2236
|
+
|
|
2237
|
+
// Vendor Risk Assessment (shared across all organizations)
|
|
2238
|
+
riskAssessmentData Json?
|
|
2239
|
+
riskAssessmentVersion String?
|
|
2240
|
+
riskAssessmentUpdatedAt DateTime?
|
|
2241
|
+
|
|
2242
|
+
approved Boolean @default(false)
|
|
2243
|
+
createdAt DateTime @default(now())
|
|
2244
|
+
|
|
2245
|
+
@@index([website])
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
enum Departments {
|
|
2249
|
+
none
|
|
2250
|
+
admin
|
|
2251
|
+
gov
|
|
2252
|
+
hr
|
|
2253
|
+
it
|
|
2254
|
+
itsm
|
|
2255
|
+
qms
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
enum Frequency {
|
|
2259
|
+
monthly
|
|
2260
|
+
quarterly
|
|
2261
|
+
yearly
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
enum Likelihood {
|
|
2265
|
+
very_unlikely
|
|
2266
|
+
unlikely
|
|
2267
|
+
possible
|
|
2268
|
+
likely
|
|
2269
|
+
very_likely
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
enum Impact {
|
|
2273
|
+
insignificant
|
|
2274
|
+
minor
|
|
2275
|
+
moderate
|
|
2276
|
+
major
|
|
2277
|
+
severe
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
// ===== soa.prisma =====
|
|
2281
|
+
// Statement of Applicability (SOA) Auto-complete Configuration and Answers
|
|
2282
|
+
|
|
2283
|
+
model SOAFrameworkConfiguration {
|
|
2284
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('soa_cfg'::text)"))
|
|
2285
|
+
frameworkId String
|
|
2286
|
+
framework FrameworkEditorFramework @relation(fields: [frameworkId], references: [id], onDelete: Cascade)
|
|
2287
|
+
|
|
2288
|
+
// Configuration versioning - allows multiple configurations per framework
|
|
2289
|
+
version Int @default(1) // Version number for this configuration (increments when config changes)
|
|
2290
|
+
isLatest Boolean @default(true) // Whether this is the latest configuration version
|
|
2291
|
+
|
|
2292
|
+
// Column definitions for SOA structure (template used when creating new documents)
|
|
2293
|
+
columns Json // Array of { name: string, type: string } objects
|
|
2294
|
+
// Example: [{ name: "Control ID", type: "string" }, { name: "Control Name", type: "string" }, { name: "Applicable", type: "boolean" }, { name: "Justification", type: "text" }]
|
|
2295
|
+
|
|
2296
|
+
// Predefined questions for this framework
|
|
2297
|
+
// Documents reference a specific configuration version via SOADocument.configurationId
|
|
2298
|
+
// Old documents keep their old config version, new documents use new config version
|
|
2299
|
+
questions Json // Array of question objects with unique IDs
|
|
2300
|
+
// Example: [{ id: "A.5.1.1", text: "Is this control applicable?", columnMapping: "Applicable", controlId: "A.5.1.1" }, ...]
|
|
2301
|
+
// IMPORTANT: question.id must be unique and stable - this is what SOAAnswer.questionId references
|
|
2302
|
+
|
|
2303
|
+
// Dates
|
|
2304
|
+
createdAt DateTime @default(now())
|
|
2305
|
+
updatedAt DateTime @updatedAt
|
|
2306
|
+
|
|
2307
|
+
// Relationships
|
|
2308
|
+
documents SOADocument[]
|
|
2309
|
+
|
|
2310
|
+
@@unique([frameworkId, version]) // Prevent duplicate configuration versions
|
|
2311
|
+
@@index([frameworkId])
|
|
2312
|
+
@@index([frameworkId, version])
|
|
2313
|
+
@@index([frameworkId, isLatest])
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
model SOADocument {
|
|
2317
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('soa_doc'::text)"))
|
|
2318
|
+
|
|
2319
|
+
// Framework and organization context
|
|
2320
|
+
frameworkId String
|
|
2321
|
+
framework FrameworkEditorFramework @relation(fields: [frameworkId], references: [id], onDelete: Cascade)
|
|
2322
|
+
organizationId String
|
|
2323
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
2324
|
+
|
|
2325
|
+
// Configuration reference - references a specific SOAFrameworkConfiguration version
|
|
2326
|
+
// Each document version can use a different configuration version
|
|
2327
|
+
// Old documents keep their old config, new documents use new config
|
|
2328
|
+
configurationId String
|
|
2329
|
+
configuration SOAFrameworkConfiguration @relation(fields: [configurationId], references: [id], onDelete: Cascade)
|
|
2330
|
+
|
|
2331
|
+
// Document versioning
|
|
2332
|
+
version Int @default(1) // Version number for this document (increments yearly)
|
|
2333
|
+
isLatest Boolean @default(true) // Whether this is the latest version
|
|
2334
|
+
|
|
2335
|
+
// Document status
|
|
2336
|
+
status SOADocumentStatus @default(draft) // draft, in_progress, completed
|
|
2337
|
+
|
|
2338
|
+
// Document metadata
|
|
2339
|
+
totalQuestions Int @default(0) // Total number of questions in this document
|
|
2340
|
+
answeredQuestions Int @default(0) // Number of questions with answers
|
|
2341
|
+
|
|
2342
|
+
// Approval tracking
|
|
2343
|
+
preparedBy String @default("Comp AI") // Always "Comp AI"
|
|
2344
|
+
approverId String? // Member ID who will approve this document (set when submitted for approval)
|
|
2345
|
+
approver Member? @relation("SOADocumentApprover", fields: [approverId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
|
2346
|
+
approvedAt DateTime? // When document was approved
|
|
2347
|
+
|
|
2348
|
+
// Dates
|
|
2349
|
+
completedAt DateTime? // When document was completed
|
|
2350
|
+
createdAt DateTime @default(now())
|
|
2351
|
+
updatedAt DateTime @updatedAt
|
|
2352
|
+
|
|
2353
|
+
// Relationships
|
|
2354
|
+
answers SOAAnswer[]
|
|
2355
|
+
|
|
2356
|
+
@@unique([frameworkId, organizationId, version]) // Prevent duplicate versions
|
|
2357
|
+
@@index([frameworkId, organizationId])
|
|
2358
|
+
@@index([frameworkId, organizationId, version])
|
|
2359
|
+
@@index([frameworkId, organizationId, isLatest])
|
|
2360
|
+
@@index([configurationId])
|
|
2361
|
+
@@index([status])
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
model SOAAnswer {
|
|
2365
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('soa_ans'::text)"))
|
|
2366
|
+
|
|
2367
|
+
// Document context (replaces direct framework/organization link)
|
|
2368
|
+
documentId String
|
|
2369
|
+
document SOADocument @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
|
2370
|
+
|
|
2371
|
+
// Question reference - references question.id from SOADocument.configuration.questions
|
|
2372
|
+
// References the specific configuration version that the document uses
|
|
2373
|
+
// If config changes, old documents still reference their old config version
|
|
2374
|
+
questionId String // Must match a question.id from SOADocument.configuration.questions
|
|
2375
|
+
|
|
2376
|
+
// Answer data - simple text answer
|
|
2377
|
+
answer String? // Text answer (nullable if not generated yet)
|
|
2378
|
+
|
|
2379
|
+
// Answer metadata
|
|
2380
|
+
status SOAAnswerStatus @default(untouched) // untouched, generated, manual
|
|
2381
|
+
sources Json? // Sources used for generated answers (similar to questionnaire)
|
|
2382
|
+
generatedAt DateTime? // When answer was generated
|
|
2383
|
+
|
|
2384
|
+
// Answer versioning (within the document)
|
|
2385
|
+
answerVersion Int @default(1) // Version number for this specific answer
|
|
2386
|
+
isLatestAnswer Boolean @default(true) // Whether this is the latest version of this answer
|
|
2387
|
+
|
|
2388
|
+
// User tracking
|
|
2389
|
+
createdBy String? // User ID who created this answer
|
|
2390
|
+
updatedBy String? // User ID who last updated this answer
|
|
2391
|
+
|
|
2392
|
+
// Dates
|
|
2393
|
+
createdAt DateTime @default(now())
|
|
2394
|
+
updatedAt DateTime @updatedAt
|
|
2395
|
+
|
|
2396
|
+
@@unique([documentId, questionId, answerVersion]) // Prevent duplicate answer versions
|
|
2397
|
+
@@index([documentId])
|
|
2398
|
+
@@index([documentId, questionId])
|
|
2399
|
+
@@index([documentId, questionId, isLatestAnswer])
|
|
2400
|
+
@@index([status])
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
enum SOADocumentStatus {
|
|
2404
|
+
draft // Document is being created/edited
|
|
2405
|
+
in_progress // Document is being generated
|
|
2406
|
+
needs_review // Document is submitted for approval
|
|
2407
|
+
completed // Document is complete and approved
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
enum SOAAnswerStatus {
|
|
2411
|
+
untouched // No answer yet (not generated)
|
|
2412
|
+
generated // AI generated answer
|
|
2413
|
+
manual // Manually written/edited by user
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
// ===== task-item.prisma =====
|
|
2417
|
+
model TaskItem {
|
|
2418
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('tski'::text)"))
|
|
2419
|
+
title String
|
|
2420
|
+
description String?
|
|
2421
|
+
status TaskItemStatus @default(todo)
|
|
2422
|
+
priority TaskItemPriority @default(medium)
|
|
2423
|
+
|
|
2424
|
+
// Polymorphic relation (like Comment and Attachment)
|
|
2425
|
+
entityId String
|
|
2426
|
+
entityType TaskItemEntityType
|
|
2427
|
+
|
|
2428
|
+
// Assignment (nullable)
|
|
2429
|
+
assigneeId String?
|
|
2430
|
+
assignee Member? @relation("TaskItemAssignee", fields: [assigneeId], references: [id], onDelete: SetNull)
|
|
2431
|
+
|
|
2432
|
+
// Creator & Updater
|
|
2433
|
+
createdById String
|
|
2434
|
+
createdBy Member @relation("TaskItemCreator", fields: [createdById], references: [id])
|
|
2435
|
+
updatedById String?
|
|
2436
|
+
updatedBy Member? @relation("TaskItemUpdater", fields: [updatedById], references: [id])
|
|
2437
|
+
|
|
2438
|
+
// Relationships
|
|
2439
|
+
organizationId String
|
|
2440
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
2441
|
+
|
|
2442
|
+
// Dates
|
|
2443
|
+
createdAt DateTime @default(now())
|
|
2444
|
+
updatedAt DateTime @updatedAt
|
|
2445
|
+
|
|
2446
|
+
@@index([entityId, entityType])
|
|
2447
|
+
@@index([organizationId])
|
|
2448
|
+
@@index([assigneeId])
|
|
2449
|
+
@@index([status])
|
|
2450
|
+
@@index([priority])
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
enum TaskItemStatus {
|
|
2454
|
+
todo
|
|
2455
|
+
in_progress
|
|
2456
|
+
in_review
|
|
2457
|
+
done
|
|
2458
|
+
canceled
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
enum TaskItemPriority {
|
|
2462
|
+
urgent
|
|
2463
|
+
high
|
|
2464
|
+
medium
|
|
2465
|
+
low
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
enum TaskItemEntityType {
|
|
2469
|
+
vendor
|
|
2470
|
+
risk
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
// ===== task.prisma =====
|
|
2474
|
+
model Task {
|
|
2475
|
+
// Metadata
|
|
2476
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('tsk'::text)"))
|
|
2477
|
+
title String
|
|
2478
|
+
description String
|
|
2479
|
+
status TaskStatus @default(todo)
|
|
2480
|
+
automationStatus TaskAutomationStatus @default(AUTOMATED)
|
|
2481
|
+
frequency TaskFrequency?
|
|
2482
|
+
integrationScheduleFrequency TaskFrequency @default(daily)
|
|
2483
|
+
integrationLastRunAt DateTime?
|
|
2484
|
+
department Departments? @default(none)
|
|
2485
|
+
order Int @default(0)
|
|
2486
|
+
|
|
2487
|
+
// Dates
|
|
2488
|
+
createdAt DateTime @default(now())
|
|
2489
|
+
updatedAt DateTime @updatedAt
|
|
2490
|
+
lastCompletedAt DateTime?
|
|
2491
|
+
reviewDate DateTime?
|
|
2492
|
+
|
|
2493
|
+
// Relationships
|
|
2494
|
+
assigneeId String?
|
|
2495
|
+
assignee Member? @relation(fields: [assigneeId], references: [id])
|
|
2496
|
+
organizationId String
|
|
2497
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
2498
|
+
taskTemplateId String?
|
|
2499
|
+
taskTemplate FrameworkEditorTaskTemplate? @relation(fields: [taskTemplateId], references: [id])
|
|
2500
|
+
controls Control[]
|
|
2501
|
+
vendors Vendor[]
|
|
2502
|
+
risks Risk[]
|
|
2503
|
+
evidenceAutomations EvidenceAutomation[]
|
|
2504
|
+
browserAutomations BrowserAutomation[]
|
|
2505
|
+
|
|
2506
|
+
evidenceAutomationRuns EvidenceAutomationRun[]
|
|
2507
|
+
integrationCheckRuns IntegrationCheckRun[]
|
|
2508
|
+
findings Finding[]
|
|
2509
|
+
|
|
2510
|
+
// Evidence approval
|
|
2511
|
+
approverId String?
|
|
2512
|
+
approver Member? @relation("TaskApprover", fields: [approverId], references: [id])
|
|
2513
|
+
approvedAt DateTime?
|
|
2514
|
+
previousStatus TaskStatus?
|
|
2515
|
+
|
|
2516
|
+
// Sync-driven archive — see Control.archivedAt.
|
|
2517
|
+
archivedAt DateTime?
|
|
2518
|
+
|
|
2519
|
+
@@index([organizationId, archivedAt])
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
enum TaskStatus {
|
|
2523
|
+
todo
|
|
2524
|
+
in_progress
|
|
2525
|
+
in_review
|
|
2526
|
+
done
|
|
2527
|
+
not_relevant
|
|
2528
|
+
failed
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
enum TaskFrequency {
|
|
2532
|
+
daily
|
|
2533
|
+
weekly
|
|
2534
|
+
monthly
|
|
2535
|
+
quarterly
|
|
2536
|
+
yearly
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
enum TaskAutomationStatus {
|
|
2540
|
+
AUTOMATED
|
|
2541
|
+
MANUAL
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
// ===== timeline.prisma =====
|
|
2545
|
+
enum TimelineStatus {
|
|
2546
|
+
DRAFT
|
|
2547
|
+
ACTIVE
|
|
2548
|
+
PAUSED
|
|
2549
|
+
COMPLETED
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
enum TimelinePhaseStatus {
|
|
2553
|
+
PENDING
|
|
2554
|
+
IN_PROGRESS
|
|
2555
|
+
COMPLETED
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
enum PhaseCompletionType {
|
|
2559
|
+
AUTO_TASKS
|
|
2560
|
+
AUTO_POLICIES
|
|
2561
|
+
AUTO_PEOPLE
|
|
2562
|
+
AUTO_FINDINGS
|
|
2563
|
+
AUTO_UPLOAD
|
|
2564
|
+
MANUAL
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
model TimelineTemplate {
|
|
2568
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('tml'::text)"))
|
|
2569
|
+
frameworkId String
|
|
2570
|
+
name String
|
|
2571
|
+
trackKey String @default("primary")
|
|
2572
|
+
cycleNumber Int
|
|
2573
|
+
templateKey String?
|
|
2574
|
+
nextTemplateKey String?
|
|
2575
|
+
createdAt DateTime @default(now())
|
|
2576
|
+
updatedAt DateTime @updatedAt
|
|
2577
|
+
|
|
2578
|
+
framework FrameworkEditorFramework @relation(fields: [frameworkId], references: [id])
|
|
2579
|
+
phases TimelinePhaseTemplate[]
|
|
2580
|
+
instances TimelineInstance[]
|
|
2581
|
+
|
|
2582
|
+
@@unique([frameworkId, trackKey, cycleNumber])
|
|
2583
|
+
// Note: Postgres treats NULLs as distinct in unique indexes, so this
|
|
2584
|
+
// constraint only enforces uniqueness among rows where templateKey is set.
|
|
2585
|
+
// That's intentional — frameworks without explicit keys (legacy defaults)
|
|
2586
|
+
// are uniquely identified by the (frameworkId, trackKey, cycleNumber)
|
|
2587
|
+
// constraint above, so multiple NULL templateKey rows are permitted.
|
|
2588
|
+
@@unique([frameworkId, templateKey])
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
model TimelinePhaseTemplate {
|
|
2592
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('tpt'::text)"))
|
|
2593
|
+
templateId String
|
|
2594
|
+
name String
|
|
2595
|
+
description String?
|
|
2596
|
+
groupLabel String?
|
|
2597
|
+
orderIndex Int
|
|
2598
|
+
defaultDurationWeeks Int
|
|
2599
|
+
completionType PhaseCompletionType @default(MANUAL)
|
|
2600
|
+
locksTimelineOnComplete Boolean @default(false)
|
|
2601
|
+
createdAt DateTime @default(now())
|
|
2602
|
+
updatedAt DateTime @updatedAt
|
|
2603
|
+
|
|
2604
|
+
template TimelineTemplate @relation(fields: [templateId], references: [id], onDelete: Cascade)
|
|
2605
|
+
phases TimelinePhase[]
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
model TimelineInstance {
|
|
2609
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('tli'::text)"))
|
|
2610
|
+
organizationId String
|
|
2611
|
+
frameworkInstanceId String
|
|
2612
|
+
templateId String
|
|
2613
|
+
trackKey String @default("primary")
|
|
2614
|
+
cycleNumber Int
|
|
2615
|
+
status TimelineStatus @default(DRAFT)
|
|
2616
|
+
startDate DateTime?
|
|
2617
|
+
pausedAt DateTime?
|
|
2618
|
+
lockedAt DateTime?
|
|
2619
|
+
lockedById String?
|
|
2620
|
+
unlockedAt DateTime?
|
|
2621
|
+
unlockedById String?
|
|
2622
|
+
unlockReason String?
|
|
2623
|
+
completedAt DateTime?
|
|
2624
|
+
createdAt DateTime @default(now())
|
|
2625
|
+
updatedAt DateTime @updatedAt
|
|
2626
|
+
|
|
2627
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
2628
|
+
frameworkInstance FrameworkInstance @relation(fields: [frameworkInstanceId], references: [id], onDelete: Cascade)
|
|
2629
|
+
template TimelineTemplate @relation(fields: [templateId], references: [id])
|
|
2630
|
+
lockedBy User? @relation("TimelineInstanceLockedBy", fields: [lockedById], references: [id])
|
|
2631
|
+
unlockedBy User? @relation("TimelineInstanceUnlockedBy", fields: [unlockedById], references: [id])
|
|
2632
|
+
phases TimelinePhase[]
|
|
2633
|
+
|
|
2634
|
+
@@unique([frameworkInstanceId, trackKey, cycleNumber])
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
model TimelinePhase {
|
|
2638
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('tlp'::text)"))
|
|
2639
|
+
instanceId String
|
|
2640
|
+
phaseTemplateId String?
|
|
2641
|
+
name String
|
|
2642
|
+
description String?
|
|
2643
|
+
groupLabel String?
|
|
2644
|
+
orderIndex Int
|
|
2645
|
+
status TimelinePhaseStatus @default(PENDING)
|
|
2646
|
+
startDate DateTime?
|
|
2647
|
+
endDate DateTime?
|
|
2648
|
+
durationWeeks Int
|
|
2649
|
+
completionType PhaseCompletionType @default(MANUAL)
|
|
2650
|
+
locksTimelineOnComplete Boolean @default(false)
|
|
2651
|
+
regressedAt DateTime?
|
|
2652
|
+
datesPinned Boolean @default(false)
|
|
2653
|
+
completedAt DateTime?
|
|
2654
|
+
completedById String?
|
|
2655
|
+
readyForReview Boolean @default(false)
|
|
2656
|
+
readyForReviewAt DateTime?
|
|
2657
|
+
documentUrl String?
|
|
2658
|
+
documentName String?
|
|
2659
|
+
createdAt DateTime @default(now())
|
|
2660
|
+
updatedAt DateTime @updatedAt
|
|
2661
|
+
|
|
2662
|
+
instance TimelineInstance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
|
2663
|
+
phaseTemplate TimelinePhaseTemplate? @relation(fields: [phaseTemplateId], references: [id])
|
|
2664
|
+
completedBy User? @relation(fields: [completedById], references: [id])
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
// ===== trust.prisma =====
|
|
2668
|
+
model Trust {
|
|
2669
|
+
organizationId String
|
|
2670
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
2671
|
+
friendlyUrl String? @unique
|
|
2672
|
+
domain String?
|
|
2673
|
+
domainVerified Boolean @default(false)
|
|
2674
|
+
isVercelDomain Boolean @default(false)
|
|
2675
|
+
vercelVerification String?
|
|
2676
|
+
status TrustStatus @default(published)
|
|
2677
|
+
contactEmail String?
|
|
2678
|
+
|
|
2679
|
+
/// Domains that bypass NDA signing when requesting trust portal access
|
|
2680
|
+
allowedDomains String[] @default([])
|
|
2681
|
+
|
|
2682
|
+
email String?
|
|
2683
|
+
privacyPolicy String?
|
|
2684
|
+
soc2 Boolean @default(false)
|
|
2685
|
+
soc2type1 Boolean @default(false)
|
|
2686
|
+
soc2type2 Boolean @default(false)
|
|
2687
|
+
iso27001 Boolean @default(false)
|
|
2688
|
+
iso42001 Boolean @default(false)
|
|
2689
|
+
nen7510 Boolean @default(false)
|
|
2690
|
+
gdpr Boolean @default(false)
|
|
2691
|
+
hipaa Boolean @default(false)
|
|
2692
|
+
pci_dss Boolean @default(false)
|
|
2693
|
+
iso9001 Boolean @default(false)
|
|
2694
|
+
|
|
2695
|
+
soc2_status FrameworkStatus @default(started)
|
|
2696
|
+
soc2type1_status FrameworkStatus @default(started)
|
|
2697
|
+
soc2type2_status FrameworkStatus @default(started)
|
|
2698
|
+
iso27001_status FrameworkStatus @default(started)
|
|
2699
|
+
iso42001_status FrameworkStatus @default(started)
|
|
2700
|
+
nen7510_status FrameworkStatus @default(started)
|
|
2701
|
+
gdpr_status FrameworkStatus @default(started)
|
|
2702
|
+
hipaa_status FrameworkStatus @default(started)
|
|
2703
|
+
pci_dss_status FrameworkStatus @default(started)
|
|
2704
|
+
iso9001_status FrameworkStatus @default(started)
|
|
2705
|
+
|
|
2706
|
+
// Overview section for public trust portal
|
|
2707
|
+
overviewTitle String?
|
|
2708
|
+
overviewContent String? // Markdown content with links
|
|
2709
|
+
showOverview Boolean @default(false)
|
|
2710
|
+
|
|
2711
|
+
// Favicon for trust portal (stored in S3)
|
|
2712
|
+
favicon String?
|
|
2713
|
+
|
|
2714
|
+
@@id([status, organizationId])
|
|
2715
|
+
@@unique([organizationId])
|
|
2716
|
+
@@index([organizationId])
|
|
2717
|
+
@@index([friendlyUrl])
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
enum TrustStatus {
|
|
2721
|
+
draft
|
|
2722
|
+
published
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
enum FrameworkStatus {
|
|
2726
|
+
started
|
|
2727
|
+
in_progress
|
|
2728
|
+
compliant
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
enum TrustFramework {
|
|
2732
|
+
iso_27001
|
|
2733
|
+
iso_42001
|
|
2734
|
+
gdpr
|
|
2735
|
+
hipaa
|
|
2736
|
+
soc2_type1
|
|
2737
|
+
soc2_type2
|
|
2738
|
+
pci_dss
|
|
2739
|
+
nen_7510
|
|
2740
|
+
iso_9001
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
model TrustResource {
|
|
2744
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('tcr'::text)"))
|
|
2745
|
+
organizationId String
|
|
2746
|
+
organization Organization @relation("OrganizationTrustResources", fields: [organizationId], references: [id], onDelete: Cascade)
|
|
2747
|
+
framework TrustFramework
|
|
2748
|
+
s3Key String
|
|
2749
|
+
fileName String
|
|
2750
|
+
fileSize Int
|
|
2751
|
+
createdAt DateTime @default(now())
|
|
2752
|
+
updatedAt DateTime @updatedAt
|
|
2753
|
+
|
|
2754
|
+
@@unique([organizationId, framework])
|
|
2755
|
+
@@index([organizationId])
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
model TrustAccessRequest {
|
|
2759
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('tar'::text)"))
|
|
2760
|
+
organizationId String
|
|
2761
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
2762
|
+
|
|
2763
|
+
name String
|
|
2764
|
+
email String
|
|
2765
|
+
company String?
|
|
2766
|
+
jobTitle String?
|
|
2767
|
+
purpose String?
|
|
2768
|
+
requestedDurationDays Int?
|
|
2769
|
+
|
|
2770
|
+
status TrustAccessRequestStatus @default(under_review)
|
|
2771
|
+
reviewerMemberId String?
|
|
2772
|
+
reviewer Member? @relation("TrustAccessRequestReviewer", fields: [reviewerMemberId], references: [id], onDelete: SetNull)
|
|
2773
|
+
reviewedAt DateTime?
|
|
2774
|
+
decisionReason String?
|
|
2775
|
+
|
|
2776
|
+
ipAddress String?
|
|
2777
|
+
userAgent String?
|
|
2778
|
+
|
|
2779
|
+
createdAt DateTime @default(now())
|
|
2780
|
+
updatedAt DateTime @updatedAt
|
|
2781
|
+
|
|
2782
|
+
grant TrustAccessGrant? @relation("RequestGrant")
|
|
2783
|
+
ndaAgreements TrustNDAAgreement[] @relation("RequestNDA")
|
|
2784
|
+
|
|
2785
|
+
@@index([organizationId])
|
|
2786
|
+
@@index([email])
|
|
2787
|
+
@@index([status])
|
|
2788
|
+
@@index([organizationId, status])
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
model TrustAccessGrant {
|
|
2792
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('tag'::text)"))
|
|
2793
|
+
|
|
2794
|
+
accessRequestId String @unique
|
|
2795
|
+
accessRequest TrustAccessRequest @relation("RequestGrant", fields: [accessRequestId], references: [id], onDelete: Cascade)
|
|
2796
|
+
|
|
2797
|
+
subjectEmail String
|
|
2798
|
+
|
|
2799
|
+
status TrustAccessGrantStatus @default(active)
|
|
2800
|
+
expiresAt DateTime
|
|
2801
|
+
|
|
2802
|
+
accessToken String? @unique
|
|
2803
|
+
accessTokenExpiresAt DateTime?
|
|
2804
|
+
|
|
2805
|
+
issuedByMemberId String?
|
|
2806
|
+
issuedBy Member? @relation("IssuedGrants", fields: [issuedByMemberId], references: [id], onDelete: SetNull)
|
|
2807
|
+
|
|
2808
|
+
revokedAt DateTime?
|
|
2809
|
+
revokedByMemberId String?
|
|
2810
|
+
revokedBy Member? @relation("RevokedGrants", fields: [revokedByMemberId], references: [id], onDelete: SetNull)
|
|
2811
|
+
revokeReason String?
|
|
2812
|
+
|
|
2813
|
+
createdAt DateTime @default(now())
|
|
2814
|
+
updatedAt DateTime @updatedAt
|
|
2815
|
+
|
|
2816
|
+
ndaAgreement TrustNDAAgreement? @relation("GrantNDA")
|
|
2817
|
+
|
|
2818
|
+
@@index([accessRequestId])
|
|
2819
|
+
@@index([subjectEmail])
|
|
2820
|
+
@@index([status])
|
|
2821
|
+
@@index([expiresAt])
|
|
2822
|
+
@@index([status, expiresAt])
|
|
2823
|
+
@@index([accessToken])
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
enum TrustAccessRequestStatus {
|
|
2827
|
+
under_review
|
|
2828
|
+
approved
|
|
2829
|
+
denied
|
|
2830
|
+
canceled
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2833
|
+
enum TrustAccessGrantStatus {
|
|
2834
|
+
active
|
|
2835
|
+
expired
|
|
2836
|
+
revoked
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
model TrustNDAAgreement {
|
|
2840
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('tna'::text)"))
|
|
2841
|
+
|
|
2842
|
+
organizationId String
|
|
2843
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
2844
|
+
|
|
2845
|
+
accessRequestId String
|
|
2846
|
+
accessRequest TrustAccessRequest @relation("RequestNDA", fields: [accessRequestId], references: [id], onDelete: Cascade)
|
|
2847
|
+
|
|
2848
|
+
grantId String? @unique
|
|
2849
|
+
grant TrustAccessGrant? @relation("GrantNDA", fields: [grantId], references: [id], onDelete: SetNull)
|
|
2850
|
+
|
|
2851
|
+
signerName String?
|
|
2852
|
+
signerEmail String?
|
|
2853
|
+
|
|
2854
|
+
status TrustNDAStatus @default(pending)
|
|
2855
|
+
|
|
2856
|
+
signToken String @unique
|
|
2857
|
+
signTokenExpiresAt DateTime
|
|
2858
|
+
|
|
2859
|
+
pdfTemplateKey String?
|
|
2860
|
+
pdfSignedKey String?
|
|
2861
|
+
|
|
2862
|
+
signedAt DateTime?
|
|
2863
|
+
|
|
2864
|
+
ipAddress String?
|
|
2865
|
+
userAgent String?
|
|
2866
|
+
|
|
2867
|
+
createdAt DateTime @default(now())
|
|
2868
|
+
updatedAt DateTime @updatedAt
|
|
2869
|
+
|
|
2870
|
+
@@index([organizationId])
|
|
2871
|
+
@@index([accessRequestId])
|
|
2872
|
+
@@index([signToken])
|
|
2873
|
+
@@index([status])
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
enum TrustNDAStatus {
|
|
2877
|
+
pending
|
|
2878
|
+
signed
|
|
2879
|
+
void
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
model TrustDocument {
|
|
2883
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('tdoc'::text)"))
|
|
2884
|
+
|
|
2885
|
+
organizationId String
|
|
2886
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
2887
|
+
|
|
2888
|
+
name String
|
|
2889
|
+
description String?
|
|
2890
|
+
s3Key String
|
|
2891
|
+
|
|
2892
|
+
isActive Boolean @default(true)
|
|
2893
|
+
|
|
2894
|
+
createdAt DateTime @default(now())
|
|
2895
|
+
updatedAt DateTime @updatedAt
|
|
2896
|
+
|
|
2897
|
+
@@index([organizationId])
|
|
2898
|
+
@@index([organizationId, isActive])
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2901
|
+
model TrustCustomLink {
|
|
2902
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('tcl'::text)"))
|
|
2903
|
+
|
|
2904
|
+
organizationId String
|
|
2905
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
2906
|
+
|
|
2907
|
+
title String
|
|
2908
|
+
description String?
|
|
2909
|
+
url String
|
|
2910
|
+
order Int @default(0)
|
|
2911
|
+
isActive Boolean @default(true)
|
|
2912
|
+
|
|
2913
|
+
createdAt DateTime @default(now())
|
|
2914
|
+
updatedAt DateTime @updatedAt
|
|
2915
|
+
|
|
2916
|
+
@@index([organizationId])
|
|
2917
|
+
@@index([organizationId, isActive, order])
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
// ===== vendor.prisma =====
|
|
2921
|
+
model Vendor {
|
|
2922
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('vnd'::text)"))
|
|
2923
|
+
name String
|
|
2924
|
+
description String
|
|
2925
|
+
category VendorCategory @default(other)
|
|
2926
|
+
status VendorStatus @default(not_assessed)
|
|
2927
|
+
inherentProbability Likelihood @default(very_unlikely)
|
|
2928
|
+
inherentImpact Impact @default(insignificant)
|
|
2929
|
+
residualProbability Likelihood @default(very_unlikely)
|
|
2930
|
+
residualImpact Impact @default(insignificant)
|
|
2931
|
+
website String?
|
|
2932
|
+
isSubProcessor Boolean @default(false)
|
|
2933
|
+
|
|
2934
|
+
// Trust Portal display settings
|
|
2935
|
+
logoUrl String?
|
|
2936
|
+
showOnTrustPortal Boolean @default(false)
|
|
2937
|
+
trustPortalOrder Int?
|
|
2938
|
+
complianceBadges Json? // Array of { type: 'soc2' | 'iso27001' | etc, verified: boolean }
|
|
2939
|
+
|
|
2940
|
+
createdAt DateTime @default(now())
|
|
2941
|
+
updatedAt DateTime @updatedAt
|
|
2942
|
+
|
|
2943
|
+
organizationId String
|
|
2944
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
2945
|
+
assigneeId String?
|
|
2946
|
+
assignee Member? @relation(fields: [assigneeId], references: [id], onDelete: Cascade)
|
|
2947
|
+
contacts VendorContact[]
|
|
2948
|
+
tasks Task[]
|
|
2949
|
+
findings Finding[]
|
|
2950
|
+
|
|
2951
|
+
@@index([organizationId])
|
|
2952
|
+
@@index([assigneeId])
|
|
2953
|
+
@@index([category])
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2956
|
+
model VendorContact {
|
|
2957
|
+
id String @id @default(dbgenerated("generate_prefixed_cuid('vct'::text)"))
|
|
2958
|
+
vendorId String
|
|
2959
|
+
name String
|
|
2960
|
+
email String
|
|
2961
|
+
phone String
|
|
2962
|
+
createdAt DateTime @default(now())
|
|
2963
|
+
updatedAt DateTime @updatedAt
|
|
2964
|
+
Vendor Vendor @relation(fields: [vendorId], references: [id], onDelete: Cascade)
|
|
2965
|
+
|
|
2966
|
+
@@index([vendorId])
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2969
|
+
enum VendorCategory {
|
|
2970
|
+
cloud
|
|
2971
|
+
infrastructure
|
|
2972
|
+
software_as_a_service
|
|
2973
|
+
finance
|
|
2974
|
+
marketing
|
|
2975
|
+
sales
|
|
2976
|
+
hr
|
|
2977
|
+
other
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2980
|
+
enum VendorStatus {
|
|
2981
|
+
not_assessed
|
|
2982
|
+
in_progress
|
|
2983
|
+
assessed
|
|
2984
|
+
}
|