Skip to content

Commit 028c438

Browse files
committed
Cover hasExactFullCommandGrant's agreement with grantScopeMatches directly
The other two live call sites (evaluateApprovals, isRequestCoveredByGrant) get a direct cross-check against grantScopeMatches; hasExactFullCommandGrant isn't exported, so it only had indirect coverage through evaluate()'s multi-segment replay path elsewhere in the suite. Drive that path directly with grants grantScopeMatches would refuse (wrong cwd, wrong providerModel) to confirm the replay never fires when the shared predicate says no.
1 parent e0efb3f commit 028c438

1 file changed

Lines changed: 53 additions & 1 deletion

File tree

src/permission/grant-scope.test.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { describe, test, expect } from "bun:test";
2+
import type { ToolCall } from "@intx/types/runtime";
23
import type { Approval, PermissionRequest } from "./types.js";
34
import { evaluateApprovals, grantScopeMatches, type GrantWorkspace } from "./authz-grants.js";
4-
import { isRequestCoveredByGrant } from "./gate.js";
5+
import { createPermissionGate, isRequestCoveredByGrant } from "./gate.js";
56

67
// evaluateApprovals and isRequestCoveredByGrant each decide, independently,
78
// whether a grant's tool/providerModel/cwd scope covers a request. Both are
@@ -63,3 +64,54 @@ describe("grant tool/providerModel/cwd scoping agrees across call sites", () =>
6364
}
6465
}
6566
});
67+
68+
// hasExactFullCommandGrant (gate.ts) is the third live call site grantScopeMatches
69+
// unifies, but it is not exported — it only surfaces through the exact-full-command
70+
// replay path inside evaluate(). This drives that path directly with grants that
71+
// grantScopeMatches would refuse (wrong cwd, wrong providerModel) to confirm the
72+
// replay never fires when the shared predicate says no, matching the coverage the
73+
// other two call sites get above.
74+
describe("hasExactFullCommandGrant agrees with grantScopeMatches", () => {
75+
const full = "npm i && curl x";
76+
const shellCall = (command: string): ToolCall => ({ id: "c", name: "run_shell", arguments: { command } });
77+
78+
test("does not replay a grant scoped to a different cwd", async () => {
79+
let asked = 0;
80+
const gate = createPermissionGate({
81+
approvals: [{ tool: "run_shell", pattern: full, cwd: "/other-project" }],
82+
requestApproval: async () => { asked++; return { allow: true }; },
83+
interactive: true,
84+
skipPermissions: false,
85+
});
86+
expect((await gate.evaluate(shellCall(full))).allowed).toBe(true);
87+
// grantScopeMatches would refuse this grant (cwd mismatch), so the
88+
// exact-full-command shortcut must not fire — the operator is still asked.
89+
expect(asked).toBeGreaterThan(0);
90+
});
91+
92+
test("does not replay a grant scoped to a different provider model", async () => {
93+
let asked = 0;
94+
const gate = createPermissionGate({
95+
approvals: [{ tool: "run_shell", pattern: full, providerModel: "openai:gpt-5" }],
96+
providerName: "anthropic",
97+
model: "opus",
98+
requestApproval: async () => { asked++; return { allow: true }; },
99+
interactive: true,
100+
skipPermissions: false,
101+
});
102+
expect((await gate.evaluate(shellCall(full))).allowed).toBe(true);
103+
expect(asked).toBeGreaterThan(0);
104+
});
105+
106+
test("replays a grant whose scope grantScopeMatches accepts", async () => {
107+
let asked = 0;
108+
const gate = createPermissionGate({
109+
approvals: [{ tool: "run_shell", pattern: full }],
110+
requestApproval: async () => { asked++; return { allow: true }; },
111+
interactive: true,
112+
skipPermissions: false,
113+
});
114+
expect((await gate.evaluate(shellCall(full))).allowed).toBe(true);
115+
expect(asked).toBe(0);
116+
});
117+
});

0 commit comments

Comments
 (0)