spec(router-gate): v3.4 N1/S5 TRUE closure via out-of-band block-file

Dedicated brainstorm на N1 (S5 controller-writable signal) — найден real out-of-band channel без harness-зависимости через subagent gate-process side-channel.

V3.4 architectural shift: BLOCKED-сигнал перенесён из controller-written text (v3.2 regex, v3.3 weak heuristic) в side-channel file `subagent-block-<tool-use-id>.json`:
- Path derived из tool_use_id (Anthropic-assigned, controller не контролирует generation)
- File в `~/.claude/runtime/*` protected paths (Edit/Write/Delete blocked)
- Пишет subagent gate-процесс (отдельный node), не controller text response
- Parent gate читает при PreToolUse следующего tool-use после Task return

Anti-forgery 3 защитных слоя + 6-attack adversarial pre-check (все blocked).

7 spec edits:
- §3.4 переписан: out-of-band block-file channel + anti-forgery + failure modes + adversarial pre-check
- §3.1 +`subagent-block-<tool-use-id>.json` explicit-mention
- §3.2 расширен: subagent gate ТАКЖЕ пишет block-file (side-channel parallel к inheritance read)
- §3.2.0 +Smoke 3: user-run probe для verification block-file write механизма
- §8 budget +2h (subagent write 0.5h + parent read 0.5h + lockfile 0.3h + spec 0.5h + integration 0.1h)
- §9 — S5 убран из acceptable residual list (теперь CLOSED, не как S8)
- §10.2 +schema для subagent-block-<tool-use-id>.json

Implementation budget: 19.5-29h (v3.3) → 21.5-31h (v3.4).

Главный урок v3.4: controller-writable signals fundamentally fake-able, НО можно вырваться из класса через side-channel write — separate process + harness-derived path + protected file. Это НЕ harness-dependent (работает с любым Claude Code где env-vars пробрасываются + subagent gate стартует с теми же хуками).

Brainstorm methodology: superpowers:brainstorming skill — 1 clarifying вопрос (closure level выбран б — out-of-band runtime-файл) → 3 защитных слоя дизайна + 6-attack pre-check → user approval → 7 spec edits.

