@gmickel/gno 1.46.0 → 2.1.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 (235) hide show
  1. package/README.md +17 -5
  2. package/THIRD_PARTY_NOTICES.md +46 -0
  3. package/assets/skill/SKILL.md +7 -6
  4. package/assets/spa-production.json.gz +0 -0
  5. package/browser-extension/artifacts/gno-browser-clipper-v2.1.0.zip +0 -0
  6. package/browser-extension/artifacts/gno-browser-clipper-v2.1.0.zip.sha256 +1 -0
  7. package/browser-extension/dist/chunk-4tc9v0ja.js +74 -0
  8. package/browser-extension/dist/manifest.json +1 -1
  9. package/browser-extension/dist/preview.html +1 -1
  10. package/browser-extension/dist/service-worker.js +32 -33
  11. package/bunfig.toml +2 -0
  12. package/package.json +40 -26
  13. package/spec/cli.md +29 -4
  14. package/spec/db/schema.sql +146 -1
  15. package/spec/mcp.md +26 -0
  16. package/src/app/context-runtime-types.ts +3 -0
  17. package/src/app/context-runtime.ts +2 -0
  18. package/src/cli/commands/ask.ts +6 -1
  19. package/src/cli/commands/daemon.ts +21 -8
  20. package/src/cli/commands/embed.ts +77 -41
  21. package/src/cli/detach.ts +3 -2
  22. package/src/config/types.ts +3 -3
  23. package/src/converters/adapters/markitdownTs/adapter.ts +1 -2
  24. package/src/converters/adapters/officeparser/adapter.ts +1 -2
  25. package/src/converters/versions.ts +6 -8
  26. package/src/core/context-evidence.ts +8 -4
  27. package/src/core/job-manager.ts +95 -13
  28. package/src/core/network-boundary-inventory.ts +10 -0
  29. package/src/core/shutdown-budget.ts +45 -0
  30. package/src/embed/backlog.ts +107 -4
  31. package/src/embed/batch.ts +42 -2
  32. package/src/embed/fingerprint.ts +16 -0
  33. package/src/embed/retry.ts +113 -5
  34. package/src/embed/variant-backlog.ts +105 -0
  35. package/src/embed/variant-plan.ts +62 -0
  36. package/src/embed/variant-retry.ts +113 -0
  37. package/src/ingestion/graph-reconciliation.ts +327 -0
  38. package/src/ingestion/sync.ts +9 -272
  39. package/src/llm/http-inference.ts +6 -0
  40. package/src/llm/httpEmbedding.ts +37 -6
  41. package/src/llm/httpGeneration.ts +18 -3
  42. package/src/llm/httpRerank.ts +23 -5
  43. package/src/llm/inference-cancellation.ts +168 -0
  44. package/src/llm/inference-scope.ts +202 -0
  45. package/src/llm/lazy-ports.ts +115 -0
  46. package/src/llm/native-worker/client.ts +541 -0
  47. package/src/llm/native-worker/dispatcher.ts +228 -0
  48. package/src/llm/native-worker/embedding-identity.ts +33 -0
  49. package/src/llm/native-worker/entry.ts +173 -0
  50. package/src/llm/native-worker/errors.ts +32 -0
  51. package/src/llm/native-worker/evaluation.ts +16 -0
  52. package/src/llm/native-worker/owned-exit.ts +108 -0
  53. package/src/llm/native-worker/owner.ts +141 -0
  54. package/src/llm/native-worker/ports.ts +317 -0
  55. package/src/llm/native-worker/protocol.ts +442 -0
  56. package/src/llm/native-worker/runtime-config.ts +92 -0
  57. package/src/llm/nodeLlamaCpp/adapter.ts +77 -20
  58. package/src/llm/nodeLlamaCpp/embedding.ts +130 -46
  59. package/src/llm/nodeLlamaCpp/generation.ts +34 -5
  60. package/src/llm/nodeLlamaCpp/lifecycle-options.ts +99 -0
  61. package/src/llm/nodeLlamaCpp/lifecycle.ts +209 -204
  62. package/src/llm/nodeLlamaCpp/rerank-capacity.ts +111 -0
  63. package/src/llm/nodeLlamaCpp/rerank.ts +118 -27
  64. package/src/llm/nodeLlamaCpp/simulator-handle.ts +73 -0
  65. package/src/llm/nodeLlamaCpp/simulator-install.ts +124 -0
  66. package/src/llm/nodeLlamaCpp/simulator-session.ts +240 -0
  67. package/src/llm/nodeLlamaCpp/simulator-types.ts +80 -0
  68. package/src/llm/types.ts +35 -5
  69. package/src/mcp/context.ts +27 -0
  70. package/src/mcp/http-transport.ts +12 -10
  71. package/src/mcp/server.ts +3 -0
  72. package/src/mcp/tool-profile.ts +30 -8
  73. package/src/mcp/tools/context.ts +8 -11
  74. package/src/mcp/tools/embed.ts +1 -1
  75. package/src/mcp/tools/index-cmd.ts +1 -1
  76. package/src/mcp/tools/index.ts +10 -8
  77. package/src/mcp/tools/query.ts +14 -30
  78. package/src/mcp/tools/vsearch.ts +1 -1
  79. package/src/pipeline/answer.ts +23 -3
  80. package/src/pipeline/claim-verifier.ts +6 -0
  81. package/src/pipeline/expansion.ts +43 -40
  82. package/src/pipeline/explain.ts +6 -2
  83. package/src/pipeline/filters.ts +63 -0
  84. package/src/pipeline/fusion.ts +29 -9
  85. package/src/pipeline/graph-retrieval.ts +29 -9
  86. package/src/pipeline/hybrid.ts +198 -55
  87. package/src/pipeline/hydration.ts +161 -0
  88. package/src/pipeline/owner-fusion.ts +87 -0
  89. package/src/pipeline/rerank.ts +35 -11
  90. package/src/pipeline/search.ts +13 -2
  91. package/src/pipeline/types.ts +5 -3
  92. package/src/pipeline/vsearch.ts +87 -7
  93. package/src/sdk/client.ts +47 -3
  94. package/src/sdk/embed.ts +63 -39
  95. package/src/serve/background-runtime.ts +1 -1
  96. package/src/serve/context.ts +41 -56
  97. package/src/serve/embed-scheduler.ts +58 -35
  98. package/src/serve/public/components/IndexingProgress.tsx +46 -60
  99. package/src/serve/public/components/PublishExportDialog.tsx +266 -0
  100. package/src/serve/public/globals.built.css +1 -1
  101. package/src/serve/public/globals.css +35 -0
  102. package/src/serve/public/lib/publish-export.ts +81 -1
  103. package/src/serve/public/lib/shiki-language-ids.ts +14 -0
  104. package/src/serve/public/pages/Collections.tsx +12 -46
  105. package/src/serve/public/pages/DocView.tsx +14 -52
  106. package/src/serve/resident-admission.ts +36 -36
  107. package/src/serve/resident-background-work.ts +20 -2
  108. package/src/serve/resident-request.ts +11 -5
  109. package/src/serve/resident-runtime.ts +97 -61
  110. package/src/serve/resident-shutdown.ts +153 -0
  111. package/src/serve/routes/api.ts +3 -1
  112. package/src/serve/server.ts +47 -26
  113. package/src/store/migrations/028-vector-variants.ts +54 -0
  114. package/src/store/migrations/029-graph-reference-state.ts +77 -0
  115. package/src/store/migrations/index.ts +4 -0
  116. package/src/store/sqlite/adapter.ts +251 -183
  117. package/src/store/sqlite/eligibility.ts +174 -0
  118. package/src/store/sqlite/graph-edge-application.ts +66 -0
  119. package/src/store/sqlite/graph-reference-state.ts +194 -0
  120. package/src/store/sqlite/legacy-vector-ownership.ts +79 -0
  121. package/src/store/types.ts +80 -12
  122. package/src/store/vector/eligibility.ts +36 -0
  123. package/src/store/vector/freshness.ts +33 -6
  124. package/src/store/vector/lazy.ts +81 -0
  125. package/src/store/vector/sqlite-vec.ts +106 -54
  126. package/src/store/vector/stats.ts +14 -3
  127. package/src/store/vector/types.ts +35 -2
  128. package/src/store/vector/variant-search.ts +192 -0
  129. package/src/store/vector/variants.ts +451 -0
  130. package/vendor/converters/markitdown-ts/LICENSE +21 -0
  131. package/vendor/converters/markitdown-ts/dist/index.cjs +1180 -0
  132. package/vendor/converters/markitdown-ts/dist/index.d.cts +46 -0
  133. package/vendor/converters/markitdown-ts/dist/index.d.mts +46 -0
  134. package/vendor/converters/markitdown-ts/dist/index.d.ts +46 -0
  135. package/vendor/converters/markitdown-ts/dist/index.mjs +1152 -0
  136. package/vendor/converters/markitdown-ts/package.json +77 -0
  137. package/vendor/converters/officeparser/LICENSE +21 -0
  138. package/vendor/converters/officeparser/dist/OfficeConverter.d.ts +47 -0
  139. package/vendor/converters/officeparser/dist/OfficeConverter.js +76 -0
  140. package/vendor/converters/officeparser/dist/OfficeGenerator.d.ts +23 -0
  141. package/vendor/converters/officeparser/dist/OfficeGenerator.js +73 -0
  142. package/vendor/converters/officeparser/dist/OfficeParser.d.ts +106 -0
  143. package/vendor/converters/officeparser/dist/OfficeParser.js +332 -0
  144. package/vendor/converters/officeparser/dist/cli.d.ts +28 -0
  145. package/vendor/converters/officeparser/dist/cli.js +381 -0
  146. package/vendor/converters/officeparser/dist/defaults.d.ts +41 -0
  147. package/vendor/converters/officeparser/dist/defaults.js +218 -0
  148. package/vendor/converters/officeparser/dist/generators/BaseGenerator.d.ts +107 -0
  149. package/vendor/converters/officeparser/dist/generators/BaseGenerator.js +248 -0
  150. package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.d.ts +82 -0
  151. package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.js +797 -0
  152. package/vendor/converters/officeparser/dist/generators/CsvGenerator.d.ts +38 -0
  153. package/vendor/converters/officeparser/dist/generators/CsvGenerator.js +245 -0
  154. package/vendor/converters/officeparser/dist/generators/EpubGenerator.d.ts +43 -0
  155. package/vendor/converters/officeparser/dist/generators/EpubGenerator.js +315 -0
  156. package/vendor/converters/officeparser/dist/generators/HtmlGenerator.d.ts +59 -0
  157. package/vendor/converters/officeparser/dist/generators/HtmlGenerator.js +1942 -0
  158. package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.d.ts +96 -0
  159. package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.js +1175 -0
  160. package/vendor/converters/officeparser/dist/generators/PdfGenerator.d.ts +22 -0
  161. package/vendor/converters/officeparser/dist/generators/PdfGenerator.js +194 -0
  162. package/vendor/converters/officeparser/dist/generators/RtfGenerator.d.ts +29 -0
  163. package/vendor/converters/officeparser/dist/generators/RtfGenerator.js +316 -0
  164. package/vendor/converters/officeparser/dist/generators/TextGenerator.d.ts +13 -0
  165. package/vendor/converters/officeparser/dist/generators/TextGenerator.js +201 -0
  166. package/vendor/converters/officeparser/dist/index.d.ts +60 -0
  167. package/vendor/converters/officeparser/dist/index.js +72 -0
  168. package/vendor/converters/officeparser/dist/index.mjs +18 -0
  169. package/vendor/converters/officeparser/dist/officeparser.browser.d.ts +2621 -0
  170. package/vendor/converters/officeparser/dist/officeparser.browser.iife.js +1336 -0
  171. package/vendor/converters/officeparser/dist/officeparser.browser.mjs +1335 -0
  172. package/vendor/converters/officeparser/dist/officeparser.browser.slim.d.ts +2621 -0
  173. package/vendor/converters/officeparser/dist/officeparser.browser.slim.iife.js +1336 -0
  174. package/vendor/converters/officeparser/dist/officeparser.browser.slim.mjs +1335 -0
  175. package/vendor/converters/officeparser/dist/parsers/CsvParser.d.ts +9 -0
  176. package/vendor/converters/officeparser/dist/parsers/CsvParser.js +115 -0
  177. package/vendor/converters/officeparser/dist/parsers/EpubParser.d.ts +8 -0
  178. package/vendor/converters/officeparser/dist/parsers/EpubParser.js +217 -0
  179. package/vendor/converters/officeparser/dist/parsers/ExcelParser.d.ts +32 -0
  180. package/vendor/converters/officeparser/dist/parsers/ExcelParser.js +736 -0
  181. package/vendor/converters/officeparser/dist/parsers/HtmlParser.d.ts +2 -0
  182. package/vendor/converters/officeparser/dist/parsers/HtmlParser.js +1287 -0
  183. package/vendor/converters/officeparser/dist/parsers/MarkdownParser.d.ts +2 -0
  184. package/vendor/converters/officeparser/dist/parsers/MarkdownParser.js +1272 -0
  185. package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.d.ts +31 -0
  186. package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.js +1819 -0
  187. package/vendor/converters/officeparser/dist/parsers/PdfParser.d.ts +67 -0
  188. package/vendor/converters/officeparser/dist/parsers/PdfParser.js +848 -0
  189. package/vendor/converters/officeparser/dist/parsers/PowerPointParser.d.ts +32 -0
  190. package/vendor/converters/officeparser/dist/parsers/PowerPointParser.js +950 -0
  191. package/vendor/converters/officeparser/dist/parsers/RtfParser.d.ts +187 -0
  192. package/vendor/converters/officeparser/dist/parsers/RtfParser.js +1801 -0
  193. package/vendor/converters/officeparser/dist/parsers/WordParser.d.ts +79 -0
  194. package/vendor/converters/officeparser/dist/parsers/WordParser.js +1177 -0
  195. package/vendor/converters/officeparser/dist/sbom.cdx.json +1763 -0
  196. package/vendor/converters/officeparser/dist/types.d.ts +2507 -0
  197. package/vendor/converters/officeparser/dist/types.js +107 -0
  198. package/vendor/converters/officeparser/dist/utils/astUtils.d.ts +16 -0
  199. package/vendor/converters/officeparser/dist/utils/astUtils.js +33 -0
  200. package/vendor/converters/officeparser/dist/utils/chartUtils.d.ts +6 -0
  201. package/vendor/converters/officeparser/dist/utils/chartUtils.js +257 -0
  202. package/vendor/converters/officeparser/dist/utils/configUtils.d.ts +44 -0
  203. package/vendor/converters/officeparser/dist/utils/configUtils.js +315 -0
  204. package/vendor/converters/officeparser/dist/utils/dateUtils.d.ts +17 -0
  205. package/vendor/converters/officeparser/dist/utils/dateUtils.js +69 -0
  206. package/vendor/converters/officeparser/dist/utils/envUtils.d.ts +29 -0
  207. package/vendor/converters/officeparser/dist/utils/envUtils.js +152 -0
  208. package/vendor/converters/officeparser/dist/utils/errorUtils.d.ts +72 -0
  209. package/vendor/converters/officeparser/dist/utils/errorUtils.js +245 -0
  210. package/vendor/converters/officeparser/dist/utils/imageUtils.d.ts +66 -0
  211. package/vendor/converters/officeparser/dist/utils/imageUtils.js +133 -0
  212. package/vendor/converters/officeparser/dist/utils/mathUtils.d.ts +42 -0
  213. package/vendor/converters/officeparser/dist/utils/mathUtils.js +385 -0
  214. package/vendor/converters/officeparser/dist/utils/moduleLoader.d.ts +18 -0
  215. package/vendor/converters/officeparser/dist/utils/moduleLoader.js +106 -0
  216. package/vendor/converters/officeparser/dist/utils/ocrUtils.d.ts +42 -0
  217. package/vendor/converters/officeparser/dist/utils/ocrUtils.js +428 -0
  218. package/vendor/converters/officeparser/dist/utils/sanitize.d.ts +148 -0
  219. package/vendor/converters/officeparser/dist/utils/sanitize.js +344 -0
  220. package/vendor/converters/officeparser/dist/utils/sheetUtils.d.ts +7 -0
  221. package/vendor/converters/officeparser/dist/utils/sheetUtils.js +35 -0
  222. package/vendor/converters/officeparser/dist/utils/styleMapper.d.ts +36 -0
  223. package/vendor/converters/officeparser/dist/utils/styleMapper.js +224 -0
  224. package/vendor/converters/officeparser/dist/utils/xmlUtils.d.ts +163 -0
  225. package/vendor/converters/officeparser/dist/utils/xmlUtils.js +461 -0
  226. package/vendor/converters/officeparser/dist/utils/zipUtils.d.ts +134 -0
  227. package/vendor/converters/officeparser/dist/utils/zipUtils.js +337 -0
  228. package/vendor/converters/officeparser/package.json +147 -0
  229. package/vendor/converters/upstream-manifest.json +124 -0
  230. package/vendor/dependency-fixes/README.md +77 -0
  231. package/vendor/dependency-fixes/vendor-converters.py +83 -0
  232. package/browser-extension/artifacts/gno-browser-clipper-v1.46.0.zip +0 -0
  233. package/browser-extension/artifacts/gno-browser-clipper-v1.46.0.zip.sha256 +0 -1
  234. package/browser-extension/dist/chunk-627emwpj.js +0 -75
  235. /package/browser-extension/dist/{chunk-ydfx5d7p.css → chunk-z74y8n8c.css} +0 -0
