@duckcodeailabs/dql-agent 1.9.5 → 1.10.1

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 (94) hide show
  1. package/dist/agent-run-engine.d.ts +13 -0
  2. package/dist/agent-run-engine.d.ts.map +1 -1
  3. package/dist/agent-run-engine.js +65 -2
  4. package/dist/agent-run-engine.js.map +1 -1
  5. package/dist/analytical-compatibility.d.ts +61 -0
  6. package/dist/analytical-compatibility.d.ts.map +1 -0
  7. package/dist/analytical-compatibility.js +515 -0
  8. package/dist/analytical-compatibility.js.map +1 -0
  9. package/dist/analytical-execution-graph.d.ts +174 -0
  10. package/dist/analytical-execution-graph.d.ts.map +1 -0
  11. package/dist/analytical-execution-graph.js +599 -0
  12. package/dist/analytical-execution-graph.js.map +1 -0
  13. package/dist/analytical-failure-repair.d.ts +91 -0
  14. package/dist/analytical-failure-repair.d.ts.map +1 -0
  15. package/dist/analytical-failure-repair.js +325 -0
  16. package/dist/analytical-failure-repair.js.map +1 -0
  17. package/dist/analytical-frame.d.ts +19 -0
  18. package/dist/analytical-frame.d.ts.map +1 -0
  19. package/dist/analytical-frame.js +299 -0
  20. package/dist/analytical-frame.js.map +1 -0
  21. package/dist/analytical-period-resolution.d.ts +53 -0
  22. package/dist/analytical-period-resolution.d.ts.map +1 -0
  23. package/dist/analytical-period-resolution.js +218 -0
  24. package/dist/analytical-period-resolution.js.map +1 -0
  25. package/dist/analytical-readiness.d.ts +17 -0
  26. package/dist/analytical-readiness.d.ts.map +1 -0
  27. package/dist/analytical-readiness.js +95 -0
  28. package/dist/analytical-readiness.js.map +1 -0
  29. package/dist/analytical-result-facts.d.ts +79 -0
  30. package/dist/analytical-result-facts.d.ts.map +1 -0
  31. package/dist/analytical-result-facts.js +301 -0
  32. package/dist/analytical-result-facts.js.map +1 -0
  33. package/dist/answer-loop.d.ts +82 -2
  34. package/dist/answer-loop.d.ts.map +1 -1
  35. package/dist/answer-loop.js +1129 -38
  36. package/dist/answer-loop.js.map +1 -1
  37. package/dist/embeddings/provider.d.ts +1 -1
  38. package/dist/embeddings/provider.d.ts.map +1 -1
  39. package/dist/embeddings/provider.js.map +1 -1
  40. package/dist/governed-relational-compiler.d.ts +189 -0
  41. package/dist/governed-relational-compiler.d.ts.map +1 -0
  42. package/dist/governed-relational-compiler.js +458 -0
  43. package/dist/governed-relational-compiler.js.map +1 -0
  44. package/dist/index.d.ts +25 -5
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +15 -4
  47. package/dist/index.js.map +1 -1
  48. package/dist/intent-controller.d.ts +5 -0
  49. package/dist/intent-controller.d.ts.map +1 -1
  50. package/dist/intent-controller.js.map +1 -1
  51. package/dist/kg/build.d.ts +2 -2
  52. package/dist/kg/build.d.ts.map +1 -1
  53. package/dist/kg/build.js +329 -9
  54. package/dist/kg/build.js.map +1 -1
  55. package/dist/meaning-resolution.d.ts +17 -0
  56. package/dist/meaning-resolution.d.ts.map +1 -1
  57. package/dist/meaning-resolution.js +83 -4
  58. package/dist/meaning-resolution.js.map +1 -1
  59. package/dist/metadata/analysis-planner.d.ts.map +1 -1
  60. package/dist/metadata/analysis-planner.js +37 -7
  61. package/dist/metadata/analysis-planner.js.map +1 -1
  62. package/dist/metadata/catalog.d.ts +86 -3
  63. package/dist/metadata/catalog.d.ts.map +1 -1
  64. package/dist/metadata/catalog.js +607 -36
  65. package/dist/metadata/catalog.js.map +1 -1
  66. package/dist/metadata/meaning-evidence.d.ts +18 -2
  67. package/dist/metadata/meaning-evidence.d.ts.map +1 -1
  68. package/dist/metadata/meaning-evidence.js +182 -7
  69. package/dist/metadata/meaning-evidence.js.map +1 -1
  70. package/dist/plan-execution-adapter.d.ts +131 -0
  71. package/dist/plan-execution-adapter.d.ts.map +1 -0
  72. package/dist/plan-execution-adapter.js +447 -0
  73. package/dist/plan-execution-adapter.js.map +1 -0
  74. package/dist/research-governance.d.ts +56 -0
  75. package/dist/research-governance.d.ts.map +1 -0
  76. package/dist/research-governance.js +68 -0
  77. package/dist/research-governance.js.map +1 -0
  78. package/dist/research-loop.d.ts +14 -0
  79. package/dist/research-loop.d.ts.map +1 -1
  80. package/dist/research-loop.js +77 -23
  81. package/dist/research-loop.js.map +1 -1
  82. package/dist/resolved-analytical-plan.d.ts +123 -0
  83. package/dist/resolved-analytical-plan.d.ts.map +1 -0
  84. package/dist/resolved-analytical-plan.js +400 -0
  85. package/dist/resolved-analytical-plan.js.map +1 -0
  86. package/dist/router.d.ts +3 -0
  87. package/dist/router.d.ts.map +1 -1
  88. package/dist/router.js +195 -25
  89. package/dist/router.js.map +1 -1
  90. package/dist/skills/loader.d.ts +17 -0
  91. package/dist/skills/loader.d.ts.map +1 -1
  92. package/dist/skills/loader.js +62 -4
  93. package/dist/skills/loader.js.map +1 -1
  94. 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';
