@gmickel/gno 1.17.0 → 1.19.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 (167) hide show
  1. package/README.md +18 -4
  2. package/assets/skill/SKILL.md +44 -1
  3. package/assets/skill/mcp-reference.md +21 -0
  4. package/package.json +3 -2
  5. package/spec/AGENTS.md +83 -0
  6. package/spec/CLAUDE.md +83 -0
  7. package/spec/bench-fixture.schema.json +137 -0
  8. package/spec/cli.md +2894 -0
  9. package/spec/db/schema.sql +442 -0
  10. package/spec/evals-agentic.md +510 -0
  11. package/spec/evals.md +1106 -0
  12. package/spec/mcp.md +2229 -0
  13. package/spec/output-schemas/activation-verification.schema.json +515 -0
  14. package/spec/output-schemas/ask.schema.json +366 -0
  15. package/spec/output-schemas/backlinks.schema.json +131 -0
  16. package/spec/output-schemas/bench-result.schema.json +120 -0
  17. package/spec/output-schemas/capture-receipt.schema.json +143 -0
  18. package/spec/output-schemas/collection-list.schema.json +45 -0
  19. package/spec/output-schemas/context-capsule-v1.schema.json +691 -0
  20. package/spec/output-schemas/context-capsule-verification.schema.json +1338 -0
  21. package/spec/output-schemas/context-list.schema.json +21 -0
  22. package/spec/output-schemas/doctor.schema.json +313 -0
  23. package/spec/output-schemas/error.schema.json +30 -0
  24. package/spec/output-schemas/expansion.schema.json +37 -0
  25. package/spec/output-schemas/get.schema.json +140 -0
  26. package/spec/output-schemas/graph-query.schema.json +99 -0
  27. package/spec/output-schemas/graph.schema.json +371 -0
  28. package/spec/output-schemas/links-list.schema.json +186 -0
  29. package/spec/output-schemas/mcp-add-collection-result.schema.json +23 -0
  30. package/spec/output-schemas/mcp-capture-result.schema.json +152 -0
  31. package/spec/output-schemas/mcp-http-error.schema.json +30 -0
  32. package/spec/output-schemas/mcp-job-list.schema.json +58 -0
  33. package/spec/output-schemas/mcp-job-status.schema.json +224 -0
  34. package/spec/output-schemas/mcp-remove-result.schema.json +39 -0
  35. package/spec/output-schemas/mcp-sync-result.schema.json +41 -0
  36. package/spec/output-schemas/mcp-tag-result.schema.json +33 -0
  37. package/spec/output-schemas/models-list.schema.json +93 -0
  38. package/spec/output-schemas/multi-get.schema.json +103 -0
  39. package/spec/output-schemas/process-status.schema.json +119 -0
  40. package/spec/output-schemas/query-diagnose.schema.json +123 -0
  41. package/spec/output-schemas/resident-status.schema.json +154 -0
  42. package/spec/output-schemas/retrieval-trace-common.schema.json +492 -0
  43. package/spec/output-schemas/retrieval-trace-delete.schema.json +16 -0
  44. package/spec/output-schemas/retrieval-trace-export.schema.json +61 -0
  45. package/spec/output-schemas/retrieval-trace-filters.schema.json +139 -0
  46. package/spec/output-schemas/retrieval-trace-judgment.schema.json +15 -0
  47. package/spec/output-schemas/retrieval-trace-list.schema.json +18 -0
  48. package/spec/output-schemas/retrieval-trace-payloads.schema.json +178 -0
  49. package/spec/output-schemas/retrieval-trace-purge.schema.json +31 -0
  50. package/spec/output-schemas/retrieval-trace-qrels.schema.json +303 -0
  51. package/spec/output-schemas/retrieval-trace-replay.schema.json +286 -0
  52. package/spec/output-schemas/retrieval-trace-show.schema.json +69 -0
  53. package/spec/output-schemas/retrieval-trace-summary.schema.json +65 -0
  54. package/spec/output-schemas/search-result.schema.json +154 -0
  55. package/spec/output-schemas/search-results.schema.json +338 -0
  56. package/spec/output-schemas/similar.schema.json +84 -0
  57. package/spec/output-schemas/status.schema.json +676 -0
  58. package/spec/output-schemas/tags-list.schema.json +48 -0
  59. package/src/app/context-runtime-types.ts +3 -0
  60. package/src/app/context-runtime.ts +15 -1
  61. package/src/cli/commands/ask.ts +106 -36
  62. package/src/cli/commands/context-build.ts +56 -9
  63. package/src/cli/commands/daemon.ts +69 -2
  64. package/src/cli/commands/get.ts +64 -3
  65. package/src/cli/commands/models/pull.ts +13 -3
  66. package/src/cli/commands/query.ts +62 -23
  67. package/src/cli/commands/replay.ts +140 -0
  68. package/src/cli/commands/search.ts +48 -3
  69. package/src/cli/commands/shared.ts +3 -1
  70. package/src/cli/commands/status.ts +2 -0
  71. package/src/cli/commands/trace.ts +200 -0
  72. package/src/cli/commands/vsearch.ts +75 -53
  73. package/src/cli/detach.ts +37 -20
  74. package/src/cli/program.ts +329 -27
  75. package/src/config/index.ts +12 -0
  76. package/src/config/retrieval-traces.ts +56 -0
  77. package/src/config/types.ts +41 -0
  78. package/src/core/context-compiler.ts +11 -4
  79. package/src/core/job-manager.ts +19 -0
  80. package/src/core/mutation-generations.ts +33 -0
  81. package/src/core/retrieval-qrels.ts +405 -0
  82. package/src/core/retrieval-replay-candidate.ts +368 -0
  83. package/src/core/retrieval-replay-types.ts +109 -0
  84. package/src/core/retrieval-replay-validation.ts +89 -0
  85. package/src/core/retrieval-replay.ts +441 -0
  86. package/src/core/retrieval-trace-evidence-origin.ts +175 -0
  87. package/src/core/retrieval-trace-export.ts +113 -0
  88. package/src/core/retrieval-trace-filter-normalization.ts +27 -0
  89. package/src/core/retrieval-trace-filters.ts +19 -0
  90. package/src/core/retrieval-trace-management-helpers.ts +247 -0
  91. package/src/core/retrieval-trace-management-types.ts +132 -0
  92. package/src/core/retrieval-trace-management.ts +422 -0
  93. package/src/core/retrieval-trace-request.ts +141 -0
  94. package/src/core/retrieval-trace-session.ts +494 -0
  95. package/src/core/retrieval-trace.ts +472 -0
  96. package/src/llm/cache.ts +13 -3
  97. package/src/llm/nodeLlamaCpp/adapter.ts +10 -1
  98. package/src/llm/nodeLlamaCpp/lifecycle.ts +71 -0
  99. package/src/mcp/context.ts +161 -0
  100. package/src/mcp/http-security.ts +477 -0
  101. package/src/mcp/http-session.ts +272 -0
  102. package/src/mcp/http-transport.ts +370 -0
  103. package/src/mcp/resources/index.ts +141 -134
  104. package/src/mcp/server.ts +19 -79
  105. package/src/mcp/tools/add-collection.ts +3 -1
  106. package/src/mcp/tools/capture.ts +3 -0
  107. package/src/mcp/tools/clear-collection-embeddings.ts +2 -0
  108. package/src/mcp/tools/context.ts +68 -16
  109. package/src/mcp/tools/embed.ts +62 -52
  110. package/src/mcp/tools/get.ts +35 -1
  111. package/src/mcp/tools/index-cmd.ts +88 -74
  112. package/src/mcp/tools/index.ts +96 -2
  113. package/src/mcp/tools/query.ts +95 -64
  114. package/src/mcp/tools/remove-collection.ts +2 -0
  115. package/src/mcp/tools/search.ts +36 -13
  116. package/src/mcp/tools/status.ts +11 -0
  117. package/src/mcp/tools/sync.ts +16 -14
  118. package/src/mcp/tools/trace.ts +143 -0
  119. package/src/mcp/tools/vsearch.ts +71 -38
  120. package/src/mcp/tools/workspace-write.ts +7 -3
  121. package/src/pipeline/answer.ts +167 -26
  122. package/src/pipeline/graph-retrieval.ts +15 -1
  123. package/src/pipeline/hybrid.ts +151 -43
  124. package/src/pipeline/search.ts +36 -3
  125. package/src/pipeline/trace-metadata.ts +47 -0
  126. package/src/pipeline/types.ts +43 -0
  127. package/src/pipeline/vsearch.ts +101 -38
  128. package/src/sdk/client.ts +380 -71
  129. package/src/sdk/documents.ts +48 -1
  130. package/src/sdk/index.ts +17 -0
  131. package/src/sdk/types.ts +28 -0
  132. package/src/serve/background-runtime.ts +12 -212
  133. package/src/serve/context-capsule.ts +67 -8
  134. package/src/serve/embed-scheduler.ts +74 -43
  135. package/src/serve/index.ts +9 -0
  136. package/src/serve/jobs.ts +78 -80
  137. package/src/serve/public/app.tsx +12 -1
  138. package/src/serve/public/components/HealthCenter.tsx +74 -1
  139. package/src/serve/public/globals.built.css +1 -1
  140. package/src/serve/public/lib/workspace-tabs.ts +2 -0
  141. package/src/serve/public/pages/Dashboard.tsx +11 -0
  142. package/src/serve/public/pages/TraceHistory.tsx +478 -0
  143. package/src/serve/public/pages/trace-history-detail.tsx +224 -0
  144. package/src/serve/resident-admission.ts +159 -0
  145. package/src/serve/resident-background-work.ts +39 -0
  146. package/src/serve/resident-request.ts +55 -0
  147. package/src/serve/resident-runtime.ts +490 -0
  148. package/src/serve/resident-status.ts +96 -0
  149. package/src/serve/retrieval-trace.ts +28 -0
  150. package/src/serve/routes/api.ts +629 -239
  151. package/src/serve/routes/mcp.ts +69 -0
  152. package/src/serve/routes/traces.ts +156 -0
  153. package/src/serve/server.ts +276 -37
  154. package/src/serve/status-model.ts +51 -0
  155. package/src/serve/status.ts +5 -0
  156. package/src/store/index.ts +31 -0
  157. package/src/store/migrations/014-retrieval-traces.ts +303 -0
  158. package/src/store/migrations/index.ts +2 -0
  159. package/src/store/retrieval-trace-codec.ts +384 -0
  160. package/src/store/sqlite/adapter.ts +179 -10
  161. package/src/store/sqlite/retrieval-trace-management-store.ts +341 -0
  162. package/src/store/sqlite/retrieval-trace-retention.ts +349 -0
  163. package/src/store/sqlite/retrieval-trace-rows.ts +267 -0
  164. package/src/store/sqlite/retrieval-trace-store.ts +515 -0
  165. package/src/store/types.ts +297 -0
  166. package/src/store/vector/sqlite-vec.ts +76 -1
  167. package/src/store/vector/types.ts +1 -1
