@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,311 @@
1
+ -- =============================================================================
2
+ -- Notes (issue #48, epic #45)
3
+ -- =============================================================================
4
+ -- Five tables: `notes`, `note_templates`, `note_generations`, `note_versions`,
5
+ -- `note_exports`. See the block comment above the `Note` model in
6
+ -- prisma/schema.prisma for the full reasoning behind every `onDelete` choice
7
+ -- below (in particular why `owner_id` cascades while the three source
8
+ -- pointers restrict, why `template_id` is `SetNull` rather than `Restrict`,
9
+ -- why `note_generations.job_id` mirrors `DatabaseBackupRun.jobId`/
10
+ -- `TranscriptExport.jobId`, and why `note_exports`' content-addressed lookup
11
+ -- is a plain non-unique index rather than the `@@unique` docs/specs/notes.md
12
+ -- §4.6's prose names), and docs/specs/notes.md §1, §4, §6, §7 for the
13
+ -- product reasoning this file intentionally does not restate at length.
14
+ --
15
+ -- NO HAND-WRITTEN, PRISMA-INEXPRESSIBLE CONSTRAINTS IN THIS MIGRATION, unlike
16
+ -- `20260914130000_add_transcripts` (which needed one, a partial unique index
17
+ -- for `transcript_speakers.label`). Every unique constraint below
18
+ -- (`note_templates` on `(owner_id, name)`, `note_generations.job_id`,
19
+ -- `note_versions` on `(note_id, version)` and `(note_id, client_batch_id)`,
20
+ -- `note_exports.job_id`) is a plain, fully Prisma-expressible unique index,
21
+ -- and the content-addressed export lookup is a plain non-unique index —
22
+ -- there is nothing here Prisma's own DSL cannot say.
23
+ -- =============================================================================
24
+
25
+ -- CreateEnum
26
+ CREATE TYPE "NoteStatus" AS ENUM ('draft', 'generating', 'ready', 'failed', 'deleting');
27
+
28
+ -- CreateEnum
29
+ CREATE TYPE "NoteSourceType" AS ENUM ('transcript', 'note', 'document');
30
+
31
+ -- CreateEnum
32
+ CREATE TYPE "NoteGenerationKind" AS ENUM ('create', 'regenerate', 'preview');
33
+
34
+ -- CreateEnum
35
+ CREATE TYPE "NoteGenerationStatus" AS ENUM ('pending', 'streaming', 'succeeded', 'failed');
36
+
37
+ -- CreateEnum
38
+ CREATE TYPE "NoteGenerationErrorClass" AS ENUM ('auth', 'refusal', 'rate_limit', 'other');
39
+
40
+ -- CreateEnum
41
+ CREATE TYPE "NoteVersionKind" AS ENUM ('ai_generated', 'edit', 'restore');
42
+
43
+ -- CreateEnum
44
+ CREATE TYPE "NoteExportStatus" AS ENUM ('pending', 'ready', 'failed');
45
+
46
+ -- CreateTable
47
+ CREATE TABLE "notes" (
48
+ "id" UUID NOT NULL,
49
+ "owner_id" UUID NOT NULL,
50
+ "title" TEXT NOT NULL,
51
+ "body" TEXT NOT NULL DEFAULT '',
52
+ "status" "NoteStatus" NOT NULL DEFAULT 'draft',
53
+ "current_version" INTEGER NOT NULL DEFAULT 0,
54
+ "provider" TEXT,
55
+ "model" TEXT,
56
+ "current_generation_id" UUID,
57
+ "source_type" "NoteSourceType" NOT NULL,
58
+ "source_transcript_id" UUID,
59
+ "source_note_id" UUID,
60
+ "source_object_id" UUID,
61
+ "template_id" UUID,
62
+ "context_text" TEXT,
63
+ "failure_reason" TEXT,
64
+ "deleted_at" TIMESTAMPTZ,
65
+ "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
66
+ "updated_at" TIMESTAMPTZ NOT NULL,
67
+
68
+ CONSTRAINT "notes_pkey" PRIMARY KEY ("id")
69
+ );
70
+
71
+ -- CreateTable
72
+ CREATE TABLE "note_templates" (
73
+ "id" UUID NOT NULL,
74
+ "owner_id" UUID,
75
+ "name" TEXT NOT NULL,
76
+ "description" TEXT NOT NULL,
77
+ "instructions" TEXT NOT NULL,
78
+ "output_format" TEXT NOT NULL,
79
+ "structure" JSONB NOT NULL,
80
+ "tone" TEXT,
81
+ "length" TEXT,
82
+ "model" TEXT,
83
+ "is_archived" BOOLEAN NOT NULL DEFAULT false,
84
+ "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
85
+ "updated_at" TIMESTAMPTZ NOT NULL,
86
+
87
+ CONSTRAINT "note_templates_pkey" PRIMARY KEY ("id")
88
+ );
89
+
90
+ -- CreateTable
91
+ CREATE TABLE "note_generations" (
92
+ "id" UUID NOT NULL,
93
+ "note_id" UUID,
94
+ "kind" "NoteGenerationKind" NOT NULL,
95
+ "status" "NoteGenerationStatus" NOT NULL DEFAULT 'pending',
96
+ "error_class" "NoteGenerationErrorClass",
97
+ "error_detail" TEXT,
98
+ "template_id" UUID,
99
+ "template_name_snapshot" TEXT NOT NULL,
100
+ "context_text" TEXT,
101
+ "source_type" "NoteSourceType" NOT NULL,
102
+ "source_transcript_id" UUID,
103
+ "source_note_id" UUID,
104
+ "source_object_id" UUID,
105
+ "provider_id" TEXT NOT NULL,
106
+ "model" TEXT NOT NULL,
107
+ "content" TEXT NOT NULL DEFAULT '',
108
+ "last_event_id" INTEGER NOT NULL DEFAULT 0,
109
+ "prompt_tokens" INTEGER,
110
+ "completion_tokens" INTEGER,
111
+ "job_id" UUID,
112
+ "expires_at" TIMESTAMPTZ,
113
+ "started_at" TIMESTAMPTZ,
114
+ "completed_at" TIMESTAMPTZ,
115
+ "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
116
+
117
+ CONSTRAINT "note_generations_pkey" PRIMARY KEY ("id")
118
+ );
119
+
120
+ -- CreateTable
121
+ CREATE TABLE "note_versions" (
122
+ "id" UUID NOT NULL,
123
+ "note_id" UUID NOT NULL,
124
+ "version" INTEGER NOT NULL,
125
+ "kind" "NoteVersionKind" NOT NULL,
126
+ "body" TEXT NOT NULL,
127
+ "summary" TEXT,
128
+ "author_id" UUID,
129
+ "generation_id" UUID,
130
+ "restored_from_version" INTEGER,
131
+ "client_batch_id" TEXT,
132
+ "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
133
+
134
+ CONSTRAINT "note_versions_pkey" PRIMARY KEY ("id")
135
+ );
136
+
137
+ -- CreateTable
138
+ CREATE TABLE "note_exports" (
139
+ "id" UUID NOT NULL,
140
+ "note_id" UUID NOT NULL,
141
+ "version" INTEGER NOT NULL,
142
+ "format" TEXT NOT NULL,
143
+ "options" JSONB NOT NULL,
144
+ "options_hash" TEXT NOT NULL,
145
+ "status" "NoteExportStatus" NOT NULL DEFAULT 'pending',
146
+ "object_id" UUID,
147
+ "job_id" UUID,
148
+ "requested_by_id" UUID NOT NULL,
149
+ "error" TEXT,
150
+ "expires_at" TIMESTAMPTZ NOT NULL,
151
+ "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
152
+
153
+ CONSTRAINT "note_exports_pkey" PRIMARY KEY ("id")
154
+ );
155
+
156
+ -- CreateIndex
157
+ -- "This owner's notes, newest first" — the notes library list view.
158
+ CREATE INDEX "notes_owner_id_updated_at_idx" ON "notes"("owner_id", "updated_at" DESC);
159
+
160
+ -- CreateIndex
161
+ -- "Every note in state X" — notes.housekeeping's scans (#53) and any future
162
+ -- admin-adjacent health check.
163
+ CREATE INDEX "notes_status_idx" ON "notes"("status");
164
+
165
+ -- CreateIndex
166
+ -- A user may not have two templates sharing a name (issue #48). PostgreSQL's
167
+ -- standard NULLS-DISTINCT behaviour means this is a no-op among built-ins —
168
+ -- every built-in's owner_id is permanently NULL, and NULL is never equal to
169
+ -- NULL for uniqueness purposes, so any number of built-in rows may coexist
170
+ -- regardless of name. See the block comment above the Note model in
171
+ -- schema.prisma for the full reasoning.
172
+ CREATE UNIQUE INDEX "note_templates_owner_id_name_key" ON "note_templates"("owner_id", "name");
173
+
174
+ -- CreateIndex
175
+ -- One note_generations row per producing job — mirrors
176
+ -- note_exports_job_id_key/transcript_exports_job_id_key exactly: a second
177
+ -- generation claiming the SAME job is unrepresentable.
178
+ CREATE UNIQUE INDEX "note_generations_job_id_key" ON "note_generations"("job_id");
179
+
180
+ -- CreateIndex
181
+ -- The version sequence itself.
182
+ CREATE UNIQUE INDEX "note_versions_note_id_version_key" ON "note_versions"("note_id", "version");
183
+
184
+ -- CreateIndex
185
+ -- The idempotent-retry key for a batch save (#53). A plain UNIQUE index,
186
+ -- deliberately not hand-written: PostgreSQL's standard NULLS-DISTINCT
187
+ -- behaviour already gives exactly the wanted semantics for this nullable
188
+ -- column (any number of null-client_batch_id versions, e.g. AI-generated or
189
+ -- restore, may coexist; at most one per note for any given non-null value).
190
+ CREATE UNIQUE INDEX "note_versions_note_id_client_batch_id_key" ON "note_versions"("note_id", "client_batch_id");
191
+
192
+ -- CreateIndex
193
+ -- One note_exports row per rendering job — mirrors
194
+ -- transcript_exports_job_id_key exactly: a second render for the SAME job is
195
+ -- unrepresentable.
196
+ CREATE UNIQUE INDEX "note_exports_job_id_key" ON "note_exports"("job_id");
197
+
198
+ -- CreateIndex
199
+ -- The reuse-by-content-hash lookup (docs/specs/notes.md §8.4). Deliberately
200
+ -- NOT unique — mirrors transcript_exports_lookup_idx's real shape rather
201
+ -- than spec §4.6's own shorthand "unique" wording: an expired or failed row
202
+ -- may briefly coexist with a fresh one sharing the same key before
203
+ -- housekeeping removes the stale one. Named explicitly
204
+ -- (`map: "note_exports_lookup_idx"` in schema.prisma) because the default
205
+ -- four-column name is one byte over PostgreSQL's 63-byte identifier limit.
206
+ CREATE INDEX "note_exports_lookup_idx" ON "note_exports"("note_id", "version", "format", "options_hash");
207
+
208
+ -- AddForeignKey
209
+ -- Cascade: a note's only reason to exist is that exactly one user owns it,
210
+ -- and this application's access model has no path to it once its owner is
211
+ -- gone (no notes:read_any, docs/specs/notes.md §6.3). See the block comment
212
+ -- above the Note model for the full reasoning.
213
+ ALTER TABLE "notes" ADD CONSTRAINT "notes_owner_id_fkey" FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
214
+
215
+ -- AddForeignKey
216
+ -- SetNull: losing the pointer loses only "which generation is live right
217
+ -- now," never the generation row itself or any note_versions row it already
218
+ -- produced.
219
+ ALTER TABLE "notes" ADD CONSTRAINT "notes_current_generation_id_fkey" FOREIGN KEY ("current_generation_id") REFERENCES "note_generations"("id") ON DELETE SET NULL ON UPDATE CASCADE;
220
+
221
+ -- AddForeignKey
222
+ -- Restrict, with a REAL user-visible consequence unlike the belt-and-
223
+ -- suspenders Restrict on storage_objects below: a transcript that has
224
+ -- produced a note cannot be deleted while that note still exists.
225
+ -- DELETE /api/transcripts/:id gains a pre-check that 409s naming the
226
+ -- blocking note id(s) before this constraint is ever reached (see the block
227
+ -- comment above the Note model — that pre-check is backend work landing
228
+ -- alongside this migration, not part of this migration itself).
229
+ ALTER TABLE "notes" ADD CONSTRAINT "notes_source_transcript_id_fkey" FOREIGN KEY ("source_transcript_id") REFERENCES "transcripts"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
230
+
231
+ -- AddForeignKey
232
+ -- Restrict, self-referencing: a note cited as another note's source cannot
233
+ -- be deleted while that derivative note still exists. #53's own
234
+ -- notes.service.ts delete guard is the note-on-note counterpart to the
235
+ -- transcript pre-check above.
236
+ ALTER TABLE "notes" ADD CONSTRAINT "notes_source_note_id_fkey" FOREIGN KEY ("source_note_id") REFERENCES "notes"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
237
+
238
+ -- AddForeignKey
239
+ -- Restrict, belt-and-suspenders: the uploaded document is
240
+ -- managed_by: 'notes', so the generic storage DELETE already 409s on it
241
+ -- before this constraint is ever tested.
242
+ ALTER TABLE "notes" ADD CONSTRAINT "notes_source_object_id_fkey" FOREIGN KEY ("source_object_id") REFERENCES "storage_objects"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
243
+
244
+ -- AddForeignKey
245
+ -- SetNull, not Restrict: a template is a reusable recipe, not evidence.
246
+ -- note_generations.template_name_snapshot keeps "which recipe produced this"
247
+ -- as a plain string forever, independent of whether the template row itself
248
+ -- survives.
249
+ ALTER TABLE "notes" ADD CONSTRAINT "notes_template_id_fkey" FOREIGN KEY ("template_id") REFERENCES "note_templates"("id") ON DELETE SET NULL ON UPDATE CASCADE;
250
+
251
+ -- AddForeignKey
252
+ -- Cascade: a user's own custom template has no meaning once its owner is
253
+ -- gone. Never fires for a built-in row, whose owner_id is permanently NULL.
254
+ ALTER TABLE "note_templates" ADD CONSTRAINT "note_templates_owner_id_fkey" FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
255
+
256
+ -- AddForeignKey
257
+ -- Cascade: a note_generations row belongs entirely to the note that owns
258
+ -- it (NULL note_id for a preview, which has none).
259
+ ALTER TABLE "note_generations" ADD CONSTRAINT "note_generations_note_id_fkey" FOREIGN KEY ("note_id") REFERENCES "notes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
260
+
261
+ -- AddForeignKey
262
+ -- SetNull, matching notes.template_id above.
263
+ ALTER TABLE "note_generations" ADD CONSTRAINT "note_generations_template_id_fkey" FOREIGN KEY ("template_id") REFERENCES "note_templates"("id") ON DELETE SET NULL ON UPDATE CASCADE;
264
+
265
+ -- AddForeignKey
266
+ -- SetNull, mirroring note_exports_job_id_fkey/transcript_exports_job_id_fkey
267
+ -- exactly: job.history.purge deletes jobs rows on a retention schedule
268
+ -- independent of this row's own lifetime, and losing the LINK must never
269
+ -- mean losing the GENERATION (its buffered content is what a
270
+ -- note_versions.ai_generated row was stamped from).
271
+ ALTER TABLE "note_generations" ADD CONSTRAINT "note_generations_job_id_fkey" FOREIGN KEY ("job_id") REFERENCES "jobs"("id") ON DELETE SET NULL ON UPDATE CASCADE;
272
+
273
+ -- AddForeignKey
274
+ ALTER TABLE "note_versions" ADD CONSTRAINT "note_versions_note_id_fkey" FOREIGN KEY ("note_id") REFERENCES "notes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
275
+
276
+ -- AddForeignKey
277
+ -- SetNull: a version is permanent history and must survive its human
278
+ -- author's own account being deleted. NULL already means "the AI" for a
279
+ -- different reason (note.generate never sets an author at all) — this FK
280
+ -- action only ever fires for a human-authored version whose author was
281
+ -- later deleted.
282
+ ALTER TABLE "note_versions" ADD CONSTRAINT "note_versions_author_id_fkey" FOREIGN KEY ("author_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
283
+
284
+ -- AddForeignKey
285
+ -- SetNull: the version's body already carries everything durable; losing
286
+ -- the pointer back to the generation that produced it loses only "which
287
+ -- streaming run wrote this," never the content itself.
288
+ ALTER TABLE "note_versions" ADD CONSTRAINT "note_versions_generation_id_fkey" FOREIGN KEY ("generation_id") REFERENCES "note_generations"("id") ON DELETE SET NULL ON UPDATE CASCADE;
289
+
290
+ -- AddForeignKey
291
+ ALTER TABLE "note_exports" ADD CONSTRAINT "note_exports_note_id_fkey" FOREIGN KEY ("note_id") REFERENCES "notes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
292
+
293
+ -- AddForeignKey
294
+ -- Restrict, matching every storage_objects FK on the transcripts feature:
295
+ -- only notes.housekeeping's expiry sweep may remove the rendered file, and
296
+ -- only after deleting this row first.
297
+ ALTER TABLE "note_exports" ADD CONSTRAINT "note_exports_object_id_fkey" FOREIGN KEY ("object_id") REFERENCES "storage_objects"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
298
+
299
+ -- AddForeignKey
300
+ -- SetNull, mirroring transcript_exports_job_id_fkey exactly: job.history
301
+ -- .purge deletes jobs rows on a retention schedule independent of this
302
+ -- row's own 7-day export expiry, and losing the LINK must never mean losing
303
+ -- the EXPORT.
304
+ ALTER TABLE "note_exports" ADD CONSTRAINT "note_exports_job_id_fkey" FOREIGN KEY ("job_id") REFERENCES "jobs"("id") ON DELETE SET NULL ON UPDATE CASCADE;
305
+
306
+ -- AddForeignKey
307
+ -- Cascade: unlike every SetNull audit column elsewhere in this schema, an
308
+ -- export is a disposable, byte-for-byte reproducible artifact with a 7-day
309
+ -- expiry already built in — nothing is lost by deleting a pending/ready
310
+ -- export row alongside the account that requested it.
311
+ ALTER TABLE "note_exports" ADD CONSTRAINT "note_exports_requested_by_id_fkey" FOREIGN KEY ("requested_by_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,150 @@
1
+ -- =============================================================================
2
+ -- Full-text search: generated tsvector columns + GIN indexes (issue #174,
3
+ -- epic #164)
4
+ -- =============================================================================
5
+ --
6
+ -- ⚠ THIS MIGRATION TAKES `ACCESS EXCLUSIVE` ON THREE TABLES AND REWRITES
7
+ -- THEM: `transcript_segments`, `notes`, `transcripts`. `ADD COLUMN ...
8
+ -- GENERATED ALWAYS AS (...) STORED` is not a metadata-only change — Postgres
9
+ -- computes the expression for every existing row and rewrites the whole
10
+ -- table to store it, holding `ACCESS EXCLUSIVE` on that table for the
11
+ -- duration (no reads, no writes, nothing, from any other session). On a
12
+ -- large corpus this is MINUTES of a hard lock per table. An operator running
13
+ -- this against production must plan a maintenance window (or accept the
14
+ -- outage) exactly the way a large `ALTER TABLE` anywhere else in this
15
+ -- codebase would require — this is not a `CREATE INDEX CONCURRENTLY`
16
+ -- migration and cannot be made into one, because a generated column's
17
+ -- initial computation is inherently a table rewrite, not an index build.
18
+ --
19
+ -- AND THAT REWRITE IS THE BACKFILL. There is no job, no API key, no
20
+ -- on-demand action, no cron sweep that populates these columns after the
21
+ -- fact. The moment this migration commits, `search_vector`/
22
+ -- `title_search_vector` exist and are already correct for every row already
23
+ -- in the table, because Postgres computed them as part of the same rewrite
24
+ -- that added the column. Every transcript segment, every note, every
25
+ -- transcript title already in this database is searchable the instant this
26
+ -- lands, with zero application code ever running. That is the entire reason
27
+ -- full-text search is layer 1 of epic #164: it needed no ingestion pipeline
28
+ -- of its own, only this one migration.
29
+ --
30
+ -- GENERATED COLUMNS, NOT TRIGGERS — and the reason is RESTORE, not tidiness.
31
+ -- This repository ships a real database restore
32
+ -- (docs/specs/database-restore.md), and `pg_restore` loads data with
33
+ -- `session_replication_role = replica` in effect for the load session (the
34
+ -- same mode `pg_restore --disable-triggers` documents). A regular
35
+ -- `AFTER INSERT/UPDATE` trigger DOES NOT FIRE under that mode — so a
36
+ -- trigger-based implementation would restore a database that LOOKS whole
37
+ -- (every row present, every column present) but whose search index is
38
+ -- silently empty, with nothing anywhere — no error, no warning, no failed
39
+ -- job — telling anyone it happened. A `GENERATED ... STORED` column is
40
+ -- computed by the storage layer itself on every tuple, independent of
41
+ -- triggers and independent of `session_replication_role`, and cannot be
42
+ -- switched off by a restore path that has no idea this column exists.
43
+ --
44
+ -- THE TWO-ARGUMENT `to_tsvector(regconfig, text)` FORM IS LOAD-BEARING.
45
+ -- `to_tsvector(text)` — the one-argument form — is STABLE, not IMMUTABLE:
46
+ -- it reads `default_text_search_config` from session state to decide which
47
+ -- configuration to use, and a generated column's expression is REQUIRED by
48
+ -- Postgres to be IMMUTABLE (a value that must be recomputable identically
49
+ -- from the row alone, forever, independent of session/GUC state). Postgres
50
+ -- enforces this at `ALTER TABLE` time — `ADD COLUMN ... GENERATED ALWAYS AS
51
+ -- (to_tsvector(coalesce(text, ''))) STORED` is REJECTED outright with
52
+ -- "generation expression is not immutable". The two-argument form used
53
+ -- below, with `'english'::regconfig` as an explicit literal, has no such
54
+ -- session dependency and IS immutable — it is the ONLY form Postgres will
55
+ -- accept here. Do not "simplify" this to the one-argument form: it does not
56
+ -- merely behave differently, it does not apply at all, and the failure mode
57
+ -- is this whole migration erroring out rather than something subtly wrong.
58
+ -- A successful `prisma migrate` against a real Postgres (as this issue's
59
+ -- verification step requires) is the actual proof the expression is
60
+ -- immutable — there is no static check that substitutes for it.
61
+ --
62
+ -- THE `'english'` CONFIGURATION IS A DELIBERATE, STATED v1 LIMITATION, not
63
+ -- an oversight. `transcripts.language` exists and is nullable, so a
64
+ -- per-row configuration is technically expressible (Postgres allows a
65
+ -- generated expression to reference sibling columns) — but it would need a
66
+ -- `regconfig` cast built from arbitrary, unvalidated row data (an invalid
67
+ -- or unrecognized language string then fails the whole insert/update, not
68
+ -- just the search feature), and a GIN index built against a per-row-varying
69
+ -- configuration is effectively unusable for a query issued before the
70
+ -- caller knows which language a given row is in. One corpus-wide
71
+ -- configuration, stated plainly here, is the v1 choice; a later issue can
72
+ -- revisit it once there is a real multi-language corpus to justify the
73
+ -- complexity. The cost of this choice: English-language stemming
74
+ -- ("discussed" matching "discuss") is WRONG for non-English content —
75
+ -- `to_tsvector('english', ...)` will still tokenize non-English text, so
76
+ -- exact-token matching keeps working, but stemming, stop-word removal and
77
+ -- ranking are all tuned for English regardless of what a row actually
78
+ -- contains.
79
+ --
80
+ -- WHY `notes.search_vector` IS WEIGHTED A/B AND `transcript_segments
81
+ -- .search_vector` IS NOT: a note has a `title` the user themselves wrote
82
+ -- and a `body` the AI generated (or the user has since edited) — a hit in
83
+ -- the title is real, human-authored evidence the note is about that term,
84
+ -- and `setweight(..., 'A')` on the title plus `setweight(..., 'B')` on the
85
+ -- body lets `ts_rank`/`ts_rank_cd` reflect that when a search matches both
86
+ -- lists. A `transcript_segments` row is one line of spoken text with no
87
+ -- title of its own to weight against — there is nothing to distinguish, so
88
+ -- its vector is a single unweighted `to_tsvector` over `text`.
89
+ --
90
+ -- WHY `transcripts.title_search_vector` EXISTS AS ITS OWN COLUMN rather
91
+ -- than being folded into (or replaced by) the segment vector: a
92
+ -- transcript's title lives on the PARENT row, one string, while its
93
+ -- segments are a separate child table with a many-to-one relationship back
94
+ -- to it. A title match has to be able to surface a transcript that has NO
95
+ -- matching segment at all (a recording titled "Q3 Pricing Review" whose
96
+ -- spoken content never says the word "pricing") — which is only possible
97
+ -- if the title has its own indexed vector to match against, independent of
98
+ -- whatever its segments do or do not contain.
99
+ --
100
+ -- INTENTIONAL SCHEMA DRIFT, named the same way `jobs_active_dedup_uniq_idx`
101
+ -- and `database_backup_runs_active_uniq_idx` are named elsewhere in this
102
+ -- repository: every generation expression below, and all three GIN
103
+ -- indexes, are HAND-WRITTEN in this file only. `schema.prisma` has no DSL
104
+ -- for a generated column's expression and no DSL for an index's access
105
+ -- method (`USING gin`) — the Prisma model below can only declare the
106
+ -- columns exist (as `Unsupported("tsvector")?`) and point a reader here.
107
+ -- `prisma migrate dev`/`diff` will never regenerate this file's contents
108
+ -- and must never be asked to "reconcile" it away. This is the fourth
109
+ -- occurrence of this exact pattern in this schema (`jobs`,
110
+ -- `database_backup_runs`, `transcript_speakers` before it) — see each of
111
+ -- those tables' own migration headers for the precedent.
112
+ -- =============================================================================
113
+
114
+ -- AlterTable: transcript_segments — unweighted, one line of speech with no
115
+ -- title of its own. See the header above for why the two-argument
116
+ -- `to_tsvector` form is REQUIRED (the one-argument form is rejected as
117
+ -- non-immutable).
118
+ ALTER TABLE "transcript_segments"
119
+ ADD COLUMN "search_vector" tsvector
120
+ GENERATED ALWAYS AS (to_tsvector('english'::regconfig, coalesce("text", ''))) STORED;
121
+
122
+ -- CreateIndex
123
+ CREATE INDEX "transcript_segments_search_vector_idx"
124
+ ON "transcript_segments" USING GIN ("search_vector");
125
+
126
+ -- AlterTable: notes — weighted A (title, human-authored) / B (body,
127
+ -- AI-generated or user-edited). See the header above for the ranking
128
+ -- argument this weighting exists to support.
129
+ ALTER TABLE "notes"
130
+ ADD COLUMN "search_vector" tsvector
131
+ GENERATED ALWAYS AS (
132
+ setweight(to_tsvector('english'::regconfig, coalesce("title", '')), 'A') ||
133
+ setweight(to_tsvector('english'::regconfig, coalesce("body", '')), 'B')
134
+ ) STORED;
135
+
136
+ -- CreateIndex
137
+ CREATE INDEX "notes_search_vector_idx"
138
+ ON "notes" USING GIN ("search_vector");
139
+
140
+ -- AlterTable: transcripts — the title lives on the parent row and must be
141
+ -- searchable even when no child segment matches at all. See the header
142
+ -- above for why this is a separate column rather than folded into the
143
+ -- segment vector.
144
+ ALTER TABLE "transcripts"
145
+ ADD COLUMN "title_search_vector" tsvector
146
+ GENERATED ALWAYS AS (to_tsvector('english'::regconfig, coalesce("title", ''))) STORED;
147
+
148
+ -- CreateIndex
149
+ CREATE INDEX "transcripts_title_search_vector_idx"
150
+ ON "transcripts" USING GIN ("title_search_vector");