# Двухуровневые переговоры + арбитраж владельца (роутер-наставник) — дизайн

**Дата:** 2026-06-12
**Кодовая фраза:** «роутер-наставник».
**Статус:** ДИЗАЙН утверждён владельцем (секции 1–5 одобрены в брейнсторме 12.06). Реализация — после плана (`writing-plans`).
**Подход:** №1 — «вкрутить готовые кирпичи» (максимум переиспользования живых модулей, минимум нового кода, TDD).
**Первоисточник замысла:** заметки `2026-06-01-router-discipline-plan-anchor-design-notes.md` (D16/D23/D24/D30/D34/В5, R-COMM) + роадмап `2026-06-12-router-mentor-roadmap.md`.
**Ветка работ:** `main` (по требованию владельца — «только работаем на мэйне»).

---

## 1. Зачем (проблема)

Замысел владельца — двухуровневые переговоры с арбитражем:

- **Уровень 1 (контроллер ↔ наставник):** готовлю план → наставник даёт замечания → **обсуждаем** → договорились → наставник пропускает к судье. Не договорились за 3 круга → эскалация владельцу.
- **Уровень 2 (судья):** судья даёт замечание → правлю → снова к наставнику (обсуждение) → судье. До 3 раз. После 3 отказов судьи → приношу владельцу **замечание судьи + свою позицию** → владелец **арбитр** (моя позиция / судьи / своё).
- **Эскалация = арбитраж:** владельцу показывают **обе стороны**.

Кирпичи уже есть (судья пишет objections в `judge-verdicts.jsonl`; наставник — вердикт+reasoning в `mentor-journal`; `round-control.mjs` умеет `escalate-owner` после 3 кругов; 12.06 добавлены `bumpJudgeNoGo` + «ЭСКАЛАЦИЯ» после 3 + судья чтит escape). **Не хватает четырёх вещей:**

1. Контроллер **видит** замечания судьи/наставника (сейчас мешает runtime read-deny) → правит по ним.
2. **Интерактивное обсуждение** контроллер↔наставник (наставник сейчас «один выстрел»).
3. Эскалация как **арбитраж**: показать владельцу замечание + позицию контроллера + выбор.
4. **Счётчики на обоих уровнях** (наставник + судья); сейчас живьём считает только судья.

---

## 2. Три неизменных принципа (на всё)

- **П1. Замечание доходит до контроллера в тексте блок-сообщения хука.** Новый канал чтения к журналам (`~/.claude/runtime`) **не открываем** — read-deny не ослабляется. Замечание «едет» на самом блоке (как контроллер уже видит сообщения хуков вроде `supreme-gate`).
- **П2. Позиция контроллера в споре живёт в разделе «переговоры» план-файла.** Видна владельцу (совпадает с D7 «всё видно в плане»), новый канал записи в runtime не нужен.
- **П3. Решение владельца на арбитраже пишется в защищённый журнал одобрений**, ворота читают его (шаблон git-подтверждений `judge-seal-channel`/`askuser-decisions`). **Со слов Claude «хозяин разрешил» не принимается.**
- **R-COMM (на весь owner-facing текст):** только по-русски, простыми словами, но информативно; не вводить владельца в заблуждение, не обходить его.

---

## 3. Архитектура — две петли, два счётчика

| Уровень | Кто спорит | О чём | Счётчик | Тупик → |
|---|---|---|---|---|
| **L1** | контроллер ↔ **наставник** | хорош ли план (метод, шаги, специалисты) | круги L1, потолок 3 | арбитраж: замечание наставника vs позиция контроллера |
| **L2** | контроллер ↔ **судья** | можно ли план пропустить (сторож ворот) | отказы судьи (NO-GO), потолок 3 | арбитраж: замечание судьи vs позиция контроллера |

**Связь петель (вложенность):** наставник согласовал → план идёт судье. Судья отказал → правлю → **снова к наставнику** (свежий круг L1, счётчик L1 сбрасывается) → снова судье (L2 +1). Внешний пинг-понг ограничен потолком судьи (≤3), внутри каждого внешнего шага наставник ограничен ≤3. Худший случай ≤ 3×3 = 9 кругов наставника, затем гарантированная эскалация.

