@marinoscar/kvox-eval 0.0.0-bootstrap → 0.1.0-next.0

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 (245) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/LICENSE +21 -0
  3. package/README.md +166 -0
  4. package/dist/ask/index.d.ts +6 -0
  5. package/dist/ask/index.js +28 -0
  6. package/dist/ask/question-schema.d.ts +121 -0
  7. package/dist/ask/question-schema.js +167 -0
  8. package/dist/ask/report.d.ts +88 -0
  9. package/dist/ask/report.js +170 -0
  10. package/dist/ask/scorer.d.ts +104 -0
  11. package/dist/ask/scorer.js +164 -0
  12. package/dist/ask-run/ask-command.d.ts +62 -0
  13. package/dist/ask-run/ask-command.js +254 -0
  14. package/dist/ask-run/host/app.d.ts +65 -0
  15. package/dist/ask-run/host/app.js +146 -0
  16. package/dist/ask-run/host/db.d.ts +38 -0
  17. package/dist/ask-run/host/db.js +126 -0
  18. package/dist/ask-run/host/ports.d.ts +35 -0
  19. package/dist/ask-run/host/ports.js +113 -0
  20. package/dist/ask-run/index.d.ts +13 -0
  21. package/dist/ask-run/index.js +35 -0
  22. package/dist/ask-run/replay.d.ts +128 -0
  23. package/dist/ask-run/replay.js +212 -0
  24. package/dist/ask-run/run-ask-eval.d.ts +125 -0
  25. package/dist/ask-run/run-ask-eval.js +193 -0
  26. package/dist/ask-run/seed-golden-graph.d.ts +64 -0
  27. package/dist/ask-run/seed-golden-graph.js +495 -0
  28. package/dist/cli/index.d.ts +3 -0
  29. package/dist/cli/index.js +12 -0
  30. package/dist/cli/kg-command.d.ts +66 -0
  31. package/dist/cli/kg-command.js +238 -0
  32. package/dist/cli/main.d.ts +18 -0
  33. package/dist/cli/main.js +69 -0
  34. package/dist/fixtures/domains.d.ts +16 -0
  35. package/dist/fixtures/domains.js +33 -0
  36. package/dist/fixtures/fixture-schema.d.ts +323 -0
  37. package/dist/fixtures/fixture-schema.js +199 -0
  38. package/dist/fixtures/index.d.ts +16 -0
  39. package/dist/fixtures/index.js +40 -0
  40. package/dist/fixtures/load.d.ts +73 -0
  41. package/dist/fixtures/load.js +137 -0
  42. package/dist/fixtures/questions.d.ts +15 -0
  43. package/dist/fixtures/questions.js +26 -0
  44. package/dist/index.d.ts +20 -0
  45. package/dist/index.js +36 -0
  46. package/dist/kg/gold-runner.d.ts +13 -0
  47. package/dist/kg/gold-runner.js +57 -0
  48. package/dist/kg/index.d.ts +17 -0
  49. package/dist/kg/index.js +65 -0
  50. package/dist/kg/internals.d.ts +7 -0
  51. package/dist/kg/internals.js +17 -0
  52. package/dist/kg/prediction-schema.d.ts +245 -0
  53. package/dist/kg/prediction-schema.js +102 -0
  54. package/dist/kg/predictions.d.ts +11 -0
  55. package/dist/kg/predictions.js +49 -0
  56. package/dist/kg/report.d.ts +84 -0
  57. package/dist/kg/report.js +125 -0
  58. package/dist/kg/run-kg-eval.d.ts +77 -0
  59. package/dist/kg/run-kg-eval.js +137 -0
  60. package/dist/kg/runner.d.ts +72 -0
  61. package/dist/kg/runner.js +51 -0
  62. package/dist/kg/runners/extract-resolve-runner.d.ts +40 -0
  63. package/dist/kg/runners/extract-resolve-runner.js +199 -0
  64. package/dist/kg/runners/extract-runner.d.ts +65 -0
  65. package/dist/kg/runners/extract-runner.js +243 -0
  66. package/dist/kg/scorer.d.ts +226 -0
  67. package/dist/kg/scorer.js +463 -0
  68. package/dist/kg/targets.d.ts +17 -0
  69. package/dist/kg/targets.js +16 -0
  70. package/dist/kg/text.d.ts +45 -0
  71. package/dist/kg/text.js +100 -0
  72. package/dist/live/adapters.d.ts +16 -0
  73. package/dist/live/adapters.js +58 -0
  74. package/dist/live/env.d.ts +39 -0
  75. package/dist/live/env.js +56 -0
  76. package/dist/live/index.d.ts +6 -0
  77. package/dist/live/index.js +16 -0
  78. package/dist/live/live-ai.d.ts +104 -0
  79. package/dist/live/live-ai.js +157 -0
  80. package/dist/parity/constants.d.ts +79 -0
  81. package/dist/parity/constants.js +104 -0
  82. package/dist/parity/index.d.ts +20 -0
  83. package/dist/parity/index.js +38 -0
  84. package/dist/parity/internals.d.ts +8 -0
  85. package/dist/parity/internals.js +16 -0
  86. package/dist/parity/scenario.d.ts +45 -0
  87. package/dist/parity/scenario.js +245 -0
  88. package/dist/parity/script.d.ts +44 -0
  89. package/dist/parity/script.js +136 -0
  90. package/dist/parity/snapshot.d.ts +43 -0
  91. package/dist/parity/snapshot.js +71 -0
  92. package/dist/real-data/index.d.ts +2 -0
  93. package/dist/real-data/index.js +12 -0
  94. package/dist/real-data/real-data.d.ts +76 -0
  95. package/dist/real-data/real-data.js +156 -0
  96. package/fixtures/kg-golden/README.md +136 -0
  97. package/fixtures/kg-golden/ask-questions.json +458 -0
  98. package/fixtures/kg-golden/meetings/m01-northwind-pilot-kickoff.json +792 -0
  99. package/fixtures/kg-golden/meetings/m02-halden-migration-planning.json +814 -0
  100. package/fixtures/kg-golden/meetings/m03-larkspur-weekly-sync.json +601 -0
  101. package/fixtures/kg-golden/meetings/m04-corvid-intake-discovery.json +690 -0
  102. package/fixtures/kg-golden/meetings/m05-hosting-vendor-selection.json +631 -0
  103. package/fixtures/kg-golden/meetings/m06-northwind-floor-data-review.json +649 -0
  104. package/fixtures/kg-golden/meetings/m07-gullwing-intro-notes.json +428 -0
  105. package/fixtures/kg-golden/meetings/m08-halden-migration-checkpoint.json +672 -0
  106. package/fixtures/kg-golden/meetings/m09-northwind-platform-org-update.json +711 -0
  107. package/fixtures/kg-golden/meetings/m10-corvid-prototype-review.json +623 -0
  108. package/fixtures/kg-golden/meetings/m11-solvane-grid-dashboard-scoping.json +689 -0
  109. package/fixtures/kg-golden/meetings/m12-northwind-pilot-replan.json +681 -0
  110. package/fixtures/kg-golden/meetings/m13-halden-cutover-go-no-go.json +721 -0
  111. package/fixtures/kg-golden/meetings/m14-gullwing-discovery-readout.json +719 -0
  112. package/fixtures/kg-golden/meetings/m15-halden-handover-ravi-leaving.json +716 -0
  113. package/fixtures/kg-golden/meetings/m16-varden-claims-portal-intro.json +704 -0
  114. package/fixtures/kg-golden/meetings/m17-corvid-usability-debrief.json +572 -0
  115. package/fixtures/kg-golden/meetings/m18-hosting-decision-revisited.json +655 -0
  116. package/fixtures/kg-golden/meetings/m19-solvane-integration-kickoff.json +726 -0
  117. package/fixtures/kg-golden/meetings/m20-northwind-go-live-retro.json +673 -0
  118. package/fixtures/kg-golden/meetings/m21-quarterly-account-review.json +449 -0
  119. package/fixtures/kg-golden/meetings/m22-halden-intro-call-2020.json +558 -0
  120. package/fixtures/kg-golden/meetings/m23-gullwing-model-review.json +702 -0
  121. package/fixtures/kg-golden/meetings/m24-varden-discovery-midpoint.json +643 -0
  122. package/fixtures/kg-golden/meetings/m25-corvid-rollout-planning.json +642 -0
  123. package/fixtures/kg-golden/meetings/m26-larkspur-engineering-prep.json +695 -0
  124. package/fixtures/kg-golden/meetings/m27-solvane-phase-one-demo.json +649 -0
  125. package/fixtures/kg-golden/meetings/m28-northwind-building-5-scoping.json +715 -0
  126. package/fixtures/kg-golden/meetings/m29-halden-southern-cutover-review.json +752 -0
  127. package/fixtures/kg-golden/meetings/m30-varden-portal-build-kickoff.json +697 -0
  128. package/fixtures/kg-golden/meetings/m31-gullwing-rollout-renewal.json +681 -0
  129. package/fixtures/kg-golden/meetings/m32-brambleway-weekly-sync.json +472 -0
  130. package/fixtures/kg-golden/meetings/m33-nina-oliver-one-on-one.json +342 -0
  131. package/fixtures/kg-golden/meetings/m34-coffee-with-dana.json +249 -0
  132. package/fixtures/kg-golden/meetings/m35-brambleway-june-sync.json +553 -0
  133. package/fixtures/kg-golden/meetings/m36-copenhagen-conference-planning.json +296 -0
  134. package/host/generated/client.d.ts +1 -0
  135. package/host/generated/client.js +5 -0
  136. package/host/generated/default.d.ts +1 -0
  137. package/host/generated/default.js +5 -0
  138. package/host/generated/edge.d.ts +1 -0
  139. package/host/generated/edge.js +1658 -0
  140. package/host/generated/index-browser.js +1684 -0
  141. package/host/generated/index.d.ts +192335 -0
  142. package/host/generated/index.js +1658 -0
  143. package/host/generated/package.json +139 -0
  144. package/host/generated/query_compiler_fast_bg.js +2 -0
  145. package/host/generated/query_compiler_fast_bg.wasm +0 -0
  146. package/host/generated/query_compiler_fast_bg.wasm-base64.js +2 -0
  147. package/host/generated/runtime/client.d.ts +3433 -0
  148. package/host/generated/runtime/client.js +110 -0
  149. package/host/generated/runtime/index-browser.d.ts +90 -0
  150. package/host/generated/runtime/index-browser.js +6 -0
  151. package/host/generated/runtime/wasm-compiler-edge.js +100 -0
  152. package/host/generated/schema.prisma +6312 -0
  153. package/host/generated/wasm-edge-light-loader.mjs +5 -0
  154. package/host/generated/wasm-worker-loader.mjs +5 -0
  155. package/host/prisma/fragments/kvox.ask.prisma +126 -0
  156. package/host/prisma/fragments/kvox.graph.prisma +1073 -0
  157. package/host/prisma/fragments/kvox.notes.prisma +787 -0
  158. package/host/prisma/fragments/kvox.search.prisma +211 -0
  159. package/host/prisma/fragments/kvox.transcripts.prisma +790 -0
  160. package/host/prisma/kvox.lock +101 -0
  161. package/host/prisma/migrations/20240101000000_initial/migration.sql +297 -0
  162. package/host/prisma/migrations/20240101000001_add_personal_access_tokens/migration.sql +31 -0
  163. package/host/prisma/migrations/20240101000002_add_credentials/migration.sql +20 -0
  164. package/host/prisma/migrations/20240101000003_add_notification_deliveries/migration.sql +27 -0
  165. package/host/prisma/migrations/20240101000004_drop_stale_uuid_defaults/migration.sql +35 -0
  166. package/host/prisma/migrations/20240101000005_add_notifications/migration.sql +22 -0
  167. package/host/prisma/migrations/20240101000006_add_push_subscriptions/migration.sql +25 -0
  168. package/host/prisma/migrations/20240101000007_add_jobs/migration.sql +110 -0
  169. package/host/prisma/migrations/20240101000008_add_worker_nodes/migration.sql +90 -0
  170. package/host/prisma/migrations/20240101000009_add_database_backup_runs/migration.sql +104 -0
  171. package/host/prisma/migrations/20240101000010_add_notification_broadcasts/migration.sql +57 -0
  172. package/host/prisma/migrations/20240101000011_add_backup_run_job_link/migration.sql +87 -0
  173. package/host/prisma/migrations/20240101000012_add_job_node_secrets/migration.sql +61 -0
  174. package/host/prisma/migrations/20240101000013_add_backup_run_pg_dump_version/migration.sql +26 -0
  175. package/host/prisma/migrations/20240101000014_add_job_claim_token/migration.sql +92 -0
  176. package/host/prisma/migrations/20240101000015_add_ai_platform/migration.sql +108 -0
  177. package/host/prisma/migrations/20240101000016_revoke_viewer_ai_use/migration.sql +32 -0
  178. package/host/prisma/migrations/20240101000017_add_user_credentials/migration.sql +29 -0
  179. package/host/prisma/migrations/20240101000018_add_device_session_credential_link/migration.sql +52 -0
  180. package/host/prisma/migrations/20240101000019_add_worker_node_vitals/migration.sql +3 -0
  181. package/host/prisma/migrations/20240101000020_add_job_trace_context/migration.sql +6 -0
  182. package/host/prisma/migrations/20240101000021_add_retention_created_at_indexes/migration.sql +13 -0
  183. package/host/prisma/migrations/20240101000022_add_organizations/migration.sql +142 -0
  184. package/host/prisma/migrations/20240101000023_split_system_org_roles/migration.sql +131 -0
  185. package/host/prisma/migrations/20240101000024_org_scoped_rls/migration.sql +215 -0
  186. package/host/prisma/migrations/20240101000025_add_groups/migration.sql +165 -0
  187. package/host/prisma/migrations/20240101000026_add_grants/migration.sql +117 -0
  188. package/host/prisma/migrations/20240101000027_add_org_settings/migration.sql +49 -0
  189. package/host/prisma/migrations/20240101000028_add_org_credentials/migration.sql +54 -0
  190. package/host/prisma/migrations/20240101000029_add_jobs_org_id/migration.sql +18 -0
  191. package/host/prisma/migrations/20240101000030_add_jobs_org_id_status_index/migration.sql +6 -0
  192. package/host/prisma/migrations/20240101000031_add_broadcast_target_org/migration.sql +21 -0
  193. package/host/prisma/migrations/20240101000032_add_android_app/migration.sql +60 -0
  194. package/host/prisma/migrations/20240101000033_open_job_reason/migration.sql +16 -0
  195. package/host/prisma/migrations/20240101000034_ai_usage_feature/migration.sql +37 -0
  196. package/host/prisma/migrations/20240101000035_add_app_metadata/migration.sql +49 -0
  197. package/host/prisma/migrations/20240101000036_open_sharing_vocabularies/migration.sql +97 -0
  198. package/host/prisma/migrations/20260914130000_kvox_add_transcripts/migration.sql +322 -0
  199. package/host/prisma/migrations/20260914140000_kvox_add_notes/migration.sql +311 -0
  200. package/host/prisma/migrations/20260915120000_kvox_add_search_vectors/migration.sql +150 -0
  201. package/host/prisma/migrations/20260915130000_kvox_add_search_embeddings/migration.sql +362 -0
  202. package/host/prisma/migrations/20260915210000_kvox_add_note_title_source/migration.sql +30 -0
  203. package/host/prisma/migrations/20260923120000_kvox_add_note_generation_context_snapshot/migration.sql +24 -0
  204. package/host/prisma/migrations/20260923130000_kvox_add_user_hidden_note_templates/migration.sql +35 -0
  205. package/host/prisma/migrations/20260923140000_kvox_add_transcript_speaker_identities/migration.sql +6 -0
  206. package/host/prisma/migrations/20260923150000_kvox_transcript_name_checks/migration.sql +95 -0
  207. package/host/prisma/migrations/20260923160000_kvox_add_note_body_format/migration.sql +27 -0
  208. package/host/prisma/migrations/20260923170000_kvox_add_note_version_body_format/migration.sql +23 -0
  209. package/host/prisma/migrations/20260926012548_kvox_add_knowledge_graph/migration.sql +680 -0
  210. package/host/prisma/migrations/20260926030000_kvox_fix_kg_relations_valid_precision_chk/migration.sql +29 -0
  211. package/host/prisma/migrations/20260926030100_kvox_add_transcript_recorded_at/migration.sql +9 -0
  212. package/host/prisma/migrations/20260926030200_kvox_kg_evidence_invariant/migration.sql +60 -0
  213. package/host/prisma/migrations/20260926040000_kvox_add_kg_graph_layouts/migration.sql +30 -0
  214. package/host/prisma/migrations/20260926050000_kvox_add_ask_conversations/migration.sql +99 -0
  215. package/host/prisma/migrations/20260926231512_kvox_add_kg_exports/migration.sql +52 -0
  216. package/host/prisma/migrations/20261016120000_kvox_transcripts_org_id/migration.sql +49 -0
  217. package/host/prisma/platform.lock +319 -0
  218. package/host/prisma/schema/app.kvox.ask.prisma +110 -0
  219. package/host/prisma/schema/app.kvox.graph.prisma +1030 -0
  220. package/host/prisma/schema/app.kvox.notes.prisma +746 -0
  221. package/host/prisma/schema/app.kvox.search.prisma +208 -0
  222. package/host/prisma/schema/app.kvox.transcripts.prisma +726 -0
  223. package/host/prisma/schema/platform.ai.prisma +419 -0
  224. package/host/prisma/schema/platform.android-app.prisma +46 -0
  225. package/host/prisma/schema/platform.base.prisma +15 -0
  226. package/host/prisma/schema/platform.credentials.prisma +156 -0
  227. package/host/prisma/schema/platform.db-backup.prisma +359 -0
  228. package/host/prisma/schema/platform.identity.prisma +638 -0
  229. package/host/prisma/schema/platform.jobs.prisma +824 -0
  230. package/host/prisma/schema/platform.notifications.prisma +641 -0
  231. package/host/prisma/schema/platform.settings.prisma +53 -0
  232. package/host/prisma/schema/platform.sharing.prisma +208 -0
  233. package/host/prisma/schema/platform.storage.prisma +118 -0
  234. package/package.json +112 -1
  235. package/parity/requests.ask.json +2681 -0
  236. package/parity/requests.m01.json +1554 -0
  237. package/parity/requests.m02.json +1554 -0
  238. package/parity/requests.m03.json +1554 -0
  239. package/parity/requests.m04.json +1554 -0
  240. package/parity/requests.m05.json +1554 -0
  241. package/parity/requests.m06.json +1554 -0
  242. package/parity/requests.m07.json +1554 -0
  243. package/parity/requests.m08.json +1554 -0
  244. package/parity/requests.m09.json +1554 -0
  245. package/parity/requests.m10.json +1554 -0
