@gmickel/gno 1.18.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 (129) hide show
  1. package/README.md +5 -3
  2. package/assets/skill/SKILL.md +27 -0
  3. package/package.json +2 -1
  4. package/spec/AGENTS.md +83 -0
  5. package/spec/CLAUDE.md +83 -0
  6. package/spec/bench-fixture.schema.json +137 -0
  7. package/spec/cli.md +2894 -0
  8. package/spec/db/schema.sql +442 -0
  9. package/spec/evals-agentic.md +510 -0
  10. package/spec/evals.md +1106 -0
  11. package/spec/mcp.md +2229 -0
  12. package/spec/output-schemas/activation-verification.schema.json +515 -0
  13. package/spec/output-schemas/ask.schema.json +366 -0
  14. package/spec/output-schemas/backlinks.schema.json +131 -0
  15. package/spec/output-schemas/bench-result.schema.json +120 -0
  16. package/spec/output-schemas/capture-receipt.schema.json +143 -0
  17. package/spec/output-schemas/collection-list.schema.json +45 -0
  18. package/spec/output-schemas/context-capsule-v1.schema.json +691 -0
  19. package/spec/output-schemas/context-capsule-verification.schema.json +1338 -0
  20. package/spec/output-schemas/context-list.schema.json +21 -0
  21. package/spec/output-schemas/doctor.schema.json +313 -0
  22. package/spec/output-schemas/error.schema.json +30 -0
  23. package/spec/output-schemas/expansion.schema.json +37 -0
  24. package/spec/output-schemas/get.schema.json +140 -0
  25. package/spec/output-schemas/graph-query.schema.json +99 -0
  26. package/spec/output-schemas/graph.schema.json +371 -0
  27. package/spec/output-schemas/links-list.schema.json +186 -0
  28. package/spec/output-schemas/mcp-add-collection-result.schema.json +23 -0
  29. package/spec/output-schemas/mcp-capture-result.schema.json +152 -0
  30. package/spec/output-schemas/mcp-http-error.schema.json +30 -0
  31. package/spec/output-schemas/mcp-job-list.schema.json +58 -0
  32. package/spec/output-schemas/mcp-job-status.schema.json +224 -0
  33. package/spec/output-schemas/mcp-remove-result.schema.json +39 -0
  34. package/spec/output-schemas/mcp-sync-result.schema.json +41 -0
  35. package/spec/output-schemas/mcp-tag-result.schema.json +33 -0
  36. package/spec/output-schemas/models-list.schema.json +93 -0
  37. package/spec/output-schemas/multi-get.schema.json +103 -0
  38. package/spec/output-schemas/process-status.schema.json +119 -0
  39. package/spec/output-schemas/query-diagnose.schema.json +123 -0
  40. package/spec/output-schemas/resident-status.schema.json +154 -0
  41. package/spec/output-schemas/retrieval-trace-common.schema.json +492 -0
  42. package/spec/output-schemas/retrieval-trace-delete.schema.json +16 -0
  43. package/spec/output-schemas/retrieval-trace-export.schema.json +61 -0
  44. package/spec/output-schemas/retrieval-trace-filters.schema.json +139 -0
  45. package/spec/output-schemas/retrieval-trace-judgment.schema.json +15 -0
  46. package/spec/output-schemas/retrieval-trace-list.schema.json +18 -0
  47. package/spec/output-schemas/retrieval-trace-payloads.schema.json +178 -0
  48. package/spec/output-schemas/retrieval-trace-purge.schema.json +31 -0
  49. package/spec/output-schemas/retrieval-trace-qrels.schema.json +303 -0
  50. package/spec/output-schemas/retrieval-trace-replay.schema.json +286 -0
  51. package/spec/output-schemas/retrieval-trace-show.schema.json +69 -0
  52. package/spec/output-schemas/retrieval-trace-summary.schema.json +65 -0
  53. package/spec/output-schemas/search-result.schema.json +154 -0
  54. package/spec/output-schemas/search-results.schema.json +338 -0
  55. package/spec/output-schemas/similar.schema.json +84 -0
  56. package/spec/output-schemas/status.schema.json +676 -0
  57. package/spec/output-schemas/tags-list.schema.json +48 -0
  58. package/src/app/context-runtime-types.ts +3 -0
  59. package/src/app/context-runtime.ts +15 -1
  60. package/src/cli/commands/ask.ts +106 -36
  61. package/src/cli/commands/context-build.ts +56 -9
  62. package/src/cli/commands/get.ts +64 -3
  63. package/src/cli/commands/query.ts +62 -23
  64. package/src/cli/commands/replay.ts +140 -0
  65. package/src/cli/commands/search.ts +48 -3
  66. package/src/cli/commands/shared.ts +3 -1
  67. package/src/cli/commands/trace.ts +200 -0
  68. package/src/cli/commands/vsearch.ts +75 -53
  69. package/src/cli/program.ts +255 -0
  70. package/src/config/index.ts +9 -0
  71. package/src/config/retrieval-traces.ts +56 -0
  72. package/src/config/types.ts +4 -0
  73. package/src/core/context-compiler.ts +11 -4
  74. package/src/core/retrieval-qrels.ts +405 -0
  75. package/src/core/retrieval-replay-candidate.ts +368 -0
  76. package/src/core/retrieval-replay-types.ts +109 -0
  77. package/src/core/retrieval-replay-validation.ts +89 -0
  78. package/src/core/retrieval-replay.ts +441 -0
  79. package/src/core/retrieval-trace-evidence-origin.ts +175 -0
  80. package/src/core/retrieval-trace-export.ts +113 -0
  81. package/src/core/retrieval-trace-filter-normalization.ts +27 -0
  82. package/src/core/retrieval-trace-filters.ts +19 -0
  83. package/src/core/retrieval-trace-management-helpers.ts +247 -0
  84. package/src/core/retrieval-trace-management-types.ts +132 -0
  85. package/src/core/retrieval-trace-management.ts +422 -0
  86. package/src/core/retrieval-trace-request.ts +141 -0
  87. package/src/core/retrieval-trace-session.ts +494 -0
  88. package/src/core/retrieval-trace.ts +472 -0
  89. package/src/mcp/tools/context.ts +59 -8
  90. package/src/mcp/tools/get.ts +35 -1
  91. package/src/mcp/tools/index.ts +74 -0
  92. package/src/mcp/tools/query.ts +95 -64
  93. package/src/mcp/tools/search.ts +36 -13
  94. package/src/mcp/tools/trace.ts +143 -0
  95. package/src/mcp/tools/vsearch.ts +71 -38
  96. package/src/pipeline/answer.ts +167 -26
  97. package/src/pipeline/graph-retrieval.ts +15 -1
  98. package/src/pipeline/hybrid.ts +151 -43
  99. package/src/pipeline/search.ts +36 -3
  100. package/src/pipeline/trace-metadata.ts +47 -0
  101. package/src/pipeline/types.ts +43 -0
  102. package/src/pipeline/vsearch.ts +101 -38
  103. package/src/sdk/client.ts +380 -71
  104. package/src/sdk/documents.ts +48 -1
  105. package/src/sdk/index.ts +17 -0
  106. package/src/sdk/types.ts +28 -0
  107. package/src/serve/context-capsule.ts +67 -8
  108. package/src/serve/public/app.tsx +12 -1
  109. package/src/serve/public/globals.built.css +1 -1
  110. package/src/serve/public/lib/workspace-tabs.ts +2 -0
  111. package/src/serve/public/pages/Dashboard.tsx +10 -0
  112. package/src/serve/public/pages/TraceHistory.tsx +478 -0
  113. package/src/serve/public/pages/trace-history-detail.tsx +224 -0
  114. package/src/serve/retrieval-trace.ts +28 -0
  115. package/src/serve/routes/api.ts +366 -72
  116. package/src/serve/routes/traces.ts +156 -0
  117. package/src/serve/server.ts +87 -2
  118. package/src/store/index.ts +31 -0
  119. package/src/store/migrations/014-retrieval-traces.ts +303 -0
  120. package/src/store/migrations/index.ts +2 -0
  121. package/src/store/retrieval-trace-codec.ts +384 -0
  122. package/src/store/sqlite/adapter.ts +153 -1
  123. package/src/store/sqlite/retrieval-trace-management-store.ts +341 -0
  124. package/src/store/sqlite/retrieval-trace-retention.ts +349 -0
  125. package/src/store/sqlite/retrieval-trace-rows.ts +267 -0
  126. package/src/store/sqlite/retrieval-trace-store.ts +515 -0
  127. package/src/store/types.ts +297 -0
  128. package/src/store/vector/sqlite-vec.ts +76 -1
  129. package/src/store/vector/types.ts +1 -1
