spec(router-gate): v2 closes 10 holes from adversarial audit

Adversarial review of v1 found 10 bypass paths:
- Fatal: AskUserQuestion = universal unlock (1 question = full bypass)
- Critical: Bash unlimited / Skill-invoke-no-followthrough / State-files edit
- Serious: Subagent inheritance / Direct-invocation regex too wide
- Edge cases: leading questions / multi-direct / failure mode / chain TTL

V2 closes all 10:
- 3.1 Protected paths (hard-deny for runtime/settings/skill/hook files)
- 3.2 Subagent gate inheritance via subagent-prompt-prefix injection
- 3.3 Failure modes — explicit fail-CLOSE policy
- 3 chain-state TTL 24h + explicit-clear markers
- 4 Direct invocation = strict whitelist (slash-cmd / Skill() / used N / делай exact)
- 4 Multiple direct invocations require AskUser between executions
- 4.5 AskUserQuestion answer parsing — gate reads transcript response,
  unlocks only for explicitly-approved action, blocks on stop answer
- 4.6 Post-skill partial unlock (Read/Grep/next-chain-step allowed,
  Edit/Write require additional AskUser, Bash requires whitelist+approval)
- 4.7 Question quality detector in rationalization-audit (blocks
  missing-stop-option, flags leading options, off-topic questions)
- 5.1 Bash content rules: whitelist read-only / hard-blacklist mutating /
  conditional-whitelist after AskUser approval / path-deny overlay

Implementation cost: 6-8.5h (v1) to 8.5-12h (v2). +2.5-3.5h for
Bash content parser, answer parser, question-quality detector,
hard-deny logic.

Spec v1 (commit 7a43c175) remains in git as baseline.

