@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.
- package/README.md +5 -3
- package/assets/skill/SKILL.md +27 -0
- package/package.json +2 -1
- 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/get.ts +64 -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/trace.ts +200 -0
- package/src/cli/commands/vsearch.ts +75 -53
- package/src/cli/program.ts +255 -0
- package/src/config/index.ts +9 -0
- package/src/config/retrieval-traces.ts +56 -0
- package/src/config/types.ts +4 -0
- package/src/core/context-compiler.ts +11 -4
- 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/mcp/tools/context.ts +59 -8
- package/src/mcp/tools/get.ts +35 -1
- package/src/mcp/tools/index.ts +74 -0
- package/src/mcp/tools/query.ts +95 -64
- package/src/mcp/tools/search.ts +36 -13
- package/src/mcp/tools/trace.ts +143 -0
- package/src/mcp/tools/vsearch.ts +71 -38
- 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/context-capsule.ts +67 -8
- package/src/serve/public/app.tsx +12 -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 +10 -0
- package/src/serve/public/pages/TraceHistory.tsx +478 -0
- package/src/serve/public/pages/trace-history-detail.tsx +224 -0
- package/src/serve/retrieval-trace.ts +28 -0
- package/src/serve/routes/api.ts +366 -72
- package/src/serve/routes/traces.ts +156 -0
- package/src/serve/server.ts +87 -2
- 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 +153 -1
- 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,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
|
+
};
|