@@ -0,0 +1,515 @@
1
+ /** SQLite CRUD for versioned private retrieval trace receipts. */
2
+
3
+ import type { Database } from "bun:sqlite";
4
+
5
+ import type {
6
+ RetrievalTraceAppendResult,
7
+ RetrievalTraceBundle,
8
+ RetrievalTraceCursor,
9
+ RetrievalTraceEventInput,
10
+ RetrievalTraceExportInput,
11
+ RetrievalTraceInput,
12
+ RetrievalTraceJudgmentInput,
13
+ RetrievalTraceRow,
14
+ RetrievalTraceRunInput,
15
+ RetrievalTraceTerminalStatus,
16
+ StoreResult,
17
+ } from "../types";
18
+
19
+ import {
20
+ canonicalTraceJson,
21
+ hashRetrievalTraceCreation,
22
+ hashTraceCanonical,
23
+ parseRetrievalTraceEventInput,
24
+ parseRetrievalTraceExportInput,
25
+ parseRetrievalTraceInput,
26
+ parseRetrievalTraceJudgmentInput,
27
+ parseRetrievalTraceRunInput,
28
+ traceUtf8Bytes,
29
+ } from "../retrieval-trace-codec";
30
+ import { err, ok } from "../types";
31
+ import {
32
+ type DbRetrievalTraceEventRow,
33
+ type DbRetrievalTraceExportRow,
34
+ type DbRetrievalTraceJudgmentRow,
35
+ type DbRetrievalTraceRow,
36
+ type DbRetrievalTraceRunRow,
37
+ mapEventRow,
38
+ mapExportRow,
39
+ mapJudgmentRow,
40
+ mapRunRow,
41
+ mapTraceRow,
42
+ RetrievalTraceConflictError,
43
+ traceReadError,
44
+ traceWriteError,
45
+ validateTraceId,
46
+ } from "./retrieval-trace-rows";
47
+
48
+ type IdempotentTable =
49
+ | "retrieval_trace_runs"
50
+ | "retrieval_trace_events"
51
+ | "retrieval_trace_judgments";
52
+ type IdColumn = "run_id" | "event_id" | "judgment_id";
53
+
54
+ const existingDigest = (
55
+ db: Database,
56
+ table: IdempotentTable,
57
+ idColumn: IdColumn,
58
+ id: string,
59
+ traceId: string,
60
+ idempotencyKey: string
61
+ ): string | null =>
62
+ db
63
+ .query<{ canonical_digest: string }, [string, string, string]>(
64
+ `SELECT canonical_digest FROM ${table}
65
+ WHERE ${idColumn} = ? OR (trace_id = ? AND idempotency_key = ?)
66
+ LIMIT 1`
67
+ )
68
+ .get(id, traceId, idempotencyKey)?.canonical_digest ?? null;
69
+
70
+ const settleIdempotentInsert = (
71
+ db: Database,
72
+ table: IdempotentTable,
73
+ idColumn: IdColumn,
74
+ id: string,
75
+ traceId: string,
76
+ idempotencyKey: string,
77
+ digest: string,
78
+ changes: number
79
+ ): RetrievalTraceAppendResult => {
80
+ if (changes > 0) return "inserted";
81
+ const stored = existingDigest(
82
+ db,
83
+ table,
84
+ idColumn,
85
+ id,
86
+ traceId,
87
+ idempotencyKey
88
+ );
89
+ if (stored === digest) return "duplicate";
90
+ throw new RetrievalTraceConflictError(
91
+ `${table} idempotency key already exists with different content`
92
+ );
93
+ };
94
+
95
+ const enforceByteLimit = (
96
+ value: string,
97
+ maxBytes: number,
98
+ label: string
99
+ ): number => {
100
+ const bytes = traceUtf8Bytes(value);
101
+ if (bytes > maxBytes) {
102
+ throw new RangeError(`${label} exceeds ${maxBytes} UTF-8 bytes`);
103
+ }
104
+ return bytes;
105
+ };
106
+
107
+ export const createTrace = (
108
+ db: Database,
109
+ input: RetrievalTraceInput
110
+ ): StoreResult<RetrievalTraceAppendResult> => {
111
+ try {
112
+ const trace = parseRetrievalTraceInput(input);
113
+ const queryShapeJson = canonicalTraceJson(trace.queryShape);
114
+ const goalShapeJson = canonicalTraceJson(trace.goalShape);
115
+ const filtersJson = canonicalTraceJson(trace.filters);
116
+ const queryBytes = enforceByteLimit(
117
+ trace.queryText ?? "",
118
+ 8192,
119
+ "Retrieval trace query"
120
+ );
121
+ const shapeBytes = enforceByteLimit(
122
+ queryShapeJson,
123
+ 1024,
124
+ "Retrieval trace query shape"
125
+ );
126
+ const goalBytes = enforceByteLimit(
127
+ trace.goalText ?? "",
128
+ 8192,
129
+ "Retrieval trace goal"
130
+ );
131
+ const goalShapeBytes = enforceByteLimit(
132
+ goalShapeJson,
133
+ 1024,
134
+ "Retrieval trace goal shape"
135
+ );
136
+ const filterBytes = enforceByteLimit(
137
+ filtersJson,
138
+ 16_384,
139
+ "Retrieval trace filters"
140
+ );
141
+ const creationDigest = hashRetrievalTraceCreation(trace);
142
+ const insert = db.run(
143
+ `INSERT OR IGNORE INTO retrieval_traces (
144
+ trace_id, schema_version, redaction_mode, replay_capable,
145
+ query_text, query_digest, query_shape_json,
146
+ goal_text, goal_digest, goal_shape_json, filters_json,
147
+ pipeline_fingerprint, model_fingerprint, config_fingerprint,
148
+ index_fingerprint, status, created_at_ms, updated_at_ms,
149
+ expires_at_ms, byte_size, creation_digest
150
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
151
+ [
152
+ trace.traceId,
153
+ trace.schemaVersion,
154
+ trace.redactionMode,
155
+ trace.replayCapable ? 1 : 0,
156
+ trace.queryText,
157
+ trace.queryDigest,
158
+ queryShapeJson,
159
+ trace.goalText,
160
+ trace.goalDigest,
161
+ goalShapeJson,
162
+ filtersJson,
163
+ trace.fingerprints.pipeline,
164
+ trace.fingerprints.model,
165
+ trace.fingerprints.config,
166
+ trace.fingerprints.index,
167
+ trace.status,
168
+ trace.createdAtMs,
169
+ trace.updatedAtMs,
170
+ trace.expiresAtMs,
171
+ queryBytes + shapeBytes + goalBytes + goalShapeBytes + filterBytes,
172
+ creationDigest,
173
+ ]
174
+ );
175
+ if (insert.changes > 0) return ok("inserted");
176
+ const stored = db
177
+ .query<{ creation_digest: string }, [string]>(
178
+ "SELECT creation_digest FROM retrieval_traces WHERE trace_id = ?"
179
+ )
180
+ .get(trace.traceId);
181
+ if (stored?.creation_digest === creationDigest) return ok("duplicate");
182
+ return err(
183
+ "CONSTRAINT_VIOLATION",
184
+ `trace_id ${trace.traceId} already exists with different content`
185
+ );
186
+ } catch (cause) {
187
+ return traceWriteError(cause, "Failed to create retrieval trace");
188
+ }
189
+ };
190
+
191
+ export const getTrace = (
192
+ db: Database,
193
+ traceId: string
194
+ ): StoreResult<RetrievalTraceBundle | null> => {
195
+ try {
196
+ validateTraceId(traceId, "traceId");
197
+ const trace = db
198
+ .query<DbRetrievalTraceRow, [string]>(
199
+ "SELECT * FROM retrieval_traces WHERE trace_id = ?"
200
+ )
201
+ .get(traceId);
202
+ if (!trace) return ok(null);
203
+ const runs = db
204
+ .query<DbRetrievalTraceRunRow, [string]>(
205
+ `SELECT * FROM retrieval_trace_runs WHERE trace_id = ?
206
+ ORDER BY created_at_ms, run_id`
207
+ )
208
+ .all(traceId);
209
+ const events = db
210
+ .query<DbRetrievalTraceEventRow, [string]>(
211
+ `SELECT * FROM retrieval_trace_events WHERE trace_id = ?
212
+ ORDER BY created_at_ms, event_id`
213
+ )
214
+ .all(traceId);
215
+ const judgments = db
216
+ .query<DbRetrievalTraceJudgmentRow, [string]>(
217
+ `SELECT * FROM retrieval_trace_judgments WHERE trace_id = ?
218
+ ORDER BY created_at_ms, judgment_id`
219
+ )
220
+ .all(traceId);
221
+ const exports = db
222
+ .query<DbRetrievalTraceExportRow, [string]>(
223
+ `SELECT x.*, et.trace_id FROM retrieval_trace_exports x
224
+ JOIN retrieval_trace_export_traces et USING (export_id)
225
+ WHERE et.trace_id = ? ORDER BY x.created_at_ms, x.export_id`
226
+ )
227
+ .all(traceId);
228
+ return ok({
229
+ trace: mapTraceRow(trace),
230
+ runs: runs.map(mapRunRow),
231
+ events: events.map(mapEventRow),
232
+ judgments: judgments.map(mapJudgmentRow),
233
+ exports: exports.map(mapExportRow),
234
+ });
235
+ } catch (cause) {
236
+ return traceReadError(cause, "Failed to get retrieval trace");
237
+ }
238
+ };
239
+
240
+ export const listTraces = (
241
+ db: Database,
242
+ limit: number,
243
+ cursor?: RetrievalTraceCursor
244
+ ): StoreResult<RetrievalTraceRow[]> => {
245
+ try {
246
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 10_000) {
247
+ return err("INVALID_INPUT", "Trace limit must be from 1 to 10000");
248
+ }
249
+ if (cursor) {
250
+ validateTraceId(cursor.traceId, "cursor.traceId");
251
+ if (!Number.isSafeInteger(cursor.createdAtMs) || cursor.createdAtMs < 0) {
252
+ return err(
253
+ "INVALID_INPUT",
254
+ "Trace cursor time must be epoch milliseconds"
255
+ );
256
+ }
257
+ return ok(
258
+ db
259
+ .query<DbRetrievalTraceRow, [number, number, string, number]>(
260
+ `SELECT * FROM retrieval_traces
261
+ WHERE created_at_ms < ?
262
+ OR (created_at_ms = ? AND trace_id > ?)
263
+ ORDER BY created_at_ms DESC, trace_id ASC LIMIT ?`
264
+ )
265
+ .all(cursor.createdAtMs, cursor.createdAtMs, cursor.traceId, limit)
266
+ .map(mapTraceRow)
267
+ );
268
+ }
269
+ return ok(
270
+ db
271
+ .query<DbRetrievalTraceRow, [number]>(
272
+ `SELECT * FROM retrieval_traces
273
+ ORDER BY created_at_ms DESC, trace_id ASC LIMIT ?`
274
+ )
275
+ .all(limit)
276
+ .map(mapTraceRow)
277
+ );
278
+ } catch (cause) {
279
+ return traceReadError(cause, "Failed to list retrieval traces");
280
+ }
281
+ };
282
+
283
+ export const finalizeTrace = (
284
+ db: Database,
285
+ traceId: string,
286
+ status: RetrievalTraceTerminalStatus,
287
+ updatedAtMs: number
288
+ ): StoreResult<RetrievalTraceAppendResult> => {
289
+ try {
290
+ validateTraceId(traceId, "traceId");
291
+ if (
292
+ !["completed", "partial", "failed", "cancelled"].includes(status) ||
293
+ !Number.isSafeInteger(updatedAtMs) ||
294
+ updatedAtMs < 0
295
+ ) {
296
+ return err("INVALID_INPUT", "Invalid retrieval trace terminal outcome");
297
+ }
298
+ const row = db
299
+ .query<
300
+ { status: string; created_at_ms: number; updated_at_ms: number },
301
+ [string]
302
+ >(
303
+ `SELECT status, created_at_ms, updated_at_ms
304
+ FROM retrieval_traces WHERE trace_id = ?`
305
+ )
306
+ .get(traceId);
307
+ if (!row) return err("NOT_FOUND", `Retrieval trace ${traceId} not found`);
308
+ if (updatedAtMs < row.created_at_ms) {
309
+ return err("INVALID_INPUT", "updatedAtMs precedes trace creation");
310
+ }
311
+ if (row.status !== "open") {
312
+ return row.status === status
313
+ ? ok("duplicate")
314
+ : err(
315
+ "CONSTRAINT_VIOLATION",
316
+ `Retrieval trace already finalized as ${row.status}`
317
+ );
318
+ }
319
+ const update = db.run(
320
+ `UPDATE retrieval_traces SET status = ?, updated_at_ms = ?
321
+ WHERE trace_id = ? AND status = 'open'`,
322
+ [status, updatedAtMs, traceId]
323
+ );
324
+ if (update.changes > 0) return ok("inserted");
325
+ return finalizeTrace(db, traceId, status, updatedAtMs);
326
+ } catch (cause) {
327
+ return traceWriteError(cause, "Failed to finalize retrieval trace");
328
+ }
329
+ };
330
+
331
+ export const appendRun = (
332
+ db: Database,
333
+ input: RetrievalTraceRunInput
334
+ ): StoreResult<RetrievalTraceAppendResult> => {
335
+ try {
336
+ const run = parseRetrievalTraceRunInput(input);
337
+ const json = canonicalTraceJson(run.payload);
338
+ const bytes = enforceByteLimit(json, 65_536, "Trace run payload");
339
+ const digest = hashTraceCanonical(run);
340
+ const insert = db.run(
341
+ `INSERT OR IGNORE INTO retrieval_trace_runs (
342
+ run_id, trace_id, idempotency_key, kind, payload_json,
343
+ payload_bytes, canonical_digest, created_at_ms
344
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
345
+ [
346
+ run.runId,
347
+ run.traceId,
348
+ run.idempotencyKey,
349
+ run.kind,
350
+ json,
351
+ bytes,
352
+ digest,
353
+ run.createdAtMs,
354
+ ]
355
+ );
356
+ return ok(
357
+ settleIdempotentInsert(
358
+ db,
359
+ "retrieval_trace_runs",
360
+ "run_id",
361
+ run.runId,
362
+ run.traceId,
363
+ run.idempotencyKey,
364
+ digest,
365
+ insert.changes
366
+ )
367
+ );
368
+ } catch (cause) {
369
+ return traceWriteError(cause, "Failed to append retrieval run");
370
+ }
371
+ };
372
+
373
+ export const appendEvent = (
374
+ db: Database,
375
+ input: RetrievalTraceEventInput
376
+ ): StoreResult<RetrievalTraceAppendResult> => {
377
+ try {
378
+ const event = parseRetrievalTraceEventInput(input);
379
+ const json = canonicalTraceJson(event.payload);
380
+ const bytes = enforceByteLimit(json, 65_536, "Trace event payload");
381
+ const digest = hashTraceCanonical(event);
382
+ const insert = db.run(
383
+ `INSERT OR IGNORE INTO retrieval_trace_events (
384
+ event_id, trace_id, run_id, idempotency_key, kind, payload_json,
385
+ payload_bytes, canonical_digest, created_at_ms
386
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
387
+ [
388
+ event.eventId,
389
+ event.traceId,
390
+ event.runId,
391
+ event.idempotencyKey,
392
+ event.kind,
393
+ json,
394
+ bytes,
395
+ digest,
396
+ event.createdAtMs,
397
+ ]
398
+ );
399
+ return ok(
400
+ settleIdempotentInsert(
401
+ db,
402
+ "retrieval_trace_events",
403
+ "event_id",
404
+ event.eventId,
405
+ event.traceId,
406
+ event.idempotencyKey,
407
+ digest,
408
+ insert.changes
409
+ )
410
+ );
411
+ } catch (cause) {
412
+ return traceWriteError(cause, "Failed to append retrieval event");
413
+ }
414
+ };
415
+
416
+ export const appendJudgment = (
417
+ db: Database,
418
+ input: RetrievalTraceJudgmentInput
419
+ ): StoreResult<RetrievalTraceAppendResult> => {
420
+ try {
421
+ const judgment = parseRetrievalTraceJudgmentInput(input);
422
+ enforceByteLimit(judgment.targetRef, 4096, "Judgment targetRef");
423
+ const json = canonicalTraceJson(judgment.target);
424
+ const bytes = enforceByteLimit(json, 16_384, "Judgment target");
425
+ const digest = hashTraceCanonical(judgment);
426
+ const insert = db.run(
427
+ `INSERT OR IGNORE INTO retrieval_trace_judgments (
428
+ judgment_id, trace_id, run_id, idempotency_key, label,
429
+ target_kind, target_ref, target_json, target_bytes,
430
+ canonical_digest, created_at_ms
431
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
432
+ [
433
+ judgment.judgmentId,
434
+ judgment.traceId,
435
+ judgment.runId,
436
+ judgment.idempotencyKey,
437
+ judgment.label,
438
+ judgment.targetKind,
439
+ judgment.targetRef,
440
+ json,
441
+ bytes,
442
+ digest,
443
+ judgment.createdAtMs,
444
+ ]
445
+ );
446
+ return ok(
447
+ settleIdempotentInsert(
448
+ db,
449
+ "retrieval_trace_judgments",
450
+ "judgment_id",
451
+ judgment.judgmentId,
452
+ judgment.traceId,
453
+ judgment.idempotencyKey,
454
+ digest,
455
+ insert.changes
456
+ )
457
+ );
458
+ } catch (cause) {
459
+ return traceWriteError(cause, "Failed to append retrieval judgment");
460
+ }
461
+ };
462
+
463
+ export const appendExport = (
464
+ db: Database,
465
+ input: RetrievalTraceExportInput
466
+ ): StoreResult<RetrievalTraceAppendResult> => {
467
+ try {
468
+ const value = parseRetrievalTraceExportInput(input);
469
+ const transaction = db.transaction((): RetrievalTraceAppendResult => {
470
+ db.run(
471
+ `INSERT OR IGNORE INTO retrieval_trace_exports
472
+ (export_id, format, artifact_hash, created_at_ms) VALUES (?, ?, ?, ?)`,
473
+ [value.exportId, value.format, value.artifactHash, value.createdAtMs]
474
+ );
475
+ const manifest = db
476
+ .query<{ format: string; artifact_hash: string }, [string]>(
477
+ `SELECT format, artifact_hash FROM retrieval_trace_exports
478
+ WHERE export_id = ?`
479
+ )
480
+ .get(value.exportId);
481
+ if (
482
+ manifest &&
483
+ (manifest.format !== value.format ||
484
+ manifest.artifact_hash !== value.artifactHash)
485
+ ) {
486
+ throw new RetrievalTraceConflictError(
487
+ "Export ID already exists with different content"
488
+ );
489
+ }
490
+ const linkedExportId =
491
+ manifest === undefined
492
+ ? db
493
+ .query<{ export_id: string }, [string, string]>(
494
+ `SELECT export_id FROM retrieval_trace_exports
495
+ WHERE format = ? AND artifact_hash = ?`
496
+ )
497
+ .get(value.format, value.artifactHash)?.export_id
498
+ : value.exportId;
499
+ if (!linkedExportId) {
500
+ throw new RetrievalTraceConflictError(
501
+ "Unable to resolve retrieval trace export manifest"
502
+ );
503
+ }
504
+ const link = db.run(
505
+ `INSERT OR IGNORE INTO retrieval_trace_export_traces
506
+ (export_id, trace_id) VALUES (?, ?)`,
507
+ [linkedExportId, value.traceId]
508
+ );
509
+ return link.changes > 0 ? "inserted" : "duplicate";
510
+ });
511
+ return ok(transaction());
512
+ } catch (cause) {
513
+ return traceWriteError(cause, "Failed to append retrieval export");
514
+ }
515
+ };