@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.
- package/dist/agent-run-engine.d.ts.map +1 -1
- package/dist/agent-run-engine.js +6 -1
- package/dist/agent-run-engine.js.map +1 -1
- package/dist/analytical-compatibility.d.ts +61 -0
- package/dist/analytical-compatibility.d.ts.map +1 -0
- package/dist/analytical-compatibility.js +515 -0
- package/dist/analytical-compatibility.js.map +1 -0
- package/dist/analytical-execution-graph.d.ts +174 -0
- package/dist/analytical-execution-graph.d.ts.map +1 -0
- package/dist/analytical-execution-graph.js +599 -0
- package/dist/analytical-execution-graph.js.map +1 -0
- package/dist/analytical-failure-repair.d.ts +91 -0
- package/dist/analytical-failure-repair.d.ts.map +1 -0
- package/dist/analytical-failure-repair.js +325 -0
- package/dist/analytical-failure-repair.js.map +1 -0
- package/dist/analytical-frame.d.ts +19 -0
- package/dist/analytical-frame.d.ts.map +1 -0
- package/dist/analytical-frame.js +299 -0
- package/dist/analytical-frame.js.map +1 -0
- package/dist/analytical-period-resolution.d.ts +53 -0
- package/dist/analytical-period-resolution.d.ts.map +1 -0
- package/dist/analytical-period-resolution.js +218 -0
- package/dist/analytical-period-resolution.js.map +1 -0
- package/dist/analytical-readiness.d.ts +17 -0
- package/dist/analytical-readiness.d.ts.map +1 -0
- package/dist/analytical-readiness.js +95 -0
- package/dist/analytical-readiness.js.map +1 -0
- package/dist/analytical-result-facts.d.ts +79 -0
- package/dist/analytical-result-facts.d.ts.map +1 -0
- package/dist/analytical-result-facts.js +301 -0
- package/dist/analytical-result-facts.js.map +1 -0
- package/dist/answer-loop.d.ts +51 -4
- package/dist/answer-loop.d.ts.map +1 -1
- package/dist/answer-loop.js +866 -35
- package/dist/answer-loop.js.map +1 -1
- package/dist/governed-relational-compiler.d.ts +51 -0
- package/dist/governed-relational-compiler.d.ts.map +1 -1
- package/dist/governed-relational-compiler.js +144 -1
- package/dist/governed-relational-compiler.js.map +1 -1
- package/dist/index.d.ts +16 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/kg/build.d.ts +2 -2
- package/dist/kg/build.d.ts.map +1 -1
- package/dist/kg/build.js +270 -13
- package/dist/kg/build.js.map +1 -1
- package/dist/meaning-resolution.d.ts +13 -0
- package/dist/meaning-resolution.d.ts.map +1 -1
- package/dist/meaning-resolution.js +69 -0
- package/dist/meaning-resolution.js.map +1 -1
- package/dist/metadata/analysis-planner.js +3 -1
- package/dist/metadata/analysis-planner.js.map +1 -1
- package/dist/metadata/catalog.d.ts +4 -2
- package/dist/metadata/catalog.d.ts.map +1 -1
- package/dist/metadata/catalog.js +264 -9
- package/dist/metadata/catalog.js.map +1 -1
- package/dist/metadata/meaning-evidence.d.ts +9 -2
- package/dist/metadata/meaning-evidence.d.ts.map +1 -1
- package/dist/metadata/meaning-evidence.js +105 -7
- package/dist/metadata/meaning-evidence.js.map +1 -1
- package/dist/plan-execution-adapter.d.ts +72 -1
- package/dist/plan-execution-adapter.d.ts.map +1 -1
- package/dist/plan-execution-adapter.js +208 -0
- package/dist/plan-execution-adapter.js.map +1 -1
- package/dist/resolved-analytical-plan.d.ts +14 -3
- package/dist/resolved-analytical-plan.d.ts.map +1 -1
- package/dist/resolved-analytical-plan.js +74 -20
- package/dist/resolved-analytical-plan.js.map +1 -1
- package/dist/router.d.ts +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +167 -21
- package/dist/router.js.map +1 -1
- package/dist/semantic-bridge/compose.d.ts +1 -1
- package/dist/semantic-bridge/compose.d.ts.map +1 -1
- package/dist/semantic-bridge/compose.js +27 -5
- package/dist/semantic-bridge/compose.js.map +1 -1
- package/dist/semantic-bridge/member-select.d.ts +1 -1
- package/dist/semantic-bridge/member-select.d.ts.map +1 -1
- package/dist/semantic-bridge/member-select.js +32 -12
- package/dist/semantic-bridge/member-select.js.map +1 -1
- package/dist/skills/loader.d.ts +17 -0
- package/dist/skills/loader.d.ts.map +1 -1
- package/dist/skills/loader.js +62 -4
- package/dist/skills/loader.js.map +1 -1
- package/package.json +4 -4
package/dist/answer-loop.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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:
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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
|
|
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:
|
|
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
|
-
? {
|
|
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
|
-
|
|
576
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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:
|