@duckcodeailabs/dql-agent 1.14.2 → 1.14.3-rc.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 +63 -2
- package/dist/agent-run-engine.d.ts.map +1 -1
- package/dist/agent-run-engine.js +864 -29
- package/dist/agent-run-engine.js.map +1 -1
- package/dist/agent-run-planner.d.ts +6 -0
- package/dist/agent-run-planner.d.ts.map +1 -1
- package/dist/agent-run-planner.js +2 -0
- package/dist/agent-run-planner.js.map +1 -1
- package/dist/agent-run-store.d.ts.map +1 -1
- package/dist/agent-run-store.js +48 -3
- package/dist/agent-run-store.js.map +1 -1
- package/dist/agentic/analyst-loop.d.ts +7 -1
- package/dist/agentic/analyst-loop.d.ts.map +1 -1
- package/dist/agentic/analyst-loop.js +163 -0
- package/dist/agentic/analyst-loop.js.map +1 -1
- package/dist/agentic/assumptions.d.ts +1 -1
- package/dist/agentic/assumptions.d.ts.map +1 -1
- package/dist/agentic/assumptions.js.map +1 -1
- package/dist/agentic/research-agent.d.ts.map +1 -1
- package/dist/agentic/research-agent.js +10 -1
- package/dist/agentic/research-agent.js.map +1 -1
- package/dist/agentic/sql-authorization.d.ts +8 -0
- package/dist/agentic/sql-authorization.d.ts.map +1 -1
- package/dist/agentic/sql-authorization.js +3 -0
- package/dist/agentic/sql-authorization.js.map +1 -1
- package/dist/analytical-compatibility.d.ts.map +1 -1
- package/dist/analytical-compatibility.js +8 -1
- package/dist/analytical-compatibility.js.map +1 -1
- package/dist/analytical-failure-repair.d.ts.map +1 -1
- package/dist/analytical-failure-repair.js +6 -2
- package/dist/analytical-failure-repair.js.map +1 -1
- package/dist/analytical-frame.d.ts +23 -1
- package/dist/analytical-frame.d.ts.map +1 -1
- package/dist/analytical-frame.js +322 -16
- package/dist/analytical-frame.js.map +1 -1
- package/dist/analytical-orchestration.d.ts +314 -3
- package/dist/analytical-orchestration.d.ts.map +1 -1
- package/dist/analytical-orchestration.js +486 -43
- package/dist/analytical-orchestration.js.map +1 -1
- package/dist/answer-loop.d.ts +64 -8
- package/dist/answer-loop.d.ts.map +1 -1
- package/dist/answer-loop.js +528 -67
- package/dist/answer-loop.js.map +1 -1
- package/dist/ask-observability/index.d.ts +11 -0
- package/dist/ask-observability/index.d.ts.map +1 -0
- package/dist/ask-observability/index.js +7 -0
- package/dist/ask-observability/index.js.map +1 -0
- package/dist/ask-observability/instrumentation.d.ts +35 -0
- package/dist/ask-observability/instrumentation.d.ts.map +1 -0
- package/dist/ask-observability/instrumentation.js +472 -0
- package/dist/ask-observability/instrumentation.js.map +1 -0
- package/dist/ask-observability/observer.d.ts +62 -0
- package/dist/ask-observability/observer.d.ts.map +1 -0
- package/dist/ask-observability/observer.js +537 -0
- package/dist/ask-observability/observer.js.map +1 -0
- package/dist/ask-observability/portable.d.ts +98 -0
- package/dist/ask-observability/portable.d.ts.map +1 -0
- package/dist/ask-observability/portable.js +527 -0
- package/dist/ask-observability/portable.js.map +1 -0
- package/dist/ask-observability/store.d.ts +130 -0
- package/dist/ask-observability/store.d.ts.map +1 -0
- package/dist/ask-observability/store.js +1114 -0
- package/dist/ask-observability/store.js.map +1 -0
- package/dist/ask-observability/types.d.ts +357 -0
- package/dist/ask-observability/types.d.ts.map +1 -0
- package/dist/ask-observability/types.js +2 -0
- package/dist/ask-observability/types.js.map +1 -0
- package/dist/ask-observability/utils.d.ts +21 -0
- package/dist/ask-observability/utils.d.ts.map +1 -0
- package/dist/ask-observability/utils.js +102 -0
- package/dist/ask-observability/utils.js.map +1 -0
- package/dist/generated-analytical-proposal.d.ts +37 -1
- package/dist/generated-analytical-proposal.d.ts.map +1 -1
- package/dist/generated-analytical-proposal.js +211 -3
- package/dist/generated-analytical-proposal.js.map +1 -1
- package/dist/governed-relational-compiler.d.ts +15 -0
- package/dist/governed-relational-compiler.d.ts.map +1 -1
- package/dist/governed-relational-compiler.js +99 -1
- package/dist/governed-relational-compiler.js.map +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/intent-controller.d.ts +10 -0
- package/dist/intent-controller.d.ts.map +1 -1
- package/dist/intent-controller.js +1 -1
- package/dist/intent-controller.js.map +1 -1
- package/dist/meaning-resolution.d.ts +78 -6
- package/dist/meaning-resolution.d.ts.map +1 -1
- package/dist/meaning-resolution.js +331 -38
- package/dist/meaning-resolution.js.map +1 -1
- package/dist/metadata/analysis-planner.d.ts.map +1 -1
- package/dist/metadata/analysis-planner.js +21 -1
- package/dist/metadata/analysis-planner.js.map +1 -1
- package/dist/metadata/catalog.d.ts.map +1 -1
- package/dist/metadata/catalog.js +1 -1
- package/dist/metadata/catalog.js.map +1 -1
- package/dist/metadata/meaning-evidence.d.ts +9 -2
- package/dist/metadata/meaning-evidence.d.ts.map +1 -1
- package/dist/metadata/meaning-evidence.js +261 -11
- package/dist/metadata/meaning-evidence.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 +93 -13
- package/dist/plan-execution-adapter.js.map +1 -1
- package/dist/provider-egress.d.ts +24 -10
- package/dist/provider-egress.d.ts.map +1 -1
- package/dist/provider-egress.js +22 -17
- package/dist/provider-egress.js.map +1 -1
- package/dist/providers/claude.d.ts.map +1 -1
- package/dist/providers/claude.js +10 -9
- package/dist/providers/claude.js.map +1 -1
- package/dist/providers/dispatch.d.ts +27 -1
- package/dist/providers/dispatch.d.ts.map +1 -1
- package/dist/providers/dispatch.js +63 -1
- package/dist/providers/dispatch.js.map +1 -1
- package/dist/providers/gemini.js +10 -10
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/index.d.ts +2 -2
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +1 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/ollama.js +15 -15
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai.js +22 -22
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/types.d.ts +52 -0
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js.map +1 -1
- package/dist/relationship-proof.d.ts +81 -0
- package/dist/relationship-proof.d.ts.map +1 -1
- package/dist/relationship-proof.js +228 -4
- package/dist/relationship-proof.js.map +1 -1
- package/dist/resolved-analytical-plan.d.ts +18 -4
- package/dist/resolved-analytical-plan.d.ts.map +1 -1
- package/dist/resolved-analytical-plan.js +764 -47
- package/dist/resolved-analytical-plan.js.map +1 -1
- package/dist/router.d.ts +10 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +1785 -213
- package/dist/router.js.map +1 -1
- package/dist/semantic-bridge/compose.d.ts.map +1 -1
- package/dist/semantic-bridge/compose.js +23 -4
- package/dist/semantic-bridge/compose.js.map +1 -1
- package/package.json +4 -4
package/dist/answer-loop.js
CHANGED
|
@@ -35,7 +35,7 @@ import { analyticalErrorDetail, } from './analytical-error.js';
|
|
|
35
35
|
import { fanoutWarningsForSql } from './metadata/grain-ledger.js';
|
|
36
36
|
import { buildAggregationSafetyProof, buildSemanticCompilationAggregationSafetyProof, } from './aggregation-safety-proof.js';
|
|
37
37
|
import { buildSemanticProofAuthorityV1 } from './semantic-proof-authority.js';
|
|
38
|
-
import { GENERATED_ANALYTICAL_TUPLE_DRIFT_MESSAGE, validateGeneratedAnalyticalProposal, } from './generated-analytical-proposal.js';
|
|
38
|
+
import { GENERATED_ANALYTICAL_TUPLE_DRIFT_MESSAGE, frozenRequiredOutputBindingProofsForPlan, validateFrozenRequiredOutputProjection, validateGeneratedAnalyticalProposal, } from './generated-analytical-proposal.js';
|
|
39
39
|
import { evaluateDbtFirstGeneratedSql } from './metadata/dbt-first-safety.js';
|
|
40
40
|
import { planAnalyticalPath, humanizeAnalyticalEntityId, analyticalPolicyUserFacingReason, } from './metadata/analytical-policy.js';
|
|
41
41
|
import { planCertifiedAdaptation } from './metadata/block-adapt.js';
|
|
@@ -1161,9 +1161,17 @@ export async function answer(input) {
|
|
|
1161
1161
|
const analyticalExecutionGraph = analyticalGraphBuild?.status === 'ready'
|
|
1162
1162
|
? analyticalGraphBuild.graph
|
|
1163
1163
|
: undefined;
|
|
1164
|
+
// A v2 plan can also be synthesized for legacy/direct AnswerLoop callers so
|
|
1165
|
+
// they can opt into the deterministic compiler. That compatibility bridge is
|
|
1166
|
+
// not a router freeze: it must not turn a missing optional graph/adapter into
|
|
1167
|
+
// a terminal answer before the established, safe legacy semantic execution
|
|
1168
|
+
// path is considered. Router-selected semantic plans carry the explicit
|
|
1169
|
+
// `selectedCascadeTier` signal and remain immutable/terminal below.
|
|
1170
|
+
const routerFrozenSemanticPlan = Boolean(frozenSemanticRouteForInput(normalizedInput));
|
|
1164
1171
|
const analyticalExecutionGraphFailure = normalizedInput.resolvedAnalyticalPlan?.schemaVersion === 2
|
|
1165
1172
|
&& !multiMetricPlan
|
|
1166
1173
|
&& !generatedProposalPlan
|
|
1174
|
+
&& routerFrozenSemanticPlan
|
|
1167
1175
|
? analyticalGraphBuild?.status === 'blocked'
|
|
1168
1176
|
? {
|
|
1169
1177
|
code: analyticalGraphBuild.code,
|
|
@@ -1248,7 +1256,23 @@ export async function answer(input) {
|
|
|
1248
1256
|
}
|
|
1249
1257
|
: compiledInput;
|
|
1250
1258
|
const loopResult = await runAnswerLoop(executionInput);
|
|
1251
|
-
|
|
1259
|
+
let result = applyHollowAnswerGate(validateCertifiedAnalyticalGraphAnswer(loopResult, executionInput.analyticalExecutionGraph, executionInput.resolvedAnalyticalPlan?.analyticalFrame));
|
|
1260
|
+
const frozenSemanticRoute = frozenSemanticRouteForInput(executionInput);
|
|
1261
|
+
// A final defensive boundary keeps future return sites from accidentally
|
|
1262
|
+
// restoring the old semantic -> generated fallthrough. Do not relabel an
|
|
1263
|
+
// actual generated result as semantic: replace it with an inspectable,
|
|
1264
|
+
// same-tier terminal failure instead.
|
|
1265
|
+
const returnedNonSemanticRoute = frozenSemanticRoute && ((result.route !== undefined && result.route.tier !== 'semantic_metric')
|
|
1266
|
+
|| (result.route === undefined && result.kind !== 'no_answer' && result.sourceTier !== 'semantic_layer'));
|
|
1267
|
+
if (returnedNonSemanticRoute && frozenSemanticRoute) {
|
|
1268
|
+
result = frozenSemanticPlanFailureAnswer({
|
|
1269
|
+
answerInput: executionInput,
|
|
1270
|
+
considered: result.considered ?? [],
|
|
1271
|
+
providerName: executionInput.provider.name,
|
|
1272
|
+
code: 'EXECUTION_GRAPH_MISMATCH',
|
|
1273
|
+
reason: `The pinned semantic plan ${frozenSemanticRoute.ref ?? 'selected by the router'} returned a non-semantic answer route. DQL did not substitute generated SQL.`,
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1252
1276
|
// Attach the canonical trust label once, at the single exit point, so every
|
|
1253
1277
|
// return site inside runAnswerLoop stays untouched and backward compatible.
|
|
1254
1278
|
// Freshness-aware trust: for a certified answer, fold the source block's data
|
|
@@ -1256,7 +1280,7 @@ export async function answer(input) {
|
|
|
1256
1280
|
// stale data" / "Certified · upstream failed". Non-certified or fresh answers
|
|
1257
1281
|
// are unaffected.
|
|
1258
1282
|
const { _semanticMetricMatch, ...publicResult } = result;
|
|
1259
|
-
const chosenRoute = result.route ?? deriveAiRoute(result, _semanticMetricMatch);
|
|
1283
|
+
const chosenRoute = frozenSemanticRoute ?? result.route ?? deriveAiRoute(result, _semanticMetricMatch);
|
|
1260
1284
|
// P0 — record the high-level action this turn warranted, so callers can route
|
|
1261
1285
|
// (compose_app → app build, investigate → research) and the UI can show the
|
|
1262
1286
|
// agent's reasoning. Computed once at the single exit from the finished answer.
|
|
@@ -1390,6 +1414,69 @@ function deriveAiRoute(result, metricMatch) {
|
|
|
1390
1414
|
? { tier: 'generated_sql', label: 'Prepared review-required DQL artifact with SQL preview.' }
|
|
1391
1415
|
: { tier: 'generated_sql', label: 'Prepared review-required SQL preview.' };
|
|
1392
1416
|
}
|
|
1417
|
+
/**
|
|
1418
|
+
* The router owns the cascade decision. Once it freezes a semantic plan, the
|
|
1419
|
+
* answer loop may either execute that exact plan or return a semantic-tier
|
|
1420
|
+
* terminal failure. It must never let an unavailable semantic adapter fall
|
|
1421
|
+
* through to the legacy generated-SQL lane merely because the latter can still
|
|
1422
|
+
* construct a preview.
|
|
1423
|
+
*/
|
|
1424
|
+
function frozenSemanticRouteForInput(input) {
|
|
1425
|
+
const plan = input.resolvedAnalyticalPlan;
|
|
1426
|
+
// `freezeLegacySemanticSelection()` upgrades older direct AnswerLoop callers
|
|
1427
|
+
// to a typed plan so they can use the deterministic semantic compiler. That
|
|
1428
|
+
// migration is not the router's immutable cascade freeze. The server-owned
|
|
1429
|
+
// cascade explicitly carries `selectedCascadeTier: 'semantic'`; only that
|
|
1430
|
+
// signal activates the no-downgrade boundary below. Otherwise direct legacy
|
|
1431
|
+
// callers retain their established deterministic semantic behavior.
|
|
1432
|
+
if (input.selectedCascadeTier !== 'semantic'
|
|
1433
|
+
|| plan?.mode !== 'authoritative'
|
|
1434
|
+
|| plan.capability !== 'semantic_execution')
|
|
1435
|
+
return undefined;
|
|
1436
|
+
const executionId = plan.executionId ?? plan.selectedConceptIds[0];
|
|
1437
|
+
return {
|
|
1438
|
+
tier: 'semantic_metric',
|
|
1439
|
+
label: executionId
|
|
1440
|
+
? `Frozen semantic plan ${executionId}`
|
|
1441
|
+
: 'Frozen semantic plan',
|
|
1442
|
+
...(executionId ? { ref: executionId } : {}),
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
function frozenSemanticPlanFailureAnswer(input) {
|
|
1446
|
+
const { answerInput, considered, providerName, code, reason } = input;
|
|
1447
|
+
const route = frozenSemanticRouteForInput(answerInput);
|
|
1448
|
+
// Callers use this only for an authoritative semantic plan. Keep the
|
|
1449
|
+
// fallback defensive so an internal misuse still produces a safe no-answer.
|
|
1450
|
+
const analyticalFailure = analyticalFailureForInput(answerInput, {
|
|
1451
|
+
error: { code, message: reason },
|
|
1452
|
+
phase: 'compilation',
|
|
1453
|
+
failedBindings: answerInput.resolvedAnalyticalPlan?.executionId
|
|
1454
|
+
? [{
|
|
1455
|
+
qualifiedId: answerInput.resolvedAnalyticalPlan.executionId,
|
|
1456
|
+
role: 'metric',
|
|
1457
|
+
reasonCode: code,
|
|
1458
|
+
}]
|
|
1459
|
+
: undefined,
|
|
1460
|
+
});
|
|
1461
|
+
return {
|
|
1462
|
+
kind: 'no_answer',
|
|
1463
|
+
sourceTier: 'no_answer',
|
|
1464
|
+
certification: 'analyst_review_required',
|
|
1465
|
+
reviewStatus: 'none',
|
|
1466
|
+
confidence: 0,
|
|
1467
|
+
text: analyticalFailure.message,
|
|
1468
|
+
answer: analyticalFailure.message,
|
|
1469
|
+
executionError: analyticalFailure.message,
|
|
1470
|
+
refusalCode: 'grounding_gap',
|
|
1471
|
+
refusalDetails: { code, message: analyticalFailure.message },
|
|
1472
|
+
analyticalFailure,
|
|
1473
|
+
...(route ? { route } : {}),
|
|
1474
|
+
citations: contextPackCitations(answerInput.contextPack, 8),
|
|
1475
|
+
considered,
|
|
1476
|
+
contextPack: answerInput.contextPack,
|
|
1477
|
+
providerUsed: providerName,
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1393
1480
|
/** "customers_customer_name" → "customers" for a question the user has to read. */
|
|
1394
1481
|
function humanizeDeicticDimension(dimension) {
|
|
1395
1482
|
const leaf = dimension.split(/[.:]/).pop() ?? dimension;
|
|
@@ -1419,6 +1506,7 @@ async function runAnswerLoop(input) {
|
|
|
1419
1506
|
// or provider is dispatched. Full retrieval stays on `input.contextPack`
|
|
1420
1507
|
// for receipts and diagnostics only.
|
|
1421
1508
|
const forcedExploratoryTier = input.selectedCascadeTier === 'exploratory_sql';
|
|
1509
|
+
const frozenSemanticRoute = frozenSemanticRouteForInput(input);
|
|
1422
1510
|
const exploratoryClosureContextPack = forcedExploratoryTier
|
|
1423
1511
|
? scopeContextPackToExploratoryCandidateClosure(scopedContextPack, input.exploratoryCandidateIds)
|
|
1424
1512
|
: undefined;
|
|
@@ -1510,6 +1598,31 @@ async function runAnswerLoop(input) {
|
|
|
1510
1598
|
const authoritativePlanBinding = !forcedExploratoryTier && input.resolvedAnalyticalPlan?.mode === 'authoritative'
|
|
1511
1599
|
? input.resolvedPlanExecutionBinding
|
|
1512
1600
|
: undefined;
|
|
1601
|
+
// `freezeLegacySemanticSelection()` attaches an authoritative-shaped v2
|
|
1602
|
+
// plan for direct AnswerLoop compatibility. It is not the router-owned
|
|
1603
|
+
// semantic cascade decision. Only an explicit selected semantic tier may
|
|
1604
|
+
// make an adapter/graph miss terminal; direct callers retain the established
|
|
1605
|
+
// deterministic metric path, which still applies the ordinary SQL safety
|
|
1606
|
+
// validation before it can execute.
|
|
1607
|
+
const legacyDirectSemanticPlan = input.selectedCascadeTier === undefined
|
|
1608
|
+
&& input.resolvedAnalyticalPlan?.mode === 'authoritative'
|
|
1609
|
+
&& input.resolvedAnalyticalPlan.recommendedRoute === 'semantic'
|
|
1610
|
+
&& !frozenSemanticRoute;
|
|
1611
|
+
// A semantic freeze is an immutable execution authority, not a hint for the
|
|
1612
|
+
// legacy loop. In particular, projects that have indexed semantic metadata
|
|
1613
|
+
// but have not configured the corresponding local semantic adapter used to
|
|
1614
|
+
// continue into generated SQL. Stop here with the selected plan's typed
|
|
1615
|
+
// diagnostic instead; no provider, tool, or SQL work is allowed to replace
|
|
1616
|
+
// it after freeze.
|
|
1617
|
+
if (!forcedExploratoryTier && frozenSemanticRoute && !input.semanticLayer) {
|
|
1618
|
+
return frozenSemanticPlanFailureAnswer({
|
|
1619
|
+
answerInput: input,
|
|
1620
|
+
considered,
|
|
1621
|
+
providerName: provider.name,
|
|
1622
|
+
code: 'SEMANTIC_LAYER_REQUIRED',
|
|
1623
|
+
reason: `The pinned semantic adapter for ${frozenSemanticRoute.ref ?? 'the selected semantic plan'} is unavailable.`,
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1513
1626
|
if (!forcedExploratoryTier && input.analyticalPeriodResolutionFailure) {
|
|
1514
1627
|
const failure = input.analyticalPeriodResolutionFailure;
|
|
1515
1628
|
const structuredFailureCode = failure.error && typeof failure.error === 'object'
|
|
@@ -1601,7 +1714,11 @@ async function runAnswerLoop(input) {
|
|
|
1601
1714
|
input,
|
|
1602
1715
|
binding: input.semanticGraphExecutionBinding,
|
|
1603
1716
|
graph: input.analyticalExecutionGraph,
|
|
1604
|
-
|
|
1717
|
+
// The execution registry identifies the adapter metric, but its compact
|
|
1718
|
+
// node payload may omit the relationship-path metadata that was frozen
|
|
1719
|
+
// on the RAP. Re-deriving aggregation proof from that projection makes
|
|
1720
|
+
// an already accepted native MetricFlow grouping fail post-freeze.
|
|
1721
|
+
capability: input.semanticGraphExecutionBinding.capability,
|
|
1605
1722
|
considered,
|
|
1606
1723
|
providerName: provider.name,
|
|
1607
1724
|
});
|
|
@@ -1716,7 +1833,7 @@ async function runAnswerLoop(input) {
|
|
|
1716
1833
|
providerUsed: provider.name,
|
|
1717
1834
|
};
|
|
1718
1835
|
}
|
|
1719
|
-
if (authoritativePlanBinding?.status === 'blocked') {
|
|
1836
|
+
if (authoritativePlanBinding?.status === 'blocked' && !legacyDirectSemanticPlan) {
|
|
1720
1837
|
// An ambiguous singular reference is answerable the moment the user says
|
|
1721
1838
|
// which one they meant, so offer the candidates rather than dead-ending.
|
|
1722
1839
|
// The old refusal shipped the binder's internal sentence as the answer
|
|
@@ -1792,7 +1909,7 @@ async function runAnswerLoop(input) {
|
|
|
1792
1909
|
? authoritativePlanBinding
|
|
1793
1910
|
: undefined;
|
|
1794
1911
|
const preferredSemanticMetric = authoritativeSemanticBinding?.metricNode
|
|
1795
|
-
?? (input.resolvedAnalyticalPlan?.mode === 'authoritative'
|
|
1912
|
+
?? (input.resolvedAnalyticalPlan?.mode === 'authoritative' && !legacyDirectSemanticPlan
|
|
1796
1913
|
? undefined
|
|
1797
1914
|
: resolvePreferredSemanticMetric([input.preferredExecutionId, ...(input.preferredEvidenceIds ?? [])], semanticMetricNodes, kg));
|
|
1798
1915
|
const semanticLayerForExec = input.semanticLayer;
|
|
@@ -1802,7 +1919,7 @@ async function runAnswerLoop(input) {
|
|
|
1802
1919
|
? null
|
|
1803
1920
|
: preferredSemanticMetric
|
|
1804
1921
|
? { metric: preferredSemanticMetric, score: 1, basis: 'name' }
|
|
1805
|
-
: input.resolvedAnalyticalPlan?.mode === 'authoritative'
|
|
1922
|
+
: input.resolvedAnalyticalPlan?.mode === 'authoritative' && !legacyDirectSemanticPlan
|
|
1806
1923
|
? null
|
|
1807
1924
|
: await matchSemanticMetric(semanticQuestion, semanticMetricNodes, {
|
|
1808
1925
|
measureTerms: [...questionPlan.requestedShape.measures, ...questionPlan.metricTerms],
|
|
@@ -1981,6 +2098,7 @@ async function runAnswerLoop(input) {
|
|
|
1981
2098
|
if (artifactHit && businessContextTerminal) {
|
|
1982
2099
|
let result;
|
|
1983
2100
|
let executionError;
|
|
2101
|
+
let executionFailureDetail;
|
|
1984
2102
|
if (artifactHit.node.kind === 'block' && input.executeCertifiedBlock) {
|
|
1985
2103
|
try {
|
|
1986
2104
|
result = await input.executeCertifiedBlock(artifactHit.node, {
|
|
@@ -1993,6 +2111,7 @@ async function runAnswerLoop(input) {
|
|
|
1993
2111
|
result = trimResultToRequestedTopN(result, questionPlan);
|
|
1994
2112
|
}
|
|
1995
2113
|
catch (err) {
|
|
2114
|
+
executionFailureDetail = analyticalErrorDetail(err);
|
|
1996
2115
|
executionError = err instanceof Error ? err.message : String(err);
|
|
1997
2116
|
}
|
|
1998
2117
|
}
|
|
@@ -2098,6 +2217,9 @@ async function runAnswerLoop(input) {
|
|
|
2098
2217
|
? undefined
|
|
2099
2218
|
: questionPlan.requestedShape.topN?.n);
|
|
2100
2219
|
const authoritativeCertifiedFailure = Boolean(authoritativeCertifiedBinding && executionError);
|
|
2220
|
+
const observabilityExecutionFailure = authoritativeCertifiedFailure
|
|
2221
|
+
? observabilityFailureForFrozenExecution(executionFailureDetail)
|
|
2222
|
+
: undefined;
|
|
2101
2223
|
// A selected certified block which returns an incomplete tuple is a
|
|
2102
2224
|
// same-tier terminal failure. It is not a reason to reinterpret meaning,
|
|
2103
2225
|
// label the partial rows generated, or retry another route after freeze.
|
|
@@ -2143,6 +2265,7 @@ async function runAnswerLoop(input) {
|
|
|
2143
2265
|
block: artifactHit.node.kind === 'block' ? artifactHit.node : undefined,
|
|
2144
2266
|
result,
|
|
2145
2267
|
executionError,
|
|
2268
|
+
...(observabilityExecutionFailure ? { observabilityExecutionFailure } : {}),
|
|
2146
2269
|
...(authoritativeCertifiedFailure || authoritativeCertifiedShapeFailure
|
|
2147
2270
|
? { refusalCode: 'grounding_gap' }
|
|
2148
2271
|
: {}),
|
|
@@ -2637,8 +2760,21 @@ async function runAnswerLoop(input) {
|
|
|
2637
2760
|
contextPack: input.contextPack,
|
|
2638
2761
|
considered,
|
|
2639
2762
|
providerUsed: provider.name,
|
|
2763
|
+
...(frozenSemanticRoute ? { route: frozenSemanticRoute } : {}),
|
|
2640
2764
|
};
|
|
2641
2765
|
}
|
|
2766
|
+
// The semantic branch above has exhausted the exact plan's compiler and
|
|
2767
|
+
// adapter choices. Do not let `metricFirst` or the ordinary generated
|
|
2768
|
+
// proposal branch reinterpret the question after a semantic freeze.
|
|
2769
|
+
if (frozenSemanticRoute && !semanticBridgeAnswer) {
|
|
2770
|
+
return frozenSemanticPlanFailureAnswer({
|
|
2771
|
+
answerInput: input,
|
|
2772
|
+
considered,
|
|
2773
|
+
providerName: provider.name,
|
|
2774
|
+
code: 'COMPILATION_FAILED',
|
|
2775
|
+
reason: `The pinned semantic adapter could not compose ${frozenSemanticRoute.ref ?? 'the selected semantic plan'} without changing the frozen plan.`,
|
|
2776
|
+
});
|
|
2777
|
+
}
|
|
2642
2778
|
const metricFirst = semanticMetricMatch
|
|
2643
2779
|
? buildGovernedMetricFirstSql({
|
|
2644
2780
|
metric: semanticMetricMatch.metric,
|
|
@@ -2970,14 +3106,36 @@ async function runAnswerLoop(input) {
|
|
|
2970
3106
|
|| (input.contextPack?.allowedSqlContext?.relations.length ?? 0) > 0
|
|
2971
3107
|
|| (input.contextPack?.allowedSqlContext?.sourceBlockSql.length ?? 0) > 0
|
|
2972
3108
|
|| contextBlocks.length > 0;
|
|
3109
|
+
// A selected bounded-exploration RAP is stronger than the advisory legacy
|
|
3110
|
+
// analytical-path diagnosis. The latter may still call raw output terms a
|
|
3111
|
+
// semantic "missing dimension" even after the router has proven one safe
|
|
3112
|
+
// physical relation and frozen the exploratory tuple. Do not let that
|
|
3113
|
+
// advisory pre-freeze diagnosis suppress the one explicitly authorized
|
|
3114
|
+
// same-plan model-decline repair; the host re-validates the exact proposal
|
|
3115
|
+
// against the frozen closure before it can execute.
|
|
3116
|
+
const frozenExploratoryRepair = frozenExploratoryModelRepairAuthority(input);
|
|
2973
3117
|
if (!parsed.sql && !governedMetricAnswer && wantsGeneratedData && hasGeneratableContext
|
|
2974
|
-
&& (
|
|
3118
|
+
&& (frozenExploratoryRepair
|
|
3119
|
+
|| analyticalPlan?.safe !== false
|
|
3120
|
+
|| analyticalPlanAllowsDbtExploration(analyticalPlan))) {
|
|
2975
3121
|
try {
|
|
2976
3122
|
proposed = await generateProposalWithOptionalTools({
|
|
2977
3123
|
provider,
|
|
2978
|
-
messages: [...messages, {
|
|
2979
|
-
|
|
2980
|
-
|
|
3124
|
+
messages: [...messages, {
|
|
3125
|
+
role: 'system',
|
|
3126
|
+
content: [
|
|
3127
|
+
FORCE_JOIN_INSTRUCTION,
|
|
3128
|
+
...(frozenExploratoryRepair
|
|
3129
|
+
? [renderFrozenExploratoryRepairContract(input.resolvedAnalyticalPlan, questionPlan)]
|
|
3130
|
+
: []),
|
|
3131
|
+
].join('\n\n'),
|
|
3132
|
+
}],
|
|
3133
|
+
// A router-frozen exploratory plan may consume exactly one additional
|
|
3134
|
+
// provider transport to correct a model decline. The host carries the
|
|
3135
|
+
// immutable plan and output tuple into this retry; it is neither a
|
|
3136
|
+
// replan nor a route change. Legacy direct callers retain their
|
|
3137
|
+
// historical composition correction, but do not receive the typed
|
|
3138
|
+
// repair authority below.
|
|
2981
3139
|
tools: [],
|
|
2982
3140
|
questionPlan,
|
|
2983
3141
|
intent,
|
|
@@ -2986,6 +3144,13 @@ async function runAnswerLoop(input) {
|
|
|
2986
3144
|
analysisDepth: input.analysisDepth,
|
|
2987
3145
|
toolCalls: proposalToolCalls,
|
|
2988
3146
|
providerPayloadGuard: input.providerPayloadGuard,
|
|
3147
|
+
...(frozenExploratoryRepair
|
|
3148
|
+
? {
|
|
3149
|
+
dispatchPhase: 'repair',
|
|
3150
|
+
egressPurpose: 'repair_sql',
|
|
3151
|
+
maxProviderDispatches: 1,
|
|
3152
|
+
}
|
|
3153
|
+
: {}),
|
|
2989
3154
|
});
|
|
2990
3155
|
parsed = parseProposal(proposed);
|
|
2991
3156
|
}
|
|
@@ -3060,6 +3225,49 @@ async function runAnswerLoop(input) {
|
|
|
3060
3225
|
}
|
|
3061
3226
|
if (parsed.sql) {
|
|
3062
3227
|
const frozenPlan = input.resolvedAnalyticalPlan;
|
|
3228
|
+
// The result contract is host-owned even in tests or embedded callers
|
|
3229
|
+
// that provide their own execution adapter. Stop before capability
|
|
3230
|
+
// preparation when a generated alias is backed by a different physical
|
|
3231
|
+
// column than the frozen plan (for example `product_id AS order_id`).
|
|
3232
|
+
// The local runtime repeats this check while minting the capability and
|
|
3233
|
+
// persists the matching proof on its receipt.
|
|
3234
|
+
if (input.selectedCascadeTier === 'exploratory_sql' && frozenPlan) {
|
|
3235
|
+
const requiredOutputValidation = validateFrozenRequiredOutputProjection({
|
|
3236
|
+
plan: frozenPlan,
|
|
3237
|
+
sql: parsed.sql,
|
|
3238
|
+
...(input.semanticDriver ? { dialect: input.semanticDriver } : {}),
|
|
3239
|
+
});
|
|
3240
|
+
if (!requiredOutputValidation.ok) {
|
|
3241
|
+
const failedOutputs = [...new Set([
|
|
3242
|
+
...requiredOutputValidation.missingOutputs,
|
|
3243
|
+
...requiredOutputValidation.bindingMismatches,
|
|
3244
|
+
])];
|
|
3245
|
+
const message = `The generated exploratory query did not prove the frozen source binding for required output ${failedOutputs.join(', ')}, so DQL did not execute it.`;
|
|
3246
|
+
return {
|
|
3247
|
+
kind: 'no_answer',
|
|
3248
|
+
sourceTier: 'no_answer',
|
|
3249
|
+
certification: 'analyst_review_required',
|
|
3250
|
+
reviewStatus: 'none',
|
|
3251
|
+
confidence: 0,
|
|
3252
|
+
text: message,
|
|
3253
|
+
answer: message,
|
|
3254
|
+
refusalCode: 'policy_blocked',
|
|
3255
|
+
refusalDetails: {
|
|
3256
|
+
code: 'OUTPUT_BINDING_TUPLE_DRIFT',
|
|
3257
|
+
message,
|
|
3258
|
+
},
|
|
3259
|
+
validationWarnings: [
|
|
3260
|
+
'OUTPUT_BINDING_TUPLE_DRIFT',
|
|
3261
|
+
...failedOutputs.map((output) => `Frozen output binding not proven: ${output}`),
|
|
3262
|
+
],
|
|
3263
|
+
citations: contextPackCitations(input.contextPack, 8),
|
|
3264
|
+
memoryContext: input.memoryContext,
|
|
3265
|
+
contextPack: input.contextPack,
|
|
3266
|
+
considered,
|
|
3267
|
+
providerUsed: provider.name,
|
|
3268
|
+
};
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
3063
3271
|
if (!governedMetricAnswer && frozenPlan?.mode === 'authoritative' && frozenPlan.schemaVersion === 2) {
|
|
3064
3272
|
const targetFingerprint = input.generatedProposalTargetFingerprint ?? '';
|
|
3065
3273
|
const validation = validateGeneratedAnalyticalProposal({
|
|
@@ -3664,12 +3872,14 @@ async function runAnswerLoop(input) {
|
|
|
3664
3872
|
let executionError;
|
|
3665
3873
|
let warehouseFailure;
|
|
3666
3874
|
let exploratoryClosureDenied = false;
|
|
3875
|
+
let semanticFanoutProbeFailure;
|
|
3667
3876
|
let repairAttempts = 0;
|
|
3668
3877
|
// A forced proposal may originate from the bounded analyst loop, but it
|
|
3669
3878
|
// cannot bypass a router-owned exploratory decision. In that case the host
|
|
3670
3879
|
// still prepares and freezes the exact validated SQL before execution.
|
|
3671
3880
|
const exploratoryExecutionSelected = input.selectedCascadeTier === 'exploratory_sql';
|
|
3672
|
-
let
|
|
3881
|
+
let initialExploratoryExecution;
|
|
3882
|
+
let exploratoryRepairExecution;
|
|
3673
3883
|
// Repair candidates that were generated but rejected before execution. These
|
|
3674
3884
|
// are diagnostics ABOUT the recovery attempt, not the reason the run failed,
|
|
3675
3885
|
// so they are reported alongside the original error rather than replacing it.
|
|
@@ -3684,7 +3894,7 @@ async function runAnswerLoop(input) {
|
|
|
3684
3894
|
executionError = deepCandidateExecutionError;
|
|
3685
3895
|
warehouseFailure = normalizeWarehouseSqlFailure(deepCandidateExecutionError, input.semanticDriver);
|
|
3686
3896
|
}
|
|
3687
|
-
const executeCurrentSql = async () => {
|
|
3897
|
+
const executeCurrentSql = async (authorizationAttempt) => {
|
|
3688
3898
|
const requestedLimit = questionPlan.requestedShape.topN?.scope === 'per_group'
|
|
3689
3899
|
? 200
|
|
3690
3900
|
: questionPlan.requestedShape.topN?.n ?? 200;
|
|
@@ -3725,17 +3935,23 @@ async function runAnswerLoop(input) {
|
|
|
3725
3935
|
});
|
|
3726
3936
|
}
|
|
3727
3937
|
// This is the only point at which a router-selected exploratory proposal
|
|
3728
|
-
// crosses from validated text into executable authority. The
|
|
3729
|
-
//
|
|
3730
|
-
//
|
|
3731
|
-
|
|
3732
|
-
|
|
3938
|
+
// crosses from validated text into executable authority. The first call
|
|
3939
|
+
// receives the original one-shot capability. A retryable warehouse
|
|
3940
|
+
// failure may mint exactly one *new* repair capability, explicitly bound
|
|
3941
|
+
// to the first SQL fingerprint; it never reopens meaning or routing.
|
|
3942
|
+
const prepared = authorizationAttempt
|
|
3943
|
+
? await input.prepareExploratorySqlExecution(parsed.sql, boundedArtifact, authorizationAttempt)
|
|
3944
|
+
: (initialExploratoryExecution ??= await input.prepareExploratorySqlExecution(parsed.sql, boundedArtifact));
|
|
3945
|
+
if (authorizationAttempt)
|
|
3946
|
+
exploratoryRepairExecution = prepared;
|
|
3947
|
+
return input.executeAgenticGeneratedSql(prepared.capability, parsed.sql, boundedArtifact);
|
|
3733
3948
|
}
|
|
3734
3949
|
if (!input.executeGeneratedSql)
|
|
3735
3950
|
throw new Error('No generated SQL executor is configured.');
|
|
3736
3951
|
return input.executeGeneratedSql(parsed.sql, boundedArtifact);
|
|
3737
3952
|
};
|
|
3738
3953
|
if ((input.executeGeneratedSql
|
|
3954
|
+
|| input.executeDqlArtifact
|
|
3739
3955
|
|| (input.forcedGeneratedProposal && input.executeAgenticGeneratedSql)
|
|
3740
3956
|
|| exploratoryExecutionSelected) && !result) {
|
|
3741
3957
|
// Fanout gate for native semantic direct-joins: a duplicate join-key row on
|
|
@@ -3744,17 +3960,31 @@ async function runAnswerLoop(input) {
|
|
|
3744
3960
|
// answers). Probe first; on structural contradiction, refuse with the cause
|
|
3745
3961
|
// instead of presenting wrong numbers — and never hand the error to SQL
|
|
3746
3962
|
// repair, which would regenerate the same multiplying join.
|
|
3747
|
-
let
|
|
3963
|
+
let semanticFanoutBlocked = false;
|
|
3748
3964
|
if (!executionError
|
|
3749
3965
|
&& !input.forcedGeneratedProposal
|
|
3750
3966
|
&& !exploratoryExecutionSelected
|
|
3751
|
-
&& input.executeGeneratedSql
|
|
3752
3967
|
&& semanticBridgeAnswer?.composeResult?.fanoutProbeSql
|
|
3753
3968
|
&& semanticBridgeAnswer.sql.trim() === parsed.sql?.trim()) {
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3969
|
+
// A DQL-artifact executor owns the semantic artifact, not an arbitrary
|
|
3970
|
+
// one-row structural SQL probe. If the host did not also provide the
|
|
3971
|
+
// read-only SQL executor needed for that probe, native direct joins fail
|
|
3972
|
+
// closed rather than treating an artifact handoff as proof of uniqueness.
|
|
3973
|
+
const fanoutProbe = input.executeGeneratedSql
|
|
3974
|
+
? await probeSemanticJoinFanout(semanticBridgeAnswer.composeResult.fanoutProbeSql, semanticBridgeAnswer.composeResult.tables, input.executeGeneratedSql)
|
|
3975
|
+
: {
|
|
3976
|
+
status: 'blocked',
|
|
3977
|
+
code: 'SEMANTIC_FANOUT_PROBE_ERROR',
|
|
3978
|
+
message: 'DQL did not execute this governed semantic answer because the host could not verify native join fanout before aggregation. Configure the semantic runtime or a read-only SQL probe executor, then retry.',
|
|
3979
|
+
};
|
|
3980
|
+
if (fanoutProbe.status === 'blocked') {
|
|
3981
|
+
// A governed semantic route freezes before execution. If DQL cannot
|
|
3982
|
+
// prove its native join is fanout-safe, it must fail closed rather than
|
|
3983
|
+
// execute, label the result governed, or quietly repair into a
|
|
3984
|
+
// different plan.
|
|
3985
|
+
executionError = fanoutProbe.message;
|
|
3986
|
+
semanticFanoutProbeFailure = fanoutProbe;
|
|
3987
|
+
semanticFanoutBlocked = true;
|
|
3758
3988
|
}
|
|
3759
3989
|
}
|
|
3760
3990
|
try {
|
|
@@ -3776,9 +4006,15 @@ async function runAnswerLoop(input) {
|
|
|
3776
4006
|
executionError = warehouseFailure.redactedMessage;
|
|
3777
4007
|
}
|
|
3778
4008
|
if (executionError
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
4009
|
+
// A host-supplied SQL proposal normally bypasses repair. A router-owned
|
|
4010
|
+
// exploratory proposal is the exception: its frozen plan may authorize
|
|
4011
|
+
// one corrected SQL statement after a retryable warehouse failure.
|
|
4012
|
+
&& (!input.forcedGeneratedProposal || exploratoryExecutionSelected)
|
|
4013
|
+
// A router-selected exploratory plan may receive one bounded
|
|
4014
|
+
// same-plan repair. The repair still goes through a fresh host
|
|
4015
|
+
// authorization; it does not reuse the consumed initial capability.
|
|
4016
|
+
&& (!exploratoryExecutionSelected || Boolean(initialExploratoryExecution))
|
|
4017
|
+
&& !semanticFanoutBlocked
|
|
3782
4018
|
&& !authoritativeSemanticBinding) {
|
|
3783
4019
|
warehouseFailure ??= normalizeWarehouseSqlFailure(executionError, input.semanticDriver);
|
|
3784
4020
|
if (isRetryableGeneratedSqlError(warehouseFailure)) {
|
|
@@ -3804,7 +4040,13 @@ async function runAnswerLoop(input) {
|
|
|
3804
4040
|
],
|
|
3805
4041
|
};
|
|
3806
4042
|
try {
|
|
3807
|
-
result = await executeCurrentSql(
|
|
4043
|
+
result = await executeCurrentSql(exploratoryExecutionSelected
|
|
4044
|
+
? {
|
|
4045
|
+
version: 1,
|
|
4046
|
+
index: 1,
|
|
4047
|
+
parentSqlFingerprint: initialExploratoryExecution.freeze.sqlFingerprint,
|
|
4048
|
+
}
|
|
4049
|
+
: undefined);
|
|
3808
4050
|
executionError = undefined;
|
|
3809
4051
|
warehouseFailure = undefined;
|
|
3810
4052
|
}
|
|
@@ -3836,6 +4078,18 @@ async function runAnswerLoop(input) {
|
|
|
3836
4078
|
schemaContext,
|
|
3837
4079
|
signal: input.signal,
|
|
3838
4080
|
reasoningEffort: input.reasoningEffort,
|
|
4081
|
+
// Only the router-frozen exploratory lane has authority to spend
|
|
4082
|
+
// the exceptional post-warehouse repair transport. Legacy callers
|
|
4083
|
+
// keep their prior unlabelled repair behavior.
|
|
4084
|
+
...(exploratoryExecutionSelected
|
|
4085
|
+
&& Boolean(initialExploratoryExecution)
|
|
4086
|
+
&& frozenExploratoryModelRepairAuthority(input)
|
|
4087
|
+
? {
|
|
4088
|
+
dispatchPhase: 'repair',
|
|
4089
|
+
egressPurpose: 'repair_sql',
|
|
4090
|
+
maxProviderDispatches: 1,
|
|
4091
|
+
}
|
|
4092
|
+
: {}),
|
|
3839
4093
|
});
|
|
3840
4094
|
const repaired = parseProposal(repairedRaw);
|
|
3841
4095
|
if (repaired.sql) {
|
|
@@ -3854,7 +4108,12 @@ async function runAnswerLoop(input) {
|
|
|
3854
4108
|
repairNarrative = repaired.text?.trim() || undefined;
|
|
3855
4109
|
parsed.sql = repaired.sql;
|
|
3856
4110
|
parsed.viz = repaired.viz ?? parsed.viz;
|
|
3857
|
-
|
|
4111
|
+
// A repair fixes SQL bytes only. On the router-owned
|
|
4112
|
+
// exploratory lane, model prose or metadata must never mutate
|
|
4113
|
+
// the host-selected frame, route, outputs, or trust state after
|
|
4114
|
+
// the plan froze.
|
|
4115
|
+
if (!exploratoryExecutionSelected)
|
|
4116
|
+
applyParsedProposalMetadata(parsed, repaired);
|
|
3858
4117
|
contextValidation = {
|
|
3859
4118
|
ok: true,
|
|
3860
4119
|
warnings: [
|
|
@@ -3863,7 +4122,13 @@ async function runAnswerLoop(input) {
|
|
|
3863
4122
|
],
|
|
3864
4123
|
};
|
|
3865
4124
|
try {
|
|
3866
|
-
result = await executeCurrentSql(
|
|
4125
|
+
result = await executeCurrentSql(exploratoryExecutionSelected
|
|
4126
|
+
? {
|
|
4127
|
+
version: 1,
|
|
4128
|
+
index: 1,
|
|
4129
|
+
parentSqlFingerprint: initialExploratoryExecution.freeze.sqlFingerprint,
|
|
4130
|
+
}
|
|
4131
|
+
: undefined);
|
|
3867
4132
|
executionError = undefined;
|
|
3868
4133
|
warehouseFailure = undefined;
|
|
3869
4134
|
}
|
|
@@ -3893,12 +4158,14 @@ async function runAnswerLoop(input) {
|
|
|
3893
4158
|
// A router-selected physical-closure denial happens before a capability
|
|
3894
4159
|
// exists. Its rejected bytes are diagnostics for the host, never a SQL
|
|
3895
4160
|
// preview that a user can review, copy, or turn into a draft.
|
|
3896
|
-
sql: exploratoryClosureDenied ? undefined : parsed.sql,
|
|
4161
|
+
sql: exploratoryClosureDenied || semanticFanoutProbeFailure ? undefined : parsed.sql,
|
|
3897
4162
|
suggestedViz: parsed.viz ?? 'table',
|
|
3898
4163
|
assumptions: [
|
|
3899
4164
|
...(exploratoryClosureDenied
|
|
3900
4165
|
? ['The router-selected physical closure rejected the generated SQL before capability minting.']
|
|
3901
|
-
:
|
|
4166
|
+
: semanticFanoutProbeFailure
|
|
4167
|
+
? ['The native semantic join could not be proven fanout-safe before plan freeze, so DQL did not execute or expose its SQL.']
|
|
4168
|
+
: ['The SQL preview is uncertified until an analyst reviews and promotes the DQL artifact.']),
|
|
3902
4169
|
...(certifiedExecutionFallback
|
|
3903
4170
|
? [`Certified block ${certifiedExecutionFallback.node.name} failed execution and was bypassed: ${certifiedExecutionFallback.error}`]
|
|
3904
4171
|
: []),
|
|
@@ -3922,6 +4189,23 @@ async function runAnswerLoop(input) {
|
|
|
3922
4189
|
}
|
|
3923
4190
|
}
|
|
3924
4191
|
const resultShape = result ? validateAnswerResultShape(questionPlan, result) : undefined;
|
|
4192
|
+
// A router-selected exploratory plan carries a host-bound output
|
|
4193
|
+
// projection. The ordinary generated-answer UX may show a partial result
|
|
4194
|
+
// with a warning, but a frozen exploratory plan may not: displaying it
|
|
4195
|
+
// would silently change the tuple DQL authorized. Keep the connector rows
|
|
4196
|
+
// out of the answer/artifact when a broken executor returns them anyway.
|
|
4197
|
+
const missingFrozenExploratoryOutputs = exploratoryExecutionSelected && result
|
|
4198
|
+
? missingFrozenRequiredOutputProjection(input.resolvedAnalyticalPlan, result)
|
|
4199
|
+
: [];
|
|
4200
|
+
const activeExploratoryOutputFreeze = exploratoryRepairExecution?.freeze ?? initialExploratoryExecution?.freeze;
|
|
4201
|
+
const missingFrozenExploratoryBindingProofs = exploratoryExecutionSelected && result
|
|
4202
|
+
? missingFrozenRequiredOutputBindingProof(input.resolvedAnalyticalPlan, activeExploratoryOutputFreeze)
|
|
4203
|
+
: [];
|
|
4204
|
+
const frozenExploratoryOutputFailures = [...new Set([
|
|
4205
|
+
...missingFrozenExploratoryOutputs,
|
|
4206
|
+
...missingFrozenExploratoryBindingProofs,
|
|
4207
|
+
])];
|
|
4208
|
+
const frozenExploratoryOutputContractFailure = frozenExploratoryOutputFailures.length > 0;
|
|
3925
4209
|
// ANY question whose SQL executed but dropped multiple requested columns used to
|
|
3926
4210
|
// REFUSE outright ("no governed answer"), throwing away a result that actually
|
|
3927
4211
|
// ran. Instead, SURFACE the partial result (review-required) with a warning that
|
|
@@ -3944,13 +4228,16 @@ async function runAnswerLoop(input) {
|
|
|
3944
4228
|
...repairNotes,
|
|
3945
4229
|
...(resultShape?.warnings ?? []),
|
|
3946
4230
|
...(partialShapeWarning ? [partialShapeWarning] : []),
|
|
4231
|
+
...(frozenExploratoryOutputContractFailure
|
|
4232
|
+
? [`The executed exploratory result violated the frozen output contract: ${frozenExploratoryOutputFailures.join(', ')}.`]
|
|
4233
|
+
: []),
|
|
3947
4234
|
...(topNTrimNote ? [topNTrimNote] : []),
|
|
3948
4235
|
...(executionError ? ['The preview execution error must be reviewed before reuse.'] : []),
|
|
3949
4236
|
];
|
|
3950
4237
|
const generatedOutputs = parsed.outputs?.length ? parsed.outputs : resultColumnNames(result);
|
|
3951
4238
|
const generatedRequestedFilters = mergeProposalStringLists(input.followUp?.filters, parsed.requestedFilters);
|
|
3952
4239
|
const generatedRequestedDimensions = mergeProposalStringLists(input.followUp?.dimensions, parsed.requestedDimensions);
|
|
3953
|
-
const baseDqlArtifact = exploratoryClosureDenied
|
|
4240
|
+
const baseDqlArtifact = exploratoryClosureDenied || semanticFanoutProbeFailure || frozenExploratoryOutputContractFailure
|
|
3954
4241
|
? undefined
|
|
3955
4242
|
: result?.dqlArtifact ?? semanticBridgeAnswer?.dqlArtifact ?? buildGeneratedSqlDqlArtifact({
|
|
3956
4243
|
question,
|
|
@@ -3979,7 +4266,7 @@ async function runAnswerLoop(input) {
|
|
|
3979
4266
|
: undefined;
|
|
3980
4267
|
let draftBlock;
|
|
3981
4268
|
let draftCaptureError;
|
|
3982
|
-
if (!exploratoryClosureDenied && input.captureGeneratedDraft && parsed.sql) {
|
|
4269
|
+
if (!exploratoryClosureDenied && !semanticFanoutProbeFailure && !frozenExploratoryOutputContractFailure && input.captureGeneratedDraft && parsed.sql) {
|
|
3983
4270
|
try {
|
|
3984
4271
|
draftBlock = await input.captureGeneratedDraft({
|
|
3985
4272
|
question,
|
|
@@ -4026,10 +4313,21 @@ async function runAnswerLoop(input) {
|
|
|
4026
4313
|
: undefined;
|
|
4027
4314
|
const certifiedMetricAnswer = semanticMetricCertification === 'certified' || semanticMetricCertification === 'reviewed';
|
|
4028
4315
|
const governedMetricExecutionFailure = governedMetricAnswer && Boolean(executionError);
|
|
4029
|
-
const terminalExecutionFailure = governedMetricExecutionFailure || exploratoryClosureDenied;
|
|
4316
|
+
const terminalExecutionFailure = governedMetricExecutionFailure || exploratoryClosureDenied || frozenExploratoryOutputContractFailure;
|
|
4317
|
+
const semanticFanoutTraceError = semanticFanoutProbeFailure
|
|
4318
|
+
? Object.assign(new Error(semanticFanoutProbeFailure.message), {
|
|
4319
|
+
code: semanticFanoutProbeFailure.code,
|
|
4320
|
+
details: { phase: 'validation' },
|
|
4321
|
+
})
|
|
4322
|
+
: executionError;
|
|
4323
|
+
const terminalFailureMessage = frozenExploratoryOutputContractFailure
|
|
4324
|
+
? `The exploratory execution result did not retain proven frozen output binding ${frozenExploratoryOutputFailures.join(', ')}, so DQL did not display it.`
|
|
4325
|
+
: exploratoryClosureDenied
|
|
4326
|
+
? 'The generated SQL referenced a relation outside the router-selected physical closure, so DQL did not execute it.'
|
|
4327
|
+
: semanticFanoutProbeFailure?.message;
|
|
4030
4328
|
const finalSemanticExecutionTrace = semanticTraceAfterExecution(semanticExecutionTrace, {
|
|
4031
4329
|
executed: Boolean(result),
|
|
4032
|
-
...(
|
|
4330
|
+
...(semanticFanoutTraceError ? { error: semanticFanoutTraceError } : {}),
|
|
4033
4331
|
...(result ? { result } : {}),
|
|
4034
4332
|
});
|
|
4035
4333
|
return {
|
|
@@ -4039,21 +4337,25 @@ async function runAnswerLoop(input) {
|
|
|
4039
4337
|
reviewStatus: terminalExecutionFailure ? 'none' : governedMetricAnswer ? 'governed' : 'draft_ready',
|
|
4040
4338
|
semanticMetricCertification,
|
|
4041
4339
|
confidence: terminalExecutionFailure ? 0 : certifiedMetricAnswer ? 0.8 : governedMetricAnswer ? 0.72 : 0.55,
|
|
4042
|
-
text:
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
? 'The generated SQL referenced a relation outside the router-selected physical closure, so DQL did not execute it.'
|
|
4047
|
-
: generatedText,
|
|
4048
|
-
...(exploratoryClosureDenied ? {} : { proposedSql: parsed.sql, sql: parsed.sql }),
|
|
4049
|
-
result,
|
|
4340
|
+
text: terminalFailureMessage ?? generatedText,
|
|
4341
|
+
answer: terminalFailureMessage ?? generatedText,
|
|
4342
|
+
...(exploratoryClosureDenied || semanticFanoutProbeFailure || frozenExploratoryOutputContractFailure ? {} : { proposedSql: parsed.sql, sql: parsed.sql }),
|
|
4343
|
+
result: frozenExploratoryOutputContractFailure ? undefined : result,
|
|
4050
4344
|
executionError,
|
|
4051
4345
|
...(warehouseFailure ? { warehouseFailure } : {}),
|
|
4052
4346
|
...(finalSemanticExecutionTrace ? { semanticExecutionTrace: finalSemanticExecutionTrace } : {}),
|
|
4053
|
-
...(terminalExecutionFailure ? {
|
|
4347
|
+
...(terminalExecutionFailure ? {
|
|
4348
|
+
refusalCode: semanticFanoutProbeFailure ? 'policy_blocked' : 'grounding_gap',
|
|
4349
|
+
} : {}),
|
|
4350
|
+
...(semanticFanoutProbeFailure ? {
|
|
4351
|
+
refusalDetails: {
|
|
4352
|
+
code: semanticFanoutProbeFailure.code,
|
|
4353
|
+
message: semanticFanoutProbeFailure.message,
|
|
4354
|
+
},
|
|
4355
|
+
} : {}),
|
|
4054
4356
|
suggestedViz: parsed.viz ?? 'table',
|
|
4055
|
-
...(exploratoryClosureDenied ? {} : { dqlArtifact: answerDqlArtifact }),
|
|
4056
|
-
...(exploratoryClosureDenied ? {} : {
|
|
4357
|
+
...(exploratoryClosureDenied || semanticFanoutProbeFailure || frozenExploratoryOutputContractFailure ? {} : { dqlArtifact: answerDqlArtifact }),
|
|
4358
|
+
...(exploratoryClosureDenied || semanticFanoutProbeFailure || frozenExploratoryOutputContractFailure ? {} : {
|
|
4057
4359
|
draftBlock,
|
|
4058
4360
|
draftBlockId: draftBlock?.path,
|
|
4059
4361
|
promoteCommand: draftBlock ? `dql certify --from-draft ${draftBlock.path}` : undefined,
|
|
@@ -4095,7 +4397,8 @@ async function runAnswerLoop(input) {
|
|
|
4095
4397
|
considered,
|
|
4096
4398
|
providerUsed: provider.name,
|
|
4097
4399
|
aggregationSafetyProof,
|
|
4098
|
-
...(
|
|
4400
|
+
...(initialExploratoryExecution ? { exploratoryExecutionFreeze: initialExploratoryExecution.freeze } : {}),
|
|
4401
|
+
...(exploratoryRepairExecution ? { exploratoryRepairExecutionFreeze: exploratoryRepairExecution.freeze } : {}),
|
|
4099
4402
|
// Carry the governed metric match so the exit point can name a
|
|
4100
4403
|
// `semantic_metric` route (spec 17, part C).
|
|
4101
4404
|
_semanticMetricMatch: governedMetricAnswer ? semanticMetricMatch ?? undefined : undefined,
|
|
@@ -4135,6 +4438,70 @@ function resultColumnNames(result) {
|
|
|
4135
4438
|
.filter(Boolean);
|
|
4136
4439
|
return columns?.length ? columns : undefined;
|
|
4137
4440
|
}
|
|
4441
|
+
/**
|
|
4442
|
+
* Check only the host-bound aliases from a frozen plan. This is intentionally
|
|
4443
|
+
* stricter than the conversational shape helper: synonym matching is useful
|
|
4444
|
+
* for ordinary narration, but it cannot substitute a requested order/product
|
|
4445
|
+
* identifier after exploratory SQL has been authorized.
|
|
4446
|
+
*/
|
|
4447
|
+
function missingFrozenRequiredOutputProjection(plan, result) {
|
|
4448
|
+
const required = plan?.outputContract?.requiredOutputs ?? [];
|
|
4449
|
+
if (required.length === 0)
|
|
4450
|
+
return [];
|
|
4451
|
+
const columns = new Set((resultColumnNames(result) ?? []).map(canonicalFrozenOutputAlias));
|
|
4452
|
+
return required.flatMap((binding) => {
|
|
4453
|
+
const alias = binding.outputName
|
|
4454
|
+
?? binding.qualifiedId?.split(/[:.]/).at(-1)
|
|
4455
|
+
?? binding.requested;
|
|
4456
|
+
const normalized = canonicalFrozenOutputAlias(alias);
|
|
4457
|
+
return normalized && columns.has(normalized) ? [] : [alias];
|
|
4458
|
+
});
|
|
4459
|
+
}
|
|
4460
|
+
/**
|
|
4461
|
+
* A column name in a returned result does not prove where it came from. The
|
|
4462
|
+
* result is accepted only when the server-owned authorization receipt carries
|
|
4463
|
+
* the exact frozen output-source binding that was parser-validated for the SQL
|
|
4464
|
+
* handed to the connector. Old persisted receipts remain readable; they
|
|
4465
|
+
* simply cannot authorize a new result for a plan that has explicit outputs.
|
|
4466
|
+
*/
|
|
4467
|
+
function missingFrozenRequiredOutputBindingProof(plan, freeze) {
|
|
4468
|
+
const required = plan?.outputContract?.requiredOutputs ?? [];
|
|
4469
|
+
if (required.length === 0)
|
|
4470
|
+
return [];
|
|
4471
|
+
const expected = frozenRequiredOutputBindingProofsForPlan(plan);
|
|
4472
|
+
const missingSourceBindingAliases = required
|
|
4473
|
+
.filter((binding) => !expected.some((proof) => canonicalFrozenOutputAlias(proof.outputName)
|
|
4474
|
+
=== canonicalFrozenOutputAlias(binding.outputName
|
|
4475
|
+
?? binding.qualifiedId?.split(/[:.]/).at(-1)
|
|
4476
|
+
?? binding.requested)))
|
|
4477
|
+
.map((binding) => binding.outputName
|
|
4478
|
+
?? binding.qualifiedId?.split(/[:.]/).at(-1)
|
|
4479
|
+
?? binding.requested);
|
|
4480
|
+
const actual = freeze?.requiredOutputBindings ?? [];
|
|
4481
|
+
const missingProofAliases = expected
|
|
4482
|
+
.filter((proof) => !actual.some((candidate) => sameFrozenRequiredOutputBindingProof(candidate, proof)))
|
|
4483
|
+
.map((proof) => proof.outputName);
|
|
4484
|
+
return [...new Set([...missingSourceBindingAliases, ...missingProofAliases])];
|
|
4485
|
+
}
|
|
4486
|
+
function sameFrozenRequiredOutputBindingProof(actual, expected) {
|
|
4487
|
+
return actual.version === 1
|
|
4488
|
+
&& actual.qualifiedId === expected.qualifiedId
|
|
4489
|
+
&& canonicalFrozenOutputAlias(actual.outputName) === canonicalFrozenOutputAlias(expected.outputName)
|
|
4490
|
+
&& canonicalFrozenSourceIdentifier(actual.relation) === canonicalFrozenSourceIdentifier(expected.relation)
|
|
4491
|
+
&& canonicalFrozenSourceIdentifier(actual.column) === canonicalFrozenSourceIdentifier(expected.column);
|
|
4492
|
+
}
|
|
4493
|
+
function canonicalFrozenOutputAlias(value) {
|
|
4494
|
+
return value.toLowerCase()
|
|
4495
|
+
.replace(/["`]/g, '')
|
|
4496
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
4497
|
+
.replace(/^_+|_+$/g, '');
|
|
4498
|
+
}
|
|
4499
|
+
function canonicalFrozenSourceIdentifier(value) {
|
|
4500
|
+
return value.toLowerCase()
|
|
4501
|
+
.replace(/["`\[\]]/g, '')
|
|
4502
|
+
.replace(/\s*\.\s*/g, '.')
|
|
4503
|
+
.trim();
|
|
4504
|
+
}
|
|
4138
4505
|
function analyticalCapabilityForPlan(plan, contextPack, registry = []) {
|
|
4139
4506
|
const selected = normalizeMetricCapabilityContract(plan.selectedCapability);
|
|
4140
4507
|
if (selected) {
|
|
@@ -4501,8 +4868,11 @@ async function executeSemanticAnalyticalGraph(input) {
|
|
|
4501
4868
|
});
|
|
4502
4869
|
aggregationSafetyProof = proof;
|
|
4503
4870
|
if (proof.status !== 'safe') {
|
|
4504
|
-
return analyticalGraphFailureAnswer(input, '
|
|
4505
|
-
|
|
4871
|
+
return analyticalGraphFailureAnswer(input, 'COMPILATION_FAILED', semanticAggregationSafetyFailure(proof), {
|
|
4872
|
+
// This is compiler/plan validation before a warehouse statement is
|
|
4873
|
+
// dispatched. It must be represented as semantic.compile, never as
|
|
4874
|
+
// result normalization or a SQL execution failure.
|
|
4875
|
+
phase: 'compilation',
|
|
4506
4876
|
dqlArtifact: composed.dqlArtifact,
|
|
4507
4877
|
compiledSql: composed.sql,
|
|
4508
4878
|
semanticExecutionTrace,
|
|
@@ -4528,11 +4898,25 @@ async function executeSemanticAnalyticalGraph(input) {
|
|
|
4528
4898
|
}
|
|
4529
4899
|
catch (error) {
|
|
4530
4900
|
const message = error instanceof Error ? error.message : String(error);
|
|
4901
|
+
// A missing local execution target fails at the host's pre-execution
|
|
4902
|
+
// boundary. It must not be recast as a semantic compiler failure merely
|
|
4903
|
+
// because the semantic graph owns the callback. Preserve the narrow,
|
|
4904
|
+
// producer-tagged fact for the durable Ask receipt; it does not alter
|
|
4905
|
+
// the frozen route, trust, or user-facing analytical failure.
|
|
4906
|
+
const observabilityExecutionFailure = observabilityFailureForFrozenExecution(analyticalErrorDetail(error));
|
|
4531
4907
|
return analyticalGraphFailureAnswer(input, 'EXECUTION_FAILED', message, {
|
|
4532
4908
|
phase: 'execution',
|
|
4533
4909
|
dqlArtifact: composed.dqlArtifact,
|
|
4534
4910
|
compiledSql: composed.sql,
|
|
4535
4911
|
semanticExecutionTrace: semanticTraceAfterExecution(semanticExecutionTrace, { executed: false, error }),
|
|
4912
|
+
// The exact aggregation proof already passed before the connector
|
|
4913
|
+
// call. Preserve it so presentation never recasts a warehouse binder
|
|
4914
|
+
// error as a failed semantic aggregation proof.
|
|
4915
|
+
aggregationSafetyProof,
|
|
4916
|
+
// This producer-tagged fact is diagnostic only: it does not alter
|
|
4917
|
+
// routing, trust, or the immutable frozen plan.
|
|
4918
|
+
warehouseFailure: normalizeWarehouseSqlFailure(error, input.input.semanticDriver),
|
|
4919
|
+
...(observabilityExecutionFailure ? { observabilityExecutionFailure } : {}),
|
|
4536
4920
|
});
|
|
4537
4921
|
}
|
|
4538
4922
|
const normalized = normalizeAnalyticalSourceResult({
|
|
@@ -4962,6 +5346,10 @@ function analyticalGraphFailureAnswer(input, code, reason, artifacts = {}) {
|
|
|
4962
5346
|
...(artifacts.dqlArtifact ? { dqlArtifact: artifacts.dqlArtifact } : {}),
|
|
4963
5347
|
...(artifacts.semanticExecutionTrace ? { semanticExecutionTrace: artifacts.semanticExecutionTrace } : {}),
|
|
4964
5348
|
...(artifacts.aggregationSafetyProof ? { aggregationSafetyProof: artifacts.aggregationSafetyProof } : {}),
|
|
5349
|
+
...(artifacts.warehouseFailure ? { warehouseFailure: artifacts.warehouseFailure } : {}),
|
|
5350
|
+
...(artifacts.observabilityExecutionFailure
|
|
5351
|
+
? { observabilityExecutionFailure: artifacts.observabilityExecutionFailure }
|
|
5352
|
+
: {}),
|
|
4965
5353
|
citations: contextPackCitations(input.input.contextPack, 8),
|
|
4966
5354
|
considered: input.considered,
|
|
4967
5355
|
contextPack: input.input.contextPack,
|
|
@@ -5133,6 +5521,38 @@ Rules:
|
|
|
5133
5521
|
// combined dataset — show them separately" refusal into the join the user asked
|
|
5134
5522
|
// for, while still allowing an honest refusal if context is truly missing.
|
|
5135
5523
|
const FORCE_JOIN_INSTRUCTION = `Your previous attempt declined to produce SQL. Re-read the supplied schema, metadata, and any "Knowledge graph join routes": this question CAN be answered by joining the grounded tables along their documented keys. Do NOT refuse, and do NOT suggest showing the datasets separately — compose ONE read-only SELECT/WITH that joins the relevant tables to answer it directly, following the JSON contract from rule 3. State the grain and the exact join path in the summary. Only if a required table, column, or join key is truly absent from the supplied context may you still ask a clarifying question.`;
|
|
5524
|
+
/**
|
|
5525
|
+
* A model-decline correction is only a repair when the router already froze a
|
|
5526
|
+
* bounded exploratory RAP and the host supplied the matching capability
|
|
5527
|
+
* mint/consume closures. Anything weaker is a new planning attempt and must
|
|
5528
|
+
* not borrow the repair transport allowance.
|
|
5529
|
+
*/
|
|
5530
|
+
function frozenExploratoryModelRepairAuthority(input) {
|
|
5531
|
+
const plan = input.resolvedAnalyticalPlan;
|
|
5532
|
+
return input.selectedCascadeTier === 'exploratory_sql'
|
|
5533
|
+
&& plan?.mode === 'authoritative'
|
|
5534
|
+
&& plan.capability === 'bounded_exploration'
|
|
5535
|
+
&& Boolean(plan.planId && plan.fingerprint && plan.snapshotId)
|
|
5536
|
+
&& Boolean(input.prepareExploratorySqlExecution && input.executeAgenticGeneratedSql);
|
|
5537
|
+
}
|
|
5538
|
+
/**
|
|
5539
|
+
* The corrective prompt repeats only host-owned tuple facts. It never asks
|
|
5540
|
+
* the model to choose a new route, relation closure, output contract, or
|
|
5541
|
+
* trust label; SQL authorization re-proves those facts after the response.
|
|
5542
|
+
*/
|
|
5543
|
+
function renderFrozenExploratoryRepairContract(plan, questionPlan) {
|
|
5544
|
+
const requiredOutputs = plan.outputContract?.requiredOutputs?.filter((output) => output.status === 'resolved' && output.outputName && output.qualifiedId)
|
|
5545
|
+
.map((output) => `${output.outputName} <- ${output.qualifiedId}`)
|
|
5546
|
+
?? [];
|
|
5547
|
+
return [
|
|
5548
|
+
'This is the one permitted correction for an already frozen, review-required exploratory plan.',
|
|
5549
|
+
renderRequestedShapeForRepair(questionPlan),
|
|
5550
|
+
requiredOutputs.length > 0
|
|
5551
|
+
? `Frozen required output bindings (preserve exactly): ${requiredOutputs.join('; ')}`
|
|
5552
|
+
: 'Preserve the frozen requested tuple exactly.',
|
|
5553
|
+
'Use only the relations and columns already supplied in the bounded context. Do not add a relation, change the ranking, remove an output, change the limit, or alter the analytical meaning. The host will validate the same frozen snapshot and closure before execution.',
|
|
5554
|
+
].join('\n');
|
|
5555
|
+
}
|
|
5136
5556
|
/**
|
|
5137
5557
|
* Produces the prompt-facing subset of a broad local context pack. Global
|
|
5138
5558
|
* records remain available, while a record explicitly owned by another domain
|
|
@@ -7842,6 +8262,11 @@ async function requestSqlRepair(input) {
|
|
|
7842
8262
|
// bumping would let an internal preview-repair exceed that cap. Escalation-level
|
|
7843
8263
|
// repairs bump-then-clamp in the host (resolveRunReasoningEffort).
|
|
7844
8264
|
reasoningEffort: input.reasoningEffort,
|
|
8265
|
+
...(input.dispatchPhase ? { dispatchPhase: input.dispatchPhase } : {}),
|
|
8266
|
+
...(input.egressPurpose ? { egressPurpose: input.egressPurpose } : {}),
|
|
8267
|
+
...(input.maxProviderDispatches !== undefined
|
|
8268
|
+
? { maxProviderDispatches: input.maxProviderDispatches }
|
|
8269
|
+
: {}),
|
|
7845
8270
|
});
|
|
7846
8271
|
}
|
|
7847
8272
|
async function generateProposalWithOptionalTools(input) {
|
|
@@ -7849,6 +8274,11 @@ async function generateProposalWithOptionalTools(input) {
|
|
|
7849
8274
|
const options = {
|
|
7850
8275
|
signal: input.signal,
|
|
7851
8276
|
reasoningEffort: input.reasoningEffort,
|
|
8277
|
+
...(input.dispatchPhase ? { dispatchPhase: input.dispatchPhase } : {}),
|
|
8278
|
+
...(input.egressPurpose ? { egressPurpose: input.egressPurpose } : {}),
|
|
8279
|
+
...(input.maxProviderDispatches !== undefined
|
|
8280
|
+
? { maxProviderDispatches: input.maxProviderDispatches }
|
|
8281
|
+
: {}),
|
|
7852
8282
|
};
|
|
7853
8283
|
// No tools → plain generation (nothing for the loop to drive).
|
|
7854
8284
|
if (tools.length === 0) {
|
|
@@ -8245,10 +8675,25 @@ function isRetryableGeneratedSqlError(failure) {
|
|
|
8245
8675
|
return failure.retryDisposition === 'model_repair';
|
|
8246
8676
|
}
|
|
8247
8677
|
/**
|
|
8248
|
-
* Only SQL-shape/binding failures may leave
|
|
8249
|
-
* authorization, cancellation, and timeout failures would affect
|
|
8250
|
-
* route and must remain terminal rather than triggering wasteful
|
|
8678
|
+
* Only SQL-shape/binding failures may leave a frozen governed lane.
|
|
8679
|
+
* Connectivity, authorization, cancellation, and timeout failures would affect
|
|
8680
|
+
* every query route and must remain terminal rather than triggering wasteful
|
|
8681
|
+
* provider work.
|
|
8251
8682
|
*/
|
|
8683
|
+
function observabilityFailureForFrozenExecution(detail) {
|
|
8684
|
+
// Do not infer this from user-facing error text. The local runtime tags the
|
|
8685
|
+
// physical pre-execution boundary; this projection only carries that exact
|
|
8686
|
+
// typed fact into the redacted trace after a governed plan has frozen.
|
|
8687
|
+
if (detail?.origin !== 'host' || detail.stage !== 'execute' || detail.code !== 'connection_not_configured') {
|
|
8688
|
+
return undefined;
|
|
8689
|
+
}
|
|
8690
|
+
return {
|
|
8691
|
+
version: 1,
|
|
8692
|
+
phase: 'execution',
|
|
8693
|
+
cause: 'connection_not_configured',
|
|
8694
|
+
safeAction: 'configure_connection',
|
|
8695
|
+
};
|
|
8696
|
+
}
|
|
8252
8697
|
function isRetryableCertifiedExecutionError(error) {
|
|
8253
8698
|
return /\b(?:binder|parser|catalog)\s+error\b/i.test(error)
|
|
8254
8699
|
// DuckDB says "ambiguous reference"; Snowflake says "ambiguous column name".
|
|
@@ -9213,29 +9658,45 @@ function uniqueAssets(assets) {
|
|
|
9213
9658
|
return Array.from(byId.values());
|
|
9214
9659
|
}
|
|
9215
9660
|
/**
|
|
9216
|
-
* Execute the semantic layer's fanout probe
|
|
9217
|
-
*
|
|
9218
|
-
*
|
|
9219
|
-
*
|
|
9661
|
+
* Execute the semantic layer's fanout probe before a governed native semantic
|
|
9662
|
+
* join freezes. A native join that cannot be checked is not a governed-safe
|
|
9663
|
+
* join: fail closed with a redacted typed failure instead of executing a query
|
|
9664
|
+
* that could multiply aggregates. MetricFlow/dbt Cloud paths have their own
|
|
9665
|
+
* compiler guarantees and do not use this native direct-join guard.
|
|
9220
9666
|
*/
|
|
9221
9667
|
export async function probeSemanticJoinFanout(probeSql, joinedTables, executeSql) {
|
|
9222
9668
|
try {
|
|
9223
9669
|
const payload = await executeSql(probeSql);
|
|
9224
9670
|
const counts = parseFanoutProbeCounts(payload);
|
|
9225
|
-
if (!counts || counts.base <= 0
|
|
9226
|
-
return
|
|
9671
|
+
if (!counts || counts.base <= 0) {
|
|
9672
|
+
return {
|
|
9673
|
+
status: 'blocked',
|
|
9674
|
+
code: 'SEMANTIC_FANOUT_PROBE_UNPARSEABLE',
|
|
9675
|
+
message: 'DQL did not execute this governed semantic answer because its join-safety probe did not return verifiable row counts. Check the declared relationship or run the metric through MetricFlow / dbt Cloud, then retry.',
|
|
9676
|
+
};
|
|
9677
|
+
}
|
|
9678
|
+
if (counts.joined <= counts.base)
|
|
9679
|
+
return { status: 'safe' };
|
|
9227
9680
|
const factor = counts.joined / counts.base;
|
|
9228
9681
|
const tables = joinedTables.filter(Boolean).join(', ');
|
|
9229
|
-
return
|
|
9230
|
-
|
|
9231
|
-
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
|
|
9235
|
-
|
|
9682
|
+
return {
|
|
9683
|
+
status: 'blocked',
|
|
9684
|
+
code: 'SEMANTIC_FANOUT_DUPLICATE_KEY',
|
|
9685
|
+
message: [
|
|
9686
|
+
`Blocked a governed semantic answer whose join inflates results: joining ${tables || 'the declared tables'}`,
|
|
9687
|
+
`turned ${counts.base.toLocaleString('en-US')} base rows into ${counts.joined.toLocaleString('en-US')} (×${factor >= 10 ? Math.round(factor) : factor.toFixed(1)}).`,
|
|
9688
|
+
'The declared join key is not unique on the joined side, so every aggregated value would be multiplied.',
|
|
9689
|
+
'Deduplicate the joined model (for example, filter a slowly-changing dimension to current records)',
|
|
9690
|
+
'or execute this metric through MetricFlow / dbt Cloud, then retry. No numbers were shown because they would be wrong.',
|
|
9691
|
+
].join(' '),
|
|
9692
|
+
};
|
|
9236
9693
|
}
|
|
9237
9694
|
catch {
|
|
9238
|
-
return
|
|
9695
|
+
return {
|
|
9696
|
+
status: 'blocked',
|
|
9697
|
+
code: 'SEMANTIC_FANOUT_PROBE_ERROR',
|
|
9698
|
+
message: 'DQL did not execute this governed semantic answer because it could not verify join fanout before aggregation. Check the declared relationship or run the metric through MetricFlow / dbt Cloud, then retry.',
|
|
9699
|
+
};
|
|
9239
9700
|
}
|
|
9240
9701
|
}
|
|
9241
9702
|
function parseFanoutProbeCounts(payload) {
|