cspell-words.txt += детектирован / fgrep / chgrp.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-05-29 05:24:40 +03:00
parent 89f124cd27
commit b510a75826
3 changed files with 261 additions and 31 deletions
+3
View File
@@ -1866,3 +1866,6 @@ nohup
вокабуляр
Бypass
sess
детектирован
fgrep
chgrp
+11 -11
View File
@@ -1,6 +1,6 @@
# Brain Status (auto-generated)
Last updated: 2026-05-29T02:07:51.664Z
Last updated: 2026-05-29T02:13:22.220Z
| Контролёр | Состояние | Детали |
|---|---|---|
@@ -8,13 +8,13 @@ Last updated: 2026-05-29T02:07:51.664Z
| C2 Cross-ref consistency | ✅ | [cross-ref-checker] OK — 0 drift in 4 files |
| C3 Observer-of-observer | ✅ | [observer-of-observer] OK — last read 0 week(s) ago |
| C4 Сигнальный статус | ✅ | This file (self-reference) |
| C5 Observer-coverage | ⚠️ | 652 episode(s) this month · Stop-hook + post-commit OK · 20 missed activation(s) — see /brain-retro |
| C5 Observer-coverage | ⚠️ | 656 episode(s) this month · Stop-hook + post-commit OK · 20 missed activation(s) — see /brain-retro |
| C6 Chain map sync | ✅ | [chain-map-checker] OK — 16 chains in sync |
## Метрики (информационные, не алерты)
- Observer evidence: 652 episodes this month, 0 observer_error markers, 130 PII matches before filter
- Legacy v1 episodes (not in factor analysis): 513
- Observer evidence: 656 episodes this month, 0 observer_error markers, 130 PII matches before filter
- Legacy v1 episodes (not in factor analysis): 517
- Last /brain-retro: 2 day(s) ago
- Использование узлов: см. `/brain-retro` (раз в спринт). missed_activations: 20. **Неиспользованные узлы — не алерт, если профильной задачи не было** (Pravila §16.4 v1.36; capability-readiness; см. memory `feedback_brain_unused_tools_not_problem` — outside-repo memory store).
@@ -26,14 +26,14 @@ Baseline дисциплины роутера (этап 2 router discipline overh
|---|---|---|---|
| analysis | 29 | 27.6% | 13.8% |
| bugfix | 18 | 22.2% | 27.8% |
| planning | 16 | 18.8% | 18.8% |
| planning | 17 | 17.6% | 17.6% |
| feature | 16 | 12.5% | 0.0% |
| cleanup | 6 | 0.0% | 0.0% |
| refactor | 1 | 0.0% | 0.0% |
Router step distribution: 1: 286, 2: 235, 3: 62, 5: 60
Router step distribution: 1: 288, 2: 236, 3: 63, 5: 60
Boundaries applied (ADR / границы): 74 of 643 эпизодов (11.5%).
Boundaries applied (ADR / границы): 75 of 647 эпизодов (11.6%).
## Активные многоэтапные проекты
@@ -51,10 +51,10 @@ Boundaries applied (ADR / границы): 74 of 643 эпизодов (11.5%).
| Компонент | Токены (in/out) | USD |
|---|---|---|
| Classifier (Sonnet 4.6) | 3378/43086 | $0.66 |
| Classifier (Sonnet 4.6) | 3448/44775 | $0.68 |
| Self-assessment (Sonnet 4.6) | 0/0 | $0.00 |
| Reviewer (Opus 4.7 + fallback) | 0/0 | $0.00 |
| **Итого** | | **$0.66** |
| **Итого** | | **$0.68** |
## Аномалии классификатора
@@ -67,7 +67,7 @@ Episodes since last run: 542 / threshold: 10
## Reviewer: субагент vs fallback
0 эпизодов проверено из 652.
0 эпизодов проверено из 656.
## Reviewer findings
@@ -110,7 +110,7 @@ Episodes since last run: 542 / threshold: 10
| Фраза | За всё время | За сегодня |
|---|---|---|
| `recovery` | 901 | 4 |
| `ремонт инфраструктуры` | 199 | 14 ⚠️ |
| `ремонт инфраструктуры` | 205 | 20 ⚠️ |
| `без скилов` | 185 | 7 ⚠️ |
| `срочно` | 93 | 0 |
| `memory dump` | 17 | 0 |
@@ -1,11 +1,14 @@
# Router-gate hard wall — Дизайн-спецификация (Уровень 4)
# Router-gate hard wall — Дизайн-спецификация (Уровень 4) v2
**Дата:** 2026-05-28
**Версия:** v2 (closes 10 holes found in v1 adversarial audit)
**Автор:** Claude (controller Opus 4.7) под руководством заказчика Дмитрия
**Статус:** Approved by owner — готов к плану implementation
**Тип:** feature — enforcement architecture rewrite
**Предшественник:** [docs/superpowers/plans/2026-05-28-router-discipline-level-1-2.md](../plans/2026-05-28-router-discipline-level-1-2.md) (Уровни 1+2, merged ранее в день)
**Changes v1 → v2:** добавлены §3.1 (protected paths), §3.2 (subagent inheritance), §3.3 (failure modes), §4.5 (AskUserQuestion answer parsing), §4.6 (post-skill partial unlock), §4.7 (question quality detector), §5.1 (Bash content rules). §4 Поведение 1 переписан со strict whitelist. §8 пересчитан (7-9 часов). §9 закрыт большинство ранее открытых вопросов.
---
## 1. Цель и контекст
@@ -105,6 +108,52 @@ Self-retrospect #2 (28.05 вечер) подтвердил: 5 commitments утр
Это **explicit acknowledged risk** — заказчик соглашается что починка любого из 7 preserved хуков требует его ручного вмешательства.
### 3.1. Protected paths (hard-deny)
Список путей, **никогда** не разрешённых для Edit / Write / MultiEdit / NotebookEdit и Bash-команд rm / mv / cp / redirects (`>` / `>>`):
- `~/.claude/runtime/*` — все gate state-файлы (router-state, chain-state, askuser-decisions, gate-decisions, gate-errors)
- `.claude/settings.json` / `.claude/settings.local.json`
- `.claude/skills/**/SKILL.md` — skill definitions
- `tools/enforce-*.mjs` — все enforcement-хуки (включая `enforce-router-gate.mjs`)
- `tools/router-classifier.mjs` + dependents в `tools/router-*.mjs`
- `tools/router-gate-decide.mjs` + dependents (новые модули gate'а)
Hard-deny check выполняется **ПЕРВЫМ** в decision flow, до любой recommendation logic. Никакой unlock (Skill match / AskUserQuestion / direct invocation) не отменяет hard-deny. Изменения этих файлов — только через заказчика снаружи Claude.
**Закрывает Дыру 4** (Edit/Write state-файлов после unlock).
### 3.2. Subagent gate inheritance
Task tool автоматически инжектит в начало prompt'а субагента (через существующий `tools/subagent-prompt-prefix.mjs` хук, расширенный):
- `[PARENT_SESSION_ID: <id>]` — ID родительской сессии
- `[PARENT_ROUTER_STATE_PATH: ~/.claude/runtime/router-state-<parent-id>.json]`
- `[PARENT_CHAIN_STATE_PATH: ~/.claude/runtime/chain-state-<parent-id>.json]`
- Инструкция: «Inherit parent's gate semantics. On lockout, return BLOCKED status to parent — don't attempt to bypass. Read parent state-files but don't write to them.»
Субагент при запуске читает parent state и работает под теми же ограничениями. Гарантирует что субагент не становится lateral bypass channel.
Свой собственный router-state субагента **не создаётся** — gate использует parent's state.
**Закрывает Дыру 5** (subagent inheritance).
### 3.3. Failure modes (fail-CLOSE)
Когда gate не может определить состояние — block all mutating tools:
| Ошибка | Поведение | Reason в ответе |
|---|---|---|
| `router-state-<sess>.json` отсутствует | Trigger Поведение 4 (silence) → AskUserQuestion required | "Router state missing — escalate" |
| `router-state` malformed (JSON parse error) | Block ALL mutating | "Router state corrupt — re-prompt required" |
| `chain-state` malformed | Reset to empty + log warning | "Chain state corrupt — restarting fresh" |
| Gate сам падает (uncaught exception) | Log в `~/.claude/runtime/gate-errors.jsonl` + block ALL mutating | "Gate internal error — see logs" |
| Hard-deny path не доступен для проверки | Block (assume protected) | "Path-check failed — defaulting to deny" |
Эта политика — explicit **fail-CLOSE**. Никаких fail-open ради «работает хоть как-то».
**Закрывает Дыру 9** (gate failure mode undefined).
### Новый файл состояния
`~/.claude/runtime/chain-state-<sess>.json`:
@@ -120,6 +169,13 @@ Self-retrospect #2 (28.05 вечер) подтвердил: 5 commitments утр
Инициализируется когда router выдаёт chain. Обновляется при matching skill invocation (chain_step++). Очищается когда `chain_step >= chain_active.length` или router даёт другую цепочку (обнуляется и переинициализируется).
**Chain-state TTL (closes Дыра 10):** chain-state считается expired и удаляется если:
- `initialized_at + 24h < now` — цепочка протухла.
- User prompt содержит явные маркеры новой задачи: `новая задача` / `забудь` / `другое` / `новая работа` / `сброс` (case-insensitive substring).
При expiry — chain-state удаляется, gate переходит в Поведение 4 (silence) если новый router-state не даёт recommendation.
### Новый файл логирования
`~/.claude/runtime/router-gate-decisions.jsonl`:
@@ -136,17 +192,37 @@ Append-only, ~1KB на запись × ~300 записей/день = ~300KB/д
Gate определяет одно из 4 поведений на основе state:
### Поведение 1 — Прямое поручение заказчика (Direct invocation)
### Поведение 1 — Прямое поручение заказчика (Direct invocation) — STRICT v2
Триггер: prompt начинается со slash-команды (`/brain-retro`, `/code-review`, etc), ИЛИ содержит явное `вызови X` / `делай subagent-driven` / `используй superpowers:writing-plans`.
**Детектор: strict whitelist (никаких fuzzy regex).** Direct invocation **только** через literal pattern match:
Детектор: regex поиск в `lastUserPromptText` для:
| Pattern | Пример | Что разрешает |
|---|---|---|
| `^/[a-z0-9_-]+($\|\s)` | `/brain-retro` / `/code-review --ultra` | Slash-команда в начале prompt'а |
| `вызови Skill\([^)]+\)` | `вызови Skill(superpowers:writing-plans)` | Literal call-syntax |
| `используй #\d+` | `используй #19` | Literal по реестру |
| `делай <exact-skill-name>` | `делай subagent-driven-development` | Literal имя из реестра (точное совпадение с node `name` или `slug`) |
- `^/[a-z0-9_-]+(\s|$)` — slash-command в начале prompt'а
- `(?:вызови|используй|делай|invoke)\s+(?:superpowers:)?[a-z0-9_-]+` — явное указание скила
**НЕ детектится как direct invocation:**
Если совпадение И tool-call матчит указанный скил**allow** (без AskUserQuestion gate).
Если совпадение И tool-call НЕ матчит → **block** (заказчик указал X, а ты вызываешь Y).
- `продолжай`, `продолжи`, `делай дальше` — это разговорное продолжение, не указание скила
- `делай как считаешь нужным` — это передача решения мне, не указание
- `сделай Х», «можешь сделать», «давай делать» — без явного skill-name это не direct invocation
- `вызови что-то` / `используй какой-нибудь скил` — без exact skill-name
**Если direct invocation детектирован:**
- Tool-call матчит указанный skill → **allow** без AskUserQuestion.
- Tool-call НЕ матчит → **block** с reason: «заказчик указал X через direct invocation, текущий tool — другое».
- Read-only baseline (Read/Grep/Glob/LS/TodoWrite/AskUserQuestion) разрешены всегда (как в обычном flow).
**Multiple direct invocations в одном prompt'е (closes Дыра 8):**
Если в prompt'е несколько direct invocations (например `вызови writing-plans, потом делай subagent-driven`):
- **Первая** invocation выполняется без AskUserQuestion.
- **Вторая и далее** требуют AskUserQuestion после завершения первой (`Ты указал A, B, C — A выполнен, продолжать с B?`).
- Не разрешается chained execution всех direct invocations подряд без явного approval per step.
### Поведение 2 — Роутер дал одиночную рекомендацию
@@ -203,22 +279,124 @@ Gate инициализирует chain-state (если не инициализ
- Моя честная оценка — подходит ли рекомендация, если нет — почему.
- 3-4 варианта на выбор с цена каждого (плюсы / минусы).
### 4.5. AskUserQuestion answer parsing (closes Дыра 1)
Gate не unlock'ает turn просто по факту вызова AskUserQuestion. Gate **читает ответ заказчика** из transcript (AskUserQuestion tool result содержит структурированную информацию о выбранной опции) и интерпретирует:
| Ответ заказчика | Действие gate'а |
|---|---|
| Выбранная опция содержит `стоп` / `отмена` / `не делать` / `ничего` / `остановись` / `cancel` / `stop` (case-insensitive) | Gate **остаётся заблокированным**. Никакого unlock. Subsequent mutating tools всё ещё блокируются |
| Выбранная опция матчит конкретный skill/tool/action в формуле «делать X» / «выполнить X» / «использовать X» | Gate unlock'ает **только** этот конкретный skill/tool. Остальные mutating tools всё ещё блокируются (требуется новый AskUserQuestion для них) |
| Выбранная опция явно говорит «direct, без скила» / «продолжить без skill'а» / «делать напрямую» | Gate unlock'ает Edit/Write/MultiEdit (но не Bash mutating и не Skill/Task non-baseline). Bash требует whitelist (§5.1) |
| Свободный ответ (заказчик выбрал «Other» и написал текст) | Gate анализирует free-form text по тем же правилам выше. Если интерпретация неоднозначна — gate требует ещё одно AskUserQuestion для уточнения |
| AskUserQuestion вызван, но заказчик ещё не ответил (in-flight) | Gate ждёт ответ — pre-answer state, никакого unlock |
Все решения логируются в новый файл `~/.claude/runtime/askuser-decisions-<sess>.jsonl`:
```
{"ts": "2026-05-28T20:30:01.123Z", "question": "...", "options": [...], "chosen": "стоп — не делать", "gate_interpretation": "stop_remain_locked", "subsequent_tools": []}
```
Это **критическое отличие v2 от v1**. В v1 AskUserQuestion был универсальным unlock'ом — в v2 он только канал явного одобрения конкретного действия.
### 4.6. Post-skill partial unlock (closes Дыра 3)
После вызова matching Skill (или Task subagent) gate **частично** unlock'ает turn:
| Tool | Разрешено после Skill match |
|---|---|
| Read / Grep / Glob / LS / TodoWrite / AskUserQuestion | ✅ Always |
| Next chain step (если chain активна) | ✅ Если матчит chain[chain_step] |
| Task subagent matching active recommendation | ✅ |
| Edit / Write / MultiEdit / NotebookEdit | ⚠️ Только после **дополнительного** AskUserQuestion с явным «выполнить такое-то изменение» |
| Bash mutating | ❌ Требует AskUserQuestion с конкретной командой (§4.5) + §5.1 whitelist |
| Skill / Task non-matching | ❌ |
Это закрывает Дыру 3 (Skill invoke без следования). После вызова рекомендованного skill'а я не могу автоматически делать **любые** правки — нужен дополнительный явный approval от заказчика на конкретное действие.
Soft-mitigation: rationalization-audit детектит pattern «Skill X called → 3+ tools потом без TodoWrite update с цитированием skill output» → flag в next turn (warn-only).
### 4.7. AskUserQuestion question quality detector (closes Дыра 7)
`enforce-rationalization-audit` расширяется новыми детекторами для AskUserQuestion content:
**Block (hard):**
- Опция «остановиться» / «отмена» / «cancel» **обязательна** в списке. Если её нет — gate **блокирует** вызов AskUserQuestion и требует переформулировать вопрос с opt-out.
**Soft warnings (surface в next turn):**
- Лейбл одной из опций содержит «(рекомендую)» / «(recommended)» / «(safe)» / «(quick)» — потенциальный leading question. Flag.
- Сильная разница в длине опций: одна > 30 слов, остальные < 5 → leading. Flag.
- Вопрос не относится к active recommendation / current task (text similarity check) — потенциальный bypass attempt. Flag.
Flags попадают в `rationalization-flags-<sess>.jsonl` и сюрфейсятся в next prompt через `enforce-prompt-injection`. Этого достаточно — закрытие 100% невозможно (creative leading questions неискоренимы), но 90% реальных случаев ловятся.
---
## 5. Безопасная база (всегда разрешено)
Эти tools never blocked, любое поведение:
- `Read` — просмотр файлов
- `Read` — просмотр файлов (кроме protected paths § 3.1)
- `Grep` — поиск по содержимому
- `Glob` — поиск по имени
- `LS` — список файлов
- `TodoWrite` — мой список задач (внутренний)
- `AskUserQuestion` — обращение к заказчику (ключевой канал)
- `AskUserQuestion` — обращение к заказчику (но gate проверяет content per §4.7 и parse-ит ответ per §4.5)
- `ListMcpResourcesTool` — список MCP-ресурсов
- `ReadMcpResourceTool` — чтение MCP-ресурса
- `ReadMcpResourceTool` — чтение MCP-ресурса (только для tools явно помеченных read-only в реестре; default — block)
- Текстовый ответ (нет tool_use)
`Bash` **НЕ** в безопасной базе — для него отдельные правила в §5.1.
### 5.1. Bash content rules (closes Дыра 2)
Gate инспектирует **content** Bash-команды (string в `tool_input.command`). Парсит через простой shell-aware tokenizer (split по `;`, `&&`, `||`, `|` — далее извлекает first token каждой ветви как command).
**Default-deny** — если command не в whitelist и не в conditional-whitelist, gate блокирует.
**Whitelist (всегда разрешено, любое поведение):**
| Команда | Допустимые args |
|---|---|
| `git status` | без mutating флагов |
| `git log` / `git show` / `git diff` / `git blame` | без `--exec` |
| `git rev-parse` / `git merge-base` | любые |
| `git branch --show-current` | (только этот sub-command для `git branch`) |
| `git remote -v` / `git remote show` | без add/set-url |
| `ls` / `pwd` / `wc` / `head` / `tail` / `file` / `stat` | без `>` redirect |
| `grep` / `egrep` / `fgrep` | без `--exec`, без `-l` после `>` |
| `cat` / `less` / `more` | без `>` redirect |
| `node` | без `-e` / `--eval` / `-p` / `--print` |
| `npx vitest run ...` / `npx vitest --version` | любые run-args, не `--reporter` с suspicious patterns |
| `npm test` / `npm run test` / `npm run lint:*` (read-only scripts) | любые |
| `php artisan` (read-only commands только: `list`, `route:list`, `migrate:status`) | по whitelist |
| `composer show` / `composer outdated` | без `--update` |
**Hard-blacklist (никогда не разрешено, даже после unlock):**
- `rm` / `mv` / `cp` (любые формы — file mutation)
- `chmod` / `chown` / `chgrp`
- `>` / `>>` — redirect к файлу (создаёт/перезаписывает файл)
- `&&` / `||` chaining двух mutating commands
- `node -e` / `node --eval` / `python -c` / `bash -c` / `sh -c` / `eval` — arbitrary code execution
- `git push` / `git commit` / `git merge` / `git rebase` / `git reset` / `git checkout` / `git switch` / `git pull` / `git stash` / `git cherry-pick` / `git revert` / `git branch -f` / `git branch -d`
- `composer install` / `composer update` / `composer require` / `composer remove`
- `npm install` / `npm update` / `npm remove` / `yarn add` / `pnpm add`
- `npx claude-*` — запуск Claude изнутри Claude
- `curl -X POST/PUT/DELETE/PATCH` / `wget --post-data` — outbound mutations
- Любые pipe-команды (`|`) где receiver — mutating command
**Conditional whitelist (разрешено только после явного AskUserQuestion approval per §4.5):**
- Specific commands из ответа заказчика (например если он выбрал «выполнить `npx vitest run`» — тогда разрешена эта exact command).
- После Skill match для конкретной семантики — например `superpowers:test-driven-development` skill разрешает Bash для запуска тестов, но не для commit'ов.
**Path-deny overlay (§3.1):** даже разрешённый `cat` блокируется если target path — в protected list (`cat ~/.claude/runtime/router-state-*.json` — block, leaks state).
Default = block. Reason возвращается в виде «Bash command «{command}» не в whitelist; либо запроси конкретное разрешение через AskUserQuestion, либо используй read-only альтернативу».
---
## 6. Recovery при lockout
@@ -297,13 +475,17 @@ Gate инициализирует chain-state (если не инициализ
| Этап | Содержание | Часы |
|---|---|---|
| 1 — Подготовка инфраструктуры | Pure decision-модуль + I/O обёртка + chain-state persistence + decision logger + ~30 unit-тестов + smoke-тесты | 2-3 |
| 1 — Подготовка инфраструктуры | Pure decision-модуль (4 поведения + §4.5/4.6/4.7) + I/O обёртка + chain-state persistence + TTL logic + decision logger + askuser-decisions logger + ~50 unit-тестов + smoke-тесты | 2.5-3.5 |
| 1.1 — Bash content parser | Pure Bash-command tokenizer + whitelist/blacklist matcher + path-deny overlay + ~20 unit-тестов на edge cases (chaining, redirects, quoting) | 1-1.5 |
| 1.2 — AskUserQuestion answer parser | Pure answer-text classifier (stop / specific-skill / direct-no-skill / freeform) + transcript reader + ~15 unit-тестов | 0.5-1 |
| 1.3 — Question quality detector | Расширение `enforce-rationalization-audit` с детекторами missing-stop / leading-options / off-topic + ~10 тестов | 0.5 |
| 2 — Удаление 5 хуков + vocab | Удалить 5 .mjs + 5 .test.mjs + vocab.json (11 файлов). 3 helper-функции (findOverride / findOverrideAttempt / loadOverrideVocab) в enforce-hook-helpers.mjs оставить как stubs — возвращают null/empty всегда. Это сохраняет совместимость с 6 preserved хуками (tdd-gate / coverage-verify / memory-coverage / verify-before-push / prompt-injection / branch-switch) без правки их кода. Регрессия GREEN | 1-2 |
| 2.1 — Расширение subagent-prompt-prefix | Добавить parent state-paths инжекшн (closes Дыра 5) + ~5 тестов | 0.5 |
| 3 — Регистрация в settings.json | Добавить router-gate, снять регистрации удалённых хуков. Smoke-test полной сессии | 0.5 |
| 4 — Документация Recovery | Памятка для заказчика по 3 уровням recovery (отдельный doc) | 1 |
| 5 — Прогон в реальной работе | Несколько дней наблюдения, brain-retro #11 — проверить новые таблицы | (не таск) |
| 6 — Brain-retro adaptation | Обновить `brain-retro-analyzer.mjs` (parse router-gate-decisions, новые cut-функции). SKILL.md mandatory tables 11→13 | 1.5-2 |
| **Итого** | | **6-8.5** |
| 6 — Brain-retro adaptation | Обновить `brain-retro-analyzer.mjs` (parse router-gate-decisions + askuser-decisions, новые cut-функции). SKILL.md mandatory tables 11→13 | 1.5-2 |
| **Итого** | | **8.5-12** |
### Риски миграции
@@ -314,13 +496,26 @@ Gate инициализирует chain-state (если не инициализ
## 9. Открытые вопросы (для плана implementation)
Эти вопросы решаются на этапе writing-plans, не сейчас:
**Большинство вопросов v1 закрыты в v2.** Остающиеся для writing-plans фазы:
- Точный формат AskUserQuestion message для каждого Поведения 2/3/4 — нужна шаблонная функция или жёсткие строки?
- Persistence chain-state при крэше Claude session — нужен периодический snapshot или достаточно текущего файла?
- Direct-invocation detection — точный regex для slash-команд и явных вызовов; покрытие угловых случаев («ну давай вызови ... что-то»).
- Логирование `direct_invocation` events — добавить отдельный bucket в decision log?
- Bash-команды как mutating — какой именно sub-pattern блокируем? Полное blacklist (`rm -rf` / `git push` / etc) или whitelist read-only (`git status` / `git log` / etc)?
- **Точный формат AskUserQuestion message** для каждого Поведения 2/3/4 — нужна шаблонная функция или жёсткие строки? Скорее всего шаблонная (template literal с placeholder'ами).
- **Тhrottling AskUserQuestion** — если за один turn я вызываю AskUserQuestion 3+ раза, gate должен warn'ать или block'ать? Не закрыто в v2.
- **Free-form answer интерпретация** — когда заказчик ответил «Other» с текстом, как gate решает что разрешено? §4.5 говорит «если интерпретация неоднозначна — ещё одно AskUserQuestion». Но «неоднозначна» — нечёткая граница. Может потребоваться LLM-call для парсинга. Стоит обсудить отдельно.
- **Logging granularity** — все decision events в одном файле или раздельно (router-gate / askuser-decisions / gate-errors)? Сейчас разделено на 3 файла — может слишком много.
- **Bash whitelist completeness** — список в §5.1 покрывает основные read-only operations, но возможно есть пропуски (например `tree`, `du`, `find` с right args). Расширяется итеративно по требованию из реальной работы.
### Закрыты в v2
- ✅ Direct-invocation regex (§4 Поведение 1 — strict whitelist).
- ✅ Bash-команды как mutating (§5.1 whitelist+blacklist).
- ✅ Logging `direct_invocation` (логируется в router-gate-decisions.jsonl как separate decision-kind).
- ✅ Persistence chain-state — TTL 24h + explicit-clear markers (§3 chain-state).
- ✅ Gate failure mode (§3.3 fail-CLOSE).
- ✅ Subagent inheritance (§3.2).
- ✅ AskUserQuestion как unlock (§4.5 — answer parsing).
- ✅ Skill invoke without follow-through (§4.6 — partial unlock).
- ✅ Leading questions (§4.7 — content detector).
- ✅ State files protection (§3.1).
---
@@ -331,3 +526,35 @@ Gate инициализирует chain-state (если не инициализ
- Self-retrospect #2: [`docs/observer/notes/2026-05-28-self-retrospect-2.md`](../../observer/notes/2026-05-28-self-retrospect-2.md)
- Pravila §16 brain governance, §17 universal skill-coverage — будет обновляться отдельной задачей через `claude-md-management` после этого эпика.
- Текущая enforcement-архитектура: 5 PreToolUse хуков + vocab.json в `tools/`.
---
## 11. История версий
### v2 (2026-05-28, вечер)
Adversarial audit спека v1 от controller'а выявил 10 дыр, из них:
- ☠️ Дыра 1 (fatal): AskUserQuestion = универсальный unlock — один хитрый вопрос обходил весь Уровень 4.
- 🔴 Дыры 2, 3, 4 (critical): Bash unlimited после unlock / Skill invoke без следования / Edit/Write state-файлов.
- 🟠 Дыры 5, 6 (serious): Subagent inheritance undefined / Direct invocation regex слишком широкий.
- 🟡 Дыры 7-10 (edge cases): leading questions / multiple direct invocations / gate failure mode / chain-state TTL.
V2 закрывает все 10. Добавлены:
- §3.1 Protected paths (hard-deny списка) — closes Дыра 4.
- §3.2 Subagent gate inheritance — closes Дыра 5.
- §3.3 Failure modes (fail-CLOSE) — closes Дыра 9.
- §3 chain-state TTL (24h + explicit-clear markers) — closes Дыра 10.
- §4 Поведение 1 переписан со strict whitelist для direct invocation — closes Дыра 6.
- §4 Multiple direct invocations rule — closes Дыра 8.
- §4.5 AskUserQuestion answer parsing — closes Дыра 1 (фатальная).
- §4.6 Post-skill partial unlock — closes Дыра 3.
- §4.7 Question quality detector — closes Дыра 7.
- §5.1 Bash content rules (whitelist + blacklist + path-deny overlay) — closes Дыра 2.
Implementation time: 6-8.5h (v1) → 8.5-12h (v2). +2.5-3.5 часа за дополнительные парсеры (Bash, answer, question-quality) и hard-deny logic.
### v1 (2026-05-28, ранее вечер)
Initial design — single PreToolUse router-gate hook заменяет 5 существующих хуков + vocab.json. Approved by owner section-by-section через brainstorming. Committed как `7a43c175`.