diff --git a/.changeset/cli-node-override-wip-lane.md b/.changeset/cli-node-override-wip-lane.md new file mode 100644 index 0000000000..872bd228e7 --- /dev/null +++ b/.changeset/cli-node-override-wip-lane.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Fix the node-override guard not blocking mid-flight changes on boards with a renamed WIP lane. +category: fix +dev: `fn_task_update` called `validateNodeOverrideChange` without options, so `wipColumns` fell back to the literal `{"in-progress"}` and the running-task check never fired on a renamed board. It now resolves the task's own WIP and COMPLETE lanes via `resolveTaskLifecycleColumns`. diff --git a/packages/cli/src/extension.ts b/packages/cli/src/extension.ts index 41f039357b..0d30428da0 100644 --- a/packages/cli/src/extension.ts +++ b/packages/cli/src/extension.ts @@ -1819,7 +1819,30 @@ export default function kbExtension(pi: ExtensionAPI) { store.updateTask all exhibit identical behavior. */ const normalizedNodeId = normalizeNullableStringInput(params.nodeId); - const validation = validateNodeOverrideChange(task, normalizedNodeId ?? null); + /* + FNXC:WorkflowResolvedColumns 2026-07-31-12:20: + Supply this task's resolved WIP and COMPLETE lanes — without them the guard does not fire. + + `validateNodeOverrideChange` defaults `wipColumns` to `{"in-progress"}`, so on a board whose + WIP lane is named anything else `wipColumns.has(task.column)` is false and the mid-flight + check passes. An operator could then change the node override on a RUNNING task, which is + precisely what that guard exists to refuse (see its own note in node-override-guard.ts). + + The guard's options doc says "Both callers supply them" and assumes a CLI tool has no cheap + IR access. Neither held here: this is a third caller, and it is an async handler that has + already awaited `store.getTask`, so one more resolve is the same cost `resolveTaskLifecycleColumns` + is already paid for elsewhere in this file (the linked-lineage label at ~1239). + + Passed as present-but-conditionally-valued rather than a conditional argument: an omitted + set keeps the documented legacy default, and only this shape is visible to + scripts/lib/lane-wiring-census.mjs, which matches an object-literal argument and cannot see a + ternary. This site was a known-unwired entry in that gate's baseline. + */ + const nodeOverrideLifecycle = await resolveTaskLifecycleColumns(store, task.id); + const validation = validateNodeOverrideChange(task, normalizedNodeId ?? null, { + wipColumns: nodeOverrideLifecycle?.wip ? new Set([nodeOverrideLifecycle.wip]) : undefined, + completeColumns: nodeOverrideLifecycle?.complete ? new Set([nodeOverrideLifecycle.complete]) : undefined, + }); if (!validation.allowed) { return { content: [{ type: "text", text: validation.message ?? "Node override change blocked" }], diff --git a/scripts/lib/lane-wiring-baseline.json b/scripts/lib/lane-wiring-baseline.json index 587e0328aa..3a54486ac3 100644 --- a/scripts/lib/lane-wiring-baseline.json +++ b/scripts/lib/lane-wiring-baseline.json @@ -15,7 +15,6 @@ "packages/dashboard/app/hooks/useBlockerFanout.ts": 1, "packages/cli/src/commands/dashboard-tui/app.tsx": 1, "packages/cli/src/commands/dashboard-tui/bucket-mapping.ts": 1, - "packages/cli/src/extension.ts": 1, "plugins/fusion-plugin-dependency-graph/src/GraphTaskNode.tsx": 1, "plugins/fusion-plugin-even-realities-glasses/src/routes/board-routes.ts": 1 }