@duckcodeailabs/dql-agent 1.14.2 → 1.14.4

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 (196) hide show
  1. package/dist/agent-run-engine.d.ts +239 -4
  2. package/dist/agent-run-engine.d.ts.map +1 -1
  3. package/dist/agent-run-engine.js +2924 -85
  4. package/dist/agent-run-engine.js.map +1 -1
  5. package/dist/agent-run-gates.d.ts.map +1 -1
  6. package/dist/agent-run-gates.js +51 -3
  7. package/dist/agent-run-gates.js.map +1 -1
  8. package/dist/agent-run-planner.d.ts +6 -0
  9. package/dist/agent-run-planner.d.ts.map +1 -1
  10. package/dist/agent-run-planner.js +2 -0
  11. package/dist/agent-run-planner.js.map +1 -1
  12. package/dist/agent-run-store.d.ts.map +1 -1
  13. package/dist/agent-run-store.js +591 -11
  14. package/dist/agent-run-store.js.map +1 -1
  15. package/dist/agentic/analyst-loop.d.ts +9 -1
  16. package/dist/agentic/analyst-loop.d.ts.map +1 -1
  17. package/dist/agentic/analyst-loop.js +204 -4
  18. package/dist/agentic/analyst-loop.js.map +1 -1
  19. package/dist/agentic/assumptions.d.ts +1 -1
  20. package/dist/agentic/assumptions.d.ts.map +1 -1
  21. package/dist/agentic/assumptions.js.map +1 -1
  22. package/dist/agentic/ledger-tools.d.ts.map +1 -1
  23. package/dist/agentic/ledger-tools.js +8 -1
  24. package/dist/agentic/ledger-tools.js.map +1 -1
  25. package/dist/agentic/orchestrator-policy.d.ts +3 -1
  26. package/dist/agentic/orchestrator-policy.d.ts.map +1 -1
  27. package/dist/agentic/orchestrator-policy.js +1 -1
  28. package/dist/agentic/orchestrator-policy.js.map +1 -1
  29. package/dist/agentic/research-agent.d.ts.map +1 -1
  30. package/dist/agentic/research-agent.js +10 -1
  31. package/dist/agentic/research-agent.js.map +1 -1
  32. package/dist/agentic/sql-authorization.d.ts +8 -0
  33. package/dist/agentic/sql-authorization.d.ts.map +1 -1
  34. package/dist/agentic/sql-authorization.js +3 -0
  35. package/dist/agentic/sql-authorization.js.map +1 -1
  36. package/dist/agentic/tool-loop.d.ts +13 -1
  37. package/dist/agentic/tool-loop.d.ts.map +1 -1
  38. package/dist/agentic/tool-loop.js +441 -32
  39. package/dist/agentic/tool-loop.js.map +1 -1
  40. package/dist/analytical-compatibility.d.ts.map +1 -1
  41. package/dist/analytical-compatibility.js +8 -1
  42. package/dist/analytical-compatibility.js.map +1 -1
  43. package/dist/analytical-failure-repair.d.ts.map +1 -1
  44. package/dist/analytical-failure-repair.js +6 -2
  45. package/dist/analytical-failure-repair.js.map +1 -1
  46. package/dist/analytical-frame.d.ts +42 -1
  47. package/dist/analytical-frame.d.ts.map +1 -1
  48. package/dist/analytical-frame.js +490 -26
  49. package/dist/analytical-frame.js.map +1 -1
  50. package/dist/analytical-orchestration.d.ts +1259 -9
  51. package/dist/analytical-orchestration.d.ts.map +1 -1
  52. package/dist/analytical-orchestration.js +970 -84
  53. package/dist/analytical-orchestration.js.map +1 -1
  54. package/dist/answer-loop.d.ts +230 -10
  55. package/dist/answer-loop.d.ts.map +1 -1
  56. package/dist/answer-loop.js +1182 -85
  57. package/dist/answer-loop.js.map +1 -1
  58. package/dist/answer-shape.d.ts +27 -0
  59. package/dist/answer-shape.d.ts.map +1 -1
  60. package/dist/answer-shape.js +55 -0
  61. package/dist/answer-shape.js.map +1 -1
  62. package/dist/ask-observability/index.d.ts +11 -0
  63. package/dist/ask-observability/index.d.ts.map +1 -0
  64. package/dist/ask-observability/index.js +7 -0
  65. package/dist/ask-observability/index.js.map +1 -0
  66. package/dist/ask-observability/instrumentation.d.ts +35 -0
  67. package/dist/ask-observability/instrumentation.d.ts.map +1 -0
  68. package/dist/ask-observability/instrumentation.js +523 -0
  69. package/dist/ask-observability/instrumentation.js.map +1 -0
  70. package/dist/ask-observability/observer.d.ts +62 -0
  71. package/dist/ask-observability/observer.d.ts.map +1 -0
  72. package/dist/ask-observability/observer.js +537 -0
  73. package/dist/ask-observability/observer.js.map +1 -0
  74. package/dist/ask-observability/portable.d.ts +98 -0
  75. package/dist/ask-observability/portable.d.ts.map +1 -0
  76. package/dist/ask-observability/portable.js +1089 -0
  77. package/dist/ask-observability/portable.js.map +1 -0
  78. package/dist/ask-observability/store.d.ts +130 -0
  79. package/dist/ask-observability/store.d.ts.map +1 -0
  80. package/dist/ask-observability/store.js +1114 -0
  81. package/dist/ask-observability/store.js.map +1 -0
  82. package/dist/ask-observability/types.d.ts +380 -0
  83. package/dist/ask-observability/types.d.ts.map +1 -0
  84. package/dist/ask-observability/types.js +2 -0
  85. package/dist/ask-observability/types.js.map +1 -0
  86. package/dist/ask-observability/utils.d.ts +21 -0
  87. package/dist/ask-observability/utils.d.ts.map +1 -0
  88. package/dist/ask-observability/utils.js +102 -0
  89. package/dist/ask-observability/utils.js.map +1 -0
  90. package/dist/ask-runtime/ask-agent-runtime-v2.d.ts +850 -0
  91. package/dist/ask-runtime/ask-agent-runtime-v2.d.ts.map +1 -0
  92. package/dist/ask-runtime/ask-agent-runtime-v2.js +1716 -0
  93. package/dist/ask-runtime/ask-agent-runtime-v2.js.map +1 -0
  94. package/dist/ask-runtime/ask-analyst-runtime.d.ts +95 -0
  95. package/dist/ask-runtime/ask-analyst-runtime.d.ts.map +1 -0
  96. package/dist/ask-runtime/ask-analyst-runtime.js +8280 -0
  97. package/dist/ask-runtime/ask-analyst-runtime.js.map +1 -0
  98. package/dist/conversation/session-store.d.ts +31 -0
  99. package/dist/conversation/session-store.d.ts.map +1 -1
  100. package/dist/conversation/session-store.js +38 -0
  101. package/dist/conversation/session-store.js.map +1 -1
  102. package/dist/conversation/snapshot.d.ts +4 -1
  103. package/dist/conversation/snapshot.d.ts.map +1 -1
  104. package/dist/conversation/snapshot.js +85 -3
  105. package/dist/conversation/snapshot.js.map +1 -1
  106. package/dist/conversation/turn-trust.d.ts.map +1 -1
  107. package/dist/conversation/turn-trust.js +2 -0
  108. package/dist/conversation/turn-trust.js.map +1 -1
  109. package/dist/embeddings/provider.d.ts +1 -0
  110. package/dist/embeddings/provider.d.ts.map +1 -1
  111. package/dist/embeddings/provider.js +18 -0
  112. package/dist/embeddings/provider.js.map +1 -1
  113. package/dist/generated-analytical-proposal.d.ts +37 -1
  114. package/dist/generated-analytical-proposal.d.ts.map +1 -1
  115. package/dist/generated-analytical-proposal.js +246 -3
  116. package/dist/generated-analytical-proposal.js.map +1 -1
  117. package/dist/governed-relational-compiler.d.ts +15 -0
  118. package/dist/governed-relational-compiler.d.ts.map +1 -1
  119. package/dist/governed-relational-compiler.js +99 -1
  120. package/dist/governed-relational-compiler.js.map +1 -1
  121. package/dist/index.d.ts +21 -12
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/index.js +14 -7
  124. package/dist/index.js.map +1 -1
  125. package/dist/intent-controller.d.ts +87 -1
  126. package/dist/intent-controller.d.ts.map +1 -1
  127. package/dist/intent-controller.js +2 -2
  128. package/dist/intent-controller.js.map +1 -1
  129. package/dist/kg/build.js +32 -2
  130. package/dist/kg/build.js.map +1 -1
  131. package/dist/meaning-resolution.d.ts +207 -6
  132. package/dist/meaning-resolution.d.ts.map +1 -1
  133. package/dist/meaning-resolution.js +439 -53
  134. package/dist/meaning-resolution.js.map +1 -1
  135. package/dist/metadata/analysis-planner.d.ts.map +1 -1
  136. package/dist/metadata/analysis-planner.js +78 -7
  137. package/dist/metadata/analysis-planner.js.map +1 -1
  138. package/dist/metadata/catalog.d.ts +14 -0
  139. package/dist/metadata/catalog.d.ts.map +1 -1
  140. package/dist/metadata/catalog.js +29 -2
  141. package/dist/metadata/catalog.js.map +1 -1
  142. package/dist/metadata/meaning-evidence.d.ts +28 -2
  143. package/dist/metadata/meaning-evidence.d.ts.map +1 -1
  144. package/dist/metadata/meaning-evidence.js +373 -16
  145. package/dist/metadata/meaning-evidence.js.map +1 -1
  146. package/dist/plan-execution-adapter.d.ts +9 -1
  147. package/dist/plan-execution-adapter.d.ts.map +1 -1
  148. package/dist/plan-execution-adapter.js +93 -13
  149. package/dist/plan-execution-adapter.js.map +1 -1
  150. package/dist/provider-egress.d.ts +28 -11
  151. package/dist/provider-egress.d.ts.map +1 -1
  152. package/dist/provider-egress.js +72 -22
  153. package/dist/provider-egress.js.map +1 -1
  154. package/dist/providers/claude.d.ts +2 -2
  155. package/dist/providers/claude.d.ts.map +1 -1
  156. package/dist/providers/claude.js +201 -37
  157. package/dist/providers/claude.js.map +1 -1
  158. package/dist/providers/dispatch.d.ts +27 -1
  159. package/dist/providers/dispatch.d.ts.map +1 -1
  160. package/dist/providers/dispatch.js +63 -1
  161. package/dist/providers/dispatch.js.map +1 -1
  162. package/dist/providers/gemini.js +10 -10
  163. package/dist/providers/gemini.js.map +1 -1
  164. package/dist/providers/index.d.ts +2 -2
  165. package/dist/providers/index.d.ts.map +1 -1
  166. package/dist/providers/index.js +1 -1
  167. package/dist/providers/index.js.map +1 -1
  168. package/dist/providers/ollama.js +15 -15
  169. package/dist/providers/ollama.js.map +1 -1
  170. package/dist/providers/openai.d.ts +2 -2
  171. package/dist/providers/openai.d.ts.map +1 -1
  172. package/dist/providers/openai.js +213 -43
  173. package/dist/providers/openai.js.map +1 -1
  174. package/dist/providers/types.d.ts +131 -1
  175. package/dist/providers/types.d.ts.map +1 -1
  176. package/dist/providers/types.js.map +1 -1
  177. package/dist/relationship-proof.d.ts +81 -0
  178. package/dist/relationship-proof.d.ts.map +1 -1
  179. package/dist/relationship-proof.js +228 -4
  180. package/dist/relationship-proof.js.map +1 -1
  181. package/dist/requirement-clauses.d.ts +23 -0
  182. package/dist/requirement-clauses.d.ts.map +1 -0
  183. package/dist/requirement-clauses.js +70 -0
  184. package/dist/requirement-clauses.js.map +1 -0
  185. package/dist/resolved-analytical-plan.d.ts +18 -4
  186. package/dist/resolved-analytical-plan.d.ts.map +1 -1
  187. package/dist/resolved-analytical-plan.js +953 -50
  188. package/dist/resolved-analytical-plan.js.map +1 -1
  189. package/dist/router.d.ts +118 -2
  190. package/dist/router.d.ts.map +1 -1
  191. package/dist/router.js +2481 -232
  192. package/dist/router.js.map +1 -1
  193. package/dist/semantic-bridge/compose.d.ts.map +1 -1
  194. package/dist/semantic-bridge/compose.js +23 -4
  195. package/dist/semantic-bridge/compose.js.map +1 -1
  196. package/package.json +4 -4
