@gmickel/gno 1.45.1 → 2.0.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 (236) hide show
  1. package/README.md +1 -1
  2. package/THIRD_PARTY_NOTICES.md +46 -0
  3. package/assets/skill/SKILL.md +7 -6
  4. package/assets/skill/cli-reference.md +14 -6
  5. package/assets/skill/mcp-reference.md +4 -1
  6. package/assets/spa-production.json.gz +0 -0
  7. package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip +0 -0
  8. package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip.sha256 +1 -0
  9. package/browser-extension/dist/chunk-4tc9v0ja.js +74 -0
  10. package/browser-extension/dist/manifest.json +1 -1
  11. package/browser-extension/dist/preview.html +1 -1
  12. package/browser-extension/dist/service-worker.js +32 -33
  13. package/bunfig.toml +2 -0
  14. package/package.json +40 -26
  15. package/spec/cli.md +30 -11
  16. package/spec/db/schema.sql +146 -1
  17. package/spec/mcp.md +26 -0
  18. package/src/app/context-runtime-types.ts +3 -0
  19. package/src/app/context-runtime.ts +2 -0
  20. package/src/cli/commands/ask.ts +6 -1
  21. package/src/cli/commands/daemon.ts +21 -8
  22. package/src/cli/commands/embed.ts +77 -41
  23. package/src/cli/commands/mcp/install.ts +20 -0
  24. package/src/cli/commands/mcp/paths.ts +25 -0
  25. package/src/cli/commands/mcp/status.ts +6 -0
  26. package/src/cli/detach.ts +3 -2
  27. package/src/cli/program.ts +6 -0
  28. package/src/config/types.ts +3 -3
  29. package/src/converters/adapters/markitdownTs/adapter.ts +1 -2
  30. package/src/converters/adapters/officeparser/adapter.ts +1 -2
  31. package/src/converters/versions.ts +6 -8
  32. package/src/core/context-evidence.ts +8 -4
  33. package/src/core/job-manager.ts +95 -13
  34. package/src/core/network-boundary-inventory.ts +10 -0
  35. package/src/core/shutdown-budget.ts +45 -0
  36. package/src/embed/backlog.ts +107 -4
  37. package/src/embed/batch.ts +42 -2
  38. package/src/embed/fingerprint.ts +16 -0
  39. package/src/embed/retry.ts +113 -5
  40. package/src/embed/variant-backlog.ts +105 -0
  41. package/src/embed/variant-plan.ts +62 -0
  42. package/src/embed/variant-retry.ts +113 -0
  43. package/src/ingestion/graph-reconciliation.ts +327 -0
  44. package/src/ingestion/sync.ts +9 -272
  45. package/src/llm/http-inference.ts +6 -0
  46. package/src/llm/httpEmbedding.ts +37 -6
  47. package/src/llm/httpGeneration.ts +18 -3
  48. package/src/llm/httpRerank.ts +23 -5
  49. package/src/llm/inference-cancellation.ts +168 -0
  50. package/src/llm/inference-scope.ts +202 -0
  51. package/src/llm/lazy-ports.ts +115 -0
  52. package/src/llm/native-worker/client.ts +541 -0
  53. package/src/llm/native-worker/dispatcher.ts +228 -0
  54. package/src/llm/native-worker/embedding-identity.ts +33 -0
  55. package/src/llm/native-worker/entry.ts +173 -0
  56. package/src/llm/native-worker/errors.ts +32 -0
  57. package/src/llm/native-worker/evaluation.ts +16 -0
  58. package/src/llm/native-worker/owned-exit.ts +108 -0
  59. package/src/llm/native-worker/owner.ts +141 -0
  60. package/src/llm/native-worker/ports.ts +317 -0
  61. package/src/llm/native-worker/protocol.ts +442 -0
  62. package/src/llm/native-worker/runtime-config.ts +92 -0
  63. package/src/llm/nodeLlamaCpp/adapter.ts +77 -20
  64. package/src/llm/nodeLlamaCpp/embedding.ts +130 -46
  65. package/src/llm/nodeLlamaCpp/generation.ts +34 -5
  66. package/src/llm/nodeLlamaCpp/lifecycle-options.ts +99 -0
  67. package/src/llm/nodeLlamaCpp/lifecycle.ts +209 -204
  68. package/src/llm/nodeLlamaCpp/rerank-capacity.ts +111 -0
  69. package/src/llm/nodeLlamaCpp/rerank.ts +118 -27
  70. package/src/llm/nodeLlamaCpp/simulator-handle.ts +73 -0
  71. package/src/llm/nodeLlamaCpp/simulator-install.ts +124 -0
  72. package/src/llm/nodeLlamaCpp/simulator-session.ts +240 -0
  73. package/src/llm/nodeLlamaCpp/simulator-types.ts +80 -0
  74. package/src/llm/types.ts +35 -5
  75. package/src/mcp/context.ts +27 -0
  76. package/src/mcp/http-transport.ts +12 -10
  77. package/src/mcp/server.ts +3 -0
  78. package/src/mcp/tool-profile.ts +30 -8
  79. package/src/mcp/tools/context.ts +8 -11
  80. package/src/mcp/tools/embed.ts +1 -1
  81. package/src/mcp/tools/index-cmd.ts +1 -1
  82. package/src/mcp/tools/index.ts +10 -8
  83. package/src/mcp/tools/query.ts +14 -30
  84. package/src/mcp/tools/vsearch.ts +1 -1
  85. package/src/pipeline/answer.ts +23 -3
  86. package/src/pipeline/claim-verifier.ts +6 -0
  87. package/src/pipeline/expansion.ts +43 -40
  88. package/src/pipeline/explain.ts +6 -2
  89. package/src/pipeline/filters.ts +63 -0
  90. package/src/pipeline/fusion.ts +29 -9
  91. package/src/pipeline/graph-retrieval.ts +29 -9
  92. package/src/pipeline/hybrid.ts +198 -55
  93. package/src/pipeline/hydration.ts +161 -0
  94. package/src/pipeline/owner-fusion.ts +87 -0
  95. package/src/pipeline/rerank.ts +35 -11
  96. package/src/pipeline/search.ts +13 -2
  97. package/src/pipeline/types.ts +5 -3
  98. package/src/pipeline/vsearch.ts +87 -7
  99. package/src/sdk/client.ts +47 -3
  100. package/src/sdk/embed.ts +63 -39
  101. package/src/serve/background-runtime.ts +1 -1
  102. package/src/serve/context.ts +41 -56
  103. package/src/serve/embed-scheduler.ts +58 -35
  104. package/src/serve/public/components/IndexingProgress.tsx +46 -60
  105. package/src/serve/public/globals.built.css +1 -1
  106. package/src/serve/public/lib/shiki-language-ids.ts +14 -0
  107. package/src/serve/resident-admission.ts +36 -36
  108. package/src/serve/resident-background-work.ts +20 -2
  109. package/src/serve/resident-request.ts +11 -5
  110. package/src/serve/resident-runtime.ts +97 -61
  111. package/src/serve/resident-shutdown.ts +153 -0
  112. package/src/serve/routes/api.ts +3 -1
  113. package/src/serve/server.ts +47 -26
  114. package/src/store/migrations/028-vector-variants.ts +54 -0
  115. package/src/store/migrations/029-graph-reference-state.ts +77 -0
  116. package/src/store/migrations/index.ts +4 -0
  117. package/src/store/sqlite/adapter.ts +251 -183
  118. package/src/store/sqlite/eligibility.ts +174 -0
  119. package/src/store/sqlite/graph-edge-application.ts +66 -0
  120. package/src/store/sqlite/graph-reference-state.ts +194 -0
  121. package/src/store/sqlite/legacy-vector-ownership.ts +79 -0
  122. package/src/store/types.ts +80 -12
  123. package/src/store/vector/eligibility.ts +36 -0
  124. package/src/store/vector/freshness.ts +33 -6
  125. package/src/store/vector/lazy.ts +81 -0
  126. package/src/store/vector/sqlite-vec.ts +106 -54
  127. package/src/store/vector/stats.ts +14 -3
  128. package/src/store/vector/types.ts +35 -2
  129. package/src/store/vector/variant-search.ts +192 -0
  130. package/src/store/vector/variants.ts +451 -0
  131. package/vendor/converters/markitdown-ts/LICENSE +21 -0
  132. package/vendor/converters/markitdown-ts/dist/index.cjs +1180 -0
  133. package/vendor/converters/markitdown-ts/dist/index.d.cts +46 -0
  134. package/vendor/converters/markitdown-ts/dist/index.d.mts +46 -0
  135. package/vendor/converters/markitdown-ts/dist/index.d.ts +46 -0
  136. package/vendor/converters/markitdown-ts/dist/index.mjs +1152 -0
  137. package/vendor/converters/markitdown-ts/package.json +77 -0
  138. package/vendor/converters/officeparser/LICENSE +21 -0
  139. package/vendor/converters/officeparser/dist/OfficeConverter.d.ts +47 -0
  140. package/vendor/converters/officeparser/dist/OfficeConverter.js +76 -0
  141. package/vendor/converters/officeparser/dist/OfficeGenerator.d.ts +23 -0
  142. package/vendor/converters/officeparser/dist/OfficeGenerator.js +73 -0
  143. package/vendor/converters/officeparser/dist/OfficeParser.d.ts +106 -0
  144. package/vendor/converters/officeparser/dist/OfficeParser.js +332 -0
  145. package/vendor/converters/officeparser/dist/cli.d.ts +28 -0
  146. package/vendor/converters/officeparser/dist/cli.js +381 -0
  147. package/vendor/converters/officeparser/dist/defaults.d.ts +41 -0
  148. package/vendor/converters/officeparser/dist/defaults.js +218 -0
  149. package/vendor/converters/officeparser/dist/generators/BaseGenerator.d.ts +107 -0
  150. package/vendor/converters/officeparser/dist/generators/BaseGenerator.js +248 -0
  151. package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.d.ts +82 -0
  152. package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.js +797 -0
  153. package/vendor/converters/officeparser/dist/generators/CsvGenerator.d.ts +38 -0
  154. package/vendor/converters/officeparser/dist/generators/CsvGenerator.js +245 -0
  155. package/vendor/converters/officeparser/dist/generators/EpubGenerator.d.ts +43 -0
  156. package/vendor/converters/officeparser/dist/generators/EpubGenerator.js +315 -0
  157. package/vendor/converters/officeparser/dist/generators/HtmlGenerator.d.ts +59 -0
  158. package/vendor/converters/officeparser/dist/generators/HtmlGenerator.js +1942 -0
  159. package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.d.ts +96 -0
  160. package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.js +1175 -0
  161. package/vendor/converters/officeparser/dist/generators/PdfGenerator.d.ts +22 -0
  162. package/vendor/converters/officeparser/dist/generators/PdfGenerator.js +194 -0
  163. package/vendor/converters/officeparser/dist/generators/RtfGenerator.d.ts +29 -0
  164. package/vendor/converters/officeparser/dist/generators/RtfGenerator.js +316 -0
  165. package/vendor/converters/officeparser/dist/generators/TextGenerator.d.ts +13 -0
  166. package/vendor/converters/officeparser/dist/generators/TextGenerator.js +201 -0
  167. package/vendor/converters/officeparser/dist/index.d.ts +60 -0
  168. package/vendor/converters/officeparser/dist/index.js +72 -0
  169. package/vendor/converters/officeparser/dist/index.mjs +18 -0
  170. package/vendor/converters/officeparser/dist/officeparser.browser.d.ts +2621 -0
  171. package/vendor/converters/officeparser/dist/officeparser.browser.iife.js +1336 -0
  172. package/vendor/converters/officeparser/dist/officeparser.browser.mjs +1335 -0
  173. package/vendor/converters/officeparser/dist/officeparser.browser.slim.d.ts +2621 -0
  174. package/vendor/converters/officeparser/dist/officeparser.browser.slim.iife.js +1336 -0
  175. package/vendor/converters/officeparser/dist/officeparser.browser.slim.mjs +1335 -0
  176. package/vendor/converters/officeparser/dist/parsers/CsvParser.d.ts +9 -0
  177. package/vendor/converters/officeparser/dist/parsers/CsvParser.js +115 -0
  178. package/vendor/converters/officeparser/dist/parsers/EpubParser.d.ts +8 -0
  179. package/vendor/converters/officeparser/dist/parsers/EpubParser.js +217 -0
  180. package/vendor/converters/officeparser/dist/parsers/ExcelParser.d.ts +32 -0
  181. package/vendor/converters/officeparser/dist/parsers/ExcelParser.js +736 -0
  182. package/vendor/converters/officeparser/dist/parsers/HtmlParser.d.ts +2 -0
  183. package/vendor/converters/officeparser/dist/parsers/HtmlParser.js +1287 -0
  184. package/vendor/converters/officeparser/dist/parsers/MarkdownParser.d.ts +2 -0
  185. package/vendor/converters/officeparser/dist/parsers/MarkdownParser.js +1272 -0
  186. package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.d.ts +31 -0
  187. package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.js +1819 -0
  188. package/vendor/converters/officeparser/dist/parsers/PdfParser.d.ts +67 -0
  189. package/vendor/converters/officeparser/dist/parsers/PdfParser.js +848 -0
  190. package/vendor/converters/officeparser/dist/parsers/PowerPointParser.d.ts +32 -0
  191. package/vendor/converters/officeparser/dist/parsers/PowerPointParser.js +950 -0
  192. package/vendor/converters/officeparser/dist/parsers/RtfParser.d.ts +187 -0
  193. package/vendor/converters/officeparser/dist/parsers/RtfParser.js +1801 -0
  194. package/vendor/converters/officeparser/dist/parsers/WordParser.d.ts +79 -0
  195. package/vendor/converters/officeparser/dist/parsers/WordParser.js +1177 -0
  196. package/vendor/converters/officeparser/dist/sbom.cdx.json +1763 -0
  197. package/vendor/converters/officeparser/dist/types.d.ts +2507 -0
  198. package/vendor/converters/officeparser/dist/types.js +107 -0
  199. package/vendor/converters/officeparser/dist/utils/astUtils.d.ts +16 -0
  200. package/vendor/converters/officeparser/dist/utils/astUtils.js +33 -0
  201. package/vendor/converters/officeparser/dist/utils/chartUtils.d.ts +6 -0
  202. package/vendor/converters/officeparser/dist/utils/chartUtils.js +257 -0
  203. package/vendor/converters/officeparser/dist/utils/configUtils.d.ts +44 -0
  204. package/vendor/converters/officeparser/dist/utils/configUtils.js +315 -0
  205. package/vendor/converters/officeparser/dist/utils/dateUtils.d.ts +17 -0
  206. package/vendor/converters/officeparser/dist/utils/dateUtils.js +69 -0
  207. package/vendor/converters/officeparser/dist/utils/envUtils.d.ts +29 -0
  208. package/vendor/converters/officeparser/dist/utils/envUtils.js +152 -0
  209. package/vendor/converters/officeparser/dist/utils/errorUtils.d.ts +72 -0
  210. package/vendor/converters/officeparser/dist/utils/errorUtils.js +245 -0
  211. package/vendor/converters/officeparser/dist/utils/imageUtils.d.ts +66 -0
  212. package/vendor/converters/officeparser/dist/utils/imageUtils.js +133 -0
  213. package/vendor/converters/officeparser/dist/utils/mathUtils.d.ts +42 -0
  214. package/vendor/converters/officeparser/dist/utils/mathUtils.js +385 -0
  215. package/vendor/converters/officeparser/dist/utils/moduleLoader.d.ts +18 -0
  216. package/vendor/converters/officeparser/dist/utils/moduleLoader.js +106 -0
  217. package/vendor/converters/officeparser/dist/utils/ocrUtils.d.ts +42 -0
  218. package/vendor/converters/officeparser/dist/utils/ocrUtils.js +428 -0
  219. package/vendor/converters/officeparser/dist/utils/sanitize.d.ts +148 -0
  220. package/vendor/converters/officeparser/dist/utils/sanitize.js +344 -0
  221. package/vendor/converters/officeparser/dist/utils/sheetUtils.d.ts +7 -0
  222. package/vendor/converters/officeparser/dist/utils/sheetUtils.js +35 -0
  223. package/vendor/converters/officeparser/dist/utils/styleMapper.d.ts +36 -0
  224. package/vendor/converters/officeparser/dist/utils/styleMapper.js +224 -0
  225. package/vendor/converters/officeparser/dist/utils/xmlUtils.d.ts +163 -0
  226. package/vendor/converters/officeparser/dist/utils/xmlUtils.js +461 -0
  227. package/vendor/converters/officeparser/dist/utils/zipUtils.d.ts +134 -0
  228. package/vendor/converters/officeparser/dist/utils/zipUtils.js +337 -0
  229. package/vendor/converters/officeparser/package.json +147 -0
  230. package/vendor/converters/upstream-manifest.json +124 -0
  231. package/vendor/dependency-fixes/README.md +77 -0
  232. package/vendor/dependency-fixes/vendor-converters.py +83 -0
  233. package/browser-extension/artifacts/gno-browser-clipper-v1.45.1.zip +0 -0
  234. package/browser-extension/artifacts/gno-browser-clipper-v1.45.1.zip.sha256 +0 -1
  235. package/browser-extension/dist/chunk-627emwpj.js +0 -75
  236. /package/browser-extension/dist/{chunk-ydfx5d7p.css → chunk-z74y8n8c.css} +0 -0
