feat(FN-3976): reclassify github import action gating

Completes FN-3976 by reclassifying GitHub import action gating in the engine's `gating-classifications.ts`, updating documentation in `docs/agents.md` accordingly, and adding test coverage across four gating test suites.

Fusion-Task-Id: FN-3976
This commit is contained in:
Fusion
2026-05-11 00:56:51 -07:00
committed by gsxdsm
parent ef3281bc9d
commit c02aadec19
7 changed files with 38 additions and 5 deletions

View File

@@ -116,6 +116,8 @@ describe("agent-action-gate", () => {
expect(evaluateAgentActionGate({ agentId: "a1", toolName: "fn_task_add_dep", args: {}, permissionPolicy: unrestrictedPolicy }).category).toBe("task_agent_mutation");
expect(evaluateAgentActionGate({ agentId: "a1", toolName: "fn_delegate_task", args: {}, permissionPolicy: unrestrictedPolicy }).category).toBe("exempt");
expect(evaluateAgentActionGate({ agentId: "a1", toolName: "fn_update_agent_config", args: {}, permissionPolicy: unrestrictedPolicy }).category).toBe("task_agent_mutation");
expect(evaluateAgentActionGate({ agentId: "a1", toolName: "fn_task_import_github", args: {}, permissionPolicy: unrestrictedPolicy }).category).toBe("task_agent_mutation");
expect(evaluateAgentActionGate({ agentId: "a1", toolName: "fn_task_import_github_issue", args: {}, permissionPolicy: unrestrictedPolicy }).category).toBe("task_agent_mutation");
expect(evaluateAgentActionGate({ agentId: "a1", toolName: "fn_update_identity", args: {}, permissionPolicy: unrestrictedPolicy }).category).toBe("exempt");
expect(evaluateAgentActionGate({ agentId: "a1", toolName: "fn_spawn_agent", args: {}, permissionPolicy: unrestrictedPolicy }).category).toBe("task_agent_mutation");
});

View File

@@ -17,4 +17,13 @@ describe("gating classifications provisioning split", () => {
expect(TASK_AGENT_MUTATION_TOOLS.has("fn_agent_create")).toBe(true);
expect(TASK_AGENT_MUTATION_TOOLS.has("fn_agent_delete")).toBe(true);
});
it("classifies github task imports as action-gated task mutations only", () => {
expect(ACTION_GATE_TASK_AGENT_MANAGEMENT_TOOLS.has("fn_task_import_github")).toBe(true);
expect(ACTION_GATE_TASK_AGENT_MANAGEMENT_TOOLS.has("fn_task_import_github_issue")).toBe(true);
expect(PERMANENT_AGENT_TASK_MUTATION_TOOLS.has("fn_task_import_github")).toBe(false);
expect(PERMANENT_AGENT_TASK_MUTATION_TOOLS.has("fn_task_import_github_issue")).toBe(false);
expect(TASK_AGENT_MUTATION_TOOLS.has("fn_task_import_github")).toBe(true);
expect(TASK_AGENT_MUTATION_TOOLS.has("fn_task_import_github_issue")).toBe(true);
});
});

View File

@@ -56,6 +56,11 @@ const gitCases = [
["echo hi\ngit checkout -b t", true, "git checkout -b"],
] as const;
const ACTION_MUTATION_PERMANENT_READONLY_TOOLS = new Set([
"fn_task_import_github",
"fn_task_import_github_issue",
]);
describe("gating-classifications parity", () => {
it("locks coordination exempt membership", () => {
expect([...COORDINATION_EXEMPT_TOOLS].sort()).toMatchInlineSnapshot(`
@@ -151,6 +156,10 @@ describe("gating-classifications parity", () => {
expect({ toolName, actionKind, permanentKind }).toEqual({ toolName, actionKind: "readonly", permanentKind: "mutating" });
continue;
}
if (ACTION_MUTATION_PERMANENT_READONLY_TOOLS.has(toolName)) {
expect({ toolName, actionKind, permanentKind }).toEqual({ toolName, actionKind: "mutating", permanentKind: "readonly" });
continue;
}
expect({ toolName, actionKind, permanentKind }).toEqual({ toolName, actionKind: permanentKind, permanentKind });
}

View File

@@ -38,6 +38,8 @@ describe("permanent-agent-gating", () => {
expect(classifyPermanentAgentToolCall("fn_task_create").category).toBe("none");
expect(classifyPermanentAgentToolCall("fn_delegate_task").category).toBe("none");
expect(classifyPermanentAgentToolCall("fn_update_agent_config").category).toBe("task_agent_mutation");
expect(classifyPermanentAgentToolCall("fn_task_import_github").category).toBe("none");
expect(classifyPermanentAgentToolCall("fn_task_import_github_issue").category).toBe("none");
expect(classifyPermanentAgentToolCall("fn_update_identity").category).toBe("none");
expect(classifyPermanentAgentToolCall("fn_task_document_write").category).toBe("none");
expect(classifyPermanentAgentToolCall("fn_memory_append").category).toBe("none");

View File

@@ -7,7 +7,13 @@ export const FILE_WRITE_BUILTIN_TOOLS: ReadonlySet<string> = new Set(["write", "
const SHARED_TASK_AGENT_TOOLS = ["fn_task_add_dep", "fn_spawn_agent", "fn_update_agent_config", "fn_agent_create", "fn_agent_delete"] as const;
const PROVISIONING_TOOLS = ["fn_agent_create", "fn_agent_delete"] as const;
const ACTION_GATE_TASK_AGENT_ONLY_TOOLS = ["fn_task_create", "fn_delegate_task", "fn_update_identity"] as const;
const ACTION_GATE_TASK_AGENT_ONLY_TOOLS = [
"fn_task_create",
"fn_delegate_task",
"fn_task_import_github",
"fn_task_import_github_issue",
"fn_update_identity",
] as const;
const ACTION_GATE_SHARED_TASK_AGENT_TOOLS = SHARED_TASK_AGENT_TOOLS.filter(
(tool) => !(PROVISIONING_TOOLS as readonly string[]).includes(tool),
);
@@ -20,8 +26,6 @@ const PERMANENT_TASK_AGENT_ONLY_TOOLS = [
"fn_task_archive",
"fn_task_unarchive",
"fn_task_delete",
"fn_task_import_github",
"fn_task_import_github_issue",
"fn_task_plan",
"fn_mission_create",
"fn_mission_delete",
@@ -69,6 +73,8 @@ export const READONLY_FN_TOOLS: ReadonlySet<string> = new Set([
"fn_task_document_write",
"fn_task_document_read",
"fn_delegate_task",
"fn_task_import_github",
"fn_task_import_github_issue",
"fn_research_list",
"fn_research_get",
"fn_insight_list",