# Дизайн: видимость вердиктов ВСЕХ участников стены (роутер + наставник + судья)

**Дата:** 2026-06-17
**Статус:** дизайн одобрен владельцем, ждёт ревью спеки → план.
**Кодовая фраза проекта:** «роутер-наставник».

## Цель

Контроллер должен **видеть вердикт каждого участника стены отдельно** (роутер, наставник,
судья) по каждому кругу церемонии — а не один безымянный итог. Сейчас контроллер слеп:
прилетает строка `✅ GO — опечатано` без подписи, и непонятно, кто её вынес и почему.

## Проблема (корень слепоты — найден по живым файлам)

Surface-инфраструктура (Фикс 1) **уже умеет показывать много строк**: `enforce-verdict-surface`
(UserPromptSubmit) льёт ВСЕ непоказанные строки из `verdict-surface-store`. Но участники
кладут в неё почти ничего:

1. **Нет ярлыка «кто».** `enforce-mentor-on-plan-write.mjs:235` и `enforce-judge-gate.mjs:499`
   пишут по одной строке через `buildVerdictOutcomeLine` (`go/nogo/degraded/skip`) — без подписи
   участника. Строки наставника и судьи неотличимы.
2. **Роутера нет вовсе.** `classify` из `router-classifier` вызывается живьём в
   `enforce-mentor-on-plan-write.mjs:20`, но его вердикт (выбор навыка / воздержание / уверенность)
   в surface не попадает — умирает в возвращаемом значении.
3. **Нет деталей и круга.** GO наставника не несёт reasoning/recommendation; GO судьи — какие
   линзы/возражения; нет номера круга.

## Подход (выбран A из трёх)

Отдельные чистые строители строк на участника + аддитивный `appendVerdictOutcome` в трёх точках,
где вердикт уже посчитан. Surface-хранилище и UserPromptSubmit-хук **не трогаются** (многострочность
уже работает). Отвергнуты: B (структурные записи — ломает строковый контракт и схему хранилища,
крупная правка discipline-source) и C (отдельный журнал — дублирует surface, YAGNI).

## Компоненты

Новый чистый модуль `tools/verdict-surface-detail.mjs` — три **тотальные** функции (мусор/null → `''`,
без побочных эффектов, не кидают):

- `buildRouterSurface(result)` — вход результат `runRouter`/`classify` (`{ok, abstain, trace, reason}`).
  Выход (полная деталь): `🧭 РОУТЕР: выбран <chosen> (увер. <confidence>) · кандидаты [<...>] · почему: <why_chosen> · близнец: <twins_considered>` | `🧭 РОУТЕР: ⚠ воздержание (увер. <c>) — <reason>` | `🧭 РОУТЕР: 🚫 <reason>` (невалид/выдумка/сбой).
- `buildMentorSurface(verdict, {round, wired, reason})` — вход результат `runMentorVerdict`.
  Выход: `🧑‍🏫 НАСТАВНИК [круг N]: <GO|NO-GO> · reasoning: <...> · recommendation: <...>` | `🧑‍🏫 НАСТАВНИК [круг N]: ⚠ не дозвонился (<reason>)` | `… несодержательный (<reason>)`.
- `buildJudgeSurface(result, {gate, round})` — вход результат `runJudge`.
  Выход: `⚖️ СУДЬЯ <gate> [круг N]: <GO|NO-GO> · возражения: <kind:ref; ...>` | `… невалидные слоты` | `… нет под-прогона`.

## Поток данных и точки инъекции

- `enforce-mentor-on-plan-write.mjs`:
  - после `classify(...)` → `appendVerdictOutcome({sessionId, line: buildRouterSurface(...)})`;
  - после вердикта наставника → `appendVerdictOutcome({..., line: buildMentorSurface(...)})`
    — **заменяет** нынешний безымянный `buildVerdictOutcomeLine` (строки 229–236).
- `enforce-judge-gate.mjs`:
  - после `runJudge(...)` → `appendVerdictOutcome({..., line: buildJudgeSurface(...)})`
    — **заменяет** нынешний безымянный (строки 497–500).
- Номер круга — из журнала переговоров / round-memory (длина = номер круга).
- Старый `buildVerdictOutcomeLine` **перестаёт вызываться** в этих точках (иначе дубль «GO» без
  подписи рядом с подписанной). Сам модуль `verdict-outcome-line.mjs` остаётся (его тесты — зелёные).

## Обработка ошибок

Всё всплытие — **best-effort, fail-open** (как `enforce-verdict-surface`): строитель вернул `''`
или `appendVerdictOutcome` упал → хук не падает, **вердикт-механика и решения гейтов не меняются**.
Видимость — побочный канал, не гейт. Ни один новый код не влияет на decision/seal.

## Тестирование (TDD)

`tools/verdict-surface-detail.test.mjs` (vitest, tools-сюита) — по каждому строителю:
GO / NO-GO / degraded / null-мусор → `''`; роутер дополнительно: воздержание, выдумка/невалид;
судья: невалидные слоты, нет под-прогона; наставник: несодержательный.
Регрессия: `npx vitest run --root app --config vitest.config.tools.mjs`.

## Ограничения исполнения

- `enforce-mentor-on-plan-write.mjs` и `enforce-judge-gate.mjs` — **discipline-source (F-K)**:
  правка в **терминале** (node string-replace по точным якорям + бэкап + verify) и **сразу коммит**;
  Write под стеной их не возьмёт (`normative §6`, write-escape не снимает).
- Новый `verdict-surface-detail.mjs` + его тест — обычные новые файлы (не discipline-source).
- claude-brain — **синхрон отдельным хвостом** (владелец выбрал «здесь, в живой копии, сейчас»).

## Вне охвата (YAGNI)

- Изменение схемы surface-хранилища (B) — нет.
- Отдельный журнал участников (C) — нет.
- Изменение самих решений роутера/наставника/судьи — нет, только видимость.
- Синхрон claude-brain в этом же заходе — отдельный хвост.

## Уборка

Демо-файл `tools/wall-verdicts-demo.mjs` (создан при выяснении задачи) — удалить, он больше не нужен.