@@ -1,9 +1,17 @@
1
1
  import type { EmbeddingPort } from "../llm/types";
2
2
  import type { StoreResult } from "../store/types";
3
- import type { BacklogItem, VectorIndexPort, VectorRow } from "../store/vector";
3
+ import type {
4
+ BacklogItem,
5
+ VectorIndexPort,
6
+ VectorRow,
7
+ VectorStatsPort,
8
+ } from "../store/vector";
4
9
 
5
10
  import { isSqliteLockContention } from "../core/file-lock";
11
+ import { assertInferenceActive } from "../llm/inference-scope";
6
12
  import { formatDocForEmbedding } from "../pipeline/contextual";
13
+ import { err, ok } from "../store/types";
14
+ import { getVectorStatsDatabase } from "../store/vector/stats";
7
15
  import { embedTextsWithRecovery } from "./batch";
8
16
 
9
17
  export const MAX_EMBED_CHUNK_ATTEMPTS = 2;
@@ -42,12 +50,21 @@ export interface EmbedStoreBatchResult {
42
50
  // src/cli/commands/index-cmd.ts) must surface contentionErrors in their
43
51
  // summary and exit non-zero — the integrator wires that.
44
52
 
45
- export function chunkRetryKey(item: Pick<BacklogItem, "mirrorHash" | "seq">) {
53
+ export function chunkRetryKey(
54
+ item: Pick<BacklogItem, "mirrorHash" | "seq"> & {
55
+ documentId?: number;
56
+ inputHash?: string;
57
+ }
58
+ ) {
59
+ if (item.documentId !== undefined && item.inputHash !== undefined) {
60
+ return `${item.documentId}\0${item.mirrorHash}\0${item.seq}\0${item.inputHash}`;
61
+ }
46
62
  return `${item.mirrorHash}\0${item.seq}`;
47
63
  }
48
64
 
49
65
  export function addUniqueSamples(target: string[], samples: string[]): void {
50
66
  for (const sample of samples) {
67
+ assertInferenceActive();
51
68
  if (target.length >= MAX_EMBED_FAILURE_SAMPLES) {
52
69
  break;
53
70
  }
@@ -127,6 +144,7 @@ export async function upsertVectorsWithContentionRetry(
127
144
  vectors: VectorRow[],
128
145
  delays?: number[]
129
146
  ): Promise<StoreResult<void>> {
147
+ assertInferenceActive();
130
148
  let storeResult = await vectorIndex.upsertVectors(vectors);
131
149
  let attempts = 1;
132
150
  while (
@@ -138,6 +156,7 @@ export async function upsertVectorsWithContentionRetry(
138
156
  if (delayMs > 0) {
139
157
  await Bun.sleep(delayMs);
140
158
  }
159
+ assertInferenceActive();
141
160
  storeResult = await vectorIndex.upsertVectors(vectors);
142
161
  attempts += 1;
143
162
  }
@@ -150,10 +169,22 @@ export async function embedAndStoreBatch(params: {
150
169
  items: BacklogItem[];
151
170
  modelUri: string;
152
171
  embedFingerprint: string;
172
+ statsPort?: VectorStatsPort;
173
+ identityStillCurrent?: () => boolean;
153
174
  /** Test seam: override contention-retry delays in milliseconds. */
154
175
  delays?: number[];
155
176
  }): Promise<EmbedStoreBatchResult> {
156
177
  const { embedPort, vectorIndex, items, modelUri, embedFingerprint } = params;
178
+ const db = params.statsPort && getVectorStatsDatabase(params.statsPort);
179
+ if (db && !vectorIndex.upsertVectorsChecked)
180
+ return {
181
+ embedded: 0,
182
+ errors: items.length,
183
+ contentionErrors: 0,
184
+ retryItems: [],
185
+ errorSamples: ["Atomic vector checkpoint unavailable"],
186
+ batchFailed: false,
187
+ };
157
188
  const embedResult = await embedTextsWithRecovery(
158
189
  embedPort,
159
190
  items.map((item) =>
@@ -180,6 +211,7 @@ export async function embedAndStoreBatch(params: {
180
211
  const vectors: VectorRow[] = [];
181
212
  const retryItems: BacklogItem[] = [];
182
213
  for (const [idx, item] of items.entries()) {
214
+ assertInferenceActive();
183
215
  const embedding = embedResult.value.vectors[idx];
184
216
  if (!embedding) {
185
217
  retryItems.push(item);
@@ -207,8 +239,75 @@ export async function embedAndStoreBatch(params: {
207
239
  };
208
240
  }
209
241
 
242
+ let written = 0;
243
+ let committedRows: VectorRow[] = [];
244
+ const itemsByKey = new Map(items.map((item) => [chunkRetryKey(item), item]));
245
+ const checkpoint = (rows: VectorRow[]): VectorRow[] => {
246
+ assertInferenceActive();
247
+ if (params.identityStillCurrent && !params.identityStillCurrent())
248
+ return [];
249
+ if (!db) return rows;
250
+ // A concurrently activated verified partition forbids a late legacy write.
251
+ if (
252
+ db
253
+ .query(
254
+ "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'vector_partitions'"
255
+ )
256
+ .get() &&
257
+ db
258
+ .query(
259
+ "SELECT 1 FROM vector_partitions WHERE model = ? AND state = 'active' AND activated_epoch IS NOT NULL LIMIT 1"
260
+ )
261
+ .get(modelUri)
262
+ )
263
+ return [];
264
+ return rows.filter((row) => {
265
+ const item = itemsByKey.get(chunkRetryKey(row));
266
+ const current = db
267
+ .query<{ text: string; title: string | null }, [string, number]>(`
268
+ SELECT c.text, d.title FROM content_chunks c
269
+ JOIN documents d ON d.mirror_hash = c.mirror_hash AND d.active = 1
270
+ WHERE c.mirror_hash = ? AND c.seq = ? ORDER BY d.id LIMIT 1
271
+ `)
272
+ .get(row.mirrorHash, row.seq);
273
+ return (
274
+ !!item &&
275
+ !!current &&
276
+ formatDocForEmbedding(
277
+ current.text,
278
+ current.title ?? undefined,
279
+ modelUri
280
+ ) ===
281
+ formatDocForEmbedding(item.text, item.title ?? undefined, modelUri)
282
+ );
283
+ });
284
+ };
210
285
  const storeResult = await upsertVectorsWithContentionRetry(
211
- vectorIndex,
286
+ {
287
+ upsertVectors: async (rows) => {
288
+ if (vectorIndex.upsertVectorsChecked) {
289
+ const result = await vectorIndex.upsertVectorsChecked(
290
+ rows,
291
+ (candidates) => {
292
+ committedRows = checkpoint(candidates);
293
+ return committedRows;
294
+ }
295
+ );
296
+ if (!result.ok) return result;
297
+ written = result.value;
298
+ return ok(undefined);
299
+ }
300
+ if (db)
301
+ return err("INVALID_INPUT", "Atomic vector checkpoint unavailable");
302
+ const valid = checkpoint(rows);
303
+ const result = await vectorIndex.upsertVectors(valid);
304
+ if (result.ok) {
305
+ written = valid.length;
306
+ committedRows = valid;
307
+ }
308
+ return result;
309
+ },
310
+ },
212
311
  vectors,
213
312
  params.delays
214
313
  );
@@ -237,11 +336,20 @@ export async function embedAndStoreBatch(params: {
237
336
  };
238
337
  }
239
338
 
339
+ const vectorKeys = new Set(vectors.map((row) => chunkRetryKey(row)));
340
+ const committedKeys = new Set(committedRows.map((row) => chunkRetryKey(row)));
240
341
  return {
241
- embedded: vectors.length,
342
+ embedded: written,
242
343
  errors: 0,
243
344
  contentionErrors: 0,
244
- retryItems,
345
+ retryItems: [
346
+ ...retryItems,
347
+ ...items.filter(
348
+ (item) =>
349
+ vectorKeys.has(chunkRetryKey(item)) &&
350
+ !committedKeys.has(chunkRetryKey(item))
351
+ ),
352
+ ],
245
353
  errorSamples: embedResult.value.failureSamples,
246
354
  suggestion: embedResult.value.retrySuggestion,
247
355
  batchFailed: embedResult.value.batchFailed,
@@ -0,0 +1,105 @@
1
+ import type { StoreResult } from "../store/types";
2
+ import type { VectorVariantStore } from "../store/vector/variants";
3
+ import type { EmbedBacklogDeps, EmbedBacklogResult } from "./backlog";
4
+
5
+ import {
6
+ assertInferenceActive,
7
+ isBackgroundInference,
8
+ } from "../llm/inference-scope";
9
+ import { err, ok } from "../store/types";
10
+ import { getVectorStatsDatabase } from "../store/vector/stats";
11
+ import { variantBacklogPage } from "./variant-plan";
12
+ import { embedVariantBatch } from "./variant-retry";
13
+
14
+ /** One bounded owner pass plus one retry; mutations stay durably pending for the next pass. */
15
+ export async function embedVariantBacklog(
16
+ deps: EmbedBacklogDeps,
17
+ store: VectorVariantStore
18
+ ): Promise<StoreResult<EmbedBacklogResult>> {
19
+ const identityStillCurrent = deps.identityStillCurrent;
20
+ if (!identityStillCurrent)
21
+ return err(
22
+ "INVALID_INPUT",
23
+ "Variant embedding requires an effective runtime identity check"
24
+ );
25
+ const db = getVectorStatsDatabase(deps.statsPort);
26
+ if (deps.collection && !db)
27
+ return err(
28
+ "INVALID_INPUT",
29
+ "Collection-scoped variant backlog requires document ownership storage"
30
+ );
31
+ const background = isBackgroundInference();
32
+ const batchSize = background
33
+ ? Math.min(deps.batchSize ?? 32, 32)
34
+ : (deps.batchSize ?? 32);
35
+ if (!Number.isSafeInteger(batchSize) || batchSize < 1)
36
+ return err("INVALID_INPUT", "Invalid embedding batch size");
37
+ const total = { embedded: 0, errors: 0, contentionErrors: 0 };
38
+ let after: { documentId: number; seq: number } | undefined;
39
+ try {
40
+ while (identityStillCurrent()) {
41
+ assertInferenceActive();
42
+ const pending = variantBacklogPage(deps, store, batchSize, after);
43
+ if (!pending.length) break;
44
+ const last = pending.at(-1)!;
45
+ after = { documentId: last.documentId, seq: last.seq };
46
+ const owners = deps.collection
47
+ ? pending.filter(
48
+ (owner) =>
49
+ !!db!
50
+ .query(
51
+ "SELECT 1 FROM documents WHERE id = ? AND collection = ? AND active = 1"
52
+ )
53
+ .get(owner.documentId, deps.collection!)
54
+ )
55
+ : pending;
56
+ let result = await embedVariantBatch({
57
+ store,
58
+ embedPort: deps.embedPort,
59
+ owners,
60
+ identityStillCurrent,
61
+ force: deps.force,
62
+ });
63
+ total.embedded += result.embedded;
64
+ total.errors += result.errors;
65
+ total.contentionErrors += result.contentionErrors;
66
+ if (background) total.errors += result.retryOwners.length;
67
+ if (!background && result.retryOwners.length) {
68
+ result = await embedVariantBatch({
69
+ store,
70
+ embedPort: deps.embedPort,
71
+ owners: result.retryOwners,
72
+ identityStillCurrent,
73
+ force: deps.force,
74
+ });
75
+ total.embedded += result.embedded;
76
+ total.errors += result.errors + result.retryOwners.length;
77
+ total.contentionErrors += result.contentionErrors;
78
+ }
79
+ deps.onProgress?.(total.embedded, total.errors);
80
+ if (background) await Bun.sleep(0);
81
+ }
82
+ assertInferenceActive();
83
+ // Capture the epoch before checking completeness; activate rechecks under write lock.
84
+ const epoch = store.epoch();
85
+ if (identityStillCurrent() && !store.pending({ limit: 1 }).length) {
86
+ try {
87
+ if (!store.isActive()) store.syncIndex();
88
+ if (!identityStillCurrent()) return ok(total);
89
+ store.activate(epoch);
90
+ } catch (cause) {
91
+ return ok({
92
+ ...total,
93
+ syncError: cause instanceof Error ? cause.message : String(cause),
94
+ });
95
+ }
96
+ }
97
+ assertInferenceActive();
98
+ return ok(total);
99
+ } catch (cause) {
100
+ return err(
101
+ "QUERY_FAILED",
102
+ cause instanceof Error ? cause.message : String(cause)
103
+ );
104
+ }
105
+ }
@@ -0,0 +1,62 @@
1
+ import type { VectorVariantStore } from "../store/vector/variants";
2
+ import type { EmbedBacklogDeps } from "./backlog";
3
+
4
+ import { getVectorStatsDatabase } from "../store/vector/stats";
5
+
6
+ /** Counts and execution enumerate the same exact current owner partition. */
7
+ export function variantBacklogPage(
8
+ deps: EmbedBacklogDeps,
9
+ store: VectorVariantStore,
10
+ limit: number,
11
+ after?: { documentId: number; seq: number }
12
+ ) {
13
+ if (!deps.force) return store.pending({ limit, after });
14
+ const db = getVectorStatsDatabase(deps.statsPort);
15
+ if (!db)
16
+ throw new Error("Forced variants require document ownership storage");
17
+ const rows = db
18
+ .query<
19
+ { documentId: number; seq: number },
20
+ [number, number, number, number]
21
+ >(`
22
+ SELECT d.id AS documentId, c.seq FROM documents d
23
+ JOIN content_chunks c ON c.mirror_hash = d.mirror_hash
24
+ WHERE d.active = 1 AND (d.id > ? OR (d.id = ? AND c.seq > ?))
25
+ ORDER BY d.id, c.seq LIMIT ?
26
+ `)
27
+ .all(
28
+ after?.documentId ?? -1,
29
+ after?.documentId ?? -1,
30
+ after?.seq ?? -1,
31
+ limit
32
+ );
33
+ return rows.flatMap((row) => {
34
+ const owner = store.current(row.documentId, row.seq);
35
+ return owner ? [owner] : [];
36
+ });
37
+ }
38
+
39
+ export function countVariantBacklog(deps: EmbedBacklogDeps): number {
40
+ const store = deps.variantStore;
41
+ if (!store) throw new Error("Verified variant identity required");
42
+ const db = getVectorStatsDatabase(deps.statsPort);
43
+ let after: { documentId: number; seq: number } | undefined;
44
+ let count = 0;
45
+ while (true) {
46
+ const owners = variantBacklogPage(deps, store, 256, after);
47
+ if (!owners.length) return count;
48
+ for (const owner of owners) {
49
+ if (
50
+ !deps.collection ||
51
+ db
52
+ ?.query(
53
+ "SELECT 1 FROM documents WHERE id = ? AND collection = ? AND active = 1"
54
+ )
55
+ .get(owner.documentId, deps.collection)
56
+ )
57
+ count += 1;
58
+ }
59
+ const last = owners.at(-1)!;
60
+ after = { documentId: last.documentId, seq: last.seq };
61
+ }
62
+ }
@@ -0,0 +1,113 @@
1
+ import type { EmbeddingPort } from "../llm/types";
2
+ import type { VectorOwnerInput } from "../store/vector/types";
3
+ import type { VectorVariantStore } from "../store/vector/variants";
4
+
5
+ import { err, ok } from "../store/types";
6
+ import { embedTextsWithRecovery } from "./batch";
7
+ import {
8
+ chunkRetryKey,
9
+ isUpsertLockContention,
10
+ upsertVectorsWithContentionRetry,
11
+ } from "./retry";
12
+
13
+ function isCurrent(
14
+ store: VectorVariantStore,
15
+ owner: VectorOwnerInput
16
+ ): boolean {
17
+ const current = store.current(owner.documentId, owner.seq);
18
+ return (
19
+ current?.mirrorHash === owner.mirrorHash &&
20
+ current.inputHash === owner.inputHash &&
21
+ current.formattedInput === owner.formattedInput
22
+ );
23
+ }
24
+
25
+ /** Exact inputs are inferred once; successful current owners checkpoint atomically. */
26
+ export async function embedVariantBatch(params: {
27
+ store: VectorVariantStore;
28
+ embedPort: EmbeddingPort;
29
+ owners: VectorOwnerInput[];
30
+ identityStillCurrent: () => boolean;
31
+ delays?: number[];
32
+ force?: boolean;
33
+ }): Promise<{
34
+ embedded: number;
35
+ errors: number;
36
+ contentionErrors: number;
37
+ retryOwners: VectorOwnerInput[];
38
+ }> {
39
+ const { store, embedPort, identityStillCurrent } = params;
40
+ const empty = {
41
+ embedded: 0,
42
+ errors: 0,
43
+ contentionErrors: 0,
44
+ retryOwners: [] as VectorOwnerInput[],
45
+ };
46
+ if (!identityStillCurrent()) return empty;
47
+ const owners = [
48
+ ...new Map(
49
+ params.owners.map((owner) => [chunkRetryKey(owner), owner])
50
+ ).values(),
51
+ ].filter((owner) => isCurrent(store, owner));
52
+ const inputs = new Map<string, string>();
53
+ const reusable = new Set<string>();
54
+ for (const owner of owners) {
55
+ if (!params.force && store.reusable(owner)) reusable.add(owner.inputHash);
56
+ else inputs.set(owner.inputHash, owner.formattedInput);
57
+ }
58
+ const keys = [...inputs.keys()];
59
+ const result = await embedTextsWithRecovery(embedPort, [...inputs.values()]);
60
+ if (!identityStillCurrent()) return empty;
61
+ const current = owners.filter((owner) => isCurrent(store, owner));
62
+ const vectors = new Map<string, Float32Array>();
63
+ if (result.ok) {
64
+ for (const [index, key] of keys.entries()) {
65
+ const vector = result.value.vectors[index];
66
+ if (vector) vectors.set(key, new Float32Array(vector));
67
+ }
68
+ }
69
+ const retryOwners = current.filter(
70
+ (owner) => !reusable.has(owner.inputHash) && !vectors.has(owner.inputHash)
71
+ );
72
+ const rows = current
73
+ .filter(
74
+ (owner) => reusable.has(owner.inputHash) || vectors.has(owner.inputHash)
75
+ )
76
+ .map((owner) => ({ owner, embedding: vectors.get(owner.inputHash) }));
77
+ if (!rows.length) return { ...empty, retryOwners };
78
+ let written = 0;
79
+ const persisted = await upsertVectorsWithContentionRetry(
80
+ {
81
+ upsertVectors: () => {
82
+ try {
83
+ // A contention wait may allow document or runtime mutations. Revalidate each attempt.
84
+ if (!identityStillCurrent()) return Promise.resolve(ok(undefined));
85
+ const valid = rows.filter(({ owner }) => isCurrent(store, owner));
86
+ store.write(valid);
87
+ written = valid.length;
88
+ return Promise.resolve(ok(undefined));
89
+ } catch (cause) {
90
+ return Promise.resolve(
91
+ err(
92
+ "QUERY_FAILED",
93
+ cause instanceof Error ? cause.message : String(cause),
94
+ cause
95
+ )
96
+ );
97
+ }
98
+ },
99
+ },
100
+ [],
101
+ params.delays
102
+ );
103
+ if (!persisted.ok)
104
+ return {
105
+ ...empty,
106
+ retryOwners,
107
+ errors: isUpsertLockContention(persisted.error) ? 0 : rows.length,
108
+ contentionErrors: isUpsertLockContention(persisted.error)
109
+ ? rows.length
110
+ : 0,
111
+ };
112
+ return { ...empty, embedded: written, retryOwners };
113
+ }