@duckcodeailabs/dql-agent 1.14.2 → 1.14.4
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/agent-run-engine.d.ts +239 -4
- package/dist/agent-run-engine.d.ts.map +1 -1
- package/dist/agent-run-engine.js +2924 -85
- package/dist/agent-run-engine.js.map +1 -1
- package/dist/agent-run-gates.d.ts.map +1 -1
- package/dist/agent-run-gates.js +51 -3
- package/dist/agent-run-gates.js.map +1 -1
- package/dist/agent-run-planner.d.ts +6 -0
- package/dist/agent-run-planner.d.ts.map +1 -1
- package/dist/agent-run-planner.js +2 -0
- package/dist/agent-run-planner.js.map +1 -1
- package/dist/agent-run-store.d.ts.map +1 -1
- package/dist/agent-run-store.js +591 -11
- package/dist/agent-run-store.js.map +1 -1
- package/dist/agentic/analyst-loop.d.ts +9 -1
- package/dist/agentic/analyst-loop.d.ts.map +1 -1
- package/dist/agentic/analyst-loop.js +204 -4
- package/dist/agentic/analyst-loop.js.map +1 -1
- package/dist/agentic/assumptions.d.ts +1 -1
- package/dist/agentic/assumptions.d.ts.map +1 -1
- package/dist/agentic/assumptions.js.map +1 -1
- package/dist/agentic/ledger-tools.d.ts.map +1 -1
- package/dist/agentic/ledger-tools.js +8 -1
- package/dist/agentic/ledger-tools.js.map +1 -1
- package/dist/agentic/orchestrator-policy.d.ts +3 -1
- package/dist/agentic/orchestrator-policy.d.ts.map +1 -1
- package/dist/agentic/orchestrator-policy.js +1 -1
- package/dist/agentic/orchestrator-policy.js.map +1 -1
- package/dist/agentic/research-agent.d.ts.map +1 -1
- package/dist/agentic/research-agent.js +10 -1
- package/dist/agentic/research-agent.js.map +1 -1
- package/dist/agentic/sql-authorization.d.ts +8 -0
- package/dist/agentic/sql-authorization.d.ts.map +1 -1
- package/dist/agentic/sql-authorization.js +3 -0
- package/dist/agentic/sql-authorization.js.map +1 -1
- package/dist/agentic/tool-loop.d.ts +13 -1
- package/dist/agentic/tool-loop.d.ts.map +1 -1
- package/dist/agentic/tool-loop.js +441 -32
- package/dist/agentic/tool-loop.js.map +1 -1
- package/dist/analytical-compatibility.d.ts.map +1 -1
- package/dist/analytical-compatibility.js +8 -1
- package/dist/analytical-compatibility.js.map +1 -1
- package/dist/analytical-failure-repair.d.ts.map +1 -1
- package/dist/analytical-failure-repair.js +6 -2
- package/dist/analytical-failure-repair.js.map +1 -1
- package/dist/analytical-frame.d.ts +42 -1
- package/dist/analytical-frame.d.ts.map +1 -1
- package/dist/analytical-frame.js +490 -26
- package/dist/analytical-frame.js.map +1 -1
- package/dist/analytical-orchestration.d.ts +1259 -9
- package/dist/analytical-orchestration.d.ts.map +1 -1
- package/dist/analytical-orchestration.js +970 -84
- package/dist/analytical-orchestration.js.map +1 -1
- package/dist/answer-loop.d.ts +230 -10
- package/dist/answer-loop.d.ts.map +1 -1
- package/dist/answer-loop.js +1182 -85
- package/dist/answer-loop.js.map +1 -1
- package/dist/answer-shape.d.ts +27 -0
- package/dist/answer-shape.d.ts.map +1 -1
- package/dist/answer-shape.js +55 -0
- package/dist/answer-shape.js.map +1 -1
- package/dist/ask-observability/index.d.ts +11 -0
- package/dist/ask-observability/index.d.ts.map +1 -0
- package/dist/ask-observability/index.js +7 -0
- package/dist/ask-observability/index.js.map +1 -0
- package/dist/ask-observability/instrumentation.d.ts +35 -0
- package/dist/ask-observability/instrumentation.d.ts.map +1 -0
- package/dist/ask-observability/instrumentation.js +523 -0
- package/dist/ask-observability/instrumentation.js.map +1 -0
- package/dist/ask-observability/observer.d.ts +62 -0
- package/dist/ask-observability/observer.d.ts.map +1 -0
- package/dist/ask-observability/observer.js +537 -0
- package/dist/ask-observability/observer.js.map +1 -0
- package/dist/ask-observability/portable.d.ts +98 -0
- package/dist/ask-observability/portable.d.ts.map +1 -0
- package/dist/ask-observability/portable.js +1089 -0
- package/dist/ask-observability/portable.js.map +1 -0
- package/dist/ask-observability/store.d.ts +130 -0
- package/dist/ask-observability/store.d.ts.map +1 -0
- package/dist/ask-observability/store.js +1114 -0
- package/dist/ask-observability/store.js.map +1 -0
- package/dist/ask-observability/types.d.ts +380 -0
- package/dist/ask-observability/types.d.ts.map +1 -0
- package/dist/ask-observability/types.js +2 -0
- package/dist/ask-observability/types.js.map +1 -0
- package/dist/ask-observability/utils.d.ts +21 -0
- package/dist/ask-observability/utils.d.ts.map +1 -0
- package/dist/ask-observability/utils.js +102 -0
- package/dist/ask-observability/utils.js.map +1 -0
- package/dist/ask-runtime/ask-agent-runtime-v2.d.ts +850 -0
- package/dist/ask-runtime/ask-agent-runtime-v2.d.ts.map +1 -0
- package/dist/ask-runtime/ask-agent-runtime-v2.js +1716 -0
- package/dist/ask-runtime/ask-agent-runtime-v2.js.map +1 -0
- package/dist/ask-runtime/ask-analyst-runtime.d.ts +95 -0
- package/dist/ask-runtime/ask-analyst-runtime.d.ts.map +1 -0
- package/dist/ask-runtime/ask-analyst-runtime.js +8280 -0
- package/dist/ask-runtime/ask-analyst-runtime.js.map +1 -0
- package/dist/conversation/session-store.d.ts +31 -0
- package/dist/conversation/session-store.d.ts.map +1 -1
- package/dist/conversation/session-store.js +38 -0
- package/dist/conversation/session-store.js.map +1 -1
- package/dist/conversation/snapshot.d.ts +4 -1
- package/dist/conversation/snapshot.d.ts.map +1 -1
- package/dist/conversation/snapshot.js +85 -3
- package/dist/conversation/snapshot.js.map +1 -1
- package/dist/conversation/turn-trust.d.ts.map +1 -1
- package/dist/conversation/turn-trust.js +2 -0
- package/dist/conversation/turn-trust.js.map +1 -1
- package/dist/embeddings/provider.d.ts +1 -0
- package/dist/embeddings/provider.d.ts.map +1 -1
- package/dist/embeddings/provider.js +18 -0
- package/dist/embeddings/provider.js.map +1 -1
- package/dist/generated-analytical-proposal.d.ts +37 -1
- package/dist/generated-analytical-proposal.d.ts.map +1 -1
- package/dist/generated-analytical-proposal.js +246 -3
- package/dist/generated-analytical-proposal.js.map +1 -1
- package/dist/governed-relational-compiler.d.ts +15 -0
- package/dist/governed-relational-compiler.d.ts.map +1 -1
- package/dist/governed-relational-compiler.js +99 -1
- package/dist/governed-relational-compiler.js.map +1 -1
- package/dist/index.d.ts +21 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -7
- package/dist/index.js.map +1 -1
- package/dist/intent-controller.d.ts +87 -1
- package/dist/intent-controller.d.ts.map +1 -1
- package/dist/intent-controller.js +2 -2
- package/dist/intent-controller.js.map +1 -1
- package/dist/kg/build.js +32 -2
- package/dist/kg/build.js.map +1 -1
- package/dist/meaning-resolution.d.ts +207 -6
- package/dist/meaning-resolution.d.ts.map +1 -1
- package/dist/meaning-resolution.js +439 -53
- package/dist/meaning-resolution.js.map +1 -1
- package/dist/metadata/analysis-planner.d.ts.map +1 -1
- package/dist/metadata/analysis-planner.js +78 -7
- package/dist/metadata/analysis-planner.js.map +1 -1
- package/dist/metadata/catalog.d.ts +14 -0
- package/dist/metadata/catalog.d.ts.map +1 -1
- package/dist/metadata/catalog.js +29 -2
- package/dist/metadata/catalog.js.map +1 -1
- package/dist/metadata/meaning-evidence.d.ts +28 -2
- package/dist/metadata/meaning-evidence.d.ts.map +1 -1
- package/dist/metadata/meaning-evidence.js +373 -16
- package/dist/metadata/meaning-evidence.js.map +1 -1
- package/dist/plan-execution-adapter.d.ts +9 -1
- package/dist/plan-execution-adapter.d.ts.map +1 -1
- package/dist/plan-execution-adapter.js +93 -13
- package/dist/plan-execution-adapter.js.map +1 -1
- package/dist/provider-egress.d.ts +28 -11
- package/dist/provider-egress.d.ts.map +1 -1
- package/dist/provider-egress.js +72 -22
- package/dist/provider-egress.js.map +1 -1
- package/dist/providers/claude.d.ts +2 -2
- package/dist/providers/claude.d.ts.map +1 -1
- package/dist/providers/claude.js +201 -37
- package/dist/providers/claude.js.map +1 -1
- package/dist/providers/dispatch.d.ts +27 -1
- package/dist/providers/dispatch.d.ts.map +1 -1
- package/dist/providers/dispatch.js +63 -1
- package/dist/providers/dispatch.js.map +1 -1
- package/dist/providers/gemini.js +10 -10
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/index.d.ts +2 -2
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +1 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/ollama.js +15 -15
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai.d.ts +2 -2
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +213 -43
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/types.d.ts +131 -1
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js.map +1 -1
- package/dist/relationship-proof.d.ts +81 -0
- package/dist/relationship-proof.d.ts.map +1 -1
- package/dist/relationship-proof.js +228 -4
- package/dist/relationship-proof.js.map +1 -1
- package/dist/requirement-clauses.d.ts +23 -0
- package/dist/requirement-clauses.d.ts.map +1 -0
- package/dist/requirement-clauses.js +70 -0
- package/dist/requirement-clauses.js.map +1 -0
- package/dist/resolved-analytical-plan.d.ts +18 -4
- package/dist/resolved-analytical-plan.d.ts.map +1 -1
- package/dist/resolved-analytical-plan.js +953 -50
- package/dist/resolved-analytical-plan.js.map +1 -1
- package/dist/router.d.ts +118 -2
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +2481 -232
- package/dist/router.js.map +1 -1
- package/dist/semantic-bridge/compose.d.ts.map +1 -1
- package/dist/semantic-bridge/compose.js +23 -4
- package/dist/semantic-bridge/compose.js.map +1 -1
- package/package.json +4 -4
package/dist/agent-run-store.js
CHANGED
|
@@ -9,10 +9,28 @@
|
|
|
9
9
|
* a user actually reopens stay complete while history stops growing unbounded.
|
|
10
10
|
*/
|
|
11
11
|
import Database from 'better-sqlite3';
|
|
12
|
+
import { createHash } from 'node:crypto';
|
|
12
13
|
import { existsSync, mkdirSync, readFileSync, renameSync } from 'node:fs';
|
|
13
14
|
import { dirname, join } from 'node:path';
|
|
15
|
+
import { normalizeProviderEgressReceiptV1 } from '@duckcodeailabs/dql-core';
|
|
14
16
|
const DEFAULT_MAX_RUNS = 300;
|
|
15
17
|
const DEFAULT_FULL_PAYLOAD_RUNS = 50;
|
|
18
|
+
const EVIDENCE_CANDIDATE_ROLES = new Set([
|
|
19
|
+
'metric',
|
|
20
|
+
'entity_key',
|
|
21
|
+
'entity_label',
|
|
22
|
+
'categorical_dimension',
|
|
23
|
+
'time_dimension',
|
|
24
|
+
'member',
|
|
25
|
+
'relationship',
|
|
26
|
+
'context',
|
|
27
|
+
]);
|
|
28
|
+
const ANALYTICAL_TASK_OUTCOME_STATUSES = new Set([
|
|
29
|
+
'completed', 'partial', 'gap', 'blocked', 'dependency_blocked',
|
|
30
|
+
]);
|
|
31
|
+
const ANALYTICAL_TASK_OUTCOME_TRUST_STATES = new Set([
|
|
32
|
+
'certified', 'governed', 'review_required', 'blocked', 'not_applicable',
|
|
33
|
+
]);
|
|
16
34
|
export function resolveAgentRunRetention(env = process.env) {
|
|
17
35
|
const configured = Number(env.DQL_AGENT_RUN_RETENTION);
|
|
18
36
|
if (!Number.isFinite(configured) || configured <= 0)
|
|
@@ -48,6 +66,7 @@ export class SqliteAgentRunStore {
|
|
|
48
66
|
this.migrateLegacyJson(options.legacyJsonPath);
|
|
49
67
|
}
|
|
50
68
|
save(run) {
|
|
69
|
+
const persistedRun = normalizeRunProviderEgressReceipts(run);
|
|
51
70
|
const now = new Date().toISOString();
|
|
52
71
|
this.db.prepare(`
|
|
53
72
|
INSERT INTO agent_runs (id, question, route, status, started_at, completed_at, compacted, payload_json, updated_at)
|
|
@@ -61,12 +80,13 @@ export class SqliteAgentRunStore {
|
|
|
61
80
|
compacted = 0,
|
|
62
81
|
payload_json = excluded.payload_json,
|
|
63
82
|
updated_at = excluded.updated_at
|
|
64
|
-
`).run(
|
|
83
|
+
`).run(persistedRun.id, persistedRun.question, persistedRun.route, persistedRun.status ?? null, persistedRun.startedAt, persistedRun.completedAt ?? null, JSON.stringify(persistedRun), now);
|
|
65
84
|
this.enforceRetention();
|
|
66
85
|
this.compactOldRuns();
|
|
67
86
|
}
|
|
68
87
|
saveProgress(progress) {
|
|
69
|
-
const
|
|
88
|
+
const persistedProgress = sanitizeProgressRoleCoverage(progress);
|
|
89
|
+
const updatedAt = persistedProgress.lifecycle.updatedAt;
|
|
70
90
|
this.db.prepare(`
|
|
71
91
|
INSERT INTO agent_runs (id, question, route, status, started_at, completed_at, compacted, payload_json, updated_at)
|
|
72
92
|
VALUES (?, ?, ?, NULL, ?, NULL, 0, ?, ?)
|
|
@@ -80,7 +100,7 @@ export class SqliteAgentRunStore {
|
|
|
80
100
|
payload_json = excluded.payload_json,
|
|
81
101
|
updated_at = excluded.updated_at
|
|
82
102
|
WHERE agent_runs.completed_at IS NULL
|
|
83
|
-
`).run(
|
|
103
|
+
`).run(persistedProgress.id, persistedProgress.question, persistedProgress.route ?? 'blocked', persistedProgress.lifecycle.startedAt, JSON.stringify(persistedProgress), updatedAt);
|
|
84
104
|
}
|
|
85
105
|
get(id) {
|
|
86
106
|
const row = this.db.prepare('SELECT payload_json FROM agent_runs WHERE id = ?').get(id);
|
|
@@ -125,12 +145,16 @@ export class SqliteAgentRunStore {
|
|
|
125
145
|
const userCancelled = progress.lifecycle.state === 'cancelling'
|
|
126
146
|
|| progress.events.some((event) => event.type === 'run.cancelled');
|
|
127
147
|
const route = progress.route ?? (userCancelled ? 'cancelled' : 'blocked');
|
|
148
|
+
const retainedArtifacts = retainedInterruptedArtifacts(progress, userCancelled);
|
|
149
|
+
const retainedIndependentResult = retainedArtifacts.some((artifact) => artifact.trustState !== 'blocked');
|
|
128
150
|
const failure = {
|
|
129
151
|
code: userCancelled ? 'RUN_CANCELLED' : 'RUN_INTERRUPTED',
|
|
130
152
|
phase: progress.lifecycle.phase,
|
|
131
153
|
message: userCancelled
|
|
132
154
|
? 'Stopped by user.'
|
|
133
|
-
:
|
|
155
|
+
: retainedIndependentResult
|
|
156
|
+
? 'The local DQL runtime restarted before this agent run completed. Completed independent task results remain available for inspection.'
|
|
157
|
+
: 'The local DQL runtime restarted before this agent run completed. No result was accepted.',
|
|
134
158
|
recoverable: !userCancelled,
|
|
135
159
|
safeActions: userCancelled ? [] : ['retry_same_request'],
|
|
136
160
|
};
|
|
@@ -141,16 +165,24 @@ export class SqliteAgentRunStore {
|
|
|
141
165
|
route,
|
|
142
166
|
plan: progress.plan,
|
|
143
167
|
steps: progress.steps,
|
|
144
|
-
artifacts:
|
|
168
|
+
artifacts: retainedArtifacts,
|
|
145
169
|
evaluations: progress.evaluations,
|
|
146
170
|
failure,
|
|
147
171
|
};
|
|
172
|
+
const runtimeReceipt = interruptedRuntimeReceiptV5(progress, userCancelled, route);
|
|
173
|
+
const runtimeReceiptV6 = runtimeReceipt
|
|
174
|
+
? interruptedRuntimeReceiptV6(progress, userCancelled, runtimeReceipt)
|
|
175
|
+
: undefined;
|
|
148
176
|
const diagnosticArtifact = {
|
|
149
177
|
id: `${progress.id}:diagnostic`,
|
|
150
178
|
kind: 'answer',
|
|
151
179
|
title: userCancelled ? 'Cancelled agent run' : 'Interrupted agent run',
|
|
152
180
|
trustState: 'blocked',
|
|
153
|
-
payload: {
|
|
181
|
+
payload: {
|
|
182
|
+
diagnosticReceipt: receipt,
|
|
183
|
+
...(runtimeReceipt ? { diagnosticReceiptV5: runtimeReceipt } : {}),
|
|
184
|
+
...(runtimeReceiptV6 ? { diagnosticReceiptV6: runtimeReceiptV6 } : {}),
|
|
185
|
+
},
|
|
154
186
|
};
|
|
155
187
|
const run = {
|
|
156
188
|
id: progress.id,
|
|
@@ -167,8 +199,8 @@ export class SqliteAgentRunStore {
|
|
|
167
199
|
steps: progress.steps,
|
|
168
200
|
summary: failure.message,
|
|
169
201
|
artifacts: userCancelled
|
|
170
|
-
?
|
|
171
|
-
: [...
|
|
202
|
+
? retainedArtifacts
|
|
203
|
+
: [...retainedArtifacts, diagnosticArtifact],
|
|
172
204
|
evaluations: [
|
|
173
205
|
...progress.evaluations,
|
|
174
206
|
userCancelled
|
|
@@ -207,6 +239,16 @@ export class SqliteAgentRunStore {
|
|
|
207
239
|
repairAttempts: 0,
|
|
208
240
|
escalationAttempts: 0,
|
|
209
241
|
diagnosticReceipt: receipt,
|
|
242
|
+
...(runtimeReceipt ? { diagnosticReceiptV5: runtimeReceipt } : {}),
|
|
243
|
+
...(runtimeReceiptV6 ? { diagnosticReceiptV6: runtimeReceiptV6 } : {}),
|
|
244
|
+
...(progress.askAnalystState ? { askAnalystState: progress.askAnalystState } : {}),
|
|
245
|
+
...(progress.analyticalTaskOutcomes?.length
|
|
246
|
+
? { analyticalTaskOutcomes: progress.analyticalTaskOutcomes }
|
|
247
|
+
: {}),
|
|
248
|
+
...(progress.analyticalTaskOutcomeSummary
|
|
249
|
+
? { analyticalTaskOutcomeSummary: progress.analyticalTaskOutcomeSummary }
|
|
250
|
+
: {}),
|
|
251
|
+
...(progress.traceReference ? { traceReference: progress.traceReference } : {}),
|
|
210
252
|
lifecycle: {
|
|
211
253
|
...progress.lifecycle,
|
|
212
254
|
state: 'terminal',
|
|
@@ -259,7 +301,9 @@ export class SqliteAgentRunStore {
|
|
|
259
301
|
return;
|
|
260
302
|
try {
|
|
261
303
|
const parsed = JSON.parse(readFileSync(legacyJsonPath, 'utf-8'));
|
|
262
|
-
const runs = Array.isArray(parsed.runs)
|
|
304
|
+
const runs = Array.isArray(parsed.runs)
|
|
305
|
+
? parsed.runs.filter(isAgentRunRecord).map(normalizeRunProviderEgressReceipts)
|
|
306
|
+
: [];
|
|
263
307
|
const insert = this.db.prepare(`
|
|
264
308
|
INSERT OR IGNORE INTO agent_runs (id, question, route, status, started_at, completed_at, compacted, payload_json, updated_at)
|
|
265
309
|
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)
|
|
@@ -285,27 +329,563 @@ export class SqliteAgentRunStore {
|
|
|
285
329
|
}
|
|
286
330
|
}
|
|
287
331
|
}
|
|
332
|
+
/**
|
|
333
|
+
* Restart recovery retains the typed continuation state for a deliberate retry,
|
|
334
|
+
* but its diagnostic artifact/export receipt must remain content-free. Never
|
|
335
|
+
* copy the question, filter values, member values, answer, SQL, or rows here.
|
|
336
|
+
*/
|
|
337
|
+
function interruptedRuntimeReceiptV5(progress, cancelled, route) {
|
|
338
|
+
const state = progress.askAnalystState;
|
|
339
|
+
if (!state)
|
|
340
|
+
return undefined;
|
|
341
|
+
const diagnosticState = interruptedDiagnosticState(state);
|
|
342
|
+
const summaryInput = {
|
|
343
|
+
version: 2,
|
|
344
|
+
runtimeMode: state.mode,
|
|
345
|
+
whatHappened: cancelled
|
|
346
|
+
? 'The Ask runtime was cancelled before its final result was accepted.'
|
|
347
|
+
: 'The Ask runtime was interrupted before its final result was accepted.',
|
|
348
|
+
why: cancelled
|
|
349
|
+
? 'The local run was cancelled before finalization.'
|
|
350
|
+
: 'The local DQL runtime restarted before finalization.',
|
|
351
|
+
impact: 'No executable data answer was accepted for this run.',
|
|
352
|
+
nextAction: cancelled ? 'none' : 'retry_same_plan',
|
|
353
|
+
...(state.resolvedPlan?.compiler ? { selectedCompiler: state.resolvedPlan.compiler } : {}),
|
|
354
|
+
programTaskCount: state.program.taskIds.length,
|
|
355
|
+
admittedCandidateCount: state.workspace.admittedCandidateIds.length,
|
|
356
|
+
toolCallCount: state.toolCalls,
|
|
357
|
+
executionAttempts: state.executionAttempts,
|
|
358
|
+
};
|
|
359
|
+
return {
|
|
360
|
+
version: 5,
|
|
361
|
+
runId: progress.id,
|
|
362
|
+
state: diagnosticState,
|
|
363
|
+
summary: {
|
|
364
|
+
...summaryInput,
|
|
365
|
+
summaryFingerprint: `sha256:${createHash('sha256').update(JSON.stringify(summaryInput)).digest('hex')}`,
|
|
366
|
+
},
|
|
367
|
+
businessAnswer: {
|
|
368
|
+
version: 1,
|
|
369
|
+
mode: 'deterministic_fallback',
|
|
370
|
+
trustState: cancelled ? 'not_applicable' : 'blocked',
|
|
371
|
+
factIds: [],
|
|
372
|
+
limitationCount: 1,
|
|
373
|
+
},
|
|
374
|
+
finalStopReason: cancelled ? 'cancelled' : 'blocked',
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Restart recovery has no live trace collector, so V6 is projected only from
|
|
379
|
+
* the persisted V2 state and its content-free V5 envelope. Do not infer a
|
|
380
|
+
* connection or SQL attempt from elapsed time/prose: an interrupted execution
|
|
381
|
+
* count is retained separately and the connection boundary remains skipped
|
|
382
|
+
* unless a terminal run receipt recorded it before restart.
|
|
383
|
+
*/
|
|
384
|
+
function interruptedRuntimeReceiptV6(progress, cancelled, receipt) {
|
|
385
|
+
const state = progress.askAnalystState;
|
|
386
|
+
const planning = state.version === 2 || state.version === 3
|
|
387
|
+
? state.planningReceipt
|
|
388
|
+
: undefined;
|
|
389
|
+
const plannerTool = state.workspace.tools.find((tool) => tool.kind === 'provider_meaning');
|
|
390
|
+
const recoveryTool = state.workspace.tools.find((tool) => tool.kind === 'candidate_extension');
|
|
391
|
+
const planFrozen = state.resolvedPlan?.planFrozen === true;
|
|
392
|
+
const verification = planning?.verification ?? {
|
|
393
|
+
version: 1,
|
|
394
|
+
status: state.phase === 'clarify' ? 'ambiguous' : state.phase === 'blocked' ? 'invalid' : 'valid',
|
|
395
|
+
missingRoles: [],
|
|
396
|
+
candidateIds: [],
|
|
397
|
+
reasonCode: state.phase === 'clarify'
|
|
398
|
+
? 'persisted_clarification_state'
|
|
399
|
+
: state.phase === 'blocked'
|
|
400
|
+
? 'persisted_blocked_state'
|
|
401
|
+
: 'persisted_program_state',
|
|
402
|
+
};
|
|
403
|
+
// A durable failed provider tool is a planner attempt too. This restores a
|
|
404
|
+
// truthful concise V6 restart story for V2 checkpoints saved between
|
|
405
|
+
// dispatch and planning-receipt finalization.
|
|
406
|
+
const plannerAttempted = plannerTool?.status === 'completed' || plannerTool?.status === 'failed';
|
|
407
|
+
const plannerCalls = Math.max(planning?.plannerCalls ?? 0, plannerAttempted ? Math.max(1, state.planningContinuations) : 0);
|
|
408
|
+
const revisionCalls = planning?.revisionCalls
|
|
409
|
+
?? (recoveryTool?.status === 'completed' && plannerCalls > 1 ? 1 : 0);
|
|
410
|
+
const planningMode = planning?.mode
|
|
411
|
+
?? (plannerCalls === 0
|
|
412
|
+
? 'deterministic_binding'
|
|
413
|
+
: revisionCalls > 0 ? 'targeted_revision' : 'initial_planner');
|
|
414
|
+
const executionAttempts = state.executionAttempts;
|
|
415
|
+
return {
|
|
416
|
+
...receipt,
|
|
417
|
+
version: 6,
|
|
418
|
+
planning: {
|
|
419
|
+
version: 1,
|
|
420
|
+
mode: planningMode,
|
|
421
|
+
plannerCalls,
|
|
422
|
+
revisionCalls,
|
|
423
|
+
verification,
|
|
424
|
+
},
|
|
425
|
+
// V2 stores count-only admission coverage, so reload can retain the
|
|
426
|
+
// meaningful "required versus admitted" story without exposing labels,
|
|
427
|
+
// prompts, or result values. Older V1 states remain content-free.
|
|
428
|
+
roleCoverage: state.workspace.version === 2
|
|
429
|
+
? (state.workspace.roleCoverage ?? [])
|
|
430
|
+
.filter((entry) => Number.isFinite(entry.candidateCount) && entry.candidateCount >= 0)
|
|
431
|
+
.map((entry) => ({
|
|
432
|
+
role: entry.role,
|
|
433
|
+
candidateCount: entry.candidateCount,
|
|
434
|
+
...(entry.state === 'alternatives' || entry.state === 'proven' ? { state: entry.state } : {}),
|
|
435
|
+
}))
|
|
436
|
+
: [],
|
|
437
|
+
cascade: {
|
|
438
|
+
attempts: [],
|
|
439
|
+
...(state.resolvedPlan?.selectedTier ? { selectedTier: state.resolvedPlan.selectedTier } : {}),
|
|
440
|
+
...(state.resolvedPlan?.compiler && state.resolvedPlan.compiler !== 'none'
|
|
441
|
+
? { stopReason: `persisted_${state.resolvedPlan.compiler}_plan` }
|
|
442
|
+
: {}),
|
|
443
|
+
planFrozen,
|
|
444
|
+
},
|
|
445
|
+
connection: { attempted: false },
|
|
446
|
+
execution: { attempts: executionAttempts },
|
|
447
|
+
facts: { factCount: 0 },
|
|
448
|
+
safeNextAction: receipt.summary.nextAction,
|
|
449
|
+
story: [
|
|
450
|
+
{ stage: 'retrieval', status: state.workspace.snapshotId ? 'completed' : 'skipped', reasonCode: state.workspace.snapshotId ? 'persisted_snapshot' : 'snapshot_not_retained' },
|
|
451
|
+
{
|
|
452
|
+
stage: 'role_coverage',
|
|
453
|
+
status: state.workspace.version === 2 && (state.workspace.roleCoverage?.length ?? 0) > 0
|
|
454
|
+
? 'completed'
|
|
455
|
+
: 'skipped',
|
|
456
|
+
reasonCode: state.workspace.version === 2 && (state.workspace.roleCoverage?.length ?? 0) > 0
|
|
457
|
+
? 'persisted_role_admission_coverage'
|
|
458
|
+
: 'content_free_restart_projection',
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
stage: 'planner',
|
|
462
|
+
status: plannerTool?.status === 'failed' ? 'blocked' : plannerCalls > 0 ? 'completed' : 'skipped',
|
|
463
|
+
reasonCode: plannerTool?.reasonCode ?? planningMode,
|
|
464
|
+
},
|
|
465
|
+
{ stage: 'verification', status: verification.status === 'valid' ? 'completed' : 'blocked', reasonCode: verification.reasonCode },
|
|
466
|
+
{ stage: 'targeted_recovery', status: recoveryTool?.status === 'completed' ? 'completed' : 'skipped', reasonCode: recoveryTool?.reasonCode ?? 'not_required' },
|
|
467
|
+
{ stage: 'cascade', status: planFrozen ? 'completed' : state.phase === 'blocked' ? 'blocked' : 'skipped', reasonCode: state.resolvedPlan?.compiler ?? 'no_persisted_compiler' },
|
|
468
|
+
{ stage: 'freeze', status: planFrozen ? 'completed' : 'skipped', reasonCode: planFrozen ? 'persisted_plan_frozen' : 'no_persisted_plan' },
|
|
469
|
+
{ stage: 'connection', status: 'skipped', reasonCode: 'connection_boundary_not_retained_on_restart' },
|
|
470
|
+
{ stage: 'execution', status: executionAttempts > 0 ? 'blocked' : 'skipped', reasonCode: executionAttempts > 0 ? 'interrupted_after_execution_attempt' : 'execution_not_attempted' },
|
|
471
|
+
{ stage: 'facts', status: 'skipped', reasonCode: cancelled ? 'run_cancelled_before_final_facts' : 'run_interrupted_before_final_facts' },
|
|
472
|
+
],
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function interruptedDiagnosticState(state) {
|
|
476
|
+
return {
|
|
477
|
+
version: 1,
|
|
478
|
+
mode: state.mode,
|
|
479
|
+
phase: state.phase,
|
|
480
|
+
questionFingerprint: state.frame.questionFingerprint,
|
|
481
|
+
kind: state.frame.kind,
|
|
482
|
+
requirementCounts: {
|
|
483
|
+
measures: state.frame.requirements.measures.length,
|
|
484
|
+
dimensions: state.frame.requirements.dimensions.length,
|
|
485
|
+
entityTerms: state.frame.requirements.entityTerms.length + state.frame.requirements.entityDisplayTerms.length,
|
|
486
|
+
members: state.frame.requirements.memberTerms.length,
|
|
487
|
+
filters: state.program.filters.length,
|
|
488
|
+
},
|
|
489
|
+
mission: {
|
|
490
|
+
mode: state.mission.mode,
|
|
491
|
+
taskCount: state.mission.tasks.length,
|
|
492
|
+
deferredTaskCount: state.mission.deferredTasks?.length ?? 0,
|
|
493
|
+
hypothesisCount: state.mission.hypotheses.length,
|
|
494
|
+
},
|
|
495
|
+
workspace: {
|
|
496
|
+
...(state.workspace.snapshotId ? { snapshotId: state.workspace.snapshotId } : {}),
|
|
497
|
+
...(state.workspace.sourceFingerprint ? { sourceFingerprint: state.workspace.sourceFingerprint } : {}),
|
|
498
|
+
admittedCandidateCount: state.workspace.admittedCandidateIds.length,
|
|
499
|
+
excludedCandidateCount: state.workspace.excludedCandidates.length,
|
|
500
|
+
sourceCoverage: state.workspace.sourceCoverage.map((coverage) => ({
|
|
501
|
+
source: coverage.source,
|
|
502
|
+
status: coverage.status,
|
|
503
|
+
candidateCount: coverage.candidateIds.length,
|
|
504
|
+
})),
|
|
505
|
+
tools: state.workspace.tools.map((tool) => ({
|
|
506
|
+
id: tool.id,
|
|
507
|
+
kind: tool.kind,
|
|
508
|
+
status: tool.status,
|
|
509
|
+
reasonCode: tool.reasonCode,
|
|
510
|
+
})),
|
|
511
|
+
},
|
|
512
|
+
program: {
|
|
513
|
+
id: state.program.id,
|
|
514
|
+
taskCount: state.program.taskIds.length,
|
|
515
|
+
candidateCount: state.program.candidateIds.length,
|
|
516
|
+
requiredRoles: state.program.requiredRoles,
|
|
517
|
+
outputAssertionCount: state.program.outputs.assertions.length,
|
|
518
|
+
},
|
|
519
|
+
...(state.resolvedPlan ? { resolvedPlan: state.resolvedPlan } : {}),
|
|
520
|
+
counters: {
|
|
521
|
+
planningContinuations: state.planningContinuations,
|
|
522
|
+
toolCalls: state.toolCalls,
|
|
523
|
+
executionAttempts: state.executionAttempts,
|
|
524
|
+
repairAttempts: state.repairAttempts,
|
|
525
|
+
},
|
|
526
|
+
};
|
|
527
|
+
}
|
|
288
528
|
export function defaultAgentRunSqlitePath(projectRoot) {
|
|
289
529
|
return join(projectRoot, '.dql', 'local', 'agent-runs.sqlite');
|
|
290
530
|
}
|
|
291
531
|
function parseRun(payload) {
|
|
292
532
|
try {
|
|
293
533
|
const value = JSON.parse(payload);
|
|
294
|
-
return isAgentRunRecord(value) ? value : undefined;
|
|
534
|
+
return isAgentRunRecord(value) ? normalizeRunProviderEgressReceipts(value) : undefined;
|
|
295
535
|
}
|
|
296
536
|
catch {
|
|
297
537
|
return undefined;
|
|
298
538
|
}
|
|
299
539
|
}
|
|
540
|
+
/**
|
|
541
|
+
* Durable run JSON is local and therefore untrusted on import/read. Keep an
|
|
542
|
+
* invalid provider receipt from advertising historical row egress through the
|
|
543
|
+
* primary run field, diagnostic receipt, or its diagnostic artifact payload.
|
|
544
|
+
*/
|
|
545
|
+
function normalizeRunProviderEgressReceipts(run) {
|
|
546
|
+
const normalizeReceiptList = (value) => Array.isArray(value)
|
|
547
|
+
? value.flatMap((receipt) => {
|
|
548
|
+
const normalized = normalizeProviderEgressReceiptV1(receipt);
|
|
549
|
+
return normalized ? [normalized] : [];
|
|
550
|
+
})
|
|
551
|
+
: undefined;
|
|
552
|
+
const normalizeReceiptContainer = (value) => {
|
|
553
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
554
|
+
return value;
|
|
555
|
+
const record = value;
|
|
556
|
+
const providerEgressReceipts = normalizeReceiptList(record.providerEgressReceipts);
|
|
557
|
+
return providerEgressReceipts === undefined
|
|
558
|
+
? value
|
|
559
|
+
: { ...record, providerEgressReceipts };
|
|
560
|
+
};
|
|
561
|
+
const rootReceipts = normalizeReceiptList(run.providerEgressReceipts);
|
|
562
|
+
const diagnosticReceipt = normalizeReceiptContainer(run.diagnosticReceipt);
|
|
563
|
+
const artifacts = run.artifacts.map((artifact) => {
|
|
564
|
+
if (!artifact.payload || typeof artifact.payload !== 'object' || Array.isArray(artifact.payload))
|
|
565
|
+
return artifact;
|
|
566
|
+
const payload = artifact.payload;
|
|
567
|
+
const normalizedDiagnosticReceipt = normalizeReceiptContainer(payload.diagnosticReceipt);
|
|
568
|
+
return normalizedDiagnosticReceipt === payload.diagnosticReceipt
|
|
569
|
+
? artifact
|
|
570
|
+
: { ...artifact, payload: { ...payload, diagnosticReceipt: normalizedDiagnosticReceipt } };
|
|
571
|
+
});
|
|
572
|
+
const askAnalystState = sanitizeAskAnalystStateRoleCoverage(run.askAnalystState);
|
|
573
|
+
const taskReceipts = sanitizeTaskOutcomeReceipts({
|
|
574
|
+
outcomes: run.analyticalTaskOutcomes,
|
|
575
|
+
summary: run.analyticalTaskOutcomeSummary,
|
|
576
|
+
steps: run.steps,
|
|
577
|
+
});
|
|
578
|
+
// Do not spread untrusted V3 receipts back onto the returned object. Older
|
|
579
|
+
// V1/V2 runs simply omit the additive fields; malformed V3 JSON is stripped
|
|
580
|
+
// or reduced to a blocked receipt before the notebook/API can render it.
|
|
581
|
+
const { analyticalTaskOutcomes: _rawTaskOutcomes, analyticalTaskOutcomeSummary: _rawTaskOutcomeSummary, ...durableRun } = run;
|
|
582
|
+
return {
|
|
583
|
+
...durableRun,
|
|
584
|
+
...(rootReceipts === undefined ? {} : { providerEgressReceipts: rootReceipts }),
|
|
585
|
+
...(diagnosticReceipt === run.diagnosticReceipt
|
|
586
|
+
? {}
|
|
587
|
+
: { diagnosticReceipt: diagnosticReceipt }),
|
|
588
|
+
artifacts,
|
|
589
|
+
...(askAnalystState === run.askAnalystState ? {} : { askAnalystState }),
|
|
590
|
+
...(taskReceipts.outcomes ? { analyticalTaskOutcomes: taskReceipts.outcomes } : {}),
|
|
591
|
+
...(taskReceipts.summary ? { analyticalTaskOutcomeSummary: taskReceipts.summary } : {}),
|
|
592
|
+
};
|
|
593
|
+
}
|
|
300
594
|
function parseProgress(payload) {
|
|
301
595
|
try {
|
|
302
596
|
const value = JSON.parse(payload);
|
|
303
|
-
return isAgentRunProgressRecord(value) ? value : undefined;
|
|
597
|
+
return isAgentRunProgressRecord(value) ? sanitizeProgressRoleCoverage(value) : undefined;
|
|
304
598
|
}
|
|
305
599
|
catch {
|
|
306
600
|
return undefined;
|
|
307
601
|
}
|
|
308
602
|
}
|
|
603
|
+
/**
|
|
604
|
+
* V2 role coverage is an observability summary, not a payload lane. Persist
|
|
605
|
+
* only the typed count tuples so an injected label, raw query fragment, or
|
|
606
|
+
* result value cannot survive a restart through the Ask state or diagnostic
|
|
607
|
+
* projection. The rest of the local continuation state remains intact.
|
|
608
|
+
*/
|
|
609
|
+
function sanitizeAskAnalystStateRoleCoverage(state) {
|
|
610
|
+
if (!state || (state.version !== 2 && state.version !== 3) || !Array.isArray(state.workspace.roleCoverage))
|
|
611
|
+
return state;
|
|
612
|
+
const roleCoverage = state.workspace.roleCoverage.flatMap((entry) => {
|
|
613
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry))
|
|
614
|
+
return [];
|
|
615
|
+
const record = entry;
|
|
616
|
+
if (typeof record.role !== 'string'
|
|
617
|
+
|| !EVIDENCE_CANDIDATE_ROLES.has(record.role)
|
|
618
|
+
|| !Number.isFinite(record.candidateCount)
|
|
619
|
+
|| Number(record.candidateCount) < 0)
|
|
620
|
+
return [];
|
|
621
|
+
const coverageState = record.state === 'alternatives' || record.state === 'proven'
|
|
622
|
+
? record.state
|
|
623
|
+
: undefined;
|
|
624
|
+
return [{
|
|
625
|
+
role: record.role,
|
|
626
|
+
candidateCount: Math.floor(Number(record.candidateCount)),
|
|
627
|
+
...(coverageState ? { state: coverageState } : {}),
|
|
628
|
+
}];
|
|
629
|
+
});
|
|
630
|
+
return {
|
|
631
|
+
...state,
|
|
632
|
+
workspace: {
|
|
633
|
+
...state.workspace,
|
|
634
|
+
roleCoverage,
|
|
635
|
+
},
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
function sanitizeProgressRoleCoverage(progress) {
|
|
639
|
+
const askAnalystState = sanitizeAskAnalystStateRoleCoverage(progress.askAnalystState);
|
|
640
|
+
const taskReceipts = sanitizeTaskOutcomeReceipts({
|
|
641
|
+
outcomes: progress.analyticalTaskOutcomes,
|
|
642
|
+
summary: progress.analyticalTaskOutcomeSummary,
|
|
643
|
+
steps: progress.steps,
|
|
644
|
+
});
|
|
645
|
+
const { analyticalTaskOutcomes: _rawTaskOutcomes, analyticalTaskOutcomeSummary: _rawTaskOutcomeSummary, ...durableProgress } = progress;
|
|
646
|
+
return {
|
|
647
|
+
...durableProgress,
|
|
648
|
+
...(askAnalystState === progress.askAnalystState ? {} : { askAnalystState }),
|
|
649
|
+
...(taskReceipts.outcomes ? { analyticalTaskOutcomes: taskReceipts.outcomes } : {}),
|
|
650
|
+
...(taskReceipts.summary ? { analyticalTaskOutcomeSummary: taskReceipts.summary } : {}),
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function isDurableString(value) {
|
|
654
|
+
return typeof value === 'string' && value.trim().length > 0 && value.length <= 1_000;
|
|
655
|
+
}
|
|
656
|
+
function durableStringList(value, maxItems = 32) {
|
|
657
|
+
if (!Array.isArray(value))
|
|
658
|
+
return [];
|
|
659
|
+
return [...new Set(value.filter(isDurableString).map((item) => item.trim()))].slice(0, maxItems);
|
|
660
|
+
}
|
|
661
|
+
function durableRecord(value) {
|
|
662
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
663
|
+
? value
|
|
664
|
+
: undefined;
|
|
665
|
+
}
|
|
666
|
+
function taskReceiptTrustForArtifact(value) {
|
|
667
|
+
if (value === 'grounded')
|
|
668
|
+
return 'governed';
|
|
669
|
+
return ANALYTICAL_TASK_OUTCOME_TRUST_STATES.has(value)
|
|
670
|
+
? value
|
|
671
|
+
: undefined;
|
|
672
|
+
}
|
|
673
|
+
function canonicalTaskResultProofForArtifact(value) {
|
|
674
|
+
const artifact = durableRecord(value);
|
|
675
|
+
const trustState = taskReceiptTrustForArtifact(artifact?.trustState);
|
|
676
|
+
if (!artifact || artifact.kind !== 'answer' || !trustState || trustState === 'blocked')
|
|
677
|
+
return undefined;
|
|
678
|
+
const payload = durableRecord(artifact.payload);
|
|
679
|
+
const result = durableRecord(payload?.result);
|
|
680
|
+
const fingerprint = result?.resultFingerprint;
|
|
681
|
+
if (!result || !isDurableString(fingerprint)
|
|
682
|
+
|| !Array.isArray(result.columns) || result.columns.length === 0
|
|
683
|
+
|| !Array.isArray(result.rows))
|
|
684
|
+
return undefined;
|
|
685
|
+
return { resultFingerprint: fingerprint, trustState };
|
|
686
|
+
}
|
|
687
|
+
function canonicalTaskResultProofsByTask(steps) {
|
|
688
|
+
const byTask = new Map();
|
|
689
|
+
if (!Array.isArray(steps))
|
|
690
|
+
return byTask;
|
|
691
|
+
for (const rawStep of steps) {
|
|
692
|
+
const step = durableRecord(rawStep);
|
|
693
|
+
const taskId = step?.askAnalystTaskId;
|
|
694
|
+
if (!step || !isDurableString(taskId) || !Array.isArray(step.artifacts))
|
|
695
|
+
continue;
|
|
696
|
+
const proofs = step.artifacts
|
|
697
|
+
.map(canonicalTaskResultProofForArtifact)
|
|
698
|
+
.filter((proof) => Boolean(proof));
|
|
699
|
+
if (proofs.length > 0) {
|
|
700
|
+
const byFingerprint = new Map();
|
|
701
|
+
for (const proof of proofs) {
|
|
702
|
+
const existing = byFingerprint.get(proof.resultFingerprint);
|
|
703
|
+
byFingerprint.set(proof.resultFingerprint, existing
|
|
704
|
+
? leastTrustedTaskReceiptState([existing, proof.trustState])
|
|
705
|
+
: proof.trustState);
|
|
706
|
+
}
|
|
707
|
+
byTask.set(taskId, byFingerprint);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return byTask;
|
|
711
|
+
}
|
|
712
|
+
function sanitizeTaskOutcomeFailure(value) {
|
|
713
|
+
const failure = durableRecord(value);
|
|
714
|
+
if (!failure || !isDurableString(failure.code) || !isDurableString(failure.message))
|
|
715
|
+
return undefined;
|
|
716
|
+
if (failure.phase !== 'planning' && failure.phase !== 'execution' && failure.phase !== 'dependency')
|
|
717
|
+
return undefined;
|
|
718
|
+
return { version: 1, code: failure.code, message: failure.message, phase: failure.phase };
|
|
719
|
+
}
|
|
720
|
+
function sanitizeTaskOutcomeGap(value) {
|
|
721
|
+
const gap = durableRecord(value);
|
|
722
|
+
const allowedCodes = new Set([
|
|
723
|
+
'MISSING_MEASURE', 'MISSING_DIMENSION', 'MISSING_ATTRIBUTE', 'MISSING_RELATIONSHIP',
|
|
724
|
+
'MISSING_RUNTIME_CAPABILITY', 'RESULT_CONTRACT_MISMATCH', 'PROVIDER_UNAVAILABLE',
|
|
725
|
+
'EXECUTION_FAILED', 'AMBIGUOUS_MEANING', 'POLICY_BLOCKED',
|
|
726
|
+
]);
|
|
727
|
+
const allowedPhases = new Set([
|
|
728
|
+
'retrieval', 'meaning', 'planning', 'compilation', 'execution', 'presentation',
|
|
729
|
+
]);
|
|
730
|
+
const allowedRoutes = new Set([
|
|
731
|
+
'certified', 'semantic', 'governed_relational', 'generated', 'research',
|
|
732
|
+
]);
|
|
733
|
+
if (!gap || !allowedCodes.has(gap.code)
|
|
734
|
+
|| !allowedPhases.has(gap.phase)
|
|
735
|
+
|| !isDurableString(gap.message) || typeof gap.recoverable !== 'boolean'
|
|
736
|
+
|| typeof gap.planFrozen !== 'boolean')
|
|
737
|
+
return undefined;
|
|
738
|
+
const attemptedRoutes = Array.isArray(gap.attemptedRoutes)
|
|
739
|
+
? gap.attemptedRoutes.filter((route) => typeof route === 'string' && allowedRoutes.has(route))
|
|
740
|
+
: [];
|
|
741
|
+
return {
|
|
742
|
+
version: 1,
|
|
743
|
+
code: gap.code,
|
|
744
|
+
phase: gap.phase,
|
|
745
|
+
message: gap.message,
|
|
746
|
+
searchedSources: durableStringList(gap.searchedSources),
|
|
747
|
+
attemptedRoutes,
|
|
748
|
+
missing: durableStringList(gap.missing),
|
|
749
|
+
recoverable: gap.recoverable,
|
|
750
|
+
planFrozen: gap.planFrozen,
|
|
751
|
+
nextActions: durableStringList(gap.nextActions),
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
function leastTrustedTaskReceiptState(states) {
|
|
755
|
+
if (states.length === 0)
|
|
756
|
+
return 'blocked';
|
|
757
|
+
const score = {
|
|
758
|
+
certified: 4, governed: 3, review_required: 2, not_applicable: 1, blocked: 0,
|
|
759
|
+
};
|
|
760
|
+
return states.reduce((least, candidate) => score[candidate] < score[least] ? candidate : least);
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Read local V3 task receipts as untrusted JSON. Successful status is valid
|
|
764
|
+
* only when the same task step contains a canonical immutable result artifact
|
|
765
|
+
* with the advertised fingerprint. This also keeps V1/V2 records readable:
|
|
766
|
+
* absent additive fields remain absent.
|
|
767
|
+
*/
|
|
768
|
+
function sanitizeTaskOutcomeReceipts(input) {
|
|
769
|
+
const rawSummary = durableRecord(input.summary);
|
|
770
|
+
const rawOutcomes = Array.isArray(input.outcomes) ? input.outcomes : undefined;
|
|
771
|
+
if (!rawOutcomes && !rawSummary)
|
|
772
|
+
return {};
|
|
773
|
+
const canonicalByTask = canonicalTaskResultProofsByTask(input.steps);
|
|
774
|
+
const outcomes = [];
|
|
775
|
+
const seenTaskIds = new Set();
|
|
776
|
+
for (const rawOutcome of rawOutcomes ?? []) {
|
|
777
|
+
const outcome = durableRecord(rawOutcome);
|
|
778
|
+
const taskId = outcome?.taskId;
|
|
779
|
+
const status = outcome?.status;
|
|
780
|
+
if (!outcome || !isDurableString(taskId)
|
|
781
|
+
|| !ANALYTICAL_TASK_OUTCOME_STATUSES.has(status)
|
|
782
|
+
|| seenTaskIds.has(taskId))
|
|
783
|
+
continue;
|
|
784
|
+
seenTaskIds.add(taskId);
|
|
785
|
+
const persistedTrustState = ANALYTICAL_TASK_OUTCOME_TRUST_STATES.has(outcome.trustState)
|
|
786
|
+
? outcome.trustState
|
|
787
|
+
: undefined;
|
|
788
|
+
const requestedSuccess = status === 'completed' || status === 'partial';
|
|
789
|
+
const resultFingerprint = isDurableString(outcome.resultFingerprint) ? outcome.resultFingerprint : undefined;
|
|
790
|
+
const canonicalTrustState = resultFingerprint
|
|
791
|
+
? canonicalByTask.get(taskId)?.get(resultFingerprint)
|
|
792
|
+
: undefined;
|
|
793
|
+
const canonicalResult = Boolean(canonicalTrustState);
|
|
794
|
+
if (requestedSuccess && !canonicalResult) {
|
|
795
|
+
outcomes.push({
|
|
796
|
+
version: 1,
|
|
797
|
+
taskId,
|
|
798
|
+
status: 'blocked',
|
|
799
|
+
trustState: 'blocked',
|
|
800
|
+
summary: 'This persisted task result has no matching immutable canonical artifact.',
|
|
801
|
+
failure: {
|
|
802
|
+
version: 1,
|
|
803
|
+
code: 'TASK_RESULT_ARTIFACT_UNVERIFIED',
|
|
804
|
+
message: 'This persisted task result has no matching immutable canonical artifact.',
|
|
805
|
+
phase: 'execution',
|
|
806
|
+
},
|
|
807
|
+
});
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
const trustState = canonicalTrustState
|
|
811
|
+
? persistedTrustState
|
|
812
|
+
? leastTrustedTaskReceiptState([persistedTrustState, canonicalTrustState])
|
|
813
|
+
: canonicalTrustState
|
|
814
|
+
: persistedTrustState ?? 'blocked';
|
|
815
|
+
outcomes.push({
|
|
816
|
+
version: 1,
|
|
817
|
+
taskId,
|
|
818
|
+
status: status,
|
|
819
|
+
trustState,
|
|
820
|
+
...(isDurableString(outcome.summary) ? { summary: outcome.summary } : {}),
|
|
821
|
+
...(canonicalResult && resultFingerprint ? { resultFingerprint } : {}),
|
|
822
|
+
...(sanitizeTaskOutcomeGap(outcome.gap) ? { gap: sanitizeTaskOutcomeGap(outcome.gap) } : {}),
|
|
823
|
+
...(sanitizeTaskOutcomeFailure(outcome.failure) ? { failure: sanitizeTaskOutcomeFailure(outcome.failure) } : {}),
|
|
824
|
+
...(status === 'dependency_blocked' ? { dependencyTaskIds: durableStringList(outcome.dependencyTaskIds) } : {}),
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
const requestedTaskCount = typeof rawSummary?.taskCount === 'number' && Number.isFinite(rawSummary.taskCount)
|
|
828
|
+
? Math.max(0, Math.min(100, Math.floor(rawSummary.taskCount)))
|
|
829
|
+
: 0;
|
|
830
|
+
// Preserve an execution-pending compiler checkpoint only when it makes no
|
|
831
|
+
// success claim. It becomes execution-final after the first child step.
|
|
832
|
+
const mayKeepEmptySummary = outcomes.length === 0
|
|
833
|
+
&& requestedTaskCount > 0
|
|
834
|
+
&& durableStringList(rawSummary?.successfulTaskIds).length === 0;
|
|
835
|
+
if (outcomes.length === 0 && !mayKeepEmptySummary)
|
|
836
|
+
return {};
|
|
837
|
+
const successfulTaskIds = outcomes
|
|
838
|
+
.filter((outcome) => outcome.status === 'completed' || outcome.status === 'partial')
|
|
839
|
+
.map((outcome) => outcome.taskId);
|
|
840
|
+
const failedTaskIds = outcomes
|
|
841
|
+
.filter((outcome) => outcome.status !== 'completed' && outcome.status !== 'partial' && outcome.status !== 'dependency_blocked')
|
|
842
|
+
.map((outcome) => outcome.taskId);
|
|
843
|
+
const dependencyBlockedTaskIds = outcomes
|
|
844
|
+
.filter((outcome) => outcome.status === 'dependency_blocked')
|
|
845
|
+
.map((outcome) => outcome.taskId);
|
|
846
|
+
const taskCount = Math.max(requestedTaskCount, outcomes.length);
|
|
847
|
+
const summary = {
|
|
848
|
+
version: 1,
|
|
849
|
+
status: successfulTaskIds.length === 0
|
|
850
|
+
? 'blocked'
|
|
851
|
+
: failedTaskIds.length > 0 || dependencyBlockedTaskIds.length > 0 || successfulTaskIds.length < taskCount
|
|
852
|
+
? 'partial'
|
|
853
|
+
: 'completed',
|
|
854
|
+
trustState: leastTrustedTaskReceiptState(outcomes
|
|
855
|
+
.filter((outcome) => outcome.status === 'completed' || outcome.status === 'partial')
|
|
856
|
+
.map((outcome) => outcome.trustState ?? 'blocked')),
|
|
857
|
+
taskCount,
|
|
858
|
+
successfulTaskIds,
|
|
859
|
+
failedTaskIds,
|
|
860
|
+
dependencyBlockedTaskIds,
|
|
861
|
+
};
|
|
862
|
+
return {
|
|
863
|
+
...(outcomes.length > 0 ? { outcomes } : {}),
|
|
864
|
+
summary,
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* A restart never turns an in-flight Ask into a completed parent answer, but
|
|
869
|
+
* it must not erase a completed independent child whose canonical result was
|
|
870
|
+
* checkpointed before the interruption. Keep only blocked diagnostics plus
|
|
871
|
+
* artifact/result pairs proved by the V3 task receipt.
|
|
872
|
+
*/
|
|
873
|
+
function retainedInterruptedArtifacts(progress, userCancelled) {
|
|
874
|
+
if (userCancelled)
|
|
875
|
+
return progress.artifacts;
|
|
876
|
+
const retainedIds = new Set(progress.artifacts
|
|
877
|
+
.filter((artifact) => artifact.trustState === 'blocked')
|
|
878
|
+
.map((artifact) => artifact.id));
|
|
879
|
+
for (const outcome of progress.analyticalTaskOutcomes ?? []) {
|
|
880
|
+
if ((outcome.status !== 'completed' && outcome.status !== 'partial') || !outcome.resultFingerprint)
|
|
881
|
+
continue;
|
|
882
|
+
const step = progress.steps.find((candidate) => candidate.askAnalystTaskId === outcome.taskId);
|
|
883
|
+
const artifact = step?.artifacts.find((candidate) => canonicalTaskResultProofForArtifact(candidate)?.resultFingerprint === outcome.resultFingerprint);
|
|
884
|
+
if (artifact)
|
|
885
|
+
retainedIds.add(artifact.id);
|
|
886
|
+
}
|
|
887
|
+
return progress.artifacts.filter((artifact) => retainedIds.has(artifact.id));
|
|
888
|
+
}
|
|
309
889
|
function isAgentRunRecord(value) {
|
|
310
890
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
311
891
|
return false;
|