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

ADR 0162 — Broker становится пятым опубликованным образом, а установщик СПРАШИВАЕТ, включать ли его

Статус​

Accepted. Решено сопровождающим 2026-09-18 (AT-097). Закрывает решение 7 ADR 0150 («broker в этой фазе не поднимается, и последствие названо»), расширяет ADR 0119 с четырёх до пяти образов и применяет к установке довод, который ADR 0146 (RN-512) применил к compose разработки. Сам broker — пять слоёв, пять операций, вычисляемая spec — это broker из ADR 0130, и в нём ничего не меняется. Ни один из них не редактируется; этот документ на них ссылается.

Брат: ADR 0161 поставил половину WEB того же решения сопровождающего («оба») — создание проекта предлагает только режим, который установка исполняет, — и письменно объявил, что половина УСТАНОВЩИКА останавливалась, потому что образ не публиковался. Этот ADR — та половина. Они дополняют друг друга, не завися друг от друга: тот не даёт экрану предлагать то, что установка не исполняет; этот даёт установке способ исполнять, с согласием. При включённом broker у установки появляется BROKER_URL, и именно по нему экран из 0161 снова предлагает оба режима.

Контекст​

В реальной установке проект container или mounted никогда не исполняется. Контейнер этих двух режимов поднимает broker (ADR 0144 ветвит по НАЗНАЧЕНИЮ: container и mounted → broker), а у compose установки нет этого сервиса. BROKER_URL остаётся неопределённой, container_start заканчивается container.start_failed с BrokerIndisponivelError, а dev agents навсегда остаются в dev.blocked_by_container — RN-502 по праву отказывается исполнять вне контейнера running.

Решение 7 ADR 0150 это объявило и назвало исправление маленьким. Измерено 2026-09-18, при открытии этого направления:

  • docker manifest inspect ghcr.io/daneiel/brabo-broker:6.1.0 → denied; тот же вопрос для brabo-api:6.1.0 разрешается. Образа нет в registry.
  • В docker-bake.hcl было четыре цели (api, engine, web, backup), а scripts/ci/images-manifest.ts принимал ровно эти четыре.
  • docker/broker/Dockerfile.prod уже существовал и уже проходил через hadolint в ci.yml вместе с остальными четырьмя — FROM по digest с тегом на строке выше, USER node, HEALTHCHECK против /health, npm/corepack удалены. Недоставало не образа: недоставало того, кто его соберёт, просканирует и опубликует. ci.yml никогда его не собирал; docker-compose.prod.yml ссылался на него как brabo-broker:prod, который существовал лишь на машине того, кто запустил docker compose build с profile.
  • Release не несёт код, поэтому --source=local — путь, который ADR 0150 указывал как выход, — требует checkout на теге, а это не установка одной строкой.

Решение​

1. Broker — ПЯТЫЙ опубликованный образ, с теми же gates, что у остальных​

Цель broker в docker-bake.hcl, внутри группы default: ci.yml начинает собирать его на каждом PR, проверять, что он не работает от root, сканировать его в Trivy по тем же правилам (HIGH,CRITICAL, --ignore-unfixed) и доказывать, что он ПОДНИМАЕТСЯ healthy с rootfs только для чтения. release.yml публикует его по DIGEST, images-manifest.ts его требует (недостающая цель по-прежнему проваливает — пять из пяти или ничего), а keyless-подпись cosign и проверка в том же запуске (RN-524) покрывают его без новой строки, потому что оба цикла читают манифест.

Kubernetes о нём не знает, и это решение, а не забывчивость: в deploy/k8s/ нет Deployment broker, а монтирование сокета узла в pod — другой разговор о привилегиях. argumentosDeSetImage начинает выдавать только образы, которые объявляет база kustomize, — make imagens-do-release по-прежнему пишет четыре строки в overlay.

2. Compose установки получает сервис, ВЫКЛЮЧЕННЫЙ по умолчанию​

Сервис входит под profiles: ["container-broker"], ТЕМ ЖЕ profile, что в compose проверки, и с пятью слоями ADR 0130 нетронутыми: без ports:, только в сети broker (internal: true, которой достигает только api и у которой нет egress), BRABO_SERVICE_TOKEN обязателен в продакшене, пять операций, spec, вычисляемая из того, что говорит api. Сокет монтируется в нём и ни в каком другом. api начинает всегда участвовать в сети broker — как уже делает в compose проверки, — а BROKER_URL в api остаётся пустой, если только .env не говорит иного.

Образ берётся из BRABO_BROKER_IMAGE, обязательной, как и остальные четыре, и это измерено, а не выбрано: Compose интерполирует ВЕСЬ файл до фильтрации по profile, поэтому ${VAR:?…} в выключенном сервисе отклоняет файл точно так же. Установщик всегда её записывает (она приходит из того же манифеста), включает он broker или нет.

