@gmickel/gno 1.46.0 → 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 (230) 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/spa-production.json.gz +0 -0
  5. package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip +0 -0
  6. package/browser-extension/artifacts/gno-browser-clipper-v2.0.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 +21 -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/globals.built.css +1 -1
  100. package/src/serve/public/lib/shiki-language-ids.ts +14 -0
  101. package/src/serve/resident-admission.ts +36 -36
  102. package/src/serve/resident-background-work.ts +20 -2
  103. package/src/serve/resident-request.ts +11 -5
  104. package/src/serve/resident-runtime.ts +97 -61
  105. package/src/serve/resident-shutdown.ts +153 -0
  106. package/src/serve/routes/api.ts +3 -1
  107. package/src/serve/server.ts +47 -26
  108. package/src/store/migrations/028-vector-variants.ts +54 -0
  109. package/src/store/migrations/029-graph-reference-state.ts +77 -0
  110. package/src/store/migrations/index.ts +4 -0
  111. package/src/store/sqlite/adapter.ts +251 -183
  112. package/src/store/sqlite/eligibility.ts +174 -0
  113. package/src/store/sqlite/graph-edge-application.ts +66 -0
  114. package/src/store/sqlite/graph-reference-state.ts +194 -0
  115. package/src/store/sqlite/legacy-vector-ownership.ts +79 -0
  116. package/src/store/types.ts +80 -12
  117. package/src/store/vector/eligibility.ts +36 -0
  118. package/src/store/vector/freshness.ts +33 -6
  119. package/src/store/vector/lazy.ts +81 -0
  120. package/src/store/vector/sqlite-vec.ts +106 -54
  121. package/src/store/vector/stats.ts +14 -3
  122. package/src/store/vector/types.ts +35 -2
  123. package/src/store/vector/variant-search.ts +192 -0
  124. package/src/store/vector/variants.ts +451 -0
  125. package/vendor/converters/markitdown-ts/LICENSE +21 -0
  126. package/vendor/converters/markitdown-ts/dist/index.cjs +1180 -0
  127. package/vendor/converters/markitdown-ts/dist/index.d.cts +46 -0
  128. package/vendor/converters/markitdown-ts/dist/index.d.mts +46 -0
  129. package/vendor/converters/markitdown-ts/dist/index.d.ts +46 -0
  130. package/vendor/converters/markitdown-ts/dist/index.mjs +1152 -0
  131. package/vendor/converters/markitdown-ts/package.json +77 -0
  132. package/vendor/converters/officeparser/LICENSE +21 -0
  133. package/vendor/converters/officeparser/dist/OfficeConverter.d.ts +47 -0
  134. package/vendor/converters/officeparser/dist/OfficeConverter.js +76 -0
  135. package/vendor/converters/officeparser/dist/OfficeGenerator.d.ts +23 -0
  136. package/vendor/converters/officeparser/dist/OfficeGenerator.js +73 -0
  137. package/vendor/converters/officeparser/dist/OfficeParser.d.ts +106 -0
  138. package/vendor/converters/officeparser/dist/OfficeParser.js +332 -0
  139. package/vendor/converters/officeparser/dist/cli.d.ts +28 -0
  140. package/vendor/converters/officeparser/dist/cli.js +381 -0
  141. package/vendor/converters/officeparser/dist/defaults.d.ts +41 -0
  142. package/vendor/converters/officeparser/dist/defaults.js +218 -0
  143. package/vendor/converters/officeparser/dist/generators/BaseGenerator.d.ts +107 -0
  144. package/vendor/converters/officeparser/dist/generators/BaseGenerator.js +248 -0
  145. package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.d.ts +82 -0
  146. package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.js +797 -0
  147. package/vendor/converters/officeparser/dist/generators/CsvGenerator.d.ts +38 -0
  148. package/vendor/converters/officeparser/dist/generators/CsvGenerator.js +245 -0
  149. package/vendor/converters/officeparser/dist/generators/EpubGenerator.d.ts +43 -0
  150. package/vendor/converters/officeparser/dist/generators/EpubGenerator.js +315 -0
  151. package/vendor/converters/officeparser/dist/generators/HtmlGenerator.d.ts +59 -0
  152. package/vendor/converters/officeparser/dist/generators/HtmlGenerator.js +1942 -0
  153. package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.d.ts +96 -0
  154. package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.js +1175 -0
  155. package/vendor/converters/officeparser/dist/generators/PdfGenerator.d.ts +22 -0
  156. package/vendor/converters/officeparser/dist/generators/PdfGenerator.js +194 -0
  157. package/vendor/converters/officeparser/dist/generators/RtfGenerator.d.ts +29 -0
  158. package/vendor/converters/officeparser/dist/generators/RtfGenerator.js +316 -0
  159. package/vendor/converters/officeparser/dist/generators/TextGenerator.d.ts +13 -0
  160. package/vendor/converters/officeparser/dist/generators/TextGenerator.js +201 -0
  161. package/vendor/converters/officeparser/dist/index.d.ts +60 -0
  162. package/vendor/converters/officeparser/dist/index.js +72 -0
  163. package/vendor/converters/officeparser/dist/index.mjs +18 -0
  164. package/vendor/converters/officeparser/dist/officeparser.browser.d.ts +2621 -0
  165. package/vendor/converters/officeparser/dist/officeparser.browser.iife.js +1336 -0
  166. package/vendor/converters/officeparser/dist/officeparser.browser.mjs +1335 -0
  167. package/vendor/converters/officeparser/dist/officeparser.browser.slim.d.ts +2621 -0
  168. package/vendor/converters/officeparser/dist/officeparser.browser.slim.iife.js +1336 -0
  169. package/vendor/converters/officeparser/dist/officeparser.browser.slim.mjs +1335 -0
  170. package/vendor/converters/officeparser/dist/parsers/CsvParser.d.ts +9 -0
  171. package/vendor/converters/officeparser/dist/parsers/CsvParser.js +115 -0
  172. package/vendor/converters/officeparser/dist/parsers/EpubParser.d.ts +8 -0
  173. package/vendor/converters/officeparser/dist/parsers/EpubParser.js +217 -0
  174. package/vendor/converters/officeparser/dist/parsers/ExcelParser.d.ts +32 -0
  175. package/vendor/converters/officeparser/dist/parsers/ExcelParser.js +736 -0
  176. package/vendor/converters/officeparser/dist/parsers/HtmlParser.d.ts +2 -0
  177. package/vendor/converters/officeparser/dist/parsers/HtmlParser.js +1287 -0
  178. package/vendor/converters/officeparser/dist/parsers/MarkdownParser.d.ts +2 -0
  179. package/vendor/converters/officeparser/dist/parsers/MarkdownParser.js +1272 -0
  180. package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.d.ts +31 -0
  181. package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.js +1819 -0
  182. package/vendor/converters/officeparser/dist/parsers/PdfParser.d.ts +67 -0
  183. package/vendor/converters/officeparser/dist/parsers/PdfParser.js +848 -0
  184. package/vendor/converters/officeparser/dist/parsers/PowerPointParser.d.ts +32 -0
  185. package/vendor/converters/officeparser/dist/parsers/PowerPointParser.js +950 -0
  186. package/vendor/converters/officeparser/dist/parsers/RtfParser.d.ts +187 -0
  187. package/vendor/converters/officeparser/dist/parsers/RtfParser.js +1801 -0
  188. package/vendor/converters/officeparser/dist/parsers/WordParser.d.ts +79 -0
  189. package/vendor/converters/officeparser/dist/parsers/WordParser.js +1177 -0
  190. package/vendor/converters/officeparser/dist/sbom.cdx.json +1763 -0
  191. package/vendor/converters/officeparser/dist/types.d.ts +2507 -0
  192. package/vendor/converters/officeparser/dist/types.js +107 -0
  193. package/vendor/converters/officeparser/dist/utils/astUtils.d.ts +16 -0
  194. package/vendor/converters/officeparser/dist/utils/astUtils.js +33 -0
  195. package/vendor/converters/officeparser/dist/utils/chartUtils.d.ts +6 -0
  196. package/vendor/converters/officeparser/dist/utils/chartUtils.js +257 -0
  197. package/vendor/converters/officeparser/dist/utils/configUtils.d.ts +44 -0
  198. package/vendor/converters/officeparser/dist/utils/configUtils.js +315 -0
  199. package/vendor/converters/officeparser/dist/utils/dateUtils.d.ts +17 -0
  200. package/vendor/converters/officeparser/dist/utils/dateUtils.js +69 -0
  201. package/vendor/converters/officeparser/dist/utils/envUtils.d.ts +29 -0
  202. package/vendor/converters/officeparser/dist/utils/envUtils.js +152 -0
  203. package/vendor/converters/officeparser/dist/utils/errorUtils.d.ts +72 -0
  204. package/vendor/converters/officeparser/dist/utils/errorUtils.js +245 -0
  205. package/vendor/converters/officeparser/dist/utils/imageUtils.d.ts +66 -0
  206. package/vendor/converters/officeparser/dist/utils/imageUtils.js +133 -0
  207. package/vendor/converters/officeparser/dist/utils/mathUtils.d.ts +42 -0
  208. package/vendor/converters/officeparser/dist/utils/mathUtils.js +385 -0
  209. package/vendor/converters/officeparser/dist/utils/moduleLoader.d.ts +18 -0
  210. package/vendor/converters/officeparser/dist/utils/moduleLoader.js +106 -0
  211. package/vendor/converters/officeparser/dist/utils/ocrUtils.d.ts +42 -0
  212. package/vendor/converters/officeparser/dist/utils/ocrUtils.js +428 -0
  213. package/vendor/converters/officeparser/dist/utils/sanitize.d.ts +148 -0
  214. package/vendor/converters/officeparser/dist/utils/sanitize.js +344 -0
  215. package/vendor/converters/officeparser/dist/utils/sheetUtils.d.ts +7 -0
  216. package/vendor/converters/officeparser/dist/utils/sheetUtils.js +35 -0
  217. package/vendor/converters/officeparser/dist/utils/styleMapper.d.ts +36 -0
  218. package/vendor/converters/officeparser/dist/utils/styleMapper.js +224 -0
  219. package/vendor/converters/officeparser/dist/utils/xmlUtils.d.ts +163 -0
  220. package/vendor/converters/officeparser/dist/utils/xmlUtils.js +461 -0
  221. package/vendor/converters/officeparser/dist/utils/zipUtils.d.ts +134 -0
  222. package/vendor/converters/officeparser/dist/utils/zipUtils.js +337 -0
  223. package/vendor/converters/officeparser/package.json +147 -0
  224. package/vendor/converters/upstream-manifest.json +124 -0
  225. package/vendor/dependency-fixes/README.md +77 -0
  226. package/vendor/dependency-fixes/vendor-converters.py +83 -0
  227. package/browser-extension/artifacts/gno-browser-clipper-v1.46.0.zip +0 -0
  228. package/browser-extension/artifacts/gno-browser-clipper-v1.46.0.zip.sha256 +0 -1
  229. package/browser-extension/dist/chunk-627emwpj.js +0 -75
  230. /package/browser-extension/dist/{chunk-ydfx5d7p.css → chunk-z74y8n8c.css} +0 -0
