coverage skill:X теперь требует X в ПЕРЕСЕЧЕНИИ «Skill-tool_use этого хода ∩ журнал
вызовов» (turn-scope от границы хода transcript, факт от журнала М1 через skillTakenByJournal
K2). Не по строке coverage: — Класс 1 закрыт; turn-scoping без false-pass (X из прошлого хода
в журнале, но не в transcript этого хода → block). direct/node/chain/hook/agent приняты на
этом слое (журналом не верифицируемы, §4.2). main обёрнут exitDisciplineDecision (fail-CLOSE
Фазы 0). Override-вокабуляр снят (§12 escape≠override). 9/9 тестов GREEN.
Закрывающий variant-analysis-гейт Фазы 1 вскрыл класс P-1 для PowerShell: у
powershell-gate был СВОЙ PS_HARD_BLACKLIST (29 паттернов), а пол использовал
отдельный узкий psContentBlock (7) — подмножество, которое дрейфовало бы (та же
проблема, что P-1 для Bash). После Фазы 8 (увольнение powershell-gate) пол оказался
бы слабее гейта, который он заменяет. Решение владельца: исправить сейчас.
Зеркало P-1:
- PS_HARD_BLACKLIST + matchPsHardBlacklist перенесены в единый дом shell-content-rules;
powershell-gate ре-экспортирует (тест single-source-identity: ссылка gate === SCR).
- +bare-egress (Invoke-WebRequest/iwr/irm/curl/wget bare — floor НЕ default-deny, нужен
в blacklist, не только в whitelist гейта) +rmdir +rm (алиасы Remove-Item, которые гейт
ловил whitelist'ом default-deny — полу нужны явно).
- psContentBlock стал ТОНКИМ делегатом над matchPsHardBlacklist (симметрия с
bashIsContentBlock); пол через него видит ТОТ ЖЕ набор, что гейт. Дрейф невозможен.
- Следствие (осознанно): floor теперь блокирует все Set-Content/sc/$env/Az/… как гейт
(симметрия с Bash-полом, блокирующим все cp/mv/redirect). Escapable. FP-толерантность
унаследована от гейта (например `sc query`/`del.txt` — gate-aligned, fail-safe).
powershell-destructive.mjs физически не удалён (живые gate'ы блокируют rm/git rm) —
оставлен тонким делегатом (НЕ второй источник). Удаление — follow-up по git-approval.
Регрессия tools-only: 3044 passed + 2 skip (baseline 2843+2, 0 регрессий).
Task 1.5 Фазы 1 М7. Код уже escapable (1.3/1.4) — тесты фиксируют инвариант против
регресса. Покрыто: Bash node -e + PS Remove-Item + PS forge-write снимаются точным
грантом; P-2 специфичность (грант A не открывает команду B) для PS И Bash; кросс-shell
изоляция (Bash-грант не открывает PS-команду — разные canonicalAction-префиксы). 72 GREEN.
Строгий sharp-edges-гейт после Task 1.3 вскрыл класс обхода: подстрочный
matchBashHardBlacklist не де-обфусцирует command-substitution. Split-assembly
`$(echo no)$(echo de) -e x` и backtick `echo node` собирают интерпретатор только
при shell-eval → в сырой строке 'node' нет → content-block FALSE → пол пропускал.
router-gate ловит сейчас, но Фаза 8 (увольнение router-gate) открыла бы класс.
Закрытие: bashIsContentBlock проверяет detectSubshell(raw).found ($()/backtick/
process-subst/heredoc) → любой sub-shell = произвольное исполнение → content-block.
Независимо от parse-успеха. Escapable; router-gate тоже блокирует все sub-shell →
0 новых FP. Подтверждено: per-segment токенайзер де-обфусцирует n''ode/no\de.
114 GREEN (floor + enforce-floor + supreme-gate).
Task 1.3 Фазы 1 М7. bashIsContentBlock (whole+per-segment, паритет с bashIsFloor, P-4)
через единый matchBashHardBlacklist (P-1). floorDecide Bash-ветка зовёт content-block
ПЕРВЫМ (до bashIsFloor); escape снимает (owner-санкция). 44 GREEN.
НАХОДКА АУДИТА (задокументирована в коде+тесте): NB плана «echo "node -e foo" НЕ
over-блокируется» недостижим при подстрочном matchBashHardBlacklist (не отличает
опасную строку-аргумент echo от команды-интерпретатора). Решение — принять FP:
floor УЖЕ принял этот класс для `git push "--force"` (fail-safe, escapable);
under-block в полу страшнее over-block. Парсинг командной позиции НЕ вводим.
Task 1.2b Фазы 1 М7 (КРИТ). canonicalAction получил ветку PowerShell:
`powershell:${normalizeCommand(command)}`. Без неё PS уходил в write-fallback,
пустой путь резолвился в cwd → один escape-грант разблокировал ЛЮБУЮ PS-команду
в окне (тест специфичности был зелёным ложно: a===b==='write:<cwd>').
Сквозной фикс: тот же canonicalAction зовут пол (Task 1.4), стена (enforce-supreme-gate)
и консьюмер. Bash/Write/mcp-ветки не задеты. 118 GREEN (escape-grant + 4 потребителя).
Task 1.0.5 Фазы 1 М7. Перенос BASH_HARD_BLACKLIST + stderrRedirectBlock +
matchBashHardBlacklist из enforce-router-gate.mjs в постоянный дом
shell-content-rules.mjs (там уже живут hasInjection + matchAny). router-gate
ре-экспортирует их для обратной совместимости (тесты + тело гейта).
Единый источник правды устраняет port-дрейф content-floor (М5) по конструкции:
content-block пола (Task 1.1/1.3) импортирует ТОТ ЖЕ матчер, а не ручную копию.
Тесты: +describe single-source identity (router-gate BASH_HARD_BLACKLIST ===
shell-content-rules ссылка) + matchBashHardBlacklist hosted-in-SCR. 233 GREEN.
Чистый рефактор-перенос, 0 изменений семантики.
Готовый промт для новой сессии: подтвердить HEAD 475d381e, прочитать
handoff#2 + спеку §13 addendum + план Фазу 1 (Task 1.0.5-1.6), спросить
владельца, НИЧЕГО не делать самому. Заменяет handoff#1 (stale HEAD 8ba9a21c).
Карта правок P-1..P-8 (план↔спека). Код НЕ строили. commit-not-push.
Готовый промт для подхвата: HEAD 8ba9a21c, дизайн закрыт + критразбор/поправки
(b98b1885) + план (8ba9a21c). Next = сборка Фазы 1 (content-floor) инлайн TDD
по команде владельца. Квирки (vitest/git/junction/escape/грязь дерева) + жёсткие
правила (commit-not-push, субагенты запрещены, ничего не делать самому).