ADR 0047 — Эксплуатируемость после dogfooding: закрытие Фазы 12
Контекст
Фаза 10 была первым реальным запуском Brabo, строящего сам Brabo. Она дала семнадцать находок, сохранённых в O que o primeiro dogfooding ensinou. Три из них касались эксплуатируемости — того, что отделяет эксперимент, проводимый вручную, от системы, с которой живут:
| # | что это было | чем закрыто |
|---|---|---|
| 1 | продукт умел только СОЗДАВАТЬ репозиторий; направить проект на существующий репозиторий требовало вручную вставлять строки в две таблицы | ADR 0044 |
| 10 | dev agent обрабатывал ОДНУ task и останавливался; фаза шла партиями, с перезапуском engine между ними | ADR 0045 |
| 13 | продвижение draft → ready было автоматическим при создании — PO сам решал, что попадает в очередь devs | ADR 0046 |
У всех трёх уже был собственный тест, когда писался этот ADR. Не хватало другого: доказательства, что они умерли вместе. Система может иметь все три исправления и всё же не быть эксплуатируемой, если они работают только по отдельности, — именно это Фаза 10 выявила о функциях, которые проходили тесты своих фаз и не переживали первого контакта с реальным использованием.
Решение
Валидация — это исполняемый скрипт, а не прозаический сценарий.
apps/api/scripts/validacao-fase-12.ts, по образцу demo-noop-execution.ts:
поднимает контекст Nest, вызывает реальные use cases и завершается с ненулевым
кодом, когда критерий не выполняется. Альтернатива — чек-лист, по которому кто-то
идёт, кликая, — дала бы валидацию, которая молча стареет, так же
как таблица наблюдений Фазы 10 осталась пустой.
Свидетельства извлекаются из базы, а не переписываются. Скрипт завершается выводом
Markdown-таблицы session_events.id (ULID) самого прогона, готовой к
вставке в документ. И отказывается завершиться успехом, если какой-то этап, который он
заявил как пройденный, не оставил следа в event log, — без этой проверки
неверный запрос дал бы короткую таблицу, и валидация всё равно прошла бы,
что является классическим режимом сбоя генерируемого отчёта.
Валидация работает локально и без LLM, и это заявлено в первом абзаце документа. Не в сноске. Две конкретные причины, обе проверенные:
- fork Фазы 10 так и не был назван —
dogfooding-mission.md:135по-прежнему являетсяTODO(humano), так что нет цели для повторного принятия. Путь принятия одинаков у обоих providers; различается сеть, покрытая smokeadopt-repository.smoke.spec.ts, закрытым реальными учётными данными; - суждение gates с локальной моделью не детерминировано, и
ADR 0020 уже говорил это. Вердикт входит
через
RecordGateVerdictUseCase— РЕАЛЬНУЮ воронку, где рождаетсяtask.gate_resolved. 12b нужно доказать цепочку вердикт → outbox → wake → claim, а не то, умеет ли 7B читать suite.
Валидация, которая притворялась бы, что покрывает больше, чем покрывает, была бы хуже её отсутствия: объявила бы закрытым то, что не было задействовано.
NoopDevAgentServer вошёл в машину состояний 12b — и это была находка
самой подготовки валидации. Фаза 12b изменила только реальный
DevAgentServer. Noop продолжал фиксировать status: :working, не подписываясь на
Engine.Dev.Wake, обрабатывая одну task и останавливаясь: находка #10 оставалась живой
внутри единственного средства, способного проверить фазу без траты токенов. Сквозной
прогон с ним провалил бы критерий "ноль перезапусков" из-за дефекта
инструмента, а не продукта.
Исправлением было не копирование машины в Noop, а её перенос в
Engine.Dev.AgentIo — модуль, который уже существовал именно потому, что "Noop, который
переписывал бы эти части, проверял бы копию, а не инфраструктуру". Этот
аргумент действовал для worktree и идентичности коммита с Фазы 4a; он стал
действовать и для перепланирования. Между двумя агентами различается run_task, и
только он, поэтому он входит как функция, а не как behaviour.
Итоги Фазы 10 написаны сейчас, с объявленными пробелами.
CLAUDE.md:77 ссылался на docs/explanation/primeiro-dogfooding.md с
конца той фазы, а файла так и не было. Он написан из того, что
восстановимо, — семнадцать находок с файлом и строкой, повествование о партиях,
ручной seed, — и всё, что зависело бы от живого подсчёта, вошло как
não medido, никогда как оценка. Таково правило самих итогов
(colheita-esqueleto.md:22-24): ни одно число не входит без запроса, который его
производит.
В таблице контраста "1 перезапуск на сданную task" фигурирует как свойство, выведенное из кода того времени, а не как наблюдаемое среднее, — различие записано в самой ячейке.
Последствия
Фаза 12 закрывается с тремя находками P1 по эксплуатируемости, решёнными и доказанными в одном прогоне. Остальные четырнадцать остаются перечисленными и открытыми в итогах; ни одна не исправлена мимоходом, по тому же принципу, который установила миссия Фазы 10: исправление находки вне фазы, которая её адресует, скрывает свидетельство того, почему она существовала.
Три вещи, которые выявила эта фаза и которые входят как backlog, а не исправление:
- Инструментирование главной метрики предшествует эксперименту. Находка #17
(P1, открыта) говорит, что
proposed_action.approved/.deniedидут только в outbox, никогда вsession_events. Количественной половины итогов Фазы 10 в значительной мере нет из-за этого. Следующий dogfooding без предварительного решения #17 снова потеряет те же числа. - Нет инструмента редактирования истории. Цикл отказа 12c закрывается
пересозданием (
create_story), а отклонённая история остаётся вdraftс записанной причиной. Это проверяемо, но оставляет остаток в backlog. Зафиксировано в ADR 0046. - В покрытии docmap был пробел ровно там, где эта фаза больше всего меняла.
Ни одно правило не наблюдало
apps/engine/lib/engine/dev/**иapps/engine/lib/engine/agents/**: машина состояний dev agents и разговорные агенты могли меняться без требования какой-либо документации. Этот ADR закрывает пробел заодно, потому что оставить его открытым значило бы, что сам механизм документирования лжёт о фазе, которая его задействовала.
Чего Фаза 12 не изменила, и стоит сказать явно, потому что это ось
продукта: конвейер одобрений остался ровно таким, каким был, и merge в защищённую
ветку по-прежнему является ручным решением пользователя. Шаг 6 валидации
предлагает merge с автономией auto_approve и разрешающим permissions.json и
требует pending в результате. Перепланирование агента — это не предоставление автономии, а
лишь то, что он не умирает между задачами.