@duckcodeailabs/dql-agent 1.10.0 → 1.10.2

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 (86) hide show
  1. package/dist/agent-run-engine.d.ts.map +1 -1
  2. package/dist/agent-run-engine.js +6 -1
  3. package/dist/agent-run-engine.js.map +1 -1
  4. package/dist/analytical-compatibility.d.ts +61 -0
  5. package/dist/analytical-compatibility.d.ts.map +1 -0
  6. package/dist/analytical-compatibility.js +515 -0
  7. package/dist/analytical-compatibility.js.map +1 -0
  8. package/dist/analytical-execution-graph.d.ts +174 -0
  9. package/dist/analytical-execution-graph.d.ts.map +1 -0
  10. package/dist/analytical-execution-graph.js +599 -0
  11. package/dist/analytical-execution-graph.js.map +1 -0
  12. package/dist/analytical-failure-repair.d.ts +91 -0
  13. package/dist/analytical-failure-repair.d.ts.map +1 -0
  14. package/dist/analytical-failure-repair.js +325 -0
  15. package/dist/analytical-failure-repair.js.map +1 -0
  16. package/dist/analytical-frame.d.ts +19 -0
  17. package/dist/analytical-frame.d.ts.map +1 -0
  18. package/dist/analytical-frame.js +299 -0
  19. package/dist/analytical-frame.js.map +1 -0
  20. package/dist/analytical-period-resolution.d.ts +53 -0
  21. package/dist/analytical-period-resolution.d.ts.map +1 -0
  22. package/dist/analytical-period-resolution.js +218 -0
  23. package/dist/analytical-period-resolution.js.map +1 -0
  24. package/dist/analytical-readiness.d.ts +17 -0
  25. package/dist/analytical-readiness.d.ts.map +1 -0
  26. package/dist/analytical-readiness.js +95 -0
  27. package/dist/analytical-readiness.js.map +1 -0
  28. package/dist/analytical-result-facts.d.ts +79 -0
  29. package/dist/analytical-result-facts.d.ts.map +1 -0
  30. package/dist/analytical-result-facts.js +301 -0
  31. package/dist/analytical-result-facts.js.map +1 -0
  32. package/dist/answer-loop.d.ts +51 -4
  33. package/dist/answer-loop.d.ts.map +1 -1
  34. package/dist/answer-loop.js +866 -35
  35. package/dist/answer-loop.js.map +1 -1
  36. package/dist/governed-relational-compiler.d.ts +51 -0
  37. package/dist/governed-relational-compiler.d.ts.map +1 -1
  38. package/dist/governed-relational-compiler.js +144 -1
  39. package/dist/governed-relational-compiler.js.map +1 -1
  40. package/dist/index.d.ts +16 -4
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +9 -2
  43. package/dist/index.js.map +1 -1
  44. package/dist/kg/build.d.ts +2 -2
  45. package/dist/kg/build.d.ts.map +1 -1
  46. package/dist/kg/build.js +270 -13
  47. package/dist/kg/build.js.map +1 -1
  48. package/dist/meaning-resolution.d.ts +13 -0
  49. package/dist/meaning-resolution.d.ts.map +1 -1
  50. package/dist/meaning-resolution.js +69 -0
  51. package/dist/meaning-resolution.js.map +1 -1
  52. package/dist/metadata/analysis-planner.js +3 -1
  53. package/dist/metadata/analysis-planner.js.map +1 -1
  54. package/dist/metadata/catalog.d.ts +4 -2
  55. package/dist/metadata/catalog.d.ts.map +1 -1
  56. package/dist/metadata/catalog.js +264 -9
  57. package/dist/metadata/catalog.js.map +1 -1
  58. package/dist/metadata/meaning-evidence.d.ts +9 -2
  59. package/dist/metadata/meaning-evidence.d.ts.map +1 -1
  60. package/dist/metadata/meaning-evidence.js +105 -7
  61. package/dist/metadata/meaning-evidence.js.map +1 -1
  62. package/dist/plan-execution-adapter.d.ts +72 -1
  63. package/dist/plan-execution-adapter.d.ts.map +1 -1
  64. package/dist/plan-execution-adapter.js +208 -0
  65. package/dist/plan-execution-adapter.js.map +1 -1
  66. package/dist/resolved-analytical-plan.d.ts +14 -3
  67. package/dist/resolved-analytical-plan.d.ts.map +1 -1
  68. package/dist/resolved-analytical-plan.js +74 -20
  69. package/dist/resolved-analytical-plan.js.map +1 -1
  70. package/dist/router.d.ts +1 -1
  71. package/dist/router.d.ts.map +1 -1
  72. package/dist/router.js +167 -21
  73. package/dist/router.js.map +1 -1
  74. package/dist/semantic-bridge/compose.d.ts +1 -1
  75. package/dist/semantic-bridge/compose.d.ts.map +1 -1
  76. package/dist/semantic-bridge/compose.js +27 -5
  77. package/dist/semantic-bridge/compose.js.map +1 -1
  78. package/dist/semantic-bridge/member-select.d.ts +1 -1
  79. package/dist/semantic-bridge/member-select.d.ts.map +1 -1
  80. package/dist/semantic-bridge/member-select.js +32 -12
  81. package/dist/semantic-bridge/member-select.js.map +1 -1
  82. package/dist/skills/loader.d.ts +17 -0
  83. package/dist/skills/loader.d.ts.map +1 -1
  84. package/dist/skills/loader.js +62 -4
  85. package/dist/skills/loader.js.map +1 -1
  86. package/package.json +4 -4
@@ -14,7 +14,8 @@
14
14
  * step. Tests can mock the provider with a canned response and exercise the
15
15
  * full pipeline.
16
16
  */
17
- import { describeDialectForPrompt, } from '@duckcodeailabs/dql-core';
17
+ import { createHash } from 'node:crypto';
18
+ import { describeDialectForPrompt, normalizeMetricCapabilityContract, } from '@duckcodeailabs/dql-core';
18
19
  import { buildSkillBlockHints, buildSkillMetricHints, buildSkillsPrompt, expandQuestionWithSkillVocabulary, selectRelevantSkills } from './skills/loader.js';
19
20
  import { detectResultSetOperation, computeResultSetOperation } from './conversation/result-ops.js';
20
21
  import { classifyGovernedQueryShape } from './semantic-bridge/query-shape.js';
