feat(FN-4813): complete Step 3 — add owning-node handoff policy

Fusion-Task-Id: FN-4813
Fusion-Task-Lineage: 846893a5-2afa-4817-8f64-8c444d2fd713
This commit is contained in:
Fusion (runfusion.ai)
2026-05-16 16:45:20 -07:00
committed by gsxdsm
parent 735c8f413b
commit 2ff19bc9c9
4 changed files with 98 additions and 1 deletions

View File

@@ -0,0 +1,48 @@
import { describe, expect, it } from "vitest";
import type { Task } from "@fusion/core";
import { decideOwningNodeHandoff } from "../node-routing-policy.js";
const baseTask: Task = {
id: "FN-1",
description: "test",
column: "todo",
dependencies: [],
steps: [],
currentStep: 0,
log: [],
prompt: "",
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z",
};
describe("decideOwningNodeHandoff", () => {
it("parks when owner is online", () => {
expect(decideOwningNodeHandoff({ task: baseTask, ownerNodeId: "node-a", ownerNodeHealth: "online", localNodeId: "node-b", handoffPolicy: "reassign-to-local" }))
.toEqual({ action: "park", reason: "owner_recovered" });
});
it("reassigns local when owner is local", () => {
expect(decideOwningNodeHandoff({ task: baseTask, ownerNodeId: "node-a", ownerNodeHealth: "offline", localNodeId: "node-a", handoffPolicy: "block" }))
.toEqual({ action: "reassign-local", reason: "owner_local_recover" });
});
it("parks for block policy", () => {
expect(decideOwningNodeHandoff({ task: baseTask, ownerNodeId: "node-a", ownerNodeHealth: "offline", localNodeId: "node-b", handoffPolicy: "block" }))
.toEqual({ action: "park", reason: "handoff_blocked_by_policy" });
});
it("reassigns local for reassign-to-local", () => {
expect(decideOwningNodeHandoff({ task: baseTask, ownerNodeId: "node-a", ownerNodeHealth: "offline", localNodeId: "node-b", handoffPolicy: "reassign-to-local" }))
.toEqual({ action: "reassign-local", reason: "owner_offline_local_takes_over" });
});
it("reassigns any for reassign-any-healthy", () => {
expect(decideOwningNodeHandoff({ task: baseTask, ownerNodeId: "node-a", ownerNodeHealth: "offline", localNodeId: "node-b", handoffPolicy: "reassign-any-healthy" }))
.toEqual({ action: "reassign-any", reason: "owner_offline_any_healthy_eligible" });
});
it("defaults to reassign-to-local when policy undefined", () => {
expect(decideOwningNodeHandoff({ task: baseTask, ownerNodeId: "node-a", ownerNodeHealth: "error", localNodeId: "node-b", handoffPolicy: undefined }))
.toEqual({ action: "reassign-local", reason: "owner_error_local_takes_over" });
});
});

View File

@@ -1,4 +1,4 @@
import type { NodeStatus, UnavailableNodePolicy } from "@fusion/core";
import type { NodeStatus, OwningNodeHandoffPolicy, Task, UnavailableNodePolicy } from "@fusion/core";
import type { EffectiveNode } from "./effective-node.js";
export type PolicyDecision =
@@ -6,6 +6,11 @@ export type PolicyDecision =
| { allowed: true; fallbackToLocal: true; reason: string }
| { allowed: false; reason: string };
export type HandoffDecision =
| { action: "park"; reason: string }
| { action: "reassign-local"; reason: string }
| { action: "reassign-any"; reason: string };
const UNHEALTHY_STATUSES: ReadonlySet<NodeStatus> = new Set(["offline", "error", "connecting"]);
export function applyUnavailableNodePolicy(params: {
@@ -40,3 +45,36 @@ export function applyUnavailableNodePolicy(params: {
reason: `Node ${effectiveNode.nodeId} is ${nodeHealth}; policy is block`,
};
}
export function decideOwningNodeHandoff(params: {
task: Task;
ownerNodeId: string;
ownerNodeHealth: NodeStatus | undefined;
localNodeId: string;
handoffPolicy: OwningNodeHandoffPolicy | undefined;
}): HandoffDecision {
const { ownerNodeId, ownerNodeHealth, localNodeId, handoffPolicy } = params;
if (ownerNodeHealth === "online") {
return { action: "park", reason: "owner_recovered" };
}
if (ownerNodeId === localNodeId) {
return { action: "reassign-local", reason: "owner_local_recover" };
}
if (handoffPolicy === "block") {
return { action: "park", reason: "handoff_blocked_by_policy" };
}
if (handoffPolicy === "reassign-any-healthy") {
return {
action: "reassign-any",
reason: `owner_${ownerNodeHealth ?? "unknown"}_any_healthy_eligible`,
};
}
return {
action: "reassign-local",
reason: `owner_${ownerNodeHealth ?? "unknown"}_local_takes_over`,
};
}