@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.
Files changed (129) hide show
  1. package/README.md +5 -3
  2. package/assets/skill/SKILL.md +27 -0
  3. package/package.json +2 -1
  4. package/spec/AGENTS.md +83 -0
  5. package/spec/CLAUDE.md +83 -0
  6. package/spec/bench-fixture.schema.json +137 -0
  7. package/spec/cli.md +2894 -0
  8. package/spec/db/schema.sql +442 -0
  9. package/spec/evals-agentic.md +510 -0
  10. package/spec/evals.md +1106 -0
  11. package/spec/mcp.md +2229 -0
  12. package/spec/output-schemas/activation-verification.schema.json +515 -0
  13. package/spec/output-schemas/ask.schema.json +366 -0
  14. package/spec/output-schemas/backlinks.schema.json +131 -0
  15. package/spec/output-schemas/bench-result.schema.json +120 -0
  16. package/spec/output-schemas/capture-receipt.schema.json +143 -0
  17. package/spec/output-schemas/collection-list.schema.json +45 -0
  18. package/spec/output-schemas/context-capsule-v1.schema.json +691 -0
  19. package/spec/output-schemas/context-capsule-verification.schema.json +1338 -0
  20. package/spec/output-schemas/context-list.schema.json +21 -0
  21. package/spec/output-schemas/doctor.schema.json +313 -0
  22. package/spec/output-schemas/error.schema.json +30 -0
  23. package/spec/output-schemas/expansion.schema.json +37 -0
  24. package/spec/output-schemas/get.schema.json +140 -0
  25. package/spec/output-schemas/graph-query.schema.json +99 -0
  26. package/spec/output-schemas/graph.schema.json +371 -0
  27. package/spec/output-schemas/links-list.schema.json +186 -0
  28. package/spec/output-schemas/mcp-add-collection-result.schema.json +23 -0
  29. package/spec/output-schemas/mcp-capture-result.schema.json +152 -0
  30. package/spec/output-schemas/mcp-http-error.schema.json +30 -0
  31. package/spec/output-schemas/mcp-job-list.schema.json +58 -0
  32. package/spec/output-schemas/mcp-job-status.schema.json +224 -0
  33. package/spec/output-schemas/mcp-remove-result.schema.json +39 -0
  34. package/spec/output-schemas/mcp-sync-result.schema.json +41 -0
  35. package/spec/output-schemas/mcp-tag-result.schema.json +33 -0
  36. package/spec/output-schemas/models-list.schema.json +93 -0
  37. package/spec/output-schemas/multi-get.schema.json +103 -0
  38. package/spec/output-schemas/process-status.schema.json +119 -0
  39. package/spec/output-schemas/query-diagnose.schema.json +123 -0
  40. package/spec/output-schemas/resident-status.schema.json +154 -0
  41. package/spec/output-schemas/retrieval-trace-common.schema.json +492 -0
  42. package/spec/output-schemas/retrieval-trace-delete.schema.json +16 -0
  43. package/spec/output-schemas/retrieval-trace-export.schema.json +61 -0
  44. package/spec/output-schemas/retrieval-trace-filters.schema.json +139 -0
  45. package/spec/output-schemas/retrieval-trace-judgment.schema.json +15 -0
  46. package/spec/output-schemas/retrieval-trace-list.schema.json +18 -0
  47. package/spec/output-schemas/retrieval-trace-payloads.schema.json +178 -0
  48. package/spec/output-schemas/retrieval-trace-purge.schema.json +31 -0
  49. package/spec/output-schemas/retrieval-trace-qrels.schema.json +303 -0
  50. package/spec/output-schemas/retrieval-trace-replay.schema.json +286 -0
  51. package/spec/output-schemas/retrieval-trace-show.schema.json +69 -0
  52. package/spec/output-schemas/retrieval-trace-summary.schema.json +65 -0
  53. package/spec/output-schemas/search-result.schema.json +154 -0
  54. package/spec/output-schemas/search-results.schema.json +338 -0
  55. package/spec/output-schemas/similar.schema.json +84 -0
  56. package/spec/output-schemas/status.schema.json +676 -0
  57. package/spec/output-schemas/tags-list.schema.json +48 -0
  58. package/src/app/context-runtime-types.ts +3 -0
  59. package/src/app/context-runtime.ts +15 -1
  60. package/src/cli/commands/ask.ts +106 -36
  61. package/src/cli/commands/context-build.ts +56 -9
  62. package/src/cli/commands/get.ts +64 -3
  63. package/src/cli/commands/query.ts +62 -23
  64. package/src/cli/commands/replay.ts +140 -0
  65. package/src/cli/commands/search.ts +48 -3
  66. package/src/cli/commands/shared.ts +3 -1
  67. package/src/cli/commands/trace.ts +200 -0
  68. package/src/cli/commands/vsearch.ts +75 -53
  69. package/src/cli/program.ts +255 -0
  70. package/src/config/index.ts +9 -0
  71. package/src/config/retrieval-traces.ts +56 -0
  72. package/src/config/types.ts +4 -0
  73. package/src/core/context-compiler.ts +11 -4
  74. package/src/core/retrieval-qrels.ts +405 -0
  75. package/src/core/retrieval-replay-candidate.ts +368 -0
  76. package/src/core/retrieval-replay-types.ts +109 -0
  77. package/src/core/retrieval-replay-validation.ts +89 -0
  78. package/src/core/retrieval-replay.ts +441 -0
  79. package/src/core/retrieval-trace-evidence-origin.ts +175 -0
  80. package/src/core/retrieval-trace-export.ts +113 -0
  81. package/src/core/retrieval-trace-filter-normalization.ts +27 -0
  82. package/src/core/retrieval-trace-filters.ts +19 -0
  83. package/src/core/retrieval-trace-management-helpers.ts +247 -0
  84. package/src/core/retrieval-trace-management-types.ts +132 -0
  85. package/src/core/retrieval-trace-management.ts +422 -0
  86. package/src/core/retrieval-trace-request.ts +141 -0
  87. package/src/core/retrieval-trace-session.ts +494 -0
  88. package/src/core/retrieval-trace.ts +472 -0
  89. package/src/mcp/tools/context.ts +59 -8
  90. package/src/mcp/tools/get.ts +35 -1
  91. package/src/mcp/tools/index.ts +74 -0
  92. package/src/mcp/tools/query.ts +95 -64
  93. package/src/mcp/tools/search.ts +36 -13
  94. package/src/mcp/tools/trace.ts +143 -0
  95. package/src/mcp/tools/vsearch.ts +71 -38
  96. package/src/pipeline/answer.ts +167 -26
  97. package/src/pipeline/graph-retrieval.ts +15 -1
  98. package/src/pipeline/hybrid.ts +151 -43
  99. package/src/pipeline/search.ts +36 -3
  100. package/src/pipeline/trace-metadata.ts +47 -0
  101. package/src/pipeline/types.ts +43 -0
  102. package/src/pipeline/vsearch.ts +101 -38
  103. package/src/sdk/client.ts +380 -71
  104. package/src/sdk/documents.ts +48 -1
  105. package/src/sdk/index.ts +17 -0
  106. package/src/sdk/types.ts +28 -0
  107. package/src/serve/context-capsule.ts +67 -8
  108. package/src/serve/public/app.tsx +12 -1
  109. package/src/serve/public/globals.built.css +1 -1
  110. package/src/serve/public/lib/workspace-tabs.ts +2 -0
  111. package/src/serve/public/pages/Dashboard.tsx +10 -0
  112. package/src/serve/public/pages/TraceHistory.tsx +478 -0
  113. package/src/serve/public/pages/trace-history-detail.tsx +224 -0
  114. package/src/serve/retrieval-trace.ts +28 -0
  115. package/src/serve/routes/api.ts +366 -72
  116. package/src/serve/routes/traces.ts +156 -0
  117. package/src/serve/server.ts +87 -2
  118. package/src/store/index.ts +31 -0
  119. package/src/store/migrations/014-retrieval-traces.ts +303 -0
  120. package/src/store/migrations/index.ts +2 -0
  121. package/src/store/retrieval-trace-codec.ts +384 -0
  122. package/src/store/sqlite/adapter.ts +153 -1
  123. package/src/store/sqlite/retrieval-trace-management-store.ts +341 -0
  124. package/src/store/sqlite/retrieval-trace-retention.ts +349 -0
  125. package/src/store/sqlite/retrieval-trace-rows.ts +267 -0
  126. package/src/store/sqlite/retrieval-trace-store.ts +515 -0
  127. package/src/store/types.ts +297 -0
  128. package/src/store/vector/sqlite-vec.ts +76 -1
  129. package/src/store/vector/types.ts +1 -1
