@trycompai/db 2.0.1 → 2.0.3

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