Files
portal/tools/classify-destructive.mjs
T
Дмитрий 22b84fbb2e feat(m5): classifyDestructive двухуровневый + rewire a2CaseSelect/detectHighRisk (§4, N1)
Пакет 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>
2026-06-07 11:21:38 +03:00

57 lines
3.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* classify-destructive (§4, N1) — единый двухуровневый классификатор разрушительности
* по СУТИ команды. floor — точный необратимый набор (hard-block пола). suspicious —
* грубый набор для голосов судьи. Инвариант: floor ⇒ suspicious. Один источник правды
* (Δ9-б: другого regex разрушительных команд в tools/ быть не должно).
*
* Parity (Step 1.0 audit-context): suspicious — надмножество двух прежних DESTRUCTIVE_RE
* (judge-orchestrator:84 + router-engine:12). Источник 1 давал голый `migrate`+`drop table`,
* источник 2 — `format`+`force-push`-литерал. Оба покрыты ниже. Standalone `-rf`-литерал
* (over-broad FP источника 1, ловил `tar -rf`) намеренно НЕ перенесён — `rm -rf` покрыт
* словом `rm` + rmIsFloor.
*/
// F2: rm разрушителен (floor) только если есть И рекурсия (-r/-R/--recursive), И force
// (-f/--force) — слитно (-rf), раздельно, короткими ИЛИ длинными флагами. Длинно-флаговый
// `rm --recursive --force` так же необратим → floor (старый regex его терял).
function rmIsFloor(cmd) {
if (!/\brm\b/i.test(cmd)) return false;
const rec = /-[a-z]*r/i.test(cmd) || /--recursive\b/i.test(cmd);
const force = /-[a-z]*f/i.test(cmd) || /--force\b/i.test(cmd);
return rec && force;
}
// Точный необратимый набор — то, что пол блокирует НАГЛУХО (rm — через rmIsFloor).
const FLOOR_RE = [
// force-push/перепись. NB (sharp-edges Step 1.9): длинные флаги --force/--force-with-lease
// БЕЗ обязательного \s перед ними — иначе `git push "--force"` (кавычки) обходил floor,
// а shell кавычки снимает → реальный force-push. Выровнено с каноном shell-content-rules.
// Короткий -f и refspec + требуют \s (чтобы не ловить подстроки/-rf).
/\bgit\s+push\b[^\n]*(?:--force\b|--force-with-lease\b|\s-f\b|\s\+\S)/i,
/\breset\s+--hard\b/i,
/\bartisan\s+migrate:(?:fresh|reset|refresh)\b/i,
/\bartisan\s+db:wipe\b/i,
];
// Грубый набор для судьи (надмножество floor) — лишний голос не вредит.
// F1: +format (его содержал detectHighRisk М3 — иначе rewire потеряет триггер форматирования диска).
// +force-push-литерал: источник 2 (router-engine:12) ловил его как отдельную альтернативу —
// сохраняем строгую parity (синтетический токен, реальный `git push --force` покрыт `--force`).
const SUSPICIOUS_RE = [
/\b(?:rm|rmdir|drop|delete|truncate|migrate|format)\b/i,
/--force\b/i,
/\bforce-push\b/i,
/\breset\s+--hard\b/i,
/\bartisan\s+migrate:(?:fresh|reset|refresh)\b/i,
/\bartisan\s+db:wipe\b/i,
];
export function classifyDestructive(command) {
const cmd = String(command || '');
const floor = rmIsFloor(cmd) || FLOOR_RE.some((re) => re.test(cmd));
const suspicious = floor || SUSPICIOUS_RE.some((re) => re.test(cmd));
const reason = floor ? 'необратимая команда (floor)' : suspicious ? 'подозрительная команда (suspicious)' : 'не разрушительная';
return { floor, suspicious, reason };
}