docs(router-mentor): stage 3 replay spec

This commit is contained in:
Дмитрий
2026-06-09 06:00:15 +03:00
parent 7e5db3ef91
commit 987feb2e40
@@ -0,0 +1,156 @@
# Router-mentor — Этап 3: shadow-обкатка через холостой прогон (design)
**Дата:** 2026-06-09 · **Эпик:** «роутер-наставник» · **Ветка:** `worktree-brainrepo` · commit-not-push.
**Статус:** дизайн на утверждение владельца.
**Источник:** реестр хвостов `2026-06-08-router-mentor-loose-ends-registry.md` (Этап 3) + handoff `2026-06-09-router-mentor-blockB-r08-handoff.md`.
---
## 1. Контекст и цель
Машины М1–М6 собраны и инертны до боевого включения (Фаза 8). Прежде чем включать их живьём,
владелец хочет **обкатать** их в режиме наблюдения — увидеть, как они РЕШАЛИ БЫ, не ломая рабочий
цикл. Прямой триггер — урок Layer 4 LLM-judge: живой судья сразу клинил `git status`/`log`
(over-block), потому что включился в hard-block без предварительного наблюдения.
**Критерий успеха (выбор владельца — «полная картина»):** по каждой машине видеть ОБА:
1. **over-block** — где машина ложно заблокировала бы безобидное действие;
2. **real-catch** — что на заведомо рискованных действиях она срабатывает как надо.
Право на Этап 4 (боевое включение) даёт отчёт, где over-block = 0 (или объяснены и приняты) и
рискованные действия ловятся (real-catch), без miss (рискованное → пропущено).
**Находка разведки (факт из кода, 2026-06-09):**
- Режим наблюдения «из коробки» есть только у **М4** (`judge-gate-config.mjs`: `inert` / `shadow`
по умолчанию при активации / `live-block`).
- **М2 (стена)** и **М5 (пол)** — режима наблюдения нет, `decide()` сразу `allow`/`block`.
- **М6 (снимок/escape)** — режима нет.
- **М3 (роутер)** — ЖИВОЙ: `router-classifier.classify()` по умолчанию зовёт реальный
`callAnthropicAPI` (ProxyAPI); мок только в тестах. Это recommender, не блокатор.
- **М1 (журнал)** — субстрат, наполняется при работе остальных; целостность через `verifyChain`.
## 2. Подход (выбор владельца — Подход 1)
**Холостой прогон (read-only).** Один новый инструмент `tools/shadow-replay.mjs` прогоняет корпус
событий через чистые `decide()` каждой машины и пишет отчёт. **Ничего не регистрирует как хук, не
трогает `settings.json`, не правит стены** — только импортирует их `decide()` и читает журналы.
Отвергнуто: Подход 2 (живой shadow судьи + холостой остальные — лишние шаги владельца, судейство
всё равно не подключено, хвост A1); Подход 3 (добавить флаг наблюдения в стены — правка несущего
кода стен, дороже всего, риск внести реальный обход).
## 3. Архитектура
Один read-only CLI-инструмент `tools/shadow-replay.mjs` (+ тест `shadow-replay.test.mjs`).
Компоненты — каждый одна понятная чистая функция:
- `buildCorpus({ journal, episodes, riskyFixture })` — собирает события: безобидные из журнала М1
и эпизодов наблюдателя + рискованные из курированной фикстуры. Возвращает массив
`{ id, kind: 'benign'|'risky', toolUse, context }`.
- `runMachine(machineId, corpus, deps)` — гоняет корпус через `decide()` одной машины; возвращает
массив `{ event, decision }`. Чистая обёртка над уже существующими `decide()` (импорт, не правка).
- `classifyOutcome(event, decision)` — раскладывает результат в ведро:
`over-block` (benign → block) / `real-catch` (risky → block) / `allow` (benign → allow) /
`miss` (risky → allow — ТРЕВОГА).
- `renderReport(resultsByMachine)` — markdown-отчёт с вердиктом по машине и сводкой.
- `main()` — склейка: читает входы, пишет отчёт в файл, печатает сводку. fail-quiet, состояние
НЕ пишет.
**Поток данных (read-only, в одну сторону):**
`журнал М1 + эпизоды + фикстура рискованных``buildCorpus``runMachine ×6``classifyOutcome`
`renderReport``отчёт.md`. Обратно в систему — ничего; машины не вызываются как хуки.
**Сигнатуры машин (считаны 2026-06-09, для вшивки):**
- М2 `supreme-gate.decide({ toolUse, frozenPlan, frozenArtifact, stepPtr, key, verifyImpl, verifyArtifactImpl, normalize })``{ decision, reason, advanceTo? }`.
- М5 `floor-decide.floorDecide({ toolUse, escapeGrants, escapeConsumed, now, normalizeImpl })`.
- М6 `snapshot-decide.snapshotNeeded(toolName, toolInput)` + escape-гранты (`escape-grant` / `floor-escape-consume`).
- М4 `enforce-judge-gate.decide({ mode, verdict, floorBlocked })` (gate-логика; `runJudgeGate` пока `GO/wired:false` — A1).
- М3 `router-classifier.classify(prompt, registry, options)` — для обкатки НЕ зовём живьём, а читаем уже залогированные рекомендации эпизодов.
- М1 `action-journal` — источник корпуса + `verifyChain` (целостность).
## 4. Корпус событий и честная разбивка «реальное vs синтетика»
- **Безобидные события** — из журнала М1 (`action-journal`) и эпизодов
(`docs/observer/episodes-*.jsonl`). Кросс-сессионные транскрипты НЕ читаем (под read-deny + exfil).
- **Рискованные события** — курированная фикстура (написана руками): push в `.env`, Write вне
плана, разрушительный `Remove-Item`, мутация мимо двери владельца. Реальные опасные действия НЕ
выполняются — только описываются как вход.
| Машина | Безобидные | Рискованные | Реальность данных |
|---|---|---|---|
| М5 пол | реальные из журнала/эпизодов | синтетика-фикстура | в основном реальные |
| М6 снимок/escape | реальные из журнала/эпизодов | синтетика-фикстура | в основном реальные |
| М2 стена | синтетика (план+шаги) | синтетика | полностью синтетика — нет живой истории |
| М4 судья | синтетика (mode/verdict) | синтетика | синтетика + судейство не подключено (A1) |
| М3 роутер | реальные логи эпизодов (советовал ↔ взяли) | — (не блокирует) | реальные, без новых LLM-вызовов |
| М1 журнал | — | — | целостность через `verifyChain` |
**Честно:** для М5/М6/М3 обкатка на реальных данных; для М2/М4 — на синтетике, потому что эти
стены ещё не работали живьём (реальной истории нет). Синтетика проверяет логику решений
(in-plan→allow, out-of-plan→block, observe-only→allow); реальное «ловит ли на живом» для М2/М4
подтвердится на Этапе 4 (М2 — когда пойдут реальные планы; М4 — после A1).
## 5. Формат отчёта и расположение
Markdown-таблица + вердикт по каждой машине:
```
## М5 пол — GREEN
events: 142 | over-block: 0 | real-catch: 7/7 | allow: 135 | miss: 0
## М2 стена — YELLOW
events: 60 (синтетика) | over-block: 2 | real-catch: 12/12 | allow: 46 | miss: 0
over-block #1: Edit app/Foo.php — reason "действие не в плане (ожидался шаг 3)"
over-block #2: Bash "git log" — reason "разговорный режим"
```
Каждая ложная блокировка показывает событие + причину машины (`reason`), чтобы владелец глазами
оценил, правда ли это ложное срабатывание. Вердикты: **GREEN** (0 over-block, 0 miss) / **YELLOW**
(есть over-block — перечислены) / **RED** (есть miss — рискованное пропущено). Внизу — сводка по
6 машинам и общий вывод «готово / не готово к Этапу 4».
**Расположение:** `docs/observer/notes/<дата>-shadow-replay.md` (рядом с brain-retro заметками,
обычный `.md`). Перезапуск перезаписывает свежим прогоном.
**Запуск:** `node tools/shadow-replay.mjs` (все 6) или `--machine M2` (одна). Печатает сводку +
пишет отчёт. Ничего не регистрирует, состояние не пишет, fail-quiet.
## 6. Тестирование (TDD)
Новый код → по TDD, наблюдаемый RED до зелёного (честный RED, не характеризация — код новый):
- `buildCorpus` — парсит журнал/эпизоды + грузит фикстуру;
- `classifyOutcome` — каждое ведро (over-block / real-catch / allow / miss) раскладывается верно;
- `runMachine` — по каждой машине известное событие → решение в нужном ведре (М2 in-plan→allow,
out-of-plan→block; М5 разрушительное→block; М6 `snapshotNeeded`→true; М4 gate-логика по
`{mode,verdict}`; М3 — расхождение «советовал↔взяли»);
- `renderReport` — стабильный markdown.
Фикстура рискованных = заодно вход для тестов. Регрессия tools-only остаётся GREEN + новые тесты.
## 7. Жёсткие ограничения
- Стены М2/М5/М4/М6 НЕ трогаем (только импорт `decide()`), не ослабляем.
- `settings.json` НЕ трогаем; инструмент не регистрируется как хук.
- Нормативку (Pravila/Tooling/PSR/MEMORY/CLAUDE.md) НЕ двигаем.
- commit-not-push (push только по слову «пуш»); чужую грязь дерева не коммитим — стейдж своих
файлов поимённо.
- fail-quiet `main()`, состояние не пишет; отчёт — единственный выход.
## 8. Согласованная цепочка скилов (утверждена владельцем 2026-06-09)
1. `brainstorming` — этот дизайн (выполнено).
2. `audit-context-building` — построчно подтвердить точные форматы входов `decide()` каждой машины
перед вшивкой.
3. `writing-plans` — план реализации `shadow-replay.mjs` (по функциям + фикстура).
4. `executing-plans` — исполнение инлайн по TDD (наблюдаемый RED → зелёный).
5. `verification-before-completion` — доказательства: новые тесты GREEN + регрессия tools-only GREEN.
6. `regression` (quick) — финальный sweep.
Сознательно НЕ включены: `sharp-edges` / `variant-analysis` (нет правки несущего кода стен —
read-only читатель); `requesting-code-review` (по правилу владельца).
## 9. Допущения и открытые мелочи
- Точные форматы записей журнала М1 и эпизодов под `buildCorpus` уточняются в шаге
`audit-context-building` (шаг 2 цепочки).
- Размер курированной фикстуры рискованных — минимально-достаточный (по 2–4 рискованных на каждую
блокирующую машину), уточняется в `writing-plans`.
- Отчёт коммитим как docs-only артефакт-доказательство первого прогона (без push).