Два корня — как в compose проверки: BRABO_PROJECTS_HOST_BASE выводится из BRABO_PROJECTS_BASE, которая уже путь хоста. Другой, PROJECT_WORKSPACES_HOST_ROOT, невозможно вывести в compose — управляемая папка установки — это ИМЕНОВАННЫЙ том project_workspaces, а путь, который разрешает демон, — путь ХОСТА. Установщик его ВЫЧИСЛЯЕТ (<DockerRootDir>/volumes/brabo_project_workspaces/_data, раскладка драйвера local) и после подъёма СВЕРЯЕТ с Mountpoint, который демон возвращает для тома; расхождение становится названным хвостом с правильным значением, никогда молча угаданным значением.

3. Установщик СПРАШИВАЕТ, с риском, сказанным текстом, и по умолчанию — НЕТ​

Новый вопрос, после базы и до .env. Он говорит, что предоставляет включение — сервис этой установки начинает говорить с Docker этой машины, и кто командует этим сервисом, командует Docker, — что сдерживают слои и что меняется без него (проекты container и mounted не исполняются; режим Runner от него не зависит). Включает только s/sim. Enter, что-либо другое или отсутствие терминала оставляют выключенным, и скрипт ГОВОРИТ, что осталось выключенным.

«Да» записывает в .env COMPOSE_PROFILES=container-broker, BROKER_URL=http://broker:8090, DOCKER_GID и вычисленный корень. DOCKER_GID измеряется изнутри контейнера, самим образом broker и с сокетом, смонтированным через --mount type=bind (который отказывает, а не создаёт папку, когда источника нет), потому что group_add нужен именно gid, который процесс видит там, — а в Docker Desktop gid сокета на хосте ничего не говорит о gid внутри VM. Невозможность измерить или то, что путь не является сокетом, — НАЗВАННЫЙ отказ, обучающий запустить снова и ответить «нет»; никогда угаданный 999.

Решение появляется в итоговой сводке, а фраза «чего этот установщик НЕ делает» перестаёт говорить, что без контейнера остаётся только «Pasta montada»: container тоже остаётся.

4. Почему довод RN-512 действует здесь​

ADR 0146 разделил два compose по тому, что ЗНАЧИТ сокет: в разработке тот, кто поднимает стек, уже имеет сокет, так что получение его broker не предоставляет ничего, чем оператор не владел бы; в продакшене сокет — граница привилегий, и оператор — не разработчик.

Установка одной строкой — первый случай, а не второй. Она работает на машине самого человека, который только что выполнил docker compose up, — установщик не работает без того, чтобы у него был сокет. Оператор и пользователь — один человек. Чего установка НЕ может предполагать, и поэтому она спрашивает, а не включает, как compose разработки, — что тот, кто устанавливает, знает, что такое сокет: compose dev читает тот, кто разрабатывает продукт; установщик запускает тот, кто никогда не открывал этот репозиторий. Явное согласие — оставшееся различие, и именно его покупает этот ADR.

docker-compose.prod.yml не меняется: он остаётся под profile, и для него прочтение ADR 0146 действует без оговорок.

Последствия​

Цена, объявленная.

  • Root-эквивалентный образ становится публичным. Любой может скачать ghcr.io/daneiel/brabo-broker. Сам образ ничего не предоставляет — предоставляет смонтированный сокет, — но он становится изучаемой целью, и уязвимость в нём превращается, в каждой установке, где его включили, в путь до Docker хоста. Поэтому он входит в Trivy каждого PR и в подпись каждого тега, а не в отдельный список.
  • Сокет Docker начинает монтироваться в сервис пользовательской установки, когда она соглашается. Кто командует broker, командует Docker машины. Что его отделяет: он не публикует порт, его достигает только api, токен сравнивается за постоянное время, и нет поля, куда можно написать privileged, cap_add, network: host или свободный -v. Это сдерживает вызывающего; это не сдерживает баг в самом broker.
  • BRABO_BROKER_IMAGE становится обязательной в .env установки, включено или нет. .env, записанный предыдущим установщиком, её не имеет — но установщик перезаписывает .env при каждом запуске, а обновление идёт через него.
  • Установщик требует broker в images.json. У Release, предшествующей этой, его нет, и установщик этой версии отказывает, называя цель. Это то же правило, что в ADR 0160: установщик тега устанавливает этот тег.
  • Управляемый корень зависит от раскладки драйвера local. Вычисляется до подъёма, сверяется после; несовпадение становится хвостом со значением, прочитанным у демона.
  • Не измерено: DOCKER_GID и вычисленный корень в Docker Desktop на macOS. E2E тега выполняет интерактивную половину только в Linux.

Что НЕ меняется. Compose проверки, smoke.sh (который поднимается без profile и продолжает доказывать те же три образа приложения), compose разработки, Kubernetes и сам broker.