@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.
- package/CHANGELOG.md +14 -0
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/dist/ask/index.d.ts +6 -0
- package/dist/ask/index.js +28 -0
- package/dist/ask/question-schema.d.ts +121 -0
- package/dist/ask/question-schema.js +167 -0
- package/dist/ask/report.d.ts +88 -0
- package/dist/ask/report.js +170 -0
- package/dist/ask/scorer.d.ts +104 -0
- package/dist/ask/scorer.js +164 -0
- package/dist/ask-run/ask-command.d.ts +62 -0
- package/dist/ask-run/ask-command.js +254 -0
- package/dist/ask-run/host/app.d.ts +65 -0
- package/dist/ask-run/host/app.js +146 -0
- package/dist/ask-run/host/db.d.ts +38 -0
- package/dist/ask-run/host/db.js +126 -0
- package/dist/ask-run/host/ports.d.ts +35 -0
- package/dist/ask-run/host/ports.js +113 -0
- package/dist/ask-run/index.d.ts +13 -0
- package/dist/ask-run/index.js +35 -0
- package/dist/ask-run/replay.d.ts +128 -0
- package/dist/ask-run/replay.js +212 -0
- package/dist/ask-run/run-ask-eval.d.ts +125 -0
- package/dist/ask-run/run-ask-eval.js +193 -0
- package/dist/ask-run/seed-golden-graph.d.ts +64 -0
- package/dist/ask-run/seed-golden-graph.js +495 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +12 -0
- package/dist/cli/kg-command.d.ts +66 -0
- package/dist/cli/kg-command.js +238 -0
- package/dist/cli/main.d.ts +18 -0
- package/dist/cli/main.js +69 -0
- package/dist/fixtures/domains.d.ts +16 -0
- package/dist/fixtures/domains.js +33 -0
- package/dist/fixtures/fixture-schema.d.ts +323 -0
- package/dist/fixtures/fixture-schema.js +199 -0
- package/dist/fixtures/index.d.ts +16 -0
- package/dist/fixtures/index.js +40 -0
- package/dist/fixtures/load.d.ts +73 -0
- package/dist/fixtures/load.js +137 -0
- package/dist/fixtures/questions.d.ts +15 -0
- package/dist/fixtures/questions.js +26 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +36 -0
- package/dist/kg/gold-runner.d.ts +13 -0
- package/dist/kg/gold-runner.js +57 -0
- package/dist/kg/index.d.ts +17 -0
- package/dist/kg/index.js +65 -0
- package/dist/kg/internals.d.ts +7 -0
- package/dist/kg/internals.js +17 -0
- package/dist/kg/prediction-schema.d.ts +245 -0
- package/dist/kg/prediction-schema.js +102 -0
- package/dist/kg/predictions.d.ts +11 -0
- package/dist/kg/predictions.js +49 -0
- package/dist/kg/report.d.ts +84 -0
- package/dist/kg/report.js +125 -0
- package/dist/kg/run-kg-eval.d.ts +77 -0
- package/dist/kg/run-kg-eval.js +137 -0
- package/dist/kg/runner.d.ts +72 -0
- package/dist/kg/runner.js +51 -0
- package/dist/kg/runners/extract-resolve-runner.d.ts +40 -0
- package/dist/kg/runners/extract-resolve-runner.js +199 -0
- package/dist/kg/runners/extract-runner.d.ts +65 -0
- package/dist/kg/runners/extract-runner.js +243 -0
- package/dist/kg/scorer.d.ts +226 -0
- package/dist/kg/scorer.js +463 -0
- package/dist/kg/targets.d.ts +17 -0
- package/dist/kg/targets.js +16 -0
- package/dist/kg/text.d.ts +45 -0
- package/dist/kg/text.js +100 -0
- package/dist/live/adapters.d.ts +16 -0
- package/dist/live/adapters.js +58 -0
- package/dist/live/env.d.ts +39 -0
- package/dist/live/env.js +56 -0
- package/dist/live/index.d.ts +6 -0
- package/dist/live/index.js +16 -0
- package/dist/live/live-ai.d.ts +104 -0
- package/dist/live/live-ai.js +157 -0
- package/dist/parity/constants.d.ts +79 -0
- package/dist/parity/constants.js +104 -0
- package/dist/parity/index.d.ts +20 -0
- package/dist/parity/index.js +38 -0
- package/dist/parity/internals.d.ts +8 -0
- package/dist/parity/internals.js +16 -0
- package/dist/parity/scenario.d.ts +45 -0
- package/dist/parity/scenario.js +245 -0
- package/dist/parity/script.d.ts +44 -0
- package/dist/parity/script.js +136 -0
- package/dist/parity/snapshot.d.ts +43 -0
- package/dist/parity/snapshot.js +71 -0
- package/dist/real-data/index.d.ts +2 -0
- package/dist/real-data/index.js +12 -0
- package/dist/real-data/real-data.d.ts +76 -0
- package/dist/real-data/real-data.js +156 -0
- package/fixtures/kg-golden/README.md +136 -0
- package/fixtures/kg-golden/ask-questions.json +458 -0
- package/fixtures/kg-golden/meetings/m01-northwind-pilot-kickoff.json +792 -0
- package/fixtures/kg-golden/meetings/m02-halden-migration-planning.json +814 -0
- package/fixtures/kg-golden/meetings/m03-larkspur-weekly-sync.json +601 -0
- package/fixtures/kg-golden/meetings/m04-corvid-intake-discovery.json +690 -0
- package/fixtures/kg-golden/meetings/m05-hosting-vendor-selection.json +631 -0
- package/fixtures/kg-golden/meetings/m06-northwind-floor-data-review.json +649 -0
- package/fixtures/kg-golden/meetings/m07-gullwing-intro-notes.json +428 -0
- package/fixtures/kg-golden/meetings/m08-halden-migration-checkpoint.json +672 -0
- package/fixtures/kg-golden/meetings/m09-northwind-platform-org-update.json +711 -0
- package/fixtures/kg-golden/meetings/m10-corvid-prototype-review.json +623 -0
- package/fixtures/kg-golden/meetings/m11-solvane-grid-dashboard-scoping.json +689 -0
- package/fixtures/kg-golden/meetings/m12-northwind-pilot-replan.json +681 -0
- package/fixtures/kg-golden/meetings/m13-halden-cutover-go-no-go.json +721 -0
- package/fixtures/kg-golden/meetings/m14-gullwing-discovery-readout.json +719 -0
- package/fixtures/kg-golden/meetings/m15-halden-handover-ravi-leaving.json +716 -0
- package/fixtures/kg-golden/meetings/m16-varden-claims-portal-intro.json +704 -0
- package/fixtures/kg-golden/meetings/m17-corvid-usability-debrief.json +572 -0
- package/fixtures/kg-golden/meetings/m18-hosting-decision-revisited.json +655 -0
- package/fixtures/kg-golden/meetings/m19-solvane-integration-kickoff.json +726 -0
- package/fixtures/kg-golden/meetings/m20-northwind-go-live-retro.json +673 -0
- package/fixtures/kg-golden/meetings/m21-quarterly-account-review.json +449 -0
- package/fixtures/kg-golden/meetings/m22-halden-intro-call-2020.json +558 -0
- package/fixtures/kg-golden/meetings/m23-gullwing-model-review.json +702 -0
- package/fixtures/kg-golden/meetings/m24-varden-discovery-midpoint.json +643 -0
- package/fixtures/kg-golden/meetings/m25-corvid-rollout-planning.json +642 -0
- package/fixtures/kg-golden/meetings/m26-larkspur-engineering-prep.json +695 -0
- package/fixtures/kg-golden/meetings/m27-solvane-phase-one-demo.json +649 -0
- package/fixtures/kg-golden/meetings/m28-northwind-building-5-scoping.json +715 -0
- package/fixtures/kg-golden/meetings/m29-halden-southern-cutover-review.json +752 -0
- package/fixtures/kg-golden/meetings/m30-varden-portal-build-kickoff.json +697 -0
- package/fixtures/kg-golden/meetings/m31-gullwing-rollout-renewal.json +681 -0
- package/fixtures/kg-golden/meetings/m32-brambleway-weekly-sync.json +472 -0
- package/fixtures/kg-golden/meetings/m33-nina-oliver-one-on-one.json +342 -0
- package/fixtures/kg-golden/meetings/m34-coffee-with-dana.json +249 -0
- package/fixtures/kg-golden/meetings/m35-brambleway-june-sync.json +553 -0
- package/fixtures/kg-golden/meetings/m36-copenhagen-conference-planning.json +296 -0
- package/host/generated/client.d.ts +1 -0
- package/host/generated/client.js +5 -0
- package/host/generated/default.d.ts +1 -0
- package/host/generated/default.js +5 -0
- package/host/generated/edge.d.ts +1 -0
- package/host/generated/edge.js +1658 -0
- package/host/generated/index-browser.js +1684 -0
- package/host/generated/index.d.ts +192335 -0
- package/host/generated/index.js +1658 -0
- package/host/generated/package.json +139 -0
- package/host/generated/query_compiler_fast_bg.js +2 -0
- package/host/generated/query_compiler_fast_bg.wasm +0 -0
- package/host/generated/query_compiler_fast_bg.wasm-base64.js +2 -0
- package/host/generated/runtime/client.d.ts +3433 -0
- package/host/generated/runtime/client.js +110 -0
- package/host/generated/runtime/index-browser.d.ts +90 -0
- package/host/generated/runtime/index-browser.js +6 -0
- package/host/generated/runtime/wasm-compiler-edge.js +100 -0
- package/host/generated/schema.prisma +6312 -0
- package/host/generated/wasm-edge-light-loader.mjs +5 -0
- package/host/generated/wasm-worker-loader.mjs +5 -0
- package/host/prisma/fragments/kvox.ask.prisma +126 -0
- package/host/prisma/fragments/kvox.graph.prisma +1073 -0
- package/host/prisma/fragments/kvox.notes.prisma +787 -0
- package/host/prisma/fragments/kvox.search.prisma +211 -0
- package/host/prisma/fragments/kvox.transcripts.prisma +790 -0
- package/host/prisma/kvox.lock +101 -0
- package/host/prisma/migrations/20240101000000_initial/migration.sql +297 -0
- package/host/prisma/migrations/20240101000001_add_personal_access_tokens/migration.sql +31 -0
- package/host/prisma/migrations/20240101000002_add_credentials/migration.sql +20 -0
- package/host/prisma/migrations/20240101000003_add_notification_deliveries/migration.sql +27 -0
- package/host/prisma/migrations/20240101000004_drop_stale_uuid_defaults/migration.sql +35 -0
- package/host/prisma/migrations/20240101000005_add_notifications/migration.sql +22 -0
- package/host/prisma/migrations/20240101000006_add_push_subscriptions/migration.sql +25 -0
- package/host/prisma/migrations/20240101000007_add_jobs/migration.sql +110 -0
- package/host/prisma/migrations/20240101000008_add_worker_nodes/migration.sql +90 -0
- package/host/prisma/migrations/20240101000009_add_database_backup_runs/migration.sql +104 -0
- package/host/prisma/migrations/20240101000010_add_notification_broadcasts/migration.sql +57 -0
- package/host/prisma/migrations/20240101000011_add_backup_run_job_link/migration.sql +87 -0
- package/host/prisma/migrations/20240101000012_add_job_node_secrets/migration.sql +61 -0
- package/host/prisma/migrations/20240101000013_add_backup_run_pg_dump_version/migration.sql +26 -0
- package/host/prisma/migrations/20240101000014_add_job_claim_token/migration.sql +92 -0
- package/host/prisma/migrations/20240101000015_add_ai_platform/migration.sql +108 -0
- package/host/prisma/migrations/20240101000016_revoke_viewer_ai_use/migration.sql +32 -0
- package/host/prisma/migrations/20240101000017_add_user_credentials/migration.sql +29 -0
- package/host/prisma/migrations/20240101000018_add_device_session_credential_link/migration.sql +52 -0
- package/host/prisma/migrations/20240101000019_add_worker_node_vitals/migration.sql +3 -0
- package/host/prisma/migrations/20240101000020_add_job_trace_context/migration.sql +6 -0
- package/host/prisma/migrations/20240101000021_add_retention_created_at_indexes/migration.sql +13 -0
- package/host/prisma/migrations/20240101000022_add_organizations/migration.sql +142 -0
- package/host/prisma/migrations/20240101000023_split_system_org_roles/migration.sql +131 -0
- package/host/prisma/migrations/20240101000024_org_scoped_rls/migration.sql +215 -0
- package/host/prisma/migrations/20240101000025_add_groups/migration.sql +165 -0
- package/host/prisma/migrations/20240101000026_add_grants/migration.sql +117 -0
- package/host/prisma/migrations/20240101000027_add_org_settings/migration.sql +49 -0
- package/host/prisma/migrations/20240101000028_add_org_credentials/migration.sql +54 -0
- package/host/prisma/migrations/20240101000029_add_jobs_org_id/migration.sql +18 -0
- package/host/prisma/migrations/20240101000030_add_jobs_org_id_status_index/migration.sql +6 -0
- package/host/prisma/migrations/20240101000031_add_broadcast_target_org/migration.sql +21 -0
- package/host/prisma/migrations/20240101000032_add_android_app/migration.sql +60 -0
- package/host/prisma/migrations/20240101000033_open_job_reason/migration.sql +16 -0
- package/host/prisma/migrations/20240101000034_ai_usage_feature/migration.sql +37 -0
- package/host/prisma/migrations/20240101000035_add_app_metadata/migration.sql +49 -0
- package/host/prisma/migrations/20240101000036_open_sharing_vocabularies/migration.sql +97 -0
- package/host/prisma/migrations/20260914130000_kvox_add_transcripts/migration.sql +322 -0
- package/host/prisma/migrations/20260914140000_kvox_add_notes/migration.sql +311 -0
- package/host/prisma/migrations/20260915120000_kvox_add_search_vectors/migration.sql +150 -0
- package/host/prisma/migrations/20260915130000_kvox_add_search_embeddings/migration.sql +362 -0
- package/host/prisma/migrations/20260915210000_kvox_add_note_title_source/migration.sql +30 -0
- package/host/prisma/migrations/20260923120000_kvox_add_note_generation_context_snapshot/migration.sql +24 -0
- package/host/prisma/migrations/20260923130000_kvox_add_user_hidden_note_templates/migration.sql +35 -0
- package/host/prisma/migrations/20260923140000_kvox_add_transcript_speaker_identities/migration.sql +6 -0
- package/host/prisma/migrations/20260923150000_kvox_transcript_name_checks/migration.sql +95 -0
- package/host/prisma/migrations/20260923160000_kvox_add_note_body_format/migration.sql +27 -0
- package/host/prisma/migrations/20260923170000_kvox_add_note_version_body_format/migration.sql +23 -0
- package/host/prisma/migrations/20260926012548_kvox_add_knowledge_graph/migration.sql +680 -0
- package/host/prisma/migrations/20260926030000_kvox_fix_kg_relations_valid_precision_chk/migration.sql +29 -0
- package/host/prisma/migrations/20260926030100_kvox_add_transcript_recorded_at/migration.sql +9 -0
- package/host/prisma/migrations/20260926030200_kvox_kg_evidence_invariant/migration.sql +60 -0
- package/host/prisma/migrations/20260926040000_kvox_add_kg_graph_layouts/migration.sql +30 -0
- package/host/prisma/migrations/20260926050000_kvox_add_ask_conversations/migration.sql +99 -0
- package/host/prisma/migrations/20260926231512_kvox_add_kg_exports/migration.sql +52 -0
- package/host/prisma/migrations/20261016120000_kvox_transcripts_org_id/migration.sql +49 -0
- package/host/prisma/platform.lock +319 -0
- package/host/prisma/schema/app.kvox.ask.prisma +110 -0
- package/host/prisma/schema/app.kvox.graph.prisma +1030 -0
- package/host/prisma/schema/app.kvox.notes.prisma +746 -0
- package/host/prisma/schema/app.kvox.search.prisma +208 -0
- package/host/prisma/schema/app.kvox.transcripts.prisma +726 -0
- package/host/prisma/schema/platform.ai.prisma +419 -0
- package/host/prisma/schema/platform.android-app.prisma +46 -0
- package/host/prisma/schema/platform.base.prisma +15 -0
- package/host/prisma/schema/platform.credentials.prisma +156 -0
- package/host/prisma/schema/platform.db-backup.prisma +359 -0
- package/host/prisma/schema/platform.identity.prisma +638 -0
- package/host/prisma/schema/platform.jobs.prisma +824 -0
- package/host/prisma/schema/platform.notifications.prisma +641 -0
- package/host/prisma/schema/platform.settings.prisma +53 -0
- package/host/prisma/schema/platform.sharing.prisma +208 -0
- package/host/prisma/schema/platform.storage.prisma +118 -0
- package/package.json +112 -1
- package/parity/requests.ask.json +2681 -0
- package/parity/requests.m01.json +1554 -0
- package/parity/requests.m02.json +1554 -0
- package/parity/requests.m03.json +1554 -0
- package/parity/requests.m04.json +1554 -0
- package/parity/requests.m05.json +1554 -0
- package/parity/requests.m06.json +1554 -0
- package/parity/requests.m07.json +1554 -0
- package/parity/requests.m08.json +1554 -0
- package/parity/requests.m09.json +1554 -0
- package/parity/requests.m10.json +1554 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
-- =============================================================================
|
|
2
|
+
-- Semantic search: pgvector storage for chunk embeddings (issue #181,
|
|
3
|
+
-- epic #165)
|
|
4
|
+
-- =============================================================================
|
|
5
|
+
--
|
|
6
|
+
-- Layer 1 (epic #164, the sibling migration `20260915120000_add_search_vectors`)
|
|
7
|
+
-- gave this application literal, lexical search over transcript/note content
|
|
8
|
+
-- via generated `tsvector` columns. This migration is layer 2: MEANING-based
|
|
9
|
+
-- search over the same content, via vectors an AI embedding model produces
|
|
10
|
+
-- and pgvector indexes for nearest-neighbour lookup. Nothing here replaces
|
|
11
|
+
-- layer 1 — a real search feature runs both and blends the results — and
|
|
12
|
+
-- nothing here runs any embedding model itself; this migration is storage
|
|
13
|
+
-- only. The pipeline that populates it (chunking, calling a provider,
|
|
14
|
+
-- writing rows here) is later issues in epic #165.
|
|
15
|
+
--
|
|
16
|
+
-- -----------------------------------------------------------------------------
|
|
17
|
+
-- `CREATE EXTENSION IF NOT EXISTS vector` IS REFUSED LOUDLY, NOT SILENTLY,
|
|
18
|
+
-- LONG BEFORE THIS MIGRATION EVER RUNS.
|
|
19
|
+
-- -----------------------------------------------------------------------------
|
|
20
|
+
--
|
|
21
|
+
-- This statement requires the `vector` extension's shared library to be
|
|
22
|
+
-- present on the PostgreSQL SERVER — not something any migration tool can
|
|
23
|
+
-- install by itself, and not something every managed-Postgres offering ships
|
|
24
|
+
-- by default. Issue #179 puts the check where a failure is actionable: the
|
|
25
|
+
-- deploy CLI's `database-vector-extension` preflight probes the target server
|
|
26
|
+
-- BEFORE any deploy proceeds, and refuses the deploy outright with a clear
|
|
27
|
+
-- operator-facing message if `vector` cannot be provided. By the time this
|
|
28
|
+
-- migration runs, that question has already been answered — this
|
|
29
|
+
-- `CREATE EXTENSION IF NOT EXISTS` is a re-affirmation of a fact the
|
|
30
|
+
-- preflight already established, not a probe of its own, and `IF NOT EXISTS`
|
|
31
|
+
-- only guards against re-running this migration idempotently, never against
|
|
32
|
+
-- the extension genuinely being unavailable.
|
|
33
|
+
--
|
|
34
|
+
-- THE REJECTED ALTERNATIVE: wrapping this statement in a `DO $$ ... $$` block
|
|
35
|
+
-- that catches the failure and skips creating the extension (and, by
|
|
36
|
+
-- extension, every table below that depends on it) when `vector` is absent.
|
|
37
|
+
-- That would trade one loud, pre-deploy refusal for permanent, silent,
|
|
38
|
+
-- PER-DEPLOYMENT schema drift: some installations would have semantic search
|
|
39
|
+
-- storage and some would not, indistinguishably, with nothing in the schema
|
|
40
|
+
-- itself recording which — and every query this feature ever issues would
|
|
41
|
+
-- have to probe `pg_extension` at runtime, forever, to find out which world
|
|
42
|
+
-- it is running in. A deployment that cannot provide `vector` should never
|
|
43
|
+
-- reach this migration in the first place; #179's preflight is what makes
|
|
44
|
+
-- that true, and this migration is written on the assumption that it did its
|
|
45
|
+
-- job.
|
|
46
|
+
--
|
|
47
|
+
-- -----------------------------------------------------------------------------
|
|
48
|
+
-- THREE TABLES
|
|
49
|
+
-- -----------------------------------------------------------------------------
|
|
50
|
+
--
|
|
51
|
+
-- `search_chunks` — one row per chunk of a document's text, independent of
|
|
52
|
+
-- any particular embedding model. `search_embeddings` — one row per
|
|
53
|
+
-- (chunk, model) vector, so multiple models (or a re-embed under a changed
|
|
54
|
+
-- model) can coexist without destroying history. `search_index_state` — one
|
|
55
|
+
-- row per DOCUMENT (not chunk), the legibility surface an admin/owner reads
|
|
56
|
+
-- to answer "is this searchable, and if not, why not".
|
|
57
|
+
--
|
|
58
|
+
-- -----------------------------------------------------------------------------
|
|
59
|
+
-- 1. `vector(1536)` IS A CONTRACT, NOT A DEFAULT.
|
|
60
|
+
-- -----------------------------------------------------------------------------
|
|
61
|
+
--
|
|
62
|
+
-- Postgres's `vector(n)` type is FIXED-WIDTH: a value produced by a model
|
|
63
|
+
-- with a different output dimensionality cannot be stored in this column at
|
|
64
|
+
-- all — not truncated, not padded, rejected outright at INSERT time. 1536 is
|
|
65
|
+
-- not picked because it is a good default; it is picked because it is the
|
|
66
|
+
-- width this deployment's embedding provider is configured to produce, and
|
|
67
|
+
-- the column is exactly as wide as that fact, no wider. `AiProvider`
|
|
68
|
+
-- (`src/ai/providers/ai-provider.interface.ts`) grows a sibling
|
|
69
|
+
-- `EMBEDDING_DIMENSIONS` constant as part of this same issue's line of work,
|
|
70
|
+
-- and `AiProviderRegistry` refuses to activate a provider whose embedding
|
|
71
|
+
-- model's width disagrees with it — AT APPLICATION BOOT, where an operator
|
|
72
|
+
-- reads a clear startup error, not inside a `search.embed` job at 3am where
|
|
73
|
+
-- the failure is a stack trace in a queue row nobody is watching. Say this
|
|
74
|
+
-- plainly: THIS COLUMN AND THAT CONSTANT ARE THE TWO HALVES OF ONE CONTRACT
|
|
75
|
+
-- and must always change together. Changing one without the other is not two
|
|
76
|
+
-- smaller changes, it is one change done incompletely — the database will
|
|
77
|
+
-- either reject every write (constant raised, column not) or silently accept
|
|
78
|
+
-- a narrower vector padded/misread by nothing in particular (this cannot
|
|
79
|
+
-- actually happen — pgvector rejects a width mismatch — but the point
|
|
80
|
+
-- stands: there is no partial, working state between them).
|
|
81
|
+
--
|
|
82
|
+
-- `search_embeddings.dimensions` is stored ANYWAY, despite the column type
|
|
83
|
+
-- already enforcing the width — not as a second constraint (Postgres already
|
|
84
|
+
-- enforces the real one) but as a RECORDED FACT about the row, independent of
|
|
85
|
+
-- whatever the column's declared width happens to be on the day the row is
|
|
86
|
+
-- read. The day a future issue widens this contract (a better model, a
|
|
87
|
+
-- migration to `vector(3072)` or similar), old rows and new rows will briefly
|
|
88
|
+
-- coexist under two different embedding models, and `dimensions` is what lets
|
|
89
|
+
-- a query or a backfill script tell them apart without re-deriving the width
|
|
90
|
+
-- from `octet_length`/array introspection every time.
|
|
91
|
+
--
|
|
92
|
+
-- -----------------------------------------------------------------------------
|
|
93
|
+
-- 2. `search_chunks.document_id` CARRIES NO FOREIGN KEY, IN EITHER DIRECTION —
|
|
94
|
+
-- THE SAME PRECEDENT `jobs.subject_type`/`subject_id` ALREADY SET.
|
|
95
|
+
-- -----------------------------------------------------------------------------
|
|
96
|
+
--
|
|
97
|
+
-- `document_type` (`'transcript'` | `'note'`) plus `document_id` is a
|
|
98
|
+
-- polymorphic reference, exactly the shape `jobs.subject_type`/`subject_id`
|
|
99
|
+
-- already uses in this schema (see the block comment above the `Job` model)
|
|
100
|
+
-- and for the identical reason: the set of document types this table indexes
|
|
101
|
+
-- is open-ended and code-owned, not a fixed pair of foreign keys Prisma could
|
|
102
|
+
-- express as two nullable relations. A fork adding a third searchable
|
|
103
|
+
-- document type costs this table zero migrations.
|
|
104
|
+
--
|
|
105
|
+
-- THE COST, STATED PLAINLY: an orphan row is POSSIBLE. Nothing at the
|
|
106
|
+
-- database level stops a `search_chunks` row from outliving the transcript
|
|
107
|
+
-- or note it was chunked from, the way a real foreign key would. THE
|
|
108
|
+
-- PROMISE, STATED EQUALLY PLAINLY: sweeping those rows is not a gap left for
|
|
109
|
+
-- someone to discover later, it is a responsibility handed to a named owner —
|
|
110
|
+
-- the deleting document's OWN purge handler. `transcript.purge` and
|
|
111
|
+
-- `note.purge` (both already existing, both already the sole path that
|
|
112
|
+
-- permanently removes a transcript or note) are wired by a later issue in
|
|
113
|
+
-- this epic to also delete their document's `search_chunks` rows (which
|
|
114
|
+
-- cascade to `search_embeddings` — see below) and its `search_index_state`
|
|
115
|
+
-- row, in the same job that already owns deleting everything else that
|
|
116
|
+
-- document's identity touches. This migration does not implement that sweep;
|
|
117
|
+
-- it states, here, that the sweep is owed and by whom.
|
|
118
|
+
--
|
|
119
|
+
-- -----------------------------------------------------------------------------
|
|
120
|
+
-- 3. HNSW, NOT IVFFLAT — CHOSEN FOR HOW EACH INDEX TYPE BEHAVES AGAINST A
|
|
121
|
+
-- TABLE THAT STARTS EMPTY.
|
|
122
|
+
-- -----------------------------------------------------------------------------
|
|
123
|
+
--
|
|
124
|
+
-- IVFFlat partitions the vector space into a fixed number of lists chosen at
|
|
125
|
+
-- BUILD time from the data the index is built against — building it against
|
|
126
|
+
-- an empty (or thinly populated) table produces a poor, effectively random
|
|
127
|
+
-- partitioning that keeps degrading as the real corpus grows past what the
|
|
128
|
+
-- index was originally trained on, and recovering good recall means
|
|
129
|
+
-- rebuilding the whole index from scratch against the grown corpus. HNSW
|
|
130
|
+
-- builds a NAVIGABLE GRAPH INCREMENTALLY, one vector at a time, and needs no
|
|
131
|
+
-- representative sample up front and no retraining as the corpus grows — the
|
|
132
|
+
-- graph a thousand vectors join is structurally the same kind of graph a
|
|
133
|
+
-- million will. THE INDEX BELOW IS THEREFORE CREATED ON A TABLE WITH ZERO
|
|
134
|
+
-- ROWS IN IT, because this migration runs before any document has ever been
|
|
135
|
+
-- embedded — and that is exactly fine for HNSW, and would have been a real
|
|
136
|
+
-- problem for IVFFlat, which is the whole reason this migration picks the
|
|
137
|
+
-- one it does.
|
|
138
|
+
--
|
|
139
|
+
-- -----------------------------------------------------------------------------
|
|
140
|
+
-- 4. `vector_cosine_ops` — MATCHED, ON PURPOSE, TO THE `<=>` OPERATOR THE
|
|
141
|
+
-- RANKING QUERY USES.
|
|
142
|
+
-- -----------------------------------------------------------------------------
|
|
143
|
+
--
|
|
144
|
+
-- This deployment's embedding provider returns NORMALISED vectors, so cosine
|
|
145
|
+
-- distance is what actually separates a relevant chunk from an irrelevant
|
|
146
|
+
-- one, and `<=>` (cosine distance) is the operator the ranking query is
|
|
147
|
+
-- written against. An HNSW index's operator class must MATCH the distance
|
|
148
|
+
-- operator a query uses, or pgvector cannot use the index for that query at
|
|
149
|
+
-- all — not "uses it less efficiently," SILENTLY FALLS BACK TO A SEQUENTIAL
|
|
150
|
+
-- SCAN, with no error, no warning, and a query plan that only reveals the
|
|
151
|
+
-- mismatch to someone who thinks to run `EXPLAIN`. `vector_cosine_ops` below
|
|
152
|
+
-- is not a stylistic pick among equivalent options; it is the specific
|
|
153
|
+
-- opclass that keeps this index reachable by `ORDER BY embedding <=>
|
|
154
|
+
-- $1 LIMIT n`, the query shape semantic search actually runs.
|
|
155
|
+
--
|
|
156
|
+
-- -----------------------------------------------------------------------------
|
|
157
|
+
-- 5. EMBEDDINGS ARE NOT A COLUMN ON THE DOCUMENT ROW.
|
|
158
|
+
-- -----------------------------------------------------------------------------
|
|
159
|
+
--
|
|
160
|
+
-- A `transcript`/`note` is a single row; its searchable content is many
|
|
161
|
+
-- chunks of text with independent character offsets. Storing "the"
|
|
162
|
+
-- embedding on the document row would force one of two bad shapes: either
|
|
163
|
+
-- one vector standing in for a whole document (useless for surfacing WHICH
|
|
164
|
+
-- part matched, which is what a search result's snippet needs), or an array
|
|
165
|
+
-- column of vectors with no per-chunk offsets to build that snippet from
|
|
166
|
+
-- and no way to re-embed one changed paragraph without rewriting the whole
|
|
167
|
+
-- array. A SEPARATE TABLE keyed on `(document_type, document_id, ordinal)`
|
|
168
|
+
-- makes re-indexing an edited document a per-chunk operation and gives every
|
|
169
|
+
-- match a `char_start`/`char_end` a snippet can be built from directly.
|
|
170
|
+
--
|
|
171
|
+
-- -----------------------------------------------------------------------------
|
|
172
|
+
-- 6. `content_hash` IS THE WHOLE POINT OF CONTENT ADDRESSING HERE.
|
|
173
|
+
-- -----------------------------------------------------------------------------
|
|
174
|
+
--
|
|
175
|
+
-- `content_hash` is `sha256` of a chunk's FINAL text. Re-indexing an edited
|
|
176
|
+
-- document re-chunks the whole thing, but only the chunks whose hash actually
|
|
177
|
+
-- changed need a fresh call to the embedding provider — an unchanged
|
|
178
|
+
-- paragraph's chunk keeps its existing embedding, no matter how far its
|
|
179
|
+
-- `ordinal` moved. The bill for every one of those provider calls lands on
|
|
180
|
+
-- the DOCUMENT OWNER'S OWN vendor account (the same strict bring-your-own-key
|
|
181
|
+
-- posture `docs/specs/notes.md` §9 already establishes for note generation),
|
|
182
|
+
-- so making a re-embed cheap is not merely an efficiency nicety, it is
|
|
183
|
+
-- directly what a re-index costs its owner in real vendor spend.
|
|
184
|
+
-- `UNIQUE (chunk_id, model)` is the constraint that makes this cheap in
|
|
185
|
+
-- practice: a repeat "does this (chunk, model) already have an embedding"
|
|
186
|
+
-- check is a single indexed lookup, not a table scan, and the pipeline this
|
|
187
|
+
-- migration exists to support relies on that lookup being cheap every time
|
|
188
|
+
-- it runs.
|
|
189
|
+
--
|
|
190
|
+
-- -----------------------------------------------------------------------------
|
|
191
|
+
-- STATUS: A REAL PRISMA ENUM. `document_type` AND `reason` STAY PLAIN TEXT.
|
|
192
|
+
-- THIS ASYMMETRY IS DELIBERATE, NOT AN OVERSIGHT.
|
|
193
|
+
-- -----------------------------------------------------------------------------
|
|
194
|
+
--
|
|
195
|
+
-- `search_index_state.status` (`pending`/`indexing`/`indexed`/`failed`/
|
|
196
|
+
-- `skipped`) is a CLOSED set this application itself owns end to end: every
|
|
197
|
+
-- value is written by code inside this repository, nothing external ever
|
|
198
|
+
-- proposes a new one, and a Prisma enum gives the compiler (not a runtime
|
|
199
|
+
-- check) the guarantee that every `.status` comparison in TypeScript is
|
|
200
|
+
-- exhaustive. `document_type` stays plain TEXT for the identical reason
|
|
201
|
+
-- `jobs.subject_type` does (see point 2 above and the `Job` model's own block
|
|
202
|
+
-- comment): the SET OF DOCUMENT TYPES is polymorphic and open to a fork's own
|
|
203
|
+
-- tables, which a Prisma enum — a closed set baked into the generated client
|
|
204
|
+
-- — is structurally the wrong tool to describe. `reason` (`ai_key_missing`,
|
|
205
|
+
-- `ai_not_configured`, `dimension_mismatch`, …) stays plain TEXT because it is
|
|
206
|
+
-- a DIAGNOSTIC STRING, not a state machine: it exists to be read by a human
|
|
207
|
+
-- debugging why one document did not index, it will grow new values as new
|
|
208
|
+
-- failure modes are discovered, and a `NULL` in this column for anything
|
|
209
|
+
-- other than `indexed` is itself informative (a `failed` row with no
|
|
210
|
+
-- `reason` is a bug in the code that set it). Three columns immediately
|
|
211
|
+
-- beside each other, three different answers to "should this be an enum",
|
|
212
|
+
-- stated here so the asymmetry reads as intentional rather than as three
|
|
213
|
+
-- decisions nobody thought about together.
|
|
214
|
+
--
|
|
215
|
+
-- -----------------------------------------------------------------------------
|
|
216
|
+
-- INTENTIONAL SCHEMA DRIFT — THE FIFTH OCCURRENCE OF THIS EXACT PATTERN.
|
|
217
|
+
-- -----------------------------------------------------------------------------
|
|
218
|
+
--
|
|
219
|
+
-- Prisma's schema DSL has no way to express `vector(1536)` as a column type
|
|
220
|
+
-- and no way to express `USING hnsw` as an index access method. In
|
|
221
|
+
-- `schema.prisma`, `search_embeddings.embedding` is declared
|
|
222
|
+
-- `Unsupported("vector(1536)")`, and the `search_embeddings_embedding_hnsw_idx`
|
|
223
|
+
-- index below exists ONLY in this file — `prisma migrate dev`/`diff` will
|
|
224
|
+
-- never regenerate it and must never be asked to "reconcile" it away. This is
|
|
225
|
+
-- the FIFTH occurrence of hand-written, Prisma-inexpressible schema in this
|
|
226
|
+
-- codebase, following (1) `jobs_active_dedup_uniq_idx` (a partial unique
|
|
227
|
+
-- index — `jobs` migration), (2) `database_backup_runs_active_uniq_idx` (the
|
|
228
|
+
-- same partial-unique pattern — `database_backup_runs` migration), (3)
|
|
229
|
+
-- `transcript_speakers_transcript_id_label_key` (a partial unique index over
|
|
230
|
+
-- labelled rows only — `transcripts` migration), and (4) the three generated
|
|
231
|
+
-- `tsvector` columns plus their GIN indexes in this migration's own sibling,
|
|
232
|
+
-- `20260915120000_add_search_vectors`. Each prior occurrence is Prisma
|
|
233
|
+
-- lacking DDL for a SQL feature this application genuinely needs; this one is
|
|
234
|
+
-- no different, and none of the five should be "fixed" by inventing a DSL
|
|
235
|
+
-- workaround that only obscures what is actually running against the
|
|
236
|
+
-- database.
|
|
237
|
+
--
|
|
238
|
+
-- -----------------------------------------------------------------------------
|
|
239
|
+
-- ⚠ A NOTE ON THE HNSW BUILD COST, IN THE SAME SPIRIT AS THIS MIGRATION'S
|
|
240
|
+
-- SIBLING'S `ACCESS EXCLUSIVE` WARNING.
|
|
241
|
+
-- -----------------------------------------------------------------------------
|
|
242
|
+
--
|
|
243
|
+
-- The index below is built against an empty table, so THIS MIGRATION is
|
|
244
|
+
-- instant — there is nothing yet to graph. That will not stay true forever:
|
|
245
|
+
-- rebuilding (or `REINDEX`ing) an HNSW index once `search_embeddings` holds a
|
|
246
|
+
-- real corpus is genuinely expensive CPU and memory work, proportional to the
|
|
247
|
+
-- number of rows and the graph's `m`/`ef_construction` parameters, and it is
|
|
248
|
+
-- governed by `maintenance_work_mem` — too small a setting during a later
|
|
249
|
+
-- rebuild materially slows the build and can spill to disk. An operator
|
|
250
|
+
-- planning a manual `REINDEX` against a populated table should raise
|
|
251
|
+
-- `maintenance_work_mem` for that session first, the same operational
|
|
252
|
+
-- awareness a large `ALTER TABLE` anywhere else in this codebase already
|
|
253
|
+
-- demands.
|
|
254
|
+
-- =============================================================================
|
|
255
|
+
|
|
256
|
+
CREATE EXTENSION IF NOT EXISTS vector;
|
|
257
|
+
|
|
258
|
+
-- CreateEnum
|
|
259
|
+
CREATE TYPE "SearchIndexStatus" AS ENUM ('pending', 'indexing', 'indexed', 'failed', 'skipped');
|
|
260
|
+
|
|
261
|
+
-- CreateTable: search_chunks — one row per chunk of a document's text. See
|
|
262
|
+
-- the header above (point 2) for why document_type/document_id carry no
|
|
263
|
+
-- foreign key, and point 5 for why chunks are not a column on the document.
|
|
264
|
+
CREATE TABLE "search_chunks" (
|
|
265
|
+
"id" UUID NOT NULL,
|
|
266
|
+
"document_type" TEXT NOT NULL,
|
|
267
|
+
"document_id" UUID NOT NULL,
|
|
268
|
+
"ordinal" INTEGER NOT NULL,
|
|
269
|
+
"text" TEXT NOT NULL,
|
|
270
|
+
"content_hash" TEXT NOT NULL,
|
|
271
|
+
"char_start" INTEGER NOT NULL,
|
|
272
|
+
"char_end" INTEGER NOT NULL,
|
|
273
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
274
|
+
|
|
275
|
+
CONSTRAINT "search_chunks_pkey" PRIMARY KEY ("id")
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
-- CreateIndex
|
|
279
|
+
CREATE UNIQUE INDEX "search_chunks_document_type_document_id_ordinal_key"
|
|
280
|
+
ON "search_chunks"("document_type", "document_id", "ordinal");
|
|
281
|
+
|
|
282
|
+
-- CreateIndex
|
|
283
|
+
CREATE INDEX "search_chunks_document_type_document_id_idx"
|
|
284
|
+
ON "search_chunks"("document_type", "document_id");
|
|
285
|
+
|
|
286
|
+
-- CreateIndex: content-addressing lookup (header point 6) — "does a chunk
|
|
287
|
+
-- with this exact text already exist" is a lookup this index makes cheap.
|
|
288
|
+
CREATE INDEX "search_chunks_content_hash_idx"
|
|
289
|
+
ON "search_chunks"("content_hash");
|
|
290
|
+
|
|
291
|
+
-- CreateTable: search_embeddings — one (chunk, model) vector. See the header
|
|
292
|
+
-- (points 1, 3, 4, 6) for the column width contract, the HNSW choice, the
|
|
293
|
+
-- cosine opclass, and why UNIQUE(chunk_id, model) is what makes a repeat
|
|
294
|
+
-- embed cheap.
|
|
295
|
+
CREATE TABLE "search_embeddings" (
|
|
296
|
+
"id" UUID NOT NULL,
|
|
297
|
+
"chunk_id" UUID NOT NULL,
|
|
298
|
+
"model" TEXT NOT NULL,
|
|
299
|
+
"dimensions" INTEGER NOT NULL,
|
|
300
|
+
"embedding" vector(1536) NOT NULL,
|
|
301
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
302
|
+
|
|
303
|
+
CONSTRAINT "search_embeddings_pkey" PRIMARY KEY ("id")
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
-- AddForeignKey: the one real FK in this migration — chunk_id points INSIDE
|
|
307
|
+
-- this same module's own table, unlike document_id above which points
|
|
308
|
+
-- sideways into another module's. See header point 2.
|
|
309
|
+
ALTER TABLE "search_embeddings"
|
|
310
|
+
ADD CONSTRAINT "search_embeddings_chunk_id_fkey"
|
|
311
|
+
FOREIGN KEY ("chunk_id") REFERENCES "search_chunks"("id")
|
|
312
|
+
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
313
|
+
|
|
314
|
+
-- CreateIndex
|
|
315
|
+
CREATE UNIQUE INDEX "search_embeddings_chunk_id_model_key"
|
|
316
|
+
ON "search_embeddings"("chunk_id", "model");
|
|
317
|
+
|
|
318
|
+
-- CreateIndex: HAND-WRITTEN, Prisma-inexpressible. See the header's
|
|
319
|
+
-- "INTENTIONAL SCHEMA DRIFT" section (occurrence 5 of 5) and point 3 (why
|
|
320
|
+
-- HNSW, built empty, is correct here) and point 4 (why the opclass must be
|
|
321
|
+
-- vector_cosine_ops to match the `<=>` operator the ranking query uses).
|
|
322
|
+
CREATE INDEX "search_embeddings_embedding_hnsw_idx"
|
|
323
|
+
ON "search_embeddings" USING hnsw ("embedding" vector_cosine_ops);
|
|
324
|
+
|
|
325
|
+
-- CreateTable: search_index_state — one row per DOCUMENT, the legibility
|
|
326
|
+
-- surface. owner_id is a REAL foreign key (it points at `users`, this
|
|
327
|
+
-- module's neighbour, the same way every other per-user table in this
|
|
328
|
+
-- schema does) and CASCADES: an index-state row for a document nobody owns
|
|
329
|
+
-- any more means nothing. See the header's "STATUS" section for why status
|
|
330
|
+
-- is an enum while document_type/reason stay plain text.
|
|
331
|
+
CREATE TABLE "search_index_state" (
|
|
332
|
+
"id" UUID NOT NULL,
|
|
333
|
+
"document_type" TEXT NOT NULL,
|
|
334
|
+
"document_id" UUID NOT NULL,
|
|
335
|
+
"owner_id" UUID NOT NULL,
|
|
336
|
+
"status" "SearchIndexStatus" NOT NULL DEFAULT 'pending',
|
|
337
|
+
"reason" TEXT,
|
|
338
|
+
"chunk_count" INTEGER NOT NULL DEFAULT 0,
|
|
339
|
+
"model" TEXT,
|
|
340
|
+
"content_fingerprint" TEXT,
|
|
341
|
+
"last_error" TEXT,
|
|
342
|
+
"indexed_at" TIMESTAMPTZ,
|
|
343
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
344
|
+
"updated_at" TIMESTAMPTZ NOT NULL,
|
|
345
|
+
|
|
346
|
+
CONSTRAINT "search_index_state_pkey" PRIMARY KEY ("id")
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
-- AddForeignKey
|
|
350
|
+
ALTER TABLE "search_index_state"
|
|
351
|
+
ADD CONSTRAINT "search_index_state_owner_id_fkey"
|
|
352
|
+
FOREIGN KEY ("owner_id") REFERENCES "users"("id")
|
|
353
|
+
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
354
|
+
|
|
355
|
+
-- CreateIndex
|
|
356
|
+
CREATE UNIQUE INDEX "search_index_state_document_type_document_id_key"
|
|
357
|
+
ON "search_index_state"("document_type", "document_id");
|
|
358
|
+
|
|
359
|
+
-- CreateIndex: the unindexedCount query — "how many of this owner's
|
|
360
|
+
-- documents are in state X".
|
|
361
|
+
CREATE INDEX "search_index_state_owner_id_status_idx"
|
|
362
|
+
ON "search_index_state"("owner_id", "status");
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
-- =============================================================================
|
|
2
|
+
-- `notes.title_source` (issue #180, epic #163)
|
|
3
|
+
-- =============================================================================
|
|
4
|
+
-- A title had no provenance until this column: nothing distinguished a name
|
|
5
|
+
-- the user typed from one the product guessed on the note's behalf. Without
|
|
6
|
+
-- it, a `regenerate` or a future bulk-retitle pass has no way to know a
|
|
7
|
+
-- note's owner already renamed it, and would silently overwrite a
|
|
8
|
+
-- human-chosen title with an AI guess — that is the failure mode this column
|
|
9
|
+
-- exists to make impossible rather than merely avoided.
|
|
10
|
+
--
|
|
11
|
+
-- Three values: `ai` (the model proposed it), `user` (a person typed it —
|
|
12
|
+
-- STICKY, the one value the AI titling path must never overwrite), and
|
|
13
|
+
-- `template` (nobody has chosen one yet; the note is still wearing the name
|
|
14
|
+
-- of the template/source it came from).
|
|
15
|
+
--
|
|
16
|
+
-- BACKFILL IS FREE. The column default applies to every existing row via a
|
|
17
|
+
-- single metadata change (`ADD COLUMN ... NOT NULL DEFAULT 'template'` needs
|
|
18
|
+
-- no table rewrite for a fixed non-null default on modern Postgres), and
|
|
19
|
+
-- `template` is the truthful value for every one of them: `notes.service.ts`
|
|
20
|
+
-- has always fallen back to the source template's `name` when generating a
|
|
21
|
+
-- note's initial title, so there is no row in this table today whose title
|
|
22
|
+
-- came from anywhere else. No data migration, no UPDATE pass, nothing to get
|
|
23
|
+
-- wrong.
|
|
24
|
+
-- =============================================================================
|
|
25
|
+
|
|
26
|
+
-- CreateEnum
|
|
27
|
+
CREATE TYPE "NoteTitleSource" AS ENUM ('ai', 'user', 'template');
|
|
28
|
+
|
|
29
|
+
-- AlterTable
|
|
30
|
+
ALTER TABLE "notes" ADD COLUMN "title_source" "NoteTitleSource" NOT NULL DEFAULT 'template';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
-- =============================================================================
|
|
2
|
+
-- `note_generations` context snapshot (issue #307)
|
|
3
|
+
-- =============================================================================
|
|
4
|
+
-- A snapshot of WHAT WAS SENT to the AI provider — the exact
|
|
5
|
+
-- `assemblePrompt()` output (apps/api/src/notes/generation/prompt.ts) —
|
|
6
|
+
-- written BEFORE the provider call so a failed generation still records it.
|
|
7
|
+
--
|
|
8
|
+
-- `system_prompt`/`user_content` are the two halves of the assembled prompt.
|
|
9
|
+
-- `source_version` is the transcript `currentVersion` / source note
|
|
10
|
+
-- `currentVersion` actually materialized for the prompt; NULL for a document
|
|
11
|
+
-- source, which carries no version. `context_captured_at` is when the
|
|
12
|
+
-- snapshot was taken, not when the generation itself completed.
|
|
13
|
+
--
|
|
14
|
+
-- NULL across all four columns means the generation predates issue #307 —
|
|
15
|
+
-- deliberately no backfill, because re-materializing the source today would
|
|
16
|
+
-- fabricate history: the source transcript/note may have been corrected
|
|
17
|
+
-- since, and the template may have been edited since.
|
|
18
|
+
-- =============================================================================
|
|
19
|
+
|
|
20
|
+
-- AlterTable
|
|
21
|
+
ALTER TABLE "note_generations" ADD COLUMN "system_prompt" TEXT,
|
|
22
|
+
ADD COLUMN "user_content" TEXT,
|
|
23
|
+
ADD COLUMN "source_version" INTEGER,
|
|
24
|
+
ADD COLUMN "context_captured_at" TIMESTAMPTZ;
|
package/host/prisma/migrations/20260923130000_kvox_add_user_hidden_note_templates/migration.sql
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
-- =============================================================================
|
|
2
|
+
-- Per-user hidden note templates: a LISTING preference, never access control
|
|
3
|
+
-- (#310, epic #306)
|
|
4
|
+
-- =============================================================================
|
|
5
|
+
-- One join table. See the block comment above `UserHiddenNoteTemplate` in
|
|
6
|
+
-- prisma/schema.prisma for why this is a join table rather than a
|
|
7
|
+
-- `user_settings` namespace (FK integrity in both directions, a plain
|
|
8
|
+
-- indexed `WHERE user_id = ...` query, no cap, no six-file settings-parity
|
|
9
|
+
-- cost) and rather than reusing `note_templates.is_archived` (archiving is a
|
|
10
|
+
-- property of the template, shared by every viewer; hiding is a property of
|
|
11
|
+
-- the (user, template) pair, and must never affect anyone else's picker).
|
|
12
|
+
--
|
|
13
|
+
-- ⚠ Hiding a template never gates access: create, regenerate and preview all
|
|
14
|
+
-- keep accepting a hidden template by id exactly as before this migration.
|
|
15
|
+
-- =============================================================================
|
|
16
|
+
|
|
17
|
+
-- CreateTable
|
|
18
|
+
CREATE TABLE "user_hidden_note_templates" (
|
|
19
|
+
"user_id" UUID NOT NULL,
|
|
20
|
+
"template_id" UUID NOT NULL,
|
|
21
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
22
|
+
|
|
23
|
+
CONSTRAINT "user_hidden_note_templates_pkey" PRIMARY KEY ("user_id","template_id")
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
-- CreateIndex
|
|
27
|
+
CREATE INDEX "user_hidden_note_templates_template_id_idx" ON "user_hidden_note_templates"("template_id");
|
|
28
|
+
|
|
29
|
+
-- AddForeignKey
|
|
30
|
+
-- Cascade: the preference is meaningless once its owner is gone.
|
|
31
|
+
ALTER TABLE "user_hidden_note_templates" ADD CONSTRAINT "user_hidden_note_templates_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
32
|
+
|
|
33
|
+
-- AddForeignKey
|
|
34
|
+
-- Cascade: hiding a deleted template has nothing left to hide.
|
|
35
|
+
ALTER TABLE "user_hidden_note_templates" ADD CONSTRAINT "user_hidden_note_templates_template_id_fkey" FOREIGN KEY ("template_id") REFERENCES "note_templates"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
package/host/prisma/migrations/20260923140000_kvox_add_transcript_speaker_identities/migration.sql
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
-- Issue #323: a map `speakerId -> displayName` recording the first time a
|
|
2
|
+
-- user named an AI-detected speaker. This is an identification (metadata
|
|
3
|
+
-- about the recording, like `title`), not a content correction, so it is
|
|
4
|
+
-- deliberately not versioned in `transcript_versions` — see the block
|
|
5
|
+
-- comment above `Transcript.speakerIdentities` in schema.prisma.
|
|
6
|
+
ALTER TABLE "transcripts" ADD COLUMN "speaker_identities" JSONB NOT NULL DEFAULT '{}';
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
-- =============================================================================
|
|
2
|
+
-- AI-assisted transcript name checks (issue #328, epic #326)
|
|
3
|
+
-- =============================================================================
|
|
4
|
+
-- Two tables. See the block comment above `TranscriptNameCheck` in
|
|
5
|
+
-- schema.prisma for the full reasoning: a `TranscriptNameCheck` is one AI run
|
|
6
|
+
-- and a `TranscriptNameSuggestion` is one proposed edit it produced. Neither
|
|
7
|
+
-- is a second source of truth for transcript content — accepting a
|
|
8
|
+
-- suggestion is recorded as an ordinary `segment.update_text` op through the
|
|
9
|
+
-- existing `/operations` path and `TranscriptVersion` history, never a
|
|
10
|
+
-- direct write to `transcript_segments.text` from this migration's tables.
|
|
11
|
+
|
|
12
|
+
-- CreateEnum
|
|
13
|
+
CREATE TYPE "TranscriptNameCheckMode" AS ENUM ('standard', 'thorough');
|
|
14
|
+
|
|
15
|
+
-- CreateEnum
|
|
16
|
+
CREATE TYPE "TranscriptNameCheckStatus" AS ENUM ('pending', 'running', 'ready', 'failed');
|
|
17
|
+
|
|
18
|
+
-- CreateEnum
|
|
19
|
+
CREATE TYPE "TranscriptNameSuggestionStatus" AS ENUM ('pending', 'accepted', 'rejected', 'stale');
|
|
20
|
+
|
|
21
|
+
-- CreateTable
|
|
22
|
+
CREATE TABLE "transcript_name_checks" (
|
|
23
|
+
"id" UUID NOT NULL,
|
|
24
|
+
"transcript_id" UUID NOT NULL,
|
|
25
|
+
"requested_by_id" UUID,
|
|
26
|
+
"mode" "TranscriptNameCheckMode" NOT NULL,
|
|
27
|
+
"status" "TranscriptNameCheckStatus" NOT NULL DEFAULT 'pending',
|
|
28
|
+
"based_on_version" INTEGER NOT NULL,
|
|
29
|
+
"terms" JSONB NOT NULL,
|
|
30
|
+
"provider_id" TEXT,
|
|
31
|
+
"model" TEXT,
|
|
32
|
+
"candidate_count" INTEGER NOT NULL DEFAULT 0,
|
|
33
|
+
"suggestion_count" INTEGER NOT NULL DEFAULT 0,
|
|
34
|
+
"input_tokens" INTEGER NOT NULL DEFAULT 0,
|
|
35
|
+
"output_tokens" INTEGER NOT NULL DEFAULT 0,
|
|
36
|
+
"error_class" TEXT,
|
|
37
|
+
"error" TEXT,
|
|
38
|
+
"job_id" UUID,
|
|
39
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
40
|
+
"started_at" TIMESTAMPTZ,
|
|
41
|
+
"completed_at" TIMESTAMPTZ,
|
|
42
|
+
|
|
43
|
+
CONSTRAINT "transcript_name_checks_pkey" PRIMARY KEY ("id")
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
-- CreateTable
|
|
47
|
+
CREATE TABLE "transcript_name_suggestions" (
|
|
48
|
+
"id" UUID NOT NULL,
|
|
49
|
+
"check_id" UUID NOT NULL,
|
|
50
|
+
"segment_id" UUID NOT NULL,
|
|
51
|
+
"segment_rev" INTEGER NOT NULL,
|
|
52
|
+
"start" INTEGER NOT NULL,
|
|
53
|
+
"end" INTEGER NOT NULL,
|
|
54
|
+
"original" TEXT NOT NULL,
|
|
55
|
+
"replacement" TEXT NOT NULL,
|
|
56
|
+
"confidence" DOUBLE PRECISION,
|
|
57
|
+
"reason" TEXT,
|
|
58
|
+
"source" TEXT NOT NULL,
|
|
59
|
+
"status" "TranscriptNameSuggestionStatus" NOT NULL DEFAULT 'pending',
|
|
60
|
+
"decided_at" TIMESTAMPTZ,
|
|
61
|
+
"decided_by_id" UUID,
|
|
62
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
63
|
+
|
|
64
|
+
CONSTRAINT "transcript_name_suggestions_pkey" PRIMARY KEY ("id")
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
-- CreateIndex
|
|
68
|
+
CREATE UNIQUE INDEX "transcript_name_checks_job_id_key" ON "transcript_name_checks"("job_id");
|
|
69
|
+
|
|
70
|
+
-- CreateIndex
|
|
71
|
+
CREATE INDEX "transcript_name_checks_transcript_id_created_at_idx" ON "transcript_name_checks"("transcript_id", "created_at" DESC);
|
|
72
|
+
|
|
73
|
+
-- CreateIndex
|
|
74
|
+
CREATE INDEX "transcript_name_suggestions_check_id_status_idx" ON "transcript_name_suggestions"("check_id", "status");
|
|
75
|
+
|
|
76
|
+
-- CreateIndex
|
|
77
|
+
CREATE INDEX "transcript_name_suggestions_segment_id_idx" ON "transcript_name_suggestions"("segment_id");
|
|
78
|
+
|
|
79
|
+
-- AddForeignKey
|
|
80
|
+
ALTER TABLE "transcript_name_checks" ADD CONSTRAINT "transcript_name_checks_transcript_id_fkey" FOREIGN KEY ("transcript_id") REFERENCES "transcripts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
81
|
+
|
|
82
|
+
-- AddForeignKey
|
|
83
|
+
ALTER TABLE "transcript_name_checks" ADD CONSTRAINT "transcript_name_checks_requested_by_id_fkey" FOREIGN KEY ("requested_by_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
84
|
+
|
|
85
|
+
-- AddForeignKey
|
|
86
|
+
ALTER TABLE "transcript_name_checks" ADD CONSTRAINT "transcript_name_checks_job_id_fkey" FOREIGN KEY ("job_id") REFERENCES "jobs"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
87
|
+
|
|
88
|
+
-- AddForeignKey
|
|
89
|
+
ALTER TABLE "transcript_name_suggestions" ADD CONSTRAINT "transcript_name_suggestions_check_id_fkey" FOREIGN KEY ("check_id") REFERENCES "transcript_name_checks"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
90
|
+
|
|
91
|
+
-- AddForeignKey
|
|
92
|
+
ALTER TABLE "transcript_name_suggestions" ADD CONSTRAINT "transcript_name_suggestions_segment_id_fkey" FOREIGN KEY ("segment_id") REFERENCES "transcript_segments"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
93
|
+
|
|
94
|
+
-- AddForeignKey
|
|
95
|
+
ALTER TABLE "transcript_name_suggestions" ADD CONSTRAINT "transcript_name_suggestions_decided_by_id_fkey" FOREIGN KEY ("decided_by_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
-- =============================================================================
|
|
2
|
+
-- `body_format` on `note_templates` and `notes` (issue #334)
|
|
3
|
+
-- =============================================================================
|
|
4
|
+
-- The markdown-vs-plain-text SYNTAX a note body is written in — a separate
|
|
5
|
+
-- axis from `output_format`, which names the document TYPE (meeting notes,
|
|
6
|
+
-- summary, …) rather than how its text is styled. Plain TEXT column, not an
|
|
7
|
+
-- enum, same reasoning as `output_format`: validated by Zod in the API
|
|
8
|
+
-- ('markdown' | 'plain_text'), not by a database enum.
|
|
9
|
+
--
|
|
10
|
+
-- `note_templates.body_format` is the author's choice, read by
|
|
11
|
+
-- `assemblePrompt` alongside the other structured fields (spec §2.x).
|
|
12
|
+
-- `notes.body_format` is SNAPSHOTTED from the generating template's
|
|
13
|
+
-- `body_format` at generation time, never a live read of the template — so a
|
|
14
|
+
-- later edit to a template's body format never changes how an
|
|
15
|
+
-- already-generated note renders, the identical snapshot reasoning
|
|
16
|
+
-- `note_generations`'s context columns (#307) already establish for prompts.
|
|
17
|
+
--
|
|
18
|
+
-- Defaulted to 'markdown' for both existing rows and new ones: every
|
|
19
|
+
-- built-in and custom template predating this issue already asks the AI to
|
|
20
|
+
-- write markdown in practice, and every existing note's body is markdown.
|
|
21
|
+
-- =============================================================================
|
|
22
|
+
|
|
23
|
+
-- AlterTable
|
|
24
|
+
ALTER TABLE "note_templates" ADD COLUMN "body_format" TEXT NOT NULL DEFAULT 'markdown';
|
|
25
|
+
|
|
26
|
+
-- AlterTable
|
|
27
|
+
ALTER TABLE "notes" ADD COLUMN "body_format" TEXT NOT NULL DEFAULT 'markdown';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
-- =============================================================================
|
|
2
|
+
-- `body_format` on `note_versions` (issue #337)
|
|
3
|
+
-- =============================================================================
|
|
4
|
+
-- Per-version tracking of the same markdown-vs-plain-text syntax axis
|
|
5
|
+
-- `note_templates.body_format`/`notes.body_format` (#334) already carry —
|
|
6
|
+
-- but snapshotted onto each individual version rather than only the note's
|
|
7
|
+
-- current one, so `GET /api/notes/:id/versions/:version` and a restore can
|
|
8
|
+
-- each answer "what syntax was THIS version written in" without consulting
|
|
9
|
+
-- the note's live (and possibly since-changed) format. Plain TEXT column,
|
|
10
|
+
-- not an enum: validated by Zod in the API ('markdown' | 'plain_text'), same
|
|
11
|
+
-- reasoning as every other body-format column in this schema.
|
|
12
|
+
--
|
|
13
|
+
-- Defaulted to 'markdown' for both existing rows and new ones. This is a
|
|
14
|
+
-- correct backfill, not merely a convenient one: a version written before
|
|
15
|
+
-- per-version tracking existed has no better answer available — the
|
|
16
|
+
-- generating template/note's format at the time was never recorded per
|
|
17
|
+
-- version — and 'plain_text' only existed as an option for a few hours
|
|
18
|
+
-- before this issue landed, so no pre-existing version could actually be
|
|
19
|
+
-- plain text in practice.
|
|
20
|
+
-- =============================================================================
|
|
21
|
+
|
|
22
|
+
-- AlterTable
|
|
23
|
+
ALTER TABLE "note_versions" ADD COLUMN "body_format" TEXT NOT NULL DEFAULT 'markdown';
|