@duckcodeailabs/dql-cli 1.14.0 → 1.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/args.d.ts +15 -0
- package/dist/args.d.ts.map +1 -1
- package/dist/args.js +25 -0
- package/dist/args.js.map +1 -1
- package/dist/assets/dql-notebook/assets/{AgentLogPage-Ch7VK20X.js → AgentLogPage-BPz-UWFh.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AiBuildDialog-DBr5TmyM.js → AiBuildDialog-BEl53WA_.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AiBuildResult-jLPzQO7O.js → AiBuildResult-B4yfGTTZ.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AiSidePanel-CdlGsiVC.js → AiSidePanel-CSZAAvuD.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AnalyticsHome-C0DXbOwY.js → AnalyticsHome-D5P6Ujwi.js} +1 -1
- package/dist/assets/dql-notebook/assets/{AppsView-CcpwjApv.js → AppsView-DQOwU9Cg.js} +4 -4
- package/dist/assets/dql-notebook/assets/{BlockStudio-CFYxafw-.js → BlockStudio-B4ap0GdY.js} +1 -1
- package/dist/assets/dql-notebook/assets/{BusinessArtifactView-C0kLYg2p.js → BusinessArtifactView-BIfNI-0S.js} +1 -1
- package/dist/assets/dql-notebook/assets/{DbtFirstModelingPage-CMwElXD_.js → DbtFirstModelingPage-D72byb2g.js} +1 -1
- package/dist/assets/dql-notebook/assets/{GitPage-JjhRDeWY.js → GitPage-lQb1uXH2.js} +1 -1
- package/dist/assets/dql-notebook/assets/{GlobalAiRail-DakE4NdR.js → GlobalAiRail-CVECf6Xj.js} +1 -1
- package/dist/assets/dql-notebook/assets/{GovernedContextPage-BokDqG6a.js → GovernedContextPage-trOyMCY6.js} +1 -1
- package/dist/assets/dql-notebook/assets/{HelpDocsPage-CjOv6_gz.js → HelpDocsPage-D8hLS5lE.js} +1 -1
- package/dist/assets/dql-notebook/assets/{HomePage-nGaNcwdw.js → HomePage-eIkfBIep.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineageDAG-CO6CFJRg.js → LineageDAG-CSqcbDrE.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineageDetailView-BnGb7OF7.js → LineageDetailView-DJZZjZu-.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineageDrawer-C5Y0Ht0b.js → LineageDrawer-BcIipIc3.js} +1 -1
- package/dist/assets/dql-notebook/assets/{LineagePathBreadcrumb-Cgh3GR3F.js → LineagePathBreadcrumb-CviIf8PN.js} +1 -1
- package/dist/assets/dql-notebook/assets/{MiniLineageGraph-Kla9PYuj.js → MiniLineageGraph-vH_MY_Ju.js} +1 -1
- package/dist/assets/dql-notebook/assets/{NewBlockModal-BR2SnmPT.js → NewBlockModal-DbCQg-pj.js} +1 -1
- package/dist/assets/dql-notebook/assets/{NewNotebookModal-BaCHMYeB.js → NewNotebookModal-5Vp6XiuK.js} +1 -1
- package/dist/assets/dql-notebook/assets/{NotebookEditor-CBLqY8cE.js → NotebookEditor-DjqJS44s.js} +1 -1
- package/dist/assets/dql-notebook/assets/{ReadinessPage-CiN0IWSS.js → ReadinessPage-BHGrC5ho.js} +1 -1
- package/dist/assets/dql-notebook/assets/{SetupOnboarding-BhiCsYF-.js → SetupOnboarding-BfS9Tdx-.js} +1 -1
- package/dist/assets/dql-notebook/assets/{SkillsPage-CCSf8VMm.js → SkillsPage-BFH21wSj.js} +1 -1
- package/dist/assets/dql-notebook/assets/{TrustBadge-BgQmFe_x.js → TrustBadge-zm6g_SxZ.js} +1 -1
- package/dist/assets/dql-notebook/assets/{UnifiedAgentRunPanel-Bw5AXNDB.js → UnifiedAgentRunPanel--oxmjlgr.js} +21 -21
- package/dist/assets/dql-notebook/assets/{answer-to-notebook-AeDYUDla.js → answer-to-notebook-DPhxIEzF.js} +1 -1
- package/dist/assets/dql-notebook/assets/{arrow-left-C55x2hq_.js → arrow-left-DNEb86Xc.js} +1 -1
- package/dist/assets/dql-notebook/assets/{arrow-right-C1cJrhOm.js → arrow-right-DpPWbwaD.js} +1 -1
- package/dist/assets/dql-notebook/assets/{book-open-text-CQf_sdv2.js → book-open-text-D7s5jo4X.js} +1 -1
- package/dist/assets/dql-notebook/assets/{circle-x-X8-Z2yLY.js → circle-x-Db3dXKg7.js} +1 -1
- package/dist/assets/dql-notebook/assets/{dagre.esm-BjjNYKyY.js → dagre.esm-C7pppQ1a.js} +1 -1
- package/dist/assets/dql-notebook/assets/{external-link-C2zrz5DH.js → external-link-BxwXitO_.js} +1 -1
- package/dist/assets/dql-notebook/assets/{grip-vertical-qGV_PYGU.js → grip-vertical-Dt4lkWRi.js} +1 -1
- package/dist/assets/dql-notebook/assets/{index-ByTDPDaH.js → index-DKo-bwNw.js} +2 -2
- package/dist/assets/dql-notebook/assets/{link-2-VpyOxQXG.js → link-2-Dfo2P6wi.js} +1 -1
- package/dist/assets/dql-notebook/assets/{list-tree-CH2Jhwms.js → list-tree-DiTmIWAL.js} +1 -1
- package/dist/assets/dql-notebook/assets/{minimize-2-B2TZJ8BT.js → minimize-2-CMTAkPzL.js} +1 -1
- package/dist/assets/dql-notebook/assets/{panel-right-open-BunF88lt.js → panel-right-open-DwYr7FW4.js} +1 -1
- package/dist/assets/dql-notebook/assets/{play-DAFVF4_G.js → play-BXhHYQ4x.js} +1 -1
- package/dist/assets/dql-notebook/assets/{rotate-ccw-DGCrrqtY.js → rotate-ccw-BNi6F8pl.js} +1 -1
- package/dist/assets/dql-notebook/assets/{semantic-fields-Ci-9QL9F.js → semantic-fields-CNOGysAy.js} +1 -1
- package/dist/assets/dql-notebook/assets/{sliders-horizontal-BOAlXXbn.js → sliders-horizontal-Ec5MUMUW.js} +1 -1
- package/dist/assets/dql-notebook/assets/{star-CkksZXHt.js → star-B9leDkp_.js} +1 -1
- package/dist/assets/dql-notebook/assets/{triangle-alert-D3mjyJZE.js → triangle-alert-BefTYCzx.js} +1 -1
- package/dist/assets/dql-notebook/assets/{upload-CTNOAVEO.js → upload-SPiOM2tQ.js} +1 -1
- package/dist/assets/dql-notebook/assets/{usePersistedAgentThreadId-DiQjc7x-.js → usePersistedAgentThreadId-C4foXeiQ.js} +1 -1
- package/dist/assets/dql-notebook/assets/{user-round-BWd5tQRg.js → user-round-comGmyw-.js} +1 -1
- package/dist/assets/dql-notebook/assets/{wand-sparkles-CqsAv8P-.js → wand-sparkles-BffR4dF8.js} +1 -1
- package/dist/assets/dql-notebook/assets/{workflow-CSqsj-sC.js → workflow-ChmPTEzH.js} +1 -1
- package/dist/assets/dql-notebook/assets/{wrench-DWqzqlX8.js → wrench-DovaG_ze.js} +1 -1
- package/dist/assets/dql-notebook/assets/{x-65M5rLCE.js → x-nRx91AgW.js} +1 -1
- package/dist/assets/dql-notebook/index.html +1 -1
- package/dist/commands/agent-eval-cassette.d.ts +73 -0
- package/dist/commands/agent-eval-cassette.d.ts.map +1 -0
- package/dist/commands/agent-eval-cassette.js +170 -0
- package/dist/commands/agent-eval-cassette.js.map +1 -0
- package/dist/commands/agent-eval-runtime.d.ts +97 -0
- package/dist/commands/agent-eval-runtime.d.ts.map +1 -0
- package/dist/commands/agent-eval-runtime.js +155 -0
- package/dist/commands/agent-eval-runtime.js.map +1 -0
- package/dist/commands/agent.d.ts +115 -1
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +289 -62
- package/dist/commands/agent.js.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/llm/analyst-loop-tools.d.ts +19 -0
- package/dist/llm/analyst-loop-tools.d.ts.map +1 -0
- package/dist/llm/analyst-loop-tools.js +56 -0
- package/dist/llm/analyst-loop-tools.js.map +1 -0
- package/dist/llm/providers/dql-agent-provider.d.ts +24 -1
- package/dist/llm/providers/dql-agent-provider.d.ts.map +1 -1
- package/dist/llm/providers/dql-agent-provider.js +306 -7
- package/dist/llm/providers/dql-agent-provider.js.map +1 -1
- package/dist/llm/types.d.ts +19 -1
- package/dist/llm/types.d.ts.map +1 -1
- package/dist/local-runtime.d.ts +108 -1
- package/dist/local-runtime.d.ts.map +1 -1
- package/dist/local-runtime.js +626 -110
- package/dist/local-runtime.js.map +1 -1
- package/dist/package.json +10 -10
- package/package.json +10 -10
package/dist/local-runtime.js
CHANGED
|
@@ -23,9 +23,10 @@ import { getRunner as getLLMRunner } from './llm/index.js';
|
|
|
23
23
|
import { rethrowIfCancelled } from './llm/cancellation.js';
|
|
24
24
|
import { fetchLatestPublishedDqlVersion, resolveDqlRuntimeVersionStatus } from './version-status.js';
|
|
25
25
|
import { resolveRetrievalHealthStatus } from './retrieval-health.js';
|
|
26
|
-
import {
|
|
26
|
+
import { applyFinding, createResearchState, narrationMaxTokensForFacts, nextHypothesis, rerankCandidates, synthesizeResearchNarrative, AgenticExecutionCapabilityGate, mintFinalSqlAuthorization, verifyAgenticSqlExecutionCapability, qualifyAuthorizationReferences, validateSqlAgainstLocalContext as validateAuthorizedSqlReferences, verifyFinalSql, } from '@duckcodeailabs/dql-agent';
|
|
27
|
+
import { applyEvalCassette, createDqlAgentProviderRunner, createGovernedTextProvider, resolveAgentFollowUpContext } from './llm/providers/dql-agent-provider.js';
|
|
27
28
|
import { listRemoteMcpSettings, saveRemoteMcpSettings } from './llm/mcp-config.js';
|
|
28
|
-
import { ClaudeProvider, ConversationStore, advanceThreadState, buildConversationSnapshot, conversationHistoryFromContext, recallRelevantTurns, renderConversationEnvelopeForPrompt, GeminiProvider, MemoryStore, OllamaProvider, OpenAIProvider, buildBlockBusinessFingerprint, buildBlockSqlFingerprints, buildAnalysisQuestionPlan, composeSemanticQueryForQuestion, aggregationIntegrityIssuesForSql, buildAggregationSafetyProof, buildLocalContextPack, applyContextPackCompatibility, toAgentRetrievalEvidence, prepareConversationPath, defaultMemoryPath, ensureDefaultMemoryFiles, ensureAgentProjectReady, isAgentProjectIndexReady, currentMetadataFingerprint, ensureMetadataCatalogFresh, readIndexedDomainKnowledge, readIndexedKnowledge360, compactSemanticRuntimeFailure, classifyAnalyticalFailure, normalizeWarehouseSqlFailure, parseProposal, propose, proposePlan, recordGovernedCorrection, HintStore, defaultHintIndexPath, ensureHintIndexFresh, listHintsFromGit, getHintEvaluationFromGit, getCorrectionTraceFromGit, inspectGovernedHint, editGovernedHintCandidate, reopenGovernedHint, retireHint, supersedeHint, hintsConflict, mineJoinPatterns, reviewGovernedHint, AgentRunEngine, SqliteAgentRunStore, defaultAgentRunGates, createLlmAgentRunPlanner, createHybridRouter, computeResultStats, buildDeterministicDashboardStory, synthesizeAnswer, streamOrGenerate, narrateResult, buildProposePreview, buildFromPrompt, internalRelationIdsInSql, defaultAgentRunStorePath, defaultAgentRunSqlitePath, resolveLocalOwner, resolveProposeConfig, recordQueryRun, recordRuntimeSchemaSnapshot, latestRuntimeSchemaSnapshotForProject, loadSkills, migrateLegacySkills, configuredSkillsPath, skillsDir, draftDomainSkillBootstrap, buildDomainSkillBootstrapPrompt, mergeDomainSkillBootstrapEnrichment, writeSkill, previewSkillChange, buildContextAuthoringProposal, contextAuthoringDependencyClosure, FileContextAuthoringProposalStore, deleteSkill, deriveGeneratedDraftSlug, deriveAnalyticalRepair, reindexProject, invalidateAgentProjectState, recordAgentRuntimeVersion, resolveDomainContextEnvelope, projectEmbeddingProvider, isHashedEmbeddingProvider, clearProjectEmbeddingCache, upgradeVectorIndexForProject, openMetadataCatalog, defaultKgPath, planAppFromPrompt, KGStore, planResearch, loadSemanticMetrics, cascadeTraceToEvidenceRouteSteps, createCascadeAnswerResult, createCascadeTrace, routeReasoningEffort, createAgentRunBudget, routeForCascadeAnswerTier, clampReasoningEffort, bumpReasoningEffort, resolveThinkingMode, coerceThinkingMode, upsertGeneratedDqlArtifactDraft, loadAgentSemanticLayer, isTrustedConversationTurn, resolveInternalRelationIds, analyticalError, tagAnalyticalError, withAnalyticalErrorOrigin, withAnalyticalErrorOriginSync, assertProviderPayloadAllowed, createProviderDispatchEgressReceipt, prepareProviderWireEnvelopeForDispatch, markProviderMetadataArray, createProviderEgressReceipt, redactProviderResultRows, composeVerifiedAnalyticalNarrative, buildCoverageGap, capResearchBranches, buildResearchEvidenceLedger, buildAnalyticalTurnPlan, DEFAULT_ASK_ROW_EGRESS_POLICY, ZERO_ROW_EGRESS_POLICY, resolveProviderResultRowEgressPolicy, normalizeCanonicalQueryResult, normalizeAnalyticalExecutionFingerprint, normalizeAnalyticalExecutionReceipt, createAgentRunCancellationError, } from '@duckcodeailabs/dql-agent';
|
|
29
|
+
import { composeBusinessExplanation, ClaudeProvider, ConversationStore, advanceThreadState, buildConversationSnapshot, conversationHistoryFromContext, recallRelevantTurns, renderConversationEnvelopeForPrompt, GeminiProvider, MemoryStore, OllamaProvider, OpenAIProvider, buildBlockBusinessFingerprint, buildBlockSqlFingerprints, buildAnalysisQuestionPlan, composeSemanticQueryForQuestion, aggregationIntegrityIssuesForSql, buildAggregationSafetyProof, buildLocalContextPack, applyContextPackCompatibility, toAgentRetrievalEvidence, prepareConversationPath, defaultMemoryPath, ensureDefaultMemoryFiles, ensureAgentProjectReady, isAgentProjectIndexReady, currentMetadataFingerprint, ensureMetadataCatalogFresh, readIndexedDomainKnowledge, readIndexedKnowledge360, compactSemanticRuntimeFailure, classifyAnalyticalFailure, normalizeWarehouseSqlFailure, parseProposal, propose, proposePlan, recordGovernedCorrection, HintStore, defaultHintIndexPath, ensureHintIndexFresh, listHintsFromGit, getHintEvaluationFromGit, getCorrectionTraceFromGit, inspectGovernedHint, editGovernedHintCandidate, reopenGovernedHint, retireHint, supersedeHint, hintsConflict, mineJoinPatterns, reviewGovernedHint, AgentRunEngine, SqliteAgentRunStore, defaultAgentRunGates, createLlmAgentRunPlanner, createHybridRouter, computeResultStats, buildDeterministicDashboardStory, synthesizeAnswer, streamOrGenerate, narrateResult, buildProposePreview, buildFromPrompt, internalRelationIdsInSql, defaultAgentRunStorePath, defaultAgentRunSqlitePath, resolveLocalOwner, resolveProposeConfig, recordQueryRun, recordRuntimeSchemaSnapshot, latestRuntimeSchemaSnapshotForProject, loadSkills, migrateLegacySkills, configuredSkillsPath, skillsDir, draftDomainSkillBootstrap, buildDomainSkillBootstrapPrompt, mergeDomainSkillBootstrapEnrichment, writeSkill, previewSkillChange, buildContextAuthoringProposal, contextAuthoringDependencyClosure, FileContextAuthoringProposalStore, deleteSkill, deriveGeneratedDraftSlug, deriveAnalyticalRepair, reindexProject, invalidateAgentProjectState, recordAgentRuntimeVersion, resolveDomainContextEnvelope, projectEmbeddingProvider, isHashedEmbeddingProvider, clearProjectEmbeddingCache, upgradeVectorIndexForProject, openMetadataCatalog, defaultKgPath, planAppFromPrompt, KGStore, planResearch, loadSemanticMetrics, cascadeTraceToEvidenceRouteSteps, createCascadeAnswerResult, createCascadeTrace, routeReasoningEffort, createAgentRunBudget, isProbeSafeColumn, deadlineScale, routeForCascadeAnswerTier, clampReasoningEffort, bumpReasoningEffort, resolveThinkingMode, coerceThinkingMode, upsertGeneratedDqlArtifactDraft, loadAgentSemanticLayer, isTrustedConversationTurn, resolveInternalRelationIds, analyticalError, tagAnalyticalError, withAnalyticalErrorOrigin, withAnalyticalErrorOriginSync, assertProviderPayloadAllowed, createProviderDispatchEgressReceipt, prepareProviderWireEnvelopeForDispatch, markProviderMetadataArray, createProviderEgressReceipt, redactProviderResultRows, composeVerifiedAnalyticalNarrative, buildCoverageGap, capResearchBranches, buildResearchEvidenceLedger, buildAnalyticalTurnPlan, resolveTopRankedRegionDependency, DEFAULT_ASK_ROW_EGRESS_POLICY, ZERO_ROW_EGRESS_POLICY, resolveProviderResultRowEgressPolicy, normalizeCanonicalQueryResult, normalizeAnalyticalExecutionFingerprint, normalizeAnalyticalExecutionReceipt, createAgentRunCancellationError, } from '@duckcodeailabs/dql-agent';
|
|
29
30
|
import { addSqlResultFilter, dashboardFilterableResultColumns, filterableResultColumns, replaceBlockStudioSql } from './sql-result-filter.js';
|
|
30
31
|
import { gatherProposeEnrichment } from './propose-enrich.js';
|
|
31
32
|
import { handleAppsApi, proposeAppAiBuild, recommendVisualization, } from './apps-api.js';
|
|
@@ -328,6 +329,10 @@ const CLIENT_PLAN_AUTHORITY_KEYS = new Set([
|
|
|
328
329
|
'priorResolvedAnalyticalPlan',
|
|
329
330
|
'resolvedAnalyticalPlan',
|
|
330
331
|
'analyticalFrame',
|
|
332
|
+
// Only the local compound executor may inject this after it has derived a
|
|
333
|
+
// canonical parent result binding. A browser-provided lookalike cannot become
|
|
334
|
+
// a child filter or skip ordinary member validation.
|
|
335
|
+
'analyticalTaskDependencyBinding',
|
|
331
336
|
]);
|
|
332
337
|
/**
|
|
333
338
|
* Browser/embedding context is useful retrieval and history input, but it is
|
|
@@ -378,6 +383,52 @@ export function agentRunDeadlineMs(request, env = process.env, activeProviderId)
|
|
|
378
383
|
? AGENT_RESEARCH_DEADLINE_MS
|
|
379
384
|
: AGENT_LOOKUP_DEADLINE_MS;
|
|
380
385
|
}
|
|
386
|
+
/**
|
|
387
|
+
* Run ready independent compound clauses concurrently, but wait for a typed
|
|
388
|
+
* parent result before executing a declared dependent clause. The scheduler
|
|
389
|
+
* itself has no authority to query or filter; callers supply both execution and
|
|
390
|
+
* a dependency resolver so immutable-plan and SQL guards remain unchanged.
|
|
391
|
+
*/
|
|
392
|
+
export async function scheduleCompoundAnalyticalTasks(input) {
|
|
393
|
+
const pending = [...input.tasks];
|
|
394
|
+
const settled = new Map();
|
|
395
|
+
while (pending.length > 0) {
|
|
396
|
+
const ready = pending.filter((task) => task.dependencies.every((dependencyId) => settled.has(dependencyId)));
|
|
397
|
+
if (ready.length === 0) {
|
|
398
|
+
for (const task of pending.splice(0)) {
|
|
399
|
+
settled.set(task.id, {
|
|
400
|
+
task,
|
|
401
|
+
error: 'The compound task dependency graph could not be resolved.',
|
|
402
|
+
dependencyError: {
|
|
403
|
+
ok: false,
|
|
404
|
+
code: 'RESULT_CONTRACT_MISMATCH',
|
|
405
|
+
message: 'The dependent task could not run because its parent dependency was unresolved.',
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
for (const task of ready)
|
|
412
|
+
pending.splice(pending.indexOf(task), 1);
|
|
413
|
+
const batch = await Promise.all(ready.map(async (task) => {
|
|
414
|
+
if (!task.dependency || task.dependency.kind !== 'top_ranked_region')
|
|
415
|
+
return input.runTask(task);
|
|
416
|
+
const parent = settled.get(task.dependency.sourceTaskId);
|
|
417
|
+
const resolution = input.resolveDependency(task, parent);
|
|
418
|
+
if (!resolution.ok) {
|
|
419
|
+
const dependencyError = resolution;
|
|
420
|
+
return { task, error: dependencyError.message, dependencyError };
|
|
421
|
+
}
|
|
422
|
+
return input.runTask(task, resolution.binding);
|
|
423
|
+
}));
|
|
424
|
+
for (const result of batch)
|
|
425
|
+
settled.set(result.task.id, result);
|
|
426
|
+
}
|
|
427
|
+
return input.tasks.map((task) => settled.get(task.id) ?? {
|
|
428
|
+
task,
|
|
429
|
+
error: 'The compound task did not produce an outcome.',
|
|
430
|
+
});
|
|
431
|
+
}
|
|
381
432
|
/**
|
|
382
433
|
* Decide how a settled answer gets its business-facing prose.
|
|
383
434
|
*
|
|
@@ -420,11 +471,64 @@ export function shouldSynthesizeAgentRunAnswer(governedAnswer, requestedMode = '
|
|
|
420
471
|
rowEgress: DEFAULT_ASK_ROW_EGRESS_POLICY,
|
|
421
472
|
}).mode !== 'skip';
|
|
422
473
|
}
|
|
474
|
+
/**
|
|
475
|
+
* A receipt may be rendered in the local inspector and exported into evaluation
|
|
476
|
+
* output. Keep only stable validation codes there; provider error messages can
|
|
477
|
+
* contain a prompt excerpt, result value, or connector detail and must not
|
|
478
|
+
* become user-visible durable data.
|
|
479
|
+
*/
|
|
480
|
+
function narrationIntegrityFailureCodes(failures) {
|
|
481
|
+
return [...new Set(failures.map((failure) => {
|
|
482
|
+
const match = failure.trim().match(/^([A-Z][A-Z0-9_]{1,80})/);
|
|
483
|
+
return match?.[1] ?? 'NARRATION_VALIDATION_FAILED';
|
|
484
|
+
}).filter(Boolean))].slice(0, 8);
|
|
485
|
+
}
|
|
423
486
|
/**
|
|
424
487
|
* AGT-010 — the semantic route label is descriptive, while the exact
|
|
425
488
|
* route-specific aggregation proof is authoritative for governed trust.
|
|
426
489
|
* Missing proof remains blocked for legacy or malformed results.
|
|
427
490
|
*/
|
|
491
|
+
/**
|
|
492
|
+
* Trust for ONE answer, by the same rule the single-answer path uses: a route
|
|
493
|
+
* label is not authority, and a semantic route earns `governed` only when its
|
|
494
|
+
* aggregation proof actually passed.
|
|
495
|
+
*/
|
|
496
|
+
export function trustStateForAgentAnswer(answer) {
|
|
497
|
+
if (answer.certification === 'certified' || answer.kind === 'certified')
|
|
498
|
+
return 'certified';
|
|
499
|
+
return semanticAnswerHasPassedAggregationProof(answer) ? 'governed' : 'review_required';
|
|
500
|
+
}
|
|
501
|
+
const TRUST_RANK = {
|
|
502
|
+
certified: 3,
|
|
503
|
+
governed: 2,
|
|
504
|
+
grounded: 1,
|
|
505
|
+
review_required: 0,
|
|
506
|
+
};
|
|
507
|
+
/**
|
|
508
|
+
* A compound answer is exactly as trustworthy as its WEAKEST successful child.
|
|
509
|
+
*
|
|
510
|
+
* The previous rule was `every child completed ? 'governed' : 'review_required'`,
|
|
511
|
+
* which stamped `governed` on a parent whose children were review-required
|
|
512
|
+
* generated SQL — completion is not proof. That is a governance violation and
|
|
513
|
+
* the worst possible failure for this product: the reader is told a number
|
|
514
|
+
* carries governed authority when nothing proved it.
|
|
515
|
+
*
|
|
516
|
+
* `certified` is deliberately NOT reachable here. Certified trust is granted
|
|
517
|
+
* only by executing the exact certified artifact; a parent that merely
|
|
518
|
+
* assembled certified children did not execute one, so it caps at `governed`.
|
|
519
|
+
*/
|
|
520
|
+
export function compoundTrustState(childTrust) {
|
|
521
|
+
if (childTrust.length === 0)
|
|
522
|
+
return 'review_required';
|
|
523
|
+
const weakest = childTrust.reduce((low, current) => (TRUST_RANK[current] ?? 0) < (TRUST_RANK[low] ?? 0) ? current : low);
|
|
524
|
+
return weakest === 'certified' ? 'governed' : weakest;
|
|
525
|
+
}
|
|
526
|
+
/** Neutral parent outcome: governed only when every child completed governed. */
|
|
527
|
+
export function compoundStopReason(completedCount, childCount, trustState) {
|
|
528
|
+
return completedCount === childCount && childCount > 0 && trustState === 'governed'
|
|
529
|
+
? 'governed_compound_answer'
|
|
530
|
+
: 'human_review_required';
|
|
531
|
+
}
|
|
428
532
|
export function semanticAnswerHasPassedAggregationProof(governedAnswer) {
|
|
429
533
|
return governedAnswer.route?.tier === 'semantic_metric'
|
|
430
534
|
&& governedAnswer.aggregationSafetyProof?.status === 'safe';
|
|
@@ -1039,6 +1143,7 @@ export function conversationTurnInputFromRun(run) {
|
|
|
1039
1143
|
sql: agentRunString(payload?.proposedSql) ?? agentRunString(payload?.sql),
|
|
1040
1144
|
dqlArtifact: agentRunRecord(payload?.dqlArtifact),
|
|
1041
1145
|
cascade: agentRunRecord(payload?.cascade),
|
|
1146
|
+
narrationIntegrityReceipt: run.narrationIntegrityReceipt,
|
|
1042
1147
|
result: columns.length > 0 || rows.length > 0
|
|
1043
1148
|
? {
|
|
1044
1149
|
columns,
|
|
@@ -1166,12 +1271,7 @@ export class RunScopedProviderDispatchEvidence {
|
|
|
1166
1271
|
* from what it already has.
|
|
1167
1272
|
*/
|
|
1168
1273
|
expectedDispatchMs() {
|
|
1169
|
-
|
|
1170
|
-
return ASSUMED_PROVIDER_DISPATCH_MS;
|
|
1171
|
-
const sorted = [...this.observedDispatchDurations].sort((left, right) => left - right);
|
|
1172
|
-
// The slowest observed call is the honest predictor: an optimistic median
|
|
1173
|
-
// still admits a dispatch that the deadline then kills.
|
|
1174
|
-
return sorted[sorted.length - 1];
|
|
1274
|
+
return predictDispatchMs(this.observedDispatchDurations);
|
|
1175
1275
|
}
|
|
1176
1276
|
/** True when the remaining wall clock cannot fit another provider call. */
|
|
1177
1277
|
cannotFitAnotherDispatch() {
|
|
@@ -1364,6 +1464,50 @@ function mergeRunScopedProviderDispatchEvidence(run, evidence) {
|
|
|
1364
1464
|
diagnosticReceiptV2,
|
|
1365
1465
|
};
|
|
1366
1466
|
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Final physical generated-SQL boundary.
|
|
1469
|
+
*
|
|
1470
|
+
* This receives the exact prepared statement immediately before the connector
|
|
1471
|
+
* callback. It intentionally validates before invoking `execute`: a bad
|
|
1472
|
+
* capability or unproven prepared reference must result in zero warehouse
|
|
1473
|
+
* calls, not a post-execution warning. It is module-exported only for the
|
|
1474
|
+
* local-runtime boundary harness; it is never an HTTP API or durable artifact.
|
|
1475
|
+
*
|
|
1476
|
+
* @internal
|
|
1477
|
+
*/
|
|
1478
|
+
export async function executePreparedAgenticSqlBoundary(input) {
|
|
1479
|
+
const capability = input.capability;
|
|
1480
|
+
if (capability) {
|
|
1481
|
+
const authorization = mintFinalSqlAuthorization({
|
|
1482
|
+
sql: input.preparedSql,
|
|
1483
|
+
proven: capability.provenIdentifiers.map((identifier) => ({
|
|
1484
|
+
identifier,
|
|
1485
|
+
evidence: capability.evidence[identifier] ?? 'catalog',
|
|
1486
|
+
})),
|
|
1487
|
+
runId: capability.runId,
|
|
1488
|
+
executionId: capability.executionId,
|
|
1489
|
+
snapshotId: capability.snapshotId,
|
|
1490
|
+
planId: capability.planId,
|
|
1491
|
+
targetFingerprint: capability.targetFingerprint,
|
|
1492
|
+
bindings: input.bindings,
|
|
1493
|
+
});
|
|
1494
|
+
const validation = validateAuthorizedSqlReferences(input.preparedSql, undefined);
|
|
1495
|
+
const verdict = verifyFinalSql(authorization, input.preparedSql, qualifyAuthorizationReferences(input.preparedSql, {
|
|
1496
|
+
relations: validation.referencedRelations ?? [],
|
|
1497
|
+
columns: validation.referencedColumns ?? [],
|
|
1498
|
+
}), {
|
|
1499
|
+
...input.scope,
|
|
1500
|
+
bindings: input.bindings,
|
|
1501
|
+
});
|
|
1502
|
+
if (process.env.DQL_ORCHESTRATOR_TRACE) {
|
|
1503
|
+
console.warn(`[dql] execution authorization: ${verdict.ok ? 'admitted' : 'REFUSED'} proven=${authorization.provenIdentifiers.length}${verdict.ok ? '' : ` reason=${verdict.reason}`}`);
|
|
1504
|
+
}
|
|
1505
|
+
if (!verdict.ok) {
|
|
1506
|
+
throw analyticalError(verdict.reason ?? 'The statement was not authorized for execution.', { origin: 'governance_gate', stage: 'execute', code: 'unauthorized_sql' });
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
return input.execute();
|
|
1510
|
+
}
|
|
1367
1511
|
export async function startLocalServer(opts) {
|
|
1368
1512
|
const { rootDir, executor, connection: rawConnection, preferredPort, projectRoot = process.cwd() } = opts;
|
|
1369
1513
|
const bindHost = opts.host ?? process.env.DQL_HOST ?? '127.0.0.1';
|
|
@@ -2061,6 +2205,9 @@ export async function startLocalServer(opts) {
|
|
|
2061
2205
|
});
|
|
2062
2206
|
};
|
|
2063
2207
|
async function runGovernedAgentAnswerForRun(request, repair, route = 'generated_answer', onProgress, routeDecision) {
|
|
2208
|
+
return runGovernedAgentAnswerForRunInner(request, repair, route, onProgress, routeDecision);
|
|
2209
|
+
}
|
|
2210
|
+
async function runGovernedAgentAnswerForRunInner(request, repair, route = 'generated_answer', onProgress, routeDecision) {
|
|
2064
2211
|
const governed = resolveGovernedAnswerRunner(projectRoot);
|
|
2065
2212
|
let resolvedProvider = governed?.provider ?? null;
|
|
2066
2213
|
let runner = governed?.runner ?? null;
|
|
@@ -2163,6 +2310,9 @@ export async function startLocalServer(opts) {
|
|
|
2163
2310
|
snapshotId: runProjectSnapshot.snapshotId,
|
|
2164
2311
|
})
|
|
2165
2312
|
: undefined;
|
|
2313
|
+
// Local to this exact answer invocation. Compound children each enter this
|
|
2314
|
+
// function separately, so no child can consume another child's capability.
|
|
2315
|
+
const agenticExecutionCapabilityGate = new AgenticExecutionCapabilityGate();
|
|
2166
2316
|
await runner.run({
|
|
2167
2317
|
provider: resolvedProvider,
|
|
2168
2318
|
...(agentRunProviderEvidenceContext.getStore()
|
|
@@ -2187,9 +2337,13 @@ export async function startLocalServer(opts) {
|
|
|
2187
2337
|
},
|
|
2188
2338
|
reasoningEffort,
|
|
2189
2339
|
...(analysisDepth ? { analysisDepth } : {}),
|
|
2340
|
+
orchestrationMode: route === 'research' ? 'research' : 'ask',
|
|
2190
2341
|
allowProviderSemanticMemberSelection: route === 'research',
|
|
2191
2342
|
researchResultRowsOptIn: route === 'research' && request.researchResultRowsOptIn === true,
|
|
2192
2343
|
projectRoot,
|
|
2344
|
+
// Keys the execution authorization, so the proofs the analyst loop
|
|
2345
|
+
// gathers can be checked against the statement this run executes.
|
|
2346
|
+
...(request.runId ? { agentRunId: request.runId } : {}),
|
|
2193
2347
|
preparedContextPack: preparedAgentContextPacks.get(request),
|
|
2194
2348
|
domainContext,
|
|
2195
2349
|
projectSnapshot: { snapshotId: runProjectSnapshot.snapshotId, manifest: runProjectSnapshot.manifest },
|
|
@@ -2357,6 +2511,20 @@ export async function startLocalServer(opts) {
|
|
|
2357
2511
|
analyticalReferenceInstant: new Date().toISOString(),
|
|
2358
2512
|
executeCertifiedBlock: (node, invocation) => executeCertifiedBlockForAgent(node, invocation, semanticConnection, semanticConnectionName),
|
|
2359
2513
|
executeGeneratedSql: (sql, artifact) => executeGeneratedArtifactForAgent(request.question, sql, artifact, semanticConnection, semanticConnectionName),
|
|
2514
|
+
executeAgenticGeneratedSql: async (capability, sql, artifact) => {
|
|
2515
|
+
if (!agenticExecutionCapabilityGate.consume(capability)) {
|
|
2516
|
+
throw analyticalError('This analyst execution capability was already consumed; DQL did not retry it with stale proof.', {
|
|
2517
|
+
origin: 'governance_gate', stage: 'execute', code: 'unauthorized_sql',
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2520
|
+
return executeGeneratedArtifactForAgent(request.question, sql, artifact, semanticConnection, semanticConnectionName, capability, {
|
|
2521
|
+
runId: request.runId,
|
|
2522
|
+
executionId: capability.executionId,
|
|
2523
|
+
snapshotId: runProjectSnapshot.snapshotId,
|
|
2524
|
+
planId: routeDecision?.resolvedAnalyticalPlan?.planId,
|
|
2525
|
+
targetFingerprint: generatedProposalTargetIdentity?.identityFingerprint,
|
|
2526
|
+
});
|
|
2527
|
+
},
|
|
2360
2528
|
executeDqlArtifact: (artifact) => executeArtifactReferenceForAgent(artifact, request.question, semanticConnection, semanticConnectionName),
|
|
2361
2529
|
getSchemaContext: (question, preparedContextPack) => getSchemaContextForAgent(question, preparedContextPack, semanticConnection, request.executionTarget?.target === 'connection'
|
|
2362
2530
|
? request.executionTarget.connectionName
|
|
@@ -2629,6 +2797,7 @@ export async function startLocalServer(opts) {
|
|
|
2629
2797
|
const runStartedAtMs = Date.now();
|
|
2630
2798
|
const turnPlan = buildAnalyticalTurnPlan({
|
|
2631
2799
|
question: request.question,
|
|
2800
|
+
mode: route === 'research' ? 'research' : 'ask',
|
|
2632
2801
|
turnId: request.runId,
|
|
2633
2802
|
candidateIds: routeDecision?.retrievalEvidence?.candidateIds ?? [],
|
|
2634
2803
|
frozen: routeDecision?.resolvedAnalyticalPlan?.mode === 'authoritative',
|
|
@@ -2641,18 +2810,27 @@ export async function startLocalServer(opts) {
|
|
|
2641
2810
|
// is copied into several labels. Independent children share the parent's
|
|
2642
2811
|
// signal/deadline and return truthful partial success.
|
|
2643
2812
|
if (turnPlan.tasks.length > 1 && !childTurn && (attempt ?? 0) === 0) {
|
|
2644
|
-
const
|
|
2813
|
+
const runChildTask = async (task, dependencyBinding) => {
|
|
2645
2814
|
if (request.signal?.aborted)
|
|
2646
2815
|
rethrowIfCancelled(request.signal.reason, request.signal);
|
|
2647
2816
|
try {
|
|
2648
2817
|
const childRequest = {
|
|
2649
2818
|
...request,
|
|
2650
2819
|
question: task.question,
|
|
2820
|
+
...(dependencyBinding ? {
|
|
2821
|
+
conversationContext: {
|
|
2822
|
+
...(request.conversationContext ?? {}),
|
|
2823
|
+
// This is the complete parent-to-child data boundary: no
|
|
2824
|
+
// parent prose, SQL, or rows cross into a dependent clause.
|
|
2825
|
+
analyticalTaskDependencyBinding: dependencyBinding,
|
|
2826
|
+
},
|
|
2827
|
+
} : {}),
|
|
2651
2828
|
workspaceContext: {
|
|
2652
2829
|
...(request.workspaceContext && typeof request.workspaceContext === 'object' ? request.workspaceContext : {}),
|
|
2653
2830
|
analyticalTaskChild: true,
|
|
2654
2831
|
analyticalParentRunId: request.runId,
|
|
2655
2832
|
analyticalTaskId: task.id,
|
|
2833
|
+
...(dependencyBinding ? { analyticalTaskDependencyBinding: dependencyBinding } : {}),
|
|
2656
2834
|
},
|
|
2657
2835
|
};
|
|
2658
2836
|
const answer = await runGovernedAgentAnswerForRun(childRequest, { attempt: 0, repairHint }, route, (message) => emit({ type: 'executor.started', message: `Task ${task.id}: ${message}`, route }), undefined);
|
|
@@ -2679,6 +2857,42 @@ export async function startLocalServer(opts) {
|
|
|
2679
2857
|
rethrowIfCancelled(error, request.signal);
|
|
2680
2858
|
return { task, error: error instanceof Error ? error.message : String(error) };
|
|
2681
2859
|
}
|
|
2860
|
+
};
|
|
2861
|
+
const scheduledChildren = await scheduleCompoundAnalyticalTasks({
|
|
2862
|
+
tasks: turnPlan.tasks.slice(0, 6),
|
|
2863
|
+
runTask: async (task, binding) => {
|
|
2864
|
+
const child = await runChildTask(task, binding);
|
|
2865
|
+
return { task, value: child.answer, error: child.error };
|
|
2866
|
+
},
|
|
2867
|
+
resolveDependency: (task, parent) => {
|
|
2868
|
+
const sourceTaskId = task.dependency?.sourceTaskId ?? '';
|
|
2869
|
+
return resolveTopRankedRegionDependency(sourceTaskId, parent?.value?.result
|
|
2870
|
+
? normalizeCanonicalQueryResult({
|
|
2871
|
+
...parent.value.result,
|
|
2872
|
+
resultFingerprint: parent.value.result.resultFingerprint ?? parent.value.result.executionReceipt?.resultFingerprint,
|
|
2873
|
+
executionReceipt: parent.value.result.executionReceipt,
|
|
2874
|
+
answerTier: parent.value.route?.tier ?? parent.value.sourceTier,
|
|
2875
|
+
})
|
|
2876
|
+
: undefined, parent?.task);
|
|
2877
|
+
},
|
|
2878
|
+
});
|
|
2879
|
+
const childResults = scheduledChildren.map(({ task, value, error, dependencyError }) => ({
|
|
2880
|
+
task,
|
|
2881
|
+
answer: value,
|
|
2882
|
+
error,
|
|
2883
|
+
...(dependencyError ? {
|
|
2884
|
+
dependencyGap: buildCoverageGap({
|
|
2885
|
+
code: dependencyError.code,
|
|
2886
|
+
phase: 'planning',
|
|
2887
|
+
message: dependencyError.message,
|
|
2888
|
+
searchedSources: routeDecision?.retrievalEvidence?.candidateIds ?? [],
|
|
2889
|
+
attemptedRoutes: ['certified', 'semantic', 'governed_relational', 'generated'],
|
|
2890
|
+
missing: ['unambiguous_top_region'],
|
|
2891
|
+
recoverable: true,
|
|
2892
|
+
planFrozen: turnPlan.frozen,
|
|
2893
|
+
nextActions: ['Ask for a single top region or review the parent result before retrying the customer task.'],
|
|
2894
|
+
}),
|
|
2895
|
+
} : {}),
|
|
2682
2896
|
}));
|
|
2683
2897
|
const outcomes = childResults.map(({ task, answer, error }) => ({
|
|
2684
2898
|
version: 1,
|
|
@@ -2687,7 +2901,7 @@ export async function startLocalServer(opts) {
|
|
|
2687
2901
|
...(answer?.answer || answer?.text ? { summary: answer.answer ?? answer.text } : {}),
|
|
2688
2902
|
...(answer?.result?.resultFingerprint ? { resultFingerprint: answer.result.resultFingerprint } : {}),
|
|
2689
2903
|
...(error || answer?.kind === 'no_answer' ? {
|
|
2690
|
-
gap: buildCoverageGap({
|
|
2904
|
+
gap: childResults.find((candidate) => candidate.task.id === task.id)?.dependencyGap ?? buildCoverageGap({
|
|
2691
2905
|
code: answer?.refusalCode === 'ambiguous' ? 'AMBIGUOUS_MEANING' : 'EXECUTION_FAILED',
|
|
2692
2906
|
phase: answer?.executionError ? 'execution' : 'meaning',
|
|
2693
2907
|
message: error ?? answer?.answer ?? answer?.text ?? 'The task did not produce an accepted analytical result.',
|
|
@@ -2706,14 +2920,23 @@ export async function startLocalServer(opts) {
|
|
|
2706
2920
|
status: outcomes.find((outcome) => outcome.taskId === task.id)?.status === 'completed' ? 'completed' : 'gap',
|
|
2707
2921
|
}));
|
|
2708
2922
|
const answerText = childResults.map(({ task, answer, error }) => `${task.question}: ${error ?? answer?.answer ?? answer?.text ?? 'No accepted result was produced.'}`).join('\n\n');
|
|
2923
|
+
const compoundTrust = compoundTrustState(childResults
|
|
2924
|
+
.filter(({ answer, error }) => !error && answer && answer.kind !== 'no_answer')
|
|
2925
|
+
.map(({ answer }) => trustStateForAgentAnswer(answer)));
|
|
2709
2926
|
return {
|
|
2710
2927
|
summary: completedCount === outcomes.length
|
|
2711
|
-
? `Answered ${completedCount}
|
|
2928
|
+
? `Answered ${completedCount} analytical clauses.`
|
|
2712
2929
|
: `Answered ${completedCount} of ${outcomes.length} analytical clauses; the remaining clauses need review.`,
|
|
2713
2930
|
answer: answerText,
|
|
2714
2931
|
status: completedCount === outcomes.length ? 'completed' : completedCount > 0 ? 'needs_review' : 'needs_clarification',
|
|
2715
|
-
|
|
2716
|
-
|
|
2932
|
+
// The parent is only as trustworthy as its weakest SUCCESSFUL child.
|
|
2933
|
+
// Completion is not proof: the previous rule stamped `governed` on a
|
|
2934
|
+
// parent assembled from review-required generated SQL.
|
|
2935
|
+
trustState: compoundTrust,
|
|
2936
|
+
// The stop reason has to agree with the trust it reports. Claiming a
|
|
2937
|
+
// governed semantic answer over generated/certified children
|
|
2938
|
+
// misrepresents provenance as much as the trust label does.
|
|
2939
|
+
stopReason: compoundStopReason(completedCount, outcomes.length, compoundTrust),
|
|
2717
2940
|
artifacts: childResults.map(({ task, answer, error }) => agentRunArtifact('answer', `Task: ${task.question}`, {
|
|
2718
2941
|
taskId: task.id,
|
|
2719
2942
|
question: task.question,
|
|
@@ -3062,7 +3285,34 @@ export async function startLocalServer(opts) {
|
|
|
3062
3285
|
? { ...preview, rows: redactProviderResultRows(preview.rows, narrationMaxRows) }
|
|
3063
3286
|
: undefined;
|
|
3064
3287
|
let narrationSource;
|
|
3065
|
-
|
|
3288
|
+
// The receipt is the durable source of truth for evaluation and inspector
|
|
3289
|
+
// display. Do not reconstruct this later from rows or the reader-facing
|
|
3290
|
+
// fallback sentence: both are presentation artifacts, not evidence that a
|
|
3291
|
+
// fact-grounded narrator actually ran.
|
|
3292
|
+
const verifiedFactNarration = narrationPlan.mode === 'verified_facts'
|
|
3293
|
+
&& Boolean(governedAnswer.analyticalFacts && governedAnswer.resolvedAnalyticalPlan?.analyticalFrame);
|
|
3294
|
+
let narrationIntegrityReceipt = narrationPlan.mode === 'skip'
|
|
3295
|
+
? {
|
|
3296
|
+
version: 1,
|
|
3297
|
+
mode: 'skip',
|
|
3298
|
+
outcome: 'skipped',
|
|
3299
|
+
attempted: false,
|
|
3300
|
+
factCount: 0,
|
|
3301
|
+
maxRows: 0,
|
|
3302
|
+
validationFailures: [],
|
|
3303
|
+
skipReason: narrationPlan.reason,
|
|
3304
|
+
}
|
|
3305
|
+
: {
|
|
3306
|
+
version: 1,
|
|
3307
|
+
mode: verifiedFactNarration ? 'verified_facts' : 'preview_grounded',
|
|
3308
|
+
// This is deliberately pessimistic until a narration outcome is
|
|
3309
|
+
// observed, so an exception cannot be persisted as a silent skip.
|
|
3310
|
+
outcome: 'error',
|
|
3311
|
+
attempted: true,
|
|
3312
|
+
factCount: verifiedFactNarration ? governedAnswer.analyticalFacts?.facts.length ?? 0 : 0,
|
|
3313
|
+
maxRows: narrationPlan.maxRows,
|
|
3314
|
+
validationFailures: [],
|
|
3315
|
+
};
|
|
3066
3316
|
if (narrationPlan.mode !== 'skip' && narrationProvider) {
|
|
3067
3317
|
const narrationStartedAtMs = Date.now();
|
|
3068
3318
|
const draft = governedAnswer.answer ?? governedAnswer.text;
|
|
@@ -3075,8 +3325,16 @@ export async function startLocalServer(opts) {
|
|
|
3075
3325
|
columnCount: providerPreview?.columns.length ?? 0,
|
|
3076
3326
|
},
|
|
3077
3327
|
});
|
|
3078
|
-
const narrationDispatchOptions = () => ({
|
|
3079
|
-
|
|
3328
|
+
const narrationDispatchOptions = (factCount = 0) => ({
|
|
3329
|
+
// The ceiling has to grow with the result. Every claim must echo the
|
|
3330
|
+
// fact ids it rests on, and a fact id is a long hex string that
|
|
3331
|
+
// tokenizes badly — ten of them consume most of the budget before a
|
|
3332
|
+
// word of prose is written. At a flat 350 a ten-row answer was
|
|
3333
|
+
// truncated mid-sentence ("...Elizabeth Shea (875), and Dyl"), so the
|
|
3334
|
+
// JSON never closed, BOTH attempts failed as UNPARSEABLE_CLAIMS, and
|
|
3335
|
+
// the reader got "Verified narration was unavailable" above a robot
|
|
3336
|
+
// dump of the very rows the model had just described correctly.
|
|
3337
|
+
maxTokens: narrationMaxTokensForFacts(factCount),
|
|
3080
3338
|
temperature: 0.3,
|
|
3081
3339
|
maxProviderDispatches: 2,
|
|
3082
3340
|
...(agentRunProviderEvidenceContext.getStore()
|
|
@@ -3093,10 +3351,19 @@ export async function startLocalServer(opts) {
|
|
|
3093
3351
|
factSet: governedAnswer.analyticalFacts,
|
|
3094
3352
|
question: request.question,
|
|
3095
3353
|
maxRows: narrationPlan.maxRows,
|
|
3096
|
-
complete: async ({ system, user }) => streamOrGenerate(narrationProvider, [{ role: 'system', content: system }, { role: 'user', content: user }], narrationDispatchOptions(), () => { }),
|
|
3354
|
+
complete: async ({ system, user }) => streamOrGenerate(narrationProvider, [{ role: 'system', content: system }, { role: 'user', content: user }], narrationDispatchOptions(governedAnswer.analyticalFacts?.facts.length ?? 0), () => { }),
|
|
3097
3355
|
});
|
|
3098
3356
|
narrationSource = composed.source;
|
|
3099
|
-
|
|
3357
|
+
narrationIntegrityReceipt = {
|
|
3358
|
+
...narrationIntegrityReceipt,
|
|
3359
|
+
outcome: composed.source === 'llm' ? 'success' : 'deterministic_fallback',
|
|
3360
|
+
validationFailures: narrationIntegrityFailureCodes(composed.validationFailures),
|
|
3361
|
+
};
|
|
3362
|
+
if (process.env.DQL_ORCHESTRATOR_TRACE) {
|
|
3363
|
+
console.warn(`[dql] narration: source=${composed.source}${narrationIntegrityReceipt.validationFailures.length > 0
|
|
3364
|
+
? ` rejected=${narrationIntegrityReceipt.validationFailures.join(',')}`
|
|
3365
|
+
: ''}`);
|
|
3366
|
+
}
|
|
3100
3367
|
synthesizedAnswer = composed.source === 'llm'
|
|
3101
3368
|
? composed.narrative.text
|
|
3102
3369
|
// A verification failure is not silent: the deterministic join is a
|
|
@@ -3126,11 +3393,22 @@ export async function startLocalServer(opts) {
|
|
|
3126
3393
|
narrationSource = result.source;
|
|
3127
3394
|
if (result.text)
|
|
3128
3395
|
synthesizedAnswer = result.text;
|
|
3396
|
+
narrationIntegrityReceipt = {
|
|
3397
|
+
...narrationIntegrityReceipt,
|
|
3398
|
+
outcome: result.source === 'llm' ? 'success' : 'deterministic_fallback',
|
|
3399
|
+
validationFailures: [],
|
|
3400
|
+
};
|
|
3129
3401
|
}
|
|
3130
3402
|
}
|
|
3131
3403
|
catch {
|
|
3132
3404
|
// Keep the governed draft on any narration failure.
|
|
3133
3405
|
synthesizedAnswer = undefined;
|
|
3406
|
+
narrationIntegrityReceipt = {
|
|
3407
|
+
...narrationIntegrityReceipt,
|
|
3408
|
+
outcome: 'error',
|
|
3409
|
+
validationFailures: [],
|
|
3410
|
+
errorCode: 'narration_error',
|
|
3411
|
+
};
|
|
3134
3412
|
}
|
|
3135
3413
|
finally {
|
|
3136
3414
|
narrationDurationMs = Date.now() - narrationStartedAtMs;
|
|
@@ -3270,7 +3548,11 @@ export async function startLocalServer(opts) {
|
|
|
3270
3548
|
trustState,
|
|
3271
3549
|
stopReason,
|
|
3272
3550
|
artifacts: isTerminalFailure
|
|
3273
|
-
? [agentRunArtifact('answer',
|
|
3551
|
+
? [agentRunArtifact('answer',
|
|
3552
|
+
// Was 'Failed governed analytical run' — internal orchestration state
|
|
3553
|
+
// used as the card heading a user reads. It names our pipeline, not
|
|
3554
|
+
// what happened to their question.
|
|
3555
|
+
terminalFailureTitle(governedAnswer), governedAnswer, governedAnswer.sourceCertifiedBlock ?? governedAnswer.block?.name, 'blocked')]
|
|
3274
3556
|
: governedAnswer.kind === 'no_answer'
|
|
3275
3557
|
// A refusal still keeps the DQL draft the answer loop produced (when any),
|
|
3276
3558
|
// so the "Review DQL draft" next-action isn't a dead link and the user can
|
|
@@ -3367,20 +3649,38 @@ export async function startLocalServer(opts) {
|
|
|
3367
3649
|
...(governedAnswer.executionError ? [
|
|
3368
3650
|
agentRunEvaluation('execution-error', 'Execution error', false, 'warning', governedAnswer.executionError),
|
|
3369
3651
|
] : []),
|
|
3370
|
-
//
|
|
3371
|
-
//
|
|
3372
|
-
//
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
agentRunEvaluation('narration-verification', 'Narration verification', false, 'warning', `The drafted narration was rejected against the result fact set, so the deterministic record was shown instead: ${narrationValidationFailures.join('; ')}`, { narrationSource, validationFailures: narrationValidationFailures }),
|
|
3652
|
+
// Keep only the content-free receipt codes in the durable inspection
|
|
3653
|
+
// record. Raw verifier prose can contain a result value, prompt excerpt,
|
|
3654
|
+
// or provider error and is not safe evidence to surface or persist.
|
|
3655
|
+
...(narrationIntegrityReceipt.outcome === 'deterministic_fallback'
|
|
3656
|
+
&& narrationIntegrityReceipt.validationFailures.length > 0 ? [
|
|
3657
|
+
agentRunEvaluation('narration-verification', 'Narration verification', false, 'warning', `The drafted narration was rejected against the result fact set, so the deterministic record was shown instead: ${narrationIntegrityReceipt.validationFailures.join(', ')}.`, { narrationSource, validationFailures: narrationIntegrityReceipt.validationFailures }),
|
|
3377
3658
|
] : []),
|
|
3378
3659
|
],
|
|
3379
3660
|
nextActions,
|
|
3380
3661
|
providerEgressReceipts: finalProviderEgressReceipts,
|
|
3381
3662
|
telemetry: finalTelemetry,
|
|
3663
|
+
narrationIntegrityReceipt,
|
|
3382
3664
|
};
|
|
3383
3665
|
};
|
|
3666
|
+
/**
|
|
3667
|
+
* A heading for a run that ended without an answer, in the user's terms.
|
|
3668
|
+
*
|
|
3669
|
+
* Says WHICH stage stopped, because "it failed" and "it was stopped before
|
|
3670
|
+
* running" call for different next moves: one is worth retrying, the other
|
|
3671
|
+
* needs the question or the model changed.
|
|
3672
|
+
*/
|
|
3673
|
+
const terminalFailureTitle = (answer) => {
|
|
3674
|
+
switch (answer.refusalCode) {
|
|
3675
|
+
case 'policy_blocked': return 'Blocked by a governance policy';
|
|
3676
|
+
case 'modeling_gap': return 'Not modeled yet';
|
|
3677
|
+
case 'grounding_gap': return 'Not enough context to answer safely';
|
|
3678
|
+
case 'model_declined': return 'The assistant declined to answer';
|
|
3679
|
+
case 'provider_error': return 'The AI provider did not respond';
|
|
3680
|
+
case 'ambiguous': return 'Needs one detail before running';
|
|
3681
|
+
default: return 'No answer was produced';
|
|
3682
|
+
}
|
|
3683
|
+
};
|
|
3384
3684
|
const conversationRunExecutor = async ({ request, routeDecision, emitAnswerDelta }) => {
|
|
3385
3685
|
const kind = routeDecision?.conversationalKind ?? 'smalltalk';
|
|
3386
3686
|
const isGeneralKnowledge = routeDecision?.category === 'general_knowledge';
|
|
@@ -3397,6 +3697,15 @@ export async function startLocalServer(opts) {
|
|
|
3397
3697
|
let text = kind === 'answer_explanation'
|
|
3398
3698
|
? buildPriorAnswerExplanation(request.question, request.conversationContext)
|
|
3399
3699
|
: undefined;
|
|
3700
|
+
// A definitional question that NAMES a governed artifact is answerable from
|
|
3701
|
+
// the catalog: the description, domain, and dimensions are already recorded.
|
|
3702
|
+
// Reaching for a provider to paraphrase facts we hold can only add drift, and
|
|
3703
|
+
// the generic conversational reply this replaces used none of them.
|
|
3704
|
+
//
|
|
3705
|
+
// Returns undefined unless the question names something real, so a turn that
|
|
3706
|
+
// does not match keeps today's behaviour exactly.
|
|
3707
|
+
if (!text)
|
|
3708
|
+
text = buildGovernedObjectExplanation(request.question);
|
|
3400
3709
|
if (text) {
|
|
3401
3710
|
emitAnswerDelta?.(text);
|
|
3402
3711
|
}
|
|
@@ -3863,10 +4172,18 @@ export async function startLocalServer(opts) {
|
|
|
3863
4172
|
const conversationHistory = request.history?.length
|
|
3864
4173
|
? request.history
|
|
3865
4174
|
: conversationHistoryFromContext(request.conversationContext);
|
|
4175
|
+
// The provider that will plan the investigation as hypotheses. Absent or
|
|
4176
|
+
// unreachable, `planResearch` keeps its deterministic template, so
|
|
4177
|
+
// research never depends on a model being available.
|
|
4178
|
+
const researchPlanner = resolveGovernedAnswerRunner(projectRoot);
|
|
4179
|
+
const researchPlannerProvider = researchPlanner
|
|
4180
|
+
? createGovernedTextProvider(researchPlanner.provider, projectRoot)
|
|
4181
|
+
: undefined;
|
|
3866
4182
|
const plan = await planResearch({
|
|
3867
4183
|
question: request.question,
|
|
3868
4184
|
metrics,
|
|
3869
4185
|
blocks,
|
|
4186
|
+
...(researchPlannerProvider ? { provider: researchPlannerProvider } : {}),
|
|
3870
4187
|
intent: request.intent,
|
|
3871
4188
|
isFollowUp: conversationHistory.length > 0,
|
|
3872
4189
|
history: conversationHistory,
|
|
@@ -3949,10 +4266,32 @@ export async function startLocalServer(opts) {
|
|
|
3949
4266
|
expectation: 'Whether the frozen context contains enough evidence for a bounded answer.',
|
|
3950
4267
|
};
|
|
3951
4268
|
const branches = capResearchBranches(plan.steps.length > 0 ? plan.steps : [fallbackBranch], 6);
|
|
4269
|
+
// The replan edge. Each branch tests one hypothesis; folding its
|
|
4270
|
+
// outcome back into the state is what lets the investigation stop
|
|
4271
|
+
// when the question is settled instead of grinding through a plan
|
|
4272
|
+
// frozen before any observation. `nextHypothesis` returning
|
|
4273
|
+
// undefined is how the loop learns to stop — it enforces the hop
|
|
4274
|
+
// budget and reports when nothing is open.
|
|
4275
|
+
let researchState = createResearchState(request.question, branches.map((branch, position) => ({
|
|
4276
|
+
id: `h${position + 1}`,
|
|
4277
|
+
statement: branch.thought,
|
|
4278
|
+
priorConfidence: 1 - position / (branches.length + 1),
|
|
4279
|
+
})));
|
|
3952
4280
|
for (let index = 0; index < branches.length; index += 1) {
|
|
3953
4281
|
const step = branches[index];
|
|
3954
4282
|
if (request.signal?.aborted)
|
|
3955
4283
|
rethrowIfCancelled(request.signal.reason, request.signal);
|
|
4284
|
+
// A hypothesis an earlier finding already closed is not
|
|
4285
|
+
// re-investigated, and an exhausted hop budget stops the run.
|
|
4286
|
+
const stillOpen = nextHypothesis(researchState);
|
|
4287
|
+
if (!stillOpen) {
|
|
4288
|
+
emit({
|
|
4289
|
+
type: 'executor.started',
|
|
4290
|
+
message: `Stopping early: ${researchState.hopsUsed} of ${branches.length} branches settled what could be settled.`,
|
|
4291
|
+
route: 'research',
|
|
4292
|
+
});
|
|
4293
|
+
break;
|
|
4294
|
+
}
|
|
3956
4295
|
const branchId = `${step.action.kind}:${step.action.target}`;
|
|
3957
4296
|
const branchQuestion = `${request.question}\nResearch branch ${index + 1} (${branchId}): ${step.expectation}`;
|
|
3958
4297
|
const childId = `${created.id}:research:${index + 1}`;
|
|
@@ -4007,7 +4346,27 @@ export async function startLocalServer(opts) {
|
|
|
4007
4346
|
baselineDqlArtifact: researchSource?.dqlArtifact,
|
|
4008
4347
|
baselineRunId: agentRunString(researchSource?.runId),
|
|
4009
4348
|
});
|
|
4010
|
-
|
|
4349
|
+
const branchRun = withNotebookResearchChecklist(executed);
|
|
4350
|
+
researchRuns.push(branchRun);
|
|
4351
|
+
// Observe, then decide. A branch that produced rows is evidence
|
|
4352
|
+
// for its hypothesis; one that did not is inconclusive, which is
|
|
4353
|
+
// a real outcome and not a failure.
|
|
4354
|
+
// Rows are not support. A branch that returned data has been
|
|
4355
|
+
// OBSERVED, not confirmed — deciding whether the observation
|
|
4356
|
+
// matches what the hypothesis predicted needs the expectation,
|
|
4357
|
+
// and nothing available at this layer can judge it. Recording
|
|
4358
|
+
// rows as `supports` would let the dossier report a driver the
|
|
4359
|
+
// evidence never established, which is the failure mode the
|
|
4360
|
+
// whole verified-fact chain exists to prevent.
|
|
4361
|
+
researchState = applyFinding(researchState, {
|
|
4362
|
+
id: `f${index + 1}`,
|
|
4363
|
+
hypothesisId: `h${index + 1}`,
|
|
4364
|
+
verdict: 'inconclusive',
|
|
4365
|
+
summary: branchRun.summary ?? '',
|
|
4366
|
+
strength: (branchRun.resultPreview?.rows?.length ?? 0) > 0
|
|
4367
|
+
? 0.5
|
|
4368
|
+
: 0.1,
|
|
4369
|
+
});
|
|
4011
4370
|
}
|
|
4012
4371
|
catch (error) {
|
|
4013
4372
|
// A child is a real durable run even when cancellation stops the
|
|
@@ -4023,6 +4382,13 @@ export async function startLocalServer(opts) {
|
|
|
4023
4382
|
const stopped = storage.getRun(child.id);
|
|
4024
4383
|
if (stopped)
|
|
4025
4384
|
researchRuns.push(withNotebookResearchChecklist(stopped));
|
|
4385
|
+
researchState = applyFinding(researchState, {
|
|
4386
|
+
id: `f${index + 1}`,
|
|
4387
|
+
hypothesisId: `h${index + 1}`,
|
|
4388
|
+
verdict: 'inconclusive',
|
|
4389
|
+
summary: message,
|
|
4390
|
+
strength: 0,
|
|
4391
|
+
});
|
|
4026
4392
|
rethrowIfCancelled(error, request.signal);
|
|
4027
4393
|
}
|
|
4028
4394
|
}
|
|
@@ -4121,20 +4487,40 @@ export async function startLocalServer(opts) {
|
|
|
4121
4487
|
reviewRequired: true,
|
|
4122
4488
|
}, request.researchResultRowsOptIn === true)
|
|
4123
4489
|
: undefined;
|
|
4490
|
+
// The cross-branch story. Every branch tested a hypothesis and produced a
|
|
4491
|
+
// finding; narrating only the one result the executor happened to carry
|
|
4492
|
+
// reported a single fact and discarded the rest, which is the visible
|
|
4493
|
+
// half of "research answers one question instead of telling a story".
|
|
4494
|
+
const researchStory = !needsClarification && plan.steps.length > 0
|
|
4495
|
+
? synthesizeResearchNarrative({
|
|
4496
|
+
question: request.question,
|
|
4497
|
+
branches: researchRuns.map((branch, index) => ({
|
|
4498
|
+
statement: plan.steps[index]?.thought ?? branch.question ?? '',
|
|
4499
|
+
produced: branch.status === 'ready'
|
|
4500
|
+
&& (branch.resultPreview?.rows?.length ?? 0) > 0,
|
|
4501
|
+
...(branch.summary ? { summary: branch.summary } : {}),
|
|
4502
|
+
...(branch.status ? { status: branch.status } : {}),
|
|
4503
|
+
})),
|
|
4504
|
+
})
|
|
4505
|
+
: undefined;
|
|
4124
4506
|
const summary = needsClarification
|
|
4125
4507
|
? 'Needs clarification before running deeper research.'
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4508
|
+
// The story leads; the verified-fact narration follows it, so the
|
|
4509
|
+
// numbers still come from the narrator that checks them.
|
|
4510
|
+
: researchStory
|
|
4511
|
+
? `${researchStory}${narration?.summary ? `\n\n${narration.summary}` : ''}`
|
|
4512
|
+
: narration?.summary
|
|
4513
|
+
?? (researchZeroRows
|
|
4514
|
+
? 'The query executed cleanly against real data and matched 0 rows.'
|
|
4515
|
+
: researchRun?.status === 'ready'
|
|
4516
|
+
? 'Saved a grounded research dossier with context evidence and next review actions.'
|
|
4517
|
+
: researchRun?.status === 'error'
|
|
4518
|
+
? 'Saved a research dossier, but the preview needs review before promotion.'
|
|
4519
|
+
: researchWorkspaceError
|
|
4520
|
+
? 'Prepared a grounded research plan; durable research storage is unavailable in this runtime.'
|
|
4521
|
+
: plan.done
|
|
4522
|
+
? 'Prepared a direct grounded-answer plan.'
|
|
4523
|
+
: 'Prepared a grounded research plan over real DQL assets.');
|
|
4138
4524
|
return {
|
|
4139
4525
|
summary,
|
|
4140
4526
|
answer: plan.followUp?.question ?? narration?.summary
|
|
@@ -4482,6 +4868,22 @@ export async function startLocalServer(opts) {
|
|
|
4482
4868
|
// lookup, and governed execution for the lifetime of a request. This removes
|
|
4483
4869
|
// both positional catalog truncation and the previous duplicate retrieval pass.
|
|
4484
4870
|
const preparedAgentContextPacks = new WeakMap();
|
|
4871
|
+
/**
|
|
4872
|
+
* Cross-encoder pass over the fused candidates, when a provider is available.
|
|
4873
|
+
* Advisory throughout: it may only reorder ids retrieval returned, and any
|
|
4874
|
+
* failure leaves retrieval's own ordering in place.
|
|
4875
|
+
*/
|
|
4876
|
+
const agentRerankCandidates = (() => {
|
|
4877
|
+
const governed = resolveGovernedAnswerRunner(projectRoot);
|
|
4878
|
+
const provider = governed
|
|
4879
|
+
? createGovernedTextProvider(governed.provider, projectRoot)
|
|
4880
|
+
: undefined;
|
|
4881
|
+
if (!provider)
|
|
4882
|
+
return undefined;
|
|
4883
|
+
return (question, candidates) => rerankCandidates(provider, question, candidates, {
|
|
4884
|
+
timeoutMs: Math.round(2_500 * deadlineScale()),
|
|
4885
|
+
});
|
|
4886
|
+
})();
|
|
4485
4887
|
const pendingAgentContextPacks = new WeakMap();
|
|
4486
4888
|
const buildAgentRunContextPack = async (request) => {
|
|
4487
4889
|
const prepared = preparedAgentContextPacks.get(request);
|
|
@@ -4549,6 +4951,10 @@ export async function startLocalServer(opts) {
|
|
|
4549
4951
|
},
|
|
4550
4952
|
strictness: request.analysisDepth === 'deep' ? 'exploratory' : 'balanced',
|
|
4551
4953
|
limit: request.analysisDepth === 'deep' ? 120 : 80,
|
|
4954
|
+
// The runtime PRE-BUILDS this pack, so wiring the reranker only at the
|
|
4955
|
+
// provider's own `buildLocalContextPack` left it unreachable on the
|
|
4956
|
+
// common path — the prepared pack is used and that call never happens.
|
|
4957
|
+
...(agentRerankCandidates ? { rerankCandidates: agentRerankCandidates } : {}),
|
|
4552
4958
|
domainContext: requestedDomain
|
|
4553
4959
|
? resolveUiDomainContext({
|
|
4554
4960
|
manifest: snapshot.manifest,
|
|
@@ -4604,6 +5010,56 @@ export async function startLocalServer(opts) {
|
|
|
4604
5010
|
};
|
|
4605
5011
|
// Compact fallback used only for plain conversational replies. Analytical
|
|
4606
5012
|
// turns use the structured, question-ranked evidence path above.
|
|
5013
|
+
/**
|
|
5014
|
+
* Explain a governed artifact the question names, from catalog metadata alone.
|
|
5015
|
+
*
|
|
5016
|
+
* Certified blocks are offered first: when a concept exists both as a
|
|
5017
|
+
* certified block and a raw model, the certified one is the authored
|
|
5018
|
+
* definition and the other is an implementation detail.
|
|
5019
|
+
*/
|
|
5020
|
+
const buildGovernedObjectExplanation = (question) => {
|
|
5021
|
+
try {
|
|
5022
|
+
const blocks = collectPlanBlocks(projectRoot, { certifiedOnly: true });
|
|
5023
|
+
const certifiedNames = new Set(blocks.map((block) => block.name));
|
|
5024
|
+
const all = [
|
|
5025
|
+
...blocks.map((block) => ({ block, status: 'certified' })),
|
|
5026
|
+
...collectPlanBlocks(projectRoot, { certifiedOnly: false })
|
|
5027
|
+
.filter((block) => !certifiedNames.has(block.name))
|
|
5028
|
+
.map((block) => ({ block, status: 'draft' })),
|
|
5029
|
+
];
|
|
5030
|
+
// Metrics as well as blocks. "How is revenue defined here?" names a
|
|
5031
|
+
// semantic metric, not a block, and answering it from the metric's own
|
|
5032
|
+
// description is the whole point of holding one.
|
|
5033
|
+
const metricObjects = loadSemanticMetrics(projectRoot).map((metric) => ({
|
|
5034
|
+
objectKey: `semantic:metric:${metric.name}`,
|
|
5035
|
+
objectType: 'semantic_metric',
|
|
5036
|
+
name: metric.name,
|
|
5037
|
+
...(metric.description ? { description: metric.description } : {}),
|
|
5038
|
+
...(metric.domain ? { domain: metric.domain } : {}),
|
|
5039
|
+
status: 'governed',
|
|
5040
|
+
payload: {},
|
|
5041
|
+
}));
|
|
5042
|
+
const explanation = composeBusinessExplanation(question, [
|
|
5043
|
+
...all.map(({ block, status }) => ({
|
|
5044
|
+
objectKey: `dql:block:${block.name}`,
|
|
5045
|
+
objectType: 'dql_block',
|
|
5046
|
+
name: block.name,
|
|
5047
|
+
...(block.description ? { description: block.description } : {}),
|
|
5048
|
+
...(block.domain ? { domain: block.domain } : {}),
|
|
5049
|
+
status,
|
|
5050
|
+
payload: {
|
|
5051
|
+
...(block.dimensions?.length ? { dimensions: block.dimensions } : {}),
|
|
5052
|
+
},
|
|
5053
|
+
})),
|
|
5054
|
+
...metricObjects,
|
|
5055
|
+
]);
|
|
5056
|
+
return explanation?.text;
|
|
5057
|
+
}
|
|
5058
|
+
catch {
|
|
5059
|
+
// Never let an explanation attempt break a conversational turn.
|
|
5060
|
+
return undefined;
|
|
5061
|
+
}
|
|
5062
|
+
};
|
|
4607
5063
|
const buildAgentRunCatalogContext = () => {
|
|
4608
5064
|
try {
|
|
4609
5065
|
const blocks = collectPlanBlocks(projectRoot, { certifiedOnly: true });
|
|
@@ -5155,12 +5611,43 @@ export async function startLocalServer(opts) {
|
|
|
5155
5611
|
* string, so the only remaining reasons to differ are the connection and the
|
|
5156
5612
|
* governance gates, both of which report themselves honestly.
|
|
5157
5613
|
*/
|
|
5158
|
-
const executeGeneratedSqlDirect = async (question, sql, seed, executionConnection, bindings, executionConnectionName) => {
|
|
5614
|
+
const executeGeneratedSqlDirect = async (question, sql, seed, executionConnection, bindings, executionConnectionName, agenticCapability, agenticScope) => {
|
|
5159
5615
|
const activeConnection = requireActiveConnection(executionConnection);
|
|
5160
5616
|
const rowBound = clampAnalyticalRowBound(seed?.limit ?? 200);
|
|
5161
5617
|
const trimmed = sql.trim().replace(/;\s*$/, '').trim();
|
|
5162
5618
|
if (!trimmed)
|
|
5163
5619
|
throw analyticalError('The generated SQL was empty.', { origin: 'host', stage: 'execute' });
|
|
5620
|
+
const bindingValue = {
|
|
5621
|
+
sqlParams: bindings?.sqlParams ?? [],
|
|
5622
|
+
variables: bindings?.variables ?? {},
|
|
5623
|
+
};
|
|
5624
|
+
if (agenticCapability) {
|
|
5625
|
+
// The proposal itself is immutable. A changed literal, comment, or
|
|
5626
|
+
// whitespace is drift here rather than a benign formatting change.
|
|
5627
|
+
const currentSnapshotId = projectSnapshot().snapshotId;
|
|
5628
|
+
let targetFingerprint;
|
|
5629
|
+
if (agenticCapability.targetFingerprint) {
|
|
5630
|
+
try {
|
|
5631
|
+
targetFingerprint = (await observeWarehouseTargetIdentity(executor, activeConnection)).identityFingerprint;
|
|
5632
|
+
}
|
|
5633
|
+
catch {
|
|
5634
|
+
throw analyticalError('DQL could not re-confirm the selected execution target, so the analyst-approved query was not run.', {
|
|
5635
|
+
origin: 'governance_gate', stage: 'execute', code: 'unauthorized_sql',
|
|
5636
|
+
});
|
|
5637
|
+
}
|
|
5638
|
+
}
|
|
5639
|
+
const capabilityVerdict = verifyAgenticSqlExecutionCapability(agenticCapability, sql, {
|
|
5640
|
+
...agenticScope,
|
|
5641
|
+
bindings: bindingValue,
|
|
5642
|
+
snapshotId: currentSnapshotId,
|
|
5643
|
+
...(targetFingerprint ? { targetFingerprint } : {}),
|
|
5644
|
+
});
|
|
5645
|
+
if (!capabilityVerdict.ok) {
|
|
5646
|
+
throw analyticalError(capabilityVerdict.reason ?? 'The analyst-approved SQL no longer matches this execution.', {
|
|
5647
|
+
origin: 'governance_gate', stage: 'execute', code: 'unauthorized_sql',
|
|
5648
|
+
});
|
|
5649
|
+
}
|
|
5650
|
+
}
|
|
5164
5651
|
// RESOLVE FIRST, VALIDATE SECOND. Executing verbatim means executing the
|
|
5165
5652
|
// statement the notebook would execute — and the notebook resolves
|
|
5166
5653
|
// `@metric()` / `@dim()` refs and dbt macros before it runs anything.
|
|
@@ -5185,8 +5672,6 @@ export async function startLocalServer(opts) {
|
|
|
5185
5672
|
// release denied; a governance boundary must not move as a side effect.
|
|
5186
5673
|
const sourceDomain = seed?.source?.match(/\bdomain\s*=\s*"([^"]+)"/i)?.[1] ?? 'uncategorized';
|
|
5187
5674
|
assertAppAccess({ app, domain: sourceDomain, level: 'execute' });
|
|
5188
|
-
// A semantic query the loop already compiled (MetricFlow / dbt Cloud) must
|
|
5189
|
-
// execute through its pinned target binding, not as loose SQL.
|
|
5190
5675
|
const semanticExecutionHolder = { value: null };
|
|
5191
5676
|
const execution = await analyticalExecutionService.execute({
|
|
5192
5677
|
sql: semantic.sql,
|
|
@@ -5198,27 +5683,46 @@ export async function startLocalServer(opts) {
|
|
|
5198
5683
|
variables: bindings?.variables,
|
|
5199
5684
|
semanticRefs: semantic.semanticRefs,
|
|
5200
5685
|
executePrepared: async (preparation) => {
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5686
|
+
// Both the native executor and the target-bound semantic adapter can
|
|
5687
|
+
// reach the warehouse from this callback. Admit the exact prepared
|
|
5688
|
+
// bytes before either path so a matching cached semantic compile cannot
|
|
5689
|
+
// bypass the generated proposal's capability.
|
|
5690
|
+
return executePreparedAgenticSqlBoundary({
|
|
5691
|
+
capability: agenticCapability,
|
|
5692
|
+
preparedSql: preparation.executedSql,
|
|
5693
|
+
bindings: bindingValue,
|
|
5694
|
+
scope: {
|
|
5695
|
+
...agenticScope,
|
|
5696
|
+
snapshotId: projectSnapshot().snapshotId,
|
|
5697
|
+
...(agenticCapability ? { targetFingerprint: agenticCapability.targetFingerprint } : {}),
|
|
5698
|
+
},
|
|
5699
|
+
execute: async () => {
|
|
5700
|
+
// A semantic query the loop already compiled (MetricFlow / dbt
|
|
5701
|
+
// Cloud) must execute through its pinned target binding, not as
|
|
5702
|
+
// loose SQL.
|
|
5703
|
+
const pinnedSemanticCompile = compiledSemanticQueries.get(executionFingerprint(preparation.preparedSql))
|
|
5704
|
+
?? compiledSemanticQueries.get(executionFingerprint(trimmed));
|
|
5705
|
+
if (pinnedSemanticCompile) {
|
|
5706
|
+
const semanticExecution = await executeTargetBoundSemanticQuery({
|
|
5707
|
+
executor,
|
|
5708
|
+
connection: activeConnection,
|
|
5709
|
+
projectRoot,
|
|
5710
|
+
plannedAdapter: pinnedSemanticCompile.engine,
|
|
5711
|
+
metricFlow: pinnedSemanticCompile.engine === 'metricflow-cli'
|
|
5712
|
+
? resolveMetricFlowTargetMetadata(projectRoot, projectConfig)
|
|
5713
|
+
: undefined,
|
|
5714
|
+
compile: async () => pinnedSemanticCompile,
|
|
5715
|
+
prepareSql: () => ({ sql: preparation.executedSql, connection: preparation.connection }),
|
|
5716
|
+
rowBound,
|
|
5717
|
+
});
|
|
5718
|
+
if (semanticExecution) {
|
|
5719
|
+
semanticExecutionHolder.value = semanticExecution;
|
|
5720
|
+
return semanticExecution.result;
|
|
5721
|
+
}
|
|
5722
|
+
}
|
|
5723
|
+
return executor.executeQuery(preparation.executedSql, bindings?.sqlParams ?? [], runtimeVariables(bindings?.variables ?? {}), preparation.connection);
|
|
5219
5724
|
}
|
|
5220
|
-
}
|
|
5221
|
-
return executor.executeQuery(preparation.executedSql, bindings?.sqlParams ?? [], runtimeVariables(bindings?.variables ?? {}), preparation.connection);
|
|
5725
|
+
});
|
|
5222
5726
|
},
|
|
5223
5727
|
});
|
|
5224
5728
|
const semanticExecution = semanticExecutionHolder.value;
|
|
@@ -5276,14 +5780,19 @@ export async function startLocalServer(opts) {
|
|
|
5276
5780
|
executableArtifact,
|
|
5277
5781
|
};
|
|
5278
5782
|
};
|
|
5279
|
-
const executeGeneratedArtifactForAgent = async (question, sql, seed, executionConnection, executionConnectionName) => {
|
|
5783
|
+
const executeGeneratedArtifactForAgent = async (question, sql, seed, executionConnection, executionConnectionName, agenticCapability, agenticScope) => {
|
|
5280
5784
|
// A seed that is already a certified/saved artifact keeps the DQL-first
|
|
5281
5785
|
// path: there the `.dql` source IS the contract, and its parameters and
|
|
5282
5786
|
// semantic refs must be compiled, not bypassed.
|
|
5283
5787
|
if (seed && seed.kind !== 'sql_block') {
|
|
5788
|
+
if (agenticCapability) {
|
|
5789
|
+
throw analyticalError('The analyst-approved SQL cannot be redirected through a saved artifact.', {
|
|
5790
|
+
origin: 'governance_gate', stage: 'execute', code: 'unauthorized_sql',
|
|
5791
|
+
});
|
|
5792
|
+
}
|
|
5284
5793
|
return executeArtifactReferenceForAgent({ ...seed, limit: seed.limit ?? 200 }, question, executionConnection, executionConnectionName);
|
|
5285
5794
|
}
|
|
5286
|
-
return executeGeneratedSqlDirect(question, sql, seed, executionConnection, undefined, executionConnectionName);
|
|
5795
|
+
return executeGeneratedSqlDirect(question, sql, seed, executionConnection, undefined, executionConnectionName, agenticCapability, agenticScope);
|
|
5287
5796
|
};
|
|
5288
5797
|
/**
|
|
5289
5798
|
* EXP-001: execution host for the deliberately narrow non-governed lane.
|
|
@@ -5891,7 +6400,10 @@ export async function startLocalServer(opts) {
|
|
|
5891
6400
|
let invocation;
|
|
5892
6401
|
let plan;
|
|
5893
6402
|
try {
|
|
5894
|
-
|
|
6403
|
+
// The source label is echoed into parse errors, which reach the user.
|
|
6404
|
+
// `<bounded-dql-repair>` is an internal artifact id and tells them nothing;
|
|
6405
|
+
// it appeared verbatim in a reported failure card.
|
|
6406
|
+
const program = new Parser(repairedSource, 'repaired query').parse();
|
|
5895
6407
|
const blocks = program.statements.filter((statement) => statement.kind === NodeKind.BlockDecl);
|
|
5896
6408
|
if (blocks.length !== 1 || program.statements.length !== 1) {
|
|
5897
6409
|
throw new Error('The repaired source must contain exactly one DQL block.');
|
|
@@ -25821,7 +26333,13 @@ async function createBlockStudioAssistProvider(projectRoot, requestedProvider) {
|
|
|
25821
26333
|
default:
|
|
25822
26334
|
return null;
|
|
25823
26335
|
}
|
|
25824
|
-
|
|
26336
|
+
// Route through the eval cassette too. This constructor serves the MEANING
|
|
26337
|
+
// call and narration — the two dispatches that decide routing and wording —
|
|
26338
|
+
// so leaving it unwrapped meant a recorded suite still hit a live model for
|
|
26339
|
+
// exactly the calls whose non-determinism it was recorded to remove. A local
|
|
26340
|
+
// baseline reproduced that: the same question blocked on one run and answered
|
|
26341
|
+
// on the next, and zero cassettes were written.
|
|
26342
|
+
return await provider.available() ? applyEvalCassette(provider) : null;
|
|
25825
26343
|
}
|
|
25826
26344
|
/** Convert a governed answer's result payload into a bounded synthesis preview. */
|
|
25827
26345
|
function agentResultToSynthesisPreview(result) {
|
|
@@ -28658,7 +29176,45 @@ async function buildAgentSchemaContextFromCatalog(projectRoot, question, prepare
|
|
|
28658
29176
|
const RUNTIME_SNAPSHOT_MAX_AGE_MS = 60 * 60 * 1000; // 1 hour
|
|
28659
29177
|
// A resolver compares at most 12 compact cards and never performs tool calls;
|
|
28660
29178
|
// ten seconds is the full allowance, not the start of another planning loop.
|
|
28661
|
-
|
|
29179
|
+
/**
|
|
29180
|
+
* Ceiling on the one bounded meaning-resolution call.
|
|
29181
|
+
*
|
|
29182
|
+
* 10s assumes a hosted model. A local Ollama model needs ~7s for a ONE-WORD
|
|
29183
|
+
* reply, so a 600-token resolution over a dozen candidates never lands: it
|
|
29184
|
+
* aborts, the router falls back to its evidence-only decision, and
|
|
29185
|
+
* `mayAssumeInterpretation` goes false — which sends every ambiguous question to
|
|
29186
|
+
* the clarification gate (AGT-017). The effect is that a local model cannot
|
|
29187
|
+
* answer anything ambiguous, in a product whose whole positioning is local-first.
|
|
29188
|
+
*
|
|
29189
|
+
* Scaled by the same `DQL_AGENT_DEADLINE_SCALE` as the run budget, so one
|
|
29190
|
+
* setting moves the provider's whole time envelope together rather than leaving
|
|
29191
|
+
* an inner bound to silently cap an outer one.
|
|
29192
|
+
*/
|
|
29193
|
+
/**
|
|
29194
|
+
* Predict how long the next provider call will take, for admission control.
|
|
29195
|
+
*
|
|
29196
|
+
* With fewer than three samples the MAX is the only honest predictor: there is
|
|
29197
|
+
* no distribution yet, and admitting a call the deadline then kills wastes the
|
|
29198
|
+
* whole remaining budget.
|
|
29199
|
+
*
|
|
29200
|
+
* With a real sample, p75 rather than the max. One slow response — a cold model
|
|
29201
|
+
* load, a retried connection — otherwise poisons admission control for the rest
|
|
29202
|
+
* of the run: every later call is refused against a worst case that already
|
|
29203
|
+
* passed. A recorded run tripped RUN_DEADLINE_INSUFFICIENT 6.4s into a 45s
|
|
29204
|
+
* budget for exactly that reason. p75 still errs slow, so a genuinely slow
|
|
29205
|
+
* provider is still respected.
|
|
29206
|
+
*/
|
|
29207
|
+
export function predictDispatchMs(observed, assumedMs = ASSUMED_PROVIDER_DISPATCH_MS) {
|
|
29208
|
+
if (observed.length === 0)
|
|
29209
|
+
return assumedMs;
|
|
29210
|
+
const sorted = [...observed].sort((left, right) => left - right);
|
|
29211
|
+
if (sorted.length < 3)
|
|
29212
|
+
return sorted[sorted.length - 1];
|
|
29213
|
+
const index = Math.max(0, Math.min(sorted.length - 1, Math.ceil(sorted.length * 0.75) - 1));
|
|
29214
|
+
return sorted[index];
|
|
29215
|
+
}
|
|
29216
|
+
const AGENT_MEANING_TIMEOUT_BASE_MS = 10_000;
|
|
29217
|
+
const AGENT_MEANING_TIMEOUT_MS = AGENT_MEANING_TIMEOUT_BASE_MS * deadlineScale();
|
|
28662
29218
|
export function boundedAgentMeaningSignal(signal, timeoutMs = AGENT_MEANING_TIMEOUT_MS) {
|
|
28663
29219
|
const timeout = AbortSignal.timeout(Math.max(1, timeoutMs));
|
|
28664
29220
|
return signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
@@ -30043,49 +30599,9 @@ function scoreAgentValueProbeColumn(table, column) {
|
|
|
30043
30599
|
return score;
|
|
30044
30600
|
}
|
|
30045
30601
|
export function isAgentValueProbeColumn(column) {
|
|
30046
|
-
|
|
30047
|
-
//
|
|
30048
|
-
|
|
30049
|
-
// can never be probed through automatic grounding.
|
|
30050
|
-
const normalizedName = column.name
|
|
30051
|
-
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
30052
|
-
.replace(/[_-]+/g, ' ')
|
|
30053
|
-
.toLowerCase();
|
|
30054
|
-
if (/\b(password|secret|token|credential|hash|salt|notes?|comments?|description|message|body|payload|content)\b/.test(normalizedName))
|
|
30055
|
-
return false;
|
|
30056
|
-
if (/\bemail\b/.test(normalizedName))
|
|
30057
|
-
return false;
|
|
30058
|
-
if (!hasAgentSchemaToken(name, [
|
|
30059
|
-
'account',
|
|
30060
|
-
'category',
|
|
30061
|
-
'channel',
|
|
30062
|
-
'city',
|
|
30063
|
-
'code',
|
|
30064
|
-
'country',
|
|
30065
|
-
'customer',
|
|
30066
|
-
'email',
|
|
30067
|
-
'full',
|
|
30068
|
-
'id',
|
|
30069
|
-
'key',
|
|
30070
|
-
'member',
|
|
30071
|
-
'name',
|
|
30072
|
-
'number',
|
|
30073
|
-
'product',
|
|
30074
|
-
'region',
|
|
30075
|
-
'segment',
|
|
30076
|
-
'sku',
|
|
30077
|
-
'state',
|
|
30078
|
-
'status',
|
|
30079
|
-
'subscriber',
|
|
30080
|
-
'type',
|
|
30081
|
-
'user',
|
|
30082
|
-
])) {
|
|
30083
|
-
return false;
|
|
30084
|
-
}
|
|
30085
|
-
const type = column.type?.toLowerCase() ?? '';
|
|
30086
|
-
if (!type)
|
|
30087
|
-
return true;
|
|
30088
|
-
return /\b(char|character|clob|email|string|text|uuid|varchar)\b/.test(type);
|
|
30602
|
+
// Delegates to the canonical predicate in dql-agent. Two copies of a security
|
|
30603
|
+
// rule drift, and the one that drifts is the one nobody is looking at.
|
|
30604
|
+
return isProbeSafeColumn({ name: column.name, ...(column.type ? { type: column.type } : {}) });
|
|
30089
30605
|
}
|
|
30090
30606
|
export function buildAgentValueProbeSql(table, column, searchTerms, connection) {
|
|
30091
30607
|
const relation = quoteAgentRelation(table.relation, connection);
|