Files
brain/tools/classify-destructive.mjs
T

68 lines
4.6 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.
*
* M7 Task 1.1 (P-1): contentBlock переиспользует ЕДИНЫЙ матчер matchBashHardBlacklist из
* shell-content-rules (тот же источник, что content-floor М5) — не ручная копия.
*/
import { matchBashHardBlacklist } from './shell-content-rules.mjs';
// 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));
// M7 Task 1.1 (правило 8 §4.1, V1): опасное-по-СОДЕРЖАНИЮ — единый источник (P-1), whole-string.
// Это поле для видимости судьи; фактический блок пола идёт через bashIsContentBlock (whole+per-segment, Task 1.3).
const contentBlock = matchBashHardBlacklist(cmd) !== null;
// P-5: судья М4 (голоса) видит content-опасное как suspicious.
const suspicious = floor || contentBlock || SUSPICIOUS_RE.some((re) => re.test(cmd));
const reason = floor ? 'необратимая команда (floor)'
: contentBlock ? 'опасная по содержанию (content-block)'
: suspicious ? 'подозрительная команда (suspicious)'
: 'не разрушительная';
return { floor, suspicious, contentBlock, reason };
}