@inflexa-ai/harness 0.4.2 → 0.4.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/app/synthesize-run.d.ts +3 -1
- package/dist/app/synthesize-run.js +14 -3
- package/dist/contracts/chat-parts.d.ts +0 -2
- package/dist/contracts/schemas/chat-parts.d.ts +3 -5
- package/dist/contracts/schemas/chat-parts.js +0 -1
- package/dist/execution/run-synthesis.d.ts +7 -1
- package/dist/execution/run-synthesis.js +181 -39
- package/dist/lib/zod-issues.d.ts +80 -0
- package/dist/lib/zod-issues.js +227 -0
- package/dist/loop/run-agent.js +18 -6
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +12 -5
- package/dist/prompts/synthesis-agent.d.ts +1 -1
- package/dist/prompts/synthesis-agent.js +39 -8
- package/dist/schemas/run-synthesis.d.ts +8 -2
- package/dist/schemas/run-synthesis.js +6 -5
- package/dist/state/index.d.ts +2 -2
- package/dist/state/index.js +2 -2
- package/dist/state/init.js +6 -0
- package/dist/state/runs.d.ts +7 -1
- package/dist/state/runs.js +21 -4
- package/dist/state/schema.d.ts +14 -0
- package/dist/state/schema.js +9 -0
- package/dist/tools/iterate-report.js +2 -0
- package/dist/tools/research/generate-plan.js +4 -2
- package/dist/tools/research/inspect-run.d.ts +4 -2
- package/dist/tools/research/inspect-run.js +12 -4
- package/dist/workflows/execute-analysis.d.ts +3 -0
- package/dist/workflows/execute-analysis.js +24 -1
- package/package.json +1 -1
package/dist/state/init.js
CHANGED
|
@@ -61,6 +61,10 @@ CREATE TABLE IF NOT EXISTS cortex_runs (
|
|
|
61
61
|
started_at TEXT NOT NULL,
|
|
62
62
|
completed_at TEXT,
|
|
63
63
|
error TEXT,
|
|
64
|
+
-- Run-level synthesis outcome. NULL means "unknown": a legacy row written
|
|
65
|
+
-- before these columns existed, or a run whose synthesis never recorded a result.
|
|
66
|
+
synthesis_status TEXT,
|
|
67
|
+
synthesis_reason TEXT,
|
|
64
68
|
parts JSONB,
|
|
65
69
|
mandate_jti TEXT, -- oss-core-managed-ok run-mandate ledger, nullable, OSS leaves null
|
|
66
70
|
mandate_expires_at TEXT -- oss-core-managed-ok run-mandate ledger, nullable, OSS leaves null
|
|
@@ -318,6 +322,8 @@ export async function initCortexState(pool, injected) {
|
|
|
318
322
|
"ALTER TABLE cortex_runs ADD COLUMN IF NOT EXISTS parts JSONB",
|
|
319
323
|
"ALTER TABLE cortex_runs ADD COLUMN IF NOT EXISTS mandate_jti TEXT", // oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
320
324
|
"ALTER TABLE cortex_runs ADD COLUMN IF NOT EXISTS mandate_expires_at TEXT", // oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
325
|
+
"ALTER TABLE cortex_runs ADD COLUMN IF NOT EXISTS synthesis_status TEXT",
|
|
326
|
+
"ALTER TABLE cortex_runs ADD COLUMN IF NOT EXISTS synthesis_reason TEXT",
|
|
321
327
|
"ALTER TABLE cortex_analysis_state ADD COLUMN IF NOT EXISTS data_profile_result JSONB",
|
|
322
328
|
"ALTER TABLE cortex_analysis_state ADD COLUMN IF NOT EXISTS billing_context JSONB",
|
|
323
329
|
// Sandbox reliability telemetry (from sandbox-reliability change on main).
|
package/dist/state/runs.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import type { ResultAsync } from "neverthrow";
|
|
13
13
|
import type { DbError } from "../lib/db-result.js";
|
|
14
14
|
import type { Querier } from "./db.js";
|
|
15
|
-
import type { CortexRunRow } from "./schema.js";
|
|
15
|
+
import type { CortexRunRow, SynthesisStatus } from "./schema.js";
|
|
16
16
|
export interface InsertRunInput {
|
|
17
17
|
runId: string;
|
|
18
18
|
analysisId: string;
|
|
@@ -67,3 +67,9 @@ export declare function queryRunsByThread(pool: Querier, analysisId: string, thr
|
|
|
67
67
|
* The JWT itself is NOT persisted — it rides in DBOS workflow input.
|
|
68
68
|
*/
|
|
69
69
|
export declare function setRunMandate(pool: Querier, runId: string, jti: string, expiresAt: string): ResultAsync<void, DbError>;
|
|
70
|
+
/**
|
|
71
|
+
* Record the run-level synthesis outcome on the run ledger. `synthesisReason`
|
|
72
|
+
* carries operator-facing detail for a skipped/failed outcome; it stays null
|
|
73
|
+
* for the "produced" case.
|
|
74
|
+
*/
|
|
75
|
+
export declare function setRunSynthesisOutcome(pool: Querier, runId: string, synthesisStatus: SynthesisStatus, synthesisReason?: string | null): ResultAsync<void, DbError>;
|
package/dist/state/runs.js
CHANGED
|
@@ -80,7 +80,7 @@ export function queryActiveRun(pool, analysisId, planId) {
|
|
|
80
80
|
return tryQuery("runs.queryActiveRun", async () => {
|
|
81
81
|
const result = await pool.query({
|
|
82
82
|
text: `SELECT run_id, analysis_id, thread_id, workflow_name,
|
|
83
|
-
status, started_at, completed_at, error, parts,
|
|
83
|
+
status, started_at, completed_at, error, synthesis_status, synthesis_reason, parts,
|
|
84
84
|
mandate_jti, mandate_expires_at, plan_id -- oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
85
85
|
FROM cortex_runs
|
|
86
86
|
WHERE analysis_id = $1
|
|
@@ -118,7 +118,7 @@ export function queryRun(pool, runId) {
|
|
|
118
118
|
return tryQuery("runs.queryRun", async () => {
|
|
119
119
|
const result = await pool.query({
|
|
120
120
|
text: `SELECT run_id, analysis_id, thread_id, workflow_name,
|
|
121
|
-
status, started_at, completed_at, error, parts,
|
|
121
|
+
status, started_at, completed_at, error, synthesis_status, synthesis_reason, parts,
|
|
122
122
|
mandate_jti, mandate_expires_at, plan_id -- oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
123
123
|
FROM cortex_runs
|
|
124
124
|
WHERE run_id = $1`,
|
|
@@ -132,7 +132,7 @@ export function queryRunsByAnalysis(pool, analysisId, { limit = 50, offset = 0 }
|
|
|
132
132
|
return tryQuery("runs.queryRunsByAnalysis", async () => {
|
|
133
133
|
const result = await pool.query({
|
|
134
134
|
text: `SELECT run_id, analysis_id, thread_id, workflow_name,
|
|
135
|
-
status, started_at, completed_at, error, parts,
|
|
135
|
+
status, started_at, completed_at, error, synthesis_status, synthesis_reason, parts,
|
|
136
136
|
mandate_jti, mandate_expires_at, plan_id -- oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
137
137
|
FROM cortex_runs
|
|
138
138
|
WHERE analysis_id = $1
|
|
@@ -146,7 +146,7 @@ export function queryRunsByThread(pool, analysisId, threadId, { limit = 50, offs
|
|
|
146
146
|
return tryQuery("runs.queryRunsByThread", async () => {
|
|
147
147
|
const result = await pool.query({
|
|
148
148
|
text: `SELECT run_id, analysis_id, thread_id, workflow_name,
|
|
149
|
-
status, started_at, completed_at, error, parts,
|
|
149
|
+
status, started_at, completed_at, error, synthesis_status, synthesis_reason, parts,
|
|
150
150
|
mandate_jti, mandate_expires_at, plan_id -- oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
151
151
|
FROM cortex_runs
|
|
152
152
|
WHERE analysis_id = $1 AND thread_id = $2
|
|
@@ -174,6 +174,21 @@ export function setRunMandate(pool, runId, jti, expiresAt) {
|
|
|
174
174
|
});
|
|
175
175
|
});
|
|
176
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Record the run-level synthesis outcome on the run ledger. `synthesisReason`
|
|
179
|
+
* carries operator-facing detail for a skipped/failed outcome; it stays null
|
|
180
|
+
* for the "produced" case.
|
|
181
|
+
*/
|
|
182
|
+
export function setRunSynthesisOutcome(pool, runId, synthesisStatus, synthesisReason) {
|
|
183
|
+
return tryMutation("runs.setRunSynthesisOutcome", async () => {
|
|
184
|
+
await pool.query({
|
|
185
|
+
text: `UPDATE cortex_runs
|
|
186
|
+
SET synthesis_status = $1, synthesis_reason = $2
|
|
187
|
+
WHERE run_id = $3`,
|
|
188
|
+
values: [synthesisStatus, synthesisReason ?? null, runId],
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
}
|
|
177
192
|
function mapRunRow(row) {
|
|
178
193
|
// JSONB is parsed by `pg` into native arrays/objects. Treat legacy TEXT
|
|
179
194
|
// rows (if anything slipped through) as strings and parse them.
|
|
@@ -198,6 +213,8 @@ function mapRunRow(row) {
|
|
|
198
213
|
startedAt: row.started_at,
|
|
199
214
|
completedAt: row.completed_at ?? null,
|
|
200
215
|
error: row.error ?? null,
|
|
216
|
+
synthesisStatus: row.synthesis_status ?? null,
|
|
217
|
+
synthesisReason: row.synthesis_reason ?? null,
|
|
201
218
|
parts,
|
|
202
219
|
mandateJti: row.mandate_jti ?? null, // oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
203
220
|
mandateExpiresAt: row.mandate_expires_at ?? null, // oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
package/dist/state/schema.d.ts
CHANGED
|
@@ -64,6 +64,13 @@ export declare const StepExecutionStatus: z.ZodEnum<{
|
|
|
64
64
|
blocked: "blocked";
|
|
65
65
|
}>;
|
|
66
66
|
export type StepExecutionStatus = z.infer<typeof StepExecutionStatus>;
|
|
67
|
+
export declare const SynthesisStatus: z.ZodEnum<{
|
|
68
|
+
failed: "failed";
|
|
69
|
+
produced: "produced";
|
|
70
|
+
skipped_no_summaries: "skipped_no_summaries";
|
|
71
|
+
skipped_blocker: "skipped_blocker";
|
|
72
|
+
}>;
|
|
73
|
+
export type SynthesisStatus = z.infer<typeof SynthesisStatus>;
|
|
67
74
|
export declare const CortexRunRowSchema: z.ZodObject<{
|
|
68
75
|
runId: z.ZodString;
|
|
69
76
|
analysisId: z.ZodString;
|
|
@@ -80,6 +87,13 @@ export declare const CortexRunRowSchema: z.ZodObject<{
|
|
|
80
87
|
startedAt: z.ZodString;
|
|
81
88
|
completedAt: z.ZodNullable<z.ZodString>;
|
|
82
89
|
error: z.ZodNullable<z.ZodString>;
|
|
90
|
+
synthesisStatus: z.ZodNullable<z.ZodEnum<{
|
|
91
|
+
failed: "failed";
|
|
92
|
+
produced: "produced";
|
|
93
|
+
skipped_no_summaries: "skipped_no_summaries";
|
|
94
|
+
skipped_blocker: "skipped_blocker";
|
|
95
|
+
}>>;
|
|
96
|
+
synthesisReason: z.ZodNullable<z.ZodString>;
|
|
83
97
|
parts: z.ZodNullable<z.ZodArray<z.ZodAny>>;
|
|
84
98
|
mandateJti: z.ZodNullable<z.ZodString>;
|
|
85
99
|
mandateExpiresAt: z.ZodNullable<z.ZodString>;
|
package/dist/state/schema.js
CHANGED
|
@@ -35,6 +35,11 @@ export const ArtifactRowSchema = z.object({
|
|
|
35
35
|
// ── Run & Step Execution Schemas ─────────────────────────────────────
|
|
36
36
|
export const RunStatus = z.enum(["running", "completed", "partial", "failed", "canceled", "suspended_insufficient_funds"]);
|
|
37
37
|
export const StepExecutionStatus = z.enum(["pending", "running", "completed", "failed", "skipped", "canceled", "blocked"]);
|
|
38
|
+
// Run-level synthesis outcome, distinct from RunStatus: a run can reach
|
|
39
|
+
// status="completed" while synthesis produced nothing. "produced" is the only
|
|
40
|
+
// outcome that wrote a synthesis result; the two "skipped_*" variants and
|
|
41
|
+
// "failed" record why none exists.
|
|
42
|
+
export const SynthesisStatus = z.enum(["produced", "skipped_no_summaries", "skipped_blocker", "failed"]);
|
|
38
43
|
export const CortexRunRowSchema = z.object({
|
|
39
44
|
runId: z.string(),
|
|
40
45
|
analysisId: z.string(),
|
|
@@ -44,6 +49,10 @@ export const CortexRunRowSchema = z.object({
|
|
|
44
49
|
startedAt: z.string(),
|
|
45
50
|
completedAt: z.string().nullable(),
|
|
46
51
|
error: z.string().nullable(),
|
|
52
|
+
// NULL synthesisStatus means "unknown" — a legacy row, or a run whose
|
|
53
|
+
// synthesis never recorded an outcome.
|
|
54
|
+
synthesisStatus: SynthesisStatus.nullable(),
|
|
55
|
+
synthesisReason: z.string().nullable(),
|
|
47
56
|
parts: z.array(z.any()).nullable(),
|
|
48
57
|
mandateJti: z.string().nullable(), // oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
49
58
|
mandateExpiresAt: z.string().nullable(), // oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
@@ -50,6 +50,7 @@ import { previewDir } from "../workspace/paths.js";
|
|
|
50
50
|
import { runReportIteration } from "../execution/report-runner.js";
|
|
51
51
|
import { formatBytes, stageReportAssets } from "./lib/report-preflight.js";
|
|
52
52
|
import { createNoopLogger } from "../lib/console-logger.js";
|
|
53
|
+
import { hintForZodIssue } from "../lib/zod-issues.js";
|
|
53
54
|
const REPORT_TOOL_ACCESS_TTL_SECONDS = 3600;
|
|
54
55
|
const PREVIEW_META_FILE = "preview-meta.json";
|
|
55
56
|
async function readPreviewMeta(metaPath) {
|
|
@@ -435,6 +436,7 @@ export function createReportSubmitTool(deps) {
|
|
|
435
436
|
issues: parsed.error.issues.map((i) => ({
|
|
436
437
|
path: i.path.join(".") || "(root)",
|
|
437
438
|
message: i.message,
|
|
439
|
+
hint: hintForZodIssue(i, input.report),
|
|
438
440
|
})),
|
|
439
441
|
hint: "The `report` brief did not match the schema. Call `plan_report` for the full schema, fix the fields named in `issues`, and resubmit.",
|
|
440
442
|
});
|
|
@@ -41,6 +41,7 @@ import { hydratePlanSteps, PlannerPlanSchema } from "../../schemas/plan-schemas.
|
|
|
41
41
|
import { validatePlan } from "../../schemas/validate-plan.js";
|
|
42
42
|
import { AnalysisPlanSchema } from "../../schemas/workflow-state.js";
|
|
43
43
|
import { unwrapOrThrow } from "../../lib/result.js";
|
|
44
|
+
import { hintForZodIssue } from "../../lib/zod-issues.js";
|
|
44
45
|
import { insertPlan, loadDataProfileStatus, loadPlan } from "../../state/index.js";
|
|
45
46
|
// ── Tool-level config ──────────────────────────────────────────────
|
|
46
47
|
/** Sub-agent identity for the planner — provenance only. */
|
|
@@ -201,11 +202,12 @@ async function persistPlan(plan, ctx, pool) {
|
|
|
201
202
|
}
|
|
202
203
|
}
|
|
203
204
|
// ── Validation ──────────────────────────────────────────────────────
|
|
204
|
-
function zodIssuesToValidationIssues(error, rootPath = "plan") {
|
|
205
|
+
function zodIssuesToValidationIssues(error, input, rootPath = "plan") {
|
|
205
206
|
return error.issues.map((i) => ({
|
|
206
207
|
path: [rootPath, ...i.path.map((p) => String(p))].join("."),
|
|
207
208
|
code: "schema",
|
|
208
209
|
message: i.message,
|
|
210
|
+
hint: hintForZodIssue(i, input),
|
|
209
211
|
}));
|
|
210
212
|
}
|
|
211
213
|
/**
|
|
@@ -215,7 +217,7 @@ function zodIssuesToValidationIssues(error, rootPath = "plan") {
|
|
|
215
217
|
function fullyValidate(candidate, resourcePolicy) {
|
|
216
218
|
const parsed = PlannerPlanSchema.safeParse(candidate);
|
|
217
219
|
if (!parsed.success) {
|
|
218
|
-
return { valid: false, issues: zodIssuesToValidationIssues(parsed.error) };
|
|
220
|
+
return { valid: false, issues: zodIssuesToValidationIssues(parsed.error, candidate) };
|
|
219
221
|
}
|
|
220
222
|
// Semantic checks operate on the AnalysisPlan shape — PlannerPlan omits
|
|
221
223
|
// maxSteps, so inject the execution-time default to satisfy the validator.
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* conversation thread on completion. The retired `run-result-writer` push
|
|
10
10
|
* payload (run status, step summaries, run synthesis, artifact paths) is
|
|
11
11
|
* surfaced here on demand: the run row + step `summaryPath` references +
|
|
12
|
-
* the `synthesisPath` for
|
|
13
|
-
* `read_file` the artefacts it needs.
|
|
12
|
+
* the `synthesisPath` for runs whose synthesis was produced let the
|
|
13
|
+
* conversation agent `read_file` the artefacts it needs.
|
|
14
14
|
*/
|
|
15
15
|
import type { Pool } from "pg";
|
|
16
16
|
import type { CortexRunRow } from "../../state/schema.js";
|
|
@@ -39,6 +39,7 @@ type InspectRunOutput = {
|
|
|
39
39
|
};
|
|
40
40
|
declare function formatRun(r: CortexRunRow, verbose: boolean): {
|
|
41
41
|
error?: string | null | undefined;
|
|
42
|
+
synthesisReason?: string | undefined;
|
|
42
43
|
runId: string;
|
|
43
44
|
workflowName: string;
|
|
44
45
|
status: "running" | "completed" | "failed" | "partial" | "canceled" | "suspended_insufficient_funds";
|
|
@@ -46,6 +47,7 @@ declare function formatRun(r: CortexRunRow, verbose: boolean): {
|
|
|
46
47
|
completedAt: string | null;
|
|
47
48
|
planId: string | null;
|
|
48
49
|
synthesisPath: string | null;
|
|
50
|
+
synthesisStatus: "failed" | "produced" | "skipped_no_summaries" | "skipped_blocker" | null;
|
|
49
51
|
};
|
|
50
52
|
export declare function createInspectRunTool(pool: Pool): import("../define-tool.js").Tool<{
|
|
51
53
|
runId?: string | undefined;
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* conversation thread on completion. The retired `run-result-writer` push
|
|
10
10
|
* payload (run status, step summaries, run synthesis, artifact paths) is
|
|
11
11
|
* surfaced here on demand: the run row + step `summaryPath` references +
|
|
12
|
-
* the `synthesisPath` for
|
|
13
|
-
* `read_file` the artefacts it needs.
|
|
12
|
+
* the `synthesisPath` for runs whose synthesis was produced let the
|
|
13
|
+
* conversation agent `read_file` the artefacts it needs.
|
|
14
14
|
*/
|
|
15
15
|
import { ok } from "neverthrow";
|
|
16
16
|
import { z } from "zod";
|
|
@@ -26,7 +26,15 @@ function formatRun(r, verbose) {
|
|
|
26
26
|
startedAt: r.startedAt,
|
|
27
27
|
completedAt: r.completedAt,
|
|
28
28
|
planId: r.planId,
|
|
29
|
-
|
|
29
|
+
// synthesis.json is written only when synthesis actually produced a
|
|
30
|
+
// result; a skipped/failed synthesis leaves no file, so pointing at a
|
|
31
|
+
// path would send the agent to a stale or absent artifact. The recorded
|
|
32
|
+
// outcome — surfaced as synthesisStatus (and synthesisReason when set) —
|
|
33
|
+
// lets a consumer tell "produced" from "skipped/failed/unknown" without
|
|
34
|
+
// reading a file.
|
|
35
|
+
synthesisPath: r.synthesisStatus === "produced" ? `runs/${r.runId}/synthesis.json` : null,
|
|
36
|
+
synthesisStatus: r.synthesisStatus,
|
|
37
|
+
...(r.synthesisReason !== null ? { synthesisReason: r.synthesisReason } : {}),
|
|
30
38
|
...(verbose ? { error: r.error } : {}),
|
|
31
39
|
};
|
|
32
40
|
}
|
|
@@ -35,7 +43,7 @@ export function createInspectRunTool(pool) {
|
|
|
35
43
|
id: "inspect_run",
|
|
36
44
|
description: "Inspect workflow runs for this analysis. " +
|
|
37
45
|
"Without a runId: lists all runs. " +
|
|
38
|
-
"With a runId: returns step-level details with paths to summary.md
|
|
46
|
+
"With a runId: returns step-level details with paths to summary.md, plus each run's synthesisStatus and a synthesisPath to synthesis.json only when synthesis was produced. " +
|
|
39
47
|
"Default response is lightweight — pass includeDiagnostics:true to add error/duration/retry telemetry. " +
|
|
40
48
|
"Read summary.md or synthesis.json for findings, or use workspace search with type:summary or type:synthesis.",
|
|
41
49
|
inputSchema: z.object({
|
|
@@ -58,6 +58,7 @@ import type { MachineBudget, ResourceSpec } from "../config/resource-limits.js";
|
|
|
58
58
|
import type { RunSession } from "../auth/types.js";
|
|
59
59
|
import type { RunAuthorizer } from "../execution/run-authorizer.js";
|
|
60
60
|
import type { Logger } from "../lib/logger.js";
|
|
61
|
+
import type { SynthesisStatus } from "../state/index.js";
|
|
61
62
|
import type { AnalysisStep } from "../schemas/workflow-state.js";
|
|
62
63
|
import type { ChatProvider, EmbeddingProvider } from "../providers/types.js";
|
|
63
64
|
import type { BioToolKeys } from "../tools/bio/keys.js";
|
|
@@ -297,6 +298,8 @@ export declare function synthesizeFindings(args: {
|
|
|
297
298
|
deps: ExecuteAnalysisDeps;
|
|
298
299
|
}): Promise<{
|
|
299
300
|
findings: readonly RunFinding[];
|
|
301
|
+
synthesisStatus: Exclude<SynthesisStatus, "failed">;
|
|
302
|
+
synthesisReason: string | null;
|
|
300
303
|
}>;
|
|
301
304
|
/**
|
|
302
305
|
* Register the executeAnalysis parent workflow with DBOS. Caller is
|
|
@@ -59,7 +59,7 @@ import { join } from "node:path";
|
|
|
59
59
|
import { forStep } from "../auth/types.js";
|
|
60
60
|
import { createNoopLogger } from "../lib/console-logger.js";
|
|
61
61
|
import { unwrapOrThrow } from "../lib/result.js";
|
|
62
|
-
import { RunDedupCollisionError, countArtifactsForRun, loadDataProfileStatus, queryActiveRun, queryStepArtifactPaths, seedStepExecutions, suspendAnalysis as suspendAnalysisQuery, sweepPendingStepExecutions, updateRunStatus, } from "../state/index.js";
|
|
62
|
+
import { RunDedupCollisionError, countArtifactsForRun, loadDataProfileStatus, queryActiveRun, queryStepArtifactPaths, seedStepExecutions, setRunSynthesisOutcome, suspendAnalysis as suspendAnalysisQuery, sweepPendingStepExecutions, updateRunStatus, } from "../state/index.js";
|
|
63
63
|
import { isBudgetExceeded } from "../loop/budget-exceeded.js";
|
|
64
64
|
import { loadStepSummariesFromDisk } from "../execution/run-synthesis.js";
|
|
65
65
|
import { MAX_UPSTREAM_ARTIFACTS, composeStepBriefing } from "../prompts/briefing.js";
|
|
@@ -224,6 +224,7 @@ export function synthesizeFindings(args) {
|
|
|
224
224
|
return DBOS.writeStream("events", part);
|
|
225
225
|
};
|
|
226
226
|
return synthesizeRun({
|
|
227
|
+
logger: deps.logger,
|
|
227
228
|
pool: deps.pool,
|
|
228
229
|
provider: deps.provider,
|
|
229
230
|
embedding: deps.embedding,
|
|
@@ -333,6 +334,9 @@ export async function runExecuteAnalysisBody(input, deps) {
|
|
|
333
334
|
// after so the workflow record goes to ERROR.
|
|
334
335
|
let synthesisError = null;
|
|
335
336
|
let synthesisFindings = [];
|
|
337
|
+
// Null when synthesis never ran (disabled or no completed steps): the ledger
|
|
338
|
+
// columns stay NULL — "unknown", not a recorded skip.
|
|
339
|
+
let synthesisOutcome = null;
|
|
336
340
|
const synthesisEnabled = deps.synthesisEnabled ?? true;
|
|
337
341
|
if (synthesisEnabled && final.completed.size > 0) {
|
|
338
342
|
try {
|
|
@@ -344,9 +348,11 @@ export async function runExecuteAnalysisBody(input, deps) {
|
|
|
344
348
|
deps,
|
|
345
349
|
}), { name: "synthesize-findings" });
|
|
346
350
|
synthesisFindings = synthOut.findings;
|
|
351
|
+
synthesisOutcome = { status: synthOut.synthesisStatus, reason: synthOut.synthesisReason };
|
|
347
352
|
}
|
|
348
353
|
catch (err) {
|
|
349
354
|
synthesisError = err;
|
|
355
|
+
synthesisOutcome = { status: "failed", reason: err instanceof Error ? err.message : String(err) };
|
|
350
356
|
logger.error("synthesizeFindings failed — failing the run", { runId, ...logger.errorFields(err) });
|
|
351
357
|
}
|
|
352
358
|
}
|
|
@@ -369,6 +375,7 @@ export async function runExecuteAnalysisBody(input, deps) {
|
|
|
369
375
|
: final.failureReason,
|
|
370
376
|
forceFailed: synthesisError !== null,
|
|
371
377
|
findings: synthesisFindings,
|
|
378
|
+
synthesisOutcome,
|
|
372
379
|
deps,
|
|
373
380
|
});
|
|
374
381
|
// Synthesis failure takes priority over the budget-exceeded cascade: a run
|
|
@@ -741,6 +748,22 @@ async function collectAndComplete(args) {
|
|
|
741
748
|
catch (err) {
|
|
742
749
|
logger.error("persist-final-status failed", { status, ...logger.errorFields(err) });
|
|
743
750
|
}
|
|
751
|
+
// Record the run's synthesis outcome AFTER the status write — a reader that
|
|
752
|
+
// sees a terminal status then also sees whether (and why) synthesis ran. A
|
|
753
|
+
// null outcome means synthesis never ran; leave the columns NULL ("unknown").
|
|
754
|
+
// Log-don't-rollback, like the sibling terminal steps: a failed ledger note
|
|
755
|
+
// must not undo an otherwise-complete run.
|
|
756
|
+
const outcome = args.synthesisOutcome;
|
|
757
|
+
if (outcome !== null) {
|
|
758
|
+
try {
|
|
759
|
+
await DBOS.runStep(async () => {
|
|
760
|
+
unwrapOrThrow(await setRunSynthesisOutcome(deps.pool, runId, outcome.status, outcome.reason));
|
|
761
|
+
}, { name: "persist-synthesis-outcome" });
|
|
762
|
+
}
|
|
763
|
+
catch (err) {
|
|
764
|
+
logger.error("persist-synthesis-outcome failed", { synthesisStatus: outcome.status, ...logger.errorFields(err) });
|
|
765
|
+
}
|
|
766
|
+
}
|
|
744
767
|
// Sweep never-started steps to `skipped` — but ONLY on genuinely-terminal
|
|
745
768
|
// paths. The gate is the BRANCH, not the written run status: the resumable
|
|
746
769
|
// 402 pause below also writes "canceled", yet its pending rows must survive
|
package/package.json
CHANGED