Files
brain/tools/enforce-floor.test.mjs
T

54 lines
2.8 KiB
JavaScript

import { describe, it, expect } from 'vitest';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { decide } from './enforce-floor.mjs';
// enforce-floor — тонкая обёртка floor-decide. decide() чистая (approvedGitOps инъект).
const ev = (tool_name, tool_input) => ({ tool_name, tool_input, session_id: 's1' });
describe('enforce-floor.decide — делегирует floor-decide', () => {
it('необратимая Bash без одобрения → block', () => {
const r = decide({ event: ev('Bash', { command: 'php artisan migrate:fresh' }), approvedGitOps: [] });
expect(r.block).toBe(true);
});
it('обычная Bash → не block', () => {
const r = decide({ event: ev('Bash', { command: 'git status' }), approvedGitOps: [] });
expect(r.block).toBe(false);
});
it('Read → не block', () => {
const r = decide({ event: ev('Read', { file_path: '/home/u/.env' }), approvedGitOps: [], normalizeImpl: (s) => s });
expect(r.block).toBe(false);
});
it('enforce-floor пробрасывает escapeGrants в floorDecide (совпавший пропуск → не block)', () => {
const now = 1_000_000;
const r = decide({ event: ev('Bash', { command: 'php artisan db:wipe' }),
escapeGrants: [{ action: 'bash:php artisan db:wipe', ts: now - 1000 }], escapeConsumed: [], now, normalizeImpl: (x) => x });
expect(r.block).toBe(false);
});
});
describe('enforce-floor — пол первее плана (не импортирует plan-lock)', () => {
it('исходник enforce-floor.mjs не ИМПОРТИРУЕТ plan-lock (Δ9: floor до плана)', () => {
const dir = dirname(fileURLToPath(import.meta.url));
const src = readFileSync(join(dir, 'enforce-floor.mjs'), 'utf8');
// таргетим именно import-стейтмент, не упоминание в комментарии
expect(/(?:import|require)\b[^\n]*['"][^'"]*plan-lock/.test(src)).toBe(false);
});
});
describe('enforce-floor.decide — panic-ветка (M7 Фаза 2, правило 7б)', () => {
const now = 1_000_000;
const boom = () => { throw new Error('floorDecide boom'); };
it('floorDecide бросает БЕЗ escape → block:true (fail-CLOSED panic)', () => {
const r = decide({ event: ev('Bash', { command: 'git status' }),
escapeGrants: [], escapeConsumed: [], now, floorDecideImpl: boom });
expect(r.block).toBe(true);
});
it('floorDecide бросает + матч escape-грант → block:false (panic-escape чтится)', () => {
const r = decide({ event: ev('Bash', { command: 'git status' }),
escapeGrants: [{ action: 'bash:git status', ts: now - 1000 }], escapeConsumed: [], now, floorDecideImpl: boom });
expect(r.block).toBe(false);
});
});