@marinoscar/kvox-eval 0.0.0-bootstrap → 0.1.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/LICENSE +21 -0
  3. package/README.md +166 -0
  4. package/dist/ask/index.d.ts +6 -0
  5. package/dist/ask/index.js +28 -0
  6. package/dist/ask/question-schema.d.ts +121 -0
  7. package/dist/ask/question-schema.js +167 -0
  8. package/dist/ask/report.d.ts +88 -0
  9. package/dist/ask/report.js +170 -0
  10. package/dist/ask/scorer.d.ts +104 -0
  11. package/dist/ask/scorer.js +164 -0
  12. package/dist/ask-run/ask-command.d.ts +62 -0
  13. package/dist/ask-run/ask-command.js +254 -0
  14. package/dist/ask-run/host/app.d.ts +65 -0
  15. package/dist/ask-run/host/app.js +146 -0
  16. package/dist/ask-run/host/db.d.ts +38 -0
  17. package/dist/ask-run/host/db.js +126 -0
  18. package/dist/ask-run/host/ports.d.ts +35 -0
  19. package/dist/ask-run/host/ports.js +113 -0
  20. package/dist/ask-run/index.d.ts +13 -0
  21. package/dist/ask-run/index.js +35 -0
  22. package/dist/ask-run/replay.d.ts +128 -0
  23. package/dist/ask-run/replay.js +212 -0
  24. package/dist/ask-run/run-ask-eval.d.ts +125 -0
  25. package/dist/ask-run/run-ask-eval.js +193 -0
  26. package/dist/ask-run/seed-golden-graph.d.ts +64 -0
  27. package/dist/ask-run/seed-golden-graph.js +495 -0
  28. package/dist/cli/index.d.ts +3 -0
  29. package/dist/cli/index.js +12 -0
  30. package/dist/cli/kg-command.d.ts +66 -0
  31. package/dist/cli/kg-command.js +238 -0
  32. package/dist/cli/main.d.ts +18 -0
  33. package/dist/cli/main.js +69 -0
  34. package/dist/fixtures/domains.d.ts +16 -0
  35. package/dist/fixtures/domains.js +33 -0
  36. package/dist/fixtures/fixture-schema.d.ts +323 -0
  37. package/dist/fixtures/fixture-schema.js +199 -0
  38. package/dist/fixtures/index.d.ts +16 -0
  39. package/dist/fixtures/index.js +40 -0
  40. package/dist/fixtures/load.d.ts +73 -0
  41. package/dist/fixtures/load.js +137 -0
  42. package/dist/fixtures/questions.d.ts +15 -0
  43. package/dist/fixtures/questions.js +26 -0
  44. package/dist/index.d.ts +20 -0
  45. package/dist/index.js +36 -0
  46. package/dist/kg/gold-runner.d.ts +13 -0
  47. package/dist/kg/gold-runner.js +57 -0
  48. package/dist/kg/index.d.ts +17 -0
  49. package/dist/kg/index.js +65 -0
  50. package/dist/kg/internals.d.ts +7 -0
  51. package/dist/kg/internals.js +17 -0
  52. package/dist/kg/prediction-schema.d.ts +245 -0
  53. package/dist/kg/prediction-schema.js +102 -0
  54. package/dist/kg/predictions.d.ts +11 -0
  55. package/dist/kg/predictions.js +49 -0
  56. package/dist/kg/report.d.ts +84 -0
  57. package/dist/kg/report.js +125 -0
  58. package/dist/kg/run-kg-eval.d.ts +77 -0
  59. package/dist/kg/run-kg-eval.js +137 -0
  60. package/dist/kg/runner.d.ts +72 -0
  61. package/dist/kg/runner.js +51 -0
  62. package/dist/kg/runners/extract-resolve-runner.d.ts +40 -0
  63. package/dist/kg/runners/extract-resolve-runner.js +199 -0
  64. package/dist/kg/runners/extract-runner.d.ts +65 -0
  65. package/dist/kg/runners/extract-runner.js +243 -0
  66. package/dist/kg/scorer.d.ts +226 -0
  67. package/dist/kg/scorer.js +463 -0
  68. package/dist/kg/targets.d.ts +17 -0
  69. package/dist/kg/targets.js +16 -0
  70. package/dist/kg/text.d.ts +45 -0
  71. package/dist/kg/text.js +100 -0
  72. package/dist/live/adapters.d.ts +16 -0
  73. package/dist/live/adapters.js +58 -0
  74. package/dist/live/env.d.ts +39 -0
  75. package/dist/live/env.js +56 -0
  76. package/dist/live/index.d.ts +6 -0
  77. package/dist/live/index.js +16 -0
  78. package/dist/live/live-ai.d.ts +104 -0
  79. package/dist/live/live-ai.js +157 -0
  80. package/dist/parity/constants.d.ts +79 -0
  81. package/dist/parity/constants.js +104 -0
  82. package/dist/parity/index.d.ts +20 -0
  83. package/dist/parity/index.js +38 -0
  84. package/dist/parity/internals.d.ts +8 -0
  85. package/dist/parity/internals.js +16 -0
  86. package/dist/parity/scenario.d.ts +45 -0
  87. package/dist/parity/scenario.js +245 -0
  88. package/dist/parity/script.d.ts +44 -0
  89. package/dist/parity/script.js +136 -0
  90. package/dist/parity/snapshot.d.ts +43 -0
  91. package/dist/parity/snapshot.js +71 -0
  92. package/dist/real-data/index.d.ts +2 -0
  93. package/dist/real-data/index.js +12 -0
  94. package/dist/real-data/real-data.d.ts +76 -0
  95. package/dist/real-data/real-data.js +156 -0
  96. package/fixtures/kg-golden/README.md +136 -0
  97. package/fixtures/kg-golden/ask-questions.json +458 -0
  98. package/fixtures/kg-golden/meetings/m01-northwind-pilot-kickoff.json +792 -0
  99. package/fixtures/kg-golden/meetings/m02-halden-migration-planning.json +814 -0
  100. package/fixtures/kg-golden/meetings/m03-larkspur-weekly-sync.json +601 -0
  101. package/fixtures/kg-golden/meetings/m04-corvid-intake-discovery.json +690 -0
  102. package/fixtures/kg-golden/meetings/m05-hosting-vendor-selection.json +631 -0
  103. package/fixtures/kg-golden/meetings/m06-northwind-floor-data-review.json +649 -0
  104. package/fixtures/kg-golden/meetings/m07-gullwing-intro-notes.json +428 -0
  105. package/fixtures/kg-golden/meetings/m08-halden-migration-checkpoint.json +672 -0
  106. package/fixtures/kg-golden/meetings/m09-northwind-platform-org-update.json +711 -0
  107. package/fixtures/kg-golden/meetings/m10-corvid-prototype-review.json +623 -0
  108. package/fixtures/kg-golden/meetings/m11-solvane-grid-dashboard-scoping.json +689 -0
  109. package/fixtures/kg-golden/meetings/m12-northwind-pilot-replan.json +681 -0
  110. package/fixtures/kg-golden/meetings/m13-halden-cutover-go-no-go.json +721 -0
  111. package/fixtures/kg-golden/meetings/m14-gullwing-discovery-readout.json +719 -0
  112. package/fixtures/kg-golden/meetings/m15-halden-handover-ravi-leaving.json +716 -0
  113. package/fixtures/kg-golden/meetings/m16-varden-claims-portal-intro.json +704 -0
  114. package/fixtures/kg-golden/meetings/m17-corvid-usability-debrief.json +572 -0
  115. package/fixtures/kg-golden/meetings/m18-hosting-decision-revisited.json +655 -0
  116. package/fixtures/kg-golden/meetings/m19-solvane-integration-kickoff.json +726 -0
  117. package/fixtures/kg-golden/meetings/m20-northwind-go-live-retro.json +673 -0
  118. package/fixtures/kg-golden/meetings/m21-quarterly-account-review.json +449 -0
  119. package/fixtures/kg-golden/meetings/m22-halden-intro-call-2020.json +558 -0
  120. package/fixtures/kg-golden/meetings/m23-gullwing-model-review.json +702 -0
  121. package/fixtures/kg-golden/meetings/m24-varden-discovery-midpoint.json +643 -0
  122. package/fixtures/kg-golden/meetings/m25-corvid-rollout-planning.json +642 -0
  123. package/fixtures/kg-golden/meetings/m26-larkspur-engineering-prep.json +695 -0
  124. package/fixtures/kg-golden/meetings/m27-solvane-phase-one-demo.json +649 -0
  125. package/fixtures/kg-golden/meetings/m28-northwind-building-5-scoping.json +715 -0
  126. package/fixtures/kg-golden/meetings/m29-halden-southern-cutover-review.json +752 -0
  127. package/fixtures/kg-golden/meetings/m30-varden-portal-build-kickoff.json +697 -0
  128. package/fixtures/kg-golden/meetings/m31-gullwing-rollout-renewal.json +681 -0
  129. package/fixtures/kg-golden/meetings/m32-brambleway-weekly-sync.json +472 -0
  130. package/fixtures/kg-golden/meetings/m33-nina-oliver-one-on-one.json +342 -0
  131. package/fixtures/kg-golden/meetings/m34-coffee-with-dana.json +249 -0
  132. package/fixtures/kg-golden/meetings/m35-brambleway-june-sync.json +553 -0
  133. package/fixtures/kg-golden/meetings/m36-copenhagen-conference-planning.json +296 -0
  134. package/host/generated/client.d.ts +1 -0
  135. package/host/generated/client.js +5 -0
  136. package/host/generated/default.d.ts +1 -0
  137. package/host/generated/default.js +5 -0
  138. package/host/generated/edge.d.ts +1 -0
  139. package/host/generated/edge.js +1658 -0
  140. package/host/generated/index-browser.js +1684 -0
  141. package/host/generated/index.d.ts +192335 -0
  142. package/host/generated/index.js +1658 -0
  143. package/host/generated/package.json +139 -0
  144. package/host/generated/query_compiler_fast_bg.js +2 -0
  145. package/host/generated/query_compiler_fast_bg.wasm +0 -0
  146. package/host/generated/query_compiler_fast_bg.wasm-base64.js +2 -0
  147. package/host/generated/runtime/client.d.ts +3433 -0
  148. package/host/generated/runtime/client.js +110 -0
  149. package/host/generated/runtime/index-browser.d.ts +90 -0
  150. package/host/generated/runtime/index-browser.js +6 -0
  151. package/host/generated/runtime/wasm-compiler-edge.js +100 -0
  152. package/host/generated/schema.prisma +6312 -0
  153. package/host/generated/wasm-edge-light-loader.mjs +5 -0
  154. package/host/generated/wasm-worker-loader.mjs +5 -0
  155. package/host/prisma/fragments/kvox.ask.prisma +126 -0
  156. package/host/prisma/fragments/kvox.graph.prisma +1073 -0
  157. package/host/prisma/fragments/kvox.notes.prisma +787 -0
  158. package/host/prisma/fragments/kvox.search.prisma +211 -0
  159. package/host/prisma/fragments/kvox.transcripts.prisma +790 -0
  160. package/host/prisma/kvox.lock +101 -0
  161. package/host/prisma/migrations/20240101000000_initial/migration.sql +297 -0
  162. package/host/prisma/migrations/20240101000001_add_personal_access_tokens/migration.sql +31 -0
  163. package/host/prisma/migrations/20240101000002_add_credentials/migration.sql +20 -0
  164. package/host/prisma/migrations/20240101000003_add_notification_deliveries/migration.sql +27 -0
  165. package/host/prisma/migrations/20240101000004_drop_stale_uuid_defaults/migration.sql +35 -0
  166. package/host/prisma/migrations/20240101000005_add_notifications/migration.sql +22 -0
  167. package/host/prisma/migrations/20240101000006_add_push_subscriptions/migration.sql +25 -0
  168. package/host/prisma/migrations/20240101000007_add_jobs/migration.sql +110 -0
  169. package/host/prisma/migrations/20240101000008_add_worker_nodes/migration.sql +90 -0
  170. package/host/prisma/migrations/20240101000009_add_database_backup_runs/migration.sql +104 -0
  171. package/host/prisma/migrations/20240101000010_add_notification_broadcasts/migration.sql +57 -0
  172. package/host/prisma/migrations/20240101000011_add_backup_run_job_link/migration.sql +87 -0
  173. package/host/prisma/migrations/20240101000012_add_job_node_secrets/migration.sql +61 -0
  174. package/host/prisma/migrations/20240101000013_add_backup_run_pg_dump_version/migration.sql +26 -0
  175. package/host/prisma/migrations/20240101000014_add_job_claim_token/migration.sql +92 -0
  176. package/host/prisma/migrations/20240101000015_add_ai_platform/migration.sql +108 -0
  177. package/host/prisma/migrations/20240101000016_revoke_viewer_ai_use/migration.sql +32 -0
  178. package/host/prisma/migrations/20240101000017_add_user_credentials/migration.sql +29 -0
  179. package/host/prisma/migrations/20240101000018_add_device_session_credential_link/migration.sql +52 -0
  180. package/host/prisma/migrations/20240101000019_add_worker_node_vitals/migration.sql +3 -0
  181. package/host/prisma/migrations/20240101000020_add_job_trace_context/migration.sql +6 -0
  182. package/host/prisma/migrations/20240101000021_add_retention_created_at_indexes/migration.sql +13 -0
  183. package/host/prisma/migrations/20240101000022_add_organizations/migration.sql +142 -0
  184. package/host/prisma/migrations/20240101000023_split_system_org_roles/migration.sql +131 -0
  185. package/host/prisma/migrations/20240101000024_org_scoped_rls/migration.sql +215 -0
  186. package/host/prisma/migrations/20240101000025_add_groups/migration.sql +165 -0
  187. package/host/prisma/migrations/20240101000026_add_grants/migration.sql +117 -0
  188. package/host/prisma/migrations/20240101000027_add_org_settings/migration.sql +49 -0
  189. package/host/prisma/migrations/20240101000028_add_org_credentials/migration.sql +54 -0
  190. package/host/prisma/migrations/20240101000029_add_jobs_org_id/migration.sql +18 -0
  191. package/host/prisma/migrations/20240101000030_add_jobs_org_id_status_index/migration.sql +6 -0
  192. package/host/prisma/migrations/20240101000031_add_broadcast_target_org/migration.sql +21 -0
  193. package/host/prisma/migrations/20240101000032_add_android_app/migration.sql +60 -0
  194. package/host/prisma/migrations/20240101000033_open_job_reason/migration.sql +16 -0
  195. package/host/prisma/migrations/20240101000034_ai_usage_feature/migration.sql +37 -0
  196. package/host/prisma/migrations/20240101000035_add_app_metadata/migration.sql +49 -0
  197. package/host/prisma/migrations/20240101000036_open_sharing_vocabularies/migration.sql +97 -0
  198. package/host/prisma/migrations/20260914130000_kvox_add_transcripts/migration.sql +322 -0
  199. package/host/prisma/migrations/20260914140000_kvox_add_notes/migration.sql +311 -0
  200. package/host/prisma/migrations/20260915120000_kvox_add_search_vectors/migration.sql +150 -0
  201. package/host/prisma/migrations/20260915130000_kvox_add_search_embeddings/migration.sql +362 -0
  202. package/host/prisma/migrations/20260915210000_kvox_add_note_title_source/migration.sql +30 -0
  203. package/host/prisma/migrations/20260923120000_kvox_add_note_generation_context_snapshot/migration.sql +24 -0
  204. package/host/prisma/migrations/20260923130000_kvox_add_user_hidden_note_templates/migration.sql +35 -0
  205. package/host/prisma/migrations/20260923140000_kvox_add_transcript_speaker_identities/migration.sql +6 -0
  206. package/host/prisma/migrations/20260923150000_kvox_transcript_name_checks/migration.sql +95 -0
  207. package/host/prisma/migrations/20260923160000_kvox_add_note_body_format/migration.sql +27 -0
  208. package/host/prisma/migrations/20260923170000_kvox_add_note_version_body_format/migration.sql +23 -0
  209. package/host/prisma/migrations/20260926012548_kvox_add_knowledge_graph/migration.sql +680 -0
  210. package/host/prisma/migrations/20260926030000_kvox_fix_kg_relations_valid_precision_chk/migration.sql +29 -0
  211. package/host/prisma/migrations/20260926030100_kvox_add_transcript_recorded_at/migration.sql +9 -0
  212. package/host/prisma/migrations/20260926030200_kvox_kg_evidence_invariant/migration.sql +60 -0
  213. package/host/prisma/migrations/20260926040000_kvox_add_kg_graph_layouts/migration.sql +30 -0
  214. package/host/prisma/migrations/20260926050000_kvox_add_ask_conversations/migration.sql +99 -0
  215. package/host/prisma/migrations/20260926231512_kvox_add_kg_exports/migration.sql +52 -0
  216. package/host/prisma/migrations/20261016120000_kvox_transcripts_org_id/migration.sql +49 -0
  217. package/host/prisma/platform.lock +319 -0
  218. package/host/prisma/schema/app.kvox.ask.prisma +110 -0
  219. package/host/prisma/schema/app.kvox.graph.prisma +1030 -0
  220. package/host/prisma/schema/app.kvox.notes.prisma +746 -0
  221. package/host/prisma/schema/app.kvox.search.prisma +208 -0
  222. package/host/prisma/schema/app.kvox.transcripts.prisma +726 -0
  223. package/host/prisma/schema/platform.ai.prisma +419 -0
  224. package/host/prisma/schema/platform.android-app.prisma +46 -0
  225. package/host/prisma/schema/platform.base.prisma +15 -0
  226. package/host/prisma/schema/platform.credentials.prisma +156 -0
  227. package/host/prisma/schema/platform.db-backup.prisma +359 -0
  228. package/host/prisma/schema/platform.identity.prisma +638 -0
  229. package/host/prisma/schema/platform.jobs.prisma +824 -0
  230. package/host/prisma/schema/platform.notifications.prisma +641 -0
  231. package/host/prisma/schema/platform.settings.prisma +53 -0
  232. package/host/prisma/schema/platform.sharing.prisma +208 -0
  233. package/host/prisma/schema/platform.storage.prisma +118 -0
  234. package/package.json +112 -1
  235. package/parity/requests.ask.json +2681 -0
  236. package/parity/requests.m01.json +1554 -0
  237. package/parity/requests.m02.json +1554 -0
  238. package/parity/requests.m03.json +1554 -0
  239. package/parity/requests.m04.json +1554 -0
  240. package/parity/requests.m05.json +1554 -0
  241. package/parity/requests.m06.json +1554 -0
  242. package/parity/requests.m07.json +1554 -0
  243. package/parity/requests.m08.json +1554 -0
  244. package/parity/requests.m09.json +1554 -0
  245. package/parity/requests.m10.json +1554 -0
