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