feat(plan): document and validate Plan Mode policy overrides (#18825)

This commit is contained in:
Jerop Kipruto
2026-02-11 12:32:02 -05:00
committed by GitHub
parent 0080589939
commit 65d26e73a2
3 changed files with 166 additions and 3 deletions
+103
View File
@@ -951,4 +951,107 @@ name = "invalid-name"
vi.doUnmock('node:fs/promises');
});
it('should allow overriding Plan Mode deny with user policy', async () => {
const actualFs =
await vi.importActual<typeof import('node:fs/promises')>(
'node:fs/promises',
);
const mockReaddir = vi.fn(
async (
path: string | Buffer | URL,
options?: Parameters<typeof actualFs.readdir>[1],
) => {
const normalizedPath = nodePath.normalize(path.toString());
if (normalizedPath.includes(nodePath.normalize('.gemini/policies'))) {
return [
{
name: 'user-plan.toml',
isFile: () => true,
isDirectory: () => false,
},
] as unknown as Awaited<ReturnType<typeof actualFs.readdir>>;
}
return actualFs.readdir(
path,
options as Parameters<typeof actualFs.readdir>[1],
);
},
);
const mockReadFile = vi.fn(
async (
path: Parameters<typeof actualFs.readFile>[0],
options: Parameters<typeof actualFs.readFile>[1],
) => {
const normalizedPath = nodePath.normalize(path.toString());
if (normalizedPath.includes('user-plan.toml')) {
return `
[[rule]]
toolName = "run_shell_command"
commandPrefix = ["git status", "git diff"]
decision = "allow"
priority = 100
modes = ["plan"]
[[rule]]
toolName = "codebase_investigator"
decision = "allow"
priority = 100
modes = ["plan"]
`;
}
return actualFs.readFile(path, options);
},
);
vi.doMock('node:fs/promises', () => ({
...actualFs,
default: { ...actualFs, readFile: mockReadFile, readdir: mockReaddir },
readFile: mockReadFile,
readdir: mockReaddir,
}));
vi.resetModules();
const { createPolicyEngineConfig } = await import('./config.js');
const settings: PolicySettings = {};
const config = await createPolicyEngineConfig(
settings,
ApprovalMode.PLAN,
nodePath.join(__dirname, 'policies'),
);
const shellRules = config.rules?.filter(
(r) =>
r.toolName === 'run_shell_command' &&
r.decision === PolicyDecision.ALLOW &&
r.modes?.includes(ApprovalMode.PLAN) &&
r.argsPattern,
);
expect(shellRules).toHaveLength(2);
expect(
shellRules?.some((r) => r.argsPattern?.test('{"command":"git status"}')),
).toBe(true);
expect(
shellRules?.some((r) => r.argsPattern?.test('{"command":"git diff"}')),
).toBe(true);
expect(
shellRules?.every(
(r) => !r.argsPattern?.test('{"command":"git commit"}'),
),
).toBe(true);
const subagentRule = config.rules?.find(
(r) =>
r.toolName === 'codebase_investigator' &&
r.decision === PolicyDecision.ALLOW &&
r.modes?.includes(ApprovalMode.PLAN),
);
expect(subagentRule).toBeDefined();
expect(subagentRule?.priority).toBeCloseTo(2.1, 5);
vi.doUnmock('node:fs/promises');
});
});