spec(router-gate): v3 closes 10 holes from v2 adversarial audit
V2 audit found 10 new bypasses: - Fatal: subagent inheritance via text-prefix (no enforcement) - Critical: hook race conditions / DoS timeout / Bash script execution - Serious: path-deny symlinks/case / AskUser fatigue / silence off-topic - Edge cases: parallel subagents / coverage-verify interaction / sub-shells V3 closes all 10: - 3.2 rewritten: env-based subagent inheritance (env vars + inheritance file), gate-on-subagent reads parent state via CLAUDE_GATE_INHERIT env - 3.4 new: subagent constraints (no AskUser, no recursive Task, max 3 parallel) - 3.5 new: atomic writes (tmp+rename) + proper-lockfile for race-free state - 3.6 new: gate budget 2s + state cache TTL 5s + lazy transcript parsing - 3.1 extended: path normalization (resolve + realpath + case-fold + env) - 4.5 extended: max 2 AskUserQuestion per turn (fatigue exploit closed) - 4.7 extended: off-topic at silence uses task_classification - 5.1 extended: file-watcher for script execution + broad sweep sub-shell blacklist (backticks, command sub, process sub, heredocs) - 5.2 new: static content scan for node/python/vitest scripts before exec - 7.1 new: coverage-hint coordination layer between gate and coverage-verify Implementation cost: 8.5-12h (v2) to 13.5-20h (v3). +5-8h for architectural fixes (env-inheritance, atomic writes, static scan) + infrastructure (gate budget, path norm, askuser counter, coverage-hint). V2 baseline preserved as commitb510a758. V1 as7a43c175. cspell-words.txt += shutil / rmtree. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1869,3 +1869,5 @@ sess
|
||||
детектирован
|
||||
fgrep
|
||||
chgrp
|
||||
shutil
|
||||
rmtree
|
||||
|
||||
+10
-10
@@ -1,6 +1,6 @@
|
||||
# Brain Status (auto-generated)
|
||||
|
||||
Last updated: 2026-05-29T02:42:37.928Z
|
||||
Last updated: 2026-05-29T03:24:37.955Z
|
||||
|
||||
| Контролёр | Состояние | Детали |
|
||||
|---|---|---|
|
||||
@@ -8,13 +8,13 @@ Last updated: 2026-05-29T02:42:37.928Z
|
||||
| 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 | ⚠️ | 664 episode(s) this month · Stop-hook + post-commit OK · 20 missed activation(s) — see /brain-retro |
|
||||
| C5 Observer-coverage | ⚠️ | 666 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: 664 episodes this month, 0 observer_error markers, 131 PII matches before filter
|
||||
- Legacy v1 episodes (not in factor analysis): 525
|
||||
- Observer evidence: 666 episodes this month, 0 observer_error markers, 131 PII matches before filter
|
||||
- Legacy v1 episodes (not in factor analysis): 527
|
||||
- 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).
|
||||
|
||||
@@ -31,9 +31,9 @@ Baseline дисциплины роутера (этап 2 router discipline overh
|
||||
| cleanup | 6 | 0.0% | 0.0% |
|
||||
| refactor | 1 | 0.0% | 0.0% |
|
||||
|
||||
Router step distribution: 1: 296, 2: 236, 3: 63, 5: 60
|
||||
Router step distribution: 1: 298, 2: 236, 3: 63, 5: 60
|
||||
|
||||
Boundaries applied (ADR / границы): 75 of 655 эпизодов (11.5%).
|
||||
Boundaries applied (ADR / границы): 75 of 657 эпизодов (11.4%).
|
||||
|
||||
## Активные многоэтапные проекты
|
||||
|
||||
@@ -51,10 +51,10 @@ Boundaries applied (ADR / границы): 75 of 655 эпизодов (11.5%).
|
||||
|
||||
| Компонент | Токены (in/out) | USD |
|
||||
|---|---|---|
|
||||
| Classifier (Sonnet 4.6) | 3513/46753 | $0.71 |
|
||||
| Classifier (Sonnet 4.6) | 3553/47294 | $0.72 |
|
||||
| Self-assessment (Sonnet 4.6) | 0/0 | $0.00 |
|
||||
| Reviewer (Opus 4.7 + fallback) | 0/0 | $0.00 |
|
||||
| **Итого** | | **$0.71** |
|
||||
| **Итого** | | **$0.72** |
|
||||
|
||||
## Аномалии классификатора
|
||||
|
||||
@@ -67,7 +67,7 @@ Episodes since last run: 542 / threshold: 10
|
||||
|
||||
## Reviewer: субагент vs fallback
|
||||
|
||||
0 эпизодов проверено из 664.
|
||||
0 эпизодов проверено из 666.
|
||||
|
||||
## Reviewer findings
|
||||
|
||||
@@ -110,7 +110,7 @@ Episodes since last run: 542 / threshold: 10
|
||||
| Фраза | За всё время | За сегодня |
|
||||
|---|---|---|
|
||||
| `recovery` | 914 | 17 ⚠️ |
|
||||
| `ремонт инфраструктуры` | 221 | 36 ⚠️ |
|
||||
| `ремонт инфраструктуры` | 229 | 44 ⚠️ |
|
||||
| `без скилов` | 201 | 23 ⚠️ |
|
||||
| `срочно` | 93 | 0 |
|
||||
| `memory dump` | 17 | 0 |
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
# Router-gate hard wall — Дизайн-спецификация (Уровень 4) v2
|
||||
# Router-gate hard wall — Дизайн-спецификация (Уровень 4) v3
|
||||
|
||||
**Дата:** 2026-05-28
|
||||
**Версия:** v2 (closes 10 holes found in v1 adversarial audit)
|
||||
**Версия:** v3 (closes 10 holes found in v2 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 закрыт большинство ранее открытых вопросов.
|
||||
**Changes v2 → v3:** §3.2 переписан под env-based subagent inheritance (вместо text-prefix). Добавлены §3.4 Subagent constraints (no AskUser, no recursive Task), §3.5 Atomic state writes / file locking, §3.6 Gate budget / timeout (2s hard limit, fail-CLOSE), §5.2 Static content scan для node/python/vitest. §3.1 расширен path normalization (resolve + realpath + case-fold). §4.5 ограничен max 2 AskUserQuestion per turn. §4.7 при silence использует task_classification вместо recommendation. §5.1 добавлены file-watcher + broad sweep hard-blacklist для sub-shells. §7 добавлен coverage-hint coordination layer. §8 пересчитан (13.5-20h эпик).
|
||||
|
||||
**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.
|
||||
|
||||
---
|
||||
|
||||
@@ -108,11 +110,11 @@ Self-retrospect #2 (28.05 вечер) подтвердил: 5 commitments утр
|
||||
|
||||
Это **explicit acknowledged risk** — заказчик соглашается что починка любого из 7 preserved хуков требует его ручного вмешательства.
|
||||
|
||||
### 3.1. Protected paths (hard-deny)
|
||||
### 3.1. Protected paths (hard-deny) + path normalization
|
||||
|
||||
Список путей, **никогда** не разрешённых для Edit / Write / MultiEdit / NotebookEdit и Bash-команд rm / mv / cp / redirects (`>` / `>>`):
|
||||
|
||||
- `~/.claude/runtime/*` — все gate state-файлы (router-state, chain-state, askuser-decisions, gate-decisions, gate-errors)
|
||||
- `~/.claude/runtime/*` — все gate state-файлы (router-state, chain-state, askuser-decisions, gate-decisions, gate-errors, subagent-inheritance, coverage-hint)
|
||||
- `.claude/settings.json` / `.claude/settings.local.json`
|
||||
- `.claude/skills/**/SKILL.md` — skill definitions
|
||||
- `tools/enforce-*.mjs` — все enforcement-хуки (включая `enforce-router-gate.mjs`)
|
||||
@@ -121,22 +123,102 @@ Self-retrospect #2 (28.05 вечер) подтвердил: 5 commitments утр
|
||||
|
||||
Hard-deny check выполняется **ПЕРВЫМ** в decision flow, до любой recommendation logic. Никакой unlock (Skill match / AskUserQuestion / direct invocation) не отменяет hard-deny. Изменения этих файлов — только через заказчика снаружи Claude.
|
||||
|
||||
**Закрывает Дыру 4** (Edit/Write state-файлов после unlock).
|
||||
**Path normalization (closes Дыра 15 в v2 audit):** перед сравнением с deny-list gate **канонизирует** путь:
|
||||
|
||||
### 3.2. Subagent gate inheritance
|
||||
1. `path.resolve(target)` — разрешает относительные пути и `..`.
|
||||
2. `fs.realpathSync(resolved)` — разрешает symlinks (если файл существует; если нет — используется resolved).
|
||||
3. Expand `~` через `os.homedir()` и env vars через `process.env`.
|
||||
4. Case-fold на Windows (`.toLowerCase()` для сравнения; на Linux/Mac case-sensitive).
|
||||
|
||||
Task tool автоматически инжектит в начало prompt'а субагента (через существующий `tools/subagent-prompt-prefix.mjs` хук, расширенный):
|
||||
Если canonicalization падает (file ENOENT) — fail-CLOSE для unknown-state файлов, fail-OPEN для проверок прав на чтение существующих.
|
||||
|
||||
- `[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.»
|
||||
**Закрывает Дыру 4 (v1)** и **Дыру 15 (v2)**.
|
||||
|
||||
Субагент при запуске читает parent state и работает под теми же ограничениями. Гарантирует что субагент не становится lateral bypass channel.
|
||||
### 3.2. Subagent gate inheritance (env-based, NOT text-prefix)
|
||||
|
||||
Свой собственный router-state субагента **не создаётся** — gate использует parent's state.
|
||||
V2 полагалась на text-prefix через `subagent-prompt-prefix.mjs` — субагент читает инструкцию и **должен** наследовать. V3 audit выявил что это **не enforcement**, а просьба. Sonnet субагенты могут проигнорировать (Phase 4 Task A прецедент).
|
||||
|
||||
**Закрывает Дыру 5** (subagent inheritance).
|
||||
V3 переходит на **environment-based inheritance**:
|
||||
|
||||
При вызове Task tool gate (PreToolUse):
|
||||
|
||||
1. Пишет `~/.claude/runtime/subagent-inheritance-<task-id>.json`:
|
||||
|
||||
```
|
||||
{
|
||||
"parent_session_id": "<parent-id>",
|
||||
"parent_router_state_path": "~/.claude/runtime/router-state-<parent-id>.json",
|
||||
"parent_chain_state_path": "~/.claude/runtime/chain-state-<parent-id>.json",
|
||||
"allowed_actions": ["Skill(matching)", "Task(subagent_type=matching)", ...],
|
||||
"created_at": "<iso-ts>"
|
||||
}
|
||||
```
|
||||
|
||||
2. Хук `subagent-prompt-prefix.mjs` устанавливает env vars для процесса субагента:
|
||||
- `CLAUDE_PARENT_SESSION_ID=<parent-id>`
|
||||
- `CLAUDE_GATE_INHERIT=true`
|
||||
- `CLAUDE_INHERITANCE_FILE=~/.claude/runtime/subagent-inheritance-<task-id>.json`
|
||||
|
||||
3. Gate-хук **тот же** `enforce-router-gate.mjs` (тот же `.claude/settings.json` наследуется субагентом) при старте проверяет:
|
||||
- Если `CLAUDE_GATE_INHERIT=true` И inheritance-файл существует и валиден → читает parent's state files (router-state, chain-state) из `parent_router_state_path` и `parent_chain_state_path`.
|
||||
- Свой собственный router-state субагента игнорируется (пустой при старте — silence-режим был бы deadlock).
|
||||
- Decisions субагента логируются в **parent's** `router-gate-decisions.jsonl` с префиксом `[subagent:<task-id>]`.
|
||||
|
||||
4. Inheritance-файл удаляется когда Task tool возвращает результат родителю.
|
||||
|
||||
**Закрывает Дыру 5 (v1 — undefined behavior)** и **Дыру 11 (v2 — prefix без enforcement)**.
|
||||
|
||||
### 3.4. Subagent constraints
|
||||
|
||||
Субагент работает с **ограниченным** set'ом возможностей по сравнению с parent:
|
||||
|
||||
- **`AskUserQuestion` НЕ в baseline субагента.** При попытке вызвать — gate субагента блокирует с reason: «Subagents cannot escalate to user — return BLOCKED status to parent».
|
||||
- **Recursive Task **запрещён**.** При попытке вызвать Task внутри Task — gate блокирует. Глубина max 1.
|
||||
- **Limit parallel Task**: max 3 параллельных Task tool calls в одном turn'е родителя. 4-й и далее → block с reason «parallel subagent limit reached».
|
||||
- При lockout субагент возвращает Task tool response: `{status: "BLOCKED", reason: "<why>", parent_should_askuser: true}`. Родитель видит → сам эскалирует к заказчику через свой AskUserQuestion.
|
||||
|
||||
**Закрывает Дыру 18** (multiple subagents fan-out) и **завершает закрытие Дыры 11**.
|
||||
|
||||
### 3.5. Atomic state writes / file locking
|
||||
|
||||
Все state-файлы (`router-state-*.json`, `chain-state-*.json`, `askuser-decisions-*.jsonl`, `router-gate-decisions.jsonl`, `subagent-inheritance-*.json`) пишутся atomically:
|
||||
|
||||
**JSON state-файлы (one-shot writes):**
|
||||
|
||||
- Запись через `write-to-tmp + rename` pattern: `fs.writeFileSync(target + '.tmp', data)` затем `fs.renameSync(target + '.tmp', target)`. POSIX `rename` атомарен; на Windows — близко к атомарному.
|
||||
|
||||
**JSONL append-only логи:**
|
||||
|
||||
- `fs.appendFileSync(target, line)` — атомарен для writes <`PIPE_BUF` (4096 bytes на POSIX) если flag `O_APPEND` установлен. Каждая строка <300 bytes — fits.
|
||||
- На Windows — appendFileSync через `O_APPEND` тоже atomic для small writes.
|
||||
|
||||
**File locking при параллельных tool calls:**
|
||||
|
||||
- Gate использует `proper-lockfile` npm package (cross-platform file locks).
|
||||
- Lock acquired на entry в decide function, released на exit.
|
||||
- Lock timeout 1s — если другой gate-call держит lock дольше → fail-CLOSE (assume contention означает что-то странное).
|
||||
|
||||
**Закрывает Дыру 12** (race conditions при parallel tool calls).
|
||||
|
||||
### 3.6. Gate budget / timeout / state cache
|
||||
|
||||
Gate имеет жёсткий бюджет времени **2 секунды** на одну decision. Если превышен — fail-CLOSE (block с reason «gate budget exceeded»).
|
||||
|
||||
**Оптимизации в gate чтобы укладываться в budget:**
|
||||
|
||||
- **In-memory state cache** (per gate-process) с TTL 5s: router-state и chain-state читаются раз, кэшируются. Следующий tool call в той же сессии в течение 5s — из кэша.
|
||||
- **Lazy transcript parsing**: gate читает только last 5 turns transcript (не весь файл). Этого достаточно для проверки `ask_user_called_this_turn`, `skill_invoked_matching`, `is_direct_invocation`.
|
||||
- **Configurable budget** через `~/.claude/runtime/gate-config.json`:
|
||||
|
||||
```
|
||||
{
|
||||
"max_decision_time_ms": 2000,
|
||||
"state_cache_ttl_ms": 5000,
|
||||
"transcript_lookback_turns": 5
|
||||
}
|
||||
```
|
||||
|
||||
**Закрывает Дыру 13** (hook timeout = no enforcement DoS).
|
||||
|
||||
### 3.3. Failure modes (fail-CLOSE)
|
||||
|
||||
@@ -299,6 +381,8 @@ Gate не unlock'ает turn просто по факту вызова AskUserQu
|
||||
|
||||
Это **критическое отличие v2 от v1**. В v1 AskUserQuestion был универсальным unlock'ом — в v2 он только канал явного одобрения конкретного действия.
|
||||
|
||||
**AskUserQuestion counter (closes Дыра 16):** gate ведёт счётчик `askuser_count_this_turn` в state-файле. Лимит **2 AskUserQuestion за один turn**. 3-я попытка → gate **fail-CLOSE** с reason: «AskUserQuestion ambiguity loop detected (2/2 limit exhausted) — заказчик пусть переформулирует prompt с явным указанием что делать». Счётчик сбрасывается при новом prompt'е.
|
||||
|
||||
### 4.6. Post-skill partial unlock (closes Дыра 3)
|
||||
|
||||
После вызова matching Skill (или Task subagent) gate **частично** unlock'ает turn:
|
||||
@@ -328,7 +412,9 @@ Soft-mitigation: rationalization-audit детектит pattern «Skill X called
|
||||
|
||||
- Лейбл одной из опций содержит «(рекомендую)» / «(recommended)» / «(safe)» / «(quick)» — потенциальный leading question. Flag.
|
||||
- Сильная разница в длине опций: одна > 30 слов, остальные < 5 → leading. Flag.
|
||||
- Вопрос не относится к active recommendation / current task (text similarity check) — потенциальный bypass attempt. Flag.
|
||||
- Вопрос не относится к active recommendation / current task — потенциальный bypass attempt. Flag.
|
||||
|
||||
**Off-topic detection при silence (closes Дыра 17):** когда recommendation отсутствует (Поведение 4), off-topic check сравнивает вопрос AskUser не с recommendation (её нет), а с **`task_classification`** field из router-state. Если classification = `bugfix`, а вопрос про дизайн / погоду / неотносящееся к bugfix — soft flag. Использует keyword-overlap heuristic (simple — без LLM call).
|
||||
|
||||
Flags попадают в `rationalization-flags-<sess>.jsonl` и сюрфейсятся в next prompt через `enforce-prompt-injection`. Этого достаточно — закрытие 100% невозможно (creative leading questions неискоренимы), но 90% реальных случаев ловятся.
|
||||
|
||||
@@ -388,6 +474,15 @@ Gate инспектирует **content** Bash-команды (string в `tool_i
|
||||
- `curl -X POST/PUT/DELETE/PATCH` / `wget --post-data` — outbound mutations
|
||||
- Любые pipe-команды (`|`) где receiver — mutating command
|
||||
|
||||
**Sub-shell / heredoc broad sweep (closes Дыра 20):** команды содержащие любой из следующих токенов — hard-blocked всегда (даже после unlock):
|
||||
|
||||
- `` ` `` (backtick — command substitution)
|
||||
- `$(...)` — POSIX command substitution
|
||||
- `<(...)` / `>(...)` — process substitution
|
||||
- `<<` / `<<-` — heredoc
|
||||
|
||||
Это broad sweep — некоторые legitimate uses (`git log $(echo HEAD)`) ломаются. Принимаем как acceptable friction для закрытия sub-shell bypass attack surface.
|
||||
|
||||
**Conditional whitelist (разрешено только после явного AskUserQuestion approval per §4.5):**
|
||||
|
||||
- Specific commands из ответа заказчика (например если он выбрал «выполнить `npx vitest run`» — тогда разрешена эта exact command).
|
||||
@@ -395,8 +490,24 @@ Gate инспектирует **content** Bash-команды (string в `tool_i
|
||||
|
||||
**Path-deny overlay (§3.1):** даже разрешённый `cat` блокируется если target path — в protected list (`cat ~/.claude/runtime/router-state-*.json` — block, leaks state).
|
||||
|
||||
**File-watcher для script execution (closes Дыра 14 part 1):** gate tracks `edited_files_this_turn` в state. Если в текущем turn'е был Edit/Write на `*.js` / `*.test.js` / `*.mjs` / `*.py` / `*.test.mjs` файл, последующие `node X`, `npx vitest run X`, `python X` блокируются если target path в `edited_files_this_turn`. Reason: «recently-edited script cannot be executed without explicit approval — request via AskUserQuestion».
|
||||
|
||||
Default = block. Reason возвращается в виде «Bash command «{command}» не в whitelist; либо запроси конкретное разрешение через AskUserQuestion, либо используй read-only альтернативу».
|
||||
|
||||
### 5.2. Static content scan for executable scripts (closes Дыра 14 part 2)
|
||||
|
||||
Перед разрешением `node <script>` / `npx vitest run <pattern>` / `python <script>` gate **читает content** target-скрипта и сканирует regex'ом suspicious patterns:
|
||||
|
||||
**Blocked patterns в script content:**
|
||||
|
||||
- `fs.writeFileSync` / `fs.writeFile` / `fs.appendFileSync` / `fs.unlinkSync` / `fs.unlink` / `fs.rmSync` / `fs.rmdirSync` — если target path matches protected list (§3.1).
|
||||
- `child_process.spawn` / `child_process.exec` / `execSync` / `spawnSync` — если args содержат commands из hard-blacklist (`rm`, `git push`, etc).
|
||||
- Python equivalents: `os.remove`, `os.unlink`, `shutil.rmtree`, `subprocess.run` с suspicious args.
|
||||
|
||||
Если matched — block с reason: «Script contains forbidden operations targeting protected paths».
|
||||
|
||||
**Limitations:** статический скан ловит direct calls, но не creative indirection (например `fs[String.fromCharCode(...)]`). 95% real cases caught. Полное решение требовало бы sandbox runtime — выходит за scope этой итерации.
|
||||
|
||||
---
|
||||
|
||||
## 6. Recovery при lockout
|
||||
@@ -467,6 +578,25 @@ Default = block. Reason возвращается в виде «Bash command «{c
|
||||
|
||||
`override-usage.jsonl` и `hook-outcomes.jsonl` остаются на диске как архив. Новый код их игнорирует. Brain-retro может опционально показывать «pre-migration baseline» как историческую справку.
|
||||
|
||||
### 7.1. Coverage-hint coordination (closes Дыра 19)
|
||||
|
||||
Gate (PreToolUse) при первом mutating tool call в turn'е пишет «expected coverage hint» в `~/.claude/runtime/coverage-hint-<sess>.json`:
|
||||
|
||||
```
|
||||
{
|
||||
"ts": "<iso>",
|
||||
"session_id": "<sess>",
|
||||
"turn_id": "<turn>",
|
||||
"expected_coverage": "skill:superpowers:writing-plans",
|
||||
"reason": "router recommended #19, AskUserQuestion approved",
|
||||
"first_mutating_tool": "Skill"
|
||||
}
|
||||
```
|
||||
|
||||
`enforce-coverage-verify` (Stop hook) при срабатывании читает hint и сравнивает с actual coverage в response. Если actual matches expected → ✅ pass. Если mismatch → flag в `rationalization-flags-<sess>.jsonl` для surface в next prompt. Это post-fact дисциплина — не блокирует turn (уже закончен), но создаёт visible feedback loop.
|
||||
|
||||
Hint удаляется при Stop event (cleanup).
|
||||
|
||||
---
|
||||
|
||||
## 8. Этапы реализации (укрупнённо)
|
||||
@@ -475,17 +605,23 @@ Default = block. Reason возвращается в виде «Bash command «{c
|
||||
|
||||
| Этап | Содержание | Часы |
|
||||
|---|---|---|
|
||||
| 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 — Подготовка инфраструктуры | Pure decision-модуль (4 поведения + §4.5/4.6/4.7) + I/O обёртка + chain-state persistence + TTL logic + decision logger + askuser-decisions logger + askuser-counter + ~60 unit-тестов | 2.5-3.5 |
|
||||
| 1.1 — Bash content parser | Pure Bash-command tokenizer + whitelist/blacklist matcher + sub-shell broad sweep + path-deny overlay + edited-files watcher + ~25 unit-тестов | 1.5-2 |
|
||||
| 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 |
|
||||
| 1.3 — Question quality detector | Расширение `enforce-rationalization-audit` с детекторами missing-stop / leading-options / off-topic + silence task_classification matching + ~12 тестов | 0.5 |
|
||||
| 1.4 — Path normalization (§3.1) | path.resolve + realpath + case-fold + env-var expansion + ~10 тестов | 0.5 |
|
||||
| 1.5 — Atomic writes / file locking (§3.5) | write-tmp+rename + proper-lockfile integration + ~15 тестов | 1 |
|
||||
| 1.6 — Gate budget / state cache (§3.6) | 2s timeout + in-memory cache TTL 5s + lazy transcript parsing + gate-config.json + ~10 тестов | 0.5-1 |
|
||||
| 1.7 — Static content scan (§5.2) | Script content reader + regex scanner for fs.write/exec/spawn + protected-path matching + ~15 тестов | 0.5 |
|
||||
| 1.8 — Coverage-hint coordination (§7.1) | Hint write on first mutating tool + coverage-verify reads hint + mismatch flag + ~8 тестов | 0.5 |
|
||||
| 2 — Удаление 5 хуков + vocab | Удалить 5 .mjs + 5 .test.mjs + vocab.json (11 файлов). 3 helper-функции в stubs. Регрессия GREEN | 1-2 |
|
||||
| 2.1 — Subagent env-based inheritance (§3.2) | Расширение subagent-prompt-prefix.mjs — env vars + inheritance file write + gate-on-subagent reads env first + ~10 тестов | 1.5-2 |
|
||||
| 2.2 — Subagent constraints (§3.4) | Remove AskUserQuestion from subagent baseline + block recursive Task + parallel limit 3 + ~8 тестов | 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 + askuser-decisions, новые cut-функции). SKILL.md mandatory tables 11→13 | 1.5-2 |
|
||||
| **Итого** | | **8.5-12** |
|
||||
| 4 — Документация Recovery | Памятка для заказчика по 3 уровням recovery + v3 update | 1 |
|
||||
| 5 — Прогон в реальной работе | Несколько дней наблюдения, brain-retro #11 | (не таск) |
|
||||
| 6 — Brain-retro adaptation | Обновить `brain-retro-analyzer.mjs` + SKILL.md mandatory tables 11→13 | 1.5-2 |
|
||||
| **Итого** | | **13.5-20** |
|
||||
|
||||
### Риски миграции
|
||||
|
||||
@@ -531,6 +667,30 @@ Default = block. Reason возвращается в виде «Bash command «{c
|
||||
|
||||
## 11. История версий
|
||||
|
||||
### v3 (2026-05-28, поздний вечер)
|
||||
|
||||
Adversarial audit спека v2 от controller'а выявил 10 новых дыр, из них:
|
||||
|
||||
- ☠️ Дыра 11 (fatal): subagent inheritance через text-prefix — без enforcement, субагент может проигнорировать.
|
||||
- 🔴 Дыры 12, 13, 14 (critical): race conditions при parallel tool calls / hook timeout = no enforcement (DoS) / Bash whitelist через test files / node scripts.
|
||||
- 🟠 Дыры 15, 16, 17 (serious): path-deny через symlinks/`..`/case / AskUser fatigue exploit / off-topic detector при silence.
|
||||
- 🟡 Дыры 18-20 (edge cases): multiple subagents fan-out / coverage-verify+gate interaction / Bash sub-shells/heredocs в tokenizer.
|
||||
|
||||
V3 закрывает все 10. Ключевые изменения:
|
||||
|
||||
- §3.2 **переписан** под env-based subagent inheritance: env vars + inheritance файл вместо text-prefix. Gate в субагенте читает parent state по env vars — закрывает Дыру 11.
|
||||
- §3.4 **новый**: subagent constraints — нет AskUserQuestion, нет recursive Task, max 3 parallel — закрывает Дыры 11, 18.
|
||||
- §3.5 **новый**: atomic writes (tmp+rename) + proper-lockfile — закрывает Дыру 12.
|
||||
- §3.6 **новый**: gate budget 2s + state cache TTL 5s + lazy transcript — закрывает Дыру 13.
|
||||
- §3.1 **расширен**: path normalization (resolve + realpath + case-fold + env expansion) — закрывает Дыру 15.
|
||||
- §4.5 **расширен**: max 2 AskUserQuestion per turn — закрывает Дыру 16.
|
||||
- §4.7 **расширен**: off-topic при silence сравнивается с task_classification — закрывает Дыру 17.
|
||||
- §5.1 **расширен**: file-watcher для script execution + broad sweep hard-blacklist для sub-shells (backticks, $(), <(), <<) — закрывает Дыры 14 (part 1), 20.
|
||||
- §5.2 **новый**: static content scan для node/python/vitest — закрывает Дыру 14 (part 2).
|
||||
- §7.1 **новый**: coverage-hint coordination layer между gate и coverage-verify — закрывает Дыру 19.
|
||||
|
||||
Implementation time: 8.5-12h (v2) → 13.5-20h (v3). +5-8 часов за архитектурные фиксы (subagent env-inheritance, atomic writes, static content scan) и инфраструктуру (gate budget, path normalization, askuser counter, coverage-hint).
|
||||
|
||||
### v2 (2026-05-28, вечер)
|
||||
|
||||
Adversarial audit спека v1 от controller'а выявил 10 дыр, из них:
|
||||
|
||||
Reference in New Issue
Block a user