@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.
Files changed (84) hide show
  1. package/dist/agent-run-engine.d.ts +9 -1
  2. package/dist/agent-run-engine.d.ts.map +1 -1
  3. package/dist/agent-run-engine.js +199 -17
  4. package/dist/agent-run-engine.js.map +1 -1
  5. package/dist/agent-run-gates.d.ts.map +1 -1
  6. package/dist/agent-run-gates.js +12 -0
  7. package/dist/agent-run-gates.js.map +1 -1
  8. package/dist/agentic/analyst-loop.d.ts.map +1 -1
  9. package/dist/agentic/analyst-loop.js +48 -28
  10. package/dist/agentic/analyst-loop.js.map +1 -1
  11. package/dist/agentic/research/synthesis.d.ts +4 -0
  12. package/dist/agentic/research/synthesis.d.ts.map +1 -1
  13. package/dist/agentic/research/synthesis.js +23 -8
  14. package/dist/agentic/research/synthesis.js.map +1 -1
  15. package/dist/agentic/research-agent.d.ts.map +1 -1
  16. package/dist/agentic/research-agent.js +3 -2
  17. package/dist/agentic/research-agent.js.map +1 -1
  18. package/dist/agentic/sql-authorization.d.ts.map +1 -1
  19. package/dist/agentic/sql-authorization.js +230 -2
  20. package/dist/agentic/sql-authorization.js.map +1 -1
  21. package/dist/analytical-frame.d.ts.map +1 -1
  22. package/dist/analytical-frame.js +10 -1
  23. package/dist/analytical-frame.js.map +1 -1
  24. package/dist/analytical-orchestration.d.ts +302 -0
  25. package/dist/analytical-orchestration.d.ts.map +1 -1
  26. package/dist/analytical-orchestration.js +564 -0
  27. package/dist/analytical-orchestration.js.map +1 -1
  28. package/dist/answer-loop.d.ts +48 -0
  29. package/dist/answer-loop.d.ts.map +1 -1
  30. package/dist/answer-loop.js +679 -85
  31. package/dist/answer-loop.js.map +1 -1
  32. package/dist/answer-shape.d.ts +32 -1
  33. package/dist/answer-shape.d.ts.map +1 -1
  34. package/dist/answer-shape.js +49 -3
  35. package/dist/answer-shape.js.map +1 -1
  36. package/dist/conversation/snapshot.d.ts +20 -0
  37. package/dist/conversation/snapshot.d.ts.map +1 -1
  38. package/dist/conversation/snapshot.js +30 -1
  39. package/dist/conversation/snapshot.js.map +1 -1
  40. package/dist/fixtures/ask-ai-office-shaped.d.ts +153 -0
  41. package/dist/fixtures/ask-ai-office-shaped.d.ts.map +1 -0
  42. package/dist/fixtures/ask-ai-office-shaped.js +94 -0
  43. package/dist/fixtures/ask-ai-office-shaped.js.map +1 -0
  44. package/dist/index.d.ts +4 -4
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +2 -2
  47. package/dist/index.js.map +1 -1
  48. package/dist/intent-controller.d.ts +28 -0
  49. package/dist/intent-controller.d.ts.map +1 -1
  50. package/dist/intent-controller.js +30 -0
  51. package/dist/intent-controller.js.map +1 -1
  52. package/dist/meaning-resolution.d.ts +69 -2
  53. package/dist/meaning-resolution.d.ts.map +1 -1
  54. package/dist/meaning-resolution.js +128 -7
  55. package/dist/meaning-resolution.js.map +1 -1
  56. package/dist/metadata/analysis-planner.d.ts.map +1 -1
  57. package/dist/metadata/analysis-planner.js +124 -7
  58. package/dist/metadata/analysis-planner.js.map +1 -1
  59. package/dist/metadata/block-fit.d.ts +18 -0
  60. package/dist/metadata/block-fit.d.ts.map +1 -1
  61. package/dist/metadata/block-fit.js +250 -38
  62. package/dist/metadata/block-fit.js.map +1 -1
  63. package/dist/metadata/catalog.d.ts.map +1 -1
  64. package/dist/metadata/catalog.js +57 -11
  65. package/dist/metadata/catalog.js.map +1 -1
  66. package/dist/metadata/meaning-evidence.d.ts +10 -1
  67. package/dist/metadata/meaning-evidence.d.ts.map +1 -1
  68. package/dist/metadata/meaning-evidence.js +261 -47
  69. package/dist/metadata/meaning-evidence.js.map +1 -1
  70. package/dist/research-loop.d.ts.map +1 -1
  71. package/dist/research-loop.js +23 -1
  72. package/dist/research-loop.js.map +1 -1
  73. package/dist/resolved-analytical-plan.d.ts +7 -0
  74. package/dist/resolved-analytical-plan.d.ts.map +1 -1
  75. package/dist/resolved-analytical-plan.js +11 -4
  76. package/dist/resolved-analytical-plan.js.map +1 -1
  77. package/dist/router.d.ts +4 -3
  78. package/dist/router.d.ts.map +1 -1
  79. package/dist/router.js +1796 -125
  80. package/dist/router.js.map +1 -1
  81. package/dist/semantic-bridge/member-select.d.ts.map +1 -1
  82. package/dist/semantic-bridge/member-select.js +39 -21
  83. package/dist/semantic-bridge/member-select.js.map +1 -1
  84. package/package.json +5 -5