**Два независимых счётчика** (выбор владельца): у каждого свой потолок 3, свой арбитраж (с соответствующей стороной). L1-счётчик **сбрасывается**, когда отказ судьи возвращает к наставнику (новый свежий круг).

---

## 4. Поток L1 (контроллер ↔ наставник)

1. Контроллер пишет/правит план через `writing-plans` (семя — ворота всегда пропускают).
2. Запись плана дёргает хук наставника (`enforce-mentor-on-plan-write`). Наставник выносит вердикт + замечания.
3. **GO** → круг сошёлся, счётчик L1 сброшен, план уходит судье (L2).
4. **NO-GO** → текст замечания приходит контроллеру в блок-сообщении (П1). На одном круге контроллер делает **одно из двух** (гибрид):
   - правит план под замечание, **или**
   - пишет «не согласен, потому что X» в раздел «переговоры» плана (П2).
   Пере-запись плана → наставник читает **весь** план (включая возражение) и пересматривает. Это +1 круг L1.
5. Круги нумеруются в `mentor-journal` (для оценщика и аудита; владельца кругами не заваливаем — В5).
6. **3 круга без согласия** → счётчик упёрся → **арбитраж владельцу** (раздел 6).

Счётчик L1 — это managed-счётчик `round-control` (`escalate-owner` при потолке 3), сейчас спящий: подключаем к живому циклу наставника + добавляем счётчик NO-GO наставника зеркалом живого `bumpJudgeNoGo`.

---

## 5. Поток L2 (судья) + вложенность

Наставник согласовал → план уходит судье (`enforce-judge-gate`). Судья — независимый сторож ворот: перепроверяет даже одобренное наставником (D30).

1. **GO судьи** → план «запечатан», контроллер исполняет; на каждом действии судья сверяет действие↔план (вторая роль судьи — надзор на исполнении, без изменений).
2. **NO-GO судьи** → текст замечания судьи приходит контроллеру в блок-сообщении (П1). Контроллер правит план → пере-запись плана **возвращает к наставнику** (новый круг L1, счётчик L1 сбрасывается) → наставник согласовал → снова судье. Это **L2 +1** (живой `bumpJudgeNoGo`).
3. **3 отказа судьи** → счётчик L2 упёрся → **арбитраж владельцу** (замечание судьи vs позиция контроллера).

«Снова к наставнику» = контроллер просто правит план-файл, хук наставника срабатывает сам. Отдельного механизма «вернуть к наставнику» строить не надо.

**Активация:** механику L2 строим и юнит-тестируем сейчас, но **живой она станет вместе с судьёй в Фазе 8** переезда М7 (в основном репо судья пока inert). Это активационный шаг владельца (регистрация в `.claude/settings.json`), не часть кода этой задачи.

---

## 6. Арбитраж: карточка + канал решения владельца

Арбитраж одинаков по форме для обоих уровней; меняется только сторона (наставник или судья).

**Сборка карточки** (контроллер собирает, но цитаты дословны):

- **Дословно** замечание стороны — из блок-сообщения/журнала (не пересказ).
- **Дословно** позиция контроллера — из раздела «переговоры» плана.
- Нейтральная строка **«что меняет каждый выбор»** простым русским.
- Аффорданс **«объясни подробнее/проще»** — контроллер даёт развёрнутое нейтральное пояснение, потом владелец решает.

**Выбор владельца** (через `AskUserQuestion` — семя, всегда разрешено): держусь позиции контроллера / соглашаюсь с замечанием / своё решение.

**Запись решения** — в защищённый журнал одобрений (шаблон `judge-seal-channel`/`askuser-decisions`): страж-хук пишет выбор владельца в защищённый файл; ворота читают точное совпадение, **одноразовый расход + окно времени** (как git-подтверждения). Со слов Claude не принимается (П3).

**После решения:**

- «держусь позиции» → ворота пропускают версию контроллера (сторона переспорена владельцем);
- «соглашаюсь с замечанием» → контроллер переделывает под замечание, круг/счётчик сбрасывается;
- «своё» → владелец вписывает своё, оно становится обязательным.

---

## 7. Компоненты (всё в `tools/`, по TDD)

**Переиспользуем (правим осторожно — наставник живой):**

