docs(m7): план Фазы 2 — escape-survivability полная (правило 7б,в + §6 escape-honor)

This commit is contained in:
Дмитрий
2026-06-08 10:30:00 +03:00
parent 199dc04db0
commit 467f2edddf
@@ -0,0 +1,43 @@
# Машина 7 — Фаза 2: escape-survivability полная (правило 7б,в + §6 escape-honor)
> **For agentic workers:** REQUIRED SUB-SKILL: владелец запретил субагентов → исполнять ИНЛАЙН через `superpowers:executing-plans`. Steps — checkbox.
**Goal:** Аварийный выход владельца переживает сбой защиты — (7б) panic-ветка: escape оценивается даже когда per-tool-логика бросает; (7в) escape-honor во ВСЕХ остающихся fail-CLOSE-стражах (`read-path-deny`, `normative-content-rules`; `mcp-classification` уже чтит), включая §6 escaped-правку ЗАКОНА.
**Architecture:** Один общий pure-предикат `escapeAllowsEvent` (escape-grant) + его проводка в panic-catch (floor) и в content-стражи. supreme-gate `decideMode` G-1α уже escape-first — добавляем тест-доказательство (escape до бросающего verify) + main-catch-recheck на сбой сетапа. Спека §4.1 правило 7(б,в), §5 (read-path-deny/mcp/normative «обязаны чтить escape»), §6 (КАРТА/ЗАКОН: правка ЗАКОНА требует escape владельца).
**Tech Stack:** Node ESM, vitest tools-only, pure-core + thin-main, escape-grant binding-key (canonicalAction тотален из Фазы 0).
**Квирки:** vitest через Bash (одна абс. команда, фильтры раздельно); git через PowerShell `-F`; for-of+it() не it.each; tdd-gate (test перед prod в ходе) + tdd-real-test-verifier (дифф теста: it(+expect(+ссылка на prod). `<wt>` = `c:\моя\проекты\портал crm\Документация\.claude\worktrees\brainrepo`.
**audit-context-building выполнен:** прочитаны enforce-floor.mjs, enforce-supreme-gate.mjs (G-1α:174), enforce-read-path-deny.mjs (escape НЕ читает), enforce-mcp-classification.mjs (escape УЖЕ чтит — эталон проводки), enforce-normative-content-rules.mjs (decide async, Layer 5 skillActive). Все 5 тест-файлов существуют.
---
## Дизайн-решения (locked)
- **D1 — общий предикат.** `escapeAllowsEvent(event, grants, consumed, now)` в escape-grant.mjs: `escapeGrantOpen(canonicalAction({name:event.tool_name, input:event.tool_input}), …)`, обёрнут в try→false (тотален). Единый источник для panic-веток (анти-дрейф).
- **D2 — floor panic (7б).** `enforce-floor.decide` оборачивает `floorDecide` в try; на throw → если `escapeAllowsEvent` → allow (panic-escape), иначе fail-CLOSED block. Инъектируемый `floorDecideImpl=floorDecide` для теста (форсим throw). floorDecide внутри уже escape-чтит (Фаза 1) — это второй, переживающий-сбой слой.
- **D3 — supreme-gate panic (7б).** `decideMode` G-1α уже escape-first (escape ДО любого verifyImpl/verifyArtifactImpl) — тест докажет: с матч-грантом и БРОСАЮЩИМ verifyArtifactImpl → allow mode:escape (verify не вызван). `main` catch получает recheck через `escapeAllowsEvent` (сбой сетапа resolveReceiptKey/stepStatePath до decideMode → escape всё равно оценён). event/escapeGrants подняты в scope main для catch.
- **D4 — read-path-deny escape-honor (7в).** `decide` +параметры `escapeGrants/escapeConsumed/now`; перед block протектед-пути: `escapeGrantOpen(canonicalAction('Read',{file_path}), …)` → allow. main грузит `loadFloorEscapes/loadConsumed`. (canonicalAction для Read → `write:<norm path>` — детерминированный binding-ключ.)
- **D5 — normative escape-honor (7в, §6).** `decide` +параметры escape; В САМОМ НАЧАЛЕ (до Layer 5 skillActive): если escaped → `{block:false, reason:'normative: escaped owner law-edit (§6)'}`. Это реализует §6 «правка ЗАКОНА требует escape-ключа владельца» — escape = санкционированный канал правки закона, минуя claude-md-management. main грузит escapes + canonicalAction(toolName, tool_input). НЕ ослабляет не-escaped (Layer 1-5 без изменений).
- **Инвариант (риск §«Фаза 2»):** escape-honor НЕ ослабляет стражей для не-escaped действий — все существующие block-кейсы остаются (регрессия = страж).
## Файлы
- Modify: `tools/escape-grant.mjs` (+`escapeAllowsEvent`); Test `escape-grant.test.mjs`
- Modify: `tools/enforce-floor.mjs` (`decide` panic try/catch + `floorDecideImpl`); Test `enforce-floor.test.mjs`
- Modify: `tools/enforce-supreme-gate.mjs` (main catch-recheck); Test `enforce-supreme-gate.test.mjs` (decideMode escape-first под throwing verify)
- Modify: `tools/enforce-read-path-deny.mjs` (decide+main escape); Test `enforce-read-path-deny.test.mjs`
- Modify: `tools/enforce-normative-content-rules.mjs` (decide+main escape, §6); Test `enforce-normative-content-rules.test.mjs`
## Задачи (TDD RED→GREEN→commit каждая)
- **2.1** `escapeAllowsEvent` — pure (event→bool через canonicalAction+escapeGrantOpen, тотален). Тест: матч-грант→true; несовпавший/пустой/малформ-event→false.
- **2.2** floor `decide` panic-branch — инъект `floorDecideImpl:()=>{throw}`: с грантом→block:false; без→block:true. Норм-поток (floorDecide реальный) не задет.
- **2.3** read-path-deny escape-honor — протектед-путь без escape→block (регресс); с матч-грантом→block:false.
- **2.4** normative escape-honor — escaped→block:false в начале decide (даже без skillActive); не-escaped без skill→block (регресс Layer 5).
- **2.5** supreme-gate — decideMode с матч-грантом + throwing verifyArtifactImpl→allow mode:escape (escape ДО verify); main catch-recheck (escapeAllowsEvent в scope).
- **2.6** Регрессия tools-only ≥ baseline (текущий 3061+2) + smoke acceptance: «страж бросает → escape оценён→проходит»; «escaped-правка ЗАКОНА проходит»; не-escaped block-кейсы целы.
## Acceptance Фазы 2
escape переживает сбой любого fail-CLOSE-стража (panic-ветка floor/supreme-gate); read-path-deny/normative/mcp чтут escape; §6 escaped-правка ЗАКОНА проходит через normative; не-escaped дисциплина НЕ ослаблена (все block-регрессы зелёные); регрессия ≥ baseline. → разблокирует Фазу 3 (skill-журналер).
## Гейт закрытия: audit-context-building (done) → sharp-edges (escape-honor не открывает дыру не-escaped? canonicalAction-ключ Read/normative корректен?) → variant-analysis (все потребители escapeGrantOpen единообразны) → regression → verification → commit.