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
This commit is contained in:
@@ -6,6 +6,17 @@ import {
|
||||
serializeWorkflowIr,
|
||||
} from "../index.js";
|
||||
|
||||
const EXECUTE_NODE_MAX_RETRIES = 2;
|
||||
|
||||
function executeNodeConfig(ir = BUILTIN_CODING_WORKFLOW_IR): Record<string, unknown> {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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" },
|
||||
|
||||
Reference in New Issue
Block a user