- `round-control.mjs` — подключить спящий managed-счётчик L1 к живому циклу наставника.
- `enforce-mentor-on-plan-write.mjs` — выдавать замечание наставника **в тексте блок-сообщения**; кормить счётчик L1; читать раздел «переговоры» плана.
- `mentor-journal*.mjs` — нумерованные круги переговоров + **счётчик NO-GO наставника** (зеркало живого `bumpJudgeNoGo`).
- `enforce-judge-gate.mjs` — выдавать замечание судьи в блок-сообщении (счётчик L2 `bumpJudgeNoGo` уже живой).

**Новое (маленькое, чистое):**

- `arbitration-card.mjs` — сборщик карточки: дословные цитаты обеих сторон + строка «что меняет выбор» + аффорданс «объясни подробнее». Чистая функция.
- **Канал решения владельца** — переиспользуем шаблон `judge-seal-channel`/`askuser-decisions`: запись выбора в защищённый файл, чтение воротами с одноразовым расходом + окном времени.
- **Соглашение «раздел переговоры» в план-файле** — формат, где живёт позиция контроллера; парсер в хуке наставника.

---

## 8. Обработка сбоев (fail-closed, R-COMM)

- Битый/нечитаемый счётчик → консервативно **эскалируем владельцу** (уже так в `round-control`: не-целое ≥0 → эскалация).
- Нет записи решения владельца в журнале → «не одобрено» (не пропускаем).
- LLM-слой лёг (D27): код однозначно сверил действие с одобренным планом → пропуск; нужно суждение, а LLM нет → **СТОП + эскалация**; **деньги кончились → громко кричим владельцу**; transient (сеть/таймаут) → тихий retry. (D27 в основном уже реализован — здесь только не нарушить.)
- Весь owner-facing текст — простым русским (R-COMM).

---

## 9. Что НЕ ломаем / границы

- Наставник живой (Этап 4, aitunnel) — трогаем бережно, регрессия на каждом шаге.
- Судья inert в main → L2 строим и юнит-тестируем, **живёт с Фазой 8**; активация (регистрация в settings.json) — шаг владельца.
- Read-deny **не ослабляем** (П1). Write в runtime контроллером **не открываем** (П2 — позиция в план-файле).
- Страж секретов (read-protection, D18) работает всегда, поверх всего.

---

## 10. Тестирование

TDD. Регрессия tools-only — **только** `npx vitest run --root app --config vitest.config.tools.mjs` (npx-кэш, vitest 4.1.8; запуск через `node app/node_modules/vitest/...` падает на out-of-root тестах — баг пина 4.1.5, см. роадмап).

Юнит-тесты на каждый модуль:

- `arbitration-card`: дословность цитат сохранена; три варианта выбора; нейтральная строка «что меняет выбор»; аффорданс «объясни подробнее».
- счётчики L1/L2: сброс/потолок 3; L1 сбрасывается при возврате от судьи; битый счётчик → эскалация.
- канал решения владельца: расход один раз; окно времени; **отказ принять «решение» авторства Claude** (только запись стража).
- хук наставника: замечание попадает в блок-сообщение; раздел «переговоры» прочитан и учтён.
- L2-механика проверяется при судье inert (без живого блока).

---

## 11. Открытые вопросы (на будущее, не блокируют реализацию)

- **OВ-1. Ручка видимости спора (D16, В5 закрыт в пользу «итог+эскалации»).** Кругов в журнале достаточно; при желании владельца — режим «показывать каждый круг» добавляется отдельной ручкой.
- **OВ-2. Авто-одобрение волн** (D17 смягчение «ослабим потом») — вне scope этой задачи.
- **OВ-3. Калибровка наставника/судьи не-программистом** (H1) — отдельная давняя тема, отложена.

---

## 12. Ссылки

- Замысел и решения: `docs/superpowers/specs/2026-06-01-router-discipline-plan-anchor-design-notes.md` (D16/D23/D24/D30/D34/D35/D36/В5/В6, R-COMM).
- Состояние эпика: `docs/superpowers/specs/2026-06-12-router-mentor-roadmap.md`.
- Живые модули: `tools/round-control.mjs`, `tools/enforce-mentor-on-plan-write.mjs`, `tools/mentor-journal*.mjs`, `tools/enforce-judge-gate.mjs`, `tools/judge-seal-channel.mjs`.