@@ -0,0 +1,451 @@
1
+ /** Exact-input storage. Synchronous methods throw and compose with DB transactions. */
2
+ import type { Database } from "bun:sqlite";
3
+
4
+ import type { VectorOwnerInput, VectorVariantIdentity } from "./types";
5
+
6
+ import { formatDocForEmbedding } from "../../pipeline/contextual";
7
+ import { decodeEmbedding, encodeEmbedding } from "./sqlite-vec";
8
+
9
+ export function embeddingInputHash(input: string): string {
10
+ return new Bun.CryptoHasher("sha256").update(input).digest("hex");
11
+ }
12
+
13
+ export function vectorVariantFingerprint(
14
+ identity: VectorVariantIdentity
15
+ ): string {
16
+ return embeddingInputHash(
17
+ JSON.stringify([
18
+ "embedding-input-v1",
19
+ identity.model,
20
+ identity.modelFingerprint,
21
+ identity.contextSize,
22
+ identity.truncationPolicy,
23
+ ])
24
+ );
25
+ }
26
+
27
+ interface OwnerRow {
28
+ documentId: number;
29
+ mirrorHash: string;
30
+ seq: number;
31
+ text: string;
32
+ title: string | null;
33
+ }
34
+
35
+ interface VariantRow {
36
+ variant_id: number;
37
+ input_hash: string;
38
+ embedding: Uint8Array;
39
+ }
40
+
41
+ export class VectorVariantStore {
42
+ readonly partitionId: string;
43
+ readonly tableName: string;
44
+ readonly fingerprint: string;
45
+ readonly identity: Readonly<VectorVariantIdentity>;
46
+
47
+ constructor(
48
+ private readonly db: Database,
49
+ identity: VectorVariantIdentity,
50
+ readonly searchAvailable: boolean
51
+ ) {
52
+ if (
53
+ !identity.model ||
54
+ !identity.modelFingerprint ||
55
+ !identity.truncationPolicy ||
56
+ !Number.isSafeInteger(identity.contextSize) ||
57
+ identity.contextSize < 1 ||
58
+ !Number.isSafeInteger(identity.dimensions) ||
59
+ identity.dimensions < 1
60
+ ) {
61
+ throw new Error("Invalid embedding variant identity");
62
+ }
63
+ this.identity = Object.freeze({ ...identity });
64
+ this.fingerprint = vectorVariantFingerprint(identity);
65
+ this.partitionId = embeddingInputHash(
66
+ JSON.stringify([identity.model, this.fingerprint, identity.dimensions])
67
+ );
68
+ this.tableName = `vec_v1_${this.partitionId}`;
69
+ db.transaction(() => {
70
+ db.run(
71
+ `INSERT OR IGNORE INTO vector_partitions
72
+ (partition_id, version, model, fingerprint, dimensions) VALUES (?, 1, ?, ?, ?)`,
73
+ [
74
+ this.partitionId,
75
+ identity.model,
76
+ this.fingerprint,
77
+ identity.dimensions,
78
+ ]
79
+ );
80
+ if (searchAvailable) {
81
+ if (
82
+ !db
83
+ .query("SELECT 1 FROM sqlite_master WHERE name = ?")
84
+ .get(this.tableName)
85
+ )
86
+ db.run(
87
+ "UPDATE vector_variant_epoch SET epoch = epoch + 1 WHERE id = 1"
88
+ );
89
+ db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS ${this.tableName} USING vec0(
90
+ variant_id INTEGER PRIMARY KEY,
91
+ embedding FLOAT[${identity.dimensions}] distance_metric=cosine
92
+ )`);
93
+ }
94
+ }).immediate();
95
+ }
96
+
97
+ epoch(): number {
98
+ return this.db
99
+ .query<{ epoch: number }, []>(
100
+ "SELECT epoch FROM vector_variant_epoch WHERE id = 1"
101
+ )
102
+ .get()!.epoch;
103
+ }
104
+
105
+ private input(row: OwnerRow): VectorOwnerInput {
106
+ const formattedInput = formatDocForEmbedding(
107
+ row.text,
108
+ row.title ?? undefined,
109
+ this.identity.model
110
+ );
111
+ return {
112
+ documentId: row.documentId,
113
+ mirrorHash: row.mirrorHash,
114
+ seq: row.seq,
115
+ formattedInput,
116
+ inputHash: embeddingInputHash(formattedInput),
117
+ };
118
+ }
119
+
120
+ /** Current active input; never accepts historical legacy origin as proof. */
121
+ current(documentId: number, seq: number): VectorOwnerInput | null {
122
+ const row = this.db
123
+ .query<OwnerRow, [number, number]>(`
124
+ SELECT d.id AS documentId, d.mirror_hash AS mirrorHash, c.seq, c.text, d.title
125
+ FROM documents d JOIN content_chunks c ON c.mirror_hash = d.mirror_hash
126
+ WHERE d.id = ? AND c.seq = ? AND d.active = 1
127
+ `)
128
+ .get(documentId, seq);
129
+ return row ? this.input(row) : null;
130
+ }
131
+
132
+ private variant(input: VectorOwnerInput): VariantRow | null {
133
+ return this.db
134
+ .query<VariantRow, [string, string]>(`
135
+ SELECT variant_id, input_hash, embedding FROM vector_variants
136
+ WHERE partition_id = ? AND input_hash = ?
137
+ `)
138
+ .get(this.partitionId, input.inputHash);
139
+ }
140
+
141
+ /** Returns a proven reusable vector, even before this owner has a binding. */
142
+ reusable(
143
+ input: VectorOwnerInput
144
+ ): { variantId: number; embedding: Float32Array } | null {
145
+ this.validate(input);
146
+ const row = this.variant(input);
147
+ return row
148
+ ? { variantId: row.variant_id, embedding: decodeEmbedding(row.embedding) }
149
+ : null;
150
+ }
151
+
152
+ private validate(input: VectorOwnerInput): void {
153
+ const current = this.current(input.documentId, input.seq);
154
+ if (
155
+ !current ||
156
+ current.mirrorHash !== input.mirrorHash ||
157
+ current.formattedInput !== input.formattedInput ||
158
+ current.inputHash !== input.inputHash
159
+ ) {
160
+ throw new Error("Stale or invalid vector owner input");
161
+ }
162
+ }
163
+
164
+ private assertWritable(): void {
165
+ if (
166
+ !this.searchAvailable &&
167
+ this.db
168
+ .query<{ name: string }, [string]>(
169
+ "SELECT name FROM sqlite_master WHERE name = ?"
170
+ )
171
+ .get(this.tableName)
172
+ ) {
173
+ throw new Error(
174
+ "Cannot mutate an existing variant index without sqlite-vec"
175
+ );
176
+ }
177
+ }
178
+
179
+ /** Atomic batch: validates current owners, stores variants, materializes vec0, binds owners. */
180
+ write(
181
+ rows: { owner: VectorOwnerInput; embedding?: Float32Array }[]
182
+ ): number[] {
183
+ this.assertWritable();
184
+ return this.db
185
+ .transaction(() =>
186
+ rows.map(({ owner, embedding }) => {
187
+ this.validate(owner);
188
+ if (
189
+ embedding &&
190
+ (embedding.length !== this.identity.dimensions ||
191
+ !embedding.every(Number.isFinite))
192
+ )
193
+ throw new Error("Invalid vector dimensions or values");
194
+ let row = this.variant(owner);
195
+ if (!row) {
196
+ if (!embedding)
197
+ throw new Error("Exact input variant needs embedding");
198
+ this.db.run(
199
+ `INSERT INTO vector_variants(partition_id, input_hash, embedding)
200
+ VALUES (?, ?, ?)`,
201
+ [this.partitionId, owner.inputHash, encodeEmbedding(embedding)]
202
+ );
203
+ row = this.variant(owner)!;
204
+ }
205
+ if (embedding && row) {
206
+ this.db.run(
207
+ "UPDATE vector_variants SET embedding = ? WHERE variant_id = ? AND partition_id = ?",
208
+ [encodeEmbedding(embedding), row.variant_id, this.partitionId]
209
+ );
210
+ row = this.variant(owner)!;
211
+ }
212
+ if (this.searchAvailable) {
213
+ this.db.run(`DELETE FROM ${this.tableName} WHERE variant_id = ?`, [
214
+ row.variant_id,
215
+ ]);
216
+ this.db.run(
217
+ `INSERT INTO ${this.tableName}(variant_id, embedding) VALUES (?, ?)`,
218
+ [row.variant_id, row.embedding]
219
+ );
220
+ }
221
+ this.db.run(
222
+ `INSERT INTO vector_owners(document_id, mirror_hash, seq, partition_id, variant_id)
223
+ VALUES (?, ?, ?, ?, ?) ON CONFLICT(document_id, seq, partition_id) DO UPDATE SET
224
+ mirror_hash = excluded.mirror_hash, variant_id = excluded.variant_id`,
225
+ [
226
+ owner.documentId,
227
+ owner.mirrorHash,
228
+ owner.seq,
229
+ this.partitionId,
230
+ row.variant_id,
231
+ ]
232
+ );
233
+ return row.variant_id;
234
+ })
235
+ )
236
+ .immediate();
237
+ }
238
+
239
+ /** Resolve only correct current active owners, never every document sharing a mirror. */
240
+ owners(variantId: number): VectorOwnerInput[] {
241
+ const variant = this.db
242
+ .query<VariantRow, [string, number]>(`
243
+ SELECT variant_id, input_hash, embedding FROM vector_variants
244
+ WHERE partition_id = ? AND variant_id = ?
245
+ `)
246
+ .get(this.partitionId, variantId);
247
+ if (!variant) return [];
248
+ const rows = this.db
249
+ .query<
250
+ { document_id: number; seq: number; mirror_hash: string },
251
+ [string, number]
252
+ >(`
253
+ SELECT document_id, seq, mirror_hash FROM vector_owners WHERE partition_id = ? AND variant_id = ?
254
+ `)
255
+ .all(this.partitionId, variantId);
256
+ return rows.flatMap((row) => {
257
+ const current = this.current(row.document_id, row.seq);
258
+ return current &&
259
+ current.mirrorHash === row.mirror_hash &&
260
+ current.inputHash === variant.input_hash
261
+ ? [current]
262
+ : [];
263
+ });
264
+ }
265
+
266
+ /** Resumable owner cursor; repeat from start after epoch changes or a completed pass. */
267
+ pending(
268
+ options: {
269
+ limit?: number;
270
+ after?: { documentId: number; seq: number };
271
+ } = {}
272
+ ): VectorOwnerInput[] {
273
+ const limit = options.limit ?? 1000;
274
+ if (!Number.isSafeInteger(limit) || limit < 1)
275
+ throw new Error("Invalid pending limit");
276
+ const statement = this.db.prepare<OwnerRow, [number, number, number]>(`
277
+ SELECT d.id AS documentId, d.mirror_hash AS mirrorHash, c.seq, c.text, d.title
278
+ FROM documents d JOIN content_chunks c ON c.mirror_hash = d.mirror_hash
279
+ WHERE d.active = 1 AND (d.id > ? OR (d.id = ? AND c.seq > ?)) ORDER BY d.id, c.seq
280
+ `);
281
+ const rows = statement.iterate(
282
+ options.after?.documentId ?? -1,
283
+ options.after?.documentId ?? -1,
284
+ options.after?.seq ?? -1
285
+ );
286
+ const pending: VectorOwnerInput[] = [];
287
+ try {
288
+ for (const row of rows) {
289
+ const input = this.input(row);
290
+ const binding = this.db
291
+ .query<
292
+ { input_hash: string; mirror_hash: string },
293
+ [number, number, string]
294
+ >(`
295
+ SELECT v.input_hash, o.mirror_hash FROM vector_owners o
296
+ JOIN vector_variants v ON v.variant_id = o.variant_id AND v.partition_id = o.partition_id
297
+ WHERE o.document_id = ? AND o.seq = ? AND o.partition_id = ?
298
+ `)
299
+ .get(input.documentId, input.seq, this.partitionId);
300
+ if (
301
+ binding?.input_hash !== input.inputHash ||
302
+ binding.mirror_hash !== input.mirrorHash
303
+ )
304
+ pending.push(input);
305
+ if (pending.length === limit) break;
306
+ }
307
+ } finally {
308
+ // Early cursor exit must not leave a cached Bun statement mid-iteration.
309
+ statement.finalize();
310
+ }
311
+ return pending;
312
+ }
313
+
314
+ /** Activation is fenced by a caller-observed epoch and complete current coverage. */
315
+ activate(expectedEpoch: number): void {
316
+ this.db
317
+ .transaction(() => {
318
+ if (this.epoch() !== expectedEpoch)
319
+ throw new Error("Variant mutation epoch changed");
320
+ if (this.pending({ limit: 1 }).length)
321
+ throw new Error("Variant coverage incomplete");
322
+ if (!this.searchAvailable) throw new Error("Variant index unavailable");
323
+ const mismatch = this.db
324
+ .query<{ count: number }, [string]>(`
325
+ SELECT count(*) AS count FROM vector_variants v LEFT JOIN ${this.tableName} x
326
+ ON x.variant_id = v.variant_id WHERE v.partition_id = ?
327
+ AND (x.variant_id IS NULL OR x.embedding != v.embedding)
328
+ `)
329
+ .get(this.partitionId)!.count;
330
+ const orphans = this.db
331
+ .query<{ count: number }, [string]>(`
332
+ SELECT count(*) AS count FROM ${this.tableName} x WHERE NOT EXISTS
333
+ (SELECT 1 FROM vector_variants v WHERE v.variant_id = x.variant_id AND v.partition_id = ?)
334
+ `)
335
+ .get(this.partitionId)!.count;
336
+ if (mismatch || orphans) throw new Error("Variant index inconsistent");
337
+ this.db.run(
338
+ `UPDATE vector_partitions SET state = 'active', activated_epoch = ? WHERE partition_id = ?`,
339
+ [expectedEpoch, this.partitionId]
340
+ );
341
+ })
342
+ .immediate();
343
+ }
344
+
345
+ /**
346
+ * Durable authority after initial promotion, independent of later mutations.
347
+ * Retrieval uses this marker plus current owner validation; an incomplete
348
+ * epoch must never force valid owners back to legacy vectors. Availability
349
+ * remains a separate capability, not permission to change authority.
350
+ */
351
+ hasActivated(): boolean {
352
+ return !!this.db
353
+ .query<{ active: number }, [string]>(`
354
+ SELECT 1 AS active FROM vector_partitions WHERE partition_id = ?
355
+ AND state = 'active' AND activated_epoch IS NOT NULL
356
+ `)
357
+ .get(this.partitionId);
358
+ }
359
+
360
+ /** Current-epoch completeness receipt only; never a blanket retrieval gate. */
361
+ isActive(): boolean {
362
+ if (!this.searchAvailable) return false;
363
+ return !!this.db
364
+ .query<{ active: number }, [string]>(`
365
+ SELECT 1 AS active FROM vector_partitions WHERE partition_id = ?
366
+ AND state = 'active' AND activated_epoch = (SELECT epoch FROM vector_variant_epoch WHERE id = 1)
367
+ `)
368
+ .get(this.partitionId);
369
+ }
370
+
371
+ /** Repair materialization after storage-only backfill; never changes legacy authority. */
372
+ syncIndex(): void {
373
+ if (!this.searchAvailable) throw new Error("Variant index unavailable");
374
+ this.db
375
+ .transaction(() => {
376
+ this.db.exec(`DELETE FROM ${this.tableName}`);
377
+ const rows = this.db
378
+ .query<VariantRow, [string]>(
379
+ "SELECT variant_id, input_hash, embedding FROM vector_variants WHERE partition_id = ?"
380
+ )
381
+ .iterate(this.partitionId);
382
+ for (const row of rows) {
383
+ this.db.run(
384
+ `INSERT INTO ${this.tableName}(variant_id, embedding) VALUES (?, ?)`,
385
+ [row.variant_id, row.embedding]
386
+ );
387
+ }
388
+ })
389
+ .immediate();
390
+ }
391
+
392
+ /** Explicit owner release and last-valid-owner GC share the vec0 transaction. */
393
+ release(documentId: number): void {
394
+ this.assertWritable();
395
+ this.db
396
+ .transaction(() => {
397
+ this.db.run(
398
+ "DELETE FROM vector_owners WHERE document_id = ? AND partition_id = ?",
399
+ [documentId, this.partitionId]
400
+ );
401
+ this.collectGarbage();
402
+ })
403
+ .immediate();
404
+ }
405
+
406
+ collectGarbage(): number {
407
+ this.assertWritable();
408
+ return this.db
409
+ .transaction(() => {
410
+ let removed = 0;
411
+ const rows = this.db
412
+ .query<{ variant_id: number }, [string]>(
413
+ "SELECT variant_id FROM vector_variants WHERE partition_id = ?"
414
+ )
415
+ .all(this.partitionId);
416
+ for (const { variant_id: id } of rows) {
417
+ if (this.owners(id).length) continue;
418
+ if (this.searchAvailable)
419
+ this.db.run(`DELETE FROM ${this.tableName} WHERE variant_id = ?`, [
420
+ id,
421
+ ]);
422
+ this.db.run(
423
+ "DELETE FROM vector_owners WHERE partition_id = ? AND variant_id = ?",
424
+ [this.partitionId, id]
425
+ );
426
+ this.db.run(
427
+ "DELETE FROM vector_variants WHERE partition_id = ? AND variant_id = ?",
428
+ [this.partitionId, id]
429
+ );
430
+ removed++;
431
+ }
432
+ return removed;
433
+ })
434
+ .immediate();
435
+ }
436
+ }
437
+
438
+ export async function createVectorVariantStore(
439
+ db: Database,
440
+ identity: VectorVariantIdentity
441
+ ): Promise<VectorVariantStore> {
442
+ let searchAvailable = false;
443
+ try {
444
+ const sqliteVec = await import("sqlite-vec");
445
+ sqliteVec.load(db);
446
+ searchAvailable = true;
447
+ } catch {
448
+ // Storage remains usable offline; activation waits for a materialized index.
449
+ }
450
+ return new VectorVariantStore(db, identity, searchAvailable);
451
+ }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Vaibhav Raj
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.