@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.
Files changed (153) hide show
  1. package/README.md +14 -7
  2. package/assets/skill/SKILL.md +54 -12
  3. package/assets/skill/mcp-reference.md +7 -2
  4. package/assets/skill/recipes/citation-and-provenance.md +32 -9
  5. package/package.json +2 -1
  6. package/spec/AGENTS.md +83 -0
  7. package/spec/CLAUDE.md +83 -0
  8. package/spec/bench-fixture.schema.json +137 -0
  9. package/spec/cli.md +2919 -0
  10. package/spec/db/schema.sql +442 -0
  11. package/spec/evals-agentic.md +592 -0
  12. package/spec/evals.md +1106 -0
  13. package/spec/mcp.md +2279 -0
  14. package/spec/output-schemas/activation-verification.schema.json +515 -0
  15. package/spec/output-schemas/ask.schema.json +564 -0
  16. package/spec/output-schemas/backlinks.schema.json +131 -0
  17. package/spec/output-schemas/bench-result.schema.json +120 -0
  18. package/spec/output-schemas/capture-receipt.schema.json +143 -0
  19. package/spec/output-schemas/claim-verification.schema.json +291 -0
  20. package/spec/output-schemas/collection-list.schema.json +45 -0
  21. package/spec/output-schemas/context-capsule-v1.schema.json +726 -0
  22. package/spec/output-schemas/context-capsule-verification.schema.json +1338 -0
  23. package/spec/output-schemas/context-list.schema.json +21 -0
  24. package/spec/output-schemas/doctor.schema.json +313 -0
  25. package/spec/output-schemas/error.schema.json +30 -0
  26. package/spec/output-schemas/expansion.schema.json +37 -0
  27. package/spec/output-schemas/get.schema.json +140 -0
  28. package/spec/output-schemas/graph-query.schema.json +99 -0
  29. package/spec/output-schemas/graph.schema.json +371 -0
  30. package/spec/output-schemas/links-list.schema.json +186 -0
  31. package/spec/output-schemas/mcp-add-collection-result.schema.json +23 -0
  32. package/spec/output-schemas/mcp-capture-result.schema.json +152 -0
  33. package/spec/output-schemas/mcp-http-error.schema.json +30 -0
  34. package/spec/output-schemas/mcp-job-list.schema.json +58 -0
  35. package/spec/output-schemas/mcp-job-status.schema.json +224 -0
  36. package/spec/output-schemas/mcp-remove-result.schema.json +39 -0
  37. package/spec/output-schemas/mcp-sync-result.schema.json +41 -0
  38. package/spec/output-schemas/mcp-tag-result.schema.json +33 -0
  39. package/spec/output-schemas/models-list.schema.json +93 -0
  40. package/spec/output-schemas/multi-get.schema.json +103 -0
  41. package/spec/output-schemas/process-status.schema.json +119 -0
  42. package/spec/output-schemas/query-diagnose.schema.json +123 -0
  43. package/spec/output-schemas/resident-status.schema.json +154 -0
  44. package/spec/output-schemas/retrieval-trace-common.schema.json +492 -0
  45. package/spec/output-schemas/retrieval-trace-delete.schema.json +16 -0
  46. package/spec/output-schemas/retrieval-trace-export.schema.json +61 -0
  47. package/spec/output-schemas/retrieval-trace-filters.schema.json +139 -0
  48. package/spec/output-schemas/retrieval-trace-judgment.schema.json +15 -0
  49. package/spec/output-schemas/retrieval-trace-list.schema.json +18 -0
  50. package/spec/output-schemas/retrieval-trace-payloads.schema.json +178 -0
  51. package/spec/output-schemas/retrieval-trace-purge.schema.json +31 -0
  52. package/spec/output-schemas/retrieval-trace-qrels.schema.json +303 -0
  53. package/spec/output-schemas/retrieval-trace-replay.schema.json +286 -0
  54. package/spec/output-schemas/retrieval-trace-show.schema.json +69 -0
  55. package/spec/output-schemas/retrieval-trace-summary.schema.json +65 -0
  56. package/spec/output-schemas/search-result.schema.json +154 -0
  57. package/spec/output-schemas/search-results.schema.json +338 -0
  58. package/spec/output-schemas/similar.schema.json +84 -0
  59. package/spec/output-schemas/status.schema.json +676 -0
  60. package/spec/output-schemas/tags-list.schema.json +48 -0
  61. package/src/app/context-runtime-contract.ts +10 -5
  62. package/src/app/context-runtime-input.ts +29 -1
  63. package/src/app/context-runtime-types.ts +7 -0
  64. package/src/app/context-runtime.ts +20 -2
  65. package/src/app/context-surface.ts +4 -0
  66. package/src/app/verified-ask.ts +291 -0
  67. package/src/cli/commands/ask-format.ts +255 -0
  68. package/src/cli/commands/ask.ts +144 -183
  69. package/src/cli/commands/context-build.ts +56 -9
  70. package/src/cli/commands/get.ts +64 -3
  71. package/src/cli/commands/query.ts +62 -23
  72. package/src/cli/commands/replay.ts +140 -0
  73. package/src/cli/commands/search.ts +48 -3
  74. package/src/cli/commands/shared.ts +3 -1
  75. package/src/cli/commands/trace.ts +200 -0
  76. package/src/cli/commands/vsearch.ts +75 -53
  77. package/src/cli/program.ts +287 -1
  78. package/src/config/index.ts +9 -0
  79. package/src/config/retrieval-traces.ts +56 -0
  80. package/src/config/types.ts +4 -0
  81. package/src/core/context-budget.ts +6 -0
  82. package/src/core/context-capsule-retrieval-schema.ts +4 -0
  83. package/src/core/context-capsule-schema.ts +17 -0
  84. package/src/core/context-capsule-validation.ts +3 -2
  85. package/src/core/context-capsule.ts +18 -0
  86. package/src/core/context-compiler.ts +44 -25
  87. package/src/core/context-evidence.ts +6 -0
  88. package/src/core/retrieval-qrels.ts +405 -0
  89. package/src/core/retrieval-replay-candidate.ts +368 -0
  90. package/src/core/retrieval-replay-types.ts +109 -0
  91. package/src/core/retrieval-replay-validation.ts +89 -0
  92. package/src/core/retrieval-replay.ts +441 -0
  93. package/src/core/retrieval-trace-evidence-origin.ts +178 -0
  94. package/src/core/retrieval-trace-export.ts +113 -0
  95. package/src/core/retrieval-trace-filter-normalization.ts +27 -0
  96. package/src/core/retrieval-trace-filters.ts +19 -0
  97. package/src/core/retrieval-trace-management-helpers.ts +247 -0
  98. package/src/core/retrieval-trace-management-types.ts +132 -0
  99. package/src/core/retrieval-trace-management.ts +422 -0
  100. package/src/core/retrieval-trace-request.ts +141 -0
  101. package/src/core/retrieval-trace-session.ts +507 -0
  102. package/src/core/retrieval-trace.ts +472 -0
  103. package/src/llm/errors.ts +10 -1
  104. package/src/llm/httpGeneration.ts +11 -1
  105. package/src/llm/nodeLlamaCpp/generation.ts +54 -10
  106. package/src/llm/types.ts +6 -0
  107. package/src/mcp/tools/ask.ts +228 -0
  108. package/src/mcp/tools/context.ts +87 -15
  109. package/src/mcp/tools/get.ts +35 -1
  110. package/src/mcp/tools/index.ts +83 -0
  111. package/src/mcp/tools/query.ts +95 -64
  112. package/src/mcp/tools/search.ts +36 -13
  113. package/src/mcp/tools/trace.ts +143 -0
  114. package/src/mcp/tools/vsearch.ts +71 -38
  115. package/src/pipeline/answer.ts +167 -26
  116. package/src/pipeline/claim-verification-schema.ts +235 -0
  117. package/src/pipeline/claim-verification.ts +487 -0
  118. package/src/pipeline/claim-verifier.ts +474 -0
  119. package/src/pipeline/graph-retrieval.ts +15 -1
  120. package/src/pipeline/hybrid.ts +151 -43
  121. package/src/pipeline/search.ts +36 -3
  122. package/src/pipeline/trace-metadata.ts +47 -0
  123. package/src/pipeline/types.ts +68 -0
  124. package/src/pipeline/vsearch.ts +101 -38
  125. package/src/sdk/client.ts +415 -73
  126. package/src/sdk/documents.ts +48 -1
  127. package/src/sdk/index.ts +17 -0
  128. package/src/sdk/types.ts +28 -0
  129. package/src/serve/context-capsule.ts +67 -8
  130. package/src/serve/public/app.tsx +12 -1
  131. package/src/serve/public/components/AskVerificationPanel.tsx +189 -0
  132. package/src/serve/public/globals.built.css +1 -1
  133. package/src/serve/public/lib/workspace-tabs.ts +2 -0
  134. package/src/serve/public/pages/Ask.tsx +42 -4
  135. package/src/serve/public/pages/Dashboard.tsx +10 -0
  136. package/src/serve/public/pages/TraceHistory.tsx +478 -0
  137. package/src/serve/public/pages/trace-history-detail.tsx +224 -0
  138. package/src/serve/retrieval-trace.ts +28 -0
  139. package/src/serve/routes/api.ts +508 -68
  140. package/src/serve/routes/traces.ts +156 -0
  141. package/src/serve/server.ts +87 -2
  142. package/src/store/index.ts +31 -0
  143. package/src/store/migrations/014-retrieval-traces.ts +303 -0
  144. package/src/store/migrations/index.ts +2 -0
  145. package/src/store/retrieval-trace-codec.ts +384 -0
  146. package/src/store/sqlite/adapter.ts +153 -1
  147. package/src/store/sqlite/retrieval-trace-management-store.ts +341 -0
  148. package/src/store/sqlite/retrieval-trace-retention.ts +349 -0
  149. package/src/store/sqlite/retrieval-trace-rows.ts +267 -0
  150. package/src/store/sqlite/retrieval-trace-store.ts +515 -0
  151. package/src/store/types.ts +297 -0
  152. package/src/store/vector/sqlite-vec.ts +76 -1
  153. package/src/store/vector/types.ts +1 -1