@@ -42,8 +43,12 @@ import { cascadeTraceToEvidenceRouteSteps, createCascadeAnswerResult, createCasc
42
43
  import { shouldClarifyBeforeGeneration } from './cascade/triage.js';
43
44
  import { stampTrustLabel } from './trust/stamp.js';
44
45
  import { deriveResolvedAnalyticalPlan } from './resolved-analytical-plan.js';
45
- import { adaptResolvedAnalyticalPlan, buildPlanExecutionRegistry, } from './plan-execution-adapter.js';
46
- import { buildGovernedRelationalRegistry, compileGovernedRelationalPlan, finalizeGovernedCompilationReceipt, renderGovernedRelationalDqlArtifact, } from './governed-relational-compiler.js';
46
+ import { adaptAnalyticalFreshnessRequest, adaptAnalyticalSemanticGraph, adaptResolvedAnalyticalPlan, buildPlanExecutionRegistry, } from './plan-execution-adapter.js';
47
+ import { buildGovernedRelationalRegistry, compileGovernedRelationalExecutionGraph, compileGovernedRelationalPlan, finalizeGovernedCompilationReceipt, renderGovernedRelationalDqlArtifact, } from './governed-relational-compiler.js';
48
+ import { buildAnalyticalExecutionGraph, executeAnalyticalExecutionGraph, } from './analytical-execution-graph.js';
49
+ import { buildAnalyticalResultFacts, renderDeterministicAnalyticalNarrative, validateAnalyticalNarrativeClaims, } from './analytical-result-facts.js';
50
+ import { resolveAnalyticalPeriods, } from './analytical-period-resolution.js';
51
+ import { classifyAnalyticalFailure, createAnalyticalFailure, } from './analytical-failure-repair.js';
47
52
  import { QUICK_PROMPT_CONTEXT_BUDGET, canUseLaneRepair, cascadeBudgetTrace, createCascadeBudgetState, deepAlternativeCountForQuestion, promptContextBudgetForQuestion, proposalToolBudgetForQuestion, recordLaneRepair, } from './cascade/budgets.js';
48
53
  /**
49
54
  * Physical column names whose sampled runtime values include `value`. Used by the
@@ -518,25 +523,165 @@ export async function answer(input) {
518
523
  && input.followUp.resolvedAnalyticalPlanDelta
519
524
  ? deriveResolvedAnalyticalPlan(input.followUp.priorResolvedAnalyticalPlan, input.followUp.resolvedAnalyticalPlanDelta)
520
525
  : undefined;
521
- const normalizedInput = inheritedPlan
526
+ let normalizedInput = inheritedPlan
522
527
  ? { ...input, resolvedAnalyticalPlan: inheritedPlan }
523
528
  : input;
529
+ // Cross-result computations are intentionally independent of the KG and
530
+ // warehouse. Preserve this earliest exit before building any execution
531
+ // registry so lightweight follow-up callers do not need runtime services.
532
+ const earlyCrossResult = tryCrossResultAnswer(normalizedInput);
533
+ if (earlyCrossResult) {
534
+ return {
535
+ ...earlyCrossResult,
536
+ resolvedAnalyticalPlan: normalizedInput.resolvedAnalyticalPlan,
537
+ };
538
+ }
539
+ const executionRegistry = buildPlanExecutionRegistry({
540
+ nodes: [
541
+ ...normalizedInput.kg.getNodesByKind('block', 100_000),
542
+ ...normalizedInput.kg.getNodesByKind('metric', 100_000),
543
+ ...normalizedInput.kg.getNodesByKind('dimension', 100_000),
544
+ ],
545
+ objects: normalizedInput.contextPack?.objects,
546
+ });
547
+ const unresolvedPlan = normalizedInput.resolvedAnalyticalPlan;
548
+ const unresolvedCapability = unresolvedPlan
549
+ ? analyticalCapabilityForPlan(unresolvedPlan, normalizedInput.contextPack, executionRegistry)
550
+ : undefined;
551
+ if (unresolvedPlan?.schemaVersion === 2
552
+ && unresolvedPlan.analyticalFrame?.timeContext?.periods.some((period) => !period.start || !period.end)
553
+ && unresolvedCapability) {
554
+ let freshness = normalizedInput.analyticalFreshnessObservation;
555
+ let freshnessError;
556
+ if (!freshness
557
+ && unresolvedPlan.analyticalFrame.timeContext.completenessPolicy !== 'partial_current'
558
+ && normalizedInput.resolveAnalyticalFreshness
559
+ && unresolvedPlan.analyticalFrame.timeContext.timeDimensionId) {
560
+ try {
561
+ const freshnessRequest = {
562
+ version: 1,
563
+ snapshotId: unresolvedPlan.snapshotId,
564
+ metricId: unresolvedCapability.metricId,
565
+ timeDimensionId: unresolvedPlan.analyticalFrame.timeContext.timeDimensionId,
566
+ ...(unresolvedCapability.freshness?.observedThroughFieldId
567
+ ? { observedThroughFieldId: unresolvedCapability.freshness.observedThroughFieldId }
568
+ : {}),
569
+ };
570
+ const adapterBinding = unresolvedPlan.capability === 'semantic_execution' && normalizedInput.semanticLayer
571
+ ? adaptAnalyticalFreshnessRequest({
572
+ plan: unresolvedPlan,
573
+ request: freshnessRequest,
574
+ registry: executionRegistry,
575
+ semanticLayer: normalizedInput.semanticLayer,
576
+ expectedSnapshotId: normalizedInput.contextPack?.knowledgeLens.snapshotId,
577
+ })
578
+ : undefined;
579
+ if (adapterBinding?.status === 'blocked') {
580
+ throw { code: adapterBinding.code, message: adapterBinding.reason };
581
+ }
582
+ freshness = await normalizedInput.resolveAnalyticalFreshness({
583
+ ...freshnessRequest,
584
+ ...(adapterBinding?.status === 'ready'
585
+ ? { authorizedAdapterRequest: adapterBinding.request }
586
+ : {}),
587
+ });
588
+ }
589
+ catch (error) {
590
+ freshnessError = error;
591
+ }
592
+ }
593
+ const periodResolution = freshnessError
594
+ ? {
595
+ status: 'blocked',
596
+ code: 'FRESHNESS_REQUIRED',
597
+ reason: 'The selected route could not obtain its authorized freshness observation.',
598
+ }
599
+ : resolveAnalyticalPeriods({
600
+ frame: unresolvedPlan.analyticalFrame,
601
+ snapshotId: unresolvedPlan.snapshotId,
602
+ referenceInstant: normalizedInput.analyticalReferenceInstant ?? new Date().toISOString(),
603
+ ...(freshness ? { freshnessObservation: freshness } : {}),
604
+ });
605
+ if (periodResolution.status === 'resolved') {
606
+ normalizedInput = {
607
+ ...normalizedInput,
608
+ resolvedAnalyticalPlan: deriveResolvedAnalyticalPlan(unresolvedPlan, {
609
+ question: unresolvedPlan.question,
610
+ analyticalFrame: periodResolution.frame,
611
+ }),
612
+ ...(periodResolution.freshnessObservation
613
+ ? { analyticalFreshnessObservation: periodResolution.freshnessObservation }
614
+ : {}),
615
+ };
616
+ }
617
+ else {
618
+ normalizedInput = {
619
+ ...normalizedInput,
620
+ analyticalPeriodResolutionFailure: {
621
+ code: periodResolution.code,
622
+ reason: periodResolution.reason,
623
+ ...(freshnessError ? { error: freshnessError } : {}),
624
+ },
625
+ };
626
+ }
627
+ }
524
628
  const resolvedPlanExecutionBinding = normalizedInput.resolvedAnalyticalPlan
525
629
  ? adaptResolvedAnalyticalPlan({
526
630
  plan: normalizedInput.resolvedAnalyticalPlan,
527
- registry: buildPlanExecutionRegistry({
528
- nodes: [
529
- ...normalizedInput.kg.getNodesByKind('block', 100_000),
530
- ...normalizedInput.kg.getNodesByKind('metric', 100_000),
531
- ...normalizedInput.kg.getNodesByKind('dimension', 100_000),
532
- ],
533
- objects: normalizedInput.contextPack?.objects,
534
- }),
631
+ registry: executionRegistry,
632
+ semanticLayer: normalizedInput.semanticLayer,
633
+ expectedSnapshotId: normalizedInput.contextPack?.knowledgeLens.snapshotId,
634
+ })
635
+ : undefined;
636
+ const analyticalCapability = normalizedInput.resolvedAnalyticalPlan
637
+ ? analyticalCapabilityForPlan(normalizedInput.resolvedAnalyticalPlan, normalizedInput.contextPack, executionRegistry)
638
+ : undefined;
639
+ const analyticalRoute = normalizedInput.resolvedAnalyticalPlan
640
+ ? analyticalRouteForPlan(normalizedInput.resolvedAnalyticalPlan)
641
+ : undefined;
642
+ const analyticalAdapterId = analyticalCapability && analyticalRoute
643
+ ? analyticalCapability.executionCapabilities.find((candidate) => candidate.route === analyticalRoute)?.adapterId
644
+ : undefined;
645
+ const analyticalGraphBuild = normalizedInput.resolvedAnalyticalPlan?.schemaVersion === 2
646
+ && normalizedInput.resolvedAnalyticalPlan.analyticalFrame
647
+ && !normalizedInput.analyticalPeriodResolutionFailure
648
+ && analyticalCapability
649
+ && analyticalRoute
650
+ ? buildAnalyticalExecutionGraph({
651
+ plan: normalizedInput.resolvedAnalyticalPlan,
652
+ capability: analyticalCapability,
653
+ route: analyticalRoute,
654
+ ...(analyticalAdapterId ? { adapterId: analyticalAdapterId } : {}),
655
+ })
656
+ : undefined;
657
+ const analyticalExecutionGraph = analyticalGraphBuild?.status === 'ready'
658
+ ? analyticalGraphBuild.graph
659
+ : undefined;
660
+ const analyticalExecutionGraphFailure = normalizedInput.resolvedAnalyticalPlan?.schemaVersion === 2
661
+ ? analyticalGraphBuild?.status === 'blocked'
662
+ ? {
663
+ code: analyticalGraphBuild.code,
664
+ reason: analyticalGraphBuild.reason,
665
+ ...(analyticalGraphBuild.field ? { field: analyticalGraphBuild.field } : {}),
666
+ }
667
+ : !analyticalCapability
668
+ ? { code: 'CAPABILITY_MISMATCH', reason: 'The selected v2 plan capability is not present in the immutable context pack.' }
669
+ : !analyticalRoute
670
+ ? { code: 'ROUTE_MISMATCH', reason: 'The selected v2 plan has no executable analytical route.' }
671
+ : undefined
672
+ : undefined;
673
+ const semanticGraphExecutionBinding = analyticalExecutionGraph?.route === 'semantic'
674
+ && normalizedInput.resolvedAnalyticalPlan
675
+ && normalizedInput.semanticLayer
676
+ ? adaptAnalyticalSemanticGraph({
677
+ graph: analyticalExecutionGraph,
678
+ plan: normalizedInput.resolvedAnalyticalPlan,
679
+ registry: executionRegistry,
535
680
  semanticLayer: normalizedInput.semanticLayer,
536
681
  expectedSnapshotId: normalizedInput.contextPack?.knowledgeLens.snapshotId,
537
682
  })
538
683
  : undefined;
539
- const planBoundInput = resolvedPlanExecutionBinding
684
+ const planBoundInput = resolvedPlanExecutionBinding || analyticalExecutionGraph || analyticalExecutionGraphFailure
540
685
  ? { ...normalizedInput, resolvedPlanExecutionBinding }
541
686
  : normalizedInput;
542
687
  const relationalSchemaContext = planBoundInput.schemaContext?.length
@@ -550,20 +695,44 @@ export async function answer(input) {
550
695
  description: column.description,
551
696
  })),
552
697
  }));
553
- const governedRelationalCompilation = planBoundInput.resolvedAnalyticalPlan?.mode === 'authoritative'
698
+ const relationalRegistry = planBoundInput.resolvedAnalyticalPlan?.capability === 'governed_relational'
699
+ ? buildGovernedRelationalRegistry({
700
+ snapshotId: planBoundInput.resolvedAnalyticalPlan.snapshotId,
701
+ schemaContext: relationalSchemaContext,
702
+ manifest: planBoundInput.manifest,
703
+ })
704
+ : undefined;
705
+ const governedAnalyticalGraphCompilation = analyticalExecutionGraph?.route === 'governed_sql'
706
+ && planBoundInput.resolvedAnalyticalPlan
707
+ && relationalRegistry
708
+ ? compileGovernedRelationalExecutionGraph({
709
+ graph: analyticalExecutionGraph,
710
+ plan: planBoundInput.resolvedAnalyticalPlan,
711
+ registry: relationalRegistry,
712
+ driver: planBoundInput.semanticDriver,
713
+ })
714
+ : undefined;
715
+ const governedRelationalCompilation = !governedAnalyticalGraphCompilation
716
+ && planBoundInput.resolvedAnalyticalPlan?.mode === 'authoritative'
554
717
  && planBoundInput.resolvedAnalyticalPlan.capability === 'governed_relational'
555
718
  ? compileGovernedRelationalPlan({
556
719
  plan: planBoundInput.resolvedAnalyticalPlan,
557
- registry: buildGovernedRelationalRegistry({
558
- snapshotId: planBoundInput.resolvedAnalyticalPlan.snapshotId,
559
- schemaContext: relationalSchemaContext,
560
- manifest: planBoundInput.manifest,
561
- }),
720
+ registry: relationalRegistry,
562
721
  driver: planBoundInput.semanticDriver,
563
722
  })
564
723
  : undefined;
565
- const compiledInput = governedRelationalCompilation
566
- ? { ...planBoundInput, governedRelationalCompilation }
724
+ const compiledInput = governedRelationalCompilation || governedAnalyticalGraphCompilation || semanticGraphExecutionBinding || analyticalExecutionGraph || analyticalExecutionGraphFailure || normalizedInput.analyticalPeriodResolutionFailure
725
+ ? {
726
+ ...planBoundInput,
727
+ ...(governedRelationalCompilation ? { governedRelationalCompilation } : {}),
728
+ ...(governedAnalyticalGraphCompilation ? { governedAnalyticalGraphCompilation } : {}),
729
+ ...(semanticGraphExecutionBinding ? { semanticGraphExecutionBinding } : {}),
730
+ ...(analyticalExecutionGraph ? { analyticalExecutionGraph } : {}),
731
+ ...(analyticalExecutionGraphFailure ? { analyticalExecutionGraphFailure } : {}),
732
+ ...(normalizedInput.analyticalPeriodResolutionFailure
733
+ ? { analyticalPeriodResolutionFailure: normalizedInput.analyticalPeriodResolutionFailure }
734
+ : {}),
735
+ }
567
736
  : planBoundInput;
568
737
  const executionInput = compiledInput.contextPack
569
738
  ? {
@@ -572,17 +741,8 @@ export async function answer(input) {
572
741
  skillsSelectionLocked: true,
573
742
  }
574
743
  : compiledInput;
575
- // Cross-result follow-up ("of these, the average") — computed from the prior
576
- // rows, before the cascade, so it never re-queries or times out.
577
- const crossResult = tryCrossResultAnswer(executionInput);
578
- if (crossResult) {
579
- return {
580
- ...crossResult,
581
- resolvedAnalyticalPlan: executionInput.resolvedAnalyticalPlan,
582
- executablePlan: executionInput.resolvedPlanExecutionBinding,
583
- };
584
- }
585
- const result = applyHollowAnswerGate(await runAnswerLoop(executionInput));
744
+ const loopResult = await runAnswerLoop(executionInput);
745
+ const result = applyHollowAnswerGate(validateCertifiedAnalyticalGraphAnswer(loopResult, executionInput.analyticalExecutionGraph, executionInput.resolvedAnalyticalPlan?.analyticalFrame));
586
746
  // Attach the canonical trust label once, at the single exit point, so every
587
747
  // return site inside runAnswerLoop stays untouched and backward compatible.
588
748
  // Freshness-aware trust: for a certified answer, fold the source block's data
@@ -613,6 +773,7 @@ export async function answer(input) {
613
773
  intentDecision,
614
774
  resolvedAnalyticalPlan: executionInput.resolvedAnalyticalPlan,
615
775
  executablePlan: executionInput.resolvedPlanExecutionBinding,
776
+ analyticalFreshnessObservation: executionInput.analyticalFreshnessObservation,
616
777
  trustLabelInfo,
617
778
  provenanceFooter: buildProvenanceFooter(result, trustLabelInfo),
618
779
  cascade: publicResult.cascade ?? createCascadeAnswerResult({
@@ -779,9 +940,147 @@ async function runAnswerLoop(input) {
779
940
  const authoritativePlanBinding = input.resolvedAnalyticalPlan?.mode === 'authoritative'
780
941
  ? input.resolvedPlanExecutionBinding
781
942
  : undefined;
943
+ if (input.analyticalPeriodResolutionFailure) {
944
+ const failure = input.analyticalPeriodResolutionFailure;
945
+ const structuredFailureCode = failure.error && typeof failure.error === 'object'
946
+ && typeof failure.error.code === 'string'
947
+ && /^[A-Z0-9_]+$/.test(failure.error.code)
948
+ ? failure.error.code
949
+ : undefined;
950
+ const analyticalFailure = analyticalFailureForInput(input, {
951
+ error: failure.error ?? { code: failure.code, message: failure.reason },
952
+ ...(failure.code === 'FRESHNESS_MISMATCH' ? { code: 'SNAPSHOT_DRIFT' } : {}),
953
+ phase: 'planning',
954
+ failedBindings: input.resolvedAnalyticalPlan?.analyticalFrame?.timeContext?.timeDimensionId
955
+ ? [{
956
+ qualifiedId: input.resolvedAnalyticalPlan.analyticalFrame.timeContext.timeDimensionId,
957
+ role: 'time_axis',
958
+ reasonCode: structuredFailureCode ?? failure.code,
959
+ }]
960
+ : undefined,
961
+ });
962
+ return {
963
+ kind: 'no_answer',
964
+ sourceTier: 'no_answer',
965
+ certification: 'analyst_review_required',
966
+ reviewStatus: 'none',
967
+ confidence: 0,
968
+ text: analyticalFailure.message,
969
+ answer: analyticalFailure.message,
970
+ refusalCode: analyticalFailure.code === 'PERMISSION_DENIED' ? 'policy_blocked' : 'grounding_gap',
971
+ refusalDetails: { code: failure.code, message: analyticalFailure.message },
972
+ analyticalFailure,
973
+ citations: contextPackCitations(input.contextPack, 8),
974
+ considered,
975
+ contextPack: input.contextPack,
976
+ providerUsed: provider.name,
977
+ };
978
+ }
979
+ if (input.analyticalExecutionGraphFailure) {
980
+ const failure = input.analyticalExecutionGraphFailure;
981
+ const analyticalFailure = analyticalFailureForInput(input, {
982
+ error: { code: failure.code, message: failure.reason },
983
+ phase: 'compilation',
984
+ ...(failure.field ? { failedBindings: [{ role: failure.field, reasonCode: failure.code }] } : {}),
985
+ });
986
+ const text = analyticalFailure.message;
987
+ return {
988
+ kind: 'no_answer',
989
+ sourceTier: 'no_answer',
990
+ certification: 'analyst_review_required',
991
+ reviewStatus: 'none',
992
+ confidence: 0,
993
+ text,
994
+ answer: text,
995
+ refusalCode: 'modeling_gap',
996
+ refusalDetails: { code: failure.code, message: analyticalFailure.message },
997
+ analyticalFailure,
998
+ citations: contextPackCitations(input.contextPack, 8),
999
+ considered,
1000
+ contextPack: input.contextPack,
1001
+ providerUsed: provider.name,
1002
+ };
1003
+ }
1004
+ if (input.semanticGraphExecutionBinding?.status === 'blocked') {
1005
+ const failure = input.semanticGraphExecutionBinding;
1006
+ const analyticalFailure = analyticalFailureForInput(input, {
1007
+ error: { code: failure.code, message: failure.reason },
1008
+ phase: 'validation',
1009
+ failedBindings: failure.candidateIds?.map((qualifiedId) => ({ qualifiedId, reasonCode: failure.code })),
1010
+ });
1011
+ const text = analyticalFailure.message;
1012
+ return {
1013
+ kind: 'no_answer',
1014
+ sourceTier: 'no_answer',
1015
+ certification: 'analyst_review_required',
1016
+ reviewStatus: 'none',
1017
+ confidence: 0,
1018
+ text,
1019
+ answer: text,
1020
+ refusalCode: 'grounding_gap',
1021
+ refusalDetails: { code: failure.code, message: analyticalFailure.message },
1022
+ analyticalFailure,
1023
+ citations: contextPackCitations(input.contextPack, 8),
1024
+ considered,
1025
+ contextPack: input.contextPack,
1026
+ providerUsed: provider.name,
1027
+ };
1028
+ }
1029
+ if (input.semanticGraphExecutionBinding?.status === 'ready' && input.analyticalExecutionGraph) {
1030
+ return executeSemanticAnalyticalGraph({
1031
+ input,
1032
+ binding: input.semanticGraphExecutionBinding,
1033
+ graph: input.analyticalExecutionGraph,
1034
+ considered,
1035
+ providerName: provider.name,
1036
+ });
1037
+ }
1038
+ if (input.governedAnalyticalGraphCompilation?.status === 'blocked') {
1039
+ const failure = input.governedAnalyticalGraphCompilation;
1040
+ const analyticalFailure = analyticalFailureForInput(input, {
1041
+ error: { code: failure.code, message: failure.reason },
1042
+ phase: 'compilation',
1043
+ failedBindings: failure.candidateIds?.map((qualifiedId) => ({ qualifiedId, reasonCode: failure.code })),
1044
+ });
1045
+ const text = analyticalFailure.message;
1046
+ return {
1047
+ kind: 'no_answer',
1048
+ sourceTier: 'no_answer',
1049
+ certification: 'analyst_review_required',
1050
+ reviewStatus: 'none',
1051
+ confidence: 0,
1052
+ text,
1053
+ answer: text,
1054
+ refusalCode: failure.code.startsWith('RELATIONSHIP_') ? 'modeling_gap' : 'grounding_gap',
1055
+ refusalDetails: { code: failure.code, message: analyticalFailure.message },
1056
+ analyticalFailure,
1057
+ citations: contextPackCitations(input.contextPack, 8),
1058
+ considered,
1059
+ contextPack: input.contextPack,
1060
+ providerUsed: provider.name,
1061
+ };
1062
+ }
1063
+ if (input.governedAnalyticalGraphCompilation?.status === 'compiled' && input.analyticalExecutionGraph) {
1064
+ return executeGovernedRelationalAnalyticalGraph({
1065
+ input,
1066
+ compilation: input.governedAnalyticalGraphCompilation,
1067
+ graph: input.analyticalExecutionGraph,
1068
+ considered,
1069
+ providerName: provider.name,
1070
+ schemaContext,
1071
+ });
1072
+ }
782
1073
  const governedRelationalCompilation = input.governedRelationalCompilation;
783
1074
  if (governedRelationalCompilation?.status === 'blocked') {
784
- const text = `The governed relational plan cannot compile safely: ${governedRelationalCompilation.reason}`;
1075
+ const analyticalFailure = analyticalFailureForInput(input, {
1076
+ error: { code: governedRelationalCompilation.code, message: governedRelationalCompilation.reason },
1077
+ phase: 'compilation',
1078
+ failedBindings: governedRelationalCompilation.candidateIds?.map((qualifiedId) => ({
1079
+ qualifiedId,
1080
+ reasonCode: governedRelationalCompilation.code,
1081
+ })),
1082
+ });
1083
+ const text = analyticalFailure.message;
785
1084
  return {
786
1085
  kind: 'no_answer',
787
1086
  sourceTier: 'no_answer',
@@ -791,7 +1090,8 @@ async function runAnswerLoop(input) {
791
1090
  text,
792
1091
  answer: text,
793
1092
  refusalCode: governedRelationalCompilation.code.startsWith('RELATIONSHIP_') ? 'modeling_gap' : 'grounding_gap',
794
- refusalDetails: { code: 'grounding_gap', message: `${governedRelationalCompilation.code}: ${governedRelationalCompilation.reason}` },
1093
+ refusalDetails: { code: 'grounding_gap', message: analyticalFailure.message },
1094
+ analyticalFailure,
795
1095
  citations: contextPackCitations(input.contextPack, 8),
796
1096
  considered,
797
1097
  contextPack: input.contextPack,
@@ -802,6 +1102,7 @@ async function runAnswerLoop(input) {
802
1102
  const dqlArtifact = renderGovernedRelationalDqlArtifact(governedRelationalCompilation);
803
1103
  let result;
804
1104
  let executionError;
1105
+ let analyticalFailure;
805
1106
  let receipt = governedRelationalCompilation.receipt;
806
1107
  if (input.executeDqlArtifact) {
807
1108
  try {
@@ -809,7 +1110,13 @@ async function runAnswerLoop(input) {
809
1110
  receipt = finalizeGovernedCompilationReceipt(receipt, result);
810
1111
  }
811
1112
  catch (error) {
812
- executionError = error instanceof Error ? error.message : String(error);
1113
+ analyticalFailure = analyticalFailureForInput(input, {
1114
+ error,
1115
+ phase: 'execution',
1116
+ dqlArtifact,
1117
+ compiledSql: governedRelationalCompilation.sql,
1118
+ });
1119
+ executionError = analyticalFailure.message;
813
1120
  }
814
1121
  }
815
1122
  const text = executionError
@@ -826,6 +1133,7 @@ async function runAnswerLoop(input) {
826
1133
  text,
827
1134
  answer: text,
828
1135
  ...(executionError ? { executionError, refusalCode: 'grounding_gap' } : {}),
1136
+ ...(analyticalFailure ? { analyticalFailure } : {}),
829
1137
  proposedSql: governedRelationalCompilation.sql,
830
1138
  sql: governedRelationalCompilation.sql,
831
1139
  dqlArtifact,
@@ -2712,6 +3020,529 @@ function resultColumnNames(result) {
2712
3020
  .filter(Boolean);
2713
3021
  return columns?.length ? columns : undefined;
2714
3022
  }
3023
+ function analyticalCapabilityForPlan(plan, contextPack, registry = []) {
3024
+ const candidates = contextPack?.retrievalDiagnostics.meaningEvidence?.candidates ?? [];
3025
+ const exactExecution = candidates.filter((candidate) => candidate.objectKey === plan.executionId || candidate.qualifiedId === plan.executionId);
3026
+ const metricId = plan.analyticalFrame?.metricConceptIds[0];
3027
+ const pool = exactExecution.length > 0
3028
+ ? exactExecution
3029
+ : candidates.filter((candidate) => candidate.analyticalCapability?.metricId === metricId);
3030
+ const normalized = pool.flatMap((candidate) => {
3031
+ const capability = normalizeMetricCapabilityContract(candidate.analyticalCapability);
3032
+ return capability ? [capability] : [];
3033
+ });
3034
+ for (const entry of registry) {
3035
+ if ((plan.executionId && entry.identities.includes(plan.executionId)) ||
3036
+ entry.node.payload?.qualifiedId === plan.analyticalFrame?.metricConceptIds[0]) {
3037
+ const capability = normalizeMetricCapabilityContract(entry.node.payload?.analyticalCapability);
3038
+ if (capability)
3039
+ normalized.push(capability);
3040
+ }
3041
+ }
3042
+ const byFingerprint = new Map(normalized.map((capability) => [capability.sourceFingerprint, capability]));
3043
+ return byFingerprint.size === 1 ? [...byFingerprint.values()][0] : undefined;
3044
+ }
3045
+ function analyticalRouteForPlan(plan) {
3046
+ if (plan.capability === 'certified_execution' && plan.recommendedRoute === 'certified')
3047
+ return 'certified';
3048
+ if (plan.capability === 'semantic_execution' && plan.recommendedRoute === 'semantic')
3049
+ return 'semantic';
3050
+ if (plan.capability === 'governed_relational' && plan.recommendedRoute === 'governed_sql')
3051
+ return 'governed_sql';
3052
+ if (plan.capability === 'bounded_exploration' && plan.recommendedRoute === 'exploratory')
3053
+ return 'exploratory';
3054
+ return undefined;
3055
+ }
3056
+ function validateCertifiedAnalyticalGraphAnswer(answer, graph, frame) {
3057
+ if (!graph || graph.route !== 'certified')
3058
+ return answer;
3059
+ if (!frame)
3060
+ return certifiedGraphValidationFailure(answer, graph, 'The certified graph has no v2 analytical frame.');
3061
+ if (!answer.result)
3062
+ return { ...answer, analyticalExecutionGraph: graph };
3063
+ const source = graph.nodes.find((node) => node.kind === 'source_invocation' && node.strategy === 'complete_asset');
3064
+ if (!source) {
3065
+ return certifiedGraphValidationFailure(answer, graph, 'The certified graph has no complete-asset source node.');
3066
+ }
3067
+ const columns = resultColumnNames(answer.result) ?? [];
3068
+ const expected = source.outputAliases.completeAssetOutputIds ?? [];
3069
+ if (columns.length !== expected.length || expected.some((outputId) => !columns.includes(outputId))) {
3070
+ return certifiedGraphValidationFailure(answer, graph, `The certified result does not satisfy the requested outputs: ${expected.join(', ')}.`);
3071
+ }
3072
+ const rows = answer.result.rows.map((row) => {
3073
+ if (Array.isArray(row))
3074
+ return Object.fromEntries(columns.map((column, index) => [column, row[index]]));
3075
+ return row && typeof row === 'object' ? row : {};
3076
+ });
3077
+ const executed = executeAnalyticalExecutionGraph({
3078
+ graph,
3079
+ sourceResults: {
3080
+ [source.id]: {
3081
+ columns,
3082
+ rows,
3083
+ receiptFingerprint: answer.result.executionReceipt?.resultFingerprint
3084
+ ?? hashAnalyticalValue({ columns, rows, rowCount: answer.result.rowCount }),
3085
+ },
3086
+ },
3087
+ });
3088
+ if (executed.status === 'failed') {
3089
+ return certifiedGraphValidationFailure(answer, graph, executed.reason);
3090
+ }
3091
+ const story = buildValidatedAnalyticalStory(frame, graph, executed);
3092
+ if ('failure' in story)
3093
+ return certifiedGraphValidationFailure(answer, graph, story.failure);
3094
+ return {
3095
+ ...answer,
3096
+ text: story.narrative.text,
3097
+ answer: story.narrative.text,
3098
+ analyticalExecutionGraph: graph,
3099
+ analyticalExecutionReceipt: executed.receipt,
3100
+ analyticalFacts: story.factSet,
3101
+ analyticalNarrative: story.narrative,
3102
+ result: analyticalGraphResultPayload(executed),
3103
+ };
3104
+ }
3105
+ function certifiedGraphValidationFailure(answer, graph, reason) {
3106
+ const analyticalFailure = createAnalyticalFailure({
3107
+ code: 'RESULT_CONTRACT_MISMATCH',
3108
+ phase: 'result_validation',
3109
+ snapshotId: graph.snapshotId,
3110
+ runId: graph.graphId,
3111
+ planFingerprint: graph.planFingerprint,
3112
+ dqlSource: answer.dqlArtifact?.source,
3113
+ compiledSql: answer.sql ?? answer.proposedSql ?? answer.dqlArtifact?.compiledSql,
3114
+ });
3115
+ const text = analyticalFailure.message;
3116
+ return {
3117
+ ...answer,
3118
+ kind: 'no_answer',
3119
+ sourceTier: 'no_answer',
3120
+ certification: 'analyst_review_required',
3121
+ reviewStatus: 'none',
3122
+ confidence: 0,
3123
+ text,
3124
+ answer: text,
3125
+ result: undefined,
3126
+ executionError: analyticalFailure.message,
3127
+ refusalCode: 'grounding_gap',
3128
+ refusalDetails: { code: 'RESULT_CONTRACT_MISMATCH', message: analyticalFailure.message },
3129
+ analyticalFailure,
3130
+ analyticalExecutionGraph: graph,
3131
+ };
3132
+ }
3133
+ async function executeSemanticAnalyticalGraph(input) {
3134
+ const layer = input.input.semanticLayer;
3135
+ if (!layer) {
3136
+ return analyticalGraphFailureAnswer(input, 'SEMANTIC_LAYER_REQUIRED', 'The pinned semantic layer is unavailable.');
3137
+ }
3138
+ const sourceResults = {};
3139
+ const compiledSql = [];
3140
+ const artifacts = [];
3141
+ for (const invocation of input.binding.invocations) {
3142
+ let composed = composeSemanticQueryFromMembers({
3143
+ semanticLayer: layer,
3144
+ question: input.input.question,
3145
+ selection: invocation.selection,
3146
+ ...(input.input.semanticDriver ? { driver: input.input.semanticDriver } : {}),
3147
+ ...(input.input.semanticTableMapping ? { tableMapping: input.input.semanticTableMapping } : {}),
3148
+ });
3149
+ if (!composed && input.input.semanticQueryCompiler) {
3150
+ try {
3151
+ const compiled = await input.input.semanticQueryCompiler(invocation.selection);
3152
+ composed = composeSemanticQueryFromCompiledMembers({
3153
+ semanticLayer: layer,
3154
+ question: input.input.question,
3155
+ selection: compiled.selection ?? invocation.selection,
3156
+ sql: compiled.sql,
3157
+ });
3158
+ }
3159
+ catch (error) {
3160
+ return analyticalGraphFailureAnswer(input, 'COMPILATION_FAILED', error instanceof Error ? error.message : String(error), {
3161
+ phase: 'compilation',
3162
+ ...(compiledSql.length ? { compiledSql: renderAnalyticalStatements(compiledSql) } : {}),
3163
+ });
3164
+ }
3165
+ }
3166
+ if (!composed) {
3167
+ return analyticalGraphFailureAnswer(input, 'COMPILATION_FAILED', `The pinned semantic adapter could not compile ${invocation.nodeId}.`, {
3168
+ phase: 'compilation',
3169
+ ...(compiledSql.length ? { compiledSql: renderAnalyticalStatements(compiledSql) } : {}),
3170
+ failedBindings: [{ role: 'source_invocation', reasonCode: 'SEMANTIC_COMPILE_FAILED' }],
3171
+ });
3172
+ }
3173
+ compiledSql.push(composed.sql);
3174
+ artifacts.push(composed.dqlArtifact);
3175
+ const executor = input.input.executeDqlArtifact
3176
+ ? () => input.input.executeDqlArtifact(composed.dqlArtifact)
3177
+ : input.input.executeGeneratedSql
3178
+ ? () => input.input.executeGeneratedSql(composed.sql, composed.dqlArtifact)
3179
+ : undefined;
3180
+ if (!executor)
3181
+ continue;
3182
+ let result;
3183
+ try {
3184
+ result = await executor();
3185
+ }
3186
+ catch (error) {
3187
+ return analyticalGraphFailureAnswer(input, 'EXECUTION_FAILED', error instanceof Error ? error.message : String(error), { phase: 'execution', dqlArtifact: composed.dqlArtifact, compiledSql: composed.sql });
3188
+ }
3189
+ const normalized = normalizeAnalyticalSourceResult({
3190
+ result,
3191
+ outputAliases: invocation.outputAliases,
3192
+ sourceNames: [
3193
+ ...(invocation.selection.dimensions ?? []),
3194
+ ...(invocation.selection.timeDimension ? [invocation.selection.timeDimension.name] : []),
3195
+ invocation.selection.metrics[0],
3196
+ ],
3197
+ sql: composed.sql,
3198
+ });
3199
+ if ('failure' in normalized) {
3200
+ return analyticalGraphFailureAnswer(input, 'RESULT_CONTRACT_MISMATCH', normalized.failure, {
3201
+ phase: 'result_validation',
3202
+ dqlArtifact: composed.dqlArtifact,
3203
+ compiledSql: composed.sql,
3204
+ });
3205
+ }
3206
+ sourceResults[invocation.nodeId] = normalized.source;
3207
+ }
3208
+ const sql = renderAnalyticalStatements(compiledSql);
3209
+ if (Object.keys(sourceResults).length === 0) {
3210
+ return {
3211
+ kind: 'uncertified',
3212
+ sourceTier: 'semantic_layer',
3213
+ certification: 'governed',
3214
+ reviewStatus: 'governed',
3215
+ confidence: 0.9,
3216
+ text: `Compiled ${compiledSql.length} snapshot-bound semantic period ${compiledSql.length === 1 ? 'query' : 'queries'}; execution was not requested.`,
3217
+ answer: `Compiled ${compiledSql.length} snapshot-bound semantic period ${compiledSql.length === 1 ? 'query' : 'queries'}; execution was not requested.`,
3218
+ proposedSql: sql,
3219
+ sql,
3220
+ ...(artifacts.length === 1 ? { dqlArtifact: artifacts[0] } : {}),
3221
+ analyticalExecutionGraph: input.graph,
3222
+ citations: contextPackCitations(input.input.contextPack, 8),
3223
+ considered: input.considered,
3224
+ contextPack: input.input.contextPack,
3225
+ providerUsed: input.providerName,
3226
+ };
3227
+ }
3228
+ const executed = executeAnalyticalExecutionGraph({ graph: input.graph, sourceResults });
3229
+ if (executed.status === 'failed') {
3230
+ return analyticalGraphFailureAnswer(input, executed.code, executed.reason, {
3231
+ phase: 'result_validation',
3232
+ compiledSql: sql,
3233
+ ...(artifacts.length === 1 ? { dqlArtifact: artifacts[0] } : {}),
3234
+ failedBindings: [{ role: executed.nodeId, reasonCode: executed.code }],
3235
+ });
3236
+ }
3237
+ const frame = input.input.resolvedAnalyticalPlan?.analyticalFrame;
3238
+ if (!frame)
3239
+ return analyticalGraphFailureAnswer(input, 'RESULT_CONTRACT_MISMATCH', 'The executed graph has no v2 analytical frame.', {
3240
+ phase: 'result_validation',
3241
+ compiledSql: sql,
3242
+ });
3243
+ const story = buildValidatedAnalyticalStory(frame, input.graph, executed);
3244
+ if ('failure' in story)
3245
+ return analyticalGraphFailureAnswer(input, 'RESULT_CONTRACT_MISMATCH', story.failure, {
3246
+ phase: 'result_validation',
3247
+ compiledSql: sql,
3248
+ });
3249
+ const result = analyticalGraphResultPayload(executed);
3250
+ return {
3251
+ kind: 'uncertified',
3252
+ sourceTier: 'semantic_layer',
3253
+ certification: 'governed',
3254
+ reviewStatus: 'governed',
3255
+ confidence: 0.95,
3256
+ text: story.narrative.text,
3257
+ answer: story.narrative.text,
3258
+ proposedSql: sql,
3259
+ sql,
3260
+ ...(artifacts.length === 1 ? { dqlArtifact: artifacts[0] } : {}),
3261
+ result,
3262
+ analyticalExecutionGraph: input.graph,
3263
+ analyticalExecutionReceipt: executed.receipt,
3264
+ analyticalFacts: story.factSet,
3265
+ analyticalNarrative: story.narrative,
3266
+ citations: contextPackCitations(input.input.contextPack, 8),
3267
+ considered: input.considered,
3268
+ contextPack: input.input.contextPack,
3269
+ providerUsed: input.providerName,
3270
+ };
3271
+ }
3272
+ async function executeGovernedRelationalAnalyticalGraph(input) {
3273
+ const sourceResults = {};
3274
+ const artifacts = [];
3275
+ for (const statement of input.compilation.statements) {
3276
+ const compiled = {
3277
+ status: 'compiled',
3278
+ ast: statement.ast,
3279
+ sql: statement.sql,
3280
+ parameterValues: statement.parameterValues,
3281
+ receipt: statement.receipt,
3282
+ };
3283
+ const artifact = renderGovernedRelationalDqlArtifact(compiled);
3284
+ artifacts.push(artifact);
3285
+ if (!input.input.executeDqlArtifact)
3286
+ continue;
3287
+ let result;
3288
+ try {
3289
+ result = await input.input.executeDqlArtifact(artifact);
3290
+ }
3291
+ catch (error) {
3292
+ return analyticalGraphFailureAnswer(input, 'EXECUTION_FAILED', error instanceof Error ? error.message : String(error), { phase: 'execution', dqlArtifact: artifact, compiledSql: statement.sql });
3293
+ }
3294
+ let receipt;
3295
+ try {
3296
+ receipt = finalizeGovernedCompilationReceipt(statement.receipt, result);
3297
+ }
3298
+ catch (error) {
3299
+ return analyticalGraphFailureAnswer(input, 'RESULT_CONTRACT_MISMATCH', error instanceof Error ? error.message : String(error), { phase: 'result_validation', dqlArtifact: artifact, compiledSql: statement.sql });
3300
+ }
3301
+ const normalized = normalizeAnalyticalSourceResult({
3302
+ result,
3303
+ outputAliases: statement.outputAliases,
3304
+ sourceNames: [
3305
+ ...statement.outputAliases.dimensions.map((dimension) => dimension.outputId),
3306
+ statement.outputAliases.metric.outputId,
3307
+ ],
3308
+ sql: statement.sql,
3309
+ receiptFingerprint: receipt.result.resultFingerprint,
3310
+ });
3311
+ if ('failure' in normalized) {
3312
+ return analyticalGraphFailureAnswer(input, 'RESULT_CONTRACT_MISMATCH', normalized.failure, {
3313
+ phase: 'result_validation',
3314
+ dqlArtifact: artifact,
3315
+ compiledSql: statement.sql,
3316
+ });
3317
+ }
3318
+ sourceResults[statement.nodeId] = normalized.source;
3319
+ }
3320
+ const statements = input.compilation.statements.map((statement) => statement.sql);
3321
+ const sql = renderAnalyticalStatements(statements);
3322
+ if (Object.keys(sourceResults).length === 0) {
3323
+ return {
3324
+ kind: 'uncertified',
3325
+ sourceTier: 'dbt_manifest',
3326
+ certification: 'governed',
3327
+ reviewStatus: 'governed',
3328
+ confidence: 0.9,
3329
+ text: `Compiled ${statements.length} governed relational period ${statements.length === 1 ? 'statement' : 'statements'}; execution was not requested.`,
3330
+ answer: `Compiled ${statements.length} governed relational period ${statements.length === 1 ? 'statement' : 'statements'}; execution was not requested.`,
3331
+ proposedSql: sql,
3332
+ sql,
3333
+ ...(artifacts.length === 1 ? { dqlArtifact: artifacts[0] } : {}),
3334
+ analyticalExecutionGraph: input.graph,
3335
+ governedAnalyticalGraphCompilationReceipt: input.compilation.receipt,
3336
+ citations: schemaCitations(input.schemaContext, 8),
3337
+ considered: input.considered,
3338
+ contextPack: input.input.contextPack,
3339
+ providerUsed: input.providerName,
3340
+ };
3341
+ }
3342
+ const executed = executeAnalyticalExecutionGraph({ graph: input.graph, sourceResults });
3343
+ if (executed.status === 'failed') {
3344
+ return analyticalGraphFailureAnswer(input, executed.code, executed.reason, {
3345
+ phase: 'result_validation',
3346
+ compiledSql: sql,
3347
+ ...(artifacts.length === 1 ? { dqlArtifact: artifacts[0] } : {}),
3348
+ failedBindings: [{ role: executed.nodeId, reasonCode: executed.code }],
3349
+ });
3350
+ }
3351
+ const frame = input.input.resolvedAnalyticalPlan?.analyticalFrame;
3352
+ if (!frame)
3353
+ return analyticalGraphFailureAnswer(input, 'RESULT_CONTRACT_MISMATCH', 'The executed graph has no v2 analytical frame.', {
3354
+ phase: 'result_validation',
3355
+ compiledSql: sql,
3356
+ });
3357
+ const story = buildValidatedAnalyticalStory(frame, input.graph, executed);
3358
+ if ('failure' in story)
3359
+ return analyticalGraphFailureAnswer(input, 'RESULT_CONTRACT_MISMATCH', story.failure, {
3360
+ phase: 'result_validation',
3361
+ compiledSql: sql,
3362
+ });
3363
+ const result = analyticalGraphResultPayload(executed);
3364
+ return {
3365
+ kind: 'uncertified',
3366
+ sourceTier: 'dbt_manifest',
3367
+ certification: 'governed',
3368
+ reviewStatus: 'governed',
3369
+ confidence: 0.95,
3370
+ text: story.narrative.text,
3371
+ answer: story.narrative.text,
3372
+ proposedSql: sql,
3373
+ sql,
3374
+ ...(artifacts.length === 1 ? { dqlArtifact: artifacts[0] } : {}),
3375
+ result,
3376
+ analyticalExecutionGraph: input.graph,
3377
+ analyticalExecutionReceipt: executed.receipt,
3378
+ analyticalFacts: story.factSet,
3379
+ analyticalNarrative: story.narrative,
3380
+ governedAnalyticalGraphCompilationReceipt: input.compilation.receipt,
3381
+ citations: schemaCitations(input.schemaContext, 8),
3382
+ considered: input.considered,
3383
+ contextPack: input.input.contextPack,
3384
+ providerUsed: input.providerName,
3385
+ };
3386
+ }
3387
+ function normalizeAnalyticalSourceResult(input) {
3388
+ const columns = resultColumnNames(input.result) ?? [];
3389
+ const desired = [
3390
+ ...input.outputAliases.dimensions.map((dimension) => dimension.outputId),
3391
+ ...(input.outputAliases.metric ? [input.outputAliases.metric.outputId] : []),
3392
+ ];
3393
+ if (columns.length !== desired.length || input.sourceNames.length !== desired.length) {
3394
+ return { failure: `Expected ${desired.length} source columns but the adapter returned ${columns.length}.` };
3395
+ }
3396
+ const used = new Set();
3397
+ const mapping = desired.map((outputId, index) => {
3398
+ const candidates = [outputId, input.sourceNames[index]].map(normalizeOutputIdentity);
3399
+ const exact = columns.find((column) => !used.has(column) && candidates.includes(normalizeOutputIdentity(column)));
3400
+ const selected = exact ?? columns[index];
3401
+ if (selected)
3402
+ used.add(selected);
3403
+ return { outputId, sourceColumn: selected };
3404
+ });
3405
+ if (mapping.some((item) => !item.sourceColumn) || new Set(mapping.map((item) => item.sourceColumn)).size !== mapping.length) {
3406
+ return { failure: 'The adapter result columns cannot be mapped uniquely to the graph output aliases.' };
3407
+ }
3408
+ const rows = input.result.rows.map((row) => {
3409
+ const source = Array.isArray(row)
3410
+ ? Object.fromEntries(columns.map((column, index) => [column, row[index]]))
3411
+ : row && typeof row === 'object'
3412
+ ? row
3413
+ : {};
3414
+ return Object.fromEntries(mapping.map((item) => [item.outputId, source[item.sourceColumn]]));
3415
+ });
3416
+ return {
3417
+ source: {
3418
+ columns: desired,
3419
+ rows,
3420
+ receiptFingerprint: input.receiptFingerprint
3421
+ ?? input.result.executionReceipt?.resultFingerprint
3422
+ ?? hashAnalyticalValue({ sql: input.sql, columns, rows, rowCount: input.result.rowCount }),
3423
+ },
3424
+ };
3425
+ }
3426
+ function buildValidatedAnalyticalStory(frame, graph, result) {
3427
+ const facts = buildAnalyticalResultFacts({
3428
+ frame,
3429
+ graph,
3430
+ receipt: result.receipt,
3431
+ columns: result.columns,
3432
+ rows: result.rows,
3433
+ });
3434
+ if (facts.status === 'blocked')
3435
+ return { failure: facts.reason };
3436
+ const narrative = renderDeterministicAnalyticalNarrative({ frame, factSet: facts.factSet });
3437
+ const validation = validateAnalyticalNarrativeClaims({ factSet: facts.factSet, claims: narrative.claims });
3438
+ if (validation.status === 'invalid')
3439
+ return { failure: validation.reason };
3440
+ return { factSet: facts.factSet, narrative };
3441
+ }
3442
+ function analyticalGraphResultPayload(result) {
3443
+ return {
3444
+ columns: result.columns,
3445
+ rows: result.rows.map((row) => result.columns.map((column) => row[column])),
3446
+ rowCount: result.rows.length,
3447
+ };
3448
+ }
3449
+ function analyticalGraphFailureAnswer(input, code, reason, artifacts = {}) {
3450
+ const analyticalFailure = analyticalFailureForInput(input.input, {
3451
+ error: { code, message: reason },
3452
+ code: stableFailureCodeForGraph(code, reason),
3453
+ phase: artifacts.phase ?? analyticalFailurePhaseForGraphCode(code),
3454
+ runId: input.graph.graphId,
3455
+ dqlArtifact: artifacts.dqlArtifact,
3456
+ compiledSql: artifacts.compiledSql,
3457
+ failedBindings: artifacts.failedBindings,
3458
+ });
3459
+ const text = analyticalFailure.message;
3460
+ return {
3461
+ kind: 'no_answer',
3462
+ sourceTier: 'no_answer',
3463
+ certification: 'analyst_review_required',
3464
+ reviewStatus: 'none',
3465
+ confidence: 0,
3466
+ text,
3467
+ answer: text,
3468
+ executionError: analyticalFailure.message,
3469
+ refusalCode: 'grounding_gap',
3470
+ refusalDetails: { code, message: analyticalFailure.message },
3471
+ analyticalFailure,
3472
+ analyticalExecutionGraph: input.graph,
3473
+ ...(artifacts.compiledSql ? { proposedSql: artifacts.compiledSql, sql: artifacts.compiledSql } : {}),
3474
+ ...(artifacts.dqlArtifact ? { dqlArtifact: artifacts.dqlArtifact } : {}),
3475
+ citations: contextPackCitations(input.input.contextPack, 8),
3476
+ considered: input.considered,
3477
+ contextPack: input.input.contextPack,
3478
+ providerUsed: input.providerName,
3479
+ };
3480
+ }
3481
+ function analyticalFailureForInput(input, diagnostic) {
3482
+ const plan = input.resolvedAnalyticalPlan;
3483
+ return createAnalyticalFailure({
3484
+ error: diagnostic.error,
3485
+ code: diagnostic.code,
3486
+ phase: diagnostic.phase,
3487
+ snapshotId: plan?.snapshotId ?? input.contextPack?.knowledgeLens.snapshotId ?? 'snapshot-unavailable',
3488
+ runId: diagnostic.runId ?? plan?.planId,
3489
+ planFingerprint: plan?.fingerprint,
3490
+ dqlSource: diagnostic.dqlArtifact?.source,
3491
+ compiledSql: diagnostic.compiledSql ?? diagnostic.dqlArtifact?.compiledSql,
3492
+ failedBindings: diagnostic.failedBindings,
3493
+ });
3494
+ }
3495
+ function stableFailureCodeForGraph(code, reason) {
3496
+ if (code === 'SOURCE_RESULT_MISSING'
3497
+ || code === 'SUB_RECEIPT_REQUIRED'
3498
+ || code === 'RESULT_CONTRACT_MISMATCH'
3499
+ || code === 'DECIMAL_VALUE_REQUIRED'
3500
+ || code === 'ROW_BOUND_EXCEEDED')
3501
+ return 'RESULT_CONTRACT_MISMATCH';
3502
+ if (code === 'SNAPSHOT_MISMATCH')
3503
+ return 'SNAPSHOT_DRIFT';
3504
+ return classifyAnalyticalFailure({ code, message: reason }, code === 'EXECUTION_FAILED' ? 'COMPILATION_FAILED' : 'COMPILATION_FAILED');
3505
+ }
3506
+ function analyticalFailurePhaseForGraphCode(code) {
3507
+ if (code === 'SOURCE_RESULT_MISSING'
3508
+ || code === 'SUB_RECEIPT_REQUIRED'
3509
+ || code === 'RESULT_CONTRACT_MISMATCH'
3510
+ || code === 'DECIMAL_VALUE_REQUIRED'
3511
+ || code === 'ROW_BOUND_EXCEEDED')
3512
+ return 'result_validation';
3513
+ if (code === 'EXECUTION_FAILED')
3514
+ return 'execution';
3515
+ return 'compilation';
3516
+ }
3517
+ function renderAnalyticalStatements(statements) {
3518
+ if (statements.length <= 1)
3519
+ return statements[0] ?? '';
3520
+ return statements.map((statement, index) => `-- analytical source ${index + 1}\n${statement}`).join('\n\n');
3521
+ }
3522
+ function normalizeOutputIdentity(value) {
3523
+ return value
3524
+ .replace(/["`[\]]/g, '')
3525
+ .split('.')
3526
+ .at(-1)
3527
+ .toLowerCase()
3528
+ .replace(/[^a-z0-9]+/g, '_')
3529
+ .replace(/^_+|_+$/g, '');
3530
+ }
3531
+ function hashAnalyticalValue(value) {
3532
+ return createHash('sha256').update(stableAnalyticalValue(value)).digest('hex');
3533
+ }
3534
+ function stableAnalyticalValue(value) {
3535
+ if (value === undefined || value === null)
3536
+ return 'null';
3537
+ if (typeof value === 'bigint')
3538
+ return JSON.stringify(value.toString());
3539
+ if (typeof value !== 'object')
3540
+ return JSON.stringify(value);
3541
+ if (Array.isArray(value))
3542
+ return `[${value.map(stableAnalyticalValue).join(',')}]`;
3543
+ const record = value;
3544
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableAnalyticalValue(record[key])}`).join(',')}}`;
3545
+ }
2715
3546
  const SYSTEM_PROMPT = `You are the DQL Analytics Agent.
2716
3547
 
2717
3548
  Rules: