@duckcodeailabs/dql-agent 1.14.1 → 1.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-run-engine.d.ts +9 -1
- package/dist/agent-run-engine.d.ts.map +1 -1
- package/dist/agent-run-engine.js +199 -17
- package/dist/agent-run-engine.js.map +1 -1
- package/dist/agent-run-gates.d.ts.map +1 -1
- package/dist/agent-run-gates.js +12 -0
- package/dist/agent-run-gates.js.map +1 -1
- package/dist/agentic/analyst-loop.d.ts.map +1 -1
- package/dist/agentic/analyst-loop.js +48 -28
- package/dist/agentic/analyst-loop.js.map +1 -1
- package/dist/agentic/research/synthesis.d.ts +4 -0
- package/dist/agentic/research/synthesis.d.ts.map +1 -1
- package/dist/agentic/research/synthesis.js +23 -8
- package/dist/agentic/research/synthesis.js.map +1 -1
- package/dist/agentic/research-agent.d.ts.map +1 -1
- package/dist/agentic/research-agent.js +3 -2
- package/dist/agentic/research-agent.js.map +1 -1
- package/dist/agentic/sql-authorization.d.ts.map +1 -1
- package/dist/agentic/sql-authorization.js +230 -2
- package/dist/agentic/sql-authorization.js.map +1 -1
- package/dist/analytical-frame.d.ts.map +1 -1
- package/dist/analytical-frame.js +10 -1
- package/dist/analytical-frame.js.map +1 -1
- package/dist/analytical-orchestration.d.ts +302 -0
- package/dist/analytical-orchestration.d.ts.map +1 -1
- package/dist/analytical-orchestration.js +564 -0
- package/dist/analytical-orchestration.js.map +1 -1
- package/dist/answer-loop.d.ts +48 -0
- package/dist/answer-loop.d.ts.map +1 -1
- package/dist/answer-loop.js +679 -85
- package/dist/answer-loop.js.map +1 -1
- package/dist/answer-shape.d.ts +32 -1
- package/dist/answer-shape.d.ts.map +1 -1
- package/dist/answer-shape.js +49 -3
- package/dist/answer-shape.js.map +1 -1
- package/dist/conversation/snapshot.d.ts +20 -0
- package/dist/conversation/snapshot.d.ts.map +1 -1
- package/dist/conversation/snapshot.js +30 -1
- package/dist/conversation/snapshot.js.map +1 -1
- package/dist/fixtures/ask-ai-office-shaped.d.ts +153 -0
- package/dist/fixtures/ask-ai-office-shaped.d.ts.map +1 -0
- package/dist/fixtures/ask-ai-office-shaped.js +94 -0
- package/dist/fixtures/ask-ai-office-shaped.js.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/intent-controller.d.ts +28 -0
- package/dist/intent-controller.d.ts.map +1 -1
- package/dist/intent-controller.js +30 -0
- package/dist/intent-controller.js.map +1 -1
- package/dist/meaning-resolution.d.ts +69 -2
- package/dist/meaning-resolution.d.ts.map +1 -1
- package/dist/meaning-resolution.js +128 -7
- package/dist/meaning-resolution.js.map +1 -1
- package/dist/metadata/analysis-planner.d.ts.map +1 -1
- package/dist/metadata/analysis-planner.js +124 -7
- package/dist/metadata/analysis-planner.js.map +1 -1
- package/dist/metadata/block-fit.d.ts +18 -0
- package/dist/metadata/block-fit.d.ts.map +1 -1
- package/dist/metadata/block-fit.js +250 -38
- package/dist/metadata/block-fit.js.map +1 -1
- package/dist/metadata/catalog.d.ts.map +1 -1
- package/dist/metadata/catalog.js +57 -11
- package/dist/metadata/catalog.js.map +1 -1
- package/dist/metadata/meaning-evidence.d.ts +10 -1
- package/dist/metadata/meaning-evidence.d.ts.map +1 -1
- package/dist/metadata/meaning-evidence.js +261 -47
- package/dist/metadata/meaning-evidence.js.map +1 -1
- package/dist/research-loop.d.ts.map +1 -1
- package/dist/research-loop.js +23 -1
- package/dist/research-loop.js.map +1 -1
- package/dist/resolved-analytical-plan.d.ts +7 -0
- package/dist/resolved-analytical-plan.d.ts.map +1 -1
- package/dist/resolved-analytical-plan.js +11 -4
- package/dist/resolved-analytical-plan.js.map +1 -1
- package/dist/router.d.ts +4 -3
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +1796 -125
- package/dist/router.js.map +1 -1
- package/dist/semantic-bridge/member-select.d.ts.map +1 -1
- package/dist/semantic-bridge/member-select.js +39 -21
- package/dist/semantic-bridge/member-select.js.map +1 -1
- package/package.json +5 -5
package/dist/answer-loop.js
CHANGED
|
@@ -30,7 +30,7 @@ import { certifiedFitAllowsTier1, certifiedTerminationVerdict, evaluateCertified
|
|
|
30
30
|
import { buildGovernedMetricFirstSql, matchSemanticMetric, resolveGovernedMetricDefinition, resolveGovernedMetricSql, semanticMetricEmbeddingOptions } from './metadata/metric-match.js';
|
|
31
31
|
import { decideAgentAction } from './intent-controller.js';
|
|
32
32
|
import { createContextLedger } from './grounding/context-ledger.js';
|
|
33
|
-
import { validateAnswerResultShape } from './answer-shape.js';
|
|
33
|
+
import { validateAnswerResultShape, } from './answer-shape.js';
|
|
34
34
|
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';
|
|
@@ -384,6 +384,142 @@ function certifiedInvocationInputs(block, plan) {
|
|
|
384
384
|
}
|
|
385
385
|
return Object.keys(parameters).length > 0 ? { parameters, parameterSources } : {};
|
|
386
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* Return only output aliases that the selected certified artifact itself
|
|
389
|
+
* declares. This is deliberately built after the router has frozen the plan:
|
|
390
|
+
* retrieval context, tags, examples, and sibling metrics never enter the
|
|
391
|
+
* result-shape proof.
|
|
392
|
+
*
|
|
393
|
+
* A block may express an entity role (`customer`) through a readable label
|
|
394
|
+
* column (`customer_name`), or a declared dimension role through a physical
|
|
395
|
+
* projection alias (`product_type`). Those are valid execution contracts only
|
|
396
|
+
* when the selected block owns both sides of the binding.
|
|
397
|
+
*/
|
|
398
|
+
function frozenCertifiedResultShapeBindings(plan, block, questionPlan, options = {}) {
|
|
399
|
+
if (!plan || block.kind !== 'block')
|
|
400
|
+
return [];
|
|
401
|
+
const canonical = (value) => value
|
|
402
|
+
.toLowerCase()
|
|
403
|
+
.replace(/[_\-./]+/g, ' ')
|
|
404
|
+
.replace(/[^a-z0-9 ]+/g, ' ')
|
|
405
|
+
.split(/\s+/)
|
|
406
|
+
.filter(Boolean)
|
|
407
|
+
.map((token) => token.endsWith('ies') && token.length > 4
|
|
408
|
+
? `${token.slice(0, -3)}y`
|
|
409
|
+
: token.endsWith('s') && token.length > 3
|
|
410
|
+
? token.slice(0, -1)
|
|
411
|
+
: token)
|
|
412
|
+
.join('_');
|
|
413
|
+
const declared = (block.outputContract?.length
|
|
414
|
+
? block.outputContract.map((output) => ({ name: output.name, role: output.role }))
|
|
415
|
+
: (block.declaredOutputs ?? []).map((name) => ({ name })))
|
|
416
|
+
.filter((output) => Boolean(output.name?.trim()));
|
|
417
|
+
const declaredByCanonical = new Map(declared.map((output) => [canonical(output.name), output]));
|
|
418
|
+
const bindings = [];
|
|
419
|
+
const add = (binding) => {
|
|
420
|
+
const key = `${binding.role}\0${canonical(binding.requested)}\0${canonical(binding.output)}`;
|
|
421
|
+
const existing = bindings.find((candidate) => `${candidate.role}\0${canonical(candidate.requested)}\0${canonical(candidate.output)}` === key);
|
|
422
|
+
if (existing) {
|
|
423
|
+
const aliases = [...(existing.aliases ?? []), ...(binding.aliases ?? [])]
|
|
424
|
+
.map((alias) => alias.trim())
|
|
425
|
+
.filter(Boolean);
|
|
426
|
+
if (aliases.length > 0)
|
|
427
|
+
existing.aliases = Array.from(new Set(aliases));
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
bindings.push(binding);
|
|
431
|
+
};
|
|
432
|
+
// Measures are only admitted when the frozen plan retained the block's
|
|
433
|
+
// strict, artifact-local declared output identity. Do not infer an alias
|
|
434
|
+
// from output role here: that would reintroduce lifetime_spend -> revenue.
|
|
435
|
+
for (const measure of plan.query.measures) {
|
|
436
|
+
if (!measure.outputName)
|
|
437
|
+
continue;
|
|
438
|
+
const output = declaredByCanonical.get(canonical(measure.outputName));
|
|
439
|
+
if (output)
|
|
440
|
+
add({ requested: measure.requested, output: output.name, role: 'measure' });
|
|
441
|
+
}
|
|
442
|
+
const requestedDimensions = Array.from(new Set([
|
|
443
|
+
...plan.query.dimensions.map((binding) => binding.requested),
|
|
444
|
+
...questionPlan.requestedShape.dimensions,
|
|
445
|
+
].map((value) => canonical(value)).filter(Boolean)));
|
|
446
|
+
const declaredEntities = new Set((block.entities ?? []).map(canonical).filter(Boolean));
|
|
447
|
+
const declaredDimensions = new Set((block.dimensions ?? []).map(canonical).filter(Boolean));
|
|
448
|
+
const typedDimensionOutputs = declared.filter((output) => output.role === 'dimension');
|
|
449
|
+
for (const requested of requestedDimensions) {
|
|
450
|
+
// The declared entity role can be rendered as a standard display label,
|
|
451
|
+
// e.g. `customer` -> `customer_name`. Require both the authored entity
|
|
452
|
+
// declaration and the output's explicit entity-label shape.
|
|
453
|
+
if (declaredEntities.has(requested)) {
|
|
454
|
+
const entityLabel = declared.find((output) => {
|
|
455
|
+
const outputName = canonical(output.name);
|
|
456
|
+
return output.role === 'entity_label'
|
|
457
|
+
|| outputName === `${requested}_name`
|
|
458
|
+
|| outputName === `${requested}_label`;
|
|
459
|
+
});
|
|
460
|
+
if (entityLabel)
|
|
461
|
+
add({ requested, output: entityLabel.name, role: 'entity_label' });
|
|
462
|
+
}
|
|
463
|
+
// An exact declared output is an artifact-local business-dimension binding
|
|
464
|
+
// even when an older authored block predates explicit `role = dimension`.
|
|
465
|
+
// This is not a pooled or lexical alias: both the requested business
|
|
466
|
+
// dimension and the selected artifact's own projection must agree.
|
|
467
|
+
const exactDeclaredDimension = declaredByCanonical.get(requested);
|
|
468
|
+
if (declaredDimensions.has(requested) && exactDeclaredDimension) {
|
|
469
|
+
add({ requested, output: exactDeclaredDimension.name, role: 'dimension' });
|
|
470
|
+
// Otherwise a physical output can stand for a business dimension only when
|
|
471
|
+
// the block declares that dimension and has one unambiguous typed
|
|
472
|
+
// dimension projection. Multiple physical outputs require an authored
|
|
473
|
+
// alias rather than a guess.
|
|
474
|
+
}
|
|
475
|
+
else if (declaredDimensions.has(requested) && typedDimensionOutputs.length === 1) {
|
|
476
|
+
add({ requested, output: typedDimensionOutputs[0].name, role: 'dimension' });
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
// An exact *unique* authored example can prove that a parser token is a
|
|
480
|
+
// member value of the selected block's already-declared dimension. For
|
|
481
|
+
// example, the block-owned example "revenue by food and drink" returns the
|
|
482
|
+
// declared `category` output; `food` is not an independently requested
|
|
483
|
+
// physical column. Keep this binding intentionally local to the frozen
|
|
484
|
+
// block and its resolved dimension: no tags, descriptions, sibling evidence,
|
|
485
|
+
// or general lexical aliases are admitted here. Measures remain strict.
|
|
486
|
+
if (options.uniqueExactExampleContract === true) {
|
|
487
|
+
const plannedDimensions = new Set(plan.query.dimensions.map((binding) => canonical(binding.requested)));
|
|
488
|
+
const resolvedDimensionBindings = bindings.filter((binding) => binding.role === 'dimension'
|
|
489
|
+
&& plannedDimensions.has(canonical(binding.requested))
|
|
490
|
+
&& declaredByCanonical.has(canonical(binding.output)));
|
|
491
|
+
if (resolvedDimensionBindings.length === 1) {
|
|
492
|
+
const dimensionBinding = resolvedDimensionBindings[0];
|
|
493
|
+
const protectedTerms = new Set([
|
|
494
|
+
...questionPlan.requestedShape.measures,
|
|
495
|
+
...plan.query.measures.map((binding) => binding.requested),
|
|
496
|
+
...questionPlan.requestedShape.filters,
|
|
497
|
+
].map(canonical).filter(Boolean));
|
|
498
|
+
const structuralRoles = new Set([
|
|
499
|
+
'product', 'customer', 'account', 'user', 'member', 'category', 'segment',
|
|
500
|
+
'region', 'channel', 'order', 'day', 'week', 'month', 'quarter', 'year',
|
|
501
|
+
]);
|
|
502
|
+
const memberNoise = Array.from(new Set([
|
|
503
|
+
...questionPlan.requestedShape.dimensions,
|
|
504
|
+
...questionPlan.requestedShape.requiredOutputs,
|
|
505
|
+
].map(canonical).filter(Boolean))).filter((token) => token !== canonical(dimensionBinding.requested)
|
|
506
|
+
&& !declaredByCanonical.has(token)
|
|
507
|
+
&& !declaredEntities.has(token)
|
|
508
|
+
&& !declaredDimensions.has(token)
|
|
509
|
+
&& !protectedTerms.has(token)
|
|
510
|
+
&& !structuralRoles.has(token));
|
|
511
|
+
if (memberNoise.length > 0) {
|
|
512
|
+
add({
|
|
513
|
+
requested: dimensionBinding.requested,
|
|
514
|
+
output: dimensionBinding.output,
|
|
515
|
+
role: 'dimension',
|
|
516
|
+
aliases: memberNoise,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return bindings;
|
|
522
|
+
}
|
|
387
523
|
function damerauLevenshteinDistance(left, right) {
|
|
388
524
|
const rows = left.length + 1;
|
|
389
525
|
const columns = right.length + 1;
|
|
@@ -1201,6 +1337,20 @@ function cascadeExecutionStatus(result) {
|
|
|
1201
1337
|
*/
|
|
1202
1338
|
function deriveAiRoute(result, metricMatch) {
|
|
1203
1339
|
if (result.kind === 'no_answer') {
|
|
1340
|
+
// Preserve the frozen certified tier as provenance when that exact block
|
|
1341
|
+
// executed but failed its own result contract. The route is not a trust
|
|
1342
|
+
// badge: `certification` remains review-required and no generated fallback
|
|
1343
|
+
// may be selected after freeze.
|
|
1344
|
+
if (result.certifiedResultShapeFailure && result.sourceTier === 'certified_artifact') {
|
|
1345
|
+
const ref = result.sourceCertifiedBlock ?? result.block?.name ?? result.citations[0]?.name;
|
|
1346
|
+
return {
|
|
1347
|
+
tier: 'certified_block',
|
|
1348
|
+
label: ref
|
|
1349
|
+
? `Certified block ${ref} did not return the complete requested output shape.`
|
|
1350
|
+
: 'The selected certified block did not return the complete requested output shape.',
|
|
1351
|
+
ref,
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1204
1354
|
if (result.exploratoryCandidate) {
|
|
1205
1355
|
return {
|
|
1206
1356
|
tier: 'no_answer',
|
|
@@ -1264,6 +1414,17 @@ async function runAnswerLoop(input) {
|
|
|
1264
1414
|
const scopedContextPack = questionDomainScope.length > 0
|
|
1265
1415
|
? scopeContextPackToQuestionDomains(input.contextPack, questionDomainScope, input.manifest)
|
|
1266
1416
|
: input.contextPack;
|
|
1417
|
+
// The router has already chosen the physical exploratory closure. Treat it
|
|
1418
|
+
// as an execution authority boundary here, before any prompt is constructed
|
|
1419
|
+
// or provider is dispatched. Full retrieval stays on `input.contextPack`
|
|
1420
|
+
// for receipts and diagnostics only.
|
|
1421
|
+
const forcedExploratoryTier = input.selectedCascadeTier === 'exploratory_sql';
|
|
1422
|
+
const exploratoryClosureContextPack = forcedExploratoryTier
|
|
1423
|
+
? scopeContextPackToExploratoryCandidateClosure(scopedContextPack, input.exploratoryCandidateIds)
|
|
1424
|
+
: undefined;
|
|
1425
|
+
const executionContextPack = forcedExploratoryTier
|
|
1426
|
+
? exploratoryClosureContextPack
|
|
1427
|
+
: scopedContextPack;
|
|
1267
1428
|
// Select the RELEVANT skills (not all) for this question; keep pinned project
|
|
1268
1429
|
// skills (SQL conventions). Block hints still come from the full set so a
|
|
1269
1430
|
// preferred-block mapping is never lost.
|
|
@@ -1305,7 +1466,7 @@ async function runAnswerLoop(input) {
|
|
|
1305
1466
|
const semanticHits = kg.search({ query: question, ...searchScope, kinds: SEMANTIC_KINDS, limit: 12 });
|
|
1306
1467
|
const manifestHits = kg.search({ query: question, ...searchScope, kinds: MANIFEST_KINDS, limit: 12 });
|
|
1307
1468
|
const considered = mergeHits(artifactHits, semanticHits, manifestHits, kg.search({ query: question, domain, limit: 10 })).slice(0, 30);
|
|
1308
|
-
const schemaContext = schemaContextWithAllowedSqlContext(schemaContextWithinQuestionScope(input.schemaContext ?? [], input.contextPack,
|
|
1469
|
+
const schemaContext = schemaContextWithAllowedSqlContext(schemaContextWithinQuestionScope(input.schemaContext ?? [], input.contextPack, executionContextPack), executionContextPack);
|
|
1309
1470
|
const catalogRoute = input.contextPack?.routeDecision;
|
|
1310
1471
|
const baseQuestionPlan = input.contextPack?.questionPlan?.requestedShape
|
|
1311
1472
|
? input.contextPack.questionPlan
|
|
@@ -1321,12 +1482,35 @@ async function runAnswerLoop(input) {
|
|
|
1321
1482
|
// "exact certified" route into the generation prompt. The model should decide
|
|
1322
1483
|
// from compatible certified evidence, semantic members, and dbt/runtime
|
|
1323
1484
|
// columns—not copy a customer-grain worked example into a product-grain ask.
|
|
1324
|
-
const promptContextPack = contextPackForRequestedShape(
|
|
1485
|
+
const promptContextPack = contextPackForRequestedShape(executionContextPack, question, questionPlan, kg);
|
|
1325
1486
|
const repairBudgetState = createCascadeBudgetState(input.cascadeBudgetModel);
|
|
1326
|
-
|
|
1487
|
+
// A pre-freeze exploratory selection is already the router's result of
|
|
1488
|
+
// evaluating certified → semantic → relational eligibility. Do not let the
|
|
1489
|
+
// legacy answer loop independently rediscover a broad block or metric and
|
|
1490
|
+
// replace that decision before it has attempted the selected bounded SQL
|
|
1491
|
+
// path. Frozen tiers retain their existing plan binding behavior.
|
|
1492
|
+
if (forcedExploratoryTier && input.contextPack && !exploratoryClosureContextPack) {
|
|
1493
|
+
const text = 'The router-selected exploratory path no longer has a complete same-snapshot physical relation closure, so DQL did not send SQL generation or execute a query.';
|
|
1494
|
+
return {
|
|
1495
|
+
kind: 'no_answer',
|
|
1496
|
+
sourceTier: 'no_answer',
|
|
1497
|
+
certification: 'analyst_review_required',
|
|
1498
|
+
reviewStatus: 'none',
|
|
1499
|
+
confidence: 0,
|
|
1500
|
+
text,
|
|
1501
|
+
answer: text,
|
|
1502
|
+
refusalCode: 'grounding_gap',
|
|
1503
|
+
refusalDetails: { code: 'grounding_gap', message: text },
|
|
1504
|
+
citations: contextPackCitations(input.contextPack, 8),
|
|
1505
|
+
contextPack: input.contextPack,
|
|
1506
|
+
considered,
|
|
1507
|
+
providerUsed: provider.name,
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
const authoritativePlanBinding = !forcedExploratoryTier && input.resolvedAnalyticalPlan?.mode === 'authoritative'
|
|
1327
1511
|
? input.resolvedPlanExecutionBinding
|
|
1328
1512
|
: undefined;
|
|
1329
|
-
if (input.analyticalPeriodResolutionFailure) {
|
|
1513
|
+
if (!forcedExploratoryTier && input.analyticalPeriodResolutionFailure) {
|
|
1330
1514
|
const failure = input.analyticalPeriodResolutionFailure;
|
|
1331
1515
|
const structuredFailureCode = failure.error && typeof failure.error === 'object'
|
|
1332
1516
|
&& typeof failure.error.code === 'string'
|
|
@@ -1362,7 +1546,7 @@ async function runAnswerLoop(input) {
|
|
|
1362
1546
|
providerUsed: provider.name,
|
|
1363
1547
|
};
|
|
1364
1548
|
}
|
|
1365
|
-
if (input.analyticalExecutionGraphFailure) {
|
|
1549
|
+
if (!forcedExploratoryTier && input.analyticalExecutionGraphFailure) {
|
|
1366
1550
|
const failure = input.analyticalExecutionGraphFailure;
|
|
1367
1551
|
const analyticalFailure = analyticalFailureForInput(input, {
|
|
1368
1552
|
error: { code: failure.code, message: failure.reason },
|
|
@@ -1387,7 +1571,7 @@ async function runAnswerLoop(input) {
|
|
|
1387
1571
|
providerUsed: provider.name,
|
|
1388
1572
|
};
|
|
1389
1573
|
}
|
|
1390
|
-
if (input.semanticGraphExecutionBinding?.status === 'blocked') {
|
|
1574
|
+
if (!forcedExploratoryTier && input.semanticGraphExecutionBinding?.status === 'blocked') {
|
|
1391
1575
|
const failure = input.semanticGraphExecutionBinding;
|
|
1392
1576
|
const analyticalFailure = analyticalFailureForInput(input, {
|
|
1393
1577
|
error: { code: failure.code, message: failure.reason },
|
|
@@ -1412,7 +1596,7 @@ async function runAnswerLoop(input) {
|
|
|
1412
1596
|
providerUsed: provider.name,
|
|
1413
1597
|
};
|
|
1414
1598
|
}
|
|
1415
|
-
if (input.semanticGraphExecutionBinding?.status === 'ready' && input.analyticalExecutionGraph) {
|
|
1599
|
+
if (!forcedExploratoryTier && input.semanticGraphExecutionBinding?.status === 'ready' && input.analyticalExecutionGraph) {
|
|
1416
1600
|
return executeSemanticAnalyticalGraph({
|
|
1417
1601
|
input,
|
|
1418
1602
|
binding: input.semanticGraphExecutionBinding,
|
|
@@ -1422,7 +1606,7 @@ async function runAnswerLoop(input) {
|
|
|
1422
1606
|
providerName: provider.name,
|
|
1423
1607
|
});
|
|
1424
1608
|
}
|
|
1425
|
-
if (input.governedAnalyticalGraphCompilation?.status === 'blocked') {
|
|
1609
|
+
if (!forcedExploratoryTier && input.governedAnalyticalGraphCompilation?.status === 'blocked') {
|
|
1426
1610
|
const failure = input.governedAnalyticalGraphCompilation;
|
|
1427
1611
|
const analyticalFailure = analyticalFailureForInput(input, {
|
|
1428
1612
|
error: { code: failure.code, message: failure.reason },
|
|
@@ -1447,7 +1631,7 @@ async function runAnswerLoop(input) {
|
|
|
1447
1631
|
providerUsed: provider.name,
|
|
1448
1632
|
};
|
|
1449
1633
|
}
|
|
1450
|
-
if (input.governedAnalyticalGraphCompilation?.status === 'compiled' && input.analyticalExecutionGraph) {
|
|
1634
|
+
if (!forcedExploratoryTier && input.governedAnalyticalGraphCompilation?.status === 'compiled' && input.analyticalExecutionGraph) {
|
|
1451
1635
|
return executeGovernedRelationalAnalyticalGraph({
|
|
1452
1636
|
input,
|
|
1453
1637
|
compilation: input.governedAnalyticalGraphCompilation,
|
|
@@ -1458,7 +1642,7 @@ async function runAnswerLoop(input) {
|
|
|
1458
1642
|
});
|
|
1459
1643
|
}
|
|
1460
1644
|
const governedRelationalCompilation = input.governedRelationalCompilation;
|
|
1461
|
-
if (governedRelationalCompilation?.status === 'blocked') {
|
|
1645
|
+
if (!forcedExploratoryTier && governedRelationalCompilation?.status === 'blocked') {
|
|
1462
1646
|
const analyticalFailure = analyticalFailureForInput(input, {
|
|
1463
1647
|
error: { code: governedRelationalCompilation.code, message: governedRelationalCompilation.reason },
|
|
1464
1648
|
phase: 'compilation',
|
|
@@ -1485,7 +1669,7 @@ async function runAnswerLoop(input) {
|
|
|
1485
1669
|
providerUsed: provider.name,
|
|
1486
1670
|
};
|
|
1487
1671
|
}
|
|
1488
|
-
if (governedRelationalCompilation?.status === 'compiled') {
|
|
1672
|
+
if (!forcedExploratoryTier && governedRelationalCompilation?.status === 'compiled') {
|
|
1489
1673
|
const dqlArtifact = renderGovernedRelationalDqlArtifact(governedRelationalCompilation);
|
|
1490
1674
|
let result;
|
|
1491
1675
|
let executionError;
|
|
@@ -1549,9 +1733,15 @@ async function runAnswerLoop(input) {
|
|
|
1549
1733
|
const text = clarificationOptions?.length
|
|
1550
1734
|
? `The previous answer listed ${choices.values.length} ${humanizeDeicticDimension(choices.dimension)}. Which one did you mean?`
|
|
1551
1735
|
: `The resolved analytical plan cannot execute safely: ${authoritativePlanBinding.reason}`;
|
|
1736
|
+
const frozenCertifiedPlan = input.resolvedAnalyticalPlan?.mode === 'authoritative'
|
|
1737
|
+
&& input.resolvedAnalyticalPlan.capability === 'certified_execution';
|
|
1738
|
+
const frozenCertifiedRef = input.resolvedAnalyticalPlan?.executionId;
|
|
1552
1739
|
return {
|
|
1553
1740
|
kind: 'no_answer',
|
|
1554
|
-
|
|
1741
|
+
// A binding/adapter failure after the router selected a certified
|
|
1742
|
+
// execution authority is still a certified-tier terminal. It must not
|
|
1743
|
+
// be relabelled as generated merely because no rows were produced.
|
|
1744
|
+
sourceTier: frozenCertifiedPlan ? 'certified_artifact' : 'no_answer',
|
|
1555
1745
|
certification: 'analyst_review_required',
|
|
1556
1746
|
reviewStatus: 'none',
|
|
1557
1747
|
confidence: 0,
|
|
@@ -1563,6 +1753,15 @@ async function runAnswerLoop(input) {
|
|
|
1563
1753
|
: 'grounding_gap',
|
|
1564
1754
|
refusalDetails: { code: authoritativePlanBinding.code, message: authoritativePlanBinding.reason },
|
|
1565
1755
|
...(clarificationOptions?.length ? { clarificationOptions } : {}),
|
|
1756
|
+
...(frozenCertifiedPlan ? {
|
|
1757
|
+
route: {
|
|
1758
|
+
tier: 'certified_block',
|
|
1759
|
+
label: frozenCertifiedRef
|
|
1760
|
+
? `The selected certified block ${frozenCertifiedRef} could not execute its frozen plan.`
|
|
1761
|
+
: 'The selected certified block could not execute its frozen plan.',
|
|
1762
|
+
...(frozenCertifiedRef ? { ref: frozenCertifiedRef } : {}),
|
|
1763
|
+
},
|
|
1764
|
+
} : {}),
|
|
1566
1765
|
citations: contextPackCitations(input.contextPack, 8),
|
|
1567
1766
|
considered,
|
|
1568
1767
|
contextPack: input.contextPack,
|
|
@@ -1599,18 +1798,20 @@ async function runAnswerLoop(input) {
|
|
|
1599
1798
|
const semanticLayerForExec = input.semanticLayer;
|
|
1600
1799
|
const canExecuteSemanticMetricForMatch = input.canExecuteSemanticMetric
|
|
1601
1800
|
?? (semanticLayerForExec ? (name) => semanticLayerForExec.canComposeMetric(name) : undefined);
|
|
1602
|
-
let semanticMetricMatch =
|
|
1603
|
-
?
|
|
1604
|
-
:
|
|
1605
|
-
?
|
|
1606
|
-
:
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
? {
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1801
|
+
let semanticMetricMatch = forcedExploratoryTier
|
|
1802
|
+
? null
|
|
1803
|
+
: preferredSemanticMetric
|
|
1804
|
+
? { metric: preferredSemanticMetric, score: 1, basis: 'name' }
|
|
1805
|
+
: input.resolvedAnalyticalPlan?.mode === 'authoritative'
|
|
1806
|
+
? null
|
|
1807
|
+
: await matchSemanticMetric(semanticQuestion, semanticMetricNodes, {
|
|
1808
|
+
measureTerms: [...questionPlan.requestedShape.measures, ...questionPlan.metricTerms],
|
|
1809
|
+
...(canExecuteSemanticMetricForMatch ? { canExecute: canExecuteSemanticMetricForMatch } : {}),
|
|
1810
|
+
...(snapshotVectorMetricShortlist(input.contextPack).length
|
|
1811
|
+
? { vectorMetricShortlist: snapshotVectorMetricShortlist(input.contextPack) }
|
|
1812
|
+
: {}),
|
|
1813
|
+
...semanticMetricEmbeddingOptions(input.embeddingProvider),
|
|
1814
|
+
}).catch(() => null);
|
|
1614
1815
|
// Stage 1: certified artifact match. Blocks can be executed; dashboards,
|
|
1615
1816
|
// Apps, and notebooks are returned as governed citations/navigation targets.
|
|
1616
1817
|
const drilldownCertifiedHit = input.followUp?.kind === 'drilldown'
|
|
@@ -1630,8 +1831,10 @@ async function runAnswerLoop(input) {
|
|
|
1630
1831
|
&& objectNameInQuestion(question, node)
|
|
1631
1832
|
&& /\b(run|use|open|show|execute|certified|saved|block)\b/i.test(question));
|
|
1632
1833
|
});
|
|
1633
|
-
const shouldTryCertifiedRoute =
|
|
1634
|
-
|
|
1834
|
+
const shouldTryCertifiedRoute = !forcedExploratoryTier
|
|
1835
|
+
&& (shouldUseCertifiedRoute(catalogRoute, intent)
|
|
1836
|
+
|| questionPlan.mode === 'definition'
|
|
1837
|
+
|| explicitlyRequestedCertifiedBlock);
|
|
1635
1838
|
const catalogCertifiedHit = shouldTryCertifiedRoute
|
|
1636
1839
|
? certifiedHitFromContextPack(input.contextPack, kg)
|
|
1637
1840
|
: null;
|
|
@@ -1641,7 +1844,9 @@ async function runAnswerLoop(input) {
|
|
|
1641
1844
|
// metric, so a high-scoring `top_beverage_customers` catalog hit could answer a
|
|
1642
1845
|
// product-type → product-name flow request with customer rows.
|
|
1643
1846
|
const unsafeCatalogCertifiedHit = catalogCertifiedHit?.node.kind === 'block'
|
|
1644
|
-
&& !hasCertifiedNodeFit(question, questionPlan, catalogCertifiedHit.node
|
|
1847
|
+
&& !hasCertifiedNodeFit(question, questionPlan, catalogCertifiedHit.node, {
|
|
1848
|
+
uniqueExactExampleContract: hasUniqueExactCatalogExample(question, input.contextPack, catalogCertifiedHit.node),
|
|
1849
|
+
})
|
|
1645
1850
|
? null
|
|
1646
1851
|
: catalogCertifiedHit;
|
|
1647
1852
|
const fallbackCertifiedHit = shouldTryCertifiedRoute ? pickCertifiedArtifact({
|
|
@@ -1658,13 +1863,85 @@ async function runAnswerLoop(input) {
|
|
|
1658
1863
|
&& authoritativePlanBinding.kind === 'certified'
|
|
1659
1864
|
? authoritativePlanBinding
|
|
1660
1865
|
: undefined;
|
|
1866
|
+
const namedDefinitionArtifact = input.resolvedAnalyticalPlan?.mode === 'authoritative'
|
|
1867
|
+
? null
|
|
1868
|
+
: uniqueNamedCertifiedDefinitionArtifact(question, kg, authorizedDomains);
|
|
1661
1869
|
let artifactHit = authoritativeCertifiedBinding
|
|
1662
1870
|
? { node: authoritativeCertifiedBinding.node, score: 1 }
|
|
1663
1871
|
: input.resolvedAnalyticalPlan?.mode === 'authoritative'
|
|
1664
1872
|
? null
|
|
1665
|
-
: drilldownCertifiedHit ?? unsafeCatalogCertifiedHit
|
|
1873
|
+
: namedDefinitionArtifact ?? drilldownCertifiedHit ?? unsafeCatalogCertifiedHit
|
|
1666
1874
|
?? (catalogCertifiedHit ? null : fallbackCertifiedHit);
|
|
1667
1875
|
let certifiedExecutionFallback;
|
|
1876
|
+
// A named certified artifact-definition request is documentation about that
|
|
1877
|
+
// artifact, not a request to execute its SQL. The planner strips the
|
|
1878
|
+
// artifact identifier from its output contract, and this deterministic path
|
|
1879
|
+
// returns only authored metadata. It is deliberately narrower than a bare
|
|
1880
|
+
// "what is revenue" question: the user must name the certified block and use
|
|
1881
|
+
// an explicit definition form before execution is bypassed.
|
|
1882
|
+
if (artifactHit?.node.kind === 'block'
|
|
1883
|
+
&& artifactHit.node.status === 'certified'
|
|
1884
|
+
&& isUniqueNamedCertifiedDefinitionQuestion(question, artifactHit.node, kg, authorizedDomains)) {
|
|
1885
|
+
const artifact = artifactHit.node;
|
|
1886
|
+
const outputs = [...new Set([
|
|
1887
|
+
...(artifact.outputContract ?? []).map((output) => output.name),
|
|
1888
|
+
...(artifact.declaredOutputs ?? []),
|
|
1889
|
+
...(artifact.outputs ?? []).map((output) => output.name),
|
|
1890
|
+
].map((output) => output.trim()).filter(Boolean))].slice(0, 16);
|
|
1891
|
+
const metadataLines = [
|
|
1892
|
+
artifact.description ?? artifact.llmContext ?? 'No authored description is available for this certified artifact.',
|
|
1893
|
+
outputs.length > 0 ? `Declared outputs: ${outputs.join(', ')}.` : undefined,
|
|
1894
|
+
artifact.grain ? `Grain: ${artifact.grain}.` : undefined,
|
|
1895
|
+
artifact.owner ? `Owner: ${artifact.owner}.` : undefined,
|
|
1896
|
+
'This is certified artifact metadata, not a query result.',
|
|
1897
|
+
].filter((line) => Boolean(line));
|
|
1898
|
+
const citations = [{
|
|
1899
|
+
nodeId: artifact.nodeId,
|
|
1900
|
+
kind: artifact.kind,
|
|
1901
|
+
name: artifact.name,
|
|
1902
|
+
gitSha: artifact.gitSha,
|
|
1903
|
+
sourceTier: 'certified_artifact',
|
|
1904
|
+
provenance: artifact.provenance,
|
|
1905
|
+
}];
|
|
1906
|
+
const analysisPlan = buildAnalysisPlan({
|
|
1907
|
+
question,
|
|
1908
|
+
intent: 'definition_lookup',
|
|
1909
|
+
routeReason: 'The question explicitly requested the meaning of one named certified artifact; no SQL execution was requested.',
|
|
1910
|
+
selectedNodes: [artifact],
|
|
1911
|
+
schemaContext,
|
|
1912
|
+
});
|
|
1913
|
+
const text = `Certified artifact **${artifact.name}**\n\n${metadataLines.join('\n\n')}`;
|
|
1914
|
+
return {
|
|
1915
|
+
kind: 'certified',
|
|
1916
|
+
sourceTier: 'certified_artifact',
|
|
1917
|
+
certification: 'certified',
|
|
1918
|
+
reviewStatus: 'certified',
|
|
1919
|
+
confidence: 0.99,
|
|
1920
|
+
text,
|
|
1921
|
+
answer: text,
|
|
1922
|
+
block: artifact,
|
|
1923
|
+
sourceCertifiedBlock: artifact.name,
|
|
1924
|
+
trustLabel: 'certified',
|
|
1925
|
+
citations,
|
|
1926
|
+
memoryContext: input.memoryContext,
|
|
1927
|
+
analysisPlan,
|
|
1928
|
+
evidence: buildCertifiedEvidence({
|
|
1929
|
+
question,
|
|
1930
|
+
artifact,
|
|
1931
|
+
businessHits,
|
|
1932
|
+
semanticHits,
|
|
1933
|
+
manifestHits,
|
|
1934
|
+
considered,
|
|
1935
|
+
executorWasAvailable: false,
|
|
1936
|
+
citations,
|
|
1937
|
+
memoryContext: input.memoryContext ?? [],
|
|
1938
|
+
analysisPlan,
|
|
1939
|
+
}),
|
|
1940
|
+
contextPack: input.contextPack,
|
|
1941
|
+
considered,
|
|
1942
|
+
providerUsed: provider.name,
|
|
1943
|
+
};
|
|
1944
|
+
}
|
|
1668
1945
|
// Certified remains first when it actually covers the question. If the
|
|
1669
1946
|
// retrieved block does not fit but a governed semantic metric does, never
|
|
1670
1947
|
// let the broad catalog match pre-empt Lane 2.
|
|
@@ -1773,7 +2050,16 @@ async function runAnswerLoop(input) {
|
|
|
1773
2050
|
providerUsed: provider.name,
|
|
1774
2051
|
};
|
|
1775
2052
|
}
|
|
1776
|
-
const resultShapeWarnings = result
|
|
2053
|
+
const resultShapeWarnings = result
|
|
2054
|
+
? validateAnswerResultShape(questionPlan, result, {
|
|
2055
|
+
outputBindings: authoritativeCertifiedBinding
|
|
2056
|
+
? frozenCertifiedResultShapeBindings(input.resolvedAnalyticalPlan, artifactHit.node, questionPlan, {
|
|
2057
|
+
uniqueExactExampleContract: hasUniqueExactCatalogExample(question, input.contextPack, artifactHit.node),
|
|
2058
|
+
})
|
|
2059
|
+
: [],
|
|
2060
|
+
requireBoundMeasures: Boolean(authoritativeCertifiedBinding),
|
|
2061
|
+
}).warnings
|
|
2062
|
+
: [];
|
|
1777
2063
|
// When a certified block's execution was ATTEMPTED and FAILED, the answer
|
|
1778
2064
|
// cannot wear the certified badge — a failed run has no data to stand behind.
|
|
1779
2065
|
// Downgrade to analyst_review_required (the error is surfaced in the text).
|
|
@@ -1812,6 +2098,10 @@ async function runAnswerLoop(input) {
|
|
|
1812
2098
|
? undefined
|
|
1813
2099
|
: questionPlan.requestedShape.topN?.n);
|
|
1814
2100
|
const authoritativeCertifiedFailure = Boolean(authoritativeCertifiedBinding && executionError);
|
|
2101
|
+
// A selected certified block which returns an incomplete tuple is a
|
|
2102
|
+
// same-tier terminal failure. It is not a reason to reinterpret meaning,
|
|
2103
|
+
// label the partial rows generated, or retry another route after freeze.
|
|
2104
|
+
const authoritativeCertifiedShapeFailure = Boolean(authoritativeCertifiedBinding && !executionError && resultShapeWarnings.length > 0);
|
|
1815
2105
|
const recoverableCertifiedFailure = artifactHit.node.kind === 'block'
|
|
1816
2106
|
&& executionError !== undefined
|
|
1817
2107
|
&& !authoritativeCertifiedFailure
|
|
@@ -1826,18 +2116,38 @@ async function runAnswerLoop(input) {
|
|
|
1826
2116
|
artifactHit = null;
|
|
1827
2117
|
}
|
|
1828
2118
|
else {
|
|
2119
|
+
const frozenCertifiedRoute = authoritativeCertifiedBinding
|
|
2120
|
+
? {
|
|
2121
|
+
tier: 'certified_block',
|
|
2122
|
+
label: certifiedShapePassed
|
|
2123
|
+
? `Answered from certified block ${artifactHit.node.name}`
|
|
2124
|
+
: `The selected certified block ${artifactHit.node.name} could not complete its frozen output contract.`,
|
|
2125
|
+
ref: artifactHit.node.name,
|
|
2126
|
+
}
|
|
2127
|
+
: undefined;
|
|
1829
2128
|
return {
|
|
1830
|
-
kind: authoritativeCertifiedFailure
|
|
1831
|
-
|
|
2129
|
+
kind: authoritativeCertifiedFailure || authoritativeCertifiedShapeFailure
|
|
2130
|
+
? 'no_answer'
|
|
2131
|
+
: certifiedShapePassed ? 'certified' : 'uncertified',
|
|
2132
|
+
// Preserve the selected certified authority even when its own
|
|
2133
|
+
// execution/result contract fails. The failure is terminal and has
|
|
2134
|
+
// review-required trust, but it is not generated SQL.
|
|
2135
|
+
sourceTier,
|
|
1832
2136
|
certification: certifiedShapePassed ? 'certified' : 'analyst_review_required',
|
|
1833
|
-
reviewStatus: authoritativeCertifiedFailure
|
|
2137
|
+
reviewStatus: authoritativeCertifiedFailure || authoritativeCertifiedShapeFailure
|
|
2138
|
+
? 'none'
|
|
2139
|
+
: certifiedShapePassed ? 'certified' : 'analyst_review_required',
|
|
1834
2140
|
confidence: certifiedShapePassed ? 0.95 : 0.45,
|
|
1835
2141
|
text,
|
|
1836
2142
|
answer: text,
|
|
1837
2143
|
block: artifactHit.node.kind === 'block' ? artifactHit.node : undefined,
|
|
1838
2144
|
result,
|
|
1839
2145
|
executionError,
|
|
1840
|
-
...(authoritativeCertifiedFailure
|
|
2146
|
+
...(authoritativeCertifiedFailure || authoritativeCertifiedShapeFailure
|
|
2147
|
+
? { refusalCode: 'grounding_gap' }
|
|
2148
|
+
: {}),
|
|
2149
|
+
...(authoritativeCertifiedShapeFailure ? { certifiedResultShapeFailure: true } : {}),
|
|
2150
|
+
...(frozenCertifiedRoute ? { route: frozenCertifiedRoute } : {}),
|
|
1841
2151
|
sql: result?.sql,
|
|
1842
2152
|
dqlArtifact,
|
|
1843
2153
|
trustLabel: certifiedShapePassed ? input.contextPack?.trustLabel ?? 'certified' : 'mixed',
|
|
@@ -1875,7 +2185,7 @@ async function runAnswerLoop(input) {
|
|
|
1875
2185
|
// a governed metric can answer. Match by name + synonyms + measure family + hybrid
|
|
1876
2186
|
// rank over the FTS semantic hits, then ALL metric KG nodes (revenue ⇄
|
|
1877
2187
|
// cumulative_revenue). Certified-first is still preserved (checked above).
|
|
1878
|
-
const clarifyBeforeGeneration = shouldClarifyBeforeGeneration({
|
|
2188
|
+
const clarifyBeforeGeneration = !forcedExploratoryTier && shouldClarifyBeforeGeneration({
|
|
1879
2189
|
intent,
|
|
1880
2190
|
routeDecision: catalogRoute,
|
|
1881
2191
|
hasSemanticMetricMatch: Boolean(semanticMetricMatch),
|
|
@@ -1933,12 +2243,14 @@ async function runAnswerLoop(input) {
|
|
|
1933
2243
|
// wins over raw dbt manifest context; memory is appended last as advisory.
|
|
1934
2244
|
// A confident metric match forces the semantic tier even when FTS returned no
|
|
1935
2245
|
// semantic hits, so the governed metric (not refusal) answers the question.
|
|
1936
|
-
const activeTier =
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
? '
|
|
1941
|
-
:
|
|
2246
|
+
const activeTier = forcedExploratoryTier
|
|
2247
|
+
? 'dbt_manifest'
|
|
2248
|
+
: sourceTierFromContextPack(input.contextPack)
|
|
2249
|
+
?? (semanticHits.length > 0 || semanticMetricMatch
|
|
2250
|
+
? 'semantic_layer'
|
|
2251
|
+
: manifestHits.length > 0
|
|
2252
|
+
? 'dbt_manifest'
|
|
2253
|
+
: 'dbt_manifest');
|
|
1942
2254
|
const reviewRequiredArtifactHits = artifactHits
|
|
1943
2255
|
.filter((hit) => hit.score >= CERTIFIED_HIT_THRESHOLD && !isCertifiedHit(hit, kg))
|
|
1944
2256
|
.slice(0, 4);
|
|
@@ -1971,7 +2283,13 @@ async function runAnswerLoop(input) {
|
|
|
1971
2283
|
{ hits: businessHits.slice(0, 4), reserve: 3 },
|
|
1972
2284
|
{ hits: manifestHits, reserve: 3 },
|
|
1973
2285
|
], 14);
|
|
1974
|
-
|
|
2286
|
+
// A frozen exploratory selection gets its relation/column/proof context only
|
|
2287
|
+
// from `executionContextPack`. KG hit prose is useful diagnostics, but it
|
|
2288
|
+
// is not execution authority and must not tell a provider about another
|
|
2289
|
+
// same-snapshot relation it could then attempt to query.
|
|
2290
|
+
const contextNodes = forcedExploratoryTier
|
|
2291
|
+
? []
|
|
2292
|
+
: mergeNodes(followUpSourceBlock && input.followUp?.kind === 'drilldown' ? [followUpSourceBlock] : [], (contextHits.length > 0 ? contextHits : considered.slice(0, 6)).map((h) => h.node)).filter((node) => questionDomainScope.length === 0 || !node.domain || questionDomainScope.includes(node.domain));
|
|
1975
2293
|
const kgJoinPathHints = buildKgJoinPathHints(kg, contextNodes, questionPlan);
|
|
1976
2294
|
const contextBlocks = contextNodes.filter((node) => {
|
|
1977
2295
|
if (node.kind !== 'block')
|
|
@@ -1991,10 +2309,10 @@ async function runAnswerLoop(input) {
|
|
|
1991
2309
|
const messages = [
|
|
1992
2310
|
{ role: 'system', content: SYSTEM_PROMPT },
|
|
1993
2311
|
];
|
|
1994
|
-
const skillsPrompt = buildSkillsPrompt(selectedSkills, userId ?? null);
|
|
2312
|
+
const skillsPrompt = forcedExploratoryTier ? '' : buildSkillsPrompt(selectedSkills, userId ?? null);
|
|
1995
2313
|
if (skillsPrompt)
|
|
1996
2314
|
messages.push({ role: 'system', content: skillsPrompt });
|
|
1997
|
-
const analyticalPlan = input.manifest
|
|
2315
|
+
const analyticalPlan = !forcedExploratoryTier && input.manifest
|
|
1998
2316
|
? planAnalyticalPath(input.manifest, {
|
|
1999
2317
|
entityIds: inferAnalyticalEntityIds(question, contextNodes, input.manifest),
|
|
2000
2318
|
ownerDomain: input.domainContext?.activeDomain ?? input.domain,
|
|
@@ -2346,7 +2664,7 @@ async function runAnswerLoop(input) {
|
|
|
2346
2664
|
// (pre-validated, since the wrapper only restricts an already-certified result)
|
|
2347
2665
|
// and is labeled BELOW certified. Falls through to generation on any miss.
|
|
2348
2666
|
let certifiedAdaptation;
|
|
2349
|
-
if (!semanticBridgeAnswer && !metricFirst && input.executeGeneratedSql) {
|
|
2667
|
+
if (!forcedExploratoryTier && !semanticBridgeAnswer && !metricFirst && input.executeGeneratedSql) {
|
|
2350
2668
|
const fit = input.contextPack?.routeDecision?.blockFit;
|
|
2351
2669
|
const sourceBlock = input.contextPack?.allowedSqlContext?.sourceBlockSql?.[0];
|
|
2352
2670
|
if (fit && sourceBlock?.sql) {
|
|
@@ -3345,7 +3663,13 @@ async function runAnswerLoop(input) {
|
|
|
3345
3663
|
let result;
|
|
3346
3664
|
let executionError;
|
|
3347
3665
|
let warehouseFailure;
|
|
3666
|
+
let exploratoryClosureDenied = false;
|
|
3348
3667
|
let repairAttempts = 0;
|
|
3668
|
+
// A forced proposal may originate from the bounded analyst loop, but it
|
|
3669
|
+
// cannot bypass a router-owned exploratory decision. In that case the host
|
|
3670
|
+
// still prepares and freezes the exact validated SQL before execution.
|
|
3671
|
+
const exploratoryExecutionSelected = input.selectedCascadeTier === 'exploratory_sql';
|
|
3672
|
+
let preparedExploratoryExecution;
|
|
3349
3673
|
// Repair candidates that were generated but rejected before execution. These
|
|
3350
3674
|
// are diagnostics ABOUT the recovery attempt, not the reason the run failed,
|
|
3351
3675
|
// so they are reported alongside the original error rather than replacing it.
|
|
@@ -3360,7 +3684,7 @@ async function runAnswerLoop(input) {
|
|
|
3360
3684
|
executionError = deepCandidateExecutionError;
|
|
3361
3685
|
warehouseFailure = normalizeWarehouseSqlFailure(deepCandidateExecutionError, input.semanticDriver);
|
|
3362
3686
|
}
|
|
3363
|
-
const executeCurrentSql = () => {
|
|
3687
|
+
const executeCurrentSql = async () => {
|
|
3364
3688
|
const requestedLimit = questionPlan.requestedShape.topN?.scope === 'per_group'
|
|
3365
3689
|
? 200
|
|
3366
3690
|
: questionPlan.requestedShape.topN?.n ?? 200;
|
|
@@ -3386,7 +3710,7 @@ async function runAnswerLoop(input) {
|
|
|
3386
3710
|
outputs: parsed.outputs,
|
|
3387
3711
|
});
|
|
3388
3712
|
const boundedArtifact = artifact ? { ...artifact, limit: requestedLimit } : undefined;
|
|
3389
|
-
if (input.forcedGeneratedProposal) {
|
|
3713
|
+
if (input.forcedGeneratedProposal && !exploratoryExecutionSelected) {
|
|
3390
3714
|
if (!input.agenticSqlExecutionCapability || !input.executeAgenticGeneratedSql) {
|
|
3391
3715
|
throw Object.assign(new Error('The analyst-approved SQL has no live execution capability, so DQL did not execute it.'), {
|
|
3392
3716
|
code: 'AGENTIC_EXECUTION_CAPABILITY_REQUIRED',
|
|
@@ -3394,11 +3718,26 @@ async function runAnswerLoop(input) {
|
|
|
3394
3718
|
}
|
|
3395
3719
|
return input.executeAgenticGeneratedSql(input.agenticSqlExecutionCapability, parsed.sql, boundedArtifact);
|
|
3396
3720
|
}
|
|
3721
|
+
if (exploratoryExecutionSelected) {
|
|
3722
|
+
if (!input.prepareExploratorySqlExecution || !input.executeAgenticGeneratedSql) {
|
|
3723
|
+
throw Object.assign(new Error('The selected exploratory SQL has no host-authorized execution capability, so DQL did not execute it.'), {
|
|
3724
|
+
code: 'EXPLORATORY_EXECUTION_CAPABILITY_REQUIRED',
|
|
3725
|
+
});
|
|
3726
|
+
}
|
|
3727
|
+
// This is the only point at which a router-selected exploratory proposal
|
|
3728
|
+
// crosses from validated text into executable authority. The callback
|
|
3729
|
+
// rechecks the snapshot, physical target, and qualified runtime columns,
|
|
3730
|
+
// then returns a one-shot capability bound to these exact SQL bytes.
|
|
3731
|
+
preparedExploratoryExecution ??= await input.prepareExploratorySqlExecution(parsed.sql, boundedArtifact);
|
|
3732
|
+
return input.executeAgenticGeneratedSql(preparedExploratoryExecution.capability, parsed.sql, boundedArtifact);
|
|
3733
|
+
}
|
|
3397
3734
|
if (!input.executeGeneratedSql)
|
|
3398
3735
|
throw new Error('No generated SQL executor is configured.');
|
|
3399
3736
|
return input.executeGeneratedSql(parsed.sql, boundedArtifact);
|
|
3400
3737
|
};
|
|
3401
|
-
if ((input.executeGeneratedSql
|
|
3738
|
+
if ((input.executeGeneratedSql
|
|
3739
|
+
|| (input.forcedGeneratedProposal && input.executeAgenticGeneratedSql)
|
|
3740
|
+
|| exploratoryExecutionSelected) && !result) {
|
|
3402
3741
|
// Fanout gate for native semantic direct-joins: a duplicate join-key row on
|
|
3403
3742
|
// the joined side multiplies fact rows BEFORE aggregation, so every summed
|
|
3404
3743
|
// value inflates silently (seen in the field as trillions-scale "governed"
|
|
@@ -3408,6 +3747,7 @@ async function runAnswerLoop(input) {
|
|
|
3408
3747
|
let fanoutContradiction = false;
|
|
3409
3748
|
if (!executionError
|
|
3410
3749
|
&& !input.forcedGeneratedProposal
|
|
3750
|
+
&& !exploratoryExecutionSelected
|
|
3411
3751
|
&& input.executeGeneratedSql
|
|
3412
3752
|
&& semanticBridgeAnswer?.composeResult?.fanoutProbeSql
|
|
3413
3753
|
&& semanticBridgeAnswer.sql.trim() === parsed.sql?.trim()) {
|
|
@@ -3422,10 +3762,24 @@ async function runAnswerLoop(input) {
|
|
|
3422
3762
|
result = await executeCurrentSql();
|
|
3423
3763
|
}
|
|
3424
3764
|
catch (err) {
|
|
3765
|
+
const executionCode = err && typeof err === 'object'
|
|
3766
|
+
&& typeof err.code === 'string'
|
|
3767
|
+
? err.code
|
|
3768
|
+
: undefined;
|
|
3769
|
+
// A router-selected exploratory proposal that leaves its candidate
|
|
3770
|
+
// closure is not an executable draft. It is a pre-capability safety
|
|
3771
|
+
// denial: do not present its SQL/artifact as review-required work after
|
|
3772
|
+
// the host rejected it.
|
|
3773
|
+
exploratoryClosureDenied = exploratoryExecutionSelected
|
|
3774
|
+
&& executionCode === 'UNAUTHORIZED_SQL';
|
|
3425
3775
|
warehouseFailure = normalizeWarehouseSqlFailure(err, input.semanticDriver);
|
|
3426
3776
|
executionError = warehouseFailure.redactedMessage;
|
|
3427
3777
|
}
|
|
3428
|
-
if (executionError
|
|
3778
|
+
if (executionError
|
|
3779
|
+
&& !input.forcedGeneratedProposal
|
|
3780
|
+
&& !exploratoryExecutionSelected
|
|
3781
|
+
&& !fanoutContradiction
|
|
3782
|
+
&& !authoritativeSemanticBinding) {
|
|
3429
3783
|
warehouseFailure ??= normalizeWarehouseSqlFailure(executionError, input.semanticDriver);
|
|
3430
3784
|
if (isRetryableGeneratedSqlError(warehouseFailure)) {
|
|
3431
3785
|
const localRepairSql = repairGeneratedSqlLocally(parsed.sql, executionError, schemaContext);
|
|
@@ -3536,10 +3890,15 @@ async function runAnswerLoop(input) {
|
|
|
3536
3890
|
: 'The question asks for a custom analysis, ranking, breakdown, comparison, or grain that should not be answered by a loose certified block match.'),
|
|
3537
3891
|
selectedNodes: contextNodes,
|
|
3538
3892
|
schemaContext,
|
|
3539
|
-
|
|
3893
|
+
// A router-selected physical-closure denial happens before a capability
|
|
3894
|
+
// exists. Its rejected bytes are diagnostics for the host, never a SQL
|
|
3895
|
+
// preview that a user can review, copy, or turn into a draft.
|
|
3896
|
+
sql: exploratoryClosureDenied ? undefined : parsed.sql,
|
|
3540
3897
|
suggestedViz: parsed.viz ?? 'table',
|
|
3541
3898
|
assumptions: [
|
|
3542
|
-
|
|
3899
|
+
...(exploratoryClosureDenied
|
|
3900
|
+
? ['The router-selected physical closure rejected the generated SQL before capability minting.']
|
|
3901
|
+
: ['The SQL preview is uncertified until an analyst reviews and promotes the DQL artifact.']),
|
|
3543
3902
|
...(certifiedExecutionFallback
|
|
3544
3903
|
? [`Certified block ${certifiedExecutionFallback.node.name} failed execution and was bypassed: ${certifiedExecutionFallback.error}`]
|
|
3545
3904
|
: []),
|
|
@@ -3591,21 +3950,23 @@ async function runAnswerLoop(input) {
|
|
|
3591
3950
|
const generatedOutputs = parsed.outputs?.length ? parsed.outputs : resultColumnNames(result);
|
|
3592
3951
|
const generatedRequestedFilters = mergeProposalStringLists(input.followUp?.filters, parsed.requestedFilters);
|
|
3593
3952
|
const generatedRequestedDimensions = mergeProposalStringLists(input.followUp?.dimensions, parsed.requestedDimensions);
|
|
3594
|
-
const baseDqlArtifact =
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3953
|
+
const baseDqlArtifact = exploratoryClosureDenied
|
|
3954
|
+
? undefined
|
|
3955
|
+
: result?.dqlArtifact ?? semanticBridgeAnswer?.dqlArtifact ?? buildGeneratedSqlDqlArtifact({
|
|
3956
|
+
question,
|
|
3957
|
+
sql: parsed.sql,
|
|
3958
|
+
intent,
|
|
3959
|
+
domain,
|
|
3960
|
+
followUp: input.followUp,
|
|
3961
|
+
contextPack: input.contextPack,
|
|
3962
|
+
sourceBlock: followUpSourceBlock ?? undefined,
|
|
3963
|
+
sourceDqlArtifact: input.followUp?.priorDqlArtifact,
|
|
3964
|
+
proposedEntity: parsed.proposedEntity,
|
|
3965
|
+
requestedFilters: generatedRequestedFilters,
|
|
3966
|
+
requestedDimensions: generatedRequestedDimensions,
|
|
3967
|
+
validationWarnings,
|
|
3968
|
+
outputs: generatedOutputs,
|
|
3969
|
+
});
|
|
3609
3970
|
const requestedTopN = questionPlan.requestedShape.topN?.scope === 'per_group'
|
|
3610
3971
|
? undefined
|
|
3611
3972
|
: questionPlan.requestedShape.topN?.n;
|
|
@@ -3618,7 +3979,7 @@ async function runAnswerLoop(input) {
|
|
|
3618
3979
|
: undefined;
|
|
3619
3980
|
let draftBlock;
|
|
3620
3981
|
let draftCaptureError;
|
|
3621
|
-
if (input.captureGeneratedDraft && parsed.sql) {
|
|
3982
|
+
if (!exploratoryClosureDenied && input.captureGeneratedDraft && parsed.sql) {
|
|
3622
3983
|
try {
|
|
3623
3984
|
draftBlock = await input.captureGeneratedDraft({
|
|
3624
3985
|
question,
|
|
@@ -3665,32 +4026,38 @@ async function runAnswerLoop(input) {
|
|
|
3665
4026
|
: undefined;
|
|
3666
4027
|
const certifiedMetricAnswer = semanticMetricCertification === 'certified' || semanticMetricCertification === 'reviewed';
|
|
3667
4028
|
const governedMetricExecutionFailure = governedMetricAnswer && Boolean(executionError);
|
|
4029
|
+
const terminalExecutionFailure = governedMetricExecutionFailure || exploratoryClosureDenied;
|
|
3668
4030
|
const finalSemanticExecutionTrace = semanticTraceAfterExecution(semanticExecutionTrace, {
|
|
3669
4031
|
executed: Boolean(result),
|
|
3670
4032
|
...(executionError ? { error: executionError } : {}),
|
|
3671
4033
|
...(result ? { result } : {}),
|
|
3672
4034
|
});
|
|
3673
4035
|
return {
|
|
3674
|
-
kind:
|
|
3675
|
-
sourceTier:
|
|
3676
|
-
certification:
|
|
3677
|
-
reviewStatus:
|
|
4036
|
+
kind: terminalExecutionFailure ? 'no_answer' : 'uncertified',
|
|
4037
|
+
sourceTier: terminalExecutionFailure ? 'no_answer' : governedMetricAnswer ? 'semantic_layer' : activeTier,
|
|
4038
|
+
certification: terminalExecutionFailure ? 'analyst_review_required' : governedMetricAnswer ? 'governed' : 'ai_generated',
|
|
4039
|
+
reviewStatus: terminalExecutionFailure ? 'none' : governedMetricAnswer ? 'governed' : 'draft_ready',
|
|
3678
4040
|
semanticMetricCertification,
|
|
3679
|
-
confidence:
|
|
3680
|
-
text:
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
4041
|
+
confidence: terminalExecutionFailure ? 0 : certifiedMetricAnswer ? 0.8 : governedMetricAnswer ? 0.72 : 0.55,
|
|
4042
|
+
text: exploratoryClosureDenied
|
|
4043
|
+
? 'The generated SQL referenced a relation outside the router-selected physical closure, so DQL did not execute it.'
|
|
4044
|
+
: generatedText,
|
|
4045
|
+
answer: exploratoryClosureDenied
|
|
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 }),
|
|
3684
4049
|
result,
|
|
3685
4050
|
executionError,
|
|
3686
4051
|
...(warehouseFailure ? { warehouseFailure } : {}),
|
|
3687
4052
|
...(finalSemanticExecutionTrace ? { semanticExecutionTrace: finalSemanticExecutionTrace } : {}),
|
|
3688
|
-
...(
|
|
4053
|
+
...(terminalExecutionFailure ? { refusalCode: 'grounding_gap' } : {}),
|
|
3689
4054
|
suggestedViz: parsed.viz ?? 'table',
|
|
3690
|
-
dqlArtifact: answerDqlArtifact,
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
4055
|
+
...(exploratoryClosureDenied ? {} : { dqlArtifact: answerDqlArtifact }),
|
|
4056
|
+
...(exploratoryClosureDenied ? {} : {
|
|
4057
|
+
draftBlock,
|
|
4058
|
+
draftBlockId: draftBlock?.path,
|
|
4059
|
+
promoteCommand: draftBlock ? `dql certify --from-draft ${draftBlock.path}` : undefined,
|
|
4060
|
+
}),
|
|
3694
4061
|
trustLabel: input.contextPack?.trustLabel,
|
|
3695
4062
|
sourceCertifiedBlock,
|
|
3696
4063
|
contextPackId: input.contextPack?.id,
|
|
@@ -3728,6 +4095,7 @@ async function runAnswerLoop(input) {
|
|
|
3728
4095
|
considered,
|
|
3729
4096
|
providerUsed: provider.name,
|
|
3730
4097
|
aggregationSafetyProof,
|
|
4098
|
+
...(preparedExploratoryExecution ? { exploratoryExecutionFreeze: preparedExploratoryExecution.freeze } : {}),
|
|
3731
4099
|
// Carry the governed metric match so the exit point can name a
|
|
3732
4100
|
// `semantic_metric` route (spec 17, part C).
|
|
3733
4101
|
_semanticMetricMatch: governedMetricAnswer ? semanticMetricMatch ?? undefined : undefined,
|
|
@@ -4816,6 +5184,147 @@ function scopeContextPackToQuestionDomains(contextPack, domains, manifest) {
|
|
|
4816
5184
|
},
|
|
4817
5185
|
};
|
|
4818
5186
|
}
|
|
5187
|
+
/**
|
|
5188
|
+
* Make the router-selected exploratory physical closure the only SQL authority
|
|
5189
|
+
* visible to generation and execution. The broad context pack remains on the
|
|
5190
|
+
* run as diagnostics, but must not make a same-snapshot neighbouring relation
|
|
5191
|
+
* executable merely because it ranked well for the surrounding question.
|
|
5192
|
+
*
|
|
5193
|
+
* Matching is canonical-ID only. In particular this deliberately does not
|
|
5194
|
+
* fall back to relation/model leaf names: duplicate `orders` relations across
|
|
5195
|
+
* schemas/domains must not inherit each other's proof.
|
|
5196
|
+
*/
|
|
5197
|
+
export function scopeContextPackToExploratoryCandidateClosure(contextPack, candidateIds) {
|
|
5198
|
+
if (!contextPack || !candidateIds?.length)
|
|
5199
|
+
return undefined;
|
|
5200
|
+
const requestedIds = new Set(candidateIds.map(normalizeExploratoryCandidateIdentity).filter(Boolean));
|
|
5201
|
+
if (requestedIds.size === 0)
|
|
5202
|
+
return undefined;
|
|
5203
|
+
const intersects = (values) => {
|
|
5204
|
+
for (const value of values) {
|
|
5205
|
+
const normalized = normalizeExploratoryCandidateIdentity(value ?? '');
|
|
5206
|
+
if (normalized && requestedIds.has(normalized))
|
|
5207
|
+
return true;
|
|
5208
|
+
}
|
|
5209
|
+
return false;
|
|
5210
|
+
};
|
|
5211
|
+
const objectIdentities = (object) => {
|
|
5212
|
+
const payload = object.payload ?? {};
|
|
5213
|
+
return [
|
|
5214
|
+
object.objectKey,
|
|
5215
|
+
object.fullName,
|
|
5216
|
+
payload.qualifiedId,
|
|
5217
|
+
payload.uniqueId,
|
|
5218
|
+
payload.id,
|
|
5219
|
+
payload.localId,
|
|
5220
|
+
payload.relation,
|
|
5221
|
+
payload.table,
|
|
5222
|
+
payload.model,
|
|
5223
|
+
...metadataStringValues(payload.sourceObjects),
|
|
5224
|
+
...metadataStringValues(payload.sourceRelations),
|
|
5225
|
+
...metadataStringValues(payload.tableDependencies),
|
|
5226
|
+
].filter((value) => typeof value === 'string');
|
|
5227
|
+
};
|
|
5228
|
+
const objectRelationReferences = (object) => {
|
|
5229
|
+
const payload = object.payload ?? {};
|
|
5230
|
+
return [
|
|
5231
|
+
payload.relation,
|
|
5232
|
+
payload.table,
|
|
5233
|
+
payload.model,
|
|
5234
|
+
...metadataStringValues(payload.sourceObjects),
|
|
5235
|
+
...metadataStringValues(payload.sourceRelations),
|
|
5236
|
+
...metadataStringValues(payload.tableDependencies),
|
|
5237
|
+
].filter((value) => typeof value === 'string');
|
|
5238
|
+
};
|
|
5239
|
+
const directObjects = contextPack.objects.filter((object) => intersects(objectIdentities(object)));
|
|
5240
|
+
const requestedRelations = new Set();
|
|
5241
|
+
for (const object of directObjects) {
|
|
5242
|
+
for (const relation of objectRelationReferences(object)) {
|
|
5243
|
+
const normalized = normalizeExploratoryCandidateIdentity(relation);
|
|
5244
|
+
if (normalized)
|
|
5245
|
+
requestedRelations.add(normalized);
|
|
5246
|
+
}
|
|
5247
|
+
}
|
|
5248
|
+
for (const relation of contextPack.allowedSqlContext.relations) {
|
|
5249
|
+
if (intersects([relation.objectKey, relation.relation])) {
|
|
5250
|
+
requestedRelations.add(normalizeExploratoryCandidateIdentity(relation.relation));
|
|
5251
|
+
}
|
|
5252
|
+
}
|
|
5253
|
+
const relations = contextPack.allowedSqlContext.relations.filter((relation) => {
|
|
5254
|
+
const normalized = normalizeExploratoryCandidateIdentity(relation.relation);
|
|
5255
|
+
return requestedIds.has(normalized)
|
|
5256
|
+
|| requestedRelations.has(normalized)
|
|
5257
|
+
|| Boolean(relation.objectKey && directObjects.some((object) => object.objectKey === relation.objectKey));
|
|
5258
|
+
});
|
|
5259
|
+
// A router-selected exploratory attempt without a physical relation is not
|
|
5260
|
+
// executable. Returning undefined is intentionally fail-closed; callers may
|
|
5261
|
+
// preserve the full pack for receipts but may not use it as SQL authority.
|
|
5262
|
+
if (relations.length === 0)
|
|
5263
|
+
return undefined;
|
|
5264
|
+
const relationIds = new Set(relations.map((relation) => normalizeExploratoryCandidateIdentity(relation.relation)));
|
|
5265
|
+
const relationshipEndpointIds = new Set();
|
|
5266
|
+
for (const object of directObjects.filter((object) => object.objectType === 'relationship')) {
|
|
5267
|
+
const payload = object.payload ?? {};
|
|
5268
|
+
for (const endpoint of [payload.from, payload.to]) {
|
|
5269
|
+
if (typeof endpoint === 'string') {
|
|
5270
|
+
const normalized = normalizeExploratoryCandidateIdentity(endpoint);
|
|
5271
|
+
if (normalized)
|
|
5272
|
+
relationshipEndpointIds.add(normalized);
|
|
5273
|
+
}
|
|
5274
|
+
}
|
|
5275
|
+
}
|
|
5276
|
+
const objects = contextPack.objects.filter((object) => {
|
|
5277
|
+
if (directObjects.includes(object))
|
|
5278
|
+
return true;
|
|
5279
|
+
const identities = objectIdentities(object).map(normalizeExploratoryCandidateIdentity);
|
|
5280
|
+
if (identities.some((identity) => relationIds.has(identity) || relationshipEndpointIds.has(identity)))
|
|
5281
|
+
return true;
|
|
5282
|
+
return objectRelationReferences(object)
|
|
5283
|
+
.map(normalizeExploratoryCandidateIdentity)
|
|
5284
|
+
.some((identity) => relationIds.has(identity));
|
|
5285
|
+
});
|
|
5286
|
+
const objectKeys = new Set(objects.map((object) => object.objectKey));
|
|
5287
|
+
const relationKeys = new Set(relations.map((relation) => normalizeRelationKey(relation.relation)));
|
|
5288
|
+
const selectedRelations = contextPack.retrievalDiagnostics.selectedRelations?.filter((relation) => relationKeys.has(normalizeRelationKey(relation.relation)));
|
|
5289
|
+
const selectedJoinPaths = contextPack.retrievalDiagnostics.selectedJoinPaths?.filter((path) => relationKeys.has(normalizeRelationKey(path.leftRelation))
|
|
5290
|
+
&& relationKeys.has(normalizeRelationKey(path.rightRelation)));
|
|
5291
|
+
return {
|
|
5292
|
+
...contextPack,
|
|
5293
|
+
focusObjectKey: contextPack.focusObjectKey && objectKeys.has(contextPack.focusObjectKey)
|
|
5294
|
+
? contextPack.focusObjectKey
|
|
5295
|
+
: objects[0]?.objectKey ?? null,
|
|
5296
|
+
objects,
|
|
5297
|
+
skills: [],
|
|
5298
|
+
edges: contextPack.edges.filter((edge) => objectKeys.has(edge.fromKey) && objectKeys.has(edge.toKey)),
|
|
5299
|
+
citations: contextPack.citations.filter((citation) => objectKeys.has(citation.objectKey)),
|
|
5300
|
+
evidenceSummaries: contextPack.evidenceSummaries.filter((summary) => !summary.objectKey || objectKeys.has(summary.objectKey)),
|
|
5301
|
+
evidenceRoles: contextPack.evidenceRoles.filter((role) => objectKeys.has(role.objectKey)),
|
|
5302
|
+
allowedSqlContext: {
|
|
5303
|
+
relations,
|
|
5304
|
+
// Authored block SQL is not a physical exploratory authority. The
|
|
5305
|
+
// selected dbt/runtime relation closure above is the whole prompt/SQL
|
|
5306
|
+
// boundary for this tier.
|
|
5307
|
+
sourceBlockSql: [],
|
|
5308
|
+
},
|
|
5309
|
+
retrievalDiagnostics: {
|
|
5310
|
+
...contextPack.retrievalDiagnostics,
|
|
5311
|
+
selectedObjects: objects.length,
|
|
5312
|
+
selectedEvidence: contextPack.retrievalDiagnostics.selectedEvidence.filter((evidence) => objectKeys.has(evidence.objectKey)),
|
|
5313
|
+
selectedRelations,
|
|
5314
|
+
selectedJoinPaths,
|
|
5315
|
+
schemaShapeCandidates: contextPack.retrievalDiagnostics.schemaShapeCandidates?.filter((candidate) => objectKeys.has(candidate.objectKey)),
|
|
5316
|
+
certifiedCandidateFits: contextPack.retrievalDiagnostics.certifiedCandidateFits.filter((candidate) => objectKeys.has(candidate.objectKey)),
|
|
5317
|
+
},
|
|
5318
|
+
};
|
|
5319
|
+
}
|
|
5320
|
+
function metadataStringValues(value) {
|
|
5321
|
+
return Array.isArray(value)
|
|
5322
|
+
? value.filter((item) => typeof item === 'string')
|
|
5323
|
+
: [];
|
|
5324
|
+
}
|
|
5325
|
+
function normalizeExploratoryCandidateIdentity(value) {
|
|
5326
|
+
return value.trim().replace(/["`\[\]]/g, '').replace(/\s*\.\s*/g, '.').toLowerCase();
|
|
5327
|
+
}
|
|
4819
5328
|
/**
|
|
4820
5329
|
* Build the prompt-facing evidence pack after enforcing the certified output
|
|
4821
5330
|
* contract. Incompatible blocks remain in the returned answer's `considered`
|
|
@@ -4831,7 +5340,13 @@ function contextPackForRequestedShape(contextPack, question, questionPlan, kg) {
|
|
|
4831
5340
|
const blockName = object.name || object.objectKey.replace(/^dql:block:/, '');
|
|
4832
5341
|
const node = kg.getNode(`block:${blockName}`);
|
|
4833
5342
|
if (node?.kind === 'block') {
|
|
4834
|
-
const fit = evaluateCertifiedBlockFit({
|
|
5343
|
+
const fit = evaluateCertifiedBlockFit({
|
|
5344
|
+
question,
|
|
5345
|
+
plan: questionPlan,
|
|
5346
|
+
block: node,
|
|
5347
|
+
exactExampleMatch: hasUniqueExactCatalogExample(question, contextPack, node),
|
|
5348
|
+
uniqueExactExampleContract: hasUniqueExactCatalogExample(question, contextPack, node),
|
|
5349
|
+
});
|
|
4835
5350
|
if (fit.kind === 'context_only' || fit.kind === 'not_applicable') {
|
|
4836
5351
|
incompatibleBlockKeys.add(object.objectKey);
|
|
4837
5352
|
}
|
|
@@ -5384,7 +5899,6 @@ function renderContextPackForPrompt(contextPack, budget) {
|
|
|
5384
5899
|
: '';
|
|
5385
5900
|
const sourceSql = renderSourceBlockSqlContext(contextPack, budget);
|
|
5386
5901
|
return [
|
|
5387
|
-
`context_pack_id: ${contextPack.id}`,
|
|
5388
5902
|
`trust_label: ${contextPack.trustLabel}`,
|
|
5389
5903
|
contextPack.trustLabelInfo ? `trust_label_canonical: ${contextPack.trustLabelInfo.display}` : '',
|
|
5390
5904
|
renderDomainBriefingForPrompt(contextPack).trim(),
|
|
@@ -5678,6 +6192,72 @@ function certifiedHitFromContextPack(contextPack, kg) {
|
|
|
5678
6192
|
const node = nodeId ? kg.getNode(nodeId) : null;
|
|
5679
6193
|
return node ? { node, score: 1, snippet: object.snippet } : null;
|
|
5680
6194
|
}
|
|
6195
|
+
/**
|
|
6196
|
+
* Preserve the catalog's unique exact-example proof when the answer boundary
|
|
6197
|
+
* rechecks the frozen block. The context pack is the same retrieval snapshot
|
|
6198
|
+
* that produced `exactObjectKey`; broad KG search is intentionally not used to
|
|
6199
|
+
* manufacture example uniqueness.
|
|
6200
|
+
*/
|
|
6201
|
+
function hasUniqueExactCatalogExample(question, contextPack, node) {
|
|
6202
|
+
const normalizedQuestion = normalizeQuestion(question);
|
|
6203
|
+
if (!normalizedQuestion || !contextPack)
|
|
6204
|
+
return false;
|
|
6205
|
+
const matches = contextPack.objects.filter((object) => {
|
|
6206
|
+
if (object.objectType !== 'dql_block')
|
|
6207
|
+
return false;
|
|
6208
|
+
if (object.status !== 'certified' && object.status !== 'approved' && object.payload?.certification !== 'certified')
|
|
6209
|
+
return false;
|
|
6210
|
+
const examples = Array.isArray(object.payload?.examples) ? object.payload.examples : [];
|
|
6211
|
+
return examples.some((example) => example
|
|
6212
|
+
&& typeof example === 'object'
|
|
6213
|
+
&& normalizeQuestion(String(example.question ?? '')) === normalizedQuestion);
|
|
6214
|
+
});
|
|
6215
|
+
return matches.length === 1 && normalizeQuestion(matches[0].name) === normalizeQuestion(node.name);
|
|
6216
|
+
}
|
|
6217
|
+
/**
|
|
6218
|
+
* A metadata-only definition answer is safe only for one certified artifact
|
|
6219
|
+
* named exactly by the user. Do not let FTS ranking decide this: a generic
|
|
6220
|
+
* definition phrase must never select a neighbouring block. Payload aliases
|
|
6221
|
+
* are authored block aliases; descriptions and tags are deliberately excluded.
|
|
6222
|
+
*/
|
|
6223
|
+
function uniqueNamedCertifiedDefinitionArtifact(question, kg, authorizedDomains) {
|
|
6224
|
+
const subject = namedArtifactDefinitionSubject(question);
|
|
6225
|
+
if (!subject)
|
|
6226
|
+
return null;
|
|
6227
|
+
const matches = kg.getNodesByKind('block', 1000).filter((node) => {
|
|
6228
|
+
if (node.status !== 'certified')
|
|
6229
|
+
return false;
|
|
6230
|
+
if (authorizedDomains.length > 0 && node.domain && !authorizedDomains.includes(node.domain))
|
|
6231
|
+
return false;
|
|
6232
|
+
return certifiedArtifactNames(node).some((name) => normalizeQuestion(name) === subject);
|
|
6233
|
+
});
|
|
6234
|
+
return matches.length === 1 ? { node: matches[0], score: 1, snippet: undefined } : null;
|
|
6235
|
+
}
|
|
6236
|
+
function isUniqueNamedCertifiedDefinitionQuestion(question, node, kg, authorizedDomains) {
|
|
6237
|
+
const selected = uniqueNamedCertifiedDefinitionArtifact(question, kg, authorizedDomains);
|
|
6238
|
+
return selected?.node.nodeId === node.nodeId;
|
|
6239
|
+
}
|
|
6240
|
+
function namedArtifactDefinitionSubject(question) {
|
|
6241
|
+
const match = /^\s*what\s+does\s+(?:the\s+)?(.+?)\s+(?:measure|mean|define|represent)\s*[?.!]*\s*$/i.exec(question);
|
|
6242
|
+
if (!match?.[1])
|
|
6243
|
+
return undefined;
|
|
6244
|
+
// Remove an optional explicit artifact noun while preserving the exact
|
|
6245
|
+
// authored identifier. `top_customers block` and `top_customers` therefore
|
|
6246
|
+
// resolve identically, while arbitrary definition questions have no subject
|
|
6247
|
+
// match and remain on the normal route.
|
|
6248
|
+
const subject = match[1]
|
|
6249
|
+
.replace(/\b(?:certified\s+)?(?:block|artifact|model|metric)\b/gi, ' ')
|
|
6250
|
+
.replace(/\s+/g, ' ')
|
|
6251
|
+
.trim();
|
|
6252
|
+
const normalized = normalizeQuestion(subject);
|
|
6253
|
+
return normalized || undefined;
|
|
6254
|
+
}
|
|
6255
|
+
function certifiedArtifactNames(node) {
|
|
6256
|
+
const aliases = Array.isArray(node.payload?.aliases)
|
|
6257
|
+
? node.payload.aliases.filter((alias) => typeof alias === 'string')
|
|
6258
|
+
: [];
|
|
6259
|
+
return [node.name, ...aliases];
|
|
6260
|
+
}
|
|
5681
6261
|
/**
|
|
5682
6262
|
* Reduce a possibly-composed prompt to the user's actual question for display.
|
|
5683
6263
|
* Callers (e.g. the Build-SQL-draft modal) sometimes prepend a multi-line instruction
|
|
@@ -6765,7 +7345,12 @@ function isBusinessDefinitionQuestion(question) {
|
|
|
6765
7345
|
}
|
|
6766
7346
|
/** Definition that may terminate with documentation instead of executing data. */
|
|
6767
7347
|
function isPureBusinessDefinitionQuestion(question) {
|
|
6768
|
-
return questionTypeFromText(question) === 'definition'
|
|
7348
|
+
return questionTypeFromText(question) === 'definition'
|
|
7349
|
+
// `questionTypeFromText` intentionally recognizes ranking before generic
|
|
7350
|
+
// definitions, so a named artifact such as `top_customers` would otherwise
|
|
7351
|
+
// look like a ranking request even in the explicit "what does … measure?"
|
|
7352
|
+
// form. Object-name matching remains mandatory at the terminal site.
|
|
7353
|
+
|| /^\s*what\s+does\s+(?:the\s+)?[^?.!]{1,160}\s+(?:measure|mean|define|represent)\s*[?.!]*\s*$/i.test(question);
|
|
6769
7354
|
}
|
|
6770
7355
|
function isBreakdownOrDrilldownQuestion(question) {
|
|
6771
7356
|
return /\b(break\s*down|breakdown|drill\s*(?:down|into)|slice|segment|split|by\s+[a-z][\w\s-]{1,40})\b/i.test(question);
|
|
@@ -6828,12 +7413,21 @@ function hasCertifiedNodeFit(question, plan, node, options = {}) {
|
|
|
6828
7413
|
plan,
|
|
6829
7414
|
block: node,
|
|
6830
7415
|
exactExampleMatch: exactExampleMatch || exactObjectRequest,
|
|
7416
|
+
uniqueExactExampleContract: options.uniqueExactExampleContract === true,
|
|
6831
7417
|
definitionLookup,
|
|
6832
7418
|
});
|
|
6833
7419
|
// Delegate the termination decision to THE single authority instead of a
|
|
6834
7420
|
// loop-local re-derivation (the seams between such copies produced certified
|
|
6835
7421
|
// answers that ignored member-scoped follow-ups).
|
|
6836
|
-
return certifiedTerminationVerdict({
|
|
7422
|
+
return certifiedTerminationVerdict({
|
|
7423
|
+
fit,
|
|
7424
|
+
// A named `what does <artifact> measure?` request is an artifact metadata
|
|
7425
|
+
// lookup, not a query-result claim. The termination authority still
|
|
7426
|
+
// rejects missing declared measures and unsupported filters before this
|
|
7427
|
+
// narrow bypass is considered.
|
|
7428
|
+
...(definitionLookup ? { bypass: 'definition_lookup' } : {}),
|
|
7429
|
+
allowInferredContract: options.allowInferredContract,
|
|
7430
|
+
}).allow;
|
|
6837
7431
|
}
|
|
6838
7432
|
function objectNameInQuestion(question, node) {
|
|
6839
7433
|
const questionText = normalizeQuestion(question);
|