@@ -0,0 +1,48 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "gno://schemas/tags-list@1.0",
4
+ "title": "GNO Tags List",
5
+ "description": "List of tags with document counts",
6
+ "type": "object",
7
+ "required": ["tags", "meta"],
8
+ "properties": {
9
+ "tags": {
10
+ "type": "array",
11
+ "description": "List of tags with counts",
12
+ "items": {
13
+ "type": "object",
14
+ "required": ["tag", "count"],
15
+ "properties": {
16
+ "tag": {
17
+ "type": "string",
18
+ "description": "Tag name (lowercase, alphanumeric with hyphens/dots/slashes)"
19
+ },
20
+ "count": {
21
+ "type": "integer",
22
+ "description": "Number of documents with this tag",
23
+ "minimum": 1
24
+ }
25
+ }
26
+ }
27
+ },
28
+ "meta": {
29
+ "type": "object",
30
+ "required": ["totalTags"],
31
+ "properties": {
32
+ "totalTags": {
33
+ "type": "integer",
34
+ "description": "Total number of unique tags",
35
+ "minimum": 0
36
+ },
37
+ "collection": {
38
+ "type": "string",
39
+ "description": "Collection filter applied"
40
+ },
41
+ "prefix": {
42
+ "type": "string",
43
+ "description": "Prefix filter applied"
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
@@ -24,8 +24,9 @@ import { resolveModelUri } from "../llm/registry";
24
24
  const fingerprint = (value: unknown): string =>
25
25
  sha256Text(canonicalVerifierJson(value));
26
26
 
27
- const configFingerprint = (deps: ContextCapsuleRuntimeDeps): string =>
28
- fingerprint(deps.config);
27
+ export const contextRuntimeConfigFingerprint = (
28
+ deps: Pick<ContextCapsuleRuntimeDeps, "config">
29
+ ): string => fingerprint(deps.config);
29
30
 
30
31
  const configuredContextFingerprint = (
31
32
  deps: ContextCapsuleRuntimeDeps
@@ -75,7 +76,7 @@ const capsuleCapabilityStates = (
75
76
  ["embedding_unavailable"]
76
77
  ),
77
78
  reranking: capabilityState(
78
- input.depthPolicy !== "fast",
79
+ input.depthPolicy !== "fast" && !input.noRerank,
79
80
  draft.retrieval.reranked,
80
81
  ["reranking_unavailable"]
81
82
  ),
@@ -191,10 +192,14 @@ export const projectContextCapsule = (
191
192
  request: {
192
193
  author: input.author,
193
194
  lang: input.lang,
195
+ intent: input.intent,
196
+ exclude: input.exclude,
197
+ minScore: input.minScore,
194
198
  queryModes: input.queryModes,
195
199
  limit: input.limit,
196
200
  candidateLimit: input.candidateLimit,
197
201
  graphRequested: input.graph,
202
+ rerankRequested: input.depthPolicy !== "fast" && !input.noRerank,
198
203
  },
199
204
  capabilityStates,
200
205
  indexSnapshot: {
@@ -221,7 +226,7 @@ export const projectContextCapsule = (
221
226
  : null,
222
227
  },
223
228
  fingerprints: {
224
- config: configFingerprint(deps),
229
+ config: contextRuntimeConfigFingerprint(deps),
225
230
  retrieval: retrievalFingerprint(base, snapshots.contextFingerprint),
226
231
  embeddingModel: capabilities.semanticSearch
227
232
  ? sha256Text(deps.embedPort?.modelUri ?? "")
@@ -292,7 +297,7 @@ export const currentContextFingerprints = (
292
297
  capsule: ContextCapsuleV1,
293
298
  deps: ContextCapsuleRuntimeDeps
294
299
  ) => ({
295
- config: configFingerprint(deps),
300
+ config: contextRuntimeConfigFingerprint(deps),
296
301
  retrieval: retrievalFingerprint(capsule, configuredContextFingerprint(deps)),
297
302
  embeddingModel: capsule.capabilities.semanticSearch
298
303
  ? sha256Text(
@@ -315,16 +315,40 @@ export const normalizeContextBuildInput = (
315
315
  "Context graph flag must be boolean"
316
316
  );
317
317
  }
318
+ if (input.noRerank !== undefined && typeof input.noRerank !== "boolean") {
319
+ throw new ContextRuntimeError(
320
+ "invalid_filter",
321
+ "Context noRerank flag must be boolean"
322
+ );
323
+ }
324
+ if (
325
+ input.minScore !== undefined &&
326
+ (typeof input.minScore !== "number" ||
327
+ !Number.isFinite(input.minScore) ||
328
+ input.minScore < 0 ||
329
+ input.minScore > 1)
330
+ ) {
331
+ throw new ContextRuntimeError(
332
+ "invalid_filter",
333
+ "Context minimum score must be between 0 and 1"
334
+ );
335
+ }
318
336
  const author =
319
337
  typeof input.author === "string"
320
338
  ? input.author.normalize("NFC").trim()
321
339
  : null;
322
340
  const lang =
323
341
  typeof input.lang === "string" ? input.lang.normalize("NFC").trim() : null;
342
+ const intent =
343
+ typeof input.intent === "string"
344
+ ? input.intent.normalize("NFC").trim()
345
+ : null;
324
346
  if (
325
347
  (input.author !== undefined &&
326
348
  (!author || author.length > MAX_FILTER_LENGTH)) ||
327
- (input.lang !== undefined && (!lang || !isValidLanguageHint(lang)))
349
+ (input.lang !== undefined && (!lang || !isValidLanguageHint(lang))) ||
350
+ (input.intent !== undefined &&
351
+ (!intent || intent.length > MAX_TEXT_LENGTH || intent.includes("\r")))
328
352
  ) {
329
353
  throw new ContextRuntimeError(
330
354
  "invalid_filter",
@@ -344,9 +368,13 @@ export const normalizeContextBuildInput = (
344
368
  categories: canonicalFilters(input.categories, "categories"),
345
369
  author,
346
370
  lang,
371
+ intent,
372
+ exclude: canonicalFilters(input.exclude, "exclude"),
373
+ minScore: input.minScore ?? null,
347
374
  since: temporalRange.since,
348
375
  until: temporalRange.until,
349
376
  graph: input.graph ?? false,
377
+ noRerank: input.noRerank ?? false,
350
378
  limit,
351
379
  candidateLimit,
352
380
  budgetTokens,
@@ -2,6 +2,7 @@ import type { Config } from "../config/types";
2
2
  import type { ContextCapsuleV1 } from "../core/context-capsule";
3
3
  import type { ContextEvidenceCompilerDeps } from "../core/context-evidence";
4
4
  import type { ContextVerifierDeps } from "../core/context-verifier";
5
+ import type { RetrievalTraceSession } from "../core/retrieval-trace-session";
5
6
  import type { EmbeddingPort, RerankPort } from "../llm/types";
6
7
  import type { QueryModeInput } from "../pipeline/types";
7
8
  import type { StorePort } from "../store/types";
@@ -21,9 +22,13 @@ export interface ContextCapsuleBuildInput {
21
22
  categories?: string[];
22
23
  author?: string;
23
24
  lang?: string;
25
+ intent?: string;
26
+ exclude?: string[];
27
+ minScore?: number;
24
28
  since?: string;
25
29
  until?: string;
26
30
  graph?: boolean;
31
+ noRerank?: boolean;
27
32
  limit?: number;
28
33
  candidateLimit?: number;
29
34
  budgetTokens: number;
@@ -45,6 +50,8 @@ export interface ContextCapsuleRuntimeDeps {
45
50
  countTokens?: (accountingJson: string) => number;
46
51
  tokenizerFingerprint?: string | null;
47
52
  resolveCurrentRanks?: ContextVerifierDeps["resolveCurrentRanks"];
53
+ /** Optional non-canonical receipt session owned by the calling surface. */
54
+ traceSession?: RetrievalTraceSession;
48
55
  }
49
56
 
50
57
  export type ContextRuntimeErrorCode =
@@ -48,7 +48,7 @@ export const buildContextCapsule = async (
48
48
  now,
49
49
  deps.config.collections.map((collection) => collection.name)
50
50
  );
51
- const noRerank = normalized.depthPolicy === "fast";
51
+ const noRerank = normalized.depthPolicy === "fast" || normalized.noRerank;
52
52
  const plan = await compileContextEvidence<ContextCapsuleV1>(
53
53
  {
54
54
  goal: normalized.goal,
@@ -62,9 +62,13 @@ export const buildContextCapsule = async (
62
62
  categories: normalized.categories,
63
63
  author: normalized.author ?? undefined,
64
64
  lang: normalized.lang ?? undefined,
65
+ intent: normalized.intent ?? undefined,
66
+ exclude: normalized.exclude,
67
+ minScore: normalized.minScore ?? undefined,
65
68
  since: normalized.since,
66
69
  until: normalized.until,
67
70
  graph: normalized.graph,
71
+ noRerank: normalized.noRerank,
68
72
  limit: normalized.limit,
69
73
  candidateLimit: normalized.candidateLimit,
70
74
  temporalNow: now,
@@ -89,7 +93,11 @@ export const buildContextCapsule = async (
89
93
  rerankPort: requestNoRerank ? null : (deps.rerankPort ?? null),
90
94
  },
91
95
  request.query,
92
- { ...request, noRerank: requestNoRerank }
96
+ {
97
+ ...request,
98
+ noRerank: requestNoRerank,
99
+ traceSession: deps.traceSession,
100
+ }
93
101
  );
94
102
  if (!result.ok) {
95
103
  throw new ContextRuntimeError(
@@ -115,6 +123,16 @@ export const buildContextCapsule = async (
115
123
  : "No in-scope evidence was available for the Context Capsule"
116
124
  );
117
125
  }
126
+ const traceResult = await deps.traceSession?.recordContext(
127
+ plan.projection.value
128
+ );
129
+ if (traceResult && !traceResult.ok) {
130
+ throw new ContextRuntimeError(
131
+ "retrieval_failed",
132
+ `Trace recording failed: ${traceResult.error.message}`,
133
+ traceResult.error.cause
134
+ );
135
+ }
118
136
  return plan.projection.value;
119
137
  };
120
138
 
@@ -30,9 +30,13 @@ export const contextBuildSurfaceSchema = z
30
30
  categories: stringList.optional(),
31
31
  author: z.string().optional(),
32
32
  lang: z.string().optional(),
33
+ intent: z.string().optional(),
34
+ exclude: stringList.optional(),
35
+ minScore: z.number().min(0).max(1).optional(),
33
36
  since: z.string().optional(),
34
37
  until: z.string().optional(),
35
38
  graph: z.boolean().optional(),
39
+ noRerank: z.boolean().optional(),
36
40
  limit: positiveInteger.optional(),
37
41
  candidateLimit: positiveInteger.optional(),
38
42
  budgetTokens: positiveInteger,
@@ -0,0 +1,291 @@
1
+ /** Shared closed-evidence Ask synthesis boundary. */
2
+
3
+ import type {
4
+ ContextCapsuleV1,
5
+ ContextCapsuleVerification,
6
+ } from "../core/context-capsule";
7
+ import type { RetrievalTraceSession } from "../core/retrieval-trace-session";
8
+ import type { GenerationPort } from "../llm/types";
9
+ import type { AskOptions, AskResult, Citation } from "../pipeline/types";
10
+ import type { ContextCapsuleRuntimeDeps } from "./context-runtime";
11
+
12
+ import { buildAnswerPrompt } from "../pipeline/answer-prompt";
13
+ import { CLAIM_ABSTENTION_TEXT } from "../pipeline/claim-verification";
14
+ import { verifyClaimsSemantically } from "../pipeline/claim-verifier";
15
+ import { attachCitationTraceMetadata } from "../pipeline/trace-metadata";
16
+ import { CITATION_TRACE_METADATA } from "../pipeline/types";
17
+ import {
18
+ buildContextCapsule,
19
+ verifyContextCapsuleRuntime,
20
+ } from "./context-runtime";
21
+ import { contextRuntimeConfigFingerprint } from "./context-runtime-contract";
22
+
23
+ const DEFAULT_CONTEXT_BUDGET_TOKENS = 12_000;
24
+ const DEFAULT_MAX_ANSWER_TOKENS = 512;
25
+ const DEFAULT_VERIFIED_ASK_LIMIT = 5;
26
+ const NUMERIC_CITATION_PATTERN = /\[(\d+)\]/g;
27
+
28
+ export interface VerifiedAskDeps extends ContextCapsuleRuntimeDeps {
29
+ genPort: GenerationPort;
30
+ traceSession?: RetrievalTraceSession;
31
+ }
32
+
33
+ const generationSources = (capsule: ContextCapsuleV1) => {
34
+ const guidanceById = new Map(
35
+ capsule.guidance.configuredContexts.map((item) => [
36
+ item.contextId,
37
+ item.text,
38
+ ])
39
+ );
40
+ return capsule.evidence.map((evidence, index) => ({
41
+ index: index + 1,
42
+ docid: evidence.docid,
43
+ uri: evidence.uri,
44
+ content: evidence.text,
45
+ guidance: evidence.contextIds
46
+ .flatMap((contextId) => {
47
+ const text = guidanceById.get(contextId);
48
+ return text ? [text] : [];
49
+ })
50
+ .join("\n"),
51
+ }));
52
+ };
53
+
54
+ /** Map model-facing numeric citations to immutable Capsule evidence IDs. */
55
+ export const mapAnswerCitationsToEvidence = (
56
+ answer: string,
57
+ capsule: ContextCapsuleV1
58
+ ): string =>
59
+ answer
60
+ .replace(NUMERIC_CITATION_PATTERN, (_marker, rawIndex: string) => {
61
+ const evidence = capsule.evidence[Number(rawIndex) - 1];
62
+ return evidence ? `[evidence:${evidence.evidenceId}]` : "";
63
+ })
64
+ .replace(/ {2,}/g, " ")
65
+ .trim();
66
+
67
+ const retainedCitations = (
68
+ capsule: ContextCapsuleV1,
69
+ evidenceIds: ReadonlySet<string>
70
+ ): Citation[] =>
71
+ capsule.evidence.flatMap((evidence) => {
72
+ if (!evidenceIds.has(evidence.evidenceId)) return [];
73
+ return [
74
+ attachCitationTraceMetadata(
75
+ {
76
+ evidenceId: evidence.evidenceId,
77
+ docid: evidence.docid,
78
+ uri: evidence.uri,
79
+ startLine: evidence.startLine,
80
+ endLine: evidence.endLine,
81
+ },
82
+ {
83
+ sourceHash: evidence.sourceHash,
84
+ mirrorHash: evidence.mirrorHash,
85
+ passageHash: evidence.passageHash,
86
+ rank: evidence.selectionRank,
87
+ plannerRank: evidence.retrievalRank,
88
+ ...(evidence.retrievalSources === undefined
89
+ ? {}
90
+ : { sources: evidence.retrievalSources }),
91
+ ...(evidence.graphExpanded === undefined
92
+ ? {}
93
+ : { graphExpanded: evidence.graphExpanded }),
94
+ }
95
+ ),
96
+ ];
97
+ });
98
+
99
+ const recordRetainedCitations = async (
100
+ traceSession: RetrievalTraceSession | undefined,
101
+ citations: readonly Citation[]
102
+ ): Promise<void> => {
103
+ if (!traceSession || citations.length === 0) return;
104
+ const evidence = citations.flatMap((citation) => {
105
+ const metadata = citation[CITATION_TRACE_METADATA];
106
+ if (
107
+ !metadata ||
108
+ citation.startLine === undefined ||
109
+ citation.endLine === undefined
110
+ ) {
111
+ return [];
112
+ }
113
+ return [
114
+ {
115
+ docid: citation.docid,
116
+ uri: citation.uri,
117
+ sourceHash: metadata.sourceHash,
118
+ mirrorHash: metadata.mirrorHash,
119
+ passageHash: metadata.passageHash,
120
+ startLine: citation.startLine,
121
+ endLine: citation.endLine,
122
+ rank: metadata.rank,
123
+ ...(metadata.plannerRank === undefined
124
+ ? {}
125
+ : { plannerRank: metadata.plannerRank }),
126
+ ...(metadata.sources === undefined
127
+ ? {}
128
+ : { sources: metadata.sources }),
129
+ ...(metadata.graphExpanded === undefined
130
+ ? {}
131
+ : { graphExpanded: metadata.graphExpanded }),
132
+ },
133
+ ];
134
+ });
135
+ if (evidence.length === 0) return;
136
+ const recorded = await traceSession.recordEvidence("cite", evidence);
137
+ if (!recorded.ok) {
138
+ throw new Error(`Trace recording failed: ${recorded.error.message}`);
139
+ }
140
+ };
141
+
142
+ const citationEvidenceIds = (
143
+ verification: Awaited<ReturnType<typeof verifyClaimsSemantically>>,
144
+ statuses: ReadonlySet<"supported" | "contradicted">
145
+ ): Set<string> =>
146
+ new Set(
147
+ verification.verification.claims.flatMap((claim) =>
148
+ statuses.has(claim.status as "supported" | "contradicted")
149
+ ? claim.evidence.map((evidence) => evidence.evidenceId)
150
+ : []
151
+ )
152
+ );
153
+
154
+ const recordCapability = async (
155
+ traceSession: RetrievalTraceSession | undefined,
156
+ capability: string,
157
+ status: "attempted" | "used" | "unavailable" | "failed",
158
+ reasonCode?: string
159
+ ): Promise<void> => {
160
+ const recorded = await traceSession?.recordCapability(
161
+ capability,
162
+ status,
163
+ reasonCode
164
+ );
165
+ if (recorded && !recorded.ok) {
166
+ throw new Error(`Trace recording failed: ${recorded.error.message}`);
167
+ }
168
+ };
169
+
170
+ export const buildVerifiedAsk = async (
171
+ query: string,
172
+ options: AskOptions,
173
+ deps: VerifiedAskDeps
174
+ ): Promise<AskResult> => {
175
+ const collection = options.collection;
176
+ const capsule = await buildContextCapsule(
177
+ {
178
+ goal: query,
179
+ query,
180
+ indexName: deps.indexName,
181
+ collections: collection ? [collection] : [],
182
+ queryModes: options.queryModes,
183
+ tagsAll: options.tagsAll,
184
+ tagsAny: options.tagsAny,
185
+ categories: options.categories,
186
+ author: options.author,
187
+ lang: options.lang,
188
+ intent: options.intent,
189
+ exclude: options.exclude,
190
+ minScore: options.minScore,
191
+ since: options.since,
192
+ until: options.until,
193
+ graph: Boolean(options.graph && !options.noGraph),
194
+ noRerank: options.noRerank,
195
+ limit: options.limit ?? DEFAULT_VERIFIED_ASK_LIMIT,
196
+ candidateLimit: options.candidateLimit,
197
+ budgetTokens:
198
+ options.contextBudgetTokens ?? DEFAULT_CONTEXT_BUDGET_TOKENS,
199
+ budgetBytes: options.contextBudgetBytes,
200
+ depthPolicy: "balanced",
201
+ },
202
+ deps
203
+ );
204
+ const freshness = await verifyContextCapsuleRuntime(capsule, deps);
205
+ return synthesizeVerifiedAsk(query, options, capsule, freshness, deps);
206
+ };
207
+
208
+ export const synthesizeVerifiedAsk = async (
209
+ query: string,
210
+ options: AskOptions,
211
+ capsule: ContextCapsuleV1,
212
+ freshness: ContextCapsuleVerification,
213
+ deps: Pick<
214
+ VerifiedAskDeps,
215
+ "config" | "genPort" | "indexName" | "traceSession"
216
+ >
217
+ ): Promise<AskResult> => {
218
+ await recordCapability(deps.traceSession, "answer_generation", "attempted");
219
+ const generated = await deps.genPort.generate(
220
+ buildAnswerPrompt(query, generationSources(capsule)),
221
+ {
222
+ temperature: 0,
223
+ maxTokens: options.maxAnswerTokens ?? DEFAULT_MAX_ANSWER_TOKENS,
224
+ }
225
+ );
226
+ await recordCapability(
227
+ deps.traceSession,
228
+ "answer_generation",
229
+ generated.ok ? "used" : "failed",
230
+ generated.ok ? undefined : "generation_failed"
231
+ );
232
+ const draftAnswer = generated.ok
233
+ ? mapAnswerCitationsToEvidence(generated.value, capsule)
234
+ : CLAIM_ABSTENTION_TEXT;
235
+ const verification = await verifyClaimsSemantically({
236
+ answer: draftAnswer,
237
+ capsule,
238
+ freshness,
239
+ genPort: generated.ok ? deps.genPort : null,
240
+ configFingerprint: contextRuntimeConfigFingerprint(deps),
241
+ });
242
+ await recordCapability(
243
+ deps.traceSession,
244
+ "claim_verification",
245
+ verification.semanticVerification.status === "completed"
246
+ ? "used"
247
+ : verification.semanticVerification.status,
248
+ verification.semanticVerification.reason
249
+ );
250
+ const citations = verification.verification.abstained
251
+ ? []
252
+ : retainedCitations(
253
+ capsule,
254
+ citationEvidenceIds(verification, new Set(["supported"]))
255
+ );
256
+ const traceCitations = retainedCitations(
257
+ capsule,
258
+ citationEvidenceIds(verification, new Set(["supported", "contradicted"]))
259
+ );
260
+ await recordRetainedCitations(deps.traceSession, traceCitations);
261
+ return {
262
+ query,
263
+ mode: capsule.capabilities.semanticSearch ? "hybrid" : "bm25_only",
264
+ queryLanguage: capsule.retrieval.request.lang ?? "und",
265
+ answer: verification.verification.abstained
266
+ ? (verification.verification.abstentionText ?? CLAIM_ABSTENTION_TEXT)
267
+ : draftAnswer,
268
+ citations,
269
+ results: [],
270
+ meta: {
271
+ expanded: false,
272
+ reranked: capsule.capabilities.reranking,
273
+ vectorsUsed: capsule.capabilities.semanticSearch,
274
+ intent: capsule.retrieval.request.intent ?? undefined,
275
+ candidateLimit: capsule.retrieval.request.candidateLimit,
276
+ exclude: capsule.retrieval.request.exclude,
277
+ answerGenerated: generated.ok,
278
+ totalResults: capsule.evidence.length,
279
+ verificationRequested: true,
280
+ abstained: verification.verification.abstained,
281
+ },
282
+ verification: {
283
+ schemaVersion: "1.0",
284
+ mode: "closed_capsule",
285
+ capsule,
286
+ freshness,
287
+ claims: verification.verification,
288
+ semantic: verification.semanticVerification,
289
+ },
290
+ };
291
+ };