@duckcodeailabs/dql-agent 1.13.4 → 1.13.5

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 (120) hide show
  1. package/dist/agent-run-engine.d.ts +66 -3
  2. package/dist/agent-run-engine.d.ts.map +1 -1
  3. package/dist/agent-run-engine.js +441 -28
  4. package/dist/agent-run-engine.js.map +1 -1
  5. package/dist/agentic/tool-loop.d.ts.map +1 -1
  6. package/dist/agentic/tool-loop.js +100 -32
  7. package/dist/agentic/tool-loop.js.map +1 -1
  8. package/dist/aggregation-safety-proof.d.ts +41 -0
  9. package/dist/aggregation-safety-proof.d.ts.map +1 -0
  10. package/dist/aggregation-safety-proof.js +511 -0
  11. package/dist/aggregation-safety-proof.js.map +1 -0
  12. package/dist/analytical-compatibility.d.ts.map +1 -1
  13. package/dist/analytical-compatibility.js +42 -25
  14. package/dist/analytical-compatibility.js.map +1 -1
  15. package/dist/analytical-execution-graph.d.ts +4 -21
  16. package/dist/analytical-execution-graph.d.ts.map +1 -1
  17. package/dist/analytical-execution-graph.js +27 -0
  18. package/dist/analytical-execution-graph.js.map +1 -1
  19. package/dist/analytical-frame.d.ts +12 -1
  20. package/dist/analytical-frame.d.ts.map +1 -1
  21. package/dist/analytical-frame.js +102 -2
  22. package/dist/analytical-frame.js.map +1 -1
  23. package/dist/analytical-result-facts.d.ts +53 -0
  24. package/dist/analytical-result-facts.d.ts.map +1 -1
  25. package/dist/analytical-result-facts.js +144 -0
  26. package/dist/analytical-result-facts.js.map +1 -1
  27. package/dist/answer-loop.d.ts +40 -3
  28. package/dist/answer-loop.d.ts.map +1 -1
  29. package/dist/answer-loop.js +681 -37
  30. package/dist/answer-loop.js.map +1 -1
  31. package/dist/cascade/budgets.js +1 -1
  32. package/dist/cascade/route-policy.d.ts +1 -1
  33. package/dist/cascade/route-policy.d.ts.map +1 -1
  34. package/dist/cascade/route-policy.js +2 -0
  35. package/dist/cascade/route-policy.js.map +1 -1
  36. package/dist/generated-analytical-proposal.d.ts +37 -0
  37. package/dist/generated-analytical-proposal.d.ts.map +1 -0
  38. package/dist/generated-analytical-proposal.js +219 -0
  39. package/dist/generated-analytical-proposal.js.map +1 -0
  40. package/dist/index.d.ts +9 -5
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +6 -3
  43. package/dist/index.js.map +1 -1
  44. package/dist/intent-controller.d.ts +8 -1
  45. package/dist/intent-controller.d.ts.map +1 -1
  46. package/dist/intent-controller.js.map +1 -1
  47. package/dist/kg/build.js +102 -6
  48. package/dist/kg/build.js.map +1 -1
  49. package/dist/meaning-resolution.d.ts +23 -0
  50. package/dist/meaning-resolution.d.ts.map +1 -1
  51. package/dist/meaning-resolution.js +61 -8
  52. package/dist/meaning-resolution.js.map +1 -1
  53. package/dist/metadata/analysis-planner.d.ts.map +1 -1
  54. package/dist/metadata/analysis-planner.js +46 -2
  55. package/dist/metadata/analysis-planner.js.map +1 -1
  56. package/dist/metadata/block-fit.d.ts.map +1 -1
  57. package/dist/metadata/block-fit.js +55 -2
  58. package/dist/metadata/block-fit.js.map +1 -1
  59. package/dist/metadata/meaning-evidence.d.ts.map +1 -1
  60. package/dist/metadata/meaning-evidence.js +323 -36
  61. package/dist/metadata/meaning-evidence.js.map +1 -1
  62. package/dist/metadata/sql-context-validation.d.ts +3 -0
  63. package/dist/metadata/sql-context-validation.d.ts.map +1 -1
  64. package/dist/metadata/sql-context-validation.js +16 -5
  65. package/dist/metadata/sql-context-validation.js.map +1 -1
  66. package/dist/plan-execution-adapter.d.ts +9 -1
  67. package/dist/plan-execution-adapter.d.ts.map +1 -1
  68. package/dist/plan-execution-adapter.js +81 -32
  69. package/dist/plan-execution-adapter.js.map +1 -1
  70. package/dist/provider-egress.d.ts +122 -0
  71. package/dist/provider-egress.d.ts.map +1 -0
  72. package/dist/provider-egress.js +504 -0
  73. package/dist/provider-egress.js.map +1 -0
  74. package/dist/providers/claude.d.ts.map +1 -1
  75. package/dist/providers/claude.js +76 -33
  76. package/dist/providers/claude.js.map +1 -1
  77. package/dist/providers/dispatch.d.ts +15 -0
  78. package/dist/providers/dispatch.d.ts.map +1 -0
  79. package/dist/providers/dispatch.js +25 -0
  80. package/dist/providers/dispatch.js.map +1 -0
  81. package/dist/providers/gemini.d.ts.map +1 -1
  82. package/dist/providers/gemini.js +16 -8
  83. package/dist/providers/gemini.js.map +1 -1
  84. package/dist/providers/index.d.ts +2 -1
  85. package/dist/providers/index.d.ts.map +1 -1
  86. package/dist/providers/index.js +1 -0
  87. package/dist/providers/index.js.map +1 -1
  88. package/dist/providers/ollama.d.ts.map +1 -1
  89. package/dist/providers/ollama.js +29 -10
  90. package/dist/providers/ollama.js.map +1 -1
  91. package/dist/providers/openai.d.ts.map +1 -1
  92. package/dist/providers/openai.js +92 -36
  93. package/dist/providers/openai.js.map +1 -1
  94. package/dist/providers/types.d.ts +43 -0
  95. package/dist/providers/types.d.ts.map +1 -1
  96. package/dist/providers/types.js.map +1 -1
  97. package/dist/relationship-proof.d.ts +19 -0
  98. package/dist/relationship-proof.d.ts.map +1 -0
  99. package/dist/relationship-proof.js +75 -0
  100. package/dist/relationship-proof.js.map +1 -0
  101. package/dist/research-loop.d.ts.map +1 -1
  102. package/dist/research-loop.js +17 -11
  103. package/dist/research-loop.js.map +1 -1
  104. package/dist/resolved-analytical-plan.d.ts +25 -3
  105. package/dist/resolved-analytical-plan.d.ts.map +1 -1
  106. package/dist/resolved-analytical-plan.js +369 -27
  107. package/dist/resolved-analytical-plan.js.map +1 -1
  108. package/dist/router.d.ts +30 -1
  109. package/dist/router.d.ts.map +1 -1
  110. package/dist/router.js +705 -44
  111. package/dist/router.js.map +1 -1
  112. package/dist/semantic-identities.d.ts +18 -0
  113. package/dist/semantic-identities.d.ts.map +1 -0
  114. package/dist/semantic-identities.js +60 -0
  115. package/dist/semantic-identities.js.map +1 -0
  116. package/dist/semantic-proof-authority.d.ts +29 -0
  117. package/dist/semantic-proof-authority.d.ts.map +1 -0
  118. package/dist/semantic-proof-authority.js +34 -0
  119. package/dist/semantic-proof-authority.js.map +1 -0
  120. package/package.json +4 -4
@@ -15,7 +15,7 @@
15
15
  * full pipeline.
16
16
  */
17
17
  import { createHash } from 'node:crypto';
18
- import { describeDialectForPrompt, normalizeMetricCapabilityContract, } from '@duckcodeailabs/dql-core';
18
+ import { describeDialectForPrompt, buildSqlOutputExpressionSignature, normalizeMetricCapabilityContract, semanticExecutionFingerprint, } from '@duckcodeailabs/dql-core';
19
19
  import { buildSkillBlockHints, buildSkillMetricHints, buildSkillsPrompt, expandQuestionWithSkillVocabulary, selectRelevantSkills } from './skills/loader.js';
20
20
  import { conversationTurnContextState, } from './conversation/snapshot.js';
21
21
  import { isTrustedConversationTurn } from './conversation/turn-trust.js';
@@ -32,6 +32,9 @@ import { createContextLedger } from './grounding/context-ledger.js';
32
32
  import { validateAnswerResultShape } from './answer-shape.js';
33
33
  import { analyticalErrorDetail, } from './analytical-error.js';
34
34
  import { fanoutWarningsForSql } from './metadata/grain-ledger.js';
35
+ import { buildAggregationSafetyProof, buildSemanticCompilationAggregationSafetyProof, } from './aggregation-safety-proof.js';
36
+ import { buildSemanticProofAuthorityV1 } from './semantic-proof-authority.js';
37
+ import { GENERATED_ANALYTICAL_TUPLE_DRIFT_MESSAGE, validateGeneratedAnalyticalProposal, } from './generated-analytical-proposal.js';
35
38
  import { evaluateDbtFirstGeneratedSql } from './metadata/dbt-first-safety.js';
