@gmickel/gno 2.10.1 → 2.11.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 (72) hide show
  1. package/README.md +11 -11
  2. package/assets/skill/SKILL.md +25 -14
  3. package/assets/skill/cli-reference.md +6 -5
  4. package/assets/skill/examples.md +16 -12
  5. package/assets/skill/mcp-reference.md +2 -1
  6. package/browser-extension/artifacts/{gno-browser-clipper-v2.10.1.zip → gno-browser-clipper-v2.11.0.zip} +0 -0
  7. package/browser-extension/artifacts/gno-browser-clipper-v2.11.0.zip.sha256 +1 -0
  8. package/browser-extension/dist/manifest.json +1 -1
  9. package/package.json +3 -1
  10. package/spec/cli.md +27 -1
  11. package/spec/mcp.md +27 -3
  12. package/spec/output-schemas/ask.schema.json +8 -0
  13. package/spec/output-schemas/claim-verification.schema.json +8 -2
  14. package/spec/output-schemas/context-capsule-v1.schema.json +6 -1
  15. package/spec/output-schemas/context-capsule-verification.schema.json +4 -2
  16. package/spec/output-schemas/get.schema.json +4 -0
  17. package/spec/output-schemas/metadata-summary.schema.json +60 -0
  18. package/spec/output-schemas/multi-get.schema.json +4 -0
  19. package/spec/output-schemas/search-result.schema.json +4 -0
  20. package/spec/output-schemas/search-results.schema.json +4 -0
  21. package/src/app/constants.ts +16 -0
  22. package/src/app/context-format.ts +1 -0
  23. package/src/app/verified-ask.ts +2 -0
  24. package/src/cli/commands/agents/block.ts +9 -8
  25. package/src/cli/commands/ask-format.ts +18 -1
  26. package/src/cli/commands/ask.ts +4 -5
  27. package/src/cli/commands/daemon.ts +5 -1
  28. package/src/cli/commands/get.ts +11 -1
  29. package/src/cli/commands/metadata.ts +79 -0
  30. package/src/cli/commands/multi-get.ts +13 -0
  31. package/src/cli/commands/shared.ts +9 -7
  32. package/src/cli/format/search-results.ts +11 -0
  33. package/src/cli/options.ts +2 -0
  34. package/src/cli/program.ts +45 -0
  35. package/src/core/context-capsule-schema.ts +2 -0
  36. package/src/core/context-capsule-verification.ts +2 -0
  37. package/src/core/context-compiler.ts +2 -5
  38. package/src/core/context-evidence.ts +1 -0
  39. package/src/core/context-verifier.ts +14 -1
  40. package/src/core/metadata-discovery.ts +169 -0
  41. package/src/ingestion/sync.ts +49 -1
  42. package/src/ingestion/walker.ts +11 -3
  43. package/src/mcp/http-egress.ts +1 -0
  44. package/src/mcp/tool-descriptions-core.ts +2 -2
  45. package/src/mcp/tool-profile.ts +16 -1
  46. package/src/mcp/tools/get.ts +10 -0
  47. package/src/mcp/tools/index.ts +39 -4
  48. package/src/mcp/tools/multi-get.ts +15 -0
  49. package/src/mcp/tools/query.ts +5 -3
  50. package/src/mcp/tools/search.ts +5 -3
  51. package/src/mcp/tools/vsearch.ts +5 -3
  52. package/src/mcp/tools/workspace-write.ts +42 -34
  53. package/src/pipeline/answer-prompt.ts +15 -2
  54. package/src/pipeline/answer.ts +3 -0
  55. package/src/pipeline/claim-verification-schema.ts +6 -1
  56. package/src/pipeline/claim-verification.ts +54 -1
  57. package/src/pipeline/explain.ts +1 -1
  58. package/src/pipeline/fusion.ts +24 -4
  59. package/src/pipeline/hybrid.ts +5 -1
  60. package/src/pipeline/result-context.ts +29 -0
  61. package/src/pipeline/search.ts +5 -1
  62. package/src/pipeline/types.ts +4 -0
  63. package/src/pipeline/vsearch.ts +5 -1
  64. package/src/sdk/client.ts +32 -4
  65. package/src/sdk/documents.ts +5 -0
  66. package/src/sdk/types.ts +5 -0
  67. package/src/serve/routes/api.ts +29 -1
  68. package/src/serve/server.ts +12 -0
  69. package/src/store/sqlite/adapter.ts +82 -2
  70. package/src/store/types.ts +16 -0
  71. package/src/store/vector/variant-search.ts +112 -74
  72. package/browser-extension/artifacts/gno-browser-clipper-v2.10.1.zip.sha256 +0 -1
