@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.
- package/dist/agent-run-engine.d.ts +13 -0
- package/dist/agent-run-engine.d.ts.map +1 -1
- package/dist/agent-run-engine.js +65 -2
- 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 +82 -2
- package/dist/answer-loop.d.ts.map +1 -1
- package/dist/answer-loop.js +1129 -38
- package/dist/answer-loop.js.map +1 -1
- package/dist/embeddings/provider.d.ts +1 -1
- package/dist/embeddings/provider.d.ts.map +1 -1
- package/dist/embeddings/provider.js.map +1 -1
- package/dist/governed-relational-compiler.d.ts +189 -0
- package/dist/governed-relational-compiler.d.ts.map +1 -0
- package/dist/governed-relational-compiler.js +458 -0
- package/dist/governed-relational-compiler.js.map +1 -0
- package/dist/index.d.ts +25 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -4
- package/dist/index.js.map +1 -1
- package/dist/intent-controller.d.ts +5 -0
- package/dist/intent-controller.d.ts.map +1 -1
- package/dist/intent-controller.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 +329 -9
- package/dist/kg/build.js.map +1 -1
- package/dist/meaning-resolution.d.ts +17 -0
- package/dist/meaning-resolution.d.ts.map +1 -1
- package/dist/meaning-resolution.js +83 -4
- package/dist/meaning-resolution.js.map +1 -1
- package/dist/metadata/analysis-planner.d.ts.map +1 -1
- package/dist/metadata/analysis-planner.js +37 -7
- package/dist/metadata/analysis-planner.js.map +1 -1
- package/dist/metadata/catalog.d.ts +86 -3
- package/dist/metadata/catalog.d.ts.map +1 -1
- package/dist/metadata/catalog.js +607 -36
- package/dist/metadata/catalog.js.map +1 -1
- package/dist/metadata/meaning-evidence.d.ts +18 -2
- package/dist/metadata/meaning-evidence.d.ts.map +1 -1
- package/dist/metadata/meaning-evidence.js +182 -7
- package/dist/metadata/meaning-evidence.js.map +1 -1
- package/dist/plan-execution-adapter.d.ts +131 -0
- package/dist/plan-execution-adapter.d.ts.map +1 -0
- package/dist/plan-execution-adapter.js +447 -0
- package/dist/plan-execution-adapter.js.map +1 -0
- package/dist/research-governance.d.ts +56 -0
- package/dist/research-governance.d.ts.map +1 -0
- package/dist/research-governance.js +68 -0
- package/dist/research-governance.js.map +1 -0
- package/dist/research-loop.d.ts +14 -0
- package/dist/research-loop.d.ts.map +1 -1
- package/dist/research-loop.js +77 -23
- package/dist/research-loop.js.map +1 -1
- package/dist/resolved-analytical-plan.d.ts +123 -0
- package/dist/resolved-analytical-plan.d.ts.map +1 -0
- package/dist/resolved-analytical-plan.js +400 -0
- package/dist/resolved-analytical-plan.js.map +1 -0
- package/dist/router.d.ts +3 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +195 -25
- package/dist/router.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';
|
|
@@ -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
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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 =
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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
|
|
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
|
-
:
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
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
|
-
|
|
730
|
-
|
|
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
|
|
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:
|