Перейти к основному содержимому

Backlog после триажа

Результат FASE 13c. Собирает открытые находки реального исполнения (achados-execucao-real.md) и старые пункты, которые были разбросаны по ADR и CLAUDE.md.

Это предложение. Классификация и группировка ниже — аргумент о том, что дороже откладывать; решение о приоритете — за пользователем.

[!warning] История — НЕ читайте это как живую очередь Это триаж FASE 13c, и он не успевал за репозиторием. Измерено 2026-09-12: как минимум двенадцать пунктов, которые всё ещё значатся здесь открытыми, уже не воспроизводятся — изоляция исполнителя (terminal_executor.ex отвергает отсутствующий контейнер), схема по агрегатам (ADR 0121), golden-set семантического gate (ADR 0123), backup git_local_repos, compose установки, «FASE 29 — только планирование», Dependabot, два пробела runbook и строгость оповещений, среди прочего.

Дефект — в ритме, а не в содержании: фазы закрывались, и никто не возвращался сюда, чтобы их отметить. Что выживает — и причина хранить этот файл, — это рассуждение триажа: почему каждый пункт дороже откладывать, чем сделать. Считайте любое состояние ниже непроверенным, пока оно не будет измерено заново.

Как классифицировано​

P1 — не даёт продукту делать то, что он обещает. P2 — продукт делает, но лжёт или сбивает с толку смотрящего. P3 — качество, никто не застрял.

Стоимость: P (одна сессия), M (небольшая фаза), G (собственная фаза, с ADR).

Риск ожидания — колонка, решающая ничью: дефект, портящий данные или стирающий доказательства, стоит позже дороже, чем сегодня; косметический стоит столько же.

Доска​

предлагаемая фазапунктыприор.стоимостьриск ожидания
A — Разблокировать задачуADR 0052, O/BСДЕЛАНО—RN-072 и RN-073
B — Engine на удалённом провайдереNСДЕЛАНО—RN-076; ADR 0056 принят
F — Граница и потолок исполнителяS, UСДЕЛАНО—RN-074 и RN-075; ADR 0055 принят
C — UI не может лгать об агентахC, I, H, L, GСДЕЛАНО—все пять пунктов
D — Мастер говорит правду и имеет выходD, E, FСДЕЛАНО—RN-078; E и F уже были сделаны
G — Исход сбоя говорит правдуP, Q, TСДЕЛАНО—RN-077
H — Состояние сессии не лжётVСДЕЛАНО—RN-064 расширена
E — Качество того, что производят агентыK, R, JСДЕЛАНО—RN-079, RN-080 и RN-081
— отдельноpromotion-check без specСДЕЛАНО—10 случаев, проверены мутацией
I — Dev agent начинает с нуляX, YСДЕЛАНО—RN-085; Y закрыт в 13b, X в FASE 14d

Покрытие: 19 из 19 находок реального исполнения, плюс восемь новых (W, X, Y, Z, AA, AB, AC, AD, AE) из проверки FASE 13b — см. validacao-real.md.

Из них закрылись пять: W, Y, AA, AB и AC в самой 13b, и X в FASE 14d (RN-085) — потолок итераций стал зависеть от ТИПА агента, что и было формой, предложенной триажем, и недостававшим продуктовым решением.

Три остаются открытыми, и ни одна не является багом для исправления:

находкачто этопочему это не исправление
Z и ADallowlist глаголов не сходится — глагол, форма и вызов — разные пространстваallowlist выполняет обещанное, и отказ bash доказывает, что граница держит. Это ПРОДУКТОВОЕ решение о политике по профилю агента, с ADR
AEагент QA пытается исправить код, который оцениваетничего не утекло: остановлено двумя независимыми барьерами. Данные — расхождение между тем, что просит промпт, и тем, что делает модель

Практический вывод 13b уже реализован и ничего не ждёт: путь — не ослаблять политику, а заставить агента ЖДАТЬ решения вместо того, чтобы умирать (ADR 0057, расширяя 0052).

Буквы фаз (A–H) и находок (B–V) пересекаются по наследству двух списков; где есть неоднозначность, текст говорит «находка».

Два пункта ушли из списка открытых со времени первого триажа: A (RN-067) и M (RN-066), оба закрыты и подтверждены в production. А ADR 0052, бывший половиной Фазы A, был реализован и доказан тестом — доставка wake была исправлена и покрыта от начала до конца после этого.

Остальное (старые пункты) — в Прежнем backlog, без назначенного приоритета: это продуктовые решения, а не дефекты.


Фаза A — Разблокировать задачу (P1) — СДЕЛАНО​

Ни один dev agent ни разу не завершил задачу. Это был факт, упорядочивший всё: реестр gates FASE 15a показывает qa-verificada, secops-segura и два gate инфраструктуры как «никогда не проходил» — не из-за отсутствия исполнения, а потому, что PR, который мог бы оценить какой-либо gate, никогда не существовал.

пунктчто былокак закрылось
ADR 0052ожидающее одобрение возвращало status pending как результат инструмента и сжигало итерацию; агент умирал на потолке, ничего не написавцикл ПРИОСТАНАВЛИВАЕТСЯ и возобновляется (RN-073); доставка исхода была исправлена позже — событие рождалось в агрегате, который drain engine не читал, — и путь теперь покрыт от начала до конца
O / Bсессия и dev agents рождались на локальной llama3.2:1b, которую ADR 0020 запрещает на семантическом шагекогда каскад приземляется на значение по умолчанию workspace, унаследованная модель — модель Criativo (RN-072)

Наследование занимает пустоту и никогда не перекрывает: binding сессии, агента или проекта — явные выборы и продолжают побеждать. А унаследованная модель проходит те же фильтры каскада — исчезнувшая из каталога или без tool calling не наследуется.

Закрытия этой фазы не хватило, и это самая полезная её находка: агент начал двигаться и умер от другого (Фаза F, 413). «Ни один dev agent ни разу не завершил задачу» осталось правдой — изменилась причина.

Риск ожидания: высокий. Пока это не закрыто, удалённый PR, gates QA/SecOps и измерение 13b стоят в очереди за ним — и каждый раунд dogfooding тратит деньги лишь на то, чтобы повторно подтвердить ту же блокировку.

Фаза B — Engine на удалённом провайдере (P1) — СДЕЛАНО​

N — get_local_repo_path/1 возвращал unsupported_provider для всего, что не local, и четыре потребителя останавливались вместе с ним. Api говорит с GitHub по HTTP; engine работает в файловой системе и знал только локальный bare-репозиторий, поэтому удалённый проект выполнял разговорную половину и останавливался на половине построения.

Закрыто ADR 0056 и RN-076: engine запрашивает рабочий удалённый репозиторий у api, у которой мастер-ключ, а учётные данные входят по вызову — origin, записанный в .git/config, чист.

Открытие, уменьшившее проблему: двум из четырёх потребителей учётные данные никогда не были нужны. Diff и ProjectContext используют только ИМЯ ветки — они останавливались из-за побочного ущерба от функции, возвращавшей больше, чем они просили.

