@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,165 @@
1
+ -- =============================================================================
2
+ -- Groups: sharing groups inside an organization (PP-7.1, issue #728)
3
+ -- =============================================================================
4
+ --
5
+ -- Three org-owned tables, each with a NOT NULL org_id and a FORCEd
6
+ -- <table>_org_isolation policy copied from 0025_org_scoped_rls:
7
+ --
8
+ -- groups a set of users inside ONE organization that can own content
9
+ -- (spec decision D6: a group is never a tenant)
10
+ -- group_members one row per (group, user), with a GroupRole
11
+ -- group_invites an invitation of an e-mail address to a group
12
+ --
13
+ -- ORG CONSISTENCY. A member and an invite reach their group through the
14
+ -- COMPOSITE key (group_id, org_id) -> groups (id, org_id), with the supporting
15
+ -- UNIQUE (id, org_id) on groups: a foreign-key check bypasses row-level
16
+ -- security, so a plain key would let one organization store a reference to
17
+ -- another's group, and the composite key also guarantees that
18
+ -- group_members.org_id and group_invites.org_id equal groups.org_id.
19
+ --
20
+ -- RAW-SQL INDEX (intentional schema drift, listed in raw-sql-indexes.json):
21
+ -- group_invites_pending_uniq_idx at most one PENDING invite per (group,
22
+ -- email); a declined, revoked or accepted
23
+ -- invite does not block a new one. Prisma
24
+ -- cannot express a partial unique index.
25
+ --
26
+ -- No backfill: the tables are new, so the policies are enabled at once.
27
+ -- =============================================================================
28
+
29
+ -- CreateEnum
30
+ CREATE TYPE "GroupRole" AS ENUM ('admin', 'editor', 'viewer');
31
+
32
+ -- CreateTable
33
+ CREATE TABLE "groups" (
34
+ "id" UUID NOT NULL,
35
+ "org_id" UUID NOT NULL,
36
+ "name" TEXT NOT NULL,
37
+ "description" TEXT,
38
+ "metadata" JSONB,
39
+ "created_by_id" UUID,
40
+ "version" INTEGER NOT NULL DEFAULT 1,
41
+ "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
42
+ "updated_at" TIMESTAMPTZ NOT NULL,
43
+
44
+ CONSTRAINT "groups_pkey" PRIMARY KEY ("id")
45
+ );
46
+
47
+ -- CreateTable
48
+ CREATE TABLE "group_members" (
49
+ "id" UUID NOT NULL,
50
+ "group_id" UUID NOT NULL,
51
+ "user_id" UUID NOT NULL,
52
+ "org_id" UUID NOT NULL,
53
+ "role" "GroupRole" NOT NULL DEFAULT 'viewer',
54
+ "added_by_id" UUID,
55
+ "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
56
+ "updated_at" TIMESTAMPTZ NOT NULL,
57
+
58
+ CONSTRAINT "group_members_pkey" PRIMARY KEY ("id")
59
+ );
60
+
61
+ -- CreateTable
62
+ CREATE TABLE "group_invites" (
63
+ "id" UUID NOT NULL,
64
+ "group_id" UUID NOT NULL,
65
+ "org_id" UUID NOT NULL,
66
+ "email" TEXT NOT NULL,
67
+ "role" "GroupRole" NOT NULL DEFAULT 'viewer',
68
+ "invited_by_id" UUID,
69
+ "expires_at" TIMESTAMPTZ,
70
+ "accepted_at" TIMESTAMPTZ,
71
+ "accepted_by_id" UUID,
72
+ "declined_at" TIMESTAMPTZ,
73
+ "revoked_at" TIMESTAMPTZ,
74
+ "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
75
+
76
+ CONSTRAINT "group_invites_pkey" PRIMARY KEY ("id")
77
+ );
78
+
79
+ -- CreateIndex
80
+ CREATE INDEX "groups_org_id_idx" ON "groups"("org_id");
81
+
82
+ -- CreateIndex
83
+ CREATE UNIQUE INDEX "groups_id_org_id_key" ON "groups"("id", "org_id");
84
+
85
+ -- CreateIndex
86
+ CREATE INDEX "group_members_user_id_idx" ON "group_members"("user_id");
87
+
88
+ -- CreateIndex
89
+ CREATE INDEX "group_members_org_id_idx" ON "group_members"("org_id");
90
+
91
+ -- CreateIndex
92
+ CREATE UNIQUE INDEX "group_members_group_id_user_id_key" ON "group_members"("group_id", "user_id");
93
+
94
+ -- CreateIndex
95
+ CREATE INDEX "group_invites_email_idx" ON "group_invites"("email");
96
+
97
+ -- CreateIndex
98
+ CREATE INDEX "group_invites_group_id_idx" ON "group_invites"("group_id");
99
+
100
+ -- CreateIndex
101
+ CREATE INDEX "group_invites_org_id_idx" ON "group_invites"("org_id");
102
+
103
+ -- AddForeignKey
104
+ ALTER TABLE "groups" ADD CONSTRAINT "groups_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
105
+
106
+ -- AddForeignKey
107
+ ALTER TABLE "groups" ADD CONSTRAINT "groups_created_by_id_fkey" FOREIGN KEY ("created_by_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
108
+
109
+ -- AddForeignKey
110
+ ALTER TABLE "group_members" ADD CONSTRAINT "group_members_group_id_org_id_fkey" FOREIGN KEY ("group_id", "org_id") REFERENCES "groups"("id", "org_id") ON DELETE CASCADE ON UPDATE CASCADE;
111
+
112
+ -- AddForeignKey
113
+ ALTER TABLE "group_members" ADD CONSTRAINT "group_members_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
114
+
115
+ -- AddForeignKey
116
+ ALTER TABLE "group_members" ADD CONSTRAINT "group_members_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
117
+
118
+ -- AddForeignKey
119
+ ALTER TABLE "group_members" ADD CONSTRAINT "group_members_added_by_id_fkey" FOREIGN KEY ("added_by_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
120
+
121
+ -- AddForeignKey
122
+ ALTER TABLE "group_invites" ADD CONSTRAINT "group_invites_group_id_org_id_fkey" FOREIGN KEY ("group_id", "org_id") REFERENCES "groups"("id", "org_id") ON DELETE CASCADE ON UPDATE CASCADE;
123
+
124
+ -- AddForeignKey
125
+ ALTER TABLE "group_invites" ADD CONSTRAINT "group_invites_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
126
+
127
+ -- AddForeignKey
128
+ ALTER TABLE "group_invites" ADD CONSTRAINT "group_invites_invited_by_id_fkey" FOREIGN KEY ("invited_by_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
129
+
130
+ -- AddForeignKey
131
+ ALTER TABLE "group_invites" ADD CONSTRAINT "group_invites_accepted_by_id_fkey" FOREIGN KEY ("accepted_by_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
132
+
133
+ -- CreateIndex (partial: one pending invite per group and address)
134
+ CREATE UNIQUE INDEX "group_invites_pending_uniq_idx" ON "group_invites"("group_id", "email") WHERE "accepted_at" IS NULL AND "declined_at" IS NULL AND "revoked_at" IS NULL;
135
+
136
+ -- =============================================================================
137
+ -- Row-level security, as in 0025_org_scoped_rls. The policy names are listed in
138
+ -- RLS_POLICIES (rls-policies.json) and asserted against pg_policies by the
139
+ -- drift test. NULLIF(..., '') because current_setting(name, true) is the empty
140
+ -- string on a connection that ever held a transaction-local value.
141
+ -- =============================================================================
142
+
143
+ ALTER TABLE "groups" ENABLE ROW LEVEL SECURITY;
144
+ ALTER TABLE "groups" FORCE ROW LEVEL SECURITY;
145
+ CREATE POLICY "groups_org_isolation" ON "groups"
146
+ USING ("org_id" = NULLIF(current_setting('app.org_id', true), '')::uuid
147
+ OR current_setting('app.rls_bypass', true) = 'on')
148
+ WITH CHECK ("org_id" = NULLIF(current_setting('app.org_id', true), '')::uuid
149
+ OR current_setting('app.rls_bypass', true) = 'on');
150
+
151
+ ALTER TABLE "group_members" ENABLE ROW LEVEL SECURITY;
152
+ ALTER TABLE "group_members" FORCE ROW LEVEL SECURITY;
153
+ CREATE POLICY "group_members_org_isolation" ON "group_members"
154
+ USING ("org_id" = NULLIF(current_setting('app.org_id', true), '')::uuid
155
+ OR current_setting('app.rls_bypass', true) = 'on')
156
+ WITH CHECK ("org_id" = NULLIF(current_setting('app.org_id', true), '')::uuid
157
+ OR current_setting('app.rls_bypass', true) = 'on');
158
+
159
+ ALTER TABLE "group_invites" ENABLE ROW LEVEL SECURITY;
160
+ ALTER TABLE "group_invites" FORCE ROW LEVEL SECURITY;
161
+ CREATE POLICY "group_invites_org_isolation" ON "group_invites"
162
+ USING ("org_id" = NULLIF(current_setting('app.org_id', true), '')::uuid
163
+ OR current_setting('app.rls_bypass', true) = 'on')
164
+ WITH CHECK ("org_id" = NULLIF(current_setting('app.org_id', true), '')::uuid
165
+ OR current_setting('app.rls_bypass', true) = 'on');
@@ -0,0 +1,117 @@
1
+ -- =============================================================================
2
+ -- Grants: share one record with a user, a group or a link (PP-7.2, issue #729)
3
+ -- =============================================================================
4
+ --
5
+ -- One org-owned table, `grants`, with a NOT NULL org_id and a FORCEd
6
+ -- grants_org_isolation policy copied from 0025_org_scoped_rls.
7
+ --
8
+ -- POLYMORPHIC RESOURCE. (resource_type, resource_id) names a row of an app
9
+ -- table the platform cannot know, so there is no foreign key to it. Integrity
10
+ -- comes from GrantsService.deleteForResources() (inside the app's delete
11
+ -- transaction) and the sharing.grants.prune job.
12
+ --
13
+ -- ORG CONSISTENCY. A group grantee reaches its group through the COMPOSITE key
14
+ -- (grantee_group_id, org_id) -> groups (id, org_id): a foreign-key check
15
+ -- bypasses row-level security, so a plain key would let a grant name another
16
+ -- organization's group. A user is not org-scoped; the API checks that the
17
+ -- grantee is an active member of the resource's organization.
18
+ --
19
+ -- The link columns (link_token_hash, link_token_ciphertext, link_label) are
20
+ -- created now so #730 needs no migration.
21
+ --
22
+ -- RAW SQL (intentional schema drift: Prisma can express neither):
23
+ -- grants_active_user_uniq_idx at most one ACTIVE (unrevoked) grant per
24
+ -- (resource, user): the API upserts on it
25
+ -- grants_active_group_uniq_idx the same per (resource, group)
26
+ -- grants_grantee_consistency_check the grantee columns match grantee_kind
27
+ -- The two indexes are listed in raw-sql-indexes.json.
28
+ --
29
+ -- No backfill: the table is new, so the policy is enabled at once.
30
+ -- =============================================================================
31
+
32
+ -- CreateEnum
33
+ CREATE TYPE "GrantGranteeKind" AS ENUM ('user', 'group', 'link');
34
+
35
+ -- CreateTable
36
+ CREATE TABLE "grants" (
37
+ "id" UUID NOT NULL,
38
+ "org_id" UUID NOT NULL,
39
+ "resource_type" TEXT NOT NULL,
40
+ "resource_id" UUID NOT NULL,
41
+ "grantee_kind" "GrantGranteeKind" NOT NULL,
42
+ "grantee_user_id" UUID,
43
+ "grantee_group_id" UUID,
44
+ "role" TEXT NOT NULL,
45
+ "link_token_hash" TEXT,
46
+ "link_token_ciphertext" TEXT,
47
+ "link_label" TEXT,
48
+ "expires_at" TIMESTAMPTZ,
49
+ "revoked_at" TIMESTAMPTZ,
50
+ "revoked_by_id" UUID,
51
+ "granted_by_id" UUID,
52
+ "metadata" JSONB,
53
+ "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
54
+ "updated_at" TIMESTAMPTZ NOT NULL,
55
+
56
+ CONSTRAINT "grants_pkey" PRIMARY KEY ("id")
57
+ );
58
+
59
+ -- CreateIndex
60
+ CREATE UNIQUE INDEX "grants_link_token_hash_key" ON "grants"("link_token_hash");
61
+
62
+ -- CreateIndex
63
+ CREATE INDEX "grants_resource_type_resource_id_idx" ON "grants"("resource_type", "resource_id");
64
+
65
+ -- CreateIndex
66
+ CREATE INDEX "grants_grantee_user_id_idx" ON "grants"("grantee_user_id");
67
+
68
+ -- CreateIndex
69
+ CREATE INDEX "grants_grantee_group_id_idx" ON "grants"("grantee_group_id");
70
+
71
+ -- CreateIndex
72
+ CREATE INDEX "grants_org_id_idx" ON "grants"("org_id");
73
+
74
+ -- AddForeignKey
75
+ ALTER TABLE "grants" ADD CONSTRAINT "grants_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
76
+
77
+ -- AddForeignKey
78
+ ALTER TABLE "grants" ADD CONSTRAINT "grants_grantee_user_id_fkey" FOREIGN KEY ("grantee_user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
79
+
80
+ -- AddForeignKey
81
+ ALTER TABLE "grants" ADD CONSTRAINT "grants_grantee_group_id_org_id_fkey" FOREIGN KEY ("grantee_group_id", "org_id") REFERENCES "groups"("id", "org_id") ON DELETE CASCADE ON UPDATE CASCADE;
82
+
83
+ -- AddForeignKey
84
+ ALTER TABLE "grants" ADD CONSTRAINT "grants_revoked_by_id_fkey" FOREIGN KEY ("revoked_by_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
85
+
86
+ -- AddForeignKey
87
+ ALTER TABLE "grants" ADD CONSTRAINT "grants_granted_by_id_fkey" FOREIGN KEY ("granted_by_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
88
+
89
+ -- CreateIndex (partial: one active grant per resource and user)
90
+ CREATE UNIQUE INDEX "grants_active_user_uniq_idx" ON "grants"("resource_type", "resource_id", "grantee_user_id") WHERE "grantee_kind" = 'user' AND "revoked_at" IS NULL;
91
+
92
+ -- CreateIndex (partial: one active grant per resource and group)
93
+ CREATE UNIQUE INDEX "grants_active_group_uniq_idx" ON "grants"("resource_type", "resource_id", "grantee_group_id") WHERE "grantee_kind" = 'group' AND "revoked_at" IS NULL;
94
+
95
+ -- AddCheckConstraint (the grantee columns match the grantee kind)
96
+ ALTER TABLE "grants" ADD CONSTRAINT "grants_grantee_consistency_check" CHECK (
97
+ ("grantee_kind" = 'user' AND "grantee_user_id" IS NOT NULL AND "grantee_group_id" IS NULL AND "link_token_hash" IS NULL)
98
+ OR
99
+ ("grantee_kind" = 'group' AND "grantee_user_id" IS NULL AND "grantee_group_id" IS NOT NULL AND "link_token_hash" IS NULL)
100
+ OR
101
+ ("grantee_kind" = 'link' AND "grantee_user_id" IS NULL AND "grantee_group_id" IS NULL AND "link_token_hash" IS NOT NULL)
102
+ );
103
+
104
+ -- =============================================================================
105
+ -- Row-level security, as in 0025_org_scoped_rls. The policy name is listed in
106
+ -- RLS_POLICIES (rls-policies.json) and asserted against pg_policies by the
107
+ -- drift test. NULLIF(..., '') because current_setting(name, true) is the empty
108
+ -- string on a connection that ever held a transaction-local value.
109
+ -- =============================================================================
110
+
111
+ ALTER TABLE "grants" ENABLE ROW LEVEL SECURITY;
112
+ ALTER TABLE "grants" FORCE ROW LEVEL SECURITY;
113
+ CREATE POLICY "grants_org_isolation" ON "grants"
114
+ USING ("org_id" = NULLIF(current_setting('app.org_id', true), '')::uuid
115
+ OR current_setting('app.rls_bypass', true) = 'on')
116
+ WITH CHECK ("org_id" = NULLIF(current_setting('app.org_id', true), '')::uuid
117
+ OR current_setting('app.rls_bypass', true) = 'on');
@@ -0,0 +1,49 @@
1
+ -- =============================================================================
2
+ -- Organization settings: one organization's overrides (PP-8.1, issue #733)
3
+ -- =============================================================================
4
+ --
5
+ -- One org-owned table, `org_settings`: one row per organization, holding only
6
+ -- the org-overridable fields of each system settings namespace that declares
7
+ -- an `org` block ({ "<namespace>": { "<field>": value } }), with its own
8
+ -- version for If-Match. NOT NULL org_id, ENABLE and FORCE ROW LEVEL SECURITY
9
+ -- and an org_settings_org_isolation policy copied from 0025_org_scoped_rls:
10
+ -- organization A's transaction never reads or writes organization B's row.
11
+ -- system_settings (deployment-level) and user_settings (user-owned) are
12
+ -- unchanged.
13
+ -- =============================================================================
14
+
15
+ -- CreateTable
16
+ CREATE TABLE "org_settings" (
17
+ "id" UUID NOT NULL,
18
+ "org_id" UUID NOT NULL,
19
+ "value" JSONB NOT NULL,
20
+ "version" INTEGER NOT NULL DEFAULT 1,
21
+ "updated_by_user_id" UUID,
22
+ "updated_at" TIMESTAMPTZ NOT NULL,
23
+
24
+ CONSTRAINT "org_settings_pkey" PRIMARY KEY ("id")
25
+ );
26
+
27
+ -- CreateIndex
28
+ CREATE UNIQUE INDEX "org_settings_org_id_key" ON "org_settings"("org_id");
29
+
30
+ -- AddForeignKey
31
+ ALTER TABLE "org_settings" ADD CONSTRAINT "org_settings_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
32
+
33
+ -- AddForeignKey
34
+ ALTER TABLE "org_settings" ADD CONSTRAINT "org_settings_updated_by_user_id_fkey" FOREIGN KEY ("updated_by_user_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
35
+
36
+ -- =============================================================================
37
+ -- Row-level security, as in 0025_org_scoped_rls. The policy name is listed in
38
+ -- RLS_POLICIES (rls-policies.json) and asserted against pg_policies by the
39
+ -- drift test. NULLIF(..., '') because current_setting(name, true) is the empty
40
+ -- string on a connection that ever held a transaction-local value.
41
+ -- =============================================================================
42
+
43
+ ALTER TABLE "org_settings" ENABLE ROW LEVEL SECURITY;
44
+ ALTER TABLE "org_settings" FORCE ROW LEVEL SECURITY;
45
+ CREATE POLICY "org_settings_org_isolation" ON "org_settings"
46
+ USING ("org_id" = NULLIF(current_setting('app.org_id', true), '')::uuid
47
+ OR current_setting('app.rls_bypass', true) = 'on')
48
+ WITH CHECK ("org_id" = NULLIF(current_setting('app.org_id', true), '')::uuid
49
+ OR current_setting('app.rls_bypass', true) = 'on');
@@ -0,0 +1,54 @@
1
+ -- =============================================================================
2
+ -- Organization credentials: the org tier of the credential store (PP-8.8, #735)
3
+ -- =============================================================================
4
+ --
5
+ -- One org-owned table, mirroring user_credentials with an organization as the
6
+ -- owner: org_credentials, addressed by (org_id, purpose, name), each secret
7
+ -- encrypted under the org-bound sub-key domain org:<orgId>:<purpose>.
8
+ --
9
+ -- A plain composite UNIQUE suffices (all three columns are NOT NULL), so no
10
+ -- raw-SQL index. The table is tenant data with a NOT NULL org_id and a FORCEd
11
+ -- org_credentials_org_isolation policy copied from 0025_org_scoped_rls.
12
+ --
13
+ -- No backfill: the table is new, so the policy is enabled at once.
14
+ -- =============================================================================
15
+
16
+ -- CreateTable
17
+ CREATE TABLE "org_credentials" (
18
+ "id" UUID NOT NULL,
19
+ "org_id" UUID NOT NULL,
20
+ "purpose" TEXT NOT NULL,
21
+ "name" TEXT NOT NULL,
22
+ "secret" TEXT NOT NULL,
23
+ "hint" TEXT,
24
+ "label" TEXT,
25
+ "updated_by_user_id" UUID,
26
+ "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
27
+ "updated_at" TIMESTAMPTZ NOT NULL,
28
+
29
+ CONSTRAINT "org_credentials_pkey" PRIMARY KEY ("id")
30
+ );
31
+
32
+ -- CreateIndex
33
+ CREATE UNIQUE INDEX "org_credentials_org_id_purpose_name_key" ON "org_credentials"("org_id", "purpose", "name");
34
+
35
+ -- AddForeignKey
36
+ ALTER TABLE "org_credentials" ADD CONSTRAINT "org_credentials_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
37
+
38
+ -- AddForeignKey
39
+ ALTER TABLE "org_credentials" ADD CONSTRAINT "org_credentials_updated_by_user_id_fkey" FOREIGN KEY ("updated_by_user_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
40
+
41
+ -- =============================================================================
42
+ -- Row-level security, as in 0025_org_scoped_rls. The policy name is listed in
43
+ -- RLS_POLICIES (rls-policies.json) and asserted against pg_policies by the
44
+ -- drift test. NULLIF(..., '') because current_setting(name, true) is the empty
45
+ -- string on a connection that ever held a transaction-local value.
46
+ -- =============================================================================
47
+
48
+ ALTER TABLE "org_credentials" ENABLE ROW LEVEL SECURITY;
49
+ ALTER TABLE "org_credentials" FORCE ROW LEVEL SECURITY;
50
+ CREATE POLICY "org_credentials_org_isolation" ON "org_credentials"
51
+ USING ("org_id" = NULLIF(current_setting('app.org_id', true), '')::uuid
52
+ OR current_setting('app.rls_bypass', true) = 'on')
53
+ WITH CHECK ("org_id" = NULLIF(current_setting('app.org_id', true), '')::uuid
54
+ OR current_setting('app.rls_bypass', true) = 'on');
@@ -0,0 +1,18 @@
1
+ -- Jobs carry the organization they belong to (issue #734, PP-8.2).
2
+ --
3
+ -- Nullable with no default: a metadata-only ADD COLUMN, no table rewrite.
4
+ -- NULL is a deployment-wide (system) job, and every row written before this
5
+ -- migration is one. No row-level security on `jobs` (the claim is one
6
+ -- cross-organization statement); see the `Job.orgId` comment in the jobs
7
+ -- fragment. The `(org_id, status)` index is built CONCURRENTLY in the next
8
+ -- migration, on its own, because `jobs` is a big table.
9
+
10
+ -- AlterTable
11
+ ALTER TABLE "jobs" ADD COLUMN "org_id" UUID;
12
+
13
+ -- AddForeignKey
14
+ -- NOT VALID then VALIDATE: the add takes its lock without scanning `jobs`,
15
+ -- and the validation scan runs under a lock that does not block writes.
16
+ -- ON DELETE SET NULL keeps an organization's job history when it is deleted.
17
+ ALTER TABLE "jobs" ADD CONSTRAINT "jobs_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "organizations"("id") ON DELETE SET NULL ON UPDATE CASCADE NOT VALID;
18
+ ALTER TABLE "jobs" VALIDATE CONSTRAINT "jobs_org_id_fkey";
@@ -0,0 +1,6 @@
1
+ -- "This organization's jobs by status" (issue #734, PP-8.2): the admin job
2
+ -- list's orgId filter and offboarding's cancel of an organization's pending
3
+ -- jobs. CONCURRENTLY, so building it never blocks the queue's writes, and
4
+ -- therefore the only statement in this migration (it cannot run inside a
5
+ -- transaction, and Prisma runs a single such statement outside one).
6
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS "jobs_org_id_status_idx" ON "jobs"("org_id", "status");
@@ -0,0 +1,21 @@
1
+ -- Broadcasts can target one organization (issue #738, PP-8.5).
2
+ --
3
+ -- Nullable with no default: a metadata-only ADD COLUMN. NULL is a system
4
+ -- broadcast (every active user of the deployment), and every row written
5
+ -- before this migration is one. No row-level security on
6
+ -- `notification_broadcasts`: a system broadcast belongs to no organization and
7
+ -- the delivering job must read it; the API authorizes `target_org_id` instead.
8
+ -- ON DELETE CASCADE: an organization's broadcasts have no audience once it is
9
+ -- deleted.
10
+
11
+ -- AlterTable
12
+ ALTER TABLE "notification_broadcasts" ADD COLUMN "target_org_id" UUID;
13
+
14
+ -- CreateIndex
15
+ CREATE INDEX "notification_broadcasts_target_org_id_idx" ON "notification_broadcasts"("target_org_id");
16
+
17
+ -- AddForeignKey
18
+ -- NOT VALID then VALIDATE, as for `jobs.org_id`: the add takes its lock
19
+ -- without scanning the table, and the validation scan does not block writes.
20
+ ALTER TABLE "notification_broadcasts" ADD CONSTRAINT "notification_broadcasts_target_org_id_fkey" FOREIGN KEY ("target_org_id") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE NOT VALID;
21
+ ALTER TABLE "notification_broadcasts" VALIDATE CONSTRAINT "notification_broadcasts_target_org_id_fkey";
@@ -0,0 +1,60 @@
1
+ -- The Android companion slice (#746, PP-9.4; merged from EvoPath and
2
+ -- MemoriaHub). Expand-only: one new table and one defaulted column.
3
+
4
+ -- CreateTable
5
+ CREATE TABLE "android_app_releases" (
6
+ "id" UUID NOT NULL,
7
+ "package_name" TEXT NOT NULL,
8
+ "version_name" VARCHAR(50) NOT NULL,
9
+ "version_code" INTEGER NOT NULL,
10
+ "signing_sha256" TEXT NOT NULL,
11
+ "file_sha256" CHAR(64) NOT NULL,
12
+ "size_bytes" BIGINT NOT NULL,
13
+ "storage_key" TEXT NOT NULL,
14
+ "storage_provider" TEXT,
15
+ "bucket" TEXT,
16
+ "notes" VARCHAR(2000),
17
+ "is_current" BOOLEAN NOT NULL DEFAULT false,
18
+ "uploaded_by_id" UUID,
19
+ "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
20
+
21
+ CONSTRAINT "android_app_releases_pkey" PRIMARY KEY ("id")
22
+ );
23
+
24
+ -- CreateIndex
25
+ CREATE INDEX "android_app_releases_created_at_idx" ON "android_app_releases"("created_at");
26
+
27
+ -- CreateIndex
28
+ CREATE INDEX "android_app_releases_uploaded_by_id_idx" ON "android_app_releases"("uploaded_by_id");
29
+
30
+ -- CreateIndex
31
+ CREATE UNIQUE INDEX "android_app_releases_package_name_version_code_key" ON "android_app_releases"("package_name", "version_code");
32
+
33
+ -- AddForeignKey
34
+ ALTER TABLE "android_app_releases" ADD CONSTRAINT "android_app_releases_uploaded_by_id_fkey" FOREIGN KEY ("uploaded_by_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
35
+
36
+ -- =============================================================================
37
+ -- Raw-SQL constraints (intentional schema drift: Prisma cannot express these).
38
+ -- Never declare android_app_releases_one_current_uniq_idx as @@unique and never
39
+ -- replace it with a findFirst pre-check; make-current clears the old flag and
40
+ -- sets the new one in one transaction, and the database rejects a second
41
+ -- current row (P2002, mapped to 409 RELEASE_CURRENT_CONFLICT).
42
+ -- =============================================================================
43
+
44
+ -- At most one current release deployment-wide.
45
+ CREATE UNIQUE INDEX "android_app_releases_one_current_uniq_idx" ON "android_app_releases" ((true)) WHERE "is_current";
46
+
47
+ -- Push subscription platform: tags each Web Push subscription with the surface
48
+ -- that registered it, so the `android_app` notification channel can target the
49
+ -- Android companion alone. Existing rows are browser subscriptions. A constant
50
+ -- default makes this a metadata-only ADD COLUMN (no table rewrite).
51
+
52
+ -- AlterTable
53
+ ALTER TABLE "push_subscriptions" ADD COLUMN "platform" TEXT NOT NULL DEFAULT 'browser';
54
+
55
+ -- Prisma cannot express a CHECK constraint, so it lives here only (intentional
56
+ -- schema drift, like the raw-SQL partial unique indexes). The contract's
57
+ -- PUSH_SUBSCRIPTION_PLATFORMS mirrors it.
58
+ ALTER TABLE "push_subscriptions"
59
+ ADD CONSTRAINT "push_subscriptions_platform_check"
60
+ CHECK ("platform" IN ('browser', 'android_app'));
@@ -0,0 +1,16 @@
1
+ -- Open the job-reason vocabulary: "jobs"."reason" goes from the closed
2
+ -- "JobReason" enum ('upload', 'rerun', 'backfill') to plain TEXT, so an app can
3
+ -- register its own reasons (`registerJobReason`) at zero migrations. Every
4
+ -- stored value is kept as it is: the cast is the enum's own label.
5
+ --
6
+ -- No CHECK constraint on purpose: a row must outlive the reason that wrote it,
7
+ -- and the allowed shape is the contract's `jobReasonSchema` pattern at the API
8
+ -- edge. "jobs" has no row-level security, so no bypass is needed here.
9
+ --
10
+ -- The column type changes in place (a table rewrite, taken under an ACCESS
11
+ -- EXCLUSIVE lock for its duration); no index covers "reason", so nothing is
12
+ -- rebuilt besides the heap. The enum type is dropped last: nothing else uses it.
13
+ ALTER TABLE "jobs" ALTER COLUMN "reason" TYPE TEXT USING "reason"::text;
14
+
15
+ -- DropEnum
16
+ DROP TYPE "JobReason";
@@ -0,0 +1,37 @@
1
+ -- =============================================================================
2
+ -- Per-feature AI usage (PP-14.17, #935)
3
+ -- =============================================================================
4
+ --
5
+ -- Persist the registered AI feature (`registerAiFeature`) a call was made for,
6
+ -- so usage can be filtered and grouped per feature from the database:
7
+ --
8
+ -- ai_runs.feature the feature a background run was started for
9
+ -- ai_usage_events.feature the feature a provider round trip was made for
10
+ --
11
+ -- Both columns are nullable plain TEXT: every row written before this
12
+ -- migration, a call made without a feature, and a catalogue sync have none.
13
+ -- No backfill, no default, no foreign key (a feature is code, not data, and a
14
+ -- row must outlive the feature that wrote it).
15
+ --
16
+ -- ai_runs and ai_usage_events are organization tables under forced row-level
17
+ -- security, so the migration runs with the bypass on for its transaction
18
+ -- (set_config with is_local = true: transaction-local, never session-level).
19
+ -- Adding a nullable column rewrites nothing; the index is built under the
20
+ -- table's SHARE lock, which blocks writes to ai_usage_events for the length of
21
+ -- the build (the table holds one row per provider round trip, an append-only
22
+ -- ledger a retention purge keeps bounded).
23
+ -- =============================================================================
24
+
25
+ BEGIN;
26
+ SELECT set_config('app.rls_bypass', 'on', true);
27
+
28
+ -- AlterTable
29
+ ALTER TABLE "ai_runs" ADD COLUMN "feature" TEXT;
30
+
31
+ -- AlterTable
32
+ ALTER TABLE "ai_usage_events" ADD COLUMN "feature" TEXT;
33
+
34
+ -- CreateIndex
35
+ CREATE INDEX "ai_usage_events_org_id_feature_created_at_idx" ON "ai_usage_events"("org_id", "feature", "created_at");
36
+
37
+ COMMIT;
@@ -0,0 +1,49 @@
1
+ -- =============================================================================
2
+ -- App metadata on users and organizations; composite-key targets (PP-14.27, #945)
3
+ -- =============================================================================
4
+ --
5
+ -- users.metadata and organizations.metadata are JSONB documents an app fills
6
+ -- with namespaced fields of its own (registerUserMetadataSchema,
7
+ -- registerOrganizationMetadataSchema): one top-level key per registered
8
+ -- namespace, validated against that namespace's zod schema on write. They are
9
+ -- not a secret store; a secret-named field is refused at registration.
10
+ --
11
+ -- NOT NULL DEFAULT '{}': every existing row gets an empty document; no backfill
12
+ -- and no rewrite (a constant default is stored in the catalog, PostgreSQL 11+).
13
+ --
14
+ -- The size CHECK is a backstop, not the contract. The writer enforces 16 KiB of
15
+ -- compact JSON per row; the constraint allows 32 KiB of jsonb text, because the
16
+ -- text form of a jsonb value carries a space after every colon and comma, so
17
+ -- the two measures differ by a few percent. A constraint that matched the
18
+ -- writer's limit exactly would reject a document the writer accepted.
19
+ --
20
+ -- ai_runs and grants get a unique index on (id, org_id): the target of a
21
+ -- composite foreign key (grant_id, org_id) from an organization-owned table an
22
+ -- app adds, exactly as groups and storage_objects already are. A plain key on
23
+ -- id alone would let a row of one organization point at another's, since a
24
+ -- foreign-key check bypasses row-level security. No existing key changes.
25
+ --
26
+ -- ai_runs and grants are organization tables under forced row-level security,
27
+ -- so the migration runs with the bypass on for its transaction (set_config with
28
+ -- is_local = true: transaction-local, never session-level). users and
29
+ -- organizations have no row-level security.
30
+ -- =============================================================================
31
+
32
+ BEGIN;
33
+ SELECT set_config('app.rls_bypass', 'on', true);
34
+
35
+ -- AlterTable
36
+ ALTER TABLE "users" ADD COLUMN "metadata" JSONB NOT NULL DEFAULT '{}';
37
+ ALTER TABLE "users" ADD CONSTRAINT "users_metadata_size_check" CHECK (jsonb_typeof("metadata") = 'object' AND octet_length("metadata"::text) <= 32768);
38
+
39
+ -- AlterTable
40
+ ALTER TABLE "organizations" ADD COLUMN "metadata" JSONB NOT NULL DEFAULT '{}';
41
+ ALTER TABLE "organizations" ADD CONSTRAINT "organizations_metadata_size_check" CHECK (jsonb_typeof("metadata") = 'object' AND octet_length("metadata"::text) <= 32768);
42
+
43
+ -- CreateIndex
44
+ CREATE UNIQUE INDEX "ai_runs_id_org_id_key" ON "ai_runs"("id", "org_id");
45
+
46
+ -- CreateIndex
47
+ CREATE UNIQUE INDEX "grants_id_org_id_key" ON "grants"("id", "org_id");
48
+
49
+ COMMIT;