@duckcodeailabs/dql-cli 1.10.0 → 1.10.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/assets/dql-notebook/assets/{index-C5VHd5JR.js → index-pl1On-IO.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 +212 -10
- 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
|
@@ -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, } 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';
|
|
@@ -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
|
});
|
|
@@ -4808,6 +4924,50 @@ export async function startLocalServer(opts) {
|
|
|
4808
4924
|
res.end(serializeJSON({ runs, total: agentRunStore.list().length, limit }));
|
|
4809
4925
|
return;
|
|
4810
4926
|
}
|
|
4927
|
+
if (req.method === 'POST' && /^\/api\/agent-runs\/[^/]+\/analytical-repair$/.test(path)) {
|
|
4928
|
+
const match = path.match(/^\/api\/agent-runs\/([^/]+)\/analytical-repair$/);
|
|
4929
|
+
const id = decodeURIComponent(match?.[1] ?? '');
|
|
4930
|
+
const run = await agentRunStore.get(id);
|
|
4931
|
+
if (!run) {
|
|
4932
|
+
res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
4933
|
+
res.end(serializeJSON({ error: 'Agent run not found.' }));
|
|
4934
|
+
return;
|
|
4935
|
+
}
|
|
4936
|
+
const source = analyticalFailedRunFromAgentRun(run);
|
|
4937
|
+
if (!source) {
|
|
4938
|
+
res.writeHead(409, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
4939
|
+
res.end(serializeJSON({ error: 'This run has no retained analytical failure artifact.' }));
|
|
4940
|
+
return;
|
|
4941
|
+
}
|
|
4942
|
+
const body = await readJSON(req).catch(() => null);
|
|
4943
|
+
const record = agentRunRecord(body);
|
|
4944
|
+
const repair = (agentRunRecord(record?.repair) ?? record);
|
|
4945
|
+
if (!repair) {
|
|
4946
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
4947
|
+
res.end(serializeJSON({ error: 'A v1 analytical repair request is required.' }));
|
|
4948
|
+
return;
|
|
4949
|
+
}
|
|
4950
|
+
// API-007: snapshot refresh is resolved by the runtime, not trusted from
|
|
4951
|
+
// browser input. projectSnapshot() recompiles when governed sources have
|
|
4952
|
+
// changed and gives the immutable identity used by the derivation.
|
|
4953
|
+
const repairWithRuntimeContext = repair.action === 'refresh_snapshot'
|
|
4954
|
+
? { ...repair, refreshedSnapshotId: projectSnapshot().snapshotId }
|
|
4955
|
+
: repair;
|
|
4956
|
+
const result = deriveAnalyticalRepair(source, repairWithRuntimeContext);
|
|
4957
|
+
if (result.status === 'blocked') {
|
|
4958
|
+
const status = result.code === 'SOURCE_FINGERPRINT_MISMATCH'
|
|
4959
|
+
? 409
|
|
4960
|
+
: result.code === 'PERMISSION_FAILURE_TERMINAL' || result.code === 'ACTION_NOT_ALLOWED'
|
|
4961
|
+
? 403
|
|
4962
|
+
: 400;
|
|
4963
|
+
res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
4964
|
+
res.end(serializeJSON(result));
|
|
4965
|
+
return;
|
|
4966
|
+
}
|
|
4967
|
+
res.writeHead(201, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
4968
|
+
res.end(serializeJSON(result));
|
|
4969
|
+
return;
|
|
4970
|
+
}
|
|
4811
4971
|
if (req.method === 'POST' && /^\/api\/agent-runs\/[^/]+\/cancel$/.test(path)) {
|
|
4812
4972
|
const match = path.match(/^\/api\/agent-runs\/([^/]+)\/cancel$/);
|
|
4813
4973
|
const id = decodeURIComponent(match?.[1] ?? '');
|
|
@@ -11462,6 +11622,48 @@ export function serializeJSON(value) {
|
|
|
11462
11622
|
return current;
|
|
11463
11623
|
});
|
|
11464
11624
|
}
|
|
11625
|
+
/**
|
|
11626
|
+
* Extract the single time value returned by an authorized semantic freshness
|
|
11627
|
+
* lookup. Adapter aliases may differ by warehouse, so typed date metadata is
|
|
11628
|
+
* the only fallback; arbitrary string columns are never treated as freshness.
|
|
11629
|
+
* Acceptance: AGT-019, SEC-004.
|
|
11630
|
+
*/
|
|
11631
|
+
export function analyticalFreshnessObservedThrough(result, request) {
|
|
11632
|
+
const binding = request.authorizedAdapterRequest;
|
|
11633
|
+
if (!binding || binding.route !== 'semantic') {
|
|
11634
|
+
throw Object.assign(new Error('The freshness lookup has no authorized semantic adapter binding.'), {
|
|
11635
|
+
code: 'FRESHNESS_BINDING_REQUIRED',
|
|
11636
|
+
});
|
|
11637
|
+
}
|
|
11638
|
+
const row = result.rows[0];
|
|
11639
|
+
if (!row) {
|
|
11640
|
+
throw Object.assign(new Error('The authorized freshness lookup returned no rows.'), {
|
|
11641
|
+
code: 'FRESHNESS_OBSERVATION_MISSING',
|
|
11642
|
+
});
|
|
11643
|
+
}
|
|
11644
|
+
const normalize = (value) => value.replace(/[^a-zA-Z0-9]+/g, '_').toLowerCase();
|
|
11645
|
+
const target = normalize(binding.outputField);
|
|
11646
|
+
const exact = result.columns.find((column) => normalize(column.name) === target);
|
|
11647
|
+
const typedDates = result.columns.filter((column) => column.type === 'date' || column.type === 'datetime');
|
|
11648
|
+
const column = exact ?? (typedDates.length === 1 ? typedDates[0] : undefined);
|
|
11649
|
+
if (!column) {
|
|
11650
|
+
throw Object.assign(new Error('The authorized freshness lookup did not return one identifiable time field.'), {
|
|
11651
|
+
code: 'FRESHNESS_OBSERVATION_INVALID',
|
|
11652
|
+
});
|
|
11653
|
+
}
|
|
11654
|
+
const value = row[column.name];
|
|
11655
|
+
const instant = value instanceof Date
|
|
11656
|
+
? value
|
|
11657
|
+
: typeof value === 'string' && value.trim()
|
|
11658
|
+
? new Date(value)
|
|
11659
|
+
: undefined;
|
|
11660
|
+
if (!instant || Number.isNaN(instant.getTime())) {
|
|
11661
|
+
throw Object.assign(new Error('The authorized freshness value is not a valid instant.'), {
|
|
11662
|
+
code: 'FRESHNESS_OBSERVATION_INVALID',
|
|
11663
|
+
});
|
|
11664
|
+
}
|
|
11665
|
+
return instant.toISOString();
|
|
11666
|
+
}
|
|
11465
11667
|
/** Serialize a Skill to the shared API contract shape (spec 16). */
|
|
11466
11668
|
function serializeSkill(skill) {
|
|
11467
11669
|
return {
|