feat(FN-3935): align engineer role routing with explicit assignment and del

Centralized routing policy helpers in core and aligned both direct assignment and delegation to route engineer-role agents consistently, ensuring assigned tasks respect explicit engineer routing the same way delegated tasks do. Added comprehensive test coverage across core, CLI, dashboard, and engin

Fusion-Task-Id: FN-3935

Fusion-Task-Lineage: 069f4d54-f7a8-4bd3-a2c3-4de830de042f
This commit is contained in:
Fusion
2026-05-11 20:20:43 -07:00
committed by gsxdsm
parent 6fd19dd0db
commit 1f7958fc0f
18 changed files with 342 additions and 29 deletions

View File

@@ -1,7 +1,10 @@
import { describe, expect, it } from "vitest";
import {
canAgentTakeImplementationTask,
canAgentTakeImplementationTaskForBacklogPickup,
canAgentTakeImplementationTaskForExplicitRouting,
formatRoleMismatchReason,
isEngineerRoleAgent,
isExecutorRoleAgent,
isImplementationTask,
} from "../agent-role-policy.js";
@@ -19,17 +22,36 @@ describe("agent-role-policy", () => {
expect(isImplementationTask({ column: "archived" })).toBe(false);
});
it("allows executor agents to take implementation tasks", () => {
it("allows executor agents in both explicit routing and backlog pickup", () => {
expect(isExecutorRoleAgent({ role: "executor" })).toBe(true);
expect(
canAgentTakeImplementationTaskForExplicitRouting({ role: "executor" }, { column: "todo" }),
).toBe(true);
expect(
canAgentTakeImplementationTaskForBacklogPickup({ role: "executor" }, { column: "todo" }),
).toBe(true);
expect(
canAgentTakeImplementationTask({ role: "executor" }, { column: "todo" }),
).toBe(true);
});
it("rejects non-executor agents for implementation tasks", () => {
it("allows durable engineer only for explicit routing", () => {
expect(isEngineerRoleAgent({ role: "engineer" })).toBe(true);
expect(
canAgentTakeImplementationTaskForExplicitRouting({ role: "engineer" }, { column: "todo" }),
).toBe(true);
expect(
canAgentTakeImplementationTaskForBacklogPickup({ role: "engineer" }, { column: "todo" }),
).toBe(false);
});
it("keeps reviewer blocked by default", () => {
expect(isExecutorRoleAgent({ role: "reviewer" })).toBe(false);
expect(
canAgentTakeImplementationTask({ role: "reviewer" }, { column: "todo" }),
canAgentTakeImplementationTaskForExplicitRouting({ role: "reviewer" }, { column: "todo" }),
).toBe(false);
expect(
canAgentTakeImplementationTaskForBacklogPickup({ role: "reviewer" }, { column: "todo" }),
).toBe(false);
});
@@ -41,5 +63,7 @@ describe("agent-role-policy", () => {
expect(reason).toContain("agent-1");
expect(reason).toContain("reviewer");
expect(reason).toContain("FN-123");
expect(reason).toContain("requires an \"executor\"-role agent by default");
expect(reason).toContain("durable \"engineer\" supported only for explicit routing");
});
});

View File

@@ -1975,6 +1975,32 @@ describe("AgentStore", () => {
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.reason).toMatch(/requires an "executor"-role agent/);
expect(result.reason).toMatch(/durable "engineer" supported only for explicit routing/);
const claimedTask = await taskStore.getTask(taskId);
expect(claimedTask?.assignedAgentId).toBeUndefined();
});
it("claimTaskForAgent allows engineer claim for explicitly assigned implementation tasks", async () => {
const engineer = await store.createAgent({ name: "Engineer", role: "engineer" });
await taskStore.updateTask(taskId, { assignedAgentId: engineer.id });
const result = await store.claimTaskForAgent(engineer.id, taskId);
expect(result.ok).toBe(true);
if (!result.ok) return;
const claimedTask = await taskStore.getTask(taskId);
expect(claimedTask?.assignedAgentId).toBe(engineer.id);
expect(claimedTask?.checkedOutBy).toBe(engineer.id);
});
it("claimTaskForAgent rejects engineer auto-claim for unassigned implementation tasks", async () => {
const engineer = await store.createAgent({ name: "Engineer", role: "engineer" });
const result = await store.claimTaskForAgent(engineer.id, taskId);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.reason).toMatch(/requires an "executor"-role agent/);
const claimedTask = await taskStore.getTask(taskId);
expect(claimedTask?.assignedAgentId).toBeUndefined();

View File

@@ -296,6 +296,33 @@ describe("TaskStore", () => {
expect(selected?.priority).toBe("todo");
});
it("returns assigned implementation todos for engineer role agents", async () => {
const todo = await store.createTask({
description: "Assigned engineer todo",
column: "todo",
assignedAgentId: "agent-1",
});
const selected = await store.selectNextTaskForAgent("agent-1", {
id: "agent-1",
role: "engineer",
});
expect(selected?.task.id).toBe(todo.id);
expect(selected?.priority).toBe("todo");
});
it("does not auto-claim unassigned implementation backlog for engineer role agents", async () => {
await store.createTask({
description: "Unassigned todo",
column: "todo",
});
await expect(
store.selectNextTaskForAgent("agent-1", { id: "agent-1", role: "engineer" }),
).resolves.toBeNull();
});
it("allows non-executor role agents to pick assigned todos when override metadata is set", async () => {
const delegated = await store.createTask({
description: "Assigned todo override",