@@ -0,0 +1,638 @@
1
+ // GENERATED by platform db compose — do not edit
2
+ // source: package:identity.prisma
3
+
4
+ // =============================================================================
5
+ // User & Identity
6
+ // =============================================================================
7
+
8
+ // @extensible — back-relations only, added with "extend model User { ... }" in a fragment
9
+ model User {
10
+ id String @id @default(uuid()) @db.Uuid
11
+ email String @unique
12
+ displayName String? @map("display_name")
13
+ providerDisplayName String? @map("provider_display_name")
14
+ profileImageUrl String? @map("profile_image_url")
15
+ providerProfileImageUrl String? @map("provider_profile_image_url")
16
+ isActive Boolean @default(true) @map("is_active")
17
+ createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
18
+ updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
19
+ // App-owned, namespaced fields (`registerUserMetadataSchema`, PP-14.27): one top-level
20
+ // key per registered namespace, each validated against its zod schema on write.
21
+ // NOT a secret store (a secret-named field is refused at registration; secrets go
22
+ // in `user_credentials`). At most 16 KiB per row, enforced by the writer and, as a
23
+ // 32 KiB backstop, by `users_metadata_size_check`. Never returned by the platform's
24
+ // routes: an app exposes a namespace only through a route it writes.
25
+ metadata Json @default("{}") @db.JsonB
26
+
27
+ // Relations
28
+ identities UserIdentity[]
29
+ userRoles UserRole[]
30
+ auditEvents AuditEvent[] @relation("ActorEvents")
31
+ refreshTokens RefreshToken[] @relation("UserRefreshTokens")
32
+ allowlistEntriesAdded AllowedEmail[] @relation("AllowlistAddedBy")
33
+ allowlistEntry AllowedEmail? @relation("AllowlistClaimedBy")
34
+ deviceCodes DeviceCode[] @relation("UserDeviceCodes")
35
+ personalAccessTokens PersonalAccessToken[] @relation("UserPersonalAccessTokens")
36
+ memberships Membership[]
37
+ organizationsCreated Organization[] @relation("OrganizationCreatedBy")
38
+ invitesSent Invite[] @relation("InviteInvitedBy")
39
+ invitesAccepted Invite[] @relation("InviteAcceptedBy")
40
+
41
+ // composed from package:ai.prisma
42
+ // AI platform (#423, epic #419). Four relations, four different
43
+ // `onDelete` answers — see each model's own block comment for why:
44
+ // - `aiModelsUpdated` (`AiModel.updatedByUser`) is provenance on a
45
+ // CATALOG row nobody owns, so `SetNull`, matching `Credential
46
+ // .updatedByUser` above.
47
+ // - `aiKeys` (`UserAiKey.user`) is `Cascade`: a BYOK key belongs to the
48
+ // user who typed it in, in exactly the sense `PushSubscription.user`
49
+ // does — there is no reader for it once the account is gone.
50
+ // - `aiRuns` (`AiRun.user`) is `SetNull`: a run is history, kept for
51
+ // usage/audit purposes independent of whether its requester's account
52
+ // still exists, matching `NotificationDelivery.user`.
53
+ // - `aiUsageEvents` (`AiUsageEvent.user`) is `SetNull` for the identical
54
+ // reason `aiRuns` is: usage accounting must outlive the account it
55
+ // billed.
56
+ aiModelsUpdated AiModel[] @relation("AiModelUpdatedBy")
57
+ aiKeys UserAiKey[]
58
+ aiRuns AiRun[]
59
+ aiUsageEvents AiUsageEvent[]
60
+
61
+ // composed from package:android-app.prisma
62
+ androidAppReleases AndroidAppRelease[] @relation("AndroidAppReleaseUploader")
63
+
64
+ // composed from package:credentials.prisma
65
+ credentialUpdates Credential[] @relation("CredentialUpdatedBy")
66
+ // Per-user encrypted credentials (issue #387). Cascade: a user-owned
67
+ // secret has no meaning once its owner is gone — see `UserCredential`.
68
+ userCredentials UserCredential[] @relation("UserCredentials")
69
+
70
+ // composed from package:credentials.prisma
71
+ // Provenance on an organization's credential (issue #735). SetNull: an
72
+ // org's key outlives the administrator who last typed it in.
73
+ orgCredentialUpdates OrgCredential[] @relation("OrgCredentialUpdatedBy")
74
+
75
+ // composed from package:db-backup.prisma
76
+ // Two relations to ONE table, deliberately (#281, epic #254): a
77
+ // `database_backup_runs` row records both "who asked for this backup" and,
78
+ // once Phase 7 restores from it, "who restored it". They are different acts
79
+ // by (usually) different people at different times, so they are different
80
+ // columns and therefore different relation names. See the block comment
81
+ // above `DatabaseBackupRun` for why the restore audit lives on the backup's
82
+ // own row rather than in a second table.
83
+ databaseBackupRuns DatabaseBackupRun[] @relation("UserDatabaseBackupRuns")
84
+ databaseRestores DatabaseBackupRun[] @relation("UserDatabaseRestores")
85
+
86
+ // composed from package:jobs.prisma
87
+ workerNodes WorkerNode[] @relation("UserWorkerNodes")
88
+ nodeCredentials NodeCredential[] @relation("UserNodeCredentials")
89
+
90
+ // composed from package:notifications.prisma
91
+ notificationDeliveries NotificationDelivery[] @relation("UserNotificationDeliveries")
92
+ notifications Notification[] @relation("UserNotifications")
93
+ pushSubscriptions PushSubscription[] @relation("UserPushSubscriptions")
94
+ broadcastsCreated NotificationBroadcast[] @relation("BroadcastAuthor")
95
+
96
+ // composed from package:settings.prisma
97
+ userSettings UserSettings?
98
+ settingsUpdates SystemSettings[] @relation("SettingsUpdater")
99
+ orgSettingsUpdates OrgSettings[] @relation("OrgSettingsUpdater")
100
+
101
+ // composed from package:sharing.prisma
102
+ groupsCreated Group[] @relation("GroupCreatedBy")
103
+ groupMemberships GroupMember[] @relation("GroupMemberUser")
104
+ groupMembersAdded GroupMember[] @relation("GroupMemberAddedBy")
105
+ groupInvitesSent GroupInvite[] @relation("GroupInviteInvitedBy")
106
+ groupInvitesClaimed GroupInvite[] @relation("GroupInviteAcceptedBy")
107
+ grantsReceived Grant[] @relation("GrantGranteeUser")
108
+ grantsRevoked Grant[] @relation("GrantRevokedBy")
109
+ grantsGiven Grant[] @relation("GrantGrantedBy")
110
+
111
+ // composed from package:storage.prisma
112
+ storageObjects StorageObject[] @relation("UserStorageObjects")
113
+
114
+ // composed from app:kvox.ask.prisma
115
+ // Back-relations onto the platform's User (an `extend model` block holds back-relation fields only).
116
+ // Ask conversations (issue #376, epic #348). CASCADES — a saved question
117
+ // and its answers are the user's own content, derived from their private
118
+ // recordings, with no read path for anyone else (no `ask:read_any`), so a
119
+ // deleted account leaves no Ask history. See the block comment above
120
+ // `AskConversation`.
121
+ askConversations AskConversation[]
122
+
123
+ // composed from app:kvox.graph.prisma
124
+ // Back-relations onto the platform's User (an `extend model` block holds back-relation fields only).
125
+ // Connected Knowledge (epic #344, issue #351). Every `kg_*` table carries
126
+ // its own `owner_id` and CASCADES — an entity, relation, item, piece of
127
+ // evidence, mention, proposal, merge, distinct-pair or attribute
128
+ // definition has no meaning and no permission path to read it once its
129
+ // owner is gone, the identical reasoning `notes.owner_id`/
130
+ // `transcripts.owner_id` already establish (§10, and there is no
131
+ // `graph:read_any` for the same reason there is no `notes:read_any`).
132
+ // `kgEntityViews` is the one exception: it is keyed on `user_id`, not
133
+ // `owner_id`, because "which entities has THIS user looked at" is a
134
+ // per-viewer fact, not an ownership fact — see the block comment above
135
+ // `KgEntityView`.
136
+ kgEntities KgEntity[]
137
+ kgEntityAliases KgEntityAlias[]
138
+ kgRelations KgRelation[]
139
+ kgItems KgItem[]
140
+ kgEvidence KgEvidence[]
141
+ kgMentions KgMention[]
142
+ kgProposals KgProposal[]
143
+ kgMerges KgMerge[]
144
+ kgDistinctPairs KgDistinctPair[]
145
+ kgAttributeDefs KgAttributeDef[]
146
+ kgEntityDigests KgEntityDigest[]
147
+ kgEntityViews KgEntityView[]
148
+ kgGraphLayouts KgGraphLayout[]
149
+
150
+ // #386 — RDF exports of the owner's graph. CASCADES: a disposable,
151
+ // reproducible artefact with its own 7-day expiry, like `note_exports`.
152
+ kgExports KgExport[]
153
+
154
+ // composed from app:kvox.notes.prisma
155
+ // Back-relations onto the platform's User (an `extend model` block holds back-relation fields only).
156
+ // Notes (issue #48, epic #45). Four relations to the note tables, split by
157
+ // role for the identical reason the six transcript relations above are:
158
+ // "owns this note", "owns this custom template", "wrote this version" and
159
+ // "asked for this export" are four different acts. See the block comment
160
+ // above the `Note` model for the full reasoning behind each `onDelete`.
161
+ notes Note[] @relation("NoteOwner")
162
+ noteTemplatesOwned NoteTemplate[] @relation("NoteTemplateOwner")
163
+ noteVersionsAuthored NoteVersion[] @relation("NoteVersionAuthor")
164
+ noteExportsRequested NoteExport[] @relation("NoteExportRequestedBy")
165
+
166
+ // Per-user "hide this template from my picker" preference (#310, epic
167
+ // #306). CASCADES — the preference is meaningless once its owner is gone.
168
+ // See the block comment above `UserHiddenNoteTemplate` for why this is a
169
+ // join table rather than a `user_settings` namespace.
170
+ hiddenNoteTemplates UserHiddenNoteTemplate[]
171
+
172
+ // composed from app:kvox.search.prisma
173
+ // Back-relations onto the platform's User (an `extend model` block holds back-relation fields only).
174
+ // Semantic search index state (issue #181, epic #165). ONE relation, and
175
+ // it CASCADES — an index-state row for a document owned by nobody means
176
+ // nothing, the identical reasoning `transcripts`/`notes` already apply to
177
+ // their own `ownerId`. See the block comment above `SearchIndexState`.
178
+ searchIndexStates SearchIndexState[] @relation("SearchIndexStateOwner")
179
+
180
+ // composed from app:kvox.transcripts.prisma
181
+ // Back-relations onto the platform's User (an `extend model` block holds back-relation fields only).
182
+ // Transcripts (issue #24, epic #19). Four relations to the transcript
183
+ // tables, split by role for the same reason `databaseBackupRuns` /
184
+ // `databaseRestores` are two relations to one table above: "owns this
185
+ // transcript", "wrote this version", "last edited this segment" and "asked
186
+ // for this export" are four different acts, not one (a share is a platform
187
+ // `grants` row since issue #485). See the block comment above the `Transcript`
188
+ // model for the full reasoning behind every one of these relations'
189
+ // `onDelete` behaviour.
190
+ transcripts Transcript[] @relation("TranscriptOwner")
191
+ transcriptVersionsAuthored TranscriptVersion[] @relation("TranscriptVersionAuthor")
192
+ transcriptSegmentEdits TranscriptSegment[] @relation("TranscriptSegmentEditor")
193
+ transcriptExportsRequested TranscriptExport[] @relation("TranscriptExportRequestedBy")
194
+
195
+ // AI name checks (issue #328, epic #326). Two relations, split by role for
196
+ // the same reason the six transcript relations above are: "asked for this
197
+ // run" and "accepted/rejected this suggestion" are different acts. See the
198
+ // block comment above the `TranscriptNameCheck` model for the full
199
+ // reasoning behind each `onDelete`.
200
+ transcriptNameChecksRequested TranscriptNameCheck[] @relation("TranscriptNameCheckRequestedBy")
201
+ transcriptNameSuggestionsDecided TranscriptNameSuggestion[] @relation("TranscriptNameSuggestionDecidedBy")
202
+
203
+ @@map("users")
204
+ }
205
+
206
+ model UserIdentity {
207
+ id String @id @default(uuid()) @db.Uuid
208
+ userId String @map("user_id") @db.Uuid
209
+ provider String
210
+ providerSubject String @map("provider_subject")
211
+ providerEmail String? @map("provider_email")
212
+ createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
213
+
214
+ // Relations
215
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
216
+
217
+ @@unique([provider, providerSubject])
218
+ @@map("user_identities")
219
+ }
220
+
221
+ // =============================================================================
222
+ // RBAC - Roles & Permissions
223
+ // =============================================================================
224
+ //
225
+ // Two kinds of role (PP-6.3): SYSTEM roles operate the deployment and are held
226
+ // in `user_roles`; ORG roles operate one organization and are held on a
227
+ // membership (`memberships.role_id`). A role is granted only permissions of its
228
+ // own scope (enforced by the app's permission registry, seeded by
229
+ // `seedPlatform`). Roles stay global rows; per-organization custom roles are a
230
+ // later seam.
231
+
232
+ enum RoleScope {
233
+ system
234
+ org
235
+ }
236
+
237
+ // @extensible — back-relations only, added with "extend model Role { ... }" in a fragment
238
+ // (a table of the app's own that points at a role, such as per-role settings). The
239
+ // role's own columns and the `role_permissions` grants stay the platform's: `seedPlatform`
240
+ // refreshes them from the registry on every run.
241
+ model Role {
242
+ id String @id @default(uuid()) @db.Uuid
243
+ name String @unique
244
+ description String?
245
+ scope RoleScope @default(system)
246
+
247
+ // Relations
248
+ rolePermissions RolePermission[]
249
+ userRoles UserRole[]
250
+ memberships Membership[]
251
+ invites Invite[]
252
+
253
+ @@map("roles")
254
+ }
255
+
256
+ // Deliberately NOT `@extensible` (PP-14.27). A permission is a string owned by code:
257
+ // the registry (`registerPermissions`) is the source of truth, `seedPlatform` upserts
258
+ // the rows by `name` and never deletes one, and a removed permission is retired by an
259
+ // explicit expand/contract migration. A foreign key from an app table would make that
260
+ // retirement fail and would tie app data to a row id that no registry declares. An app
261
+ // refers to a permission by its name string in code, never by a relation.
262
+ model Permission {
263
+ id String @id @default(uuid()) @db.Uuid
264
+ name String @unique
265
+ description String?
266
+ scope RoleScope @default(system)
267
+
268
+ // Relations
269
+ rolePermissions RolePermission[]
270
+
271
+ @@map("permissions")
272
+ }
273
+
274
+ model RolePermission {
275
+ roleId String @map("role_id") @db.Uuid
276
+ permissionId String @map("permission_id") @db.Uuid
277
+
278
+ // Relations
279
+ role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
280
+ permission Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)
281
+
282
+ @@id([roleId, permissionId])
283
+ @@map("role_permissions")
284
+ }
285
+
286
+ model UserRole {
287
+ userId String @map("user_id") @db.Uuid
288
+ roleId String @map("role_id") @db.Uuid
289
+
290
+ // Relations
291
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
292
+ role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
293
+
294
+ @@id([userId, roleId])
295
+ @@map("user_roles")
296
+ }
297
+
298
+ // =============================================================================
299
+ // Audit
300
+ // =============================================================================
301
+
302
+ model AuditEvent {
303
+ id String @id @default(uuid()) @db.Uuid
304
+ actorUserId String? @map("actor_user_id") @db.Uuid
305
+ action String
306
+ targetType String @map("target_type")
307
+ targetId String @map("target_id")
308
+ meta Json?
309
+ // The organization the action happened in (#725). NULL for a system event
310
+ // (settings, backups, restores, the allowlist): see the backfill in
311
+ // `0025_org_scoped_rls`. `SetNull`: history outlives the organization. No
312
+ // row-level security on this table in this release (`org-optional`).
313
+ orgId String? @map("org_id") @db.Uuid
314
+ createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
315
+
316
+ // Relations
317
+ actorUser User? @relation("ActorEvents", fields: [actorUserId], references: [id], onDelete: SetNull)
318
+ org Organization? @relation(fields: [orgId], references: [id], onDelete: SetNull)
319
+
320
+ @@index([actorUserId])
321
+ @@index([targetType, targetId])
322
+ @@index([createdAt])
323
+ @@index([orgId, createdAt])
324
+ @@map("audit_events")
325
+ }
326
+
327
+ // =============================================================================
328
+ // Refresh Tokens (for token rotation)
329
+ // =============================================================================
330
+
331
+ model RefreshToken {
332
+ id String @id @default(uuid()) @db.Uuid
333
+ userId String @map("user_id") @db.Uuid
334
+ tokenHash String @unique @map("token_hash")
335
+ expiresAt DateTime @map("expires_at") @db.Timestamptz
336
+ createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
337
+ revokedAt DateTime? @map("revoked_at") @db.Timestamptz
338
+ // The device-authorization session that minted this refresh token, if any
339
+ // (issue #518). Lets revoking a `DeviceCode` session reach the refresh
340
+ // token it handed out. Nullable: most refresh tokens come from the
341
+ // interactive login flow, not the device flow. `SetNull` because a device
342
+ // code can be purged independently of the token lifecycle it kicked off.
343
+ deviceCodeId String? @map("device_code_id") @db.Uuid
344
+ // The organization this session is active in (PP-6.1). Nullable (expand
345
+ // step); backfilled to the default organization. Carried by the active-org
346
+ // token (PP-6.4). `Cascade`: a credential has no meaning once its org is gone.
347
+ orgId String? @map("org_id") @db.Uuid
348
+
349
+ // Relations
350
+ user User @relation("UserRefreshTokens", fields: [userId], references: [id], onDelete: Cascade)
351
+ deviceCode DeviceCode? @relation(fields: [deviceCodeId], references: [id], onDelete: SetNull)
352
+ org Organization? @relation(fields: [orgId], references: [id], onDelete: Cascade)
353
+
354
+ @@index([userId])
355
+ @@index([expiresAt])
356
+ @@index([deviceCodeId])
357
+ @@index([orgId])
358
+ @@map("refresh_tokens")
359
+ }
360
+
361
+ // =============================================================================
362
+ // Personal Access Tokens (PAT)
363
+ // =============================================================================
364
+
365
+ enum PatDurationUnit {
366
+ minutes
367
+ days
368
+ months
369
+ }
370
+
371
+ model PersonalAccessToken {
372
+ id String @id @default(uuid()) @db.Uuid
373
+ userId String @map("user_id") @db.Uuid
374
+ name String
375
+ tokenHash String @unique @map("token_hash")
376
+ tokenPrefix String @map("token_prefix")
377
+ durationValue Int @map("duration_value")
378
+ durationUnit PatDurationUnit @map("duration_unit")
379
+ expiresAt DateTime @map("expires_at") @db.Timestamptz
380
+ lastUsedAt DateTime? @map("last_used_at") @db.Timestamptz
381
+ createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
382
+ revokedAt DateTime? @map("revoked_at") @db.Timestamptz
383
+ // The organization the token acts in (PP-6.1). Nullable (expand step);
384
+ // backfilled to the default organization. `Cascade`: see `RefreshToken.orgId`.
385
+ orgId String? @map("org_id") @db.Uuid
386
+
387
+ // Relations
388
+ user User @relation("UserPersonalAccessTokens", fields: [userId], references: [id], onDelete: Cascade)
389
+ org Organization? @relation(fields: [orgId], references: [id], onDelete: Cascade)
390
+ // The device-authorization sessions that issued this PAT (issue #518).
391
+ // Almost always at most one, but not constrained `@unique` here — see
392
+ // `DeviceCode.patId`.
393
+ deviceCodes DeviceCode[]
394
+
395
+ @@index([userId])
396
+ @@index([expiresAt])
397
+ @@index([orgId])
398
+ @@map("personal_access_tokens")
399
+ }
400
+
401
+ // =============================================================================
402
+ // Allowlist (Email Access Control)
403
+ // =============================================================================
404
+
405
+ model AllowedEmail {
406
+ id String @id @default(uuid()) @db.Uuid
407
+ email String @unique
408
+ addedById String? @map("added_by_id") @db.Uuid
409
+ addedAt DateTime @default(now()) @map("added_at") @db.Timestamptz
410
+ claimedById String? @unique @map("claimed_by_id") @db.Uuid
411
+ claimedAt DateTime? @map("claimed_at") @db.Timestamptz
412
+ notes String?
413
+
414
+ // Relations
415
+ addedBy User? @relation("AllowlistAddedBy", fields: [addedById], references: [id], onDelete: SetNull)
416
+ claimedBy User? @relation("AllowlistClaimedBy", fields: [claimedById], references: [id], onDelete: SetNull)
417
+
418
+ @@map("allowed_emails")
419
+ }
420
+
421
+ // =============================================================================
422
+ // Device Authorization Flow (RFC 8628)
423
+ // =============================================================================
424
+
425
+ enum DeviceCodeStatus {
426
+ pending
427
+ approved
428
+ denied
429
+ expired
430
+ }
431
+
432
+ model DeviceCode {
433
+ id String @id @default(uuid()) @db.Uuid
434
+ deviceCode String @unique @map("device_code")
435
+ userCode String @unique @map("user_code")
436
+ userId String? @map("user_id") @db.Uuid
437
+ status DeviceCodeStatus @default(pending)
438
+ clientInfo Json? @map("client_info")
439
+ scopes String[]
440
+ // The code's own expiry (RFC 8628 `expires_in`) — when the user/device
441
+ // codes stop being redeemable if never approved. Distinct from
442
+ // `credentialExpiresAt`, which is the lifetime of what got issued once
443
+ // approval happened (issue #518).
444
+ expiresAt DateTime @map("expires_at") @db.Timestamptz
445
+ createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
446
+ updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
447
+
448
+ // The PAT minted for this device once the polling client collected it
449
+ // (issue #518). Nullable until collection; `SetNull` so deleting the PAT
450
+ // (or the PAT row aging out) never cascades into deleting the device-code
451
+ // history. This is what lets "revoke this device session" reach the
452
+ // credential the device is actually holding.
453
+ patId String? @map("pat_id") @db.Uuid
454
+
455
+ // When the polling client (`POST /api/auth/device/token`) collected the
456
+ // credential above. Null until collection. Distinguishes "approved,
457
+ // waiting to be picked up" from "picked up and in active use" for the
458
+ // sessions list.
459
+ collectedAt DateTime? @map("collected_at") @db.Timestamptz
460
+
461
+ // The collected credential's own expiry, copied from the PAT at
462
+ // collection time so the sessions list can filter "still valid" without a
463
+ // join. Null until collection.
464
+ credentialExpiresAt DateTime? @map("credential_expires_at") @db.Timestamptz
465
+
466
+ // Set when this device session (and the credential it issued, if any) is
467
+ // revoked — from the code itself if revoked before collection, from the
468
+ // session-revocation endpoint after. Null means still active.
469
+ revokedAt DateTime? @map("revoked_at") @db.Timestamptz
470
+
471
+ // The organization the approved device session acts in (PP-6.1). Nullable
472
+ // (expand step); backfilled to the default organization. `Cascade`: see
473
+ // `RefreshToken.orgId`.
474
+ orgId String? @map("org_id") @db.Uuid
475
+
476
+ // Relations
477
+ user User? @relation("UserDeviceCodes", fields: [userId], references: [id], onDelete: Cascade)
478
+ org Organization? @relation(fields: [orgId], references: [id], onDelete: Cascade)
479
+ pat PersonalAccessToken? @relation(fields: [patId], references: [id], onDelete: SetNull)
480
+
481
+ // Back-relation: refresh tokens minted through this device-authorization
482
+ // session (issue #518). See `RefreshToken.deviceCodeId`.
483
+ refreshTokens RefreshToken[]
484
+
485
+ @@index([deviceCode])
486
+ @@index([userCode])
487
+ @@index([status, expiresAt])
488
+ @@index([userId])
489
+ // Serves the sessions list (`GET /api/auth/device/sessions`, issue #518):
490
+ // "this user's device sessions that are not revoked" —
491
+ // approved-but-uncollected and collected-unexpired both live under
492
+ // `revokedAt IS NULL`; the remaining collected/expiry filtering happens
493
+ // in the query predicate, not a second index.
494
+ @@index([userId, revokedAt])
495
+ @@index([orgId])
496
+ @@map("device_codes")
497
+ }
498
+
499
+ // =============================================================================
500
+ // Organizations (tenancy foundation, PP-6.1)
501
+ // =============================================================================
502
+ //
503
+ // Identity tables, read across organizations at login and guarded by service
504
+ // code: no RLS here (ADR 0002). The default organization is unique by the
505
+ // raw-SQL partial index `organizations_default_uniq_idx` (migration SQL only;
506
+ // Prisma cannot express it). A membership carries its org role (PP-6.3); an
507
+ // invite may name the org role the invitee will get.
508
+
509
+ enum MembershipStatus {
510
+ active
511
+ suspended
512
+ }
513
+
514
+ enum InviteStatus {
515
+ pending
516
+ accepted
517
+ revoked
518
+ expired
519
+ }
520
+
521
+ // @extensible — back-relations only, added with "extend model Organization { ... }" in a fragment
522
+ model Organization {
523
+ id String @id @default(uuid()) @db.Uuid
524
+ name String
525
+ // Lower-case [a-z0-9-], 2..63 characters (validated by the service).
526
+ slug String @unique
527
+ // Exactly one row is true: organizations_default_uniq_idx.
528
+ isDefault Boolean @default(false) @map("is_default")
529
+ createdById String? @map("created_by_id") @db.Uuid
530
+ createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
531
+ updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
532
+ // App-owned, namespaced fields (`registerOrganizationMetadataSchema`, PP-14.27); same
533
+ // contract as `User.metadata`: validated per namespace, no secrets, 16 KiB per row
534
+ // (32 KiB backstop `organizations_metadata_size_check`), never returned by the
535
+ // platform's routes.
536
+ metadata Json @default("{}") @db.JsonB
537
+
538
+ // Relations
539
+ createdBy User? @relation("OrganizationCreatedBy", fields: [createdById], references: [id], onDelete: SetNull)
540
+ memberships Membership[]
541
+ invites Invite[]
542
+ refreshTokens RefreshToken[]
543
+ personalAccessTokens PersonalAccessToken[]
544
+ deviceCodes DeviceCode[]
545
+ // Audit history outlives the organization (`SetNull`); system events have none (#725).
546
+ auditEvents AuditEvent[]
547
+
548
+ // composed from package:ai.prisma
549
+ // AI runs and usage events (#725): see `AiRun.orgId` and `AiUsageEvent.orgId`
550
+ // for why one is `Restrict` and the other `SetNull`.
551
+ aiRuns AiRun[]
552
+ aiUsageEvents AiUsageEvent[]
553
+
554
+ // composed from package:credentials.prisma
555
+ orgCredentials OrgCredential[]
556
+
557
+ // composed from package:jobs.prisma
558
+ // The organization's jobs (issue #734): see `Job.orgId` for why `SetNull`.
559
+ jobs Job[]
560
+
561
+ // composed from package:notifications.prisma
562
+ notificationBroadcasts NotificationBroadcast[] @relation("BroadcastTargetOrg")
563
+
564
+ // composed from package:settings.prisma
565
+ settings OrgSettings?
566
+
567
+ // composed from package:sharing.prisma
568
+ groups Group[]
569
+ groupMembers GroupMember[]
570
+ groupInvites GroupInvite[]
571
+ grants Grant[]
572
+
573
+ // composed from package:storage.prisma
574
+ storageObjects StorageObject[]
575
+ storageObjectChunks StorageObjectChunk[]
576
+
577
+ // composed from app:kvox.transcripts.prisma
578
+ // Back-relations onto the platform's Organization (an `extend model` block holds back-relation fields only).
579
+ // `transcripts.org_id` (issue #485): the organization the sharing slice decides a transcript's access in.
580
+ transcripts Transcript[]
581
+
582
+ @@map("organizations")
583
+ }
584
+
585
+ model Membership {
586
+ id String @id @default(uuid()) @db.Uuid
587
+ orgId String @map("org_id") @db.Uuid
588
+ userId String @map("user_id") @db.Uuid
589
+ status MembershipStatus @default(active)
590
+ // Picks the default active organization at login (PP-6.4).
591
+ lastActiveAt DateTime? @map("last_active_at") @db.Timestamptz
592
+ // The member's org-scoped role (PP-6.3). `Restrict`: a role in use cannot be
593
+ // deleted out from under its members.
594
+ roleId String @map("role_id") @db.Uuid
595
+ createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
596
+ updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
597
+
598
+ // Relations
599
+ org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
600
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
601
+ role Role @relation(fields: [roleId], references: [id], onDelete: Restrict)
602
+
603
+ @@unique([orgId, userId])
604
+ @@index([userId])
605
+ @@index([roleId])
606
+ @@map("memberships")
607
+ }
608
+
609
+ model Invite {
610
+ id String @id @default(uuid()) @db.Uuid
611
+ orgId String @map("org_id") @db.Uuid
612
+ // Stored lower-cased.
613
+ email String
614
+ status InviteStatus @default(pending)
615
+ // sha256 of an emailed accept token, if one is used.
616
+ tokenHash String? @unique @map("token_hash")
617
+ expiresAt DateTime? @map("expires_at") @db.Timestamptz
618
+ invitedById String? @map("invited_by_id") @db.Uuid
619
+ acceptedById String? @map("accepted_by_id") @db.Uuid
620
+ acceptedAt DateTime? @map("accepted_at") @db.Timestamptz
621
+ notes String?
622
+ // The org role the invitee gets on acceptance (PP-6.3). NULL means the
623
+ // default org role (`viewer`). `Restrict`, as on `Membership.roleId`.
624
+ roleId String? @map("role_id") @db.Uuid
625
+ createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
626
+
627
+ // Relations
628
+ org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
629
+ role Role? @relation(fields: [roleId], references: [id], onDelete: Restrict)
630
+ invitedBy User? @relation("InviteInvitedBy", fields: [invitedById], references: [id], onDelete: SetNull)
631
+ acceptedBy User? @relation("InviteAcceptedBy", fields: [acceptedById], references: [id], onDelete: SetNull)
632
+
633
+ // Re-inviting updates the row.
634
+ @@unique([orgId, email])
635
+ @@index([email])
636
+ @@index([roleId])
637
+ @@map("org_invites")
638
+ }