@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,359 @@
|
|
|
1
|
+
// GENERATED by platform db compose — do not edit
|
|
2
|
+
// source: package:db-backup.prisma
|
|
3
|
+
|
|
4
|
+
// =============================================================================
|
|
5
|
+
// Database Backup Runs (issue #281, epic #254)
|
|
6
|
+
// =============================================================================
|
|
7
|
+
//
|
|
8
|
+
// One row per attempt to dump this deployment's PostgreSQL database into
|
|
9
|
+
// object storage — and, from Phase 7 onwards, per attempt to restore FROM one.
|
|
10
|
+
// `DatabaseBackupRunnerService` (`src/db-backup/db-backup-runner.service.ts`)
|
|
11
|
+
// is the only writer of the backup half; the columns in the restore-audit
|
|
12
|
+
// group are declared here now and written by #285.
|
|
13
|
+
//
|
|
14
|
+
// -----------------------------------------------------------------------------
|
|
15
|
+
// ### Why this WAS not a queue job, and why it now is (issue #351, epic #345)
|
|
16
|
+
// -----------------------------------------------------------------------------
|
|
17
|
+
//
|
|
18
|
+
// ⚠ THIS SECTION USED TO SAY "DO NOT MIGRATE THIS ONTO THE QUEUE". It has
|
|
19
|
+
// been migrated, deliberately, and the history is kept because the three
|
|
20
|
+
// objections were correct and a reader needs to know WHERE EACH IS NOW
|
|
21
|
+
// ANSWERED rather than assume they were waved away.
|
|
22
|
+
//
|
|
23
|
+
// 1. `jobs.stuckThresholdMinutes` DEFAULTS TO 30 MINUTES. A dump of a real
|
|
24
|
+
// production database routinely runs longer than that. The lease reaper
|
|
25
|
+
// (`JobStuckResetTask`) would see a `running` job whose lease had
|
|
26
|
+
// expired, conclude its executor had died, and RESET IT TO `pending` —
|
|
27
|
+
// while `pg_dump` was still streaming. The next claim starts a SECOND
|
|
28
|
+
// `pg_dump`, writing to the SAME derived storage key as the first, and
|
|
29
|
+
// two processes interleave their output into one object.
|
|
30
|
+
// → ANSWERED BY #346's PER-TYPE PROFILES. `db.backup.run` declares
|
|
31
|
+
// `maxRuntimeMs: 6h`, and the claim's lease is DERIVED from that ceiling
|
|
32
|
+
// (`resolveJobLeaseMs`), never declared beside it — so a lease shorter
|
|
33
|
+
// than the permitted runtime is unrepresentable rather than merely
|
|
34
|
+
// avoided.
|
|
35
|
+
// 2. THE IN-PROCESS WORKER HAS NO LEASE-RENEWAL PATH. `JobWorker` claimed
|
|
36
|
+
// with a lease and never extended it, so the reaper's verdict above was
|
|
37
|
+
// not a rare race but unconditional for any long job.
|
|
38
|
+
// → ANSWERED BY #347. The worker renews on a ticker (lease ÷ 3) for
|
|
39
|
+
// exactly as long as `process()` runs.
|
|
40
|
+
// 3. A JOB HAS `attempts` AND A RETRY BUDGET. Automatically re-running a
|
|
41
|
+
// failed multi-gigabyte dump is exactly the wrong behaviour: it burns
|
|
42
|
+
// hours of I/O on a database that is probably already unwell, and the
|
|
43
|
+
// correct retry for a backup is "the next scheduled one", tomorrow.
|
|
44
|
+
// → ANSWERED BY `maxAttempts: 1` on the same profile. The policy did not
|
|
45
|
+
// change; what changed is that `JobStuckService`'s give-up phase now
|
|
46
|
+
// ENFORCES it, instead of it holding by the accident of there being no
|
|
47
|
+
// queue to disagree.
|
|
48
|
+
//
|
|
49
|
+
// This table still has its OWN heartbeat (`lastHeartbeatAt`, written every
|
|
50
|
+
// ~20s with live `bytesWritten`), its OWN staleness policy
|
|
51
|
+
// (`databaseBackup.runStaleMinutes`) and its OWN terminal states, and all
|
|
52
|
+
// three are still load-bearing — but their jobs have narrowed. The heartbeat
|
|
53
|
+
// is now PROGRESS REPORTING for the UI (the lease is the liveness signal),
|
|
54
|
+
// and the stale sweep is the backstop for the one path that still has no job:
|
|
55
|
+
// the `pre_restore` safety dump, which `DatabaseRestoreService` takes through
|
|
56
|
+
// `startBackup` because a restore must not wait on a worker slot.
|
|
57
|
+
//
|
|
58
|
+
// ⚠ THE TWO ROWS ARE NOT REDUNDANT. The `jobs` row answers who asked, when,
|
|
59
|
+
// which executor, which attempt and what error; this row answers what was
|
|
60
|
+
// PRODUCED — key, bucket, bytes, checksum, `verifiedAt`, the db/app/migration
|
|
61
|
+
// audit and the restore audit. They also have independent lifetimes: a job is
|
|
62
|
+
// purged by `job.history.purge` on a retention schedule that has nothing to do
|
|
63
|
+
// with how long the archive is kept, which is exactly why `jobId` is
|
|
64
|
+
// `onDelete: SetNull` and not `Cascade`.
|
|
65
|
+
//
|
|
66
|
+
// -----------------------------------------------------------------------------
|
|
67
|
+
// THE SINGLE-ACTIVE-RUN GUARD IS A PARTIAL UNIQUE INDEX, NOT APPLICATION CODE
|
|
68
|
+
// -----------------------------------------------------------------------------
|
|
69
|
+
//
|
|
70
|
+
// `database_backup_runs_active_uniq_idx` (hand-written in
|
|
71
|
+
// `prisma/migrations/20260907120000_add_database_backup_runs/migration.sql`,
|
|
72
|
+
// TIGHTENED by
|
|
73
|
+
// `prisma/migrations/<add_backup_run_job_link>/migration.sql`, epic #345,
|
|
74
|
+
// issue #351 — see that migration's own header for why) is a UNIQUE index on
|
|
75
|
+
// the CONSTANT EXPRESSION `(true)` restricted to `status IN
|
|
76
|
+
// ('pending','running')`. It is what makes "at most one backup at a time"
|
|
77
|
+
// true ACROSS REPLICAS.
|
|
78
|
+
//
|
|
79
|
+
// REJECTED: a `findFirst({ where: { status: 'running' } })` before the insert.
|
|
80
|
+
// It is a check-then-act race, and it is racy exactly when it matters — a
|
|
81
|
+
// scheduled tick on replica A and an admin's manual click on replica B, in
|
|
82
|
+
// the same second. Both would see no active run, both would insert, and two
|
|
83
|
+
// `pg_dump` processes would stream into two objects while the settings say
|
|
84
|
+
// one backup a night. Only the database can make "is one already active"
|
|
85
|
+
// atomic with the insert that would violate it.
|
|
86
|
+
//
|
|
87
|
+
// The loser's Prisma `P2002` becomes a typed `DatabaseBackupAlreadyRunningError`
|
|
88
|
+
// carrying the winner's run id, which #282/#283 turn into a 409.
|
|
89
|
+
//
|
|
90
|
+
// ⚠ PRISMA CANNOT EXPRESS THIS INDEX — INTENTIONAL SCHEMA DRIFT. The schema
|
|
91
|
+
// language has no syntax for a partial index OR an expression index, exactly
|
|
92
|
+
// as recorded above the `Job` model for `jobs_active_dedup_uniq_idx` and its
|
|
93
|
+
// two siblings. So `prisma migrate dev`/`diff` will not regenerate it and, if
|
|
94
|
+
// anyone diffs this schema against a live database, will want to DROP it.
|
|
95
|
+
// That drift is deliberate and permanent: do not "fix" it, and do not try to
|
|
96
|
+
// force it into `@@unique` syntax that does not exist. The migration file
|
|
97
|
+
// carries the same warning next to the statement itself.
|
|
98
|
+
//
|
|
99
|
+
// ⚠ WHAT THE INDEX ADMITS, PRECISELY, AND WHY THAT CHANGED. The ORIGINAL index
|
|
100
|
+
// keyed on the `status` COLUMN, filtered to two values — that admits at most
|
|
101
|
+
// one `pending` row AND at most one `running` row AT THE SAME TIME, i.e. TWO
|
|
102
|
+
// active runs, not one. That was harmless only as long as
|
|
103
|
+
// `DatabaseBackupRunnerService` inserted its claim directly as `running` and
|
|
104
|
+
// never wrote `pending`. #351 makes the backup a queue job: `POST
|
|
105
|
+
// /api/admin/db-backup/runs` now enqueues the job and creates the run row in
|
|
106
|
+
// `pending` BEFORE any worker has claimed it (so the endpoint can return a run
|
|
107
|
+
// id immediately and `GET /runs/{id}` keeps working), and the handler flips it
|
|
108
|
+
// to `running` once claimed. The moment `pending` rows are real, the
|
|
109
|
+
// column-keyed index stops meaning "at most one active run" and starts
|
|
110
|
+
// meaning "at most two". Keying on `(true)` instead makes every active row —
|
|
111
|
+
// whichever of the two statuses it holds — index to the SAME key, so at most
|
|
112
|
+
// one can exist across both statuses COMBINED. This is the exact tightening
|
|
113
|
+
// this comment always said a future path needing both states populated at
|
|
114
|
+
// once would require ("tighten this index to a constant expression, never
|
|
115
|
+
// move the guard into a service") — it has now happened.
|
|
116
|
+
//
|
|
117
|
+
// -----------------------------------------------------------------------------
|
|
118
|
+
// WHY THE RESTORE AUDIT LIVES ON THE BACKUP'S OWN ROW
|
|
119
|
+
// -----------------------------------------------------------------------------
|
|
120
|
+
//
|
|
121
|
+
// `restoreStatus`, `restoreError`, `restoredAt`, `restoredById`,
|
|
122
|
+
// `restoreScratchDb`, `restoreOldDb`, `swappedAt` and `preRestoreBackupId` are
|
|
123
|
+
// all nullable and all belong to Phase 7. They are columns on THIS table
|
|
124
|
+
// rather than a `database_restore_runs` table because A RESTORE IS ALWAYS
|
|
125
|
+
// DEFINED IN TERMS OF EXACTLY ONE BACKUP: there is no restore without an
|
|
126
|
+
// archive to restore, no archive is restored twice concurrently, and every
|
|
127
|
+
// question anyone asks about a restore ("which dump was this?", "how big was
|
|
128
|
+
// it?", "what migration was the schema on?") is answered by the backup's own
|
|
129
|
+
// columns. A separate table would buy a join on a strict 1:1 relationship and
|
|
130
|
+
// would let the two rows disagree about which archive was replayed.
|
|
131
|
+
//
|
|
132
|
+
// The cost is honest: a backup restored twice (a restore that failed, then
|
|
133
|
+
// retried) overwrites the first attempt's audit fields. That is the correct
|
|
134
|
+
// trade — the interesting record is the state of the LAST restore, and the
|
|
135
|
+
// full history of attempts is in `audit_events`.
|
|
136
|
+
//
|
|
137
|
+
// `preRestoreBackupId` is a SELF-FK: before a restore swaps a database away,
|
|
138
|
+
// #285 takes a `pre_restore` backup, and this column points the restore at the
|
|
139
|
+
// safety net it took. `onDelete: SetNull`, so pruning that safety backup by
|
|
140
|
+
// retention never deletes the restore record that referenced it.
|
|
141
|
+
model DatabaseBackupRun {
|
|
142
|
+
id String @id @default(uuid()) @db.Uuid
|
|
143
|
+
|
|
144
|
+
// The `jobs` row that drove this run, once the dump became a queue job
|
|
145
|
+
// (`db.backup.run`, epic #345, issue #351). Three properties, each
|
|
146
|
+
// load-bearing:
|
|
147
|
+
//
|
|
148
|
+
// 1. `@unique` is what makes a SECOND run row for one job
|
|
149
|
+
// UNREPRESENTABLE. #348 lets a handler derive its own node upload
|
|
150
|
+
// key, and a node may legitimately ask for that key twice (a
|
|
151
|
+
// timed-out transfer, a lost response, a restarted process still
|
|
152
|
+
// holding the lease). The backup's `deriveOutputKey` re-reads by
|
|
153
|
+
// `jobId` and returns the existing key; the unique index is what
|
|
154
|
+
// GUARANTEES there is at most one to find, rather than that being a
|
|
155
|
+
// convention the service has to remember.
|
|
156
|
+
//
|
|
157
|
+
// 2. Nullable, ON PURPOSE. Not every run has a job: the `pre_restore`
|
|
158
|
+
// dump `DatabaseRestoreService` takes goes through the runner
|
|
159
|
+
// directly, with no job in between, and rows predating this
|
|
160
|
+
// migration have no job at all. A required column would either break
|
|
161
|
+
// the restore path or need a backfill that invents job rows for
|
|
162
|
+
// history.
|
|
163
|
+
//
|
|
164
|
+
// 3. `onDelete: SetNull`, NOT `Cascade`. `job.history.purge` deletes
|
|
165
|
+
// `jobs` rows on a retention schedule that is completely independent
|
|
166
|
+
// of the archive retention. Cascade would let a routine history purge
|
|
167
|
+
// delete the record of a backup whose archive is still in the bucket
|
|
168
|
+
// and still restorable — the exact "deleting history must not delete
|
|
169
|
+
// history" hazard `JobStatsRollup` already exists to avoid on the
|
|
170
|
+
// stats side. Losing the LINK is acceptable; losing the RUN ROW is
|
|
171
|
+
// not.
|
|
172
|
+
jobId String? @unique @map("job_id") @db.Uuid
|
|
173
|
+
job Job? @relation(fields: [jobId], references: [id], onDelete: SetNull)
|
|
174
|
+
|
|
175
|
+
// `pending` = claimed, dump not yet spawned. `running` = a `pg_dump` is
|
|
176
|
+
// streaming. `completed` = uploaded AND verified (see `verifiedAt`).
|
|
177
|
+
// `failed` = terminal, `lastError` says why. `stale` = the run's heartbeat
|
|
178
|
+
// stopped for longer than `databaseBackup.runStaleMinutes` and #282's sweep
|
|
179
|
+
// gave up on it — DISTINCT from `failed` on purpose, because nothing
|
|
180
|
+
// observed this run fail; the process holding it disappeared, and an
|
|
181
|
+
// operator reading the list needs to be able to tell "the dump errored"
|
|
182
|
+
// from "the container went away mid-dump".
|
|
183
|
+
status DatabaseBackupStatus @default(pending)
|
|
184
|
+
|
|
185
|
+
// `manual` = an administrator clicked. `scheduled` = #282's cron.
|
|
186
|
+
// `pre_restore` = the safety backup #285 takes immediately before swapping
|
|
187
|
+
// a restored database into place. No default: a run with no known origin is
|
|
188
|
+
// a run nobody can attribute, and every caller knows which it is.
|
|
189
|
+
trigger DatabaseBackupTrigger
|
|
190
|
+
|
|
191
|
+
// NULL until the dump is claimed / until it settles. `lastHeartbeatAt` is
|
|
192
|
+
// now PROGRESS REPORTING for the UI rather than this table's liveness
|
|
193
|
+
// signal — the job's LEASE is the liveness signal since #351/#352 — see the
|
|
194
|
+
// "Why this WAS not a queue job, and why it now is" block above.
|
|
195
|
+
startedAt DateTime? @map("started_at") @db.Timestamptz
|
|
196
|
+
finishedAt DateTime? @map("finished_at") @db.Timestamptz
|
|
197
|
+
lastHeartbeatAt DateTime? @map("last_heartbeat_at") @db.Timestamptz
|
|
198
|
+
|
|
199
|
+
// BigInt, both of them, and NOT `Int`. A PostgreSQL dump crossing 2 GiB is
|
|
200
|
+
// ordinary, and `Int` is a signed 32-bit column that overflows at 2147483647
|
|
201
|
+
// bytes — the failure would be a write error on the LARGEST backups, i.e.
|
|
202
|
+
// exactly the deployments this feature exists for, and it would surface as
|
|
203
|
+
// a failed run rather than as a wrong number.
|
|
204
|
+
//
|
|
205
|
+
// ⚠ A `BigInt` field is the JS `bigint` primitive, which `JSON.stringify`
|
|
206
|
+
// REFUSES to serialise. #283's response DTO must convert these two to
|
|
207
|
+
// `string` (or `Number`) explicitly — see the note above `JobStatsRollup`
|
|
208
|
+
// for the same hazard, decided the other way there because that column is a
|
|
209
|
+
// running average and this one is a byte count that must stay exact.
|
|
210
|
+
//
|
|
211
|
+
// `bytesWritten` is LIVE PROGRESS, rewritten by the heartbeat while the dump
|
|
212
|
+
// streams; `sizeBytes` is the final size, written once at completion. They
|
|
213
|
+
// converge on success and deliberately do not on a failure, where the
|
|
214
|
+
// difference is "how far did it get".
|
|
215
|
+
bytesWritten BigInt @default(0) @map("bytes_written")
|
|
216
|
+
sizeBytes BigInt @default(0) @map("size_bytes")
|
|
217
|
+
|
|
218
|
+
// The storage triple: WHICH provider, WHICH key, WHICH bucket. All three
|
|
219
|
+
// are recorded rather than derived on read, because all three can change
|
|
220
|
+
// under a deployment (a bucket rename, a provider swap) and a backup that
|
|
221
|
+
// cannot say where it was written is a backup nobody can restore. The key
|
|
222
|
+
// is chosen by the SERVER (`buildBackupStorageKey`), never by a caller.
|
|
223
|
+
storageProvider String @map("storage_provider")
|
|
224
|
+
storageKey String @map("storage_key")
|
|
225
|
+
bucket String
|
|
226
|
+
|
|
227
|
+
// `pg_dump`'s archive format — `custom` (`-Fc`) for everything this
|
|
228
|
+
// repository writes. Stored so a future format change does not make old
|
|
229
|
+
// archives unreadable-by-inspection: `pg_restore` needs to know, and
|
|
230
|
+
// guessing from the key's extension is not knowing.
|
|
231
|
+
format String
|
|
232
|
+
|
|
233
|
+
// sha256 of the archive, computed IN THE SAME SINGLE PASS as `sizeBytes`
|
|
234
|
+
// while the bytes stream past on their way to storage — the archive is
|
|
235
|
+
// never buffered and never read twice. NULL until the upload completes.
|
|
236
|
+
checksumSha256 String? @map("checksum_sha256")
|
|
237
|
+
|
|
238
|
+
// The audit quartet: what produced this archive. All four nullable because
|
|
239
|
+
// each is read best-effort and NONE of them may fail a backup — a run that
|
|
240
|
+
// could not read the server version is still a valid backup.
|
|
241
|
+
//
|
|
242
|
+
// `dbVersion` — the PostgreSQL server version this was dumped from.
|
|
243
|
+
// A restore onto an older major will not work, and this
|
|
244
|
+
// is the only place that fact is recorded.
|
|
245
|
+
// `appVersion` — the application build (`resolveApiVersion()`).
|
|
246
|
+
// `migrationName` — the newest applied `_prisma_migrations` row. THE most
|
|
247
|
+
// important of the four for a restore: it says which
|
|
248
|
+
// schema the archive contains, so an operator can tell
|
|
249
|
+
// before replaying it whether the running code will
|
|
250
|
+
// understand what comes back.
|
|
251
|
+
// `pgDumpVersion` — the `pg_dump` client that WROTE the archive (#352,
|
|
252
|
+
// epic #345). It was a trio until the dump could run on
|
|
253
|
+
// a worker node: on that path the client is a binary on
|
|
254
|
+
// a machine this deployment may not own and cannot
|
|
255
|
+
// inspect, so unless the node reports it, nothing
|
|
256
|
+
// anywhere records which `pg_dump` produced the file —
|
|
257
|
+
// and a `pg_restore` older than the `pg_dump` that wrote
|
|
258
|
+
// an archive simply cannot read it. The server path
|
|
259
|
+
// fills the same column from its own image's client, so
|
|
260
|
+
// the row means one thing whichever executor ran.
|
|
261
|
+
dbVersion String? @map("db_version")
|
|
262
|
+
appVersion String? @map("app_version")
|
|
263
|
+
migrationName String? @map("migration_name")
|
|
264
|
+
pgDumpVersion String? @map("pg_dump_version")
|
|
265
|
+
|
|
266
|
+
// Set when the UPLOADED OBJECT was streamed back out of storage and read by
|
|
267
|
+
// `pg_restore --list` with a non-empty table of contents. A run is never
|
|
268
|
+
// `completed` without it: the checksum proves what we sent, this proves what
|
|
269
|
+
// arrived is a readable archive.
|
|
270
|
+
verifiedAt DateTime? @map("verified_at") @db.Timestamptz
|
|
271
|
+
|
|
272
|
+
lastError String? @map("last_error")
|
|
273
|
+
|
|
274
|
+
// NULL for a `scheduled` run — nobody asked for it, a timer did. `SetNull`
|
|
275
|
+
// rather than `Cascade`, matching `AuditEvent.actorUserId` and
|
|
276
|
+
// `Credential.updatedByUser`: this is a HISTORY RECORD, and deleting an
|
|
277
|
+
// administrator's account must never delete the record that a backup was
|
|
278
|
+
// taken. Contrast `WorkerNode.createdBy`, which cascades because a node is
|
|
279
|
+
// live inventory rather than history.
|
|
280
|
+
createdById String? @map("created_by_id") @db.Uuid
|
|
281
|
+
createdBy User? @relation("UserDatabaseBackupRuns", fields: [createdById], references: [id], onDelete: SetNull)
|
|
282
|
+
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
// Restore audit (Phase 7). Declared by #281, written by #285's
|
|
285
|
+
// `DatabaseRestoreService`. See the "WHY THE RESTORE AUDIT LIVES ON THE
|
|
286
|
+
// BACKUP'S OWN ROW" block above.
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
// A plain string, not an enum, for the same reason `Job.executor` is one: a
|
|
290
|
+
// small closed set owned by code rather than by this table's own state
|
|
291
|
+
// machine, and adding a state to it must not cost a migration. NULL means
|
|
292
|
+
// "this backup has never been restored".
|
|
293
|
+
//
|
|
294
|
+
// `restoring` -> `verifying` -> `swapping` -> `completed` | `failed`, plus
|
|
295
|
+
// `rolled_back` for a restore that was swapped in and then undone. That last
|
|
296
|
+
// one is the argument for the column being a string: it was added by #285
|
|
297
|
+
// without a migration. The set lives in `RESTORE_STATUSES`
|
|
298
|
+
// (`src/db-backup/database-restore.service.ts`).
|
|
299
|
+
restoreStatus String? @map("restore_status")
|
|
300
|
+
restoreError String? @map("restore_error")
|
|
301
|
+
|
|
302
|
+
restoredAt DateTime? @map("restored_at") @db.Timestamptz
|
|
303
|
+
restoredById String? @map("restored_by_id") @db.Uuid
|
|
304
|
+
restoredBy User? @relation("UserDatabaseRestores", fields: [restoredById], references: [id], onDelete: SetNull)
|
|
305
|
+
|
|
306
|
+
// The names of the two databases a swap-style restore creates and displaces:
|
|
307
|
+
// the scratch database the archive was replayed into, and the original that
|
|
308
|
+
// was renamed out of the way. Recorded as NAMES because after the swap they
|
|
309
|
+
// are the only handle an operator has on either — `databaseBackup
|
|
310
|
+
// .restoreRollbackMode` decides whether the displaced one survives, and
|
|
311
|
+
// `oldDatabaseRetentionHours` decides for how long.
|
|
312
|
+
restoreScratchDb String? @map("restore_scratch_db")
|
|
313
|
+
restoreOldDb String? @map("restore_old_db")
|
|
314
|
+
swappedAt DateTime? @map("swapped_at") @db.Timestamptz
|
|
315
|
+
|
|
316
|
+
// The `pre_restore` backup taken immediately before this row's archive was
|
|
317
|
+
// swapped in — the way back. `SetNull` so retention pruning that safety
|
|
318
|
+
// backup never deletes the restore record pointing at it.
|
|
319
|
+
preRestoreBackupId String? @map("pre_restore_backup_id") @db.Uuid
|
|
320
|
+
preRestoreBackup DatabaseBackupRun? @relation("PreRestoreBackup", fields: [preRestoreBackupId], references: [id], onDelete: SetNull)
|
|
321
|
+
// The back-reference Prisma requires for the self-relation above. Nothing
|
|
322
|
+
// reads it today; it exists because a named relation needs both sides.
|
|
323
|
+
restoresUsingThis DatabaseBackupRun[] @relation("PreRestoreBackup")
|
|
324
|
+
|
|
325
|
+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
|
326
|
+
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
|
327
|
+
|
|
328
|
+
// The queries #282 and #283 issue, and nothing speculative:
|
|
329
|
+
//
|
|
330
|
+
// - the admin list and the retention sweep are both "newest first", which
|
|
331
|
+
// is `createdAt DESC`;
|
|
332
|
+
// - `startedAt DESC` answers "when did backups actually run", which is a
|
|
333
|
+
// different question from "when were they requested" for any run that
|
|
334
|
+
// waited or never started at all;
|
|
335
|
+
// - `[status]` backs the stale sweep (`status = 'running'`) and the
|
|
336
|
+
// active-run lookup that turns a P2002 into an id;
|
|
337
|
+
// - `[status, createdAt DESC]` is the retention query — "the completed
|
|
338
|
+
// runs, newest first, keep N" — which neither single-column index
|
|
339
|
+
// answers without a sort.
|
|
340
|
+
@@index([createdAt(sort: Desc)])
|
|
341
|
+
@@index([startedAt(sort: Desc)])
|
|
342
|
+
@@index([status])
|
|
343
|
+
@@index([status, createdAt(sort: Desc)])
|
|
344
|
+
@@map("database_backup_runs")
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
enum DatabaseBackupStatus {
|
|
348
|
+
pending
|
|
349
|
+
running
|
|
350
|
+
completed
|
|
351
|
+
failed
|
|
352
|
+
stale
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
enum DatabaseBackupTrigger {
|
|
356
|
+
manual
|
|
357
|
+
scheduled
|
|
358
|
+
pre_restore
|
|
359
|
+
}
|