@duckcodeailabs/dql-cli 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 (86) hide show
  1. package/dist/assets/dql-notebook/assets/{AgentLogPage-BPz-UWFh.js → AgentLogPage-DKbGpRQS.js} +1 -1
  2. package/dist/assets/dql-notebook/assets/{AiBuildDialog-BEl53WA_.js → AiBuildDialog-DPSu0Mly.js} +1 -1
  3. package/dist/assets/dql-notebook/assets/{AiBuildResult-B4yfGTTZ.js → AiBuildResult-1uaGnpi1.js} +1 -1
  4. package/dist/assets/dql-notebook/assets/{AiSidePanel-CSZAAvuD.js → AiSidePanel-BwMREwa7.js} +1 -1
  5. package/dist/assets/dql-notebook/assets/{AnalyticsHome-D5P6Ujwi.js → AnalyticsHome-BGfey_ve.js} +1 -1
  6. package/dist/assets/dql-notebook/assets/{AppsView-DQOwU9Cg.js → AppsView-CM1tPywy.js} +4 -4
  7. package/dist/assets/dql-notebook/assets/{BlockStudio-B4ap0GdY.js → BlockStudio--S6WFVO4.js} +1 -1
  8. package/dist/assets/dql-notebook/assets/{BusinessArtifactView-BIfNI-0S.js → BusinessArtifactView-BEAJ-yNW.js} +1 -1
  9. package/dist/assets/dql-notebook/assets/{DbtFirstModelingPage-D72byb2g.js → DbtFirstModelingPage-CNyU5MBX.js} +1 -1
  10. package/dist/assets/dql-notebook/assets/{GitPage-lQb1uXH2.js → GitPage-IcydAai3.js} +1 -1
  11. package/dist/assets/dql-notebook/assets/{GlobalAiRail-CVECf6Xj.js → GlobalAiRail-CSKW-5eD.js} +1 -1
  12. package/dist/assets/dql-notebook/assets/{GovernedContextPage-trOyMCY6.js → GovernedContextPage-CFen0eFT.js} +1 -1
  13. package/dist/assets/dql-notebook/assets/{HelpDocsPage-D8hLS5lE.js → HelpDocsPage-D0D9UCIz.js} +1 -1
  14. package/dist/assets/dql-notebook/assets/{HomePage-eIkfBIep.js → HomePage-CmSxapR3.js} +1 -1
  15. package/dist/assets/dql-notebook/assets/{LineageDAG-CSqcbDrE.js → LineageDAG-BGUcIt1B.js} +1 -1
  16. package/dist/assets/dql-notebook/assets/{LineageDetailView-DJZZjZu-.js → LineageDetailView-Dx48Zfzb.js} +1 -1
  17. package/dist/assets/dql-notebook/assets/{LineageDrawer-BcIipIc3.js → LineageDrawer-zaBfSLZO.js} +1 -1
  18. package/dist/assets/dql-notebook/assets/{LineagePathBreadcrumb-CviIf8PN.js → LineagePathBreadcrumb-CTZJp4_r.js} +1 -1
  19. package/dist/assets/dql-notebook/assets/{MiniLineageGraph-vH_MY_Ju.js → MiniLineageGraph-CdivNR1S.js} +1 -1
  20. package/dist/assets/dql-notebook/assets/{NewBlockModal-DbCQg-pj.js → NewBlockModal-DMBFB7nE.js} +1 -1
  21. package/dist/assets/dql-notebook/assets/{NewNotebookModal-5Vp6XiuK.js → NewNotebookModal-DsC9CWlW.js} +1 -1
  22. package/dist/assets/dql-notebook/assets/{NotebookEditor-DjqJS44s.js → NotebookEditor-hs-kw8v9.js} +1 -1
  23. package/dist/assets/dql-notebook/assets/{ReadinessPage-BHGrC5ho.js → ReadinessPage-DJ83cMik.js} +1 -1
  24. package/dist/assets/dql-notebook/assets/{SetupOnboarding-BfS9Tdx-.js → SetupOnboarding-B1Pu_Bvv.js} +1 -1
  25. package/dist/assets/dql-notebook/assets/{SkillsPage-BFH21wSj.js → SkillsPage-BHKDay8n.js} +1 -1
  26. package/dist/assets/dql-notebook/assets/{TrustBadge-zm6g_SxZ.js → TrustBadge-BkyGgob2.js} +1 -1
  27. package/dist/assets/dql-notebook/assets/UnifiedAgentRunPanel-BYdrTaEW.js +89 -0
  28. package/dist/assets/dql-notebook/assets/{answer-to-notebook-DPhxIEzF.js → answer-to-notebook-DmNiuLQA.js} +1 -1
  29. package/dist/assets/dql-notebook/assets/{arrow-left-DNEb86Xc.js → arrow-left--1rsrxm8.js} +1 -1
  30. package/dist/assets/dql-notebook/assets/{arrow-right-DpPWbwaD.js → arrow-right-D5TdqY1G.js} +1 -1
  31. package/dist/assets/dql-notebook/assets/{book-open-text-D7s5jo4X.js → book-open-text-Bw7nHbzg.js} +1 -1
  32. package/dist/assets/dql-notebook/assets/{circle-x-Db3dXKg7.js → circle-x-DLe6NNM4.js} +1 -1
  33. package/dist/assets/dql-notebook/assets/{dagre.esm-C7pppQ1a.js → dagre.esm-CW5QZdBt.js} +1 -1
  34. package/dist/assets/dql-notebook/assets/{external-link-BxwXitO_.js → external-link-C9Q97sA3.js} +1 -1
  35. package/dist/assets/dql-notebook/assets/{grip-vertical-Dt4lkWRi.js → grip-vertical-CztvkIgo.js} +1 -1
  36. package/dist/assets/dql-notebook/assets/{index-DKo-bwNw.js → index-zHHzDn6l.js} +127 -127
  37. package/dist/assets/dql-notebook/assets/{link-2-Dfo2P6wi.js → link-2-CiKAvumL.js} +1 -1
  38. package/dist/assets/dql-notebook/assets/{list-tree-DiTmIWAL.js → list-tree-BtnP2nQ5.js} +1 -1
  39. package/dist/assets/dql-notebook/assets/{minimize-2-CMTAkPzL.js → minimize-2-TSFGxcCP.js} +1 -1
  40. package/dist/assets/dql-notebook/assets/{panel-right-open-DwYr7FW4.js → panel-right-open-BfXIUWy0.js} +1 -1
  41. package/dist/assets/dql-notebook/assets/{play-BXhHYQ4x.js → play-DVbSFJHD.js} +1 -1
  42. package/dist/assets/dql-notebook/assets/{rotate-ccw-BNi6F8pl.js → rotate-ccw-D_cesDcX.js} +1 -1
  43. package/dist/assets/dql-notebook/assets/{semantic-fields-CNOGysAy.js → semantic-fields-CoVStdYB.js} +1 -1
  44. package/dist/assets/dql-notebook/assets/{sliders-horizontal-Ec5MUMUW.js → sliders-horizontal-l7xV9K5A.js} +1 -1
  45. package/dist/assets/dql-notebook/assets/{star-B9leDkp_.js → star-CSBS0H3b.js} +1 -1
  46. package/dist/assets/dql-notebook/assets/{triangle-alert-BefTYCzx.js → triangle-alert-BTrnyY4q.js} +1 -1
  47. package/dist/assets/dql-notebook/assets/{upload-SPiOM2tQ.js → upload-sySLq9zb.js} +1 -1
  48. package/dist/assets/dql-notebook/assets/{usePersistedAgentThreadId-C4foXeiQ.js → usePersistedAgentThreadId-CzwgGdus.js} +1 -1
  49. package/dist/assets/dql-notebook/assets/{user-round-comGmyw-.js → user-round-ChlgXi9j.js} +1 -1
  50. package/dist/assets/dql-notebook/assets/{wand-sparkles-BffR4dF8.js → wand-sparkles-CGw0ytyT.js} +1 -1
  51. package/dist/assets/dql-notebook/assets/{workflow-ChmPTEzH.js → workflow-C_RltiK5.js} +1 -1
  52. package/dist/assets/dql-notebook/assets/{wrench-DovaG_ze.js → wrench-D-wLfeu0.js} +1 -1
  53. package/dist/assets/dql-notebook/assets/{x-nRx91AgW.js → x-B28hIJIC.js} +1 -1
  54. package/dist/assets/dql-notebook/index.html +1 -1
  55. package/dist/commands/agent-eval-cassette.d.ts +97 -6
  56. package/dist/commands/agent-eval-cassette.d.ts.map +1 -1
  57. package/dist/commands/agent-eval-cassette.js +165 -22
  58. package/dist/commands/agent-eval-cassette.js.map +1 -1
  59. package/dist/commands/agent-eval-runtime.d.ts +14 -2
  60. package/dist/commands/agent-eval-runtime.d.ts.map +1 -1
  61. package/dist/commands/agent-eval-runtime.js +12 -2
  62. package/dist/commands/agent-eval-runtime.js.map +1 -1
  63. package/dist/commands/agent.d.ts +9 -3
  64. package/dist/commands/agent.d.ts.map +1 -1
  65. package/dist/commands/agent.js +126 -49
  66. package/dist/commands/agent.js.map +1 -1
  67. package/dist/commands/compile.d.ts +13 -1
  68. package/dist/commands/compile.d.ts.map +1 -1
  69. package/dist/commands/compile.js +36 -4
  70. package/dist/commands/compile.js.map +1 -1
  71. package/dist/commands/sync.d.ts.map +1 -1
  72. package/dist/commands/sync.js +11 -3
  73. package/dist/commands/sync.js.map +1 -1
  74. package/dist/llm/providers/dql-agent-provider.d.ts +28 -1
  75. package/dist/llm/providers/dql-agent-provider.d.ts.map +1 -1
  76. package/dist/llm/providers/dql-agent-provider.js +272 -22
  77. package/dist/llm/providers/dql-agent-provider.js.map +1 -1
  78. package/dist/llm/types.d.ts +29 -1
  79. package/dist/llm/types.d.ts.map +1 -1
  80. package/dist/local-runtime.d.ts +16 -1
  81. package/dist/local-runtime.d.ts.map +1 -1
  82. package/dist/local-runtime.js +824 -66
  83. package/dist/local-runtime.js.map +1 -1
  84. package/dist/package.json +10 -10
  85. package/package.json +10 -10
  86. package/dist/assets/dql-notebook/assets/UnifiedAgentRunPanel--oxmjlgr.js +0 -88