@@ -1,11 +1,10 @@
1
+ import type { McpServer } from "@modelcontextprotocol/server";
1
2
  /**
2
3
  * MCP tool registration and shared utilities.
3
4
  *
4
5
  * @module src/mcp/tools
5
6
  */
6
7
 
7
- import type { McpServer } from "@modelcontextprotocol/server";
8
-
9
8
  import { z } from "zod";
10
9
 
11
10
  import type { ToolContext } from "../server";
@@ -18,6 +17,10 @@ import { CAPTURE_MAX_TEXT_BYTES } from "../../core/capture";
18
17
  import { NOTE_PRESETS, type NotePresetId } from "../../core/note-presets";
19
18
  import { RETRIEVAL_TRACE_METADATA } from "../../core/retrieval-trace-session";
20
19
  import { normalizeTag } from "../../core/tags";
20
+ import {
21
+ assertInferenceActive,
22
+ acquireInferencePermit,
23
+ } from "../../llm/inference-scope";
21
24
  import { profileToolDescription } from "../tool-descriptions-core";
22
25
  import {
23
26
  createProfileToolRegistrar,
@@ -940,10 +943,11 @@ export async function runTool<T>(
940
943
  }
941
944
 
942
945
  // Sequential execution via mutex
943
- const release = await ctx.toolMutex.acquire();
944
- const modelLease = ctx.acquireModelLease?.();
946
+ const release = await acquireInferencePermit(() => ctx.toolMutex.acquire());
945
947
  try {
948
+ assertInferenceActive();
946
949
  const data = await (ctx.runWithSnapshot?.(fn) ?? fn());
950
+ assertInferenceActive();
947
951
  const traceMetadata =
948
952
  data !== null && typeof data === "object"
949
953
  ? (data as Record<PropertyKey, unknown>)[RETRIEVAL_TRACE_METADATA]
@@ -966,7 +970,6 @@ export async function runTool<T>(
966
970
  structuredContent: parsedError,
967
971
  };
968
972
  } finally {
969
- modelLease?.release();
970
973
  release();
971
974
  }
972
975
  }