@@ -82,6 +82,32 @@ export function vectorSearchUnavailableMessage(
82
82
  }`;
83
83
  }
84
84
 
85
+ const MAX_CACHED_PROOFS = 100_000;
86
+ const proofCaches = new WeakMap<
87
+ Database,
88
+ { marker: string; proofs: Map<string, boolean> }
89
+ >();
90
+
91
+ /**
92
+ * Input-hash proofs cached per connection. Any committed write, from this
93
+ * connection or another, changes the marker and drops the cache, so a cached
94
+ * proof never outlives the titles and chunk text it was computed from.
95
+ */
96
+ function provenOwners(db: Database): Map<string, boolean> {
97
+ const { version, changes } = db
98
+ .query<{ version: number; changes: number }, []>(
99
+ "SELECT (SELECT data_version FROM pragma_data_version) AS version, total_changes() AS changes"
100
+ )
101
+ .get()!;
102
+ const marker = `${version}:${changes}`;
103
+ let cache = proofCaches.get(db);
104
+ if (cache?.marker !== marker || cache.proofs.size > MAX_CACHED_PROOFS) {
105
+ cache = { marker, proofs: new Map() };
106
+ proofCaches.set(db, cache);
107
+ }
108
+ return cache.proofs;
109
+ }
110
+
85
111
  /** Null means legacy authority. Once promoted, unavailable provenance fails closed. */
86
112
  function searchVectorVariantsInSnapshot(
87
113
  db: Database,
@@ -148,14 +174,9 @@ function searchVectorVariantsInSnapshot(
148
174
  const tableName = `vec_v1_${partitionId}`;
149
175
  if (!db.query("SELECT 1 FROM sqlite_master WHERE name = ?").get(tableName))
150
176
  throw new Error("Activated variant index unavailable; run gno embed");
151
- if (
152
- db
153
- .query(
154
- `SELECT 1 FROM vector_variants v LEFT JOIN ${tableName} x ON x.variant_id = v.variant_id WHERE v.partition_id = ? AND (x.variant_id IS NULL OR x.embedding != v.embedding) LIMIT 1`
155
- )
156
- .get(partitionId)
157
- )
158
- throw new Error("Activated variant index inconsistent; run gno embed");
177
+ // The vec0 mirror is written in the same transactions as vector_variants and
178
+ // checked at activation and by `gno vec sync`; ranking below reads only
179
+ // vector_variants, so the mirror is never trusted here.
159
180
  const eligible = buildEligibleDocumentQuery(
160
181
  { ...options.eligibility, semanticMetadata: true },
161
182
  db
@@ -170,75 +191,92 @@ function searchVectorVariantsInSnapshot(
170
191
  conditions.push("o.mirror_hash IN (SELECT value FROM json_each(?))");
171
192
  params.push(JSON.stringify(options.allowedMirrorHashes));
172
193
  }
173
- const bindings = db
174
- .query<
175
- {
176
- document_id: number;
177
- seq: number;
178
- text: string;
179
- title: string | null;
180
- input_hash: string;
181
- },
182
- (string | number)[]
183
- >(`
184
- SELECT o.document_id, o.seq, c.text, d.title, v.input_hash
185
- FROM vector_owners o
186
- JOIN (${eligible.sql}) e ON e.id = o.document_id AND e.mirror_hash = o.mirror_hash
187
- JOIN documents d ON d.id = o.document_id
188
- JOIN content_chunks c ON c.mirror_hash = o.mirror_hash AND c.seq = o.seq
189
- JOIN vector_variants v ON v.variant_id = o.variant_id AND v.partition_id = o.partition_id
190
- WHERE ${conditions.join(" AND ")}
191
- `)
192
- .all(...eligible.params, ...params);
193
- const proven = bindings
194
- .filter(
195
- (row) =>
196
- row.input_hash ===
197
- embeddingInputHash(
198
- formatDocForEmbedding(row.text, row.title ?? undefined, model)
199
- )
200
- )
201
- .map((row) => `${row.document_id}:${row.seq}`);
202
- // Both owner eligibility and formatted-input validation precede ranking/LIMIT.
203
- const rows = db
204
- .query<
205
- {
206
- mirrorHash: string;
207
- seq: number;
208
- distance: number;
209
- documentIds: string;
210
- },
211
- (Uint8Array | string | number)[]
212
- >(`
213
- SELECT o.mirror_hash AS mirrorHash, o.seq, vec_distance_cosine(v.embedding, ?) AS distance,
214
- json_group_array(o.document_id) AS documentIds
215
- FROM vector_owners o
216
- JOIN (${eligible.sql}) e ON e.id = o.document_id AND e.mirror_hash = o.mirror_hash
217
- JOIN documents d ON d.id = o.document_id
218
- JOIN content_chunks c ON c.mirror_hash = o.mirror_hash AND c.seq = o.seq
219
- JOIN vector_variants v ON v.variant_id = o.variant_id AND v.partition_id = o.partition_id
220
- JOIN ${tableName} x ON x.variant_id = v.variant_id AND x.embedding = v.embedding
221
- WHERE ${conditions.join(" AND ")}
222
- AND (o.document_id || ':' || o.seq) IN (SELECT value FROM json_each(?))
223
- GROUP BY v.variant_id, o.mirror_hash, o.seq
224
- ORDER BY distance, o.mirror_hash, o.seq, v.variant_id LIMIT ?
225
- `)
226
- .all(
227
- encodeEmbedding(embedding),
194
+ // Owner eligibility precedes ranking/LIMIT. Grouping before the distance
195
+ // keeps embedding blobs out of the group sorter.
196
+ const ranked = db.query<
197
+ {
198
+ mirrorHash: string;
199
+ seq: number;
200
+ inputHash: string;
201
+ distance: number;
202
+ documentIds: string;
203
+ },
204
+ (Uint8Array | string | number)[]
205
+ >(`
206
+ SELECT g.mirrorHash, g.seq, v.input_hash AS inputHash,
207
+ vec_distance_cosine(v.embedding, ?) AS distance, g.documentIds
208
+ FROM (
209
+ SELECT o.variant_id AS variantId, o.mirror_hash AS mirrorHash, o.seq,
210
+ json_group_array(o.document_id) AS documentIds
211
+ FROM vector_owners o
212
+ JOIN (${eligible.sql}) e ON e.id = o.document_id AND e.mirror_hash = o.mirror_hash
213
+ JOIN content_chunks c ON c.mirror_hash = o.mirror_hash AND c.seq = o.seq
214
+ WHERE ${conditions.join(" AND ")}
215
+ GROUP BY o.variant_id, o.mirror_hash, o.seq
216
+ ) g
217
+ JOIN vector_variants v ON v.variant_id = g.variantId AND v.partition_id = ?
218
+ ORDER BY distance, g.mirrorHash, g.seq, g.variantId LIMIT ?
219
+ `);
220
+ const owners = db.query<
221
+ { id: number; title: string | null; text: string },
222
+ [string, number, string]
223
+ >(`
224
+ SELECT d.id, d.title, c.text FROM documents d
225
+ JOIN content_chunks c ON c.mirror_hash = ? AND c.seq = ?
226
+ WHERE d.id IN (SELECT value FROM json_each(?))
227
+ `);
228
+ // A vector counts only while it matches its owner's current formatted
229
+ // input. Proving candidates in rank order and skipping unproven owners
230
+ // returns exactly what filtering every owner before ranking would.
231
+ const proofs = provenOwners(db);
232
+ const queryVector = encodeEmbedding(embedding);
233
+ let limit = k;
234
+ let results: VectorSearchResult[] = [];
235
+ for (;;) {
236
+ const rows = ranked.all(
237
+ queryVector,
228
238
  ...eligible.params,
229
239
  ...params,
230
- JSON.stringify(proven),
231
- k
240
+ partitionId,
241
+ limit
232
242
  );
233
- return rows
234
- .filter(
235
- (row) =>
236
- options.minScore === undefined || 1 - row.distance >= options.minScore
237
- )
238
- .map((row) => ({
239
- ...row,
240
- documentIds: JSON.parse(row.documentIds) as number[],
241
- }));
243
+ results = [];
244
+ for (const row of rows) {
245
+ const documentIds = JSON.parse(row.documentIds) as number[];
246
+ const key = (id: number) =>
247
+ `${model}\0${id}\0${row.mirrorHash}\0${row.seq}\0${row.inputHash}`;
248
+ const unknown = documentIds.filter((id) => !proofs.has(key(id)));
249
+ if (unknown.length > 0) {
250
+ for (const owner of owners.all(
251
+ row.mirrorHash,
252
+ row.seq,
253
+ JSON.stringify(unknown)
254
+ )) {
255
+ proofs.set(
256
+ key(owner.id),
257
+ embeddingInputHash(
258
+ formatDocForEmbedding(owner.text, owner.title ?? undefined, model)
259
+ ) === row.inputHash
260
+ );
261
+ }
262
+ }
263
+ const proven = documentIds.filter((id) => proofs.get(key(id)));
264
+ if (proven.length === 0) continue;
265
+ results.push({
266
+ mirrorHash: row.mirrorHash,
267
+ seq: row.seq,
268
+ distance: row.distance,
269
+ documentIds: proven,
270
+ });
271
+ if (results.length === k) break;
272
+ }
273
+ if (results.length === k || rows.length < limit) break;
274
+ limit *= 4;
275
+ }
276
+ return results.filter(
277
+ (row) =>
278
+ options.minScore === undefined || 1 - row.distance >= options.minScore
279
+ );
242
280
  }
243
281
 
244
282
  /** Keep eligibility, provenance validation and ranking on one SQLite snapshot. */
@@ -1 +0,0 @@
1
- e42897b28d0fc0fca7d1d7aca6ab016255cc3b5bf20d38bcac51535499eb7f25 gno-browser-clipper-v2.10.1.zip