@duckcodeailabs/dql-cli 1.10.0 → 1.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/dql-notebook/assets/{index-C5VHd5JR.js → index-DVimJaeG.js} +351 -350
- package/dist/assets/dql-notebook/index.html +1 -1
- package/dist/llm/providers/dql-agent-provider.d.ts.map +1 -1
- package/dist/llm/providers/dql-agent-provider.js +9 -0
- package/dist/llm/providers/dql-agent-provider.js.map +1 -1
- package/dist/llm/types.d.ts +7 -1
- package/dist/llm/types.d.ts.map +1 -1
- package/dist/local-runtime.d.ts +11 -2
- package/dist/local-runtime.d.ts.map +1 -1
- package/dist/local-runtime.js +337 -62
- package/dist/local-runtime.js.map +1 -1
- package/dist/package.json +10 -10
- package/dist/semantic-import.d.ts +6 -0
- package/dist/semantic-import.d.ts.map +1 -1
- package/dist/semantic-import.js +45 -13
- package/dist/semantic-import.js.map +1 -1
- package/dist/semantic-runtime.d.ts +20 -11
- package/dist/semantic-runtime.d.ts.map +1 -1
- package/dist/semantic-runtime.js +76 -37
- package/dist/semantic-runtime.js.map +1 -1
- package/package.json +10 -10
package/dist/local-runtime.js
CHANGED
|
@@ -10,7 +10,7 @@ import { Readable } from "node:stream";
|
|
|
10
10
|
import { buildMixedSourceWarehouseFallbackSql, findMentionedNotebookDataset, planMixedSourceNotebookSql, planMixedSourceSql, } from "./mixed-source-sql.js";
|
|
11
11
|
import { resolveNpmInvocation } from './npm-runtime.js';
|
|
12
12
|
import { buildExecutionPlan, createWelcomeNotebook, deserializeNotebook, getConnectorFormSchemas, hasSemanticRefs, hasStandaloneSemanticRef, resolveSemanticRefs, } from '@duckcodeailabs/dql-notebook';
|
|
13
|
-
import { loadSemanticLayerFromDir, normalizeDqlArtifactReference, serializeMetricDefinitionToYaml, resolveSemanticLayerAsync, resolveRepoSource, getDialect, Parser, NodeKind, blockParameterDefinitions, buildLineageGraph, buildManifest, collectInputFiles, findAppDocuments, findDashboardsForApp, isBlockIdRef, loadAppDocument, loadDashboardDocument, analyzeImpact, buildTrustChain, detectDomainFlows, getDomainTrustOverview, queryLineage, queryBusiness360, queryCompleteLineagePaths, LineageGraph, canonicalize, canonicalizeNotebook, diffDQL, diffNotebook, domainFolderSlug, previewModelingChange, applyModelingChange, previewDbtSourcePatch, applyDbtSourcePatch, loadDomainPackageRegistry, loadDbtNodeAuthoringDetail, relationshipValidationProofFingerprint, discoverDbtDomains, renderDomainDeclaration, ProjectSnapshotService, analyzeSqlReferences, } from '@duckcodeailabs/dql-core';
|
|
13
|
+
import { loadSemanticLayerFromDir, normalizeDqlArtifactReference, serializeMetricDefinitionToYaml, resolveSemanticLayerAsync, resolveRepoSource, getDialect, Parser, NodeKind, blockParameterDefinitions, buildLineageGraph, buildManifest, collectInputFiles, findAppDocuments, findDashboardsForApp, isBlockIdRef, loadAppDocument, loadDashboardDocument, analyzeImpact, buildTrustChain, detectDomainFlows, getDomainTrustOverview, queryLineage, queryBusiness360, queryCompleteLineagePaths, LineageGraph, canonicalize, canonicalizeNotebook, diffDQL, diffNotebook, domainFolderSlug, previewModelingChange, applyModelingChange, previewDbtSourcePatch, applyDbtSourcePatch, loadDomainPackageRegistry, loadDbtNodeAuthoringDetail, normalizeAnalyticalFailureV1, relationshipValidationProofFingerprint, discoverDbtDomains, renderDomainDeclaration, ProjectSnapshotService, analyzeSqlReferences, semanticDimensionReference, } from '@duckcodeailabs/dql-core';
|
|
14
14
|
import { load as loadYaml } from 'js-yaml';
|
|
15
15
|
import { listBlockTemplates } from './block-templates.js';
|
|
16
16
|
import { getRunner as getLLMRunner } from './llm/index.js';
|
|
@@ -19,7 +19,7 @@ import { fetchLatestPublishedDqlVersion, resolveDqlRuntimeVersionStatus } from '
|
|
|
19
19
|
import { resolveRetrievalHealthStatus } from './retrieval-health.js';
|
|
20
20
|
import { createDqlAgentProviderRunner, resolveAgentFollowUpContext } from './llm/providers/dql-agent-provider.js';
|
|
21
21
|
import { listRemoteMcpSettings, saveRemoteMcpSettings } from './llm/mcp-config.js';
|
|
22
|
-
import { ClaudeProvider, ConversationStore, advanceThreadState, buildConversationSnapshot, recallRelevantTurns, GeminiProvider, MemoryStore, OllamaProvider, OpenAIProvider, buildBlockBusinessFingerprint, buildBlockSqlFingerprints, buildAnalysisQuestionPlan, aggregationIntegrityIssuesForSql, buildLocalContextPack, applyContextPackCompatibility, toAgentRetrievalEvidence, defaultConversationPath, defaultMemoryPath, ensureDefaultMemoryFiles, ensureAgentProjectReady, isAgentProjectIndexReady, currentMetadataFingerprint, ensureMetadataCatalogFresh, readIndexedDomainKnowledge, readIndexedKnowledge360, compactSemanticRuntimeFailure, propose, proposePlan, recordCorrectionTrace, emitCorrectionEvalCase, mineJoinPatterns, reviewHint, AgentRunEngine, SqliteAgentRunStore, defaultAgentRunGates, createLlmAgentRunPlanner, createHybridRouter, computeResultStats, buildDeterministicDashboardStory, synthesizeAnswer, streamOrGenerate, narrateResult, buildProposePreview, buildFromPrompt, defaultAgentRunStorePath, defaultAgentRunSqlitePath, resolveLocalOwner, resolveProposeConfig, recordQueryRun, recordRuntimeSchemaSnapshot, latestRuntimeSchemaSnapshotForProject, loadSkills, migrateLegacySkills, configuredSkillsPath, skillsDir, draftDomainSkillBootstrap, buildDomainSkillBootstrapPrompt, mergeDomainSkillBootstrapEnrichment, writeSkill, deleteSkill, deriveGeneratedDraftSlug, reindexProject, invalidateAgentProjectState, recordAgentRuntimeVersion, resolveDomainContextEnvelope, defaultKgPath, planAppFromPrompt, KGStore, planResearch, loadSemanticMetrics, cascadeTraceToEvidenceRouteSteps, createCascadeAnswerResult, createCascadeTrace, routeReasoningEffort, routeForCascadeAnswerTier, clampReasoningEffort, bumpReasoningEffort, resolveThinkingMode, coerceThinkingMode, upsertGeneratedDqlArtifactDraft, loadAgentSemanticLayer, } from '@duckcodeailabs/dql-agent';
|
|
22
|
+
import { ClaudeProvider, ConversationStore, advanceThreadState, buildConversationSnapshot, recallRelevantTurns, GeminiProvider, MemoryStore, OllamaProvider, OpenAIProvider, buildBlockBusinessFingerprint, buildBlockSqlFingerprints, buildAnalysisQuestionPlan, aggregationIntegrityIssuesForSql, buildLocalContextPack, applyContextPackCompatibility, toAgentRetrievalEvidence, defaultConversationPath, defaultMemoryPath, ensureDefaultMemoryFiles, ensureAgentProjectReady, isAgentProjectIndexReady, currentMetadataFingerprint, ensureMetadataCatalogFresh, readIndexedDomainKnowledge, readIndexedKnowledge360, compactSemanticRuntimeFailure, propose, proposePlan, recordCorrectionTrace, emitCorrectionEvalCase, mineJoinPatterns, reviewHint, AgentRunEngine, SqliteAgentRunStore, defaultAgentRunGates, createLlmAgentRunPlanner, createHybridRouter, computeResultStats, buildDeterministicDashboardStory, synthesizeAnswer, streamOrGenerate, narrateResult, buildProposePreview, buildFromPrompt, defaultAgentRunStorePath, defaultAgentRunSqlitePath, resolveLocalOwner, resolveProposeConfig, recordQueryRun, recordRuntimeSchemaSnapshot, latestRuntimeSchemaSnapshotForProject, loadSkills, migrateLegacySkills, configuredSkillsPath, skillsDir, draftDomainSkillBootstrap, buildDomainSkillBootstrapPrompt, mergeDomainSkillBootstrapEnrichment, writeSkill, deleteSkill, deriveGeneratedDraftSlug, deriveAnalyticalRepair, reindexProject, invalidateAgentProjectState, recordAgentRuntimeVersion, resolveDomainContextEnvelope, defaultKgPath, planAppFromPrompt, KGStore, planResearch, loadSemanticMetrics, cascadeTraceToEvidenceRouteSteps, createCascadeAnswerResult, createCascadeTrace, routeReasoningEffort, routeForCascadeAnswerTier, clampReasoningEffort, bumpReasoningEffort, resolveThinkingMode, coerceThinkingMode, upsertGeneratedDqlArtifactDraft, loadAgentSemanticLayer, } from '@duckcodeailabs/dql-agent';
|
|
23
23
|
import { gatherProposeEnrichment } from './propose-enrich.js';
|
|
24
24
|
import { handleAppsApi, proposeAppAiBuild, recommendVisualization } from './apps-api.js';
|
|
25
25
|
import { getActiveProvider, getEffectiveProviderConfig, isProviderSettingsId, listProviderSettings, saveProviderSettings, } from './settings/provider-settings.js';
|
|
@@ -33,7 +33,7 @@ import { buildSemanticObjectDetail, buildSemanticTree, computeSyncDiff, loadSema
|
|
|
33
33
|
import { clearBlockStudioImportSessions, candidateToDqlSource, createBlockStudioImportSession, deleteBlockStudioImportSession, listBlockStudioImportSessions, loadBlockStudioImportSession, readBlockStudioImportCandidate, parameterizeSqlForDqlImport, updateBlockStudioImportCandidate, writeBlockStudioImportSession, writeBlockStudioImportCandidate, } from './block-studio-import.js';
|
|
34
34
|
import { MetricFlowUnavailableError, hasDbtSemanticManifest, } from "./metricflow.js";
|
|
35
35
|
import { ManagedMetricFlowInstaller, isMetricFlowWarehouseAdapter, metricFlowAdapterForDriver, } from './metricflow-installer.js';
|
|
36
|
-
import { compileSemanticRuntimeQuery, describeRuntimeCompatibility, explainMissingSemanticRuntime, getSemanticRuntimeStatus,
|
|
36
|
+
import { compileSemanticRuntimeQuery, describeRuntimeCompatibility, explainMissingSemanticRuntime, getSemanticRuntimeStatus, semanticRuntimeErrorCode, semanticMetricExecutionCapability as runtimeMetricExecutionCapability, SemanticRuntimeRequiredError, testSemanticRuntimeDraft, } from './semantic-runtime.js';
|
|
37
37
|
import { getSemanticRuntimeSettings, saveTestedSemanticRuntimeSettings, saveSemanticRuntimePreference, } from './semantic-runtime-settings.js';
|
|
38
38
|
import { NotebookDatasetWorkspace, } from "./notebook-datasets.js";
|
|
39
39
|
import { prepareBlockInvocation } from './block-invocation.js';
|
|
@@ -279,6 +279,53 @@ export function agentAnswerHasExecutionFailure(governedAnswer) {
|
|
|
279
279
|
return typeof governedAnswer.executionError === 'string'
|
|
280
280
|
&& governedAnswer.executionError.trim().length > 0;
|
|
281
281
|
}
|
|
282
|
+
/** Rebuild the immutable failed-run input from the artifact retained by API-007. */
|
|
283
|
+
export function analyticalFailedRunFromAgentRun(run) {
|
|
284
|
+
for (const artifact of run.artifacts) {
|
|
285
|
+
const payload = agentRunRecord(artifact.payload);
|
|
286
|
+
if (!payload)
|
|
287
|
+
continue;
|
|
288
|
+
const failure = normalizeAnalyticalFailureV1(payload.analyticalFailure);
|
|
289
|
+
const plan = agentRunRecord(payload.resolvedAnalyticalPlan);
|
|
290
|
+
const planFingerprint = agentRunString(plan?.fingerprint) ?? failure?.planFingerprint;
|
|
291
|
+
if (!failure || !planFingerprint)
|
|
292
|
+
continue;
|
|
293
|
+
const dqlArtifact = normalizeDqlArtifactReference(payload.dqlArtifact);
|
|
294
|
+
const dqlSource = dqlArtifact?.source;
|
|
295
|
+
const compiledSql = agentRunString(payload.sql)
|
|
296
|
+
?? agentRunString(payload.proposedSql)
|
|
297
|
+
?? dqlArtifact?.compiledSql;
|
|
298
|
+
const graph = agentRunRecord(payload.analyticalExecutionGraph);
|
|
299
|
+
const rawRoute = agentRunString(graph?.route) ?? agentRunString(plan?.recommendedRoute);
|
|
300
|
+
const route = rawRoute === 'certified'
|
|
301
|
+
? 'certified'
|
|
302
|
+
: rawRoute === 'semantic'
|
|
303
|
+
? 'semantic'
|
|
304
|
+
: rawRoute === 'governed_sql'
|
|
305
|
+
? 'governed_sql'
|
|
306
|
+
: 'generated_sql';
|
|
307
|
+
const rawTrust = dqlArtifact?.trustState ?? agentRunString(payload.reviewStatus) ?? artifact.trustState;
|
|
308
|
+
const trustState = rawTrust === 'certified'
|
|
309
|
+
? 'certified'
|
|
310
|
+
: rawTrust === 'governed'
|
|
311
|
+
? 'governed'
|
|
312
|
+
: 'review_required';
|
|
313
|
+
return {
|
|
314
|
+
version: 1,
|
|
315
|
+
runId: failure.runId,
|
|
316
|
+
snapshotId: failure.snapshotId,
|
|
317
|
+
route,
|
|
318
|
+
trustState,
|
|
319
|
+
planFingerprint,
|
|
320
|
+
...(dqlSource ? { dqlSource } : {}),
|
|
321
|
+
...(failure.dqlFingerprint ? { dqlFingerprint: failure.dqlFingerprint } : {}),
|
|
322
|
+
...(compiledSql ? { compiledSql } : {}),
|
|
323
|
+
...(failure.sqlFingerprint ? { sqlFingerprint: failure.sqlFingerprint } : {}),
|
|
324
|
+
failure,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
return undefined;
|
|
328
|
+
}
|
|
282
329
|
function businessNarrativeGaps(warnings) {
|
|
283
330
|
const businessRelevant = (warnings ?? []).filter((warning) => !(/missing requested output column/i.test(warning)
|
|
284
331
|
|| /\bquery plan\b/i.test(warning)
|
|
@@ -1220,6 +1267,57 @@ export async function startLocalServer(opts) {
|
|
|
1220
1267
|
},
|
|
1221
1268
|
...(semanticDriver ? { semanticDriver } : {}),
|
|
1222
1269
|
...(semanticTableMapping ? { semanticTableMapping } : {}),
|
|
1270
|
+
...(semanticLayer && semanticConnection ? {
|
|
1271
|
+
resolveAnalyticalFreshness: async (freshnessRequest) => {
|
|
1272
|
+
const binding = freshnessRequest.authorizedAdapterRequest;
|
|
1273
|
+
if (!binding || binding.route !== 'semantic') {
|
|
1274
|
+
throw Object.assign(new Error('The analytical freshness request is not bound to an authorized semantic route.'), {
|
|
1275
|
+
code: 'FRESHNESS_BINDING_REQUIRED',
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
if (freshnessRequest.snapshotId !== routeDecision?.resolvedAnalyticalPlan?.snapshotId) {
|
|
1279
|
+
throw Object.assign(new Error('The analytical freshness request does not match the routed metadata snapshot.'), {
|
|
1280
|
+
code: 'SNAPSHOT_DRIFT',
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
projectSnapshot();
|
|
1284
|
+
projectSnapshots.assertCurrent(runProjectSnapshot.snapshotId);
|
|
1285
|
+
const compiled = await compileSemanticRuntimeQuery({
|
|
1286
|
+
metrics: [binding.metric],
|
|
1287
|
+
dimensions: [],
|
|
1288
|
+
timeDimension: {
|
|
1289
|
+
name: binding.timeDimension,
|
|
1290
|
+
granularity: binding.granularity,
|
|
1291
|
+
},
|
|
1292
|
+
orderBy: [{ name: binding.outputField, direction: 'desc' }],
|
|
1293
|
+
limit: 1,
|
|
1294
|
+
}, {
|
|
1295
|
+
projectRoot,
|
|
1296
|
+
projectConfig,
|
|
1297
|
+
detectedProvider: semanticDetectedProvider,
|
|
1298
|
+
semanticLayer: semanticLayer,
|
|
1299
|
+
driver: semanticDriver,
|
|
1300
|
+
tableMapping: semanticTableMapping,
|
|
1301
|
+
});
|
|
1302
|
+
if (!compiled) {
|
|
1303
|
+
throw Object.assign(new Error('The authorized semantic freshness query could not be compiled.'), {
|
|
1304
|
+
code: 'COMPILATION_FAILED',
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
const prepared = prepareLocalExecution(compiled.sql, semanticConnection, projectRoot, projectConfig);
|
|
1308
|
+
const result = await executor.executeQuery(prepared.sql, [], runtimeVariables({}), prepared.connection);
|
|
1309
|
+
projectSnapshot();
|
|
1310
|
+
projectSnapshots.assertCurrent(runProjectSnapshot.snapshotId);
|
|
1311
|
+
return {
|
|
1312
|
+
version: 1,
|
|
1313
|
+
snapshotId: freshnessRequest.snapshotId,
|
|
1314
|
+
metricId: freshnessRequest.metricId,
|
|
1315
|
+
timeDimensionId: freshnessRequest.timeDimensionId,
|
|
1316
|
+
observedThrough: analyticalFreshnessObservedThrough(result, freshnessRequest),
|
|
1317
|
+
observedAt: new Date().toISOString(),
|
|
1318
|
+
};
|
|
1319
|
+
},
|
|
1320
|
+
} : {}),
|
|
1223
1321
|
...(semanticLayer ? {
|
|
1224
1322
|
semanticQueryCompiler: async (selection) => {
|
|
1225
1323
|
const compiled = await compileSemanticRuntimeQuery({
|
|
@@ -1262,6 +1360,7 @@ export async function startLocalServer(opts) {
|
|
|
1262
1360
|
...(routeDecision?.resolvedAnalyticalPlan
|
|
1263
1361
|
? { resolvedAnalyticalPlan: routeDecision.resolvedAnalyticalPlan }
|
|
1264
1362
|
: {}),
|
|
1363
|
+
analyticalReferenceInstant: new Date().toISOString(),
|
|
1265
1364
|
executeCertifiedBlock: executeCertifiedBlockForAgent,
|
|
1266
1365
|
executeGeneratedSql: (sql, artifact) => executeGeneratedArtifactForAgent(request.question, sql, artifact),
|
|
1267
1366
|
executeDqlArtifact: (artifact) => executeArtifactReferenceForAgent(artifact, request.question),
|
|
@@ -1622,6 +1721,8 @@ export async function startLocalServer(opts) {
|
|
|
1622
1721
|
&& (governedAnswer.refusalCode === 'grounding_gap' || governedAnswer.refusalCode === 'modeling_gap')
|
|
1623
1722
|
&& !isExploratory;
|
|
1624
1723
|
const isProviderError = governedAnswer.kind === 'no_answer' && governedAnswer.refusalCode === 'provider_error';
|
|
1724
|
+
const isAnalyticalFailure = Boolean(governedAnswer.analyticalFailure);
|
|
1725
|
+
const isTerminalFailure = isProviderError || isExecutionFailure || isAnalyticalFailure;
|
|
1625
1726
|
// AGT-004: a rejected attribution/export/proof policy is a deliberate
|
|
1626
1727
|
// governance boundary, not an ambiguous user question and not a repairable
|
|
1627
1728
|
// retrieval miss. Keep the precise policy detail visible, but never burn two
|
|
@@ -1681,14 +1782,14 @@ export async function startLocalServer(opts) {
|
|
|
1681
1782
|
synthesizedAnswer = undefined;
|
|
1682
1783
|
}
|
|
1683
1784
|
}
|
|
1684
|
-
const status =
|
|
1785
|
+
const status = isTerminalFailure
|
|
1685
1786
|
? 'blocked'
|
|
1686
1787
|
: needsClarification
|
|
1687
1788
|
? 'needs_clarification'
|
|
1688
1789
|
: isCertified || isSemantic
|
|
1689
1790
|
? 'completed'
|
|
1690
1791
|
: 'needs_review';
|
|
1691
|
-
const trustState =
|
|
1792
|
+
const trustState = isTerminalFailure
|
|
1692
1793
|
? 'blocked'
|
|
1693
1794
|
: needsClarification
|
|
1694
1795
|
? 'not_applicable'
|
|
@@ -1697,7 +1798,7 @@ export async function startLocalServer(opts) {
|
|
|
1697
1798
|
: isSemantic
|
|
1698
1799
|
? 'governed'
|
|
1699
1800
|
: 'review_required';
|
|
1700
|
-
const stopReason =
|
|
1801
|
+
const stopReason = isTerminalFailure
|
|
1701
1802
|
? 'blocked'
|
|
1702
1803
|
: needsClarification
|
|
1703
1804
|
? 'needs_clarification'
|
|
@@ -1706,10 +1807,23 @@ export async function startLocalServer(opts) {
|
|
|
1706
1807
|
: isSemantic
|
|
1707
1808
|
? 'governed_semantic_answer'
|
|
1708
1809
|
: 'human_review_required';
|
|
1810
|
+
const terminalFailureActions = governedAnswer.analyticalFailure
|
|
1811
|
+
? governedAnswer.analyticalFailure.code === 'PERMISSION_DENIED'
|
|
1812
|
+
? [{ id: 'repair-access', label: 'Change connection or request access', route: 'blocked' }]
|
|
1813
|
+
: governedAnswer.analyticalFailure.code === 'TIMEOUT'
|
|
1814
|
+
? [{ id: 'retry-same-plan', label: 'Retry the same bounded plan', route: 'generated_answer' }]
|
|
1815
|
+
: governedAnswer.analyticalFailure.code === 'SNAPSHOT_DRIFT'
|
|
1816
|
+
|| governedAnswer.analyticalFailure.code === 'COLUMN_NOT_FOUND'
|
|
1817
|
+
|| governedAnswer.analyticalFailure.code === 'RELATION_NOT_FOUND'
|
|
1818
|
+
? [{ id: 'refresh-snapshot', label: 'Refresh the governed snapshot before retrying', route: 'blocked' }]
|
|
1819
|
+
: [{ id: 'review-analytical-failure', label: 'Review the plan, DQL, SQL, and safe repair actions', route: 'blocked' }]
|
|
1820
|
+
: isProviderError
|
|
1821
|
+
? [{ id: 'retry-after-provider', label: 'Retry after fixing the AI provider', route: 'generated_answer' }]
|
|
1822
|
+
: [{ id: 'retry-after-connection', label: 'Retry after fixing the database connection', route: 'generated_answer' }];
|
|
1709
1823
|
const nextActions = needsClarification
|
|
1710
1824
|
? [{ id: 'clarify', label: 'Clarify question', route: 'generated_answer' }]
|
|
1711
|
-
:
|
|
1712
|
-
?
|
|
1825
|
+
: isTerminalFailure
|
|
1826
|
+
? terminalFailureActions
|
|
1713
1827
|
: isGroundingGap
|
|
1714
1828
|
? [{ id: 'research-gap', label: 'Research missing metadata coverage', route: 'research', artifactKind: 'research_run' }]
|
|
1715
1829
|
: [
|
|
@@ -1726,15 +1840,15 @@ export async function startLocalServer(opts) {
|
|
|
1726
1840
|
resolvedRoute,
|
|
1727
1841
|
answerRefusalCode: governedAnswer.kind === 'no_answer' ? governedAnswer.refusalCode : undefined,
|
|
1728
1842
|
answerTier: governedAnswer.route?.tier,
|
|
1729
|
-
summary:
|
|
1843
|
+
summary: isTerminalFailure
|
|
1730
1844
|
? 'The governed query could not be executed.'
|
|
1731
1845
|
: governedAnswer.route?.label ?? (isCertified ? 'Answered from certified DQL context.' : isExploratory ? 'Exploratory DBT-grounded analysis requires review.' : 'Answered with review-required generated analysis.'),
|
|
1732
1846
|
answer: synthesizedAnswer ?? governedAnswer.answer ?? governedAnswer.text,
|
|
1733
1847
|
status,
|
|
1734
1848
|
trustState,
|
|
1735
1849
|
stopReason,
|
|
1736
|
-
artifacts:
|
|
1737
|
-
? []
|
|
1850
|
+
artifacts: isTerminalFailure
|
|
1851
|
+
? [agentRunArtifact('answer', 'Failed governed analytical run', governedAnswer, governedAnswer.sourceCertifiedBlock ?? governedAnswer.block?.name, 'blocked')]
|
|
1738
1852
|
: governedAnswer.kind === 'no_answer'
|
|
1739
1853
|
// A refusal still keeps the DQL draft the answer loop produced (when any),
|
|
1740
1854
|
// so the "Review DQL draft" next-action isn't a dead link and the user can
|
|
@@ -2415,6 +2529,8 @@ export async function startLocalServer(opts) {
|
|
|
2415
2529
|
snapshotId: pack.knowledgeLens.snapshotId,
|
|
2416
2530
|
sourceFingerprint: pack.freshness.fingerprint ?? undefined,
|
|
2417
2531
|
knowledgeLens: pack.knowledgeLens,
|
|
2532
|
+
analyticalPolicies: pack.skills.flatMap((skill) => skill.analyticalPolicy ? [skill.analyticalPolicy] : []),
|
|
2533
|
+
contextObjects: pack.objects,
|
|
2418
2534
|
durationMs: Date.now() - startedAt,
|
|
2419
2535
|
truncated: pack.retrievalDiagnostics.topRejected.length > 0,
|
|
2420
2536
|
});
|
|
@@ -2566,7 +2682,6 @@ export async function startLocalServer(opts) {
|
|
|
2566
2682
|
const resolved = resolveNotebookBlockReferenceCell(cell, projectRoot);
|
|
2567
2683
|
const activeConnection = requireActiveConnection();
|
|
2568
2684
|
const tableMapping = await resolveSemanticTableMapping(executor, activeConnection, semanticLayer);
|
|
2569
|
-
const plan = buildExecutionPlan(resolved.cell, { semanticLayer, driver: activeConnection.driver, tableMapping });
|
|
2570
2685
|
const semanticCompose = resolved.cell.type === 'dql'
|
|
2571
2686
|
&& semanticLayer
|
|
2572
2687
|
&& /\btype\s*=\s*"semantic"/i.test(resolved.cell.source)
|
|
@@ -2581,6 +2696,12 @@ export async function startLocalServer(opts) {
|
|
|
2581
2696
|
if (semanticCompose && !semanticCompose.sql) {
|
|
2582
2697
|
throw new Error(semanticCompose.diagnostics.map((diagnostic) => diagnostic.message).join(' '));
|
|
2583
2698
|
}
|
|
2699
|
+
const plan = buildExecutionPlan(resolved.cell, {
|
|
2700
|
+
semanticLayer,
|
|
2701
|
+
driver: activeConnection.driver,
|
|
2702
|
+
tableMapping,
|
|
2703
|
+
semanticSql: semanticCompose?.sql ?? undefined,
|
|
2704
|
+
});
|
|
2584
2705
|
const executableSql = semanticCompose?.sql ?? plan?.sql;
|
|
2585
2706
|
if (!executableSql) {
|
|
2586
2707
|
snapshotCells.push({ cellId, status: 'idle', executionCount: 0, executedAt });
|
|
@@ -2646,7 +2767,6 @@ export async function startLocalServer(opts) {
|
|
|
2646
2767
|
}
|
|
2647
2768
|
const activeConnection = requireActiveConnection();
|
|
2648
2769
|
const tableMapping = await resolveSemanticTableMapping(executor, activeConnection, semanticLayer);
|
|
2649
|
-
const plan = buildExecutionPlan({ id: `agent-${metadata.name ?? 'dql-artifact'}`, type: 'dql', source, title: metadata.name ?? 'DQL artifact' }, { semanticLayer, driver: activeConnection.driver, tableMapping, parameters: invocation.values });
|
|
2650
2770
|
const semanticCompose = semanticLayer
|
|
2651
2771
|
? await composeSemanticBlockSqlForRuntime(source, semanticLayer, {
|
|
2652
2772
|
driver: activeConnection.driver,
|
|
@@ -2657,6 +2777,13 @@ export async function startLocalServer(opts) {
|
|
|
2657
2777
|
parameters: invocation.values,
|
|
2658
2778
|
})
|
|
2659
2779
|
: null;
|
|
2780
|
+
const plan = buildExecutionPlan({ id: `agent-${metadata.name ?? 'dql-artifact'}`, type: 'dql', source, title: metadata.name ?? 'DQL artifact' }, {
|
|
2781
|
+
semanticLayer,
|
|
2782
|
+
driver: activeConnection.driver,
|
|
2783
|
+
tableMapping,
|
|
2784
|
+
parameters: invocation.values,
|
|
2785
|
+
semanticSql: semanticCompose?.sql ?? undefined,
|
|
2786
|
+
});
|
|
2660
2787
|
if (!plan && !semanticCompose?.sql) {
|
|
2661
2788
|
const semanticError = semanticCompose?.diagnostics.find((diagnostic) => diagnostic.severity === 'error')?.message;
|
|
2662
2789
|
throw new Error(semanticError ?? `DQL artifact "${metadata.name ?? 'draft'}" produced no executable SQL.`);
|
|
@@ -3724,7 +3851,12 @@ export async function startLocalServer(opts) {
|
|
|
3724
3851
|
?? 'No executable SQL found in block source.';
|
|
3725
3852
|
throw new Error(message);
|
|
3726
3853
|
}
|
|
3727
|
-
const plan = buildExecutionPlan({ id: 'block-studio', type: 'dql', source, title: 'Block Studio' }, {
|
|
3854
|
+
const plan = buildExecutionPlan({ id: 'block-studio', type: 'dql', source, title: 'Block Studio' }, {
|
|
3855
|
+
semanticLayer,
|
|
3856
|
+
driver: activeConnection.driver,
|
|
3857
|
+
tableMapping,
|
|
3858
|
+
semanticSql: semanticCompose?.sql ?? undefined,
|
|
3859
|
+
});
|
|
3728
3860
|
const prepared = prepareLocalExecution(semanticCompose?.sql ?? plan?.sql ?? executableSql, activeConnection, projectRoot, projectConfig);
|
|
3729
3861
|
const result = await executor.executeQuery(prepared.sql, plan?.sqlParams ?? [], runtimeVariables({ ...(plan?.variables ?? {}), ...invocation.values }), prepared.connection);
|
|
3730
3862
|
return {
|
|
@@ -3738,7 +3870,24 @@ export async function startLocalServer(opts) {
|
|
|
3738
3870
|
const activeConnection = requireActiveConnection(targetConnection);
|
|
3739
3871
|
const start = Date.now();
|
|
3740
3872
|
const tableMapping = await resolveSemanticTableMapping(executor, activeConnection, semanticLayer);
|
|
3741
|
-
|
|
3873
|
+
// Run tests against the SAME SQL the preview runs: for a semantic block with a
|
|
3874
|
+
// pre-compiled query, that's the query (not a recompiled metric), so the test's
|
|
3875
|
+
// output columns match the block's declared outputs.
|
|
3876
|
+
const semanticCompose = semanticLayer
|
|
3877
|
+
? await composeSemanticBlockSqlForRuntime(source, semanticLayer, {
|
|
3878
|
+
driver: activeConnection.driver,
|
|
3879
|
+
tableMapping,
|
|
3880
|
+
projectRoot,
|
|
3881
|
+
projectConfig,
|
|
3882
|
+
detectedProvider: semanticDetectedProvider,
|
|
3883
|
+
})
|
|
3884
|
+
: null;
|
|
3885
|
+
const plan = buildExecutionPlan({ id: 'block-studio-tests', type: 'dql', source, title: 'Block Studio' }, {
|
|
3886
|
+
semanticLayer,
|
|
3887
|
+
driver: activeConnection.driver,
|
|
3888
|
+
tableMapping,
|
|
3889
|
+
semanticSql: semanticCompose?.sql ?? undefined,
|
|
3890
|
+
});
|
|
3742
3891
|
const tests = plan?.tests ?? [];
|
|
3743
3892
|
if (!plan || !plan.sql) {
|
|
3744
3893
|
return {
|
|
@@ -3749,7 +3898,8 @@ export async function startLocalServer(opts) {
|
|
|
3749
3898
|
assertions: [{
|
|
3750
3899
|
name: 'build execution plan',
|
|
3751
3900
|
passed: false,
|
|
3752
|
-
error:
|
|
3901
|
+
error: semanticCompose?.diagnostics.find((diagnostic) => diagnostic.severity === 'error')?.message
|
|
3902
|
+
?? 'Could not build an execution plan for this block.',
|
|
3753
3903
|
}],
|
|
3754
3904
|
runAt: new Date(),
|
|
3755
3905
|
};
|
|
@@ -3757,18 +3907,6 @@ export async function startLocalServer(opts) {
|
|
|
3757
3907
|
if (tests.length === 0) {
|
|
3758
3908
|
return { passed: 0, failed: 0, skipped: 0, duration: Date.now() - start, assertions: [], runAt: new Date() };
|
|
3759
3909
|
}
|
|
3760
|
-
// Run tests against the SAME SQL the preview runs: for a semantic block with a
|
|
3761
|
-
// pre-compiled query, that's the query (not a recompiled metric), so the test's
|
|
3762
|
-
// output columns match the block's declared outputs.
|
|
3763
|
-
const semanticCompose = semanticLayer
|
|
3764
|
-
? await composeSemanticBlockSqlForRuntime(source, semanticLayer, {
|
|
3765
|
-
driver: activeConnection.driver,
|
|
3766
|
-
tableMapping,
|
|
3767
|
-
projectRoot,
|
|
3768
|
-
projectConfig,
|
|
3769
|
-
detectedProvider: semanticDetectedProvider,
|
|
3770
|
-
})
|
|
3771
|
-
: null;
|
|
3772
3910
|
const prepared = prepareLocalExecution(semanticCompose?.sql ?? plan.sql, activeConnection, projectRoot, projectConfig);
|
|
3773
3911
|
const rawResult = await executor.executeQuery(prepared.sql, plan.sqlParams ?? [], runtimeVariables(plan.variables ?? {}), prepared.connection);
|
|
3774
3912
|
const rows = Array.isArray(rawResult?.rows) ? rawResult.rows : [];
|
|
@@ -4808,6 +4946,50 @@ export async function startLocalServer(opts) {
|
|
|
4808
4946
|
res.end(serializeJSON({ runs, total: agentRunStore.list().length, limit }));
|
|
4809
4947
|
return;
|
|
4810
4948
|
}
|
|
4949
|
+
if (req.method === 'POST' && /^\/api\/agent-runs\/[^/]+\/analytical-repair$/.test(path)) {
|
|
4950
|
+
const match = path.match(/^\/api\/agent-runs\/([^/]+)\/analytical-repair$/);
|
|
4951
|
+
const id = decodeURIComponent(match?.[1] ?? '');
|
|
4952
|
+
const run = await agentRunStore.get(id);
|
|
4953
|
+
if (!run) {
|
|
4954
|
+
res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
4955
|
+
res.end(serializeJSON({ error: 'Agent run not found.' }));
|
|
4956
|
+
return;
|
|
4957
|
+
}
|
|
4958
|
+
const source = analyticalFailedRunFromAgentRun(run);
|
|
4959
|
+
if (!source) {
|
|
4960
|
+
res.writeHead(409, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
4961
|
+
res.end(serializeJSON({ error: 'This run has no retained analytical failure artifact.' }));
|
|
4962
|
+
return;
|
|
4963
|
+
}
|
|
4964
|
+
const body = await readJSON(req).catch(() => null);
|
|
4965
|
+
const record = agentRunRecord(body);
|
|
4966
|
+
const repair = (agentRunRecord(record?.repair) ?? record);
|
|
4967
|
+
if (!repair) {
|
|
4968
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
4969
|
+
res.end(serializeJSON({ error: 'A v1 analytical repair request is required.' }));
|
|
4970
|
+
return;
|
|
4971
|
+
}
|
|
4972
|
+
// API-007: snapshot refresh is resolved by the runtime, not trusted from
|
|
4973
|
+
// browser input. projectSnapshot() recompiles when governed sources have
|
|
4974
|
+
// changed and gives the immutable identity used by the derivation.
|
|
4975
|
+
const repairWithRuntimeContext = repair.action === 'refresh_snapshot'
|
|
4976
|
+
? { ...repair, refreshedSnapshotId: projectSnapshot().snapshotId }
|
|
4977
|
+
: repair;
|
|
4978
|
+
const result = deriveAnalyticalRepair(source, repairWithRuntimeContext);
|
|
4979
|
+
if (result.status === 'blocked') {
|
|
4980
|
+
const status = result.code === 'SOURCE_FINGERPRINT_MISMATCH'
|
|
4981
|
+
? 409
|
|
4982
|
+
: result.code === 'PERMISSION_FAILURE_TERMINAL' || result.code === 'ACTION_NOT_ALLOWED'
|
|
4983
|
+
? 403
|
|
4984
|
+
: 400;
|
|
4985
|
+
res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
4986
|
+
res.end(serializeJSON(result));
|
|
4987
|
+
return;
|
|
4988
|
+
}
|
|
4989
|
+
res.writeHead(201, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
4990
|
+
res.end(serializeJSON(result));
|
|
4991
|
+
return;
|
|
4992
|
+
}
|
|
4811
4993
|
if (req.method === 'POST' && /^\/api\/agent-runs\/[^/]+\/cancel$/.test(path)) {
|
|
4812
4994
|
const match = path.match(/^\/api\/agent-runs\/([^/]+)\/cancel$/);
|
|
4813
4995
|
const id = decodeURIComponent(match?.[1] ?? '');
|
|
@@ -6318,7 +6500,13 @@ export async function startLocalServer(opts) {
|
|
|
6318
6500
|
parameters: invocation.values,
|
|
6319
6501
|
})
|
|
6320
6502
|
: null;
|
|
6321
|
-
const plan = buildExecutionPlan({ id: item.i, type: 'dql', source, title: item.title ?? block.name }, {
|
|
6503
|
+
const plan = buildExecutionPlan({ id: item.i, type: 'dql', source, title: item.title ?? block.name }, {
|
|
6504
|
+
semanticLayer,
|
|
6505
|
+
driver: targetConnection.driver,
|
|
6506
|
+
tableMapping,
|
|
6507
|
+
parameters: invocation.values,
|
|
6508
|
+
semanticSql: semanticCompose?.sql ?? undefined,
|
|
6509
|
+
});
|
|
6322
6510
|
if (!plan && !semanticCompose?.sql) {
|
|
6323
6511
|
tiles.push({
|
|
6324
6512
|
tileId: item.i,
|
|
@@ -9234,7 +9422,7 @@ export async function startLocalServer(opts) {
|
|
|
9234
9422
|
owner: m.owner ?? null,
|
|
9235
9423
|
source: m.source ?? null,
|
|
9236
9424
|
}));
|
|
9237
|
-
const dimensions = semanticLayer.listDimensions().map((d) => ({
|
|
9425
|
+
const dimensions = semanticLayer.listDimensions(undefined, { includeVariants: true }).map((d) => ({
|
|
9238
9426
|
name: d.name,
|
|
9239
9427
|
label: d.label,
|
|
9240
9428
|
description: d.description,
|
|
@@ -9245,11 +9433,15 @@ export async function startLocalServer(opts) {
|
|
|
9245
9433
|
tags: d.tags ?? [],
|
|
9246
9434
|
owner: d.owner ?? null,
|
|
9247
9435
|
cube: d.cube ?? null,
|
|
9436
|
+
reference: semanticDimensionReference(d),
|
|
9437
|
+
qualifiedName: d.qualifiedName ?? null,
|
|
9438
|
+
entityLink: d.entityLink ?? null,
|
|
9439
|
+
canonicalId: `semantic:${d.domain || 'uncategorized'}:dimension:${semanticDimensionReference(d)}`,
|
|
9248
9440
|
isTimeDimension: d.isTimeDimension ?? false,
|
|
9249
9441
|
typeParams: d.typeParams ?? null,
|
|
9250
9442
|
source: d.source ?? null,
|
|
9251
9443
|
}));
|
|
9252
|
-
const timeDimensions = semanticLayer.listTimeDimensions().map((d) => ({
|
|
9444
|
+
const timeDimensions = semanticLayer.listTimeDimensions(undefined, { includeVariants: true }).map((d) => ({
|
|
9253
9445
|
name: d.name,
|
|
9254
9446
|
label: d.label,
|
|
9255
9447
|
description: d.description,
|
|
@@ -9258,6 +9450,10 @@ export async function startLocalServer(opts) {
|
|
|
9258
9450
|
type: d.type,
|
|
9259
9451
|
table: d.table,
|
|
9260
9452
|
cube: d.cube ?? null,
|
|
9453
|
+
reference: semanticDimensionReference(d),
|
|
9454
|
+
qualifiedName: d.qualifiedName ?? null,
|
|
9455
|
+
entityLink: d.entityLink ?? null,
|
|
9456
|
+
canonicalId: `semantic:${d.domain || 'uncategorized'}:dimension:${semanticDimensionReference(d)}`,
|
|
9261
9457
|
granularities: d.granularities ?? [],
|
|
9262
9458
|
primaryTime: d.primaryTime ?? false,
|
|
9263
9459
|
tags: d.tags ?? [],
|
|
@@ -9604,10 +9800,15 @@ export async function startLocalServer(opts) {
|
|
|
9604
9800
|
sql: d.sql,
|
|
9605
9801
|
type: d.type,
|
|
9606
9802
|
table: d.table,
|
|
9803
|
+
cube: d.cube ?? null,
|
|
9804
|
+
reference: semanticDimensionReference(d),
|
|
9805
|
+
qualifiedName: d.qualifiedName ?? null,
|
|
9806
|
+
entityLink: d.entityLink ?? null,
|
|
9807
|
+
canonicalId: `semantic:${d.domain || 'uncategorized'}:dimension:${semanticDimensionReference(d)}`,
|
|
9607
9808
|
tags: d.tags ?? [],
|
|
9608
9809
|
owner: d.owner ?? null,
|
|
9609
9810
|
})),
|
|
9610
|
-
timeDimensions: semanticLayer.listTimeDimensions().filter((d) => results.dimensions.some((dim) => dim
|
|
9811
|
+
timeDimensions: semanticLayer.listTimeDimensions(undefined, { includeVariants: true }).filter((d) => results.dimensions.some((dim) => semanticDimensionReference(dim) === semanticDimensionReference(d))).map((d) => ({
|
|
9611
9812
|
name: d.name,
|
|
9612
9813
|
label: d.label,
|
|
9613
9814
|
description: d.description,
|
|
@@ -9615,6 +9816,11 @@ export async function startLocalServer(opts) {
|
|
|
9615
9816
|
sql: d.sql,
|
|
9616
9817
|
type: d.type,
|
|
9617
9818
|
table: d.table,
|
|
9819
|
+
cube: d.cube ?? null,
|
|
9820
|
+
reference: semanticDimensionReference(d),
|
|
9821
|
+
qualifiedName: d.qualifiedName ?? null,
|
|
9822
|
+
entityLink: d.entityLink ?? null,
|
|
9823
|
+
canonicalId: `semantic:${d.domain || 'uncategorized'}:dimension:${semanticDimensionReference(d)}`,
|
|
9618
9824
|
tags: d.tags ?? [],
|
|
9619
9825
|
owner: d.owner ?? null,
|
|
9620
9826
|
})),
|
|
@@ -9677,6 +9883,10 @@ export async function startLocalServer(opts) {
|
|
|
9677
9883
|
table: d.table,
|
|
9678
9884
|
tags: d.tags ?? [],
|
|
9679
9885
|
owner: d.owner ?? null,
|
|
9886
|
+
cube: d.cube ?? null,
|
|
9887
|
+
reference: semanticDimensionReference(d),
|
|
9888
|
+
entityLink: d.entityLink ?? null,
|
|
9889
|
+
canonicalId: `semantic:${d.domain || 'uncategorized'}:dimension:${semanticDimensionReference(d)}`,
|
|
9680
9890
|
// Additive: qualified name, real grains, and time flag (existing fields
|
|
9681
9891
|
// above are byte-identical for compatibility).
|
|
9682
9892
|
qualifiedName: d.qualifiedName ?? null,
|
|
@@ -9768,16 +9978,16 @@ export async function startLocalServer(opts) {
|
|
|
9768
9978
|
});
|
|
9769
9979
|
}
|
|
9770
9980
|
if (semanticLayer && (kind === "all" || kind === "dimension")) {
|
|
9771
|
-
for (const dimension of semanticLayer.listDimensions())
|
|
9981
|
+
for (const dimension of semanticLayer.listDimensions(undefined, { includeVariants: true }))
|
|
9772
9982
|
candidates.push({
|
|
9773
9983
|
type: "dimension",
|
|
9774
|
-
name: dimension
|
|
9984
|
+
name: semanticDimensionReference(dimension),
|
|
9775
9985
|
label: dimension.label,
|
|
9776
9986
|
description: dimension.description ?? "",
|
|
9777
9987
|
domain: dimension.domain,
|
|
9778
9988
|
tags: dimension.tags ?? [],
|
|
9779
|
-
detail:
|
|
9780
|
-
apply: dimension
|
|
9989
|
+
detail: `${dimension.cube ?? dimension.table} · @dim`,
|
|
9990
|
+
apply: semanticDimensionReference(dimension),
|
|
9781
9991
|
governance: "semantic",
|
|
9782
9992
|
});
|
|
9783
9993
|
}
|
|
@@ -10243,13 +10453,13 @@ export async function startLocalServer(opts) {
|
|
|
10243
10453
|
res.end(serializeJSON({ error: error.message, code: 'unauthorized' }));
|
|
10244
10454
|
return;
|
|
10245
10455
|
}
|
|
10246
|
-
const
|
|
10247
|
-
const status =
|
|
10456
|
+
const runtimeCode = semanticRuntimeErrorCode(error);
|
|
10457
|
+
const status = runtimeCode ? 400 : 500;
|
|
10248
10458
|
res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
10249
10459
|
res.end(serializeJSON({
|
|
10250
10460
|
error: error instanceof Error ? error.message : String(error),
|
|
10251
|
-
code:
|
|
10252
|
-
hint:
|
|
10461
|
+
code: runtimeCode,
|
|
10462
|
+
hint: runtimeCode === 'SEMANTIC_RUNTIME_REQUIRED'
|
|
10253
10463
|
? 'Configure dbt Cloud Semantic Layer in Project & dbt settings, or install a compatible local MetricFlow runtime.'
|
|
10254
10464
|
: undefined,
|
|
10255
10465
|
}));
|
|
@@ -10314,13 +10524,13 @@ export async function startLocalServer(opts) {
|
|
|
10314
10524
|
}));
|
|
10315
10525
|
}
|
|
10316
10526
|
catch (error) {
|
|
10317
|
-
const
|
|
10318
|
-
const status =
|
|
10527
|
+
const runtimeCode = semanticRuntimeErrorCode(error);
|
|
10528
|
+
const status = runtimeCode ? 400 : 500;
|
|
10319
10529
|
res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
10320
10530
|
res.end(serializeJSON({
|
|
10321
10531
|
error: error instanceof Error ? error.message : String(error),
|
|
10322
|
-
code:
|
|
10323
|
-
hint:
|
|
10532
|
+
code: runtimeCode,
|
|
10533
|
+
hint: runtimeCode === 'SEMANTIC_RUNTIME_REQUIRED'
|
|
10324
10534
|
? 'Configure dbt Cloud Semantic Layer in Project & dbt settings, or install a compatible local MetricFlow runtime.'
|
|
10325
10535
|
: undefined,
|
|
10326
10536
|
}));
|
|
@@ -10389,13 +10599,13 @@ export async function startLocalServer(opts) {
|
|
|
10389
10599
|
res.end(serializeJSON({ error: 'Block already exists' }));
|
|
10390
10600
|
return;
|
|
10391
10601
|
}
|
|
10392
|
-
const
|
|
10393
|
-
const status =
|
|
10602
|
+
const runtimeCode = semanticRuntimeErrorCode(error);
|
|
10603
|
+
const status = runtimeCode ? 400 : 500;
|
|
10394
10604
|
res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
10395
10605
|
res.end(serializeJSON({
|
|
10396
10606
|
error: error instanceof Error ? error.message : String(error),
|
|
10397
|
-
code:
|
|
10398
|
-
hint:
|
|
10607
|
+
code: runtimeCode,
|
|
10608
|
+
hint: runtimeCode === 'SEMANTIC_RUNTIME_REQUIRED'
|
|
10399
10609
|
? 'Configure dbt Cloud Semantic Layer in Project & dbt settings, or install a compatible local MetricFlow runtime.'
|
|
10400
10610
|
: undefined,
|
|
10401
10611
|
}));
|
|
@@ -10846,12 +11056,6 @@ export async function startLocalServer(opts) {
|
|
|
10846
11056
|
const tableMapping = needsSemanticTableMapping(executableCell)
|
|
10847
11057
|
? await resolveSemanticTableMapping(executor, cellConnection, semanticLayer)
|
|
10848
11058
|
: undefined;
|
|
10849
|
-
const plan = buildExecutionPlan(executableCell, {
|
|
10850
|
-
semanticLayer,
|
|
10851
|
-
driver: cellConnection.driver,
|
|
10852
|
-
tableMapping,
|
|
10853
|
-
parameters: invocation?.values,
|
|
10854
|
-
});
|
|
10855
11059
|
const semanticCompose = executableCell.type === 'dql'
|
|
10856
11060
|
&& semanticLayer
|
|
10857
11061
|
&& /\btype\s*=\s*"semantic"/i.test(executableCell.source)
|
|
@@ -10867,6 +11071,13 @@ export async function startLocalServer(opts) {
|
|
|
10867
11071
|
if (semanticCompose && !semanticCompose.sql) {
|
|
10868
11072
|
throw new Error(semanticCompose.diagnostics.map((diagnostic) => diagnostic.message).join(' '));
|
|
10869
11073
|
}
|
|
11074
|
+
const plan = buildExecutionPlan(executableCell, {
|
|
11075
|
+
semanticLayer,
|
|
11076
|
+
driver: cellConnection.driver,
|
|
11077
|
+
tableMapping,
|
|
11078
|
+
parameters: invocation?.values,
|
|
11079
|
+
semanticSql: semanticCompose?.sql ?? undefined,
|
|
11080
|
+
});
|
|
10870
11081
|
const executableSql = semanticCompose?.sql ?? plan?.sql;
|
|
10871
11082
|
if (!executableSql) {
|
|
10872
11083
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
@@ -11462,6 +11673,48 @@ export function serializeJSON(value) {
|
|
|
11462
11673
|
return current;
|
|
11463
11674
|
});
|
|
11464
11675
|
}
|
|
11676
|
+
/**
|
|
11677
|
+
* Extract the single time value returned by an authorized semantic freshness
|
|
11678
|
+
* lookup. Adapter aliases may differ by warehouse, so typed date metadata is
|
|
11679
|
+
* the only fallback; arbitrary string columns are never treated as freshness.
|
|
11680
|
+
* Acceptance: AGT-019, SEC-004.
|
|
11681
|
+
*/
|
|
11682
|
+
export function analyticalFreshnessObservedThrough(result, request) {
|
|
11683
|
+
const binding = request.authorizedAdapterRequest;
|
|
11684
|
+
if (!binding || binding.route !== 'semantic') {
|
|
11685
|
+
throw Object.assign(new Error('The freshness lookup has no authorized semantic adapter binding.'), {
|
|
11686
|
+
code: 'FRESHNESS_BINDING_REQUIRED',
|
|
11687
|
+
});
|
|
11688
|
+
}
|
|
11689
|
+
const row = result.rows[0];
|
|
11690
|
+
if (!row) {
|
|
11691
|
+
throw Object.assign(new Error('The authorized freshness lookup returned no rows.'), {
|
|
11692
|
+
code: 'FRESHNESS_OBSERVATION_MISSING',
|
|
11693
|
+
});
|
|
11694
|
+
}
|
|
11695
|
+
const normalize = (value) => value.replace(/[^a-zA-Z0-9]+/g, '_').toLowerCase();
|
|
11696
|
+
const target = normalize(binding.outputField);
|
|
11697
|
+
const exact = result.columns.find((column) => normalize(column.name) === target);
|
|
11698
|
+
const typedDates = result.columns.filter((column) => column.type === 'date' || column.type === 'datetime');
|
|
11699
|
+
const column = exact ?? (typedDates.length === 1 ? typedDates[0] : undefined);
|
|
11700
|
+
if (!column) {
|
|
11701
|
+
throw Object.assign(new Error('The authorized freshness lookup did not return one identifiable time field.'), {
|
|
11702
|
+
code: 'FRESHNESS_OBSERVATION_INVALID',
|
|
11703
|
+
});
|
|
11704
|
+
}
|
|
11705
|
+
const value = row[column.name];
|
|
11706
|
+
const instant = value instanceof Date
|
|
11707
|
+
? value
|
|
11708
|
+
: typeof value === 'string' && value.trim()
|
|
11709
|
+
? new Date(value)
|
|
11710
|
+
: undefined;
|
|
11711
|
+
if (!instant || Number.isNaN(instant.getTime())) {
|
|
11712
|
+
throw Object.assign(new Error('The authorized freshness value is not a valid instant.'), {
|
|
11713
|
+
code: 'FRESHNESS_OBSERVATION_INVALID',
|
|
11714
|
+
});
|
|
11715
|
+
}
|
|
11716
|
+
return instant.toISOString();
|
|
11717
|
+
}
|
|
11465
11718
|
/** Serialize a Skill to the shared API contract shape (spec 16). */
|
|
11466
11719
|
function serializeSkill(skill) {
|
|
11467
11720
|
return {
|
|
@@ -14457,7 +14710,10 @@ function composeSemanticBlockSql(source, semanticLayer, options) {
|
|
|
14457
14710
|
: [];
|
|
14458
14711
|
const semanticRefs = {
|
|
14459
14712
|
metrics,
|
|
14460
|
-
dimensions:
|
|
14713
|
+
dimensions: Array.from(new Set([
|
|
14714
|
+
...config.dimensions,
|
|
14715
|
+
...(config.timeDimension ? [config.timeDimension] : []),
|
|
14716
|
+
])),
|
|
14461
14717
|
segments: [],
|
|
14462
14718
|
};
|
|
14463
14719
|
const diagnostics = [];
|
|
@@ -14479,7 +14735,11 @@ function composeSemanticBlockSql(source, semanticLayer, options) {
|
|
|
14479
14735
|
message: `Semantic block selects time_dimension = "${config.timeDimension}" but is missing granularity.`,
|
|
14480
14736
|
});
|
|
14481
14737
|
}
|
|
14482
|
-
const refValidation = semanticLayer.validateReferences([
|
|
14738
|
+
const refValidation = semanticLayer.validateReferences([
|
|
14739
|
+
...metrics,
|
|
14740
|
+
...config.dimensions,
|
|
14741
|
+
...(config.timeDimension ? [config.timeDimension] : []),
|
|
14742
|
+
]);
|
|
14483
14743
|
for (const unknown of refValidation.unknown) {
|
|
14484
14744
|
diagnostics.push({
|
|
14485
14745
|
severity: 'error',
|
|
@@ -14568,13 +14828,18 @@ function composeSemanticBlockSql(source, semanticLayer, options) {
|
|
|
14568
14828
|
*/
|
|
14569
14829
|
async function composeSemanticBlockSqlForRuntime(source, semanticLayer, options) {
|
|
14570
14830
|
const native = composeSemanticBlockSql(source, semanticLayer, options);
|
|
14571
|
-
if (native.sql)
|
|
14572
|
-
return native;
|
|
14573
14831
|
const config = parseSemanticBlockConfig(source);
|
|
14574
14832
|
if (config.blockType !== 'semantic')
|
|
14575
14833
|
return native;
|
|
14834
|
+
// An authored/precompiled query is already the governed artifact payload.
|
|
14835
|
+
// Keep that contract intact rather than sending it through a second compiler.
|
|
14836
|
+
if (extractBlockStudioSql(source))
|
|
14837
|
+
return native;
|
|
14576
14838
|
const metrics = config.metrics.length > 0 ? config.metrics : config.metric ? [config.metric] : [];
|
|
14577
|
-
if (metrics.length === 0
|
|
14839
|
+
if (metrics.length === 0
|
|
14840
|
+
|| native.diagnostics.some((diagnostic) => diagnostic.code === 'semantic_ref'
|
|
14841
|
+
|| diagnostic.code === 'semantic_metric_missing'
|
|
14842
|
+
|| diagnostic.code === 'semantic_granularity_missing'))
|
|
14578
14843
|
return native;
|
|
14579
14844
|
const runtimeBindings = semanticRuntimeParameterBindings(source, options.parameters ?? {});
|
|
14580
14845
|
try {
|
|
@@ -14593,10 +14858,18 @@ async function composeSemanticBlockSqlForRuntime(source, semanticLayer, options)
|
|
|
14593
14858
|
driver: options.driver,
|
|
14594
14859
|
tableMapping: options.tableMapping,
|
|
14595
14860
|
});
|
|
14596
|
-
if (!compiled)
|
|
14597
|
-
return
|
|
14861
|
+
if (!compiled) {
|
|
14862
|
+
return {
|
|
14863
|
+
sql: null,
|
|
14864
|
+
diagnostics: native.diagnostics,
|
|
14865
|
+
semanticRefs: native.semanticRefs,
|
|
14866
|
+
};
|
|
14867
|
+
}
|
|
14598
14868
|
return {
|
|
14599
14869
|
sql: compiled.sql,
|
|
14870
|
+
// Native composition may have failed for a derived/ratio metric before
|
|
14871
|
+
// the selected full runtime compiled it successfully. That diagnostic is
|
|
14872
|
+
// no longer true for this run.
|
|
14600
14873
|
diagnostics: native.diagnostics.filter((diagnostic) => diagnostic.code !== 'semantic_compose_failed'),
|
|
14601
14874
|
semanticRefs: native.semanticRefs,
|
|
14602
14875
|
};
|
|
@@ -14608,7 +14881,9 @@ async function composeSemanticBlockSqlForRuntime(source, semanticLayer, options)
|
|
|
14608
14881
|
...native.diagnostics.filter((diagnostic) => diagnostic.code !== 'semantic_compose_failed'),
|
|
14609
14882
|
{
|
|
14610
14883
|
severity: 'error',
|
|
14611
|
-
code:
|
|
14884
|
+
code: semanticRuntimeErrorCode(error) === 'SEMANTIC_RUNTIME_REQUIRED'
|
|
14885
|
+
? 'semantic_runtime_required'
|
|
14886
|
+
: 'semantic_compose_failed',
|
|
14612
14887
|
message: error instanceof Error ? error.message : String(error),
|
|
14613
14888
|
},
|
|
14614
14889
|
],
|