@@ -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, scopedContextPack), scopedContextPack);
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(scopedContextPack, question, questionPlan, kg);
1485
+ const promptContextPack = contextPackForRequestedShape(executionContextPack, question, questionPlan, kg);
1325
1486
  const repairBudgetState = createCascadeBudgetState(input.cascadeBudgetModel);
1326
- const authoritativePlanBinding = input.resolvedAnalyticalPlan?.mode === 'authoritative'
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
- sourceTier: 'no_answer',
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 = preferredSemanticMetric
1603
- ? { metric: preferredSemanticMetric, score: 1, basis: 'name' }
1604
- : input.resolvedAnalyticalPlan?.mode === 'authoritative'
1605
- ? null
1606
- : await matchSemanticMetric(semanticQuestion, semanticMetricNodes, {
1607
- measureTerms: [...questionPlan.requestedShape.measures, ...questionPlan.metricTerms],
1608
- ...(canExecuteSemanticMetricForMatch ? { canExecute: canExecuteSemanticMetricForMatch } : {}),
1609
- ...(snapshotVectorMetricShortlist(input.contextPack).length
1610
- ? { vectorMetricShortlist: snapshotVectorMetricShortlist(input.contextPack) }
1611
- : {}),
1612
- ...semanticMetricEmbeddingOptions(input.embeddingProvider),
1613
- }).catch(() => null);
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 = shouldUseCertifiedRoute(catalogRoute, intent)
1634
- || explicitlyRequestedCertifiedBlock;
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 ? validateAnswerResultShape(questionPlan, result).warnings : [];
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 ? 'no_answer' : certifiedShapePassed ? 'certified' : 'uncertified',
1831
- sourceTier: authoritativeCertifiedFailure ? 'no_answer' : sourceTier,
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 ? 'none' : certifiedShapePassed ? 'certified' : 'analyst_review_required',
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 ? { refusalCode: 'grounding_gap' } : {}),
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 = sourceTierFromContextPack(input.contextPack)
1937
- ?? (semanticHits.length > 0 || semanticMetricMatch
1938
- ? 'semantic_layer'
1939
- : manifestHits.length > 0
1940
- ? 'dbt_manifest'
1941
- : 'dbt_manifest');
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
- const contextNodes = 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));
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 || (input.forcedGeneratedProposal && input.executeAgenticGeneratedSql)) && !result) {
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 && !input.forcedGeneratedProposal && !fanoutContradiction && !authoritativeSemanticBinding) {
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
- sql: parsed.sql,
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
- 'The SQL preview is uncertified until an analyst reviews and promotes the DQL artifact.',
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 = result?.dqlArtifact ?? semanticBridgeAnswer?.dqlArtifact ?? buildGeneratedSqlDqlArtifact({
3595
- question,
3596
- sql: parsed.sql,
3597
- intent,
3598
- domain,
3599
- followUp: input.followUp,
3600
- contextPack: input.contextPack,
3601
- sourceBlock: followUpSourceBlock ?? undefined,
3602
- sourceDqlArtifact: input.followUp?.priorDqlArtifact,
3603
- proposedEntity: parsed.proposedEntity,
3604
- requestedFilters: generatedRequestedFilters,
3605
- requestedDimensions: generatedRequestedDimensions,
3606
- validationWarnings,
3607
- outputs: generatedOutputs,
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: governedMetricExecutionFailure ? 'no_answer' : 'uncertified',
3675
- sourceTier: governedMetricExecutionFailure ? 'no_answer' : governedMetricAnswer ? 'semantic_layer' : activeTier,
3676
- certification: governedMetricExecutionFailure ? 'analyst_review_required' : governedMetricAnswer ? 'governed' : 'ai_generated',
3677
- reviewStatus: governedMetricExecutionFailure ? 'none' : governedMetricAnswer ? 'governed' : 'draft_ready',
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: governedMetricExecutionFailure ? 0 : certifiedMetricAnswer ? 0.8 : governedMetricAnswer ? 0.72 : 0.55,
3680
- text: generatedText,
3681
- answer: generatedText,
3682
- proposedSql: parsed.sql,
3683
- sql: parsed.sql,
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
- ...(governedMetricExecutionFailure ? { refusalCode: 'grounding_gap' } : {}),
4053
+ ...(terminalExecutionFailure ? { refusalCode: 'grounding_gap' } : {}),
3689
4054
  suggestedViz: parsed.viz ?? 'table',
3690
- dqlArtifact: answerDqlArtifact,
3691
- draftBlock,
3692
- draftBlockId: draftBlock?.path,
3693
- promoteCommand: draftBlock ? `dql certify --from-draft ${draftBlock.path}` : undefined,
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({ question, plan: questionPlan, block: node });
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({ fit, allowInferredContract: options.allowInferredContract }).allow;
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);