@duckcodeailabs/dql-agent 1.13.4 → 1.14.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 (137) hide show
  1. package/dist/agent-run-engine.d.ts +87 -7
  2. package/dist/agent-run-engine.d.ts.map +1 -1
  3. package/dist/agent-run-engine.js +559 -40
  4. package/dist/agent-run-engine.js.map +1 -1
  5. package/dist/agent-run-store.d.ts.map +1 -1
  6. package/dist/agent-run-store.js +42 -25
  7. package/dist/agent-run-store.js.map +1 -1
  8. package/dist/agentic/tool-loop.d.ts.map +1 -1
  9. package/dist/agentic/tool-loop.js +100 -32
  10. package/dist/agentic/tool-loop.js.map +1 -1
  11. package/dist/aggregation-safety-proof.d.ts +41 -0
  12. package/dist/aggregation-safety-proof.d.ts.map +1 -0
  13. package/dist/aggregation-safety-proof.js +511 -0
  14. package/dist/aggregation-safety-proof.js.map +1 -0
  15. package/dist/analytical-compatibility.d.ts.map +1 -1
  16. package/dist/analytical-compatibility.js +42 -25
  17. package/dist/analytical-compatibility.js.map +1 -1
  18. package/dist/analytical-execution-graph.d.ts +4 -21
  19. package/dist/analytical-execution-graph.d.ts.map +1 -1
  20. package/dist/analytical-execution-graph.js +27 -0
  21. package/dist/analytical-execution-graph.js.map +1 -1
  22. package/dist/analytical-frame.d.ts +12 -1
  23. package/dist/analytical-frame.d.ts.map +1 -1
  24. package/dist/analytical-frame.js +102 -2
  25. package/dist/analytical-frame.js.map +1 -1
  26. package/dist/analytical-orchestration.d.ts +256 -0
  27. package/dist/analytical-orchestration.d.ts.map +1 -0
  28. package/dist/analytical-orchestration.js +419 -0
  29. package/dist/analytical-orchestration.js.map +1 -0
  30. package/dist/analytical-result-facts.d.ts +54 -1
  31. package/dist/analytical-result-facts.d.ts.map +1 -1
  32. package/dist/analytical-result-facts.js +183 -0
  33. package/dist/analytical-result-facts.js.map +1 -1
  34. package/dist/answer-loop.d.ts +44 -3
  35. package/dist/answer-loop.d.ts.map +1 -1
  36. package/dist/answer-loop.js +771 -38
  37. package/dist/answer-loop.js.map +1 -1
  38. package/dist/cascade/budgets.js +1 -1
  39. package/dist/cascade/route-policy.d.ts +1 -1
  40. package/dist/cascade/route-policy.d.ts.map +1 -1
  41. package/dist/cascade/route-policy.js +2 -0
  42. package/dist/cascade/route-policy.js.map +1 -1
  43. package/dist/conversation/snapshot.d.ts.map +1 -1
  44. package/dist/conversation/snapshot.js +3 -0
  45. package/dist/conversation/snapshot.js.map +1 -1
  46. package/dist/conversation/turn-trust.d.ts.map +1 -1
  47. package/dist/conversation/turn-trust.js +6 -0
  48. package/dist/conversation/turn-trust.js.map +1 -1
  49. package/dist/generated-analytical-proposal.d.ts +37 -0
  50. package/dist/generated-analytical-proposal.d.ts.map +1 -0
  51. package/dist/generated-analytical-proposal.js +219 -0
  52. package/dist/generated-analytical-proposal.js.map +1 -0
  53. package/dist/index.d.ts +11 -5
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +7 -3
  56. package/dist/index.js.map +1 -1
  57. package/dist/intent-controller.d.ts +8 -1
  58. package/dist/intent-controller.d.ts.map +1 -1
  59. package/dist/intent-controller.js.map +1 -1
  60. package/dist/kg/build.js +102 -6
  61. package/dist/kg/build.js.map +1 -1
  62. package/dist/meaning-resolution.d.ts +30 -0
  63. package/dist/meaning-resolution.d.ts.map +1 -1
  64. package/dist/meaning-resolution.js +61 -8
  65. package/dist/meaning-resolution.js.map +1 -1
  66. package/dist/metadata/analysis-planner.d.ts.map +1 -1
  67. package/dist/metadata/analysis-planner.js +46 -2
  68. package/dist/metadata/analysis-planner.js.map +1 -1
  69. package/dist/metadata/block-fit.d.ts.map +1 -1
  70. package/dist/metadata/block-fit.js +55 -2
  71. package/dist/metadata/block-fit.js.map +1 -1
  72. package/dist/metadata/catalog.d.ts +24 -0
  73. package/dist/metadata/catalog.d.ts.map +1 -1
  74. package/dist/metadata/catalog.js +227 -42
  75. package/dist/metadata/catalog.js.map +1 -1
  76. package/dist/metadata/meaning-evidence.d.ts.map +1 -1
  77. package/dist/metadata/meaning-evidence.js +408 -36
  78. package/dist/metadata/meaning-evidence.js.map +1 -1
  79. package/dist/metadata/sql-context-validation.d.ts +3 -0
  80. package/dist/metadata/sql-context-validation.d.ts.map +1 -1
  81. package/dist/metadata/sql-context-validation.js +16 -5
  82. package/dist/metadata/sql-context-validation.js.map +1 -1
  83. package/dist/plan-execution-adapter.d.ts +9 -1
  84. package/dist/plan-execution-adapter.d.ts.map +1 -1
  85. package/dist/plan-execution-adapter.js +81 -32
  86. package/dist/plan-execution-adapter.js.map +1 -1
  87. package/dist/provider-egress.d.ts +122 -0
  88. package/dist/provider-egress.d.ts.map +1 -0
  89. package/dist/provider-egress.js +504 -0
  90. package/dist/provider-egress.js.map +1 -0
  91. package/dist/providers/claude.d.ts.map +1 -1
  92. package/dist/providers/claude.js +76 -33
  93. package/dist/providers/claude.js.map +1 -1
  94. package/dist/providers/dispatch.d.ts +15 -0
  95. package/dist/providers/dispatch.d.ts.map +1 -0
  96. package/dist/providers/dispatch.js +25 -0
  97. package/dist/providers/dispatch.js.map +1 -0
  98. package/dist/providers/gemini.d.ts.map +1 -1
  99. package/dist/providers/gemini.js +16 -8
  100. package/dist/providers/gemini.js.map +1 -1
  101. package/dist/providers/index.d.ts +2 -1
  102. package/dist/providers/index.d.ts.map +1 -1
  103. package/dist/providers/index.js +1 -0
  104. package/dist/providers/index.js.map +1 -1
  105. package/dist/providers/ollama.d.ts.map +1 -1
  106. package/dist/providers/ollama.js +29 -10
  107. package/dist/providers/ollama.js.map +1 -1
  108. package/dist/providers/openai.d.ts.map +1 -1
  109. package/dist/providers/openai.js +92 -36
  110. package/dist/providers/openai.js.map +1 -1
  111. package/dist/providers/types.d.ts +43 -0
  112. package/dist/providers/types.d.ts.map +1 -1
  113. package/dist/providers/types.js.map +1 -1
  114. package/dist/relationship-proof.d.ts +19 -0
  115. package/dist/relationship-proof.d.ts.map +1 -0
  116. package/dist/relationship-proof.js +75 -0
  117. package/dist/relationship-proof.js.map +1 -0
  118. package/dist/research-loop.d.ts.map +1 -1
  119. package/dist/research-loop.js +17 -11
  120. package/dist/research-loop.js.map +1 -1
  121. package/dist/resolved-analytical-plan.d.ts +25 -3
  122. package/dist/resolved-analytical-plan.d.ts.map +1 -1
  123. package/dist/resolved-analytical-plan.js +460 -31
  124. package/dist/resolved-analytical-plan.js.map +1 -1
  125. package/dist/router.d.ts +38 -1
  126. package/dist/router.d.ts.map +1 -1
  127. package/dist/router.js +1060 -53
  128. package/dist/router.js.map +1 -1
  129. package/dist/semantic-identities.d.ts +18 -0
  130. package/dist/semantic-identities.d.ts.map +1 -0
  131. package/dist/semantic-identities.js +60 -0
  132. package/dist/semantic-identities.js.map +1 -0
  133. package/dist/semantic-proof-authority.d.ts +29 -0
  134. package/dist/semantic-proof-authority.d.ts.map +1 -0
  135. package/dist/semantic-proof-authority.js +34 -0
  136. package/dist/semantic-proof-authority.js.map +1 -0
  137. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"resolved-analytical-plan.d.ts","sourceRoot":"","sources":["../src/resolved-analytical-plan.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resolved-analytical-plan.d.ts","sourceRoot":"","sources":["../src/resolved-analytical-plan.ts"],"names":[],"mappings":"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"}