@@ -0,0 +1,422 @@
1
+ /** Shared local management service for private retrieval trace receipts. */
2
+
3
+ import type {
4
+ RetrievalTraceBundle,
5
+ RetrievalTraceExportFormat,
6
+ StorePort,
7
+ StoreResult,
8
+ } from "../store/types";
9
+ import type {
10
+ ReplayRetrievalTraceInput,
11
+ ReplayRetrievalTraceResult,
12
+ } from "./retrieval-replay-types";
13
+ import type {
14
+ DeleteRetrievalTraceResult,
15
+ ExportRetrievalTracesInput,
16
+ ExportRetrievalTracesResult,
17
+ LabelRetrievalTraceInput,
18
+ LabelRetrievalTraceResult,
19
+ PurgeRetrievalTracesResult,
20
+ RetrievalTraceDetail,
21
+ RetrievalTraceDetailOptions,
22
+ RetrievalTraceListOptions,
23
+ RetrievalTraceListResult,
24
+ RetrievalTraceSummary,
25
+ } from "./retrieval-trace-management-types";
26
+
27
+ import {
28
+ canonicalTraceJson,
29
+ hashTraceCanonical,
30
+ } from "../store/retrieval-trace-codec";
31
+ import { err, ok } from "../store/types";
32
+ import {
33
+ replayRetrievalTraces,
34
+ type RetrievalReplayDeps,
35
+ } from "./retrieval-replay";
36
+ import { exportRetrievalTraces } from "./retrieval-trace-export";
37
+ import {
38
+ allEvidence,
39
+ applyTargetKind,
40
+ compactTarget,
41
+ decodeCursor,
42
+ encodeCursor,
43
+ type EvidenceTarget,
44
+ latestForTarget,
45
+ refMatches,
46
+ summaryOf,
47
+ targetKey,
48
+ } from "./retrieval-trace-management-helpers";
49
+
50
+ const DEFAULT_LIST_LIMIT = 50;
51
+ const MAX_LIST_LIMIT = 1000;
52
+ const DEFAULT_DETAIL_LIMIT = 1000;
53
+ const MAX_DETAIL_LIMIT = 10_000;
54
+ const SHA256_PATTERN = /^[a-f0-9]{64}$/;
55
+ const DOCID_PATTERN = /^#[a-f0-9]{6,}$/;
56
+
57
+ interface ManagementDeps {
58
+ clock?: () => number;
59
+ }
60
+
61
+ export class RetrievalTraceManagementService {
62
+ private readonly clock: () => number;
63
+
64
+ constructor(
65
+ private readonly store: StorePort,
66
+ deps: ManagementDeps = {}
67
+ ) {
68
+ this.clock = deps.clock ?? Date.now;
69
+ }
70
+
71
+ async list(
72
+ options: RetrievalTraceListOptions = {}
73
+ ): Promise<StoreResult<RetrievalTraceListResult>> {
74
+ const limit = options.limit ?? DEFAULT_LIST_LIMIT;
75
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_LIST_LIMIT) {
76
+ return err(
77
+ "INVALID_INPUT",
78
+ `Trace limit must be from 1 to ${MAX_LIST_LIMIT}`
79
+ );
80
+ }
81
+ const cursor = decodeCursor(options.cursor);
82
+ if (!cursor.ok) return cursor;
83
+ const rows = await this.store.listRetrievalTraces(limit + 1, cursor.value);
84
+ if (!rows.ok) return rows;
85
+ const page = rows.value.slice(0, limit);
86
+ const last = page.at(-1);
87
+ return ok({
88
+ schemaVersion: "1.0",
89
+ traces: page.map(summaryOf),
90
+ nextCursor:
91
+ rows.value.length > limit && last
92
+ ? encodeCursor({
93
+ createdAtMs: last.createdAtMs,
94
+ traceId: last.traceId,
95
+ })
96
+ : null,
97
+ });
98
+ }
99
+
100
+ async show(
101
+ traceId: string,
102
+ options: RetrievalTraceDetailOptions = {}
103
+ ): Promise<StoreResult<RetrievalTraceDetail>> {
104
+ const limit = options.detailLimit ?? DEFAULT_DETAIL_LIMIT;
105
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_DETAIL_LIMIT) {
106
+ return err(
107
+ "INVALID_INPUT",
108
+ `Trace detail limit must be from 1 to ${MAX_DETAIL_LIMIT}`
109
+ );
110
+ }
111
+ const stored = await this.store.getBoundedRetrievalTrace(traceId, limit);
112
+ if (!stored.ok) return stored;
113
+ if (!stored.value) return err("NOT_FOUND", "Retrieval trace not found");
114
+ const { trace, runs, events, judgments, exports } = stored.value.bundle;
115
+ const totals = stored.value.totals;
116
+ return ok({
117
+ schemaVersion: "1.0",
118
+ trace,
119
+ runs: runs.slice(0, limit),
120
+ events: events.slice(0, limit),
121
+ judgments: judgments.slice(0, limit),
122
+ exports: exports.slice(0, limit),
123
+ totals,
124
+ truncated: {
125
+ runs: totals.runs > runs.length,
126
+ events: totals.events > events.length,
127
+ judgments: totals.judgments > judgments.length,
128
+ exports: totals.exports > exports.length,
129
+ },
130
+ });
131
+ }
132
+
133
+ async label(
134
+ input: LabelRetrievalTraceInput
135
+ ): Promise<StoreResult<LabelRetrievalTraceResult>> {
136
+ if (
137
+ typeof input.traceId !== "string" ||
138
+ typeof input.targetRef !== "string"
139
+ ) {
140
+ return err("INVALID_INPUT", "Invalid retrieval trace label");
141
+ }
142
+ const stored = await this.store.getRetrievalTrace(input.traceId);
143
+ if (!stored.ok) return stored;
144
+ if (!stored.value) return err("NOT_FOUND", "Retrieval trace not found");
145
+ const resolved = await this.resolveLabelTarget(stored.value, input);
146
+ if (!resolved.ok) return resolved;
147
+ const { target, targetKind, runId } = resolved.value;
148
+ const key = targetKey(targetKind, target);
149
+ const latest = latestForTarget(stored.value.judgments, key);
150
+ if (latest?.label === input.label) {
151
+ return ok({
152
+ schemaVersion: "1.0",
153
+ result: "duplicate",
154
+ judgment: latest,
155
+ });
156
+ }
157
+ const now = Math.max(this.clock(), (latest?.createdAtMs ?? -1) + 1);
158
+ if (!Number.isSafeInteger(now) || now < 0) {
159
+ return err("INVALID_INPUT", "Trace clock must return epoch milliseconds");
160
+ }
161
+ const idempotencyKey =
162
+ input.idempotencyKey ??
163
+ `label:${hashTraceCanonical({
164
+ traceId: input.traceId,
165
+ key,
166
+ label: input.label,
167
+ supersedes: latest?.judgmentId ?? null,
168
+ })}`;
169
+ const judgmentId = `judgment-${hashTraceCanonical({
170
+ traceId: input.traceId,
171
+ idempotencyKey,
172
+ }).slice(0, 40)}`;
173
+ const targetRef = await this.redactTargetRef(
174
+ stored.value.trace.redactionMode,
175
+ input.targetRef
176
+ );
177
+ if (!targetRef.ok) return targetRef;
178
+ const judgment = {
179
+ judgmentId,
180
+ traceId: input.traceId,
181
+ runId,
182
+ idempotencyKey,
183
+ label: input.label,
184
+ targetKind,
185
+ targetRef: targetRef.value,
186
+ target,
187
+ createdAtMs: now,
188
+ };
189
+ const appended = await this.store.appendRetrievalTraceJudgment(judgment);
190
+ if (!appended.ok) {
191
+ if (appended.error.code !== "CONSTRAINT_VIOLATION") return appended;
192
+ const concurrent = await this.store.getRetrievalTrace(input.traceId);
193
+ if (!concurrent.ok) return concurrent;
194
+ const settled = concurrent.value
195
+ ? latestForTarget(concurrent.value.judgments, key)
196
+ : undefined;
197
+ if (
198
+ settled?.label === input.label &&
199
+ settled.idempotencyKey === idempotencyKey
200
+ ) {
201
+ return ok({
202
+ schemaVersion: "1.0",
203
+ result: "duplicate",
204
+ judgment: settled,
205
+ });
206
+ }
207
+ return appended;
208
+ }
209
+ const refreshed = await this.store.getRetrievalTrace(input.traceId);
210
+ if (!refreshed.ok) return refreshed;
211
+ const row = refreshed.value?.judgments.find(
212
+ ({ judgmentId: id }) => id === judgmentId
213
+ );
214
+ return row
215
+ ? ok({ schemaVersion: "1.0", result: appended.value, judgment: row })
216
+ : err("QUERY_FAILED", "Stored retrieval trace judgment is unavailable");
217
+ }
218
+
219
+ async export<Format extends RetrievalTraceExportFormat = "agentic-receipt">(
220
+ input: ExportRetrievalTracesInput<Format>
221
+ ): Promise<StoreResult<ExportRetrievalTracesResult<Format>>> {
222
+ return exportRetrievalTraces(this.store, this.clock, input);
223
+ }
224
+
225
+ async replay(
226
+ input: ReplayRetrievalTraceInput,
227
+ deps: Omit<RetrievalReplayDeps, "store">
228
+ ): Promise<StoreResult<ReplayRetrievalTraceResult>> {
229
+ return replayRetrievalTraces({ ...deps, store: this.store }, input);
230
+ }
231
+
232
+ async delete(
233
+ traceId: string
234
+ ): Promise<StoreResult<DeleteRetrievalTraceResult>> {
235
+ const deleted = await this.store.deleteRetrievalTrace(traceId);
236
+ if (!deleted.ok) return deleted;
237
+ if (deleted.value.traces === 0) {
238
+ return err("NOT_FOUND", "Retrieval trace not found");
239
+ }
240
+ return ok({
241
+ schemaVersion: "1.0",
242
+ traceId,
243
+ deleted: true,
244
+ counts: deleted.value,
245
+ });
246
+ }
247
+
248
+ async purge(): Promise<StoreResult<PurgeRetrievalTracesResult>> {
249
+ const purged = await this.store.purgeRetrievalTraces();
250
+ return purged.ok ? ok({ schemaVersion: "1.0", ...purged.value }) : purged;
251
+ }
252
+
253
+ private async resolveLabelTarget(
254
+ bundle: RetrievalTraceBundle,
255
+ input: LabelRetrievalTraceInput
256
+ ): Promise<
257
+ StoreResult<{
258
+ target: EvidenceTarget;
259
+ targetKind: NonNullable<LabelRetrievalTraceInput["targetKind"]>;
260
+ runId: string | null;
261
+ }>
262
+ > {
263
+ if (
264
+ typeof input.traceId !== "string" ||
265
+ typeof input.targetRef !== "string" ||
266
+ !["relevant", "irrelevant", "missing_expected"].includes(input.label) ||
267
+ input.targetRef.length < 1 ||
268
+ input.targetRef.length > 4096 ||
269
+ (input.targetKind !== undefined &&
270
+ !["document", "chunk", "span"].includes(input.targetKind)) ||
271
+ (input.sourceHash !== undefined &&
272
+ !SHA256_PATTERN.test(input.sourceHash)) ||
273
+ (input.docid !== undefined && !DOCID_PATTERN.test(input.docid))
274
+ ) {
275
+ return err("INVALID_INPUT", "Invalid retrieval trace label");
276
+ }
277
+ if (input.label === "missing_expected") {
278
+ return await this.resolveMissingExpected(bundle, input);
279
+ }
280
+ const match = allEvidence(bundle).find(({ target }) =>
281
+ refMatches(target, input.targetRef)
282
+ );
283
+ if (!match) {
284
+ return err(
285
+ "INVALID_INPUT",
286
+ "Relevant and irrelevant labels must reference recorded evidence"
287
+ );
288
+ }
289
+ const target = applyTargetKind(match.target, input);
290
+ if (!target.ok) return target;
291
+ const targetKind =
292
+ input.targetKind ??
293
+ (input.startLine !== undefined
294
+ ? "span"
295
+ : target.value.seq !== undefined
296
+ ? "chunk"
297
+ : "document");
298
+ return ok({ target: target.value, targetKind, runId: match.runId });
299
+ }
300
+
301
+ private async resolveMissingExpected(
302
+ bundle: RetrievalTraceBundle,
303
+ input: LabelRetrievalTraceInput
304
+ ): Promise<
305
+ StoreResult<{
306
+ target: EvidenceTarget;
307
+ targetKind: "document";
308
+ runId: null;
309
+ }>
310
+ > {
311
+ if (input.targetKind !== undefined && input.targetKind !== "document") {
312
+ return err(
313
+ "INVALID_INPUT",
314
+ "missing_expected judgments must target a document"
315
+ );
316
+ }
317
+ let document: Awaited<ReturnType<StorePort["getDocumentByUri"]>> | null =
318
+ null;
319
+ if (input.targetRef.startsWith("gno://")) {
320
+ document = await this.store.getDocumentByUri(input.targetRef);
321
+ } else if (DOCID_PATTERN.test(input.targetRef)) {
322
+ document = await this.store.getDocumentByDocid(input.targetRef);
323
+ } else if (!SHA256_PATTERN.test(input.targetRef)) {
324
+ return err(
325
+ "INVALID_INPUT",
326
+ "Expected documents require a gno:// URI, docid, or source hash"
327
+ );
328
+ }
329
+ if (document && !document.ok) return document;
330
+ const row = document?.ok ? document.value : null;
331
+ const target: EvidenceTarget = compactTarget(
332
+ row
333
+ ? {
334
+ docid: row.docid,
335
+ sourceHash: row.sourceHash,
336
+ mirrorHash: row.mirrorHash ?? undefined,
337
+ uri: row.uri,
338
+ }
339
+ : {
340
+ docid:
341
+ input.docid ??
342
+ (DOCID_PATTERN.test(input.targetRef)
343
+ ? input.targetRef
344
+ : undefined),
345
+ sourceHash:
346
+ input.sourceHash ??
347
+ (SHA256_PATTERN.test(input.targetRef)
348
+ ? input.targetRef
349
+ : undefined),
350
+ uri: input.targetRef.startsWith("gno://")
351
+ ? input.targetRef
352
+ : undefined,
353
+ }
354
+ );
355
+ if (!(target.uri || target.docid || target.sourceHash)) {
356
+ return err("INVALID_INPUT", "Expected document identity is incomplete");
357
+ }
358
+ if (input.sourceHash && row && row.sourceHash !== input.sourceHash) {
359
+ return err("INVALID_INPUT", "Expected document source hash mismatch");
360
+ }
361
+ if (input.docid && row && row.docid !== input.docid) {
362
+ return err("INVALID_INPUT", "Expected document docid mismatch");
363
+ }
364
+ if (
365
+ allEvidence(bundle).some(({ target: evidence }) =>
366
+ [target.uri, target.docid, target.sourceHash]
367
+ .filter(Boolean)
368
+ .some((identity) => refMatches(evidence, identity!))
369
+ )
370
+ ) {
371
+ return err(
372
+ "CONSTRAINT_VIOLATION",
373
+ "Expected document is already present in recorded evidence"
374
+ );
375
+ }
376
+ return ok({ target, targetKind: "document", runId: null });
377
+ }
378
+
379
+ private async redactTargetRef(
380
+ mode: RetrievalTraceBundle["trace"]["redactionMode"],
381
+ targetRef: string
382
+ ): Promise<StoreResult<string>> {
383
+ if (mode === "replay") return ok(targetRef.normalize("NFC"));
384
+ const secret = await this.store.getOrCreateRetrievalTraceRedactionSecret();
385
+ if (!secret.ok) return secret;
386
+ return ok(
387
+ `redacted:${new Bun.CryptoHasher("sha256")
388
+ .update(`${secret.value}\0${targetRef.normalize("NFC")}`)
389
+ .digest("hex")}`
390
+ );
391
+ }
392
+ }
393
+
394
+ export type {
395
+ DeleteRetrievalTraceResult,
396
+ ExportRetrievalTracesInput,
397
+ ExportRetrievalTracesResult,
398
+ LabelRetrievalTraceInput,
399
+ LabelRetrievalTraceResult,
400
+ PurgeRetrievalTracesResult,
401
+ RetrievalTraceDetail,
402
+ RetrievalTraceDetailOptions,
403
+ RetrievalTraceListOptions,
404
+ RetrievalTraceListRequest,
405
+ RetrievalTraceListResult,
406
+ RetrievalTraceLabelRequest,
407
+ RetrievalTraceLabelResult,
408
+ RetrievalTraceExportRequest,
409
+ RetrievalTraceExportResult,
410
+ RetrievalTraceDeleteResult,
411
+ RetrievalTracePurgeResult,
412
+ RetrievalTraceSummary,
413
+ } from "./retrieval-trace-management-types";
414
+ export type {
415
+ ReplayRetrievalTraceInput,
416
+ ReplayRetrievalTraceResult,
417
+ RetrievalReplayCandidate,
418
+ } from "./retrieval-replay-types";
419
+
420
+ export const serializeRetrievalTraceArtifact = (
421
+ result: ExportRetrievalTracesResult
422
+ ): string => canonicalTraceJson(result.artifact);
@@ -0,0 +1,141 @@
1
+ /** Surface-boundary request setup for private retrieval traces. */
2
+
3
+ import type { Config } from "../config/types";
4
+ import type { HybridSearchOptions, SearchOptions } from "../pipeline/types";
5
+ import type { StorePort, StoreResult } from "../store/types";
6
+ import type { RetrievalTraceFingerprints } from "../store/types";
7
+ import type { RetrievalTraceTerminalStatus } from "../store/types";
8
+
9
+ import { canonicalTraceJson } from "../store/retrieval-trace-codec";
10
+ import { err, ok } from "../store/types";
11
+ import { RetrievalTraceSession } from "./retrieval-trace-session";
12
+
13
+ export const retrievalTraceFailureStatus = (
14
+ cause: unknown
15
+ ): RetrievalTraceTerminalStatus => {
16
+ if (
17
+ cause instanceof Error &&
18
+ (cause.name === "AbortError" ||
19
+ (cause as Error & { code?: string }).code === "ABORT_ERR")
20
+ ) {
21
+ return "cancelled";
22
+ }
23
+ return "failed";
24
+ };
25
+
26
+ export const finishRetrievalTraceAfterError = async (
27
+ session: RetrievalTraceSession | null | undefined,
28
+ cause: unknown
29
+ ): Promise<void> => {
30
+ await session?.finish(retrievalTraceFailureStatus(cause));
31
+ };
32
+
33
+ const fingerprint = (value: unknown): string =>
34
+ new Bun.CryptoHasher("sha256")
35
+ .update(canonicalTraceJson(JSON.parse(JSON.stringify(value))))
36
+ .digest("hex");
37
+
38
+ export const buildRetrievalTraceFingerprints = async (input: {
39
+ store: StorePort;
40
+ config: Config;
41
+ pipeline: string;
42
+ pipelineOptions?: Record<string, unknown>;
43
+ indexName?: string;
44
+ modelUris?: string[];
45
+ }): Promise<RetrievalTraceFingerprints> => {
46
+ const collections = await input.store.getCollections();
47
+ if (!collections.ok) throw new Error(collections.error.message);
48
+ const snapshots = [];
49
+ for (const collection of [...collections.value].sort((left, right) =>
50
+ left.name.localeCompare(right.name)
51
+ )) {
52
+ const snapshot = await input.store.getActivationIndexSnapshot(
53
+ collection.name
54
+ );
55
+ if (!snapshot.ok) throw new Error(snapshot.error.message);
56
+ snapshots.push({ collection: collection.name, value: snapshot.value });
57
+ }
58
+ return {
59
+ pipeline: fingerprint({
60
+ contract: "retrieval-trace-v1",
61
+ pipeline: input.pipeline,
62
+ ...(input.pipelineOptions ? { options: input.pipelineOptions } : {}),
63
+ }),
64
+ model: fingerprint(
65
+ [...(input.modelUris ?? [])].sort((left, right) =>
66
+ left.localeCompare(right)
67
+ )
68
+ ),
69
+ config: fingerprint(input.config),
70
+ index: fingerprint({
71
+ indexName: input.indexName ?? "default",
72
+ snapshots,
73
+ }),
74
+ };
75
+ };
76
+
77
+ /** Start a real surface session; all expensive fingerprint work stays lazy. */
78
+ export const startRetrievalTraceRequest = async (input: {
79
+ store: StorePort;
80
+ config: Config;
81
+ query: string;
82
+ goal?: string;
83
+ filters?: Record<string, unknown>;
84
+ pipeline: string;
85
+ indexName?: string;
86
+ modelUris?: string[];
87
+ }): Promise<StoreResult<RetrievalTraceSession | null>> => {
88
+ if (input.config.retrievalTraces?.enabled !== true) return ok(null);
89
+ try {
90
+ return await RetrievalTraceSession.start({
91
+ store: input.store,
92
+ config: input.config.retrievalTraces,
93
+ query: input.query,
94
+ goal: input.goal,
95
+ filters: JSON.parse(JSON.stringify(input.filters ?? {})),
96
+ fingerprints: () => buildRetrievalTraceFingerprints(input),
97
+ });
98
+ } catch (cause) {
99
+ return err(
100
+ "QUERY_FAILED",
101
+ cause instanceof Error
102
+ ? `Trace fingerprinting failed: ${cause.message}`
103
+ : "Trace fingerprinting failed",
104
+ cause
105
+ );
106
+ }
107
+ };
108
+
109
+ export const retrievalTraceFilters = (
110
+ options: SearchOptions | HybridSearchOptions
111
+ ): Record<string, unknown> =>
112
+ JSON.parse(
113
+ JSON.stringify({
114
+ limit: options.limit,
115
+ minScore: options.minScore,
116
+ collection: options.collection,
117
+ lang: options.lang,
118
+ full: options.full,
119
+ lineNumbers: options.lineNumbers,
120
+ tagsAll: options.tagsAll,
121
+ tagsAny: options.tagsAny,
122
+ since: options.since,
123
+ until: options.until,
124
+ categories: options.categories,
125
+ author: options.author,
126
+ intent: options.intent,
127
+ exclude: options.exclude,
128
+ ...("noExpand" in options
129
+ ? {
130
+ noExpand: options.noExpand,
131
+ noRerank: options.noRerank,
132
+ candidateLimit: options.candidateLimit,
133
+ explain: options.explain,
134
+ graph: options.graph,
135
+ noGraph: options.noGraph,
136
+ queryLanguageHint: options.queryLanguageHint,
137
+ queryModes: options.queryModes,
138
+ }
139
+ : {}),
140
+ })
141
+ );