36
39
  import { planAnalyticalPath, humanizeAnalyticalEntityId, analyticalPolicyUserFacingReason, } from './metadata/analytical-policy.js';
37
40
  import { planCertifiedAdaptation } from './metadata/block-adapt.js';
@@ -47,14 +50,14 @@ import { questionTypeFromText } from './meaning-resolution.js';
47
50
  import { cascadeTraceToEvidenceRouteSteps, createCascadeAnswerResult, createCascadeTrace, } from './cascade/cascade.js';
48
51
  import { shouldClarifyBeforeGeneration } from './cascade/triage.js';
49
52
  import { stampTrustLabel } from './trust/stamp.js';
50
- import { deriveResolvedAnalyticalPlan } from './resolved-analytical-plan.js';
53
+ import { buildResolvedAnalyticalPlan, deriveResolvedAnalyticalPlan } from './resolved-analytical-plan.js';
51
54
  import { adaptAnalyticalFreshnessRequest, adaptAnalyticalSemanticGraph, adaptResolvedAnalyticalPlan, buildPlanExecutionRegistry, } from './plan-execution-adapter.js';
52
55
  import { buildGovernedRelationalRegistry, compileGovernedRelationalExecutionGraph, compileGovernedRelationalPlan, finalizeGovernedCompilationReceipt, renderGovernedRelationalDqlArtifact, } from './governed-relational-compiler.js';
53
56
  import { buildAnalyticalExecutionGraph, executeAnalyticalExecutionGraph, } from './analytical-execution-graph.js';
54
57
  import { buildAnalyticalResultFacts, renderDeterministicAnalyticalNarrative, validateAnalyticalNarrativeClaims, } from './analytical-result-facts.js';
55
58
  import { resolveAnalyticalPeriods, } from './analytical-period-resolution.js';
56
59
  import { classifyAnalyticalFailure, createAnalyticalFailure, } from './analytical-failure-repair.js';
57
- import { QUICK_PROMPT_CONTEXT_BUDGET, canUseLaneRepair, cascadeBudgetTrace, createCascadeBudgetState, deepAlternativeCountForQuestion, promptContextBudgetForQuestion, proposalToolBudgetForQuestion, recordLaneRepair, } from './cascade/budgets.js';
60
+ import { QUICK_PROMPT_CONTEXT_BUDGET, canUseLaneRepair, cascadeBudgetTrace, createCascadeBudgetState, promptContextBudgetForQuestion, proposalToolBudgetForQuestion, recordLaneRepair, } from './cascade/budgets.js';
58
61
  function semanticCompilerFailure(error) {
59
62
  const record = error && typeof error === 'object' ? error : undefined;
60
63
  const message = error instanceof Error ? error.message : String(error);
@@ -675,6 +678,141 @@ export function materializeKnowledgeLensSkills(contextPack, available) {
675
678
  };
676
679
  });
677
680
  }
681
+ /**
682
+ * Older direct AnswerLoop callers supplied the semantic catalog but no frozen
683
+ * route contract. Migrate that one bounded retrieval result exactly once at
684
+ * ingress: the chosen KG identity and its normalized capability are copied into
685
+ * an authoritative RAP before compiler/proof code runs. No downstream stage is
686
+ * permitted to repeat this name/family selection.
687
+ */
688
+ async function freezeLegacySemanticSelection(input) {
689
+ const metricNodes = input.kg.getNodesByKind('metric', 100_000);
690
+ if (metricNodes.length === 0)
691
+ return undefined;
692
+ const questionPlan = input.contextPack?.questionPlan?.requestedShape
693
+ ? input.contextPack.questionPlan
694
+ : buildAnalysisQuestionPlan(input.question, input.followUp);
695
+ const semanticLayer = input.semanticLayer;
696
+ const match = await matchSemanticMetric(input.question, metricNodes, {
697
+ measureTerms: [...questionPlan.requestedShape.measures, ...questionPlan.metricTerms],
698
+ ...(semanticLayer ? { canExecute: (name) => semanticLayer.canComposeMetric(name) } : {}),
699
+ }).catch(() => null);
700
+ if (!match)
701
+ return undefined;
702
+ const normalizedCapability = normalizeMetricCapabilityContract(match.metric.payload?.analyticalCapability);
703
+ if (!normalizedCapability)
704
+ return undefined;
705
+ const metricDefinition = semanticLayer?.listMetrics().find((metric) => metric.name === match.metric.name
706
+ || metric.name.endsWith(`.${match.metric.name}`)
707
+ || match.metric.name.endsWith(`.${metric.name}`));
708
+ const composed = semanticLayer ? composeSemanticQueryForQuestion({
709
+ semanticLayer,
710
+ question: input.question,
711
+ questionPlan,
712
+ matchedMetric: match.metric,
713
+ filterValueColumns: (value) => resolveFilterValueColumns(value, input.schemaContext ?? []),
714
+ filterValueBindings: (value) => resolveAgentFilterValueBindings(value, input.schemaContext ?? []),
715
+ ...(input.semanticDriver ? { driver: input.semanticDriver } : {}),
716
+ ...(input.semanticTableMapping ? { tableMapping: input.semanticTableMapping } : {}),
717
+ }) : undefined;
718
+ // A semantic runtime may still resolve the shape later (Research/member-tool
719
+ // lane). Do not freeze a partial tuple here.
720
+ if (semanticLayer && !composed)
721
+ return undefined;
722
+ const capability = {
723
+ ...normalizedCapability,
724
+ dimensions: normalizedCapability.dimensions.map((dimension) => {
725
+ const definition = semanticLayer?.resolveGroupBy(dimension.dimensionId);
726
+ const compilerSelected = composed?.dimensions.includes(dimension.dimensionId)
727
+ || composed?.timeDimension?.name === dimension.dimensionId;
728
+ return definition
729
+ ? {
730
+ ...dimension,
731
+ nativeGroupingReference: definition.qualifiedName ?? definition.name,
732
+ ...(compilerSelected && questionPlan.requestedShape.rankingDirection
733
+ ? { supportedRoles: [...new Set([...dimension.supportedRoles, 'rank_entity'])] }
734
+ : {}),
735
+ }
736
+ : dimension;
737
+ }),
738
+ };
739
+ const identity = match.metric.nodeId;
740
+ const selectedMetricNames = composed?.metrics ?? [match.metric.name];
741
+ const selectedNodes = selectedMetricNames.flatMap((name) => {
742
+ const matches = metricNodes.filter((node) => node.name === name || node.name.endsWith(`.${name}`) || name.endsWith(`.${node.name}`));
743
+ return matches.length === 1 ? matches : [];
744
+ });
745
+ if (selectedNodes.length !== selectedMetricNames.length)
746
+ return undefined;
747
+ const candidates = selectedNodes.flatMap((node) => {
748
+ const nodeCapability = normalizeMetricCapabilityContract(node.payload?.analyticalCapability);
749
+ if (!nodeCapability)
750
+ return [];
751
+ const isPrimary = node.nodeId === match.metric.nodeId;
752
+ return [{
753
+ id: node.nodeId,
754
+ kind: 'semantic_metric',
755
+ semanticObjectType: 'metric',
756
+ trustTier: 'semantic',
757
+ name: node.name,
758
+ aliases: node.tags,
759
+ definition: node.description,
760
+ domain: node.domain,
761
+ primaryEntity: nodeCapability.primaryEntityId,
762
+ dimensions: nodeCapability.dimensions.map((dimension) => dimension.dimensionId),
763
+ sourceObjects: nodeCapability.semanticModelId ? [nodeCapability.semanticModelId] : [],
764
+ relevanceScore: isPrimary ? match.score : 1,
765
+ matchReasons: [`bounded_${isPrimary ? match.basis ?? 'semantic' : 'compiler'}_retrieval`],
766
+ compatibility: 'compatible',
767
+ compatibilityFacts: ['The selected KG identity carries the exact normalized semantic capability embedded in this plan.'],
768
+ analyticalCapability: isPrimary ? capability : nodeCapability,
769
+ eligible: true,
770
+ exactMatch: true,
771
+ }];
772
+ });
773
+ if (candidates.length !== selectedNodes.length)
774
+ return undefined;
775
+ return buildResolvedAnalyticalPlan({
776
+ question: input.question,
777
+ resolution: {
778
+ interpretedQuestion: input.question,
779
+ questionType: questionTypeFromText(input.question),
780
+ selectedConceptIds: candidates.map((item) => item.id),
781
+ recommendedExecutionId: identity,
782
+ queryIntent: {
783
+ measures: composed?.metrics ?? [match.metric.name],
784
+ dimensions: composed ? [
785
+ ...composed.dimensions,
786
+ ...(composed.timeDimension ? [composed.timeDimension.name] : []),
787
+ ] : [
788
+ ...questionPlan.requestedShape.dimensions,
789
+ ...(questionPlan.timeTerms.length > 0 && (metricDefinition?.aggTimeDimension ?? capability.timeDimensions[0]?.dimensionId)
790
+ ? [metricDefinition?.aggTimeDimension ?? capability.timeDimensions[0].dimensionId]
791
+ : []),
792
+ ],
793
+ filters: composed?.filters.map((filter) => ({ field: filter.dimension, value: String(filter.values[0] ?? '') })) ?? [],
794
+ ...(composed?.timeDimension?.granularity
795
+ ? { timeGrain: composed.timeDimension.granularity }
796
+ : questionPlan.timeTerms.some((term) => /month/i.test(term)) ? { timeGrain: 'month' } : {}),
797
+ ...(questionPlan.requestedShape.rankingDirection
798
+ ? { order: questionPlan.requestedShape.rankingDirection === 'top' ? 'desc' : 'asc' }
799
+ : {}),
800
+ ...(questionPlan.requestedShape.topN ? { limit: questionPlan.requestedShape.topN.n } : {}),
801
+ },
802
+ rejectedCandidates: [],
803
+ confidence: match.score >= 0.7 ? 'high' : 'medium',
804
+ missingInformation: [],
805
+ recommendedRoute: 'semantic',
806
+ },
807
+ evidence: {
808
+ snapshotId: input.contextPack?.knowledgeLens?.snapshotId ?? input.contextPack?.id ?? 'legacy-answer-loop-snapshot',
809
+ sourceFingerprint: capability.sourceFingerprint,
810
+ candidates,
811
+ },
812
+ candidates,
813
+ mode: 'authoritative',
814
+ });
815
+ }
678
816
  export async function answer(input) {
679
817
  const inheritedPlan = !input.resolvedAnalyticalPlan
680
818
  && input.followUp?.priorResolvedAnalyticalPlan
@@ -694,6 +832,11 @@ export async function answer(input) {
694
832
  resolvedAnalyticalPlan: normalizedInput.resolvedAnalyticalPlan,
695
833
  };
696
834
  }