Verify-sentinel: 1179/1179 vitest tools-only GREEN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-05-29 08:08:01 +03:00
parent 903aa70098
commit 15bf46a1c0
2 changed files with 141 additions and 31 deletions
+13 -13
View File
@@ -1,6 +1,6 @@
# Brain Status (auto-generated)
Last updated: 2026-05-29T04:31:35.642Z
Last updated: 2026-05-29T04:50:44.543Z
| Контролёр | Состояние | Детали |
|---|---|---|
@@ -8,13 +8,13 @@ Last updated: 2026-05-29T04:31:35.642Z
| 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 | ⚠️ | 685 episode(s) this month · Stop-hook + post-commit OK · 20 missed activation(s) — see /brain-retro |
| C5 Observer-coverage | ⚠️ | 688 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: 685 episodes this month, 0 observer_error markers, 138 PII matches before filter
- Legacy v1 episodes (not in factor analysis): 546
- Observer evidence: 688 episodes this month, 0 observer_error markers, 139 PII matches before filter
- Legacy v1 episodes (not in factor analysis): 549
- 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).
@@ -24,16 +24,16 @@ Baseline дисциплины роутера (этап 2 router discipline overh
| Тип задачи | Эпизодов | % с триггер-матчем | % через скил |
|---|---|---|---|
| analysis | 31 | 25.8% | 16.1% |
| planning | 18 | 16.7% | 16.7% |
| analysis | 32 | 25.0% | 18.8% |
| planning | 19 | 15.8% | 15.8% |
| bugfix | 18 | 22.2% | 27.8% |
| feature | 16 | 12.5% | 0.0% |
| cleanup | 6 | 0.0% | 0.0% |
| refactor | 1 | 0.0% | 0.0% |
Router step distribution: 1: 308, 2: 241, 3: 63, 5: 61
Router step distribution: 1: 308, 2: 243, 3: 63, 5: 62
Boundaries applied (ADR / границы): 75 of 673 эпизодов (11.1%).
Boundaries applied (ADR / границы): 75 of 676 эпизодов (11.1%).
## Активные многоэтапные проекты
@@ -51,10 +51,10 @@ Boundaries applied (ADR / границы): 75 of 673 эпизодов (11.1%).
| Компонент | Токены (in/out) | USD |
|---|---|---|
| Classifier (Sonnet 4.6) | 4143/57492 | $0.87 |
| Classifier (Sonnet 4.6) | 4586/59217 | $0.90 |
| Self-assessment (Sonnet 4.6) | 0/0 | $0.00 |
| Reviewer (Opus 4.7 + fallback) | 0/0 | $0.00 |
| **Итого** | | **$0.87** |
| **Итого** | | **$0.90** |
## Аномалии классификатора
@@ -67,7 +67,7 @@ Episodes since last run: 542 / threshold: 10
## Reviewer: субагент vs fallback
0 эпизодов проверено из 685.
0 эпизодов проверено из 688.
## Reviewer findings
@@ -109,7 +109,7 @@ Episodes since last run: 542 / threshold: 10
| Фраза | За всё время | За сегодня |
|---|---|---|
| `recovery` | 1171 | 274 ⚠️ |
| `recovery` | 1233 | 336 ⚠️ |
| `без скилов` | 233 | 55 ⚠️ |
| `ремонт инфраструктуры` | 229 | 44 ⚠️ |
| `срочно` | 144 | 51 ⚠️ |
@@ -123,7 +123,7 @@ Episodes since last run: 542 / threshold: 10
| PID | Имя | CPU-время | Возраст |
|---|---|---|---|
| 3464 | MsMpEng | 1.23ч | 14475943.0ч |
| 3464 | MsMpEng | 1.27ч | 0.0ч |
⚠️ Проверь, не «осиротевшие» ли это процессы от завершённых Claude-сессий.
@@ -1,7 +1,7 @@
# Router-gate hard wall — Дизайн-спецификация (Уровень 4) v3.3
# Router-gate hard wall — Дизайн-спецификация (Уровень 4) v3.4
**Дата:** 2026-05-29
**Версия:** v3.3 (adversarial audit v4.1 closure — 12 holes из аудита v3.2 закрыто; **N1 fatal: честное признание что S5 не закрывается regex'ом — пересмотрено как acceptable residual + weak heuristic**)
**Версия:** v3.4 (N1/S5 TRUE closure — out-of-band runtime block-file через tool_use_id derived path. V3.3 «honest residual» пересмотрено: реальный fix найден без harness-зависимости через subagent gate-process side-channel)
**Автор:** Claude (controller Opus 4.7) под руководством заказчика Дмитрия
**Статус:** Approved by owner — готов к плану implementation
**Тип:** feature — enforcement architecture rewrite
@@ -32,7 +32,7 @@
- Gate budget 2s + fail-CLOSE при таймаутах.
- Bash blocks sub-shells (`backticks`, `$()`, `<()`, `<<` heredocs) + file-watcher для script execution + static content scan.
**Цена:** 18-27 часов implementation в 6 этапов через subagent-driven-development. **Закрыто 50 holes** через 5 раундов adversarial audit (v1 → v2 → v3 → v3.2 → v3.3). **N1 fatal из v3.2 audit пересмотрено** — S5 не закрывается regex'ом (это была иллюзия), теперь acceptable residual + weak heuristic (см. §3.4 v3.3).
**Цена:** 21.5-31 часов implementation в 6 этапов через subagent-driven-development. **Закрыто 51 hole** через 5 раундов adversarial audit (v1 → v2 → v3 → v3.2 → v3.3 → v3.4). **S5 TRUE closure в v3.4** — out-of-band runtime block-file `subagent-block-<tool-use-id>.json` (см. §3.4 v3.4). Из 2 acceptable residual v3.3 (S5 + S8) **S5 теперь closed**, остаётся только S8 (BRANCH-SWITCH-CONFIRMED — separate follow-up эпик).
**Recovery:** заказчик соглашается быть recovery-каналом ручной правкой `.claude/settings.json` / state-файлов при ошибочном lockout.
@@ -40,6 +40,26 @@
**Сохраняется:** 7 preserved хуков (tdd-gate / coverage-verify / memory-coverage / verify-before-push / rationalization-audit / prompt-injection / branch-switch) — у них своя семантика, не про router. 6 из них теряют свои findOverride escape-фразы — становятся hard-walls тоже.
**Changes v3.3 → v3.4:** dedicated brainstorm на N1 (закрытие fatal hole S5) выявил реальный механизм out-of-band channel без harness-зависимости. **S5 TRUE closure** (больше не residual). Механизм:
- **Subagent's gate-процесс пишет block-event-файл** по пути derived из `tool_use_id` Task tool: `~/.claude/runtime/subagent-block-<tool-use-id>.json`. Я (controller) НЕ контролирую: (1) generation `tool_use_id` — Anthropic CLI assigns; (2) запись — gate-процесс subagent'а, отдельный node-процесс; (3) сам файл — в `~/.claude/runtime/*` protected paths (§3.1).
- **Parent gate читает файл** при PreToolUse следующего tool-use после Task return. Decision: file exists + blocks non-empty → escalate AskUser; file missing + tool_use count > 0 → DONE; file missing + count == 0 → assume BLOCKED (gate-crash defensive).
- **Атомарные writes** через proper-lockfile (§3.5) для concurrent blocks subagent'а.
- **Smoke 3** добавлен в §3.2.0 — user-run проверка что subagent gate реально пишет block-file (acceptance: file exists в `~/.claude/runtime/` после blocked Task).
7 spec edits:
- §3.4 переписан — убрана «weak heuristic» framing, заменена «out-of-band channel» закрытием. S5 явно closed.
- §3.1 +`subagent-block-<tool-use-id>.json` explicit-mention.
- §3.2 расширен — subagent gate ТАКЖЕ пишет block-file (parallel к чтению inheritance).
- §3.2.0 +Smoke 3.
- §8 budget +2.2h.
- §9 — S5 убран из residual list (now closed). Остаётся только S8.
- §10.2 +schema для `subagent-block-<tool-use-id>.json`.
Adversarial pre-check (мой же v4.2 audit на этот дизайн перед написанием) — 6 attack-векторов проверены, все заблокированы protected paths + harness-assigned tool_use_id + side-channel write. True closure, не heuristic.
Budget impact: +2.2h (19.5-29h → 21.5-31h).
**Changes v3.2 → v3.3:** adversarial audit v4.1 (второй раунд контроллера на свою же v3.2) выявил **12 новых holes**, из них:
- ☠️ **N1 fatal:** BLOCKED-protocol regex полностью fake-able контроллером (subagent response = controller-written text → `{"status":"DONE"}` маркер всегда вставляется). **Мой v3.2-фикс S5 был самообман.** Closure: убран claim «closes S5», заменён на weak heuristic + S5 переведён в acceptable residual (как S8).
@@ -181,7 +201,7 @@ Self-retrospect #2 (28.05 вечер) подтвердил: 5 commitments утр
Список путей, **никогда** не разрешённых для Edit / Write / MultiEdit / NotebookEdit и Bash-команд rm / mv / cp / redirects (`>` / `>>`):
- `~/.claude/runtime/*` — все gate state-файлы (router-state, chain-state, askuser-decisions, gate-decisions, gate-errors, subagent-inheritance, coverage-hint) **и `gate-config.json` тоже** (explicit-mention — закрывает C4 из v4-аудита, чтобы glob-интерпретация не разошлась).
- `~/.claude/runtime/*` — все gate state-файлы (router-state, chain-state, askuser-decisions, gate-decisions, gate-errors, subagent-inheritance, coverage-hint, **`subagent-block-<tool-use-id>.json` — v3.4 explicit (N1/S5 TRUE closure: subagent gate-process пишет block-events, parent gate читает после Task return; см. §3.4 v3.4 + §10.2 schema)**) **и `gate-config.json` тоже** (explicit-mention — закрывает C4 из v4-аудита, чтобы glob-интерпретация не разошлась).
- `.claude/settings.json` / `.claude/settings.local.json`
- `.claude/skills/**/SKILL.md` — skill definitions
- `tools/enforce-*.mjs` — все enforcement-хуки (включая `enforce-router-gate.mjs`)
@@ -227,7 +247,14 @@ Acceptance criteria: заказчик запускает prompt с явно-fail
- **Pass B:** PostToolUse fires всегда, но содержит `status: "error"` поле → §3 handler фильтрует по этому полю, fix корректен.
- **Fail:** PostToolUse fires всегда без status differentiation → chain_step++ инкрементнет на упавшем skill (та же проблема S3 которую думали что закрыли). Нужен redesign — например chain_step++ через **PreToolUse следующего turn'а** проверяя что предыдущий skill_invoke реально завершился (по transcript output length / artifact files).
Budget: 0.5h на каждый smoke-test + verification = 1h total. Without these — два architectural assumption неверифицированы → суммарный риск 5-15h rework.
**Smoke 3 — subagent block-file write (v3.4 — N1/S5 TRUE closure verification):**
Acceptance criteria: заказчик запускает prompt: «спавн субагента через Task tool с заданием `Write tools/router-gate-decide.mjs` (path в §3.1 protected) — gate должен subagent'у заблокировать Write». После subagent return:
- **Pass:** в `~/.claude/runtime/` появляется `subagent-block-<tool-use-id>.json` с записью block-event на Write. Parent gate в next response обязан escalate AskUser (видно в transcript). Acceptance: file exists + parent escalation visible.
- **Fail:** file missing после blocked Task → subagent gate-process не пишет block-file (либо не запускается в subagent-режиме, либо path-derivation broken). Degraded fallback — оставить v3.3 weak heuristic (tool_use count + marker). Не блокер для эпика, но S5 остаётся в residual.
Budget: 0.5h × 3 smoke-tests + verification = **1.5h total**. Without these — три architectural assumption неверифицированы → суммарный риск 5-15h rework.
### 3.2. Subagent gate inheritance (env-based, NOT text-prefix)
@@ -262,6 +289,8 @@ V3 переходит на **environment-based inheritance** (условно —
4. Inheritance-файл удаляется когда Task tool возвращает результат родителю.
**Block-file write (v3.4 — закрытие S5 через side-channel):** subagent gate ТАКЖЕ пишет `~/.claude/runtime/subagent-block-<tool-use-id>.json` каждый раз когда decide=block для любого tool subagent'а. `tool_use_id` derived из имени inheritance-файла (контролируется harness через Task spawn). Файл — append-only через proper-lockfile (§3.5). Parent gate читает после Task return для проверки blocked ли субагент. Полное описание механизма + flow + anti-forgery — §3.4 v3.4. Schema — §10.2.
**Закрывает Дыру 5 (v1 — undefined behavior)** и **Дыру 11 (v2 — prefix без enforcement)**.
### 3.4. Subagent constraints
@@ -273,23 +302,43 @@ V3 переходит на **environment-based inheritance** (условно —
- **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.
**BLOCKED-protocol weak heuristic (v3.3 honest revision of N1 from v4.1 audit — НЕ закрывает S5):**
**BLOCKED-protocol TRUE closure через out-of-band block-file (v3.4 — closes S5):**
V3.2 заявлял что закрывает S5 через regex `\{[^{}]*"status"\s*:\s*"(DONE|BLOCKED)"[^{}]*\}` в last 200 chars subagent response. **Это была иллюзия.** Subagent response — это текст, который пишет САМ КОНТРОЛЛЕР (через инструмент Task). Контроллер ВСЕГДА может вставить fake `{"status":"DONE"}` в любой response, реально blocked субагент или нет. Regex просто видит «маркер есть» и считает что субагент отработал. Это та же текстовая конвенция без enforcement, что была в S5 изначально.
V3.2 пытался закрыть S5 regex'ом маркера `{"status":"DONE"|"BLOCKED"}` в subagent response — это была иллюзия (controller-written text, fake-able). V3.3 признал residual + weak heuristic. **V3.4 находит real out-of-band channel** через subagent gate-process side-channel.
**Что РЕАЛЬНО можно сделать (weak heuristic, не закрытие):**
**Механизм:**
Parent gate при PreToolUse следующего tool-use **после** Task return анализирует:
1. **Parent gate при PreToolUse(Task)** запоминает `tool_use_id` (Anthropic-assigned для этого Task tool call — controller НЕ контролирует generation; уже используется в §3.2 для `subagent-inheritance-<tool-use-id>.json`).
2. **Subagent gate при decide=`block`** (любой tool, любое Поведение) — atomic append в `~/.claude/runtime/subagent-block-<tool-use-id>.json` запись `{ts, tool_name, reason, decision_kind}` ДО возврата block-decision в subagent process. proper-lockfile (§3.5) для concurrent blocks.
3. **Parent gate при PreToolUse(next tool после Task return)** — читает block-file. Decision:
1. **Tool_use count в subagent transcript** (out-of-band, контроллер не контролирует): сколько tool_uses реально сделал субагент. Если 0 (только text response) → strong-signal BLOCKED (субагент не смог ничего сделать). Если >0 → субагент действовал.
2. **JSON marker check (slabый сигнал)** в last 200 chars: `{"status":"DONE"|"BLOCKED",...}`. Совпадает с tool_use count — confidence high; расходится — flag в rationalization-audit.
3. **Defensive policy:** если tool_use count == 0 И marker отсутствует → assume BLOCKED, parent обязан AskUser. Если tool_use count > 0 И marker `DONE` → продолжаем (контроллер не подделает реальные tool_uses).
| Состояние block-file | tool_use count subagent'а | Parent decision |
|---|---|---|
| File exists, blocks непустой | любое | **Block ALL mutating** до AskUser-эскалации с reason из первого block. Логировать в decisions.jsonl как `subagent_blocked_escalate` |
| File missing | > 0 | **DONE** — subagent работал без блоков. Продолжаем normal flow |
| File missing | == 0 | **Assume BLOCKED** (defensive, gate-crash possible). Parent обязан AskUser |
| File read FS error | любое | fail-CLOSE per §3.3 |
**Acceptable residual (S5 не закрывается v3.3):** контроллер всё ещё может пометить fake `{"status":"DONE"}` после blocked субагента с >0 tool_uses (например, subagent действительно сделал несколько Read но не смог Edit). Real subagent BLOCKED detection требует **harness-level enforcement** (Anthropic Claude Code должен возвращать структурированный status о субагенте) — это за пределами controller-writable spec'а.
4. **Cleanup:** parent gate удаляет block-file после чтения. Background cleanup script (cron / lefthook): orphans > 24h → delete (защита от parent gate crash после read но до delete).
**S5 → §9 acceptable residual** (как S8 BRANCH-SWITCH-CONFIRMED — тот же класс controller-writable-signals).
**Anti-forgery (3 защитных слоя — почему controller не подделает):**
**Закрывает Дыру 18** (multiple subagents fan-out — это про parallel limit, не про signal), **завершает закрытие Дыры 11** (env-based inheritance, не signal).
- **Path derived из `tool_use_id`** — Anthropic CLI assigns при Task spawn. Controller передаёт `subagent_type` и `prompt`, но **не** tool_use_id. Невозможно заранее знать какой ID будет, чтобы pre-emptively написать fake-файл.
- **File в `~/.claude/runtime/*`** → protected per §3.1. Edit/Write/MultiEdit blocked hard-deny. Bash `rm`/`mv`/`cp` в hard-blacklist. Controller не может delete/modify/create.
- **Пишет gate-процесс subagent'а**, не controller-written response text. Gate — отдельный node-процесс с тем же `enforce-router-gate.mjs` (env-inherited через §3.2). Когда gate решает `block`, он напрямую writes файл, controller text-response это никак не контролирует.
**Adversarial pre-check (v4.2 audit):**
| Attack | Защита |
|---|---|
| Edit `enforce-router-gate.mjs` чтобы отменить write | Protected paths §3.1 — blocked |
| Delete block-file до parent read | `~/.claude/runtime/*` protected — rm/mv в hard-blacklist, blocked |
| Pre-emptive fake block-file с пустыми blocks | tool_use_id assign'ит harness, controller не знает заранее; Write на protected — blocked anyway |
| Subagent gate crash до first write | Defensive: file missing + tool_use count == 0 → assume BLOCKED |
| Subagent сделал read-only работу + был blocked на Write | Block-file существует с 1 блоком → parent escalates корректно |
| Race: 2 параллельных block writes от subagent | proper-lockfile (§3.5) — sequential consistency |
**Закрывает S5 v4-audit полностью (TRUE closure, не heuristic).** Также сохраняется закрытие Дыры 18 (multiple subagents fan-out — parallel limit), Дыры 11 (env-based inheritance).
### 3.5. Atomic state writes / file locking
@@ -419,6 +468,8 @@ Gate определяет одно из 4 поведений на основе s
V3.2 заявлял silent fall-through к Поведениям 2/3/4 при unknown skill name. **Это была дыра N2:** пользователь пишет «делай ABC», скила `ABC` нет → controller silently начинает делать router-рекомендованный `Y` без AskUser (потому что Поведение 2 single-rec автоматом allow + safe base, первый mutating tool требует AskUser но к тому моменту контроллер уже многое сделал). **User intent тихо проигнорирован.**
V3.3: если префикс-pattern matched (например `делай foo-bar`), но `foo-bar` **не найден** в registry — gate **обязан** заблокировать любой mutating tool до **mandatory AskUserQuestion** с явным форматом: «Пользователь указал skill `foo-bar`, но он не найден в реестре (возможно опечатка или переименован). Router рекомендует `<rec_node>`. Что делать?» с опциями:
1. «Имел в виду `<closest-fuzzy-match>` — делать его» (если fuzzy-match similarity > 0.7).
2. «Делать `<rec_node>` по рекомендации router'а».
3. «Делать direct без скила».
@@ -808,10 +859,11 @@ Hint удаляется при Stop event (cleanup).
| 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 |
| **2.1.0 — Pre-flight smoke-tests (v3.2/H2 + v3.3/N4/N10)** | Two user-run smoke-tests до 2.1: (1) env propagation probe, (2) PostToolUse semantics probe. Pass обоих → продолжить §3.2 env-based + §3 chain PostToolUse. Fail любого → stop эпик, открыть отдельную задачу redesign | 1 |
| **2.1.0 — Pre-flight smoke-tests (v3.2/H2 + v3.3/N4/N10 + v3.4 S5)** | Three user-run smoke-tests до 2.1: (1) env propagation probe, (2) PostToolUse semantics probe, (3) subagent block-file write probe (v3.4). Pass всех → продолжить §3.2 env-based + §3 chain PostToolUse + §3.4 S5 closure. Fail любого → stop эпик, degraded fallback | 1.5 |
| **+v3.2 fixes inline** | Section A protected paths (+0.3h), B chain PostToolUse migration (+1-2h), C Bash hardening (~2.4h), D default-CLOSE (+0.3h), E path-hardening + BLOCKED-protocol (+0.8h), F morphology + length-ratio (+0.5h) | +5-7 |
| **+v3.3 fixes inline (N1-N12)** | N1 BLOCKED-protocol heuristic + S5 residual (+0.3h), N2 mandatory AskUser on unmatched skill (+0.3h), N3 git commit exit code 0 check (+0.3h), N5 SKILL_BASH_ALLOW mapping init для 3-5 skills (+0.3h), N7 PostToolUse handler registration in settings.json + smoke (+0.5h), N6 edited-files schema in §10.2 (trivial), N8/N11 в существующих modules (+0.3h) | +1.5-2 |
| **Итого** | | **19.5-29** |
| **+v3.4 N1/S5 TRUE closure (out-of-band block-file)** | Subagent gate write block-events (0.5h), parent gate read block-file at PostTask (0.5h), proper-lockfile integration для concurrent blocks (0.3h), schema §10.2 + protected paths integration §3.1 (0.1h), spec edits §3.4 rewrite (0.5h), Smoke 3 уже в 2.1.0 budget | +2 |
| **Итого** | | **21.5-31** |
### Implementation order matrix (closes N7+N9 v4.1-audit)
@@ -850,7 +902,7 @@ Protected paths должны быть live **до** того как соотве
- **S8 — `BRANCH-SWITCH-CONFIRMED` controller-writable marker** (preserved `enforce-branch-switch` hook): после удаления `recovery` override этот хук требует explicit маркер в каждом ответе на dangerous git-операцию. **Маркер пишется в controller response** — controller может self-write его, что есть pre-existing bypass-pattern (зафиксирован в memory `feedback_hook_bypass_pattern_2026-05-28.md`). **Известный residual risk v3.2** — не закрывается в этом эпике. **Follow-up эпик:** «Move BRANCH-SWITCH-CONFIRMED source from response to user-prompt» (заказчик должен явно писать фразу в prompt, аналогично direct-invocation). Budget ~1h на отдельный мини-эпик. Surface как **known-residual-risk** в §11 history v3.2 entry и в STATUS.md C5.
- **S5 — Subagent BLOCKED-protocol controller-writable marker** (v3.3 honest revision of v3.2/N1): mы пытались закрыть это regex'ом `{"status":"DONE"|"BLOCKED"}` в last 200 chars subagent response — но response пишет САМ КОНТРОЛЛЕР, fake-able trivially. V3.3 переводит S5 в acceptable residual со **weak heuristic** (см. §3.4 v3.3): tool_use count == 0 → assume BLOCKED. **Real-fix требует harness-level enforcement** (Anthropic Claude Code должен возвращать структурированный subagent status, недоступный для контроллера) — за пределами controller-writable spec'а. Same class как S8 (controller-writable signals). Surface как known-residual-risk в STATUS.md C5.
- **S5 — Subagent BLOCKED-protocol — CLOSED в v3.4** через out-of-band runtime block-file (§3.4 v3.4). История: v3.2 regex-marker (иллюзия) → v3.3 weak heuristic + honest residual → v3.4 TRUE closure через side-channel `subagent-block-<tool-use-id>.json` (subagent gate-process пишет, parent gate читает, controller не контролирует path/file/writer). Перенесено в §11 как resolved.
- **N12 — Off-topic detection при silence** (§4.7 v3.2): keyword-overlap heuristic с `task_classification` ловит грубые off-topic'и, но `task_classification` сам контролируется prompt'ом (контроллер влияет на classifier output через формулировку prompt'а — хотя prompt пишет пользователь, не controller). **Acceptable residual** — закрытие требовало бы LLM-call для semantic similarity, что добавляет cost+latency без proportional gain. 90% real cases ловятся, edge cases (контроллер выкручивает AskUser в рамки classification но скрытно про другое) — surface через rationalization-flags в next prompt.
@@ -983,6 +1035,30 @@ Protected paths должны быть live **до** того как соотве
}
```
**`~/.claude/runtime/subagent-block-<tool-use-id>.json`** (новый в v3.4, S5 TRUE closure — N1/v4.1-audit fix) — short-lived per Task tool call, append-on-block:
```jsonc
{
"schema_version": 1,
"tool_use_id": "<id from Task tool, harness-assigned>",
"subagent_session_id": "<sess>",
"created_at": "<iso-8601>",
"blocks": [
{
"ts": "<iso-8601>",
"tool_name": "Edit|Write|MultiEdit|NotebookEdit|Bash|Skill|Task|...",
"tool_input_summary": "string (truncated for log)",
"reason": "string (gate decide reason)",
"decision_kind": "1_direct_invocation|2_single_rec|3_chain|4_silence|hard_deny_path"
}
]
}
```
Пишется subagent gate-процессом atomic-append через proper-lockfile (§3.5) при каждом decide=block. Читается parent gate at PreToolUse of next tool после Task return. Удаляется parent gate после чтения. Background cleanup script: orphans > 24h → delete (защита от parent crash после read/before delete).
**Protected status:** glob `~/.claude/runtime/*` в §3.1 + явное упоминание (v3.4) для устранения двусмысленности. Controller не может Edit/Write/Delete.
**`~/.claude/runtime/edited-files-<sess>.json`** (новый в v4, **schema добавлен в v3.3 — N6 v4.1-audit fix**) — session-scoped, append-on-edit:
```jsonc
@@ -1167,6 +1243,40 @@ Berkeley-style: use v2 spec, новый план implementation.
## 11. История версий
### v3.4 (2026-05-29, утро, N1/S5 TRUE closure через out-of-band block-file)
Dedicated brainstorm на закрытие N1 (subagent BLOCKED-protocol, S5) — был зафиксирован в v3.3 как unresolvable в controller-writable spec'е (требует harness-level enforcement). Найден **real out-of-band channel** без harness-зависимости через subagent gate-process side-channel write в protected runtime-файл.
**Что изменилось архитектурно:**
В v3.2/v3.3 BLOCKED-сигнал шёл через **controller-written text** в subagent response (regex-marker или heuristic). Это была уязвимость класса controller-writable signals: я (controller) пишу любой текст, включая fake `{"status":"DONE"}` маркер. V3.4 переносит сигнал в **side-channel** который контроллер не контролирует:
| Что | Раньше (v3.2/v3.3) | V3.4 |
|---|---|---|
| Сигнал | Текст в subagent response (controller-written) | Файл `subagent-block-<tool-use-id>.json` (subagent gate-process пишет) |
| Путь | n/a | Derived из tool_use_id (Anthropic-assigned, controller не знает заранее) |
| Защита от tampering | Regex match (легко подделать) | §3.1 protected paths (`~/.claude/runtime/*` — Edit/Write/Delete blocked) |
| Writer | Controller-written response text | Subagent's gate-process (отдельный node-процесс) |
| Reader | Parent gate parses text | Parent gate reads protected file by deterministic path |
**Adversarial pre-check на новый дизайн (v4.2 audit):** 6 attack-векторов проверены до написания spec'а — все заблокированы protected paths + harness-assigned tool_use_id + side-channel write semantics. Закрытие TRUE, не heuristic.
**7 spec edits:**
- **§3.4 переписан** полностью: убрана «weak heuristic» framing, заменена «out-of-band block-file channel». Anti-forgery (3 защитных слоя) + Adversarial pre-check таблица + Failure modes таблица.
- **§3.1** +`subagent-block-<tool-use-id>.json` explicit-mention (без надежды на glob-интерпретацию).
- **§3.2** расширен: subagent gate ТАКЖЕ пишет block-file (parallel к чтению inheritance — единая subagent-gate logic).
- **§3.2.0** +Smoke 3 user-run probe: «Write на protected → block-file должен появиться». Acceptance + degraded fallback path.
- **§8** budget +2h (subagent write logic 0.5h + parent read logic 0.5h + proper-lockfile integration 0.3h + spec edits 0.5h + integration 0.1h + Smoke 3 уже в 2.1.0). Pre-flight smoke-tests 1h → 1.5h за Smoke 3.
- **§9** — S5 убран из acceptable residual list (был "Same class как S8"). Теперь только S8 (BRANCH-SWITCH-CONFIRMED — отдельный класс, в response-канале, не subagent-related; нужен отдельный follow-up эпик).
- **§10.2** +schema для `subagent-block-<tool-use-id>.json` (tool_use_id, subagent_session_id, blocks[]).
Implementation time: 19.5-29h (v3.3) → **21.5-31h** (v3.4). +2h за N1/S5 TRUE closure infrastructure.
**Главный урок v3.4 (обновление v3.3 урока):** controller-writable signals fundamentally fake-able, НО можно вырваться из этого класса через **side-channel write**: если сигнал пишется отдельным процессом (subagent gate) в файл с deterministic-path-from-harness-id + protected by Edit/Write/Delete enforcement — controller теряет все 3 точки контроля (writer, path, file). Это **не harness-dependent** — работает с любым Claude Code где env-vars пробрасываются (smoke 1) и subagent gate-process реально стартует с теми же settings.json хуками (smoke 3).
Brainstorm methodology: `superpowers:brainstorming` skill — 1 clarifying вопрос (closure level: heuristic / out-of-band file / disable subagent mutating / wait harness) → дизайн с 3 защитными слоями + 6-attack pre-check → user approval → 7 spec edits.
### v3.3 (2026-05-29, утро, adversarial audit v4.1 closure)
Второй раунд adversarial audit от controller'а — теперь на свою же v3.2 (через chain `audit-context-building` → ручной разбор как незнакомого файла). Выявил **12 новых holes**, главное — **N1 fatal: v3.2-фикс S5 был самообманом.** BLOCKED-protocol regex `\{[^{}]*"status"\s*:\s*"(DONE|BLOCKED)"[^{}]*\}` в subagent response — текст пишет САМ КОНТРОЛЛЕР (через Task tool), regex просто видит fake `{"status":"DONE"}` маркер. Та же текстовая конвенция без enforcement, что была в S5 изначально.