@trycompai/db 2.0.1 → 2.0.2

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