@@ -0,0 +1,254 @@
1
+ "use strict";
2
+ // =============================================================================
3
+ // `kvox-eval ask` — the Ask evaluation, headless (issue #608; the web app's
4
+ // `npm run ask:eval`, #382)
5
+ // =============================================================================
6
+ //
7
+ // kvox-eval ask --model gpt-4o-mini
8
+ // kvox-eval ask --model <id> --only q01,q22
9
+ // kvox-eval ask --model <id> --category negative --min-match 0.7
10
+ // kvox-eval ask --provider openai-compatible --base-url <url> --model <id>
11
+ // kvox-eval ask --replay
12
+ //
13
+ // NO web app and NO external database: the run starts PGlite in memory, applies
14
+ // the platform and domain migrations, seeds the golden graph through the graph
15
+ // write service, and runs every selected question through the real
16
+ // `ask.respond` handler of `@marinoscar/kvox-api/ask` on the live model port
17
+ // (`createLiveKvoxAi`). Everything dies with the process. It prints the table
18
+ // and writes `report.json` and `answers.md` under `--out` (default: a stamped
19
+ // folder under the system temporary directory, or under the host's own
20
+ // git-ignored runs directory).
21
+ //
22
+ // `--replay` runs the harness's self-test instead: six scripted questions on a
23
+ // scripted runtime (no model, no key, no network), checked for citation
24
+ // validity, every turn complete and the sensitive-fact leak gate; it prints one
25
+ // line per question and writes `replay.json` (the same `--out` default).
26
+ //
27
+ // The API key comes from KVOX_EVAL_OPENAI_API_KEY only (ASK_EVAL_OPENAI_API_KEY
28
+ // for one more release), never a flag, and is never printed or written. The key
29
+ // gate runs FIRST, before the database starts. A failed provider round trip is
30
+ // reported by its error code and HTTP status only.
31
+ //
32
+ // Exit codes: 0 pass · 1 a gate failed (citation validity < 1, a sensitive
33
+ // leak) or answer match fell below `--min-match`, or a replay expectation
34
+ // failed · 2 configuration error (no key, a refused provider, a bad flag, an
35
+ // unknown question id, a malformed fixture, every model call failed, or a rate
36
+ // limit the run cannot wait out). A turn whose model call failed is otherwise
37
+ // scored as a failed answer and the run goes on.
38
+ // =============================================================================
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.ASK_COMMAND_USAGE = exports.ASK_EVAL_EXIT = void 0;
41
+ exports.runAskCommand = runAskCommand;
42
+ const fs_1 = require("fs");
43
+ const os_1 = require("os");
44
+ const path_1 = require("path");
45
+ const util_1 = require("util");
46
+ const ports_1 = require("@marinoscar/kvox-api/ports");
47
+ const index_js_1 = require("../ask/index.js");
48
+ const index_js_2 = require("../fixtures/index.js");
49
+ const index_js_3 = require("../live/index.js");
50
+ const index_js_4 = require("../live/index.js");
51
+ const replay_js_1 = require("./replay.js");
52
+ const run_ask_eval_js_1 = require("./run-ask-eval.js");
53
+ /**
54
+ * The exit codes of `kvox-eval ask` (and of the web app's `npm run ask:eval`).
55
+ *
56
+ * @stability experimental
57
+ */
58
+ exports.ASK_EVAL_EXIT = Object.freeze({ ok: 0, failed: 1, config: 2 });
59
+ /**
60
+ * The usage text of `kvox-eval ask`.
61
+ *
62
+ * @stability experimental
63
+ */
64
+ exports.ASK_COMMAND_USAGE = `usage: kvox-eval ask [--provider openai|openai-compatible] [--model <id>] [--base-url <url>]
65
+ [--only q01,q07] [--category <c>] [--out <dir>] [--min-match <0..1>]
66
+ kvox-eval ask --replay the scripted self-test (no model, no key)
67
+ reads the API key from ${index_js_3.KVOX_EVAL_API_KEY_ENV} (never a flag); default model ${run_ask_eval_js_1.DEFAULT_ASK_EVAL_MODEL}`;
68
+ /** A command line that cannot run; printed with the usage text. */
69
+ class UsageError extends Error {
70
+ }
71
+ /** Flags `--replay` does not take: the replay's questions, model and gate are fixed. */
72
+ const LIVE_ONLY_FLAGS = ['provider', 'model', 'base-url', 'only', 'category', 'min-match'];
73
+ /**
74
+ * Runs `kvox-eval ask` with the given arguments (everything after `ask`).
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * const code = await runAskCommand(['--replay'], { out: console.log, err: console.error, env: process.env });
79
+ * ```
80
+ *
81
+ * @param argv - the arguments.
82
+ * @param io - output streams and environment.
83
+ * @param options - the host's name, usage text and runs directory.
84
+ * @returns the exit code ({@link ASK_EVAL_EXIT}).
85
+ * @stability experimental
86
+ */
87
+ async function runAskCommand(argv, io, options = {}) {
88
+ const name = options.name ?? 'kvox-eval ask';
89
+ const usage = options.usage ?? exports.ASK_COMMAND_USAGE;
90
+ try {
91
+ return await run(argv, io, options, name, usage);
92
+ }
93
+ catch (err) {
94
+ if (err instanceof UsageError) {
95
+ io.err(`${name}: ${err.message}`);
96
+ io.err(usage);
97
+ return exports.ASK_EVAL_EXIT.config;
98
+ }
99
+ if (err instanceof index_js_1.AskEvalQuestionLoadError || err instanceof index_js_2.KgEvalLoadError || err instanceof index_js_4.LiveProviderError) {
100
+ io.err(`${name}: ${err.message}`);
101
+ return exports.ASK_EVAL_EXIT.config;
102
+ }
103
+ throw err;
104
+ }
105
+ }
106
+ async function run(argv, io, options, name, usage) {
107
+ let values;
108
+ try {
109
+ ({ values } = (0, util_1.parseArgs)({
110
+ args: argv,
111
+ strict: true,
112
+ allowPositionals: false,
113
+ options: {
114
+ provider: { type: 'string' },
115
+ model: { type: 'string' },
116
+ 'base-url': { type: 'string' },
117
+ only: { type: 'string' },
118
+ category: { type: 'string' },
119
+ out: { type: 'string' },
120
+ 'min-match': { type: 'string' },
121
+ replay: { type: 'boolean' },
122
+ help: { type: 'boolean' },
123
+ },
124
+ }));
125
+ }
126
+ catch (err) {
127
+ throw new UsageError(err.message);
128
+ }
129
+ const str = (k) => (typeof values[k] === 'string' ? values[k] : undefined);
130
+ if (values.help) {
131
+ io.out(usage);
132
+ return exports.ASK_EVAL_EXIT.ok;
133
+ }
134
+ const log = (line) => io.err(`${name}: ${line}`);
135
+ if (values.replay) {
136
+ const extra = LIVE_ONLY_FLAGS.filter((flag) => values[flag] !== undefined);
137
+ if (extra.length > 0)
138
+ throw new UsageError(`--replay takes no ${extra.map((f) => `--${f}`).join(', ')}`);
139
+ return replay(io, log, outDirFor(str('out'), options));
140
+ }
141
+ const minMatchArg = str('min-match');
142
+ const minMatch = minMatchArg !== undefined ? Number(minMatchArg) : 0.8;
143
+ if (!Number.isFinite(minMatch) || minMatch < 0 || minMatch > 1)
144
+ throw new UsageError('--min-match must be a number between 0 and 1');
145
+ // The provider is refused (copilot, an unknown id) before the key is looked for,
146
+ // and the key gate runs before anything is loaded or started: a run with no key
147
+ // is a configuration error, not a run that seeds a graph and then fails.
148
+ const provider = str('provider') ?? 'openai';
149
+ (0, index_js_4.assertLiveProvider)(provider);
150
+ const model = str('model') ?? run_ask_eval_js_1.DEFAULT_ASK_EVAL_MODEL;
151
+ const key = (0, index_js_3.readEvalApiKey)(io.env, io.err);
152
+ if (key === null && provider === 'openai') {
153
+ io.err(`${name}: reads its API key from ${index_js_3.KVOX_EVAL_API_KEY_ENV}; it is not set`);
154
+ return exports.ASK_EVAL_EXIT.config;
155
+ }
156
+ const baseUrl = str('base-url');
157
+ const { ai, failures } = (0, index_js_4.createLiveKvoxAi)({
158
+ provider,
159
+ model,
160
+ apiKey: key?.apiKey ?? null,
161
+ ...(baseUrl !== undefined ? { baseUrl } : {}),
162
+ });
163
+ const only = str('only')
164
+ ?.split(',')
165
+ .map((s) => s.trim())
166
+ .filter(Boolean);
167
+ const questions = (0, index_js_1.selectAskEvalQuestions)((0, index_js_2.loadGoldenAskQuestions)(), only ?? null, str('category') ?? null);
168
+ if (questions.length === 0) {
169
+ io.err(`${name}: no questions selected`);
170
+ return exports.ASK_EVAL_EXIT.config;
171
+ }
172
+ // A malformed fixture is a configuration error (`KgEvalLoadError`), and must not seed a partial graph.
173
+ const fixtures = (0, index_js_2.loadGoldenSet)();
174
+ const outDir = outDirFor(str('out'), options);
175
+ let result;
176
+ try {
177
+ result = await (0, run_ask_eval_js_1.runAskEval)({ ai, model, questions, fixtures, minMatch, log });
178
+ }
179
+ catch (err) {
180
+ // A failure the run cannot go on from (a rate limit before any text, which
181
+ // a queue would re-run later): reported by class, code and status only.
182
+ if (err instanceof ports_1.KvoxAiError || err instanceof ports_1.KvoxRateLimitError) {
183
+ reportFailures(failures, log);
184
+ io.err(`${name}: the live call failed (${err instanceof ports_1.KvoxAiError ? err.errorClass : 'rate_limit'}); the run stopped`);
185
+ return exports.ASK_EVAL_EXIT.config;
186
+ }
187
+ throw err;
188
+ }
189
+ reportFailures(failures, log);
190
+ io.out((0, index_js_1.formatAskEvalReport)(result.report));
191
+ (0, fs_1.mkdirSync)(outDir, { recursive: true });
192
+ (0, fs_1.writeFileSync)((0, path_1.join)(outDir, 'report.json'), `${JSON.stringify(result.report, null, 2)}\n`);
193
+ const answers = new Map(result.results.map((r) => [r.question.id, { question: r.question.question, content: r.message.content }]));
194
+ (0, fs_1.writeFileSync)((0, path_1.join)(outDir, 'answers.md'), (0, index_js_1.formatAnswersMarkdown)(result.report, answers));
195
+ log(`report written to ${outDir}`);
196
+ // Not one turn reached the model: a configuration problem (a wrong key, model
197
+ // or base URL), not a measurement.
198
+ if (result.results.every((r) => r.modelError !== undefined)) {
199
+ io.err(`${name}: every model call failed (${[...new Set(result.results.map((r) => r.modelError?.errorClass))].join(', ')}); check the key, model and base URL`);
200
+ return exports.ASK_EVAL_EXIT.config;
201
+ }
202
+ if (!result.report.gatesPassed || !result.report.answerMatchPassed)
203
+ return exports.ASK_EVAL_EXIT.failed;
204
+ return exports.ASK_EVAL_EXIT.ok;
205
+ }
206
+ /** `--out`, else `<runs dir>/runs/<stamp>`, else the system temporary directory. */
207
+ function outDirFor(out, options) {
208
+ if (out)
209
+ return (0, path_1.resolve)(out);
210
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
211
+ const root = options.syntheticRunsDir ?? (0, path_1.join)((0, os_1.tmpdir)(), 'kvox-eval', 'ask');
212
+ return (0, path_1.join)(root, 'runs', stamp);
213
+ }
214
+ /** How many provider round trips failed, by code and status (never a message body). */
215
+ function reportFailures(failures, log) {
216
+ if (failures.length === 0)
217
+ return;
218
+ const counts = new Map();
219
+ for (const f of failures) {
220
+ const label = `${f.code}${f.status !== null ? `, HTTP ${f.status}` : ''}`;
221
+ counts.set(label, (counts.get(label) ?? 0) + 1);
222
+ }
223
+ log(`${failures.length} provider round trip(s) failed: ${[...counts].map(([l, n]) => `${n} x (${l})`).join('; ')}`);
224
+ }
225
+ /** `--replay`: the six scripted questions, one line each, then the gates; `replay.json` under `--out`. */
226
+ async function replay(io, log, outDir) {
227
+ const run = await (0, replay_js_1.runAskReplay)({ log });
228
+ io.out('Ask evaluation replay (scripted runtime, no model)');
229
+ for (const r of run.results) {
230
+ const tools = r.message.toolCalls.map((c) => c.name).join(' > ') || '-';
231
+ const cites = r.message.citations.map((c) => `[^${c.marker}] ${c.valid ? 'valid' : 'INVALID'}`).join(', ') || 'none';
232
+ const verdict = r.failures.length === 0 ? 'ok' : `FAILED: ${r.failures.join('; ')}`;
233
+ io.out(` ${r.scenario.id} ${r.message.status} tools: ${tools} citations: ${cites} validity ${r.citationValidity.toFixed(2)} ${verdict}`);
234
+ }
235
+ io.out(` leak gate: ${run.leakFree ? 'ok (the sensitive fact reached no request and no answer)' : 'FAILED'}`);
236
+ io.out(run.passed ? 'replay passed' : 'replay FAILED');
237
+ (0, fs_1.mkdirSync)(outDir, { recursive: true });
238
+ const summary = {
239
+ passed: run.passed,
240
+ leakFree: run.leakFree,
241
+ scenarios: run.results.map((r) => ({
242
+ id: r.scenario.id,
243
+ title: r.scenario.title,
244
+ status: r.message.status,
245
+ toolCalls: r.message.toolCalls.map((c) => c.name),
246
+ citations: r.message.citations.map((c) => ({ marker: c.marker, kind: c.kind, valid: c.valid })),
247
+ citationValidity: r.citationValidity,
248
+ failures: r.failures,
249
+ })),
250
+ };
251
+ (0, fs_1.writeFileSync)((0, path_1.join)(outDir, 'replay.json'), `${JSON.stringify(summary, null, 2)}\n`);
252
+ log(`replay summary written to ${outDir}`);
253
+ return run.passed ? exports.ASK_EVAL_EXIT.ok : exports.ASK_EVAL_EXIT.failed;
254
+ }
@@ -0,0 +1,65 @@
1
+ import 'reflect-metadata';
2
+ import type { INestApplicationContext, Type } from '@nestjs/common';
3
+ import type { KvoxAiPort, KvoxSettingsPort } from '@marinoscar/kvox-api/ports';
4
+ import { type AskEvalPrismaClient } from './db.js';
5
+ /**
6
+ * The evaluation user's email. A reserved domain (`.invalid`): the account exists
7
+ * only in the run's in-memory database.
8
+ *
9
+ * @stability experimental
10
+ */
11
+ export declare const ASK_EVAL_USER_EMAIL = "ask-eval@kvox-eval.invalid";
12
+ /**
13
+ * What {@link startAskEvalHost} is built from.
14
+ *
15
+ * @stability experimental
16
+ */
17
+ export interface StartAskEvalHostOptions {
18
+ /** The model port every turn runs on (the port under test). */
19
+ ai: KvoxAiPort;
20
+ /** Receives progress lines (steps and counts, never data). */
21
+ log?: (line: string) => void;
22
+ }
23
+ /**
24
+ * A running evaluation host.
25
+ *
26
+ * @stability experimental
27
+ */
28
+ export interface AskEvalHost {
29
+ /** The Nest application context the slices run in. */
30
+ readonly app: INestApplicationContext;
31
+ /** The client on the in-memory database. */
32
+ readonly prisma: AskEvalPrismaClient;
33
+ /** The model port the slices were given. */
34
+ readonly ai: KvoxAiPort;
35
+ /** The settings port the slices were given (in memory; the `ai` namespace declared). */
36
+ readonly settings: KvoxSettingsPort;
37
+ /** The evaluation user (owner of every seeded row). */
38
+ readonly userId: string;
39
+ /** The default organization (adopted from the platform migrations). */
40
+ readonly orgId: string;
41
+ /** A provider from anywhere in the container (`app.get(type, { strict: false })`). */
42
+ resolve<T>(type: Type<T>): T;
43
+ /** Closes the application context, the client and the database. Safe to call twice. */
44
+ close(): Promise<void>;
45
+ }
46
+ /**
47
+ * Starts the in-memory database, adopts its default organization, creates the
48
+ * evaluation user and boots the domain slices over the evaluation's ports.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * const host = await startAskEvalHost({ ai });
53
+ * try {
54
+ * const seeded = await seedGoldenGraph(host, loadGoldenSet());
55
+ * // ... post questions, run the handler
56
+ * } finally {
57
+ * await host.close();
58
+ * }
59
+ * ```
60
+ *
61
+ * @param options - the model port and a progress sink.
62
+ * @returns the host; call `close()` when done.
63
+ * @stability experimental
64
+ */
65
+ export declare function startAskEvalHost(options: StartAskEvalHostOptions): Promise<AskEvalHost>;
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ // =============================================================================
3
+ // The headless Ask evaluation's host: PGlite + the domain slices on Nest (#608)
4
+ // =============================================================================
5
+ //
6
+ // What `ask.respond` needs and nothing more, as a Nest APPLICATION CONTEXT (no
7
+ // HTTP server): the ports module, then the transcription, search, transcripts,
8
+ // notes, graph and Ask slices, every optional feature switched off the way the
9
+ // bare host (examples/bare-host) switches them off:
10
+ //
11
+ // schedules: false no `@nestjs/schedule` timers; nothing housekeeps a
12
+ // database that lives for one run
13
+ // media.transcode: false no ffmpeg
14
+ // exporters / documentSources pdf, docx: false no pdfkit, docx, unpdf
15
+ // providers.assemblyai: false no transcription provider at all: the seeded
16
+ // transcripts are already `ready`
17
+ // semantic search off the seeded rows carry no embeddings, and a query
18
+ // embedding would spend the key under test
19
+ //
20
+ // NOT graph-extraction and NOT graph-rdf: the golden graph is seeded from the
21
+ // fixtures' HAND LABELS through `GraphWriteService`, so the evaluation isolates
22
+ // the agent (extraction quality is `kvox-eval kg`'s question).
23
+ //
24
+ // THE `peers` OPTION passes the very module objects this module imports, so the
25
+ // container holds one module per slice and each job handler registers once.
26
+ //
27
+ // The host module is a plain class: a dynamic module needs no decorator, and the
28
+ // package compiles without decorator metadata (the slices carry their own).
29
+ // =============================================================================
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.ASK_EVAL_USER_EMAIL = void 0;
32
+ exports.startAskEvalHost = startAskEvalHost;
33
+ require("reflect-metadata");
34
+ const core_1 = require("@nestjs/core");
35
+ const ask_1 = require("@marinoscar/kvox-api/ask");
36
+ const graph_1 = require("@marinoscar/kvox-api/graph");
37
+ const notes_1 = require("@marinoscar/kvox-api/notes");
38
+ const search_1 = require("@marinoscar/kvox-api/search");
39
+ const testing_1 = require("@marinoscar/kvox-api/testing");
40
+ const transcription_1 = require("@marinoscar/kvox-api/transcription");
41
+ const transcripts_1 = require("@marinoscar/kvox-api/transcripts");
42
+ const db_js_1 = require("./db.js");
43
+ const ports_js_1 = require("./ports.js");
44
+ /**
45
+ * The evaluation user's email. A reserved domain (`.invalid`): the account exists
46
+ * only in the run's in-memory database.
47
+ *
48
+ * @stability experimental
49
+ */
50
+ exports.ASK_EVAL_USER_EMAIL = 'ask-eval@kvox-eval.invalid';
51
+ /** The domain slices `ask.respond` needs, wired like the bare host's, minus extraction and RDF. */
52
+ function createAskEvalModule(ports) {
53
+ const transcription = transcription_1.TranscriptionModule.forRoot({ providers: { assemblyai: false } });
54
+ const searchIndexing = search_1.SearchIndexingModule.forRoot();
55
+ const transcripts = transcripts_1.TranscriptsModule.forRoot({
56
+ schedules: false,
57
+ media: { transcode: false },
58
+ exporters: { pdf: false },
59
+ peers: { transcription, searchIndexing },
60
+ });
61
+ const notes = notes_1.NotesModule.forRoot({
62
+ schedules: false,
63
+ exporters: { pdf: false, docx: false },
64
+ documentSources: { pdf: false },
65
+ peers: { transcripts, searchIndexing },
66
+ });
67
+ const search = search_1.SearchModule.forRoot({ semantic: { enabled: false } });
68
+ const graph = graph_1.GraphModule.forRoot({ schedules: false, peers: { transcripts, notes, search } });
69
+ const ask = ask_1.AskModule.forRoot({ peers: { graph, notes, search } });
70
+ class AskEvalHostModule {
71
+ }
72
+ return {
73
+ module: AskEvalHostModule,
74
+ imports: [(0, testing_1.createKvoxTestingModule)(ports), transcription, search, searchIndexing, transcripts, notes, graph, ask],
75
+ };
76
+ }
77
+ /**
78
+ * Starts the in-memory database, adopts its default organization, creates the
79
+ * evaluation user and boots the domain slices over the evaluation's ports.
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * const host = await startAskEvalHost({ ai });
84
+ * try {
85
+ * const seeded = await seedGoldenGraph(host, loadGoldenSet());
86
+ * // ... post questions, run the handler
87
+ * } finally {
88
+ * await host.close();
89
+ * }
90
+ * ```
91
+ *
92
+ * @param options - the model port and a progress sink.
93
+ * @returns the host; call `close()` when done.
94
+ * @stability experimental
95
+ */
96
+ async function startAskEvalHost(options) {
97
+ const log = options.log ?? (() => { });
98
+ const database = await (0, db_js_1.startAskEvalDatabase)(log);
99
+ const { prisma } = database;
100
+ let app = null;
101
+ try {
102
+ // The platform's own migrations create the default organization (its partial
103
+ // unique index allows exactly one): adopt it, as the bare host does.
104
+ const org = await prisma.organization.findFirst({ where: { isDefault: true }, select: { id: true } });
105
+ if (!org)
106
+ throw new Error('The platform migrations created no default organization.');
107
+ const user = await prisma.user.create({
108
+ data: { email: exports.ASK_EVAL_USER_EMAIL, displayName: 'Ask evaluation', isActive: true },
109
+ select: { id: true },
110
+ });
111
+ const settings = (0, ports_js_1.createAskEvalSettings)();
112
+ app = await core_1.NestFactory.createApplicationContext(createAskEvalModule({
113
+ overrides: {
114
+ ai: options.ai,
115
+ prisma: prisma,
116
+ settings,
117
+ jobs: (0, ports_js_1.createAskEvalJobs)(org.id),
118
+ identity: (0, ports_js_1.createAskEvalIdentity)(user.id, org.id),
119
+ },
120
+ }), { logger: ['error', 'warn'] });
121
+ log('host: slices started');
122
+ const context = app;
123
+ let closed = false;
124
+ return {
125
+ app: context,
126
+ prisma,
127
+ ai: options.ai,
128
+ settings,
129
+ userId: user.id,
130
+ orgId: org.id,
131
+ resolve: (type) => context.get(type, { strict: false }),
132
+ async close() {
133
+ if (closed)
134
+ return;
135
+ closed = true;
136
+ await context.close().catch(() => undefined);
137
+ await database.close();
138
+ },
139
+ };
140
+ }
141
+ catch (error) {
142
+ await app?.close().catch(() => undefined);
143
+ await database.close();
144
+ throw error;
145
+ }
146
+ }
@@ -0,0 +1,38 @@
1
+ import { PrismaClient } from '../../../host/generated/index.js';
2
+ /**
3
+ * The package's `host/` folder: `prisma/migrations/` and `generated/` (built by
4
+ * `scripts/prepare-host.mjs`). `dist/ask-run/host/db.js` and `src/ask-run/host/db.ts`
5
+ * are both three levels below the package root.
6
+ *
7
+ * @stability experimental
8
+ */
9
+ export declare const ASK_EVAL_HOST_DIR: string;
10
+ /**
11
+ * The Prisma client of the evaluation's database: the platform and domain schema,
12
+ * generated into `host/generated/` at build time.
13
+ *
14
+ * @stability experimental
15
+ */
16
+ export type AskEvalPrismaClient = PrismaClient;
17
+ /**
18
+ * A started, fully migrated in-memory database and the client on it.
19
+ *
20
+ * @stability experimental
21
+ */
22
+ export interface AskEvalDatabase {
23
+ /** The client (one pooled connection). */
24
+ readonly prisma: AskEvalPrismaClient;
25
+ /** How many migration folders were applied. */
26
+ readonly migrations: number;
27
+ /** Disconnects the client, stops the socket and closes PGlite. Safe to call twice. */
28
+ close(): Promise<void>;
29
+ }
30
+ /**
31
+ * Starts PGlite in memory, applies every shipped migration, serves it on a
32
+ * loopback socket and connects a Prisma client to it.
33
+ *
34
+ * @param log - receives one line per step (never data).
35
+ * @returns the database; call `close()` when done.
36
+ * @stability experimental
37
+ */
38
+ export declare function startAskEvalDatabase(log?: (line: string) => void): Promise<AskEvalDatabase>;
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ // =============================================================================
3
+ // The in-memory database of the headless Ask evaluation (issue #608)
4
+ // =============================================================================
5
+ //
6
+ // PGlite (PostgreSQL compiled to WebAssembly) in THIS process, with `vector`,
7
+ // `pg_trgm` and `uuid-ossp`, served on a loopback socket so the Prisma client's
8
+ // `pg` driver adapter can reach it. The data lives in memory and dies with the
9
+ // run: nothing an evaluation does ever touches a real database, so there is no
10
+ // safe-database guard to pass any more.
11
+ //
12
+ // THE SCHEMA. Extensions first (the bootstrap superuser's job, never a
13
+ // migration's), then every migration file the package ships under
14
+ // `host/prisma/migrations/` (the platform history stamped 2024-01-01, then the
15
+ // domain's under stable names, so the folder names sort in apply order), each
16
+ // executed as one simple-protocol script, exactly what `prisma migrate deploy`
17
+ // sends. No Prisma CLI runs at evaluation time; `scripts/prepare-host.mjs`
18
+ // composed the schema and generated the client at build time.
19
+ //
20
+ // ONE SESSION. PGlite serialises every query onto one backend, so the client
21
+ // uses a pool of ONE connection: an interactive transaction holds the only
22
+ // connection and anything outside it waits. A port implementation must never
23
+ // await a query on the pool while holding a transaction open (on PGlite that is
24
+ // a deadlock); the fake jobs port of `ports.ts` writes through the caller's tx.
25
+ // =============================================================================
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.ASK_EVAL_HOST_DIR = void 0;
28
+ exports.startAskEvalDatabase = startAskEvalDatabase;
29
+ const node_fs_1 = require("node:fs");
30
+ const node_net_1 = require("node:net");
31
+ const node_path_1 = require("node:path");
32
+ const pglite_1 = require("@electric-sql/pglite");
33
+ const pg_trgm_1 = require("@electric-sql/pglite/contrib/pg_trgm");
34
+ const uuid_ossp_1 = require("@electric-sql/pglite/contrib/uuid_ossp");
35
+ const pglite_pgvector_1 = require("@electric-sql/pglite-pgvector");
36
+ const pglite_socket_1 = require("@electric-sql/pglite-socket");
37
+ const adapter_pg_1 = require("@prisma/adapter-pg");
38
+ const index_js_1 = require("../../../host/generated/index.js");
39
+ /**
40
+ * The package's `host/` folder: `prisma/migrations/` and `generated/` (built by
41
+ * `scripts/prepare-host.mjs`). `dist/ask-run/host/db.js` and `src/ask-run/host/db.ts`
42
+ * are both three levels below the package root.
43
+ *
44
+ * @stability experimental
45
+ */
46
+ exports.ASK_EVAL_HOST_DIR = (0, node_path_1.join)(__dirname, '..', '..', '..', 'host');
47
+ /** A free loopback port (bound and released). */
48
+ function freePort() {
49
+ return new Promise((resolve, reject) => {
50
+ const probe = (0, node_net_1.createServer)();
51
+ probe.on('error', reject);
52
+ probe.listen(0, '127.0.0.1', () => {
53
+ const address = probe.address();
54
+ const port = typeof address === 'object' && address ? address.port : 0;
55
+ probe.close(() => resolve(port));
56
+ });
57
+ });
58
+ }
59
+ /** The shipped migration folders, in apply order. */
60
+ function migrationFolders() {
61
+ const dir = (0, node_path_1.join)(exports.ASK_EVAL_HOST_DIR, 'prisma', 'migrations');
62
+ let names;
63
+ try {
64
+ names = (0, node_fs_1.readdirSync)(dir);
65
+ }
66
+ catch {
67
+ throw new Error(`The evaluation's database host is missing (${dir}); build the package first (node scripts/prepare-host.mjs).`);
68
+ }
69
+ return names
70
+ .filter((name) => (0, node_fs_1.statSync)((0, node_path_1.join)(dir, name)).isDirectory())
71
+ .sort()
72
+ .map((name) => (0, node_path_1.join)(dir, name, 'migration.sql'));
73
+ }
74
+ /**
75
+ * Starts PGlite in memory, applies every shipped migration, serves it on a
76
+ * loopback socket and connects a Prisma client to it.
77
+ *
78
+ * @param log - receives one line per step (never data).
79
+ * @returns the database; call `close()` when done.
80
+ * @stability experimental
81
+ */
82
+ async function startAskEvalDatabase(log = () => { }) {
83
+ const files = migrationFolders();
84
+ const db = await pglite_1.PGlite.create({ extensions: { vector: pglite_pgvector_1.vector, pg_trgm: pg_trgm_1.pg_trgm, uuid_ossp: uuid_ossp_1.uuid_ossp } });
85
+ let server = null;
86
+ let prisma = null;
87
+ const close = async () => {
88
+ const [p, s] = [prisma, server];
89
+ prisma = null;
90
+ server = null;
91
+ await p?.$disconnect().catch(() => undefined);
92
+ await s?.stop().catch(() => undefined);
93
+ if (!db.closed)
94
+ await db.close().catch(() => undefined);
95
+ };
96
+ try {
97
+ // What the application's bootstrap superuser does before the first migration.
98
+ await db.exec('CREATE EXTENSION IF NOT EXISTS vector; CREATE EXTENSION IF NOT EXISTS pg_trgm;');
99
+ log(`database: applying ${files.length} migrations in memory`);
100
+ for (const file of files)
101
+ await db.exec((0, node_fs_1.readFileSync)(file, 'utf8'));
102
+ const port = await freePort();
103
+ server = new pglite_socket_1.PGLiteSocketServer({ db, port, host: '127.0.0.1', maxConnections: 1 });
104
+ await server.start();
105
+ const url = `postgresql://postgres@127.0.0.1:${port}/postgres?sslmode=disable`;
106
+ prisma = new index_js_1.PrismaClient({ adapter: new adapter_pg_1.PrismaPg({ connectionString: url, max: 1 }) });
107
+ await prisma.$connect();
108
+ log('database: ready');
109
+ }
110
+ catch (error) {
111
+ await close();
112
+ throw error;
113
+ }
114
+ const client = prisma;
115
+ let closed = false;
116
+ return {
117
+ prisma: client,
118
+ migrations: files.length,
119
+ async close() {
120
+ if (closed)
121
+ return;
122
+ closed = true;
123
+ await close();
124
+ },
125
+ };
126
+ }
@@ -0,0 +1,35 @@
1
+ import type { KvoxJobsPort, KvoxSettingsPort } from '@marinoscar/kvox-api/ports';
2
+ import { type FakeKvoxIdentity } from '@marinoscar/kvox-api/testing';
3
+ /**
4
+ * The permissions the evaluation user holds: what `ask.respond` probes for its
5
+ * actor. Read-only on purpose; the agent writes nothing.
6
+ *
7
+ * @stability experimental
8
+ */
9
+ export declare const ASK_EVAL_PERMISSIONS: readonly string[];
10
+ /**
11
+ * In-memory settings with the system `ai` namespace declared and the graph switch on.
12
+ *
13
+ * @returns the settings port.
14
+ * @stability experimental
15
+ */
16
+ export declare function createAskEvalSettings(): KvoxSettingsPort;
17
+ /**
18
+ * The fake queue, with `enqueueWithin` writing a real `jobs` row in the caller's
19
+ * transaction (the turn's `ask_messages.job_id` references it). Nothing claims
20
+ * the row: the runner hands it to the handler directly.
21
+ *
22
+ * @param orgId - the organization every job row is stamped with (the default one).
23
+ * @returns the jobs port.
24
+ * @stability experimental
25
+ */
26
+ export declare function createAskEvalJobs(orgId: string): KvoxJobsPort;
27
+ /**
28
+ * One principal: the evaluation user with {@link ASK_EVAL_PERMISSIONS}.
29
+ *
30
+ * @param userId - the evaluation user's id.
31
+ * @param orgId - the default organization's id.
32
+ * @returns the identity port.
33
+ * @stability experimental
34
+ */
35
+ export declare function createAskEvalIdentity(userId: string, orgId: string): FakeKvoxIdentity;