import { describe, it, expect } from 'vitest'; import { canonicalAction, escapeGrantOpen } from './escape-grant.mjs'; import { floorDecide } from './floor-decide.mjs'; import { toFloorEscapeRecord } from './askuser-answer-parser.mjs'; describe('M6 escape — сквозной binding', () => { const now = 1000, ID = (x) => x; it('показанное владельцу действие == живое → открыт; подмена → закрыт', () => { // парсер строит грант из показанного варианта const rec = toFloorEscapeRecord('да FLOOR-ESCAPE: bash:git push --force', { nowMs: now - 5 }); const grants = [{ action: rec.action, ts: rec.ts }]; // живое совпадает expect(floorDecide({ toolUse: { name: 'Bash', input: { command: 'git push --force' } }, escapeGrants: grants, escapeConsumed: [], now, normalizeImpl: ID }).block).toBe(false); // живое ДРУГОЕ (подмена) → блок expect(floorDecide({ toolUse: { name: 'Bash', input: { command: 'git reset --hard' } }, escapeGrants: grants, escapeConsumed: [], now, normalizeImpl: ID }).block).toBe(true); }); it('F-S1: floor_escape и approve_git_operation не пересекаются (разные type/reader)', () => { // approve_git_operation-запись не имеет поля action → escapeGrantOpen её не видит expect(escapeGrantOpen('bash:git push --force', [{ command: 'git push --force', ts: now }], [], now)).toBe(false); }); it('канон-строка детерминирована для Bash/Write/MCP', () => { expect(canonicalAction('Bash', { command: 'git push' }, { normalizeImpl: ID })).toBe('bash:git push'); expect(canonicalAction('Write', { file_path: '/a/.env' }, { normalizeImpl: ID })).toBe('write:/a/.env'); expect(canonicalAction('mcp__x__send', { url: 'http://1.2.3.4' }, { normalizeImpl: ID })).toBe('mcp:mcp__x__send:{"url":"http://1.2.3.4"}'); }); });