@@ -41,6 +42,13 @@ import { questionTypeFromText } from './meaning-resolution.js';
41
42
  import { cascadeTraceToEvidenceRouteSteps, createCascadeAnswerResult, createCascadeTrace, } from './cascade/cascade.js';
42
43
  import { shouldClarifyBeforeGeneration } from './cascade/triage.js';
43
44
  import { stampTrustLabel } from './trust/stamp.js';
45
+ import { deriveResolvedAnalyticalPlan } from './resolved-analytical-plan.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';
44
52
  import { QUICK_PROMPT_CONTEXT_BUDGET, canUseLaneRepair, cascadeBudgetTrace, createCascadeBudgetState, deepAlternativeCountForQuestion, promptContextBudgetForQuestion, proposalToolBudgetForQuestion, recordLaneRepair, } from './cascade/budgets.js';
45
53
  /**
46
54
  * Physical column names whose sampled runtime values include `value`. Used by the
@@ -460,13 +468,281 @@ function tryCrossResultAnswer(input) {
460
468
  }
461
469
  return answer;
462
470
  }
471
+ /**
472
+ * Materialize exactly the Skill IDs/hashes selected in the immutable context
473
+ * pack. No downstream answer route may rerun trigger/domain selection or add a
474
+ * Skill that was not recorded in the KnowledgeLens.
475
+ *
476
+ * Acceptance: SKILL-003, AGT-013.
477
+ */
478
+ export function materializeKnowledgeLensSkills(contextPack, available) {
479
+ const byIdentity = new Map();
480
+ for (const skill of available) {
481
+ byIdentity.set(skill.qualifiedId ?? skill.id, skill);
482
+ if (!byIdentity.has(skill.id))
483
+ byIdentity.set(skill.id, skill);
484
+ }
485
+ return (contextPack.skills ?? []).map((selected) => {
486
+ const identity = selected.qualifiedId ?? selected.id;
487
+ const source = byIdentity.get(identity) ?? byIdentity.get(selected.id);
488
+ if (source) {
489
+ // Guidance is the immutable, bounded snapshot body. Retain structured
490
+ // source fields but never reread or inject a newer disk body mid-run.
491
+ return { ...source, body: selected.guidance };
492
+ }
493
+ return {
494
+ id: selected.id,
495
+ localId: selected.id,
496
+ qualifiedId: selected.qualifiedId,
497
+ scope: 'project',
498
+ domain: selected.domain,
499
+ domains: selected.domains,
500
+ modelAreaRefs: selected.modelAreaRefs,
501
+ kind: selected.kind,
502
+ status: selected.status,
503
+ owner: selected.owner,
504
+ triggers: selected.triggers,
505
+ exclusions: selected.exclusions,
506
+ description: selected.description,
507
+ preferredMetrics: selected.preferredMetrics,
508
+ preferredBlocks: selected.preferredBlocks,
509
+ preferredDimensions: selected.preferredDimensions,
510
+ requiredFilters: selected.requiredFilters,
511
+ clarifyWhen: selected.clarifyWhen,
512
+ examples: [],
513
+ sourceRefs: selected.sourceRefs,
514
+ vocabulary: selected.vocabulary,
515
+ body: selected.guidance,
516
+ sourcePath: selected.sourcePath ?? `snapshot:${selected.objectKey}`,
517
+ };
518
+ });
519
+ }
463
520
  export async function answer(input) {
464
- // Cross-result follow-up ("of these, the average") — computed from the prior
465
- // rows, before the cascade, so it never re-queries or times out.
466
- const crossResult = tryCrossResultAnswer(input);
467
- if (crossResult)
468
- return crossResult;
469
- const result = applyHollowAnswerGate(await runAnswerLoop(input));
521
+ const inheritedPlan = !input.resolvedAnalyticalPlan
522
+ && input.followUp?.priorResolvedAnalyticalPlan
523
+ && input.followUp.resolvedAnalyticalPlanDelta
524
+ ? deriveResolvedAnalyticalPlan(input.followUp.priorResolvedAnalyticalPlan, input.followUp.resolvedAnalyticalPlanDelta)
525
+ : undefined;
526
+ let normalizedInput = inheritedPlan
527
+ ? { ...input, resolvedAnalyticalPlan: inheritedPlan }
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
+ }
628
+ const resolvedPlanExecutionBinding = normalizedInput.resolvedAnalyticalPlan
629
+ ? adaptResolvedAnalyticalPlan({
630
+ plan: normalizedInput.resolvedAnalyticalPlan,
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,
680
+ semanticLayer: normalizedInput.semanticLayer,
681
+ expectedSnapshotId: normalizedInput.contextPack?.knowledgeLens.snapshotId,
682
+ })
683
+ : undefined;
684
+ const planBoundInput = resolvedPlanExecutionBinding || analyticalExecutionGraph || analyticalExecutionGraphFailure
685
+ ? { ...normalizedInput, resolvedPlanExecutionBinding }
686
+ : normalizedInput;
687
+ const relationalSchemaContext = planBoundInput.schemaContext?.length
688
+ ? planBoundInput.schemaContext
689
+ : (planBoundInput.contextPack?.allowedSqlContext.relations ?? []).map((relation) => ({
690
+ relation: relation.relation,
691
+ name: relation.name,
692
+ columns: relation.columns.map((column) => ({
693
+ name: column.name,
694
+ type: column.type,
695
+ description: column.description,
696
+ })),
697
+ }));
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'
717
+ && planBoundInput.resolvedAnalyticalPlan.capability === 'governed_relational'
718
+ ? compileGovernedRelationalPlan({
719
+ plan: planBoundInput.resolvedAnalyticalPlan,
720
+ registry: relationalRegistry,
721
+ driver: planBoundInput.semanticDriver,
722
+ })
723
+ : undefined;
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
+ }
736
+ : planBoundInput;
737
+ const executionInput = compiledInput.contextPack
738
+ ? {
739
+ ...compiledInput,
740
+ skills: materializeKnowledgeLensSkills(compiledInput.contextPack, compiledInput.skills ?? []),
741
+ skillsSelectionLocked: true,
742
+ }
743
+ : compiledInput;
744
+ const loopResult = await runAnswerLoop(executionInput);
745
+ const result = applyHollowAnswerGate(validateCertifiedAnalyticalGraphAnswer(loopResult, executionInput.analyticalExecutionGraph, executionInput.resolvedAnalyticalPlan?.analyticalFrame));
470
746
  // Attach the canonical trust label once, at the single exit point, so every
