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('дверь владельца: свежее одобрение точной команды → не block', () => { const now = 1_000_000; const r = decide({ event: ev('Bash', { command: 'php artisan db:wipe' }), approvedGitOps: [{ command: 'php artisan db:wipe', ts: now - 1000 }], now }); 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); }); });