@gmickel/gno 1.19.0 → 1.21.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 (85) hide show
  1. package/README.md +28 -8
  2. package/assets/skill/SKILL.md +73 -27
  3. package/assets/skill/mcp-reference.md +7 -2
  4. package/assets/skill/recipes/citation-and-provenance.md +32 -9
  5. package/package.json +1 -1
  6. package/spec/cli.md +142 -17
  7. package/spec/db/schema.sql +170 -0
  8. package/spec/evals-agentic.md +87 -5
  9. package/spec/mcp.md +75 -3
  10. package/spec/output-schemas/ask.schema.json +198 -0
  11. package/spec/output-schemas/capsule-reverified-event.schema.json +47 -0
  12. package/spec/output-schemas/changes.schema.json +280 -0
  13. package/spec/output-schemas/claim-verification.schema.json +291 -0
  14. package/spec/output-schemas/context-capsule-v1.schema.json +36 -1
  15. package/spec/output-schemas/document-diff.schema.json +185 -0
  16. package/spec/output-schemas/impact.schema.json +122 -0
  17. package/spec/output-schemas/saved-capsule-list.schema.json +16 -0
  18. package/spec/output-schemas/saved-capsule-registration.schema.json +172 -0
  19. package/spec/output-schemas/saved-capsule-reverification.schema.json +59 -0
  20. package/spec/output-schemas/saved-capsule-unwatch.schema.json +16 -0
  21. package/spec/output-schemas/saved-capsule-watch.schema.json +17 -0
  22. package/src/app/context-runtime-contract.ts +10 -5
  23. package/src/app/context-runtime-input.ts +29 -1
  24. package/src/app/context-runtime-types.ts +4 -0
  25. package/src/app/context-runtime.ts +5 -1
  26. package/src/app/context-surface.ts +4 -0
  27. package/src/app/verified-ask.ts +291 -0
  28. package/src/cli/commands/ask-format.ts +255 -0
  29. package/src/cli/commands/ask.ts +40 -149
  30. package/src/cli/commands/changes.ts +160 -0
  31. package/src/cli/commands/context-saved.ts +189 -0
  32. package/src/cli/options.ts +8 -0
  33. package/src/cli/program.ts +227 -1
  34. package/src/core/capsule-registry.ts +279 -0
  35. package/src/core/capsule-reverification-scheduler.ts +218 -0
  36. package/src/core/capsule-reverification.ts +289 -0
  37. package/src/core/change-diff.ts +182 -0
  38. package/src/core/change-journal.ts +228 -0
  39. package/src/core/context-budget.ts +6 -0
  40. package/src/core/context-capsule-retrieval-schema.ts +4 -0
  41. package/src/core/context-capsule-schema.ts +17 -0
  42. package/src/core/context-capsule-validation.ts +3 -2
  43. package/src/core/context-capsule.ts +18 -0
  44. package/src/core/context-compiler.ts +33 -21
  45. package/src/core/context-evidence.ts +6 -0
  46. package/src/core/knowledge-delta.ts +395 -0
  47. package/src/core/knowledge-impact.ts +202 -0
  48. package/src/core/retrieval-trace-evidence-origin.ts +3 -0
  49. package/src/core/retrieval-trace-session.ts +15 -2
  50. package/src/ingestion/sync.ts +214 -165
  51. package/src/llm/errors.ts +10 -1
  52. package/src/llm/httpGeneration.ts +11 -1
  53. package/src/llm/nodeLlamaCpp/generation.ts +54 -10
  54. package/src/llm/types.ts +6 -0
  55. package/src/mcp/tools/ask.ts +228 -0
  56. package/src/mcp/tools/changes.ts +80 -0
  57. package/src/mcp/tools/context.ts +28 -7
  58. package/src/mcp/tools/index.ts +38 -0
  59. package/src/pipeline/claim-verification-schema.ts +235 -0
  60. package/src/pipeline/claim-verification.ts +487 -0
  61. package/src/pipeline/claim-verifier.ts +474 -0
  62. package/src/pipeline/types.ts +25 -0
  63. package/src/sdk/client.ts +77 -2
  64. package/src/sdk/index.ts +7 -0
  65. package/src/sdk/types.ts +22 -0
  66. package/src/serve/doc-events.ts +12 -1
  67. package/src/serve/public/components/AskVerificationPanel.tsx +189 -0
  68. package/src/serve/public/globals.built.css +1 -1
  69. package/src/serve/public/pages/Ask.tsx +42 -4
  70. package/src/serve/resident-runtime.ts +22 -0
  71. package/src/serve/routes/api.ts +162 -3
  72. package/src/serve/routes/changes.ts +102 -0
  73. package/src/serve/server.ts +34 -0
  74. package/src/serve/watch-service.ts +9 -0
  75. package/src/store/index.ts +21 -0
  76. package/src/store/migrations/015-document-change-journal.ts +85 -0
  77. package/src/store/migrations/016-saved-capsules.ts +131 -0
  78. package/src/store/migrations/017-document-change-retention-counters.ts +33 -0
  79. package/src/store/migrations/018-saved-capsule-registration-epoch.ts +24 -0
  80. package/src/store/migrations/019-saved-capsule-registration-generation.ts +53 -0
  81. package/src/store/migrations/index.ts +10 -0
  82. package/src/store/sqlite/adapter.ts +291 -7
  83. package/src/store/sqlite/capsule-registry-store.ts +534 -0
  84. package/src/store/sqlite/change-journal-store.ts +473 -0
  85. package/src/store/types.ts +262 -0
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Shared, storage-agnostic contracts for the document change journal.
3
+ */
4
+
5
+ import type {
6
+ DocumentChangeDateDelta,
7
+ DocumentChangeSet,
8
+ DocumentChangeStructureDelta,
9
+ DocumentChangeRetentionPolicy,
10
+ } from "../store/types";
11
+
12
+ const CURSOR_PREFIX = "gno-change-v1.";
13
+ const MAX_DELTA_ITEMS_PER_SIDE = 16;
14
+ const MAX_DELTA_VALUE_JSON_BYTES = 256;
15
+ export const MAX_DOCUMENT_CHANGE_DELTA_JSON_BYTES = 16 * 1024;
16
+ const UTF8_ENCODER = new TextEncoder();
17
+
18
+ export const DEFAULT_DOCUMENT_CHANGE_RETENTION: DocumentChangeRetentionPolicy =
19
+ {
20
+ maxAgeDays: 30,
21
+ maxEntries: 10_000,
22
+ maxBytes: 16 * 1024 * 1024,
23
+ };
24
+
25
+ export const EMPTY_DOCUMENT_CHANGE_SET: DocumentChangeSet = {
26
+ added: [],
27
+ removed: [],
28
+ };
29
+
30
+ export const EMPTY_DOCUMENT_CHANGE_DATE_DELTA: DocumentChangeDateDelta = {
31
+ added: [],
32
+ removed: [],
33
+ changed: [],
34
+ };
35
+
36
+ export const EMPTY_DOCUMENT_CHANGE_STRUCTURE_DELTA: DocumentChangeStructureDelta =
37
+ {
38
+ headings: EMPTY_DOCUMENT_CHANGE_SET,
39
+ links: EMPTY_DOCUMENT_CHANGE_SET,
40
+ typedEdges: EMPTY_DOCUMENT_CHANGE_SET,
41
+ dates: EMPTY_DOCUMENT_CHANGE_DATE_DELTA,
42
+ truncated: false,
43
+ };
44
+
45
+ const normalizeValues = (
46
+ values: readonly string[] | undefined
47
+ ): { values: string[]; truncated: boolean } => {
48
+ const unique = [
49
+ ...new Set(
50
+ (values ?? [])
51
+ .map((value) => value.trim())
52
+ .filter((value) => value.length > 0)
53
+ ),
54
+ ].sort();
55
+ const selected: string[] = [];
56
+ const selectedValues = new Set<string>();
57
+ let truncated = false;
58
+ for (const value of unique) {
59
+ let normalized = "";
60
+ let jsonByteLength = 0;
61
+ for (const character of value) {
62
+ const escaped = JSON.stringify(character).slice(1, -1);
63
+ const characterBytes = UTF8_ENCODER.encode(escaped).byteLength;
64
+ if (jsonByteLength + characterBytes > MAX_DELTA_VALUE_JSON_BYTES) {
65
+ truncated = true;
66
+ break;
67
+ }
68
+ normalized += character;
69
+ jsonByteLength += characterBytes;
70
+ }
71
+ if (normalized !== value) {
72
+ truncated = true;
73
+ }
74
+ if (selectedValues.has(normalized)) {
75
+ truncated = true;
76
+ continue;
77
+ }
78
+ if (selected.length === MAX_DELTA_ITEMS_PER_SIDE) {
79
+ truncated = true;
80
+ continue;
81
+ }
82
+ selected.push(normalized);
83
+ selectedValues.add(normalized);
84
+ }
85
+ return {
86
+ values: selected,
87
+ truncated,
88
+ };
89
+ };
90
+
91
+ export interface SerializedDocumentChangeStructureDelta {
92
+ delta: DocumentChangeStructureDelta;
93
+ headingDeltaJson: string;
94
+ linkDeltaJson: string;
95
+ typedEdgeDeltaJson: string;
96
+ dateDeltaJson: string;
97
+ }
98
+
99
+ const normalizeSet = (
100
+ value: Partial<DocumentChangeSet> | undefined
101
+ ): { value: DocumentChangeSet; truncated: boolean } => {
102
+ const added = normalizeValues(value?.added);
103
+ const removed = normalizeValues(value?.removed);
104
+ return {
105
+ value: { added: added.values, removed: removed.values },
106
+ truncated: added.truncated || removed.truncated,
107
+ };
108
+ };
109
+
110
+ const normalizeDates = (
111
+ value: Partial<DocumentChangeDateDelta> | undefined
112
+ ): { value: DocumentChangeDateDelta; truncated: boolean } => {
113
+ const added = normalizeValues(value?.added);
114
+ const removed = normalizeValues(value?.removed);
115
+ const changed = normalizeValues(value?.changed);
116
+ return {
117
+ value: {
118
+ added: added.values,
119
+ removed: removed.values,
120
+ changed: changed.values,
121
+ },
122
+ truncated: added.truncated || removed.truncated || changed.truncated,
123
+ };
124
+ };
125
+
126
+ export const normalizeDocumentChangeStructureDelta = (
127
+ value?: Partial<DocumentChangeStructureDelta>
128
+ ): DocumentChangeStructureDelta => {
129
+ const headings = normalizeSet(value?.headings);
130
+ const links = normalizeSet(value?.links);
131
+ const typedEdges = normalizeSet(value?.typedEdges);
132
+ const dates = normalizeDates(value?.dates);
133
+ return {
134
+ headings: headings.value,
135
+ links: links.value,
136
+ typedEdges: typedEdges.value,
137
+ dates: dates.value,
138
+ truncated:
139
+ (value?.truncated ?? false) ||
140
+ headings.truncated ||
141
+ links.truncated ||
142
+ typedEdges.truncated ||
143
+ dates.truncated,
144
+ };
145
+ };
146
+
147
+ /**
148
+ * Canonical storage projection for migration 015's UTF-8 byte constraints.
149
+ * Callers must use this instead of independently stringifying normalized deltas.
150
+ */
151
+ export const serializeDocumentChangeStructureDelta = (
152
+ value?: Partial<DocumentChangeStructureDelta>
153
+ ): SerializedDocumentChangeStructureDelta => {
154
+ const delta = normalizeDocumentChangeStructureDelta(value);
155
+ const serialized = {
156
+ headingDeltaJson: JSON.stringify(delta.headings),
157
+ linkDeltaJson: JSON.stringify(delta.links),
158
+ typedEdgeDeltaJson: JSON.stringify(delta.typedEdges),
159
+ dateDeltaJson: JSON.stringify(delta.dates),
160
+ };
161
+ for (const json of Object.values(serialized)) {
162
+ if (
163
+ UTF8_ENCODER.encode(json).byteLength >
164
+ MAX_DOCUMENT_CHANGE_DELTA_JSON_BYTES
165
+ ) {
166
+ throw new RangeError(
167
+ "Normalized document change structure exceeds its UTF-8 storage limit"
168
+ );
169
+ }
170
+ }
171
+ return { delta, ...serialized };
172
+ };
173
+
174
+ export const encodeDocumentChangeCursor = (sequence: number): string => {
175
+ if (!Number.isSafeInteger(sequence) || sequence < 0) {
176
+ throw new RangeError(
177
+ "Document change cursor sequence must be non-negative"
178
+ );
179
+ }
180
+ return `${CURSOR_PREFIX}${btoa(JSON.stringify({ sequence }))}`;
181
+ };
182
+
183
+ export const decodeDocumentChangeCursor = (cursor: string): number => {
184
+ if (!cursor.startsWith(CURSOR_PREFIX)) {
185
+ throw new TypeError("Invalid document change cursor");
186
+ }
187
+ try {
188
+ const parsed: unknown = JSON.parse(
189
+ atob(cursor.slice(CURSOR_PREFIX.length))
190
+ );
191
+ if (
192
+ !parsed ||
193
+ typeof parsed !== "object" ||
194
+ !("sequence" in parsed) ||
195
+ !Number.isSafeInteger(parsed.sequence) ||
196
+ (parsed.sequence as number) < 0
197
+ ) {
198
+ throw new TypeError("Invalid document change cursor");
199
+ }
200
+ return parsed.sequence as number;
201
+ } catch (cause) {
202
+ if (cause instanceof TypeError) {
203
+ throw cause;
204
+ }
205
+ throw new TypeError("Invalid document change cursor", { cause });
206
+ }
207
+ };
208
+
209
+ export const validateDocumentChangeRetentionPolicy = (
210
+ policy: DocumentChangeRetentionPolicy,
211
+ nowMs: number
212
+ ): void => {
213
+ if (
214
+ !Number.isSafeInteger(nowMs) ||
215
+ nowMs < 0 ||
216
+ !Number.isSafeInteger(policy.maxAgeDays) ||
217
+ policy.maxAgeDays < 1 ||
218
+ policy.maxAgeDays > 3650 ||
219
+ !Number.isSafeInteger(policy.maxEntries) ||
220
+ policy.maxEntries < 1 ||
221
+ policy.maxEntries > 1_000_000 ||
222
+ !Number.isSafeInteger(policy.maxBytes) ||
223
+ policy.maxBytes < 1 ||
224
+ policy.maxBytes > 1024 * 1024 * 1024
225
+ ) {
226
+ throw new RangeError("Invalid document change retention policy");
227
+ }
228
+ };
@@ -6,6 +6,8 @@
6
6
  * canonical Capsule payload, including coverage, omissions, and guidance.
