From 7d8ae5333eefb55aef1cc4fa664055b188d0b9f7 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Mon, 8 Jun 2026 12:34:42 -0700 Subject: [PATCH] FN-6028: add retries to builtin execute node Ensure the built-in coding workflow keeps an explicit retry budget on execute. - set `maxRetries: 2` on the built-in coding workflow execute prompt node - add IR-level tests that assert only the execute seam carries the retry budget - add built-in workflow registry tests that preserve the execute retry setting through parse/serialize round-trips Files changed: .../__tests__/builtin-coding-workflow-ir.test.ts | 31 ++++++++++++++++++++++ .../core/src/__tests__/builtin-workflows.test.ts | 27 +++++++++++++++++++ packages/core/src/builtin-coding-workflow-ir.ts | 7 ++++- 3 files changed, 64 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-6028 Fusion-Task-Lineage: cd9fb5ac-c593-438d-aeff-423b4a95fc31 --- .../builtin-coding-workflow-ir.test.ts | 31 +++++++++++++++++++ .../src/__tests__/builtin-workflows.test.ts | 27 ++++++++++++++++ .../core/src/builtin-coding-workflow-ir.ts | 7 ++++- 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/packages/core/src/__tests__/builtin-coding-workflow-ir.test.ts b/packages/core/src/__tests__/builtin-coding-workflow-ir.test.ts index 8ad04711b9..c7218735b8 100644 --- a/packages/core/src/__tests__/builtin-coding-workflow-ir.test.ts +++ b/packages/core/src/__tests__/builtin-coding-workflow-ir.test.ts @@ -6,6 +6,17 @@ import { serializeWorkflowIr, } from "../index.js"; +const EXECUTE_NODE_MAX_RETRIES = 2; + +function executeNodeConfig(ir = BUILTIN_CODING_WORKFLOW_IR): Record { + const executeNodes = ir.nodes.filter((node) => node.id === "execute" && node.config?.seam === "execute"); + expect(executeNodes).toHaveLength(1); + const config = executeNodes[0].config; + expect(config).toBeDefined(); + expect(Object.keys(config ?? {})).not.toHaveLength(0); + return config ?? {}; +} + describe("builtin coding workflow ir", () => { it("parses and round-trips", () => { const parsed = parseWorkflowIr(BUILTIN_CODING_WORKFLOW_IR); @@ -65,4 +76,24 @@ describe("builtin coding workflow ir", () => { expect(byId.get("review")?.config?.name).toBe("Review"); expect(byId.get("merge")?.config?.name).toBe("Merge boundary"); }); + + it("declares a bounded retry budget only on the execute seam", () => { + const config = executeNodeConfig(); + expect(config.maxRetries).toBe(EXECUTE_NODE_MAX_RETRIES); + expect(Number.isInteger(config.maxRetries)).toBe(true); + expect(config.maxRetries).toBeGreaterThanOrEqual(1); + expect(config.maxRetries).toBeLessThanOrEqual(10); + + const byId = new Map(BUILTIN_CODING_WORKFLOW_IR.nodes.map((n) => [n.id, n])); + expect(byId.get("review")?.config?.name).toBe("Review"); + expect(byId.get("merge")?.config?.name).toBe("Merge boundary"); + expect(byId.get("review")?.config?.maxRetries).toBeUndefined(); + expect(byId.get("merge")?.config?.maxRetries).toBeUndefined(); + }); + + it("preserves the execute retry declaration through parse/serialize round-trip", () => { + const reparsed = parseWorkflowIr(serializeWorkflowIr(BUILTIN_CODING_WORKFLOW_IR)); + const config = executeNodeConfig(reparsed); + expect(config.maxRetries).toBe(EXECUTE_NODE_MAX_RETRIES); + }); }); diff --git a/packages/core/src/__tests__/builtin-workflows.test.ts b/packages/core/src/__tests__/builtin-workflows.test.ts index ccba51dd06..d9baaf7fbd 100644 --- a/packages/core/src/__tests__/builtin-workflows.test.ts +++ b/packages/core/src/__tests__/builtin-workflows.test.ts @@ -7,6 +7,8 @@ import { compileWorkflowToSteps } from "../workflow-compiler.js"; import { DEFAULT_WORKFLOW_COLUMN_IDS, parseWorkflowIr, serializeWorkflowIr } from "../workflow-ir.js"; import { createTaskStoreTestHarness } from "./store-test-helpers.js"; +const EXECUTE_NODE_MAX_RETRIES = 2; + describe("built-in workflows", () => { // Graph-only built-ins (step inversion, KTD-9) model branching/foreach/rework // structure the linear compiler cannot lower to a step list — they run only @@ -87,6 +89,31 @@ describe("built-in workflows", () => { ]); }); + it("builtin:coding exposes execute retries after registry lookup and parse round-trip", () => { + const coding = getBuiltinWorkflow("builtin:coding"); + expect(coding).toBeDefined(); + const ir = parseWorkflowIr(coding!.ir); + const reparsed = parseWorkflowIr(serializeWorkflowIr(ir)); + + for (const candidate of [ir, reparsed]) { + const executeNodes = candidate.nodes.filter((node) => node.id === "execute" && node.config?.seam === "execute"); + expect(executeNodes).toHaveLength(1); + const executeConfig = executeNodes[0].config; + expect(executeConfig).toBeDefined(); + expect(Object.keys(executeConfig ?? {})).not.toHaveLength(0); + expect(executeConfig?.maxRetries).toBe(EXECUTE_NODE_MAX_RETRIES); + expect(Number.isInteger(executeConfig?.maxRetries)).toBe(true); + expect(executeConfig?.maxRetries).toBeGreaterThanOrEqual(1); + expect(executeConfig?.maxRetries).toBeLessThanOrEqual(10); + + const byId = new Map(candidate.nodes.map((node) => [node.id, node])); + expect(byId.get("review")?.config?.name).toBe("Review"); + expect(byId.get("merge")?.config?.name).toBe("Merge boundary"); + expect(byId.get("review")?.config?.maxRetries).toBeUndefined(); + expect(byId.get("merge")?.config?.maxRetries).toBeUndefined(); + } + }); + it("builtin:coding exposes merge-blocker and human-review traits on in-review", () => { const coding = getBuiltinWorkflow("builtin:coding"); expect(coding).toBeDefined(); diff --git a/packages/core/src/builtin-coding-workflow-ir.ts b/packages/core/src/builtin-coding-workflow-ir.ts index 2567fcb115..d4515a1a97 100644 --- a/packages/core/src/builtin-coding-workflow-ir.ts +++ b/packages/core/src/builtin-coding-workflow-ir.ts @@ -47,7 +47,12 @@ const RAW_BUILTIN_CODING_WORKFLOW_IR: WorkflowIr = { ], nodes: [ { id: "start", kind: "start", column: "triage" }, - { id: "execute", kind: "prompt", column: "in-progress", config: builtinPromptConfig("execute", "Execute") }, + { + id: "execute", + kind: "prompt", + column: "in-progress", + config: { ...builtinPromptConfig("execute", "Execute"), maxRetries: 2 }, + }, { id: "review", kind: "prompt", column: "in-review", config: builtinPromptConfig("review", "Review") }, { id: "merge", kind: "prompt", column: "in-review", config: builtinPromptConfig("merge", "Merge boundary") }, { id: "end", kind: "end", column: "done" },