# Видимость вердикта судьи и наставника для контроллера (Фикс 1)

## Цель

Контроллер должен видеть исход проверки наставника и судьи на КАЖДОЙ записи плана или спеки —
включая успех и пропуск, а не только блокировку. Сейчас исход виден только когда хук блокирует
(exit 2); при разрешении (GO/печать или пропуск) хук завершается тихо, и контроллер не знает,
опечатан ли план. Цель — детерминированная громкая строка исхода на каждую судимую запись, без
обращения к внешним логам.

## Контракт строки исхода {#D1}

На запись плана или спеки наставник и судья возвращают одну из детерминированных строк:

- `✅ GO — опечатано` — вердикт GO, печать поставлена;
- `🚫 NO-GO: <дословный текст возражения>` — заворот;
- `⚠ не дозвонился (<причина>)` — degraded (нет ключа / таймаут транспорта);
- `⏭ пропуск (<причина>)` — судья выключен / нет одобрения наставника / запись не судится.

Строка обязана появляться и на разрешении (не только на блоке). Текст возражения берётся
дословно (verbatim), без пересказа.

## Канал доставки на разрешении {#D2}

Текущая `exitDecision` доставляет текст только при block (stderr + exit 2). На allow пишет `{}`
в stdout без сообщения. Нужен неблокирующий канал доставки текста контроллеру на allow. В
Claude Code PostToolUse это поле дополнительного контекста хука (точное имя поля сверяется с
актуальной документацией Claude Code на этапе реализации, не угадывается). Контракт: allow +
сообщение → exit 0, текст доставлен контроллеру; allow без сообщения → текущее поведение (`{}`).

## Проброс через оркестратор {#D3}

Оркестратор `enforce-mentor-then-judge` запускает наставника и судью как дочерние процессы и
читает у дочки только stderr и только при коде 2. Строка исхода дочки на разрешении (exit 0)
теряется. Контракт: оркестратор обязан пробросить наружу неблокирующую строку исхода дочернего
хука и при разрешении, а не только текст блокировки.

## Крайние случаи {#D4}

- Запись НЕ является планом/спекой → строки исхода нет (хук не вмешивается).
- Оба хука дали allow с сообщением → доставляются оба исхода (наставник, затем судья) — порядок
  сохраняется, исходы не перетирают друг друга.
- Сбой построения строки исхода → молчаливый best-effort (никогда не превращать диагностику в
  блок): отсутствие строки не должно ронять разрешение.
- degraded и пропуск различимы по тексту (не сливаются в один невнятный исход).

## Конвенция заголовков и тестов {#D5}

Чистые строители строки исхода — отдельные экспортируемые функции (детерминированы, тестируются
без I/O), по образцу существующих `buildObjectionFeedback` / `buildDegradedFeedback`. Новый канал
доставки — отдельная экспортируемая функция рядом с `exitDecision`, не ломающая её сигнатуру.
Каждое поведение покрыто vitest-тестом (RED → GREEN).

## Критерий приёмки {#D6}

- На записи плана/спеки с вердиктом GO контроллер получает строку `✅ GO — опечатано` (тест на
  строитель + тест на канал доставки на allow).
- NO-GO / degraded / пропуск дают свои различимые строки.
- Оркестратор пробрасывает строку исхода на разрешении (тест на проброс).
- Полная регрессия tools зелёная (`npx vitest run --root app --config vitest.config.tools.mjs`).

```verified-context-json
[
  {"id":"D2-exitDecision","kind":"EXTRACTED","ref":"tools/enforce-hook-helpers.mjs","anchor":"export function exitDecision("},
  {"id":"D3-orchestrator","kind":"EXTRACTED","ref":"tools/enforce-mentor-then-judge.mjs","anchor":"export async function runMentorThenJudge("}
]
```
