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), backupgit_local_repos, compose установки, «FASE 29 — только планирование», Dependabot, два пробела runbook и строгость оповещений, среди прочего.Дефект — в ритме, а не в содержании: фазы закрывались, и никто не возвращался сюда, чтобы их отметить. Что выживает — и причина хранить этот файл, — это рассуждение триажа: почему каждый пункт дороже откладывать, чем сделать. Считайте любое состояние ниже непроверенным, пока оно не будет измерено заново.
Как классифицировано
P1 — не даёт продукту делать то, что он обещает. P2 — продукт делает, но лжёт или сбивает с толку смотрящего. P3 — качество, никто не застрял.
Стоимость: P (одна сессия), M (небольшая фаза), G (собственная фаза, с ADR).
Риск ожидания — колонка, решающая ничью: дефект, портящий данные или стирающий доказательства, стоит позже дороже, чем сегодня; косметический стоит столько же.
Доска
| предлагаемая фаза | пункты | приор. | стоимость | риск ожидания |
|---|---|---|---|---|
| СДЕЛАНО | — | RN-072 и RN-073 | ||
| СДЕЛАНО | — | RN-076; ADR 0056 принят | ||
| СДЕЛАНО | — | RN-074 и RN-075; ADR 0055 принят | ||
| СДЕЛАНО | — | все пять пунктов | ||
| СДЕЛАНО | — | RN-078; E и F уже были сделаны | ||
| СДЕЛАНО | — | RN-077 | ||
| СДЕЛАНО | — | RN-064 расширена | ||
| СДЕЛАНО | — | RN-079, RN-080 и RN-081 | ||
| СДЕЛАНО | — | 10 случаев, проверены мутацией | ||
| СДЕЛАНО | — | 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 и AD | allowlist глаголов не сходится — глагол, форма и вызов — разные пространства | 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) — СДЕЛАНО
| пункт | что было | как закрылось |
|---|---|---|
| D | Protect 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 сирот — без дедупликации и предупреждения |
| R | PO породил две истории, покрывающие один endpoint |
| J | Psicó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 без собственного specpr-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 |
ЗАКРЫТО — 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/rcfix | ADR 0030 |
ИСПРАВЛЕНО И ЗАКРЫТО. Ссылка «становится изменением 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-mount | apps/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 закрыта позже, в той же волне решения):
У пункта «долгоживущий токен учётной записи» исходной таблицы были ДВЕ текстовые ошибки, исправленные во время реализации, а не выполненные вслепую, — записаны здесь, потому что таблица ниже их повторяла:
- «хеш argon2 в базе» было неверно. Настоящий шаблон продукта для
секрета ВЫСОКОЙ энтропии (256 бит CSPRNG) — HMAC-SHA256 + pepper через
hashDeToken()/TokenFactory— тот же механизм, что у refresh tokens и account tokens. Argon2 — для секрета НИЗКОЙ энтропии (пароля), где важна стойкость к словарю; здесь он лишь сломал бы индексированный поискWHERE token_hash = $1без всякой выгоды. - «переиспользовать инфраструктуру ротации семейства 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 0106 | ADR 0112 |
Прикрепить бинарники к Release и запустить darwin-x64 вообще — СБОРКА доказана на четырёх платформах с v4.0.0 (2026-09-04), прикрепление — ни на одной | — | происходит автоматически при следующем теге vX.Y.Z, теперь, когда шаг прикрепления ждёт Release вместо того, чтобы прочитать его отсутствие в первое же мгновение. darwin-x64 зависит от того, будет ли когда-нибудь запланирован runner macos-13 | ADR 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), а не рейтинг приоритета, и решение о приоритете —
за пользователем.
Действительно новые (ранее нигде не записанные)
| пункт | стоимость | доказательство | примечание |
|---|---|---|---|
| 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 в обоих compose | P | docker/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, а не копировать вручную | M | apps/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, ...>) |
| 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.tsx | G | SessionPage.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.yml и Dockerfile.prod | P | ci.yml:45-46 уже утверждал в комментарии, что версии должны совпадать, но ничто этого не гарантировало | СДЕЛАНО (#408) — новый шаг в job lint делает grep по обоим файлам и проваливает любое расхождение GITLEAKS_VERSION/HADOLINT_VERSION/ACTIONLINT_VERSION |
| P–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 в прозе, а не набирать их вручную | M | business-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.yml | P | Keycloak удалён ещё в Фазе 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, а не изменение в репозитории, — остаётся за пользователем |
sudo chown -R | M | ни одной директивы USER в Dockerfile dev (api/web/engine); README.md:160 и getting-started.md документировали обход как принятый факт | СДЕЛАНО (#406) — см. подробное примечание в Отдельно выше; DEV_UID/DEV_GID сопоставляют контейнер с пользователем хоста, что доказано запуском настоящего стека |
website/ собственный lockfile, вне pnpm audit продукта | M | website — член 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 — ревью их не обнаружило, а оценило стоимость того, что уже
было записано без неё. Один из трёх уже закрыт, и это тот, который ревью
назвало самым дорогим:
| долг | уже объявлен в | оценка стоимости ревьюера |
|---|---|---|
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:546 | M |
| Golden-set (5–10 случаев, свободный ожидаемый вывод, закреплённая модель, allow-failure) для семантического пути gate | architecture.md:548, ADR 0020 | не оценено ревьюером |
Чего этот триаж НЕ делает
Не исправляет ничего. Дисциплина, действующая с Фазы 10, продолжается: каждая находка ждёт фазы, которая ею займётся, а исправление вне её стирает доказательство того, почему она существовала.
И не выдумывает приоритет там, где нет данных: пункты прежнего backlog не получили P1/P2/P3, потому что решение по ним — продуктовое, а не инженерное, — а догадка, переодетая в классификацию, была бы хуже сырого списка.