@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,384 @@
1
+ /** Runtime validation and deterministic encoding for local retrieval receipts. */
2
+
3
+ import { z } from "zod";
4
+
5
+ import type {
6
+ RetrievalTraceEventInput,
7
+ RetrievalTraceExportInput,
8
+ RetrievalTraceInput,
9
+ RetrievalTraceJudgmentInput,
10
+ RetrievalTraceRunInput,
11
+ } from "./types";
12
+
13
+ const MAX_ID_LENGTH = 128;
14
+ const MAX_IDEMPOTENCY_KEY_LENGTH = 256;
15
+ const sha256Schema = z.string().regex(/^[a-f0-9]{64}$/);
16
+ const idSchema = z.string().min(1).max(MAX_ID_LENGTH);
17
+ const idempotencyKeySchema = z.string().min(1).max(MAX_IDEMPOTENCY_KEY_LENGTH);
18
+ const epochMsSchema = z.number().int().safe().nonnegative();
19
+ const DANGEROUS_JSON_KEYS = new Set(["__proto__", "constructor", "prototype"]);
20
+
21
+ const jsonValueSchema: z.ZodType<unknown> = z.lazy(() =>
22
+ z.union([
23
+ z.string(),
24
+ z.number().finite(),
25
+ z.boolean(),
26
+ z.null(),
27
+ z.array(jsonValueSchema),
28
+ z.record(z.string(), jsonValueSchema),
29
+ ])
30
+ );
31
+
32
+ export const traceJsonObjectSchema = z.record(z.string(), jsonValueSchema);
33
+
34
+ const SAFE_RECEIPT_STRING_KEYS = new Set([
35
+ "capability",
36
+ "capsuleId",
37
+ "collection",
38
+ "code",
39
+ "docid",
40
+ "filterFingerprint",
41
+ "heading",
42
+ "id",
43
+ "kind",
44
+ "mirrorHash",
45
+ "mode",
46
+ "name",
47
+ "outcome",
48
+ "passageHash",
49
+ "reasonCode",
50
+ "ref",
51
+ "sourceHash",
52
+ "status",
53
+ "type",
54
+ "uri",
55
+ ]);
56
+ const SAFE_RECEIPT_STRING_LIST_KEYS = new Set([
57
+ "capabilities",
58
+ "categories",
59
+ "collections",
60
+ "fallbackCodes",
61
+ "fallbacks",
62
+ "itemTypes",
63
+ "sources",
64
+ "tags",
65
+ ]);
66
+
67
+ const validateReceiptStrings = (
68
+ value: unknown,
69
+ context: z.RefinementCtx,
70
+ key = "",
71
+ path: Array<string | number> = []
72
+ ): void => {
73
+ if (typeof value === "string") {
74
+ if (
75
+ !(
76
+ SAFE_RECEIPT_STRING_KEYS.has(key) ||
77
+ SAFE_RECEIPT_STRING_LIST_KEYS.has(key)
78
+ )
79
+ ) {
80
+ context.addIssue({
81
+ code: "custom",
82
+ path,
83
+ message: `String field ${key || "<root>"} is not allowed in trace receipt payloads`,
84
+ });
85
+ } else if (key === "uri" && !value.startsWith("gno://")) {
86
+ context.addIssue({
87
+ code: "custom",
88
+ path,
89
+ message: "Trace evidence URI must use gno://",
90
+ });
91
+ } else if (traceUtf8Bytes(value) > 4096) {
92
+ context.addIssue({
93
+ code: "custom",
94
+ path,
95
+ message: `String field ${key} exceeds 4096 UTF-8 bytes`,
96
+ });
97
+ }
98
+ return;
99
+ }
100
+ if (Array.isArray(value)) {
101
+ for (const [index, child] of value.entries()) {
102
+ validateReceiptStrings(child, context, key, [...path, index]);
103
+ }
104
+ return;
105
+ }
106
+ if (value !== null && typeof value === "object") {
107
+ for (const [childKey, child] of Object.entries(value)) {
108
+ validateReceiptStrings(child, context, childKey, [...path, childKey]);
109
+ }
110
+ }
111
+ };
112
+
113
+ const traceReceiptPayloadSchema = traceJsonObjectSchema.superRefine(
114
+ (value, context) => validateReceiptStrings(value, context)
115
+ );
116
+
117
+ const queryShapeSchema = z
118
+ .object({
119
+ characters: z.number().int().nonnegative(),
120
+ terms: z.number().int().nonnegative(),
121
+ })
122
+ .strict();
123
+
124
+ const traceInputBaseSchema = z
125
+ .object({
126
+ traceId: idSchema,
127
+ schemaVersion: z.literal("1.0"),
128
+ redactionMode: z.enum(["metadata", "replay"]),
129
+ replayCapable: z.boolean(),
130
+ queryText: z.string().max(8192).nullable(),
131
+ queryDigest: sha256Schema.nullable(),
132
+ queryShape: queryShapeSchema,
133
+ goalText: z.string().max(8192).nullable(),
134
+ goalDigest: sha256Schema.nullable(),
135
+ goalShape: queryShapeSchema,
136
+ filters: traceJsonObjectSchema,
137
+ fingerprints: z
138
+ .object({
139
+ pipeline: sha256Schema,
140
+ model: sha256Schema,
141
+ config: sha256Schema,
142
+ index: sha256Schema,
143
+ })
144
+ .strict(),
145
+ status: z.literal("open"),
146
+ createdAtMs: epochMsSchema,
147
+ updatedAtMs: epochMsSchema,
148
+ expiresAtMs: epochMsSchema,
149
+ })
150
+ .strict();
151
+
152
+ export const retrievalTraceInputSchema = traceInputBaseSchema.superRefine(
153
+ (value, context) => {
154
+ if (value.updatedAtMs < value.createdAtMs) {
155
+ context.addIssue({
156
+ code: "custom",
157
+ path: ["updatedAtMs"],
158
+ message: "updatedAtMs must not precede createdAtMs",
159
+ });
160
+ }
161
+ if (value.updatedAtMs !== value.createdAtMs) {
162
+ context.addIssue({
163
+ code: "custom",
164
+ path: ["updatedAtMs"],
165
+ message: "new traces must start with updatedAtMs equal to createdAtMs",
166
+ });
167
+ }
168
+ if (value.expiresAtMs <= value.createdAtMs) {
169
+ context.addIssue({
170
+ code: "custom",
171
+ path: ["expiresAtMs"],
172
+ message: "expiresAtMs must be later than createdAtMs",
173
+ });
174
+ }
175
+ const metadataInvariant =
176
+ value.redactionMode === "metadata" &&
177
+ !value.replayCapable &&
178
+ value.queryText === null &&
179
+ value.queryDigest === null &&
180
+ value.goalText === null &&
181
+ value.goalDigest === null;
182
+ const replayInvariant =
183
+ value.redactionMode === "replay" &&
184
+ value.replayCapable &&
185
+ value.queryText !== null &&
186
+ value.queryDigest !== null &&
187
+ ((value.goalText === null && value.goalDigest === null) ||
188
+ (value.goalText !== null && value.goalDigest !== null));
189
+ if (!(metadataInvariant || replayInvariant)) {
190
+ context.addIssue({
191
+ code: "custom",
192
+ message:
193
+ "metadata traces cannot retain query material; replay traces require explicit replay inputs",
194
+ });
195
+ }
196
+ if (value.redactionMode === "replay" && value.queryText !== null) {
197
+ const expectedDigest = new Bun.CryptoHasher("sha256")
198
+ .update(value.queryText)
199
+ .digest("hex");
200
+ if (value.queryDigest !== expectedDigest) {
201
+ context.addIssue({
202
+ code: "custom",
203
+ path: ["queryDigest"],
204
+ message: "queryDigest does not match queryText",
205
+ });
206
+ }
207
+ const expectedShape = shapeTraceText(value.queryText);
208
+ if (
209
+ value.queryShape.characters !== expectedShape.characters ||
210
+ value.queryShape.terms !== expectedShape.terms
211
+ ) {
212
+ context.addIssue({
213
+ code: "custom",
214
+ path: ["queryShape"],
215
+ message: "queryShape does not match queryText",
216
+ });
217
+ }
218
+ }
219
+ if (value.redactionMode === "replay" && value.goalText !== null) {
220
+ const expectedDigest = new Bun.CryptoHasher("sha256")
221
+ .update(value.goalText)
222
+ .digest("hex");
223
+ if (value.goalDigest !== expectedDigest) {
224
+ context.addIssue({
225
+ code: "custom",
226
+ path: ["goalDigest"],
227
+ message: "goalDigest does not match goalText",
228
+ });
229
+ }
230
+ const expectedShape = shapeTraceText(value.goalText);
231
+ if (
232
+ value.goalShape.characters !== expectedShape.characters ||
233
+ value.goalShape.terms !== expectedShape.terms
234
+ ) {
235
+ context.addIssue({
236
+ code: "custom",
237
+ path: ["goalShape"],
238
+ message: "goalShape does not match goalText",
239
+ });
240
+ }
241
+ }
242
+ }
243
+ );
244
+
245
+ export const retrievalTraceRunInputSchema = z
246
+ .object({
247
+ runId: idSchema,
248
+ traceId: idSchema,
249
+ idempotencyKey: idempotencyKeySchema,
250
+ kind: z.enum(["retrieval", "context", "get"]),
251
+ payload: traceReceiptPayloadSchema,
252
+ createdAtMs: epochMsSchema,
253
+ })
254
+ .strict();
255
+
256
+ export const retrievalTraceEventInputSchema = z
257
+ .object({
258
+ eventId: idSchema,
259
+ traceId: idSchema,
260
+ runId: idSchema.nullable(),
261
+ idempotencyKey: idempotencyKeySchema,
262
+ kind: z.enum([
263
+ "query",
264
+ "retrieval",
265
+ "context",
266
+ "get",
267
+ "open",
268
+ "cite",
269
+ "pin",
270
+ "capability",
271
+ "complete",
272
+ ]),
273
+ payload: traceReceiptPayloadSchema,
274
+ createdAtMs: epochMsSchema,
275
+ })
276
+ .strict();
277
+
278
+ export const retrievalTraceJudgmentInputSchema = z
279
+ .object({
280
+ judgmentId: idSchema,
281
+ traceId: idSchema,
282
+ runId: idSchema.nullable(),
283
+ idempotencyKey: idempotencyKeySchema,
284
+ label: z.enum(["relevant", "irrelevant", "missing_expected"]),
285
+ targetKind: z.enum(["document", "chunk", "span", "query"]),
286
+ targetRef: z.string().min(1).max(4096),
287
+ target: traceReceiptPayloadSchema,
288
+ createdAtMs: epochMsSchema,
289
+ })
290
+ .strict()
291
+ .superRefine((value, context) => {
292
+ if (value.label === "missing_expected" && value.targetKind !== "document") {
293
+ context.addIssue({
294
+ code: "custom",
295
+ path: ["targetKind"],
296
+ message: "missing_expected judgments must target a document",
297
+ });
298
+ }
299
+ });
300
+
301
+ export const retrievalTraceExportInputSchema = z
302
+ .object({
303
+ exportId: idSchema,
304
+ traceId: idSchema,
305
+ format: z.enum(["agentic-receipt", "qrels"]),
306
+ artifactHash: sha256Schema,
307
+ createdAtMs: epochMsSchema,
308
+ })
309
+ .strict();
310
+
311
+ const compareCodeUnits = (left: string, right: string): number =>
312
+ left < right ? -1 : left > right ? 1 : 0;
313
+
314
+ const canonicalizeJsonValue = (value: unknown): unknown => {
315
+ if (Array.isArray(value)) return value.map(canonicalizeJsonValue);
316
+ if (value !== null && typeof value === "object") {
317
+ const sorted: Record<string, unknown> = Object.create(null) as Record<
318
+ string,
319
+ unknown
320
+ >;
321
+ for (const key of Object.keys(value).sort(compareCodeUnits)) {
322
+ if (DANGEROUS_JSON_KEYS.has(key)) {
323
+ throw new Error(`Canonical JSON rejects dangerous key ${key}`);
324
+ }
325
+ const child = (value as Record<string, unknown>)[key];
326
+ if (child === undefined) {
327
+ throw new Error(`Canonical JSON rejects undefined at ${key}`);
328
+ }
329
+ sorted[key] = canonicalizeJsonValue(child);
330
+ }
331
+ return sorted;
332
+ }
333
+ if (typeof value === "number" && !Number.isFinite(value)) {
334
+ throw new Error("Canonical JSON rejects non-finite numbers");
335
+ }
336
+ return value;
337
+ };
338
+
339
+ const shapeTraceText = (
340
+ value: string
341
+ ): { characters: number; terms: number } => ({
342
+ characters: Array.from(value).length,
343
+ terms: value.trim() ? value.trim().split(/\s+/u).length : 0,
344
+ });
345
+
346
+ export const canonicalTraceJson = (value: unknown): string =>
347
+ JSON.stringify(canonicalizeJsonValue(value));
348
+
349
+ export const traceUtf8Bytes = (value: string): number =>
350
+ new TextEncoder().encode(value).byteLength;
351
+
352
+ export const hashTraceCanonical = (value: unknown): string =>
353
+ new Bun.CryptoHasher("sha256")
354
+ .update(canonicalTraceJson(value))
355
+ .digest("hex");
356
+
357
+ /** Stable trace identity excludes terminal status and mutable updatedAtMs. */
358
+ export const hashRetrievalTraceCreation = (
359
+ trace: RetrievalTraceInput
360
+ ): string => {
361
+ const { status: _status, updatedAtMs: _updatedAtMs, ...immutable } = trace;
362
+ return hashTraceCanonical(immutable);
363
+ };
364
+
365
+ export const parseRetrievalTraceInput = (
366
+ input: RetrievalTraceInput
367
+ ): RetrievalTraceInput => retrievalTraceInputSchema.parse(input);
368
+
369
+ export const parseRetrievalTraceRunInput = (
370
+ input: RetrievalTraceRunInput
371
+ ): RetrievalTraceRunInput => retrievalTraceRunInputSchema.parse(input);
372
+
373
+ export const parseRetrievalTraceEventInput = (
374
+ input: RetrievalTraceEventInput
375
+ ): RetrievalTraceEventInput => retrievalTraceEventInputSchema.parse(input);
376
+
377
+ export const parseRetrievalTraceJudgmentInput = (
378
+ input: RetrievalTraceJudgmentInput
379
+ ): RetrievalTraceJudgmentInput =>
380
+ retrievalTraceJudgmentInputSchema.parse(input);
381
+
382
+ export const parseRetrievalTraceExportInput = (
383
+ input: RetrievalTraceExportInput
384
+ ): RetrievalTraceExportInput => retrievalTraceExportInputSchema.parse(input);
@@ -10,6 +10,8 @@
10
10
  // CRITICAL: Import setup FIRST to configure custom SQLite before any Database use
