@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.
- package/README.md +18 -4
- package/assets/skill/SKILL.md +44 -1
- package/assets/skill/mcp-reference.md +21 -0
- package/package.json +3 -2
- package/spec/AGENTS.md +83 -0
- package/spec/CLAUDE.md +83 -0
- package/spec/bench-fixture.schema.json +137 -0
- package/spec/cli.md +2894 -0
- package/spec/db/schema.sql +442 -0
- package/spec/evals-agentic.md +510 -0
- package/spec/evals.md +1106 -0
- package/spec/mcp.md +2229 -0
- package/spec/output-schemas/activation-verification.schema.json +515 -0
- package/spec/output-schemas/ask.schema.json +366 -0
- package/spec/output-schemas/backlinks.schema.json +131 -0
- package/spec/output-schemas/bench-result.schema.json +120 -0
- package/spec/output-schemas/capture-receipt.schema.json +143 -0
- package/spec/output-schemas/collection-list.schema.json +45 -0
- package/spec/output-schemas/context-capsule-v1.schema.json +691 -0
- package/spec/output-schemas/context-capsule-verification.schema.json +1338 -0
- package/spec/output-schemas/context-list.schema.json +21 -0
- package/spec/output-schemas/doctor.schema.json +313 -0
- package/spec/output-schemas/error.schema.json +30 -0
- package/spec/output-schemas/expansion.schema.json +37 -0
- package/spec/output-schemas/get.schema.json +140 -0
- package/spec/output-schemas/graph-query.schema.json +99 -0
- package/spec/output-schemas/graph.schema.json +371 -0
- package/spec/output-schemas/links-list.schema.json +186 -0
- package/spec/output-schemas/mcp-add-collection-result.schema.json +23 -0
- package/spec/output-schemas/mcp-capture-result.schema.json +152 -0
- package/spec/output-schemas/mcp-http-error.schema.json +30 -0
- package/spec/output-schemas/mcp-job-list.schema.json +58 -0
- package/spec/output-schemas/mcp-job-status.schema.json +224 -0
- package/spec/output-schemas/mcp-remove-result.schema.json +39 -0
- package/spec/output-schemas/mcp-sync-result.schema.json +41 -0
- package/spec/output-schemas/mcp-tag-result.schema.json +33 -0
- package/spec/output-schemas/models-list.schema.json +93 -0
- package/spec/output-schemas/multi-get.schema.json +103 -0
- package/spec/output-schemas/process-status.schema.json +119 -0
- package/spec/output-schemas/query-diagnose.schema.json +123 -0
- package/spec/output-schemas/resident-status.schema.json +154 -0
- package/spec/output-schemas/retrieval-trace-common.schema.json +492 -0
- package/spec/output-schemas/retrieval-trace-delete.schema.json +16 -0
- package/spec/output-schemas/retrieval-trace-export.schema.json +61 -0
- package/spec/output-schemas/retrieval-trace-filters.schema.json +139 -0
- package/spec/output-schemas/retrieval-trace-judgment.schema.json +15 -0
- package/spec/output-schemas/retrieval-trace-list.schema.json +18 -0
- package/spec/output-schemas/retrieval-trace-payloads.schema.json +178 -0
- package/spec/output-schemas/retrieval-trace-purge.schema.json +31 -0
- package/spec/output-schemas/retrieval-trace-qrels.schema.json +303 -0
- package/spec/output-schemas/retrieval-trace-replay.schema.json +286 -0
- package/spec/output-schemas/retrieval-trace-show.schema.json +69 -0
- package/spec/output-schemas/retrieval-trace-summary.schema.json +65 -0
- package/spec/output-schemas/search-result.schema.json +154 -0
- package/spec/output-schemas/search-results.schema.json +338 -0
- package/spec/output-schemas/similar.schema.json +84 -0
- package/spec/output-schemas/status.schema.json +676 -0
- package/spec/output-schemas/tags-list.schema.json +48 -0
- package/src/app/context-runtime-types.ts +3 -0
- package/src/app/context-runtime.ts +15 -1
- package/src/cli/commands/ask.ts +106 -36
- package/src/cli/commands/context-build.ts +56 -9
- package/src/cli/commands/daemon.ts +69 -2
- package/src/cli/commands/get.ts +64 -3
- package/src/cli/commands/models/pull.ts +13 -3
- package/src/cli/commands/query.ts +62 -23
- package/src/cli/commands/replay.ts +140 -0
- package/src/cli/commands/search.ts +48 -3
- package/src/cli/commands/shared.ts +3 -1
- package/src/cli/commands/status.ts +2 -0
- package/src/cli/commands/trace.ts +200 -0
- package/src/cli/commands/vsearch.ts +75 -53
- package/src/cli/detach.ts +37 -20
- package/src/cli/program.ts +329 -27
- package/src/config/index.ts +12 -0
- package/src/config/retrieval-traces.ts +56 -0
- package/src/config/types.ts +41 -0
- package/src/core/context-compiler.ts +11 -4
- package/src/core/job-manager.ts +19 -0
- package/src/core/mutation-generations.ts +33 -0
- package/src/core/retrieval-qrels.ts +405 -0
- package/src/core/retrieval-replay-candidate.ts +368 -0
- package/src/core/retrieval-replay-types.ts +109 -0
- package/src/core/retrieval-replay-validation.ts +89 -0
- package/src/core/retrieval-replay.ts +441 -0
- package/src/core/retrieval-trace-evidence-origin.ts +175 -0
- package/src/core/retrieval-trace-export.ts +113 -0
- package/src/core/retrieval-trace-filter-normalization.ts +27 -0
- package/src/core/retrieval-trace-filters.ts +19 -0
- package/src/core/retrieval-trace-management-helpers.ts +247 -0
- package/src/core/retrieval-trace-management-types.ts +132 -0
- package/src/core/retrieval-trace-management.ts +422 -0
- package/src/core/retrieval-trace-request.ts +141 -0
- package/src/core/retrieval-trace-session.ts +494 -0
- package/src/core/retrieval-trace.ts +472 -0
- package/src/llm/cache.ts +13 -3
- package/src/llm/nodeLlamaCpp/adapter.ts +10 -1
- package/src/llm/nodeLlamaCpp/lifecycle.ts +71 -0
- package/src/mcp/context.ts +161 -0
- package/src/mcp/http-security.ts +477 -0
- package/src/mcp/http-session.ts +272 -0
- package/src/mcp/http-transport.ts +370 -0
- package/src/mcp/resources/index.ts +141 -134
- package/src/mcp/server.ts +19 -79
- package/src/mcp/tools/add-collection.ts +3 -1
- package/src/mcp/tools/capture.ts +3 -0
- package/src/mcp/tools/clear-collection-embeddings.ts +2 -0
- package/src/mcp/tools/context.ts +68 -16
- package/src/mcp/tools/embed.ts +62 -52
- package/src/mcp/tools/get.ts +35 -1
- package/src/mcp/tools/index-cmd.ts +88 -74
- package/src/mcp/tools/index.ts +96 -2
- package/src/mcp/tools/query.ts +95 -64
- package/src/mcp/tools/remove-collection.ts +2 -0
- package/src/mcp/tools/search.ts +36 -13
- package/src/mcp/tools/status.ts +11 -0
- package/src/mcp/tools/sync.ts +16 -14
- package/src/mcp/tools/trace.ts +143 -0
- package/src/mcp/tools/vsearch.ts +71 -38
- package/src/mcp/tools/workspace-write.ts +7 -3
- package/src/pipeline/answer.ts +167 -26
- package/src/pipeline/graph-retrieval.ts +15 -1
- package/src/pipeline/hybrid.ts +151 -43
- package/src/pipeline/search.ts +36 -3
- package/src/pipeline/trace-metadata.ts +47 -0
- package/src/pipeline/types.ts +43 -0
- package/src/pipeline/vsearch.ts +101 -38
- package/src/sdk/client.ts +380 -71
- package/src/sdk/documents.ts +48 -1
- package/src/sdk/index.ts +17 -0
- package/src/sdk/types.ts +28 -0
- package/src/serve/background-runtime.ts +12 -212
- package/src/serve/context-capsule.ts +67 -8
- package/src/serve/embed-scheduler.ts +74 -43
- package/src/serve/index.ts +9 -0
- package/src/serve/jobs.ts +78 -80
- package/src/serve/public/app.tsx +12 -1
- package/src/serve/public/components/HealthCenter.tsx +74 -1
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/lib/workspace-tabs.ts +2 -0
- package/src/serve/public/pages/Dashboard.tsx +11 -0
- package/src/serve/public/pages/TraceHistory.tsx +478 -0
- package/src/serve/public/pages/trace-history-detail.tsx +224 -0
- package/src/serve/resident-admission.ts +159 -0
- package/src/serve/resident-background-work.ts +39 -0
- package/src/serve/resident-request.ts +55 -0
- package/src/serve/resident-runtime.ts +490 -0
- package/src/serve/resident-status.ts +96 -0
- package/src/serve/retrieval-trace.ts +28 -0
- package/src/serve/routes/api.ts +629 -239
- package/src/serve/routes/mcp.ts +69 -0
- package/src/serve/routes/traces.ts +156 -0
- package/src/serve/server.ts +276 -37
- package/src/serve/status-model.ts +51 -0
- package/src/serve/status.ts +5 -0
- package/src/store/index.ts +31 -0
- package/src/store/migrations/014-retrieval-traces.ts +303 -0
- package/src/store/migrations/index.ts +2 -0
- package/src/store/retrieval-trace-codec.ts +384 -0
- package/src/store/sqlite/adapter.ts +179 -10
- package/src/store/sqlite/retrieval-trace-management-store.ts +341 -0
- package/src/store/sqlite/retrieval-trace-retention.ts +349 -0
- package/src/store/sqlite/retrieval-trace-rows.ts +267 -0
- package/src/store/sqlite/retrieval-trace-store.ts +515 -0
- package/src/store/types.ts +297 -0
- package/src/store/vector/sqlite-vec.ts +76 -1
- 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 =
|
|
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.
|
|
423
|
-
|
|
424
|
-
|
|
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
|
|