@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,494 @@
|
|
|
1
|
+
/** Shared query-to-evidence trace lifecycle used by every application surface. */
|
|
2
|
+
|
|
3
|
+
import type { RetrievalTraceConfig } from "../config/retrieval-traces";
|
|
4
|
+
import type { SearchResult, SearchResults } from "../pipeline/types";
|
|
5
|
+
import type {
|
|
6
|
+
RetrievalTraceEventKind,
|
|
7
|
+
RetrievalTraceFingerprints,
|
|
8
|
+
RetrievalTraceTerminalStatus,
|
|
9
|
+
StorePort,
|
|
10
|
+
StoreResult,
|
|
11
|
+
} from "../store/types";
|
|
12
|
+
import type { ContextCapsuleV1 } from "./context-capsule";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
SEARCH_RESULT_PLANNER_METADATA,
|
|
16
|
+
SEARCH_RESULTS_TRACE_METADATA,
|
|
17
|
+
} from "../pipeline/types";
|
|
18
|
+
import { err, ok } from "../store/types";
|
|
19
|
+
import {
|
|
20
|
+
MIN_RETRIEVAL_TRACE_RECORDS,
|
|
21
|
+
RetrievalTraceRecorder,
|
|
22
|
+
type RetrievalTraceWriteResult,
|
|
23
|
+
} from "./retrieval-trace";
|
|
24
|
+
import {
|
|
25
|
+
type RetrievalTraceEvidence,
|
|
26
|
+
RetrievalTraceEvidenceOrigins,
|
|
27
|
+
} from "./retrieval-trace-evidence-origin";
|
|
28
|
+
|
|
29
|
+
type EvidenceEventKind = Extract<
|
|
30
|
+
RetrievalTraceEventKind,
|
|
31
|
+
"get" | "open" | "cite" | "pin"
|
|
32
|
+
>;
|
|
33
|
+
|
|
34
|
+
export interface StartRetrievalTraceSessionInput {
|
|
35
|
+
store: StorePort;
|
|
36
|
+
config: RetrievalTraceConfig | undefined;
|
|
37
|
+
query: string;
|
|
38
|
+
goal?: string;
|
|
39
|
+
filters?: Record<string, unknown>;
|
|
40
|
+
/**
|
|
41
|
+
* Lazy by contract: disabled tracing must not perform fingerprint or index
|
|
42
|
+
* work merely to discover that no receipt will be written.
|
|
43
|
+
*/
|
|
44
|
+
fingerprints: () =>
|
|
45
|
+
| RetrievalTraceFingerprints
|
|
46
|
+
| Promise<RetrievalTraceFingerprints>;
|
|
47
|
+
clock?: () => number;
|
|
48
|
+
idFactory?: () => string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface RetrievalTraceSurfaceMetadata {
|
|
52
|
+
traceId: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const RETRIEVAL_TRACE_METADATA = Symbol("gno.retrievalTraceMetadata");
|
|
56
|
+
|
|
57
|
+
export const attachRetrievalTraceMetadata = <T extends object>(
|
|
58
|
+
value: T,
|
|
59
|
+
session: RetrievalTraceSession | undefined
|
|
60
|
+
): T => {
|
|
61
|
+
const metadata = session?.metadata();
|
|
62
|
+
if (!metadata) return value;
|
|
63
|
+
Object.defineProperty(value, RETRIEVAL_TRACE_METADATA, {
|
|
64
|
+
configurable: false,
|
|
65
|
+
enumerable: false,
|
|
66
|
+
value: metadata,
|
|
67
|
+
writable: false,
|
|
68
|
+
});
|
|
69
|
+
return value;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const getRetrievalTraceMetadata = (
|
|
73
|
+
value: object
|
|
74
|
+
): RetrievalTraceSurfaceMetadata | undefined =>
|
|
75
|
+
(value as { [RETRIEVAL_TRACE_METADATA]?: RetrievalTraceSurfaceMetadata })[
|
|
76
|
+
RETRIEVAL_TRACE_METADATA
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
const evidenceFromResult = (
|
|
80
|
+
result: SearchResult,
|
|
81
|
+
fallbackRank: number
|
|
82
|
+
): RetrievalTraceEvidence | null => {
|
|
83
|
+
const metadata = result[SEARCH_RESULT_PLANNER_METADATA];
|
|
84
|
+
const sourceHash = result.source.sourceHash;
|
|
85
|
+
const mirrorHash = metadata?.mirrorHash ?? result.conversion?.mirrorHash;
|
|
86
|
+
const startLine = metadata?.startLine ?? result.snippetRange?.startLine;
|
|
87
|
+
const endLine = metadata?.endLine ?? result.snippetRange?.endLine;
|
|
88
|
+
const passageHash = metadata?.passageHash;
|
|
89
|
+
if (
|
|
90
|
+
!sourceHash ||
|
|
91
|
+
!mirrorHash ||
|
|
92
|
+
!result.uri.startsWith("gno://") ||
|
|
93
|
+
startLine === undefined ||
|
|
94
|
+
endLine === undefined ||
|
|
95
|
+
!passageHash
|
|
96
|
+
) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
docid: result.docid,
|
|
101
|
+
sourceHash,
|
|
102
|
+
mirrorHash,
|
|
103
|
+
uri: result.uri,
|
|
104
|
+
startLine,
|
|
105
|
+
endLine,
|
|
106
|
+
passageHash,
|
|
107
|
+
rank: fallbackRank,
|
|
108
|
+
score: result.score,
|
|
109
|
+
...(metadata?.seq === undefined ? {} : { seq: metadata.seq }),
|
|
110
|
+
...(metadata?.retrievalRank === undefined
|
|
111
|
+
? {}
|
|
112
|
+
: { plannerRank: metadata.retrievalRank }),
|
|
113
|
+
...(metadata?.sources === undefined ? {} : { sources: metadata.sources }),
|
|
114
|
+
...(metadata?.graphExpanded === undefined
|
|
115
|
+
? {}
|
|
116
|
+
: { graphExpanded: metadata.graphExpanded }),
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const evidenceFromCapsule = (
|
|
121
|
+
capsule: ContextCapsuleV1
|
|
122
|
+
): RetrievalTraceEvidence[] =>
|
|
123
|
+
capsule.evidence.map((item) => ({
|
|
124
|
+
docid: item.docid,
|
|
125
|
+
sourceHash: item.sourceHash,
|
|
126
|
+
mirrorHash: item.mirrorHash,
|
|
127
|
+
uri: item.uri,
|
|
128
|
+
startLine: item.startLine,
|
|
129
|
+
endLine: item.endLine,
|
|
130
|
+
passageHash: item.passageHash,
|
|
131
|
+
rank: item.retrievalRank,
|
|
132
|
+
}));
|
|
133
|
+
|
|
134
|
+
export class RetrievalTraceSession {
|
|
135
|
+
readonly traceId: string;
|
|
136
|
+
|
|
137
|
+
private counter = 0;
|
|
138
|
+
private available = true;
|
|
139
|
+
private persistedRecords = 0;
|
|
140
|
+
private terminal = false;
|
|
141
|
+
private retrievalRunId: string | null = null;
|
|
142
|
+
private readonly evidenceOrigins = new RetrievalTraceEvidenceOrigins();
|
|
143
|
+
|
|
144
|
+
private constructor(
|
|
145
|
+
private readonly recorder: RetrievalTraceRecorder,
|
|
146
|
+
traceId: string,
|
|
147
|
+
private readonly clock: () => number,
|
|
148
|
+
private readonly operationId = traceId,
|
|
149
|
+
private readonly maxRecords = 100_000
|
|
150
|
+
) {
|
|
151
|
+
this.traceId = traceId;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
static async start(
|
|
155
|
+
input: StartRetrievalTraceSessionInput
|
|
156
|
+
): Promise<StoreResult<RetrievalTraceSession | null>> {
|
|
157
|
+
if (input.config?.enabled !== true) return ok(null);
|
|
158
|
+
if (
|
|
159
|
+
input.config.retention.maxRecordsPerTrace < MIN_RETRIEVAL_TRACE_RECORDS
|
|
160
|
+
) {
|
|
161
|
+
return ok(null);
|
|
162
|
+
}
|
|
163
|
+
const rawClock = input.clock ?? Date.now;
|
|
164
|
+
let lastTimestamp = -1;
|
|
165
|
+
const clock = (): number => {
|
|
166
|
+
const current = rawClock();
|
|
167
|
+
lastTimestamp = Math.max(current, lastTimestamp + 1);
|
|
168
|
+
return lastTimestamp;
|
|
169
|
+
};
|
|
170
|
+
const recorder = new RetrievalTraceRecorder(input.store, input.config, {
|
|
171
|
+
clock,
|
|
172
|
+
idFactory: input.idFactory,
|
|
173
|
+
});
|
|
174
|
+
const started = await recorder.start({
|
|
175
|
+
query: input.query,
|
|
176
|
+
goal: input.goal,
|
|
177
|
+
filters: input.filters,
|
|
178
|
+
fingerprints: await input.fingerprints(),
|
|
179
|
+
});
|
|
180
|
+
if (!started.ok) return started;
|
|
181
|
+
if (!started.value.recorded) return ok(null);
|
|
182
|
+
const session = new RetrievalTraceSession(
|
|
183
|
+
recorder,
|
|
184
|
+
started.value.traceId,
|
|
185
|
+
clock,
|
|
186
|
+
started.value.traceId,
|
|
187
|
+
input.config.retention.maxRecordsPerTrace
|
|
188
|
+
);
|
|
189
|
+
const queryEvent = await session.appendEvent("query", {});
|
|
190
|
+
if (queryEvent.ok) return ok(session);
|
|
191
|
+
await recorder.finalize(started.value.traceId, "failed");
|
|
192
|
+
return ok(null);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
static async resume(input: {
|
|
196
|
+
store: StorePort;
|
|
197
|
+
config: RetrievalTraceConfig | undefined;
|
|
198
|
+
traceId: string;
|
|
199
|
+
clock?: () => number;
|
|
200
|
+
}): Promise<StoreResult<RetrievalTraceSession | null>> {
|
|
201
|
+
if (input.config?.enabled !== true) return ok(null);
|
|
202
|
+
const stored = await input.store.getRetrievalTrace(input.traceId);
|
|
203
|
+
if (!stored.ok) return stored;
|
|
204
|
+
if (!stored.value) {
|
|
205
|
+
return err("NOT_FOUND", `Retrieval trace ${input.traceId} was not found`);
|
|
206
|
+
}
|
|
207
|
+
if (stored.value.trace.status !== "open") {
|
|
208
|
+
return err(
|
|
209
|
+
"CONSTRAINT_VIOLATION",
|
|
210
|
+
`Retrieval trace ${input.traceId} is already ${stored.value.trace.status}`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
const rawClock = input.clock ?? Date.now;
|
|
214
|
+
let lastTimestamp = Math.max(
|
|
215
|
+
stored.value.trace.updatedAtMs,
|
|
216
|
+
...stored.value.runs.map((run) => run.createdAtMs),
|
|
217
|
+
...stored.value.events.map((event) => event.createdAtMs)
|
|
218
|
+
);
|
|
219
|
+
const clock = (): number => {
|
|
220
|
+
lastTimestamp = Math.max(rawClock(), lastTimestamp + 1);
|
|
221
|
+
return lastTimestamp;
|
|
222
|
+
};
|
|
223
|
+
const session = new RetrievalTraceSession(
|
|
224
|
+
new RetrievalTraceRecorder(input.store, input.config, { clock }),
|
|
225
|
+
input.traceId,
|
|
226
|
+
clock,
|
|
227
|
+
crypto.randomUUID(),
|
|
228
|
+
input.config.retention.maxRecordsPerTrace
|
|
229
|
+
);
|
|
230
|
+
session.persistedRecords =
|
|
231
|
+
stored.value.runs.length +
|
|
232
|
+
stored.value.events.length +
|
|
233
|
+
stored.value.judgments.length +
|
|
234
|
+
stored.value.exports.length;
|
|
235
|
+
session.retrievalRunId =
|
|
236
|
+
stored.value.runs.filter((run) => run.kind === "retrieval").at(-1)
|
|
237
|
+
?.runId ?? null;
|
|
238
|
+
session.evidenceOrigins.addStoredRuns(stored.value.runs);
|
|
239
|
+
return ok(session);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
metadata(): RetrievalTraceSurfaceMetadata | undefined {
|
|
243
|
+
return this.available ? { traceId: this.traceId } : undefined;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async recordRetrieval(
|
|
247
|
+
result: SearchResults,
|
|
248
|
+
latencyMs?: number
|
|
249
|
+
): Promise<StoreResult<"inserted" | "duplicate" | "disabled">> {
|
|
250
|
+
if (!this.hasCapacity(2)) return ok("disabled");
|
|
251
|
+
const ranked = result.results.flatMap((item, index) => {
|
|
252
|
+
const evidence = evidenceFromResult(item, index + 1);
|
|
253
|
+
return evidence ? [evidence] : [];
|
|
254
|
+
});
|
|
255
|
+
const traceMetadata = result[SEARCH_RESULTS_TRACE_METADATA];
|
|
256
|
+
const capabilityOutcomes =
|
|
257
|
+
traceMetadata?.capabilityOutcomes ??
|
|
258
|
+
(result.meta.mode === "vector"
|
|
259
|
+
? [{ capability: "semantic_search", status: "used" as const }]
|
|
260
|
+
: [
|
|
261
|
+
{ capability: "lexical_search", status: "used" as const },
|
|
262
|
+
...(result.meta.vectorsUsed
|
|
263
|
+
? [{ capability: "semantic_search", status: "used" as const }]
|
|
264
|
+
: []),
|
|
265
|
+
]);
|
|
266
|
+
const capabilities = capabilityOutcomes
|
|
267
|
+
.filter((outcome) => outcome.status === "used")
|
|
268
|
+
.map((outcome) => outcome.capability);
|
|
269
|
+
const fallbackCodes =
|
|
270
|
+
traceMetadata?.fallbackCodes ??
|
|
271
|
+
result.meta.graphExpansion?.fallbackReasons ??
|
|
272
|
+
[];
|
|
273
|
+
const payload = {
|
|
274
|
+
ranked,
|
|
275
|
+
capabilities,
|
|
276
|
+
fallbackCodes,
|
|
277
|
+
...(latencyMs === undefined ? {} : { latencyMs }),
|
|
278
|
+
};
|
|
279
|
+
const runId = this.nextId("retrieval-run");
|
|
280
|
+
const run = await this.recorder.appendRun({
|
|
281
|
+
runId,
|
|
282
|
+
traceId: this.traceId,
|
|
283
|
+
idempotencyKey: runId,
|
|
284
|
+
kind: "retrieval",
|
|
285
|
+
payload,
|
|
286
|
+
createdAtMs: this.clock(),
|
|
287
|
+
});
|
|
288
|
+
if (!run.ok) return this.softenWriteFailure();
|
|
289
|
+
this.persistedRecords += 1;
|
|
290
|
+
this.retrievalRunId = runId;
|
|
291
|
+
const retrievalEvent = await this.appendEvent("retrieval", payload, runId);
|
|
292
|
+
if (!retrievalEvent.ok) return retrievalEvent;
|
|
293
|
+
this.evidenceOrigins.add(runId, ranked);
|
|
294
|
+
for (const outcome of capabilityOutcomes) {
|
|
295
|
+
const recorded = await this.recordCapability(
|
|
296
|
+
outcome.capability,
|
|
297
|
+
outcome.status,
|
|
298
|
+
outcome.reasonCode,
|
|
299
|
+
runId
|
|
300
|
+
);
|
|
301
|
+
if (!recorded.ok) return recorded;
|
|
302
|
+
}
|
|
303
|
+
return retrievalEvent;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async recordContext(
|
|
307
|
+
capsule: ContextCapsuleV1,
|
|
308
|
+
latencyMs?: number
|
|
309
|
+
): Promise<StoreResult<"inserted" | "duplicate" | "disabled">> {
|
|
310
|
+
if (!this.hasCapacity(2)) return ok("disabled");
|
|
311
|
+
const payload = {
|
|
312
|
+
evidence: evidenceFromCapsule(capsule),
|
|
313
|
+
capsuleId: capsule.capsuleId,
|
|
314
|
+
...(latencyMs === undefined ? {} : { latencyMs }),
|
|
315
|
+
};
|
|
316
|
+
const runId = this.nextId("context-run");
|
|
317
|
+
const run = await this.recorder.appendRun({
|
|
318
|
+
runId,
|
|
319
|
+
traceId: this.traceId,
|
|
320
|
+
idempotencyKey: runId,
|
|
321
|
+
kind: "context",
|
|
322
|
+
payload,
|
|
323
|
+
createdAtMs: this.clock(),
|
|
324
|
+
});
|
|
325
|
+
if (!run.ok) return this.softenWriteFailure();
|
|
326
|
+
this.persistedRecords += 1;
|
|
327
|
+
return this.appendEvent("context", payload, runId);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async recordEvidence(
|
|
331
|
+
kind: EvidenceEventKind,
|
|
332
|
+
evidence: RetrievalTraceEvidence[],
|
|
333
|
+
latencyMs?: number
|
|
334
|
+
): Promise<StoreResult<"inserted" | "duplicate" | "disabled">> {
|
|
335
|
+
const payload = {
|
|
336
|
+
evidence,
|
|
337
|
+
...(latencyMs === undefined ? {} : { latencyMs }),
|
|
338
|
+
};
|
|
339
|
+
if (kind === "get") {
|
|
340
|
+
if (!this.hasCapacity(2)) return ok("disabled");
|
|
341
|
+
const runId = this.nextId("get-run");
|
|
342
|
+
const run = await this.recorder.appendRun({
|
|
343
|
+
runId,
|
|
344
|
+
traceId: this.traceId,
|
|
345
|
+
idempotencyKey: runId,
|
|
346
|
+
kind,
|
|
347
|
+
payload,
|
|
348
|
+
createdAtMs: this.clock(),
|
|
349
|
+
});
|
|
350
|
+
if (!run.ok) return this.softenWriteFailure();
|
|
351
|
+
this.persistedRecords += 1;
|
|
352
|
+
const appended = await this.appendEvent(kind, payload, runId);
|
|
353
|
+
if (appended.ok && appended.value !== "disabled") {
|
|
354
|
+
this.evidenceOrigins.addFallback(runId, evidence);
|
|
355
|
+
}
|
|
356
|
+
return appended;
|
|
357
|
+
}
|
|
358
|
+
if (!this.hasCapacity(1)) return ok("disabled");
|
|
359
|
+
const origins = this.evidenceOrigins.group(evidence);
|
|
360
|
+
if (!origins.ok) return origins;
|
|
361
|
+
if (!this.hasCapacity(origins.value.size)) return ok("disabled");
|
|
362
|
+
let result: "inserted" | "duplicate" | "disabled" = "inserted";
|
|
363
|
+
for (const [runId, runEvidence] of origins.value) {
|
|
364
|
+
const appended = await this.appendEvent(
|
|
365
|
+
kind,
|
|
366
|
+
{
|
|
367
|
+
evidence: runEvidence,
|
|
368
|
+
...(latencyMs === undefined ? {} : { latencyMs }),
|
|
369
|
+
},
|
|
370
|
+
runId
|
|
371
|
+
);
|
|
372
|
+
if (!appended.ok) return appended;
|
|
373
|
+
result = appended.value;
|
|
374
|
+
}
|
|
375
|
+
return ok(result);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async recordCapability(
|
|
379
|
+
capability: string,
|
|
380
|
+
status: "attempted" | "used" | "unavailable" | "failed",
|
|
381
|
+
reasonCode?: string,
|
|
382
|
+
runId = this.retrievalRunId
|
|
383
|
+
): Promise<StoreResult<"inserted" | "duplicate" | "disabled">> {
|
|
384
|
+
if (!this.hasCapacity(1)) return ok("disabled");
|
|
385
|
+
return this.appendEvent(
|
|
386
|
+
"capability",
|
|
387
|
+
{
|
|
388
|
+
capability,
|
|
389
|
+
status,
|
|
390
|
+
...(reasonCode === undefined ? {} : { reasonCode }),
|
|
391
|
+
},
|
|
392
|
+
runId
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async finish(
|
|
397
|
+
status: RetrievalTraceTerminalStatus,
|
|
398
|
+
latencyMs?: number
|
|
399
|
+
): Promise<StoreResult<"inserted" | "duplicate" | "disabled">> {
|
|
400
|
+
if (this.terminal) return ok("duplicate");
|
|
401
|
+
const completed = await this.appendEvent("complete", {
|
|
402
|
+
outcome: status,
|
|
403
|
+
...(latencyMs === undefined ? {} : { latencyMs }),
|
|
404
|
+
});
|
|
405
|
+
if (!completed.ok) {
|
|
406
|
+
const finalizedAfterEventFailure = await this.recorder.finalize(
|
|
407
|
+
this.traceId,
|
|
408
|
+
status
|
|
409
|
+
);
|
|
410
|
+
this.terminal = finalizedAfterEventFailure.ok;
|
|
411
|
+
return ok("disabled");
|
|
412
|
+
}
|
|
413
|
+
if (completed.value === "disabled") return completed;
|
|
414
|
+
const finalized = await this.recorder.finalize(this.traceId, status);
|
|
415
|
+
if (finalized.ok) this.terminal = true;
|
|
416
|
+
return finalized;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
private async appendEvent(
|
|
420
|
+
kind: RetrievalTraceEventKind,
|
|
421
|
+
payload: Record<string, unknown>,
|
|
422
|
+
runId: string | null = null
|
|
423
|
+
): Promise<StoreResult<"inserted" | "duplicate" | "disabled">> {
|
|
424
|
+
const eventId = this.nextId(kind);
|
|
425
|
+
const appended = await this.recorder.appendEvent({
|
|
426
|
+
eventId,
|
|
427
|
+
traceId: this.traceId,
|
|
428
|
+
runId,
|
|
429
|
+
idempotencyKey: eventId,
|
|
430
|
+
kind,
|
|
431
|
+
payload,
|
|
432
|
+
createdAtMs: this.clock(),
|
|
433
|
+
});
|
|
434
|
+
if (!appended.ok) return this.softenWriteFailure();
|
|
435
|
+
this.persistedRecords += 1;
|
|
436
|
+
return appended;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
private hasCapacity(records: number): boolean {
|
|
440
|
+
return this.persistedRecords + records + 1 <= this.maxRecords;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private async softenWriteFailure(): Promise<
|
|
444
|
+
StoreResult<"inserted" | "duplicate" | "disabled">
|
|
445
|
+
> {
|
|
446
|
+
await this.recorder.finalize(this.traceId, "failed");
|
|
447
|
+
this.available = false;
|
|
448
|
+
this.terminal = true;
|
|
449
|
+
return ok("disabled");
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private nextId(kind: string): string {
|
|
453
|
+
this.counter += 1;
|
|
454
|
+
return `${this.operationId}:${kind}:${this.counter}`;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export type { RetrievalTraceWriteResult };
|
|
459
|
+
export type { RetrievalTraceEvidence };
|
|
460
|
+
|
|
461
|
+
export const evidenceFromExactDocument = (input: {
|
|
462
|
+
docid: string;
|
|
463
|
+
uri: string;
|
|
464
|
+
sourceHash?: string;
|
|
465
|
+
mirrorHash?: string;
|
|
466
|
+
content: string;
|
|
467
|
+
startLine: number;
|
|
468
|
+
endLine: number;
|
|
469
|
+
seq?: number;
|
|
470
|
+
}): RetrievalTraceEvidence | null => {
|
|
471
|
+
if (
|
|
472
|
+
!input.sourceHash ||
|
|
473
|
+
!input.mirrorHash ||
|
|
474
|
+
!input.uri.startsWith("gno://") ||
|
|
475
|
+
!input.content ||
|
|
476
|
+
input.startLine < 1 ||
|
|
477
|
+
input.endLine < input.startLine ||
|
|
478
|
+
input.content.split("\n").length !== input.endLine - input.startLine + 1
|
|
479
|
+
) {
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
return {
|
|
483
|
+
docid: input.docid,
|
|
484
|
+
uri: input.uri,
|
|
485
|
+
sourceHash: input.sourceHash,
|
|
486
|
+
mirrorHash: input.mirrorHash,
|
|
487
|
+
startLine: input.startLine,
|
|
488
|
+
endLine: input.endLine,
|
|
489
|
+
passageHash: new Bun.CryptoHasher("sha256")
|
|
490
|
+
.update(input.content)
|
|
491
|
+
.digest("hex"),
|
|
492
|
+
...(input.seq === undefined ? {} : { seq: input.seq }),
|
|
493
|
+
};
|
|
494
|
+
};
|