11
11
  import "./setup";
12
12
  import { Database } from "bun:sqlite";
13
+ // node:async_hooks binds nested transactions to one async request; Bun has no separate native equivalent.
14
+ import { AsyncLocalStorage } from "node:async_hooks";
13
15
  // node:path basename: no Bun path utilities.
14
16
  import { basename } from "node:path";
15
17
 
@@ -50,6 +52,24 @@ import type {
50
52
  IngestErrorInput,
51
53
  IngestErrorRow,
52
54
  MigrationResult,
55
+ RetrievalTraceAppendResult,
56
+ RetrievalTraceBundle,
57
+ RetrievalTraceCursor,
58
+ RetrievalTraceBoundedBundle,
59
+ RetrievalTraceDeleteCounts,
60
+ RetrievalTraceEventInput,
61
+ RetrievalTraceExportInput,
62
+ RetrievalTraceExportBundle,
63
+ RetrievalTraceExportManifestInput,
64
+ RetrievalTraceExportManifestRow,
65
+ RetrievalTraceInput,
66
+ RetrievalTraceJudgmentInput,
67
+ RetrievalTracePurgeResult,
68
+ RetrievalTraceRetentionPolicy,
69
+ RetrievalTraceRetentionResult,
70
+ RetrievalTraceRow,
71
+ RetrievalTraceRunInput,
72
+ RetrievalTraceTerminalStatus,
53
73
  StorePort,
54
74
  StoreResult,
55
75
  TagCount,
@@ -76,6 +96,28 @@ import { err, ok } from "../types";
76
96
  import { getStoredEmbeddingFingerprint } from "../vector/freshness";
77
97
  import { modelTableName } from "../vector/sqlite-vec";
78
98
  import { loadFts5Snowball } from "./fts5-snowball";
99
+ import {
100
+ appendExportManifest as appendStoredTraceExportManifest,
101
+ getBoundedTrace as getBoundedStoredTrace,
102
+ getExportBundle as getStoredTraceExportBundle,
103
+ getExportManifest as getStoredTraceExportManifest,
104
+ getOrCreateRedactionSecret as getOrCreateStoredTraceRedactionSecret,
105
+ } from "./retrieval-trace-management-store";
106
+ import {
107
+ deleteTrace as deleteStoredTrace,
108
+ enforceRetention as enforceStoredTraceRetention,
109
+ purgeTraces as purgeStoredTraces,
110
+ } from "./retrieval-trace-retention";
111
+ import {
112
+ appendEvent as appendStoredTraceEvent,
113
+ appendExport as appendStoredTraceExport,
114
+ appendJudgment as appendStoredTraceJudgment,
115
+ appendRun as appendStoredTraceRun,
116
+ createTrace as createStoredTrace,
117
+ finalizeTrace as finalizeStoredTrace,
118
+ getTrace as getStoredTrace,
119
+ listTraces as listStoredTraces,
120
+ } from "./retrieval-trace-store";
79
121
 
80
122
  // ─────────────────────────────────────────────────────────────────────────────
81
123
  // FTS5 Query Escaping
@@ -312,8 +354,9 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
312
354
  private dbPath = "";
313
355
  private ftsTokenizer: FtsTokenizer = "unicode61";
314
356
  private configPath = ""; // Set by CLI layer for status output
315
- private txDepth = 0; // Transaction nesting depth
316
357
  private txCounter = 0; // Savepoint counter for unique names
358
+ private readonly txContext = new AsyncLocalStorage<{ depth: number }>();
359
+ private txTail: Promise<void> = Promise.resolve();
317
360
  private contextGeneration = 0;
318
361
 
319
362
  // ─────────────────────────────────────────────────────────────────────────
@@ -407,9 +450,12 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
407
450
  */
408
451
  async withTransaction<T>(fn: () => Promise<T>): Promise<StoreResult<T>> {
409
452
  const db = this.ensureOpen();
410
-
411
- const isOuter = this.txDepth === 0;
453
+ const parent = this.txContext.getStore();
454
+ const isOuter = parent === undefined;
412
455
  const savepoint = `sp_${++this.txCounter}`;
456
+ const releaseWriter = isOuter
457
+ ? await this.acquireTransactionWriter()
458
+ : null;
413
459
 
414
460
  try {
415
461
  if (isOuter) {
@@ -419,9 +465,10 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
419
465
  db.exec(`SAVEPOINT ${savepoint}`);
420
466
  }
421
467
 
422
- this.txDepth += 1;
423
- const value = await fn();
424
- this.txDepth -= 1;
468
+ const value = await this.txContext.run(
469
+ { depth: (parent?.depth ?? 0) + 1 },
470
+ fn
471
+ );
425
472
 
426
473
  if (isOuter) {
427
474
  db.exec("COMMIT");
@@ -431,8 +478,6 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
431
478
 
432
479
  return ok(value);
433
480
  } catch (cause) {
434
- this.txDepth = Math.max(0, this.txDepth - 1);
435
-
436
481
  try {
437
482
  if (isOuter) {
438
483
  db.exec("ROLLBACK");
@@ -447,9 +492,21 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
447
492
  const message =
448
493
  cause instanceof Error ? cause.message : "Transaction failed";
449
494
  return err("TRANSACTION_FAILED", message, cause);
495
+ } finally {
496
+ releaseWriter?.();
450
497
  }
451
498
  }
452
499
 
500
+ private async acquireTransactionWriter(): Promise<() => void> {
501
+ const previous = this.txTail;
502
+ let release!: () => void;
503
+ this.txTail = new Promise<void>((resolve) => {
504
+ release = resolve;
505
+ });
506
+ await previous;
507
+ return release;
508
+ }
509
+
453
510
  /**
454
511
  * Set config path for status output (called by CLI layer).
455
512
  */
@@ -765,6 +822,107 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
765
822
  }
766
823
  }
767
824
 
825
+ async createRetrievalTrace(
826
+ input: RetrievalTraceInput
827
+ ): Promise<StoreResult<RetrievalTraceAppendResult>> {
828
+ return createStoredTrace(this.ensureOpen(), input);
829
+ }
830
+
831
+ async getRetrievalTrace(
832
+ traceId: string
833
+ ): Promise<StoreResult<RetrievalTraceBundle | null>> {
834
+ return getStoredTrace(this.ensureOpen(), traceId);
835
+ }
836
+
837
+ async getBoundedRetrievalTrace(
838
+ traceId: string,
839
+ detailLimit: number
840
+ ): Promise<StoreResult<RetrievalTraceBoundedBundle | null>> {
841
+ return getBoundedStoredTrace(this.ensureOpen(), traceId, detailLimit);
842
+ }
843
+
844
+ async listRetrievalTraces(
845
+ limit: number,
846
+ cursor?: RetrievalTraceCursor
847
+ ): Promise<StoreResult<RetrievalTraceRow[]>> {
848
+ return listStoredTraces(this.ensureOpen(), limit, cursor);
849
+ }
850
+
851
+ async finalizeRetrievalTrace(
852
+ traceId: string,
853
+ status: RetrievalTraceTerminalStatus,
854
+ updatedAtMs: number
855
+ ): Promise<StoreResult<RetrievalTraceAppendResult>> {
856
+ return finalizeStoredTrace(this.ensureOpen(), traceId, status, updatedAtMs);
857
+ }
858
+
859
+ async appendRetrievalTraceRun(
860
+ input: RetrievalTraceRunInput
861
+ ): Promise<StoreResult<RetrievalTraceAppendResult>> {
862
+ return appendStoredTraceRun(this.ensureOpen(), input);
863
+ }
864
+
865
+ async appendRetrievalTraceEvent(
866
+ input: RetrievalTraceEventInput
867
+ ): Promise<StoreResult<RetrievalTraceAppendResult>> {
868
+ return appendStoredTraceEvent(this.ensureOpen(), input);
869
+ }
870
+
871
+ async appendRetrievalTraceJudgment(
872
+ input: RetrievalTraceJudgmentInput
873
+ ): Promise<StoreResult<RetrievalTraceAppendResult>> {
874
+ return appendStoredTraceJudgment(this.ensureOpen(), input);
875
+ }
876
+
877
+ async appendRetrievalTraceExport(
878
+ input: RetrievalTraceExportInput
879
+ ): Promise<StoreResult<RetrievalTraceAppendResult>> {
880
+ return appendStoredTraceExport(this.ensureOpen(), input);
881
+ }
882
+
883
+ async appendRetrievalTraceExportManifest(
884
+ input: RetrievalTraceExportManifestInput
885
+ ): Promise<StoreResult<RetrievalTraceAppendResult>> {
886
+ return appendStoredTraceExportManifest(this.ensureOpen(), input);
887
+ }
888
+
889
+ async getRetrievalTraceExportManifest(
890
+ exportId: string
891
+ ): Promise<StoreResult<RetrievalTraceExportManifestRow | null>> {
892
+ return getStoredTraceExportManifest(this.ensureOpen(), exportId);
893
+ }
894
+
895
+ async getRetrievalTraceExportBundle(
896
+ exportId: string
897
+ ): Promise<StoreResult<RetrievalTraceExportBundle | null>> {
898
+ return getStoredTraceExportBundle(this.ensureOpen(), exportId);
899
+ }
900
+
901
+ async getOrCreateRetrievalTraceRedactionSecret(): Promise<
902
+ StoreResult<string>
903
+ > {
904
+ return getOrCreateStoredTraceRedactionSecret(this.ensureOpen());
905
+ }
906
+
907
+ async deleteRetrievalTrace(
908
+ traceId: string
909
+ ): Promise<StoreResult<RetrievalTraceDeleteCounts>> {
910
+ return deleteStoredTrace(this.ensureOpen(), traceId);
911
+ }
912
+
913
+ async purgeRetrievalTraces(): Promise<
914
+ StoreResult<RetrievalTracePurgeResult>
915
+ > {
916
+ return purgeStoredTraces(this.ensureOpen());
917
+ }
918
+
919
+ async enforceRetrievalTraceRetention(
920
+ policy: RetrievalTraceRetentionPolicy,
921
+ nowMs: number
922
+ ): Promise<StoreResult<RetrievalTraceRetentionResult>> {
923
+ return enforceStoredTraceRetention(this.ensureOpen(), policy, nowMs);
924
+ }
925
+
768
926
  getContextGeneration(): number {
769
927
  return this.contextGeneration;
770
928
  }
@@ -1635,6 +1793,11 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
1635
1793
  SELECT id FROM documents
1636
1794
  WHERE active = 1
1637
1795
  ${options.collection ? "AND collection = ?" : ""}
1796
+ ${
1797
+ options.relPathPrefix !== undefined
1798
+ ? "AND (rel_path = ? OR substr(rel_path, 1, length(?) + 1) = ? || '/')"
1799
+ : ""
1800
+ }
1638
1801
  )
1639
1802
  ORDER BY score
1640
1803
  LIMIT ?
@@ -1688,6 +1851,13 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
1688
1851
  const queryParams = [
1689
1852
  builtQuery.query,
1690
1853
  ...(options.collection ? [options.collection] : []),
1854
+ ...(options.relPathPrefix !== undefined
1855
+ ? [
1856
+ options.relPathPrefix,
1857
+ options.relPathPrefix,
1858
+ options.relPathPrefix,
1859
+ ]
1860
+ : []),
1691
1861
  ftsLimit,
1692
1862
  ...params,
1693
1863
  ];
@@ -4232,7 +4402,7 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
4232
4402
  // Last updated (max updated_at from documents)
4233
4403
  const lastUpdatedRow = db
4234
4404
  .query<{ last_updated: string | null }, []>(
4235
- "SELECT MAX(updated_at) as last_updated FROM documents"
4405
+ "SELECT strftime('%Y-%m-%dT%H:%M:%fZ', MAX(updated_at)) as last_updated FROM documents"
4236
4406
  )
4237
4407
  .get();
4238
4408
 
@@ -4605,7 +4775,6 @@ interface DbIngestErrorRow {
4605
4775
  details_json: string | null;
4606
4776
  }
4607
4777
 
4608
- // ─────────────────────────────────────────────────────────────────────────────
4609
4778
  // Row Mappers (snake_case -> camelCase)
4610
4779
  // ─────────────────────────────────────────────────────────────────────────────
4611
4780