@@ -990,9 +993,10 @@ export async function runToolNoMutex<T>(
990
993
  };
991
994
  }
992
995
 
993
- const modelLease = ctx.acquireModelLease?.();
994
996
  try {
997
+ assertInferenceActive();
995
998
  const data = await (ctx.runWithSnapshot?.(fn) ?? fn());
999
+ assertInferenceActive();
996
1000
  return {
997
1001
  content: [{ type: "text", text: formatText(data) }],
998
1002
  structuredContent: data as { [x: string]: unknown },
@@ -1007,8 +1011,6 @@ export async function runToolNoMutex<T>(
1007
1011
  content: [{ type: "text", text: `Error: ${message}` }],
1008
1012
  structuredContent: parsedError,
1009
1013
  };
1010
- } finally {
1011
- modelLease?.release();
1012
1014
  }
1013
1015
  }
1014
1016
 
@@ -17,6 +17,7 @@ import type {
17
17
  SearchResult,
18
18
  SearchResults,
19
19
  } from "../../pipeline/types";
20
+ import type { VectorIndexPort } from "../../store/vector";
20
21
  import type { ToolContext } from "../server";
21
22
 
22
23
  import { decorateUriForIndex, parseUri } from "../../app/constants";
@@ -42,10 +43,7 @@ import { resolveDownloadPolicy } from "../../llm/policy";
42
43
  import { getActivePreset, resolveModelUri } from "../../llm/registry";