Чего Фаза B НЕ закрыла​

  • Изоляцию, снова. Токен ушёл с диска, но агент по-прежнему в том же контейнере, что и монорепозиторий Brabo. Это та же нерешённость, которую уже объявлял ADR 0055, и теперь на неё указывают две фазы.
  • Доказательство против настоящего GitHub. fetch и push проходят через GitAuth, а тесты покрывают локальный путь от начала до конца (push, доходящий до bare на другой стороне) и именованные ошибки. Чего ни один тест дать не может — настоящего удалённого репозитория с настоящим токеном; это исполнение 13b, которое теперь может произойти.

Фаза F — Граница и потолок исполнителя (P1) — СДЕЛАНО​

Исполнение hello-limpo умерло здесь, и у двух пунктов был один корень: у терминального исполнителя не было ограничения — ни на то, куда дотягивается команда, ни на то, сколько она возвращает.

пунктчто былокак закрылось
Sнакопленный контекст превышал байтовый лимит провайдера, и вызов возвращал 413. Каждый вывод терминала оставался в истории и путешествовал в каждом следующем ходебайтовый потолок в исполнителе, с меткой, адресованной модели (RN-074)
U/workspace внутри исполнителя — монорепозиторий самого Brabo, а /data/project-workspaces/*/ даёт доступ к worktree других проектовобласть путей в решении (RN-075, ADR 0055 принят)

Область закрыла обе стороны разом: ужесточила (разрешённый глагол, указывающий наружу, перестал автоодобряться) и ослабила (cd внутрь перестал проваливать составную команду, что было самым дорогим дефектом лестницы).

Чего Фаза F НЕ закрыла​

Записано, а не выдано за готовое:

  • Изоляцию. ADR 0055 — это политика, и он сам так о себе говорит. Пока монорепозиторий Brabo смонтирован в контейнер, выполняющий команды, граница зависит от того, верно ли правило. Нормализация лексическая: .. проваливается, symlink изнутри, указывающий наружу, не обнаруживается.
  • Пункт 6 ADR — «Всегда разрешать» с обобщением. По-прежнему записывает литеральную команду, которая больше никогда не совпадает. Не вошло, потому что обобщение РАСШИРЯЕТ то, что разрешает клик (одобрение cat foo стало бы открывать cat для чего угодно), а это продуктовое решение, заслуживающее собственного рассмотрения, а не попутного.
  • Пункт 7 ADR — событие должно записывать, какая область разрешила. Причина решения уже это говорит, но не сохраняется в proposed_action.created.

Фаза C — UI не может лгать об агентах (P2) — СДЕЛАНО​

Пять пунктов с одним корнем: экран рассказывал историю, отличную от истории event log.

пунктчто делает экрансостояние
Cживой пузырь подписан моделью; агент появляется только с приходом сохранённого события — и сообщение дублируется до перезагрузкиСДЕЛАНО — дельта стала нести агента, а refetch откладывается, пока ход стримится
Gприглашение Criativo не появляется в созданном проекте, потому что в ленте уже есть карточки bootstrapСДЕЛАНО — условием стало «разговор начался», а не «лента пуста»
Lкнопка в подвале продолжает показывать «Estou pronto para produzir» после handoff, исходящего от CriativoСДЕЛАНО — исчезает, когда существует handoff, исходящий от Criativo
Hсобытия bootstrap все отображаются как «atividade em system»СДЕЛАНО — пять типов получили собственное семейство, с переведённым шагом
Iсмена модели сессии задним числом переписывает подпись старых действийСДЕЛАНО — карточка перестала утверждать модель, которую не может знать

C был самым серьёзным из пяти и был указан вами во время исполнения: говорит агент, модель — деталь исполнения.

I заслуживал решения, а не только исправления. Карточка получала ТЕКУЩУЮ модель сессии, а настоящего источника модели по действию не существует — proposed_actions её не хранит, а token_usage не связан с действием. Между выдумыванием источника и отказом утверждать второе — единственно честное: тот, кто предложил, уже на карточке, жирным, и это агент — то, что не меняется. Подпись ушла вместе с prop, у которого не осталось других потребителей.

H стал формулировкой: пять типов bootstrap.step_* получили собственное семейство с шагом, переведённым на португальский, и только step_failed помечен как плохой — degraded и skipped — предусмотренные исходы, и окрашивание их в красный приучило бы игнорировать красный. create_rc_branch остаётся переведённым, хотя и выведен из употребления (ADR 0030), потому что у проектов, прошедших bootstrap раньше, событие есть в логе.

Фаза D — Мастер говорит правду и имеет выход (P2) — СДЕЛАНО​

пунктчто былокак закрылось
DProtect branches падает в приватном репозитории на бесплатном плане, и мастер предупреждает об этом заранее. Единственное предлагаемое потом действие было «Tentar novamente», которое падает всегдаRN-078
Eпревью репозитория лгало: зашитый repo: brabo/{slug}, а настоящий owner брался из PATуже было сделано (коммит 4dd7a073) — подпись стала показывать только slug, который действительно известен
Fшаг «Política de branches» перечислял rc среди постоянныхуже было сделано (коммит 4dd7a073)

E и F уже были исправлены, когда я взялся за них, — в коммите, закрывшем четыре находки разом. Обнаружение этого стоило одного чтения; backlog не знал, потому что был написан раньше.

Пункт D оказался больше, чем предполагало описание. Это было не просто «экран без выхода»: provision_failed заставляет дашборд перенаправлять клик по проекту обратно на страницу provisioning, поэтому проект становился недостижимым навсегда, застряв на шаге, который не может завершиться успехом. Выходу понадобились маршрут, сценарий и событие — а не только кнопка.

Признать можно только шаг защиты, и это важное решение: это последний шаг и единственный, сбой которого всё ещё оставляет пригодный репозиторий. Предложение «продолжить» при более раннем сбое было бы второй ложью поверх первой.

Фаза G — Исход сбоя говорит правду (P2) — СДЕЛАНО​

Одно и то же правило CLAUDE.md, нарушенное трижды: P (dev.blocked с origin: null), Q (agent.error с "undetermined") и T (повторение: dev.blocked с "undetermined" на сбое, поле diagnosis которого называло причину в ТОЙ ЖЕ строке).

Закрыто RN-077, и диагноз причины изменил форму исправления: классификатор уже существовал и уже угадал бы — FalhaDeTurno.origem/1 отображает статус ≥ 400 в codigo, что правильно классифицирует 413 из находки T. Дефектом никогда не было отсутствие правила; им было то, что у block_task "undetermined" было значением по умолчанию, а места вызова ничего не передавали.

Поэтому исправление структурное, а не ещё одно правило: значение по умолчанию убрано. Забыть источник теперь — ошибка компиляции, а не синтаксически корректное и семантически пустое событие.

indeterminada перестала существовать. Она означала классификатор не распознал эту форму — пробел в нашем коде, — а codigo — источник, который указывает правильное действие. indeterminada не указывала никакого, а это ровно и было жалобой находки.

Фаза H — Состояние сессии не лжёт (P2) — СДЕЛАНО​

V — сессия 1f94de49 числилась closed с 23:34:42, а исполнение продолжалось до 00:56.

Причиной была не машина состояний, а heartbeat. Сессия родилась в 23:34:12 и закрылась в 23:34:42 — ровно 30 с SESSION_HEARTBEAT_TIMEOUT_MS. RN-064 уже требовала проверять, есть ли ожидающая работа, перед закрытием, но «ожидающей работой» был только handoff offered — а действие pending было с 23:34:13, созданное через секунду после рождения сессии.

Действие, ожидающее решения, стало учитываться. Это тот же дефект handoff уровнем ниже: кто-то ждёт вас, и агент может быть приостановлен в ожидании исхода (RN-073).

Предыдущая версия правила письменно говорила, что включение работы агента «без теста, доказывающего взаимодействие, было бы гаданием». Исполнение дало доказательство, и именно это отличает это исправление от догадки.

Чего Фаза H НЕ закрыла​

  • Задача in_progress без ожидающего действия и без handoff. Сигнал потребовал бы, чтобы api читала dev_agent_states, принадлежащую engine, — решение о границе, а не попутное исправление.
  • closed продолжает принимать одобрение. С исправленным heartbeat сессия перестаёт закрываться с висящим действием, поэтому случай становится редким. Блокировка решения в уже закрытой сессии — изменение поведения с собственным последствием: осиротевшее действие уже завершённой сессии осталось бы без того, кто его решит.

Фаза E — Качество того, что производят агенты (P3)​

пунктчто происходит
Kдвукратный запуск Criativo в одном проекте оставил 10 правил, 5 сирот — без дедупликации и предупреждения
RPO породил две истории, покрывающие один endpoint
JPsicólogo запускается в только что открытой сессии с гипотезами предыдущей и пустым логом, пытается сослаться на несуществующие события и сдаётся

СДЕЛАНО, с объявленным срезом посередине.

J оказался механизмом, вопреки тому, что предполагал этот раздел. Пустой лог — проверяемое условие, а дефект был в подсчёте, решавшем, стоит ли запускать: он суммировал машинные шаги bootstrap и — что хуже — след, который сам Psicólogo оставляет в сессии, анализируя её, из-за чего пустая сессия казалась заполненной начиная с первого анализа и всё более заполненной с каждой повторной попыткой. Закрыто RN-079, с сессией из находки, воспроизведённой как тест.

K и R были одним промптом, и поэтому закрылись лишь настолько, насколько дотягивается код. ТОЧНЫЙ дубликат правила отвергается при выпуске (RN-080); история с идентичным заголовком отвергается, а история, не добавляющая покрытия, становится предупреждением (RN-081) — предупреждением, а не блокировкой, потому что второй срез того же правила может быть законным, а судит пользователь.

Что не было решено и записано во всех трёх RN, а не подразумевается: семантический дубликат. Точная пара из находки R — «Endpoint público de saudação determinística» и «Endpoint público GET /hello que responde saudação imediata» — по-прежнему проходит, потому что ничто механическое их не связывает. Есть тест, утверждающий это ограничение, чтобы оно было видимым решением, а не забытым пробелом.

Отдельно​

promotion-check без собственного spec — СДЕЛАНО. Это была единственная обязательная проверка семейства без теста (у pr-police, approval-ladder и gate они есть). Найдено при написании реестра gates (FASE 15a, PR #145).

scripts/ci/promotion-check.spec.ts покрывает две чистые функции, утверждая ПРАВИЛО, а не реализацию: какой штамп требует каждая цель (qa требует dev, main требует qa, dev не требует ничего), и что считается штампом этого конкретного коммита — тег другого коммита не действует, тег другого этапа не действует, а тег, не разрешивший sha, не становится штампом по умолчанию. Именно этот набор не даёт qa получить код, никогда не проходивший через dev.

Четыре родственных секрета compose production — СДЕЛАНО (RN-114). AUTH_JWT_SECRET, BRABO_SERVICE_TOKEN, CREDENTIALS_MASTER_KEY и SECRET_KEY_BASE имели значения по умолчанию для разработки в docker/docker-compose.prod.yml, который работает с NODE_ENV=production, — тот же шаблон, который ADR 0059 уже закрыл для GIT_OAUTH_STATE_SECRET по той же причине: значение публично, оно в этом репозитории.

Записанное ниже опасение — что каждый заслуживает отдельного решения — указывало не на четыре разных РЕШЕНИЯ, а лишь на три проверки в разных местах: passphraseAtual() (auth-key-material.ts), tokenDeServicoAtual() (service-token.ts) и конструктор EnvelopeEncryptionService, каждый с ТЕМ ЖЕ правилом, что и resolveOauthStateSecret() (отсутствие/пример/короткое значение роняет загрузку в production). Отказ CREDENTIALS_MASTER_KEY при ЗАГРУЗКЕ — не та проблема, которой опасались: он не трогает никакую ротацию, которая по-прежнему существует через CREDENTIALS_MASTER_KEY_PREVIOUS + rewrap-deks.ts; проверка лишь не даёт ключу-примеру дойти до production. У SECRET_KEY_BASE уже был правильный raise в runtime.exs — настоящим дефектом было то, что compose маскировал его публичным запасным значением, и исправление свелось к удалению этого запасного значения, без единого изменения кода на Elixir.

Внешнее ревью от 2026-08-28, пункт №13 — контейнеры dev работали от root — СДЕЛАНО. В docker/api/Dockerfile, docker/web/Dockerfile и docker/engine/Dockerfile не было директивы USER, поэтому всё, что они писали в bind mount (node_modules, apps/api/dist и то, что агент сгенерирует внутри проекта в режиме исполнения mounted), попадало на хост с владельцем root — README и руководство по первым шагам этой документации описывали ручной обход sudo chown -R $USER ... вместо исправления класса проблемы. Образы Dockerfile.prod уже были non-root; это касалось только dev.

Исправление сопоставляет пользователя контейнера с UID/GID ХОСТА, вместо того чтобы работать от root и чистить потом: build args DEV_UID/DEV_GID (читаемые из .env/окружения через docker-compose.yml, по умолчанию 1000/1000), намеренно названные DEV_*, потому что ${UID} в bash только для чтения и по умолчанию не экспортируется в окружение — чтение его прямо из файла compose всегда видело бы пустоту. Каждый Dockerfile создаёт группу/ пользователя с этим UID/GID, только когда он не конфликтует с уже имеющимся в базовом образе (node:24-alpine уже имеет node в 1000:1000), и переключается через USER <uid>:<gid> — числовой, поэтому неважно, какое имя пользователя в итоге владеет этим id. Все шаги, требующие root (apk add, pip install, загрузки gitleaks/hadolint/actionlint), остаются ДО переключения; в образе engine _build/deps/.mix/.hex — под /root, потому что туда пишут mix local.hex/mix local.rebar, смонтированные как именованные тома через docker-compose.yml, — получают mkdir+chown на целевой UID/GID до USER, поэтому совершенно новый именованный том наследует правильного владельца при первом монтировании (Docker наполняет новый том тем, что уже есть по этому пути в образе).

Доказано запуском настоящего стека (DEV_UID=$(id -u) DEV_GID=$(id -g) docker compose -f docker/docker-compose.yml up --build postgres api web engine): три контейнера поднялись с нулём ошибок EACCES/прав, а apps/api/dist на хосте оказался во владении пользователя хоста, а не root. Реальная деталь, а не блокировка: именованные тома, созданные окружением до этого исправления (*_node_modules, engine_build, engine_deps, engine_mix, engine_hex), всё ещё хранят содержимое, записанное старыми контейнерами root, — обновление требует однократного chown этих существующих данных томов (или снятия томов через docker compose down -v, чтобы следующий up их пересоздал), что задокументировано в README и в руководстве по первым шагам вместе с инструкциями по DEV_UID/DEV_GID.


Прежний backlog​

Пункты, существовавшие до этого раунда. Это не дефекты, а отложенные продуктовые решения — поэтому здесь без приоритета.

пунктгде решено
Бюджет на областьИСПРАВЛЕНО И ЗАКРЫТО. agent_areas получила budget_micros/spent_micros — ТРЕТИЙ независимый потолок рядом с проектом/сессией, аддитивный (не каскад ADR 0064), повторяющий шаблон max_parallel в той же строке (RN-443, ADR 0110)
Dev Lead и области через module_mapушло из backlog: ADR 0053, реализовано FASE 14d
Ручной handoff агенту по выборуЗАКРЫТО — ADR 0109/RN-440/RN-441. SessionPage.tsx получил селектор над addressableAgents() (leads ∪ одиночные агенты), который делает POST в POST .../sessions/:sessionId/handoffs — ТОТ ЖЕ CreateHandoffUseCase, что использует offer_handoff самого агента, с actor: {kind:'user'}, фиксирующим, кто решил. Staff (ADR 0088) и ux-designer (ADR 0087), оба обнаруженные достижимыми только через внутренний маршрут, вошли в AGENTES_DE_CHAT в том же изменении
MFA, социальный вход, OIDC, федерацияADR 0031 — социальный вход (GitHub/GitLab) ВЫШЕЛ из запрета и реализован (ADR 0084); остальное по-прежнему вне рамок
Deploy (DEPLOY_ENABLED + Environments)gate operavel уже объявлен как planned
Возврат rc/rcfixADR 0030
Режим community в approval-ladderИСПРАВЛЕНО И ЗАКРЫТО. Ссылка «становится изменением aprovacao_humana в реестре gates» была неточной — она шла из спекулятивной фразы ADR 0054, а не из чего-то недостающего. Режим community уже реализован и протестирован со времён FASE 6 (scripts/ci/approval-ladder.ts), лишь выключен через APPROVAL_MODE=solo (по умолчанию); aprovacao_humana gate aprovacoes-da-escada уже true в обоих режимах, без учёта APPROVAL_MODE в схеме. Чего действительно не хватало — TODO(humano) в branching-policy.md, критерия того, кто входит в каждый список одобряющих, — закрыто GOVERNANCE.md (корень репозитория). Фактическое включение режима остаётся операционным решением (набрать настоящих людей на три роли), а не инженерной задачей
Предпочтение валюты с ручным курсомНЕ БУДЕТ СДЕЛАНО — решение записано. Конвертация потребовала бы обменного курса, поддерживаемого вручную, который стареет; неверное число хуже честного числа в USD (тот же принцип, что уже применён в formatarCustoMicros, ProjectSettingsTab.tsx)
Повторное включение Anamnese (ANAMNESE_ENABLED=true)приостановлено решением пользователя 2026-08-10 — «hoje ele não está trazendo dados de muito valor» (RN-115). Никакие данные не удалены (гипотезы, профили квалификации и патчи инструкций остаются нетронутыми и видимыми); пауза касается только ПУТИ нового прогона, в ожидании будущей доработки того, что выводит Anamnese, перед повторным включением
Повторное включение Psicólogo (PSYCHOLOGIST_ENABLED=true)приостановлено решением пользователя 2026-08-10, по той же причине и по тому же шаблону, что и Anamnese выше (RN-117). Никакие данные не удалены (уже выпущенные анализы и гипотезы остаются нетронутыми и видимыми); пауза касается только ПУТИ нового прогона (автоматического и по запросу)
Установка одной строкой и подпись того, что мы публикуемвзято на себя FASE 29 (fase-29-instalacao-de-uma-linha.md), которая пока лишь планирование — ничего здесь не закрыто. FASE 28 письменно отказалась от установщика одной строкой, пока артефакты остаются неподписанными (BRB-005), поэтому фаза сначала подписывает, а потом устанавливает. Планирование измерило три вещи, которых этот файл не фиксировал: в docker-compose.prod.yml нет ни одной ссылки на ghcr.io, и он называет себя compose для проверки, поэтому нужен новый compose установки; make test-restore — путь Kubernetes, а backup.sh покрывает только Postgres, поэтому у миграции установки через compose не было доказанного пути; и git_local_repos — bare-репозитории проектов с провайдером local — не входят ни в какой backup, что означает потерю кода при потере тома в любом окружении. Id BRB теперь имеют каноническое место в реестре BRB, где три из шести пунктов, унаследованных этой фазой, оказались уже исправленными, — что обнаруживается только измерением, а это и есть аргумент в пользу существования реестра
Панель Problems/lint/тестов на вкладке Кодобъявленная нерешённость FASE 26 (CLAUDE.md) вместе с blame, списком PR и деталями ветки — эти три закрылись в FASE 26b, эта — нет и так и не была возобновлена. Источника данных пока не существует (нет интеграции lint/test-runner в продукте); записано здесь, чтобы не выпасть из отслеживания молча

Backlog runner/execution_mode (ADR 0104)​

Первая находка вида «два ADR, расходящиеся между собой», записанная в этом документе, — табличные прецеденты ниже (аудит fluxo.yml×код, уже закрытый) всегда были декларативный документ × код, но никогда ADR × ADR. Форма таблицы переиспользуется; вид находки — новый.

Расхождение, закрытое ADR 0104 — примирение ПРИНЯТО, Волна 1 (RN-421/422/423) СДЕЛАНА:

#СтрогостьПунктДоказательство (файл:строка)
1ЗАКРЫТО (RN-423)RN-170 требовала bind-mount при создании; RN-420 направляла в runner под тем же флагом workspace_mode == 'local', вовсе без bind-mountapps/api/src/application/use-cases/iam/confirm-project-workspace.use-case.ts (runner подтверждает и становится источником истины); apps/engine/lib/engine/actions/terminal_executor.ex (decisao_de_execucao/1, четыре исхода — явный отказ без проверенного workspace/подключённого runner, никогда не откат на контейнер)
2ЗАКРЫТО (RN-422)Мастер учил только bind-mount, но никогда команду runner, хотя она уже существовалаapps/web/src/routes/NewProjectWizard.tsx — третья запись в MODOS_DE_WORKSPACE (runner), с командой brabo-runner --project <id> --dir <pasta> прямо на шаге «Onde o código vai morar», а не только в Терминале
3ЗАКРЫТО (RN-421)Enum из 2 значений (workspace_mode) не выражал 3 физически различных режима исполненияapps/api/src/db/migrations/0048_quiet_iron_fist.sql (project_execution_mode, три значения); apps/api/src/domain/iam/project.entity.ts (PROJECT_EXECUTION_MODES)

Исправление, записанное во время реализации Волны 1, ЗАКРЫТО отдельно (само по себе это не одна из нумерованных волн runner в этом документе — см. ниже): пункт 4 ADR 0104 утверждал, что преобразование между тремя режимами СУЩЕСТВУЮЩЕГО проекта «становится разрешённым без пересоздания проекта». Волна 1 нашла это неверным — UpdateProjectDto всё ещё намеренно исключал executionMode/workspacePath, и фактически было поставлено лишь поле трёх значений при СОЗДАНИИ. ADR 0111 снова делает фразу ВЕРНОЙ через выделенный маршрут (PUT .../execution-mode, ConvertProjectExecutionModeUseCase, RN-447..450) — не редактируя принятый текст ADR 0104, который остаётся таким, каким был написан: неверным на тот момент и исправленным здесь:

пунктстоимостькритерий активациигде решено
Преобразовать execution_mode СУЩЕСТВУЮЩЕГО проекта без его пересозданияЗАКРЫТО (RN-447..450)—ADR 0111

Волна 2 — PAT, ADR 0105, СДЕЛАНА (RN-424/425/426; RN-427 закрыта позже, в той же волне решения):

У пункта «долгоживущий токен учётной записи» исходной таблицы были ДВЕ текстовые ошибки, исправленные во время реализации, а не выполненные вслепую, — записаны здесь, потому что таблица ниже их повторяла:

  1. «хеш argon2 в базе» было неверно. Настоящий шаблон продукта для секрета ВЫСОКОЙ энтропии (256 бит CSPRNG) — HMAC-SHA256 + pepper через hashDeToken()/TokenFactory — тот же механизм, что у refresh tokens и account tokens. Argon2 — для секрета НИЗКОЙ энтропии (пароля), где важна стойкость к словарю; здесь он лишь сломал бы индексированный поиск WHERE token_hash = $1 без всякой выгоды.
  2. «переиспользовать инфраструктуру ротации семейства refresh token» было неточно. PAT предъявляется многократно БЕЗ ИЗМЕНЕНИЙ — никогда не потребляется-и-перевыпускается, как refresh token. Переиспользуются лишь генерация (TokenFactory.gerar()) и хеш; всё семейство/ротация было бы выдумыванием поведения, которое PAT не нужно.
#СтрогостьПунктДоказательство (файл:строка)
1ЗАКРЫТО (RN-424)PAT должен был аутентифицировать runner-ticket, не становясь действительными учётными данными ни для одного другого маршрута пользователяapps/api/src/interfaces/http/auth/pat-route.decorator.ts (@RequirePatAuth()), apps/api/src/interfaces/http/auth/jwt-auth.guard.ts (третий ранний выход), apps/api/src/interfaces/http/auth/pat-auth.guard.ts (PatAuthGuard, применён только к runnerTicket)
2ЗАКРЫТО (RN-425)Проверка токена высокой энтропии без утечки того, какая из трёх причин отказа (не существует/отозван/истёк) применима; last_used_at без регрессии при законном переподключенииapps/api/src/infrastructure/persistence/drizzle/personal-access-token.repository.ts (validarEUsar — единственный условный UPDATE, без throttle в том же WHERE)
3ЗАКРЫТО (RN-426)Выпуск/отзыв/перечисление без утечки токена одного пользователя другомуapps/api/src/application/use-cases/auth/*-personal-access-token*.use-case.ts, ограничено userId в WHERE запроса
4ЗАКРЫТО (RN-427)maintainer, отзывающий PAT ДРУГОГО пользователя (реакция на инцидент — dev уходит с утекающим токеном), объявленное в то время вне рамокapps/api/src/application/use-cases/auth/*-personal-access-token-as-maintainer.use-case.ts, apps/api/src/interfaces/http/runner/personal-access-tokens.controller.ts (listAllPats/revokePatAsMaintainer, отдельные маршруты, @RequireRole('maintainer')), ограничено projectId в WHERE запроса

apps/runner/src/auth.ts полностью потерял интерактивный вход, cookies и ~/.brabo/runner-credentials.json — он лишь проверяет формат и передаёт --token/BRABO_ACCOUNT_TOKEN, никогда не записываемый CLI на диск.

Волна 3 — распространение через npm, ADR 0106, СДЕЛАНА:

#СтрогостьПунктДоказательство (файл:строка)
1ЗАКРЫТОapps/runner был "private": true, с bin, указывающим на сырой .ts, достижимый только клонированием монорепозиторияapps/runner/tsup.config.ts (format: cjs, external: ['node-pty']), apps/runner/package.json (bin → dist/index.cjs, publishConfig.access: public), .github/workflows/publish-runner.yml (публикует на каждом финальном теге, собственный workflow, параллельный release.yml)

Реальная находка во время реализации, эмпирически проверенная до попадания в код: очевидное исправление стража самозапуска в index.ts (import.meta.url === pathToFileURL(argv[1]).href) было СЛОМАНО именно в том случае, ради которого существует эта волна, — вызове через установленный bin (npm install -g создаёт symlink, а process.argv[1] никогда не разрешается через realpath, тогда как import.meta.url всегда разрешается). Окончательное исправление применяет realpathSync к argv[1] перед сравнением — см. ADR 0106.

Волна 4 — автономный бинарник, ADR 0112, СДЕЛАНА с честным пробелом по платформам:

#СтрогостьПунктДоказательство (файл:строка)
1ЗАКРЫТОnode-pty разрешает свой нативный аддон .node по ВЫЧИСЛЯЕМОМУ во время выполнения пути — bun build --compile не встраивает то, что не может разрешить статически, что эмпирически подтверждено (сборка проходит, runtime выбрасывает Cannot find module) до проектирования исправленияapps/runner/src/native-pty-loader.ts (реальное извлечение в fs.mkdtempSync с сохранением исходной относительной раскладки node-pty, а затем import() по абсолютному пути — никогда require('node-pty') по имени пакета); apps/runner/scripts/build-bin.mjs (генерирует native-pty-embed.generated.ts, статический import with { type: 'file' } на файл — единственная форма, которую Bun принимает для встраивания)
2ЗАКРЫТОbun build --compile молча упаковывал ЗАПАСНОЙ await import('node-pty') (используемый только вне скомпилированного бинарника) и выбрасывал исключение во время выполнения, а не при сборке, потратив реальное время отладки на вводящую в заблуждение ошибку, прежде чем её изолировалиapps/runner/scripts/build-bin.mjs (--external node-pty в вызове bun build)
3ЗАКРЫТОИсправление стража самозапуска из ADR 0106 (realpathSync на argv[1]) выбрасывает ENOENT, без перехвата, внутри скомпилированного бинарника — там process.argv[1] равен /$bunfs/root/<name>, виртуальному пути, который realpathSync не разрешаетapps/runner/src/index.ts (СНАЧАЛА проверяет import.meta.url.includes('/$bunfs/') и в этом случае запускает main() безусловно)
4ЧАСТИЧНО — половина СБОРКИ теперь доказана, половина ПРИКРЕПЛЕНИЯ — нетТолько linux-x64 был проверен РЕАЛЬНЫМ исполнением в этой песочнице (build:bin+smoke:bin зелёные, повторно, включая два бага выше, найденные запуском настоящего скомпилированного бинарника); остальные четыре были выведены чтением исходного кода node-pty, но ни разу не исполнялись на реальной машине той ОС. Первый реальный тег пришёл 2026-09-04 (v4.0.0) и решил половину сборки: linux-x64, linux-arm64, darwin-arm64 и win32-x64 собрались и прошли smoke:bin каждый на своём нативном runner — включая Windows, с его ТРЕМЯ нативными файлами и путём worker_threads, самым вероятным кандидатом на поломку. Упал шаг ПОСЛЕ сборки, одинаково у всех четырёх: прикрепление шло ~3m25s, прежде чем release.yml опубликовал Release, поэтому ни один бинарник не был прикреплён ни к одному Release. darwin-x64 (macos-13) даже не начался — 40+ мин в очереди, отдельный и ещё не исследованный вопрос об этой метке runner. Прикрепление теперь ЖДЁТ Release с потолком, поэтому следующий финальный тег — то, что докажет прикрепление и darwin-x64.github/workflows/build-runner-binaries.yml (матрица из 5 целей, каждая на своём нативном runner; шаг «Anexar à GitHub Release» несёт измеренную гонку); таблица матрицы целей в ADR 0112 содержит точное состояние по платформам

Ни один запасной вариант по платформе (более дешёвая альтернатива «бинарник + папка node-pty-native/», явно предложенная владельцу продукта и отвергнутая им) нигде не был реализован — ничто, найденное в этом исследовании, не ОПРОВЕРГЛО подход с действительно одним файлом ни для одной из пяти целей. Если сборка настоящего тега покажет, что одна из них действительно так не работает (Windows — самый вероятный кандидат), это станет здесь НОВЫМ пунктом для одной платформы — а не чем-то, что нужно угадывать и обходить превентивно сегодня. Сборка v4.0.0 на это ответила: ни один запасной вариант по платформе не нужен на четырёх, которые запустились, включая Windows, а единственный пункт, который она породила, — не по платформе: это шаг прикрепления, соревнующийся с release.yml.

Что оставлено на потом — backlog, приоритизированный владельцем продукта, в таком порядке:

пунктстоимостькритерий активациигде решено
Эксклюзивность runner по {project_id, machine_id} вместо только project_id (apps/engine/lib/engine/runners/registry.ex)MОТЛОЖЕНО — явный критерий активации: второй dev, действительно использующий тот же проект одновременноADR 0104
Подпись кода автономных бинарников (нотаризация на macOS, Authenticode на Windows)Mдействие оператора — требует, чтобы владелец продукта получил/оплатил идентичность подписи, та же категория, что NPM_TOKEN в ADR 0106ADR 0112
Прикрепить бинарники к Release и запустить darwin-x64 вообще — СБОРКА доказана на четырёх платформах с v4.0.0 (2026-09-04), прикрепление — ни на одной—происходит автоматически при следующем теге vX.Y.Z, теперь, когда шаг прикрепления ждёт Release вместо того, чтобы прочитать его отсутствие в первое же мгновение. darwin-x64 зависит от того, будет ли когда-нибудь запланирован runner macos-13ADR 0112

apps/runner/src/guard.ts (лексическая проверка cwd по принципу лучшего усилия) не является пунктом backlog — это инвариант, объявленный со времён ADR 0103 и ПОДТВЕРЖДЁННЫЙ ADR 0104: настоящая граница безопасности runner — аутентификация + обычный конвейер одобрения + согласие пользователя, но никогда не песочница.

Backlog модели команды (ADR 0085) — АУДИТ ЗАКРЫТ​

Результат аудита fluxo.yml × код (auditoria-fluxo-vs-codigo.md). Это были пункты, объявленные в модели (docs/fluxo.yml), о ролях, уже активных, — ни один не ждал, пока сначала активируется какая-то роль proposto/planned. Шесть волн плана закрылись, и эта таблица теперь пуста: последний пункт (B4, PO читает metricas-de-produto) закрылся с RN-407, без нового ADR — по тому же шаблону, уже установленному RN-164 (чтение агентом в рамках проекта, без внешнего эффекта). Документ аудита содержит полный план волн, со стоимостью и критерием проверки по каждому пункту, для тех, кому нужна история.

Закрыто после аудита (не удалено из исходной ссылки, только из этой таблицы незавершённого): gate implementavel (B3, ADR 0090); устаревший docs/gates.yml в paralelismo-autorizado (A1/B5, исправлено вместе с A3–A5/A8 — неверные цитаты RN и подписи в fluxo.yml); реальные deployment frequency и lead time через analise:funil (часть B7, ADR 0089) — остальное от B7 (MTTR, change failure rate) не закрылось: остаётся объявленным ПОСТОЯННЫМ пробелом в fluxo.yml (роль delivery-metricas), а не инженерной задачей; делегирование Dev Lead → dev (B1) и RN-160 без перепроверки в backend (A6/B6) — Волна 2 аудита, ADR 0094, RN-404/405; gate necessidade-validada (B2) — Волна 6 (последняя) аудита, ADR 0095, RN-406; продуктовые метрики → PO (B4) — отчёт (analise:funil, ADR 0089) уже существовал, не хватало лишь того, чтобы PO ЧИТАЛ metricas-de-produto; закрыто инструментом listar_metricas_de_produto и чистыми функциями, вынесенными в apps/api/src/application/services/funil-metrics.ts (RN-407) — последняя нерешённость таблицы, завершившая аудит.

Backlog внешнего ревью (2026-08-28)​

Внешнее ревью статическим чтением (melhorias-brabo-2026-08-28.md, корень рабочего дерева, ни один набор не запускался) предложило 15 пунктов. Сверено с этим файлом, таблицей технического долга в architecture.md, achados-execucao-real.md, ADR 0020 и ADR 0027, а также с самим рабочим деревом. То же правило, что и в остальном документе: никаких P1/P2/P3, выдуманных здесь, — стоимость ниже — оценка самого ревьюера (P = одна сессия, M = небольшая фаза, G = собственная фаза с ADR), а не рейтинг приоритета, и решение о приоритете — за пользователем.

Действительно новые (ранее нигде не записанные)​

пунктстоимостьдоказательствопримечание
Контрольная сумма для бинарников, скачиваемых CI (gitleaks, hadolint, actionlint, kustomize, kubeconform) + GitHub Actions, закреплённые по SHA коммита, а не по тегуPв .github/workflows/ci.yml было ноль проверок sha256sum/контрольных сумм; каждый action закреплялся изменяемым тегомСДЕЛАНО (#408 + #419) — sha256sum -c после каждого curl в ci.yml и в docker/engine/Dockerfile (образ dev; Dockerfile.prod уже это делал). Закрепления заняли два PR, и причина — находка: #408 перевёл 9 actions из ci.yml на SHA коммита, а эта строка читалась так, будто это все, — остальные 15 workflow остались на изменяемых тегах, включая четыре, несущие учётные данные (release.yml → GHCR, publish-runner.yml → npm, tag-release.yml, docs-deploy.yml → gh-pages). #419 закрепил оставшиеся 49 uses: и добавил недостающий механизм: scripts/ci/actions-pinadas.ts проваливает job lint на любом action, закреплённом тегом, или любом SHA без комментария с версией. Задокументировано в cadeia-de-suprimentos-do-ci.md, где также объявлено то, что всё ещё принимается на веру (без Dependabot, без происхождения зависимостей, без подписи артефактов)
Закрепить ollama/ollama:latest в обоих composePdocker/docker-compose.yml:81,118 и docker/docker-compose.prod.yml:328,346 были единственным образом без закрепления в обоих файлахСДЕЛАНО (#401 + #419) — закреплён на ollama/ollama:0.33.1 в 4 местах compose; пятое, deploy/k8s/base/ollama/job-model-loader.yaml, было вне доказательства ревьюера (который называл только compose) и оставалось на latest до #419
Генерировать apps/web/src/lib/api-types.ts из экспорта OpenAPI, а не копировать вручнуюMapps/api/src/scripts/export-openapi.ts существует; ни скрипта openapi:types, ни зависимости openapi-typescript нигде нет; заголовок самого api-types.ts говорит, что он отражается вручнуюСДЕЛАНО (ADR 0116) — мигрировал только ActionType (тип с доказанной и повторяющейся стоимостью в production), а не весь файл: lib/api-types.generated.ts теперь генерируется openapi-typescript из docs/reference/openapi.json, а lib/api-types.ts реэкспортирует из него ActionType. lib/aprovacoes.test.ts остаётся, сужённый до утверждений о содержимом, которых не делает компилятор (исчерпываемость теперь проверяется tsc через Record<ActionType, ...>)
E2E в настоящем браузере (Playwright) для критического пути — auth через cookie, CSRF, ticket socket Phoenix, стримингMни playwright.config.*, ни e2e/, ни зависимости @playwright/test нигде в репозиторииСДЕЛАНО (#420, ADR 0120) — e2e/, только chromium, работает против compose PRODUCTION, который уже поднимает docker/smoke.sh (SMOKE_KEEP_UP=1), и внутри того же job images, поскольку четыре образа — большая часть настенного времени этого job. Два spec, оба доказаны мутацией: путь cookie/CSRF/кросс-origin (включая переживание перезагрузки — единственный способ доказать, что access token в памяти был ВОССОЗДАН из cookie) и одноразовый ticket socket из RN-108, утверждаемый по наблюдаемому URL WebSocket, а не по индикатору статуса. e2e/ не член workspace — собственный lockfile, та же форма, что в ADR 0117, по той же причине аудита. Объявлено НЕ покрытым: различия между браузерами, встроенное одобрение и стриминг
Декомпозировать SessionPage.tsx / ProjectSettingsTab.tsxGSessionPage.tsx весил 169 КиБ, и его импортировали 25 тестовых файлов; ProjectSettingsTab.tsx весил 90 КиБСДЕЛАНО (ADR 0122 + ADR 0124 + ADR 0125) — половина этой строки про SessionPage.tsx теперь ЗАВЕРШЕНА в общей сложности за семь PR. Механическая декомпозиция ADR 0122 в 5 PR мержилась по одному (каждый до начала следующего, поскольку файл активно менялся): PR 1 — чистые хелперы timeline/хода → apps/web/src/lib/session-timeline.ts. PR 2 — StorySlide → apps/web/src/routes/StorySlide.tsx. PR 3 — StructuredQuestionCard (плюс его приватный хелпер permiteOutra) → apps/web/src/routes/StructuredQuestionCard.tsx. PR 4 — хелперы дерева backlog (urlDaPr/vinculoDeBacklog/montarArvoreDeBacklog/totalDeDescendentes) → apps/web/src/lib/session-backlog-tree.ts, а ItemDeBacklog + ContextAside (вся правая боковая панель) → apps/web/src/routes/ContextAside.tsx. PR 5 — шесть выводов готовности (criativoActive/arquitetoActive/hasBusinessRule/hasPromotedStory/hasProductBrief/activeAgent) → hook useSessionReadiness в apps/web/src/lib/session-readiness.ts, единственный из пяти PR, не являвшийся механическим изменением файла, — объявляющий скопление состояния канала хода (turnoViaCanal/statusAgent/pensandoVisivel/atividadeDoTurno) явно вне рамок, для будущего собственного ADR. ADR 0124 — это тот ADR, плюс два PR: механический PR дедупликации (сам без ADR), заставивший handleSend/handleReadiness/handleArchitectureReadiness вызывать уже существующие iniciarTurnoDoAgente/finalizarTurnoDoAgente, а не дублировать их ветку inline, и затем PR извлечения, переместивший состояние, эффект канала connectSessionHeartbeat и три функции жизненного цикла в hook useTurnoDoAgente в apps/web/src/lib/session-turno.ts. SessionPage.tsx сократился с 3 807 до 2 479 строк за семь PR; все 25 (теперь 24, один перенесён в тест lib/ в PR 1) файлов SessionPage.*.test.tsx проходили без правок всё это время. ADR 0125 закрывает строку, ОДНИМ PR, а не пятью: ProjectSettingsTab.tsx (2 532 строки) делится на файл на раздел в apps/web/src/routes/settings/ — 17 разделов плюс settings/shared.ts из двух символов (ORIGIN_TONE, formatarCustoMicros; у остальных десяти хелперов уровня модуля было ровно по одному вызывающему, и они ушли в файл этого вызывающего), — и ОСТАЁТСЯ на своём пути как точка входа и barrel, в 77 строк. Barrel структурен, а не косметичен: ProjectSettingsTab.test.tsx импортирует 11 имён по этому пути, а ProjectPage.test.tsx/project-tabs.test.tsx делают его vi.mock ПО ПУТИ. Один PR, а не пять, потому что файл никогда не имел формы SessionPage.tsx — родитель ничего не хранил (17 JSX-потомков, ни hook, ни query, ни t, ни проверки роли), ни один раздел не получал больше, чем {projectId} (два не получают props), и 11 из 17 уже экспортировались. 6 приватных разделов получили export в своём модуле (механически — barrel должен их импортировать), но намеренно НЕ реэкспортируются barrel, поэтому публичная поверхность вкладки байт в байт той же формы. Весь набор web зелёный — 142 файла, 1 537 тестов — с НУЛЁМ изменённых тестовых файлов
Проверять в самом CI закреплённые версии инструментов между ci.yml и Dockerfile.prodPci.yml:45-46 уже утверждал в комментарии, что версии должны совпадать, но ничто этого не гарантировалоСДЕЛАНО (#408) — новый шаг в job lint делает grep по обоим файлам и проваливает любое расхождение GITLEAKS_VERSION/HADOLINT_VERSION/ACTIONLINT_VERSION
Нижняя граница покрытия (храповик на текущем значении) в CIP–Mни флага --coverage/--cover, ни порога, настроенного в каком-либо vitest.config.* или mix.exs в api/web/engineСДЕЛАНО (#410) — нижняя граница на измеренном значении (api 80/69/74/81, web 82/77/72/83, engine 78%, всё vitest/mix test --cover); никакой excoveralls не добавлен, по правилу не ставить библиотеку без обоснования
Разделить business-rules.md по доменам; расширить шаблон readme-version.ts, чтобы генерировать подсчёты RN/ADR в прозе, а не набирать их вручнуюMbusiness-rules.md весит 650 КБ с 334 заголовками RN-XXX до RN-466 — README.md:200 всё ещё говорит «as 158 RNs», дрейф гораздо больший, чем объясняет старение текста; подсчёт ADR (115) в README.md:211 верен по сравнению, поэтому исправления сегодня требует только подсчёт RNСДЕЛАНО (#417 + #418) — в таком порядке, и порядок — в этом суть: #417 научил docs:check читать ПОДСЧЁТ из артефакта (ADR из файлов в docs/adr/, RN из заголовков ### RN-NNN, провайдеры из литералов capabilities) и сравнивать его с прозой, что и не даёт дрейфу вернуться. Затем #418 разделил по РАЗМЕРУ, а не по теме — одни Custo и Autenticação составляли половину страницы — на docs/business-rules/custo.md и docs/business-rules/autenticacao.md, не изменив ни запятой содержимого и не переименовав ни одного якоря {#rn-NNN}; 293 ссылки переписаны скриптом, а подсчёт остался 334. Нумерация никогда не начинается заново по файлу, а проверка суммирует все три по glob
Унифицировать AGENTS.md/CLAUDE.md (генерация или symlink + проверка)Pони не были идентичны байт в байт, как предполагалось, — одинаковый размер (35043 байта), разный MD5; одна строка уже расходилась (AGENTS.md:444 против CLAUDE.md:444, каждая ссылалась на собственное имя файла)СДЕЛАНО (#402) — самоссылающаяся строка стала нейтральной в обоих, а затем AGENTS.md стал symlink на CLAUDE.md; расхождение теперь невозможно на уровне файловой системы, а не только обнаруживается
Гигиена репозитория: убрать оставшийся блок KEYCLOAK_* в .env (уже отсутствующий в .env.example) и вывести из употребления migrate-keycloak-users.ts; переместить/заархивировать spike/session-engine и design_handoff_brabo/ из корня репозитория; привязать опубликованные порты Neo4j к 127.0.0.1 в docker-compose.prod.ymlPKeycloak удалён ещё в Фазе 7 (ADR 0032); docker-compose.prod.yml:80-82 публиковал 7474/7687 без привязкиСДЕЛАНО (#403) — скрипт и его раздел runbook удалены, spike//design_handoff_brabo/ удалены из дерева (восстановимы в коммите f5f771ac6), Neo4j привязан к 127.0.0.1 в production. Сама очистка KEYCLOAK_* в .env — локальная/gitignored, а не изменение в репозитории, — остаётся за пользователем
Контейнеры dev работают от root; сопоставить uid/gid хоста вместо обхода sudo chown -RMни одной директивы USER в Dockerfile dev (api/web/engine); README.md:160 и getting-started.md документировали обход как принятый фактСДЕЛАНО (#406) — см. подробное примечание в Отдельно выше; DEV_UID/DEV_GID сопоставляют контейнер с пользователем хоста, что доказано запуском настоящего стека
Дать website/ собственный lockfile, вне pnpm audit продуктаMwebsite — член pnpm-workspace.yaml, делящий единый lockfile; из 13 overrides безопасности 8 приходят из зависимостей Docusaurus/website, 3 — со стороны продукта (js-yaml 5.x, lodash, esbuild), 1 смешанный (undici, используемый также jsdom самого apps/web)СДЕЛАНО (#414, ADR 0117) — аудит 13 через pnpm why <pkg> -r нашёл разделение более запутанным, чем эта оценка: лишь 2 (serialize-javascript, yaml@1.x) были исключительно для website; 10, а не 1, оказались смешанными (mermaid/dompurify/uuid/postcss/nanoid разрешаются также через apps/web, js-yaml/fast-uri/lodash — через apps/api) и остаются продублированными в pnpm-workspace.yaml и в новом website/pnpm-workspace.yaml; esbuild был единственным чисто со стороны продукта

Уже объявленный долг, всё ещё без оценки​

У этих трёх уже есть строка в таблице технического долга architecture.md — ревью их не обнаружило, а оценило стоимость того, что уже было записано без неё. Один из трёх уже закрыт, и это тот, который ревью назвало самым дорогим:

долгуже объявлен воценка стоимости ревьюера
Публиковать образы в registry, чтобы deploy production был исполним от начала до концаarchitecture.md:552, ADR 0027СДЕЛАНО (ADR 0119) — четыре образа публикуются в GHCR (публично) на каждом финальном теге, а то, что опубликовал каждый тег, записывается ПО DIGEST в .release/images.json; overlay хранит маркер, а make imagens-do-release его применяет, поэтому репозиторий никогда не объявляет, какой релиз в production. Без третьего исключения push: файл — ассет Release и идёт в PR CHANGELOG. Найдено при выполнении, и этого НЕ было в этой оценке: overlays перечисляли три образа, а не четыре — CronJob backup наследовал brabo-backup:prod, неразрешимый ни в каком registry. Пока не сделано и не заявлено: ничто не делает deploy автоматически (DEPLOY_ENABLED по-прежнему не существует), а подпись/аттестация образа остаётся вне, вместе с пунктом подписи кода бинарников runner
Разделить schema.ts по агрегатам (Drizzle поддерживает схему в нескольких файлах с реэкспортом)architecture.md:546M
Golden-set (5–10 случаев, свободный ожидаемый вывод, закреплённая модель, allow-failure) для семантического пути gatearchitecture.md:548, ADR 0020не оценено ревьюером

Чего этот триаж НЕ делает​

Не исправляет ничего. Дисциплина, действующая с Фазы 10, продолжается: каждая находка ждёт фазы, которая ею займётся, а исправление вне её стирает доказательство того, почему она существовала.

И не выдумывает приоритет там, где нет данных: пункты прежнего backlog не получили P1/P2/P3, потому что решение по ним — продуктовое, а не инженерное, — а догадка, переодетая в классификацию, была бы хуже сырого списка.