|
5 | 5 | */ |
6 | 6 |
|
7 | 7 | import { describe, it, expect, beforeEach, afterEach } from 'vitest'; |
8 | | -import { PolicyDecision } from './types.js'; |
| 8 | +import { |
| 9 | + PolicyDecision, |
| 10 | + ApprovalMode, |
| 11 | + PRIORITY_SUBAGENT_TOOL, |
| 12 | +} from './types.js'; |
9 | 13 | import * as fs from 'node:fs/promises'; |
10 | 14 | import * as path from 'node:path'; |
11 | 15 | import * as os from 'node:os'; |
| 16 | +import { fileURLToPath } from 'node:url'; |
12 | 17 | import { loadPoliciesFromToml } from './toml-loader.js'; |
13 | 18 | import type { PolicyLoadResult } from './toml-loader.js'; |
| 19 | +import { PolicyEngine } from './policy-engine.js'; |
| 20 | + |
| 21 | +const __filename = fileURLToPath(import.meta.url); |
| 22 | +const __dirname = path.dirname(__filename); |
14 | 23 |
|
15 | 24 | describe('policy-toml-loader', () => { |
16 | 25 | let tempDir: string; |
@@ -500,4 +509,60 @@ priority = 100 |
500 | 509 | expect(error.message).toContain('Failed to read policy directory'); |
501 | 510 | }); |
502 | 511 | }); |
| 512 | + |
| 513 | + describe('Built-in Plan Mode Policy', () => { |
| 514 | + it('should override default subagent rules when in Plan Mode', async () => { |
| 515 | + const planTomlPath = path.resolve(__dirname, 'policies', 'plan.toml'); |
| 516 | + const fileContent = await fs.readFile(planTomlPath, 'utf-8'); |
| 517 | + const tempPolicyDir = await fs.mkdtemp( |
| 518 | + path.join(os.tmpdir(), 'plan-policy-test-'), |
| 519 | + ); |
| 520 | + try { |
| 521 | + await fs.writeFile(path.join(tempPolicyDir, 'plan.toml'), fileContent); |
| 522 | + const getPolicyTier = () => 1; // Default tier |
| 523 | + |
| 524 | + // 1. Load the actual Plan Mode policies |
| 525 | + const result = await loadPoliciesFromToml( |
| 526 | + [tempPolicyDir], |
| 527 | + getPolicyTier, |
| 528 | + ); |
| 529 | + |
| 530 | + // 2. Initialize Policy Engine with these rules |
| 531 | + const engine = new PolicyEngine({ |
| 532 | + rules: result.rules, |
| 533 | + approvalMode: ApprovalMode.PLAN, |
| 534 | + }); |
| 535 | + |
| 536 | + // 3. Simulate a Subagent being registered (Dynamic Rule) |
| 537 | + engine.addRule({ |
| 538 | + toolName: 'codebase_investigator', |
| 539 | + decision: PolicyDecision.ALLOW, |
| 540 | + priority: PRIORITY_SUBAGENT_TOOL, |
| 541 | + source: 'AgentRegistry (Dynamic)', |
| 542 | + }); |
| 543 | + |
| 544 | + // 4. Verify Behavior: |
| 545 | + // The Plan Mode "Catch-All Deny" (from plan.toml) should override the Subagent Allow |
| 546 | + const checkResult = await engine.check( |
| 547 | + { name: 'codebase_investigator' }, |
| 548 | + undefined, |
| 549 | + ); |
| 550 | + |
| 551 | + expect( |
| 552 | + checkResult.decision, |
| 553 | + 'Subagent should be DENIED in Plan Mode', |
| 554 | + ).toBe(PolicyDecision.DENY); |
| 555 | + |
| 556 | + // 5. Verify Explicit Allows still work |
| 557 | + // e.g. 'read_file' should be allowed because its priority in plan.toml (70) is higher than the deny (60) |
| 558 | + const readResult = await engine.check({ name: 'read_file' }, undefined); |
| 559 | + expect( |
| 560 | + readResult.decision, |
| 561 | + 'Explicitly allowed tools (read_file) should be ALLOWED in Plan Mode', |
| 562 | + ).toBe(PolicyDecision.ALLOW); |
| 563 | + } finally { |
| 564 | + await fs.rm(tempPolicyDir, { recursive: true, force: true }); |
| 565 | + } |
| 566 | + }); |
| 567 | + }); |
503 | 568 | }); |
0 commit comments