test(FN-4517): complete Step 2 — add gating regressions for room posts

Fusion-Task-Id: FN-4517
Fusion-Task-Lineage: 4e582377-3c56-49a4-8e3c-23e652113b61
This commit is contained in:
Fusion
2026-05-14 13:18:29 -07:00
committed by gsxdsm
parent f57af5419a
commit f48375cd73
3 changed files with 48 additions and 16 deletions

View File

@@ -54,7 +54,7 @@ type Harness = {
agentId: string;
};
async function createHarness(): Promise<Harness> {
async function createHarness(permissionPolicy?: any): Promise<Harness> {
const rootDir = mkdtempSync(join(tmpdir(), "hb-room-root-"));
const globalDir = mkdtempSync(join(tmpdir(), "hb-room-global-"));
const taskStore = new TaskStore(rootDir, globalDir, { inMemoryDb: true });
@@ -66,6 +66,7 @@ async function createHarness(): Promise<Harness> {
role: "engineer",
soul: "Surfaces relevant room updates.",
runtimeConfig: { enabled: true },
...(permissionPolicy ? { permissionPolicy } : {}),
});
return { rootDir, globalDir, taskStore, agentStore, chatStore, agentId: agent.id };
}
@@ -187,8 +188,17 @@ describe("heartbeat room messages", () => {
expect(sessionCapture.prompt).toContain("(10 more truncated)");
});
it("registers fn_post_room_message and posts through the real ChatStore", async () => {
harness = await createHarness();
it("registers fn_post_room_message and posts through the real ChatStore under restrictive policy", async () => {
harness = await createHarness({
presetId: "approval-required",
rules: {
git_write: "require-approval",
file_write_delete: "require-approval",
command_execution: "require-approval",
network_api: "require-approval",
task_agent_mutation: "require-approval",
},
});
const room = harness.chatStore.createRoom({ name: "reply-room", memberAgentIds: [harness.agentId] });
harness.chatStore.addRoomMessage(room.id, { role: "user", content: "can you confirm?" });
@@ -210,7 +220,10 @@ describe("heartbeat room messages", () => {
replyToMessageId: "rmsg-parent",
});
const posted = harness.chatStore.getRoomMessages(room.id).find((message) => message.id === result.details.messageId);
expect((result as any).isError).not.toBe(true);
expect((result as any)?.details?.requiresApproval).not.toBe(true);
const posted = harness.chatStore.getRoomMessages(room.id).find((message) => message.id === (result as any).details.messageId);
expect(posted).toMatchObject({
senderAgentId: harness.agentId,
content: "Confirmed.",

View File

@@ -16,6 +16,7 @@ const FN_3548_COORDINATION_TOOLS = [
"fn_agent_org_chart",
"fn_send_message",
"fn_read_messages",
"fn_post_room_message",
"fn_memory_search",
"fn_memory_get",
"fn_memory_append",

View File

@@ -561,7 +561,9 @@ describe("wrapToolsWithPermanentAgentGating", () => {
it.each<[
"locked-down" | "approval-required",
Record<string, "block" | "require-approval">
Record<string, "block" | "require-approval">,
"fn_send_message" | "fn_post_room_message",
Record<string, string>
]>([
["locked-down", {
git_write: "block",
@@ -569,19 +571,32 @@ describe("wrapToolsWithPermanentAgentGating", () => {
command_execution: "block",
network_api: "block",
task_agent_mutation: "block",
}],
}, "fn_send_message", { message: "ping" }],
["approval-required", {
git_write: "require-approval",
file_write_delete: "require-approval",
command_execution: "require-approval",
network_api: "require-approval",
task_agent_mutation: "require-approval",
}],
])("bypasses wrapping for fn_send_message under %s policy", async (presetId, rules) => {
const args = { message: "ping" };
}, "fn_send_message", { message: "ping" }],
["locked-down", {
git_write: "block",
file_write_delete: "block",
command_execution: "block",
network_api: "block",
task_agent_mutation: "block",
}, "fn_post_room_message", { roomId: "room-1", content: "pong" }],
["approval-required", {
git_write: "require-approval",
file_write_delete: "require-approval",
command_execution: "require-approval",
network_api: "require-approval",
task_agent_mutation: "require-approval",
}, "fn_post_room_message", { roomId: "room-1", content: "pong" }],
])("bypasses wrapping for %s under %s policy", async (presetId, rules, toolName, args) => {
const result = { ok: true, messageId: "msg-1" };
const execute = vi.fn().mockResolvedValue(result);
const tool = { name: "fn_send_message", label: "Send Message", description: "", parameters: {}, execute };
const tool = { name: toolName, label: "Message Tool", description: "", parameters: {}, execute };
const createApprovalRequest = vi.fn();
const findPendingApprovalRequest = vi.fn();
const { wrapToolsWithPermanentAgentGating } = await import("../pi.js");
@@ -771,15 +786,18 @@ describe("wrapToolsWithActionGate", () => {
it.each<[
"locked-down" | "approval-required",
typeof lockedDownRules | typeof approvalRules
typeof lockedDownRules | typeof approvalRules,
"fn_send_message" | "fn_post_room_message",
Record<string, string>
]>([
["locked-down", lockedDownRules],
["approval-required", approvalRules],
])("bypasses wrapping for fn_send_message under %s policy", async (presetId, rules) => {
const args = { message: "ping" };
["locked-down", lockedDownRules, "fn_send_message", { message: "ping" }],
["approval-required", approvalRules, "fn_send_message", { message: "ping" }],
["locked-down", lockedDownRules, "fn_post_room_message", { roomId: "room-1", content: "pong" }],
["approval-required", approvalRules, "fn_post_room_message", { roomId: "room-1", content: "pong" }],
])("bypasses wrapping for %s under %s policy", async (presetId, rules, toolName, args) => {
const result = { ok: true, messageId: "msg-2" };
const execute = vi.fn().mockResolvedValue(result);
const tool = { name: "fn_send_message", label: "Send Message", description: "", parameters: {}, execute };
const tool = { name: toolName, label: "Message Tool", description: "", parameters: {}, execute };
const createApprovalRequest = vi.fn();
const pauseForApproval = vi.fn();
const { wrapToolsWithActionGate } = await import("../pi.js");