diff --git a/cspell-words.txt b/cspell-words.txt index 4cdc0e01..4863469f 100644 --- a/cspell-words.txt +++ b/cspell-words.txt @@ -1866,3 +1866,6 @@ nohup вокабуляр Бypass sess +детектирован +fgrep +chgrp diff --git a/docs/observer/STATUS.md b/docs/observer/STATUS.md index ec5b9802..b7c2130f 100644 --- a/docs/observer/STATUS.md +++ b/docs/observer/STATUS.md @@ -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 | diff --git a/docs/superpowers/specs/2026-05-28-router-gate-hard-wall-design.md b/docs/superpowers/specs/2026-05-28-router-gate-hard-wall-design.md index bfa1f38a..4a98021b 100644 --- a/docs/superpowers/specs/2026-05-28-router-gate-hard-wall-design.md +++ b/docs/superpowers/specs/2026-05-28-router-gate-hard-wall-design.md @@ -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 родительской сессии +- `[PARENT_ROUTER_STATE_PATH: ~/.claude/runtime/router-state-.json]` +- `[PARENT_CHAIN_STATE_PATH: ~/.claude/runtime/chain-state-.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-.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-.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 по реестру | +| `делай ` | `делай 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-.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-.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`.