835
+ if (!normalizedInput.resolvedAnalyticalPlan) {
836
+ const migratedPlan = await freezeLegacySemanticSelection(normalizedInput);
837
+ if (migratedPlan)
838
+ normalizedInput = { ...normalizedInput, resolvedAnalyticalPlan: migratedPlan };
839
+ }
697
840
  const executionRegistry = buildPlanExecutionRegistry({
698
841
  nodes: [
699
842
  ...normalizedInput.kg.getNodesByKind('block', 100_000),
@@ -731,7 +874,7 @@ export async function answer(input) {
731
874
  request: freshnessRequest,
732
875
  registry: executionRegistry,
733
876
  semanticLayer: normalizedInput.semanticLayer,
734
- expectedSnapshotId: normalizedInput.contextPack?.knowledgeLens.snapshotId,
877
+ expectedSnapshotId: normalizedInput.contextPack?.knowledgeLens?.snapshotId,
735
878
  })
736
879
  : undefined;
737
880
  if (adapterBinding?.status === 'blocked') {
@@ -788,7 +931,7 @@ export async function answer(input) {
788
931
  plan: normalizedInput.resolvedAnalyticalPlan,
789
932
  registry: executionRegistry,
790
933
  semanticLayer: normalizedInput.semanticLayer,
791
- expectedSnapshotId: normalizedInput.contextPack?.knowledgeLens.snapshotId,
934
+ expectedSnapshotId: normalizedInput.contextPack?.knowledgeLens?.snapshotId,
792
935
  })
793
936
  : undefined;
794
937
  const analyticalCapability = normalizedInput.resolvedAnalyticalPlan
@@ -801,9 +944,11 @@ export async function answer(input) {
801
944
  ? analyticalCapability.executionCapabilities.find((candidate) => candidate.route === analyticalRoute)?.adapterId
802
945
  : undefined;
803
946
  const multiMetricPlan = (normalizedInput.resolvedAnalyticalPlan?.analyticalFrame?.metricConceptIds.length ?? 0) > 1;
947
+ const generatedProposalPlan = normalizedInput.resolvedAnalyticalPlan?.capability === 'bounded_exploration';
804
948
  const analyticalGraphBuild = normalizedInput.resolvedAnalyticalPlan?.schemaVersion === 2
805
949
  && normalizedInput.resolvedAnalyticalPlan.analyticalFrame
806
950
  && !multiMetricPlan
951
+ && !generatedProposalPlan
807
952
  && !normalizedInput.analyticalPeriodResolutionFailure
808
953
  && analyticalCapability
809
954
  && analyticalRoute
@@ -819,6 +964,7 @@ export async function answer(input) {
819
964
  : undefined;
820
965
  const analyticalExecutionGraphFailure = normalizedInput.resolvedAnalyticalPlan?.schemaVersion === 2
821
966
  && !multiMetricPlan
967
+ && !generatedProposalPlan
822
968
  ? analyticalGraphBuild?.status === 'blocked'
823
969
  ? {
824
970
  code: analyticalGraphBuild.code,
@@ -839,7 +985,7 @@ export async function answer(input) {
839
985
  plan: normalizedInput.resolvedAnalyticalPlan,
840
986
  registry: executionRegistry,
841
987
  semanticLayer: normalizedInput.semanticLayer,
842
- expectedSnapshotId: normalizedInput.contextPack?.knowledgeLens.snapshotId,
988
+ expectedSnapshotId: normalizedInput.contextPack?.knowledgeLens?.snapshotId,
843
989
  })
844
990
  : undefined;
845
991
  const planBoundInput = resolvedPlanExecutionBinding || analyticalExecutionGraph || analyticalExecutionGraphFailure
@@ -1017,17 +1163,27 @@ function deriveAiRoute(result, metricMatch) {
1017
1163
  };
1018
1164
  }
1019
1165
  // Uncertified: a governed metric matched → semantic_metric; else generated preview.
1020
- if (result.sourceTier === 'semantic_layer' && metricMatch) {
1166
+ if (result.sourceTier === 'semantic_layer' && (metricMatch || result.analyticalExecutionGraph?.route === 'semantic')) {
1167
+ const metricName = metricMatch?.metric.name
1168
+ ?? result.dqlArtifact?.metrics?.join(', ')
1169
+ ?? result.analyticalExecutionGraph?.metricId;
1021
1170
  return {
1022
1171
  tier: 'semantic_metric',
1023
- label: `Answered from metric ${metricMatch.metric.name}`,
1024
- ref: metricMatch.metric.name,
1172
+ label: metricName ? `Answered from metric ${metricName}` : 'Answered from the snapshot-bound semantic plan',
1173
+ ...(metricName ? { ref: metricName } : {}),
1025
1174
  };
1026
1175
  }
1027
1176
  return result.dqlArtifact
1028
1177
  ? { tier: 'generated_sql', label: 'Prepared review-required DQL artifact with SQL preview.' }
1029
1178
  : { tier: 'generated_sql', label: 'Prepared review-required SQL preview.' };
1030
1179
  }
1180
+ /** "customers_customer_name" → "customers" for a question the user has to read. */
1181
+ function humanizeDeicticDimension(dimension) {
1182
+ const leaf = dimension.split(/[.:]/).pop() ?? dimension;
1183
+ const words = leaf.replace(/[_-]+/g, ' ').trim().toLowerCase();
1184
+ const last = words.split(' ').filter(Boolean).pop() ?? words;
1185
+ return last.endsWith('s') ? last : `${last}s`;
1186
+ }
1031
1187
  async function runAnswerLoop(input) {
1032
1188
  const { question, userId, domain, provider, kg, skills = [], blockHints = [] } = input;
1033
1189
  // AGT-004: with no explicit domain selection, let direct question evidence
@@ -1198,6 +1354,7 @@ async function runAnswerLoop(input) {
1198
1354
  input,
1199
1355
  binding: input.semanticGraphExecutionBinding,
1200
1356
  graph: input.analyticalExecutionGraph,
1357
+ capability: normalizeMetricCapabilityContract(input.semanticGraphExecutionBinding.metricNode.payload?.analyticalCapability),
1201
1358
  considered,
1202
1359
  providerName: provider.name,
1203
1360
  });
@@ -1313,7 +1470,22 @@ async function runAnswerLoop(input) {
1313
1470
  };
1314
1471
  }
1315
1472
  if (authoritativePlanBinding?.status === 'blocked') {
1316
- const text = `The resolved analytical plan cannot execute safely: ${authoritativePlanBinding.reason}`;
1473
+ // An ambiguous singular reference is answerable the moment the user says
1474
+ // which one they meant, so offer the candidates rather than dead-ending.
1475
+ // The old refusal shipped the binder's internal sentence as the answer
1476
+ // text, which reached the user as a generic "could not be completed" card.
1477
+ const choices = input.followUp?.deicticChoices;
1478
+ const clarificationOptions = choices
1479
+ ? choices.values.slice(0, 8).map((value) => ({
1480
+ id: `member:${choices.dimension}:${value}`,
1481
+ label: value,
1482
+ kind: 'member',
1483
+ question: `${question} (${value})`,
1484
+ }))
1485
+ : undefined;
1486
+ const text = clarificationOptions?.length
1487
+ ? `The previous answer listed ${choices.values.length} ${humanizeDeicticDimension(choices.dimension)}. Which one did you mean?`
1488
+ : `The resolved analytical plan cannot execute safely: ${authoritativePlanBinding.reason}`;
1317
1489
  return {
1318
1490
  kind: 'no_answer',
1319
1491
  sourceTier: 'no_answer',
@@ -1322,8 +1494,12 @@ async function runAnswerLoop(input) {
1322
1494
  confidence: 0,
1323
1495
  text,
1324
1496
  answer: text,
1325
- refusalCode: authoritativePlanBinding.code === 'PLAN_BLOCKED' ? 'ambiguous' : 'grounding_gap',
1497
+ // Offering real choices makes this a clarify turn, not a terminal block.
1498
+ refusalCode: clarificationOptions?.length || authoritativePlanBinding.code === 'PLAN_BLOCKED'
1499
+ ? 'ambiguous'
1500
+ : 'grounding_gap',
1326
1501
  refusalDetails: { code: authoritativePlanBinding.code, message: authoritativePlanBinding.reason },
1502
+ ...(clarificationOptions?.length ? { clarificationOptions } : {}),
1327
1503
  citations: contextPackCitations(input.contextPack, 8),
1328
1504
  considered,
1329
1505
  contextPack: input.contextPack,
@@ -1795,6 +1971,7 @@ async function runAnswerLoop(input) {
1795
1971
  let semanticBridgeAnswer;
1796
1972
  let semanticRuntimeFailure;
1797
1973
  let semanticExecutionTrace;
1974
+ let aggregationCompilerReceipt;
1798
1975
  let semanticAttemptedArtifact;
1799
1976
  let semanticRuntimeCompiledAnswer = false;
1800
1977
  if (authoritativeSemanticBinding && input.semanticLayer) {
@@ -1902,7 +2079,7 @@ async function runAnswerLoop(input) {
1902
2079
  // AGT-005: a governed runtime failure is terminal for this selected metric. Do not
1903
2080
  // spend another model call re-selecting the same member or silently fall
1904
2081
  // into exploratory SQL; surface the compiler's actionable error instead.
1905
- if (!semanticBridgeAnswer && !semanticRuntimeFailure) {
2082
+ if (!semanticBridgeAnswer && !semanticRuntimeFailure && input.allowProviderSemanticMemberSelection === true) {
1906
2083
  const requestedGroupingDims = requestedGroupingDimensions(questionPlan);
1907
2084
  const wantedBreakdown = questionPlan.requestedShape.dimensions.length > 0
1908
2085
  || questionPlan.dimensionTerms.length > 0;
@@ -2219,6 +2396,7 @@ async function runAnswerLoop(input) {
2219
2396
  reasoningEffort: input.reasoningEffort,
2220
2397
  analysisDepth: input.analysisDepth,
2221
2398
  toolCalls: proposalToolCalls,
2399
+ providerPayloadGuard: input.providerPayloadGuard,
2222
2400
  });
2223
2401
  }
2224
2402
  catch (err) {
@@ -2230,7 +2408,19 @@ async function runAnswerLoop(input) {
2230
2408
  throw input.signal.reason ?? err;
2231
2409
  if (err instanceof Error && (err.name === 'AbortError' || err.name === 'TimeoutError'))
2232
2410
  throw err;
2233
- const text = `Provider error: ${err.message}`;
2411
+ // DQL's own orchestration budget refusing a dispatch is NOT an upstream
2412
+ // outage. Reporting it as one ("Claude subscription failed: the 25-second
2413
+ // soft target elapsed") blames the user's AI provider for DQL's timer and
2414
+ // sends them to debug the wrong system.
2415
+ const budgetCode = err && typeof err === 'object' && 'code' in err
2416
+ ? String(err.code)
2417
+ : '';
2418
+ const orchestrationBudget = budgetCode === 'RUN_SOFT_TARGET_EXCEEDED'
2419
+ || budgetCode === 'PROVIDER_DISPATCH_BUDGET_EXHAUSTED'
2420
+ || budgetCode === 'RUN_DEADLINE_INSUFFICIENT';
2421
+ const text = orchestrationBudget
2422
+ ? 'DQL stopped this run at its own time budget before the query could be settled. The AI provider did not fail. Retry, or use Research for a longer budget.'
2423
+ : `Provider error: ${err.message}`;
2234
2424
  return {
2235
2425
  kind: 'no_answer',
2236
2426
  sourceTier: 'no_answer',
@@ -2240,7 +2430,10 @@ async function runAnswerLoop(input) {
2240
2430
  text,
2241
2431
  answer: text,
2242
2432
  refusalCode: 'provider_error',
2243
- refusalDetails: { code: 'provider_error', message: text },
2433
+ refusalDetails: {
2434
+ code: orchestrationBudget ? 'orchestration_budget_exhausted' : 'provider_error',
2435
+ message: orchestrationBudget ? `${text} (${err.message})` : text,
2436
+ },
2244
2437
  citations: [],
2245
2438
  memoryContext: input.memoryContext,
2246
2439
  evidence: buildNoAnswerEvidence({
@@ -2307,7 +2500,10 @@ async function runAnswerLoop(input) {
2307
2500
  executeGeneratedSql: input.executeGeneratedSql,
2308
2501
  signal: input.signal,
2309
2502
  reasoningEffort: input.reasoningEffort,
2310
- maxAlternatives: deepAlternativeCountForQuestion(questionPlan, intent),
2503
+ // One initial generation plus one diverse candidate stays within the
2504
+ // ordinary two-round ceiling. Candidates are validated without preview
2505
+ // execution below; only the selected SQL reaches the gateway once.
2506
+ maxAlternatives: 1,
2311
2507
  });
2312
2508
  if (selection.selected) {
2313
2509
  proposed = selection.selected.raw;
@@ -2368,13 +2564,16 @@ async function runAnswerLoop(input) {
2368
2564
  proposed = await generateProposalWithOptionalTools({
2369
2565
  provider,
2370
2566
  messages: [...messages, { role: 'system', content: FORCE_JOIN_INSTRUCTION }],
2371
- tools: stageBTools,
2567
+ // One tool round per ordinary Ask. The second generation is a bounded
2568
+ // composition correction over already-retrieved evidence.
2569
+ tools: [],
2372
2570
  questionPlan,
2373
2571
  intent,
2374
2572
  signal: input.signal,
2375
2573
  reasoningEffort: input.reasoningEffort,
2376
2574
  analysisDepth: input.analysisDepth,
2377
2575
  toolCalls: proposalToolCalls,
2576
+ providerPayloadGuard: input.providerPayloadGuard,
2378
2577
  });
2379
2578
  parsed = parseProposal(proposed);
2380
2579
  }
@@ -2446,6 +2645,48 @@ async function runAnswerLoop(input) {
2446
2645
  };
2447
2646
  }
2448
2647
  if (parsed.sql) {
2648
+ const frozenPlan = input.resolvedAnalyticalPlan;
2649
+ if (!governedMetricAnswer && frozenPlan?.mode === 'authoritative' && frozenPlan.schemaVersion === 2) {
2650
+ const targetFingerprint = input.generatedProposalTargetFingerprint ?? '';
2651
+ const validation = validateGeneratedAnalyticalProposal({
2652
+ plan: frozenPlan,
2653
+ expectedTargetFingerprint: targetFingerprint,
2654
+ contextPack: input.contextPack,
2655
+ dialect: input.semanticDriver,
2656
+ proposal: {
2657
+ version: 1,
2658
+ planId: frozenPlan.planId,
2659
+ planFingerprint: frozenPlan.fingerprint,
2660
+ snapshotId: frozenPlan.snapshotId,
2661
+ executionId: frozenPlan.executionId ?? '',
2662
+ capabilityFingerprint: frozenPlan.selectedCapabilityFingerprint ?? '',
2663
+ targetFingerprint,
2664
+ sql: parsed.sql,
2665
+ },
2666
+ });
2667
+ if (!validation.ok) {
2668
+ return {
2669
+ kind: 'no_answer',
2670
+ sourceTier: 'no_answer',
2671
+ certification: 'analyst_review_required',
2672
+ reviewStatus: 'none',
2673
+ confidence: 0,
2674
+ text: GENERATED_ANALYTICAL_TUPLE_DRIFT_MESSAGE,
2675
+ answer: GENERATED_ANALYTICAL_TUPLE_DRIFT_MESSAGE,
2676
+ refusalCode: 'policy_blocked',
2677
+ refusalDetails: {
2678
+ code: validation.code,
2679
+ message: `${validation.message}: ${validation.driftCodes.join(', ')}`,
2680
+ },
2681
+ validationWarnings: validation.driftCodes,
2682
+ citations: contextPackCitations(input.contextPack, 8),
2683
+ memoryContext: input.memoryContext,
2684
+ contextPack: input.contextPack,
2685
+ considered,
2686
+ providerUsed: provider.name,
2687
+ };
2688
+ }
2689
+ }
2449
2690
  const tightenedFlow = tightenSourceTargetFlowProjection(parsed.sql, question, questionPlan);
2450
2691
  if (tightenedFlow && tightenedFlow.sql !== parsed.sql) {
2451
2692
  parsed.sql = tightenedFlow.sql;
@@ -2479,8 +2720,52 @@ async function runAnswerLoop(input) {
2479
2720
  // semantic environment. A thin local retrieval pack cannot safely reject it
2480
2721
  // and replace it with a guessed leaf-measure SQL. Warehouse execution remains
2481
2722
  // the final binder/runtime check.
2723
+ const toolOwnedMetricNames = semanticBridgeAnswer?.metrics
2724
+ ?? compiledSemanticRecords.flatMap((record) => record.metrics);
2725
+ const semanticProofAuthority = input.resolvedAnalyticalPlan
2726
+ ? exactSemanticProofAuthority(input.resolvedAnalyticalPlan, semanticMetricNodes)
2727
+ : (input.allowProviderSemanticMemberSelection === true || compiledSemanticRecords.length > 0)
2728
+ ? exactToolSemanticProofAuthority(toolOwnedMetricNames, semanticMetricNodes)
2729
+ : { metricObjects: [] };
2730
+ const semanticProofContext = semanticRouteAggregationProofContext(input.contextPack, input.resolvedAnalyticalPlan, semanticProofAuthority.capability, semanticProofAuthority.metricObjects);
2731
+ const toolOwnedSemanticProofContext = !input.resolvedAnalyticalPlan
2732
+ && semanticProofAuthority.capability
2733
+ && (input.allowProviderSemanticMemberSelection === true || compiledSemanticRecords.length > 0)
2734
+ ? {
2735
+ ...(semanticProofContext ?? {}),
2736
+ questionPlan: {
2737
+ ...(semanticProofContext?.questionPlan ?? buildAnalysisQuestionPlan(question)),
2738
+ requestedShape: {
2739
+ ...(semanticProofContext?.questionPlan?.requestedShape ?? buildAnalysisQuestionPlan(question).requestedShape),
2740
+ grain: semanticBridgeAnswer?.dimensions[0] ?? semanticProofAuthority.capability.defaultResultGrainId,
2741
+ dimensions: semanticBridgeAnswer?.dimensions ?? [],
2742
+ },
2743
+ },
2744
+ }
2745
+ : semanticProofContext;
2746
+ const semanticAggregationProof = governedMetricAnswer && !certifiedAdaptation
2747
+ ? buildAggregationSafetyProof(parsed.sql, toolOwnedSemanticProofContext, input.semanticDriver, input.resolvedAnalyticalPlan?.fingerprint)
2748
+ : undefined;
2749
+ const semanticExecutionRequested = governedMetricAnswer
2750
+ && !certifiedAdaptation
2751
+ && Boolean(input.executeGeneratedSql || input.executeDqlArtifact);
2752
+ const semanticAggregationAuthorityBlocked = semanticExecutionRequested
2753
+ && semanticAggregationProof?.status !== 'safe';
2482
2754
  const initialValidation = semanticRuntimeCompiledAnswer
2483
- ? { ok: true, warnings: ['SQL compiled by the configured semantic runtime.'] }
2755
+ ? semanticAggregationProof?.status === 'safe' || !semanticExecutionRequested
2756
+ ? {
2757
+ ok: true,
2758
+ warnings: ['SQL compiled by the configured semantic runtime.'],
2759
+ aggregationSafetyProof: semanticAggregationProof,
2760
+ }
2761
+ : {
2762
+ ok: false,
2763
+ code: 'unsafe_aggregation',
2764
+ error: semanticAggregationSafetyFailure(semanticAggregationProof),
2765
+ warnings: ['Semantic runtime compilation did not establish complete aggregation authority.'],
2766
+ offending: undefined,
2767
+ aggregationSafetyProof: semanticAggregationProof,
2768
+ }
2484
2769
  : contextLedger.validateSql(parsed.sql, {
2485
2770
  question,
2486
2771
  intent,
@@ -2489,6 +2774,9 @@ async function runAnswerLoop(input) {
2489
2774
  memberBindings: input.followUp?.memberBindings,
2490
2775
  enforceAggregationIntegrity: !governedMetricAnswer,
2491
2776
  });
2777
+ let aggregationSafetyProof = 'aggregationSafetyProof' in initialValidation
2778
+ ? initialValidation.aggregationSafetyProof
2779
+ : undefined;
2492
2780
  const rankedGrainGap = missingRankedGrainOutput(questionPlan, parsed.sql, semanticBridgeAnswer?.dimensions);
2493
2781
  contextValidation = initialValidation.ok && rankedGrainGap
2494
2782
  ? {
@@ -2498,14 +2786,37 @@ async function runAnswerLoop(input) {
2498
2786
  warnings: initialValidation.warnings,
2499
2787
  }
2500
2788
  : initialValidation.ok
2501
- ? { ok: true, warnings: initialValidation.warnings }
2789
+ ? { ok: true, warnings: initialValidation.warnings, aggregationSafetyProof }
2502
2790
  : {
2503
2791
  ok: false,
2504
2792
  code: initialValidation.code,
2505
2793
  error: initialValidation.error,
2506
2794
  warnings: initialValidation.warnings,
2507
2795
  offending: initialValidation.offending,
2796
+ aggregationSafetyProof,
2508
2797
  };
2798
+ // The route-specific semantic proof is authoritative for execution. Context
2799
+ // validation can prove SQL names without proving that the selected metric's
2800
+ // exact capability, grain, additivity and joins own this aggregate. Never let
2801
+ // a broad context pack or warehouse success elevate that blocked state.
2802
+ if (contextValidation.ok
2803
+ && semanticAggregationAuthorityBlocked) {
2804
+ aggregationSafetyProof = semanticAggregationProof;
2805
+ contextValidation = {
2806
+ ok: false,
2807
+ code: 'unsafe_aggregation',
2808
+ error: semanticAggregationSafetyFailure(semanticAggregationProof),
2809
+ warnings: [
2810
+ ...contextValidation.warnings,
2811
+ 'The selected semantic route lacks an exact positive aggregation proof.',
2812
+ ],
2813
+ aggregationSafetyProof: semanticAggregationProof,
2814
+ };
2815
+ }
2816
+ else if (semanticAggregationProof) {
2817
+ aggregationSafetyProof = semanticAggregationProof;
2818
+ contextValidation = { ...contextValidation, aggregationSafetyProof: semanticAggregationProof };
2819
+ }
2509
2820
  // A canonical member value that resolves to exactly one inspected column is
2510
2821
  // compiler-owned request state, not creative SQL. If the proposal omitted
2511
2822
  // that predicate entirely, inject it deterministically before spending the
@@ -2529,6 +2840,7 @@ async function runAnswerLoop(input) {
2529
2840
  const reboundGrainGap = rebound.ok
2530
2841
  ? missingRankedGrainOutput(questionPlan, parsed.sql, semanticBridgeAnswer?.dimensions)
2531
2842
  : undefined;
2843
+ aggregationSafetyProof = rebound.aggregationSafetyProof;
2532
2844
  contextValidation = rebound.ok && !reboundGrainGap
2533
2845
  ? { ok: true, warnings: [...rebound.warnings, ...injected.notes] }
2534
2846
  : rebound.ok
@@ -2676,7 +2988,20 @@ async function runAnswerLoop(input) {
2676
2988
  // provider get the same single chance; any provider failure keeps the honest
2677
2989
  // refusal below. This mirrors the engine-level repair loop, applied to the
2678
2990
  // context-validation gate that previously refused on first failure.
2679
- if (!contextValidation.ok && !governedMetricAnswer && canUseLaneRepair(repairBudgetState, 'validation')) {
2991
+ // Semantic aggregation authority cannot be created by a provider rewrite. In
2992
+ // particular, clearing governedMetricAnswer after a blocked semantic proof
2993
+ // must not turn that refusal into the generic SQL-repair lane: the provider
2994
+ // could return grounded raw-row SQL and silently downgrade the attempt to an
2995
+ // uncertified execution. Keep this semantic proof failure terminal while
2996
+ // retaining only the separate typed warehouse SQL-only repair capability.
2997
+ // Unsafe aggregation/rounding is never rewritten automatically until the SQL
2998
+ // parser supplies exact nested-expression AST spans.
2999
+ if (!contextValidation.ok
3000
+ && contextValidation.code !== 'unsafe_aggregation'
3001
+ && input.resolvedAnalyticalPlan?.mode !== 'authoritative'
3002
+ && !semanticAggregationAuthorityBlocked
3003
+ && !governedMetricAnswer
3004
+ && canUseLaneRepair(repairBudgetState, 'validation')) {
2680
3005
  recordLaneRepair(repairBudgetState, 'validation');
2681
3006
  try {
2682
3007
  const failedSql = parsed.sql ?? '';
@@ -2782,7 +3107,9 @@ async function runAnswerLoop(input) {
2782
3107
  answer: text,
2783
3108
  proposedSql: parsed.sql,
2784
3109
  sql: parsed.sql,
2785
- trustLabel: input.contextPack?.trustLabel,
3110
+ trustLabel: contextValidation.code === 'unsafe_aggregation'
3111
+ ? 'blocked'
3112
+ : input.contextPack?.trustLabel,
2786
3113
  sourceCertifiedBlock: followUpSourceBlock?.name ?? input.followUp?.sourceBlockName,
2787
3114
  contextPackId: input.contextPack?.id,
2788
3115
  validationWarnings: [
@@ -2808,6 +3135,7 @@ async function runAnswerLoop(input) {
2808
3135
  contextPack: input.contextPack,
2809
3136
  considered,
2810
3137
  providerUsed: provider.name,
3138
+ aggregationSafetyProof,
2811
3139
  };
2812
3140
  }
2813
3141
  // The dbt-first join guard adjudicates GENERATED SQL — joins an LLM invented,
@@ -3050,6 +3378,7 @@ async function runAnswerLoop(input) {
3050
3378
  memberBindings: input.followUp?.memberBindings,
3051
3379
  });
3052
3380
  if (repairedValidation.ok) {
3381
+ aggregationSafetyProof = repairedValidation.aggregationSafetyProof;
3053
3382
  repairAttempts += 1;
3054
3383
  // Adopt the corrected SQL, but do NOT let the repair prose become the answer.
3055
3384
  repairNarrative = repaired.text?.trim() || undefined;
@@ -3282,6 +3611,7 @@ async function runAnswerLoop(input) {
3282
3611
  contextPack: input.contextPack,
3283
3612
  considered,
3284
3613
  providerUsed: provider.name,
3614
+ aggregationSafetyProof,
3285
3615
  // Carry the governed metric match so the exit point can name a
3286
3616
  // `semantic_metric` route (spec 17, part C).
3287
3617
  _semanticMetricMatch: governedMetricAnswer ? semanticMetricMatch ?? undefined : undefined,
@@ -3322,6 +3652,23 @@ function resultColumnNames(result) {
3322
3652
  return columns?.length ? columns : undefined;
3323
3653
  }
3324
3654
  function analyticalCapabilityForPlan(plan, contextPack, registry = []) {
3655
+ const selected = normalizeMetricCapabilityContract(plan.selectedCapability);
3656
+ if (selected) {
3657
+ const selectedMetricId = plan.analyticalFrame?.metricConceptIds[0]
3658
+ ?? plan.query.measures[0]?.qualifiedId;
3659
+ const exactRegistryAuthority = registry.some((entry) => Boolean(plan.executionId)
3660
+ && entry.identities.includes(plan.executionId)
3661
+ && normalizeMetricCapabilityContract(entry.node.payload?.analyticalCapability)?.sourceFingerprint
3662
+ === selected.sourceFingerprint);
3663
+ if (plan.selectedCapabilityFingerprint !== selected.sourceFingerprint
3664
+ || (selectedMetricId && selected.metricId !== selectedMetricId && !exactRegistryAuthority))
3665
+ return undefined;
3666
+ return selected;
3667
+ }
3668
+ // New authoritative v2 plans always carry the exact normalized capability.
3669
+ // A missing authority is terminal rather than permission to rematch by name.
3670
+ if (plan.mode === 'authoritative' && plan.schemaVersion === 2)
3671
+ return undefined;
3325
3672
  const candidates = contextPack?.retrievalDiagnostics.meaningEvidence?.candidates ?? [];
3326
3673
  const exactExecution = candidates.filter((candidate) => candidate.objectKey === plan.executionId || candidate.qualifiedId === plan.executionId);
3327
3674
  const metricId = plan.analyticalFrame?.metricConceptIds[0];
@@ -3343,6 +3690,142 @@ function analyticalCapabilityForPlan(plan, contextPack, registry = []) {
3343
3690
  const byFingerprint = new Map(normalized.map((capability) => [capability.sourceFingerprint, capability]));
3344
3691
  return byFingerprint.size === 1 ? [...byFingerprint.values()][0] : undefined;
3345
3692
  }
3693
+ function semanticRouteAggregationProofContext(contextPack, plan, capability, exactMetricObjects = []) {
3694
+ if (!plan || !capability) {
3695
+ return exactMetricObjects.length > 0
3696
+ ? { ...(contextPack ?? {}), objects: exactMetricObjects }
3697
+ : contextPack;
3698
+ }
3699
+ const baseQuestionPlan = buildAnalysisQuestionPlan(plan.question);
3700
+ if ((plan.selectedCapabilityFingerprint !== undefined
3701
+ && plan.selectedCapabilityFingerprint !== capability.sourceFingerprint)
3702
+ || (plan.analyticalFrame !== undefined
3703
+ && plan.analyticalFrame.metricConceptIds[0] !== capability.metricId))
3704
+ return {
3705
+ ...(contextPack ?? {}),
3706
+ objects: [],
3707
+ questionPlan: {
3708
+ ...baseQuestionPlan,
3709
+ requestedShape: { ...baseQuestionPlan.requestedShape, dimensions: [] },
3710
+ },
3711
+ };
3712
+ const contextMetricObjects = (contextPack?.objects ?? []).filter((object) => {
3713
+ const objectCapability = normalizeMetricCapabilityContract(object.payload?.analyticalCapability);
3714
+ if (!objectCapability || objectCapability.sourceFingerprint !== capability.sourceFingerprint)
3715
+ return false;
3716
+ return [object.objectKey, object.fullName, object.name, object.payload?.qualifiedId]
3717
+ .filter((value) => Boolean(value))
3718
+ .includes(plan.executionId ?? capability.metricId);
3719
+ });
3720
+ const relationshipIds = new Set(plan.relationshipPathIds);
3721
+ const relationshipObjects = (contextPack?.objects ?? []).filter((object) => object.objectType.includes('relationship')
3722
+ && [object.objectKey, object.fullName, object.name, object.payload?.qualifiedId]
3723
+ .filter((value) => Boolean(value))
3724
+ .some((value) => relationshipIds.has(value)));
3725
+ const frozenDimensions = plan.analyticalFrame?.dimensions
3726
+ .filter((dimension) => dimension.role === 'group_by' || dimension.role === 'rank_entity')
3727
+ .map((dimension) => dimension.dimensionId);
3728
+ const legacyCapabilityDimensions = !plan.analyticalFrame && plan.query.dimensions.length > 0
3729
+ ? capability.dimensions.filter((dimension) => dimension.supportedRoles.includes('group_by'))
3730
+ : [];
3731
+ const dimensions = frozenDimensions
3732
+ ?? (legacyCapabilityDimensions.length === plan.query.dimensions.length
3733
+ ? legacyCapabilityDimensions.map((dimension) => dimension.dimensionId)
3734
+ : plan.query.dimensions.flatMap((dimension) => dimension.qualifiedId ? [dimension.qualifiedId] : []));
3735
+ const legacyGrains = legacyCapabilityDimensions.map((dimension) => dimension.entityId)
3736
+ .filter((entityId) => capability.resultGrainIds.includes(entityId));
3737
+ const grain = plan.analyticalFrame?.entityGrainIds[0]
3738
+ ?? plan.entityGrain
3739
+ ?? (legacyGrains.length === 1 ? legacyGrains[0] : undefined)
3740
+ ?? (dimensions.length === 0 ? capability.defaultResultGrainId : undefined);
3741
+ const exactObjects = new Map();
3742
+ for (const object of [...contextMetricObjects, ...exactMetricObjects, ...relationshipObjects]) {
3743
+ const authorityKey = `${object.objectKey}:${normalizeMetricCapabilityContract(object.payload?.analyticalCapability)?.sourceFingerprint ?? ''}`;
3744
+ if (!exactObjects.has(authorityKey))
3745
+ exactObjects.set(authorityKey, object);
3746
+ }
3747
+ return {
3748
+ ...(contextPack ?? {}),
3749
+ objects: [...exactObjects.values()],
3750
+ questionPlan: {
3751
+ ...baseQuestionPlan,
3752
+ requestedShape: {
3753
+ ...baseQuestionPlan.requestedShape,
3754
+ dimensions,
3755
+ ...(grain ? { grain } : {}),
3756
+ },
3757
+ },
3758
+ };
3759
+ }
3760
+ function exactSemanticProofAuthority(plan, metricNodes) {
3761
+ if (!plan)
3762
+ return { metricObjects: [] };
3763
+ const embedded = normalizeMetricCapabilityContract(plan.selectedCapability);
3764
+ const planMetricIds = new Set([
3765
+ plan.executionId,
3766
+ ...plan.query.measures.flatMap((binding) => binding.qualifiedId ? [binding.qualifiedId] : []),
3767
+ ].filter((value) => Boolean(value)));
3768
+ const exactNodes = metricNodes.filter((metric) => {
3769
+ const identities = [
3770
+ metric.nodeId,
3771
+ metric.payload?.qualifiedId,
3772
+ metric.payload?.localId,
3773
+ ].filter((value) => typeof value === 'string' && Boolean(value));
3774
+ return identities.some((identity) => planMetricIds.has(identity));
3775
+ });
3776
+ const capabilities = exactNodes.flatMap((metric) => {
3777
+ const capability = normalizeMetricCapabilityContract(metric.payload?.analyticalCapability);
3778
+ return capability ? [capability] : [];
3779
+ });
3780
+ const selected = embedded ?? (new Map(capabilities.map((capability) => [capability.sourceFingerprint, capability])).size === 1
3781
+ ? capabilities[0]
3782
+ : undefined);
3783
+ if (!selected || (plan.selectedCapabilityFingerprint && plan.selectedCapabilityFingerprint !== selected.sourceFingerprint)) {
3784
+ return { metricObjects: [] };
3785
+ }
3786
+ return {
3787
+ capability: selected,
3788
+ metricObjects: exactNodes.flatMap((metric) => metric.payload ? [{
3789
+ objectKey: metric.nodeId,
3790
+ objectType: 'semantic_metric',
3791
+ name: metric.name,
3792
+ ...(metric.domain ? { domain: metric.domain } : {}),
3793
+ ...(metric.status ? { status: metric.status } : {}),
3794
+ payload: metric.payload,
3795
+ }] : []),
3796
+ };
3797
+ }
3798
+ function exactToolSemanticProofAuthority(metricNames, metricNodes) {
3799
+ const exactNodes = [...new Set(metricNames)].flatMap((name) => {
3800
+ const matches = metricNodes.filter((node) => node.name === name || node.name.endsWith(`.${name}`) || name.endsWith(`.${node.name}`));
3801
+ return matches.length === 1 ? matches : [];
3802
+ });
3803
+ if (exactNodes.length !== new Set(metricNames).size)
3804
+ return { metricObjects: [] };
3805
+ const capabilities = exactNodes.flatMap((node) => {
3806
+ const capability = normalizeMetricCapabilityContract(node.payload?.analyticalCapability);
3807
+ return capability ? [capability] : [];
3808
+ });
3809
+ if (capabilities.length !== exactNodes.length)
3810
+ return { metricObjects: [] };
3811
+ return {
3812
+ capability: capabilities[0],
3813
+ metricObjects: exactNodes.map((metric) => ({
3814
+ objectKey: metric.nodeId,
3815
+ objectType: 'semantic_metric',
3816
+ name: metric.name,
3817
+ ...(metric.domain ? { domain: metric.domain } : {}),
3818
+ ...(metric.status ? { status: metric.status } : {}),
3819
+ payload: metric.payload,
3820
+ })),
3821
+ };
3822
+ }
3823
+ function semanticAggregationSafetyFailure(proof) {
3824
+ const issues = proof?.issueCodes.length
3825
+ ? proof.issueCodes.join(', ')
3826
+ : 'AGGREGATION_PROOF_MISSING';
3827
+ return `The selected semantic route did not establish an exact aggregation safety proof (${issues}). No SQL was executed.`;
3828
+ }
3346
3829
  function analyticalRouteForPlan(plan) {
3347
3830
  if (plan.capability === 'certified_execution' && plan.recommendedRoute === 'certified')
3348
3831
  return 'certified';
@@ -3440,6 +3923,8 @@ async function executeSemanticAnalyticalGraph(input) {
3440
3923
  const compiledSql = [];
3441
3924
  const artifacts = [];
3442
3925
  let semanticExecutionTrace;
3926
+ let aggregationCompilerReceipt;
3927
+ let aggregationSafetyProof;
3443
3928
  for (const invocation of input.binding.invocations) {
3444
3929
  const attemptedArtifact = semanticAttemptArtifact(layer, input.input.question, invocation.selection);
3445
3930
  let composed = composeSemanticQueryFromMembers({
@@ -3453,6 +3938,7 @@ async function executeSemanticAnalyticalGraph(input) {
3453
3938
  try {
3454
3939
  const compiled = await input.input.semanticQueryCompiler(invocation.selection);
3455
3940
  semanticExecutionTrace = compiled.trace;
3941
+ aggregationCompilerReceipt = compiled.aggregationCompilerReceipt;
3456
3942
  composed = composeSemanticQueryFromCompiledMembers({
3457
3943
  semanticLayer: layer,
3458
3944
  question: input.input.question,
@@ -3503,6 +3989,42 @@ async function executeSemanticAnalyticalGraph(input) {
3503
3989
  failedBindings: [{ role: 'source_invocation', reasonCode: 'SEMANTIC_COMPILE_FAILED' }],
3504
3990
  });
3505
3991
  }
3992
+ const compilerProof = semanticRatioCompilerProofBinding(layer, invocation.selection.metrics, input.capability, input.input.resolvedAnalyticalPlan, semanticTraceTargetFingerprint(semanticExecutionTrace), composed.sql, aggregationCompilerReceipt);
3993
+ const proof = buildSemanticCompilationAggregationSafetyProof({
3994
+ sql: composed.sql,
3995
+ capability: input.capability,
3996
+ semanticAuthority: input.input.resolvedAnalyticalPlan
3997
+ ? buildSemanticProofAuthorityV1({
3998
+ plan: input.input.resolvedAnalyticalPlan,
3999
+ capability: input.capability,
4000
+ contextPack: input.input.contextPack,
4001
+ compilerReceipt: aggregationCompilerReceipt,
4002
+ })
4003
+ : undefined,
4004
+ relationshipProofs: input.input.resolvedAnalyticalPlan?.relationshipProofs,
4005
+ executionId: input.input.resolvedAnalyticalPlan?.executionId,
4006
+ snapshotId: input.input.resolvedAnalyticalPlan?.snapshotId,
4007
+ capabilityFingerprint: input.input.resolvedAnalyticalPlan?.selectedCapabilityFingerprint,
4008
+ route: 'semantic',
4009
+ adapterId: input.graph.adapterId,
4010
+ ...compilerProof.binding,
4011
+ ...(compilerProof.issueCodes.length
4012
+ ? { compilerAuthorityIssueCodes: compilerProof.issueCodes }
4013
+ : {}),
4014
+ contextPack: semanticRouteAggregationProofContext(input.input.contextPack, input.input.resolvedAnalyticalPlan, input.capability),
4015
+ dialect: input.input.semanticDriver,
4016
+ planFingerprint: input.binding.planFingerprint,
4017
+ });
4018
+ aggregationSafetyProof = proof;
4019
+ if (proof.status !== 'safe') {
4020
+ return analyticalGraphFailureAnswer(input, 'EXECUTION_GRAPH_MISMATCH', semanticAggregationSafetyFailure(proof), {
4021
+ phase: 'validation',
4022
+ dqlArtifact: composed.dqlArtifact,
4023
+ compiledSql: composed.sql,
4024
+ semanticExecutionTrace,
4025
+ aggregationSafetyProof: proof,
4026
+ });
4027
+ }
3506
4028
  compiledSql.push(composed.sql);
3507
4029
  artifacts.push(composed.dqlArtifact);
3508
4030
  const executor = input.input.executeDqlArtifact
@@ -3562,6 +4084,7 @@ async function executeSemanticAnalyticalGraph(input) {
3562
4084
  sql,
3563
4085
  ...(artifacts.length === 1 ? { dqlArtifact: artifacts[0] } : {}),
3564
4086
  analyticalExecutionGraph: input.graph,
4087
+ aggregationSafetyProof,
3565
4088
  ...(semanticExecutionTrace
3566
4089
  ? { semanticExecutionTrace: semanticTraceAfterExecution(semanticExecutionTrace, { executed: false }) }
3567
4090
  : {}),
@@ -3606,6 +4129,7 @@ async function executeSemanticAnalyticalGraph(input) {
3606
4129
  ...(artifacts.length === 1 ? { dqlArtifact: artifacts[0] } : {}),
3607
4130
  result,
3608
4131
  analyticalExecutionGraph: input.graph,
4132
+ aggregationSafetyProof,
3609
4133
  analyticalExecutionReceipt: executed.receipt,
3610
4134
  analyticalFacts: story.factSet,
3611
4135
  analyticalNarrative: story.narrative,
@@ -3618,6 +4142,137 @@ async function executeSemanticAnalyticalGraph(input) {
3618
4142
  providerUsed: input.providerName,
3619
4143
  };
3620
4144
  }
4145
+ function semanticRatioCompilerProofBinding(layer, selectedMetricNames, capability, plan, targetFingerprint, compiledSql, receipt) {
4146
+ if (capability.aggregation.trim().toLowerCase() !== 'ratio')
4147
+ return { issueCodes: [] };
4148
+ if (receipt) {
4149
+ const outputAlias = capability.declaredOutputIds?.length === 1
4150
+ ? capability.declaredOutputIds[0]
4151
+ : capability.metricId.split(/[:.]/).at(-1);
4152
+ const { receiptFingerprint, ...receiptBody } = receipt;
4153
+ const compiledExpression = outputAlias
4154
+ ? buildSqlOutputExpressionSignature(compiledSql, outputAlias)
4155
+ : undefined;
4156
+ const dimensionIds = [...new Set(plan.analyticalFrame?.dimensions.map((dimension) => dimension.dimensionId) ?? [])].sort();
4157
+ const relationshipPathIds = [...plan.relationshipPathIds].sort();
4158
+ const relationshipProofFingerprints = [...new Set(plan.relationshipProofs?.map((proof) => proof.authorityFingerprint) ?? [])].sort();
4159
+ const expectedOrderBy = semanticCompilerReceiptOrder(plan, capability);
4160
+ if (receipt.version !== 1
4161
+ || receipt.executionId !== plan.executionId
4162
+ || receipt.capabilityFingerprint !== plan.selectedCapabilityFingerprint
4163
+ || receipt.metricId !== capability.metricId
4164
+ || receipt.operator !== 'ratio'
4165
+ || receipt.planFingerprint !== plan.fingerprint
4166
+ || receipt.snapshotId !== plan.snapshotId
4167
+ || !targetFingerprint
4168
+ || receipt.targetFingerprint !== targetFingerprint
4169
+ || receipt.compiledSqlFingerprint !== semanticExecutionFingerprint(compiledSql)
4170
+ || !compiledExpression
4171
+ || receipt.compiledExpressionFingerprint !== semanticExecutionFingerprint(compiledExpression.canonicalExpression)
4172
+ || receipt.orderedMeasureIds.join('|') !== capability.measureIds.join('|')
4173
+ || [...receipt.dimensionIds].sort().join('|') !== dimensionIds.join('|')
4174
+ || [...receipt.relationshipPathIds].sort().join('|') !== relationshipPathIds.join('|')
4175
+ || [...receipt.relationshipProofFingerprints].sort().join('|') !== relationshipProofFingerprints.join('|')
4176
+ || receipt.orderBy.length !== expectedOrderBy.length
4177
+ || receipt.orderBy.some((order, index) => {
4178
+ const expected = expectedOrderBy[index];
4179
+ return !expected
4180
+ || normalizeSemanticProofId(order.expression) !== normalizeSemanticProofId(expected.expression)
4181
+ || order.direction !== expected.direction;
4182
+ })
4183
+ || receipt.outputAlias !== outputAlias
4184
+ || receiptFingerprint !== semanticExecutionFingerprint(receiptBody)) {
4185
+ return { issueCodes: ['SEMANTIC_COMPILER_RECEIPT_AUTHORITY_MISMATCH'] };
4186
+ }
4187
+ return {
4188
+ binding: {
4189
+ compilerMetricExpressionSql: receipt.metricExpressionSql,
4190
+ compilerMetricId: receipt.metricId,
4191
+ compilerMeasureIds: receipt.orderedMeasureIds,
4192
+ compilerRelation: receipt.physicalRelation,
4193
+ compilerRelationAliases: receipt.relationAliases,
4194
+ },
4195
+ issueCodes: [],
4196
+ };
4197
+ }
4198
+ const selected = new Set(selectedMetricNames.map(normalizeSemanticProofId));
4199
+ const capabilityMetric = normalizeSemanticProofId(capability.metricId);
4200
+ const metrics = layer.listMetrics(undefined, { includeMeasures: false }).filter((metric) => {
4201
+ const names = [metric.name, metric.cube ? `${metric.cube}.${metric.name}` : undefined]
4202
+ .filter((value) => Boolean(value))
4203
+ .map(normalizeSemanticProofId);
4204
+ return names.includes(capabilityMetric)
4205
+ && names.some((name) => selected.has(name));
4206
+ });
4207
+ if (metrics.length !== 1 || !metrics[0].sql.trim())
4208
+ return { issueCodes: ['SEMANTIC_COMPILER_RECEIPT_AUTHORITY_MISSING'] };
4209
+ const metric = metrics[0];
4210
+ const measures = capability.measureIds.map((measureId) => {
4211
+ const normalizedId = normalizeSemanticProofId(measureId);
4212
+ const matches = layer.listMeasures().filter((measure) => {
4213
+ const names = [measure.name, measure.cube ? `${measure.cube}.${measure.name}` : undefined]
4214
+ .filter((value) => Boolean(value))
4215
+ .map(normalizeSemanticProofId);
4216
+ return names.includes(normalizedId)
4217
+ && (!metric.cube || !measure.cube || measure.cube === metric.cube);
4218
+ });
4219
+ return matches.length === 1 ? matches[0] : undefined;
4220
+ });
4221
+ if (measures.some((measure) => !measure))
4222
+ return { issueCodes: ['SEMANTIC_COMPILER_RECEIPT_AUTHORITY_MISSING'] };
4223
+ const relations = Array.from(new Set(measures.map((measure) => measure.table.trim()).filter(Boolean)));
4224
+ if (relations.length !== 1)
4225
+ return { issueCodes: ['SEMANTIC_COMPILER_RECEIPT_AUTHORITY_MISSING'] };
4226
+ return {
4227
+ binding: {
4228
+ compilerMetricExpressionSql: metric.sql,
4229
+ compilerMetricId: capability.metricId,
4230
+ compilerMeasureIds: [...capability.measureIds],
4231
+ compilerRelation: relations[0],
4232
+ ...(metric.cube ? { compilerRelationAliases: [metric.cube] } : {}),
4233
+ },
4234
+ issueCodes: [],
4235
+ };
4236
+ }
4237
+ function semanticCompilerReceiptOrder(plan, capability) {
4238
+ const ranking = plan.analyticalFrame?.ranking;
4239
+ if (!ranking || ranking.byMetricId !== capability.metricId)
4240
+ return [];
4241
+ const metricOutput = plan.analyticalFrame?.requestedOutputs.find((output) => output.kind === 'metric_value'
4242
+ && output.metricId === ranking.byMetricId
4243
+ && (!ranking.byPeriodId || output.periodId === ranking.byPeriodId));
4244
+ if (!metricOutput)
4245
+ return [];
4246
+ const order = [{ expression: metricOutput.id, direction: ranking.direction }];
4247
+ if (ranking.tiePolicy !== 'stable_secondary_key')
4248
+ return order;
4249
+ const dimensionOutputs = plan.analyticalFrame.requestedOutputs
4250
+ .filter((output) => output.kind === 'dimension');
4251
+ return [
4252
+ ...order,
4253
+ ...dimensionOutputs.map((output) => ({ expression: output.id, direction: 'asc' })),
4254
+ ];
4255
+ }
4256
+ function semanticTraceTargetFingerprint(trace) {
4257
+ const targetBinding = trace?.targetBinding;
4258
+ if (!targetBinding || typeof targetBinding !== 'object' || Array.isArray(targetBinding))
4259
+ return undefined;
4260
+ const executionTarget = targetBinding.executionTarget;
4261
+ if (!executionTarget || typeof executionTarget !== 'object' || Array.isArray(executionTarget))
4262
+ return undefined;
4263
+ const identityFingerprint = executionTarget.identityFingerprint;
4264
+ return typeof identityFingerprint === 'string' && identityFingerprint.trim()
4265
+ ? identityFingerprint
4266
+ : undefined;
4267
+ }
4268
+ function normalizeSemanticProofId(value) {
4269
+ return value
4270
+ .trim()
4271
+ .toLowerCase()
4272
+ .replace(/^semantic(?:::|:)/, '')
4273
+ .replace(/^(?:metric|measure)[.:]/, '')
4274
+ .replaceAll(':', '.');
4275
+ }
3621
4276
  async function executeGovernedRelationalAnalyticalGraph(input) {
3622
4277
  const sourceResults = {};
3623
4278
  const artifacts = [];
@@ -3822,6 +4477,7 @@ function analyticalGraphFailureAnswer(input, code, reason, artifacts = {}) {
3822
4477
  ...(artifacts.compiledSql ? { proposedSql: artifacts.compiledSql, sql: artifacts.compiledSql } : {}),
3823
4478
  ...(artifacts.dqlArtifact ? { dqlArtifact: artifacts.dqlArtifact } : {}),
3824
4479
  ...(artifacts.semanticExecutionTrace ? { semanticExecutionTrace: artifacts.semanticExecutionTrace } : {}),
4480
+ ...(artifacts.aggregationSafetyProof ? { aggregationSafetyProof: artifacts.aggregationSafetyProof } : {}),
3825
4481
  citations: contextPackCitations(input.input.contextPack, 8),
3826
4482
  considered: input.considered,
3827
4483
  contextPack: input.input.contextPack,
@@ -3834,7 +4490,7 @@ function analyticalFailureForInput(input, diagnostic) {
3834
4490
  error: diagnostic.error,
3835
4491
  code: diagnostic.code,
3836
4492
  phase: diagnostic.phase,
3837
- snapshotId: plan?.snapshotId ?? input.contextPack?.knowledgeLens.snapshotId ?? 'snapshot-unavailable',
4493
+ snapshotId: plan?.snapshotId ?? input.contextPack?.knowledgeLens?.snapshotId ?? 'snapshot-unavailable',
3838
4494
  runId: diagnostic.runId ?? plan?.planId,
3839
4495
  planFingerprint: plan?.fingerprint,
3840
4496
  dqlSource: diagnostic.dqlArtifact?.source,
@@ -6405,6 +7061,7 @@ async function generateProposalWithOptionalTools(input) {
6405
7061
  ...options,
6406
7062
  toolPolicy,
6407
7063
  maxToolCalls: toolBudget.maxToolCalls,
7064
+ providerPayloadGuard: input.providerPayloadGuard,
6408
7065
  onToolCall: (event) => {
6409
7066
  const sink = input.toolCalls;
6410
7067
  if (sink)
@@ -6502,22 +7159,9 @@ async function scoreDeepGeneratedProposalCandidate(input, candidate) {
6502
7159
  validationOk = validation.ok;
6503
7160
  if (validation.ok) {
6504
7161
  score += 100;
6505
- if (input.executeGeneratedSql) {
6506
- try {
6507
- result = await input.executeGeneratedSql(parsed.sql);
6508
- score += 40;
6509
- if (result.rowCount > 0)
6510
- score += 8;
6511
- const resultShape = validateAnswerResultShape(input.questionPlan, result);
6512
- score -= resultShape.warnings.length * 6;
6513
- if (generatedResultShapeIsPartial(resultShape))
6514
- score -= 120;
6515
- }
6516
- catch (error) {
6517
- executionError = error instanceof Error ? error.message : String(error);
6518
- score -= 20;
6519
- }
6520
- }
7162
+ // Budget authority: candidate comparison is parser/context validation
7163
+ // only. Executing each candidate would violate the one-SQL-call ordinary
7164
+ // Ask budget and make telemetry dependent on speculative work.
6521
7165
  }
6522
7166
  else {
6523
7167
  validationError = validation.error;