@@ -55,9 +55,10 @@ import {
55
55
  shouldSortByRecency,
56
56
  } from "./temporal";
57
57
  import {
58
- DEFAULT_PIPELINE_CONFIG,
59
- SEARCH_RESULT_PLANNER_METADATA,
60
- } from "./types";
58
+ attachSearchResultPlannerMetadata,
59
+ attachSearchResultsTraceMetadata,
60
+ } from "./trace-metadata";
61
+ import { DEFAULT_PIPELINE_CONFIG } from "./types";
61
62
 
62
63
  // ─────────────────────────────────────────────────────────────────────────────
63
64
  // Dependencies
@@ -121,11 +122,13 @@ async function checkBm25Strength(
121
122
  until?: string;
122
123
  categories?: string[];
123
124
  author?: string;
125
+ relPathPrefix?: string;
124
126
  }
125
127
  ): Promise<boolean> {
126
128
  const result = await store.searchFts(query, {
127
129
  limit: 5,
128
130
  collection: options?.collection,
131
+ relPathPrefix: options?.relPathPrefix,
129
132
  language: options?.lang,
130
133
  tagsAll: options?.tagsAll,
131
134
  tagsAny: options?.tagsAny,
@@ -179,11 +182,13 @@ async function searchFtsChunks(
179
182
  until?: string;
180
183
  categories?: string[];
181
184
  author?: string;
185
+ relPathPrefix?: string;
182
186
  }
183
187
  ): Promise<FtsChunksResult> {
184
188
  const result = await store.searchFts(query, {
185
189
  limit: options.limit,
186
190
  collection: options.collection,
191
+ relPathPrefix: options.relPathPrefix,
187
192
  language: options.lang,
188
193
  tagsAll: options.tagsAll,
189
194
  tagsAny: options.tagsAny,
@@ -216,7 +221,11 @@ async function searchVectorChunks(
216
221
  vectorIndex: VectorIndexPort,
217
222
  embedPort: EmbeddingPort,
218
223
  query: string,
219
- options: { limit: number; minScore?: number }
224
+ options: {
225
+ limit: number;
226
+ minScore?: number;
227
+ allowedMirrorHashes?: string[];
228
+ }
220
229
  ): Promise<ChunkId[]> {
221
230
  if (!vectorIndex.searchAvailable) {
222
231
  return [];
@@ -234,7 +243,10 @@ async function searchVectorChunks(
234
243
  const searchResult = await vectorIndex.searchNearest(
235
244
  queryEmbedding,
236
245
  options.limit,
237
- { minScore: options.minScore }
246
+ {
247
+ minScore: options.minScore,
248
+ allowedMirrorHashes: options.allowedMirrorHashes,
249
+ }
238
250
  );
239
251
 
240
252
  if (!searchResult.ok) {
@@ -381,6 +393,7 @@ export async function searchHybrid(
381
393
  until: temporalRange.until,
382
394
  categories: options.categories,
383
395
  author: options.author,
396
+ relPathPrefix: options.retrievalScope?.relPathPrefix,
384
397
  });
385
398
 
386
399
  if (hasStrongSignal) {
@@ -397,6 +410,8 @@ export async function searchHybrid(
397
410
  });
398
411
  if (expandResult.ok) {
399
412
  expansion = expandResult.value;
413
+ } else {
414
+ counters.fallbackEvents.push("expansion_error");
400
415
  }
401
416
  }
402
417
  }
@@ -428,6 +443,7 @@ export async function searchHybrid(
428
443
  until: temporalRange.until,
429
444
  categories: options.categories,
430
445
  author: options.author,
446
+ relPathPrefix: options.retrievalScope?.relPathPrefix,
431
447
  });
432
448
 
433
449
  // Propagate FTS syntax errors as INVALID_INPUT
@@ -462,6 +478,7 @@ export async function searchHybrid(
462
478
  until: temporalRange.until,
463
479
  categories: options.categories,
464
480
  author: options.author,
481
+ relPathPrefix: options.retrievalScope?.relPathPrefix,
465
482
  })
466
483
  )
467
484
  );
@@ -509,6 +526,7 @@ export async function searchHybrid(
509
526
  query,
510
527
  {
511
528
  limit: limit * 2 * retrievalMultiplier,
529
+ allowedMirrorHashes: options.retrievalScope?.allowedMirrorHashes,
512
530
  }
513
531
  );
514
532
 
@@ -552,7 +570,10 @@ export async function searchHybrid(
552
570
 
553
571
  const searchResult = await vectorIndex.searchNearest(
554
572
  new Float32Array(embedding),
555
- variant.limit
573
+ variant.limit,
574
+ {
575
+ allowedMirrorHashes: options.retrievalScope?.allowedMirrorHashes,
576
+ }
556
577
  );
557
578
  if (!searchResult.ok || searchResult.value.length === 0) {
558
579
  continue;
@@ -615,6 +636,7 @@ export async function searchHybrid(
615
636
  limit,
616
637
  candidateLimit,
617
638
  disabled: !options.graph || options.noGraph,
639
+ relPathPrefix: options.retrievalScope?.relPathPrefix,
618
640
  lang: options.lang,
619
641
  tagsAll: options.tagsAll,
620
642
  tagsAny: options.tagsAny,
@@ -735,6 +757,14 @@ export async function searchHybrid(
735
757
  const matchesMetadataFilters = (
736
758
  doc: (typeof docsResult.value)[number]
737
759
  ): boolean => {
760
+ const relPathPrefix = options.retrievalScope?.relPathPrefix;
761
+ if (
762
+ relPathPrefix !== undefined &&
763
+ doc.relPath !== relPathPrefix &&
764
+ !doc.relPath.startsWith(`${relPathPrefix}/`)
765
+ ) {
766
+ return false;
767
+ }
738
768
  if (!isWithinTemporalRange(doc.sourceMtime, temporalRange)) {
739
769
  return false;
740
770
  }
@@ -923,42 +953,51 @@ export async function searchHybrid(
923
953
  if (!docidMap.has(docidKey)) docidMap.set(docidKey, doc.docid);
924
954
  const collectionPath = collectionPaths.get(doc.collection);
925
955
  seenDocids.add(doc.docid);
926
- results.push({
927
- docid: doc.docid,
928
- score: candidate.blendedScore,
929
- uri: doc.uri,
930
- title: doc.title ?? undefined,
931
- contentType: doc.contentType ?? undefined,
932
- categories: doc.categories ?? undefined,
933
- line: snippetChunk.startLine,
934
- snippet,
935
- snippetLanguage: chunk.language ?? undefined,
936
- snippetRange,
937
- source: {
938
- relPath: doc.relPath,
939
- absPath: collectionPath
940
- ? `${collectionPath}/${doc.relPath}`
941
- : undefined,
942
- mime: doc.sourceMime,
943
- ext: doc.sourceExt,
944
- modifiedAt: doc.sourceMtime,
945
- documentDate: doc.frontmatterDate ?? undefined,
946
- sizeBytes: doc.sourceSize,
947
- sourceHash: doc.sourceHash,
948
- },
949
- conversion: {
950
- mirrorHash: candidate.mirrorHash,
951
- converterId: doc.converterId ?? undefined,
952
- converterVersion: doc.converterVersion ?? undefined,
953
- },
954
- [SEARCH_RESULT_PLANNER_METADATA]: {
955
- retrievalRank: candidateIndex + 1,
956
- mirrorHash: candidate.mirrorHash,
957
- seq: snippetChunk.seq,
958
- sources: [...candidate.sources].sort(),
959
- graphExpanded: candidate.sources.includes("graph"),
960
- },
961
- });
956
+ results.push(
957
+ attachSearchResultPlannerMetadata(
958
+ {
959
+ docid: doc.docid,
960
+ score: candidate.blendedScore,
961
+ uri: doc.uri,
962
+ title: doc.title ?? undefined,
963
+ contentType: doc.contentType ?? undefined,
964
+ categories: doc.categories ?? undefined,
965
+ line: snippetChunk.startLine,
966
+ snippet,
967
+ snippetLanguage: chunk.language ?? undefined,
968
+ snippetRange,
969
+ source: {
970
+ relPath: doc.relPath,
971
+ absPath: collectionPath
972
+ ? `${collectionPath}/${doc.relPath}`
973
+ : undefined,
974
+ mime: doc.sourceMime,
975
+ ext: doc.sourceExt,
976
+ modifiedAt: doc.sourceMtime,
977
+ documentDate: doc.frontmatterDate ?? undefined,
978
+ sizeBytes: doc.sourceSize,
979
+ sourceHash: doc.sourceHash,
980
+ },
981
+ conversion: {
982
+ mirrorHash: candidate.mirrorHash,
983
+ converterId: doc.converterId ?? undefined,
984
+ converterVersion: doc.converterVersion ?? undefined,
985
+ },
986
+ },
987
+ {
988
+ retrievalRank: candidateIndex + 1,
989
+ mirrorHash: candidate.mirrorHash,
990
+ seq: snippetChunk.seq,
991
+ sources: [...candidate.sources].sort(),
992
+ graphExpanded: candidate.sources.includes("graph"),
993
+ startLine: snippetChunk.startLine,
994
+ endLine: snippetChunk.endLine,
995
+ passageHash: new Bun.CryptoHasher("sha256")
996
+ .update(snippetChunk.text)
997
+ .digest("hex"),
998
+ }
999
+ )
1000
+ );
962
1001
  }
963
1002
  }
964
1003
  timings.assemblyMs = performance.now() - assemblyStartedAt;
@@ -1002,7 +1041,7 @@ export async function searchHybrid(
1002
1041
  const finalResults = results.slice(0, limit);
1003
1042
  await attachSearchResultContexts(store, finalResults);
1004
1043
 
1005
- return ok({
1044
+ const output: SearchResults = {
1006
1045
  results: finalResults,
1007
1046
  meta: {
1008
1047
  query,
@@ -1033,5 +1072,74 @@ export async function searchHybrid(
1033
1072
  explain: explainData,
1034
1073
  trace: diagnoseTrace,
1035
1074
  },
1075
+ };
1076
+ const fallbackCodes = [...new Set(counters.fallbackEvents)].sort();
1077
+ const capabilityOutcomes = [
1078
+ { capability: "lexical_search", status: "used" as const },
1079
+ vectorAvailable
1080
+ ? fallbackCodes.includes("vector_embed_error")
1081
+ ? {
1082
+ capability: "semantic_search",
1083
+ status: "failed" as const,
1084
+ reasonCode: "vector_embed_error",
1085
+ }
1086
+ : { capability: "semantic_search", status: "used" as const }
1087
+ : {
1088
+ capability: "semantic_search",
1089
+ status: "unavailable" as const,
1090
+ reasonCode: "vector_unavailable",
1091
+ },
1092
+ expansion !== null
1093
+ ? { capability: "query_expansion", status: "used" as const }
1094
+ : {
1095
+ capability: "query_expansion",
1096
+ status:
1097
+ expansionStatus === "attempted"
1098
+ ? ("failed" as const)
1099
+ : ("unavailable" as const),
1100
+ reasonCode:
1101
+ expansionStatus === "attempted"
1102
+ ? "expansion_error"
1103
+ : expansionStatus === "skipped_strong"
1104
+ ? "expansion_skipped_strong"
1105
+ : "expansion_disabled",
1106
+ },
1107
+ rerankResult.reranked
1108
+ ? { capability: "reranking", status: "used" as const }
1109
+ : {
1110
+ capability: "reranking",
1111
+ status:
1112
+ rerankResult.fallbackReason === "error"
1113
+ ? ("failed" as const)
1114
+ : ("unavailable" as const),
1115
+ reasonCode:
1116
+ rerankResult.fallbackReason === "error"
1117
+ ? "rerank_error"
1118
+ : "rerank_disabled",
1119
+ },
1120
+ graphExpansion.meta.enabled
1121
+ ? { capability: "graph_expansion", status: "used" as const }
1122
+ : {
1123
+ capability: "graph_expansion",
1124
+ status: "unavailable" as const,
1125
+ reasonCode:
1126
+ graphExpansion.meta.fallbackReasons[0] ?? "graph_disabled",
1127
+ },
1128
+ ];
1129
+ attachSearchResultsTraceMetadata(output, {
1130
+ capabilityOutcomes,
1131
+ fallbackCodes,
1036
1132
  });
1133
+ const traceResult = await options.traceSession?.recordRetrieval(
1134
+ output,
1135
+ timings.totalMs
1136
+ );
1137
+ if (traceResult && !traceResult.ok) {
1138
+ return err(
1139
+ "QUERY_FAILED",
1140
+ `Trace recording failed: ${traceResult.error.message}`,
1141
+ traceResult.error.cause
1142
+ );
1143
+ }
1144
+ return ok(output);
1037
1145
  }
@@ -27,6 +27,8 @@ import {
27
27
  resolveTemporalRange,
28
28
  shouldSortByRecency,
29
29
  } from "./temporal";
30
+ import { attachSearchResultPlannerMetadata } from "./trace-metadata";
31
+ import { SEARCH_RESULT_PLANNER_METADATA } from "./types";
30
32
 
31
33
  // ─────────────────────────────────────────────────────────────────────────────
32
34
  // Score Normalization
@@ -113,7 +115,7 @@ function buildSearchResult(ctx: BuildResultContext): SearchResult {
113
115
  : undefined;
114
116
  }
115
117
 
116
- return {
118
+ const result: SearchResult = {
117
119
  docid: fts.docid ?? "",
118
120
  score: fts.score, // Raw score, normalized later as batch
119
121
  uri: fts.uri ?? "",
@@ -127,6 +129,19 @@ function buildSearchResult(ctx: BuildResultContext): SearchResult {
127
129
  source,
128
130
  conversion: fts.mirrorHash ? { mirrorHash: fts.mirrorHash } : undefined,
129
131
  };
132
+ if (!(chunk && fts.mirrorHash)) return result;
133
+ return attachSearchResultPlannerMetadata(result, {
134
+ retrievalRank: 0,
135
+ mirrorHash: fts.mirrorHash,
136
+ seq: chunk.seq,
137
+ sources: ["bm25"],
138
+ graphExpanded: false,
139
+ startLine: chunk.startLine,
140
+ endLine: chunk.endLine,
141
+ passageHash: new Bun.CryptoHasher("sha256")
142
+ .update(chunk.text)
143
+ .digest("hex"),
144
+ });
130
145
  }
131
146
 
132
147
  // ─────────────────────────────────────────────────────────────────────────────
@@ -142,6 +157,7 @@ export async function searchBm25(
142
157
  query: string,
143
158
  options: SearchOptions = {}
144
159
  ): Promise<ReturnType<typeof ok<SearchResults>>> {
160
+ const traceStartedAt = options.traceSession ? performance.now() : 0;
145
161
  const limit = options.limit ?? 20;
146
162
  const minScore = options.minScore ?? 0;
147
163
  const recencySort = shouldSortByRecency(query);
@@ -161,6 +177,7 @@ export async function searchBm25(
161
177
  const ftsResult = await store.searchFts(query, {
162
178
  limit: retrievalLimit,
163
179
  collection: options.collection,
180
+ relPathPrefix: options.retrievalScope?.relPathPrefix,
164
181
  language: options.lang,
165
182
  snippet: !(options.full || options.lineNumbers),
166
183
  tagsAll: options.tagsAll,
@@ -331,9 +348,13 @@ export async function searchBm25(
331
348
  }
332
349
 
333
350
  const finalResults = filteredResults.slice(0, limit);
351
+ for (const [index, result] of finalResults.entries()) {
352
+ const metadata = result[SEARCH_RESULT_PLANNER_METADATA];
353
+ if (metadata) metadata.retrievalRank = index + 1;
354
+ }
334
355
  await attachSearchResultContexts(store, finalResults);
335
356
 
336
- return ok({
357
+ const output: SearchResults = {
337
358
  results: finalResults,
338
359
  meta: {
339
360
  query,
@@ -349,5 +370,17 @@ export async function searchBm25(
349
370
  author: options.author,
350
371
  queryLanguage,
351
372
  },
352
- });
373
+ };
374
+ const traceResult = await options.traceSession?.recordRetrieval(
375
+ output,
376
+ performance.now() - traceStartedAt
377
+ );
378
+ if (traceResult && !traceResult.ok) {
379
+ return err(
380
+ "QUERY_FAILED",
381
+ `Trace recording failed: ${traceResult.error.message}`,
382
+ traceResult.error.cause
383
+ );
384
+ }
385
+ return ok(output);
353
386
  }
@@ -0,0 +1,47 @@
1
+ /** Non-canonical retrieval metadata attached without changing public payloads. */
2
+
3
+ import type {
4
+ Citation,
5
+ CitationTraceMetadata,
6
+ SearchResult,
7
+ SearchResultPlannerMetadata,
8
+ SearchResults,
9
+ SearchResultsTraceMetadata,
10
+ } from "./types";
11
+
12
+ import {
13
+ CITATION_TRACE_METADATA,
14
+ SEARCH_RESULT_PLANNER_METADATA,
15
+ SEARCH_RESULTS_TRACE_METADATA,
16
+ } from "./types";
17
+
18
+ const attachHiddenMetadata = <Value extends object, Metadata>(
19
+ value: Value,
20
+ key: symbol,
21
+ metadata: Metadata
22
+ ): Value => {
23
+ Object.defineProperty(value, key, {
24
+ configurable: false,
25
+ enumerable: false,
26
+ value: metadata,
27
+ writable: false,
28
+ });
29
+ return value;
30
+ };
31
+
32
+ export const attachSearchResultPlannerMetadata = <Result extends SearchResult>(
33
+ result: Result,
34
+ metadata: SearchResultPlannerMetadata
35
+ ): Result =>
36
+ attachHiddenMetadata(result, SEARCH_RESULT_PLANNER_METADATA, metadata);
37
+
38
+ export const attachCitationTraceMetadata = <Result extends Citation>(
39
+ citation: Result,
40
+ metadata: CitationTraceMetadata
41
+ ): Result => attachHiddenMetadata(citation, CITATION_TRACE_METADATA, metadata);
42
+
43
+ export const attachSearchResultsTraceMetadata = <Result extends SearchResults>(
44
+ results: Result,
45
+ metadata: SearchResultsTraceMetadata
46
+ ): Result =>
47
+ attachHiddenMetadata(results, SEARCH_RESULTS_TRACE_METADATA, metadata);
@@ -5,6 +5,7 @@
5
5
  * @module src/pipeline/types
6
6
  */
7
7
 
8
+ import type { RetrievalTraceSession } from "../core/retrieval-trace-session";
8
9
  import type { StoreResult } from "../store/types";
9
10
 
10
11
  // ─────────────────────────────────────────────────────────────────────────────
@@ -48,6 +49,11 @@ export interface SearchResultPlannerMetadata {
48
49
  seq: number;
49
50
  sources: FusionSource[];
50
51
  graphExpanded: boolean;
52
+ /** Exact canonical chunk coordinates, retained even for full-content output. */
53
+ startLine?: number;
54
+ endLine?: number;
55
+ /** SHA-256 of the complete canonical chunk lines. */
56
+ passageHash?: string;
51
57
  }
52
58
 
53
59
  /** Single search result matching output schema */
@@ -122,10 +128,26 @@ export interface SearchMeta {
122
128
  trace?: QueryDiagnoseTrace;
123
129
  }
124
130
 
131
+ export const SEARCH_RESULTS_TRACE_METADATA = Symbol(
132
+ "gno.searchResultsTraceMetadata"
133
+ );
134
+
135
+ export interface SearchCapabilityOutcome {
136
+ capability: string;
137
+ status: "attempted" | "used" | "unavailable" | "failed";
138
+ reasonCode?: string;
139
+ }
140
+
141
+ export interface SearchResultsTraceMetadata {
142
+ capabilityOutcomes: SearchCapabilityOutcome[];
143
+ fallbackCodes: string[];
144
+ }
145
+
125
146
  /** Complete search results wrapper */
126
147
  export interface SearchResults {
127
148
  results: SearchResult[];
128
149
  meta: SearchMeta;
150
+ [SEARCH_RESULTS_TRACE_METADATA]?: SearchResultsTraceMetadata;
129
151
  }
130
152
 
131
153
  // ─────────────────────────────────────────────────────────────────────────────
@@ -134,12 +156,19 @@ export interface SearchResults {
134
156
 
135
157
  /** Common options for all search commands */
136
158
  export interface SearchOptions {
159
+ /** Internal receipt seam; never serialized or included in public schemas. */
160
+ traceSession?: RetrievalTraceSession;
137
161
  /** Max results */
138
162
  limit?: number;
139
163
  /** Min score threshold (0-1) */
140
164
  minScore?: number;
141
165
  /** Filter by collection */
142
166
  collection?: string;
167
+ /** Internal exact corpus scope used by deterministic retrieval replay. */
168
+ retrievalScope?: {
169
+ relPathPrefix?: string;
170
+ allowedMirrorHashes: string[];
171
+ };
143
172
  /** Language filter/hint (BCP-47) */
144
173
  lang?: string;
145
174
  /** Include full content instead of snippet */
@@ -357,11 +386,25 @@ export const DEFAULT_PIPELINE_CONFIG: PipelineConfig = {
357
386
  // ─────────────────────────────────────────────────────────────────────────────
358
387
 
359
388
  /** Citation reference */
389
+ export const CITATION_TRACE_METADATA = Symbol("gno.citationTraceMetadata");
390
+
391
+ export interface CitationTraceMetadata {
392
+ sourceHash: string;
393
+ mirrorHash: string;
394
+ passageHash: string;
395
+ seq?: number;
396
+ rank: number;
397
+ plannerRank?: number;
398
+ sources?: FusionSource[];
399
+ graphExpanded?: boolean;
400
+ }
401
+
360
402
  export interface Citation {
361
403
  docid: string;
362
404
  uri: string;
363
405
  startLine?: number;
364
406
  endLine?: number;
407
+ [CITATION_TRACE_METADATA]?: CitationTraceMetadata;
365
408
  }
366
409
 
367
410
  /** Source selection entry for answer-generation explain */