22b84fbb2e
Пакет 1 Машины 5 (роутер-наставник, пол). Единый источник разрушительности classify-destructive.mjs: floor (точный необратимый набор, hard-block) + suspicious (грубый набор для голосов судьи), инвариант floor => suspicious. - N1: голый migrate/migrate:rollback/migrate --force => suspicious, НЕ floor (деплой не ломается). - rewire a2CaseSelect (M4) и detectHighRisk (M3) на classifyDestructive.suspicious; оба локальных DESTRUCTIVE_RE удалены (Δ9-б — единственный источник). - Δ9(б) seed CI-инвариант m5-floor-invariants.test.mjs (positive-control, не вакуумный). - sharp-edges (Step 1.9): floor force-push выровнен с каноном shell-content — закрыт обход кавычками git push "--force" (длинные флаги без обязательного \s; -f/+ с \s). - parity к двум прежним regex сохранён (format/db:wipe/force-push-литерал). Регрессия tools-only: 2608 passed + 2 skip (+48). Residuals (chaining/reset-quote) переданы Пакету 2 (tokenizeBash посегментно). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
91 lines
3.7 KiB
JavaScript
91 lines
3.7 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
|
import { classifyDestructive } from './classify-destructive.mjs';
|
|
|
|
// NB: используем for-of + it() вместо it.each — активный пол tdd-real-test-verifier
|
|
// (regex /\b(test|it)\s*\(/) не распознаёт it.each( как тест-блок. Поведение идентично.
|
|
|
|
describe('classifyDestructive — floor-уровень (точный необратимый набор)', () => {
|
|
const FLOOR_CASES = [
|
|
'git push --force',
|
|
'git push --force-with-lease origin main',
|
|
'git push -f',
|
|
'git reset --hard HEAD~3',
|
|
'php artisan migrate:fresh',
|
|
'php artisan migrate:reset',
|
|
'php artisan migrate:refresh',
|
|
'php artisan db:wipe',
|
|
'rm -rf build',
|
|
'rm -fr node_modules',
|
|
'rm --recursive --force build',
|
|
'rm --force --recursive /tmp/x',
|
|
];
|
|
for (const cmd of FLOOR_CASES) {
|
|
it(`floor:true для ${cmd}`, () => {
|
|
const r = classifyDestructive(cmd);
|
|
expect(r.floor).toBe(true);
|
|
expect(r.suspicious).toBe(true); // инвариант floor ⇒ suspicious
|
|
});
|
|
}
|
|
});
|
|
|
|
describe('classifyDestructive — suspicious без floor (N1: пол не ломает деплой)', () => {
|
|
const MIGRATE_CASES = [
|
|
'php artisan migrate',
|
|
'php artisan migrate:rollback',
|
|
'php artisan migrate --force',
|
|
];
|
|
for (const cmd of MIGRATE_CASES) {
|
|
it(`suspicious:true, floor:false для ${cmd}`, () => {
|
|
const r = classifyDestructive(cmd);
|
|
expect(r.suspicious).toBe(true);
|
|
expect(r.floor).toBe(false); // КРИТИЧНО: пол НЕ блокирует обычную миграцию
|
|
});
|
|
}
|
|
|
|
const SUSPICIOUS_ONLY = ['rm file.txt', 'rm -r dir', 'DROP TABLE x', 'truncate logs', 'format D:', 'npm run format'];
|
|
for (const cmd of SUSPICIOUS_ONLY) {
|
|
it(`suspicious:true, floor:false для ${cmd}`, () => {
|
|
const r = classifyDestructive(cmd);
|
|
expect(r.suspicious).toBe(true);
|
|
expect(r.floor).toBe(false);
|
|
});
|
|
}
|
|
|
|
it('не разрушительная команда → оба false', () => {
|
|
const r = classifyDestructive('git status');
|
|
expect(r.floor).toBe(false);
|
|
expect(r.suspicious).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('classifyDestructive — sharp-edges: обфускация force-push (Step 1.9)', () => {
|
|
// Scoundrel: кавычки вокруг флага. Shell снимает кавычки → реальный force-push.
|
|
// Floor обязан ловить (выровнено с каноном shell-content-rules:177).
|
|
const FORCE_FLOOR = [
|
|
'git push --force',
|
|
'git push --force', // двойной пробел
|
|
'git push origin main --force', // флаг в конце после аргументов
|
|
'git push "--force"', // кавычки (RED до фикса)
|
|
"git push '--force'", // одинарные кавычки (RED до фикса)
|
|
'git push "--force-with-lease"', // кавычки + lease (RED до фикса)
|
|
'git push -f',
|
|
'git push origin +main', // refspec-force
|
|
];
|
|
for (const cmd of FORCE_FLOOR) {
|
|
it(`floor:true для force-push ${cmd}`, () => {
|
|
expect(classifyDestructive(cmd).floor).toBe(true);
|
|
});
|
|
}
|
|
|
|
// Контроль: НЕ должно ложно срабатывать на не-force push / похожих словах.
|
|
const NOT_FLOOR = [
|
|
'git push origin main', // обычный push — не floor
|
|
'git pushed --forcefully nothing', // не команда push, не флаг --force
|
|
];
|
|
for (const cmd of NOT_FLOOR) {
|
|
it(`floor:false (контроль FP) для ${cmd}`, () => {
|
|
expect(classifyDestructive(cmd).floor).toBe(false);
|
|
});
|
|
}
|
|
});
|