Skip to content

Commit 80c900f

Browse files
committed
test(permissions): prove fleet carve-out under a restricted worktree
Name resume_agent and read_agent_trace in the agentId carve-out so all five single-target verbs are explicit, and build the CL-9362 gate on a registered worktree with a session-state control write that still asks.
1 parent 4312666 commit 80c900f

2 files changed

Lines changed: 32 additions & 2 deletions

File tree

‎src/permission/classify.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,8 @@ const AGENT_ID_TARGETED_FLEET_TOOLS = new Set([
262262
"close_agent",
263263
"interrupt_agent",
264264
"send_input",
265+
"resume_agent",
266+
"read_agent_trace",
265267
]);
266268

267269
export function callTargetsRestricted(
@@ -280,8 +282,10 @@ export function callTargetsRestricted(
280282
// here would duplicate that enforcement at a layer with no session access,
281283
// so these calls always report "not restricted", exactly like
282284
// spawn_agent/wait_agents. (The full fleet verb list lives in
283-
// subagent/authority.ts as FLEET_VERBS; only the agentId-addressed
284-
// continuation verbs need naming here.)
285+
// subagent/authority.ts as FLEET_VERBS; the five single-`target`
286+
// agentId-addressed verbs are named above — spawn_agent, wait_agents,
287+
// list_agents, and search_agents take no single-agent `target` argument
288+
// and already fall through to false below.)
285289
if (AGENT_ID_TARGETED_FLEET_TOOLS.has(name)) return false;
286290
if (name === "run_shell")
287291
return commandTargetsRestricted(stringArg(call, "command"), isRestricted);

‎src/permission/permission.test.ts‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1607,6 +1607,11 @@ describe("createPermissionGate", () => {
16071607
// auto-allows in auto mode, exactly like spawn_agent/wait_agents.
16081608
test("auto mode auto-allows agentId-targeted fleet calls regardless of target worktree", async () => {
16091609
let asked = 0;
1610+
// A registered worktree standing in for the restricted target worktree.
1611+
// Restriction is live in this gate — the session-state control write
1612+
// below still asks — so the fleet auto-allows prove worktree-independence
1613+
// instead of assuming it.
1614+
const worktree = mkdtempSync(join(tmpdir(), "corbits-restricted-wt-"));
16101615
const gate = createPermissionGate({
16111616
approvals: [],
16121617
requestApproval: async () => {
@@ -1617,6 +1622,8 @@ describe("createPermissionGate", () => {
16171622
skipPermissions: false,
16181623
reactorGated: false,
16191624
auto: true,
1625+
cwd: worktree,
1626+
rootsProvider: () => [realpathSync(worktree)],
16201627
});
16211628
const calls: ToolCall[] = [
16221629
{ id: "c", name: "close_agent", arguments: { target: "worker-1" } },
@@ -1626,12 +1633,31 @@ describe("createPermissionGate", () => {
16261633
name: "send_input",
16271634
arguments: { target: "worker-1", message: "continue" },
16281635
},
1636+
{
1637+
id: "c",
1638+
name: "resume_agent",
1639+
arguments: { target: "worker-1", message: "continue" },
1640+
},
1641+
{
1642+
id: "c",
1643+
name: "read_agent_trace",
1644+
arguments: { target: "worker-1" },
1645+
},
16291646
];
16301647
for (const call of calls) {
16311648
const verdict = await gate.evaluate(call);
16321649
expect(verdict.allowed).toBe(true);
16331650
}
16341651
expect(asked).toBe(0);
1652+
// Control: restriction is live in this gate — a session-state write
1653+
// through the same gate still asks (and is denied here).
1654+
const control = await gate.evaluate({
1655+
id: "c",
1656+
name: "write_file",
1657+
arguments: { path: ".agent-state/run.json" },
1658+
});
1659+
expect(control.allowed).toBe(false);
1660+
expect(asked).toBe(1);
16351661
// The carve-out is intentional, not an oversight: even an isRestricted
16361662
// that reports everything restricted (the target worktree is restricted)
16371663
// does not flag these calls — they carry agent ids, not paths.

0 commit comments

Comments
 (0)