43
44
  import { diagnoseQueryTarget } from "../../pipeline/diagnose";
44
45
  import { type HybridSearchDeps, searchHybrid } from "../../pipeline/hybrid";
45
- import {
46
- createVectorIndexPort,
47
- type VectorIndexPort,
48
- } from "../../store/vector";
46
+ import { createLazyVectorIndex } from "../../store/vector/lazy";
49
47
  import { normalizeTagFilters, runTool, type ToolResult } from "./index";
50
48
 
51
49
  interface QueryInput {
@@ -278,7 +276,7 @@ export function handleQuery(
278
276
  });
279
277
  if (!traceStart.ok) throw new Error(traceStart.error.message);
280
278
  traceSession = traceStart.value ?? undefined;
281
- const llm = new LlmAdapter(ctx.config);
279
+ const llm = ctx.getModelAdapter?.() ?? new LlmAdapter(ctx.config);
282
280
  const egressCollections = args.collection
283
281
  ? [args.collection]
284
282
  : ("all" as const);
@@ -325,18 +323,11 @@ export function handleQuery(
325
323
 
326
324
  // Create vector index (optional)
327
325
  if (embedPort) {
328
- const embedInitResult = await embedPort.init();
329
- if (embedInitResult.ok) {
330
- const dimensions = embedPort.dimensions();
331
- const db = ctx.store.getRawDb();
332
- const vectorResult = await createVectorIndexPort(db, {
333
- model: embedUri,
334
- dimensions,
335
- });
336
- if (vectorResult.ok) {
337
- vectorIndex = vectorResult.value;
338
- }
339
- }
326
+ vectorIndex = await createLazyVectorIndex(
327
+ ctx.store.getRawDb(),
328
+ embedUri,
329
+ embedPort
330
+ );
340
331
  }
341
332
 
342
333
  const deps: HybridSearchDeps = {
@@ -428,7 +419,7 @@ export function handleQueryDiagnose(
428
419
  : undefined;
429
420
 
430
421
  const preset = getActivePreset(ctx.config);
431
- const llm = new LlmAdapter(ctx.config);
422
+ const llm = ctx.getModelAdapter?.() ?? new LlmAdapter(ctx.config);
432
423
  const policy = resolveDownloadPolicy(process.env, {});
433
424
  const downloadProgress = createNonTtyProgressRenderer();
434
425
  const egressCollections = collection ? [collection] : ("all" as const);
@@ -501,18 +492,11 @@ export function handleQueryDiagnose(
501
492
  }
502
493
 
503
494
  if (embedPort) {
504
- const embedInitResult = await embedPort.init();
505
- if (embedInitResult.ok) {
506
- const dimensions = embedPort.dimensions();
507
- const db = ctx.store.getRawDb();
508
- const vectorResult = await createVectorIndexPort(db, {
509
- model: embedUri,
510
- dimensions,
511
- });
512
- if (vectorResult.ok) {
513
- vectorIndex = vectorResult.value;
514
- }
515
- }
495
+ vectorIndex = await createLazyVectorIndex(
496
+ ctx.store.getRawDb(),
497
+ embedUri,
498
+ embedPort
499
+ );
516
500
  }
517
501
 
518
502
  const deps: HybridSearchDeps = {
@@ -178,7 +178,7 @@ export function handleVsearch(
178
178
  const downloadProgress = createNonTtyProgressRenderer();
179
179
 
180
180
  // Create LLM adapter for embeddings
181
- const llm = new LlmAdapter(ctx.config);
181
+ const llm = ctx.getModelAdapter?.() ?? new LlmAdapter(ctx.config);
182
182
  const embedResult = await llm.createEmbeddingPort(modelUri, {
183
183
  egressCollections: args.collection ? [args.collection] : "all",
184
184
  policy,
@@ -1,13 +1,13 @@
1
+ import type { RetrievalTraceSession } from "../core/retrieval-trace-session";
1
2
  /**
2
3
  * Grounded answer generation.
3
4
  * Shared between CLI ask command and web API.
4
5
  *
5
6
  * @module src/pipeline/answer
6
7
  */
7
-
8
- import type { RetrievalTraceSession } from "../core/retrieval-trace-session";
9
8
  import type { GenerationPort } from "../llm/types";
10
9
  import type { StorePort } from "../store/types";
10
+ import type { RequestHydration } from "./hydration";
11
11
  import type {
12
12
  AnswerContextEntry,
13
13
  AnswerContextExplain,
@@ -15,6 +15,10 @@ import type {
15
15
  SearchResult,
16
16
  } from "./types";
17
17
 
18
+ import {
19
+ assertInferenceActive,
20
+ assertInferenceResult,
21
+ } from "../llm/inference-scope";
18
22
  import { buildAnswerPrompt, type AnswerPromptSource } from "./answer-prompt";
19
23
  import { attachCitationTraceMetadata } from "./trace-metadata";
20
24
  import {
@@ -117,6 +121,7 @@ const completeLinePrefix = (
117
121
  const selected: string[] = [];
118
122
  let characters = 0;
119
123
  for (const line of lines) {
124
+ assertInferenceActive();
120
125
  const addition = selected.length === 0 ? line.length : line.length + 1;
121
126
  if (characters + addition > maxChars) break;
122
127
  selected.push(line);
@@ -172,6 +177,7 @@ export function extractValidCitationNumbers(
172
177
  const re = /\[(\d+)\]/g;
173
178
  const matches = answer.matchAll(re);
174
179
  for (const match of matches) {
180
+ assertInferenceActive();
175
181
  const n = Number(match[1]);
176
182
  if (Number.isInteger(n) && n >= 1 && n <= maxCitation) {
177
183
  nums.add(n);
@@ -231,6 +237,8 @@ export interface AnswerGenerationResult {
231
237
  export interface AnswerGenerationDeps {
232
238
  genPort: GenerationPort;
233
239
  store: StorePort | null;
240
+ /** Raw request-owned content only; passage/hash validation still runs. */
241
+ hydration?: Pick<RequestHydration, "getContent">;
234
242
  }
235
243
 
236
244
  function normalizeScore(score: number): number {
@@ -273,6 +281,7 @@ function buildCandidates(
273
281
 
274
282
  const matchedQueryTokens = new Set<string>();
275
283
  for (const token of queryTokenSet) {
284
+ assertInferenceActive();
276
285
  if (signalTokenSet.has(token)) {
277
286
  matchedQueryTokens.add(token);
278
287
  }
@@ -280,7 +289,9 @@ function buildCandidates(
280
289
 
281
290
  const matchedFacetIndexes = new Set<number>();
282
291
  for (const [index, facetTokenSet] of facetTokenSets.entries()) {
292
+ assertInferenceActive();
283
293
  for (const token of facetTokenSet) {
294
+ assertInferenceActive();
284
295
  if (signalTokenSet.has(token)) {
285
296
  matchedFacetIndexes.add(index);
286
297
  break;
@@ -301,6 +312,7 @@ function dedupeByDocidBestScore(results: SearchResult[]): SearchResult[] {
301
312
  const bestByDocid = new Map<string, SearchResult>();
302
313
 
303
314
  for (const result of results) {
315
+ assertInferenceActive();
304
316
  const existing = bestByDocid.get(result.docid);
305
317
  if (!existing || result.score > existing.score) {
306
318
  bestByDocid.set(result.docid, result);
@@ -358,6 +370,7 @@ function selectAdaptiveSources(
358
370
  let bestReason = "relevance";
359
371
 
360
372
  for (const candidate of candidates) {
373
+ assertInferenceActive();
361
374
  const docid = candidate.result.docid;
362
375
  if (selectedDocids.has(docid)) {
363
376
  continue;
@@ -418,15 +431,18 @@ function selectAdaptiveSources(
418
431
  selected.push({ candidate: bestCandidate, reason: bestReason });
419
432
  selectedDocids.add(bestCandidate.result.docid);
420
433
  for (const token of bestCandidate.matchedQueryTokens) {
434
+ assertInferenceActive();
421
435
  coveredTokens.add(token);
422
436
  }
423
437
  for (const index of bestCandidate.matchedFacetIndexes) {
438
+ assertInferenceActive();
424
439
  coveredFacets.add(index);
425
440
  }
426
441
  }
427
442
 
428
443
  if (comparisonIntent && selected.length < 2) {
429
444
  for (const candidate of candidates) {
445
+ assertInferenceActive();
430
446
  if (selectedDocids.has(candidate.result.docid)) {
431
447
  continue;
432
448
  }
@@ -501,6 +517,7 @@ export async function generateGroundedAnswer(
501
517
  let citationIndex = 0;
502
518
 
503
519
  for (const r of sourceSelection.selected) {
520
+ assertInferenceActive();
504
521
  let passage: ExactAnswerPassage | null = null;
505
522
  const plannerMetadata = r[SEARCH_RESULT_PLANNER_METADATA];
506
523
  const sourceHash = r.source.sourceHash;
@@ -508,7 +525,9 @@ export async function generateGroundedAnswer(
508
525
 
509
526
  // Try to fetch full document content if store available
510
527
  if (store && mirrorHash) {
511
- const contentResult = await store.getContent(mirrorHash);
528
+ const contentResult = await (deps.hydration ?? store).getContent(
529
+ mirrorHash
530
+ );
512
531
  if (contentResult.ok && contentResult.value) {
513
532
  if (sha256(contentResult.value) === mirrorHash) {
514
533
  passage =
@@ -578,6 +597,7 @@ export async function generateGroundedAnswer(
578
597
  maxTokens,
579
598
  });
580
599
 
600
+ assertInferenceResult(result);
581
601
  if (!result.ok) {
582
602
  return null;
583
603
  }
@@ -8,6 +8,10 @@ import type {
8
8
  } from "./claim-verification";
9
9
 
10
10
  import { sha256Text } from "../core/context-capsule-validation";
11
+ import {
12
+ assertInferenceActive,
13
+ assertInferenceResult,
14
+ } from "../llm/inference-scope";
11
15
  import {
12
16
  semanticClaimJudgmentSchema,
13
17
  verifyClaimsDeterministically,
@@ -251,6 +255,7 @@ const parseEnvelope = (
251
255
  }
252
256
  const judgments: SemanticClaimJudgment[] = [];
253
257
  for (const rawJudgment of parsed.data.judgments) {
258
+ assertInferenceActive();
254
259
  const candidate = candidatesById.get(rawJudgment.claimId);
255
260
  const allowed = new Set(
256
261
  candidate?.evidence.map((item) => item.evidenceId) ?? []
@@ -416,6 +421,7 @@ export const verifyClaimsSemantically = async (
416
421
  maxTokens: MAX_OUTPUT_TOKENS,
417
422
  jsonSchema: schema,
418
423
  });
424
+ assertInferenceResult(generated);
419
425
  if (!generated.ok) {
420
426
  return failedResult(
421
427
  deterministic,
@@ -1,16 +1,21 @@
1
+ import { createHash } from "node:crypto"; // No Bun alternative for hashing
2
+
3
+ import type { InferenceOptions } from "../llm/types";
1
4
  /**
2
5
  * Query expansion for hybrid search.
3
6
  * Uses GenerationPort to generate query variants.
4
7
  *
5
8
  * @module src/pipeline/expansion
6
9
  */
7
-
8
- import { createHash } from "node:crypto"; // No Bun alternative for hashing
9
-
10
10
  import type { GenerationPort } from "../llm/types";
11
11
  import type { StoreResult } from "../store/types";
12
12
  import type { ExpansionResult } from "./types";
13
13
 
14
+ import {
15
+ assertInferenceActive,
16
+ assertInferenceResult,
17
+ inferenceOptions,
18
+ } from "../llm/inference-scope";
14
19
  import { ok } from "../store/types";
15
20
 
16
21
  // ─────────────────────────────────────────────────────────────────────────────
@@ -449,7 +454,7 @@ export function parseExpansionOutput(
449
454
  // Expansion Function
450
455
  // ─────────────────────────────────────────────────────────────────────────────
451
456
 
452
- export interface ExpansionOptions {
457
+ export interface ExpansionOptions extends InferenceOptions {
453
458
  /** Language hint for prompt selection */
454
459
  lang?: string;
455
460
  /** Timeout in milliseconds */
@@ -474,49 +479,47 @@ export async function expandQuery(
474
479
  // Build prompt
475
480
  const prompt = buildExpansionPrompt(query, options);
476
481
 
477
- // Run with timeout (clear timer to avoid resource leak)
478
- let timeoutId: ReturnType<typeof setTimeout> | undefined;
479
- const timeoutPromise = new Promise<null>((resolve) => {
480
- timeoutId = setTimeout(() => resolve(null), timeout);
481
- });
482
-
482
+ assertInferenceActive(options);
483
+ const operational = inferenceOptions(options);
484
+ const budget = new AbortController();
485
+ const expiresAt = performance.now() + timeout;
486
+ const timer = setTimeout(() => budget.abort(), timeout);
483
487
  try {
484
- const result = await Promise.race([
485
- genPort.generate(prompt, {
488
+ const result = await genPort.generate(
489
+ prompt,
490
+ {
486
491
  temperature: 0,
487
492
  seed: 42,
488
493
  maxTokens: 512,
489
494
  contextSize: options.contextSize,
490
- }),
491
- timeoutPromise,
492
- ]);
493
-
494
- // Clear timeout if generation completed first
495
- if (timeoutId) {
496
- clearTimeout(timeoutId);
497
- }
498
-
499
- // Timeout
500
- if (result === null) {
495
+ },
496
+ {
497
+ ...operational,
498
+ signal: operational.signal
499
+ ? AbortSignal.any([operational.signal, budget.signal])
500
+ : budget.signal,
501
+ }
502
+ );
503
+ // The independent expansion budget preserves its existing fallback. Caller
504
+ // cancellation/deadline always wins and cannot become a lexical success.
505
+ assertInferenceActive(options);
506
+ if (budget.signal.aborted || performance.now() >= expiresAt)
501
507
  return ok(null);
502
- }
503
-
504
- // Generation failed
505
- if (!result.ok) {
506
- return ok(null); // Graceful degradation
507
- }
508
-
509
- // Parse result
510
- const parsed = parseExpansionOutput(result.value, query);
511
- if (!parsed) {
508
+ assertInferenceResult(result);
509
+ if (!result.ok) return ok(null);
510
+ return ok(parseExpansionOutput(result.value, query));
511
+ } catch (cause) {
512
+ assertInferenceActive(options);
513
+ if (budget.signal.aborted || performance.now() >= expiresAt)
512
514
  return ok(null);
513
- }
514
- return ok(parsed);
515
- } catch {
516
- if (timeoutId) {
517
- clearTimeout(timeoutId);
518
- }
519
- return ok(null); // Graceful degradation
515
+ if (
516
+ cause instanceof Error &&
517
+ ["AbortError", "TimeoutError"].includes(cause.name)
518
+ )
519
+ throw cause;
520
+ return ok(null);
521
+ } finally {
522
+ clearTimeout(timer);
520
523
  }
521
524
  }
522
525
 
@@ -234,7 +234,11 @@ export function buildExplainResults(
234
234
  const candidate = candidates.find(
235
235
  (entry) =>
236
236
  entry.mirrorHash === result.conversion?.mirrorHash &&
237
- entry.seq === (planner?.retrievalSeq ?? planner?.seq)
237
+ entry.seq === (planner?.retrievalSeq ?? planner?.seq) &&
238
+ (entry.documentId === undefined ||
239
+ docidMap.get(
240
+ `${entry.mirrorHash}:${entry.seq}:${entry.documentId}`
241
+ ) === result.docid)
238
242
  );
239
243
  return {
240
244
  ...buildExplainResult(result.docid, result.score, index, candidate),
@@ -244,7 +248,7 @@ export function buildExplainResults(
244
248
  });
245
249
  }
246
250
  return candidates.slice(0, 20).map((candidate, index) => {
247
- const key = `${candidate.mirrorHash}:${candidate.seq}`;
251
+ const key = `${candidate.mirrorHash}:${candidate.seq}${candidate.documentId === undefined ? "" : `:${candidate.documentId}`}`;
248
252
  return buildExplainResult(
249
253
  docidMap.get(key) ?? "#unknown",
250
254
  candidate.blendedScore,
@@ -15,6 +15,69 @@ export interface QueryFilterEvaluation {
15
15
  reasons: string[];
16
16
  }
17
17
 
18
+ /** Internal pre-budget contract. Undefined metadata is a failed lookup, never
19
+ * an unrestricted candidate. Owners must be evaluated separately; this does
20
+ * not replace complete ownership lineage or change ranking/deduplication.
21
+ * Managed-memory scopes/supersession must already be enforced by the store.
22
+ */
23
+ export async function evaluateRetrievalEligibility(
24
+ store: StorePort,
25
+ query: string,
26
+ doc: DocumentRow | undefined,
27
+ chunks: ChunkRow[] | undefined,
28
+ options: HybridSearchOptions,
29
+ callerScope?: Pick<HybridSearchOptions, "collection" | "retrievalScope">
30
+ ): Promise<QueryFilterEvaluation & { chunks: ChunkRow[] }> {
31
+ if (!doc || !chunks || !doc.mirrorHash) {
32
+ return { matches: false, reasons: ["metadata"], chunks: [] };
33
+ }
34
+ const reasons: string[] = [];
35
+ if (!doc.active) reasons.push("inactive");
36
+ const sourcePath = doc.recordSourcePath ?? doc.relPath;
37
+ for (const scope of [callerScope, options]) {
38
+ if (scope?.collection && doc.collection !== scope.collection) {
39
+ reasons.push("collection");
40
+ }
41
+ if (scope?.retrievalScope) {
42
+ if (!scope.retrievalScope.allowedMirrorHashes.includes(doc.mirrorHash)) {
43
+ reasons.push("scope");
44
+ }
45
+ const prefix = scope.retrievalScope.relPathPrefix;
46
+ if (
47
+ prefix !== undefined &&
48
+ sourcePath !== prefix &&
49
+ !sourcePath.startsWith(`${prefix}/`)
50
+ ) {
51
+ reasons.push("path");
52
+ }
53
+ }
54
+ }
55
+ // Whole-document exclusions inspect every chunk before language selection.
56
+ // A mismatched hash is incomplete/corrupt metadata, not a usable owner.
57
+ if (chunks.some((chunk) => chunk.mirrorHash !== doc.mirrorHash)) {
58
+ reasons.push("metadata");
59
+ }
60
+ if (reasons.length === 0) {
61
+ try {
62
+ reasons.push(
63
+ ...(
64
+ await evaluateQueryTargetFilters(store, query, doc, chunks, options)
65
+ ).reasons
66
+ );
67
+ } catch {
68
+ reasons.push("metadata");
69
+ }
70
+ }
71
+ const eligibleChunks = chunks.filter(
72
+ (chunk) => !options.lang || chunk.language === options.lang
73
+ );
74
+ return {
75
+ matches: reasons.length === 0,
76
+ reasons,
77
+ chunks: reasons.length === 0 ? eligibleChunks : [],
78
+ };
79
+ }
80
+
18
81
  export function evaluateDocumentChunkFilters(
19
82
  query: string,
20
83
  doc: DocumentRow,
@@ -15,6 +15,8 @@ import type { FusionCandidate, FusionSource, RrfConfig } from "./types";
15
15
  export interface RankedInput {
16
16
  source: FusionSource;
17
17
  results: Array<{
18
+ sourceDocid?: string;
19
+ documentId?: number;
18
20
  mirrorHash: string;
19
21
  seq: number;
20
22
  rank: number; // 1-based rank
@@ -73,11 +75,14 @@ export function rrfFuse(
73
75
  : config.bm25Weight * 0.5;
74
76
 
75
77
  for (const result of input.results) {
76
- const key = `${result.mirrorHash}:${result.seq}`;
78
+ const key = `${result.mirrorHash}:${result.seq}:${result.documentId ?? ""}`;
77
79
  let candidate = candidates.get(key);
78
80
 
79
81
  if (!candidate) {
80
82
  candidate = {
83
+ ...(result.documentId === undefined
84
+ ? {}
85
+ : { documentId: result.documentId }),
81
86
  mirrorHash: result.mirrorHash,
82
87
  seq: result.seq,
83
88
  bm25Rank: null,
@@ -112,11 +117,14 @@ export function rrfFuse(
112
117
  }
113
118
 
114
119
  for (const result of input.results) {
115
- const key = `${result.mirrorHash}:${result.seq}`;
120
+ const key = `${result.mirrorHash}:${result.seq}:${result.documentId ?? ""}`;
116
121
  let candidate = candidates.get(key);
117
122
 
118
123
  if (!candidate) {
119
124
  candidate = {
125
+ ...(result.documentId === undefined
126
+ ? {}
127
+ : { documentId: result.documentId }),
120
128
  mirrorHash: result.mirrorHash,
121
129
  seq: result.seq,
122
130
  bm25Rank: null,
@@ -145,11 +153,14 @@ export function rrfFuse(
145
153
  const weight = config.bm25Weight * 0.8;
146
154
 
147
155
  for (const result of input.results) {
148
- const key = `${result.mirrorHash}:${result.seq}`;
156
+ const key = `${result.mirrorHash}:${result.seq}:${result.documentId ?? ""}`;
149
157
  let candidate = candidates.get(key);
150
158
 
151
159
  if (!candidate) {
152
160
  candidate = {
161
+ ...(result.documentId === undefined
162
+ ? {}
163
+ : { documentId: result.documentId }),
153
164
  mirrorHash: result.mirrorHash,
154
165
  seq: result.seq,
155
166
  bm25Rank: null,
@@ -211,14 +222,23 @@ export function rrfFuse(
211
222
  */
212
223
  export function toRankedInput(
213
224
  source: FusionSource,
214
- results: Array<{ mirrorHash: string; seq: number }>
225
+ results: Array<{
226
+ mirrorHash: string;
227
+ seq: number;
228
+ documentIds?: number[];
229
+ sourceDocid?: string;
230
+ }>
215
231
  ): RankedInput {
216
232
  return {
217
233
  source,
218
- results: results.map((r, i) => ({
219
- mirrorHash: r.mirrorHash,
220
- seq: r.seq,
221
- rank: i + 1,
222
- })),
234
+ results: results.flatMap((r, i) =>
235
+ (r.documentIds ?? [undefined]).map((documentId) => ({
236
+ ...(documentId === undefined ? {} : { documentId }),
237
+ ...(r.sourceDocid === undefined ? {} : { sourceDocid: r.sourceDocid }),
238
+ mirrorHash: r.mirrorHash,
239
+ seq: r.seq,
240
+ rank: i + 1,
241
+ }))
242
+ ),
223
243
  };
224
244
  }