7
7
  */
8
8
 
9
+ import type { FusionSource } from "../pipeline/types";
10
+
9
11
  export const CONTEXT_OMISSION_REASONS = [
10
12
  "duplicate",
11
13
  "overlap",
@@ -43,6 +45,10 @@ export interface MaterializedContextCandidate<
43
45
  text: string;
44
46
  facets: string[];
45
47
  retrievalRank: number;
48
+ /** Absent only for legacy results that predate planner provenance metadata. */
49
+ retrievalSources?: FusionSource[];
50
+ /** Absent only for legacy results that predate planner provenance metadata. */
51
+ graphExpanded?: boolean;
46
52
  value: T;
47
53
  }
48
54
 
@@ -59,10 +59,14 @@ export const contextCapsuleRetrievalSchema = z
59
59
  .object({
60
60
  author: z.string().min(1).max(256).nullable(),
61
61
  lang: z.string().min(1).max(64).nullable(),
62
+ intent: z.string().min(1).max(16_384).nullable().optional(),
63
+ exclude: z.array(nonEmptyText.max(256)).max(128).optional(),
64
+ minScore: z.number().min(0).max(1).nullable().optional(),
62
65
  queryModes: z.array(queryModeSchema).max(128),
63
66
  limit: z.number().int().positive(),
64
67
  candidateLimit: z.number().int().positive(),
65
68
  graphRequested: z.boolean(),
69
+ rerankRequested: z.boolean().optional(),
66
70
  })