@@ -1,9 +1,10 @@
1
- import { deadlineScale, planAnalystTurn, rerankCandidates } from '@duckcodeailabs/dql-agent';
1
+ import { classifyProviderFailure, deadlineScale, planAnalystTurn, rerankCandidates, } from '@duckcodeailabs/dql-agent';
2
2
  import { buildAnalystLoopTools } from '../analyst-loop-tools.js';
3
- import { ClaudeProvider, KGStore, MemoryStore, defaultKgPath, defaultMemoryPath, GeminiProvider, loadAgentSemanticLayer, OllamaProvider, OpenAIProvider, answer, buildAnalysisQuestionPlan, buildLocalContextPack, contextRetrievalBudgetForQuestion, ensureAgentProjectReady, isLikelyClarificationReply, isTrustedConversationTurn, createProviderDispatchEgressReceipt, createProviderEgressReceipt, assertProviderPayloadAllowed, prepareProviderContextForDispatch, prepareProviderWireEnvelopeForDispatch, prepareServerOwnedProviderSchemaContext, projectEmbeddingProvider, answerAgentic, qualifyAuthorizationReferences, createAnalystLaneHandler, parseProposal, renderContextValidationRefusalForUser, validateSqlAgainstLocalContext, resolveOrchestratorPolicy, } from '@duckcodeailabs/dql-agent';
4
- import { CassetteStore, resolveCassetteModeFromEnv, withCassette } from '../../commands/agent-eval-cassette.js';
3
+ import { ClaudeProvider, KGStore, MemoryStore, defaultKgPath, defaultMemoryPath, GeminiProvider, loadAgentSemanticLayer, OllamaProvider, OpenAIProvider, answer, buildAnalysisQuestionPlan, buildLocalContextPack, contextRetrievalBudgetForQuestion, ensureAgentProjectReady, isLikelyClarificationReply, isTrustedConversationTurn, createProviderDispatchEgressReceipt, createProviderEgressReceipt, assertProviderPayloadAllowed, prepareProviderContextForDispatch, prepareProviderWireEnvelopeForDispatch, prepareServerOwnedProviderSchemaContext, projectEmbeddingProvider, answerAgentic, qualifyAuthorizationReferences, scopeContextPackToExploratoryCandidateClosure, createAnalystLaneHandler, parseProposal, renderContextValidationRefusalForUser, validateSqlAgainstLocalContext, resolveOrchestratorPolicy, } from '@duckcodeailabs/dql-agent';
4
+ import { CassetteStore, evalCassetteCanonicalizationV2, resolveCassetteModeFromEnv, withCassette, } from '../../commands/agent-eval-cassette.js';
5
5
  import { buildManifest, normalizeDqlArtifactReference, resolveDbtManifestPath } from '@duckcodeailabs/dql-core';