@@ -1,6 +1,6 @@
1
1
  import { type AnalysisQuestionPlan } from "./analysis-planner.js";
2
2
  import { type AnalyticalPolicyContract, type MetricCapabilityContract } from "@duckcodeailabs/dql-core";
3
- import type { LocalContextPack, MetadataObject } from "./catalog.js";
3
+ import type { LocalContextPack, MetadataObject, MetadataRetrievalLaneResult } from "./catalog.js";
4
4
  import { type AgentRetrievalEvidence, type AgentRelationshipSafetyEvidence } from '../meaning-resolution.js';
5
5
  import type { KnowledgeLens } from '../domain-context.js';
6
6
  export type MetadataEvidenceClass = 'certified' | 'semantic' | 'sql';
@@ -20,6 +20,10 @@ export interface MetadataMeaningCandidate {
20
20
  definition?: string;
21
21
  formula?: string;
22
22
  semanticModel?: string;
23
+ /** Immutable semantic compiler field identity, not a provider-facing alias. */
24
+ semanticRuntimeName?: string;
25
+ /** Source-authored semantic/member or physical column type. */
26
+ dataType?: string;
23
27
  relevanceReasons: string[];
24
28
  compatibilityFacts: string[];
25
29
  businessShape: {
@@ -38,6 +42,11 @@ export interface MetadataMeaningCandidate {
38
42
  /** Structured relationship facts when this card itself is a relationship. */
39
43
  relationshipSafety?: AgentRelationshipSafetyEvidence[];
40
44
  provenance?: string;
45
+ /** Actual snapshot retrieval memberships; never inferred from a display name. */
46
+ retrievalLanes?: Array<{
47
+ lane: 'exact' | 'lexical' | 'vector' | 'graph' | 'conversation';
48
+ rank?: number;
49
+ }>;
41
50
  ambiguityPeerIds: string[];
42
51
  }
43
52
  export interface MetadataMeaningEvidencePackage {
@@ -49,6 +58,15 @@ export interface MetadataMeaningEvidencePackage {
49
58
  * role-balanced.
50
59
  */
51
60
  qualifiedCandidates?: MetadataMeaningCandidate[];
61
+ /**
62
+ * The one broad, immutable retrieval snapshot retained by an Ask host before
63
+ * it makes its own 32-item workspace and 16-card planner package. This is
64
+ * deliberately larger than `qualifiedCandidates`: a relevance-only first
65
+ * cut must not erase a physical product/order relation before role-balanced
66
+ * admission has a chance to reserve it. It is not a provider package and
67
+ * callers must still make a bounded, auditable admission decision.
68
+ */
69
+ snapshotCandidates?: MetadataMeaningCandidate[];
52
70
  byEvidenceClass: Record<MetadataEvidenceClass, MetadataMeaningCandidate[]>;
53
71
  ambiguousGroups: Array<{
54
72
  candidateIds: string[];
@@ -65,6 +83,14 @@ export interface AgentRetrievalEvidenceAdapterOptions {
65
83
  analyticalPolicies?: AnalyticalPolicyContract[];
66
84
  /** Same immutable context-pack objects used to prove member/value bindings. */
67
85
  contextObjects?: MetadataObject[];
86
+ /** Snapshot retrieval results used to preserve multi-lane provenance. */
87
+ retrievalLanes?: MetadataRetrievalLaneResult[];
88
+ /**
89
+ * Ask Analyst Runtime consumes the broad immutable snapshot and performs
90
+ * its own role-balanced 32/16 admission. Legacy callers retain the compact
91
+ * qualified package by default.
92
+ */
93
+ preferSnapshotCandidates?: boolean;
68
94
  }
69
95
  export interface MeaningEvidenceInputCandidate {
70
96
  row: MetadataObject;
@@ -78,7 +104,7 @@ export interface MeaningEvidenceInputCandidate {
78
104
  * intentionally remain separate: certification labels an execution lane but
79
105
  * never makes an unrelated candidate more relevant to the question.
80
106
  */
81
- export declare function buildMeaningEvidencePackage(question: string, questionPlan: AnalysisQuestionPlan, ranked: MeaningEvidenceInputCandidate[]): MetadataMeaningEvidencePackage;
107
+ export declare function buildMeaningEvidencePackage(question: string, questionPlan: AnalysisQuestionPlan, ranked: MeaningEvidenceInputCandidate[], retrievalLanes?: MetadataRetrievalLaneResult[]): MetadataMeaningEvidencePackage;
82
108
  export declare function toAgentRetrievalEvidence(evidence: MetadataMeaningEvidencePackage, questionPlan: AnalysisQuestionPlan, options?: AgentRetrievalEvidenceAdapterOptions): AgentRetrievalEvidence;
83
109
  /**
84
110
  * One host-neutral compatibility projection shared by CLI, Browser Ask, MCP,
@@ -1 +1 @@
1
- {"version":3,"file":"meaning-evidence.d.ts","sourceRoot":"","sources":["../../src/metadata/meaning-evidence.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"meaning-evidence.d.ts","sourceRoot":"","sources":["../../src/metadata/meaning-evidence.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { scoreMetadataObjectWithAnalysisPlan, } from "./analysis-planner.js";
2
2
  import { normalizeMetricCapabilityContract, } from "@duckcodeailabs/dql-core";
3
- import { candidateMatchesCategoricalDimensionRequirement } from '../analytical-orchestration.js';
3
+ import { candidateMatchesCategoricalDimensionRequirement, categoricalDimensionTermsMatch, } from '../analytical-orchestration.js';
4
4
  import { certifiedCandidateExplicitlyCoversMeasures, } from '../meaning-resolution.js';
5
5
  const GENERIC_MEANING_TOKENS = new Set([
6
6
  'amount', 'value', 'metric', 'measure', 'total', 'overall', 'monthly', 'daily',
@@ -8,13 +8,15 @@ const GENERIC_MEANING_TOKENS = new Set([
8
8
  ]);
9
9
  const MAX_CANDIDATES_PER_CLASS = 4;
10
10
  const MAX_QUALIFIED_CANDIDATES = 32;
11
+ const MAX_SNAPSHOT_CANDIDATES = 80;
11
12
  const ANALYTICAL_TIME_GRAINS = new Set(['day', 'week', 'month', 'quarter', 'year', 'season', 'period']);
12
13
  /**
13
14
  * Produce compact meaning-rich cards for an AI resolver. Relevance and trust
14
15
  * intentionally remain separate: certification labels an execution lane but
15
16
  * never makes an unrelated candidate more relevant to the question.
16
17
  */
17
- export function buildMeaningEvidencePackage(question, questionPlan, ranked) {
18
+ export function buildMeaningEvidencePackage(question, questionPlan, ranked, retrievalLanes = []) {
19
+ const laneMembership = laneMembershipByObjectKey(retrievalLanes);
18
20
  const questionText = normalizeText(question);
19
21
  const questionTokens = tokenSet(questionText);
20
22
  const eligible = ranked.flatMap((item) => {
@@ -43,14 +45,20 @@ export function buildMeaningEvidencePackage(question, questionPlan, ranked) {
43
45
  eligible.sort((left, right) => right.relevanceScore - left.relevanceScore
44
46
  || left.item.rank - right.item.rank
45
47
  || left.item.row.objectKey.localeCompare(right.item.row.objectKey));
46
- const qualifiedPeerMap = buildAmbiguityPeers(eligible.slice(0, MAX_QUALIFIED_CANDIDATES).map((candidate) => ({
48
+ const snapshotEligible = eligible.slice(0, MAX_SNAPSHOT_CANDIDATES);
49
+ const qualifiedPeerMap = buildAmbiguityPeers(snapshotEligible.map((candidate) => ({
47
50
  objectKey: candidate.item.row.objectKey,
48
51
  aliases: candidate.aliases,
49
52
  })));
53
+ const snapshotCounts = { certified: 0, semantic: 0, sql: 0 };
54
+ const snapshotCandidates = snapshotEligible.map((candidate) => {
55
+ snapshotCounts[candidate.evidenceClass] += 1;
56
+ return candidateCard(candidate.item.row, candidate.evidenceClass, snapshotCounts[candidate.evidenceClass], candidate.relevanceScore, candidate.aliases, candidate.relevanceReasons, qualifiedPeerMap.get(candidate.item.row.objectKey) ?? [], laneMembership.get(candidate.item.row.objectKey));
57
+ });
50
58
  const qualifiedCounts = { certified: 0, semantic: 0, sql: 0 };
51
- const qualifiedCandidates = eligible.slice(0, MAX_QUALIFIED_CANDIDATES).map((candidate) => {
59
+ const qualifiedCandidates = snapshotEligible.slice(0, MAX_QUALIFIED_CANDIDATES).map((candidate) => {
52
60
  qualifiedCounts[candidate.evidenceClass] += 1;
53
- return candidateCard(candidate.item.row, candidate.evidenceClass, qualifiedCounts[candidate.evidenceClass], candidate.relevanceScore, candidate.aliases, candidate.relevanceReasons, qualifiedPeerMap.get(candidate.item.row.objectKey) ?? []);
61
+ return candidateCard(candidate.item.row, candidate.evidenceClass, qualifiedCounts[candidate.evidenceClass], candidate.relevanceScore, candidate.aliases, candidate.relevanceReasons, qualifiedPeerMap.get(candidate.item.row.objectKey) ?? [], laneMembership.get(candidate.item.row.objectKey));
54
62
  });
55
63
  const requiredSemanticMetricKey = questionPlan.requestedShape.measures.length > 0
56
64
  ? eligible.find((candidate) => candidate.evidenceClass === 'semantic'
@@ -103,12 +111,13 @@ export function buildMeaningEvidencePackage(question, questionPlan, ranked) {
103
111
  };
104
112
  for (const candidate of selected) {
105
113
  const lane = byEvidenceClass[candidate.evidenceClass];
106
- lane.push(candidateCard(candidate.item.row, candidate.evidenceClass, lane.length + 1, candidate.relevanceScore, candidate.aliases, candidate.relevanceReasons, peerMap.get(candidate.item.row.objectKey) ?? []));
114
+ lane.push(candidateCard(candidate.item.row, candidate.evidenceClass, lane.length + 1, candidate.relevanceScore, candidate.aliases, candidate.relevanceReasons, peerMap.get(candidate.item.row.objectKey) ?? [], laneMembership.get(candidate.item.row.objectKey)));
107
115
  }
108
116
  return {
109
117
  candidates: ['certified', 'semantic', 'sql']
110
118
  .flatMap((evidenceClass) => byEvidenceClass[evidenceClass]),
111
119
  qualifiedCandidates,
120
+ snapshotCandidates,
112
121
  byEvidenceClass,
113
122
  ambiguousGroups: ambiguityGroups(peerMap),
114
123
  };
@@ -371,11 +380,18 @@ function attachRelationshipEvidence(candidates, contextObjects) {
371
380
  }
372
381
  }
373
382
  export function toAgentRetrievalEvidence(evidence, questionPlan, options = {}) {
374
- const sourceCandidates = evidence.qualifiedCandidates?.length
375
- ? evidence.qualifiedCandidates
376
- : evidence.candidates;
383
+ const sourceCandidates = options.preferSnapshotCandidates && evidence.snapshotCandidates?.length
384
+ ? evidence.snapshotCandidates
385
+ : evidence.qualifiedCandidates?.length
386
+ ? evidence.qualifiedCandidates
387
+ : evidence.candidates;
377
388
  const maxRelevance = Math.max(1, ...sourceCandidates.map((candidate) => candidate.relevanceScore));
378
- const candidates = sourceCandidates.map((candidate) => agentCandidateFromMeaning(candidate, maxRelevance));
389
+ const optionLaneMembership = laneMembershipByObjectKey(options.retrievalLanes ?? []);
390
+ const candidates = sourceCandidates.map((candidate) => agentCandidateFromMeaning(candidate, maxRelevance, candidate.retrievalLanes ?? optionLaneMembership.get(candidate.objectKey)));
391
+ // Keep runtime-value proof attached to the exact qualified physical column
392
+ // that owns it. The provider never receives these values; the Ask compiler
393
+ // can use them only after a planner has selected that categorical field.
394
+ attachSafeValueEvidence(candidates, options.contextObjects ?? []);
379
395
  const trustedBindings = trustedMemberBindingEvidence(questionPlan, candidates, options.contextObjects ?? []);
380
396
  for (const member of trustedBindings.candidates) {
381
397
  if (!candidates.some((candidate) => candidate.id === member.id))
@@ -435,7 +451,7 @@ export function toAgentRetrievalEvidence(evidence, questionPlan, options = {}) {
435
451
  },
436
452
  };
437
453
  }
438
- function agentCandidateFromMeaning(candidate, maxRelevance = 1) {
454
+ function agentCandidateFromMeaning(candidate, maxRelevance = 1, retrievalLanes) {
439
455
  return {
440
456
  id: candidate.objectKey,
441
457
  qualifiedId: candidate.qualifiedId,
@@ -452,6 +468,8 @@ function agentCandidateFromMeaning(candidate, maxRelevance = 1) {
452
468
  provenance: candidate.provenance,
453
469
  domain: candidate.domain,
454
470
  semanticModel: candidate.semanticModel,
471
+ ...(candidate.semanticRuntimeName ? { semanticRuntimeName: candidate.semanticRuntimeName } : {}),
472
+ dataType: candidate.dataType,
455
473
  primaryEntity: candidate.businessShape.entities[0],
456
474
  dimensions: dimensionLookupAliases(candidate.businessShape.dimensions),
457
475
  timeGrains: candidate.businessShape.timeGrains,
@@ -467,8 +485,73 @@ function agentCandidateFromMeaning(candidate, maxRelevance = 1) {
467
485
  analyticalCapability: candidate.analyticalCapability,
468
486
  eligible: true,
469
487
  exactMatch: candidate.relevanceReasons.includes("exact name or alias"),
488
+ ...(retrievalLanes?.length ? { retrievalLanes } : {}),
470
489
  };
471
490
  }
491
+ /**
492
+ * Attach a bounded, snapshot-local runtime-value observation to its qualified
493
+ * physical column. This is stricter than lexical matching: both the physical
494
+ * relation and the terminal column must agree. It lets a later deterministic
495
+ * compiler bind `in Philadelphia` to one selected SQL column without making
496
+ * the literal or a source-table guess visible to the planner.
497
+ */
498
+ function attachSafeValueEvidence(candidates, objects) {
499
+ const values = objects.flatMap((object) => {
500
+ if (object.objectType !== 'runtime_value')
501
+ return [];
502
+ const relation = firstString(object.payload?.relation);
503
+ const column = firstString(object.payload?.column);
504
+ const value = firstString(object.payload?.value);
505
+ const normalizedValue = firstString(object.payload?.normalizedValue)
506
+ ?? (value ? normalizeText(value) : undefined);
507
+ if (!relation || !column || !value || !normalizedValue)
508
+ return [];
509
+ return [{ version: 1, relation, column, value, normalizedValue }];
510
+ });
511
+ if (values.length === 0)
512
+ return;
513
+ for (const candidate of candidates) {
514
+ if (candidate.kind !== 'sql_column')
515
+ continue;
516
+ const matching = values.filter((value) => physicalCandidateColumnMatches(candidate, value.column)
517
+ && physicalCandidateRelationMatches(candidate, value.relation));
518
+ if (matching.length === 0)
519
+ continue;
520
+ const deduped = new Map();
521
+ for (const value of matching) {
522
+ const key = [
523
+ canonicalPhysicalRelation(value.relation),
524
+ normalizeIdentifier(value.column),
525
+ normalizeText(value.normalizedValue),
526
+ ].join('|');
527
+ if (!deduped.has(key))
528
+ deduped.set(key, value);
529
+ }
530
+ candidate.safeValueEvidence = [...deduped.values()]
531
+ .sort((left, right) => left.relation.localeCompare(right.relation)
532
+ || left.column.localeCompare(right.column)
533
+ || left.normalizedValue.localeCompare(right.normalizedValue));
534
+ }
535
+ }
536
+ function physicalCandidateColumnMatches(candidate, column) {
537
+ const expected = normalizeIdentifier(column);
538
+ return [candidate.name, ...(candidate.aliases ?? [])]
539
+ .some((value) => normalizeIdentifier(value) === expected);
540
+ }
541
+ function physicalCandidateRelationMatches(candidate, relation) {
542
+ const expected = canonicalPhysicalRelation(relation);
543
+ // A qualified physical source relation is mandatory. Do not infer one from
544
+ // a display name or a loose candidate ID, because that would allow a value
545
+ // observation from another table to authorize a filter.
546
+ return (candidate.sourceObjects ?? []).some((source) => {
547
+ const actual = canonicalPhysicalRelation(source);
548
+ return actual === expected || actual.endsWith(`.${expected}`) || expected.endsWith(`.${actual}`);
549
+ });
550
+ }
551
+ function canonicalPhysicalRelation(value) {
552
+ return normalizeRelation(value)
553
+ .replace(/^(?:runtime:relation:|dbt:model:|dbt:source:|sql:table:)/, '');
554
+ }
472
555
  function trustedMemberBindingEvidence(plan, candidates, objects) {
473
556
  const dimensions = new Map();
474
557
  for (const object of objects) {
@@ -581,6 +664,87 @@ function supplementalClarificationEvidence(plan, objects, existing) {
581
664
  exactMatch: false,
582
665
  }];
583
666
  }));
667
+ // A compact retrieval package can contain the selected semantic metric but
668
+ // not a separately indexed card for one of that metric's declared grouping
669
+ // dimensions. Retain one exact, same-snapshot extension for that field when
670
+ // the request names its categorical role. This does not infer a join or a
671
+ // business alias: the metric capability owns both the qualified dimension
672
+ // id and the MetricFlow-native grouping path, and the router later admits it
673
+ // only beside this exact metric. It is intentionally separate from the
674
+ // geography-only recovery below, which may bind a location-like field for
675
+ // the distinct business term `region` under a stricter sole-field rule.
676
+ const exactCapabilityDimensionCards = existing.flatMap((metric) => {
677
+ if (metric.kind !== 'semantic_metric')
678
+ return [];
679
+ const capability = normalizeMetricCapabilityContract(metric.analyticalCapability);
680
+ if (!capability || !capability.executionCapabilities.some((execution) => execution.route === 'semantic'))
681
+ return [];
682
+ const metricId = metric.qualifiedId ?? metric.id;
683
+ return requestedDimensions.flatMap((requestedTerm) => capability.dimensions.flatMap((dimension) => {
684
+ if (!dimension.supportedRoles.includes('group_by'))
685
+ return [];
686
+ // `existing` is the unbounded retrieval snapshot, not the compact
687
+ // meaning package. A separately indexed semantic dimension can be
688
+ // present here yet later lose its role-balanced admission to a raw/dbt
689
+ // column with the same business words. Preserve the capability-backed
690
+ // extension in that case so the router can admit it beside its exact
691
+ // metric. The final de-duplication deliberately retains this extension
692
+ // (with its metric/role proof) over a plain dimension card.
693
+ const identities = [
694
+ dimension.dimensionId,
695
+ dimension.label ?? '',
696
+ ...(dimension.aliases ?? []),
697
+ ];
698
+ if (!identities.some((identity) => categoricalDimensionTermsMatch(requestedTerm, identity)))
699
+ return [];
700
+ return [{
701
+ id: dimension.dimensionId,
702
+ qualifiedId: dimension.dimensionId,
703
+ kind: 'semantic_member',
704
+ semanticObjectType: 'dimension',
705
+ trustTier: 'semantic',
706
+ name: dimension.label ?? dimension.dimensionId.split(/[.:/]/).at(-1) ?? dimension.dimensionId,
707
+ aliases: uniqueStrings([
708
+ dimension.label ?? '',
709
+ ...(dimension.aliases ?? []),
710
+ requestedTerm,
711
+ ]),
712
+ definition: `Same-snapshot MetricFlow grouping field for ${requestedTerm}.`,
713
+ primaryEntity: dimension.entityId,
714
+ dimensions: [dimension.dimensionId],
715
+ sourceObjects: metric.sourceObjects,
716
+ relationshipEvidence: metric.relationshipEvidence,
717
+ relationshipSafety: metric.relationshipSafety,
718
+ relevanceScore: metric.relevanceScore,
719
+ matchReasons: ['same snapshot exact MetricFlow grouping extension'],
720
+ compatibility: 'compatible',
721
+ eligible: true,
722
+ exactMatch: false,
723
+ sameSnapshotRoleExtension: {
724
+ version: 1,
725
+ role: 'categorical_dimension',
726
+ requestedTerm,
727
+ metricId,
728
+ dimensionId: dimension.dimensionId,
729
+ basis: 'exact_metricflow_grouping_dimension',
730
+ },
731
+ }];
732
+ }));
733
+ });
734
+ // A location-like semantic dimension is not generally a synonym for
735
+ // "region". There is one deliberately narrow exception: when every
736
+ // current-turn requested metric proves the same one MetricFlow grouping
737
+ // dimension in this immutable snapshot. Surface that card as a typed,
738
+ // host-authored extension so the bounded meaning call can bind it; the
739
+ // selected metric capability still verifies the native grouping path and
740
+ // remains the only execution authority. This recovers the real Jaffle
741
+ // `revenue by ... region` path without guessing a raw join or widening
742
+ // lexical geography matching.
743
+ const sameSnapshotRoleExtensions = sameSnapshotGeographyRoleExtensions({
744
+ requestedMeasures,
745
+ requestedDimensions,
746
+ existing,
747
+ });
584
748
  // A bare ranking ("who are the top customers") names an ENTITY and no
585
749
  // measure, so every lane above — each keyed on a REQUESTED measure or
586
750
  // dimension term — returns nothing, and the clarification has no choices to
@@ -624,8 +788,32 @@ function supplementalClarificationEvidence(plan, objects, existing) {
624
788
  return [card];
625
789
  }).sort((left, right) => right.relevanceScore - left.relevanceScore || left.id.localeCompare(right.id));
626
790
  })();
627
- const unique = [...objectCards, ...declaredDimensionCards, ...rankingMeasureCards]
628
- .filter((candidate, index, all) => all.findIndex((other) => (other.qualifiedId ?? other.id) === (candidate.qualifiedId ?? candidate.id)) === index);
791
+ // The index may yield a generic semantic-dimension card and then this
792
+ // function may enrich that exact qualified ID with a metric-declared,
793
+ // same-snapshot role proof. Retain the enriched card: it is not a second
794
+ // candidate or a guessed relationship, but the only metadata authority that
795
+ // says this field is queryable with the selected MetricFlow metric for the
796
+ // current requested role. First-seen de-duplication discarded that proof in
797
+ // real local indexes and manufactured a false coverage gap for
798
+ // `product category` / `product_type`.
799
+ const uniqueByQualifiedId = new Map();
800
+ for (const candidate of [
801
+ ...objectCards,
802
+ ...exactCapabilityDimensionCards,
803
+ ...declaredDimensionCards,
804
+ ...sameSnapshotRoleExtensions,
805
+ ...rankingMeasureCards,
806
+ ]) {
807
+ const key = candidate.qualifiedId ?? candidate.id;
808
+ const existingCandidate = uniqueByQualifiedId.get(key);
809
+ if (!existingCandidate
810
+ || (!existingCandidate.sameSnapshotRoleExtension && candidate.sameSnapshotRoleExtension)) {
811
+ // Map replacement preserves stable first-seen ordering while retaining
812
+ // the stricter extension provenance for this one qualified object.
813
+ uniqueByQualifiedId.set(key, candidate);
814
+ }
815
+ }
816
+ const unique = [...uniqueByQualifiedId.values()];
629
817
  const roleLanes = [
630
818
  ...requestedDimensions.map((term) => ({ kind: 'dimension', term })),
631
819
  ...requestedMeasures.map((term) => ({ kind: 'metric', term })),
@@ -658,6 +846,95 @@ function supplementalClarificationEvidence(plan, objects, existing) {
658
846
  }
659
847
  return selected.slice(0, MAX_SUPPLEMENTAL_CANDIDATES);
660
848
  }
849
+ function sameSnapshotGeographyRoleExtensions(input) {
850
+ const geographyTerms = [...new Set(input.requestedDimensions
851
+ .map(normalizeText)
852
+ .filter((term) => term === 'region' || term === 'geography' || term === 'geographic'))];
853
+ if (geographyTerms.length === 0 || input.requestedMeasures.length === 0)
854
+ return [];
855
+ const existingGeography = input.existing.some((candidate) => geographyTerms.some((term) => candidateMatchesCategoricalDimensionRequirement(candidate, [term])));
856
+ if (existingGeography)
857
+ return [];
858
+ const canonicalMetricTerm = (value) => {
859
+ const normalized = normalizeText(value);
860
+ return normalized === 'sale' || normalized === 'sales' ? 'revenue' : normalized;
861
+ };
862
+ const metricMatches = (candidate, requested) => {
863
+ if (candidate.kind !== 'semantic_metric' || !candidate.analyticalCapability)
864
+ return false;
865
+ const expected = canonicalMetricTerm(requested);
866
+ return [candidate.name, ...(candidate.aliases ?? []), candidate.analyticalCapability.metricId]
867
+ .map(canonicalMetricTerm)
868
+ .some((identity) => phraseTermsMatch(expected, identity));
869
+ };
870
+ const geographicDimension = (dimension) => {
871
+ const identity = [dimension.dimensionId, dimension.entityId, dimension.label ?? '', ...(dimension.aliases ?? [])]
872
+ .map(normalizeText)
873
+ .join(' ');
874
+ return /\b(?:region|geograph(?:y|ic)|location|country|state|province|city|territory)\b/.test(identity);
875
+ };
876
+ const candidatesForMeasure = (requested) => input.existing.flatMap((candidate) => {
877
+ if (!metricMatches(candidate, requested))
878
+ return [];
879
+ const capability = candidate.analyticalCapability;
880
+ if (!capability.executionCapabilities.some((execution) => execution.route === 'semantic'))
881
+ return [];
882
+ return capability.dimensions
883
+ .filter((dimension) => dimension.supportedRoles.includes('group_by') && geographicDimension(dimension))
884
+ .map((dimension) => ({ candidate, dimension }));
885
+ });
886
+ const matchesByMeasure = input.requestedMeasures.map((requested) => ({
887
+ requested,
888
+ matches: candidatesForMeasure(requested),
889
+ }));
890
+ if (matchesByMeasure.some(({ matches }) => matches.length === 0))
891
+ return [];
892
+ const sharedDimensionIds = matchesByMeasure
893
+ .map(({ matches }) => new Set(matches.map(({ dimension }) => dimension.dimensionId)))
894
+ .reduce((shared, ids) => shared === undefined
895
+ ? ids
896
+ : new Set([...shared].filter((id) => ids.has(id))), undefined);
897
+ if (!sharedDimensionIds || sharedDimensionIds.size !== 1)
898
+ return [];
899
+ const dimensionId = [...sharedDimensionIds][0];
900
+ const witness = matchesByMeasure[0].matches.find((match) => match.dimension.dimensionId === dimensionId);
901
+ if (!witness)
902
+ return [];
903
+ const { candidate: metricCandidate, dimension } = witness;
904
+ const requestedTerm = geographyTerms[0];
905
+ return [{
906
+ id: dimensionId,
907
+ qualifiedId: dimensionId,
908
+ kind: 'semantic_member',
909
+ semanticObjectType: 'dimension',
910
+ trustTier: 'semantic',
911
+ name: dimension.label ?? dimension.dimensionId.split(/[.:/]/).at(-1) ?? dimension.dimensionId,
912
+ aliases: uniqueStrings([
913
+ dimension.label ?? '',
914
+ ...(dimension.aliases ?? []),
915
+ requestedTerm,
916
+ ]),
917
+ definition: `Same-snapshot MetricFlow grouping field for ${requestedTerm}.`,
918
+ primaryEntity: dimension.entityId,
919
+ dimensions: [dimensionId],
920
+ sourceObjects: metricCandidate.sourceObjects,
921
+ relationshipEvidence: metricCandidate.relationshipEvidence,
922
+ relationshipSafety: metricCandidate.relationshipSafety,
923
+ relevanceScore: metricCandidate.relevanceScore,
924
+ matchReasons: ['same snapshot role-targeted MetricFlow grouping extension'],
925
+ compatibility: 'compatible',
926
+ eligible: true,
927
+ exactMatch: false,
928
+ sameSnapshotRoleExtension: {
929
+ version: 1,
930
+ role: 'categorical_dimension',
931
+ requestedTerm,
932
+ metricId: metricCandidate.qualifiedId ?? metricCandidate.id,
933
+ dimensionId,
934
+ basis: 'sole_metricflow_grouping_dimension',
935
+ },
936
+ }];
937
+ }
661
938
  function expandedRequestedTerms(terms, objects) {
662
939
  const vocabulary = new Map();
663
940
  for (const object of objects) {
@@ -832,13 +1109,45 @@ export function applyContextPackCompatibility(evidence, pack, selectedEvidenceId
832
1109
  // paraphrase "What is monthly revenue?". Do not promote lexical
833
1110
  // relevance alone; require the catalog's executable fit verdict.
834
1111
  const requestedMeasures = pack.questionPlan.requestedShape.measures;
835
- const blockDeclaresRequestedMeasures = certifiedCandidateExplicitlyCoversMeasures(candidate, requestedMeasures);
1112
+ // `declaredOutputs` is preferable because it remains independently
1113
+ // visible in the block source. Some older-but-valid certified blocks
1114
+ // only expose their projection through the catalog's immutable SQL
1115
+ // contract, however. Preserve that proof *only* when the reader typed
1116
+ // the exact block title and the same snapshot's catalog has already
1117
+ // selected this precise block as a high-confidence certified answer.
1118
+ // A fuzzy alias, paraphrase, or ordinary semantic question cannot use
1119
+ // this bridge, so non-exact role and output constraints remain intact.
1120
+ const exactCertifiedTitle = typeof pack.questionPlan.question === 'string'
1121
+ && normalizeText(pack.questionPlan.question) === normalizeText(candidate.name)
1122
+ && pack.routeDecision.route === 'certified'
1123
+ && pack.routeDecision.exactObjectKey === candidate.id
1124
+ && fit?.action === 'certified_answer'
1125
+ && (fit.fit.kind === 'exact' || fit.fit.kind === 'trim_safe')
1126
+ && fit.fit.confidence === 'high';
1127
+ const compatibilityFacts = exactCertifiedTitle
1128
+ ? uniqueStrings([
1129
+ ...(candidate.compatibilityFacts ?? []),
1130
+ ...requestedMeasures.map((measure) => `catalog-proven-output: ${measure}`),
1131
+ // The catalog fit also proves this block's declared result
1132
+ // grain. Preserve it as an output only on the exact-title
1133
+ // bridge, so the immutable plan can bind `customer` to its
1134
+ // actual `customer_name` result without making a fuzzy block
1135
+ // title a display-key authority.
1136
+ ...(candidate.compatibilityFacts ?? []).flatMap((fact) => /^grain:\s*(.+)$/i.exec(fact)?.[1]
1137
+ ?.split(',')
1138
+ .map((value) => value.trim())
1139
+ .filter(Boolean)
1140
+ .map((value) => `catalog-proven-output: ${value}`) ?? []),
1141
+ ])
1142
+ : candidate.compatibilityFacts;
1143
+ const blockDeclaresRequestedMeasures = certifiedCandidateExplicitlyCoversMeasures({ ...candidate, compatibilityFacts }, requestedMeasures);
836
1144
  const completeCertifiedFit = fit?.action === 'certified_answer'
837
1145
  && (fit.fit.kind === 'exact' || fit.fit.kind === 'trim_safe')
838
1146
  && fit.fit.confidence === 'high'
839
1147
  && blockDeclaresRequestedMeasures;
840
1148
  return {
841
1149
  ...candidate,
1150
+ ...(compatibilityFacts ? { compatibilityFacts } : {}),
842
1151
  // Lexical equality to a block name/alias is useful retrieval signal,
843
1152
  // but it is not exact analytical authority. The catalog's complete
844
1153
  // certified fit is the one host-owned exception and is still checked
@@ -915,11 +1224,31 @@ function trustTierFor(object, evidenceClass) {
915
1224
  return 'governed_sql';
916
1225
  return 'exploratory';
917
1226
  }
918
- function candidateCard(object, evidenceClass, classRank, relevanceScore, aliases, relevanceReasons, ambiguityPeerIds) {
1227
+ function candidateCard(object, evidenceClass, classRank, relevanceScore, aliases, relevanceReasons, ambiguityPeerIds, retrievalLanes) {
919
1228
  const payload = object.payload ?? {};
1229
+ const typeParams = recordValue(payload.typeParams) ?? recordValue(payload.type_params) ?? {};
1230
+ const dataType = firstString(payload.dataType, payload.data_type, payload.columnType, payload.column_type, payload.dimensionType, payload.dimension_type, payload.type, typeParams.dataType, typeParams.data_type);
1231
+ const declaredTimeGrain = firstString(payload.timeGranularity, payload.time_granularity, typeParams.timeGranularity, typeParams.time_granularity);
1232
+ const semanticRuntimeName = evidenceClass === 'semantic'
1233
+ ? firstString(payload.localId, payload.local_id)
1234
+ : undefined;
920
1235
  const analyticalCapability = normalizeMetricCapabilityContract(payload.analyticalCapability);
921
1236
  const relationshipSafety = relationshipSafetyFromObject(object);
922
- const qualifiedId = firstString(payload.qualifiedId, payload.uniqueId, object.fullName, object.objectKey) ?? object.objectKey;
1237
+ // A dbt/runtime column inherits its parent node's `uniqueId`. That value
1238
+ // is useful lineage metadata, but it is not a column identity: using it as
1239
+ // the candidate's qualified ID collapsed every selected column on a model
1240
+ // into the model itself. The physical cascade then appeared safe but could
1241
+ // not freeze an exact output contract. Keep the parent relation in
1242
+ // `sourceRelations`; give a column its source-qualified field identity.
1243
+ const columnObject = object.objectType.endsWith('_column');
1244
+ // Semantic registry IDs are model-qualified execution identities. The
1245
+ // historic `qualifiedId` field on a MetricFlow time dimension was only
1246
+ // domain + local name (`semantic:uncategorized:dimension:metric_time`), so
1247
+ // every model's synthesized global axis collapsed into one V2 capability.
1248
+ // Prefer the registry-qualified identity captured by the KG for semantic
1249
+ // cards; retain the older value in aliases for bounded backward reads.
1250
+ // Physical columns intentionally preserve their own source-qualified rule.
1251
+ const qualifiedId = firstString(...(evidenceClass === 'semantic' ? [payload.registryQualifiedId] : []), payload.qualifiedId, ...(columnObject ? [object.fullName] : []), payload.uniqueId, object.fullName, object.objectKey) ?? object.objectKey;
923
1252
  const parameters = arrayNames(payload.parameters ?? payload.parameterPolicy);
924
1253
  const outputs = uniqueStrings([
925
1254
  ...stringArray(payload.declaredOutputs),
@@ -963,6 +1292,12 @@ function candidateCard(object, evidenceClass, classRank, relevanceScore, aliases
963
1292
  ? truncate(firstString(payload.formula, payload.expr, payload.sql), 320)
964
1293
  : undefined,
965
1294
  semanticModel: firstString(payload.semanticModel, payload.cube),
1295
+ // dbt/MetricFlow semantic objects often use a model-qualified display
1296
+ // name (`order_item.metric_time`) while the compiler accepts their local
1297
+ // declaration (`metric_time`). Preserve the latter only as host-side
1298
+ // capability data. It must never become a fuzzy retrieval alias.
1299
+ ...(semanticRuntimeName ? { semanticRuntimeName } : {}),
1300
+ ...(dataType ? { dataType } : {}),
966
1301
  relevanceReasons,
967
1302
  compatibilityFacts,
968
1303
  businessShape: {
@@ -973,6 +1308,14 @@ function candidateCard(object, evidenceClass, classRank, relevanceScore, aliases
973
1308
  timeGrains: uniqueStrings([
974
1309
  ...stringArray(payload.timeGrains),
975
1310
  ...stringArray(payload.supportedTimeGrains),
1311
+ // MetricFlow/dbt semantic dimensions are indexed with their authored
1312
+ // `granularities` array. Keep that declaration in the bounded
1313
+ // retrieval card: Ask V2 validates a selected time grain against this
1314
+ // exact snapshot-local capability and must not infer one from a name.
1315
+ ...stringArray(payload.granularities),
1316
+ ...stringArray(payload.queryableGranularities),
1317
+ ...stringArray(payload.queryable_granularities),
1318
+ declaredTimeGrain ?? '',
976
1319
  ]).slice(0, 8),
977
1320
  parameters: parameters.slice(0, 12),
978
1321
  filters: stringArray(payload.allowedFilters).slice(0, 12),
@@ -986,9 +1329,23 @@ function candidateCard(object, evidenceClass, classRank, relevanceScore, aliases
986
1329
  ...(analyticalCapability ? { analyticalCapability } : {}),
987
1330
  ...(relationshipSafety ? { relationshipSafety: [relationshipSafety] } : {}),
988
1331
  provenance: firstString(payload.provenance, object.sourceSystem),
1332
+ ...(retrievalLanes?.length ? { retrievalLanes } : {}),
989
1333
  ambiguityPeerIds,
990
1334
  };
991
1335
  }
1336
+ function laneMembershipByObjectKey(lanes) {
1337
+ const memberships = new Map();
1338
+ for (const lane of lanes) {
1339
+ for (const candidate of lane.candidates) {
1340
+ const next = memberships.get(candidate.objectKey) ?? [];
1341
+ if (!next.some((entry) => entry.lane === lane.lane && entry.rank === candidate.rank)) {
1342
+ next.push({ lane: lane.lane, rank: candidate.rank });
1343
+ }
1344
+ memberships.set(candidate.objectKey, next);
1345
+ }
1346
+ }
1347
+ return memberships;
1348
+ }
992
1349
  function agentEvidenceKind(candidate) {
993
1350
  if (candidate.evidenceClass === 'certified')
994
1351
  return 'certified_block';