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