feat(FN-2721): enforce node override guard across task workflows

- Add a core node-override-guard module, export it from @fusion/core, and enforce conflicts in store updates
- Add API route and CLI extension safeguards so nodeId override updates are blocked when ownership would conflict
- Wire node override routing and validation through dashboard quick-create, list, modal, settings, and task form/detail surfaces
- Add focused unit and integration tests for core guard logic, workflow routes, and dashboard node override UX
This commit is contained in:
Fusion
2026-04-28 01:49:55 -07:00
committed by gsxdsm
parent 3174f0dc64
commit 789afc30b0
22 changed files with 670 additions and 39 deletions

View File

@@ -5,6 +5,7 @@ import {
TaskStore,
COLUMNS,
COLUMN_LABELS,
validateNodeOverrideChange,
type Task,
} from "@fusion/core";
import {
@@ -228,14 +229,20 @@ export default function kbExtension(pi: ExtensionAPI) {
description: "Agent ID to assign this task to, or null to clear (e.g. 'agent-abc123')",
}),
),
nodeId: Type.Optional(
Type.Union([Type.String(), Type.Null()], {
description: "Node ID override for this task, or null to clear",
}),
),
}),
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
const store = await getStore(ctx.cwd);
// Validate task exists
let task: Task;
try {
await store.getTask(params.id);
task = await store.getTask(params.id);
} catch {
return {
content: [{ type: "text", text: `Task ${params.id} not found` }],
@@ -264,10 +271,22 @@ export default function kbExtension(pi: ExtensionAPI) {
updates.assignedAgentId = params.agentId;
updatedFields.push("agentId");
}
if (params.nodeId !== undefined) {
const validation = validateNodeOverrideChange(task, params.nodeId ?? null);
if (!validation.allowed) {
return {
content: [{ type: "text", text: validation.message ?? "Node override change blocked" }],
isError: true,
details: { error: validation.reason },
};
}
updates.nodeId = params.nodeId;
updatedFields.push("nodeId");
}
if (updatedFields.length === 0) {
return {
content: [{ type: "text", text: "No fields to update. Provide at least one of: title, description, depends, agentId." }],
content: [{ type: "text", text: "No fields to update. Provide at least one of: title, description, depends, agentId, nodeId." }],
isError: true,
details: { error: "No fields provided" },
};