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

История фаз

Неизменяемая запись завершённых фаз Brabo, перенесённая из CLAUDE.md 2026-08-23 без переписывания. ДЕЙСТВУЮЩЕЕ состояние продукта живёт в CLAUDE.md, в RN (docs/business-rules.md) и в ADR — этот файл отвечает на вопрос «как мы сюда пришли», а никогда не «что действует сегодня». Принятый ADR никогда не редактируется; исправления ADR, записанные в этом тексте, остаются действительными там, где они есть.

Статус​

  • ФАЗА 1 (MVP) — ЗАВЕРШЕНА: IAM/RBAC, сессии с неизменяемым event log, чат с маршрутизатором LLM (Ollama + API), metering/бюджет токенов, конвейер proposed_actions + permissions.json, движок Elixir/OTP с супервизией и событием завершения.
  • ФАЗА 2 — ЗАВЕРШЕНА: GitProvider (Local/GitHub/GitLab) с контрактным набором тестов и capabilities, зашифрованные учётные данные, идемпотентный и возобновляемый через конвейер bootstrap Gitflow, мастер с прогрессом в реальном времени.
  • ФАЗА 3 — ЗАВЕРШЕНА: Harness (PromptAssembler, ToolLoop, ContextManager, InstructionFiles, Hooks), явные handoffs, агенты Criativo, PO и Arquiteto с артефактами и проверками в домене.
  • ФАЗА 4 — ЗАВЕРШЕНА: агенты выполнения (динамические dev по модулю в изолированных worktrees, QA и SecOps как gate-ы PR, проактивная Infra), блокировка защищённого merge, живая панель команды; настоящий Psicólogo и Anamnese с замкнутым циклом. Gate-ы проверены реальным выполнением (ADR 0020).
  • ФАЗА 5 — ЗАВЕРШЕНА: образы production non-root и CI (ADR 0024), Kubernetes с Kustomize, HPA по очереди Oban, NetworkPolicies и ESO (ADR 0025), graceful shutdown + OpenTelemetry + dashboards (ADR 0026), бэкап с протестированным restore, runbooks и hardening (ADR 0027).
  • ФАЗА DOC — ЗАВЕРШЕНА: docs/ как единственный источник по Diátaxis, сайт Docusaurus в website/, читающий из docs/, генерируемые справочники, docs/.docmap.yml + проверка дрейфа (docs:check) в CI, слой сообщества, проверенная MIT, механизм задокументирован в docs/explanation/documentation-workflow.md.
  • ФАЗА 6 — ЗАВЕРШЕНА: механизированная политика веток (ADR 0030) — pr-police, approval-ladder с двумя режимами, promote/tag-release с вычисляемой версией и якорем по дереву, backmerge gate с автоматическим обратным распространением, версионируемые rulesets. Конвейер проверен от начала до конца (v0.1.0 → v0.2.0), а цепочка hotfix подтверждена реальным выполнением.
  • ФАЗА 7 — ЗАВЕРШЕНА: first-party auth вместо Keycloak — argon2id, access Ed25519, ротация refresh с отзывом семейства, прогрессивный lockout, токены учётной записи и MailSender только в лог (ADR 0031, RN-030..033); атомарный переход с собственным эмитентом, RBAC нетронут, /internal/* с service token, cookie httpOnly + CSRF, миграция пользователей и полное удаление Keycloak (ADR 0032, RN-034/035); OpenAPI в 23 контроллерах с DTO, закреплёнными по типу, табличный тест, требующий метаданных, сгенерированный docs/reference/api/ с манифестом хешей (ADR 0033).
  • ФАЗА 8 — ЗАВЕРШЕНА С ОТСЕЧЕНИЕМ: иерархия агентов — области с единственным lead как внешним контактом, приватные внутренние делегирования (таблица delegations, area как TEXT), консолидированный вердикт с прослеживаемостью, сбой с источником; QA как область (Lead + Автоматизация + Производительность/Безопасность, внешний контракт gate-ов не изменён — набор тестов Фазы 4 зелёный без изменений); Infra как область с субагентом Workflows, генерирующим CI по provider-у; панель команды по областям и Psicólogo/Anamnese, нацеленные на субагентов (ADR 0038). ОТСЕЧЕНИЕ ОБЪЁМА того времени, ОТМЕНЁННОЕ ФАЗОЙ 14d: agent_areas/agent_area_members существуют с тех пор, по проекту, с настраиваемым max_parallel — заставила это область dev, первая ДИНАМИЧЕСКАЯ. Отсечённым остаётся только бюджет по области (реальными пределами бюджета остаются проект, сессия и task). Список в apps/web/src/lib/agents.ts уцелел для цвета, иконки и ПРАВИЛА адресации; участники dev берутся из таблицы.
  • ФАЗА 9 — ЗАВЕРШЕНА: контрактный набор LLMProvider, запущенный против существующих, база OpenAICompatibleProvider поверх node:http с timeout БЕЗДЕЙСТВИЯ, ошибка, нормализованная по code, двухслойные capabilities (ADR 0041); синхронизация каталога, где обнаруженная модель входит выключенной, исчезнувшая модель помечается, а не удаляется, цена заморожена в token_usage с append-only аудитом (ADR 0042); ModelPicker перегруппирован по происхождению с курированием. Шесть provider-ов 9b вошли через Фазу 11 (ADR 0043).
  • ФАЗА 10 — ЗАВЕРШЕНА: Bitbucket + GenericGitProvider сданы ЧЕРЕЗ dogfooding (первое реальное выполнение, где Brabo строил сам себя, в форке с ручным seed, партиями с перезапуском между tasks, Criativo обязателен в цепочке). Итоги записаны в Фазе 12d, в docs/explanation/primeiro-dogfooding.md — 17 находок с arquivo:linha настоящие; КОЛИЧЕСТВЕННАЯ половина (перезапуски, вмешательства, стоимость) осталась não medido, потому что таблица наблюдений так и не была заполнена. Три P1 по операбельности закрыла Фаза 12. Остальные 14 остаются открытыми и перечислены в итогах — не исправляйте попутно.
  • ФАЗА 11 — ЗАВЕРШЕНА: шесть provider-ов Фазы 9b как конфигурация поверх базы, каждый исследован с нуля по официальной документации (запрещено наследовать quirk); LLM_PROVIDER_NAMES с 3 до 9; DTO учётных данных и тестер подключения, выводимые из единого списка; capability объявляется только доказанной, с двумя откатами вживую (DeepInfra и Vultr); единственный новый hook в базе (parseErrorFrame, +31 строка), доказанно необходимый для OpenRouter (ADR 0043). Незакрытые smoke-тесты с настоящими учётными данными: поглощены ФАЗОЙ 13.
  • ФАЗА 12 — ЗАВЕРШЕНА: операбельность после dogfooding, три находки P1 закрыты и доказаны в одном выполнении (ADR 0047). 12a — принятие существующего репозитория: собственный маршрут, getRepo, проверяющий доступ до записи, origin (created|adopted) в обеих таблицах, и ПЛАН как шлюз — dry-run, описывающий расхождение и ничего не меняющий, пока решение пустое; повторное принятие сходится (ADR 0044, RN-045/046). 12b — перепланирование dev agent по событию: сохраняемая машина состояний (working|awaiting_gate|idle|idle_tripped), реагирующая на два события существующей outbox, awaiting_gate, удерживающий worktree (который принадлежит АГЕНТУ, а не task), пока gate не закончит, и circuit breaker с явным re-arm (ADR 0045, RN-047). 12c — продвижение story с полномочиями пользователя: story_promotion по проекту с manual как НОВЫМ значением по умолчанию (направленный backfill ставит существующие проекты в auto), proposed_ready как предложение, а не состояние, продвижение, переиспользующее TransitionStoryUseCase (мёртвый код находки #13), и отказ, возвращающий к PO с причиной, закреплённой в его сессии (ADR 0046, RN-048). 12d — Noop вошёл в машину состояний 12b; скрипт pnpm --filter api validacao:fase-12, который завершается != 0, когда критерий не выполняется, и извлекает улики из базы; итоги Фазы 10 записаны; docmap покрывает engine/dev и engine/agents. Незакрытое (выполнение проверки; объявленные ограничения Local/Noop): поглощено ФАЗОЙ 13.
  • Не рефакторьте готовое без явного запроса.

ФАЗА 13 — ЗАВЕРШЕНА 2026-08-07 (доказать по-настоящему и рассортировать)​

Ни одной новой функции. Фаза закрывает объявленные незакрытые пункты, доказывает то, чего проверка Local/Noop объявленно не доказывает, и превращает 19 открытых находок в приоритизированный план. Усвоенный урок: ручная таблица Фазы 10 так и не была заполнена — в этой фазе КАЖДАЯ метрика извлекается скриптом из event log/token_usage, никогда не записывается вручную.

13a — Закрыть объявленные незакрытые пункты​

  1. ЗАВЕРШЕНО 2026-08-07: pnpm --filter api validacao:fase-12 завершается 0, а таблица event id находится в docs/explanation/validacao-fase-12.md (TODO(humano) ушёл). Это стоило четырёх исправлений — три в ИНСТРУМЕНТЕ (подопытный рождался в /tmp, невидимый для engine; Noop не открывал gate; Noop умирал на pr_settled) и одно в ПРОДУКТЕ (находка W: dev agent умирал, когда очередь пустела).
  2. ЧАСТИЧНО 2026-08-07: smoke OpenRouter прошёл с настоящими учётными данными и закрылся (реальная стоимость US$ 0,000005, прочитана из token_usage). Остальные пять по-прежнему пропускаются — их ключей в среде нет. Smoke протух относительно ADR 0049 и никогда не запускался; исправлен заодно. Датированный результат — в docs/explanation/aceite-providers.md

13b — ЗАВЕРШЕНА 2026-08-07 (v2.4.0)​

Вся цепочка доказана против НАСТОЯЩЕГО GitHub (daneiel/test), в десяти выполнениях: удалённое принятие → план с решением пользователя → ручное продвижение ОДНОЙ story → настоящий dev agent пишет код → commit, push и УДАЛЁННЫЙ PR → gate открывается → область делегирует и освобождает с обоснованием → субагент ПРИОСТАНАВЛИВАЕТСЯ на одобрении → отказ пользователя ВОЗОБНОВЛЯЕТ цикл → вердикт changes_requested от LLM. Ноль перезапусков engine. Merge исключён по замыслу (RN-014). 3. Сценарий — pnpm --filter api validacao:real, с фазами, разделёнными по СТОИМОСТИ (--ate adocao|backlog|execucao) — дешёвая запускается одна первой, и именно так ошибки конфигурации проявлялись до того, как стоили денег. Требует выполнения ИЗНУТРИ контейнера api: политика — файл в общем томе, а с хоста она рождается в файловой системе, которую engine не видит. 4. pnpm --filter api medir:execucao — это инструмент, и он ДОКАЗАН: сам воспроизводит числа, которые предыдущий dogfooding записал вручную (18 вызовов, 292.211 токенов, ~US$ 0,03). 5. Результат — в docs/explanation/validacao-real.md, с десятью выполнениями и тем, чему научило каждое.

СЕМЬ P1 закрыты по пути, все найдены ВЫПОЛНЕНИЕМ и ни одного — тестом: W (dev agent умирал при пустой очереди), Y (поиск не отличал пустое от ненайденного), AA (учётные данные git разрешались по тому, кто кликает, а не по тому, кто платит, — RN-082), AB (gate называл ожидающее решение сбоем инфраструктуры — ADR 0057), AC (перенаправление делало любую команду неодобряемой), ReDoS в области пути (CodeQL HIGH) и CVE postgrex.

Из находок, которые открыла фаза, X закрылась позже (ФАЗА 14d, RN-085 — предел итераций стал по ТИПУ агента). ДВЕ остаются открытыми, и их аргумент стоит больше чисел: Z и AD (allowlist глаголов НЕ сходится: глагол, форма и вызов — разные пространства, и выполнения 6/7/8 застряли по одному на каждом), и AE (агент QA пытается исправить код, который судит, вопреки собственному промпту, — сдерживается двумя независимыми барьерами, allowlist и областью). Ни одна из двух не баг для исправления: это продуктовое решение, и они — в docs/explanation/achados-execucao-real.md.

Вывод, который сдаёт фаза и который важнее PR: путь к автономии НЕ проходит через ослабление политики. Он проходит через то, что агент ЖДЁТ решения, а не умирает, — что ADR 0057 сделал для gate-ов, расширив 0052.

13c — ЗАВЕРШЕНА: сортировка стала docs/explanation/backlog.md​

19 находок закрыты (19 из 19). Документ остаётся живым и получил новые находки 13b. 6. Сессия сортировки: прочитать docs/explanation/primeiro-dogfooding.md и классифицировать 19 находок в P1/P2/P3 с предложением группировки в связные фазы (по теме и зависимости между ними), оценкой относительной стоимости (P/M/G) и риском ожидания. Результат — ПРЕДЛОЖЕНИЕ: решение о приоритете — за пользователем. 7. Консолидировать полный backlog в живом документе (docs/explanation/backlog.md или эквиваленте): рассортированные находки + старые пункты — бюджет по области (ADR 0038; аппарат областей и Dev Lead вышли из backlog с ADR 0053, реализованным ФАЗОЙ 14d), ручной handoff выбранному агенту, MFA/social/OIDC/федерация (ADR 0031), настоящий SMTP в MailSender, deploy (DEPLOY_ENABLED + Environments), возврат rc/rcfix (ADR 0030), режим community в approval-ladder, «N агентов онлайн» в dashboard, предпочтение валюты с ручным курсом.

ФАЗА 14 — ЗАВЕРШЕНА 2026-08-07 (что потребовало реальное выполнение)​

Выполнение hello world не прошло дальше первого хода и выявило дефекты, которые не ловил ни один набор тестов, потому что всё до этого работало с ЛОКАЛЬНОЙ моделью. Исправления уже внесены; остаётся функциональность, решённая пользователем во время выполнения.

14a — Операбельность агентов (ЗАВЕРШЕНА)​

  • Сбой хода стал ДОЛГОВЕЧНЫМ agent.error с источником, и агент ГОВОРИТ в нити; четыре разговорных агента перестали записывать пустой agent.response (RN-059). Путь ошибки, рассказанной в финальном frame-е, который не выдавал ни одного события, тоже закрыт.
  • Чат Criativo открывается приглашением, а не пустотой: он активируется и ждёт, пока пользователь заговорит первым, и экран теперь говорит об этом — в том числе что технология и код — НЕ его дело.
  • Ключ, который тратит агент, — ключ OWNER-а workspace (RN-058). Раньше ход искал учётные данные по SLUG агента в столбце UUID: ни один агент никогда не использовал provider с учётными данными, и работал только ollama.
  • Отчёт о расходах по ключам, только для владельца, с агентом, отделённым от человека (RN-060).
  • Bootstrap Gitflow в GitHub: только что созданный репозиторий отвечает 409 во всём Git Data API, и первый commit проходит через Contents API. Поддельный backend тестов лгал (404 там, где GitHub отвечает 409) — исправлено заодно, именно это держало набор тестов зелёным при сломанном продукте.

14b — Древовидная временная шкала (ЗАВЕРШЕНА)​

Одна ветвь на агента, от первой вехи до того, что он делает СЕЙЧАС, выведенная на 100% из event log, который экран уже получает. Хронологическая лента остаётся в столбце активности: она отвечает «что произошло», дерево отвечает «кто что делает».

Пересмотрено после ФАЗЫ 26 (без новой фазы, в том же духе точечного исправления, что 12d/ПОСЛЕ-ФАЗЫ 15): критерий открытия по умолчанию сменился с одного лишь «активен/стоит» на 5 агентов с самой СВЕЖЕЙ активностью — активный по-прежнему в приоритете (RN-118); каждая свёрнутая ветвь получила собственный счётчик НОВИЗНЫ, с «последним просмотренным» по агенту в read-state.ts (тот же механизм, что у колокольчика, теперь гранулярный); а tool.call/tool.result/agent.response стали раскрываться ПО ОТДЕЛЬНОСТИ (аргументы, результат, итерация), сгруппированные по границе итерации ToolLoop.

14c — Автоматическая проверка UI (ЗАВЕРШЕНА)​

Контраст стал тестом над design/tokens.css, с известным долгом, измеренным и закреплённым; макет (обрезанный текст, меню вне viewport, dropdown, обрезанный предком, цель < 24px) стал браузерным проверяющим в scripts/dev/validacao-visual.js. Без новой зависимости.

14d — ЗАВЕРШЕНА: параллелизм решает LEAD, разрешаете вы​

Сдана в четырёх PR (#181, #182, #183, #184). ADR 0053 стал ПРИНЯТЫМ, окончательно отменив три отсечения (Dev Lead, динамические области через module_map и общий аппарат областей).

  1. Области стали данными: agent_areas/agent_area_members по проекту, с max_parallel по умолчанию 2. Заставила это область dev — первая ДИНАМИЧЕСКАЯ, чьи участники — по одному на модуль module_map. То, что не перечислимо в коде, должно быть данными.
  2. Предел принадлежит СЕССИИ, а не модулю (RN-083). Счёт по модулю позволил бы N модулей × 2 без какого-либо разрешения — прежняя дыра под другим именем. Выше предела становится proposed_action.
  3. Настраивается по области в Настройках, требуя maintainer по той же причине, что и активация выполнения: изменить предел — значит решить, сколько продукт тратит, не спрашивая.
  4. Anamnese предлагает повысить, когда разрешение стало рутиной, — три одобрения и НИ ОДНОГО отказа (RN-086). Один отказ гасит сигнал: если вы хоть раз отказали, предел делает свою работу.
  5. Dev Lead существует (RN-087), разговорный, получающий handoff от Arquiteto в том же подтверждении, которое уже передаёт Infra, и предлагающий план: сколько агентов на модуль и почему. dev-<modulo> перестали быть адресуемыми через handoff — правило ADR 0038 стало действовать для dev так же, как уже действовало для QA и Infra.

Находка X закрыта здесь (RN-085): TOOL_LOOP_MAX_ITERATIONS стал пределом ПО ТИПУ — 8 для разговорного, 60 для выполнения и gate. Критерий того, кто поднимается, — НЕ «кто много работает»: это тот, у кого под ним есть token_budget_micros, удерживающий расходы. infra-workflows использует тяжёлый инструмент и остаётся на 8, потому что работает без бюджета.

Три пробела самой фазы, найденные мной при написании следующего среза, и все — один и тот же сбой: правило предела вошло с зелёными тестами, а продукт его не задействовал — ни один маршрут не вызывал вариант использования, одобрение действия ничего не выполняло, а тип мог быть автоматически одобрен через permissions.json (закрыто RN-086). Тестировать деталь — не значит тестировать путь к ней.

Вне объёма, по объявленному решению (ADR 0053, пункт 5): смена владельца кнопки «Ativar execução». Делегирование Dev Lead → dev-<modulo> через таблицу delegations с area = "dev", которое этот же пункт тоже объявлял, ПЕРЕСТАЛО быть вне объёма — закрыто ADR 0094 (Аудит fluxo.yml × код, Волна 2, RN-405). Кнопка остаётся обратимой; выполнение остаётся на текущем пути, а исправление после gate по-прежнему идёт прямо к dev, открывшему PR.

ФАЗА 15 — ЗАВЕРШЕНА 2026-08-07 (gate-ы как данные)​

Ни одного НОВОГО gate. Фаза выносит в docs/gates.yml gate-ы, которые УЖЕ неявно существуют в продукте, с проверкой скриптом и серьёзностью, в том же духе, что docs/.docmap.yml. Gate агента, которого не существует (dev-lead, platform), входит как status: planned со ссылкой на backlog — никогда как активный. Внешний контракт gate-ов НЕ меняется.

15a — ЗАВЕРШЕНА: декларативный реестр​

  1. docs/gates.yml с существующими gate-ами (см. ADR 0054, расширяющий ADR 0048 — решение в event log делает прохождение gate измеримым), схема проверена тестом.
  2. Загрузчик в api, читающий реестр; внутренний endpoint чтения.
  3. Скрипт pnpm --filter api validacao:gates, который извлекает из event log последнее прохождение каждого активного gate и завершается != 0, если у какого-либо gate block нет улик проверки скриптом.

Не у каждого gate есть доказательство в event log, и реестр говорит, где оно живёт: merge-protegida гарантирован ТЕСТОМ (блокировка в decide.ts не выдаёт собственного события), а backmerge — это CI, с состоянием в .release/gate.json. Поэтому каждый gate объявляет evidencia: event_log | teste | ci с локатором — понизить их до warn значило бы солгать о самых жёстких блокировках продукта.

15b — ЗАВЕРШЕНА: потребление​

  1. Панель команды показывает gate, которого ждёт каждая story/PR, выведенный из реестра (не захардкоженный).
  2. docs/explanation/gates.md, объясняющий механизм, в docmap.

ПОСЛЕ ФАЗЫ 15 — ЗАВЕРШЕНО 2026-08-08 (v2.5.0 и v2.5.1)​

Это не запланированная фаза: это то, что ИСПОЛЬЗОВАНИЕ нашло после 15, и оно здесь, потому что происхождение каждого пункта важно. Ничего из этого не пришло из сценария — пришло из навигации по приложению в Chrome и чтения панели безопасности.

Дефекты UI, найденные при навигации (RN-088..091):

  • 429 превращался в БЕЛЫЙ ЭКРАН, а приложение отвечало ещё большим трафиком. Правило стало общим: каждый экран различает ТРИ состояния — загрузка, ошибка (с trace_id и кнопкой) и пусто. if (!dado) return null схлопывает все три.
  • Dashboard делал по запросу на проект: 3.824 → 12 req/min, с GET /workspaces/:id/projects-summary. Колокольчик был недостающей половиной: 289 → один запрос за цикл (RN-090/091).

Ужесточение безопасности, с обеими панелями на НУЛЕ:

  • ADR 0058 — закрытый CSP в api (default-src 'none'), пересматривающий пункт 7 ADR 0027; и projectId, ограниченный корнем (RN-092), что затрагивало не только permissions.json, но и область, которая РАЗРЕШАЕТ терминал.
  • ADR 0059 — GIT_OAUTH_STATE_SECRET без значения по умолчанию в production (RN-093). Значение по умолчанию было ПУБЛИЧНЫМ (.env.example), и compose production подставлял его как fallback: требование «не пустая» ничего бы не поймало, потому что на реальном пути ошибки переменная была ЗАДАНА.
  • Алерт, который не выдерживал проверки, был ОТКЛОНЁН с письменным обоснованием, никогда не заглушён — и отклонение — решение пользователя, а не агента.

Три операционных урока, стоившие цикла CI, и они действуют как правило:

  1. Зелёный CodeQL в PR не доказывает, что старый алерт закрыт — проверка PR сообщает о НОВОМ алерте. Алерт закрывается при сканировании ветки ПО УМОЛЧАНИЮ, то есть только после того, как исправление дойдёт до main.
  2. Gitleaks сканирует commit-ы, достижимые из refs/heads/*, а не дерево. Значение с высокой энтропией, закоммиченное однажды, навсегда проваливает CI на этой ветке; исправление поверх НЕ очищает. Выход — сделать commit недостижимым (пересобрать из dev и УДАЛИТЬ старую ветку ДО открытия нового PR) — никогда allowlist, который ослепляет сканер на этом пути для всех commit-ов. Refs PR в область не входят. Fixture, представляющий секрет, рождается БЕЗ энтропии.
  3. Барьер, живущий в другой функции, CodeQL не видит — отсюда три js/path-injection, пережившие исправление. Проверка осталась централизованной (RN-092), и цена была уплачена на панели: дублирование её в каждом вызывающем было бы проверкой, которая однажды разойдётся.

ПРОГРАММА 16–26 — что попросила навигация (в работе)​

Одиннадцать запросов, родившихся из ПЕРВОЙ реальной навигации по приложению после сброса базы. Это не сценарий: это использование. Три открытия расследования определили размер и ПОРЯДОК программы, и стоит записать каждое, потому что ни одного не было в исходном запросе:

  1. В design_handoff_brabo/ есть ПОЛНЫЙ дизайн-handoff — 8 экранов высокой точности, токены, типографика и бренд. Решено принять его ЦЕЛИКОМ и ПЕРВЫМ: так одиннадцать запросов рождаются в новом визуале, а не делаются дважды.
  2. Терминал вкладки Code стал решением АРХИТЕКТУРЫ. У каждого проекта теперь свой контейнер, и образ решает ARQUITETO. Внутри него агент свободен; git push, PR и deploy остаются за людьми. Это погашает самый большой открытый долг продукта — сегодня агент выполняется в ТОМ ЖЕ контейнере, что и монорепозиторий Brabo, а ADR 0055 говорит о себе, что он политика, а не изоляция.
  3. По пути проявились два настоящих дефекта, и ни один не был запрошен: agent_areas НИКОГДА не записывается (upsert без вызывающего, API возвращает []), а ящик колокольчика сортирует seq ASC в SQL — показывает САМЫЕ СТАРЫЕ.

Предел выполнения: не более ЧЕТЫРЁХ субагентов параллельно. Фазы были нарезаны по ОСПАРИВАЕМОМУ ФАЙЛУ, а не по теме — именно это делает параллелизм возможным. Ограничитель backend-а — apps/api/src/db/migrations/meta/_journal.json и снимки drizzle: ОДНА миграция на волну, потому что ручное разрешение конфликта снимка — путь к расходящейся схеме. Диапазоны RN и номера ADR ЗАРАНЕЕ ВЫДЕЛЯЮТСЯ до начала волны: две параллельные фазы, пишущие RN-094, заставляют одну перенумеровываться при merge, а перенумерованная RN ломает ссылки #rn-0xx, которые проваливает pnpm docs:check.

ФАЗА 16 — ЗАВЕРШЕНА: фундамент (разблокировать параллелизм)​

Ни один из одиннадцати запросов здесь не сдаётся. Фаза существует, потому что три из них затрагивают ТЕ ЖЕ четыре точки router.tsx/ProjectPage.tsx, а ещё три упираются в отсутствие общей детали. Без неё волны схлопываются в последовательное выполнение.

  1. design/tokens.css получает --violet (агенты/ИИ) и всё прочее, что определяет handoff. Шрифты ОСТАЮТСЯ self-hosted — следование handoff здесь сломало бы приложение под CSP nginx (ADR 0036). Тест контраста покрывает новые пары, а известный долг в 4 пары остаётся закреплённым.
  2. Вкладка перестаёт быть закрытым списком в ДВУХ местах: единый реестр, из которого выводятся PROJECT_TABS, type TabKey, линейка и render. Тест, проваливающий ключ, присутствующий в одном месте и отсутствующий в другом, — это настоящий дефект.
  3. Disclosure в дизайн-системе, с семантикой самой полной уже существующей реализации (ModelCatalogSection). ТОЛЬКО КОМПОНЕНТ: ни одна из шести ad-hoc реализаций здесь не мигрирует, иначе фаза открыла бы три самых оспариваемых файла программы.
  4. Метка сессии становится helper-ом, и пять inline slice(0, 8) мигрируют. Именно эта миграция не даёт ФАЗЕ 20 столкнуться с 19.
  5. CLAUDE.md входит в определение готовности (см. раздел Документация), с правилом warn в docmap. СДАНО: правило — claude-md в docs/.docmap.yml, срабатывающее от package.json, pnpm-workspace.yaml, apps/*/package.json, apps/engine/mix.exs, docs/adr/*.md и docs/gates.yml — фраза «сегодня у него НОЛЬ покрытия», которая была здесь, описывала состояние ДО фазы и осталась лгать после неё.

ФАЗА 17 — ЗАВЕРШЕНА: 8 экранов по handoff​

Визуальная точность ДО поведения, чтобы не переделывать работу. Ни одно бизнес-правило не меняется: если экрану нужны данные, которых нет, это становится объявленным незакрытым пунктом, а не попутной функцией. 6. Вход и App/список проектов. 7. Проект и Сессия, с сохранением трёх состояний RN-088 (ошибка до пустоты). 8. Одобрения и Настройки — Одобрения являются визуальной базой ФАЗЫ 19. 9. Доказательство, что точность достигнута: контраст, измеренный на токенах, и макет, проверенный scripts/dev/validacao-visual.js на ВСЕХ затронутых экранах. .dc.html — эталон, а НЕ код для копирования — это говорит сам README handoff-а.

ФАЗА 18 — ЗАВЕРШЕНА: область существует в базе (дефект, исправлен раньше)​

  1. У AgentAreaRepository.upsert НЕТ ни одного вызывающего, поэтому четыре варианта использования работают над пустой таблицей. Provisioning при создании проекта + backfill, с тестом, доказывающим, что только что созданный проект ИМЕЕТ области, — это тот же сбой, что в ФАЗЕ 14d: тестировать деталь — не значит тестировать путь к ней.
  2. Схлопнуть ТРИ копии списка областей (api, web, engine) в один источник.

ФАЗА 19 — ЗАВЕРШЕНА: одобрение, которое читается​

  1. Убить общий fallback ApprovalCard, который вываливает chave: JSON.stringify(valor) — вероятная причина «трудно читать». Каждый тип получает ФРАЗУ на pt-BR; тип без фразы показывает глагол + «ver detalhes», а сырой payload рождается СВЁРНУТЫМ, никогда не вываленным.
  2. Свёртывание в ТРЁХ местах: Одобрения, Insights и карточка в чате, с глаголом и фразой из ОДНОГО модуля.
  3. Ограничение проекта: свёртывание НЕ вводит новый обязательный prop в ApprovalCard. Именно это оставляет SessionPage.tsx нетронутым и убирает ребро с ФАЗОЙ 20.

ФАЗА 20 — ЗАВЕРШЕНА: сессия получает идентичность​

  1. sessions получает kind и name в ОДНОЙ миграции — две миграции над одной таблицей сталкиваются в journal и снимках.
  2. Согласовать с выводом по событию: kind классифицирует НАМЕРЕНИЕ создания, execution.activated продолжает классифицировать СОСТОЯНИЕ выполнения, и ни одно не перезаписывает другое. execution.activated в консультативной сессии — явная ошибка, а не молчаливая конвертация.
  3. Переименование с сохранением хэштега; без имени деградирует до одного хэштега.
  4. Кнопка возврата к dashboard — сегодня SessionPage.tsx не импортирует ни Link, ни useNavigate, и НИ ОДНА навигация не выходит с экрана. Назначение пересмотрено в более позднем раунде: кнопка возвращает к ПРОЕКТУ сессии (/projects/:projectId), а не к корневому dashboard — сессия всегда рождается внутри проекта.

ФАЗА 21 — ЗАВЕРШЕНА: объём событий (RN-099/100)​

  1. useSessionEvents остаётся ТЕКУЩИМ СОСТОЯНИЕМ (latest, четыре потребителя: roster, дерево, выполнение, Одобрения). ИСТОРИЯ стала useSessionEventHistory, только для Активности, с afterSeq, который endpoint уже возвращал. Ни нового маршрута, ни нового параметра. Якорь — ХВОСТ, а не начало сессии: endpoint пагинирует вперёд, и beforeSeq не существует, но открыть ленту на событии № 1 сессии из тысяч — значит выдать не тот экран. Поэтому первая страница — то же чтение latest (ТОТ ЖЕ queryKey, дедуплицированный), и каждый клик спускается на фиксированное окно назад.
  2. Колокольчик сортирует DESC в SQL. Отсечка «прочитано» НЕ сменила семантику, и поэтому ADR нет: отсечка по seq помечает ПРЕФИКС, а ящик показывает СУФФИКС, так что «пометить 50 показанных» невыразимо без таблицы прочитанных по событию. Изменилось то, что ящик перестал скрывать то, что продвижение поглощает, — итог по проекту, + N mais antigos и кнопка, говорящая, сколько она помечает. Недостающее число получается ВЫЧИТАНИЕМ (latestSeq минус отсечка), а не запросом.
  3. Экономия RN-090/091 не регрессировала: один запрос на цикл, а первое «загрузить более старые» стоит НОЛЬ — чтение latest приносит 200, а окно показывает 100. У старой страницы staleTime: Infinity и нет refetchInterval: закрытое окно seq над неизменяемым событием не меняется.

ФАЗА 22 — ЗАВЕРШЕНА 2026-08-09 (расходы для двух аудиторий)​

Сдана как вкладка Gastos (ADR 0063, RN-101). 22. Недостающие АГРЕГАЦИИ вошли в один метод — sumGroupedBy(dimensao, escopo) с model, project, actor, session и day. provider НЕ является его измерением, и отсутствие структурное: разбивка по provider-у — это разбивка по УЧЁТНЫМ ДАННЫМ, и именно это не даёт представлению участника случайно получить эту ось — нет аргумента, который можно передать. 23. Без библиотеки графиков: дневные столбцы и рейтинг во встроенном SVG. Дневной ряд приходит из api ПЛОТНЫМ (день без расходов входит с нулём), иначе три вызова за три недели превращаются в три слипшихся столбца. 24. Коллизия решена разделением ВОПРОСОВ, а не фильтров. Отчёт по УЧЁТНЫМ ДАННЫМ остаётся исключительным для владельца (RN-060) и получил рядом разбивку workspace по модели/проекту/актору/дню, тоже owner. Представление участника — по АКТОРУ, в токенах и оценочной стоимости, без provider-а и без учётных данных — и актор берётся из аутентифицированного ТОКЕНА, без параметра, куда можно вписать id другого человека. Агент не входит в счёт участника: token_usage записывает, кто потратил, а не кто велел тратить.

Без миграции, но С измерением: на 525 тысячах строк запросы выполняются за 55 ms и 38 ms seq scan-ом, а индекс по token_usage(created_at) довёл бы их до 32 ms и 19 ms. Число — в ADR; индекс войдёт в волну, у которой будет слот.

ФАЗА 23 — ЗАВЕРШЕНА 2026-08-09 (модель, наследуемая по области)​

  1. Область area вошла в каскад (sessão > agente > área > projeto > workspace), МЕЖДУ агентом и проектом: это шаблон, который разделяют lead и субагенты, а агент расходится, перекрывая его (ADR 0064, RN-102).
  2. Несогласованность решена ДО написания кода: binding агента был ГЛОБАЛЬНЫМ (scope_id = чистый slug, :projectId маршрута игнорировался с Фазы 9a), а область — по проекту начиная с ADR 0053. Проектная область над глобальной заставила бы одного и того же агента разрешать разные модели только там, где есть область. Решение: binding агента тоже стал по проекту — scope_id для agent/area стал составным, <projectId>:<slug|chave> (RN-103), — а не область под агентом, что противоречило бы «наследуемому шаблону» (почти каждый binding сегодня — агентский).
  3. UI показывает, кто НАСЛЕДУЕТ и кто РАЗОШЁЛСЯ (AreaModelsSection и столбец Origem в ModelsSection, в ProjectSettingsTab.tsx); «вернуться к наследованию» — это DELETE binding-а агента/области, никогда не запись в него модели нижнего уровня — копирование стало бы копией, которая сама разойдётся при следующем изменении области. Роль maintainer для смены модели области (та же линейка, что у предела параллелизма, RN-083); для агента по-прежнему developer.

ФАЗА 24 — ЗАВЕРШЕНА: Chat и Criativo как места (RN-104)​

  1. Две вкладки на экране ПРОЕКТА, каждая перечисляет сессии своего kind и создаёт в этом kind, не спрашивая снова. Сессия остаётся отдельным экраном — вкладка не стала контейнером чата.
  2. Коллизия решена тем, что «Sessões» УШЛИ с линейки. От неё осталась КЛЮЧ deep-link: Chat несёт key: 'sessions', и именно это заставляет старый ?tab=sessions открываться в Chat с ОТМЕЧЕННОЙ вкладкой. Alias, разрешённый только в панели, открыл бы нужную панель и оставил линейку без выделения, потому что Tabs сравнивает active с key, а active пишет ProjectPage — один из двух файлов, которые волна держала закрытыми.
  3. Вердикт по «Iniciar ideação»: она ОСТАЁТСЯ. Половина проблемы уже закрылась в ФАЗЕ 20 (кнопка исчезла из консультативной сессии, RN-097), а другая половина не была избыточностью: именно она приводит Criativo, и с этого момента тратится ключ владельца (RN-058). Неверным было МЕСТО — приглашение тратило абзац, указывая на topbar. Теперь действие живёт ВНУТРИ приглашения, пока оно на экране, и возвращается в topbar, когда его нет; conviteVisivel — единственный вопрос, который они разделяют, чтобы оно не появлялось дважды и не исчезало.

ФАЗА 25 — ЗАВЕРШЕНА С ОТСЕЧЕНИЕМ: контейнер на проект (граница перестаёт быть только политикой)​

Самое большое архитектурное изменение программы, и то, что гасит долг, на который Фазы B и F уже указывали по отдельности. Сданы 25a (решение Arquiteto и шлюз) и 25c (граница внешнего эффекта); 25b (жизненный цикл контейнера) остался ОТСЕЧЁННЫМ и объявленным — не реализованным наполовину (ADR 0065, RN-105 и RN-106). 30. ЗАВЕРШЕНО: ARQUITETO решает, какой образ поднимается для проекта, как свой артефакт (artifact.project_image в event log, версионируемый, без таблицы) — проверяемый, а не скрытая конфигурация. Пока он не решил, вкладка Code отвечает 409 и не открывается (RN-105). 31. НЕ СДАНО В ТО ВРЕМЯ, объявлено: жизненный цикл по проекту (provisioning, перезапуск, очистка, реально применяемый предел ресурсов), с worktree агента, живущим внутри контейнера. Состояние контейнера ИЗМЕНЯЕМО и требовало собственной таблицы — импровизация в event log лишь бы не ждать слота миграции породила бы исправление сразу же после. ЧАСТИЧНО ОТМЕНЕНО Волной 4/фронтом F1 ПРОГРАММЫ 28: ТАБЛИЦА состояния (project_containers, ADR 0081) появилась. По-прежнему не хватает, без смягчения, половины, которая делала таблицу хоть чего-то стоящей: ни один настоящий оркестратор ещё не вызывает Docker, и worktree агента остаётся вне контейнера. Половина «внутри агент свободен» политики терминала ЕЩЁ НЕ изменилась — ADR 0055 продолжает действовать как есть, пока контейнер не поднимется по-настоящему. 32. ЗАВЕРШЕНО В ТО ВРЕМЯ, ПЕРЕСМОТРЕНО локальным runner-ом (ADR 0102/0103, RN-418): граница внешнего эффекта. git push, PR и deploy не выходят через терминал — даже внутри области проекта, — и правило БЫЛО deny (не require_approval, из-за «sempre permitir»), с сообщением, перенаправляющим к ТИПИЗИРОВАННОМУ действию (git_push/git_merge/ pr_open), которое рождается proposed_action (RN-106, пересмотрена). deny стал АБСОЛЮТНЫМ ПРЕДЕЛОМ (безусловный require_approval, никогда не автоодобряемый) — решение владельца продукта, «sempre permitir» закрыт в источнике, чтобы не стать декоративным (см. раздел о локальном runner-е). Merge в защищённую по-прежнему ручной (RN-014), нетронут. Сеть и расходы имеют собственный вердикт в АРТЕФАКТЕ образа (network: none/egress, предел cpus/памяти/pids, который отказывает, а не молча понижает) — решено ОДИН раз, а не по команде, по той же причине, по которой Z и AD доказали, что allowlist глаголов не сходится. Само закрытие Z и AD зависит от 25b (физической стены), которая ещё не поднялась.

ФАЗА 26 — ЗАВЕРШЕНА: Code, только чтение​

  1. ЗАВЕРШЕНО (26a): GitProviderContract получил listTree и diff PR (11-я/12-я операция), capability объявлена ТОЛЬКО будучи доказанной набором тестов.

  2. ЗАВЕРШЕНО (26b): четыре маршрута code.controller.ts — дерево, файл, поиск и diff — за ЦЕНТРАЛИЗОВАННОЙ проверкой RN-092/095, с пределом и кешем в поиске (который СОСТАВНОЙ, а не операция контракта).

  3. ЗАВЕРШЕНО: экран (ProjectCodeTab/code/*), зарегистрированный в project-tabs.ts без касания router.tsx/ProjectPage.tsx. Rail (Explorador/Buscar — единственные с реальными данными), проводник, загружаемый по КАТАЛОГУ, вкладки редактора с breadcrumb, реальный поиск с filesScanned/truncated, diff PR по известному id (с patch: null, трактуемым как «нет текста», никогда «нет изменения»). Подсветка синтаксиса — СОБСТВЕННЫЙ токенизатор на регулярных выражениях (code/highlight.ts) — НОЛЬ новых зависимостей, против 15–90 KB Prism/highlight.js/Shiki для того, что нужно вкладке; три новых цветовых токена (--syntax-function/ -comment/-operator), откалиброванных на 4,5:1 против --code-bg в ОБЕИХ темах. Четвёртое состояние RN-088 — заблокировано ожидающим решением Arquiteto (RN-105) — стало RN-107, запрашиваемым ДО попытки читать код, с собственным повторным запросом каждые 15s, пока заблокировано.

    ОБЪЯВЛЕННЫЕ незакрытые пункты, без реальных данных за ними: интерактивный терминал (ФАЗА 25b, которая остаётся отсечённой, — честное пустое состояние во вкладке), blame, список PR внутри вкладки (diff достижим только по известному id, пришедшему из Одобрений) и панель Проблем/lint/тестов. Виртуализация строк тоже осталась за бортом в то время — сам handoff называет вкладку кода «самой дорогой в программе»; предел 512 KB на файл (GIT_BLOB_MAX_BYTES) до тех пор ограничивал худший случай. ЗАКРЫТО Волной 4/фронтом E2 ПРОГРАММЫ 28 (RN-239..242): только видимое окно становится узлом DOM, а миникарта переиспользует ту же токенизацию без второго прохода.

ФАЗА 26b — ЗАВЕРШЕНА: фундамент blame, навигируемые PR и богатая ветка​

Фаза началась как ТОЛЬКО слой API, и фраза «никакого UI», оставшаяся здесь, описывала состояние ПЕРВОЙ партии — она устарела и начала учить неверному. ВСЕ ТРИ UI существуют: богатый dropdown веток (пункт 38, CodeBranchPicker.tsx), переключатель blame по требованию (CodeEditor.tsx, RN-113) и список PR внутри вкладки (CodeDiffPanel.tsx), последние два сданы в v3.0.0. Три объявленных незакрытых пункта — blame, богатый dropdown веток и список PR — затронули бы ТЕ ЖЕ файлы (контракт, три provider-а, вариант использования, контроллер, контрактный набор), если бы их атаковали по отдельности параллельные агенты; решение было сдать все три вместе, разом, и оставить UI каждого трём следующим агентам без риска коллизии. 36. GitProviderContract получил blame, listPullRequests и listBranchesDetailed (13-я, 14-я и 15-я операции — RN-110/111/112), capability объявлена только доказанной контрактным набором против трёх provider-ов (local против настоящего git; github/gitlab с mock — настоящие smoke-тесты по-прежнему пропускаются без GITHUB_TEST_TOKEN/GITLAB_TEST_TOKEN в среде, та же ситуация, что в ФАЗЕ 13a). listBranchesDetailed — СОБСТВЕННАЯ операция, а не расширение listBranches (RN-112) — обогащение стоит лишнего вызова provider-а НА КАЖДУЮ ВЕТКУ, а listBranches остаётся операцией bootstrap Gitflow, который эту цену не платит. 37. Три новых маршрута в code.controller.ts (GET .../code/blame, GET .../code/pull-requests, GET .../code/branches), тот же role: viewer и та же центральная проверка пути (RN-095), что у четырёх предыдущих. apps/web/src/lib/api-client.ts/api-types.ts получили соответствующие функции и типы (getCodeBlame, getCodePullRequests, getCodeBranches) для потребления следующей волной — CodeShell.tsx и CodeDiffPanel.tsx НЕ тронуты сверх комментария, документирующего, что фундамент уже существует. 38. ЗАВЕРШЕНО: богатый dropdown веток (RN-112) — CodeBranchPicker.tsx, открываемый из CodeShell.tsx, заменяет простое текстовое поле селектором, перечисляющим каждую ветку с ahead/behind относительно ветки по умолчанию и связанным PR, если он есть (aberta/mesclada/fechada). Ref вне списка (tag или sha) остаётся достижимым — listBranchesDetailed не перечисляет эти два, поэтому в подвале dropdown есть ручное поле.

Заморозка программы: каждая фаза объявляет, чего она не делает, и самое жёсткое — у 26: ТОЛЬКО ЧТЕНИЕ кода, никакого сохранения через вкладку. Редактирование — следующая фаза, и когда она придёт, запись — внешний эффект: рождается proposed_action.

ПОСЛЕ ПРОГРАММЫ 16–26 — что использование попросило потом (RN-148)​

Тот же дух, что ПОСЛЕ ФАЗЫ 15: это не запланированная фаза, это то, что попросило ИСПОЛЬЗОВАНИЕ. Истории с одновременно ожидающим продвижением (RN-048/RN-126) превращались в N отдельных карточек на временной шкале сессии PO, когда он производил несколько за одну партию. Carousel (apps/web/src/components/ui/Carousel.tsx) — первый компонент поэлементной навигации в дизайн-системе: 2+ ожидающих становятся ОДНОЙ навигируемой каруселью (стрелки, точки, клавиатура), где «Aprovar todas» продвигает всю партию одним вызовом (promoteStories уже был пакетным, без изменения контракта); 1 ожидающая остаётся привычной простой карточкой. Решение о том, когда формируется «партия», пересматривается при каждом render — это множество ЕЩЁ ОЖИДАЮЩИХ продвижений в сессии, а не «созданных подряд без перерыва», — чтобы разрешение истории посреди карусели само пересчитывало партию, без собственного состояния для синхронизации.

Проект Local или Container (RN-169/RN-170, ADR 0072). Запрос владельца продукта: «перейти границу записи кода только в контейнере и иметь возможность писать код из папки пользователя», с вариантом свободно набираемого пути, выбранным им, с пониманием, что это работает, только будучи смонтированным в контейнер. projects получает (workspace_mode, workspace_path) в миграции 0043, с container по умолчанию — для того, кто не выбирает, ничего не меняется. ADR 0072 ПЕРЕСМАТРИВАЕТ часть 0065 и затрагивает территорию 0055, которые решили обратное направление (стену контейнера), и поэтому следствие записано без смягчения: СТРУКТУРНОЕ сдерживание join(env, coluna) — ни один повреждённый столбец не выходит из управляемого корня — перестаёт существовать для проекта Local, и остаётся защита при создании плюс лексическая повторная проверка при чтении. ФАЗА 25b остаётся отсечённой: проект Local работает в ТОМ ЖЕ контейнере, что и сегодня, изменилась только папка.

Диаграмма C4 Arquiteto (RN-149, ADR 0068). Новый результат: create_c4_diagram генерирует Context + Container (Simon Brown) в Mermaid, отображаемые в Обзоре. Уровень Container ВЫВОДИТСЯ из действующего module_map вариантом использования — никогда не перепечатывается моделью, — чтобы не открывать второй источник, расходящийся с первым; только Context (имя системы и внешние акторы) приходит из tool call. artifact.c4_diagram — артефакт без таблицы, версионируемый в event log, тот же замысел, что у artifact.project_image (ADR 0065). mermaid вошёл как новая RUNTIME-зависимость apps/web — первая такого рода (сайт документации уже использует Mermaid, но на этапе build) — изолированная за apps/web/src/lib/mermaid-render.ts с динамическим import(); vite build подтвердил, что только entrypoint загружается сразу, а тяжёлые chunk-и Mermaid остаются по требованию, и ни один из них не использует eval/new Function — закрытый CSP из ADR 0058 остался нетронутым, без изменения конфигурации.

РАУНД exp003 — ЗАВЕРШЁН (RN-150..162, PR #278–#293)​

Это не фаза: это 17 пунктов, которые ИСПОЛЬЗОВАНИЕ нашло в живой тестовой сессии над проектом exp003, исправленные пятью последовательными волнами по ОСПАРИВАЕМОМУ ФАЙЛУ — четыре из них сходились в SessionPage.tsx, что вынудило сериализовать. Важно происхождение: ни один из них не был найден тестом.

Чему научил раунд, и это стоит больше списка: самое большое исправление (RN-155) было невидимо для любого набора тестов, потому что дефект был в сравнении двух РАЗНЫХ числовых пространств, которые случайно оба number, — event.seq (без пропусков, по СЕССИИ) против action.seq (proposed_actions.seq, глобальный bigserial ВСЕЙ таблицы, общий для всех проектов и всех сессий системы и задокументированный в схеме как намеренный контраст). Порядок казался правильным, лишь пока таблица была маленькой. Исправление не выдумало поле: оно использовало событие proposed_action.created, которое ProposeActionUseCase уже записывает в ТОЙ ЖЕ транзакции, с payload.actionId, указывающим обратно.

Engine и сдержанное чтение (RN-150) — search_workspace возвращал ВСЕ совпадения, без предела: вторая настоящая причина 413 при ревью PR, после исправления read_file. Два НЕЗАВИСИМЫХ предела, потому что поиск переполняется двумя способами — количеством совпадений (SEARCH_WORKSPACE_MAX_HITS, 500, над Stream, который ПРЕКРАЩАЕТ сканировать на max_hits + 1) и байтами итогового текста (SEARCH_WORKSPACE_MAX_BYTES, 32.768). Собственные переменные, не переиспользованные из двух других: тот же класс переполнения, но расхождение одной не должно требовать трогать другие. Метка усечения адресована МОДЕЛИ (велит уточнить термин) и никогда не выдумывает итог, потому что подсчёт итога снова оплатил бы I/O, ради экономии которого существует предел.

Числа, которые лгут (RN-151) — бейдж проекта в боковой панели считал ЛЮБОЕ непрочитанное событие: exp003 показывал 392, тогда как вкладка Одобрения того же проекта показывала 8. Число, которое не соответствует ничему исполнимому при клике, хуже, чем никакое. Стало pendingApprovalsCount в уже существовавшей read model (RN-090), одной агрегацией больше в Promise.all — никогда N+1.

Прослеживаемость того, что произвёл агент (RN-152) — ветка feature/task-XXXXXXXX в dropdown вкладки Code теперь говорит, какому dev agent и модулю она принадлежит. Модуль берётся из действующего module_map ТЕМИ ЖЕ функциями, что его сгенерировали, никогда обратным regex по имени; без связи деградирует до null, а не угадывает.

Auto mode (RN-153/154, см. Соглашения) — подстановка actionType: "*". Важно, где она живёт: разрешение принадлежит РЕПОЗИТОРИЮ (findMode), а decide.ts не получил ни строки — именно это делает три абсолютных предела истинными по ПОСТРОЕНИЮ, а не ещё одним if, который однажды кто-то забудет.

Нить сессии, которая читается (RN-155..159) — упорядочивание (выше); индикатор 5s с фиксированным текстом «Reunindo informações...» вместо подстановки агента, который уже появляется в заголовке (RN-156); создание эпика/истории PO, превращающееся в компактное уведомление, а не в пузырь с тем же визуальным весом, что у настоящего ответа (RN-157); лёгкий Markdown с подсветкой в чате собственным парсером на регулярных выражениях, ноль новых зависимостей и ни одного dangerouslySetInnerHTML, причём отображается только ВЫВОД агента — то, что набирает человек, остаётся буквальным (RN-158); и «Artefatos gerados», сгруппированные по агенту с Disclosure из RN-138, каждый артефакт ведёт туда, где он живёт (RN-159). module_map/C4 остались ВНЕ по зафиксированному решению: это ДЕЙСТВУЮЩЕЕ состояние проекта, а не артефакт, датированный сессией.

Порядок полномочий (RN-160/161, ADR 0069) — «Confirmar arquitetura pronta» включается только при хотя бы 1 ПРОДВИНУТОЙ истории (недостаточно иметь захваченное правило); а принятие handoff для Dev Lead цепляет активацию выполнения, когда эффективная роль принимающего уже это разрешает. Слияние — только на клиенте: backend по-прежнему требует maintainer (RN-137), поэтому оно избавляет от лишнего клика, не сдвигая границу авторизации.

Структурированный ввод (RN-162) — ask_structured_questions у Criativo: модель объявляет вопросы в схеме, форма отображается StructuredQuestionCard, а ответ ПЕРЕИСПОЛЬЗУЕТ SendAgentMessageUseCase, а не открывает второй путь сообщения. Ответ — однократный акт: повторная отправка — 409, а не перезапись.

РАУНД exp001 — Criativo выполняет обещание (RN-163)​

Тот же дух, что у других раундов: пришло из ИСПОЛЬЗОВАНИЯ, а не из сценария. Жалоба была: «Criativo не ответил после того, как сказал, что исправит и попробует снова», и фраза буквально была в коде — run_turn_capturing/1 вызывал модель ОДИН раз, отправлял инструменты и возвращался. Результат инструмента попадал в историю в памяти, и никто его больше не читал, поэтому обещанное исправление происходило, только если пользователь присылал другое сообщение.

Criativo был единственным разговорным агентом без цикла tool use; он получил такой же, как у PO, с собственным пределом 12. Что важно сверх исправления: решать, что объявляется, стал предел, а не фиксированный текст — обещание повторной попытки входит во фразу, только когда ещё есть ход, чтобы его выполнить. Исчерпанный предел стал рассказанным agent.error (po_server в этом случае заканчивал молча, и это был следующий очевидный долг — закрытый чуть ниже RN-166), а сбой инструмента перестал быть agent.response — в event log он был неотличим от обычного ответа и не называл никакого источника, ровно то, что RN-059 закрыла для сбоя хода.

РАУНД exp001 — PO читает уже существующее, и с него спрашивают (RN-164..166)​

Это не фаза: это дефект, найденный реальным ИСПОЛЬЗОВАНИЕМ, и происхождение важно, потому что ни один набор тестов его бы не поймал. Backlog вышел с эпиком и ни одной историей — значит, без задачи, значит, выполнение застряло без какой-либо ошибки. Расследование нашло четыре наложенные причины, и первые три — одна и та же: PO не мог знать и не мог предупредить.

  1. У PO было четыре инструмента, и все — ЗАПИСИ. Контекст собирался ОДИН раз, на kickoff, из 200 последних событий СЕССИИ — с этого момента он больше ничего не перечитывал: не знал, какие правила существуют, какие он уже покрыл и что сам уже создал. Вошли listar_regras_de_negocio и listar_backlog (RN-164), обслуживаемые двумя внутренними маршрутами с областью ПРОЕКТА — и область здесь ключевая: чтение по сессии было ровно тем, что скрывало ранее захваченное правило. Они переиспользуют уже существовавшую трубопроводку (listByTypeForProject, ListBacklogUseCase, computeCoverage); не хватало маршрута, метода в клиенте и инструмента.
  2. Ничто не требовало историю после эпика, и цикл заканчивался молча. Эпик без истории стал ЯВНЫМ исходом — backlog.epic_without_story, долговечным, по образцу RN-059 (RN-165), — а предел итераций PoServer начал выдавать toolloop.limit_reached, ТОТ ЖЕ тип, что у ToolLoop, который разговорные агенты никогда не выдавали, имея собственный цикл (RN-166).
  3. Инструкция kickoff не говорила ни слова о том, что делать, когда не хватает информации, — а перед пробелом без инструкции модель выбирает между выдумыванием и остановкой. Остановка — то, что она сделала. Третий выход стал записан, с инструментом для его применения: ask_structured_questions, ТОТ ЖЕ, что у Criativo (RN-162), теперь объявленный и PO.

Два решения замысла, важные за пределами этого случая: только create_story, который удался, гасит обязательство эпика (история, отклонённая api, ничего не покрыла, и обращение с ней так, будто покрыла, — это замена одного молчания другим); а требование — по СЛУЧАЮ, сообщается один раз, никогда не тревога, повторяющаяся каждый ход, пока кто-нибудь не научится её игнорировать.

РАУНД exp001 — нить читается так, как происходит ход (RN-172/173)​

То же происхождение, что у других: ИСПОЛЬЗОВАНИЕ, а не сценарий. Четыре жалобы на экран Сессии — handoff PO появлялся НАД его последней репликой, одобрение поднималось в середину ответа Arquiteto и опускалось только когда он заканчивал, карточка одобрения «плохо свёрстана», и прокрутка не следовала за разговором.

Расследование выяснило, что упорядочивание было ВЕРНЫМ, и в этом урок раунда. RN-155 верна event log; а лог говорит, что po_server.ex#run_turn/2 выдаёт в ТОЙ ЖЕ итерации agent.response, ЗАТЕМ tool.call для offer_handoff и ТОЛЬКО ПОТОМ рекурсирует к заключительной реплике — значит, seq handoff честно МЕНЬШЕ, чем у последней реплики. То же для proposed_action.created. Исправление, следовательно, не в упорядочивании: это объявленное правило ПРЕДСТАВЛЕНИЯ (RN-172), в отдельном проходе (afundarDesfechos) ПОСЛЕ sort по seq — а не компаратор из трёх членов, который никто не смог бы обосновать через год.

То, что не даёт ходам смешиваться, — три барьера, и каждый существует из-за реального случая: ход (у границы между двумя ходами может не быть ВИДИМОГО элемента — agent.activated открывает ход и не становится элементом нити), автор (в сессии выполнения несколько агентов пишут, ни разу не дав пользователю сказать, и все остаются в одном ходе) и не-исход (два исхода подряд сохраняют порядок между собой — Infra перед Dev Lead).

RN-173 закрывает две другие жалобы, и они были одной и той же: эффект прокрутки зависел только от [events.length, streamingText], а actions — ОТДЕЛЬНЫЙ запрос — пришедшая карточка выталкивала разговор с экрана, ничего не прокручивая. Зависимость исправлена, плюс ResizeObserver над содержимым для того, до чего НИ ОДИН список зависимостей не дотягивается (сворачивание Disclosure, перекомпоновка Markdown). Защита в 120px от конца остаётся нетронутой, намеренно: нить следует за разговором, а не захватывает чтение того, кто прокрутил вверх.

РАУНД exp001 — нить говорит, кто говорит, с какой моделью и что спрашивает (RN-171/174/175/176)​

То же происхождение: ИСПОЛЬЗОВАНИЕ. Четыре жалобы на экран Сессии, и три из них разделяют один урок — продукт знал информацию и не показывал её.

Вопрос по центру, и с выходом (RN-171). Жалоба была буквальной: «sempre dê a opção de input do usuário quando ele seleciona Escreva». Модель предлагала вариант вроде «Напишите сами», а в форме негде было писать — схема ask_structured_questions не умела выразить «помимо этих, что угодно». allowOther вошёл в select со значением по умолчанию true, и асимметрия — аргумент: список, закрытый по ЗАБЫВЧИВОСТИ, стопорит разговор, и пользователь не может разблокировать его извне; список, открытый по ошибке, предлагает лишнее поле. Интерфейсный sentinel никогда не попадает в backend, а кнопка отправки по-прежнему требует всего заполненного (backend в любом случае отклоняет с 400). Блок также стал центрированным с пределом 560px от ApprovalCard и получил аватар: это был единственный элемент нити, ни к чему не выровненный.

Действие, запускающее ход, взводит индикатор (RN-174). Анимация «думает» уже существовала — не хватало ПОКРЫТИЯ. Два действия экрана запускают синхронный ход в engine и ничего не включали: ответ на форму (AnswerStructuredQuestionUseCase переиспользует SendAgentMessageUseCase) и возврат истории PO (ReturnStoryUseCase вызывает reviseStory, то есть handle_call({:revise, …})). Канал Phoenix не закрывает эту дыру: пока join ещё идёт (RN-108), у agent.status «working» нет слушателя, и он теряется.

Модель — для ВСЕХ (RN-175). Предпосылка, что дефект был у PO, не подтвердилась: четыре разговорных агента записывают modelName начиная с RN-146, с зелёным тестом. Никогда не записывал ToolLoop — путь каждого агента выполнения и gate — и чат без активного агента в api. Ни одного нового вызова: RunLlmTurnUseCase уже возвращал поле в теле. Экран перестал писать одинокое слово «modelo» (которое читается так, будто модель так и называется) и стал читаемым chip-ом; без данных он ГОВОРИТ, что они не были записаны, — угадывание по текущему binding приписало бы старому ответу модель, которой, возможно, даже не существовало, — та же ошибка, которую предотвращает замороженная цена из RN-044.

Таблица в Markdown становится таблицей (RN-176). Было возможно два выхода, и выбран поддерживать таблицу в Markdown чата, с Table дизайн-системы. Отображение artifact.module_map в нити было ОТКЛОНЕНО по уже зафиксированному решению: это ДЕЙСТВУЮЩЕЕ состояние проекта, а не артефакт, датированный сессией (RN-159), и он живёт в Обзоре. Кроме того, запрос касался таблицы внутри сообщения — и исправление служит любому агенту, который её напишет, а не только Карте модулей. Разделительная строка остаётся обязательной (GFM), поэтому проза с | никогда не становится таблицей по ошибке.

РАУНД exp001 — панель группирует, упорядочивает и говорит, чего не показывает (RN-177..181)​

То же происхождение, что у других: ИСПОЛЬЗОВАНИЕ, а не сценарий. Пять жалоб на панель контекста и лог Сессии, и урок раунда один — панель показывала срез так, будто это целое, в трёх разных измерениях одновременно: скрывала шесть типов событий, не предлагая альтернативы, читала от самого старого к самому новому и обрезала сессию на 200 событиях, не говоря об этом.

Источник как новая классификация (RN-177). ActivityKind отвечает «о каком ПРЕДМЕТЕ говорит событие» и определяет иконку и цвет; origem отвечает «из какого СЛОЯ оно пришло», и именно он делает историю читаемой — eventos, sistema, llm, harness, agente, usuario, выводимые из СУЩЕСТВУЮЩИХ данных (actor.kind и префикс type). Приоритет — то, что делает её предсказуемой, и он задан порядком if: механизм побеждает актора (tool.call принадлежит harness, кто бы ни был), а актор побеждает префикс агента (chat.message существует с обеих сторон). 5 самых свежих остаются открытыми, а остальное сворачивается по источнику, в обоих местах — в панели лога и в нити, где ось инвертирована (нить идёт по возрастанию, поэтому история оказывается ВВЕРХУ). Фильтр машинного шума по-прежнему включён по умолчанию: его причина не изменилась (116 из 193 реальных событий), изменилось то, что он стал ВЫБОРОМ, а не свершившимся фактом.

Порядок и пагинация (RN-178). Четыре раздела стали убывающими, и кнопка «Carregar mais antigos» как следствие сменила сторону — прежний аргумент с обратным знаком. Бизнес-правила свыше 5 пагинируются, причём страница разрешается через clamp, а не эффектом синхронизации.

Дерево PO (RN-179). backlog.task_created никогда не попадал в панель; теперь эпик → история → задача образуют дерево по СВЯЗИ, которую событие уже несёт (epicId/storyId), никогда по соседству в логе — узел без загруженного родителя поднимается в корень, а не подвешивается наугад.

Предел, который стал видимым (RN-180), — самый ценный пункт. Панель читала 200 событий через prop и не могла сказать, что их больше. Она стала читать пагинированную историю RN-099 (тот же queryKey, что у хвоста, НОЛЬ лишних запросов — RN-090/091), получила pager, который у ActivityFeed был всегда, но ни одно место вызова не передавало, а заметка считает, сколько не хватает, ВЫЧИТАНИЕМ по seq, как колокольчик из RN-100. Производные разделы читают baixados (всё уже пришедшее), а не окно ленты, иначе стали бы показывать МЕНЬШЕ, чем раньше.

Делегирование в нити (RN-181). delegation.completed|failed|dispensed существовали только в панели лога: gate открывался и закрывался без какого-либо знака того, что снизу была вторая попытка. Они стали компактным уведомлением в формате RN-157, с фразой из classifyEvent — той же, что в панели. Внешний контракт области не меняется (ADR 0038): нить рассказывает то, что записал lead, и не начинает адресовать субагента.

ПРОГРАММА 28 — Волна 1, фронт A: тема перестаёт быть недостижимой (RN-182..185)​

Светлая тема существовала в design/tokens.css, имела тест паритета и была описана в design/README.md так, будто это экран, — и ничто в apps/web не писало data-theme. Единственный способ её увидеть — вписать атрибут в DevTools. Оба теста контраста знали и говорили об этом: один утверждал через expect, что ТРИ пары светлой темы проваливаются, — «запись того, что наследуется, а не гарантия того, что отображается». Пока это длилось, аргумент был честным — измерять экран, который никто не может открыть, значит измерять намерение. С приходом кнопки темы он меняет знак.

Boot — это ФАЙЛ (apps/web/public/theme-boot.js), синхронный в <head>, до бандла. Handoff велит использовать INLINE-скрипт; образ раздаётся под script-src 'self' (ADR 0058), поэтому inline работал бы в pnpm dev:web и блокировался бы в production — сбой ADR 0036 с другим подлежащим. tema.ts — API, которое потребляет shell; кнопка в нём не живёт. localStorage, который выбрасывает исключение, деградирует до «рисует и не сохраняет», а неизвестное значение падает на значение по умолчанию.

Шесть токенов светлой темы изменили значение (ADR 0074), потому что самый требовательный фон светлой темы — бумажный --code-bg: accent 3,56 → 4,81, warning 3,15 → 4,98, success 3,89 → 5,12, violet 4,16 → 4,95, text-muted 2,76 → 5,17, плюс accent-hover на ступень ниже. В ТЁМНОЙ не изменился ни один, и её долг остаётся закреплённым на тех же пяти числах. Два зафиксированных побочных эффекта: пять пар, являющихся долгом в тёмной, ПРОХОДЯТ в светлой, а «известное исключение дизайн-системы» (--on-accent поверх --accent, 3,20:1) исчезает в светлой — исправление, которое описывал комментарий теста, затемнение до --terracota-500, — как раз то, что стала делать светлая.

Пять из восьми значений --syn-* из handoff были ОТКЛОНЕНЫ измерением ( худшее, --syn-cm, даёт 2,32:1 в светлой). Где handoff проваливается, действует измеренное число: это та же линейка, что и для шрифтов. Восемь ролей начинают существовать с префиксом --syntax-*; пять семантических, которые ещё раскрашивают, измеряются рядом с ними.

ПРОГРАММА 28 — Волна 1, фронт D0: расходы по provider-у (RN-186..188)​

Сдерживание сменило природу, и важно именно это — а не новая ось. provider снова стал измерением sumGroupedBy, пересматривая ADR 0063 по решению владельца продукта. Раньше участник не дотягивался до разбивки по УЧЁТНЫМ ДАННЫМ, потому что её не существовало, — ADR 0063 написал «нет аргумента, который можно передать». Теперь она существует, и сдерживает ТИП: две перегрузки, и область, которая несёт actor (единственная в представлении участника), принимает только Exclude<SpendDimension, 'provider'>. Гарантия перешла от «невозможно выразить» к «невозможно скомпилировать», что слабее, — и именно поэтому барьеров ДВА, и они независимы: у маршрута участника тоже нет параметра измерения. Ни одного if по комбинации, намеренно (тот же урок, что у RN-153/154), а доказательство — ИНВЕРСИЕЙ: без @ts-expect-error tsc указывает TS2769 на точной строке.

porOwner/porAgente — разбиение porAtor по actor_kind, без лишнего запроса; system остаётся вне обоих и по-прежнему входит в итог. Индекс по token_usage(created_at), который ADR 0063 измерил и отложил, вошёл в миграцию 0044. ЭКРАН не изменился — это более поздняя волна.

ПРОГРАММА 28 — Волна 2, фронт H2: рейтинг моделей без выдуманной оценки (RN-210, ADR 0077)​

Handoff просил двух вещей, зависевших от ОДНИХ И ТЕХ ЖЕ данных, которых у продукта нет: оценку качества по способности (mock: «код → claude-sonnet-4 / qwen2.5-coder:14b (9.4)») и бейдж «ideal» в ModelPicker, когда модель покрывает «все способности, требуемые агентом». Расследование дважды подтвердило, что этих вторых данных не существует: сам ModelsSection уже переименовал столбец «Agente · capacidades» просто в «Agente» по этой причине, а ModelPicker binding-а агента читает GET /projects/:id/models (роль viewer) — курирование (uses, ADR 0051) существует только в ModelComCuradoria, из маршрута только для maintainer. Бейдж НЕ был построен; таблица «Melhores modelos por capacidade» БЫЛА, с двумя реальными сигналами: сколько агентов проекта уже разрешаются через каскад в каждую курированную модель, и стоимость каталога как тай-брейк — никогда вычисленная оценка.

ПРОГРАММА 28 — Волна 2, фронт B: боковая панель сворачивается (RN-195..201)​

264px/62px с дорожкой иконок при свёртывании; раскрываемые проекты, показывающие вкладки (читаемые из project-tabs.ts, никогда не захардкоженные); раздел Активность, сгруппированный по базовому агенту/НАСТОЯЩЕМУ экземпляру — суффикс -2 из extraDevAgentId, никогда выдуманный счётчик -01/-02 (находка Волны 1/фронта B0), — с областью проекта текущего маршрута, а не всех, чтобы не открыть N+1 по событиям. Кнопка темы в подвале; два инертных глобальных пункта уходят, только Проекты и Активность глобальны; автосвёртывание вкладки Código через AutoCollapseContext (sidebar-state.ts), без записи предпочтения. Сохранение централизовано в sidebar-state.ts, шесть ключей brabo.* из handoff.

Два расхождения с handoff задокументированы и НЕ решены (RN-197): бейдж проекта остаётся pendingApprovalsCount (RN-151 побеждает, она свежее), а цвет идентичности из handoff появляется только на свёрнутой дорожке — развёрнутая строка сохраняет только существующую точку статуса (RN-039), две точки были бы шумом.

ПРОГРАММА 28 — Волна 2, фронт C: рамка экрана (RN-202/203, ADR 0078)​

Четыре буквальных исправления из чек-листа «moldura de tela» handoff-а: .headerTop получил min-height: var(--header-h) — ПОЛ, а не потолок, потому что заголовок проекта (идентичность + компактный TokenMeter) богаче общего заголовка 6 внутренних экранов handoff-а, и принудительные 60px обрезали бы предупреждение о бюджете — тот же класс ошибки, который запрещает RN-088; активная вкладка с box-shadow: inset 0 -2px 0 var(--accent) вместо border-bottom; горизонтальная прокрутка в линейке; контейнер содержимого с max-width: 1040px во вкладках документального вида (не в semRespiro). Значения отступов линейки, жившие как CSS-override потомка в ProjectPage.module.css с ФАЗЫ 16, переехали в Tabs.module.css, теперь с тем же владельцем.

Часть, требовавшая решения, а не только CSS: handoff перечисляет 7 вкладок проекта, а реестр (project-tabs.ts) — 10. executores (RN-121), backlog (RN-048) и insights (гипотезы Psicólogo) родились ПОСЛЕ handoff-а, с реальными данными и собственной RN, и ОСТАЮТСЯ — handoff — эталон ВИЗУАЛЬНОЙ точности, а не потолок того, сколько вкладок у продукта (RN-203, ADR 0078). А ключ sessions по-прежнему подписан «Chat», никогда «Chat RAG», как просит более свежий handoff (RN-202): «Chat RAG» описывал бы функциональность, которой не существует — контракт embeddings готов (ADR 0075), но ничто его ещё не потребляет, без конвейера индексации и без UI цитирования, — и переименование вкладки сегодня было бы той же ошибкой, которую ADR 0042 уже отвергает для модели каталога: объявлять способность до того, как она существует. Ни одна ФОРМА экспорта не изменилась (AbaDoProjeto, ABAS_DO_PROJETO); только метка code сменилась с «Code» на «Código».

ПРОГРАММА 28 — Волна 2, фронт H1: видимый фокус (ADR 0036)​

Select, Modal (кнопка закрытия) и ProjectCard получили :focus-visible с той же откалиброванной обработкой, что в Input.module.css, включая блок forced-colors: ни у одного из трёх не было индикации фокуса, достижимой только с клавиатуры. Кнопка закрытия Modal выросла с 30px до 32px, минимальной цели касания на десктопе. Table и Badge прошли аудит и НЕ потребовали изменений — ни один из двух не открывает собственной интерактивной возможности: строка Table — чистое представление (тот, кому нужна кликабельная строка, уже использует <button>/<a> внутри ячейки через render), а Badge нигде в продукте не используется с onClick.

ПРОГРАММА 28 — Волна 3, фронт H3: вкладка Criativo получает KPI, не изображая несуществующих данных (RN-227..230)​

Handoff просит 4 значка статуса для 5 реальных состояний сессии (created/active/closing/closed/closed_abnormally). closed_abnormally→ прервана и created→ожидает — прямые; closing НЕ был слит с «закрыта» — он получает собственный значок («encerrando», тон accent, пульсирующий), потому что в closing исход ещё неизвестен, и назвать её «закрытой» было бы ложью об этом. Pill-фильтры (только 4, по той же причине) группируют created и closing по ТРАЕКТОРИИ, а не по внешнему виду: created попадает в «Ativas» (ещё в игре), closing — в «Fechadas» (на пути к закрытию без ошибки) — значок строки фильтр никогда не переписывает, меняется только группировка. Из 4 KPI два честны отсутствием данных: «стоимость месяца» ПЕРЕИСПОЛЬЗУЕТ getMySpend (тот же queryKey, что у вкладки Gastos, представление участника/RN-101) и никогда не показывает итог проекта — это данные владельца (porProjeto), а вкладку Criativo видит любой участник; «доля идеация → commit» ОБЪЯВЛЕНА отсутствующей (показывает «—»), потому что в продукте сегодня нет связи между сессией и commit — выдумывать расчёт было бы тем же классом ошибки, который ADR 0042 уже отвергает для оценки модели.

ПРОГРАММА 28 — Волна 4, фронт G2: Chat RAG получает конвейер индексации и гибридный поиск (RN-231..238, ADR 0080)​

Поверх таблицы chunks из Волны 3 (ADR 0079): РУЧНАЯ индексация (без watcher, POST .../rag/reindex, идемпотентная полная пересборка — удаляет область/сессию перед воссозданием) трёх честных областей — docs/adr через ReadProjectCodeUseCase (те же учётные данные/шлюз/проверка пути, что у вкладки Code) и session только из chat.message/agent.response. Чанкинг по заголовку/абзацу, 1200 символов с перекрытием 150 — задокументирован как отправная точка, а не калибровка по реальным данным (пока не существует корпуса вопросов, прогнанного по индексу). Гибридный поиск — это ДВА независимых запроса (вектор через HNSW, лексический через GIN), слитых взвешенной суммой (0.6/0.4) и обрезанных по порогу (0.2) — числа тоже отправная точка. Когда provider embedding (фиксированный: ollama) не отвечает, конвейер записывает чанки БЕЗ вектора и объявляет пробел в отчёте — никогда не изображает полную индексацию, а поиск деградирует до чисто лексического с предупреждением. Экран (Chat RAG как отдельная вкладка) — Волна 5; этот фронт — только backend.

ПРОГРАММА 28 — Волна 4, фронт F1: жизненный цикл контейнера становится таблицей, без оркестратора (RN-243..248, ADR 0081)​

См. пункт 31 ФАЗЫ 25 (ЧАСТИЧНО ОТМЕНЁН): project_containers (миграция 0046) записывает изменяемое СОСТОЯНИЕ — provisioning → running ⇄ stopped, failed достижим из всех трёх, из removed выходят только повторным provisioning — отличное от artifact.project_image (неизменяемого РЕШЕНИЯ Arquiteto). Подтверждено расследованием (grep, ноль вхождений): НИ ОДИН сервис продукта не монтирует /var/run/docker.sock и не работает privileged — оба варианта использования только ЗАПИСЫВАЮТ и ЧИТАЮТ, ни одного настоящего вызова Docker. Первый переход требует уже решённого образа (RN-105) и ЗАМОРАЖИВАЕТ версию/ресурсы в строке; проект local (ADR 0072) отклоняется, потому что не поднимает собственный контейнер. Предел ресурсов (cpus/memory_mb/pids_limit) ОБЪЯВЛЕН, не применён — ни один экран не утверждает «контейнер ограничен X», только «зарегистрированным намерением было X». Ни одного нового HTTP-маршрута в то время: без реального потребителя открывать его значило бы угадывать контракт — закрыто Волной 5/фронтом F2 чуть ниже.

ПРОГРАММА 28 — Волна 5, фронт G3: Chat RAG получает собственную вкладку (RN-252..254, ADR 0082)​

Экран, который RN-202 (Волна 2/C) отложила — «Chat RAG» описывал бы способность, которой ещё не было, — приходит вместе с backend-ом Волны 4/G2: key: 'rag', НОВАЯ вкладка (ordem: 28, между Código и Backlog), НИКОГДА не переименовывающая sessions. Различие осталось тем же, изменилась только причина: sessions — разговор с АКТИВИРОВАННЫМ агентом (тратит ключ владельца за ход, RN-058); rag — поиск по уже построенному индексу, без какого-либо агента посередине, только чтение по природе. Экран потребляет три контракта ADR 0080, не угадывая форму, и показывает две честные деградации, которые backend уже объявлял и ни один экран не читал, — vectorAvailable: false становится видимым предупреждением, а покрытие никогда не выдумывает «переиндексировано X мин назад». Цитата с источником session ведёт к точному СОБЫТИЮ, переиспользуя механизм, который уже используют chip-ы улик Psicólogo; источник file показывает путь/заголовок как текст, без ссылки — у вкладки Código сегодня нет deep-link по пути, это вне объёма здесь.

ПРОГРАММА 28 — Волна 5, фронт F2: настоящий потребитель жизненного цикла контейнера (не терминал) (RN-267/268, ADR 0083)​

Исходным планом этого фронта был полноценный интерактивный терминал. Расследование подтвердило, что ФАЗА 25b остаётся отсечённой: ни один сервис не монтирует /var/run/docker.sock, и даже после ADR 0081 ничто в production не переводит project_containers — не существует настоящего контейнера, чтобы открыть терминал ВНУТРИ него. Реализация притворного терминала или терминала, работающего в том же контейнере, что и монорепозиторий Brabo (долг, который ADR 0055 уже описывает как политику, а не изоляцию), выдумала бы способность — та же ошибка, которую ADR 0041/0042 отвергают для provider-а LLM и модели каталога. Настоящая сдача: GET /projects/:projectId/container/lifecycle (role:viewer), первое HTTP-представление жизненного цикла (пересматривает ADR 0081, который отложил маршрут из-за отсутствия потребителя), и вкладка Terminal, показывающая это состояние — переведённый бейдж, причину сбоя — ПОД уже существовавшим поясняющим текстом, никогда вместо него, запрашиваемое только пока вкладка открыта. null (ни разу не подготовлен) — честный и самый частый сегодня результат.

ПРОГРАММА 28 — Волна 5, фронт I: социальный вход отменяет запрет из backlog ADR 0031 (RN-272..283, ADR 0084)​

Явный запрос владельца продукта, осознающего последствия для безопасности — запрет снимается только для GitHub/GitLab; MFA, OIDC provider и общая федерация остаются вне объёма (см. «O que NÃO fazer»). SocialLoginCallbackUseCase заканчивается в ТОМ ЖЕ EmitirSessaoUseCase, что и вход по паролю, — никакого второго формата сессии. Переиспользует существующий клиент OAuth, но у state СОБСТВЕННОЕ назначение (purpose: 'social_login', проверяется РАНЬШЕ любого другого поля): он никогда не принимает state потока ПОДКЛЮЧЕНИЯ git к проекту, что было бы эскалацией привилегий. Новая таблица social_identities (миграция 0047), ключ (provider, provider_user_id) — никогда e-mail/логин. Решение в три шага: известная идентичность → вход; e-mail совпадает с существующей учётной записью И подтверждён provider-ом → связывает (и подтверждает e-mail учётной записи, если он ещё не был); e-mail совпадает, но НЕ подтверждён → отказ — набранный e-mail не доказывает владения, принятие открыло бы account takeover; без соответствующей учётной записи → создаёт новую учётную запись без пароля, в том же состоянии «pendente», что и при миграции из Keycloak. Переиспользует ТОТ ЖЕ OAuth app подключения git — ноль новых переменных окружения, только разные redirect_uri/scope на поток; действие ОПЕРАТОРА по регистрации второго callback у provider-а — то, что оправдывает ветку breaking/.

Аудит fluxo.yml × код — план Dev Lead становится настоящим одобрением (RN-284, ADR 0086)​

Это не запланированная фаза: она закрывает расхождение, которое нашёл аудит только для чтения docs/fluxo.yml × код (находка A2, docs/explanation/auditoria-fluxo-vs-codigo.md) — поток уже объявлял выход plano-de-paralelismo у dev-lead как via: proposed_action начиная с ADR 0085, а код так и не был приведён в соответствие. Владелец продукта решил, что ошибался код.

propose_execution_plan вошёл в decide.ts (минимальная роль maintainer), НАМЕРЕННО вне блока абсолютных пределов — его можно настроить на автоодобрение, в отличие от parallelize/raise_max_parallel. Dev Lead — ПЕРВЫЙ разговорный агент (все выполняют синхронный ход через GenServer.call до 180s), который приостанавливается в ожидании человеческого решения — шаблон уже существовал для dev agent (ADR 0052) и gate-ов QA/Infra (ADR 0057), но оба запускаются через cast и никогда не имели дела с ожидающим синхронным from. TurnoAssincrono.tratar_resultado/2 получил ветку: при наличии ключа :aguardando_aprovacao (проверяемого по ЗНАЧЕНИЮ, а не по наличию — Dev Lead несёт его nil начиная с init/1) отвечает на from так же и в то же время, что всегда, но выдаёт только agent.status: awaiting_approval вместо agent.done — ход не закончился. LiveBroadcast.agent_status/4 получил новый статус в guard. Возобновление использует ТОТ ЖЕ Engine.Dev.Wake, который QaLeadServer уже переиспользует для субагентов QA (доставка — по АГЕНТУ, а не по типу).

Принятый, объявленный пробел: без собственной таблицы состояния перезапуск engine, пока Dev Lead приостановлен, теряет подписку — решение остаётся записанным и видимым в Одобрениях, но он не рассказывает исход сам. Закрытие этого потребовало бы того же механизма сохранения, что у ADR 0052; вне объёма этого исправления, которое лишь приводит поведение к тому, что docs/fluxo.yml уже объявлял.

UX Designer — пятый разговорный агент, досрочно (RN-285..287, ADR 0087)​

Это не сработавший триггер разделения — docs/fluxo.yml всегда объявлял «когда у УПРАВЛЯЕМОГО проекта будет собственный интерфейс для проектирования» критерием, и он не сработал: дизайн-система остаётся статичным входом. ОСОЗНАННОЕ решение владельца продукта всё равно ввести роль досрочно.

Engine.Agents.UxDesignerServer отражает DevLeadServer — GenServer на сессию, предел 14 итераций, активируется handoff-ом accepted, адресованным «ux-designer», через уже существующий ОБЩИЙ механизм (ни одна строка не изменилась в ActivateAgentUseCase). SOLO: без области, без субагентов. Kickoff читает самый свежий artifact.product_brief — ту же «бизнес-потребность», которую производит Criativo, без нового артефакта, — а дизайн-система (design/tokens.css, design/COMPONENTS.md) ОПИСАНА в идентичности агента (Engine.Harness.Agents), статичный текст: у разговорных агентов нет инструмента чтения файлов репозитория.

propose_prototype — ЕДИНСТВЕННЫЙ инструмент (personas, jornadas, prototipo с telas/anotacoes, resumo). Записывает artifact.prototipo_navegavel без выделенного варианта использования в api — в отличие от choose_project_image/create_c4_diagram, которым он нужен, потому что у них есть содержимое, ВЫВОДИМОЕ из другого артефакта, или общий доменный отказ; ни одна из этих причин здесь не действует, поэтому проверка формы живёт в engine (ArtifactSchemas), а запись использует общий append_event_returning, который api уже открывает, — тот же путь, что artifact.product_brief. Один артефакт, два handoff-а по нему — для «po» и для «dev-lead», — никогда второй артефакт для «spec-visual»: прототип (экраны + аннотации) И ЕСТЬ визуальная спецификация, а его дублирование рисковало бы расхождением двух копий потом. Переиспользует половину замысла ADR 0086, которая выживает без его приостановки: успешный propose_prototype завершает ход, чтобы модель не предлагала снова и не дублировала артефакт, — без «aguardando_aprovacao», потому что предложение прототипа не имеет внешнего эффекта.

apps/web/src/lib/agents.ts получил запись (color: var(--accent), наименее переиспользуемый семантический токен roster; icon: PencilIcon). uxDesignerActive вошёл в roster в ОБОИХ источниках, которые RN-090 требует синхронизировать, — панели команды (agent-status.ts) и карточке dashboard (projects-summary.repository.ts, тот же запрос, что infraActive, расширенный, без нового query).

Вне досягаемости, объявлено: teste-de-usabilidade требует настоящего пользователя-человека — ни один агент это не заменит. metricas-de-uso остаётся пробелом даже при analytics active (ADR 0089): отчёт о воронке измеряет сессия→commit→PR→merge, а не принятие функций КОНЕЧНЫМИ пользователями проекта, который строит Brabo, — «улики принятия по функции» ОБЪЯВЛЕНЫ метрикой без пути к существованию сегодня, а не незакрытым пунктом, который закроют в следующем раунде.

Staff — код готов, спит для автоматического запуска (RN-305/306, ADR 0088)​

Это не запланированная фаза: docs/fluxo.yml объявляет Staff/Principal Engineer как status: planned начиная с ADR 0085 («контракт готов, активация решена, ждёт триггера»). ОСОЗНАННОЕ решение владельца продукта: написать КОД заранее, зная, что АВТОМАТИЧЕСКИЙ триггер (Anamnese, замечающая ПОВТОРЯЮЩУЮСЯ системную проблему) не сработает — Anamnese на паузе (ANAMNESE_ENABLED=false, решение от 2026-08-10). Спит для автоматического запуска, но не для РУЧНОГО вызова.

Шестой solo-разговорный агент (Engine.Agents.StaffServer), рядом с Criativo/PO/Arquiteto/Dev Lead/UX Designer, отражающий Arquiteto (ограниченный цикл с пределом 14) с двумя отличиями: БЕЗ kickoff/1 (нет артефакта сессии для резюмирования — поднимается и простаивает до первого user_message, которым тот, кто адресовал handoff, объясняет проблему) и активируется ОБЩИМ путём canActivateAgent (handoff accepted, адресованный «staff»), не входя в USER_STARTED_AGENTS — расследование подтвердило, что никаких доменных изменений в api не требовалось, потому что assertHandoffTargetAllowed отклоняет только субагента области, а у Staff нет области. Единственный инструмент, propose_rfc (проблема, варианты с компромиссами, рекомендация, одноразовый PoC), записывает artifact.rfc_staff НАПРЯМУЮ через append_event_returning — тот же бестабличный шаблон, что у emit_insight, а не у artifact.c4_diagram (который выводит уровень Container из module_map в api), — и возвращает handoff Arquiteto в ТОМ ЖЕ tool call, без proposed_action (регистрация документа — не внешний эффект).

staffActive вошёл в roster панели команды и в карточку dashboard (ProjectCardSummary.roster), по тому же критерию, что infraActive, чтобы не открыть расхождение, о котором уже предупреждал комментарий RosterFacts. Объявлено, не скрыто: SessionPage.tsx/AGENTES_DE_CHAT не тронуты — тот же уже принятый шаблон, что и для infra (НАСТОЯЩИЙ и АКТИВНЫЙ lead тоже вне этого списка). Сквозной путь использования сегодня — внутренний маршрут (POST .../agent/message, agent: "staff"), а не экран Сессии; общий UI «ручного handoff выбранному агенту» остаётся в backlog.

Активный gate implementavel — QA-стратегия как второй момент qa-lead (ADR 0090)​

Осознанное решение владельца продукта досрочно ввести триггер, который docs/fluxo.yml уже предусматривал («когда gate implementavel активируется»), — триггер ЗДЕСЬ — сама работа по построению механизма, а не симптом использования, ждущий своего часа. docs/gates.yml, implementavel: status: planned → active (владелец dev-lead, severidade: warn не тронута — реестр уже говорил «рождается warn, даже когда активируется»).

QA-стратегия перестаёт быть ролью proposto: это САМ qa-lead во втором МОМЕНТЕ — тот же процесс, результат отдельный от вердикта PR, ровно как docs/fluxo.yml уже объявлял («может быть сам qa-lead во втором МОМЕНТЕ, не обязательно новый агент: разделение — по результату»). Engine.Gates.QaLeadServer.run_design/3 — НОВАЯ и ДОБАВОЧНАЯ точка входа (без касания run/2, привычного пути, привязанного к DevAgentState.find_by_task_id), вызываемая через Engine.Gates.Dispatcher.run_qa_estrategia/3 (та же заменяемая в тесте косвенность, что уже используют run_qa/2/run_secops/2).

Engine.Gates.QaEstrategiaAgent — модуль БЕЗ СОСТОЯНИЯ (не GenServer), той же формы, что QaPerformanceSegurancaAgent, — реестр без Terminal (ReadFile, SearchWorkspace, EmitPlanoDeTeste), — но КОНТЕКСТ другой: Engine.Gates.QaEstrategiaContext.fetch/3 получает ТОЛЬКО story (из list_backlog) и действующий module_map (из get_infra_context, переиспользуемого только ради поля moduleMap — ноль новых маршрутов), без dev_state и worktree_path, потому что gate работает PRE-DEV: ещё нет ни dev agent, ни worktree, ни task_id. Ни один из трёх инструментов не проходит через ActionPipeline (проходят только terminal/write_file), поэтому этот агент НИКОГДА не приостанавливается — run_design/3 выполняется синхронно внутри собственного handle_cast. Предел итераций остаётся 8 (разговорный), а не 60 (gate) — НАМЕРЕННО: без token_budget_micros под ним (нет task, PRE-DEV) — та же причина, по которой infra-workflows остаётся на 8, даже используя инструмент (RN-085). Результат (emit_plano_de_teste: синтез, исполнимые критерии, ОБЩАЯ стратегия автоматизации без фреймворка) становится artifact.plano_de_teste в event log вызвавшей сессии.

assess_implementability, новый инструмент Dev Lead, читает самый свежий artifact.plano_de_teste story в истории СОБСТВЕННОЙ сессии: если плана ещё нет, запускает оценку и возвращает ошибку с просьбой повторить (ошибка инструмента — ВХОД цикла, а не конец пути — RN-163); при наличии плана предлагает заключение (implementavel/inviavel + обоснование, план встроен в payload) как proposed_action, по ТОМУ ЖЕ шаблону трёх исходов, что propose_execution_plan (ADR 0086) — maintainer, НАМЕРЕННО вне блока абсолютных пределов decide.ts.

appsec перестаёт быть proposto — второй момент SecOps входит в следующем разделе.

appsec получает второй момент secops (RN-360/361, ADR 0090)​

docs/fluxo.yml (id: appsec, camada_seguranca) заранее объявлял «тот же шаблон, что у QA: два МОМЕНТА, а не два агента пока» — осознанное решение владельца продукта ввести активацию досрочно, не дожидаясь gate implementavel (фронт qa-estrategia, тот же концептуальный ADR 0090), который сам реестр называл триггером.

Engine.Gates.SecOpsAgentServer.run_design/2 выполняется в ТОМ ЖЕ процессе, что secops PR (тот же ключ Registry), без каких-либо Diff/Scanner/ DevAgentState: получает story из backlog + действующий module_map (Engine.Gates.AppSecContextBuilder, без dev_state/worktree_path) и вызывает Engine.Gates.AppSecAgent.run/3 — модуль БЕЗ СОСТОЯНИЯ (не GenServer, той же формы, что QaPerformanceSegurancaAgent), реестр инструментов БЕЗ Terminal, выполняющий чек-лист STRIDE-lite через ToolLoop.run/1 над ПРОЕКТОМ story, никогда над кодом. Заканчивает выдачей artifact.threat_model и созданием handoff для трёх объявленных lead-ов (arquiteto, dev-lead, infra — АДРЕСУЕМЫЙ агент id area-infra, RN-361).

Объявленный пробел, а не баг: run_design/2 ВЫЗЫВАЕМ, но ничто пока не вызывает его само — естественный триггер — assess_implementability от Dev Lead (фронт qa-estrategia), вне объёма этой сдачи, которая была сохранена самодостаточной (decide.ts/docs/gates.yml/dev_lead_tools.ex не тронуты).

analytics/delivery-metricas становятся отчётом (RN-320..322, ADR 0089)​

Осознанное решение владельца продукта ДОСРОЧНО ввести две роли целевой модели (docs/fluxo.yml, status: proposto), не дожидаясь органического триггера, который каждая уже объявляла. Форма — та, которую сам поток предписывал: analytics «поглощён medicao», пока метрика ПРОДУКТА не станет обязательным входом PO, delivery-metricas «никогда не становится агентом — становится ОТЧЁТОМ medicao». Обе становятся одним СКРИПТОМ — apps/api/scripts/analise-funil.ts (pnpm --filter api analise:funil -- --projeto <uuid> [--json]), в ТОМ ЖЕ формате, что medir-execucao.ts (Фаза 13b): чистое чтение через Drizzle, ноль записи, без GenServer, без агента LLM.

Измеряет ПО-НАСТОЯЩЕМУ, по proposed_actions.execution_result трёх git-действий dev agent (git_commit/pr_open/git_merge, только status: 'executed'): настоящую воронку (сколько сессий дали commit / открытый PR / смерженный PR, и конверсию между этапами — считает СЕССИИ, а не действия), настоящий lead time (от первого commit до первого merge сессии, по updated_at ВЫПОЛНЕНИЯ, а не предложения) и настоящий deployment frequency (merge в ветку PROTECTED_BRANCHES, по дням, со ссылочной сверкой с gate backmerge из docs/gates.yml — чья улика — CI, вне досягаемости скрипта, который только читает базу).

Три метрики остаются в разделе «Não medido, de propósito» — ПОСТОЯННОЕ объявление, а не пробел, закрываемый в следующем раунде: полная продуктовая воронка идеация → commit (sessions без storyId, RN-230, потребовала бы новой схемы — ни одной миграции в этом фронте); улики принятия по функции (это не недостающие данные для сбора — Brabo не инструментирует проекты, которые он СТРОИТ, и нет никакого пути к существованию этой телеметрии сегодня); MTTR и change failure rate (требуют сигнала настоящего ИНЦИДЕНТА production, та же зависимость, что у secops-runtime/platform — другой фронт).

secops-runtime как скрипт-отчёт (RN-375..377, ADR 0091)​

Решённое владельцем продукта досрочное введение роли secops-runtime (docs/fluxo.yml, camada_seguranca, ранее proposto), не дожидаясь объявленного триггера («production с реальным трафиком, после DEPLOY_ENABLED + активного platform»). Входит только то, чего триггер НЕ требует: pnpm --filter api relatorio:seguranca-runtime читает rate_limit_hits — данные, которые RateLimitGuard из ADR 0027 уже записывает, в том числе при трафике dev/CI, — и производит рейтинг корзин (user:<uuid>/ip:<endereço>, два единственных записываемых формата; без маршрута и причины, которых таблица никогда не хранила) и временное распределение по фиксированным срезам. Это СКРИПТ, а не агент LLM и не GenServer: по данным нечего решать, только агрегировать.

Автоматическое обнаружение инцидентов, реагирование на инцидент и постмортем безопасности остаются ВНЕ — они зависят от того же несработавшего триггера, — и отчёт перечисляет их в ПОСТОЯННОМ разделе «não medido», не симулируя примерный инцидент и не выдумывая числа (тот же принцип, что у ADR 0041/0042/0077). Хранимое окно коротко по замыслу (DomainGaugesCollector.pruneRateLimit удаляет hits старше 2 × RATE_LIMIT_WINDOW_MS, 240s по умолчанию), и отчёт объявляет оба окна — НАСТРОЕННОЕ и НАБЛЮДАЕМОЕ, — никогда не давая второму выдать себя за историю длиннее, чем она есть.

platform получает первую сдачу: отчёт телеметрии по требованию (RN-385/386, ADR 0092)​

Осознанное решение владельца продукта досрочно ввести часть роли platform (docs/fluxo.yml, camada_plataforma) — чей status остаётся planned и чья ativacao остаётся синхронизированной с DEPLOY_ENABLED, которого не существует. pnpm --filter api relatorio:telemetria [--projeto <uuid>] [--json] — это СКРИПТ (не агент LLM, не GenServer), который читает по требованию ТЕ ЖЕ вопросы, которые DomainGaugesCollector уже собирает для scrape Prometheus, — активные/closing сессии по проекту, заблокированные tasks по проекту, состояние последнего бэкапа (всегда ГЛОБАЛЬНОЕ, никогда по проекту, потому что продукт делает бэкап всей базы), — и завершается после печати. SQL-запросы ПОВТОРЕНЫ, а не импортированы из коллектора: его методы приватны и заканчиваются записью в gauge Prometheus, без чистой половины «только запрос», которую можно было бы переиспользовать, не привязывая CLI-скрипт к жизненному циклу @Injectable.

Вывод всегда содержит «где посмотреть больше» (версионируемые dashboards в deploy/k8s/observability/dashboards/*.json, алерты в deploy/k8s/observability/alerts/brabo-alerts.yaml, docs/runbook.md #observabilidade, pnpm dev:obs для локальной наблюдаемости) — ссылка, никогда дублирование — и раздел «não medido», который ОБЪЯВЛЯЕТ, не выдумывая: ни один числовой SLO в продукте не определён, постмортем зависит от реального инцидента, которого не было, а телеметрия, возвращающаяся в продукт по замкнутому циклу, — то, что сделало бы platform active, — триггер (DEPLOY_ENABLED) по-прежнему отсутствует. gate_saida: { id: operavel, status: planned } роли не меняется; docs/fluxo.yml лишь получает nota в выходе telemetria-consolidada, говорящую, что ручная/по требованию версия уже реальна.

dbre становится двумя механическими скриптами (RN-400..403, ADR 0093)​

Из роли dbre в fluxo.yml только два из четырёх результатов зависят от реального объёма данных (plano-de-capacidade, tuning — остаются пробелом, без срока). Двум другим реальная нагрузка не нужна: parecer-de- migracao — распознавание шаблонов в SQL-тексте (apps/api/scripts/lint-migracao.ts, обходит apps/api/src/db/migrations/ *.sql, сигнализирует DROP TABLE/TRUNCATE/DROP COLUMN/ALTER COLUMN ... TYPE/ADD COLUMN ... NOT NULL без DEFAULT, != 0, если что-то найдено, — сегодня находит 3 вхождения в уже смерженных и принятых миграциях, информативно, не исправлены попутно); backup-restore-testado уже был реальным с Фазы 5 (CronJob + backup_runs + выполненный make test-restore, RTO ~40s) — не хватало лишь отчёта по требованию (relatorio:backup, то же чтение, что у DomainGaugesCollector.collectBackup()). Ни один из двух пока не входит в CI: линтер обходит весь репозиторий, а не diff PR, и стал бы gate-ом, проваливающим PR из-за находки, которая ему не принадлежит, — см. ADR 0093 о технике области (pr-police.ts), когда это изменится. Правило «одна миграция на волну» (meta/_journal.json) перестало называться «механизированной версией» этой роли: оно предотвращает конфликт снимков между параллельными агентами — забота, ортогональная «в этом SQL есть рискованный шаблон».

Аудит fluxo.yml × код — Волна 1: fluxo.yml/gates.yml в актуальном состоянии​

Первая из шести волн плана аудита (раздел D, docs/explanation/auditoria-fluxo-vs-codigo.md), которая закрылась, — Волны 3, 4 и 5 уже были введены досрочно вне порядка (analytics/DORA, gate implementavel, QA-стратегия/appsec); эта — только метаданные, без ADR, без строки production-кода.

docs/gates.yml (paralelismo-autorizado): status: planned → active, с evidencia в ТОМ ЖЕ формате, что у соседнего gate implementavel — event_types: [proposed_action.created/approved/denied], filtro: { actionType: parallelize }, onde: request-parallelization.use-case.ts (находка A1/B5 — fluxo.yml уже говорил ativo начиная с ADR 0053/ФАЗЫ 14d, отстал только gates.yml). docs/fluxo.yml получил ещё три исправления аудита, все — цитирования или метки, ни одного поведенческого: машина состояний dev выросла с 4 до 5 состояний (вошёл awaiting_approval, Фаза 12e/ADR 0052 — A3); цитата RN-160 ушла из входа backlog-promovido Arquiteto и переехала туда, где она реально применяется, — на выход handoff-duplo (кнопку «Confirmar arquitetura pronta», которую она блокирует, — A4); цитата RN-161 ушла из handoff-duplo и стала заметкой во входе Dev Lead (именно шаг ПРИНЯТИЯ handoff цепляет активацию выполнения, а не сам двойной handoff — A5); и происхождение worktree-por-agente у dev перестало говорить harness (Engine.Dev.WorktreeManager не является одним из 4 компонентов роли harness — A8). docs/explanation/backlog.md тоже был обновлён: строка об устаревшем gates.yml ушла из таблицы незакрытых пунктов (закрыта здесь), вместе с gate implementavel (B3, уже закрытым ADR 0090 до этой волны, но никем не убранным из таблицы) и частью B7 (deployment frequency/lead time), которую analise:funil (ADR 0089) уже сдаёт по-настоящему — MTTR/change failure rate остаются вне, но как ПОСТОЯННЫЙ пробел, уже объявленный в fluxo.yml, а не инженерный незакрытый пункт.

Проверка: pnpm --filter api validacao:gates -- --sem-banco (реестр действителен, без ativo-sem-evidencia) и pnpm docs:check зелёные. Фаза 3 скрипта (реальные улики в event log) требует --projeto <uuid> против базы с данными — вне досягаемости этого исправления, которое касается только метаданных.

Незакрытые пункты того же плана, не этой волны: Волна 2 (делегирование Dev Lead → dev, RN-160, повторно проверяемая в backend) и Волна 6 (gate necessidade-validada, который требует ADR и продуктового решения до написания кода).

Аудит fluxo.yml × код — Волна 2: RN-160 в backend и делегирование Dev Lead → dev (RN-404/405, ADR 0094)​

Вторая из шести волн плана аудита (раздел D), ДВА пункта наибольшего объёма после Волны 1 — остальные четыре (3, 4, 5 введены досрочно вне порядка; 6 всё ещё не закрыта, требует ADR и продуктового решения). Оба живут исключительно в apps/api; никаких изменений в engine/web не потребовалось.

B6 — RN-160 повторно проверяется в backend. Правило («Confirmar arquitetura pronta» требует хотя бы 1 продвинутой истории) гарантировалось только на КЛИЕНТЕ (SessionPage.tsx, hasPromotedStory) — прямой HTTP-вызов POST /agents/arquiteto/handoff-infra полностью игнорировал правило. OfferInfraHandoffUseCase.execute теперь запрашивает StoryRepository.findByProject и отказывает с BadRequestException ДО записи события или вызова engine, когда ни у одной истории нет status !== 'draft'.

B1 — делегирование Dev Lead → dev становится данными. ADR 0053 (пункт 5) уже предусматривал это и объявил вне объёма в то время — CLAUDE.md перечислял это как обратимое отсечение. Закрыто здесь: AcceptParallelizationUseCase.execute (сторона API, даром покрывает прямой И одобренный пути, потому что оба уже сходятся в нём) записывает delegations с area: 'dev', leadAgent: 'dev-lead', subagent = id только что активированного dev-<modulo>. В отличие от QA/Infra (которые записывают на стороне ENGINE, потому что там субагент производит заключение), status: 'completed' был ПЕРЕОПРЕДЕЛЁН для этой области: он означает «делегирование ОСУЩЕСТВЛЕНО» (агент поднялся), а не «заключение выдано», — а parecerArtifactId указывает на самый свежий artifact.module_map проекта (через SessionEventRepository.listByTypeForProject, уже существующий метод, без нового запроса). Без module_map в проекте — чего не должно случаться — делегирование не записывается с фальшивым id: лишь логирует неожиданное состояние и продолжает, потому что активация dev agent уже успешна, когда эта запись предпринимается; сбой в RecordDelegationUseCase тоже не роняет активацию, по той же причине. Решение зафиксировано в ADR 0094, который также отменяет отсечение пункта 5 ADR 0053.

docs/fluxo.yml (dev-lead, выход delegacao): status: lacuna → status: ativo, regra: RN-405.

Аудит fluxo.yml × код — Волна 6 (последняя): gate necessidade-validada (RN-406, ADR 0095)​

Закрывает ПОСЛЕДНЮЮ из шести волн плана аудита (раздел D, docs/explanation/auditoria-fluxo-vs-codigo.md) — закрылись все шесть: 3, 4 и 5 были введены досрочно вне порядка (analytics/DORA, gate implementavel, QA-стратегия/appsec, ADR 0089/0090); 1 и 2 закрылись в предыдущих PR (fluxo.yml/gates.yml в актуальном состоянии; RN-160 в backend + делегирование Dev Lead → dev). Это 6-я: gate necessidade-validada (Criativo → PO), объявленный proposto начиная с ADR 0085 без какого-либо механизма за ним.

modelo-de-time.md уже фиксировал, почему он стоял: Criativo ( модель), в одиночку решающий, что потребность, которую он сам произвёл, подтверждена, — это самоподтверждение, а не настоящий gate. Решение владельца продукта — ОТДЕЛЬНОЕ человеческое подтверждение — третья кнопка в SessionPage.tsx, «Confirmar necessidade validada», по ТОМУ ЖЕ шаблону, что «Confirmar arquitetura pronta» (RN-160): выделенный маршрут (POST .../agents/criativo/validate-necessity), выделенный вариант использования (ValidateNecessityUseCase), собственное событие (necessity.validated). Никогда не переиспользовал confirm_readiness/RN-142 (который остаётся лишь структурным полом, «≥1 захваченное правило»), ни принятие handoff со стороны PO (AcceptHandoffUseCase, структурное, без суждения о содержании).

Выбранная цепочка: кнопка включается только ПОСЛЕ того, как confirm_readiness уже консолидировал artifact.product_brief (hasProductBrief) — нет смысла «подтверждать» резюме, которого ещё нет, и это самое согласованное с docs/fluxo.yml прочтение (gate — ВЫХОДНОЙ для Criativo, момент, когда его работа уже сдала конкретный артефакт). В отличие от OfferInfraHandoffUseCase, это подтверждение НЕ сигнализирует engine — handoff Criativo→PO уже произошёл внутри самого confirm_readiness, поэтому новое событие — лишь запись ПО СУЩЕСТВУ, без какого-либо триггера, ждущего его.

docs/gates.yml получает gate necessidade-validada: active, aprovacao_humana: true, но severidade: warn — ничто в продукте сегодня не СПРАШИВАЕТ о прохождении этого gate, прежде чем позволить PO продолжить, в отличие от block у story-promovida/plano-de-adocao, за которыми стоит настоящая блокировка в коде. docs/fluxo.yml (criativo, gate_saida): status: proposto → status: ativo, regra: RN-406.

Аудит fluxo.yml × код — B4 (ПОСЛЕДНИЙ незакрытый пункт): PO читает метрики продукта (RN-407)​

Закрывает пункт B4 — последнюю строку таблицы «Backlog do modelo de time (ADR 0085)» в docs/explanation/backlog.md; таблица становится ПУСТОЙ после этого исправления, и аудит fluxo.yml × код здесь заканчивается. Без нового ADR: следует тому же шаблону, уже установленному RN-164 (чтение агента с областью проекта, без внешнего эффекта, без proposed_action), только с новой RN. docs/fluxo.yml (роль po, вход metricas-de-produto): status: lacuna → regra: RN-407.

ДАННЫЕ существовали начиная с ADR 0089 (analise:funil — настоящая воронка сессия → commit → PR → merge, настоящий lead time, настоящий deployment frequency); не хватало лишь того, чтобы PO мог ПРОЧИТАТЬ этот отчёт внутри хода. Третий инструмент чтения PO, listar_metricas_de_produto (GET .../product-metrics), в точности того же замысла, что два родственных из RN-164, — :direct, без параметра, область закрыта на проекте. Чистые функции РАСЧЁТА и запрос (calcularFunil/calcularLeadTimes/ leadTimeMedioMs/deploymentFrequencyPorDia/buscarAcoesGitDoFunil) переехали из apps/api/scripts/analise-funil.ts в apps/api/src/application/services/funil-metrics.ts — МЕХАНИЧЕСКИЙ и необходимый рефакторинг (scripts/ не может жить в src/, а src/ не импортирует из scripts/): скрипт стал РЕЭКСПОРТИРОВАТЬ оттуда, не меняя сигнатуры и поведения, а регрессионный тест скрипта остаётся зелёным, не будучи тронутым. В JSON-теле нет поля для трёх постоянных отсутствий, которые скрипт уже объявлял только текстом (полная продуктовая воронка идеация → commit, улики принятия по функции, MTTR/change failure rate), — инструмент PO всегда называет их по имени в ТЕКСТЕ, который возвращает модели, чтобы она никогда не заключила по отсутствию чисел, что пробела нет.

GOVERNANCE.md — критерий режима community, а не его механизм​

Пункт прежнего backlog («Modo community do approval-ladder») стал исправлением записи, а не реализацией: расследование подтвердило, что режим community уже существует в коде начиная с ФАЗЫ 6 (scripts/ci/approval-ladder.ts), протестирован с обеих сторон и лишь выключен через APPROVAL_MODE=solo (по умолчанию). Фраза backlog («становится изменением aprovacao_humana в реестре gate-ов») пришла из СПЕКУЛЯТИВНОЙ строки ADR 0054 — не отредактированной, по правилу, но неточность зафиксирована в docs/explanation/backlog.md: aprovacao_humana у gate aprovacoes-da-escada уже фиксированно true в обоих режимах, без какого-либо поля в схеме Gate, реагирующего на APPROVAL_MODE.

На самом деле не хватало другого TODO(humano), в docs/explanation/branching-policy.md: критерия того, кто входит в каждый список одобряющих (devs/po/gestão) — кто входит, кто выходит, на основании чего. GOVERNANCE.md (корень репозитория, новый) закрывает это: МЕХАНИЗМ остаётся в branching-policy.md (лестница, разные люди, пошаговая смена переменной), КРИТЕРИЙ теперь живёт в GOVERNANCE.md. Это предложение, а не работающий комитет — у проекта по-прежнему один мейнтейнер, и документ говорит об этом прямо: большая его часть описывает то, что начнёт действовать, КОГДА людей станет больше, а не уже активную структуру. Новое правило warn в docmap (governanca) требует пересмотра, когда меняются approval-ladder.ts/ branching-policy.md, — отдельно от существующего правила block (politica-de-branches) по той же причине, по которой checks-e-rulesets отделено от него: МЕХАНИЗМ вытекает из кода, КРИТЕРИЙ — человеческое решение.

Настоящий SMTP в MailSender (RN-408, ADR 0096)​

Закрывает пункт backlog, открытый с отказа от Keycloak (ADR 0032, «настоящий SMTP остаётся будущей конфигурацией»). nodemailer поверх чистого SMTP-транспорта (СТРОЧНЫЙ протокол с состоянием/MIME/STARTTLS/AUTH, в отличие от JSON HTTP API, с которыми интегрируется остальной продукт), за MAIL_TRANSPORT — log (по умолчанию, В ТОМ ЧИСЛЕ в production: отправка настоящей почты — opt-in оператора) или smtp, выбираемый через useFactory в AuthUseCasesModule, не трогая ни одного варианта использования (RegisterUseCase, RequestPasswordResetUseCase, LoginUseCase, скрипт migrate-keycloak-users.ts по-прежнему лишь внедряют MailSender). SMTP_HOST/SMTP_USER/SMTP_PASSWORD/SMTP_FROM следуют шаблону RN-114 в production, когда режим smtp, — но БЕЗ публичного значения по умолчанию, которое есть у четырёх исходных RN-114, поэтому линейка вступает в действие, только когда оператор выбрал smtp; проверка выполняется в конструкторе SmtpMailSender, задействованном через useFactory при сборке графа provider-ов (тот же замысел, что у CREDENTIALS_MASTER_KEY), а не немедленным вызовом в main.ts. Тело — ЧИСТЫЙ ТЕКСТ, никогда HTML: порт не несёт структуры для богатого тела, а шаблонизатор был бы поверхностью инъекции без какого-либо выигрыша.

Расследование нашло настоящий пробел до написания кода: у email_verification не было никакого web-маршрута — verificarEmail существовал в apps/web/src/lib/auth.ts с Фазы 7a без ЕДИНОГО вызывающего, так что ссылка из письма подтверждения вела бы в никуда, как только SMTP начал бы отправлять настоящую почту. /verificar-email (VerifyEmailPage.tsx) закрывает это, отражая SetPasswordPage.tsx — тот же шаблон validateSearch в router.tsx, тот же единый ответ для несуществующей/ истёкшей/уже использованной ссылки — с одним отличием: без формы подтверждение срабатывает само при монтировании, поэтому экрану нужны три состояния RN-088 (загрузка/ошибка/успех), а не только два.

«N агентов онлайн» в dashboard — ЖИВОЙ статус, никогда историческое присутствие (RN-409, ADR 0097)​

Пункт прежнего backlog закрыт. Предварительное расследование подтвердило, что не существовало никакого агрегата liveness, ни по проекту, ни по workspace — только ИСТОРИЧЕСКОЕ присутствие (RosterFacts, «уже хоть раз появлялся»), а настоящий живой статус — только на клиенте, выводимый из event log, только когда проект ОТКРЫТ. ProjectCardSummary.onlineAgentCount суммирует два механизма по ОДНОЙ линейке («не простаивает, не застрял»): dev agents (engine.dev_agent_states.status NOT IN ('idle', 'idle_tripped'), пакетная агрегация в engine) и разговорных агентов (последний agent.status самой свежей сессии с status !== 'idle', payload, принимающий только working/idle/awaiting_approval). QA/SecOps никогда не считаются — они не выдают agent.status, единственный вердикт на вызов, без понятия «простоя» между вызовами.

Архитектурное решение, которое стоит записать: запрос читает engine.dev_agent_states НАПРЯМУЮ, сырым SQL, пакетированным по workspace, внутри ТОГО ЖЕ Promise.all, который уже суммирует одиннадцать запросов read model RN-090 (ДВЕНАДЦАТЬ → ЧЕТЫРНАДЦАТЬ, по-прежнему КОНСТАНТА), — вместо того чтобы открывать новый внутренний HTTP-маршрут в engine. api и engine разделяют ОДНУ физическую базу, разделённые лишь схемой Postgres, и прецедент уже был (apps/api/scripts/medir-execucao.ts уже читает engine.oban_peers); это решение поднимает шаблон с ручного скрипта до ПРОТЕСТИРОВАННОГО production-кода (минимальный и объявленный fixture таблицы engine в test/support/global-setup.ts). Принятое и объявленное в ADR 0097 следствие: запрос предполагает, что тот, кто эксплуатирует продукт, мигрирует обе стороны вместе (db:migrate И engine:migrate), и падает громко/видимо, если мигрировала только api — без try/catch, скрывающего ошибку.

Сессия выполнения больше не закрывается при ожидающем dev agent (RN-411)​

Исправление дефекта, найденного в РЕАЛЬНОЙ сессии выполнения, а не пункт сценария: пять dev agents поднялись, стали idle_tripped (RN-047, circuit breaker), застряв в ожидании, пока пользователь разблокирует задачу за задачей, а 30-секундный heartbeat сессии (Engine.Sessions.SessionServer) закрыл сессию из-под них, пока ручная разблокировка продолжалась. Третий сигнал GetSessionPendingWorkUseCase (RN-064) читает только agent.status, словарь разговорных агентов, — dev agents говорят dev.* (Engine.Dev.AgentIo) и никогда не выдают agent.status, поэтому ни один dev agent никогда не удерживал сессию этим сигналом. Четвёртый сигнал читает последнее событие dev.* каждого dev-<modulo>/dev-<modulo>-2 в сессии: dev.working/dev.blocked/dev.idle_tripped удерживают сессию (застрявший в ожидании человеческого решения — ЭТО незакрытая работа); только dev.idle (по-настоящему дренированный) не удерживает. dev.awaiting_gate/dev.awaiting_approval ВОШЛИ в линейку через RN-412 — см. ниже; остаточный пробел, который описывал этот абзац, закрыт.

Автоматический личный workspace при регистрации (RN-410)​

Найдено при навигации: кнопка «Novo projeto» в dashboard ничего не делала, без ошибки. Корневая причина: RegisterUseCase и SocialLoginCallbackUseCase (в ветке, которая создаёт новую учётную запись) создавали пользователя и учётные данные, но НИКОГДА workspace — КАЖДАЯ новая регистрация упиралась в эту стену, и это не проявлялось раньше только потому, что seed.ts всегда создаёт workspace вместе с демонстрационными данными. Обе точки теперь создают workspace и добавляют пользователя как owner в ТОЙ ЖЕ транзакции, которая уже создаёт учётную запись. Имя/slug получаются из единственной чистой функции nomeESlugDoWorkspacePessoal (domain/auth/personal-workspace.ts), чтобы правило не расходилось в двух файлах; slug всегда получает суффикс userId.slice(0, 8) (тот же шаблон, что extraDevAgentId), чтобы быть уникальным без обращения к базе (RN-410).

413 в PR и Executores «без выполнения» — оба конца и цепочка (RN-412, ADR 0098)​

Это не запланированная фаза: исправление, запрошенное реальным ИСПОЛЬЗОВАНИЕМ («nas PRs sempre está estourando entity too large»), плюс родственный дефект вкладки Executores, появляющейся пустой при реально идущей работе. Расследование нашло их сцепленными: gate умирает с 413 → dev-ы застревают в dev.awaiting_gate → это состояние не удерживало сессию (пробел RN-411) → 30-секундный heartbeat закрывает сессию из-под них → вкладка требует активной сессии и исчезает со всем roster.

Причина 413 была в API Brabo, а не в provider-е LLM — комментарии, приписывавшие ошибку provider-у (runtime.exs, terminal_executor.ex, docker-compose.yml), были неверны; FalhaDeTurno.origem({413, _}) уже классифицировал её как "codigo", и был прав. apps/api/src/main.ts никогда не настраивал предел тела Express — действовало значение по умолчанию 100 KB, намного ниже 8 MB, которые принимает Phoenix, в самом тяжёлом направлении транспорта (engine → api, POST /internal/sessions/:sessionId/llm-turn, который пересылает ВСЮ историю на каждой итерации ToolLoop). API_JSON_BODY_LIMIT (по умолчанию 10 MB) закрывает транспортный конец.

Второй конец, без которого поднятие лимита лишь отложило бы переполнение: ContextManager engine (ADR 0098) имел ДВА дефекта, которые делали компактизацию недостижимой до переполнения тела — estimate/1 считал только content, поэтому сообщения assistant, полные toolCalls, стоили в оценке ~ноль токенов; а окно компактизации использовало только context_window (128.000 токенов у агентов gate), что давало ~350 KB до компактизации — далеко после любого разумного транспортного предела. ЭФФЕКТИВНОЕ окно теперь min(context_window, teto_de_transporte), с обрезкой всегда на ГРАНИЦЕ ИТЕРАЦИИ ToolLoop (никогда не отделяя assistant с toolCalls от role: "tool", которые на него отвечают, — это сломало бы протокол tool-use provider-а).

RN-412 расширяет RN-411: DEV_PENDING_TYPES получил dev.awaiting_gate (исходный аргумент — gate может умереть и бессрочно держать dev agent в этом состоянии), а также dev.awaiting_approval, по новому аргументу, найденному в расследовании: решение об одобрении записывает proposed_actions.status СИНХРОННО, а возобновление dev agent через outbox/Oban — АСИНХРОННО; в окне между ними ничто не удерживало сессию — тот же дефект RN-411 уровнем глубже.

Вкладка Executores/Обзор имели родственный дефект на стороне web: executionActivated выводился из events.some(...) по окну всего из 200 событий useSessionEvents — execution.activated — одно из ПЕРВЫХ событий сессии и выпадало из окна в любом реальном выполнении, стирая весь roster. Правильное значение уже существовало, агрегированное по ВСЕМ событиям, в сводке workspace (RN-090, projects-summary.repository.ts) — оба экрана стали потреблять это значение. Бонусная находка по пути: ExecutionSection (Обзор) пересчитывал activated снова, независимо, по тому же окну — длительное выполнение снова предлагало «Ativar execução», будто никогда не начиналось; исправлено заодно. gatesEverOpened страдает от ТОГО ЖЕ класса дефекта и остался ОБЪЯВЛЕННЫМ известным ограничением, не исправленным здесь — его исправление потребовало бы изменить сигнатуру deriveAgentRoster/rosterFactsFromEvents, вне объёма этого точечного исправления.

Neo4j как граф знаний — фундамент (RN-413/414/415, ADR 0099/0100)​

Запрос владельца продукта: Neo4j + ollama-model-loader (gemma:1b, yi-coder:1.5b, nomic-embed-text), явно вдохновлённый репозиторием ErickWendel/neo4j-ai-experiments — благодарность зафиксирована в ADR 0099 и в prompts/README.md, за конкретное вдохновение шаблоном «промпт как версионируемый файл, а не строка, зашитая в код» и графом как памятью агентов.

Две ответственности, один фундамент: версионируемые шаблоны промптов (PromptTemplate/PromptVersion, идемпотентно по хешу — RN-413) и реляционная память (взаимодействия, гипотезы Psicólogo с уликами, профили Anamnese, handoffs — реальное потребление — будущая волна, эта сдача лишь строит механизм). Граф — ПРОИЗВОДНАЯ память, никогда не источник истины; pgvector остаётся векторным индексом чанков, без дублирования embedding в двух базах. Драйвер neo4j-driver в api (не в engine) — api уже владеет всей персистентностью и RAG, engine потребляет по внутреннему HTTP с service token, как всегда.

rag_search (RN-414) закрывает самую большую дыру существующего RAG: ни один агент его не запрашивал, только web-вкладка «Chat RAG». Новый инструмент в engine, собственные пределы (RN-150), деградация всегда видна в начале текста для модели. ollama-model-loader (RN-415) закрывает отдельный настоящий баг: nomic-embed-text никогда не скачивался автоматически, и RAG молча деградировал до чисто лексического в любой чистой среде.

Первая партия шаблонов вынесена в prompts/*.md (ux-designer, Psicólogo, Anamnese, суммаризация ContextManager).

Потребление графа — шаблоны без касания GenServers, релевантность без замены свежести (RN-416/417, ADR 0101)​

InstructionFiles получил источник :graph (db > graph > dir > root — instruction_patch пользователя по-прежнему побеждает всё), и ТРИ агента стали ПЫТАТЬСЯ разрешить шаблон из графа перед inline-текстом, который стал обязательным fallback-ом: ux-designer (graph_instruction_templates_enabled?), Psicólogo и Anamnese (graph_templates_enabled?, один ключ на обоих). Два флага, а не один — они столкнулись бы ПРОТИВОПОЛОЖНЫМИ значениями по умолчанию между параллельными фронтами, если бы делили ключ; оба значения по умолчанию — false. Только context-manager-summarize (из четырёх шаблонов партии) остался без потребителя в то время — пробел, который ADR 0101 объявил открытым и который закрылся позже, через ЧЕТВЁРТОГО потребителя ТОГО ЖЕ graph_templates_enabled?: промпт суммаризации ContextManager (prompt/1, см. RN-417).

Psychologist.ContextBuilder/Anamnese.ContextBuilder получили ВТОРОЙ источник контекста — rag_search, с запросом, выведенным из триггера анализа, — в КОМПОЗИЦИИ с привычным чтением свежих/временного окна, никогда не в замену: без совпадения поведение идентично прежнему. Фрагменты входят в СУЩЕСТВУЮЩИЙ бюджет Triage, и degraded: true от RAG всегда видим в контексте, никогда не скрыт.

Граф пишет себя сам: GraphProjector (api) дренирует ВТОРУЮ строку outbox (aggregateType: 'graph_projection', та же транзакция, значение, которое фильтр engine — IN ('session', 'task') — никогда не совпадает, избегая гонки с Engine.Outbox.Drain, который уже дренирует 'session' за ~2s), и вызывает уже существующие варианты использования записи. Engine НИКОГДА не пишет в граф напрямую — это сохраняет event log единственным источником истины; граф восстанавливается воспроизведением.

Настоящая находка во время проверки, записанная, потому что учит: тест предела тела RN-412 (apps/api/test/main.spec.ts) имел СКРЫТЫЙ баг — AppModule, импортированный СТАТИЧЕСКИ в начале файла, вычисляет DrizzleModule (который создаёт свой пул подключений в области МОДУЛЯ, а не в фабрике Nest) ДО того, как beforeAll успевает направить DATABASE_URL на тестовую базу. Это маскировалось, пока локальная база dev тоже была мигрирована (та же таблица, поэтому запрос «находил», даже указывая не туда); вскрылось, как только база dev была пересоздана пустой (настоящим smoke-тестом этой же волны против Neo4j+Postgres). Исправлено ДИНАМИЧЕСКИМ await import(...) внутри beforeAll, после того как DATABASE_URL уже задан, — это шаблон, которому следовать всегда, когда тесту нужно перенаправить DATABASE_URL для настоящего AppModule.

Локальный runner — выполнение на машине пользователя (RN-418/419/420, ADR 0102/0103)​

Запрос владельца продукта: агент, работающий на МАШИНЕ ПОЛЬЗОВАТЕЛЯ, в локальной папке проекта, через стандартный терминал пользователя, — плюс настоящий интерактивный терминал во вкладке Code. Два изменения, одно зависит от другого.

Политика (ADR 0102 — ГЛОБАЛЬНОЕ решение, явно подтверждённое владельцем продукта после автоматического предупреждения безопасности об изменении): git push/PR/deploy (RN-106) и sudo/doas (новые) выходят из безусловного deny и становятся АБСОЛЮТНЫМ ПРЕДЕЛОМ — безусловным require_approval, в ТОМ ЖЕ блоке и по ТОМУ ЖЕ шаблону, что соседние пределы (current.policy === 'auto_approve'), никогда не автоодобряемым ни через agent_autonomy (подстановка "*"), ни через permissions.json. Щель, которую исходный deny затыкал силой, закрыта в ИСТОЧНИКЕ: ApproveAlwaysActionUseCase/patternForAction отказываются записывать шаблон в allow для действия с внешним git-эффектом или привилегированной командой — «sempre permitir» одобряет только экземпляр, никогда шаблон, для этих двух случаев. Без этой второй половины предел был бы декоративным. sudo/doas совпадают по ГЛАГОЛУ в comandoPrivilegiadoNoComando (external-effect.ts), обходя все сегменты команды, — тот же принцип, что у efeitoExternoNoComando.

Runner (apps/runner, новый workspace Node/TS, ADR 0103): CLI (brabo-runner --project <id> --dir <pasta> --token brb_...), который пользователь запускает на своей машине, подключается к ENGINE через НОВЫЙ канал Phoenix (/runner, топик terminal:<projectId>), аутентифицированный ОДНОРАЗОВЫМ тикетом (тот же шаблон RN-108, но тикет ВЫПУСКАЕТСЯ ENGINE — схема "engine", собственная миграция — и запрашивается api через внутренний HTTP-маршрут, инвертируя поток тикета сессии). Выполняет exec (УЖЕ ОДОБРЕННУЮ команду агента — маршрутизация в TerminalExecutor происходит только ПОСЛЕ привычного конвейера decide()/proposed_action, никогда до) в $SHELL пользователя, и интерактивный режим PTY для вкладки Terminal (pty_open/pty_data/ pty_input/pty_resize/pty_close — engine делает чистый RELAY, никогда не интерпретируя байты). Только ОДИН runner на проект (Engine.Runners.Registry, :global, исключительность :global.register_name/3); режим runner БЕЗ проверенного workspace или БЕЗ подключённого runner-а явно ОТКАЗЫВАЕТ — никогда не падает в System.cmd/bind-mount, которых для проекта runner не существует (пересмотрено ADR 0104/RN-423, см. выше; откат на привычный путь по-прежнему действует, но только для container/mounted). PTY — действие аутентифицированного ПОЛЬЗОВАТЕЛЯ, не проходит через proposed_action, но аудируется (terminal.session.started/ended в event log, в том числе когда вкладка падает без явного pty_close).

Граница безопасности runner-а — НЕ sandboxing, это аутентификация (CLI предъявляет Personal Access Token УЧЁТНОЙ ЗАПИСИ пользователя) + привычный конвейер одобрения + согласие пользователя запускать CLI на собственной машине, с его привилегиями. apps/runner/src/guard.ts проверяет cwd внутри корня лексическим разрешением, но объявлен BEST-EFFORT, а не настоящей гарантией.

PAT закрыл пробел токена автоматизации (ADR 0105/RN-424..426). Настоящая находка Волны 1 — «у продукта нет механизма ДОЛГОЖИВУЩЕГО токена учётной записи для автоматизации, поэтому runner повторяет вход браузера (cookie/CSRF, сохранённые в ~/.brabo/runner-credentials.json)» — была закрыта в Волне 2: personal_access_tokens (brb_…, хеш HMAC-SHA256+ pepper через hashDeToken(), никогда argon2 — неверно для секрета ВЫСОКОЙ энтропии) выпускается в Настройках проекта, с областью ОДНОГО проекта, отзываемый, с опциональным истечением. apps/runner/src/auth.ts полностью потерял интерактивный вход, cookies и файл учётных данных — он лишь проверяет формат и передаёт --token/BRABO_ACCOUNT_TOKEN, НИКОГДА не записываемый CLI на диск. PAT никогда не аутентифицирует вне POST .../runner-ticket, по ПОСТРОЕНИЮ (IS_PAT_ROUTE_KEY/ @RequirePatAuth() + PatAuthGuard) — никогда ветка в глобальном JwtAuthGuard, которая позволила бы RolesGuard авторизовать PAT для любого маршрута роли пользователя.

maintainer отзывает PAT другого пользователя (RN-427), закрывая пункт, который ADR 0105 пока объявил вне объёма (реагирование на инцидент — уволенный dev с утекающим токеном). Расширение той же модели, без нового ADR: ОТДЕЛЬНЫЕ маршруты (GET .../personal-access-tokens/all, DELETE .../personal-access-tokens/:tokenId/admin), область по projectId вместо userId — самоотзыв каждого пользователя (RN-426) не меняется.

@xterm/xterm/@xterm/addon-fit (web) и phoenix/node-pty (runner) — четыре новые зависимости — та же линейка исключения, что у mermaid (ADR 0068): динамический import(), отсутствие eval/new Function подтверждено grep-ом по установленному пакету (это не формальная гарантия против обфускации, только сильная улика — объявлено как неопределённость, а не утверждено как уверенность).

ADR 0104 — Волна 1 ЗАВЕРШЕНА (RN-421/422/423): ADR 0072 и 0103 никогда не разговаривали друг с другом — RN-170 требовал bind-mount при создании, а маршрутизация в runner (RN-420) переиспользовала тот же флаг workspace_mode == 'local' без какого-либо bind-mount, поэтому настоящее использование runner-а по-прежнему было обязано проходить проверку смонтированной папки. workspace_mode (2 значения) стал execution_mode (container/mounted/runner, миграция 0048) — RN-170 (теперь RN-422) начинает действовать только для mounted; runner рождается с путём, проверенным только ЛЕКСИЧЕСКИ (без I/O), и workspaceVerifiedAt: null, продвигаемым в проверенный, когда runner подключается и подтверждает путь на ХОСТЕ (POST /internal/projects/:projectId/workspace-verification, RN-423) — runner — ИСТОЧНИК ИСТИНЫ пути, перезаписывающий то, что было набрано в мастере. Engine.Actions.TerminalExecutor.decisao_de_execucao/1 получил ЧЕТЫРЕ выхода: маршрутизирует только при проверенном workspace И подключённом runner-е; без любого из двух явно ОТКАЗЫВАЕТ — никогда не падает в System.cmd/bind-mount от mounted, которых для проекта runner не существует.

Находка реализации, исправляющая ADR (который не редактируется — исправление живёт здесь и в backlog.md): фраза ADR 0104 о том, что конвертация между тремя режимами существующего проекта «становится разрешённой без пересоздания проекта», НЕВЕРНА. UpdateProjectDto по-прежнему намеренно исключает executionMode/workspacePath — это не тривиальный PATCH (worktree, permissions.json и кеш engine указывают на старую область). Волна 1 сдала лишь поле из трёх значений при СОЗДАНИИ; конвертация существующего проекта — будущая волна, с собственным замыслом, ещё не запланированная. Backlog runner-а: Волна 2 (PAT, ADR 0105) закрыла пункт, блокировавший npm publish @brabo/runner, а Волна 3 (ADR 0106) сдала само распространение — tsup упаковывает apps/runner в единый dist/index.cjs (node-pty как обязательный external, нативный binding), публикуемый на каждом финальном теге собственным workflow (publish-runner.yml), параллельным release.yml. Настоящая находка, проверенная эмпирически: защите автозапуска в index.ts понадобился realpathSync на process.argv[1] — без этого main() никогда не выполнялся, когда CLI вызывался через bin, установленный npm install -g (symlink, который process.argv[1] никогда не разрешает через realpath, но import.meta.url всегда разрешает). Объявленный операционный незакрытый пункт: настоящая публикация требует, чтобы владелец продукта создал Automation Token npm и настроил секрет NPM_TOKEN — без него workflow предупреждает и пропускает, никогда не падает. Исключительность по {project_id, machine_id} по-прежнему отложена до второго реального одновременного dev; best-effort guard.ts — ПОДТВЕРЖДЁННЫЙ инвариант, а не пробел. См. ADR 0104/ ADR 0105/ ADR 0106.

ИНСТРУМЕНТ РАЗРАБОТКИ — pnpm bootstrap​

Терминальное меню в scripts/dev/bootstrap.sh, группирующее то, что делается изо дня в день: Docker, K8s, Database и Test. Существует, потому что эти команды живут в ТРЁХ местах, которые не разговаривают, — package.json, Makefile и скрипты в deploy/k8s//docker/ — а заученное знание гниёт.

Оно ничего не переизобретает: каждый лист вызывает уже существующую команду. Режим --print-commands разрешает всё дерево без TTY и без выполнения, и именно на нём работает тест (scripts/dev/bootstrap.spec.ts) — TUI не тестируется модульно, но отображение меню→команда — да, и ошибается именно оно. Ноль новых зависимостей; цвета берутся из design/tokens.css в 24-битном ANSI с деградацией до 256 цветов и до отсутствия цвета.

Вывод выполняющейся команды ПРОКРУЧИВАЕТСЯ (колесо мыши в SGR, j/k, PageUp/PageDown, G для возврата в конец). Не хватало не чтения колеса: не хватало СМЕЩЕНИЯ — tail -n умеет показывать только конец, — поэтому окно стало срезом через sed -n 'a,bp', тестируемым без TTY через --print-window. Два следствия, которые объявляет подвал: прокрутка назад ЗАМОРАЖИВАЕТ окно (как less +F, иначе перерисовка с частотой 5 Hz отменяет прокрутку), а отслеживание мыши, включаемое только на экране выполнения, выключается при ЛЮБОМ выходе — \e[?1006l\e[?1000l в restaurar_terminal, который покрывает trap на EXIT. Парсер escape — общий CSI (читает до финального байта): чтение двух фиксированных байтов оставляло остаток последовательности в буфере, а поскольку меню трактует [1-9] как выбор, поворот колеса запускал пункты меню.

Три зафиксированных решения: Create готовит с нуля, а Deploy публикует в уже существующую среду (поэтому выбор по сервису есть только у Deploy); в K8s работает только All, а Api/Engine/Web появляются ВЫКЛЮЧЕННЫМИ, а не исчезают, потому что меню должно говорить, чего продукт не делает; и Database › Delete пересоздаёт расширение pgvector, поскольку docker/postgres/init.sql выполняется только при первой инициализации тома — чистый DROP SCHEMA заставил бы следующую миграцию упасть, а поскольку engine делит ту же базу, восстановление требует db:migrate И engine:migrate.

Docker › Reset total (scripts/dev/reset-total.sh) объединяет всю эту последовательность в один лист — preflight, up --build --wait ( --wait из Compose v2.17+ — это «пока всё не станет здоровым», без цикла здоровья, написанного вручную), тот же DROP SCHEMA, что у Delete, db:migrate + engine:migrate и seed — с СОБСТВЕННЫМ экраном подтверждения (confirmar_reset, требует набрать RESET), отличным от экрана Delete (который требует ИМЯ базы), и без предусловия Delete «postgres уже запущен», потому что именно эта команда поднимает compose с нуля. Seed (apps/api/src/db/seed.ts) получил шаг, активирующий учётные данные provider-а из .env, переиспользуя ТЕ ЖЕ переменные <PROVIDER>_TEST_KEY, которые уже используют smoke-тесты LLM (apps/api/test/infrastructure/llm/*.smoke.spec.ts), — одно соглашение об именах, два потребителя; provider без заданной переменной не входит, без ошибки, и учётные данные остаются у OWNER-а (RN-058).

Две находки при первом запуске сброса, а не из-за самого пункта — ни одна не гипотетична, обе сломали реальное выполнение:

  1. gemma:1b не существует в registry Ollama (manifest unknown — существует только gemma3:1b), поэтому ollama-model-loader всегда падал, и --wait никогда не закрывался зелёным. Исправлено в шести местах, нёсших неверное значение как КОД — docker-compose.yml/.prod.yml, .env.example, docker/ollama/pull-models.sh, scripts/dev/verificar-modelos-ollama.sh, deploy/k8s/base/ollama/job-model-loader.yaml и RN-415 в docs/business-rules.md — без касания ADR 0099/0100 (принятых) и исторического повествования этого же раздела CLAUDE.md, которые описывают то, что было запрошено/решено тогда, а не текущее операционное значение.
  2. Серьёзнее и старше — Database › Delete (пункт 3.4, существующий с создания меню) никогда не удалял по-настоящему: DROP SCHEMA public CASCADE не достаёт до engine.* (Ecto/Oban живёт в СОБСТВЕННОЙ схеме, engine) и до drizzle.__drizzle_migrations (управление drizzle-kit живёт в СОБСТВЕННОЙ схеме, drizzle). Результат: mix ecto.migrate упирался в duplicate_table на выживших таблицах engine, а pnpm db:migrate — хуже, БЕЗ какой-либо ошибки — видел нетронутое управление drizzle-kit, заключал, что уже всё выполнил, и не пересоздавал НИ ОДНОЙ таблицы api. Delete/Reset total теперь удаляют также engine и drizzle, в этом порядке, до public.

Сгруппированные вкладки, PR на уровне проекта, локальная папка через Runner, карусель PO (2026-08-20)​

Пять независимых волн, выполненных параллельно по оспариваемому файлу, пришедших из реального использования экрана проекта — ни одна не запланирована заранее. Шестая волна той же программы (полный перевод продукта и документации, i18n) больше пяти вместе взятых; её ФУНДАМЕНТ (Волна 6a — механизм, пока без перевода содержимого) уже закрыт: react-i18next+i18next в web (en стал языком приложения по умолчанию, pt-BR сохранён — RN-425), столбец locale в users, встроенный в payload входа/refresh, новая AccountPage (/account), доказывающая механизм, и Docusaurus с i18n.defaultLocale: 'en'/locales: ['en', 'pt-BR'] — текущий снимок pt-BR из docs/ уже сохранён в website/i18n/pt-BR/docusaurus-plugin-content-docs/current/. Волна 6b (массовое извлечение интерфейса + перевод docs/) сильно продвинулась 2026-08-22, на четырёх параллельных фронтах — подробности каждого см. в CHANGELOG.md (Unreleased/ Документация), — но ЕЩЁ НЕ закрылась: не хватает docs/business-rules.md (переведён только front-matter; тело, с сотнями RN, по-прежнему на 100% на португальском — это самый большой файл всей документации, и он растёт быстрее, чем его можно попутно перевести) и остаточного среза меньших компонентов .tsx. Когда она по-настоящему закроется, этот раздел получит итоговое резюме, а остальной CLAUDE.md (Stack, «Documentação») будет полностью пересмотрен, чтобы зафиксировать английский как настоящий основной язык.

  • Сгруппированная линейка вкладок: 11 отдельных вкладок стали 6 наверху — Visão geral, Agentes ▾ (Executores, Criativo, Chat, Insights), Dev ▾ (Código, PRs, Aprovações), Documentação ▾ (Backlog, Arquitetura), Gastos, Configurações — через новый GroupedTabs (apps/web/src/components/ ui/GroupedTabs.tsx), построенный ПОВЕРХ общего Tabs, который остаётся без групп для других экранов. ChaveDeAba остаётся плоским — сгруппировано только представление, чтобы не трогать deep-link ?tab=. Chat и Chat RAG стали ОДНОЙ вкладкой (ProjectChatShell.tsx) с внутренним сегментированным управлением («Conversar»/«Buscar») — бизнес-различие между ними (RN-202: разговор активирует агента и тратит ключ владельца за ход; RAG — чтение по индексу, без агента) не изменилось, изменился только UI-контейнер.
  • Вкладка PR, на уровне проекта: ProjectApprovalsTab ограничивал «PRs em revisão» самой свежей сессией — ревью PR из предыдущей сессии исчезало с экрана, как только новая сессия предлагала другой. Новая вкладка prs перечисляет напрямую из git-provider-а (никогда по сессии) и сверяет с ожидающим proposed_action через новый findPendingByProject, рядом с уже существующим методом с областью сессии — решение использует sessionId САМОГО действия, никогда latestSession (RN-423). Это первый настоящий производитель git_merge через UI; блокировка защищённой ветки (RN-154) остаётся абсолютной и нетронутой — кнопка «Merge» лишь ПРЕДЛАГАЕТ, человеческое одобрение по-прежнему единственный способ выполнить.
  • Вкладка Arquitetura: выделена из Обзора (module_map, диаграмма C4, ADR, незакрытые пункты), в котором остаётся сжатое резюме + ссылка. Первый lightbox дизайн-системы — Modal получил size="full" — для увеличения диаграммы C4 (SVG, без потери качества) (RN-424).
  • Навигация по локальной папке через Runner (RN-422, ADR 0104): пересматривает ADR 0072 (который явно отклонил выбор папки), НЕ редактируя его — навигация существует, но через Runner, который уже работает с реальными привилегиями пользователя на его машине, никогда через api, перечисляющую файловую систему контейнера. Два новых события в уже существующем канале (fs_list_dir/fs_home_dir), чистый relay engine, тот же шаблон, что у PTY. FolderBrowserModal работает там, где проект уже существует. При создании (NewProjectWizard.tsx) пробел был объявлен до ADR 0108: в режиме mounted проект по-прежнему рождается только при подтверждении (проверка пути там касается диска), но в режиме runner «Procurar pasta...» начинает создавать проект ЗАРАНЕЕ — переиспользуемый по снимку идентичности, а не дублируемый при каждом клике, — чтобы можно было привязать тикет канала до подтверждения (RN-437, ADR 0108). Новый RunnerOnboardingPanel (общий с вкладкой Terminal) заменяет прежний сырой <code>. apps/runner получил README — настоящая публикация в npm/упаковка подписанного бинарника остаётся вне объёма, объявлено в README. Сам проводник папок стал трёхколоночным (ярлыки, список, где один клик выбирает/двойной клик входит, панель деталей — RN-436), следуя визуальному эталону владельца продукта.
  • Баг карусели PO исправлен (RN-421): партия ожидающих продвижений зависела от сканирования последних 200 событий (useSessionEvents) — в длинной сессии предложение выпадало из окна, и карусель деградировала до одной карточки или исчезала. Тот же класс бага, который RN-180 уже исправила для ContextAside; источник теперь — useBacklog (полный, без окна), с откатом по истории, когда backlog ещё не ответил.

Ручной handoff выбранному агенту (RN-440/441, ADR 0109)​

Закрывает пункт backlog, открытый с ФАЗЫ 13c. CreateHandoffUseCase остаётся ЕДИНСТВЕННЫМ местом, которое записывает toAgent (ADR 0038), — он получил лишь опциональный actor?: Actor, по умолчанию привычный {kind:'agent', id: fromAgent}. RequestManualHandoffUseCase (новый маршрут, POST .../sessions/:sessionId/handoffs, роль developer) проверяет toAgent против addressableAgents() — ЗАКРЫТОГО каталога (lead-ы областей ∪ SOLO_CONVERSATIONAL_AGENTS), строже, чем assertHandoffTargetAllowed (который отклоняет только субагента), — разрешает fromAgent по самому свежему agent.activated сессии (sentinel "usuario", если ни одного), и записывает actor: {kind:'user'}. Handoff рождается offered, как и автоматический: уже существующая карточка принятия (SessionPage.tsx) подхватывает его сама, ноль изменений на пути принятия.

Расследование ДО написания кода нашло ux-designer (ADR 0087) в ТОЙ ЖЕ ситуации, что и Staff (ADR 0088), которая мотивировала пункт: клауза message/2 готова в agent_command_controller.ex, никакого человеческого пути к ней. Оба вошли в AGENTES_DE_CHAT в одном изменении — infra/qa остаются вне, по тому же уже задокументированному там шаблону (lead-ы без kickoff/1 и без клаузы message). Объявлено, не скрыто: зеркало SOLO_CONVERSATIONAL_AGENTS на стороне web (apps/web/src/lib/agents.ts) НЕ сверяется тестом с зеркалом api — расхождение даёт в худшем случае устаревший вариант в селекторе, который backend всё равно отклоняет с 400.

Бюджет по области — закрывает отсечение ADR 0038 (RN-443, ADR 0110)​

Одобренный пункт backlog («Older backlog», docs/explanation/backlog.md). agent_areas получает budget_micros/spent_micros, в точности отражая max_parallel — та же строка, тот же владелец решения (maintainer), без новой таблицы. Это ТРЕТИЙ ДОБАВОЧНЫЙ предел рядом с проектом и сессией (CheckBudgetGateUseCase проверяет все три параллельно, любой заблокированный уже отказывает), а не каскад binding модели из ADR 0064 (sessão > agente > área > projeto > workspace, «побеждает наиболее конкретный») — два механизма разделяют слово «область» и больше ничего; не путать их — центральная мысль ADR 0110, так же как RN-101/ADR 0063 уже отделяет представление владельца от представления участника в отчёте о расходах. spent_micros суммирует ВСЕГДА, когда актор принадлежит области (lead или участник, через areaDo — чистая функция, без обращения к базе для актора без области), с настроенным budget_micros или без (null — значение по умолчанию, без предела).

Автономный бинарник runner-а (2026-08-23, RN-451/452, ADR 0112)​

Пункт backlog ADR 0104 («standalone binary, pkg/bun build --compile»), спутник ADR 0106 (распространение через npm). Явное решение владельца продукта: по-настоящему ЕДИНЫЙ бинарник, полная матрица с Windows с v1 — более дешёвая альтернатива («бинарник + папка node-pty-native/ рядом») была предложена и отклонена.

bun build --compile встраивает нативный .node из node-pty через with { type: 'file' } и извлекает его в НАСТОЯЩИЙ ВРЕМЕННЫЙ каталог в runtime (native-pty-loader.ts) — необходимо, потому что node-pty разрешает собственный addon через require() по ВЫЧИСЛЯЕМОМУ пути (lib/utils.js#loadNativeModule), который Bun не умеет встроить сам (build проходит, runtime выбрасывает Cannot find module, протестировано до проектирования исправления). Пять платформ (linux-x64/linux-arm64/darwin-x64/darwin-arm64/win32-x64), каждая собрана на своём НАТИВНОМ runner-е (.github/workflows/build-runner-binaries.yml, триггер финального тега, тот же шаблон, что publish-runner.yml), бинарник прикреплён к уже существующему Release.

Честная проверка, по платформам: только linux-x64 был действительно выполнен в этой среде (build:bin+smoke:bin зелёные, многократно — именно при запуске настоящего бинарника проявились два бага ниже). Остальные четыре — особенно Windows, с ТРЕМЯ нативными файлами вместо одного — были продуманы чтением исходного кода node-pty, ни разу не выполненные на реальной машине той ОС; их первое настоящее выполнение — СЛЕДУЮЩИЙ тег версии, а не этот PR. Ни один платформенный fallback не был применён превентивно — ничто в этом расследовании не ДОКАЗАЛО, что какая-либо из пяти не работает так; если настоящий тег покажет, что да, это станет новым пунктом, а не сегодняшним предположением.

Два дефекта, найденные только запуском скомпилированного бинарника, а не чтением API: --external node-pty должен быть в команде bun build, даже при отсутствии какого-либо статического import node-pty в коде — fallback await import(...) (используемый только вне бинарника) всё равно обнаруживается и встраивается Bun и падает в runtime с обманчивой ошибкой; и защите автозапуска (ADR 0106) понадобилась вторая ветка — process.argv[1] внутри бинарника — ВИРТУАЛЬНЫЙ путь (/$bunfs/root/...), до которого realpathSync не дотягивается, выбрасывая ENOENT вне любого try/catch до того, как main() будет вызван.

Конвертация execution_mode существующего проекта (RN-447..450, ADR 0111)​

Закрывает исправление, которое Волна 1 runner-а (ADR 0104) зафиксировала в docs/explanation/backlog.md: пункт 4 того ADR говорил, что конвертация execution_mode между container/mounted/runner в уже существующем проекте «становится разрешённой без пересоздания проекта», и до сих пор это было ложью — UpdateProjectDto по-прежнему намеренно исключает оба поля. PUT projects/:projectId/execution-mode (maintainer, ConvertProjectExecutionModeUseCase) — ВЫДЕЛЕННЫЙ маршрут, а не ослабленный PATCH: он оркестрирует миграцию, а не просто меняет столбец.

Отказывает с 409, пока любой dev agent проекта (в ЛЮБОЙ сессии, прочитанный напрямую из engine.dev_agent_states по project_id, тот же путь, что у RN-409) не находится в idle — idle_tripped здесь СЧИТАЕТСЯ активным, в отличие от критерия «онлайн» RN-409, потому что у агента ещё есть workspace_root, захваченный в памяти в ожидании человеческой разблокировки (RN-447). PermissionsFileStore получил move(from, to): содержимое permissions.json никогда не меняется, меняется только физический путь, потому что projectScopeRoot выводится из пары (режим, путь) (RN-448). При выходе из container с подготовленным контейнером жизненный цикл (ADR 0081) доводится до removed — через stopped сначала, если был running, — ДО изменения столбца, потому что RegistrarTransicaoDeContainerUseCase отклоняет переход вне режима container; вход В container никогда не готовит автоматически (RN-449). workspaceVerifiedAt обнуляется при каждой настоящей конвертации, в том числе при возврате в runner с внешне одинаковым путём (RN-450). Та же (режим, путь), что и сегодня, — no-op, даже не проверяет активного dev agent.

Объявленное, не скрытое следствие: конвертация НИКОГДА не копирует уже написанный код между старым и новым корнем. Это безопасно для того, что уже ЗАКОММИЧЕНО — engine выводит worktree из bare-репозитория с нуля (Engine.Actions.Workspace.ensure!/4) всякий раз, когда папка назначения ещё не помечена готовой, поэтому следующий агент перестраивает рабочее дерево в новом корне из настоящего git. Что НЕ выживает — это НЕзакоммиченный diff в брошенном worktree: он остаётся сиротой на старом диске, никогда не мигрируемый и не удаляемый. Предел активного dev agent (RN-447) снижает риск (никогда не конвертирует при агенте working), но не устраняет: агент idle между tasks может сидеть на грязном diff, и ничто здесь не проверяет чистоту рабочего дерева перед конвертацией.

Локальная папка, прикреплённая как справочник Chat RAG (2026-08-23, RN-455..457, ADR 0113)​

Запрос владельца продукта: прикрепить папку СОБСТВЕННОЙ машины пользователя к проекту как справочник ДЛЯ ЧТЕНИЯ агентами — без CLI brabo-runner (ADR 0103). Явно НЕ является выполнением/workspace проекта: execution_mode: runner (ADR 0104) нужен CLI, потому что нужен настоящий путь ХОСТА, чтобы контейнер маршрутизировал команду; это больше похоже на «прикрепить файлы к разговору». Браузер читает СОДЕРЖИМОЕ (File.text()) и ОТНОСИТЕЛЬНЫЙ путь (File.webkitRelativePath) выбранных файлов — никогда абсолютный путь хоста (API File не открывает его ни одному сайту), и поэтому ADR 0113 прямо говорит, что это НЕ пересмотр того, что отклонили ADR 0072/0107: те отклоняли РАЗРЕШЕНИЕ пути хоста из браузера; здесь не существует никакого пути для разрешения, только байты, которые браузер уже имел право читать.

chunks.scope (ADR 0079/0080) получает ЧЕТВЁРТОЕ значение, 'local' (миграция 0052, ALTER TYPE ... ADD VALUE, добавочное — оба CHECK миграции 0045 уже записаны как «это session или нет», поэтому local попадает на ту же сторону, что docs/adr, без нового CHECK). IndexLocalFolderUseCase близко отражает IndexProjectDocsUseCase — Markdown-aware чанкинг для .md/.mdx, RagEmbeddingService, чанк без вектора вместо падения, когда embedding отваливается (RN-233), — но ОТКЛОНЯЕТ (400) всю загрузку, когда АГРЕГИРОВАННЫЕ пределы превышены (количество файлов, суммарные байты), никогда не обрезает молча: это ЕДИНИЧНЫЙ жест пользователя с селектором папки на экране, а не фоновое сканирование. Отдельный слишком большой файл или файл с нераспознанным расширением лишь ПРОПУСКАЕТСЯ (filesSkipped), никогда не роняет партию (RN-456). origemDoChunk не понадобилась никакая новая ветка: local несёт sourcePath, как docs/adr, и попадает в kind: 'file', который цитирование уже умело отображать.

Никогда не входит в общее «Reindexar agora» (RN-457): ReindexProjectUseCase переиндексирует, читая из источника, который СЕРВЕР может снова посетить (репозиторий проекта, event log); у local нет никакого источника для повторного посещения — текст существует только в браузере того, кто прикрепил. Повторное прикрепление папки (новая загрузка в POST .../rag/local, maintainer) И ЕСТЬ механизм повторной синхронизации, намеренно отдельная кнопка, с перекрёстным комментарием в обоих вариантах использования, указывающим друг на друга и на ADR 0113, чтобы будущее «исправление» не связало их снова и не стёрло материал пользователя. Покрытие (RagCoverage.local) — СОБСТВЕННОЙ формы — нет «итога в репозитории» для сравнения — и lastAttachedAt — ЕДИНСТВЕННОЕ настоящее исключение из правила «никогда X мин» (RN-237): настоящий MAX(chunks.created_at), а не угаданный (RN-458).

Полоса активности хода — нить получает ответ только в конце (RN-459/460)​

Запрос владельца продукта: экран Сессии теперь показывает НАД composer полосу, которая человеческим языком рассказывает, что разговорный агент (Criativo, PO, Arquiteto, Dev Lead, UX Designer, Staff) делает ВО ВРЕМЯ хода — визуальный эталон: строка статуса Claude Code. Нить получает пузырь ответа только ПОСЛЕ того, как ход заканчивается; правило относится к ЧАТУ (канал Phoenix) и действует только для шести разговорных агентов — консультативный чат без активного агента (SSE, streamChatMessage) остаётся с привычным потоковым пузырём, нетронутым.

Механизм: шесть серверов уже выдавали ДОЛГОВЕЧНЫЙ tool.call; теперь они также делают broadcast(state, "tool.call", %{tool: name, agent: @agent}) — ЭФЕМЕРНЫЙ, без args (RN-096/RN-412), — чтобы канал доставлял в реальном времени. Нет общего behaviour/макроса между шестью dispatch_tool (четыре разные структурные формы, подтверждено полным чтением) — изменение — шесть правок, приспособленных к локальной форме каждого, а не рефакторинг. На стороне web ЧИСТЫЙ reducer (reduzirAtividadeDoTurno/lib/atividade-do-turno.ts) накапливает agent.delta как текущий текст; при приходе tool.call архивирует непустой текущий как строку повествования, обнуляет и всегда добавляет строку инструмента с fraseDaFerramenta(tool) (словарь 19 инструментов, lib/narracao-de-ferramentas.ts, без собственной RN). СОБСТВЕННЫЙ флаг (turnoViaCanal, никогда не выводимый из statusAgent/streamingAgent, которые законно проходят через окна null посреди хода) решает, появляется ли полоса или старый пузырь — никогда оба.

Правило представления (история): agruparNarracoesDoTurno, новый чистый проход после afundarDesfechos (RN-172) в том же useMemo — последовательные agent.response одного turno+autor становятся компактным Disclosure («Passos do turno · N»), только последний остаётся нетронутым вне его. turno === 0 (сторожевой пролог turnoDoSeq) остаётся ВНЕ группировки — старые fixtures складывают agent.response без границы хода, чтобы тестировать другой механизм (RN-138/RN-177), и без исключения новая группировка дала бы там Disclosure внутри Disclosure. Сам afundarDesfechos не изменился.

Находка по пути, объём расширен по решению владельца продукта: расследование выяснило, что ЧЕТЫРЕ из шести серверов (Arquiteto, Dev Lead, UX Designer, Staff) заканчивали МОЛЧА на пределе итераций — defp run_turn(state, remaining) when remaining <= 0, do: state, без какого-либо события, вопреки постоянному правилу, что цикл агента не заканчивается молча. Только PO уже выдавал toolloop.limit_reached (RN-166); Criativo намеренно расходится с agent.error (решение задокументировано в самом коде). Четыре исправленных теперь выдают toolloop.limit_reached, то же событие, что и PO (RN-459).

Перенесено из индекса CLAUDE.md 2026-09-11​

Записи ниже пришли из таблицы-индекса CLAUDE.md и не имели собственного раздела в этом файле — их повествование существовало только в одном месте. Они были перенесены без переписывания, в том порядке, в каком были, когда таблица была сведена к индексу. То, что здесь, — история; то, что действует сегодня, живёт в RN, в ADR и в разделе «Estado atual e aberto» файла CLAUDE.md.

ПРОГРАММА 16–26 (ADR 0060–0072)​

Принят дизайн-handoff; 8 экранов; kind сессии; пагинированные события; Gastos; модель по области; контейнер на проект (25b отсечена); вкладка Code

pnpm bootstrap​

Терминальное меню поверх существующих команд; Reset total

Нативный Ollama / pull из Hugging Face (RN-461..463, ADR 0114–0115)​

Bootstrap dev спрашивает один раз и сохраняет режим Ollama в .env; навигатор моделей Hub с pull в два этапа и allowlist официальных издателей

Собственный lockfile сайта (ADR 0117)​

website/ выходит из корневого workspace pnpm — lockfile и overrides безопасности изолированы, pnpm audit продукта перестаёт сообщать о дереве Docusaurus

Автоматическая настройка runner-а через браузер (RN-464..466, ADR 0118)​

Ключ устройства Ed25519, генерируемый в браузере, в дополнение к PAT; прокси бинарника через GitHub Releases; File System Access API записывает настроенную папку, с откатом на загрузку вне Chromium; --project/--dir/--token становятся опциональными в CLI

Образы, опубликованные в GHCR (ADR 0119)​

release.yml публикует четыре образа на каждом финальном теге и записывает digest-ы в .release/images.json; overlay production перестаёт указывать на заглушку

Схема по агрегату (ADR 0121)​

db/schema.ts становится barrel; 51 таблица и 34 enum становятся 16 файлами под db/schema/, по одному на агрегат domain/*

SessionPage.tsx в 5 механических PR (ADR 0122)​

3 807 → 2 661 строка; временная шкала/ход, StorySlide, StructuredQuestionCard, дерево backlog + ContextAside, hook useSessionReadiness — кластер канала хода и ProjectSettingsTab.tsx остаются вне, объявлено

Hook канала хода SessionPage.tsx (ADR 0124)​

2 661 → 2 479 строк; механический PR дедупликации (iniciarTurnoDoAgente/finalizarTurnoDoAgente/cancelarTurnoOtimista), за которым следует вынос состояния + эффекта канала Phoenix в hook useTurnoDoAgente (lib/session-turno.ts) — закрывает пункт, который ADR 0122 оставил объявленным открытым; ProjectSettingsTab.tsx остаётся вне, отдельное решение

ProjectSettingsTab.tsx по разделам (ADR 0125)​

2 532 → 77 строк; 17 разделов становятся по файлу каждый под routes/settings/ (+ shared.ts с ДВУМЯ helper-ами, у которых больше одного вызывающего), а старый файл остаётся на пути как вход и barrel — путь и 11 экспортируемых имён являются контрактом 3 тестов. Закрывает ПОСЛЕДНЮЮ половину строки долга в docs/architecture.md

Вертикальная дорожка навигации проекта (ADR 0126, RN-201)​

Горизонтальная двухуровневая линейка (GroupedTabs) становится вертикальной дорожкой со ВСЕМИ ТРЕМЯ группами, открытыми одновременно; геометрия дорожки CodeShell, перенесённая клавиатура, 5 счётчиков остаются раздельными. Пересматривает RN-201: вкладка Código больше не сворачивает боковую панель сама

Якорное оглавление Настроек (CHANGELOG)​

17 разделов вкладки получают id, <section> с доступным именем и оглавление в четырёх группах ВНУТРИ области содержимого — без новой вертикальной полосы, рамка остаётся в 444px из ADR 0126. Реестр в routes/settings/sumario.ts (третий список того же замысла, что project-tabs.ts), deep-link ?section=, spy через IntersectionObserver; в оглавление входит только СМОНТИРОВАННЫЙ раздел

Единый шаблон унаследованного значения (CHANGELOG)​

ЧЕТЫРЕ формулировки «нет собственного значения» во вкладке Настроек становятся двумя полюсами и одним глаголом, из одного источника (routes/settings/heranca.tsx): метка говорит о СОСТОЯНИИ, деталь — о ПОСЛЕДСТВИИ. Словарь унифицирован, форма нет — метка входит только туда, где элемент управления не показывает состояние сам

Сохранение по разделу в Настройках (RN-469)​

Параллелизм и Предел расходов заменяют N кнопок по строкам ОДНОЙ кнопкой раздела (settings/secao-salvavel.tsx), со счётом ожидающих строк и исходом ПО СТРОКЕ — сохранение — это N вызовов, а не транзакция, и экран никогда не утверждает то, чего не получил. Три раздела с автосохранением и Учётные данные остаются вне, каждый по объявленной причине

Панель «нужно ваше участие» (RN-467)​

Chip наверху проекта открывает ПЯТЬ очередей решений в одном месте — раздельно, упорядочено по срочности, без какого-либо суммирования (даже в chip). Одобрения и merge решаются там через ApprovalCard; остальные три ведут во вкладку. Незакрытый пункт архитектуры заимствует дату истории и ГОВОРИТ, что заимствовал

Каскад модели как видимая цепочка (RN-470)​

Столбец Origem перестаёт печатать enum базы и становится workspace › projeto › área › agente с четырьмя состояниями на узел (settings/cascata.tsx) — разделяя ДВА смысла origin: 'agent', не трогая api. Предупреждение об отброшенном уровне входит в цепочку; три — получают три текста. Закрывает канвас улучшений UI (взяты 7 из 7 пунктов)

Папка до runner-а (RN-473/474)​

Настройка runner-а через браузер (ADR 0118) меняет порядок: showDirectoryPicker — ПЕРВЫЙ шаг, а бинарник — ПОСЛЕДНИЙ, best-effort — релиз без asset возвращал 502 до открытия селектора, и папка становилась недостижимой. Сбой бинарника сохраняет оба записанных файла и меняет инструкцию на путь npm install -g @brabo/runner. После инструкции EsperaDoRunner опрашивает workspaceVerifiedAt с тремя состояниями и пределом

kid в ключе устройства (RN-475)​

Автоматический режим ADR 0118 НИКОГДА не аутентифицировал: браузер отбрасывал id, который возвращала регистрация, и записанный JWK рождался без kid — CLI его всегда отклонял. Регистрация и экспорт становятся ОДНОЙ функцией (registrarChaveEExportarPrivada), на ОБОИХ путях; и отказ CLI перестаёт быть неотличимым от «ключа нет». Тест, который это пропустил, утверждал, что файл был ОТКРЫТ, а не его содержимое

Provisioning, который не молчит (RN-477)​

Два сбоя не превращались ни в какое состояние — step.check вне try и отказ createRepo ДО появления строки bootstrap, — а экран опрашивал «Iniciando…» вечно без кнопки. Корневая причина всего: /data/git-repos и /data/project-workspaces рождались root в образах DEV, которые Dockerfile.prod уже умел создавать до USER

Файл политики и область терминала (RN-478)​

У projectScopeRoot было ДВА потребителя с противоположными потребностями, и режим runner (без bind-mount) их разделил: область по-прежнему указывает на ХОСТ, permissions.json начинает жить в УПРАВЛЯЕМОМ корне. Активация выполнения возвращала 500 (mkdir '/home/<usuario>'), а ЧТЕНИЕ молча деградировало — в проекте runner файла никогда не было. Заодно: типизированная ошибка с 400 вместо сырого Error, Обзор перестаёт проглатывать сообщение api, и аннотация OpenAPI, обещавшая 409 для двух случаев, которые никогда не были 409

Предупреждение о человеческом шаге до клика (RN-473/477)​

Шаг терминала runner-а объявлялся только в состоянии успеха; теперь он называется и в начальном, а финальная команда получает cd <caminho>, когда можно утверждать, что выбранная папка — папка проекта. Метка кнопки НЕ меняется — она говорит о папке, которая действительно автоматическая

Одна модель для всех агентов (RN-476)​

Таблица Modelos por agente получает панель, применяющую ОДНУ модель ко всем 17 разом, записывая на уровне АГЕНТА (на уровне проекта это был бы maintainer, и затронуло бы значение по умолчанию сессии). Первое ИСКЛЮЧЕНИЕ из линейки «названное значение сохраняется в onChange» из RN-469 — селектор является аргументом действия, а не конфигурацией, — с исходом в трёх состояниях из самой RN-469

Пределы понижения в project_members (ADR 0127, RN-472)​

Перекрытие projectRole ?? workspaceRole ОСТАЁТСЯ в обоих направлениях (это способность, а не баг); входят два предела 403 в варианте использования — никто не понижает owner workspace, никто не понижает самого себя. Три описания OpenAPI, которые RN-471 объявила ложными, начинают описывать код; gate для Select — отдельный PR, поскольку экран не может вычислить первый предел

Телеметрия поиска RAG (RN-479..481)​

Гибридный поиск оставляет след: rag_searches (с ЗАМОРОЖЕННЫМИ весами в строке) и rag_feedback (голос полезно/нерелевантно, единственный настоящий сигнал). ТАБЛИЦА, а не только событие, потому что session_events.session_id — NOT NULL, а у поиска из вкладки нет сессии — событие rag.search/rag.feedback — ПОВЕСТВОВАНИЕ, только когда есть сессия. Инструмент rag_feedback (:direct) у шести агентов, у которых уже был rag_search, и medir:rag для чтения. НИЧЕГО не откалибровано — этот этап лишь инструментирует

Broker контейнеров (ADR 0130, RN-485/486)​

Рождается apps/broker, ЕДИНСТВЕННЫЙ процесс, говорящий с демоном Docker на сервере, — и он НЕ принимает спецификацию: получает projectId + операцию, ЧИТАЕТ решение Arquiteto из api и СОСТАВЛЯЕТ образ, сеть, ресурсы и единственный mount. Порт ADR 0128 ПЕРЕЕЗЖАЕТ из apps/runner/src/ в packages/docker-port (оба потребителя его упаковывают; api не может его потреблять). Маршрут жизненного цикла начинает возвращать наблюдаемое рядом с зарегистрированным, не сливая

Маршрутизация модулей в infra (PR 1.4) (ADR 0131, RN-487)​

Седьмой инструмент Arquiteto, route_modules_to_infra: элемент {modulo, imagemCandidata, porque} на модуль действующего module_map, записанный как artifact.module_routing (без таблицы, тот же замысел, что у artifact.project_image/artifact.c4_diagram). Он ВЫДВИГАЕТ КАНДИДАТОВ — образ каждого элемента проходит через ту же validarDecisaoDeImagem, что у choose_project_image, — а ВЫБИРАЕТ среди кандидатов Infra, в отдельном PR (1.5, закрыт чуть ниже)

Golden-set точности RAG (ADR 0132, RN-490)​

Шаблон golden-set QA (ADR 0123), применённый к гибридному поиску: поскольку суждение живёт не в engine (это HybridSearchUseCase, в api), seed-golden-set-rag.ts готовит ОДИН проект с реальным курированным корпусом (22 файла из docs/) И запускает поиск для 17 вопросов, составленных из реальных RN/ADR; rag_golden_test.exs лишь вызывает скрипт и применяет пол. Критерий попадания — путь файла, top-5, никогда точный chunk/rank 1 — чистая функция, протестированная. Новый gate rag-acertivo, warn (без CI с LLM). Измерено по-настоящему, дважды, детерминированно: 17/17 в top-5

Dev agents выполняются ВНУТРИ настоящего контейнера (PR 1.6) (ADR 0134, RN-492/493)​

Закрывает то, что RN-491 объявила незакрытым: Engine.Actions.TerminalExecutor получает ПЯТЫЙ выход, :executar_no_container — проект execution_mode: container с ЗАРЕГИСТРИРОВАННОЙ строкой running (Engine.Containers.ProjectContainerLifecycle, прямое чтение, тот же шаблон, что у Engine.Projects.Project) — и команда проходит engine → api (POST internal/projects/:projectId/container-exec, ExecutarComandoNoContainerUseCase) → broker (ContainerBrokerPort.exec, ПЕРВЫЙ настоящий вызывающий последней из пяти операций ADR 0128/0130), выполняясь через docker exec вместо локального System.cmd. cwd переводится из пути ХОСТА внутрь /work в engine, ДО вызова — подтверждено в коде, что это ТОТ ЖЕ физический каталог, в который Workspace.ensure!/4 уже пишет (worktree dev agent не меняет ни места, ни механизма). BrokerRecusouError/BrokerIndisponivelError/сбой транспорта становятся обычным failed_result, никогда не падением и не молчаливым откатом за пределы контейнера (RN-486: зарегистрированный running никогда не гарантирует запущенный контейнер). Терминал ВНУТРИ контейнера получает ПОЛ автоодобрения (ProposeActionUseCase/decide.ts, containerExecutionActive) — пять абсолютных пределов и привычная лексическая область продолжают работать поверх, байт в байт

Runner поднимает контейнер проекта (PR 1.3, Часть 1 вне порядка — закрывает половину, которую RN-494/PR 1.7 оставил объявленной) (ADR 0137, RN-497)​

container_start/container_stop/container_remove получают ВТОРОЙ путь выполнения, ветвящийся по executionMode: container идёт через broker (без изменений); mounted/runner начинают просить ПОДКЛЮЧЁННЫЙ runner, через ТРИ новые пары в канале Phoenix (container_start/_result, container_stop/_result, container_remove/_result, тот же шаблон, что exec/exec_result) — EngineWeb.ContainerCommandController передаёт в RunnerRouter, которому runner отвечает через DockerViaCli (ЕГО Docker, @brabo/docker-port, получающий реальное использование помимо --self-test-docker). Образ ЧИТАЕТСЯ (ObterSpecDeContainerUseCase, тот же вариант использования, что у broker-а), никогда не выбирается заново. «Нет runner-а»/«timeout» (RunnerNaoConectadoError) и «runner отказал» (RunnerRecusouContainerError) становятся именованным failed, никогда исключением. Engine.Actions.TerminalExecutor не получил нового выхода — он уже безусловно маршрутизировал каждую команду подключённого проекта runner в канал; выбор хост-или-контейнер — ВНУТРЕННИЙ для runner-а (EstadoDoRunner.containerAtivo, tratarExec маршрутизирует через docker exec с cwd, переведённым заменой префикса, cwdParaContainer). guard.ts/RN-434 начинает покрывать bind-mount без КАКОЙ-ЛИБО новой проверки — mount И ЕСТЬ корень, уже подтверждённый при старте CLI

Handoff Infra нельзя было принять ни на одном экране (D0) (RN-499)​

offeredHandoff фильтрует карточку нити по AGENTES_DE_CHAT, который исключает infra — и фильтр ВЕРЕН (у engine нет клаузы message для Infra Lead; его расширение заставило бы экран предлагать несуществующую нить, а composer — слать сообщение Criativo). Дефектом было следствие, которое комментарий самого кода записал как принятое: у acceptHandoff был ОДИН потребитель, за этим фильтром, поэтому handoff от OfferInfraHandoffUseCase оставался offered навсегда, Infra Lead никогда не активировался, propose_container_start никогда не вызывался, и НИ ОДИН проект ни в каком режиме не доходил до запущенного контейнера. Исправление: СОБСТВЕННАЯ исполнимая карточка, вне нити, в фиксированной полосе между прокручиваемой областью и composer — той же, что уже содержит ручной handoff и объявляет себя местом действий handoff, не являющихся разговором, — вызывающая уже существовавший acceptHandoff, закрытая тем же activeFor. handoff.offered Infra по-прежнему РАССКАЗЫВАЕТСЯ в нити как немой разделитель

Единая база смонтированных проектов (PR 1, BREAKING) (ADR 0141, RN-500)​

Создание проекта mounted требовало строки bind-mount, написанной ВРУЧНУЮ в api И engine, плюс перезапуска обоих — что убивает каждый ход агента, сокет терминала и вызов LLM в полёте во всей установке, ради подключения ОДНОГО проекта. Рождается BRABO_PROJECTS_BASE: ОДНА база, настраиваемая оператором один раз, монтируемая по ИДЕНТИЧНОСТИ ($X:$X) в обоих сервисах, со всеми смонтированными проектами внутри неё и НИЧЕГО не редактируется по проекту. Идентичность, а не фиксированная точка монтирования, потому что workspace_path набирается пользователем и показывается ему обратно — и это то, что позволяет projectScopeRoot/workspace_dir/2 оставаться верными без нового кода. СОБСТВЕННАЯ переменная (коллизия пространства имён с UNIQUE workspace_dir_name привела бы к git init в папке другого проекта; противоположный владелец; база навигируема, а управляемый корень не должен быть таковым). ОТСУТСТВИЕ — норма: projectsBase: null (GET workspaces/:id/projects-base, maintainer), и режим не предлагается. И pnpm dev ОТКАЗЫВАЕТСЯ стартовать при базе, пересекающейся с checkout-ом, — единственная возможная проверка, потому что api сравнивает с /workspace. Две объявленные цены: symlink под базой и только одна база

Контейнер смонтированного проекта (PR 3) (ADR 0144, RN-503)​

Проект mounted не мог получить НИКАКОГО контейнера из-за двух независимых блокировок — api отправляла каждый не-container режим в RUNNER (который требует подключённого brabo-runner), а broker отклонял не-container в источнике. Обе касались ГЕОМЕТРИИ, а не имени режима, и единая база ADR 0141 изменила геометрию. Ветвление становится по НАЗНАЧЕНИЮ: container И mounted → broker, только runner → runner (и container_stop/_remove меняются ВМЕСТЕ с _start, иначе поднимется на сервере и остановится на машине пользователя). Инвариант ADR 0130 не сдвигается — ни один абсолютный путь не пересекает сеть: broker получает ВТОРОЙ корень (BRABO_PROJECTS_HOST_BASE), а api отправляет РАЗЛИЧЁННЫЙ локатор (gerenciada |montada |indisponivel), говорящий, относительно какого корня действует относительный сегмент. ТРИ варианта, потому что у indisponivel два разных исправления (runner × устаревший mounted вне базы), и недостающий корень НИКОГДА не подменяется другим. mounted ВЫБИРАЕТ образ, как container, — broker составляет из artifact.project_image, поэтому незаписанный выбор — инертный выбор (ADR 0133 во втором режиме)

Dev-агенты только после контейнера (PR 7) (ADR 0143, RN-502)​

В реальном выполнении exp001 ДЕСЯТЬ dev-tasks застряли разом — и находкой была не упавшая папка, а то, что ничто не упорядочивало контейнер до dev agent. Engine.Dev.AgentIo.try_claim/2 — ЕДИНСТВЕННАЯ точка claim — начинает запрашивать ProjectContainerLifecycle.running?/1 (предикат, который ADR 0134 уже имел) ДО вызова api: без ЗАРЕГИСТРИРОВАННОЙ строки running агент падает в :idle, сохраняется и выдаёт dev.blocked_by_container. В ENGINE, а не только в activate-execution api, потому что РЕГИДРАТАЦИЯ доходит до try_claim/2, не проходя ни через какой маршрут (init/1 → finish_restart_recovery/1). :idle, а НЕ новый статус, по причине, которую сам docblock try_claim/2 уже записывал с Фазы 12b, — это единственное состояние, из которого wake ещё спасает, и все guard-ы handle_info/2 на это опираются; «пустую очередь» от «нет контейнера» отличает СОБЫТИЕ, никогда статус. Wake приходит через outbox: RegistrarTransicaoDeContainerUseCase публикует container.running в ТОЙ ЖЕ транзакции, которая записывает приход в running, в НОВОМ aggregate_type (container, третий, который читает Engine.Outbox.Drain), вместо того чтобы одалживать task, с ПРОЕКТОМ как aggregateId; доставляемое сообщение — уже существовавшее {:wake, :became_claimable}. Вторая половина: TerminalExecutor перестаёт молча деградировать — container без running падал в System.cmd ВНУТРИ процесса engine, а теперь ОТКАЗЫВАЕТ, с mounted в той же ветке

Тревога merge конвейера с получателем (ADR 0139)​

Правило «продвижение — это --no-ff» было нарушено ТРИЖДЫ (#367, #394, #464 — третий был PR, исправлявшим первые два). Находка: обнаружение УЖЕ существовало и УЖЕ сработало (tag-release провалил первые два с правильным сообщением) — не хватало КОМУ звонить, потому что workflow push, падающий на постоянной ветке, не имеет PR, где стать красным, а в репозитории было ноль issues. tag-release получает job avisar, который открывает issue (и комментирует в уже открытом, никогда не дублирует) с github.token и никогда с PAT — недействительный PAT — один из сбоев, о которых он сообщает, — и это перестало быть гипотетическим: BRABO_BOT_TOKEN истёк вечером 2026-09-03, провалил два запуска tag-release и был ротирован 2026-09-04; v4.0.0 проштамповала и ЗАПУСТИЛА Release, что происходит только с действительным PAT (тег, созданный с GITHUB_TOKEN, не запускает workflow). Правило начинает покрывать dev УЗКО: дефектом является только PR, принёсший НОВОЕ РЕБРО (head был merge, чей второй родитель не был в базе), никогда squash рабочего PR, что является соглашением. Отключение squash в репозитории было ИЗМЕРЕНО и отклонено (обложило бы каждый PR и затронуло бы changelog.mjs, который запускается с --no-merges)

Артефакт решения разговорных агентов (Фронт 3, общий срез) (RN-505)​

decision_record переиспользует ОБЩИЙ шаблон emit_artifact/ArtifactSchemas (тот же, что у note/business_rule) вместо выделенного шаблона project_image/c4_diagram — решение — это append-only лог, никогда не «действующее», которое заменяется. PO, Arquiteto, Dev Lead, UX Designer и Staff получают инструмент (alias + tool_specs + run_tool/3 + та же фраза в system_prompt/1); у Criativo emit_artifact уже был с Фазы 3b, и он лишь получил новый тип, без изменений в нём. Отличается от open_adr_pr (только Arquiteto, настоящий commit + PR + человеческое одобрение): оба СОСУЩЕСТВУЮТ для разных масштабов решений. Вне объёма, объявлено: агенты выполнения (ToolLoop, без общего harness) и связка с Infra Lead — выдача decision_record при предложении container_start_via_runner, — которая зависит от инструмента другого фронта того же плана, в отдельной ветке

«Sempre permitir», разделённое по Dev Agent модуля (RN-509)​

Клик «sempre permitir» на действии dev-<modulo> записывал в permissions.json, область ВСЕГО ПРОЕКТА — освобождая ту же команду для любого другого агента. Теперь записывает в agent_autonomy(projeto, agente, tipo), ТОТ ЖЕ механизм, что уже используется для 3 git-действий, засеваемых при активации выполнения. dev-lead (возглавляет область, но не является её участником) и любой не-модульный актор продолжают идти в привычный permissions.json; два абсолютных предела (терминал с внешним эффектом/привилегированная команда, container_remove) по-прежнему отклоняют весь клик ДО новой ветки. Без миграции: старые записи остаются в permissions.json, осознанное решение

Docker становится настоящей предпосылкой режима Runner (RN-507/508, ADR 0145)​

runner маршрутизировал каждый одобренный терминал в CLI, как только проверенный workspace

  • подключённый runner совпадали, без проверки контейнера running — без запущенного Docker runner сам падал в чистый ХОСТ, ТОТ ЖЕ молчаливый откат, который ADR 0143 уже закрыл для container/mounted. Engine.Runners.RunnerReadiness объединяет ТРИ предусловия (проверен, подключён, контейнер running) с двумя потребителями: TerminalExecutor и материализацией worktree dev agent, которая перестаёт пытаться делать ЛОКАЛЬНЫЕ File.mkdir_p!/git init против пути ХОСТА (пробел, открытый с RN-478) и начинает работать ВНУТРИ настоящего контейнера, на машине пользователя, через ТОТ ЖЕ канал exec/exec_result (Engine.Actions.Workspace.RunnerGit, новый) — WorktreeManager разветвляется по тому же критерию, а периодический job очистки молча ПРОПУСКАЕТ проект runner без готового сейчас runner-а. Протокол exec получает опциональный env, чтобы учётные данные git (ADR 0056) передавались в окружении дочернего процесса ХОСТА — слитые поверх process.env, никогда не передаваемые в docker exec и не логируемые. container_start перестаёт обслуживать runner (его payload, с выбором образа, там никогда не имел смысла); рождается container_start_via_runner — схема только с rationale, поднимает уже решённый образ, — а новый инструмент Infra Lead ЛОКАЛЬНО отказывает (без HTTP), вместо того чтобы предлагать вслепую

RN-505, задублированная в dev, исправлена​

«Артефакт решения» и «sempre permitir по Dev Agent» смержились с разницей в 2 минуты, каждый выделив RN-505, не видя другого; «sempre permitir по Dev Agent» перенумерован в RN-509 (docs/business-rules.md, CHANGELOG, комментарий approve-always-action.use-case.ts и ApprovalCard.test.tsx), освободив якорь {#rn-505} для decision_record. Найдено заодно: перенумерация 505→507/506→508, сделанная во время разрешения конфликта другого PR (Docker/runner, ADR 0145), осталась НЕПОЛНОЙ — в этом самом файле оставалось три упоминания «RN-505», тоже исправленных на «RN-507»

ФАЗА 28 (сессия 5) — join локального агента перестаёт быть немым (RN-514, ADR 0147, пункт 1)​

join канала terminal:<projectId> был немым с ОБЕИХ сторон — runner отправлял {}, а сервер делал _params, — поэтому у сервера не было способа узнать, понимает ли бинарник на другой стороне новое сообщение, и дефект был наихудшим из возможных: сообщение приходит, обработчика нет, и НИЧЕГО не происходит, без ошибки и без лога. Теперь runner ОБЪЯВЛЯЕТ в params (Engine.Runners.Capacidades, словарь exec/pty/espelho), а сервер ПРЕДОСТАВЛЯЕТ пересечение с тем, что ЗНАЕТ, в socket.assigns и НИКОГДА в таблице (способность — свойство этого ПОДКЛЮЧЕНИЯ). Runner объявляет только exec и pty — объявить espelho, не реализовав его, — ровно тот дефект, который предотвращает согласование; он входит как ИМЯ словаря и не более, а сессия 6 его реализует. Отсутствующие/пустые params — это УСТАРЕВШИЙ бинарник, и они предоставляют {exec, pty} — ФАКТ, а не благосклонность: это две функции, с которыми runner родился, а отклонение устаревшего было бы breaking/ и MAJOR. Неизвестное ИГНОРИРУЕТСЯ (runner НОВЕЕ engine подключается); требуемое-но-не-объявленное отклоняет join, НАЗЫВАЯ недостающее, ДО Registry — механизм реализован и протестирован, но сегодня НЕ срабатывает (runner требует exec, который есть у каждого бинарника; ничто пока не требует espelho), и таков замысел. Половина, приносящая пользу СЕГОДНЯ: сообщение без способности ОТКЛОНЯЕТСЯ с именованным ответом — exec отвечает в формате exec_result (126 + причина), вместо того чтобы RunnerRouter ждал timeout, а pty_* становится pty_error во вкладке вместо исчезновения. RunnerReadiness байт в байт как есть

ФАЗА 28 (сессия 6, половина B) — зеркало начинает существовать (RN-516, ADR 0147, пункты 2/3/4/8)​

Способность espelho выходит из имени словаря и становится кодом с ОБЕИХ сторон. НАЗНАЧЕНИЕ передаётся в ПРЕДОСТАВЛЕНИИ join (ответ перестаёт быть пустым для :runner: %{espelho: %{destino: ...}}), живёт только в этом подключении, и runner ОТКЛОНЯЕТ mirror_sync для непредоставленного назначения — никогда глобальная конфигурация или переменная, из-за которой артефакт проекта B приземлился бы в папке A. Непустой mirror_path начинает ТРЕБОВАТЬ способность, и это ПЕРВОЕ настоящее срабатывание механизма отказа RN-514: старый бинарник в проекте с назначением ПЕРЕСТАЁТ подключаться, именованно и фатально — opt-in, цена объявлена в ADR. espelho-guard.ts рождается РОДСТВЕННИКОМ guard.ts (переиспользует три helper-а и двойной проход, письменно наследует оговорку о TOCTOU) и отклоняет три вещи ADR; находка при реализации: realpathMaisProximo падает к предку, а поскольку назначение почти никогда не существует, схлопывание обеих сторон превратило бы /base-outra и /base в / — второй проход разрешает предка и ВОЗВРАЩАЕТ суффикс. ОДНО направление и НИКОГДА не удаляет: файл, удалённый в источнике, ОСТАЁТСЯ в назначении (папка пользователя — накопление, а не реплика). Копируется СПИСОК GIT (отслеживаемые + неотслеживаемые-неигнорируемые), без собственного списка исключений; падающий git становится ИМЕНОВАННОЙ ошибкой, никогда cp -r как план Б; вложенный git-репозиторий (worktree dev agent) возвращается как ОДНА запись и не обходится вглубь, а symlink источника пропускается. СОБСТВЕННЫЙ предикат (Engine.Runners.Espelho), ДВА предусловия и НИКОГДА RunnerReadiness с флагом — runner_readiness.ex остаётся байт в байт. Триггер: COMMIT, именованный момент, fire-and-forget и никогда не watcher; «конец хода агента» остаётся ОБЪЯВЛЕННЫМ и не подключённым, поскольку нет однозначной зацепки

Broker, который никогда не поднимался (исправление RN-512) (RN-512, ADR 0130/0146)​

RN-512 убрала broker из profiles, чтобы он был запущен в dev, и он НИКОГДА не поднимался: corepack prepare выполнялся от ROOT при build (кеш в /root/.cache), контейнер работает как uid 1000, а без кеша corepack пытается СКАЧАТЬ pnpm в сервисе, чья единственная сеть — internal: true, — EAI_AGAIN registry.npmjs.org, Exited (1) через пять секунд. Без healthcheck up --wait говорил Healthy, а полный сброс объявлял «reset completo» с мёртвым процессом; пока существовал profile, broker поднимал тот, кто уже на него смотрел. Исправление убирает registry из RUNTIME и НЕ открывает сеть (первый из пяти слоёв ADR 0130): COREPACK_HOME, подготовленный при build с владельцем uid runtime, pnpm install как шаг BUILD, и — поскольку три тома node_modules перекрывают то, что установил образ, а Docker заполняет только ПУСТОЙ том, — entrypoint, сверяющий их с копией вне /workspace, помеченной sha256 lockfile из build (та же метка ПРЕДУПРЕЖДАЕТ, что образ отстал, не отказываясь стартовать). Healthcheck в сервисе, изнутри контейнера. Production от этого не страдал и не меняется

ФАЗА 28 (сессия 7) — локальный агент становится пользовательской службой (RN-518, ADR 0147, пункт 5)​

brabo-runner service install|uninstall|status, и прецедент был НУЛЕВЫМ (ни одного упоминания systemd/launchd в apps/runner/; моделью был foreground, убиваемый SIGINT). Всегда уровень ПОЛЬЗОВАТЕЛЯ — systemd --user/LaunchAgent, — а root — это ОТКАЗ, а не предупреждение: docblock guard.ts объявляет «работает с ЕГО привилегиями» предпосылкой трёх настоящих границ, а root-служба оставила бы best-effort защиту cwd единственной преградой между одобренной командой и диском. Windows — ИМЕНОВАННЫЙ отказ (третий механизм, а не вариация двух). Один unit на ПРОЕКТ, потому что сервер уже навязывает один runner на проект — именование по папке создало бы два unit-а, которые никогда не поднимутся вместе, и второй проявился бы как сбой подключения, а не как ошибка установки; в Linux файл идёт туда, где systemd ИЩЕТ ($XDG_CONFIG_HOME/systemd/user, иначе ~/.config). Restart=on-abnormal и НИКОГДА on-failure (в plist — KeepAlive/Crashed): exit 1 runner-а — это фатальный отказ join (RN-514) или исчерпанный предел попыток, и перезапуск был бы циклом, от которого CLI отказывается сам. Служба аутентифицируется КЛЮЧОМ УСТРОЙСТВА и никогда токеном — запись его в unit оставила бы его на диске, а этот CLI никогда не записывал учётные данные на диск. uninstall удаляет ВСЕ ТРИ, читая папку из UNIT-а и никогда из cwd (без unit — только с --dir; угадывание стёрло бы ключ другого проекта), и ГОВОРИТ, что ключ ушёл с диска и НЕ был отозван на сервере. status отвечает ЧЕТЫРЬМЯ состояниями с собственной фразой и кодом (0/3/4/5), и первый ответ приходит с ДИСКА — поэтому он остаётся верным на машине без менеджера; неудавшийся spawn никогда не становится «менеджер сказал нет». BRB-031 здесь НЕ закрывается

Spec runner-а уходила без projectId, и этот путь НИКОГДА не поднимал контейнер (RN-508, ADR 0145)​

container_start_via_runner (RN-508, ADR 0145) заканчивался failed в 100% случаев — «especificação de container recusada em projectId: esperava texto não vazio, recebi undefined», — а в реальном выполнении exp004 было видно СЛЕДСТВИЕ: десять dev-tasks без единого агента, который бы их забрал. Эта блокировка была ВЕРНОЙ (RN-507 и RN-502/ADR 0143 отказывают без ЗАРЕГИСТРИРОВАННОГО контейнера running); неверным был предыдущий шаг. EspecificacaoDeContainerParaRunner родилась с ДЕВЯТЬЮ полями, а especificacaoValidada (packages/docker-port) всегда требовала ДЕСЯТЬ — данные были под рукой в SpecDeContainer.projectId и просто не копировались; engine передаёт карту НЕПРОЗРАЧНО, поэтому посередине ничего не терялось. Для контраста, BROKER всегда работал, потому что он ПОЛУЧАЕТ spec из api и получает весь SpecDeContainer — именно поэтому ломался только режим runner. Три зелёных набора тестов не поймали это, потому что каждое звено собирало собственный fixture: никто не тестировал ЦЕПОЧКУ. Она входит — payload, который составляет НАСТОЯЩИЙ вариант использования, проходящий через НАСТОЯЩИЙ валидатор, и закреплённое МНОЖЕСТВО полей (raizDoProjeto — единственное, которое api не отправляет), — в набор тестов api, через ОТНОСИТЕЛЬНЫЙ import, единственное место, где обе стороны существуют вживую; инвариант api-nao-consome-docker-port остаётся зелёным и был РАСШИРЕН, чтобы проваливать относительный import из src/. Родственники проверены и ВЕРНЫ: container_stop/container_remove отправляют только workspaceDirName, а это всё, что требует nomeDeWorkspaceValidado

Пауза, которую нельзя было снять (RN-540)​

Anamnese и Psicólogo были на ПАУЗЕ с 2026-08-10, и три места называли паузу обратимой — docblock-и AnamneseSchedulerWorker.enabled?/0/PsychologistWorker.enabled?/0 («Ligar de volta é X=true e reiniciar o engine») и docs/reference/configuration.md. Она не была обратимой НИ в одной среде: environment: сервиса engine отображал ТРИНАДЦАТЬ переменных Anamnese/Psicólogo — все — ПРЕДЕЛЫ стоимости — и ни одной из двух ПРОДУКТОВЫХ, а docker compose НЕ передаёт окружение хоста, поэтому ANAMNESE_ENABLED=true в .env был инертен, и runtime.exs МОЛЧА падал на значение по умолчанию "false". Измерено в контейнере dev: [true] [] []. Вероятное происхождение: когда значение по умолчанию стало false, никому не нужна была переменная, чтобы ВЫКЛЮЧИТЬ, — лишь тот, кто пытается ВКЛЮЧИТЬ, обнаруживает, что провода нет, месяцы спустя. ВОСЕМЬ булевых флагов (System.get_env("X", "true"|"false") == "true") входят в ОБА compose с ТЕМ ЖЕ значением по умолчанию, что в коде, — четыре дополнительных (START_MODEL_SYNC, START_GATE_RESCUE, два GRAPH_*), потому что это тот же дефект, найденный самим механизмом, а исключение их потребовало бы четырёх оправданий. Ноль изменений поведения: продуктовое решение держать оба выключенными остаётся байт в байт, появляется ПЕРЕКЛЮЧАТЕЛЬ. Механизм — scripts/ci/flags-do-engine-no-compose.spec.ts, который ВЫВОДИТ список из runtime.exs (остальные 50 переменных — пределы с хорошим значением по умолчанию и вещи релиза, законно отсутствующие) и требует одинакового значения по умолчанию только в compose DEV — production намеренно выключает фоновых агентов. deploy/k8s/ НЕ меняется, по согласованности: там нет слоя, который ломается

ФАЗА 29 (сессия 1) — установка одной строкой, только планирование (ADR 0149-0152, fase-29-instalacao-de-uma-linha.md)​

Установка Brabo — это ТРИ разрозненных жеста (compose с пятью секретами вручную, runner через браузер с chmod +x, pnpm bootstrap, который существует только в checkout-е), и ФАЗА 28 письменно отказалась от однострочного установщика, пока артефакты не подписаны (BRB-005), — поэтому фаза подписывает ДО установки. Четыре ADR Proposed: подпись cosign keyless по DIGEST плюс ОДИН подписанный checksums.txt, никогда пять подписей (0149); установщик, вызываемый через sh -c "$(curl …)" и НИКОГДА curl|sh, потому что с pipe скрипт ЯВЛЯЕТСЯ stdin, и согласие умирает (0150); согласованная база runner-а, переиспользующая dentroDoEscopo и отказ от петли по СЕГМЕНТУ из espelho-guard.ts вместо четвёртой копии линейки (0151); и бэкап/restore против compose (0152). Измерение исправило ПЯТЬ пунктов исходного среза: workspace.verified НЕ существует (существует workspace_confirm, и он ОДНОНАПРАВЛЕННЫЙ), пункт 5 ADR 0146 версионирует consentir-base.mjs, а не install.sh, docker-compose.prod.yml не потребляет GHCR и объявляет себя compose ПРОВЕРКИ, make test-restore — путь Kubernetes с бэкапом только Postgres, а «один runner на проект» навязан в ПЯТИ независимых местах — что вывело runner на машину из этой фазы в ФАЗУ 30. Собственная находка: git_local_repos не входит ни в один бэкап, и потерять его — значит потерять код проектов local. Рождается docs/reference/brb.md — и находка в том, что реестр BRB не принадлежит репозиторию: он живёт в vault мейнтейнера, с 33 пунктами, а здесь в прозе появлялись лишь два id. Измерение: BRB-032 и BRB-033 описывают дефекты, которые больше не воспроизводятся, а BRB-004 — не то, что подсказывает id: он просит сторонний образ по DIGEST, а не тег, который закрепил #419. Здешний файл хранит УЛИКИ (измеренное состояние, arquivo:linha); vault остаётся владельцем находки. Диапазон RN-524..538 и ADR 0149-0152, пропуская то, что ВЕДЁТСЯ: три коллизии, замеченные в один момент (ADR 0148 ×2, RN-522 ×2, RN-523 ×2), разрешены позже по критерию ДАТЫ — кто выделил первым, сохранил номер, а ветки от 08.09 стали ADR 0153, RN-539 и RN-540

ФАЗА 29 (сессия 2) — конвейер подписывает то, что публикует (RN-524, ADR 0149)​

Продукт не подписывал ничего: cosign/sigstore/attestation не существовали в .github/, scripts/ или docker/, а единственным происхождением был --provenance npm-пакета runner-а. release.yml публиковал четыре образа без id-token: write; build-runner-binaries.yml загружал пять бинарников через gh release upload и ничего больше — тогда как уже существовавшие sha256sum -c проверяют сторонние бинарники, шаблон, который репозиторий умел применять и никогда не применял к самому себе. Теперь образы подписываются по DIGEST (никогда по тегу, подвижному указателю), а бинарники получают ОДИН подписанный checksums.txt, в job-е checksums, который ждёт матрицу, — а не пять подписей, потому что тот, кто проверяет четыре и забывает пятую, не знает, что забыл. Job намеренно if: always() (fail-fast: false в матрице) и называет то, что манифест не покрывает. Оба workflow проверяют перед публикацией. Keyless по истории дома: приватный ключ был бы ещё одним секретом CI, а PAT уже однажды молча истёк (ADR 0139). Процедура проверки — в runbook, с двумя обязательными флагами --certificate-* — без них cosign принимает действительную подпись ОТ КОГО УГОДНО. Закрывает BRB-005 для образов и бинарников; вне остаются, объявлено, code-signing ОС и прокси загрузки

ФАЗА 29 (сессия 3) — установщик, который ещё не устанавливает (RN-526, ADR 0150)​

Рождается install.sh в КОРНЕ, версионируемый и раздаваемый через Release. Форма — sh -c "$(curl …)" и никогда curl|sh, по МЕХАНИЧЕСКОЙ причине: с pipe stdin процесса ЯВЛЯЕТСЯ загрузкой, и каждый read читает байты самого скрипта или находит EOF — установщик, который не спрашивает, сам выбирал бы, где создавать папку на чьей-то машине. Он ПРОВЕРЯЕТ СОБСТВЕННОЕ ПРОИСХОЖДЕНИЕ (подпись checksums.txt из RN-524 и свой хеш ВНУТРИ проверенного манифеста), с закреплённым cosign, сверяемым через sha256sum с хешем, записанным в скрипте; ОБНАРУЖЕНИЕ называет найденное, прежде чем спрашивать; без TTY сообщает и выходит с 0; и никогда не удаляет папку пользователя — база проектов и папка зеркала остаются вне любого подтверждения, по линейке зеркала (RN-516). Маркер в $XDG_STATE_HOME/brabo/, никогда в ~/.brabo/, который ПО ПРОЕКТУ. В этой версии ничего не устанавливает, и ГОВОРИТ об этом, вместо того чтобы молча закончить. --print-plan/--print-state раскрывают решение без TTY, без сети и без эффекта — по образцу --print-commands из bootstrap.sh, тестируемые так же. Два бага bash, найденные при запуске, оба одного рода: под set -e+pipefail, [ cond ] && atribuição, дающее ложь, УБИВАЕТ скрипт, а несовпавший ls роняет всю подстановку — в функции, чей нормальный случай — «ничего не нашёл». КОРЕНЬ репозитория не покрывался ни одним docmap (glob-ы корня — буквальные имена); рождается правило instalador

ФАЗА 29 (сессия 4) — установка поднимается из собственного compose (RN-527, ADR 0150)​

Рождается docker/docker-compose.install.yml, СГЕНЕРИРОВАННЫЙ из production-а и отличающийся в трёх пунктах: четыре образа берутся из обязательной переменной (${BRABO_API_IMAGE:?…} — со значением по умолчанию отсутствие переменной подняло бы половину стека с образом, который никто не выбирал, и ошибка стала бы странным поведением, а не отказом), ни один build: не выживает, и сервиса broker НЕ существует, потому что его образ не публикуется (у bake четыре цели) — с --source=ghcr mounted не поднимает контейнер, и compose ГОВОРИТ это в комментарии к BROKER_URL. docker-compose.prod.yml остаётся нетронутым: он объявляет себя compose ПРОВЕРКИ и используется smoke.sh с локальной сборкой. Цена двух файлов объявлена, а предел МЕХАНИЧЕСКИЙ — composes-em-conformidade.spec.ts проваливает лишний сервис/том/порт, выживший build: и ослабленный сторонний образ. Две вещи из плана умерли при контакте с диском, по противоположным причинам: migrate — не шаг (compose связывает api→migrate-api через service_completed_successfully, и два места, велящих мигрировать, были бы двумя источниками одной истины), и ** smoke.sh не переиспользуется** (он СОБИРАЕТ то, что установка скачала по digest, и роняет стек в конце) — вместо этого установщик СПРАШИВАЕТ, прежде чем утверждать, стуча в /health обоих

ФАЗА 29 (сессия 6) — миграция требует ДОКАЗАННОГО бэкапа (RN-530, ADR 0150/0152)​

Установка поверх существующей установки не предлагается — либо миграция, либо остановка: up на томах другой версии — это ущерб, который не предупреждает. Порядок: бэкап → ДОКАЗАТЬ → спросить → удалить → установить → восстановить, и средний шаг — то, что даёт установщику право удалять: бэкап, который никто не пытался восстановить, — это файл, и время узнать об этом — не после down -v. Доказательство запускает test-restore-compose.sh (ТЕ ЖЕ три проверки ADR 0152); упавшие бэкап или доказательство останавливают миграцию ничего не удаляя. Назначение — папка ХОСТА — значение по умолчанию compose — том backup_local, который down -v удалил бы ВМЕСТЕ с тем, что хотят сохранить. Дамп Postgres НЕ восстанавливается сам поверх заполненной базы (это разрушительно, а brabo-restore никогда не трогает источник); локальные git-репозитории — да, потому что они родились бы пустыми

ФАЗА 29 (сессия 7) — одна база для обеих сторон, и BRB-031 закрывается (RN-531, ADR 0150/0151)​

Установщик спрашивает базу ОДИН раз и записывает её в ОБА места: BRABO_PROJECTS_BASE в .env (сервер, ADR 0141) и поле base в runner.json (локальный агент, RN-529) — между mounted и runner различается то, КТО выполняет, а не где живёт код, и две базы для одной папки были бы коллизией, которую отклонил ADR 0141. Отказ в ОБОИХ направлениях против checkout-а, и ~ намеренно не раскрывается (значение идёт в файл конфигурации, где ~ читается буквально). Бинарник runner-а сверяется с тем же checksums.txt, который скрипт уже проверил, чтобы проверить себя, — повторная его загрузка дала бы два шанса получить разные манифесты, и второй не был бы проверен, — и устанавливается через install -m 0755. BRB-031 закрывается здесь: он пережил ФАЗУ 28, потому что установка не шла из версионируемого артефакта, а тот, кто доходил до service install, уже нуждался в chmod. Release без бинарника платформы НЕ прерывает установку

ФАЗА 29 (сессии 8-10) — протокол, picker и E2E (RN-532..534, ADR 0149/0150/0151)​

8 (RN-532): рождается пара workspace_create/_result по образцу exec/exec_result, а подтверждение ПЕРЕИСПОЛЬЗУЕТ уже существующий workspace_confirm — ни одного нового маршрута записи, engine по-прежнему не пишет таблицу. Предикат СОБСТВЕННЫЙ по более сильной причине, чем у зеркала: там третье предусловие было бы циклическим — у проекта runner есть контейнер running только после того, как runner его поднимет, а контейнер поднимается ПОВЕРХ папки, которую создаёт это сообщение. Способность workspace — единственная, чьё объявление зависит от СОСТОЯНИЯ (согласованная база), а не от версии бинарника. 9 (RN-533): picker снова читает диск пользователя через fs_list_dir, а решение НЕ скрывать режим без runner-а симметрично RN-513, а не противоположно — там у api ЕСТЬ сигнал, и без базы создание заканчивается 400; здесь нет сигнала присутствия ( RN-521 уже это объявляет), и скрытие означало бы решить «нет» из «не знаю». Находка: экран принимал решение, сопоставляя ФРАЗУ 'Nenhum runner conectado', которая живёт в .ex engine, — она стала различённой причиной. 10 (RN-534): install.sh входит в подписанный checksums.txt (без этого проверка, которую он делает САМОГО СЕБЯ, была мёртвой буквой) и проверяется на чистой машине, с TTY, имитируемым через script -qec, и никогда через pipe — pipe — то, что убивает согласие. НЕ запускается в PR, по той же причине, что и подпись

ФАЗА 29 (сессия 8) — база локального агента получает потребителя (RN-532, ADR 0151, пункты 3–6)​

RN-529 построила БАЗУ и письменно объявила, что её ничто не потребляет. Входит пара workspace_create/workspace_create_result, по образцу ЗАПРОСА С ОТВЕТОМ exec/exec_result — и НИКОГДА по образцу workspace_confirm, который ОДНОНАПРАВЛЕННЫЙ ({:noreply, socket}, ничего не отправляет обратно), уточнение, которое фаза уже записала вместе с «workspace.verified НЕ существует». Только относительный СЕГМЕНТ пересекает сеть (инвариант ADR 0130/0144: тот, у кого есть корень, тот и выполняет), а АБСОЛЮТНЫЙ сегмент отклоняется лексически. Подтверждение ПЕРЕИСПОЛЬЗУЕТ уже существовавший workspace_confirm, и именно это экономит целый фронт: не рождается ни одного нового маршрута записи, engine по-прежнему не пишет таблицу, а единственный путь, штампующий workspace_verified_at, остаётся единственным — с ПОРЯДКОМ как механизмом (confirm ДО результата, потому что оба приходят в один процесс канала по порядку, а обработчик confirm синхронный). Способность workspace входит в словарь В ТОЙ СЕССИИ, В КОТОРОЙ ВХОДИТ КОД, с обеих сторон — RN-514 сделала противоположную ставку с espelho, и она окупилась лишь потому, что кто-то её востребовал, — и это ЕДИНСТВЕННАЯ из четырёх, чьё объявление зависит от СОСТОЯНИЯ данного выполнения: runner объявляет её только при согласованной базе, и именно по этой строке и ни по какой другой сервер ЗНАЕТ, что база существует (engine не читает диск пользователя). НИКТО её не требует в join, намеренно: требование её в runner было бы breaking/ и MAJOR ради функции, которую этот проект, возможно, никогда не использует. СОБСТВЕННЫЙ предикат (Engine.Runners.PastaDoProjeto), ДВА предусловия, RunnerReadiness байт в байт — и здесь третье было бы хуже, чем ненужным, оно было бы ЦИКЛИЧЕСКИМ: проект runner доходит до контейнера running только после того, как runner его поднимет, а контейнер поднимается поверх папки, для создания которой существует это сообщение. Он также не спрашивает workspace_verified_at (здесь это РЕЗУЛЬТАТ) и режим. ДВА предусловия отвечаются в разных местах, потому что «согласованная база» — свойство ПОДКЛЮЧЕНИЯ (socket.assigns, никогда таблица): канал отказывает ДО отправки, с motivo: "sem-base" по тому же ref, в формате, которого ожидает модуль, — никогда timeout. Пять именованных причин, которые не схлопываются, идемпотентность с СОБСТВЕННЫМ исходом (ja-era-repositorio — успех), и estado.dir НЕ меняется. Это не proposed_action, и ни один предел не получает исключения

ФАЗА 29 (сессия 5) — бэкап начинает покрывать то, что больно терять (ADR 0152, RN-528)​

Бэкап покрывал ОДНУ базу и работал в ОДНОМ месте: make test-restore — путь Kubernetes (требует kubectl, кластер и CronJob), а в docker-compose.prod.yml не было НИКАКОГО сервиса backup — у миграции установки через compose не было доказанного пути. Решение начинается с ОТКАЗА от лёгкого хода («бэкап всех томов») и с КЛАССИФИКАЦИИ семи: neo4j_data и project_workspaces — производные, ollama_data — получаемый заново, brabo_projects_base — пользовательский: их копирование создаёт впечатление покрытия, не добавляя восстановления. Источников истины ДВА, и находка — второй: git_local_repos не входил ни в один бэкап, а проект с provider-ом local хранит там bare — он не восстанавливается из Postgres (event log хранит ПОВЕСТВОВАНИЕ, а не объекты git), так что потерять том — значит потерять код агентов. Проза runbook утверждала обратное («the real repositories live in GitHub/GitLab»), и именно она объясняет годы без покрытия: это верно для github/gitlab, а для local upstream нет. Гарантия архива ОБЪЯВЛЕНА и меньше, чем «снимок», — согласованность по ССЫЛКЕ (git пишет объекты до сдвига ref и меняет ref атомарным rename), без глобального момента и без git fsck (в образе намеренно нет git); конкурентный gc становится СБОЕМ, а не частичным архивом, со статусом tar, захваченным в ФАЙЛ, потому что в POSIX sh статус конвейера — статус последней команды. Пять переменных S3 перестают быть обязательными и становятся конфигурацией ОДНОГО назначения (BACKUP_DIR выбирает диск; без неё — S3, этот вывод и сохраняет CronJob НЕТРОНУТЫМ). make test-restore-compose запускает НАСТОЯЩИЙ бэкап и вызывает ТОТ ЖЕ brabo-restore с ТЕМИ ЖЕ тремя проверками: меняется ОБЁРТКА, никогда суждение, и deploy/k8s/test-restore.sh остаётся НЕТРОНУТЫМ (объединение сделало бы вариант compose зависимым от kubectl); restore.sh по-прежнему не трогает ИСХОДНУЮ базу. Восстановление репозиториев — РОДСТВЕННАЯ команда (brabo-restore-git, по умолчанию проверяет, пишет только с --restaurar), а не фаза другой — два артефакта, два суждения, и их объединение заставило бы сбой git проваливать проверку event log. Находка ПРИ ЗАПУСКЕ, которую чтение бы не дало: том разделяется тремя образами с разными uid и несёт владельца того, кто смонтировал его первым (измерено 1000:1000, режим 0755), тогда как образ работает как uid 70 — извлечение умирало посередине, оставляя том наполовину; теперь отказывает ДО tar, называя --user 0:0, а извлечённое от root возвращается владельцу КАТАЛОГА, иначе том вернулся бы целым и БЕСПОЛЕЗНЫМ (api и engine, non-root, не могли бы писать в только что восстановленное). Neo4j получает ПЕРЕПРОЕКЦИЮ, а не бэкап — здесь не построена (BRB-018): мигрированная установка рождается с ПУСТЫМ графом, названная деградация, а не потеря данных (RAG живёт в pgvector, внутри дампа)

ФАЗА 30 — локальный агент машины (ADR 0154/0155, RN-543..552, закрывается в сессии 8 с RN-549)​

ФАЗА 29 сдала установку одной строкой и ОСТАНОВИЛАСЬ в двух пунктах, которые сам продукт уже письменно объявлял: локальный агент оставался сопряжённым по ПРОЕКТУ (кто хотел использовать установленный бинарник, возвращался в браузер, чтобы вставить команду в терминал), и никто не мог войти — сгенерированный .env не содержит переменной e-mail, MAIL_TRANSPORT падает в log, а регистрация требует подтвердить e-mail, так что выходом было выуживать ссылку в docker compose logs api. Оба решались в одном месте, и именно это сделало из них одну фазу. Находка, которая определила её РАЗМЕР, пришла из повторного измерения с arquivo:linha утверждения ФАЗЫ 29 о том, что «один runner на проект навязан в пяти местах»: три из пяти не нужно было менять, потому что топик, socket id и тикет описывают ПОДКЛЮЧЕНИЕ, а процесс, открывающий N подключений — по одному на проект, — удовлетворяет им байт в байт, сохраняя отказ второму runner-у в том же проекте, что является настоящей гарантией, а не случайностью. Ничто в engine не изменилось за всю фазу, и это сохранило RunnerReadiness, зеркало и workspace_create нетронутыми. Осталось то, что принадлежит ПРОЦЕССУ: учётные данные (runner_device_keys.project_id становится nullable, RN-543 — и список начинает возвращать ОБА помеченных вида, иначе ключ машины был бы невидимым и постоянным — дефект, который закрыла RN-519, возродившийся) и unit (RN-545, service install --machine, один на машину, СОСУЩЕСТВУЮЩИЙ с unit-ами по проекту). Два решения противоречили букве ADR, и оба были ИЗМЕРЕНЫ: дискриминатор unit — флаг --machine, а не отсутствие --project (resolverProjeto имеет два источника, и обычный путь — запуск install без флага изнутри папки, которую настроил браузер, — буквально ADR молча сконвертировал бы установку того, кто уже пользуется продуктом), и install ОТКАЗЫВАЕТ, когда другой вид уже установлен, без --force, потому что оба вместе были бы двумя процессами, оспаривающими один проект. Сессия 6 затронула бы три workspace разом и была РАЗДЕЛЕНА на три, причём две непересекающиеся части вышли раньше, а install.sh — после них, как оркестратор и ничего больше. Из этого разделения вышел самый интересный пересмотр фазы: при НУЛЕ подключений агент перестал ЗАВЕРШАТЬСЯ и начал ЖДАТЬ, повторно опрашивая через 15s/30s/60s (RN-550) — аргумент RN-544 против повторного опроса остаётся целым там, где он действует (список, вернувшийся МЕНЬШИМ, неоднозначен, а обрыв ЖИВОГО подключения из-за неоднозначности меняет верное состояние на догадку), а при нуле подключений обрывать нечего; пал аргумент про exit 0 («активная служба, которая ничего не делает»), потому что процесс начал что-то делать и ГОВОРИТЬ, что делает. Измерено: Restart=on-abnormal не смотрит на код выхода, поэтому ни один unit не изменился, а эффект в том, что unit машины становится действительно active (running). Другая часть (RN-551) закрывает «никто не СОЗДАЁТ ключ машины» со стороны агента, и решение там — ОТСЕЧЕНИЕ: CLI не говорит с api, потому что регистрирует установщик с BRABO_SERVICE_TOKEN — каждая сторона хранит секрет, и ни одна не видит секрета другой. Отсюда вытекает замысел из двух шагов, а из него — инвариант: kid ЯВЛЯЕТСЯ id записи и существует только после неё, поэтому create записывает .parcial, который читатель игнорирует, а finish записывает настоящее имя — файл, который читает runner, никогда не существует без kid, дефект RN-475, ставший невозможным по построению. Половина api (RN-552) объявила цену, вместо того чтобы её скрыть: учётные данные — service token, и утёкший токен начинает мочь ФАБРИКОВАТЬ долговременные учётные данные — поэтому три сдерживания живут в МАРШРУТЕ, а не в тексте (в теле нет userId; регистрация ЗАМЕНЯЕТ активные ключи машины владельца в той же транзакции, так что переустановленная машина проходит, а тысяча ключей невозможна по клаузе, без числа, которое устаревает; и JWK с d отклоняется по имени). Сессия 6 (RN-547) связала пять команд цепочкой и закрепила три правила: упавшее среднее звено ничего не откатывает, становится именованной строкой в финальном блоке, и скрипт ВСЕГДА завершается с 0 (откат потребовал бы удалить учётную запись и отозвать ключ, а маршрута для этого нет, и не должно быть такого, который установщик вызывает сам); идемпотентность — это HTTP-КОД, и 409 — ОЖИДАЕМЫЙ исход, а не сбой, с 000, обрабатываемым отдельно, чтобы «api не поднялась» не читалось как «api отказала»; а пароль передаётся через STDIN curl, никогда через argv, потому что /proc/<pid>/cmdline читаем любым пользователем машины. Находка этой сессии: --help ЛГАЛ месяцами («ESTA VERSÃO NÃO INSTALA NADA»), потому что печатает заголовок, являющийся комментарием, а правило, запрещающее говорить о «сессиях фазы», читало только строки кода. Сессия 7 (RN-548) дала потребителя метке вида, и решение было о ЧЕСТНОСТИ: с ключом и без живого подключения — самый частый случай — экран говорит две вещи, которые знает, и называет две, которых не знает (зарегистрированный ключ — не работающий агент, список — от вашей УЧЁТНОЙ ЗАПИСИ, а не этого браузера), тон accent и никогда success. Сессия 8 (RN-549) — единственная, которая могла доказать всё вместе, и она РАСШИРИЛА workflow, который ФАЗА 29 уже имела, вместо создания второго. Её центральное решение — СИГНАЛ: perguntarEstado отображает activating в rodando — обоснованно, — поэтому systemctl is-active и service status --machine дали бы зелёный процессу, который умрёт через две секунды; выбранный сигнал — СТРОКА, которую агент пишет при входе в ожидание, которая существует только после того, как он прочитал ключ, взял тикет, поговорил с api и получил ПУСТОЙ список, и которая — ЗАПИСАННЫЙ ФАКТ, а не выборка состояния. Исход доказывается через API, а не через браузер, потому что субъект утверждения — АГЕНТ, а то, что он наблюдает, — GET /runner/projects; браузер создал бы проект через тот же маршрут с лишним слоем, падающим по посторонним причинам, а утверждение по-прежнему было бы строкой журнала. И E2E остаётся ВНЕ pull_request, со следствием, объявленным, а не скрытым (PR, который его написал, его не выполнил): открыть эту дверь — значит дать установщику способ пропустить проверку происхождения, что отвергает ADR 0150, — отсюда половина, работающая в PR, — это ТЕСТ (scripts/dev/install-e2e.spec.ts), охраняющий два способа, которыми workflow может молча сгнить: ослабленный gate и переписанная фраза install.sh (из-за которой утверждения не падают, а ИСЧЕЗАЮТ). Проявились два дефекта, оба невидимые, потому что workflow никогда не запускался: ответы TTY начинались с nao-migrar, что на чистой машине попадает на вопрос МАРКЕРА и заставляло скрипт выходить с 0, говоря «Nada foi gravado»; и НАХОДКА, оставшаяся находкой, — опубликованный install.sh ничего сам не поднимает на чистой машине, потому что docker/docker-compose.install.yml (вместе с ./postgres/init.sql) не является asset-ом Release, не входит в подписанный checksums.txt, и он его нигде не скачивает, так что тот, кто следует curl … install.sh из runbook, умирает на «no such file or directory» ПОСЛЕ того, как уже проверил подпись и записал .env. Исправление — решение между подписанным asset-ом и clone, и это отдельная сдача. Что фаза оставила ОБЪЯВЛЕННЫМ и не сделанным: отзыв по-прежнему достигает {projeto, usuário} и никогда {chave}, и теперь это стоит дороже (роняет агента во ВСЕХ проектах этой машины); нет экрана, где перечислить и отозвать ключ устройства, а фаза добавила вид для перечисления, не построив экран, — пробел стал БОЛЬШЕ, намеренно; один процесс на машину — единая точка отказа, цена замысла; и install --machine по-прежнему не знает, является ли ключ этой папки действительно ключом машины, потому что на диске оба вида — один и тот же файл, а знает это сервер

Golden-set QA снова измеряет и запускается по расписанию, еженедельно (AT-076/AT-149, ADR 0168)​

Две сдачи, одно человеческое решение между ними. AT-067 измерила, что время укладывается в ubuntu-latest без GPU и что инструмент не измерял: начиная с RN-502 npm test проекта container без контейнера running отклоняется, а seed никогда его не поднимал — 0/6 по построению. Мейнтейнер выбрал «путь 1», и AT-076 его сдала: seed поднимает контейнер каждого случая путём PRODUCTION (module_map → маршрутизация → container_start, предложенный Infra и одобренный владельцем → ExecuteContainerStartUseCase → broker → running через машину состояний), с node:24.11.1-bookworm-slim, закреплённым по digest, нетронутой RN-502 и контейнером, который не поднимается, становящимся ИМЕНОВАННОЙ ошибкой, проваливающей ExUnit (раньше {:skip, _} из тела молча проходил зелёным). Два раунда в CI дали 3/6 и 3/6 против пола 1/6, с разными случаями, за 52 и 86 min job-а. AT-149 закрыла периодичность: ЕЖЕНЕДЕЛЬНО (суббота 01:17 UTC, плюс workflow_dispatch, никогда pull_request), в golden-set-qa.yml, по образцу golden-set-rag.yml. Еженедельно, а не еженощно, потому что раунд стоит до 86 min и МЕНЯЕТСЯ — тот же итог по разным случаям, — и одного в неделю достаточно, чтобы пол обнаружил падение. timeout-minutes: 150 по худшему измеренному случаю. Вердикт — вердикт самого теста; workflow добавляет лишь две защиты ИНСТРУМЕНТА (строка счёта должна существовать, а в event log не может быть отказа RN-502) и, при сбое, печатает логи, как у RAG, — без issue. Образ для pull до seed ЧИТАЕТСЯ из константы seed, никогда из второго литерала, потому что docker run broker-а работает под управляющим пределом 30 s (RN-604). Раунд доказательства, с временным триггером push, удалённым до PR, был 36327281774: снова 3/6, ноль отказов RN-502, 10 min mix golden_set.qa и 13 min 41 s job-а. Объявлено: у broker-а CI нет пяти слоёв compose, heartbeat закрывает сессии seed, пока QA ещё в них работает (без видимого эффекта), а PR, затрагивающий workflow, его не выполняет.

Infra Lead разговаривает через composer (AT-141, ADR 0175, RN-617)​

RN-584 (AT-098) убрала получателя по умолчанию из чата и оставила продуктовый вопрос письменно: разговаривает ли Infra Lead? Мейнтейнер ответил да 2026-09-27, с условием, которое поставила сама RN-584, — сначала ход. Ход Infra Lead выполнялся ЦЕЛИКОМ внутри handle_call/handle_cast: без принятия, без «Parar», вне TurnoOrfao, с 14-м шагом цикла, заканчивающимся молча, и пределом 180 s меньше 225 s propose_action контейнера (RN-605). Kickoff, будучи cast, не держал клик, но держал процесс — 8,4 min измерено в AT-089, — и сообщение в это время ждало бы в очереди. Этап 1 перевёл три хода (kickoff, исправление gate, сообщение) в TurnoAssincrono, с консолидацией Workflows внутри Task; исправление gate, пришедшее при идущем ходе, получило очередь, потому что TurnoAssincrono без from его отбросил бы. Этап 2 дал infra клаузу message/2 (ушёл отказ agente_sem_conversa), via_for для «Parar» и место в AGENTES_DE_CHAT; принятие его handoff осталось в собственной карточке RN-499, а карточка нити исключает его по имени. SOLO_CONVERSATIONAL_AGENTS не изменился: Infra Lead — lead области. Объявлено: он не получил ни структурированных вопросов, ни чтения backlog.

Аутентифицированный git выполняется на хосте runner-а, код — в контейнере (AT-116, ADR 0193, RN-676)​

2026-10-01, раунд 36. RN-558 закрыла половину МОЛЧАНИЯ — аутентифицированный git fetch в режиме runner, при запущенном контейнере, перестал выполняться с пустыми переменными и стал отклоняться с меткой credencial-nao-atravessa-o-container, — и объявила другую половину незакрытым ADR, потому что каждый известный вариант затрагивал порт сдерживания ADR 0130. Решение владельца (01.10) выбрало тот, что не затрагивает: git-операция с учётными данными на ХОСТЕ, код в контейнере. Измерено заранее: единственный exec engine с env был fetch из RunnerGit.fetch!/3; не было ни push, ни аутентифицированного clone через exec (clone при создании папки уже выполнялся на хосте, RN-532); и папка — одна и та же с обеих сторон (estado.dir, смонтированная в /work). Выбранный дискриминатор — явная МЕТКА engine (gitCredenciado: true, ставится только через RunnerRouter.exec_git_credenciado/5), а не уже существовавший env — с env в роли ключа любая команда с env ускользнула бы из контейнера. Отказ RN-558 выжил в уменьшенном виде, для env без метки, и на практике стал означать «runner старше ADR 0193». Доказательство — AT-111, с обеих сторон: в runner-е настоящие git credential fill и git fetch origin с helper-ом GitAuth проходят на хосте при активном контейнере; в engine цепочка через настоящий TerminalChannel заканчивается {:ok, _}. ExUnit не запускались в среде сдачи (repo.hex.pm 403) и остались для CI.

Консультативная сессия без агента просит агента (AT-254, RN-682)​

2026-10-01, раунд 36. В реальном использовании 29.09 консультативная сессия без агента ответила «у меня нет доступа к предыдущим разговорам»: сообщение без получателя шло в SSE POST .../chat, который отправляет привязанной модели только текущий текст — без истории, без системного промпта — и записывает ответ с именем модели в качестве актора. Решение владельца (01.10) — просить агента, а не давать историю свободному чату. Измерено заранее: экран был единственным клиентом маршрута (curl, печатаемый seed-ом, — подсказка для разработки, в креативной сессии), а у варианта использования SendChatMessageUseCase был второй законный потребитель, smoke-тесты provider-ов, которые используют его как сквозной инструмент в сессии без агента. Поэтому отказ api живёт в МАРШРУТЕ, в собственной защите, а вариант использования остался нетронутым. На экране строка получателя стала нести собственный селектор ручного handoff, когда агента нет, — тот же привычный жест, теперь в том месте, где он — единственный способ дать сообщению назначение, — а Criativo остался вне списка, потому что консультативная сессия обещает, что он не войдёт.

Отзыв нацелен на ключ, а не на пару {projeto, usuário} (AT-013, ADR 0201, RN-685)​

2026-10-01, раунд 36. RN-520 заставила отзыв достигать живого подключения, с целью {projeto, usuário}, потому что идентичность учётных данных умирала в PatAuthGuard; ADR 0154 поставил «отзыв по ключу» в список того, чего он не делал, а PR #534 дал ключу машины ту же пару, применяемую проект за проектом. AT-013 была заблокирована по объёму и по решению, пока владелец не выбрал (01.10): по КЛЮЧУ, с ADR, затрагивая engine. Измерено заранее: тикет рождался из {userId, kind}, сокет хранил только проект/пользователь/kind, а канал сравнивал пользователя — runner того же пользователя с PAT или другим ключом падал вместе с ним; подключение ключа машины в проекте вне списка api оставалось запущенным; тикет, выпущенный до отзыва, входил после него; а отзыв PAT ничего не ронял. Сдано: два nullable-столбца в тикете (миграция Ecto, таблица принадлежит engine, — журнал Drizzle остался на 0068), guard, аннотирующий учётные данные после авторизации, id сокета, получающий вид и id учётных данных, и маршрут runner/disconnect-credential, который аннулирует ожидающие тикеты и спрашивает каждый runner кластера, родился ли он из них. Запрет AT-013 (не перестать ронять то, что падало) стал двумя переходными частями: устаревшее подключение падает по паре в проектах строки, а engine без маршрута заставляет api вернуться к паре. Пара осталась целью удаления участника. Экран сменил лишь текст подтверждения (RN-561). Runner не изменился. ExUnit не запускались в среде сдачи (repo.hex.pm 403) и остались для CI.