67
71
  .strict(),
68
72
  capabilityStates: z
@@ -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: {
@@ -64,8 +64,8 @@ export interface ContextRetrievalRequest extends HybridSearchOptions {
64
64
  export interface ContextRetrievalCandidate {
65
65
  result: SearchResult;
66
66
  retrievalRank: number;
67
- retrievalSources: FusionSource[];
68
- graphExpanded: boolean;
67
+ retrievalSources?: FusionSource[];
68
+ graphExpanded?: boolean;
69
69
  contextIds: string[];
70
70
  observedAt: string | null;
71
71
  }
@@ -97,9 +97,13 @@ export interface ContextCompilerInput {
97
97
  categories?: string[];
98
98
  author?: string;
99
99
  lang?: string;
100
+ intent?: string;
101
+ exclude?: string[];
102
+ minScore?: number;
100
103
  since?: string;
101
104
  until?: string;
102
105
  graph?: boolean;
106
+ noRerank?: boolean;
103
107
  limit?: number;
104
108
  candidateLimit?: number;
105
109
  /** Frozen once by the caller; never defaulted from wall-clock time. */
@@ -164,16 +168,9 @@ const compareCodeUnits = (left: string, right: string): number => {
164
168
  };
165
169
 
166
170
  const plannerMeta = (
167
- result: SearchResult,
168
- fallbackRank: number
169
- ): SearchResultPlannerMetadata =>
170
- result[SEARCH_RESULT_PLANNER_METADATA] ?? {
171
- retrievalRank: fallbackRank,
172
- mirrorHash: result.conversion?.mirrorHash ?? "",
173
- seq: 0,
174
- sources: [],
175
- graphExpanded: false,
176
- };
171
+ result: SearchResult
172
+ ): SearchResultPlannerMetadata | undefined =>
173
+ result[SEARCH_RESULT_PLANNER_METADATA];
177
174
 
178
175
  const compareSearchResults = (
179
176
  left: SearchResult,
@@ -219,7 +216,9 @@ const referenceFromResult = (
219
216
  const normalizeMaterialized = <T>(
220
217
  draft: ContextMaterializedDraft<T>,
221
218
  facets: string[],
222
- retrievalRank: number
219
+ retrievalRank: number,
220
+ retrievalSources: FusionSource[] | undefined,
221
+ graphExpanded: boolean | undefined
223
222
  ): MaterializedContextCandidate<T> => {
224
223
  const text = draft.text;
225
224
  if (
@@ -250,6 +249,10 @@ const normalizeMaterialized = <T>(
250
249
  text,
251
250
  facets,
252
251
  retrievalRank,
252
+ ...(retrievalSources === undefined
253
+ ? {}
254
+ : { retrievalSources: [...retrievalSources].sort(compareCodeUnits) }),
255
+ ...(graphExpanded === undefined ? {} : { graphExpanded }),
253
256
  value: draft.value,
254
257
  };
255
258
  };
@@ -352,10 +355,13 @@ export const planContextEvidence = async <T, P>(
352
355
  categories: input.categories,
353
356
  author: input.author,
354
357
  lang: input.lang,
358
+ intent: input.intent,
359
+ exclude: input.exclude,
360
+ minScore: input.minScore,
355
361
  since: temporalRange.since,
356
362
  until: temporalRange.until,
357
363
  graph: hasRerankBudget ? input.graph : false,
358
- noRerank: hasRerankBudget ? undefined : true,
364
+ noRerank: input.noRerank || !hasRerankBudget ? true : undefined,
359
365
  limit: resultLimit === undefined ? undefined : Math.max(1, resultLimit),
360
366
  candidateLimit:
361
367
  rerankLimit === undefined ? undefined : Math.max(1, rerankLimit),
@@ -377,7 +383,7 @@ export const planContextEvidence = async <T, P>(
377
383
  const results = decoratedResults
378
384
  .map((result, index) => ({
379
385
  result,
380
- retrievalRank: plannerMeta(result, index + 1).retrievalRank,
386
+ retrievalRank: plannerMeta(result)?.retrievalRank ?? index + 1,
381
387
  }))
382
388
  .sort(
383
389
  (left, right) =>
@@ -407,8 +413,8 @@ export const planContextEvidence = async <T, P>(
407
413
  const referencesByCandidate: ContextCandidateReference[] = [];
408
414
 
409
415
  for (const [index, result] of results.entries()) {
410
- const meta = plannerMeta(result, index + 1);
411
- for (const source of meta.sources) retrievalSources.add(source);
416
+ const meta = plannerMeta(result);
417
+ for (const source of meta?.sources ?? []) retrievalSources.add(source);
412
418
  const retrievalReference = referenceFromResult(result);
413
419
  if (!isContextUriInScope(result.uri, indexName, collections, uriPrefix)) {
414
420
  for (const facet of facetPlan) {
@@ -431,9 +437,13 @@ export const planContextEvidence = async <T, P>(
431
437
  }
432
438
  plannedCandidates.push({
433
439
  result,
434
- retrievalRank: meta.retrievalRank,
435
- retrievalSources: [...meta.sources].sort(compareCodeUnits),
436
- graphExpanded: meta.graphExpanded,
440
+ retrievalRank: meta?.retrievalRank ?? index + 1,
441
+ ...(meta === undefined
442
+ ? {}
443
+ : {
444
+ retrievalSources: [...meta.sources].sort(compareCodeUnits),
445
+ graphExpanded: meta.graphExpanded,
446
+ }),
437
447
  contextIds:
438
448
  guidance.idsByResultIdentity.get(
439
449
  contextGuidanceResultIdentity(result)
@@ -486,7 +496,9 @@ export const planContextEvidence = async <T, P>(
486
496
  normalizeMaterialized(
487
497
  outcome.candidate,
488
498
  matchedFacets,
489
- plannedCandidate.retrievalRank
499
+ plannedCandidate.retrievalRank,
500
+ plannedCandidate.retrievalSources,
501
+ plannedCandidate.graphExpanded
490
502
  )
491
503
  );
492
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
  };