6
- import { existsSync, readFileSync } from 'node:fs';
6
+ import { createHash } from 'node:crypto';
7
+ import { existsSync, readFileSync, statSync } from 'node:fs';
7
8
  import { join } from 'node:path';
8
9
  import { buildAnswerLoopTools, createGroundingContextExpander } from '../answer-loop-tools.js';
9
10
  import { getSemanticRuntimeStatus } from '../../semantic-runtime.js';
@@ -75,6 +76,39 @@ const SPECS = {
75
76
  },
76
77
  },
77
78
  };
79
+ /**
80
+ * Capture a content-free provider failure at the closest boundary. Local
81
+ * runtime may later choose a user-facing headline, but must not need raw
82
+ * provider errors or URLs to reconstruct the cause.
83
+ */
84
+ function providerBoundaryDiagnostic(input) {
85
+ const config = getEffectiveProviderConfig(input.projectRoot, input.providerId);
86
+ const message = input.error instanceof Error ? input.error.message : String(input.error ?? 'provider readiness failed');
87
+ const code = input.code
88
+ ?? (input.error && typeof input.error === 'object' ? String(input.error.code ?? '') : '');
89
+ const fingerprint = (value) => value?.trim()
90
+ ? `sha256:${createHash('sha256').update(value.trim()).digest('hex')}`
91
+ : undefined;
92
+ let origin;
93
+ if (config.baseUrl) {
94
+ try {
95
+ origin = new URL(config.baseUrl).origin;
96
+ }
97
+ catch {
98
+ // The full malformed URL never leaves this function; its fingerprint is
99
+ // still a useful support correlation key without disclosing content.
100
+ origin = config.baseUrl;
101
+ }
102
+ }
103
+ return classifyProviderFailure({
104
+ code,
105
+ message,
106
+ phase: input.phase,
107
+ providerFingerprint: fingerprint(input.providerId),
108
+ modelFingerprint: fingerprint(config.model),
109
+ baseOriginFingerprint: fingerprint(origin),
110
+ });
111
+ }
78
112
  function createCertifiedFitConfirmation(provider, signal) {
79
113
  return async ({ question, questionPlan, block, fit }) => {
80
114
  const response = await provider.generate([
@@ -228,6 +262,40 @@ function stringArray(value) {
228
262
  function stringValue(value) {
229
263
  return typeof value === 'string' && value.trim().length > 0 ? value : undefined;
230
264
  }
265
+ /**
266
+ * A precomputed exploratory pack is an optimization/witness only. The runner
267
+ * independently derives the pack from the immutable router-selected IDs and
268
+ * accepts the witness only when its snapshot, source fingerprint, physical
269
+ * relations, and metadata object set are exactly the same. This prevents a
270
+ * caller from replacing a safe same-snapshot closure with a broader one.
271
+ */
272
+ function exploratoryClosureMatches(derived, witness) {
273
+ if (!derived)
274
+ return false;
275
+ if (derived.knowledgeLens.snapshotId !== witness.knowledgeLens.snapshotId)
276
+ return false;
277
+ if (derived.freshness.fingerprint !== witness.freshness.fingerprint)
278
+ return false;
279
+ const normalize = (value) => value
280
+ .trim()
281
+ .split('.')
282
+ .map((part) => part.trim().replace(/^["`\[]|["`\]]$/g, '').toLowerCase())
283
+ .filter(Boolean)
284
+ .join('.');
285
+ const sameSet = (left, right) => {
286
+ if (left.length !== right.length)
287
+ return false;
288
+ const rightSet = new Set(right);
289
+ return left.every((value) => rightSet.has(value));
290
+ };
291
+ const derivedRelations = [...new Set(derived.allowedSqlContext.relations.map((relation) => normalize(relation.relation)))].sort();
292
+ const witnessRelations = [...new Set(witness.allowedSqlContext.relations.map((relation) => normalize(relation.relation)))].sort();
293
+ if (!sameSet(derivedRelations, witnessRelations))
294
+ return false;
295
+ const derivedObjects = [...new Set(derived.objects.map((object) => object.objectKey))].sort();
296
+ const witnessObjects = [...new Set(witness.objects.map((object) => object.objectKey))].sort();
297
+ return sameSet(derivedObjects, witnessObjects);
298
+ }
231
299
  function truncateForFitPrompt(value, max) {
232
300
  if (!value)
233
301
  return undefined;
@@ -279,16 +347,27 @@ function emitProposalFromText(text, emit) {
279
347
  * this one (local-runtime.ts:164), so reaching back would close an import cycle
280
348
  * through a 34k-line file. A few lines of JSON reading is the cheaper trade.
281
349
  *
282
- * Cached per project root because this runs on every turn and the answer is a
283
- * migration setting, not live state. A malformed file resolves to `null`, which
284
- * `resolveOrchestratorPolicy` turns into `legacy` a broken config must not
285
- * route real questions onto an unproven path.
350
+ * Cached by a lightweight file fingerprint, not forever. Settings writes must
351
+ * take effect for the next Ask without requiring a notebook-server restart.
352
+ * A malformed file resolves to `null`, which `resolveOrchestratorPolicy` turns
353
+ * into `legacy` — a broken config must not route real questions onto an
354
+ * unproven path.
286
355
  */
287
356
  const agentConfigCache = new Map();
357
+ function agentConfigFingerprint(projectRoot) {
358
+ try {
359
+ const stats = statSync(join(projectRoot, 'dql.config.json'));
360
+ return `${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeMs}`;
361
+ }
362
+ catch {
363
+ return 'missing';
364
+ }
365
+ }
288
366
  function readAgentConfig(projectRoot) {
367
+ const fingerprint = agentConfigFingerprint(projectRoot);
289
368
  const cached = agentConfigCache.get(projectRoot);
290
- if (cached !== undefined)
291
- return cached;
369
+ if (cached && cached.fingerprint === fingerprint)
370
+ return cached.value;
292
371
  let resolved = null;
293
372
  try {
294
373
  const raw = readFileSync(join(projectRoot, 'dql.config.json'), 'utf-8');
@@ -300,7 +379,7 @@ function readAgentConfig(projectRoot) {
300
379
  catch {
301
380
  resolved = null;
302
381
  }
303
- agentConfigCache.set(projectRoot, resolved);
382
+ agentConfigCache.set(projectRoot, { fingerprint, value: resolved });
304
383
  return resolved;
305
384
  }
306
385
  function readOrchestratorConfig(projectRoot) {
@@ -355,11 +434,43 @@ function valueLookupEnabled(projectRoot) {
355
434
  function agenticLaneForRequest(req) {
356
435
  return req.orchestrationMode === 'research' ? 'research' : 'generated';
357
436
  }
358
- export function applyEvalCassette(provider) {
437
+ export function applyEvalCassette(provider, projectRoot) {
359
438
  const dir = process.env.DQL_EVAL_CASSETTE_DIR;
360
439
  if (!dir)
361
440
  return provider;
362
- return withCassette(provider, new CassetteStore(dir), resolveCassetteModeFromEnv(process.env));
441
+ return withCassette(provider, new CassetteStore(dir), resolveCassetteModeFromEnv(process.env), evalCassetteCanonicalizationV2(projectRoot));
442
+ }
443
+ /**
444
+ * Create a replay-only provider when the runtime is launched for an offline
445
+ * evaluation. This is intentionally unavailable outside explicit cassette
446
+ * replay: recording and live modes still require a configured real provider.
447
+ *
448
+ * The cassette's recorded provider identity is part of its key. Recover it
449
+ * from a single-provider cassette directory instead of borrowing a user's
450
+ * active provider or guessing from an API setting. The base provider cannot
451
+ * make a network call; replay misses remain CassetteMissError failures.
452
+ */
453
+ export function createEvalCassetteReplayProvider(projectRoot) {
454
+ const dir = process.env.DQL_EVAL_CASSETTE_DIR;
455
+ if (!dir || resolveCassetteModeFromEnv(process.env) !== 'replay')
456
+ return undefined;
457
+ const store = new CassetteStore(dir);
458
+ const providerNames = store.providerNames();
459
+ const providerName = providerNames.length === 1 ? asAgentProviderName(providerNames[0]) : undefined;
460
+ if (!providerName)
461
+ return undefined;
462
+ return withCassette({
463
+ name: providerName,
464
+ available: async () => true,
465
+ generate: async () => {
466
+ throw new Error('Eval cassette replay miss: no live provider is available.');
467
+ },
468
+ }, store, 'replay', evalCassetteCanonicalizationV2(projectRoot));
469
+ }
470
+ function asAgentProviderName(value) {
471
+ return value === 'claude' || value === 'openai' || value === 'gemini' || value === 'ollama'
472
+ ? value
473
+ : undefined;
363
474
  }
364
475
  /**
365
476
  * A raw text provider for planning calls that are not the answer itself.
@@ -373,7 +484,7 @@ export function createGovernedTextProvider(id, projectRoot) {
373
484
  if (!spec)
374
485
  return undefined;
375
486
  try {
376
- return applyEvalCassette(spec.create(projectRoot));
487
+ return applyEvalCassette(spec.create(projectRoot), projectRoot);
377
488
  }
378
489
  catch {
379
490
  return undefined;
@@ -383,7 +494,7 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
383
494
  return {
384
495
  async run(req, emit, signal) {
385
496
  const spec = SPECS[id];
386
- const rawProvider = applyEvalCassette(providerOverride ?? spec.create(req.projectRoot));
497
+ const rawProvider = applyEvalCassette(providerOverride ?? spec.create(req.projectRoot), req.projectRoot);
387
498
  const isResearch = req.orchestrationMode === 'research';
388
499
  const maxProviderDispatches = isResearch ? 8 : 4;
389
500
  const researchRowsOptIn = isResearch && req.researchResultRowsOptIn === true;
@@ -483,15 +594,41 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
483
594
  return envelope;
484
595
  },
485
596
  });
597
+ // One retry only, on the SAME configured provider and only for failures
598
+ // that are normally transient. The physical-dispatch observer remains in
599
+ // the path for both attempts, so a retry cannot exceed the run-wide
600
+ // dispatch budget or silently fail over to another provider.
601
+ let transientRetryUsed = false;
602
+ const mayRetrySameProvider = (error) => {
603
+ if (signal.aborted || transientRetryUsed)
604
+ return false;
605
+ const code = error && typeof error === 'object' ? String(error.code ?? '') : '';
606
+ const message = error instanceof Error ? error.message : String(error ?? '');
607
+ const diagnostic = `${code} ${message}`;
608
+ return !/PROVIDER_DISPATCH_BUDGET|RUN_SOFT_TARGET|RUN_DEADLINE|PROJECT_SNAPSHOT|abort|cancel/i.test(diagnostic)
609
+ && /(?:429|rate limit|too many requests|502|503|504|gateway|econn|network|fetch failed|connection refused|timeout|timed out)/i.test(diagnostic);
610
+ };
611
+ const retrySameProviderOnce = async (operation) => {
612
+ try {
613
+ return await operation();
614
+ }
615
+ catch (error) {
616
+ if (!mayRetrySameProvider(error))
617
+ throw error;
618
+ transientRetryUsed = true;
619
+ emit({ kind: 'thinking', text: 'The configured AI provider had a transient error; retrying it once within this run budget.' });
620
+ return operation();
621
+ }
622
+ };
486
623
  const provider = {
487
624
  name: rawProvider.name,
488
625
  available: () => rawProvider.available(),
489
626
  generate: (...args) => {
490
- return rawProvider.generate(args[0], withPhysicalDispatchObserver(args[1]));
627
+ return retrySameProviderOnce(() => rawProvider.generate(args[0], withPhysicalDispatchObserver(args[1])));
491
628
  },
492
629
  ...(rawProvider.generateWithTools ? {
493
630
  generateWithTools: (...args) => {
494
- return rawProvider.generateWithTools(args[0], args[1], withPhysicalDispatchObserver(args[2]));
631
+ return retrySameProviderOnce(() => rawProvider.generateWithTools(args[0], args[1], withPhysicalDispatchObserver(args[2])));
495
632
  },
496
633
  } : {}),
497
634
  ...(rawProvider.generateStream ? {
@@ -502,7 +639,20 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
502
639
  };
503
640
  const available = await provider.available().catch(() => false);
504
641
  if (!available) {
505
- emit({ kind: 'error', message: `${spec.label} is not configured or reachable. ${spec.setup}` });
642
+ const message = `${spec.label} is not configured or reachable. ${spec.setup}`;
643
+ emit({
644
+ kind: 'error',
645
+ message,
646
+ providerDiagnostic: providerBoundaryDiagnostic({
647
+ providerId: id,
648
+ projectRoot: req.projectRoot,
649
+ phase: 'preflight',
650
+ error: message,
651
+ // Local Ollama readiness is a reachability concern; configured
652
+ // remote providers missing credentials are authentication issues.
653
+ code: id === 'ollama' ? 'NETWORK_FAILURE' : 'AUTHENTICATION_FAILED',
654
+ }),
655
+ });
506
656
  return;
507
657
  }
508
658
  try {
@@ -633,6 +783,63 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
633
783
  const schemaContext = req.getSchemaContext && shouldLoadSchemaContext(contextPack, Boolean(semanticLayer))
634
784
  ? await req.getSchemaContext(question, contextPack).catch(() => [])
635
785
  : [];
786
+ // The router's exploratory candidate IDs are server-owned execution
787
+ // authority. Once that tier is selected, provider prompt/schema
788
+ // context must be the candidate closure rather than the broad
789
+ // retrieval pack. The latter remains available to the host for
790
+ // receipts only and cannot be used to introduce another relation.
791
+ const forcedExploratoryTier = req.selectedCascadeTier === 'exploratory_sql';
792
+ // Re-derive the closure from the broad immutable pack and the
793
+ // router-selected IDs. `preparedExploratoryContextPack` is only a
794
+ // server-side consistency witness; it cannot override or widen the
795
+ // derivation even if a future caller constructs an AgentRunner
796
+ // request directly.
797
+ const derivedExploratoryContextPack = forcedExploratoryTier
798
+ ? scopeContextPackToExploratoryCandidateClosure(contextPack, req.exploratoryCandidateIds)
799
+ : undefined;
800
+ const closureWitnessMatches = !req.preparedExploratoryContextPack
801
+ || exploratoryClosureMatches(derivedExploratoryContextPack, req.preparedExploratoryContextPack);
802
+ const exploratoryContextPack = closureWitnessMatches
803
+ ? derivedExploratoryContextPack
804
+ : undefined;
805
+ if (forcedExploratoryTier && (!exploratoryContextPack || !req.exploratoryCandidateIds?.length)) {
806
+ const text = 'The router-selected exploratory path no longer has a complete same-snapshot physical closure, so DQL did not send SQL generation or execute a query.';
807
+ emit({
808
+ kind: 'tool_result',
809
+ id: 'governed_answer',
810
+ output: {
811
+ kind: 'no_answer',
812
+ sourceTier: 'no_answer',
813
+ certification: 'analyst_review_required',
814
+ reviewStatus: 'none',
815
+ confidence: 0,
816
+ text,
817
+ answer: text,
818
+ refusalCode: 'grounding_gap',
819
+ refusalDetails: {
820
+ code: closureWitnessMatches ? 'EXPLORATORY_CLOSURE_UNAVAILABLE' : 'EXPLORATORY_CLOSURE_MISMATCH',
821
+ message: text,
822
+ },
823
+ contextPack,
824
+ providerUsed: provider.name,
825
+ },
826
+ });
827
+ return;
828
+ }
829
+ const answerContextPack = forcedExploratoryTier
830
+ ? exploratoryContextPack ?? contextPack
831
+ : contextPack;
832
+ const normalizeQualifiedRelation = (value) => value
833
+ .trim()
834
+ .split('.')
835
+ .map((part) => part.trim().replace(/^["`\[]|["`\]]$/g, '').toLowerCase())
836
+ .filter(Boolean)
837
+ .join('.');
838
+ const closureRelations = new Set((exploratoryContextPack?.allowedSqlContext.relations ?? [])
839
+ .map((relation) => normalizeQualifiedRelation(relation.relation)));
840
+ const answerSchemaContext = forcedExploratoryTier && closureRelations.size > 0
841
+ ? schemaContext.filter((table) => closureRelations.has(normalizeQualifiedRelation(table.relation)))
842
+ : schemaContext;
636
843
  const schemaDurationMs = Date.now() - schemaStartedAt;
637
844
  const selectedBlockHints = shouldUseSelectedBlockHint(req, question, followUp)
638
845
  ? extractSelectedBlockHints(req)
@@ -655,8 +862,8 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
655
862
  question,
656
863
  ...(conversationSnapshot ? { conversationSnapshot } : {}),
657
864
  ...(memoryContext ? { memoryContext } : {}),
658
- schemaContext: prepareServerOwnedProviderSchemaContext(schemaContext),
659
- ...(contextPack ? { contextPack } : {}),
865
+ schemaContext: prepareServerOwnedProviderSchemaContext(answerSchemaContext),
866
+ ...(answerContextPack ? { contextPack: answerContextPack } : {}),
660
867
  skills,
661
868
  ...(followUp ? { followUp } : {}),
662
869
  }), {
@@ -672,6 +879,12 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
672
879
  ...(req.resolvedAnalyticalPlan
673
880
  ? { resolvedAnalyticalPlan: req.resolvedAnalyticalPlan }
674
881
  : {}),
882
+ ...(req.selectedCascadeTier
883
+ ? { selectedCascadeTier: req.selectedCascadeTier }
884
+ : {}),
885
+ ...(req.exploratoryCandidateIds?.length
886
+ ? { exploratoryCandidateIds: [...req.exploratoryCandidateIds] }
887
+ : {}),
675
888
  ...(req.generatedProposalTargetFingerprint
676
889
  ? { generatedProposalTargetFingerprint: req.generatedProposalTargetFingerprint }
677
890
  : {}),
@@ -692,7 +905,7 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
692
905
  followUp,
693
906
  conversationSnapshot,
694
907
  memoryContext,
695
- schemaContext,
908
+ schemaContext: answerSchemaContext,
696
909
  semanticLayer,
697
910
  // Runtime-aware executability for metric SELECTION: with a full
698
911
  // semantic runtime active (dbt Cloud / MetricFlow CLI) every
@@ -704,7 +917,7 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
704
917
  canExecuteSemanticMetric: (metricName) => semanticRuntimeActive !== 'native' || semanticLayer.canComposeMetric(metricName),
705
918
  }
706
919
  : {}),
707
- contextPack,
920
+ contextPack: answerContextPack,
708
921
  // The project's configured embedder (dql.config.json ai.embeddings).
709
922
  // Without it, matchSemanticMetric falls back to the offline hashed
710
923
  // provider, whose vectors can never ground a match on similarity
@@ -726,6 +939,28 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
726
939
  executeGeneratedSql: req.executeGeneratedSql
727
940
  ? async (...args) => { guardSnapshot(); sqlExecutions += 1; return req.executeGeneratedSql(...args); }
728
941
  : undefined,
942
+ prepareExploratorySqlExecution: req.prepareExploratorySqlExecution
943
+ ? async (sql, ...args) => {
944
+ guardSnapshot();
945
+ // The execution host repeats this validation immediately
946
+ // before capability minting. Keep the same exact-qualified
947
+ // closure check here as well, before the provider runner can
948
+ // even invoke that host boundary. A provider response cannot
949
+ // use another same-snapshot relation merely because it was
950
+ // present in broad retrieval diagnostics.
951
+ if (forcedExploratoryTier && exploratoryContextPack) {
952
+ const proposalValidation = validateSqlAgainstLocalContext(sql, exploratoryContextPack, {
953
+ runtimeSchema: answerSchemaContext,
954
+ });
955
+ const outsideClosure = !proposalValidation.ok
956
+ || proposalValidation.referencedRelations.some((relation) => !closureRelations.has(normalizeQualifiedRelation(relation)));
957
+ if (outsideClosure) {
958
+ throw Object.assign(new Error('The generated SQL references a relation outside the router-selected physical closure, so it was not executed.'), { code: 'UNAUTHORIZED_SQL' });
959
+ }
960
+ }
961
+ return req.prepareExploratorySqlExecution(sql, ...args);
962
+ }
963
+ : undefined,
729
964
  executeAgenticGeneratedSql: req.executeAgenticGeneratedSql
730
965
  ? async (capability, sql, artifact) => {
731
966
  guardSnapshot();
@@ -985,6 +1220,19 @@ export function createDqlAgentProviderRunner(id, providerOverride) {
985
1220
  dispatchEvidence: dispatchEvidence(orchestrationBudgetExhausted ? 'orchestration_budget_exhausted'
986
1221
  : snapshotDrift ? 'project_snapshot_mismatch'
987
1222
  : 'provider_error'),
1223
+ providerDiagnostic: providerBoundaryDiagnostic({
1224
+ providerId: id,
1225
+ projectRoot: req.projectRoot,
1226
+ phase: orchestrationBudgetExhausted
1227
+ ? 'planning'
1228
+ : 'generation',
1229
+ error: err,
1230
+ code: orchestrationBudgetExhausted
1231
+ ? 'PROVIDER_DISPATCH_BUDGET'
1232
+ : snapshotDrift
1233
+ ? 'ADMISSION_DENIED'
1234
+ : budgetCode,
1235
+ }),
988
1236
  });
989
1237
  }
990
1238
  },
@@ -2088,6 +2336,8 @@ export const __test__ = {
2088
2336
  shouldSearchProjectFiles,
2089
2337
  renderProjectSourceSearch,
2090
2338
  researchDispatchPurposeForTool,
2339
+ readAgentConfig,
2340
+ providerBoundaryDiagnostic,
2091
2341
  };
2092
2342
  function researchDispatchPurposeForTool(toolName) {
2093
2343
  return toolName === 'execute_local_analysis' ? 'research_tool' : 'research_narration';