@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,1030 @@
|
|
|
1
|
+
// GENERATED by platform db compose — do not edit
|
|
2
|
+
// source: app:kvox.graph.prisma
|
|
3
|
+
|
|
4
|
+
// GENERATED by kvox-db sync — do not edit
|
|
5
|
+
// source: @marinoscar/kvox-db:schema/kvox.graph.prisma
|
|
6
|
+
|
|
7
|
+
// =============================================================================
|
|
8
|
+
// Connected Knowledge (epic #344)
|
|
9
|
+
// =============================================================================
|
|
10
|
+
//
|
|
11
|
+
// The graph foundation: entities, relations, items (commitments, decisions,
|
|
12
|
+
// claims, person facts), the evidence that anchors every claim back to a
|
|
13
|
+
// transcript segment or note span, and the review-and-commit proposal model
|
|
14
|
+
// extraction runs through. Full design in `docs/specs/ontology.md`; §10 is
|
|
15
|
+
// this section's own summary, kept in sync with the column list below.
|
|
16
|
+
// SCHEMA ONLY in this migration — no service, no endpoint, no data. The
|
|
17
|
+
// evidence-invariant trigger is #355's own migration, landing with the code
|
|
18
|
+
// that satisfies it; `kg_graph_layouts` and `ask_*` are #371's and #376's
|
|
19
|
+
// own migrations.
|
|
20
|
+
//
|
|
21
|
+
// Five conventions every model below shares, stated once here rather than
|
|
22
|
+
// repeated per model:
|
|
23
|
+
//
|
|
24
|
+
// 1. `owner_id → users.id`, `onDelete: Cascade`, on every table but one. An
|
|
25
|
+
// entity/relation/item/evidence row/mention/proposal/merge/distinct-pair/
|
|
26
|
+
// attribute-def has no meaning and no permission path to read it once its
|
|
27
|
+
// owner is gone — the identical reasoning `notes.owner_id`/
|
|
28
|
+
// `transcripts.owner_id` already establish, and there is no
|
|
29
|
+
// `graph:read_any` for the identical reason there is no `notes:read_any`.
|
|
30
|
+
// `kg_entity_views` is the one exception: it carries `user_id`, because
|
|
31
|
+
// "which entities has THIS user looked at" is a per-viewer fact, not an
|
|
32
|
+
// ownership fact.
|
|
33
|
+
// 2. `type`/`kind` columns that name an ONTOLOGY key (`kg_entities.type`,
|
|
34
|
+
// `kg_relations.type`) are plain TEXT, never a Prisma enum — the same
|
|
35
|
+
// "small, code-owned, growing set" reasoning `jobs.type` and
|
|
36
|
+
// `storage_objects.managed_by` already establish elsewhere in this
|
|
37
|
+
// schema. Every enum below instead closes a set this SCHEMA itself
|
|
38
|
+
// enforces (a review lifecycle, a proposal's own status machine) — see
|
|
39
|
+
// §17.4 for the full type-vs-enum argument.
|
|
40
|
+
// 3. `props`/`stats`/`user_guidance` JSONB columns default `'{}'` where
|
|
41
|
+
// listed, exactly like `jobs.payload`/`kg_entities.props` elsewhere in
|
|
42
|
+
// this file — an empty object, never NULL, so a reader never has to
|
|
43
|
+
// special-case "no extra data yet" against "extra data exists but is
|
|
44
|
+
// empty."
|
|
45
|
+
// 4. `ontology_version` (text) on every row a §17.4 definition-file version
|
|
46
|
+
// could apply to (`kg_entities`, `kg_relations`, `kg_items`) — which
|
|
47
|
+
// version of the ontology's own type/attribute definitions this row was
|
|
48
|
+
// written against, so a later definition-file migration can tell an
|
|
49
|
+
// old row from a new one without re-deriving it.
|
|
50
|
+
// 5. `valid tstzrange` (on `kg_relations` and `kg_items`) is written and
|
|
51
|
+
// read entirely OUTSIDE Prisma's query API. Prisma cannot construct,
|
|
52
|
+
// bind, or select a `tstzrange` value — the column is declared
|
|
53
|
+
// `Unsupported("tstzrange")` below specifically so the generated client
|
|
54
|
+
// refuses to touch it at all, the same "declare it unsupported so a
|
|
55
|
+
// mistaken ORM write is impossible" shape `SearchEmbedding.embedding`
|
|
56
|
+
// already establishes for `vector(1536)`. Writers set `valid` with
|
|
57
|
+
// `$executeRaw` inside the SAME transaction as the `create`; readers
|
|
58
|
+
// select `lower(valid)`, `upper(valid)` and `lower_inc(valid)`/
|
|
59
|
+
// `upper_inc(valid)` via `$queryRaw`. #353 provides the `toPgRange`/
|
|
60
|
+
// `fromPgRange` helpers every future reader/writer uses instead of
|
|
61
|
+
// reinventing this.
|
|
62
|
+
//
|
|
63
|
+
// -----------------------------------------------------------------------------
|
|
64
|
+
// WHY THERE IS NO ANCHOR CHECK ON `kg_evidence`.
|
|
65
|
+
// -----------------------------------------------------------------------------
|
|
66
|
+
//
|
|
67
|
+
// Every FK `kg_evidence` holds into another module's table
|
|
68
|
+
// (`transcript_id`/`segment_id`/`note_id`/`import_object_id`) is `SetNull`,
|
|
69
|
+
// by design (§10) — an evidence row is a CITATION, and a citation survives
|
|
70
|
+
// losing the thing it pointed to; `quote` (NOT NULL) is stored precisely so
|
|
71
|
+
// the citation stays readable once every anchor has gone to NULL. A CHECK
|
|
72
|
+
// requiring at least one anchor to be non-NULL would make deleting a
|
|
73
|
+
// transcript FAIL the moment its own evidence rows lose their last pointer —
|
|
74
|
+
// exactly the failure `SetNull` exists to avoid. So there is deliberately no
|
|
75
|
+
// such CHECK: a fully-anchorless evidence row (every FK NULL, `quote` intact)
|
|
76
|
+
// is a valid, expected end state, not a bug to prevent.
|
|
77
|
+
//
|
|
78
|
+
// -----------------------------------------------------------------------------
|
|
79
|
+
// INTENTIONAL SCHEMA DRIFT — HAND-WRITTEN, PRISMA-INEXPRESSIBLE SQL LIVES
|
|
80
|
+
// ONLY IN `migration.sql`, THE SAME PATTERN `jobs_active_dedup_uniq_idx` AND
|
|
81
|
+
// `search_embeddings_embedding_hnsw_idx` ALREADY ESTABLISH.
|
|
82
|
+
// -----------------------------------------------------------------------------
|
|
83
|
+
//
|
|
84
|
+
// `migration.sql` for this section hand-writes:
|
|
85
|
+
// - `CREATE EXTENSION IF NOT EXISTS pg_trgm` — a new capability for this
|
|
86
|
+
// codebase (pgvector already is not new; `SearchEmbedding` already
|
|
87
|
+
// depends on it). Trusted on PG13+, so the migration role needs no
|
|
88
|
+
// superuser.
|
|
89
|
+
// - Two `gin (… gin_trgm_ops)` trigram indexes (fuzzy candidate generation,
|
|
90
|
+
// §7): `kg_entities_label_trgm_idx`, `kg_entity_aliases_normalized_trgm_idx`.
|
|
91
|
+
// - Two `gist (valid)` range indexes (§5.4's containment/overlap queries):
|
|
92
|
+
// `kg_relations_valid_gist_idx`, `kg_items_valid_gist_idx`.
|
|
93
|
+
// - Two `hnsw (embedding vector_cosine_ops)` indexes, matching
|
|
94
|
+
// `SearchEmbedding`'s own hand-written index discipline exactly:
|
|
95
|
+
// `kg_entities_embedding_hnsw_idx`, `kg_items_embedding_hnsw_idx`.
|
|
96
|
+
// - Five partial unique indexes and one CHECK-shaped guard, all below.
|
|
97
|
+
// - Eight CHECK constraints, all below.
|
|
98
|
+
// None of the above has a Prisma DSL equivalent; `prisma migrate dev`/`diff`
|
|
99
|
+
// will propose to drop every one of them and must never be asked to
|
|
100
|
+
// reconcile that "drift" away.
|
|
101
|
+
//
|
|
102
|
+
// -----------------------------------------------------------------------------
|
|
103
|
+
// FIVE PARTIAL UNIQUE INDEXES, EACH ENFORCED IN THE DATABASE, NEVER BY A
|
|
104
|
+
// `findFirst` BEFORE THE INSERT — THE `database_backup_runs_active_uniq_idx`
|
|
105
|
+
// REASONING, FIVE TIMES OVER.
|
|
106
|
+
// -----------------------------------------------------------------------------
|
|
107
|
+
//
|
|
108
|
+
// - `kg_items_live_statement_uniq_idx` — the "known, skipped" dedup guard
|
|
109
|
+
// (§8): a verbatim restatement of an already-`accepted`/`edited` item
|
|
110
|
+
// attaches new evidence instead of inserting a duplicate row. Scoped to
|
|
111
|
+
// `review_status IN ('accepted','edited')` and `subject_id IS NOT NULL`
|
|
112
|
+
// so a `rejected` row's hash never blocks a legitimate later insert.
|
|
113
|
+
// - `kg_proposals_note_draft_uniq_idx` — at most one OPEN draft per note.
|
|
114
|
+
// Whoever creates a new draft discards the previous one in the same
|
|
115
|
+
// transaction (#363); this index is what makes "at most one" true even
|
|
116
|
+
// under a race, not the service-layer discard alone.
|
|
117
|
+
// - `kg_proposals_note_extracting_uniq_idx` — at most one RUNNING
|
|
118
|
+
// extraction per note (#363's 409 `extraction_running`).
|
|
119
|
+
// - `kg_proposals_owner_import_extracting_uniq_idx` — at most one RUNNING
|
|
120
|
+
// import per OWNER (#387's), scoped by `owner_id` rather than `note_id`
|
|
121
|
+
// because an import proposal has no note at all.
|
|
122
|
+
// - `kg_relations_speaker_link_uniq_idx` — at most one `IDENTIFIED_AS`
|
|
123
|
+
// edge per diarized speaker (a speaker names at most one entity).
|
|
124
|
+
//
|
|
125
|
+
// -----------------------------------------------------------------------------
|
|
126
|
+
// EIGHT CHECK CONSTRAINTS.
|
|
127
|
+
// -----------------------------------------------------------------------------
|
|
128
|
+
//
|
|
129
|
+
// - `kg_distinct_pairs_order_chk` (`a_id < b_id`) — canonicalizes an
|
|
130
|
+
// unordered pair into exactly one storable row, so `(a, b)` and `(b, a)`
|
|
131
|
+
// can never both be inserted as two different "confirmed distinct" facts.
|
|
132
|
+
// - `kg_mentions_one_source_chk` (`num_nonnulls(note_id, transcript_id) =
|
|
133
|
+
// 1`) — a mention always comes from exactly one source document.
|
|
134
|
+
// - `kg_relations_one_source_chk` (`num_nonnulls(from_id, from_speaker_id)
|
|
135
|
+
// = 1`) — a relation's `from` side is EITHER a resolved entity OR a
|
|
136
|
+
// diarized speaker awaiting `IDENTIFIED_AS`, never both, never neither.
|
|
137
|
+
// - `kg_relations_speaker_type_chk` (`from_speaker_id IS NULL OR type =
|
|
138
|
+
// 'IDENTIFIED_AS'`) — `from_speaker_id` exists for exactly one
|
|
139
|
+
// relation type, and no other.
|
|
140
|
+
// - `kg_relations_valid_precision_chk` (`(valid IS NULL) =
|
|
141
|
+
// (valid_precision IS NULL) OR valid_precision IS NOT DISTINCT FROM
|
|
142
|
+
// 'unknown'` — `IS NOT DISTINCT FROM`, not `=`, since #398: with `=` a
|
|
143
|
+
// NULL precision made the CHECK evaluate NULL, which passes) — a
|
|
144
|
+
// temporal relation always states a precision, but `unknown` is
|
|
145
|
+
// representable even should `valid` itself later become NULL (e.g. a
|
|
146
|
+
// future correction that clears a range while a precision is still on
|
|
147
|
+
// record as having once been claimed).
|
|
148
|
+
// - `kg_items_sensitivity_chk` (`(kind = 'person_fact') = (sensitivity IS
|
|
149
|
+
// NOT NULL)`) — sensitivity is meaningful for exactly one item kind
|
|
150
|
+
// (§5.6), and this makes "a claim with a sensitivity" and "a person
|
|
151
|
+
// fact with none" both unrepresentable rather than merely discouraged.
|
|
152
|
+
// - `kg_items_subject_required_chk` (`kind NOT IN ('claim','person_fact')
|
|
153
|
+
// OR subject_id IS NOT NULL`) — a claim or a person fact is always
|
|
154
|
+
// ABOUT something; a commitment/decision may stand on its own.
|
|
155
|
+
// - `kg_evidence_char_range_chk` / `kg_evidence_ms_range_chk` — an end
|
|
156
|
+
// offset, when both ends are given, is never before its start.
|
|
157
|
+
// - `kg_proposal_items_kind_payload_chk` (`jsonb_typeof(payload) =
|
|
158
|
+
// 'object'`) — `payload` is always a JSON object, never a bare array or
|
|
159
|
+
// scalar, so every reader can assume key access without a type guard.
|
|
160
|
+
// - `kg_proposal_items_merge_into_chk` (`merge_into_id IS NULL OR decision
|
|
161
|
+
// = 'merge_into'`) — the override column and the decision it overrides
|
|
162
|
+
// can never disagree about which decision this row represents.
|
|
163
|
+
// `merge_into_id` is `SetNull`: forgetting or purging the target entity
|
|
164
|
+
// clears the override, and the row falls back to `pending` semantics at
|
|
165
|
+
// commit (#366 refuses a `merge_into` row without a target).
|
|
166
|
+
// - `kg_proposals_note_source_chk` (`CASE WHEN kind = 'extraction' THEN
|
|
167
|
+
// note_version IS NOT NULL ELSE note_id IS NULL AND note_version IS
|
|
168
|
+
// NULL END`) — keys on `note_version`, NEVER on `note_id`, and this is
|
|
169
|
+
// deliberate: `note_id` is inserted non-null for every extraction
|
|
170
|
+
// proposal and can only become NULL afterwards through the note's own
|
|
171
|
+
// `SetNull` FK once the note is hard-deleted. A CHECK keyed on `note_id`
|
|
172
|
+
// would then start failing for rows that were valid when written; a
|
|
173
|
+
// CHECK keyed on `note_version` (never nulled by any FK action) stays
|
|
174
|
+
// satisfied for the life of the row.
|
|
175
|
+
//
|
|
176
|
+
// -----------------------------------------------------------------------------
|
|
177
|
+
// FUTURE MIGRATIONS: when a later `prisma migrate dev` proposes `DROP INDEX`
|
|
178
|
+
// for any hand-written name above, DELETE those proposed lines. This is
|
|
179
|
+
// intentional drift, not decay to fix.
|
|
180
|
+
// =============================================================================
|
|
181
|
+
|
|
182
|
+
enum KgReviewStatus {
|
|
183
|
+
unreviewed
|
|
184
|
+
accepted
|
|
185
|
+
edited
|
|
186
|
+
rejected
|
|
187
|
+
merged
|
|
188
|
+
superseded
|
|
189
|
+
|
|
190
|
+
@@map("kg_review_status")
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
enum KgValidPrecision {
|
|
194
|
+
day
|
|
195
|
+
month
|
|
196
|
+
year
|
|
197
|
+
unknown
|
|
198
|
+
|
|
199
|
+
@@map("kg_valid_precision")
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
enum KgItemKind {
|
|
203
|
+
commitment
|
|
204
|
+
decision
|
|
205
|
+
claim
|
|
206
|
+
person_fact
|
|
207
|
+
|
|
208
|
+
@@map("kg_item_kind")
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
enum KgSensitivity {
|
|
212
|
+
business
|
|
213
|
+
personal
|
|
214
|
+
sensitive
|
|
215
|
+
|
|
216
|
+
@@map("kg_sensitivity")
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
enum KgAliasSource {
|
|
220
|
+
user
|
|
221
|
+
extraction
|
|
222
|
+
speaker_naming
|
|
223
|
+
import
|
|
224
|
+
|
|
225
|
+
@@map("kg_alias_source")
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
enum KgEvidenceSubjectKind {
|
|
229
|
+
entity
|
|
230
|
+
relation
|
|
231
|
+
item
|
|
232
|
+
proposal_item
|
|
233
|
+
import
|
|
234
|
+
|
|
235
|
+
@@map("kg_evidence_subject_kind")
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
enum KgMentionStatus {
|
|
239
|
+
suggested
|
|
240
|
+
linked
|
|
241
|
+
new
|
|
242
|
+
ignored
|
|
243
|
+
|
|
244
|
+
@@map("kg_mention_status")
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// The COMPLETE proposal model, up front — every status and kind #363, #364,
|
|
248
|
+
// #366 and #387 need, so none of them ever needs `ALTER TYPE … ADD VALUE` or
|
|
249
|
+
// a proposal-table migration of its own. `extracting`/`resolution` are used
|
|
250
|
+
// by no code yet; #363 owns the prose for the `extracting` lifecycle and the
|
|
251
|
+
// `resolution` kind, this migration owns only the column list (issue #351).
|
|
252
|
+
enum KgProposalKind {
|
|
253
|
+
extraction
|
|
254
|
+
import
|
|
255
|
+
resolution
|
|
256
|
+
|
|
257
|
+
@@map("kg_proposal_kind")
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Lifecycle: `extracting → draft | failed`, `draft → committed | discarded`,
|
|
261
|
+
// `failed → discarded`, `committed → reverted`. Transitions are enforced by
|
|
262
|
+
// the SERVICES (#363/#366), not by a database trigger — this enum only
|
|
263
|
+
// closes the set of states a row may ever be in.
|
|
264
|
+
enum KgProposalStatus {
|
|
265
|
+
draft
|
|
266
|
+
extracting
|
|
267
|
+
committed
|
|
268
|
+
discarded
|
|
269
|
+
failed
|
|
270
|
+
reverted
|
|
271
|
+
|
|
272
|
+
@@map("kg_proposal_status")
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
enum KgProposalItemKind {
|
|
276
|
+
entity
|
|
277
|
+
relation
|
|
278
|
+
item
|
|
279
|
+
closing
|
|
280
|
+
|
|
281
|
+
@@map("kg_proposal_item_kind")
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
enum KgProposalItemDecision {
|
|
285
|
+
pending
|
|
286
|
+
accept
|
|
287
|
+
edit
|
|
288
|
+
reject
|
|
289
|
+
merge_into
|
|
290
|
+
|
|
291
|
+
@@map("kg_proposal_item_decision")
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
enum KgProposalItemOrigin {
|
|
295
|
+
ai
|
|
296
|
+
user
|
|
297
|
+
|
|
298
|
+
@@map("kg_proposal_item_origin")
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
enum KgAttributeKind {
|
|
302
|
+
text
|
|
303
|
+
number
|
|
304
|
+
date
|
|
305
|
+
boolean
|
|
306
|
+
select
|
|
307
|
+
multi_select
|
|
308
|
+
url
|
|
309
|
+
entity_ref
|
|
310
|
+
|
|
311
|
+
@@map("kg_attribute_kind")
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// -----------------------------------------------------------------------------
|
|
315
|
+
// `kg_entities` — Person, Organization, Project, Meeting (`type` is a plain
|
|
316
|
+
// ontology key, §17.4 — never a Prisma enum; see the section header above).
|
|
317
|
+
// `owner_id` Cascade — see convention 1 above. `embedding vector(1536)`
|
|
318
|
+
// reuses the EXACT model/dimension contract `SearchEmbedding` already
|
|
319
|
+
// carries (see that model's own block comment for the full "why
|
|
320
|
+
// `Unsupported`, why raw SQL" argument) rather than inventing a second
|
|
321
|
+
// embedding convention; `embedding_hash` is the content hash #364's
|
|
322
|
+
// `kg.embed` keys re-embedding on, mirroring `SearchChunk.contentHash`.
|
|
323
|
+
// `merged_into_id` is a self-relation: once an entity is merged into
|
|
324
|
+
// another (§7), this points at the survivor and `review_status` moves to
|
|
325
|
+
// `merged` — SetNull so deleting the survivor un-merges rather than
|
|
326
|
+
// cascading a delete backward onto every entity it absorbed.
|
|
327
|
+
// `occurred_at` is set on `Meeting` rows only (the ontology's own type
|
|
328
|
+
// contract, not a database constraint — a fork's own type could reuse it
|
|
329
|
+
// differently).
|
|
330
|
+
// -----------------------------------------------------------------------------
|
|
331
|
+
model KgEntity {
|
|
332
|
+
id String @id @default(uuid()) @db.Uuid
|
|
333
|
+
|
|
334
|
+
ownerId String @map("owner_id") @db.Uuid
|
|
335
|
+
type String
|
|
336
|
+
label String
|
|
337
|
+
props Json @default("{}") @db.JsonB
|
|
338
|
+
|
|
339
|
+
// See the section header's convention 5 — `Unsupported`, written/read only
|
|
340
|
+
// through `$executeRaw`/`$queryRaw`, matching `SearchEmbedding.embedding`.
|
|
341
|
+
// Written as `Unsupported("vector(1536)")` with the parentheses as \u0028/\u0029: the
|
|
342
|
+
// composer's field reader (platform-db 0.1.0-next.5) stops a type at the first `)`.
|
|
343
|
+
// Prisma decodes the escapes, so the column type is still vector(1536).
|
|
344
|
+
embedding Unsupported("vector\u00281536\u0029")?
|
|
345
|
+
embeddingModel String? @map("embedding_model")
|
|
346
|
+
embeddingHash String? @map("embedding_hash")
|
|
347
|
+
|
|
348
|
+
reviewStatus KgReviewStatus @default(accepted) @map("review_status")
|
|
349
|
+
mergedIntoId String? @map("merged_into_id") @db.Uuid
|
|
350
|
+
occurredAt DateTime? @map("occurred_at") @db.Timestamptz
|
|
351
|
+
|
|
352
|
+
ontologyVersion String @map("ontology_version")
|
|
353
|
+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
|
354
|
+
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
|
355
|
+
|
|
356
|
+
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
|
|
357
|
+
mergedInto KgEntity? @relation("KgEntityMergedInto", fields: [mergedIntoId], references: [id], onDelete: SetNull)
|
|
358
|
+
|
|
359
|
+
mergedFrom KgEntity[] @relation("KgEntityMergedInto")
|
|
360
|
+
aliases KgEntityAlias[]
|
|
361
|
+
relationsFrom KgRelation[] @relation("KgRelationFrom")
|
|
362
|
+
relationsTo KgRelation[] @relation("KgRelationTo")
|
|
363
|
+
itemsAsSubject KgItem[] @relation("KgItemSubject")
|
|
364
|
+
itemsAsMeeting KgItem[] @relation("KgItemMeeting")
|
|
365
|
+
itemsAsOwnerPerson KgItem[] @relation("KgItemOwnerPerson")
|
|
366
|
+
itemsAsCounterparty KgItem[] @relation("KgItemCounterparty")
|
|
367
|
+
mentions KgMention[]
|
|
368
|
+
proposalItemsMergedInto KgProposalItem[] @relation("KgProposalItemMergeInto")
|
|
369
|
+
mergesAsSurvivor KgMerge[] @relation("KgMergeSurvivor")
|
|
370
|
+
mergesAsMerged KgMerge[] @relation("KgMergeMerged")
|
|
371
|
+
distinctPairsAsA KgDistinctPair[] @relation("KgDistinctPairA")
|
|
372
|
+
distinctPairsAsB KgDistinctPair[] @relation("KgDistinctPairB")
|
|
373
|
+
digest KgEntityDigest?
|
|
374
|
+
views KgEntityView[]
|
|
375
|
+
// Ask conversations scoped to this entity (issue #376). SetNull on the
|
|
376
|
+
// conversation's side — forgetting or merging a person drops the scope
|
|
377
|
+
// link, never the user's own conversation.
|
|
378
|
+
askConversationsScoped AskConversation[]
|
|
379
|
+
|
|
380
|
+
// "This owner's entities of type X, by review state" and "this owner's
|
|
381
|
+
// entities, recently updated" — the two shapes an entity list/filter reads.
|
|
382
|
+
@@index([ownerId, type, reviewStatus])
|
|
383
|
+
@@index([ownerId, updatedAt(sort: Desc)])
|
|
384
|
+
// "Every entity that merged into this one" — the merge-reversal read path.
|
|
385
|
+
@@index([mergedIntoId])
|
|
386
|
+
// HAND-WRITTEN in migration.sql only: `kg_entities_label_trgm_idx` (gin,
|
|
387
|
+
// gin_trgm_ops) and `kg_entities_embedding_hnsw_idx` (hnsw,
|
|
388
|
+
// vector_cosine_ops). See the section header's "INTENTIONAL SCHEMA DRIFT"
|
|
389
|
+
// note — Prisma has no DSL for either.
|
|
390
|
+
@@map("kg_entities")
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// -----------------------------------------------------------------------------
|
|
394
|
+
// `kg_entity_aliases` — a SEPARATE table, not an array column on
|
|
395
|
+
// `kg_entities`, specifically because an alias needs its own indexed
|
|
396
|
+
// exact/fuzzy lookup AND its own provenance (`source`) — an array column
|
|
397
|
+
// could hold the strings but not which of four very different origins each
|
|
398
|
+
// one came from (§10). `normalized` is produced by #355's `normalizeAlias()`
|
|
399
|
+
// (NFKC, lowercase, trimmed, whitespace collapsed) — this REPLACES `citext`,
|
|
400
|
+
// which is not installed and is not needed: a normalized `text` column
|
|
401
|
+
// produced by one pure function gives exact matching AND feeds `pg_trgm`
|
|
402
|
+
// directly, at the cost of nothing `citext` would have bought here.
|
|
403
|
+
// `entity_id`/`owner_id` both Cascade — an alias has no meaning once either
|
|
404
|
+
// its entity or its owner is gone.
|
|
405
|
+
// -----------------------------------------------------------------------------
|
|
406
|
+
model KgEntityAlias {
|
|
407
|
+
id String @id @default(uuid()) @db.Uuid
|
|
408
|
+
entityId String @map("entity_id") @db.Uuid
|
|
409
|
+
ownerId String @map("owner_id") @db.Uuid
|
|
410
|
+
|
|
411
|
+
alias String
|
|
412
|
+
// See the block comment above the model for the full `normalizeAlias()`
|
|
413
|
+
// argument — this is why there is no `citext` column here.
|
|
414
|
+
normalized String
|
|
415
|
+
|
|
416
|
+
source KgAliasSource
|
|
417
|
+
|
|
418
|
+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
|
419
|
+
|
|
420
|
+
entity KgEntity @relation(fields: [entityId], references: [id], onDelete: Cascade)
|
|
421
|
+
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
|
|
422
|
+
|
|
423
|
+
// One alias per (entity, normalized form) — a repeat submission of the
|
|
424
|
+
// identically-normalized alias is a no-op, not a duplicate row.
|
|
425
|
+
@@unique([entityId, normalized])
|
|
426
|
+
@@index([ownerId, normalized])
|
|
427
|
+
@@index([entityId])
|
|
428
|
+
// HAND-WRITTEN in migration.sql only: `kg_entity_aliases_normalized_trgm_idx`
|
|
429
|
+
// (gin, gin_trgm_ops) — fuzzy candidate generation (§7). See the section
|
|
430
|
+
// header's "INTENTIONAL SCHEMA DRIFT" note.
|
|
431
|
+
@@map("kg_entity_aliases")
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// -----------------------------------------------------------------------------
|
|
435
|
+
// `kg_relations` — `type` is one of §5.2's sixteen ontology edge types
|
|
436
|
+
// (plain text, never a Prisma enum — convention 2 above). No inverse row is
|
|
437
|
+
// EVER stored (§5.2): a "from" side is either a resolved `KgEntity`
|
|
438
|
+
// (`from_id`) or a diarized speaker awaiting identification
|
|
439
|
+
// (`from_speaker_id`) — `kg_relations_one_source_chk` makes exactly one of
|
|
440
|
+
// the two representable, and `kg_relations_speaker_type_chk` restricts
|
|
441
|
+
// `from_speaker_id` to the one relation type (`IDENTIFIED_AS`) whose source
|
|
442
|
+
// is a speaker rather than an entity. This is a contract addition beyond
|
|
443
|
+
// spec §10's original text (§5.1/§5.2): a polymorphic `from_kind`/`from_id`
|
|
444
|
+
// pair without FKs was rejected in favour of a REAL FK to
|
|
445
|
+
// `transcript_speakers`, so deleting a merged speaker's row cleans up the
|
|
446
|
+
// edge for free. `valid`/`valid_precision` are the §5.4 bitemporal pair —
|
|
447
|
+
// see the section header's convention 5 for how `valid` is actually
|
|
448
|
+
// written/read. `superseded_by_id` is a self-relation (§5.5's "closing a
|
|
449
|
+
// temporal edge" case), SetNull so deleting the successor un-supersedes
|
|
450
|
+
// rather than cascading backward.
|
|
451
|
+
// -----------------------------------------------------------------------------
|
|
452
|
+
model KgRelation {
|
|
453
|
+
id String @id @default(uuid()) @db.Uuid
|
|
454
|
+
ownerId String @map("owner_id") @db.Uuid
|
|
455
|
+
type String
|
|
456
|
+
|
|
457
|
+
// Endpoints — see the block comment above the model for the full
|
|
458
|
+
// exactly-one-of argument enforced by `kg_relations_one_source_chk`.
|
|
459
|
+
fromId String? @map("from_id") @db.Uuid
|
|
460
|
+
fromSpeakerId String? @map("from_speaker_id") @db.Uuid
|
|
461
|
+
toId String @map("to_id") @db.Uuid
|
|
462
|
+
|
|
463
|
+
props Json @default("{}") @db.JsonB
|
|
464
|
+
|
|
465
|
+
// §5.4's bitemporal pair. See the section header's convention 5 for why
|
|
466
|
+
// `valid` is `Unsupported` and written/read only via raw SQL.
|
|
467
|
+
valid Unsupported("tstzrange")?
|
|
468
|
+
validPrecision KgValidPrecision? @map("valid_precision")
|
|
469
|
+
|
|
470
|
+
reviewStatus KgReviewStatus @default(accepted) @map("review_status")
|
|
471
|
+
confidence Float?
|
|
472
|
+
supersededById String? @map("superseded_by_id") @db.Uuid
|
|
473
|
+
|
|
474
|
+
ontologyVersion String @map("ontology_version")
|
|
475
|
+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
|
476
|
+
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
|
477
|
+
|
|
478
|
+
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
|
|
479
|
+
fromEntity KgEntity? @relation("KgRelationFrom", fields: [fromId], references: [id], onDelete: Cascade)
|
|
480
|
+
fromSpeaker TranscriptSpeaker? @relation(fields: [fromSpeakerId], references: [id], onDelete: Cascade)
|
|
481
|
+
toEntity KgEntity @relation("KgRelationTo", fields: [toId], references: [id], onDelete: Cascade)
|
|
482
|
+
supersededBy KgRelation? @relation("KgRelationSupersededBy", fields: [supersededById], references: [id], onDelete: SetNull)
|
|
483
|
+
|
|
484
|
+
supersedes KgRelation[] @relation("KgRelationSupersededBy")
|
|
485
|
+
|
|
486
|
+
// §5.2's "joins are symmetric" argument, made concrete: a relation is
|
|
487
|
+
// looked up from either endpoint equally often.
|
|
488
|
+
@@index([ownerId, fromId, type])
|
|
489
|
+
@@index([ownerId, toId, type])
|
|
490
|
+
@@index([fromSpeakerId])
|
|
491
|
+
@@index([supersededById])
|
|
492
|
+
@@index([ownerId, type, reviewStatus])
|
|
493
|
+
// HAND-WRITTEN in migration.sql only: `kg_relations_valid_gist_idx` (gist,
|
|
494
|
+
// §5.4's containment/overlap queries) and `kg_relations_speaker_link_uniq_idx`
|
|
495
|
+
// (a partial unique index — see the section header). See the section
|
|
496
|
+
// header's "INTENTIONAL SCHEMA DRIFT" note.
|
|
497
|
+
@@map("kg_relations")
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// -----------------------------------------------------------------------------
|
|
501
|
+
// `kg_items` — Commitment, Decision, Claim, and PersonFact in ONE table,
|
|
502
|
+
// distinguished by `kind` (a real Prisma enum here, unlike `kg_entities
|
|
503
|
+
// .type`/`kg_relations.type` — see §10, this is the schema's own closed
|
|
504
|
+
// four-way split, not an open ontology key). `meeting_id` backs
|
|
505
|
+
// `CREATED_IN`/`DECIDED_IN` (a contract addition beyond the original §10
|
|
506
|
+
// text) — SetNull, so deleting the meeting entity leaves the item itself
|
|
507
|
+
// intact with a now-unknown venue rather than deleting the item.
|
|
508
|
+
// `owner_person_id`/`counterparty_id` Cascade (deleting the person this
|
|
509
|
+
// commitment is FOR/BETWEEN removes the commitment's meaning entirely, the
|
|
510
|
+
// same argument as `subject_id`); `subject_id` also Cascade. `sensitivity`
|
|
511
|
+
// is meaningful for exactly one `kind` (§5.6), enforced by
|
|
512
|
+
// `kg_items_sensitivity_chk`. `statement_hash` is the §7 dedup/suppression
|
|
513
|
+
// key `kg_items_live_statement_uniq_idx` enforces. `valid`/`valid_precision`
|
|
514
|
+
// mirror `kg_relations`'s own bitemporal pair (§5.4) so an item's own valid
|
|
515
|
+
// period is never a second stored shape from its edges' shape.
|
|
516
|
+
// -----------------------------------------------------------------------------
|
|
517
|
+
model KgItem {
|
|
518
|
+
id String @id @default(uuid()) @db.Uuid
|
|
519
|
+
ownerId String @map("owner_id") @db.Uuid
|
|
520
|
+
kind KgItemKind
|
|
521
|
+
|
|
522
|
+
subjectId String? @map("subject_id") @db.Uuid
|
|
523
|
+
meetingId String? @map("meeting_id") @db.Uuid
|
|
524
|
+
ownerPersonId String? @map("owner_person_id") @db.Uuid
|
|
525
|
+
counterpartyId String? @map("counterparty_id") @db.Uuid
|
|
526
|
+
|
|
527
|
+
title String?
|
|
528
|
+
// Commitment: `open | done | dropped | superseded`. Others: `active |
|
|
529
|
+
// superseded`. Plain text, not an enum — the same "small, code-owned set
|
|
530
|
+
// that varies per `kind`" reasoning `props`/§10 already give this table
|
|
531
|
+
// its single-table shape for.
|
|
532
|
+
statement String
|
|
533
|
+
status String
|
|
534
|
+
props Json @default("{}") @db.JsonB
|
|
535
|
+
|
|
536
|
+
occurredAt DateTime? @map("occurred_at") @db.Timestamptz
|
|
537
|
+
dueAt DateTime? @map("due_at") @db.Timestamptz
|
|
538
|
+
|
|
539
|
+
supersededById String? @map("superseded_by_id") @db.Uuid
|
|
540
|
+
sensitivity KgSensitivity?
|
|
541
|
+
// The §7 dedup/suppression key — see `kg_items_live_statement_uniq_idx`
|
|
542
|
+
// below (hand-written, since Prisma cannot express its `WHERE` clause).
|
|
543
|
+
statementHash String @map("statement_hash")
|
|
544
|
+
|
|
545
|
+
// §5.4's bitemporal pair, mirroring `kg_relations` exactly. See the
|
|
546
|
+
// section header's convention 5 for why `valid` is `Unsupported`.
|
|
547
|
+
valid Unsupported("tstzrange")?
|
|
548
|
+
validPrecision KgValidPrecision? @map("valid_precision")
|
|
549
|
+
|
|
550
|
+
// See the section header's convention 5 — same `Unsupported`/raw-SQL
|
|
551
|
+
// contract as `kg_entities.embedding` and `SearchEmbedding.embedding`.
|
|
552
|
+
// Written as `Unsupported("vector(1536)")` with the parentheses as \u0028/\u0029: the
|
|
553
|
+
// composer's field reader (platform-db 0.1.0-next.5) stops a type at the first `)`.
|
|
554
|
+
// Prisma decodes the escapes, so the column type is still vector(1536).
|
|
555
|
+
embedding Unsupported("vector\u00281536\u0029")?
|
|
556
|
+
embeddingModel String? @map("embedding_model")
|
|
557
|
+
embeddingHash String? @map("embedding_hash")
|
|
558
|
+
|
|
559
|
+
reviewStatus KgReviewStatus @default(accepted) @map("review_status")
|
|
560
|
+
confidence Float?
|
|
561
|
+
|
|
562
|
+
ontologyVersion String @map("ontology_version")
|
|
563
|
+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
|
564
|
+
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
|
565
|
+
|
|
566
|
+
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
|
|
567
|
+
subject KgEntity? @relation("KgItemSubject", fields: [subjectId], references: [id], onDelete: Cascade)
|
|
568
|
+
meeting KgEntity? @relation("KgItemMeeting", fields: [meetingId], references: [id], onDelete: SetNull)
|
|
569
|
+
ownerPerson KgEntity? @relation("KgItemOwnerPerson", fields: [ownerPersonId], references: [id], onDelete: Cascade)
|
|
570
|
+
counterparty KgEntity? @relation("KgItemCounterparty", fields: [counterpartyId], references: [id], onDelete: Cascade)
|
|
571
|
+
supersededBy KgItem? @relation("KgItemSupersededBy", fields: [supersededById], references: [id], onDelete: SetNull)
|
|
572
|
+
|
|
573
|
+
supersedes KgItem[] @relation("KgItemSupersededBy")
|
|
574
|
+
|
|
575
|
+
// §9.1's "what's changed for this entity" brief query.
|
|
576
|
+
@@index([ownerId, subjectId, occurredAt(sort: Desc)])
|
|
577
|
+
@@index([ownerId, kind, status])
|
|
578
|
+
@@index([ownerId, statementHash])
|
|
579
|
+
@@index([ownerPersonId])
|
|
580
|
+
@@index([counterpartyId])
|
|
581
|
+
@@index([meetingId])
|
|
582
|
+
@@index([supersededById])
|
|
583
|
+
// HAND-WRITTEN in migration.sql only: `kg_items_valid_gist_idx` (gist),
|
|
584
|
+
// `kg_items_embedding_hnsw_idx` (hnsw, vector_cosine_ops), and
|
|
585
|
+
// `kg_items_live_statement_uniq_idx` (a partial unique index — the "known,
|
|
586
|
+
// skipped" §8 dedup guard). See the section header's "INTENTIONAL SCHEMA
|
|
587
|
+
// DRIFT" note.
|
|
588
|
+
@@map("kg_items")
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// -----------------------------------------------------------------------------
|
|
592
|
+
// `kg_evidence` — the §5.3 evidence contract. `subject_kind`/`subject_id`
|
|
593
|
+
// are POLYMORPHIC, carrying NO foreign key — the identical `jobs.subject_
|
|
594
|
+
// type`/`subject_id` pattern this schema already uses, because the set of
|
|
595
|
+
// things evidence can anchor (an entity, a relation, an item, a proposal
|
|
596
|
+
// item, an import) is open-ended and code-owned. The transcript/note/import
|
|
597
|
+
// anchors ARE real foreign keys, and every one of them is `SetNull` — see
|
|
598
|
+
// the section header's "WHY THERE IS NO ANCHOR CHECK" note for the full
|
|
599
|
+
// argument; `quote` (NOT NULL) is what keeps a citation readable once every
|
|
600
|
+
// anchor has gone to NULL. `char_start`/`char_end` are offsets into the
|
|
601
|
+
// cited NOTE VERSION BODY when `note_id` is set, or into the cited
|
|
602
|
+
// SEGMENT'S TEXT when `segment_id` is set — NULL means the whole segment.
|
|
603
|
+
// On a NOTE citation, both NULL means "the note's Context field" (#440): the
|
|
604
|
+
// Context has no body offsets, so the quote alone carries the span.
|
|
605
|
+
// #363 relies on this exact convention; do not reinterpret it per-caller.
|
|
606
|
+
// -----------------------------------------------------------------------------
|
|
607
|
+
model KgEvidence {
|
|
608
|
+
id String @id @default(uuid()) @db.Uuid
|
|
609
|
+
ownerId String @map("owner_id") @db.Uuid
|
|
610
|
+
subjectKind KgEvidenceSubjectKind @map("subject_kind")
|
|
611
|
+
// Polymorphic, no FK — see the block comment above the model.
|
|
612
|
+
subjectId String @map("subject_id") @db.Uuid
|
|
613
|
+
|
|
614
|
+
// Transcript anchor. SetNull — see the section header's "WHY THERE IS NO
|
|
615
|
+
// ANCHOR CHECK" note.
|
|
616
|
+
transcriptId String? @map("transcript_id") @db.Uuid
|
|
617
|
+
segmentId String? @map("segment_id") @db.Uuid
|
|
618
|
+
segmentRev Int? @map("segment_rev")
|
|
619
|
+
startMs Int? @map("start_ms")
|
|
620
|
+
endMs Int? @map("end_ms")
|
|
621
|
+
|
|
622
|
+
// Note anchor. SetNull, same reasoning.
|
|
623
|
+
noteId String? @map("note_id") @db.Uuid
|
|
624
|
+
noteVersion Int? @map("note_version")
|
|
625
|
+
|
|
626
|
+
// Offsets into the cited NOTE VERSION BODY when `noteId` is set, or into
|
|
627
|
+
// the cited SEGMENT'S TEXT when `segmentId` is set. NULL means the whole
|
|
628
|
+
// segment; on a note citation, both NULL means the note's Context field
|
|
629
|
+
// (#440). See the block comment above the model — #363 reads this
|
|
630
|
+
// convention directly.
|
|
631
|
+
charStart Int? @map("char_start")
|
|
632
|
+
charEnd Int? @map("char_end")
|
|
633
|
+
|
|
634
|
+
// The text AT ANCHORING TIME (§5.3) — NOT NULL, deliberately: this is
|
|
635
|
+
// what keeps the citation readable once every anchor above has gone to
|
|
636
|
+
// NULL (see the section header's "WHY THERE IS NO ANCHOR CHECK" note).
|
|
637
|
+
quote String
|
|
638
|
+
|
|
639
|
+
// Import anchor. SetNull, same reasoning.
|
|
640
|
+
importObjectId String? @map("import_object_id") @db.Uuid
|
|
641
|
+
sourceIri String? @map("source_iri")
|
|
642
|
+
|
|
643
|
+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
|
644
|
+
|
|
645
|
+
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
|
|
646
|
+
transcript Transcript? @relation(fields: [transcriptId], references: [id], onDelete: SetNull)
|
|
647
|
+
segment TranscriptSegment? @relation(fields: [segmentId], references: [id], onDelete: SetNull)
|
|
648
|
+
note Note? @relation(fields: [noteId], references: [id], onDelete: SetNull)
|
|
649
|
+
importObject StorageObject? @relation(fields: [importObjectId], references: [id], onDelete: SetNull)
|
|
650
|
+
|
|
651
|
+
@@index([subjectKind, subjectId])
|
|
652
|
+
@@index([ownerId, transcriptId])
|
|
653
|
+
@@index([segmentId])
|
|
654
|
+
@@index([ownerId, noteId])
|
|
655
|
+
@@map("kg_evidence")
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// -----------------------------------------------------------------------------
|
|
659
|
+
// `kg_mentions` — the coarse `MENTIONS` shortcut (§5.2): what an entity
|
|
660
|
+
// page's "notes about Joe" section reads, distinct from `kg_evidence`'s
|
|
661
|
+
// precise per-claim citation. Exactly one source document per row
|
|
662
|
+
// (`kg_mentions_one_source_chk`). `entity_id`/`note_id`/`transcript_id` ALL
|
|
663
|
+
// Cascade — a mention has no meaning once either side is gone, the opposite
|
|
664
|
+
// of `kg_evidence`'s pointer relationship above.
|
|
665
|
+
// -----------------------------------------------------------------------------
|
|
666
|
+
model KgMention {
|
|
667
|
+
id String @id @default(uuid()) @db.Uuid
|
|
668
|
+
ownerId String @map("owner_id") @db.Uuid
|
|
669
|
+
|
|
670
|
+
entityId String @map("entity_id") @db.Uuid
|
|
671
|
+
|
|
672
|
+
noteId String? @map("note_id") @db.Uuid
|
|
673
|
+
transcriptId String? @map("transcript_id") @db.Uuid
|
|
674
|
+
|
|
675
|
+
// `{charStart,charEnd}` or `{segmentId}` — which shape depends on which of
|
|
676
|
+
// `noteId`/`transcriptId` is set.
|
|
677
|
+
span Json? @db.JsonB
|
|
678
|
+
status KgMentionStatus @default(linked)
|
|
679
|
+
score Float?
|
|
680
|
+
|
|
681
|
+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
|
682
|
+
|
|
683
|
+
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
|
|
684
|
+
entity KgEntity @relation(fields: [entityId], references: [id], onDelete: Cascade)
|
|
685
|
+
note Note? @relation(fields: [noteId], references: [id], onDelete: Cascade)
|
|
686
|
+
transcript Transcript? @relation(fields: [transcriptId], references: [id], onDelete: Cascade)
|
|
687
|
+
|
|
688
|
+
@@index([ownerId, entityId])
|
|
689
|
+
@@index([noteId])
|
|
690
|
+
@@index([transcriptId])
|
|
691
|
+
// HAND-WRITTEN in migration.sql only: `kg_mentions_one_source_chk`. See
|
|
692
|
+
// the section header's "EIGHT CHECK CONSTRAINTS" note.
|
|
693
|
+
@@map("kg_mentions")
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// -----------------------------------------------------------------------------
|
|
697
|
+
// `kg_proposals` — one row per extraction, import, or resolution run
|
|
698
|
+
// (§8, §18.3; the complete kind/status enums are declared above THIS
|
|
699
|
+
// migration, issue #351, so #363/#364/#366/#387 never need `ALTER TYPE`).
|
|
700
|
+
// Only `kind = 'extraction'` has a note — `kg_proposals_note_source_chk`
|
|
701
|
+
// requires `note_version` for `extraction` and requires BOTH `note_id` AND
|
|
702
|
+
// `note_version` NULL for `import`/`resolution`. The CHECK keys on
|
|
703
|
+
// `note_version`, never `note_id`: `note_id` is inserted non-null for every
|
|
704
|
+
// extraction and can only become NULL afterwards via the note's own
|
|
705
|
+
// `SetNull` FK once the note is hard-deleted, so a CHECK on `note_id` would
|
|
706
|
+
// start failing for rows that were valid when written. `commit_log` is
|
|
707
|
+
// #366's internal undo record — the commit writes it, the revert reads it —
|
|
708
|
+
// and it is NEVER serialized to clients; `stats` is the display copy.
|
|
709
|
+
// `job_id` mirrors `TranscriptExport.jobId` exactly (`@unique`, nullable,
|
|
710
|
+
// SetNull): a proposal's own review lifetime is independent of
|
|
711
|
+
// `job.history.purge`'s schedule for the job row.
|
|
712
|
+
// -----------------------------------------------------------------------------
|
|
713
|
+
model KgProposal {
|
|
714
|
+
id String @id @default(uuid()) @db.Uuid
|
|
715
|
+
ownerId String @map("owner_id") @db.Uuid
|
|
716
|
+
kind KgProposalKind
|
|
717
|
+
status KgProposalStatus @default(draft)
|
|
718
|
+
|
|
719
|
+
// Only `kind = 'extraction'` has a note. See the block comment above the
|
|
720
|
+
// model for why `kg_proposals_note_source_chk` keys on `noteVersion`.
|
|
721
|
+
noteId String? @map("note_id") @db.Uuid
|
|
722
|
+
noteVersion Int? @map("note_version")
|
|
723
|
+
|
|
724
|
+
// Generation context — which task-model resolution actually ran (§20),
|
|
725
|
+
// recorded rather than re-derived, so an administrator changing the
|
|
726
|
+
// default tomorrow never rewrites what an already-committed proposal used
|
|
727
|
+
// yesterday.
|
|
728
|
+
model String?
|
|
729
|
+
provider String?
|
|
730
|
+
systemPrompt String? @map("system_prompt")
|
|
731
|
+
userContent String? @map("user_content")
|
|
732
|
+
userGuidance Json? @map("user_guidance") @db.JsonB
|
|
733
|
+
|
|
734
|
+
stats Json @default("{}") @db.JsonB
|
|
735
|
+
|
|
736
|
+
committedAt DateTime? @map("committed_at") @db.Timestamptz
|
|
737
|
+
revertedAt DateTime? @map("reverted_at") @db.Timestamptz
|
|
738
|
+
jobId String? @unique @map("job_id") @db.Uuid
|
|
739
|
+
|
|
740
|
+
// #366's internal undo record — the commit writes it, the revert reads
|
|
741
|
+
// it. NEVER serialized to clients; `stats` above is the display copy.
|
|
742
|
+
commitLog Json? @map("commit_log") @db.JsonB
|
|
743
|
+
|
|
744
|
+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
|
745
|
+
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
|
746
|
+
|
|
747
|
+
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
|
|
748
|
+
note Note? @relation(fields: [noteId], references: [id], onDelete: SetNull)
|
|
749
|
+
job Job? @relation(fields: [jobId], references: [id], onDelete: SetNull)
|
|
750
|
+
|
|
751
|
+
items KgProposalItem[]
|
|
752
|
+
|
|
753
|
+
@@index([ownerId, status, createdAt(sort: Desc)])
|
|
754
|
+
@@index([noteId, createdAt(sort: Desc)])
|
|
755
|
+
// HAND-WRITTEN in migration.sql only: `kg_proposals_note_draft_uniq_idx`,
|
|
756
|
+
// `kg_proposals_note_extracting_uniq_idx`,
|
|
757
|
+
// `kg_proposals_owner_import_extracting_uniq_idx` (three partial unique
|
|
758
|
+
// indexes) and `kg_proposals_note_source_chk`. See the section header's
|
|
759
|
+
// "FIVE PARTIAL UNIQUE INDEXES" / "EIGHT CHECK CONSTRAINTS" notes.
|
|
760
|
+
@@map("kg_proposals")
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// -----------------------------------------------------------------------------
|
|
764
|
+
// `kg_proposal_items` — one row per proposed entity, relation, item, or
|
|
765
|
+
// closing edit (`closing` is §7's "closing a temporal edge is a proposal
|
|
766
|
+
// row" case). `merge_into_id`/`distinct_from` are #366's reviewer-override
|
|
767
|
+
// columns, declared up front by this migration (issue #351) so #366 needs
|
|
768
|
+
// no schema change of its own: `merge_into_id` is set IFF `decision =
|
|
769
|
+
// 'merge_into'` (`kg_proposal_items_merge_into_chk`), SetNull so losing the
|
|
770
|
+
// target entity clears the override rather than blocking the delete;
|
|
771
|
+
// `distinct_from` records the "not the same as" candidates a reviewer ruled
|
|
772
|
+
// out, as plain uuids (no FK — these are recorded facts about a review
|
|
773
|
+
// decision, not live references another table's delete must cascade
|
|
774
|
+
// through). `committed_ref_id` is polymorphic (no FK), the row this item
|
|
775
|
+
// became once committed — #366's revert reads this to know exactly what to
|
|
776
|
+
// undo.
|
|
777
|
+
// -----------------------------------------------------------------------------
|
|
778
|
+
model KgProposalItem {
|
|
779
|
+
id String @id @default(uuid()) @db.Uuid
|
|
780
|
+
proposalId String @map("proposal_id") @db.Uuid
|
|
781
|
+
kind KgProposalItemKind
|
|
782
|
+
|
|
783
|
+
payload Json @db.JsonB
|
|
784
|
+
resolution Json? @db.JsonB
|
|
785
|
+
editedPayload Json? @map("edited_payload") @db.JsonB
|
|
786
|
+
|
|
787
|
+
decision KgProposalItemDecision @default(pending)
|
|
788
|
+
// Values from #363's `PROPOSAL_ITEM_FLAGS` — plain text[], not an enum:
|
|
789
|
+
// the review panel's pre-check flags are a growing, code-owned set, the
|
|
790
|
+
// same reasoning `kg_entities.type` stays plain text.
|
|
791
|
+
flags String[] @default([])
|
|
792
|
+
origin KgProposalItemOrigin @default(ai)
|
|
793
|
+
|
|
794
|
+
// #366's reviewer-override columns. See the block comment above the model
|
|
795
|
+
// for the exactly-one-decision argument `kg_proposal_items_merge_into_chk`
|
|
796
|
+
// enforces, and why `mergeIntoId` is SetNull.
|
|
797
|
+
mergeIntoId String? @map("merge_into_id") @db.Uuid
|
|
798
|
+
distinctFrom String[] @default([]) @map("distinct_from")
|
|
799
|
+
|
|
800
|
+
// Polymorphic, no FK — the `kg_entities`/`kg_relations`/`kg_items` row
|
|
801
|
+
// this item became once committed.
|
|
802
|
+
committedRefId String? @map("committed_ref_id") @db.Uuid
|
|
803
|
+
sortOrder Int @default(0) @map("sort_order")
|
|
804
|
+
|
|
805
|
+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
|
806
|
+
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
|
807
|
+
|
|
808
|
+
proposal KgProposal @relation(fields: [proposalId], references: [id], onDelete: Cascade)
|
|
809
|
+
mergeInto KgEntity? @relation("KgProposalItemMergeInto", fields: [mergeIntoId], references: [id], onDelete: SetNull)
|
|
810
|
+
|
|
811
|
+
@@index([proposalId, kind, sortOrder])
|
|
812
|
+
// HAND-WRITTEN in migration.sql only: `kg_proposal_items_kind_payload_chk`
|
|
813
|
+
// and `kg_proposal_items_merge_into_chk`. See the section header's "EIGHT
|
|
814
|
+
// CHECK CONSTRAINTS" note.
|
|
815
|
+
@@map("kg_proposal_items")
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// One row per merge (§7), with the full reversal payload — what #366 (and
|
|
819
|
+
// an entity page's "undo merge" action) replays to restore the merged
|
|
820
|
+
// entity. `survivor_id`/`merged_id` both Cascade: a merge record has no
|
|
821
|
+
// meaning once either entity it names is gone.
|
|
822
|
+
model KgMerge {
|
|
823
|
+
id String @id @default(uuid()) @db.Uuid
|
|
824
|
+
ownerId String @map("owner_id") @db.Uuid
|
|
825
|
+
survivorId String @map("survivor_id") @db.Uuid
|
|
826
|
+
mergedId String @map("merged_id") @db.Uuid
|
|
827
|
+
|
|
828
|
+
reversal Json @db.JsonB
|
|
829
|
+
reversedAt DateTime? @map("reversed_at") @db.Timestamptz
|
|
830
|
+
|
|
831
|
+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
|
832
|
+
|
|
833
|
+
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
|
|
834
|
+
survivor KgEntity @relation("KgMergeSurvivor", fields: [survivorId], references: [id], onDelete: Cascade)
|
|
835
|
+
merged KgEntity @relation("KgMergeMerged", fields: [mergedId], references: [id], onDelete: Cascade)
|
|
836
|
+
|
|
837
|
+
@@index([ownerId, createdAt(sort: Desc)])
|
|
838
|
+
@@index([survivorId])
|
|
839
|
+
@@index([mergedId])
|
|
840
|
+
@@map("kg_merges")
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Confirmed-NOT-the-same pairs (§7), skipped by every future
|
|
844
|
+
// candidate-generation pass. `a_id < b_id` is enforced by
|
|
845
|
+
// `kg_distinct_pairs_order_chk` (hand-written — Prisma cannot express a
|
|
846
|
+
// cross-column CHECK), so `(a, b)` and `(b, a)` can never both exist as two
|
|
847
|
+
// different rows for what is the same unordered fact. Both entity FKs
|
|
848
|
+
// Cascade.
|
|
849
|
+
model KgDistinctPair {
|
|
850
|
+
ownerId String @map("owner_id") @db.Uuid
|
|
851
|
+
aId String @map("a_id") @db.Uuid
|
|
852
|
+
bId String @map("b_id") @db.Uuid
|
|
853
|
+
|
|
854
|
+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
|
855
|
+
|
|
856
|
+
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
|
|
857
|
+
entityA KgEntity @relation("KgDistinctPairA", fields: [aId], references: [id], onDelete: Cascade)
|
|
858
|
+
entityB KgEntity @relation("KgDistinctPairB", fields: [bId], references: [id], onDelete: Cascade)
|
|
859
|
+
|
|
860
|
+
@@id([ownerId, aId, bId])
|
|
861
|
+
// HAND-WRITTEN in migration.sql only: `kg_distinct_pairs_order_chk`. See
|
|
862
|
+
// the section header's "EIGHT CHECK CONSTRAINTS" note.
|
|
863
|
+
@@map("kg_distinct_pairs")
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// One row per user-defined attribute (§17.3) — the schema half of the
|
|
867
|
+
// ontology's user-extensibility story. `owner_id` Cascade, the same
|
|
868
|
+
// reasoning every other `kg_*` table's `owner_id` follows.
|
|
869
|
+
model KgAttributeDef {
|
|
870
|
+
id String @id @default(uuid()) @db.Uuid
|
|
871
|
+
ownerId String @map("owner_id") @db.Uuid
|
|
872
|
+
|
|
873
|
+
entityType String @map("entity_type")
|
|
874
|
+
key String
|
|
875
|
+
label String
|
|
876
|
+
kind KgAttributeKind
|
|
877
|
+
options Json? @db.JsonB
|
|
878
|
+
|
|
879
|
+
extractable Boolean @default(false)
|
|
880
|
+
extractionHint String? @map("extraction_hint")
|
|
881
|
+
|
|
882
|
+
sensitivity KgSensitivity?
|
|
883
|
+
sortOrder Int @default(0) @map("sort_order")
|
|
884
|
+
deprecatedAt DateTime? @map("deprecated_at") @db.Timestamptz
|
|
885
|
+
|
|
886
|
+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
|
887
|
+
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
|
888
|
+
|
|
889
|
+
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
|
|
890
|
+
|
|
891
|
+
@@unique([ownerId, entityType, key])
|
|
892
|
+
@@index([ownerId, entityType, sortOrder])
|
|
893
|
+
@@map("kg_attribute_defs")
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// §9.2's precomputed entity brief — "precompute once, read cheaply," the
|
|
897
|
+
// same economy `kg_graph_layouts` (its own, later migration) applies to a
|
|
898
|
+
// whole-graph layout. `entity_id` is the PRIMARY KEY (not a separate `id`):
|
|
899
|
+
// there is exactly one digest per entity, always replaced in place, never
|
|
900
|
+
// versioned.
|
|
901
|
+
model KgEntityDigest {
|
|
902
|
+
entityId String @id @map("entity_id") @db.Uuid
|
|
903
|
+
ownerId String @map("owner_id") @db.Uuid
|
|
904
|
+
|
|
905
|
+
summary String
|
|
906
|
+
citations Json @db.JsonB
|
|
907
|
+
coversUntil DateTime @map("covers_until") @db.Timestamptz
|
|
908
|
+
model String
|
|
909
|
+
generatedAt DateTime @map("generated_at") @db.Timestamptz
|
|
910
|
+
|
|
911
|
+
entity KgEntity @relation(fields: [entityId], references: [id], onDelete: Cascade)
|
|
912
|
+
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
|
|
913
|
+
|
|
914
|
+
@@index([ownerId])
|
|
915
|
+
@@map("kg_entity_digests")
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// (user_id, entity_id, last_viewed_at) — §9.2's "recently viewed" list. The
|
|
919
|
+
// ONE table in this section keyed on `user_id`, not `owner_id` — see
|
|
920
|
+
// convention 1 above: which entities a viewer has looked at is a per-viewer
|
|
921
|
+
// fact, not an ownership fact (a shared/admin view is out of scope today,
|
|
922
|
+
// but the column name is deliberately not `owner_id` so it never implies
|
|
923
|
+
// one). A plain `@@unique`, exactly as issue #351's canonical column list
|
|
924
|
+
// specifies — NOT the partial unique §10's prose mentions elsewhere;
|
|
925
|
+
// nothing about this row is conditional.
|
|
926
|
+
model KgEntityView {
|
|
927
|
+
id String @id @default(uuid()) @db.Uuid
|
|
928
|
+
userId String @map("user_id") @db.Uuid
|
|
929
|
+
entityId String @map("entity_id") @db.Uuid
|
|
930
|
+
lastViewedAt DateTime @map("last_viewed_at") @db.Timestamptz
|
|
931
|
+
|
|
932
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
933
|
+
entity KgEntity @relation(fields: [entityId], references: [id], onDelete: Cascade)
|
|
934
|
+
|
|
935
|
+
@@unique([userId, entityId])
|
|
936
|
+
@@map("kg_entity_views")
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// =============================================================================
|
|
940
|
+
// `kg_exports` — RDF exports of one owner's graph (issue #386, epic #349;
|
|
941
|
+
// docs/specs/ontology.md §18.2)
|
|
942
|
+
// =============================================================================
|
|
943
|
+
//
|
|
944
|
+
// Mirrors `note_exports`: one row per requested render, produced by the
|
|
945
|
+
// `kg.export` job into a `managed_by: 'graph'` storage object, expiring 7 days
|
|
946
|
+
// after it was requested (the daily sweep deletes the row and the file).
|
|
947
|
+
//
|
|
948
|
+
// CONTENT-ADDRESSED on `graph_fingerprint` — a SHA-256 over the ontology
|
|
949
|
+
// version, the format, and the owner's graph "shape" (max updated_at plus row
|
|
950
|
+
// counts of the kg_* tables). `POST /api/graph/exports` reuses an unexpired,
|
|
951
|
+
// non-failed row with the same `(owner, format, fingerprint)`, so asking twice
|
|
952
|
+
// for an unchanged graph renders once. A plain, NON-unique index — an expired
|
|
953
|
+
// or failed row may briefly coexist with a fresh one sharing the key, exactly
|
|
954
|
+
// as `note_exports_lookup_idx` allows.
|
|
955
|
+
//
|
|
956
|
+
// `owner_id` Cascade; `object_id` Restrict (the file goes only after the row
|
|
957
|
+
// lets go of it); `job_id` `@unique`/SetNull (the `NoteExport.jobId` shape).
|
|
958
|
+
// No hand-written SQL: every constraint here is Prisma-expressible.
|
|
959
|
+
// =============================================================================
|
|
960
|
+
|
|
961
|
+
enum KgExportFormat {
|
|
962
|
+
jsonld
|
|
963
|
+
turtle
|
|
964
|
+
nquads
|
|
965
|
+
|
|
966
|
+
@@map("kg_export_format")
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
enum KgExportStatus {
|
|
970
|
+
pending
|
|
971
|
+
running
|
|
972
|
+
ready
|
|
973
|
+
failed
|
|
974
|
+
|
|
975
|
+
@@map("kg_export_status")
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
model KgExport {
|
|
979
|
+
id String @id @default(uuid()) @db.Uuid
|
|
980
|
+
ownerId String @map("owner_id") @db.Uuid
|
|
981
|
+
format KgExportFormat
|
|
982
|
+
status KgExportStatus @default(pending)
|
|
983
|
+
graphFingerprint String @map("graph_fingerprint")
|
|
984
|
+
ontologyVersion String @map("ontology_version")
|
|
985
|
+
objectId String? @map("object_id") @db.Uuid
|
|
986
|
+
// `{ entities, relations, items, evidence, excludedSensitive, bytes }` — counts only.
|
|
987
|
+
stats Json @default("{}") @db.JsonB
|
|
988
|
+
// Short and generic; never row data.
|
|
989
|
+
errorMessage String? @map("error_message")
|
|
990
|
+
jobId String? @unique @map("job_id") @db.Uuid
|
|
991
|
+
expiresAt DateTime @map("expires_at") @db.Timestamptz(6)
|
|
992
|
+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
|
|
993
|
+
|
|
994
|
+
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
|
|
995
|
+
object StorageObject? @relation(fields: [objectId], references: [id], onDelete: Restrict)
|
|
996
|
+
job Job? @relation(fields: [jobId], references: [id], onDelete: SetNull)
|
|
997
|
+
|
|
998
|
+
@@index([ownerId, format, graphFingerprint])
|
|
999
|
+
@@index([expiresAt])
|
|
1000
|
+
@@map("kg_exports")
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/// Latest whole-graph layout per owner (spec §22, issue #371). A cache of derived data — never
|
|
1004
|
+
/// a source of truth; labels are NOT stored (read live, so a renamed or forgotten entity never
|
|
1005
|
+
/// shows a stale label). `clusters`/`positions` are versioned JSON blobs (`{ version: 1, ... }`)
|
|
1006
|
+
/// rather than typed columns, produced by the pure `computeLayout()` function and consumed only
|
|
1007
|
+
/// by `GraphOverviewService`, which joins live entity rows back in at read time.
|
|
1008
|
+
model KgGraphLayout {
|
|
1009
|
+
id String @id @default(uuid()) @db.Uuid
|
|
1010
|
+
ownerId String @map("owner_id") @db.Uuid
|
|
1011
|
+
|
|
1012
|
+
computedAt DateTime @map("computed_at") @db.Timestamptz(6)
|
|
1013
|
+
|
|
1014
|
+
nodeCount Int @map("node_count")
|
|
1015
|
+
edgeCount Int @map("edge_count")
|
|
1016
|
+
|
|
1017
|
+
clusters Json @db.JsonB
|
|
1018
|
+
positions Json @db.JsonB
|
|
1019
|
+
|
|
1020
|
+
ontologyVersion String @map("ontology_version")
|
|
1021
|
+
|
|
1022
|
+
// max(updated_at) over the owner's kg_entities/kg_relations read by this run — the staleness
|
|
1023
|
+
// check `GET /api/graph/overview` compares against a fresh count, never recomputing itself.
|
|
1024
|
+
sourceUpdatedAt DateTime? @map("source_updated_at") @db.Timestamptz(6)
|
|
1025
|
+
|
|
1026
|
+
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
|
|
1027
|
+
|
|
1028
|
+
@@index([ownerId, computedAt(sort: Desc)])
|
|
1029
|
+
@@map("kg_graph_layouts")
|
|
1030
|
+
}
|