@gmickel/gno 1.18.0 → 1.20.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 +14 -7
- package/assets/skill/SKILL.md +54 -12
- package/assets/skill/mcp-reference.md +7 -2
- package/assets/skill/recipes/citation-and-provenance.md +32 -9
- 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 +2919 -0
- package/spec/db/schema.sql +442 -0
- package/spec/evals-agentic.md +592 -0
- package/spec/evals.md +1106 -0
- package/spec/mcp.md +2279 -0
- package/spec/output-schemas/activation-verification.schema.json +515 -0
- package/spec/output-schemas/ask.schema.json +564 -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/claim-verification.schema.json +291 -0
- package/spec/output-schemas/collection-list.schema.json +45 -0
- package/spec/output-schemas/context-capsule-v1.schema.json +726 -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-contract.ts +10 -5
- package/src/app/context-runtime-input.ts +29 -1
- package/src/app/context-runtime-types.ts +7 -0
- package/src/app/context-runtime.ts +20 -2
- package/src/app/context-surface.ts +4 -0
- package/src/app/verified-ask.ts +291 -0
- package/src/cli/commands/ask-format.ts +255 -0
- package/src/cli/commands/ask.ts +144 -183
- 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 +287 -1
- 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-budget.ts +6 -0
- package/src/core/context-capsule-retrieval-schema.ts +4 -0
- package/src/core/context-capsule-schema.ts +17 -0
- package/src/core/context-capsule-validation.ts +3 -2
- package/src/core/context-capsule.ts +18 -0
- package/src/core/context-compiler.ts +44 -25
- package/src/core/context-evidence.ts +6 -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 +178 -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 +507 -0
- package/src/core/retrieval-trace.ts +472 -0
- package/src/llm/errors.ts +10 -1
- package/src/llm/httpGeneration.ts +11 -1
- package/src/llm/nodeLlamaCpp/generation.ts +54 -10
- package/src/llm/types.ts +6 -0
- package/src/mcp/tools/ask.ts +228 -0
- package/src/mcp/tools/context.ts +87 -15
- package/src/mcp/tools/get.ts +35 -1
- package/src/mcp/tools/index.ts +83 -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/claim-verification-schema.ts +235 -0
- package/src/pipeline/claim-verification.ts +487 -0
- package/src/pipeline/claim-verifier.ts +474 -0
- 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 +68 -0
- package/src/pipeline/vsearch.ts +101 -38
- package/src/sdk/client.ts +415 -73
- 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/components/AskVerificationPanel.tsx +189 -0
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/lib/workspace-tabs.ts +2 -0
- package/src/serve/public/pages/Ask.tsx +42 -4
- 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 +508 -68
- 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
|
@@ -29,6 +29,14 @@ const positiveIntegerSchema = z.number().int().positive();
|
|
|
29
29
|
const nonNegativeIntegerSchema = z.number().int().nonnegative();
|
|
30
30
|
const COLLECTION_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
31
31
|
const collectionSchema = nonEmptyTextSchema.max(64).regex(COLLECTION_PATTERN);
|
|
32
|
+
const retrievalSourceSchema = z.enum([
|
|
33
|
+
"bm25",
|
|
34
|
+
"vector",
|
|
35
|
+
"bm25_variant",
|
|
36
|
+
"vector_variant",
|
|
37
|
+
"hyde",
|
|
38
|
+
"graph",
|
|
39
|
+
]);
|
|
32
40
|
const compareCodeUnits = (left: string, right: string): number =>
|
|
33
41
|
left < right ? -1 : left > right ? 1 : 0;
|
|
34
42
|
|
|
@@ -244,6 +252,15 @@ export const contextCapsuleEvidenceSchema = z
|
|
|
244
252
|
contextIds: z.array(sha256Schema).max(128),
|
|
245
253
|
retrievalRank: positiveIntegerSchema,
|
|
246
254
|
selectionRank: positiveIntegerSchema,
|
|
255
|
+
retrievalSources: z
|
|
256
|
+
.array(retrievalSourceSchema)
|
|
257
|
+
.min(1)
|
|
258
|
+
.max(6)
|
|
259
|
+
.refine((sources) => new Set(sources).size === sources.length, {
|
|
260
|
+
message: "retrievalSources must be unique",
|
|
261
|
+
})
|
|
262
|
+
.optional(),
|
|
263
|
+
graphExpanded: z.boolean().optional(),
|
|
247
264
|
facets: z.array(nonEmptyTextSchema.max(512)).max(128),
|
|
248
265
|
trust: z.literal("untrusted"),
|
|
249
266
|
egress: z.enum([
|
|
@@ -352,11 +352,12 @@ export const validateContextCapsulePayload = (
|
|
|
352
352
|
});
|
|
353
353
|
}
|
|
354
354
|
const semanticRequested = value.retrieval.depthPolicy !== "fast";
|
|
355
|
+
const rerankRequested =
|
|
356
|
+
value.retrieval.request.rerankRequested ?? semanticRequested;
|
|
355
357
|
if (
|
|
356
358
|
value.retrieval.capabilityStates.semanticSearch.requested !==
|
|
357
359
|
semanticRequested ||
|
|
358
|
-
value.retrieval.capabilityStates.reranking.requested !==
|
|
359
|
-
semanticRequested ||
|
|
360
|
+
value.retrieval.capabilityStates.reranking.requested !== rerankRequested ||
|
|
360
361
|
value.retrieval.capabilityStates.graphExpansion.requested !==
|
|
361
362
|
value.retrieval.request.graphRequested
|
|
362
363
|
) {
|
|
@@ -84,6 +84,17 @@ const normalizePayload = (
|
|
|
84
84
|
value.retrieval.request.lang === null
|
|
85
85
|
? null
|
|
86
86
|
: normalizeText(value.retrieval.request.lang),
|
|
87
|
+
...(value.retrieval.request.intent === undefined
|
|
88
|
+
? {}
|
|
89
|
+
: {
|
|
90
|
+
intent:
|
|
91
|
+
value.retrieval.request.intent === null
|
|
92
|
+
? null
|
|
93
|
+
: normalizeText(value.retrieval.request.intent),
|
|
94
|
+
}),
|
|
95
|
+
...(value.retrieval.request.exclude === undefined
|
|
96
|
+
? {}
|
|
97
|
+
: { exclude: normalizeSet(value.retrieval.request.exclude) }),
|
|
87
98
|
queryModes: value.retrieval.request.queryModes.map((mode) => ({
|
|
88
99
|
...mode,
|
|
89
100
|
text: normalizeText(mode.text),
|
|
@@ -110,6 +121,13 @@ const normalizePayload = (
|
|
|
110
121
|
documentDate: normalizeDocumentDate(item.documentDate),
|
|
111
122
|
observedAt: normalizeDate(item.observedAt),
|
|
112
123
|
contextIds: normalizeSet(item.contextIds),
|
|
124
|
+
...(item.retrievalSources === undefined
|
|
125
|
+
? {}
|
|
126
|
+
: {
|
|
127
|
+
retrievalSources: [...new Set(item.retrievalSources)].sort(
|
|
128
|
+
compareCodeUnits
|
|
129
|
+
),
|
|
130
|
+
}),
|
|
113
131
|
facets: normalizeSet(item.facets),
|
|
114
132
|
})),
|
|
115
133
|
guidance: {
|
|
@@ -29,6 +29,7 @@ import type { ContextConfiguredGuidance } from "./context-guidance";
|
|
|
29
29
|
import { decorateUriForIndex } from "../app/constants";
|
|
30
30
|
import { canonicalizeIndexName } from "../app/index-name";
|
|
31
31
|
import { resolveTemporalRange } from "../pipeline/temporal";
|
|
32
|
+
import { attachSearchResultPlannerMetadata } from "../pipeline/trace-metadata";
|
|
32
33
|
import {
|
|
33
34
|
SEARCH_RESULT_PLANNER_METADATA,
|
|
34
35
|
type SearchResultPlannerMetadata,
|
|
@@ -63,8 +64,8 @@ export interface ContextRetrievalRequest extends HybridSearchOptions {
|
|
|
63
64
|
export interface ContextRetrievalCandidate {
|
|
64
65
|
result: SearchResult;
|
|
65
66
|
retrievalRank: number;
|
|
66
|
-
retrievalSources
|
|
67
|
-
graphExpanded
|
|
67
|
+
retrievalSources?: FusionSource[];
|
|
68
|
+
graphExpanded?: boolean;
|
|
68
69
|
contextIds: string[];
|
|
69
70
|
observedAt: string | null;
|
|
70
71
|
}
|
|
@@ -96,9 +97,13 @@ export interface ContextCompilerInput {
|
|
|
96
97
|
categories?: string[];
|
|
97
98
|
author?: string;
|
|
98
99
|
lang?: string;
|
|
100
|
+
intent?: string;
|
|
101
|
+
exclude?: string[];
|
|
102
|
+
minScore?: number;
|
|
99
103
|
since?: string;
|
|
100
104
|
until?: string;
|
|
101
105
|
graph?: boolean;
|
|
106
|
+
noRerank?: boolean;
|
|
102
107
|
limit?: number;
|
|
103
108
|
candidateLimit?: number;
|
|
104
109
|
/** Frozen once by the caller; never defaulted from wall-clock time. */
|
|
@@ -163,16 +168,9 @@ const compareCodeUnits = (left: string, right: string): number => {
|
|
|
163
168
|
};
|
|
164
169
|
|
|
165
170
|
const plannerMeta = (
|
|
166
|
-
result: SearchResult
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
result[SEARCH_RESULT_PLANNER_METADATA] ?? {
|
|
170
|
-
retrievalRank: fallbackRank,
|
|
171
|
-
mirrorHash: result.conversion?.mirrorHash ?? "",
|
|
172
|
-
seq: 0,
|
|
173
|
-
sources: [],
|
|
174
|
-
graphExpanded: false,
|
|
175
|
-
};
|
|
171
|
+
result: SearchResult
|
|
172
|
+
): SearchResultPlannerMetadata | undefined =>
|
|
173
|
+
result[SEARCH_RESULT_PLANNER_METADATA];
|
|
176
174
|
|
|
177
175
|
const compareSearchResults = (
|
|
178
176
|
left: SearchResult,
|
|
@@ -218,7 +216,9 @@ const referenceFromResult = (
|
|
|
218
216
|
const normalizeMaterialized = <T>(
|
|
219
217
|
draft: ContextMaterializedDraft<T>,
|
|
220
218
|
facets: string[],
|
|
221
|
-
retrievalRank: number
|
|
219
|
+
retrievalRank: number,
|
|
220
|
+
retrievalSources: FusionSource[] | undefined,
|
|
221
|
+
graphExpanded: boolean | undefined
|
|
222
222
|
): MaterializedContextCandidate<T> => {
|
|
223
223
|
const text = draft.text;
|
|
224
224
|
if (
|
|
@@ -249,6 +249,10 @@ const normalizeMaterialized = <T>(
|
|
|
249
249
|
text,
|
|
250
250
|
facets,
|
|
251
251
|
retrievalRank,
|
|
252
|
+
...(retrievalSources === undefined
|
|
253
|
+
? {}
|
|
254
|
+
: { retrievalSources: [...retrievalSources].sort(compareCodeUnits) }),
|
|
255
|
+
...(graphExpanded === undefined ? {} : { graphExpanded }),
|
|
252
256
|
value: draft.value,
|
|
253
257
|
};
|
|
254
258
|
};
|
|
@@ -351,10 +355,13 @@ export const planContextEvidence = async <T, P>(
|
|
|
351
355
|
categories: input.categories,
|
|
352
356
|
author: input.author,
|
|
353
357
|
lang: input.lang,
|
|
358
|
+
intent: input.intent,
|
|
359
|
+
exclude: input.exclude,
|
|
360
|
+
minScore: input.minScore,
|
|
354
361
|
since: temporalRange.since,
|
|
355
362
|
until: temporalRange.until,
|
|
356
363
|
graph: hasRerankBudget ? input.graph : false,
|
|
357
|
-
noRerank: hasRerankBudget ?
|
|
364
|
+
noRerank: input.noRerank || !hasRerankBudget ? true : undefined,
|
|
358
365
|
limit: resultLimit === undefined ? undefined : Math.max(1, resultLimit),
|
|
359
366
|
candidateLimit:
|
|
360
367
|
rerankLimit === undefined ? undefined : Math.max(1, rerankLimit),
|
|
@@ -363,14 +370,20 @@ export const planContextEvidence = async <T, P>(
|
|
|
363
370
|
}
|
|
364
371
|
const decoratedResults = responses
|
|
365
372
|
.flatMap((response) => response.results)
|
|
366
|
-
.map((result) =>
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
373
|
+
.map((result) => {
|
|
374
|
+
const decorated = {
|
|
375
|
+
...result,
|
|
376
|
+
uri: decorateUriForIndex(result.uri, indexName),
|
|
377
|
+
};
|
|
378
|
+
const metadata = result[SEARCH_RESULT_PLANNER_METADATA];
|
|
379
|
+
return metadata
|
|
380
|
+
? attachSearchResultPlannerMetadata(decorated, metadata)
|
|
381
|
+
: decorated;
|
|
382
|
+
});
|
|
370
383
|
const results = decoratedResults
|
|
371
384
|
.map((result, index) => ({
|
|
372
385
|
result,
|
|
373
|
-
retrievalRank: plannerMeta(result
|
|
386
|
+
retrievalRank: plannerMeta(result)?.retrievalRank ?? index + 1,
|
|
374
387
|
}))
|
|
375
388
|
.sort(
|
|
376
389
|
(left, right) =>
|
|
@@ -400,8 +413,8 @@ export const planContextEvidence = async <T, P>(
|
|
|
400
413
|
const referencesByCandidate: ContextCandidateReference[] = [];
|
|
401
414
|
|
|
402
415
|
for (const [index, result] of results.entries()) {
|
|
403
|
-
const meta = plannerMeta(result
|
|
404
|
-
for (const source of meta
|
|
416
|
+
const meta = plannerMeta(result);
|
|
417
|
+
for (const source of meta?.sources ?? []) retrievalSources.add(source);
|
|
405
418
|
const retrievalReference = referenceFromResult(result);
|
|
406
419
|
if (!isContextUriInScope(result.uri, indexName, collections, uriPrefix)) {
|
|
407
420
|
for (const facet of facetPlan) {
|
|
@@ -424,9 +437,13 @@ export const planContextEvidence = async <T, P>(
|
|
|
424
437
|
}
|
|
425
438
|
plannedCandidates.push({
|
|
426
439
|
result,
|
|
427
|
-
retrievalRank: meta
|
|
428
|
-
|
|
429
|
-
|
|
440
|
+
retrievalRank: meta?.retrievalRank ?? index + 1,
|
|
441
|
+
...(meta === undefined
|
|
442
|
+
? {}
|
|
443
|
+
: {
|
|
444
|
+
retrievalSources: [...meta.sources].sort(compareCodeUnits),
|
|
445
|
+
graphExpanded: meta.graphExpanded,
|
|
446
|
+
}),
|
|
430
447
|
contextIds:
|
|
431
448
|
guidance.idsByResultIdentity.get(
|
|
432
449
|
contextGuidanceResultIdentity(result)
|
|
@@ -479,7 +496,9 @@ export const planContextEvidence = async <T, P>(
|
|
|
479
496
|
normalizeMaterialized(
|
|
480
497
|
outcome.candidate,
|
|
481
498
|
matchedFacets,
|
|
482
|
-
plannedCandidate.retrievalRank
|
|
499
|
+
plannedCandidate.retrievalRank,
|
|
500
|
+
plannedCandidate.retrievalSources,
|
|
501
|
+
plannedCandidate.graphExpanded
|
|
483
502
|
)
|
|
484
503
|
);
|
|
485
504
|
}
|
|
@@ -490,6 +490,12 @@ export const toContextCapsuleEvidence = (
|
|
|
490
490
|
text: candidate.text,
|
|
491
491
|
retrievalRank: candidate.retrievalRank,
|
|
492
492
|
selectionRank,
|
|
493
|
+
...(candidate.retrievalSources === undefined
|
|
494
|
+
? {}
|
|
495
|
+
: { retrievalSources: [...candidate.retrievalSources] }),
|
|
496
|
+
...(candidate.graphExpanded === undefined
|
|
497
|
+
? {}
|
|
498
|
+
: { graphExpanded: candidate.graphExpanded }),
|
|
493
499
|
facets: [...candidate.facets],
|
|
494
500
|
};
|
|
495
501
|
};
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/** Canonical qrels artifacts derived only from persisted retrieval receipts. */
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
RetrievalTraceBundle,
|
|
5
|
+
RetrievalTraceFingerprints,
|
|
6
|
+
RetrievalTraceJudgmentLabel,
|
|
7
|
+
RetrievalTraceTerminalStatus,
|
|
8
|
+
StoreResult,
|
|
9
|
+
} from "../store/types";
|
|
10
|
+
import type { EvidenceTarget } from "./retrieval-trace-management-helpers";
|
|
11
|
+
|
|
12
|
+
import { hashTraceCanonical } from "../store/retrieval-trace-codec";
|
|
13
|
+
import { err, ok } from "../store/types";
|
|
14
|
+
import { parseRetrievalTraceFilters } from "./retrieval-trace-filters";
|
|
15
|
+
import { stableTarget, targetKey } from "./retrieval-trace-management-helpers";
|
|
16
|
+
|
|
17
|
+
export interface RetrievalQrelsEvidence {
|
|
18
|
+
docid: string;
|
|
19
|
+
sourceHash: string;
|
|
20
|
+
mirrorHash: string;
|
|
21
|
+
uri: string;
|
|
22
|
+
seq: number | null;
|
|
23
|
+
startLine: number;
|
|
24
|
+
endLine: number;
|
|
25
|
+
passageHash: string;
|
|
26
|
+
rank: number | null;
|
|
27
|
+
plannerRank: number | null;
|
|
28
|
+
score: number | null;
|
|
29
|
+
sources: string[];
|
|
30
|
+
graphExpanded: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface RetrievalQrel {
|
|
34
|
+
qrelId: string;
|
|
35
|
+
judgmentId: string;
|
|
36
|
+
label: RetrievalTraceJudgmentLabel;
|
|
37
|
+
relevance: 0 | 1;
|
|
38
|
+
baselineMissing: boolean;
|
|
39
|
+
target: EvidenceTarget;
|
|
40
|
+
evidence: RetrievalQrelsEvidence | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface RetrievalQrelsCase {
|
|
44
|
+
caseId: string;
|
|
45
|
+
traceId: string;
|
|
46
|
+
retrievalRunId: string;
|
|
47
|
+
terminalStatus: RetrievalTraceTerminalStatus;
|
|
48
|
+
query: {
|
|
49
|
+
text: string;
|
|
50
|
+
digest: string;
|
|
51
|
+
goalText: string | null;
|
|
52
|
+
goalDigest: string | null;
|
|
53
|
+
filters: Record<string, unknown>;
|
|
54
|
+
};
|
|
55
|
+
fingerprints: RetrievalTraceFingerprints;
|
|
56
|
+
baseline: {
|
|
57
|
+
ranked: RetrievalQrelsEvidence[];
|
|
58
|
+
capabilities: string[];
|
|
59
|
+
capabilityOutcomes: Array<{
|
|
60
|
+
capability: string;
|
|
61
|
+
status: "attempted" | "used" | "unavailable" | "failed";
|
|
62
|
+
reasonCode: string | null;
|
|
63
|
+
}>;
|
|
64
|
+
fallbackCodes: string[];
|
|
65
|
+
outcomes: {
|
|
66
|
+
opened: RetrievalQrelsEvidence[];
|
|
67
|
+
cited: RetrievalQrelsEvidence[];
|
|
68
|
+
pinned: RetrievalQrelsEvidence[];
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
judgments: {
|
|
72
|
+
history: Array<{
|
|
73
|
+
judgmentId: string;
|
|
74
|
+
label: RetrievalTraceJudgmentLabel;
|
|
75
|
+
targetKind: string;
|
|
76
|
+
target: EvidenceTarget;
|
|
77
|
+
createdAtMs: number;
|
|
78
|
+
canonicalDigest: string;
|
|
79
|
+
}>;
|
|
80
|
+
effective: string[];
|
|
81
|
+
};
|
|
82
|
+
qrels: RetrievalQrel[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface RetrievalTraceQrelsArtifact {
|
|
86
|
+
schemaVersion: "1.0";
|
|
87
|
+
format: "qrels";
|
|
88
|
+
cases: RetrievalQrelsCase[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const exactEvidence = (value: unknown): RetrievalQrelsEvidence | null => {
|
|
92
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
93
|
+
const item = value as Record<string, unknown>;
|
|
94
|
+
if (
|
|
95
|
+
typeof item.docid !== "string" ||
|
|
96
|
+
typeof item.sourceHash !== "string" ||
|
|
97
|
+
typeof item.mirrorHash !== "string" ||
|
|
98
|
+
typeof item.uri !== "string" ||
|
|
99
|
+
!item.uri.startsWith("gno://") ||
|
|
100
|
+
typeof item.startLine !== "number" ||
|
|
101
|
+
typeof item.endLine !== "number" ||
|
|
102
|
+
typeof item.passageHash !== "string"
|
|
103
|
+
) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
docid: item.docid,
|
|
108
|
+
sourceHash: item.sourceHash,
|
|
109
|
+
mirrorHash: item.mirrorHash,
|
|
110
|
+
uri: item.uri,
|
|
111
|
+
seq: typeof item.seq === "number" ? item.seq : null,
|
|
112
|
+
startLine: item.startLine,
|
|
113
|
+
endLine: item.endLine,
|
|
114
|
+
passageHash: item.passageHash,
|
|
115
|
+
rank: typeof item.rank === "number" ? item.rank : null,
|
|
116
|
+
plannerRank: typeof item.plannerRank === "number" ? item.plannerRank : null,
|
|
117
|
+
score: typeof item.score === "number" ? item.score : null,
|
|
118
|
+
sources: Array.isArray(item.sources)
|
|
119
|
+
? item.sources
|
|
120
|
+
.filter((entry): entry is string => typeof entry === "string")
|
|
121
|
+
.sort()
|
|
122
|
+
: [],
|
|
123
|
+
graphExpanded: item.graphExpanded === true,
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const evidenceArray = (
|
|
128
|
+
payload: Record<string, unknown>,
|
|
129
|
+
key: "ranked" | "evidence"
|
|
130
|
+
): RetrievalQrelsEvidence[] =>
|
|
131
|
+
Array.isArray(payload[key])
|
|
132
|
+
? payload[key].flatMap((value) => {
|
|
133
|
+
const parsed = exactEvidence(value);
|
|
134
|
+
return parsed ? [parsed] : [];
|
|
135
|
+
})
|
|
136
|
+
: [];
|
|
137
|
+
|
|
138
|
+
const sameDocument = (
|
|
139
|
+
target: EvidenceTarget,
|
|
140
|
+
evidence: RetrievalQrelsEvidence
|
|
141
|
+
): boolean =>
|
|
142
|
+
(target.sourceHash !== undefined &&
|
|
143
|
+
target.sourceHash === evidence.sourceHash) ||
|
|
144
|
+
(target.docid !== undefined && target.docid === evidence.docid) ||
|
|
145
|
+
(target.uri !== undefined && target.uri === evidence.uri);
|
|
146
|
+
|
|
147
|
+
const evidenceMatches = (
|
|
148
|
+
target: EvidenceTarget,
|
|
149
|
+
evidence: RetrievalQrelsEvidence
|
|
150
|
+
): boolean =>
|
|
151
|
+
sameDocument(target, evidence) &&
|
|
152
|
+
(target.seq === undefined || target.seq === evidence.seq) &&
|
|
153
|
+
(target.startLine === undefined || target.startLine === evidence.startLine) &&
|
|
154
|
+
(target.endLine === undefined || target.endLine === evidence.endLine) &&
|
|
155
|
+
(target.passageHash === undefined ||
|
|
156
|
+
target.passageHash === evidence.passageHash);
|
|
157
|
+
|
|
158
|
+
const effectiveJudgments = (
|
|
159
|
+
judgments: RetrievalTraceBundle["judgments"]
|
|
160
|
+
): RetrievalTraceBundle["judgments"] => {
|
|
161
|
+
const latest = new Map<string, RetrievalTraceBundle["judgments"][number]>();
|
|
162
|
+
for (const judgment of judgments) {
|
|
163
|
+
const target = stableTarget(judgment.target);
|
|
164
|
+
if (!target) continue;
|
|
165
|
+
if (judgment.targetKind === "query") continue;
|
|
166
|
+
const key = targetKey(judgment.targetKind, target);
|
|
167
|
+
const previous = latest.get(key);
|
|
168
|
+
if (
|
|
169
|
+
!previous ||
|
|
170
|
+
judgment.createdAtMs > previous.createdAtMs ||
|
|
171
|
+
(judgment.createdAtMs === previous.createdAtMs &&
|
|
172
|
+
judgment.judgmentId > previous.judgmentId)
|
|
173
|
+
) {
|
|
174
|
+
latest.set(key, judgment);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return [...latest.values()].sort((left, right) =>
|
|
178
|
+
left.judgmentId.localeCompare(right.judgmentId)
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const capabilityOutcomes = (
|
|
183
|
+
trace: RetrievalTraceBundle,
|
|
184
|
+
runId: string
|
|
185
|
+
): RetrievalQrelsCase["baseline"]["capabilityOutcomes"] =>
|
|
186
|
+
trace.events
|
|
187
|
+
.filter((event) => event.kind === "capability" && event.runId === runId)
|
|
188
|
+
.flatMap((event) => {
|
|
189
|
+
const { capability, status, reasonCode } = event.payload;
|
|
190
|
+
if (
|
|
191
|
+
typeof capability !== "string" ||
|
|
192
|
+
!["attempted", "used", "unavailable", "failed"].includes(String(status))
|
|
193
|
+
) {
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
return [
|
|
197
|
+
{
|
|
198
|
+
capability,
|
|
199
|
+
status: status as "attempted" | "used" | "unavailable" | "failed",
|
|
200
|
+
reasonCode: typeof reasonCode === "string" ? reasonCode : null,
|
|
201
|
+
},
|
|
202
|
+
];
|
|
203
|
+
})
|
|
204
|
+
.sort((left, right) =>
|
|
205
|
+
`${left.capability}\0${left.status}\0${left.reasonCode ?? ""}`.localeCompare(
|
|
206
|
+
`${right.capability}\0${right.status}\0${right.reasonCode ?? ""}`
|
|
207
|
+
)
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const outcomeEvidence = (
|
|
211
|
+
trace: RetrievalTraceBundle,
|
|
212
|
+
kind: "open" | "cite" | "pin",
|
|
213
|
+
runId: string
|
|
214
|
+
): RetrievalQrelsEvidence[] =>
|
|
215
|
+
trace.events
|
|
216
|
+
.filter((event) => event.kind === kind && event.runId === runId)
|
|
217
|
+
.flatMap((event) => evidenceArray(event.payload, "evidence"));
|
|
218
|
+
|
|
219
|
+
const assignedRun = (
|
|
220
|
+
judgment: RetrievalTraceBundle["judgments"][number],
|
|
221
|
+
retrievalRuns: RetrievalTraceBundle["runs"]
|
|
222
|
+
): StoreResult<string> => {
|
|
223
|
+
if (retrievalRuns.some((run) => run.runId === judgment.runId)) {
|
|
224
|
+
return ok(judgment.runId!);
|
|
225
|
+
}
|
|
226
|
+
if (
|
|
227
|
+
judgment.runId === null &&
|
|
228
|
+
judgment.label === "missing_expected" &&
|
|
229
|
+
retrievalRuns.length === 1
|
|
230
|
+
) {
|
|
231
|
+
return ok(retrievalRuns[0]!.runId);
|
|
232
|
+
}
|
|
233
|
+
return err(
|
|
234
|
+
"CONSTRAINT_VIOLATION",
|
|
235
|
+
"ambiguous_missing_expected_run: judgment cannot be assigned to one retrieval run"
|
|
236
|
+
);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const sortedStringSet = (value: unknown): string[] =>
|
|
240
|
+
Array.isArray(value)
|
|
241
|
+
? [
|
|
242
|
+
...new Set(
|
|
243
|
+
value.filter((item): item is string => typeof item === "string")
|
|
244
|
+
),
|
|
245
|
+
].sort()
|
|
246
|
+
: [];
|
|
247
|
+
|
|
248
|
+
export const buildRetrievalQrelsArtifact = (
|
|
249
|
+
bundles: RetrievalTraceBundle[]
|
|
250
|
+
): StoreResult<RetrievalTraceQrelsArtifact> => {
|
|
251
|
+
const cases: RetrievalQrelsCase[] = [];
|
|
252
|
+
for (const trace of [...bundles].sort((a, b) =>
|
|
253
|
+
a.trace.traceId.localeCompare(b.trace.traceId)
|
|
254
|
+
)) {
|
|
255
|
+
const header = trace.trace;
|
|
256
|
+
if (
|
|
257
|
+
header.status === "open" ||
|
|
258
|
+
header.redactionMode !== "replay" ||
|
|
259
|
+
!header.replayCapable
|
|
260
|
+
) {
|
|
261
|
+
return err(
|
|
262
|
+
"CONSTRAINT_VIOLATION",
|
|
263
|
+
`redaction_incompatible: ${header.traceId} is not a terminal replay receipt`
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
if (!header.queryText || !header.queryDigest) {
|
|
267
|
+
return err(
|
|
268
|
+
"CONSTRAINT_VIOLATION",
|
|
269
|
+
`query_missing: ${header.traceId} lacks replay query text or digest`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
const filters = parseRetrievalTraceFilters(header.filters);
|
|
273
|
+
if (!filters.ok) return filters;
|
|
274
|
+
const retrievalRuns = trace.runs.filter((run) => run.kind === "retrieval");
|
|
275
|
+
if (retrievalRuns.length === 0) {
|
|
276
|
+
return err("CONSTRAINT_VIOLATION", `no_retrieval_run: ${header.traceId}`);
|
|
277
|
+
}
|
|
278
|
+
const rankedByRun = new Map(
|
|
279
|
+
retrievalRuns.map((run) => [
|
|
280
|
+
run.runId,
|
|
281
|
+
evidenceArray(run.payload, "ranked"),
|
|
282
|
+
])
|
|
283
|
+
);
|
|
284
|
+
const historyByRun = new Map<string, RetrievalTraceBundle["judgments"]>();
|
|
285
|
+
for (const judgment of trace.judgments) {
|
|
286
|
+
const assignment = assignedRun(judgment, retrievalRuns);
|
|
287
|
+
if (!assignment.ok) return assignment;
|
|
288
|
+
const current = historyByRun.get(assignment.value) ?? [];
|
|
289
|
+
current.push(judgment);
|
|
290
|
+
historyByRun.set(assignment.value, current);
|
|
291
|
+
}
|
|
292
|
+
const effectiveByRun = new Map<string, RetrievalTraceBundle["judgments"]>();
|
|
293
|
+
for (const run of retrievalRuns) {
|
|
294
|
+
effectiveByRun.set(
|
|
295
|
+
run.runId,
|
|
296
|
+
effectiveJudgments(historyByRun.get(run.runId) ?? [])
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
if (
|
|
300
|
+
![...effectiveByRun.values()]
|
|
301
|
+
.flat()
|
|
302
|
+
.some(
|
|
303
|
+
({ label }) => label === "relevant" || label === "missing_expected"
|
|
304
|
+
)
|
|
305
|
+
) {
|
|
306
|
+
return err(
|
|
307
|
+
"CONSTRAINT_VIOLATION",
|
|
308
|
+
`qrels export requires relevant or missing_expected judgments: ${header.traceId}`
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
for (const run of retrievalRuns) {
|
|
312
|
+
const runJudgments = effectiveByRun.get(run.runId) ?? [];
|
|
313
|
+
if (runJudgments.length === 0) continue;
|
|
314
|
+
const runOutcomes = {
|
|
315
|
+
opened: outcomeEvidence(trace, "open", run.runId),
|
|
316
|
+
cited: outcomeEvidence(trace, "cite", run.runId),
|
|
317
|
+
pinned: outcomeEvidence(trace, "pin", run.runId),
|
|
318
|
+
};
|
|
319
|
+
const allExact = [
|
|
320
|
+
...(rankedByRun.get(run.runId) ?? []),
|
|
321
|
+
...runOutcomes.opened,
|
|
322
|
+
...runOutcomes.cited,
|
|
323
|
+
...runOutcomes.pinned,
|
|
324
|
+
];
|
|
325
|
+
const qrels: RetrievalQrel[] = [];
|
|
326
|
+
for (const judgment of runJudgments) {
|
|
327
|
+
const target = stableTarget(judgment.target);
|
|
328
|
+
if (!target) {
|
|
329
|
+
return err("CONSTRAINT_VIOLATION", "Judgment target is incomplete");
|
|
330
|
+
}
|
|
331
|
+
const evidence =
|
|
332
|
+
judgment.label === "missing_expected"
|
|
333
|
+
? null
|
|
334
|
+
: (allExact.find((item) => evidenceMatches(target, item)) ?? null);
|
|
335
|
+
if (judgment.label !== "missing_expected" && !evidence) {
|
|
336
|
+
return err(
|
|
337
|
+
"CONSTRAINT_VIOLATION",
|
|
338
|
+
`Judgment ${judgment.judgmentId} lacks exact evidence provenance`
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
qrels.push({
|
|
342
|
+
qrelId: `qrel-${hashTraceCanonical({
|
|
343
|
+
judgmentId: judgment.judgmentId,
|
|
344
|
+
label: judgment.label,
|
|
345
|
+
target,
|
|
346
|
+
}).slice(0, 40)}`,
|
|
347
|
+
judgmentId: judgment.judgmentId,
|
|
348
|
+
label: judgment.label,
|
|
349
|
+
relevance: judgment.label === "irrelevant" ? 0 : 1,
|
|
350
|
+
baselineMissing: judgment.label === "missing_expected",
|
|
351
|
+
target,
|
|
352
|
+
evidence,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
cases.push({
|
|
356
|
+
caseId: `trace-case-${hashTraceCanonical({
|
|
357
|
+
traceId: header.traceId,
|
|
358
|
+
runId: run.runId,
|
|
359
|
+
}).slice(0, 40)}`,
|
|
360
|
+
traceId: header.traceId,
|
|
361
|
+
retrievalRunId: run.runId,
|
|
362
|
+
terminalStatus: header.status,
|
|
363
|
+
query: {
|
|
364
|
+
text: header.queryText,
|
|
365
|
+
digest: header.queryDigest,
|
|
366
|
+
goalText: header.goalText,
|
|
367
|
+
goalDigest: header.goalDigest,
|
|
368
|
+
filters: filters.value,
|
|
369
|
+
},
|
|
370
|
+
fingerprints: header.fingerprints,
|
|
371
|
+
baseline: {
|
|
372
|
+
ranked: rankedByRun.get(run.runId) ?? [],
|
|
373
|
+
capabilities: sortedStringSet(run.payload.capabilities),
|
|
374
|
+
capabilityOutcomes: capabilityOutcomes(trace, run.runId),
|
|
375
|
+
fallbackCodes: sortedStringSet(run.payload.fallbackCodes),
|
|
376
|
+
outcomes: runOutcomes,
|
|
377
|
+
},
|
|
378
|
+
judgments: {
|
|
379
|
+
history: (historyByRun.get(run.runId) ?? []).flatMap((judgment) => {
|
|
380
|
+
const target = stableTarget(judgment.target);
|
|
381
|
+
return target
|
|
382
|
+
? [
|
|
383
|
+
{
|
|
384
|
+
judgmentId: judgment.judgmentId,
|
|
385
|
+
label: judgment.label,
|
|
386
|
+
targetKind: judgment.targetKind,
|
|
387
|
+
target,
|
|
388
|
+
createdAtMs: judgment.createdAtMs,
|
|
389
|
+
canonicalDigest: judgment.canonicalDigest,
|
|
390
|
+
},
|
|
391
|
+
]
|
|
392
|
+
: [];
|
|
393
|
+
}),
|
|
394
|
+
effective: runJudgments.map((judgment) => judgment.judgmentId),
|
|
395
|
+
},
|
|
396
|
+
qrels: qrels.sort((a, b) => a.qrelId.localeCompare(b.qrelId)),
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return ok({
|
|
401
|
+
schemaVersion: "1.0",
|
|
402
|
+
format: "qrels",
|
|
403
|
+
cases: cases.sort((a, b) => a.caseId.localeCompare(b.caseId)),
|
|
404
|
+
});
|
|
405
|
+
};
|