@@ -1,11 +1,14 @@
1
1
  /**
2
2
  * Immutable, snapshot-bound analytical plan shared by every downstream route.
3
- * The router emits it authoritatively by default. Shadow mode remains the
4
- * bounded route-level rollback switch while integration verification closes.
3
+ * The router emits it authoritatively by default. The `shadow` discriminator
4
+ * remains only for explicit dev/test comparison fixtures; production has no
5
+ * environment flag or request field that can select it.
5
6
  *
6
7
  * Acceptance: AGT-013, API-006.
7
8
  */
8
9
  import { createHash } from "node:crypto";
10
+ import { normalizeMetricCapabilityContract, } from "@duckcodeailabs/dql-core";
11
+ import { buildResolvedRelationshipProofsV1 } from './relationship-proof.js';
9
12
  export function buildResolvedAnalyticalPlan(input) {
10
13
  const byLegacyId = new Map(input.candidates.map((candidate) => [candidate.id, candidate]));
11
14
  const selectedCandidates = input.resolution.selectedConceptIds
@@ -13,18 +16,23 @@ export function buildResolvedAnalyticalPlan(input) {
13
16
  const executionCandidate = input.resolution.recommendedExecutionId
14
17
  ? byLegacyId.get(input.resolution.recommendedExecutionId)
15
18
  : selectedCandidates[0];
16
- const bindingCandidates = resolutionUsesRelationalEvidence(input.resolution)
17
- ? input.candidates
18
- : selectedCandidates.length > 0
19
- ? selectedCandidates
20
- : executionCandidate
21
- ? [executionCandidate]
22
- : input.candidates;
19
+ const bindingCandidates = input.resolution.recommendedRoute === 'certified' && executionCandidate
20
+ // A deterministically compatible certified block has already proved the
21
+ // complete tuple. Bind its declared inputs/outputs as one execution
22
+ // authority instead of mixing in the semantic concept used to select it.
23
+ ? [executionCandidate]
24
+ : resolutionUsesRelationalEvidence(input.resolution)
25
+ ? input.candidates
26
+ : selectedCandidates.length > 0
27
+ ? selectedCandidates
28
+ : executionCandidate
29
+ ? [executionCandidate]
30
+ : input.candidates;
23
31
  const canonicalId = (candidate) => candidate.qualifiedId ?? candidate.id;
24
32
  const selectedConceptIds = selectedCandidates.map(canonicalId);
25
33
  const executionId = executionCandidate ? canonicalId(executionCandidate) : undefined;
26
34
  const measures = input.resolution.queryIntent.measures.length > 0
27
- ? input.resolution.queryIntent.measures.map((requested) => bindRequestedMember(requested, bindingCandidates, 'measure'))
35
+ ? input.resolution.queryIntent.measures.map((requested) => bindRequestedMember(requested, bindingCandidates, 'measure', input.question))
28
36
  : selectedCandidates
29
37
  .filter((candidate) => candidate.kind === 'semantic_metric')
30
38
  .map((candidate) => ({
@@ -40,31 +48,123 @@ export function buildResolvedAnalyticalPlan(input) {
40
48
  const dimensionBindingCandidates = input.resolution.analyticalFrame
41
49
  ? input.candidates
42
50
  : bindingCandidates;
51
+ const selectedCapability = normalizeMetricCapabilityContract(executionCandidate?.analyticalCapability);
52
+ const bindDimension = (requested, roles) => {
53
+ const capabilityBinding = bindSelectedCapabilityDimension(requested, selectedCapability, roles, input.candidates);
54
+ // A provider frame may describe a selected capability, but it cannot pick
55
+ // one of that capability's grouping dimensions for the host. Once a
56
+ // normalized capability supplies a relevant binding, its identifiers,
57
+ // roles, entity/grain and relationship proof are authoritative.
58
+ // A selected capability owns its grouping dimensions, but when it reports
59
+ // AMBIGUITY the question text can still settle it — and until it did, an
60
+ // answerable lookup stopped to ask.
61
+ if (capabilityBinding && capabilityBinding.status === 'ambiguous') {
62
+ const settled = memberNamedInQuestion(capabilityBinding.candidateIds, input.candidates, input.question);
63
+ if (settled)
64
+ return { ...capabilityBinding, qualifiedId: settled, status: 'resolved', candidateIds: [settled] };
65
+ }
66
+ if (capabilityBinding && capabilityBinding.status !== 'unresolved')
67
+ return capabilityBinding;
68
+ const normalizedRequested = normalize(requested);
69
+ const frameIds = uniqueSorted((input.resolution.analyticalFrame?.dimensions ?? [])
70
+ .filter((binding) => capabilityEntailsFrameDimension(selectedCapability, binding)
71
+ && (!selectedCapability || binding.role === 'time_axis')
72
+ && memberTermMatches(normalize(binding.dimensionId), normalizedRequested))
73
+ .map((binding) => binding.dimensionId));
74
+ if (frameIds.length === 1) {
75
+ return {
76
+ requested,
77
+ qualifiedId: frameIds[0],
78
+ status: 'resolved',
79
+ candidateIds: frameIds,
80
+ };
81
+ }
82
+ if (capabilityBinding)
83
+ return capabilityBinding;
84
+ return bindRequestedMember(requested, dimensionBindingCandidates, 'dimension', input.question);
85
+ };
86
+ const rankingRequested = input.resolution.questionType === 'ranking'
87
+ || input.resolution.queryIntent.order !== undefined
88
+ || input.resolution.queryIntent.limit !== undefined;
43
89
  const dimensions = input.resolution.queryIntent.dimensions
44
- .map((requested) => bindRequestedMember(requested, dimensionBindingCandidates, 'dimension'));
45
- const filters = input.resolution.queryIntent.filters.map((filter) => ({
90
+ .map((requested) => bindDimension(requested, rankingRequested ? ['group_by', 'rank_entity'] : ['group_by']));
91
+ // A provider may name the field it is grouping by and forget the MEMBER the
92
+ // reader asked about: "What customer type is Wesley Jenkins?" came back with
93
+ // `filters: []`, so the run returned all 200 customers and the narration
94
+ // truthfully reported that his value was not present.
95
+ //
96
+ // DQL had already found him. `evidence.parsedIntent.filters` are GROUNDED
97
+ // member bindings — matched against the retrieved context, not guessed — so
98
+ // falling back to them adds no unproven predicate. The provider's own filters
99
+ // still win whenever it supplies any.
100
+ const requestedFilters = input.resolution.queryIntent.filters.length > 0
101
+ ? input.resolution.queryIntent.filters
102
+ : (input.evidence.parsedIntent?.filters ?? []).filter((filter) => filter.field && filter.value);
103
+ const filters = requestedFilters.map((filter) => ({
46
104
  ...filter,
47
- binding: bindRequestedMember(filter.field, dimensionBindingCandidates, 'dimension'),
105
+ binding: bindDimension(filter.field, ['filter']),
48
106
  }));
49
- const compatibilityProof = selectedCandidates.map((candidate) => ({
107
+ const capabilityDimensionProof = resolvedCapabilityDimensionProof(selectedCapability, dimensions, filters);
108
+ const proofCandidates = [
109
+ ...selectedCandidates,
110
+ ...(executionCandidate && !selectedCandidates.some((candidate) => candidate.id === executionCandidate.id)
111
+ ? [executionCandidate]
112
+ : []),
113
+ ];
114
+ const compatibilityProof = proofCandidates.map((candidate) => ({
50
115
  candidateId: canonicalId(candidate),
51
116
  compatibility: candidate.compatibility,
52
- facts: [...(candidate.compatibilityFacts ?? [])].sort(),
117
+ facts: uniqueSorted([
118
+ ...(candidate.compatibilityFacts ?? []),
119
+ ...(candidate.id === executionCandidate?.id && selectedCapability
120
+ ? selectedCapabilityProofFacts(selectedCapability, capabilityDimensionProof)
121
+ : []),
122
+ ]),
53
123
  }));
54
124
  const capability = resolveCapability(input.resolution, executionCandidate, measures, dimensions, filters);
125
+ const bindingGaps = bindingMissingInformation(measures, dimensions, filters);
126
+ // The reader named a member that no grounding lane could bind. Answering the
127
+ // question WITHOUT it silently changes what was asked — "what customer type
128
+ // is Wesley Jenkins" became "list every customer" — so the plan carries it as
129
+ // a gap rather than dropping it. Live value lookup is opt-in per project, so
130
+ // the remedy is named here instead of leaving a bare failure.
131
+ const unboundMembers = filters.length === 0 ? (input.evidence.unboundMemberTerms ?? []) : [];
132
+ const unboundMemberGaps = unboundMembers.length > 0
133
+ ? [`The named value ${unboundMembers.map((term) => `“${term}”`).join(' and ')} could not be bound to a governed member, so it was not applied as a filter. Enable live value grounding (agent.runtimeValueGrounding.mode = "safe_automatic" with a searchSafeColumns allowlist) or filter on a modeled dimension.`]
134
+ : [];
135
+ const missingInformation = uniqueSorted([
136
+ ...input.resolution.missingInformation,
137
+ ...bindingGaps,
138
+ ...unboundMemberGaps,
139
+ ...(capability === 'blocked' && input.resolution.missingInformation.length === 0 && bindingGaps.length === 0
140
+ ? [`The selected ${input.resolution.recommendedRoute} capability is not executable for this analytical tuple.`]
141
+ : []),
142
+ ]);
55
143
  const timeBounds = input.resolution.queryIntent.timeRange
56
144
  ? resolvePlanTimeRange(input.resolution.queryIntent.timeRange, input.referenceTime ?? new Date())
57
145
  : undefined;
146
+ const snapshotId = input.evidence.knowledgeLens?.snapshotId
147
+ ?? input.evidence.snapshotId
148
+ ?? input.evidence.sourceFingerprint
149
+ ?? 'snapshot-unavailable';
150
+ const selectedExecutionCapability = selectedCapability?.executionCapabilities.find((candidate) => candidate.route === input.resolution.recommendedRoute);
151
+ const relationshipProofs = selectedCapability && selectedExecutionCapability && executionId
152
+ ? buildResolvedRelationshipProofsV1({
153
+ capability: selectedCapability,
154
+ dimensionIds: capabilityDimensionProof.map((dimension) => dimension.dimensionId),
155
+ route: selectedExecutionCapability.route,
156
+ ...(selectedExecutionCapability.adapterId ? { adapterId: selectedExecutionCapability.adapterId } : {}),
157
+ executionId,
158
+ snapshotId,
159
+ })
160
+ : [];
58
161
  const payload = {
59
162
  schemaVersion: input.resolution.analyticalFrame
60
163
  ? 2
61
164
  : 1,
62
- mode: input.mode ?? "shadow",
165
+ mode: input.mode ?? "authoritative",
63
166
  revision: 0,
64
- snapshotId: input.evidence.knowledgeLens?.snapshotId
65
- ?? input.evidence.snapshotId
66
- ?? input.evidence.sourceFingerprint
67
- ?? 'snapshot-unavailable',
167
+ snapshotId,
68
168
  sourceFingerprint: input.evidence.sourceFingerprint,
69
169
  question: input.question,
70
170
  interpretedQuestion: input.resolution.interpretedQuestion,
@@ -72,6 +172,12 @@ export function buildResolvedAnalyticalPlan(input) {
72
172
  confidence: input.resolution.confidence,
73
173
  selectedConceptIds,
74
174
  executionId,
175
+ ...(selectedCapability
176
+ ? {
177
+ selectedCapability,
178
+ selectedCapabilityFingerprint: selectedCapability.sourceFingerprint,
179
+ }
180
+ : {}),
75
181
  recommendedRoute: input.resolution.recommendedRoute,
76
182
  capability,
77
183
  query: {
@@ -84,10 +190,14 @@ export function buildResolvedAnalyticalPlan(input) {
84
190
  ...(input.resolution.queryIntent.order ? { order: input.resolution.queryIntent.order } : {}),
85
191
  ...(input.resolution.queryIntent.limit !== undefined ? { limit: input.resolution.queryIntent.limit } : {}),
86
192
  },
87
- entityGrain: input.resolution.analyticalFrame?.entityGrainIds[0] ??
88
- executionCandidate?.primaryEntity,
193
+ entityGrain: selectedCapability
194
+ ? resolvedCapabilityEntityGrain(selectedCapability, dimensions)
195
+ : input.resolution.analyticalFrame?.entityGrainIds[0] ?? executionCandidate?.primaryEntity,
89
196
  sourceRelationIds: uniqueSorted(selectedCandidates.flatMap((candidate) => candidate.sourceObjects ?? [])),
90
- relationshipPathIds: uniqueSorted(selectedCandidates.flatMap((candidate) => candidate.relationshipEvidence ?? [])),
197
+ relationshipPathIds: selectedCapability
198
+ ? uniqueSorted(relationshipProofs.flatMap((proof) => proof.kind === 'dql_relationship_path' ? proof.relationshipPathIds : []))
199
+ : uniqueSorted(selectedCandidates.flatMap((candidate) => candidate.relationshipEvidence ?? [])),
200
+ relationshipProofs,
91
201
  compatibilityProof,
92
202
  outputContract: {
93
203
  measures: uniqueSorted(measures.flatMap((binding) => binding.qualifiedId ? [binding.qualifiedId] : [binding.requested])),
@@ -107,8 +217,34 @@ export function buildResolvedAnalyticalPlan(input) {
107
217
  const retrieved = byLegacyId.get(candidate.id);
108
218
  return { id: retrieved ? canonicalId(retrieved) : candidate.id, reason: candidate.reason };
109
219
  }),
110
- missingInformation: [...input.resolution.missingInformation],
220
+ missingInformation,
111
221
  clarification: input.resolution.clarifyingQuestion,
222
+ ...(capability === 'blocked'
223
+ ? {
224
+ resolutionFailure: {
225
+ outcome: input.resolution.compatibilityOutcome
226
+ ?? (input.resolution.recommendedRoute === 'clarify' || input.resolution.confidence === 'low'
227
+ ? 'clarify'
228
+ : bindingCandidateIds(measures, dimensions, filters).length > 0
229
+ ? 'clarify'
230
+ : 'modeling_gap'),
231
+ codes: uniqueSorted([
232
+ ...(input.resolution.compatibilityFailures ?? []).map((failure) => failure.code),
233
+ ...bindingFailureCodes(measures, dimensions, filters),
234
+ ]),
235
+ candidateIds: uniqueSorted([
236
+ ...(input.resolution.compatibilityFailures ?? []).flatMap((failure) => failure.candidateIds),
237
+ ...bindingCandidateIds(measures, dimensions, filters),
238
+ ]),
239
+ ...(selectedCapability?.metricId
240
+ ? { selectedCapabilityId: selectedCapability.metricId }
241
+ : {}),
242
+ ...(executionId ? { selectedExecutionId: executionId } : {}),
243
+ finalCapability: capability,
244
+ bindings: unresolvedBindingReceipts(measures, dimensions, filters),
245
+ },
246
+ }
247
+ : {}),
112
248
  knowledgeLens: input.evidence.knowledgeLens,
113
249
  ...((input.resolution.analyticalPolicyIds?.length ?? 0) > 0
114
250
  ? {
@@ -133,6 +269,233 @@ export function buildResolvedAnalyticalPlan(input) {
133
269
  fingerprint,
134
270
  });
135
271
  }
272
+ /**
273
+ * The selected metric capability is the sole authority for metric-relative
274
+ * dimensions. Standalone member cards can help the model explain meaning, but
275
+ * cannot introduce a second execution binding or substitute a same-name ID.
276
+ */
277
+ function bindSelectedCapabilityDimension(requested, capability, requiredRoles, candidates) {
278
+ if (!capability)
279
+ return undefined;
280
+ const eligible = eligibleCapabilityDimensions(capability, requiredRoles);
281
+ const exactIds = uniqueSorted(eligible
282
+ .filter((dimension) => dimension.dimensionId === requested)
283
+ .map((dimension) => dimension.dimensionId));
284
+ if (exactIds.length === 1) {
285
+ return {
286
+ requested,
287
+ qualifiedId: exactIds[0],
288
+ status: 'resolved',
289
+ candidateIds: exactIds,
290
+ };
291
+ }
292
+ const requestedEntityEvidence = candidates.filter((candidate) => candidate.semanticObjectType === 'entity'
293
+ && candidateTerms(candidate).some((term) => termMatchSpecificity(term, canonicalTokens(requested)) > 0));
294
+ const entityScoped = requestedEntityEvidence.length > 0
295
+ ? eligible.filter((dimension) => capabilityDimensionMatchesEntityEvidence(dimension, requestedEntityEvidence))
296
+ : [];
297
+ const bindingPool = entityScoped.length > 0 ? entityScoped : eligible;
298
+ const scored = bindingPool
299
+ .map((dimension) => ({
300
+ dimension,
301
+ score: capabilityDimensionMatchScore(requested, dimension, capability, candidates),
302
+ }))
303
+ .filter((match) => match.score > 0);
304
+ if (scored.length === 0) {
305
+ return {
306
+ requested,
307
+ status: 'unresolved',
308
+ // Role-compatible but semantically unrelated members are not choices a
309
+ // user can use to repair this binding. Keep the list empty so the router
310
+ // reports a modeling/binding gap instead of offering arbitrary booleans.
311
+ candidateIds: [],
312
+ };
313
+ }
314
+ const bestScore = Math.max(...scored.map((match) => match.score));
315
+ const matches = scored
316
+ .filter((match) => match.score === bestScore)
317
+ .map((match) => match.dimension);
318
+ const ids = uniqueSorted(matches.map((dimension) => dimension.dimensionId));
319
+ return {
320
+ requested,
321
+ ...(ids.length === 1 ? { qualifiedId: ids[0] } : {}),
322
+ status: ids.length === 1 ? 'resolved' : 'ambiguous',
323
+ candidateIds: ids,
324
+ };
325
+ }
326
+ function capabilityDimensionMatchScore(requested, dimension, capability, candidates) {
327
+ const requestedTokens = canonicalTokens(requested);
328
+ if (requestedTokens.length === 0)
329
+ return 0;
330
+ const evidenceTerms = candidates
331
+ .filter((candidate) => candidate.kind === 'semantic_member'
332
+ && candidate.semanticObjectType !== 'entity'
333
+ && (candidate.qualifiedId ?? candidate.id) === dimension.dimensionId)
334
+ .flatMap((candidate) => [candidate.name, ...(candidate.aliases ?? [])]);
335
+ const evidenceScore = Math.max(0, ...evidenceTerms.map((term) => termMatchSpecificity(term, requestedTokens)));
336
+ const authoredDisplayScore = Math.max(0, ...[dimension.label, ...(dimension.aliases ?? [])]
337
+ .filter((term) => Boolean(term))
338
+ .map((term) => termMatchSpecificity(term, requestedTokens)));
339
+ const localScore = termMatchSpecificity(localQualifiedLeaf(dimension.dimensionId), requestedTokens);
340
+ const conventionalIdentityScore = conventionalIdentitySpecificity(localQualifiedLeaf(dimension.dimensionId), requestedTokens);
341
+ const entityScore = termMatchSpecificity(localQualifiedLeaf(dimension.entityId), requestedTokens);
342
+ const identityScore = Math.max(evidenceScore > 0 ? 1_000 + evidenceScore : 0, authoredDisplayScore > 0 ? 900 + authoredDisplayScore : 0, conventionalIdentityScore > 0 ? 1_100 + conventionalIdentityScore : 0, localScore > 0 ? 600 + localScore : 0, entityScore > 0 ? 300 + entityScore : 0);
343
+ if (identityScore === 0)
344
+ return 0;
345
+ const authorityScore = (dimension.supportedRoles.includes('display') ? 8 : 0)
346
+ + (dimension.entityId === capability.primaryEntityId ? 4 : 0)
347
+ + (capability.resultGrainIds.includes(dimension.entityId) ? 4 : 0)
348
+ + ((dimension.relationshipPathIds?.length ?? 0) > 0 ? 4 : 0);
349
+ return identityScore + authorityScore;
350
+ }
351
+ function conventionalIdentitySpecificity(term, requestedTokens) {
352
+ const termTokens = canonicalTokens(term);
353
+ if (!requestedTokens.every((token) => termTokens.includes(token)))
354
+ return 0;
355
+ const remaining = termTokens.filter((token) => !requestedTokens.includes(token));
356
+ const identityDescriptors = new Set(['display', 'identifier', 'id', 'key', 'label', 'name', 'title']);
357
+ return remaining.length === 1 && identityDescriptors.has(remaining[0]) ? 100 : 0;
358
+ }
359
+ function capabilityDimensionMatchesEntityEvidence(dimension, entityCandidates) {
360
+ const ownerTokens = canonicalTokens(localQualifiedOwner(dimension.dimensionId));
361
+ return entityCandidates.some((candidate) => {
362
+ const candidateId = candidate.qualifiedId ?? candidate.id;
363
+ if (dimension.entityId === candidateId)
364
+ return true;
365
+ if (ownerTokens.length === 0)
366
+ return false;
367
+ const entityTerms = [candidate.name, ...(candidate.aliases ?? []), localQualifiedLeaf(candidateId)];
368
+ return entityTerms.some((term) => {
369
+ const tokens = canonicalTokens(localQualifiedLeaf(term));
370
+ return tokens.length > 0 && tokens.every((token) => ownerTokens.includes(token));
371
+ });
372
+ });
373
+ }
374
+ function termMatchSpecificity(term, requestedTokens) {
375
+ const termTokens = canonicalTokens(term);
376
+ if (termTokens.length === 0 || !requestedTokens.every((token) => termTokens.includes(token)))
377
+ return 0;
378
+ if (termTokens.length === requestedTokens.length
379
+ && termTokens.every((token, index) => token === requestedTokens[index]))
380
+ return 100;
381
+ return 50 + requestedTokens.length;
382
+ }
383
+ function canonicalTokens(value) {
384
+ return normalize(value)
385
+ .split(' ')
386
+ .filter(Boolean)
387
+ .map(singularizeBindingToken);
388
+ }
389
+ function singularizeBindingToken(value) {
390
+ if (value.length <= 4 || value.endsWith('ss') || value.endsWith('us') || value.endsWith('is'))
391
+ return value;
392
+ if (value.endsWith('ies'))
393
+ return `${value.slice(0, -3)}y`;
394
+ return value.endsWith('s') ? value.slice(0, -1) : value;
395
+ }
396
+ function localQualifiedLeaf(value) {
397
+ const leaf = value.split(/[:./]+/).filter(Boolean).at(-1);
398
+ return leaf ?? value;
399
+ }
400
+ function localQualifiedOwner(value) {
401
+ const local = value.split(':').at(-1) ?? value;
402
+ const segments = local.split(/[./]+/).filter(Boolean);
403
+ return segments.length > 1 ? segments[0] : '';
404
+ }
405
+ function eligibleCapabilityDimensions(capability, requiredRoles) {
406
+ if (!capability)
407
+ return [];
408
+ return capability.dimensions.filter((dimension) => {
409
+ if (!requiredRoles.every((role) => dimension.supportedRoles.includes(role)))
410
+ return false;
411
+ const sameEntity = dimension.entityId === capability.primaryEntityId;
412
+ const declaredGrain = capability.resultGrainIds.includes(dimension.entityId);
413
+ const relationshipProven = (dimension.relationshipPathIds?.length ?? 0) > 0;
414
+ const nativeGroupingProven = Boolean(dimension.nativeGroupingReference)
415
+ && capability.executionCapabilities.some((execution) => execution.route === 'semantic');
416
+ return sameEntity || declaredGrain || relationshipProven || nativeGroupingProven;
417
+ });
418
+ }
419
+ function capabilityEntailsFrameDimension(capability, binding) {
420
+ if (!capability)
421
+ return false;
422
+ if (binding.role === 'time_axis') {
423
+ return capability.timeDimensions.some((dimension) => dimension.dimensionId === binding.dimensionId);
424
+ }
425
+ return eligibleCapabilityDimensions(capability, [binding.role])
426
+ .some((dimension) => dimension.dimensionId === binding.dimensionId);
427
+ }
428
+ function resolvedCapabilityDimensionProof(capability, dimensions, filters) {
429
+ if (!capability)
430
+ return [];
431
+ const boundIds = new Set([
432
+ ...dimensions.flatMap((binding) => binding.qualifiedId ? [binding.qualifiedId] : []),
433
+ ...filters.flatMap((filter) => filter.binding.qualifiedId ? [filter.binding.qualifiedId] : []),
434
+ ]);
435
+ return capability.dimensions
436
+ .filter((dimension) => boundIds.has(dimension.dimensionId))
437
+ .sort((left, right) => left.dimensionId.localeCompare(right.dimensionId));
438
+ }
439
+ function resolvedCapabilityEntityGrain(capability, dimensions) {
440
+ const groupedIds = new Set(dimensions.flatMap((binding) => binding.qualifiedId ? [binding.qualifiedId] : []));
441
+ const groupedEntityIds = uniqueSorted(capability.dimensions
442
+ .filter((dimension) => groupedIds.has(dimension.dimensionId))
443
+ .map((dimension) => dimension.entityId));
444
+ if (groupedEntityIds.length === 1 && capability.resultGrainIds.includes(groupedEntityIds[0])) {
445
+ return groupedEntityIds[0];
446
+ }
447
+ return capability.defaultResultGrainId;
448
+ }
449
+ function selectedCapabilityProofFacts(capability, dimensions) {
450
+ return [
451
+ `capability:metric:${capability.metricId}`,
452
+ `capability:primary_entity:${capability.primaryEntityId}`,
453
+ `capability:default_result_grain:${capability.defaultResultGrainId}`,
454
+ ...capability.resultGrainIds.map((grain) => `capability:result_grain:${grain}`),
455
+ ...dimensions.map((dimension) => `capability:dimension:${dimension.dimensionId}:${dimension.entityId}`),
456
+ ...dimensions.flatMap((dimension) => dimension.nativeGroupingReference
457
+ ? [`capability:native_grouping:${dimension.dimensionId}:${dimension.nativeGroupingReference}`]
458
+ : []),
459
+ ...dimensions.flatMap((dimension) => (dimension.relationshipPathIds ?? []).map((relationshipId) => `capability:relationship:${relationshipId}`)),
460
+ ...capability.executionCapabilities.map((execution) => `capability:route:${execution.route}:${execution.adapterId ?? 'native'}`),
461
+ ];
462
+ }
463
+ function unresolvedBindingReceipts(measures, dimensions, filters) {
464
+ return [
465
+ ...measures.map((binding) => ({ kind: 'measure', binding })),
466
+ ...dimensions.map((binding) => ({ kind: 'dimension', binding })),
467
+ ...filters.map((filter) => ({ kind: 'filter', binding: filter.binding })),
468
+ ]
469
+ .filter(({ binding }) => binding.status !== 'resolved')
470
+ .map(({ kind, binding }) => ({
471
+ kind,
472
+ requested: binding.requested,
473
+ status: binding.status,
474
+ candidateIds: [...binding.candidateIds],
475
+ }));
476
+ }
477
+ function bindingCandidateIds(measures, dimensions, filters) {
478
+ return unresolvedBindingReceipts(measures, dimensions, filters)
479
+ .flatMap((binding) => binding.candidateIds);
480
+ }
481
+ function bindingFailureCodes(measures, dimensions, filters) {
482
+ return unresolvedBindingReceipts(measures, dimensions, filters)
483
+ .map((binding) => `${binding.kind.toUpperCase()}_${binding.status.toUpperCase()}`);
484
+ }
485
+ function bindingMissingInformation(measures, dimensions, filters) {
486
+ return [
487
+ ...measures.map((binding) => ({ kind: 'measure', binding })),
488
+ ...dimensions.map((binding) => ({ kind: 'dimension', binding })),
489
+ ...filters.map((filter) => ({ kind: 'filter', binding: filter.binding })),
490
+ ].flatMap(({ kind, binding }) => {
491
+ if (binding.status === 'resolved')
492
+ return [];
493
+ const choices = binding.candidateIds.length > 0
494
+ ? ` Qualified choices: ${binding.candidateIds.join(', ')}.`
495
+ : '';
496
+ return [`Requested ${kind} “${binding.requested}” is ${binding.status}.${choices}`];
497
+ });
498
+ }
136
499
  /**
137
500
  * Apply an explicitly typed follow-up delta. No prose, prior SQL, or answer text
138
501
  * is inspected; every carried member remains a qualified binding from the root.
@@ -220,24 +583,74 @@ function cloneBinding(binding) {
220
583
  function cloneFilter(filter) {
221
584
  return { ...filter, binding: cloneBinding(filter.binding) };
222
585
  }
223
- function bindRequestedMember(requested, candidates, kind) {
586
+ /**
587
+ * Disambiguate a member binding using the QUESTION TEXT.
588
+ *
589
+ * A planner (or a provider) often reduces "customer type" to the bare term
590
+ * `type`, and worse, may split it into two requested dimensions ("customer",
591
+ * "type"). A bare `type` then matches `customers.customer_type` AND
592
+ * `products.product_type` and reads as ambiguous, so an answerable question
593
+ * stops to ask which dimension was meant. The question already answers it:
594
+ * "customer type" appears in it and "product type" does not.
595
+ *
596
+ * Returns the single id the reader actually named, or undefined when the text
597
+ * does not settle it. Only multi-word names count — one shared token is not
598
+ * evidence.
599
+ */
600
+ function memberNamedInQuestion(ids, candidates, question) {
601
+ if (ids.length < 2)
602
+ return undefined;
603
+ const questionText = normalize(question);
604
+ if (!questionText)
605
+ return undefined;
606
+ const named = ids.filter((id) => {
607
+ const candidate = candidates.find((item) => (item.qualifiedId ?? item.id) === id || item.id === id);
608
+ const terms = [candidate?.name ?? '', ...(candidate?.aliases ?? []), id.split(/[.:/]/).at(-1) ?? '']
609
+ .map((term) => normalize(String(term)))
610
+ .filter((term) => term.split(' ').length > 1);
611
+ return terms.some((term) => questionText === term
612
+ || questionText.startsWith(`${term} `)
613
+ || questionText.endsWith(` ${term}`)
614
+ || questionText.includes(` ${term} `));
615
+ });
616
+ return named.length === 1 ? named[0] : undefined;
617
+ }
618
+ function bindRequestedMember(requested, candidates, kind, question = '') {
224
619
  const normalized = normalize(requested);
225
- const directCandidates = candidates.filter((candidate) => {
620
+ // A lexical decoy that compatibility already rejected must never introduce
621
+ // a second binding identity into the frozen plan. Only candidates still
622
+ // eligible for the requested tuple can contribute member authority.
623
+ const eligibleCandidates = candidates.filter((candidate) => candidate.compatibility !== 'incompatible');
624
+ const directCandidates = eligibleCandidates.filter((candidate) => {
226
625
  const kindMatches = kind === 'measure'
227
626
  ? candidate.kind === 'semantic_metric' || candidate.kind === 'sql_column'
228
627
  : candidate.kind === 'semantic_member' || candidate.kind === 'sql_column';
229
628
  return kindMatches
230
629
  && candidateTerms(candidate).some((term) => memberTermMatches(term, normalized));
231
630
  });
232
- const ids = uniqueSorted([
631
+ const collectedIds = uniqueSorted([
233
632
  ...directCandidates.map((candidate) => candidate.qualifiedId ?? candidate.id),
234
- ...(kind === 'dimension' ? candidates.flatMap((candidate) => (candidate.dimensions ?? []).filter((dimension) => {
633
+ ...(kind === 'dimension' ? eligibleCandidates.flatMap((candidate) => (candidate.dimensions ?? []).filter((dimension) => {
235
634
  const value = normalize(dimension);
236
635
  return value === normalized || value.endsWith(` ${normalized}`);
237
636
  }).map((dimension) => qualifyDeclaredDimension(candidate, dimension))) : []),
238
637
  ]);
239
- if (ids.length === 0 && kind === 'measure' && candidates.length === 1) {
240
- const certified = candidates[0];
638
+ // A candidate contributes its qualified identity AND its own declared
639
+ // dimension list, so a dimension that IS the candidate arrived twice —
640
+ // `semantic:dimension:customers.customer_type` alongside a bare
641
+ // `customer_type`. Two ids read as ambiguous, the binding failed, and the
642
+ // plan blocked, which is why a metric-free attribute lookup could never
643
+ // freeze. A bare name that is the LEAF of a qualified id in the same set is
644
+ // the same member named twice, not a second choice.
645
+ const qualifiedIds = collectedIds.filter((id) => /[:./]/.test(id));
646
+ const ids = collectedIds.filter((id) => {
647
+ if (/[:./]/.test(id))
648
+ return true;
649
+ const bare = normalize(id);
650
+ return !qualifiedIds.some((qualified) => normalize(qualified.split(/[.:/]/).at(-1) ?? '') === bare);
651
+ });
652
+ if (ids.length === 0 && kind === 'measure' && eligibleCandidates.length === 1) {
653
+ const certified = eligibleCandidates[0];
241
654
  if (certified.kind === 'certified_block' && certified.compatibility === 'compatible') {
242
655
  const id = certified.qualifiedId ?? certified.id;
243
656
  return {
@@ -248,6 +661,17 @@ function bindRequestedMember(requested, candidates, kind) {
248
661
  };
249
662
  }
250
663
  }
664
+ const namedWinner = memberNamedInQuestion(ids, eligibleCandidates, question);
665
+ if (namedWinner) {
666
+ const winnerCandidate = directCandidates.find((candidate) => (candidate.qualifiedId ?? candidate.id) === namedWinner);
667
+ return {
668
+ requested,
669
+ qualifiedId: namedWinner,
670
+ ...(winnerCandidate?.aggregation ? { aggregation: winnerCandidate.aggregation } : {}),
671
+ status: 'resolved',
672
+ candidateIds: [namedWinner],
673
+ };
674
+ }
251
675
  const direct = ids.length === 1
252
676
  ? directCandidates.find((candidate) => (candidate.qualifiedId ?? candidate.id) === ids[0])
253
677
  : undefined;
@@ -316,7 +740,12 @@ function resolveCapability(resolution, execution, measures, dimensions, filters)
316
740
  return 'blocked';
317
741
  }
318
742
  function normalize(value) {
319
- return value.toLowerCase().replace(/[_./:-]+/g, ' ').replace(/[^a-z0-9 ]+/g, ' ').replace(/\s+/g, ' ').trim();
743
+ return value.toLowerCase()
744
+ .replace(/%/g, ' percentage ')
745
+ .replace(/[_./:-]+/g, ' ')
746
+ .replace(/[^a-z0-9 ]+/g, ' ')
747
+ .replace(/\s+/g, ' ')
748
+ .trim();
320
749
  }
321
750
  /** Resolve common analytical ranges once so no executor reinterprets "last month". */
322
751
  export function resolvePlanTimeRange(expression, referenceTime) {