471
747
  // return site inside runAnswerLoop stays untouched and backward compatible.
472
748
  // Freshness-aware trust: for a certified answer, fold the source block's data
@@ -495,6 +771,9 @@ export async function answer(input) {
495
771
  ...publicResult,
496
772
  domainContext: input.domainContext,
497
773
  intentDecision,
774
+ resolvedAnalyticalPlan: executionInput.resolvedAnalyticalPlan,
775
+ executablePlan: executionInput.resolvedPlanExecutionBinding,
776
+ analyticalFreshnessObservation: executionInput.analyticalFreshnessObservation,
498
777
  trustLabelInfo,
499
778
  provenanceFooter: buildProvenanceFooter(result, trustLabelInfo),
500
779
  cascade: publicResult.cascade ?? createCascadeAnswerResult({
@@ -514,15 +793,17 @@ export async function answer(input) {
514
793
  // Stamp the SELECTED skills that shaped the answer (transparency). Computed
515
794
  // here so every return site inside runAnswerLoop stays untouched.
516
795
  appliedSkills: result.appliedSkills ??
517
- selectRelevantSkills(input.skills ?? [], input.question, {
518
- userId: input.userId ?? null,
519
- modelAreaIds: input.domainContext?.modelAreaId ? [input.domainContext.modelAreaId] : [],
520
- domains: Array.from(new Set([
521
- ...domainContextSearchDomains(input.domainContext),
522
- ...(input.domain ? [input.domain] : []),
523
- ...(input.contextPack?.objects ?? []).slice(0, 20).flatMap((object) => object.domain ? [object.domain] : []),
524
- ])),
525
- }).map((s) => ({
796
+ (executionInput.skillsSelectionLocked
797
+ ? executionInput.skills ?? []
798
+ : selectRelevantSkills(executionInput.skills ?? [], executionInput.question, {
799
+ userId: executionInput.userId ?? null,
800
+ modelAreaIds: executionInput.domainContext?.modelAreaId ? [executionInput.domainContext.modelAreaId] : [],
801
+ domains: Array.from(new Set([
802
+ ...domainContextSearchDomains(executionInput.domainContext),
803
+ ...(executionInput.domain ? [executionInput.domain] : []),
804
+ ...(executionInput.contextPack?.objects ?? []).slice(0, 20).flatMap((object) => object.domain ? [object.domain] : []),
805
+ ])),
806
+ })).map((s) => ({
526
807
  id: s.id,
527
808
  description: s.description,
528
809
  })),
@@ -614,11 +895,13 @@ async function runAnswerLoop(input) {
614
895
  ? (input.contextPack?.objects ?? []).slice(0, 20).flatMap((object) => object.domain ? [object.domain] : [])
615
896
  : []),
616
897
  ]));
617
- const selectedSkills = selectRelevantSkills(skills, question, {
618
- userId: userId ?? null,
619
- domains: inferredDomains,
620
- modelAreaIds: input.domainContext?.modelAreaId ? [input.domainContext.modelAreaId] : [],
621
- });
898
+ const selectedSkills = input.skillsSelectionLocked
899
+ ? skills
900
+ : selectRelevantSkills(skills, question, {
901
+ userId: userId ?? null,
902
+ domains: inferredDomains,
903
+ modelAreaIds: input.domainContext?.modelAreaId ? [input.domainContext.modelAreaId] : [],
904
+ });
622
905
  const effectiveBlockHints = Array.from(new Set([
623
906
  ...blockHints,
624
907
  // Only selected skills may influence block ranking. Previously a preferred
@@ -654,6 +937,232 @@ async function runAnswerLoop(input) {
654
937
  // columns—not copy a customer-grain worked example into a product-grain ask.
655
938
  const promptContextPack = contextPackForRequestedShape(scopedContextPack, question, questionPlan, kg);
656
939
  const repairBudgetState = createCascadeBudgetState(input.cascadeBudgetModel);
940
+ const authoritativePlanBinding = input.resolvedAnalyticalPlan?.mode === 'authoritative'
941
+ ? input.resolvedPlanExecutionBinding
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
+ }
1073
+ const governedRelationalCompilation = input.governedRelationalCompilation;
1074
+ if (governedRelationalCompilation?.status === 'blocked') {
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;
1084
+ return {
1085
+ kind: 'no_answer',
1086
+ sourceTier: 'no_answer',
1087
+ certification: 'analyst_review_required',
1088
+ reviewStatus: 'none',
1089
+ confidence: 0,
1090
+ text,
1091
+ answer: text,
1092
+ refusalCode: governedRelationalCompilation.code.startsWith('RELATIONSHIP_') ? 'modeling_gap' : 'grounding_gap',
1093
+ refusalDetails: { code: 'grounding_gap', message: analyticalFailure.message },
1094
+ analyticalFailure,
1095
+ citations: contextPackCitations(input.contextPack, 8),
1096
+ considered,
1097
+ contextPack: input.contextPack,
1098
+ providerUsed: provider.name,
1099
+ };
1100
+ }
1101
+ if (governedRelationalCompilation?.status === 'compiled') {
1102
+ const dqlArtifact = renderGovernedRelationalDqlArtifact(governedRelationalCompilation);
1103
+ let result;
1104
+ let executionError;
1105
+ let analyticalFailure;
1106
+ let receipt = governedRelationalCompilation.receipt;
1107
+ if (input.executeDqlArtifact) {
1108
+ try {
1109
+ result = await input.executeDqlArtifact(dqlArtifact);
1110
+ receipt = finalizeGovernedCompilationReceipt(receipt, result);
1111
+ }
1112
+ catch (error) {
1113
+ analyticalFailure = analyticalFailureForInput(input, {
1114
+ error,
1115
+ phase: 'execution',
1116
+ dqlArtifact,
1117
+ compiledSql: governedRelationalCompilation.sql,
1118
+ });
1119
+ executionError = analyticalFailure.message;
1120
+ }
1121
+ }
1122
+ const text = executionError
1123
+ ? `The governed relational query compiled, but execution failed: ${executionError}`
1124
+ : result
1125
+ ? `Compiled and executed the snapshot-bound governed relational plan. Returned ${result.rowCount} row${result.rowCount === 1 ? '' : 's'}.`
1126
+ : 'Compiled the snapshot-bound governed relational plan. Execution was not requested.';
1127
+ return {
1128
+ kind: executionError ? 'no_answer' : 'uncertified',
1129
+ sourceTier: executionError ? 'no_answer' : 'dbt_manifest',
1130
+ certification: executionError ? 'analyst_review_required' : 'governed',
1131
+ reviewStatus: executionError ? 'analyst_review_required' : 'governed',
1132
+ confidence: executionError ? 0 : 0.9,
1133
+ text,
1134
+ answer: text,
1135
+ ...(executionError ? { executionError, refusalCode: 'grounding_gap' } : {}),
1136
+ ...(analyticalFailure ? { analyticalFailure } : {}),
1137
+ proposedSql: governedRelationalCompilation.sql,
1138
+ sql: governedRelationalCompilation.sql,
1139
+ dqlArtifact,
1140
+ result,
1141
+ governedCompilationReceipt: receipt,
1142
+ citations: schemaCitations(schemaContext, 8),
1143
+ considered,
1144
+ contextPack: input.contextPack,
1145
+ providerUsed: provider.name,
1146
+ };
1147
+ }
1148
+ if (authoritativePlanBinding?.status === 'blocked') {
1149
+ const text = `The resolved analytical plan cannot execute safely: ${authoritativePlanBinding.reason}`;
1150
+ return {
1151
+ kind: 'no_answer',
1152
+ sourceTier: 'no_answer',
1153
+ certification: 'analyst_review_required',
1154
+ reviewStatus: 'none',
1155
+ confidence: 0,
1156
+ text,
1157
+ answer: text,
1158
+ refusalCode: authoritativePlanBinding.code === 'PLAN_BLOCKED' ? 'ambiguous' : 'grounding_gap',
1159
+ refusalDetails: { code: authoritativePlanBinding.code, message: authoritativePlanBinding.reason },
1160
+ citations: contextPackCitations(input.contextPack, 8),
1161
+ considered,
1162
+ contextPack: input.contextPack,
1163
+ providerUsed: provider.name,
1164
+ };
1165
+ }
657
1166
  const fallbackIntent = classifyAgentIntent({
658
1167
  question,
659
1168
  followUp: input.followUp,
@@ -673,16 +1182,25 @@ async function runAnswerLoop(input) {
673
1182
  if (node && !semanticMetricNodes.some((candidate) => candidate.nodeId === node.nodeId))
674
1183
  semanticMetricNodes.push(node);
675
1184
  }
676
- const preferredSemanticMetric = resolvePreferredSemanticMetric([input.preferredExecutionId, ...(input.preferredEvidenceIds ?? [])], semanticMetricNodes, kg);
1185
+ const authoritativeSemanticBinding = authoritativePlanBinding?.status === 'ready'
1186
+ && authoritativePlanBinding.kind === 'semantic'
1187
+ ? authoritativePlanBinding
1188
+ : undefined;
1189
+ const preferredSemanticMetric = authoritativeSemanticBinding?.metricNode
1190
+ ?? (input.resolvedAnalyticalPlan?.mode === 'authoritative'
1191
+ ? undefined
1192
+ : resolvePreferredSemanticMetric([input.preferredExecutionId, ...(input.preferredEvidenceIds ?? [])], semanticMetricNodes, kg));
677
1193
  const semanticLayerForExec = input.semanticLayer;
678
1194
  const canExecuteSemanticMetricForMatch = input.canExecuteSemanticMetric
679
1195
  ?? (semanticLayerForExec ? (name) => semanticLayerForExec.canComposeMetric(name) : undefined);
680
1196
  let semanticMetricMatch = preferredSemanticMetric
681
1197
  ? { metric: preferredSemanticMetric, score: 1, basis: 'name' }
682
- : await matchSemanticMetric(semanticQuestion, semanticMetricNodes, {
683
- measureTerms: [...questionPlan.requestedShape.measures, ...questionPlan.metricTerms],
684
- ...(canExecuteSemanticMetricForMatch ? { canExecute: canExecuteSemanticMetricForMatch } : {}),
685
- }).catch(() => null);
1198
+ : input.resolvedAnalyticalPlan?.mode === 'authoritative'
1199
+ ? null
1200
+ : await matchSemanticMetric(semanticQuestion, semanticMetricNodes, {
1201
+ measureTerms: [...questionPlan.requestedShape.measures, ...questionPlan.metricTerms],
1202
+ ...(canExecuteSemanticMetricForMatch ? { canExecute: canExecuteSemanticMetricForMatch } : {}),
1203
+ }).catch(() => null);
686
1204
  // Stage 1: certified artifact match. Blocks can be executed; dashboards,
687
1205
  // Apps, and notebooks are returned as governed citations/navigation targets.
688
1206
  const drilldownCertifiedHit = input.followUp?.kind === 'drilldown'
@@ -726,8 +1244,16 @@ async function runAnswerLoop(input) {
726
1244
  excludedArtifactIds,
727
1245
  kg,
728
1246
  }) : null;
729
- let artifactHit = drilldownCertifiedHit ?? unsafeCatalogCertifiedHit
730
- ?? (catalogCertifiedHit ? null : fallbackCertifiedHit);
1247
+ const authoritativeCertifiedBinding = authoritativePlanBinding?.status === 'ready'
1248
+ && authoritativePlanBinding.kind === 'certified'
1249
+ ? authoritativePlanBinding
1250
+ : undefined;
1251
+ let artifactHit = authoritativeCertifiedBinding
1252
+ ? { node: authoritativeCertifiedBinding.node, score: 1 }
1253
+ : input.resolvedAnalyticalPlan?.mode === 'authoritative'
1254
+ ? null
1255
+ : drilldownCertifiedHit ?? unsafeCatalogCertifiedHit
1256
+ ?? (catalogCertifiedHit ? null : fallbackCertifiedHit);
731
1257
  let certifiedExecutionFallback;
732
1258
  // Certified remains first when it actually covers the question. If the
733
1259
  // retrieved block does not fit but a governed semantic metric does, never
@@ -875,8 +1401,10 @@ async function runAnswerLoop(input) {
875
1401
  const dqlArtifact = buildCertifiedBlockDqlArtifact(artifactHit.node, result, questionPlan.requestedShape.topN?.scope === 'per_group'
876
1402
  ? undefined
877
1403
  : questionPlan.requestedShape.topN?.n);
1404
+ const authoritativeCertifiedFailure = Boolean(authoritativeCertifiedBinding && executionError);
878
1405
  const recoverableCertifiedFailure = artifactHit.node.kind === 'block'
879
1406
  && executionError !== undefined
1407
+ && !authoritativeCertifiedFailure
880
1408
  && isRetryableCertifiedExecutionError(executionError);
881
1409
  if (recoverableCertifiedFailure) {
882
1410
  // A certified artifact is trusted evidence, not an obligation to return a
@@ -889,16 +1417,17 @@ async function runAnswerLoop(input) {
889
1417
  }
890
1418
  else {
891
1419
  return {
892
- kind: certifiedShapePassed ? 'certified' : 'uncertified',
893
- sourceTier,
1420
+ kind: authoritativeCertifiedFailure ? 'no_answer' : certifiedShapePassed ? 'certified' : 'uncertified',
1421
+ sourceTier: authoritativeCertifiedFailure ? 'no_answer' : sourceTier,
894
1422
  certification: certifiedShapePassed ? 'certified' : 'analyst_review_required',
895
- reviewStatus: certifiedShapePassed ? 'certified' : 'analyst_review_required',
1423
+ reviewStatus: authoritativeCertifiedFailure ? 'none' : certifiedShapePassed ? 'certified' : 'analyst_review_required',
896
1424
  confidence: certifiedShapePassed ? 0.95 : 0.45,
897
1425
  text,
898
1426
  answer: text,
899
1427
  block: artifactHit.node.kind === 'block' ? artifactHit.node : undefined,
900
1428
  result,
901
1429
  executionError,
1430
+ ...(authoritativeCertifiedFailure ? { refusalCode: 'grounding_gap' } : {}),
902
1431
  sql: result?.sql,
903
1432
  dqlArtifact,
904
1433
  trustLabel: certifiedShapePassed ? input.contextPack?.trustLabel ?? 'certified' : 'mixed',
@@ -1095,7 +1624,44 @@ async function runAnswerLoop(input) {
1095
1624
  let semanticBridgeAnswer;
1096
1625
  let semanticRuntimeFailure;
1097
1626
  let semanticRuntimeCompiledAnswer = false;
1098
- if (input.semanticLayer && semanticMetricMatch) {
1627
+ if (authoritativeSemanticBinding && input.semanticLayer) {
1628
+ const selection = authoritativeSemanticBinding.selection;
1629
+ semanticBridgeAnswer = composeSemanticQueryFromMembers({
1630
+ semanticLayer: input.semanticLayer,
1631
+ question,
1632
+ selection,
1633
+ ...(input.semanticDriver ? { driver: input.semanticDriver } : {}),
1634
+ ...(input.semanticTableMapping ? { tableMapping: input.semanticTableMapping } : {}),
1635
+ });
1636
+ if (!semanticBridgeAnswer && input.semanticQueryCompiler) {
1637
+ try {
1638
+ const compiled = await input.semanticQueryCompiler(selection);
1639
+ semanticBridgeAnswer = composeSemanticQueryFromCompiledMembers({
1640
+ semanticLayer: input.semanticLayer,
1641
+ question,
1642
+ selection: compiled.selection ?? selection,
1643
+ sql: compiled.sql,
1644
+ });
1645
+ semanticRuntimeCompiledAnswer = Boolean(semanticBridgeAnswer && compiled.engine !== 'native');
1646
+ }
1647
+ catch (error) {
1648
+ semanticRuntimeFailure = error instanceof Error ? error.message : String(error);
1649
+ }
1650
+ }
1651
+ if (semanticBridgeAnswer) {
1652
+ semanticBridgeToolCalls.push({
1653
+ name: 'compile_resolved_analytical_plan',
1654
+ status: 'checked',
1655
+ inputSummary: `plan: ${authoritativeSemanticBinding.planId}; metric: ${selection.metrics.join(', ')}`,
1656
+ outputSummary: 'Compiled the exact snapshot-bound member selection without rematching.',
1657
+ order: 1,
1658
+ });
1659
+ }
1660
+ else if (!semanticRuntimeFailure) {
1661
+ semanticRuntimeFailure = `The exact plan ${authoritativeSemanticBinding.planId} is not composable by the pinned semantic runtime.`;
1662
+ }
1663
+ }
1664
+ if (!authoritativeSemanticBinding && input.semanticLayer && semanticMetricMatch) {
1099
1665
  semanticBridgeAnswer = composeSemanticQueryForQuestion({
1100
1666
  semanticLayer: input.semanticLayer,
1101
1667
  question,
@@ -2146,7 +2712,7 @@ async function runAnswerLoop(input) {
2146
2712
  catch (err) {
2147
2713
  executionError = err instanceof Error ? err.message : String(err);
2148
2714
  }
2149
- if (executionError && !fanoutContradiction) {
2715
+ if (executionError && !fanoutContradiction && !authoritativeSemanticBinding) {
2150
2716
  if (isRetryableGeneratedSqlError(executionError)) {
2151
2717
  const localRepairSql = repairGeneratedSqlLocally(parsed.sql, executionError, schemaContext);
2152
2718
  if (localRepairSql && canUseLaneRepair(repairBudgetState, 'execution')) {
@@ -2368,19 +2934,21 @@ async function runAnswerLoop(input) {
2368
2934
  ? semanticMetricMatch?.metric.certification
2369
2935
  : undefined;
2370
2936
  const certifiedMetricAnswer = semanticMetricCertification === 'certified' || semanticMetricCertification === 'reviewed';
2937
+ const governedMetricExecutionFailure = governedMetricAnswer && Boolean(executionError);
2371
2938
  return {
2372
- kind: 'uncertified',
2373
- sourceTier: governedMetricAnswer ? 'semantic_layer' : activeTier,
2374
- certification: governedMetricAnswer ? 'governed' : 'ai_generated',
2375
- reviewStatus: governedMetricAnswer ? 'governed' : 'draft_ready',
2939
+ kind: governedMetricExecutionFailure ? 'no_answer' : 'uncertified',
2940
+ sourceTier: governedMetricExecutionFailure ? 'no_answer' : governedMetricAnswer ? 'semantic_layer' : activeTier,
2941
+ certification: governedMetricExecutionFailure ? 'analyst_review_required' : governedMetricAnswer ? 'governed' : 'ai_generated',
2942
+ reviewStatus: governedMetricExecutionFailure ? 'none' : governedMetricAnswer ? 'governed' : 'draft_ready',
2376
2943
  semanticMetricCertification,
2377
- confidence: certifiedMetricAnswer ? 0.8 : governedMetricAnswer ? 0.72 : 0.55,
2944
+ confidence: governedMetricExecutionFailure ? 0 : certifiedMetricAnswer ? 0.8 : governedMetricAnswer ? 0.72 : 0.55,
2378
2945
  text: generatedText,
2379
2946
  answer: generatedText,
2380
2947
  proposedSql: parsed.sql,
2381
2948
  sql: parsed.sql,
2382
2949
  result,
2383
2950
  executionError,
2951
+ ...(governedMetricExecutionFailure ? { refusalCode: 'grounding_gap' } : {}),
2384
2952
  suggestedViz: parsed.viz ?? 'table',
2385
2953
  dqlArtifact: answerDqlArtifact,
2386
2954
  draftBlock,
@@ -2452,6 +3020,529 @@ function resultColumnNames(result) {
2452
3020
  .filter(Boolean);
2453
3021
  return columns?.length ? columns : undefined;
2454
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
+ }
2455
3546
  const SYSTEM_PROMPT = `You are the DQL Analytics Agent.
2456
3547
 
2457
3548
  Rules: