Конфигурация
Вся конфигурация задаётся переменными окружения. Файла конфигурации
приложения нет — есть permissions.json, но это политика проекта, а не
конфигурация процесса.
Два версионируемых файла читаются во время выполнения и конфигурацией не
являются, хотя их легко с ней спутать: permissions.json выше и
docs/gates.yml (ADR 0054),
декларативный реестр gates. Ни у одного из них нет переменной окружения,
указывающей путь, — реестр находится поднятием от __dirname, — и ни один не
меняет поведение при редактировании в production: реестр ОПИСЫВАЕТ gates, а не
применяет их. Он едет внутри образа api; см.
runbook.
Едет реестр и ничего из того, на что он указывает. evidencia gate
teste/ci называет файлы под apps/api/test/, scripts/ci/ и .github/, и
ни одного из них в образе нет. Проверка существования этих целей поэтому —
утверждение о репозитории, проверяемое в CI, а не процессом, обслуживающим
GET /gates, — поэтому api читает реестр, не разыскивая их. Раньше она
разыскивала и отвечала 500 в каждой установке
(RN-070).
up --waitдоказывает только то, у чего есть healthcheck. В compose для разработки уapi,engineиwebих не было, поэтомуdocker compose up --waitсчитал их готовыми в момент старта контейнера — до того, как процесс начинал слушать. Так контейнер мог умереть через секунды, а команда всё ещё сообщала об успехе. Теперь все три несут ту же проверку, которую образы production уже объявляют в Dockerfile (/healthв api и engine, затрагивающий базу;/на сервере Vite). Брокер контейнеров получил свою в отдельном изменении; уpostgresиneo4jони были всегда. Ничего здесь не является переменной окружения — это написано потому, что на--waitполагаются скрипты, а сервис без healthcheck заставляет его лгать.
Значения по умолчанию ниже извлечены из кода, а не из прежней документации. Колонка когда ломается — та часть, что экономит время: почти у каждой переменной есть умолчание, работающее в разработке, и особый режим отказа в production.
Переменная доходит до процесса, только если compose её отображает.
docker composeне передаёт окружение хоста внутрь контейнера: переменная доходит, если — и только если — она записана вenvironment:этого сервиса (или вenv_file). Это не мелочь: именно такANAMNESE_ENABLEDиPSYCHOLOGIST_ENABLEDмолча стали недостижимы, пока три места в коде обещали, что пауза от 2026-08-10 отменяется черезX=trueи перезапуск. Измеренные внутри работающего контейнера engine, они вернулись пустыми, иruntime.exsупал в"false"— без ошибки где-либо. Каждый булев флаг, который читает engine, теперь отображён в трёх файлах compose (dev, production и установка — установочный только с AT-202), с умолчанием самого кода, кроме двух ключей загрузки фоновых агентов, которые production и установка намеренно выключают, иscripts/ci/flags-do-engine-no-compose.spec.tsпроваливает сборку для следующего, которого не окажется (RN-540). Kubernetes исключён из этого правила намеренно: Deployment/ConfigMap ничего не перехватывает, так что там нет сломанного ключа, который нужно чинить.
Умолчания разработки намеренно небезопасны. Значения вроде
dev-master-key-change-meсуществуют, чтобыpnpm devподнимался без церемоний. В production их нужно заменить — и для шести из них процесс отказывается стартовать без замены (api или engine, помечены 🔒). Пять следуют образцу ADR 0059/RN-093: отсутствующее, равное публичному литералу-примеру или слишком короткое значение роняет загрузку. См. RN-114 о четырёх, присоединившихся к исходномуGIT_OAUTH_STATE_SECRET.
Compose установки (docker-compose.install.yml)
Compose, работающий на установленной машине, берёт пять образов продукта из
обязательных переменных, без умолчания — install.sh записывает их в
.env:
| переменная | что |
|---|---|
BRABO_API_IMAGE | полная ссылка на образ api; ghcr.io/…@sha256:…, когда источник GHCR, brabo-api:prod, когда собран локально |
BRABO_ENGINE_IMAGE | то же для engine |
BRABO_WEB_IMAGE | то же для web |
BRABO_BACKUP_IMAGE | то же для job резервного копирования (под profile backup) |
BRABO_BROKER_IMAGE | то же для брокера контейнеров (ADR 0162). Записывается независимо от того, включён ли брокер: Compose интерполирует весь файл до фильтрации по profile, так что ${VAR:?…} в выключенном сервисе всё равно отклоняет файл (измерено) |
Они намеренно ${VAR:?…}. С умолчанием отсутствующая переменная подняла бы
половину стека на образе, который никто не выбирал, и ошибка проявилась бы как
странное поведение, а не как отказ.
Их нет в сгенерированном инвентаре ниже: генератор обходит apps/, а эти
читает Compose, а не код продукта
(RN-527).
Брокер есть в этом compose с ADR 0162,
выключенный, под profile container-broker, и install.sh включает его,
только когда спрашивает и ответ «s» (RN-575).
Тогда он записывает четыре строки — все вместе или ни одной:
| переменная | что записывает install.sh |
|---|---|
COMPOSE_PROFILES | container-broker — читается Compose из --env-file, так что up -d --wait поднимает брокер без флага |
BROKER_URL | http://broker:8090 — сторона api; без неё api никогда не вызывает брокер |
DOCKER_GID | gid сокета, видимый изнутри контейнера, измеренный самим образом брокера; сбой измерения — именованный отказ, никогда умолчание 999 |
PROJECT_WORKSPACES_HOST_ROOT | <DockerRootDir>/volumes/brabo_project_workspaces/_data, вычисляется до подъёма и сверяется с реальным Mountpoint тома после него |
BRABO_PROJECTS_HOST_BASE не записывается: compose выводит её из
BRABO_PROJECTS_BASE, как в compose валидации. Включение или выключение позже
описано в runbook.
О локальных моделях (Ollama, profile llm) спрашивают сразу после брокера
(RN-691). Ответ «s» сначала измеряет свободный
порт хоста и записывает:
| переменная | что записывает install.sh |
|---|---|
OLLAMA_PORT | 11434 или первый свободный порт до 11443, когда что-то (нативный Ollama) уже слушает там; весь занятый диапазон — именованный отказ, без записи чего-либо |
COMPOSE_PROFILES | llm, вместе с container-broker в ОДНОЙ строке, только когда приняты оба (container-broker,llm) — две строки не складываются, побеждает последняя |
Без них проверка семантического дубликата пропускается. Включение позже — в runbook.
api
Основные
| переменная | умолчание | когда ломается |
|---|---|---|
DATABASE_URL | postgres://brabo:brabo@localhost:5432/brabo | без неё ничего не поднимается |
PORT | 3000 | — |
API_JSON_BODY_LIMIT | 10mb | потолок тела JSON, которое принимает api, применяемый к парсеру, зарегистрированному в main.ts, до любого маршрута. Существует, чтобы изменение транспортного потолка engine (TRANSPORT_MAX_BODY_BYTES, 8 MiB) не требовало нового deploy — умолчание оставляет запас над ним. Слишком маленький — и ход с большим контекстом возвращается как 413 |
NODE_ENV | — | production включает строгий CORS и проверку ключей |
API_PUBLIC_URL | http://localhost:3000 | используется в callbacks OAuth git; неверная = сломанный callback |
ENGINE_URL | http://localhost:4000 в коде, http://engine:4000 в Compose | синхронные команды api→engine падают. Оставьте её пустой в .env: заданная там, она побеждает умолчание Compose, и api пытается говорить с localhost:4000 изнутри собственного контейнера — каждая активация сессии умирает с ECONNREFUSED, и frontend не двигается с места. У каждого окружения уже есть правильное умолчание без этой строки |
ENGINE_PUBLIC_URL | то же, что ENGINE_URL | используется только для сборки engineWsUrl (WebSocket), возвращаемого в POST .../runner-ticket/.../terminal-ticket — runner и web могут быть ВНЕ кластера, поэтому внутренний адрес (http://engine:4000) не подходит; ENGINE_URL по-прежнему используется в обычных синхронных вызовах api→engine (RN-419) |
BRABO_VERSION | dev | становится service.version в resource OpenTelemetry — так узнают, какая сборка породила трассу. Образ релиза внедряет тег через ARG из docker-bake.hcl; вне релиза остаётся dev. Не появляется в /health, который намеренно не возвращает версию (см. description маршрута) |
MIGRATIONS_FOLDER | ./src/db/migrations | — |
Безопасность 🔒
| переменная | умолчание | когда ломается |
|---|---|---|
CREDENTIALS_MASTER_KEY 🔒 | dev-master-key-change-me только вне production | оборачивает DEKs. В production api отказывается стартовать, если её нет, если это умолчание выше (публичное — оно в .env.example) или если в ней меньше 16 символов (RN-114). Это лишь проверка ЗАГРУЗКИ: замена на действительный, но другой ключ без перезаворачивания всё равно делает все учётные данные нечитаемыми без какой-либо ошибки — сбой проявляется при первом использовании. См. ротацию |
CREDENTIALS_MASTER_KEY_PREVIOUS | — | только во время ротации. Присутствует = api пробует предыдущий ключ, когда текущий не подходит |
GIT_OAUTH_STATE_SECRET 🔒 | dev-oauth-state-secret-change-me только вне production | подписывает state OAuth; слабый = CSRF в потоке подключения git. В production api отказывается стартовать без него, с умолчанием выше (публичным — оно в .env.example) или с менее чем 16 символами. Генерируйте openssl rand -base64 32. См. ADR 0059 и RN-093 |
WEB_ORIGIN 🔒 | http://localhost:${WEB_PORT} | в production api отказывается стартовать, если её нет или она равна *. CORS строгий для каждого окружения. Порт — часть значения: web на :5174 — другой origin и блокируется — см. ADR 0037. В composes умолчание выводится из WEB_PORT, так что смена порта тянет за собой CORS; ручное задание WEB_ORIGIN перекрывает вывод, и поддерживать согласованность становится вашей ответственностью |
WEB_PORT | 5173 (dev) · 8088 (prod) | опубликованный порт web на хосте. Ни один сервис её не читает — она питает умолчание WEB_ORIGIN в composes, и именно это не даёт порту и CORS разойтись |
Auth first-party
Auth живёт в домене api, а после перехода является и единственным эмитентом. Решения в ADR 0031 и ADR 0032.
| переменная | умолчание | что делает |
|---|---|---|
AUTH_JWT_SECRET 🔒 | dev-auth-jwt-secret-change-me только вне production | фраза, из которой пара Ed25519 access token выводится через scrypt — ни один приватный ключ не коммитится. В production api отказывается стартовать, если её нет, если это умолчание выше (публичное — оно в .env.example) или если в ней меньше 16 символов (RN-114) |
AUTH_JWT_SECRET_PREVIOUS | — | принимается только для проверки во время ротации; входит в JWKS и никогда не подписывает |
AUTH_TOKEN_PEPPER 🔒 | dev-auth-token-pepper-change-me только вне production | ключ HMAC хэша непрозрачных токенов (refresh, привязки аккаунтов, PATs) и ключа корзины блокировки. Собственный секрет — больше не падает в AUTH_JWT_SECRET (RN-613). В production api отказывается стартовать, если его нет, если это любое из значений-примеров этого репозитория или если в нём меньше 16 символов. Установка, работавшая без него, должна задать его текущим AUTH_JWT_SECRET, иначе всех разлогинит — см. runbook |
AUTH_ACCESS_TOKEN_TTL_MS | 900000 | 15 мин |
AUTH_REFRESH_TOKEN_TTL_MS | 1209600000 | 14 дней |
AUTH_REFRESH_ABSOLUTE_TTL_MS | 2592000000 | абсолютный потолок семейства, считая от входа — без него ротация даёт вечную сессию |
AUTH_REGISTRATION_ENABLED | true | любое значение, отличное от "false", оставляет регистрацию открытой |
AUTH_LOCKOUT_ENABLED | true | то же соглашение |
AUTH_LOCKOUT_WINDOW_MS | 900000 | скользящее окно подсчёта |
AUTH_LOCKOUT_THRESHOLDS | 5:30,8:300,12:900 | лестница корзины по email, сбои:секунды |
AUTH_LOCKOUT_IP_THRESHOLDS | 20:30,30:120 | лестница корзины по IP, мягче и с коротким потолком |
AUTH_EMAIL_TOKEN_TTL_MS | 172800000 | подтверждение email, 48 ч |
AUTH_RESET_TOKEN_TTL_MS | 3600000 | сброс пароля, 1 ч |
AUTH_SET_PASSWORD_TTL_MS | 604800000 | установка первого пароля (мигрированный пользователь), 7 дней — дольше сброса, потому что получатель его не запрашивал |
AUTH_IP_ATTEMPT_THRESHOLD | 60 | потолок попыток по IP на маршрутах auth |
AUTH_MAIL_LOG_TOKENS | false | только dev: печатает в лог токен подтверждения/сброса |
Потолок лестницы по email намеренно равен окну. При скользящем окне настойчивый сдвигает окно и остаётся заблокированным, пока настаивает; переставший возвращается с чистым окном. Потолок больше окна создал бы блокировку, которую окно не может представить, и потребовал бы постоянной колонки
locked_untilс очередью разблокировки. Не трогайте одно без другого.
Ротация
AUTH_TOKEN_PEPPERразлогинивает всех и аннулирует ожидающие токены подтверждения и сброса. В отличие от ключей, у pepper нет_PREVIOUS. См. runbook.
Докуда доходят переменные
_PREVIOUS.AUTH_JWT_SECRET_PREVIOUS,CREDENTIALS_MASTER_KEY_PREVIOUSиBRABO_SERVICE_TOKEN_PREVIOUSотображены вenvironment:трёх composes (dev, production и установка) как${X:-}— api получает все три, engine и брокер получаютBRABO_SERVICE_TOKEN_PREVIOUS(RN-595). Пустое значение равносильно отсутствию для каждого читателя, так что вне ротации ничего не меняется; во время неё достаточно задать их в.envи пересоздать сервис. До RN-595 они не были отображены нигде, аdocker composeне передаёт окружение хоста, так что ротация молча превращалась в жёсткую замену. В Kubernetes они доходят до Pods через хранилище секретов (AT-220):ExternalSecretвытягивает весь объектbraboчерезdataFrom.extract, так что присутствующий там_PREVIOUSпопадает вbrabo-secrets, а отсутствующий просто там отсутствует. Цена в том, что каждый ключ этого объекта становится переменной окружения api, engine, migrations и backup — объект хранилища не может хранить ничего другого. Процедура в runbook.
Настоящий SMTP (MailSender)
MailSender отправляет настоящий email только с MAIL_TRANSPORT=smtp —
умолчание log (ссылка/токен идут в лог api, AUTH_MAIL_LOG_TOKENS выше), в
том числе в production: отправка email — выбор оператора. Решение в
ADR 0096.
| переменная | умолчание | что делает |
|---|---|---|
MAIL_TRANSPORT | log | log (умолчание) или smtp. Любое другое значение падает в log |
SMTP_HOST 🔒 | — | хост SMTP-провайдера. Только с MAIL_TRANSPORT=smtp: в production api отказывается стартовать, если его нет, если он из одних пробелов или если это значение-пример, опубликованное в .env.example (RN-114) |
SMTP_PORT | 587 | порт провайдера — 587 это STARTTLS, 465 — неявный TLS (SMTP_SECURE=true) |
SMTP_SECURE | false | true включает неявный TLS на соединении (обычно порт 465) |
SMTP_USER 🔒 | — | пользователь auth SMTP. То же требование, что и SMTP_HOST, в production |
SMTP_PASSWORD 🔒 | — | пароль/токен auth SMTP. То же требование, что и SMTP_HOST, в production — никогда не появляется в логе |
SMTP_FROM | — | отправитель в формате "Имя <email@домен>" (или только email). То же требование, что и SMTP_HOST, в production, плюс проверка формата |
Тело email — простой текст, без HTML: порт
MailSenderне несёт структуры для богатого тела, а шаблонизатор был бы поверхностью инъекции без выгоды. Ссылка используетWEB_ORIGIN(выше).
Seed для разработки
Потребляются pnpm --filter api seed, а не работающей api. Без внешнего IdP
отсюда берутся учётные данные для входа в локальный web и в smoke.
| переменная | умолчание | что делает |
|---|---|---|
BRABO_SEED_PASSWORD | brabo12345678 | пароль засеянных пользователей (owner@brabo.dev, dev@brabo.dev), создаваемых с уже подтверждённым email |
BRABO_FORCE_SEED | — | разблокирует seed при NODE_ENV=production, где по умолчанию он отказывается запускаться. Не задавайте её в реальном окружении: аккаунт рождается с известным и подтверждённым паролем |
Seed идемпотентен и не трогает пароль уже существующих. Повторный запуск после того, как кто-то сменил свой пароль, его не откатывает.
Rate limit
Скользящее окно в Postgres — Redis нет (ADR 0027).
| переменная | умолчание | что делает |
|---|---|---|
RATE_LIMIT_ENABLED | true | любое значение, отличное от "false", оставляет его включённым |
RATE_LIMIT_WINDOW_MS | 60000 | размер окна |
RATE_LIMIT_USER | 300 | запросов на пользователя за окно |
RATE_LIMIT_IP | 600 | запросов на IP за окно |
Если таблица rate limit недоступна, запрос проходит. Guard защищает от злоупотреблений, а не от несанкционированного доступа — это работа guard аутентификации, который выполняется раньше.
Внутренний трафик 🔒
Общий секрет, аутентифицирующий api ↔ engine. Одна и та же переменная с обеих сторон — каждая отправляет текущее значение и принимает оба, и именно это делает ротацию возможной без простоя (RN-035).
| переменная | умолчание | что делает |
|---|---|---|
BRABO_SERVICE_TOKEN 🔒 | dev-service-token-change-me только вне production | идёт в заголовке X-Brabo-Service-Token и сравнивается EngineServiceGuard за постоянное время. В production api отказывается стартовать, если его нет, если это умолчание выше (публичное — оно в .env.example) или если в нём меньше 16 символов (RN-114) |
BRABO_SERVICE_TOKEN_PREVIOUS | — | принимается только для проверки во время ротации. Когда задан, проходит то же правило, что и текущий токен: окружающие пробелы обрезаются, и в production api отказывается стартовать, если это публичное умолчание или в нём меньше 16 символов. В отличие от текущего, никогда не обязателен (RN-598) |
Задание только НОВОГО значения с одной стороны (без танца с
_PREVIOUS) никому не ломает загрузку: симптом —403на/internal/*и401в вызовах api к engine. Процедура в runbook. Проверка ЗАГРУЗКИ выше (RN-114) — другое: она падает только с публичным умолчанием или отсутствующей/короткой переменной, а не при расхождении сторон.
Git
| переменная | умолчание | примечание |
|---|---|---|
GIT_LOCAL_REPOS_ROOT | /tmp/brabo-git-repos | локальный provider. В /tmp репозитории исчезают при перезагрузке |
PROJECT_WORKSPACES_ROOT | /tmp/brabo-project-workspaces | worktrees агентов проекта в режиме Container. Должен быть тем же путём в engine и тем же томом |
BRABO_PROJECTS_BASE | пусто | единая папка хоста, под которой живут проекты в режиме Mounted, монтируемая по идентичности в api и engine. Пусто — НОРМАЛЬНОЕ состояние: api сообщает projectsBase: null, и мастер проекта даже не предлагает режим Mounted |
GITHUB_OAUTH_CLIENT_ID / _SECRET | пусто | пусто = OAuth-подключение к GitHub недоступно (PAT продолжает работать) |
GITLAB_OAUTH_CLIENT_ID / _SECRET | пусто | то же |
Проект в режиме Mounted: одна база, монтируемая по идентичности
С ADR 0072 проект может родиться в
режиме Mounted (прежний local, переименованный
ADR 0104)
— код живёт в папке пользователя, и PROJECT_WORKSPACES_ROOT не участвует в
его корне.
Путь проекта — данные проекта (projects.workspace_path), выбираемые при
создании и никогда не переменная окружения. ОКРУЖЕНИЕ предоставляет базу,
под которой живут эти папки, — BRABO_PROJECTS_BASE, одну на установку
(ADR 0141,
RN-500):
# .env — ABSOLUTO. `~` não é expandido pelo Compose.
BRABO_PROJECTS_BASE=/home/voce/projetos-brabo
Писать эту строку вручную не нужно. С
ADR 0146 pnpm bootstrap →
Docker › Base de projetos запускает scripts/dev/consentir-base.mjs, который
предлагает $HOME/projetos-brabo, отклоняет то, что пересекается с checkout
Brabo или с PROJECT_WORKSPACES_HOST_DIR (два корня с противоположными
владельцами — общая папка заставила бы проект Mounted с именем loja упасть
поверх проекта Container, чей workspace_dir_name тоже loja), доказывает на
macOS и Windows, что Docker действительно видит папку, монтируя её, и только
затем записывает переменную.
Доказательство — это монтирование, а не чтение settings.json Docker Desktop:
этот файл не документирован, меняется между версиями и платформами и описывает
то, что настроил пользователь, а не то, что сделает daemon, — то же правило,
которое продукт уже применяет к providers LLM, где capability объявляется только
тогда, когда доказана выполнением. У него три исхода, и они не
схлопываются: доказано, отклонено (переменная не записывается) и доказать не
удалось — нет локального образа для монтирования, — тогда переменная
записывается, и вслух сообщается, что доказательство не выполнялось.
Запущенный из меню, он только информирует: каждый пункт меню bootstrap
намеренно запускается со stdin из /dev/null, так что спрашивать он не может.
Чтобы выбрать, запустите node scripts/dev/consentir-base.mjs в собственном
терминале.
# docker/docker-compose.yml — em `api` E `engine`, já escrito
volumes:
- ${BRABO_PROJECTS_BASE:-brabo_projects_base}:${BRABO_PROJECTS_BASE:-/data/brabo-projects-base}
Оператор задаёт это один раз и перезапускает api + engine; ничего не
редактируется по проекту, никогда. До этого каждый смонтированный проект
требовал вручную написанной строки bind-mount в обоих сервисах и перезапуска —
который убивает каждый ход агента, сокет терминала и вызов LLM в полёте в
установке.
Монтирование выполняется по идентичности ($X:$X): тот же абсолютный путь
на хосте и внутри обоих контейнеров. Именно это сохраняет честной строку,
которую вводит пользователь и которую экран показывает обратно, и именно поэтому
projectScopeRoot (api) и Engine.Actions.Workspace.workspace_dir/2 (engine) не
нуждаются в слое трансляции.
Это не PROJECT_WORKSPACES_HOST_DIR, и они никогда не могут указывать на одну
папку. Управляемый workspace именуется workspace_dir_name (UNIQUE), а
смонтированный проект именуется пользователем, так что <base>/loja и
container-проект, чья папка называется loja, столкнулись бы, и bootstrap
репозитория запустил бы git init внутри чужого проекта. В ADR есть два других
довода.
Когда переменная не задана, режим Mounted не предлагается — никогда не
предлагайте режим, который установка не может выполнить. А pnpm dev
отказывается подниматься, когда база пересекается с checkout Brabo в любом
направлении; эта проверка живёт в preflight, потому что он работает на хосте, а
api может сравнивать только с process.cwd() (/workspace внутри собственного
контейнера). См. runbook.
LLM
| переменная | умолчание | примечание |
|---|---|---|
OLLAMA_HOST | http://localhost:11434 | — |
OLLAMA_REQUEST_TIMEOUT_MS | 300000 | потолок бездействия сокета Ollama, а не общей длительности. У локальной модели задержка до первого токена другого порядка, отсюда собственная переменная; см. окружение инференса |
LLM_REQUEST_TIMEOUT_MS | 300000 | тот же потолок бездействия для API-providers (OpenAI и совместимые, Anthropic). Действует для «даже не прислал заголовки» и для «перестал слать фрагменты посреди потока» — см. providers LLM |
HUGGINGFACE_API_TOKEN | (не задана) | необязательный токен Hugging Face Hub для поиска моделей (GET /workspaces/:workspaceId/huggingface/models). Присутствует — идёт как Authorization: Bearer … и поднимает rate limit Hub; отсутствует — заголовок просто не отправляется: Hub принимает публичный поиск без учётных данных, и отсутствие токена никогда не ошибка |
HUGGINGFACE_HUB_URL | https://huggingface.co | базовый URL Hub, с которым говорит поиск. Переопределение для тестов (фальшивый HTTP-сервер на эфемерном порту); в production не задаётся |
HUGGINGFACE_REQUEST_TIMEOUT_MS | 15000 | потолок бездействия запроса поиска к Hub, та же семантика, что LLM_REQUEST_TIMEOUT_MS (имя собирается в константе, поэтому сгенерированный инвентарь ниже его не видит) |
TODO(humano): ни один файл compose не отображает три переменные
HUGGINGFACE_*вenvironment:сервисаapi, а у сервиса нетenv_file, так что их задание в.envне действует под Compose — нужно ли передаватьHUGGINGFACE_API_TOKEN(хотя бы в production и установке)?
Граф знаний (ADR 0099)
| переменная | умолчание | примечание |
|---|---|---|
NEO4J_URI | — | напр. bolt://neo4j:7687. Это и есть ключ: пусто = граф ВЫКЛЮЧЕН, зависимые маршруты деградируют (GraphUnavailableError/503) — никому не нужен локальный Neo4j только ради набора тестов. В production отсутствие любой из трёх роняет загрузку. Включение графа в разработке — эта строка в .env и перезапуск api; до 2026-09-04 это ничего не делало, потому что compose для dev не передавал три переменные сервису api, а у сервиса нет env_file — docker-compose.prod.yml предоставлял их с первого дня, поэтому затронута была только разработка |
NEO4J_USER | neo4j (dev) | доходит до контейнера с тем же умолчанием, что сервис neo4j использует для NEO4J_AUTH, так что задавайте её только чтобы СМЕНИТЬ пользователя — смена там меняет обе стороны разом. Два независимых умолчания оставили бы api аутентифицирующейся учётными данными, которых у сервера больше нет |
NEO4J_PASSWORD 🔒 | dev-neo4j-password-change-me (только dev) | то же правило пары, что NEO4J_USER. Без публичного умолчания в production намеренно — правдоподобного «значения-примера» для пароля базы нет, и docker-compose.prod.yml оставляет NEO4J_AUTH пустым, чтобы сам entrypoint официального образа отказался стартовать, а не стартовал с угадываемым паролем |
GRAPH_PROJECTOR_INTERVAL_MS | 2000 | период poller, опустошающего очередь graph_projection в outbox и пишущего handoffs/гипотезы/профили/взаимодействия в граф (RN-416) |
ARTIFACT_PROJECTOR_INTERVAL_MS | 2000 | период poller, опустошающего очередь artifact_projection в outbox и пишущего артефакты каждого агента как Markdown в папку docs/ проекта (RN-523). ПРОИЗВОДНАЯ проекция: источник — event log, и папку можно удалить и восстановить |
Наблюдаемость
| переменная | умолчание | примечание |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT | — | отсутствие выключает ЭКСПОРТ, а не инструментирование (ADR 0035). Span по-прежнему создаётся, и trace_id по-прежнему появляется в логе — это даёт корреляцию в разработке без коллектора. Без неё span отбрасывается в конце, а не покидает процесс |
OTEL_SERVICE_NAME | brabo-api | — |
OTEL_DIAG_LOG | — | 1 включает диагностический лог самого OTel |
LOG_LEVEL | info в production, debug вне | вместе с NODE_ENV определяет и ФОРМАТ: вне production лог читаемый (pino-pretty в процессе, с деревом слоёв); в production — одна строка JSON на событие |
METRICS_GAUGE_INTERVAL_MS | 15000 | период сбора доменных gauges |
engine
Основные
| переменная | умолчание | когда ломается |
|---|---|---|
DATABASE_URL | ecto://brabo:brabo@localhost:5432/brabo | обратите внимание на схему ecto://, а не postgres:// |
POSTGRES_HOST / _USER / _PASSWORD | localhost / brabo / brabo | используются, когда DATABASE_URL не собран |
POOL_SIZE | — | исчерпанный pool блокирует Oban, и очередь перестаёт потребляться |
PORT | 4000 | — |
PHX_HOST / PHX_SERVER | — | PHX_SERVER=true — то, что заставляет релиз обслуживать HTTP |
SECRET_KEY_BASE 🔒 | — | обязательна в релизе (runtime.exs, блок :prod, стандартный raise Phoenix). До RN-114 docker-compose.prod.yml предоставлял публичный литерал как fallback и маскировал этот raise — переменная всегда приходила ЗАДАННОЙ. Сам raise не изменился |
API_URL | http://localhost:3000 | engine обращается к api обратно отсюда |
ECTO_IPV6 | — | — |
SKIP_MIGRATIONS | — | используется Job миграции |
Кластер и остановка
| переменная | умолчание | примечание |
|---|---|---|
DNS_CLUSTER_QUERY | — | headless Service, формирующий кластер Erlang. Без неё каждая реплика — остров, и каждый rollout дренирует всё |
SHUTDOWN_DRAIN_TIMEOUT_MS | 45000 | окно preStop. Повышается вместе с terminationGracePeriodSeconds, никогда в одиночку |
SESSION_HEARTBEAT_TIMEOUT_MS | 30000 | сколько сессия ждёт heartbeat вкладки, прежде чем спросить api, есть ли незавершённая работа |
SESSION_CONVERSATION_IDLE_TIMEOUT_MS | 28800000 (8ч) | потолок простаивающего разговора: разговорный агент, ждущий пользователя, удерживает сессию сверх heartbeat на это время, считая от конца своего хода; после этого сессия закрывается как closed с причиной conversation_idle_timeout (RN-581). Отображена для engine в трёх composes (dev, production, установка) с тем же умолчанием; не записана в deploy/k8s/ намеренно — там ничто не перехватывает окружение, так что отсутствие и есть умолчание, а её изменение — элемент env: в патче engine оверлея |
RELEASE_NAME / RELEASE_NODE | — | идентичность узла в распределении |
Harness
| переменная | умолчание | примечание |
|---|---|---|
TOOL_LOOP_MAX_ITERATIONS | 8 | потолок итераций цикла разговорного агента. Исчерпан — агент завершает работу артефактом блокировки |
TOOL_LOOP_MAX_ITERATIONS_EXECUCAO | 60 | потолок dev agents. Больше, потому что они исследуют репозиторий перед записью — и потому что task_budget_micros сдерживает расход снизу |
TOOL_LOOP_MAX_ITERATIONS_GATE | 60 | потолок субагентов QA, по той же причине |
DEFAULT_CONTEXT_WINDOW | 128000 | используется, когда модель не объявляет окно (разговорные агенты никогда не объявляют); был 8192 до RN-702 |
CONTEXT_COMPACTION_THRESHOLD | 0.7 | доля окна, запускающая компактизацию |
LLM_TURN_TIMEOUT_MS | 300000 | 5 мин на ход |
TOOL_ROUTER_TIMEOUT_MS | 2000 | потолок в мс ОДНОГО вызова маршрутизатора инструментов Jev (ADR 0179, RN-625). Читается api, а не engine. После потолка шаг падает на весь каталог инструментов с motivoDaQueda: timeout; ход никогда не падает из-за Jev. 0 из 328 измеренных запросов превысили 2 000 мс. Ключ вкл/выкл задаётся на workspace (PUT workspaces/:id/tool-router), а не переменной окружения |
TERMINAL_ACTION_TIMEOUT_MS | 120000 | потолок команды терминала (2 мин с RN-757: простой npm install не укладывался в прежние 15 с). Engine ждёт это значение + 90 с в container-exec и + 120 с в propose_action действия terminal; выше ~180 с внешнее ожидание превышает лимит 300 с на заголовки fetch Node, который api использует для вызова /internal/actions/execute |
TERMINAL_OUTPUT_MAX_BYTES | 32768 | потолок в БАЙТАХ вывода команды (RN-074). Вывод остаётся в истории цикла и едет в каждом следующем ходе; без потолка find по большому дереву обрушивает всё выполнение с 413 от provider |
READ_FILE_MAX_BYTES | 32768 | потолок в БАЙТАХ содержимого, прочитанного read_file (RN-141) — тот же класс переполнения, что RN-074, через дверь read_file вместо терминала; независимая переменная, то же значение по совпадению контекста |
SEARCH_WORKSPACE_MAX_BYTES | 32768 | потолок в БАЙТАХ итогового текста search_workspace (RN-150) — тот же класс переполнения, что RN-074/RN-141, через дверь поиска; независимая переменная |
SEARCH_WORKSPACE_MAX_HITS | 500 | потолок ЧИСЛА совпадений, которые search_workspace собирает до сборки ответа (RN-150) — прекращает обход/чтение содержимого, как только достигнут потолок, чтобы не платить I/O на дереве со слишком многими совпадениями лишь ради последующей обрезки по байтам |
SECOPS_SCAN_TIMEOUT_MS | 180000 | 3 мин на сканер SecOps |
TRANSPORT_MAX_BODY_BYTES | 8388608 (8 MiB) | ТРАНСПОРТНЫЙ потолок, который компактизация контекста соблюдает поверх окна модели (RN-412) — эффективное окно min(context_window, этот потолок в токенах), так что компактизация срабатывает ДО того, как тело превысит HTTP-лимит api, а не только когда модель «забыла бы» |
GRAPH_INSTRUCTION_TEMPLATES_ENABLED | false | включает источник :graph в InstructionFiles — сегодня только идентичность ux-designer разрешает шаблон графа до inline-текста (RN-413). СОБСТВЕННОЕ имя, не GRAPH_TEMPLATES_ENABLED ниже — они бы столкнулись с противоположными умолчаниями, если бы делили ключ |
Психолог
| переменная | умолчание | примечание |
|---|---|---|
PSYCHOLOGIST_ENABLED | false | ГЛОБАЛЬНАЯ пауза НОВЫХ раундов (автоматических и по запросу) — продуктовое решение пользователя от 2026-08-10, не баг, та же форма флага, что ANAMNESE_ENABLED ниже (который был включён обратно в RN-680; этот — нет). Ничего существующего не удаляет. Включение требует перезапуска engine (RN-117). Ни один ключ загрузки с ним не парен: автоматический триггер Психолога — закрытие сессии, а не тик |
PSYCHOLOGIST_TRIAGE_THRESHOLD | 20 | число событий в сессии, разделяющее лёгкий и тяжёлый анализ |
PSYCHOLOGIST_MAX_ITERATIONS_LEVE / _PESADA | 4 / 8 | — |
PSYCHOLOGIST_BUDGET_MICROS_LEVE / _PESADA | 50000 / 300000 | USD 0,05 и USD 0,30 за анализ |
PSYCHOLOGIST_MAX_PROMPT_EVENTS_LEVE / _PESADA | 50 / 400 | сколько событий входит в prompt |
PSYCHOLOGIST_MAX_PAYLOAD_CHARS | 600 | обрезка payload каждого события |
PSYCHOLOGIST_RAG_TOP_K | 3 | сколько релевантных фрагментов rag_search входит в контекст, вычитаемых из потолка недавних событий выше (RN-417) |
GRAPH_TEMPLATES_ENABLED | false | включает разрешение psychologist-kickoff/anamnese-kickoff/context-manager-summarize как шаблона графа — ОБЩИЙ ключ для Психолога, Анамнеза и prompt резюмирования ContextManager (RN-417), не путать с GRAPH_INSTRUCTION_TEMPLATES_ENABLED выше. Выключен (умолчание) — API графа даже не вызывается; включён — любой сбой падает на inline-текст без ошибки |
Анамнез
| переменная | умолчание | примечание |
|---|---|---|
ANAMNESE_ENABLED | true | продуктовый флаг: может ли произойти НОВЫЙ раунд (периодический и по запросу). false — ГЛОБАЛЬНАЯ пауза, ничего не удаляющая (RN-115); её изменение требует перезапуска engine. Был false с 2026-08-10 (пауза пользователя), пока владелец не включил Анамнез обратно с исправлениями 2026-10-01 (RN-680): раунд без подходящего субъекта — эффективного участника проекта с собственным взаимодействием в окне — не делает вызова LLM. Одного его недостаточно для ПЕРИОДИЧЕСКОГО раунда: START_ANAMNESE (ключ загрузки, ниже) тоже должен быть true, и они отвечают на разные вопросы |
ANAMNESE_INTERVAL_SECONDS | 900 | 15 мин между раундами |
ANAMNESE_MIN_EVENTS | 10 | ниже этого не запускается — избегает построения профиля по шуму |
ANAMNESE_INITIAL_WINDOW_DAYS | 30 | окно первого раунда |
ANAMNESE_MAX_ITERATIONS | 6 | — |
ANAMNESE_BUDGET_MICROS | 200000 | USD 0,20 за раунд |
ANAMNESE_MAX_PROMPT_EVENTS | 500 | — |
ANAMNESE_MAX_PAYLOAD_CHARS | 600 | — |
Ограничители нагрузки
| переменная | умолчание | примечание |
|---|---|---|
START_OUTBOX_DRAIN | true | ключ загрузки drain outbox (Engine.Workers.OutboxDrainWorker, каждые 2 с) — ЕДИНСТВЕННОГО потребителя событий session/task/container, которые api пишет в outbox, — плюс очистка осиротевших worktree и усыновление осиротевших сессий. Выключен — никто не доставляет эти события: сессия, закрытая api, продолжает работать с процессом и разговорными агентами (RN-581), а dev agent, ждущий решения о действии, gate, своего PR или перехода контейнера в running, никогда не просыпается. Он НЕ включает Психолога: drain ставит его в очередь только при PSYCHOLOGIST_ENABLED=true. false только в compose production (docker-compose.prod.yml), намеренно; compose установки использует true из кода с AT-219 |
START_ANAMNESE | true | ограничитель НАГРУЗКИ для тестов/dev: не даёт kickoff/0 даже быть вызванным при загрузке, но ничего продуктового не решает — не путать с ANAMNESE_ENABLED (продукт: ГЛОБАЛЬНАЯ пауза, переживает любое значение этой). Выключение предотвращает новые постановки в очередь, не очищает очередь. Накопленные jobs выполнятся при следующей загрузке — очередь нужно вычистить. См. окружение инференса |
START_MODEL_SYNC | true | периодический тик синхронизации каталога моделей. Выключение ничего не замораживает: кнопка «Обновить каталог» на экране настроек вызывает тот же use case (RN-043) |
MODEL_SYNC_INTERVAL_SECONDS | 21600 (6ч) | каталог provider меняется в масштабе дней, а каждый раунд тратит один вызов API на provider — отсюда щедрое умолчание |
START_GATE_RESCUE | true | периодический тик спасения цикла gate (Engine.Gates.GateRescuer, RN-140). Выключение не меняет загрузку: спасение выполняется там один раз в любом случае |
GATE_RESCUE_INTERVAL_SECONDS | 300 (5 мин) | застрявший gate блокирует весь PR пользователя — интервал намного короче, чем у Анамнеза/синхронизации моделей, а каждый тик стоит лишь одного почти всегда пустого запроса |
GATE_RESCUE_STALE_AFTER_SECONDS | 900 (15 мин) | щедро намеренно: ToolLoop субагента QA может законно выполнять до TOOL_LOOP_MAX_ITERATIONS_GATE (60) итераций, и короткий порог спас бы — и продублировал бы — просто медленный цикл |
Внутренний трафик и наблюдаемость
| переменная | умолчание |
|---|---|
BRABO_SERVICE_TOKEN 🔒 | dev-service-token-change-me только вне production — то же значение, что у api. Окружающие пробелы обрезаются, а значение из одних пробелов считается отсутствующим. В релизе :prod engine отказывается стартовать, если его нет, если это публичное умолчание выше или если в нём меньше 16 символов, называя переменную, — то же правило, что у api (RN-601) |
BRABO_SERVICE_TOKEN_PREVIOUS 🔒 | — принимается только для проверки во время ротации. Обрезается; в релизе :prod engine отказывается стартовать, если это публичное умолчание или в нём меньше 16 символов. Никогда не обязателен, и равенство текущему означает, что ротации нет (RN-601) |
OTEL_EXPORTER_OTLP_ENDPOINT | — экспортёр Elixir говорит HTTP/protobuf на 4318, не gRPC на 4317. Отсутствие выключает только экспорт (traces_exporter: :none), а не инструментирование — см. ADR 0035 |
WEB_ORIGIN | — та же переменная, что у api, и здесь она питает ДВЕ вещи: check_origin сокета Phoenix (живая панель команды) и HTTP CORS маршрутов здоровья, который нужен браузеру, чтобы читать /health (ADR 0037). Отсутствие в production закрывает CORS и оставляет check_origin на строгом умолчании Phoenix — engine всё равно стартует, в отличие от api |
PROJECT_WORKSPACES_ROOT | /tmp/brabo-project-workspaces — та же, что у api, на том же томе |
SOME_APP_SSL_CERT_PATH,SOME_APP_SSL_KEY_PATHиMIX_TEST_PARTITION— остатки scaffold Phoenix и тестовой конфигурации. Не настраивайте их.
web
Web статический, обслуживается nginx. Он читает конфигурацию из двух источников, и различие важно:
| источник | когда | как |
|---|---|---|
import.meta.env.VITE_* | build | встраивается в bundle. Изменение требует пересборки |
window.__BRABO_CONFIG__ | runtime | отдаётся в /config.js, генерируемом entrypoint контейнера |
Поэтому один и тот же образ обслуживает все окружения: /config.js
переписывается при загрузке. VITE_* — fallback для разработки.
| переменная | для чего |
|---|---|
VITE_API_URL | адрес api |
VITE_ENGINE_URL | адрес engine (канал Phoenix) |
VITE_LOG_LEVEL | уровень JSON-логгера браузера (умолчание info). В кластере решает ключ WEB_LOG_LEVEL из brabo-config, который entrypoint пишет в /config.js — VITE_* важна только в локальной сборке |
VITE_BRABO_VERSION | версия, показываемая в подвале экранов auth (умолчание dev). Единственная, задаваемая при сборке по выбору, а не из-за ограничения — см. ниже |
Пустая страница после deploy — почти всегда /config.js, указывающий на
localhost, — smoke деплоя проверяет именно это.
Почему версия не идёт через /config.js
URL принадлежат окружению: один образ должен говорить с api staging и с api
production, и для этого /config.js и существует (ADR 0024). Версия
принадлежит артефакту — образ brabo-web:1.1.2 не должен быть способен
сообщить что-то иное. Если бы она приходила из /config.js, подвал стал бы
редактируемым полем вместо идентичности, а неверный ConfigMap заставил бы экран
лгать о том, какая сборка работает.
Весь путь, от commit до экрана: release.yml вычисляет
versao=${TAG#v} → передаёт как VERSION в docker buildx bake → цель
web в docker-bake.hcl превращает её в VITE_BRABO_VERSION →
ARG/ENV в docker/web/Dockerfile.prod открывает её для pnpm build → Vite
встраивает её в import.meta.env → runtime-config.ts её читает → AuthLayout
её показывает. Тот же VERSION питает BRABO_VERSION образа api
(ADR 0036).
Резервное копирование
Потребляются образом резервного копирования, а не приложениями — как CronJob в
Kubernetes и как docker compose run --rm backup … в установке через compose.
Подробности в Восстановлении; обоснование — в
ADR 0152.
Назначение — одно из двух, и выбор делается BACKUP_DIR. Задайте её — и
резервная копия идёт на диск; оставьте пустой — и она идёт в S3, что и делает
CronJob Kubernetes. Пять переменных BACKUP_S3_* перестали быть безусловно
обязательными: это конфигурация одного назначения, а требование bucket до
возможности миграции требует инфраструктуры, которую установка на одной машине
никогда не просила.
| переменная | умолчание | примечание |
|---|---|---|
BACKUP_DIR | — (S3) | каталог назначения. Задана ⇒ диск; пуста ⇒ S3. В сервисе compose умолчание /backups |
BRABO_BACKUP_HOST_DIR | backup_local (именованный том) | путь хоста, монтируемый в /backups. Умолчание с именованным томом годится для проверки резервной копии; для миграции это неверное назначение, потому что docker compose down -v удаляет его вместе с тем, что он должен был сохранить |
BACKUP_S3_ENDPOINT / BACKUP_S3_BUCKET | — | S3-совместимое назначение; обязательны только когда BACKUP_DIR пуста |
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY | — | учётные данные bucket |
BACKUP_KEEP_DAILY | 7 | хранение по количеству, а не по возрасту |
BACKUP_KEEP_WEEKLY | 4 | — |
GIT_REPOS_DIR | /data/git-repos | bare-репозитории проектов provider local. Отсутствует ⇒ шаг пропускается с сообщением (CronJob Kubernetes не монтирует этот том) |
RESTORE_DB | — | имя базы-назначения восстановления |
RESTORE_PREFIX | daily/ | weekly/ для восстановления из еженедельной копии |
RESTORE_GIT_PREFIX | git-daily/ | git-weekly/ для еженедельной копии bare-репозиториев |
RESTORE_GIT_FORCE | — | 1 разрешает распаковку поверх существующих bare-репозиториев. Выключено по умолчанию: наложение двух состояний репозитория даёт смесь, на которую ни один git не пожалуется |
RESTORE_ADMIN_URL | — | соединение с правом CREATEDB; в production отдельно от DATABASE_URL |
Четыре префикса, а не два: daily/ и weekly/ хранят дампы Postgres,
git-daily/ и git-weekly/ — архивы bare-репозиториев. Они разделены, потому
что хранение идёт по количеству — два вида архивов под одним префиксом
заставили бы «хранить 7» означать три с половиной резервные копии.
Локальный инференс (контейнеры)
Эти не читаются нашим кодом — они принадлежат контейнеру ollama и находятся
здесь, потому что являются самой частой причиной странного поведения агента.
Таблица симптомов — в
окружении инференса.
| переменная | почему |
|---|---|
OLLAMA_CONTEXT_LENGTH | умолчание 4096 молча обрезает prompt, собранный на 128k |
OLLAMA_MAX_LOADED_MODELS | при высоком OLLAMA_KEEP_ALIVE модели накапливаются, пока не кончится память |
OLLAMA_KEEP_ALIVE | как долго модель остаётся резидентной |
DEMO_QA_MODEL | направляет gate QA на API-модель — binding по агенту побеждает binding проекта |
Версия образа
ollama— не переменная, а digest. Как и любой сторонний образ вdocker/, он закреплён какollama/ollama:0.40.6@sha256:…(ADR 0159, inline-тег с ADR 0178), и тот же digest используетсяci.yml/golden-set-rag.yml, потому что нижняя граница golden-set RAG привязана к модели, а не к окружению (ADR 0138). Поэтому поведение инференса, изменившееся между двумяdocker compose pull, перестало быть возможной причиной: ничего не сдвинется, если кто-то не отредактирует digest. Повышение версии — процедура из runbook, иscripts/ci/imagens-pinadas.tsотклоняет повышение, затрагивающее лишь часть мест, где встречается тег.
Локальная наблюдаемость (контейнеры)
Тоже не читаются нашим кодом: это порты оверлея
docker/docker-compose.observability.yml, который поднимает Prometheus, Loki и
Grafana рядом со стеком разработки (pnpm dev:obs). Механизм — в
локальной наблюдаемости.
| переменная | умолчание | почему |
|---|---|---|
GRAFANA_PORT | 3001 | тот же порт, что использует Grafana кластера; они не сосуществуют по той же причине, по которой не сосуществуют pnpm dev и make deploy-local |
PROMETHEUS_PORT | 9090 | порт, который runbook уже использует в kubectl port-forward кластера |
LOKI_PORT | 3100 | только для прямых запросов; обычный путь — через Grafana |
Оверлей не задаёт OTEL_EXPORTER_OTLP_ENDPOINT: без Collector посередине
направить приложения на несуществующий адрес лишь порождает ошибку экспорта на
каждом ходе
(ADR 0035
разделил инструментирование и экспорт именно поэтому). Метрики и логи работают
без неё; трассы по-прежнему требуют кластера.
Брокер контейнеров
Брокер (ADR 0130) — единственный процесс
продукта, говорящий с daemon Docker на сервере, и единственный сервис со
смонтированным /var/run/docker.sock. Локально он поднимается с pnpm dev; в
production он под profiles: ["container-broker"]. Два файла compose
расходятся намеренно (ADR 0146,
пункт 3) — не унифицируйте их.
Когда-то он был выключен по умолчанию в обоих под тем доводом, что его ещё
ничто не вызывало. Эта предпосылка умерла: у него четыре реальных вызывающих, и
ADR 0144 заставил режим Mounted
поднимать контейнер через брокер — так что при выключенном profile стандартный
локальный режим заканчивался BrokerIndisponivelError. Асимметрию поддерживает
то, что сокет означает на каждой стороне: у запускающего pnpm dev он уже есть
(он запускает docker compose), тогда как в production это реальная граница
привилегий, и оператор — не разработчик.
Это меняет когда брокер работает, но никогда что он принимает: пять слоёв
сдерживания нетронуты, и он по-прежнему получает projectId плюс одну из пяти
операций, без параметра где-либо, куда можно было бы записать privileged или
свободный -v.
Подъём по умолчанию обнажил дефект, который прежде никогда не имел значения:
локальный образ устанавливал зависимости при START, а единственная сеть брокера
— internal: true, так что registry недостижим — контейнер умирал с
getaddrinfo EAI_AGAIN registry.npmjs.org, а без healthcheck up --wait всё
равно печатал Healthy. Теперь зависимости ставятся при BUILD, и у сервиса есть
healthcheck; сеть не тронута, и трогать её не следует. Эксплуатационные детали
— в runbook.
В production поднимайте его явно:
docker compose -f docker/docker-compose.prod.yml \
--profile container-broker up -d broker
| переменная | умолчание | когда ломается |
|---|---|---|
BROKER_URL | http://broker:8090 в compose dev; пусто в production и в установке (читается api) | Пусто — НОРМАЛЬНОЕ состояние: читающий наблюдаемое состояние контейнера говорит тогда «не наблюдалось», а не наследует зарегистрированное (RN-486), и api отвечает brokerConfigurado: false, так что создание проекта предлагает только runner (RN-573). Локально брокер поднимается по умолчанию, так что compose dev по умолчанию направляет её на сервис, который он поднимает (RN-599) — значение в .env всё равно побеждает. Composes production и установки намеренно без умолчания: там брокер под profile, и включающий его записывает URL |
BROKER_PORT | 8090 | Порт, на котором слушает брокер. Он НИЧЕГО не публикует на хосте — только api достигает его через сеть internal: true compose |
BRABO_SERVICE_TOKEN 🔒 | dev-service-token-change-me в разработке | ТОТ ЖЕ секрет api ↔ engine, в том же заголовке. При NODE_ENV=production брокер отказывается стартовать, когда он пуст, когда это публичный литерал репозитория или в нём меньше 16 символов — правило RN-114, которое здесь охраняет процесс, говорящий с Docker хоста. BRABO_SERVICE_TOKEN_PREVIOUS, когда задан, проходит то же правило, кроме обязательности: обрезается, и в production брокер отказывается стартовать, если это публичный литерал или в нём меньше 16 символов (RN-601) |
API_URL | http://api:3000 | Откуда брокер ЧИТАЕТ решение Архитектора. Он не получает спецификацию контейнера; он сам приходит за ней (RN-485) |
PROJECT_WORKSPACES_HOST_ROOT | — | Корень папок проектов на ХОСТЕ, а не внутри какого-либо контейнера. Без неё start отказывает, называя эту переменную, а остальные четыре операции продолжают работать. Не путайте с PROJECT_WORKSPACES_ROOT, это путь внутри контейнеров: -v разрешается DAEMON по файловой системе хоста, и путь изнутри api заставил бы его создать и смонтировать ПУСТУЮ папку |
DOCKER_GID | 999 (compose) | gid группы docker хоста (getent group docker | cut -d: -f3). Сокет root:docker, а брокер работает без root, поэтому compose использует group_add. Умолчание самое распространённое и неверно во многих дистрибутивах. С ADR 0146 это важно на КАЖДОЙ машине разработки, а не только там, где кто-то включил profile — ошибка не ломает загрузку, она ломает использование: каждая операция умирает с «permission denied» на сокете, и это проявляется только когда кто-то предлагает container_start. pnpm dev сообщает состояние этой переменной при каждом запуске (RN-512) и говорит «не применимо» на машине без группы docker, вместо того чтобы её обвинять |
PROJECT_WORKSPACES_HOST_ROOT не может выводиться из управляемого тома Docker.
В compose dev она выводится из PROJECT_WORKSPACES_HOST_DIR (выше) —
поэтому, оставив ту незаданной, как в только что скопированном .env.example,
вы оставляете брокер без корня, а режим container без контейнера: стек
поднимается здоровым, а container_start заканчивается отказом. pnpm dev
сообщает об этом при каждом запуске, по образцу DOCKER_GID, и никогда не
отказывается подниматься (RN-599). Он также
сообщает о случае с ~: Compose раскрывает ~ в источнике bind-mount, который
используют api/engine, но НЕ в переменной, которую получает брокер
(измерено docker compose config), так что пишите путь УЖЕ РАСКРЫТЫМ. А явный
PROJECT_WORKSPACES_HOST_ROOT, отличающийся от папки, которую монтируют
api/engine, сообщается как расходящийся — контейнер получил бы папку, в
которую агенты никогда не писали. Замена управляемого тома папкой хоста не
переносит то, что уже лежит в томе project_workspaces: данные остаются там,
просто перестают быть тем, что видят api и engine.
BRABO_PROJECTS_HOST_BASE — ВТОРОЙ корень брокера — база проектов
Mounted, тоже на хосте
(ADR 0141,
ADR 0144). В отличие от верхнего, его
не нужно заполнять: compose выводит его из BRABO_PROJECTS_BASE, который уже по
определению путь хоста (это то, что api и engine монтируют по
идентичности). Задавайте его явно, только если daemon Docker достигает этой
папки по другому пути.
Именно он позволяет проекту Mounted иметь контейнер
(RN-503): api отправляет дискриминированный
локатор (localizacao.tipo — gerenciada или montada) плюс относительный
сегмент, и брокер разрешает его относительно соответствующего корня. Два корня
никогда не заменяют друг друга. Отсутствие этого заставляет start проекта
mounted отказать с 503, НАЗЫВАЯ переменную, не трогая контейнер — падение
в PROJECT_WORKSPACES_HOST_ROOT смонтировало бы папку другого проекта, потому
что управляемый корень именуется workspace_dir_name, а база именуется
пользователем.
Пользователь контейнеров dev (сборка)
Тоже не читаются нашим кодом и даже не являются переменными окружения
runtime: это build.args Docker для трёх образов dev
(docker/api/Dockerfile, docker/web/Dockerfile, docker/engine/Dockerfile),
считываемые один раз при docker compose build и встраиваемые в USER, от
которого работает каждый контейнер. Они существуют, чтобы контейнеры работали с
тем же UID/GID, что и ваш пользователь хоста, а не root — всё, что пишется в
bind mount (apps/api/dist, workspace проекта в режиме выполнения mounted),
уже ваше, без последующего sudo chown.
| переменная | умолчание | почему |
|---|---|---|
DEV_UID | 1000 | ваш id -u. НЕ читается из ${UID} оболочки — в bash он только для чтения и по умолчанию не экспортируется в окружение, так что файл compose всегда видел бы его пустым |
DEV_GID | 1000 | ваш id -g, то же рассуждение |
1000/1000 совпадает с самой распространённой конфигурацией Linux одного
разработчика и с пользователем node, который уже есть в node:26-alpine
(базовый образ api/web) — когда ваша пара совпадает, Dockerfiles
переиспользуют этого пользователя, а не создают нового. Окружению, в котором
контейнеры уже работали от root до этого изменения, нужен разовый chown
именованных томов node_modules/_build/deps/.mix/.hex (или
docker compose down -v, чтобы дать им пересоздаться) — см. примечание в
Первых шагах.
Composes production и установки отличаются: engine работает из образа
production под фиксированным пользователем engine (uid/gid 1000, без
DEV_UID), с read_only: true и tmpfs в /home/engine. Этот tmpfs объявлен
uid=1000,gid=1000,mode=0700 — это не переменная — потому что semgrep (gate
SecOps) пишет туда ~/.semgrep; созданный от root, скан ничего не возвращает,
и gate одобряет без статического анализа
(RN-707, runbook
Gate SecOps застрял на «SAST не запускался»).
Оба образа engine закрепляют semgrep аргументом сборки SEMGREP_VERSION
(1.183.0, одинаковым в docker/engine/Dockerfile и Dockerfile.prod,
поддерживаемым одинаковым scripts/ci/semgrep-do-engine.spec.ts). Это аргумент
сборки, а не переменная runtime — ничего не идёт в .env. Без закрепления pip
взял 1.179.0, которая не публикует wheel musllinux: в образе Alpine пакет
приходил из sdist без semgrep-core, и каждый скан падал. Повышение версии —
это выбор релиза с wheel musllinux на PyPI и изменение обоих Dockerfiles в
одном PR.
Образы engine работают на Elixir 1.20.4 / OTP 29.1.1 поверх Alpine 3.24
(builder hexpm/elixir:1.20.4-erlang-29.1.1-alpine-3.24.2, runtime
alpine:3.24.3, оба по digest — ADR 0209).
OTP ниже 29.1 аварийно завершается при загрузке под musl 1.2.6 из Alpine 3.24
на хостах, чей CPU имеет AMX (sys_sigaltstack(): Internal error); не
понижайте OTP, не понизив musl. Ничего не идёт в .env.
Ни один из шести Dockerfiles не использует corepack (Node 25+ его больше не
несёт, AT-360): сборка устанавливает pnpm из tarball registry, сверяемого с
sha512, записанным в самом Dockerfile, — аргументы сборки PNPM_VERSION
(11.3.0, тот же, что packageManager в package.json, чтобы pnpm никогда не
пытался скачать другую версию в runtime) и PNPM_SHA512. Это аргументы сборки,
а не переменные runtime, и в .env задавать нечего: повышение pnpm — это
изменение packageManager, PNPM_VERSION в workflows и двух аргументов в шести
Dockerfiles в одном PR — см. раздел runbook об образе брокера.
Переменные инструментов (не продукта)
Всё выше — продукт: то, что эксплуатирующий установку задаёт в .env
deploy. Далее — инструменты: читаются только CI и теми, кто разрабатывает
Brabo. Ничто из этого не принадлежит .env оператора, и это различие — причина,
по которой инвентарь ниже помечает каждый источник.
Демонстрационные скрипты и seed — apps/api/scripts/
Они ведут реальных агентов против локального Ollama, поэтому получают имя модели и срок. У всех есть умолчание, и ни один не обязателен.
| переменная | умолчание | что меняет |
|---|---|---|
DEMO_MODEL | qwen2.5-coder:7b | модель, которую использует каждый демонстрационный скрипт, если одна из нижеследующих не перекрывает её для конкретной роли |
DEMO_QA_MODEL | DEMO_MODEL | только агент QA, в демонстрациях gates PR — чтобы судить моделью, отличной от той, что написала код |
DEMO_WORKFLOWS_MODEL | DEMO_MODEL | только агент Infra в демонстрации workflows GitHub |
DEMO_MODEL_LEVE | qwen2.5-coder:7b | лёгкая модель Психолога (та, что использует дешёвый проход) |
DEMO_MODEL_PESADO | qwen-psicologo-pesado:latest | тяжёлая модель Психолога. Это вариант локального Modelfile, а не опубликованный тег — отсюда нижняя |
DEMO_OLLAMA_COPY_FROM | qwen2.5-coder:7b | база, из которой демонстрация копирует, когда нужно создать тяжёлую модель в локальном Ollama |
DEMO_TIMEOUT_MS | 1800000 (30 мин); 900000 в демонстрации dev agent | сколько скрипт ждёт завершения выполнения, прежде чем сдаться |
DEMO_ANAMNESE_TIMEOUT_MS | 900000 (15 мин) | тот же срок для демонстрации Анамнеза |
DEMO_PSICOLOGO_TIMEOUT_MS | 600000 (10 мин) | тот же срок для демонстрации Психолога |
GOLDEN_SET_QA_MODEL | qwen2.5-coder:latest | модель, которую seed-golden-set-qa.ts регистрирует как судью golden-set QA (ADR 0123) |
NOME, которую инвентарь ниже перечисляет под этим источником, не
переменная: это литерал process.env.NOME внутри строки prompt в
demo-pr-gates.ts, обучающий агента заменять зашитый в код секрет. Инвентарь
извлекает по шаблону и не может отличить упоминание от чтения — она указана
здесь, а не отфильтрована в источнике, потому что поимённый список
игнорируемых был бы вторым местом, которое нужно поддерживать.
Браузерный E2E — e2e/
e2e/ не входит в workspace (ADR 0120) и запускается против
compose production, поэтому его адреса — опубликованные порты, а не порты
dev. Учётные данные — от аккаунта seed, те же, что создаёт docker/smoke.sh.
Инвентарь обходит каждый .ts под e2e/, включая specs Playwright — там
spec и есть код, читающий окружение, а не модульный тест рядом с ним (AT-124).
| переменная | умолчание | что меняет |
|---|---|---|
E2E_API_URL | http://localhost:3000 | куда HTTP-поддержка набора обращается к api |
E2E_WEB_URL | http://localhost:8088 | baseURL, который открывает Playwright, и origin, от которого зависит утверждение о CSRF |
E2E_USER | owner@brabo.dev | аккаунт, под которым входит набор |
E2E_PASSWORD | пароль seed, зафиксированный в e2e/suporte/api.ts | пароль этого аккаунта. Это пароль локального seed, никогда не реальный — здесь он не повторяется, чтобы единственной копией оставалась копия в коде |
Локальный кластер и плановые проверки — deploy/k8s/
Bootstrap (make deploy-local) и проверки свойств, которые
.github/workflows/propriedades.yml запускает каждую неделю, читают эти из
окружения. У всех есть умолчание; ни одна не принадлежит .env установки.
Инвентарь обходит каждый deploy/k8s/*.sh и сопоставляет только раскрытие с
умолчанием (${X:-…}) — так shell-скрипт читает внешнюю переменную (AT-212).
| переменная | умолчание | что меняет |
|---|---|---|
BRABO_CLUSTER_NAME | brabo | имя кластера k3d/kind, который bootstrap создаёт или переиспользует |
BRABO_CLUSTER_TOOL | k3d | kind переключает bootstrap на kind — который молча игнорирует NetworkPolicy, отсюда k3d по умолчанию |
BRABO_KEEP_CLUSTER | не задана | 1 переиспользует существующий кластер с тем же именем, вместо того чтобы удалить и пересоздать его |
BRABO_SKIP_BUILD | не задана | 1 пропускает сборку образов production и использует уже имеющиеся в daemon Docker |
BRABO_SKIP_OBSERVABILITY | не задана | 1 пропускает Tempo, Loki, Collector, Alloy и Grafana (Prometheus и adapter остаются: HPA engine их читает). Задаёт её только propriedades.yml (AT-177) |
TAG | не задана | тег релиза (vX.Y.Z-qa.N), чтобы bootstrap сделал checkout и deploy вместо рабочего дерева; требует чистого дерева |
BRABO_NAMESPACE | brabo | namespace, с которым говорит каждый проверочный скрипт |
API_URL / ENGINE_URL / WEB_URL | http://localhost:3000 / :4000 / :8088 | где проверочные скрипты достигают сервисов кластера — порты, которые публикует load balancer k3d. Те же имена, что у продуктовых переменных выше, другой читатель: здесь это лишь цель скриптов |
SMOKE_USER | owner@brabo.dev | аккаунт, под которым входят smoke и проверки |
BRABO_SMOKE_PASSWORD | локальный пароль seed, зафиксированный в скриптах | пароль этого аккаунта — а также то, что bootstrap передаёт pod seed как BRABO_SEED_PASSWORD |
HPA_JOBS | 60 | сколько зондирующих jobs make hpa-test ставит в очередь, чтобы engine масштабировался |
HPA_TIMEOUT | 240 | секунды, которые make hpa-test ждёт scale-up |
ROLLOUT_SESSIONS | 5 | сколько сессий make rollout-test открывает до rollout |
ROLLOUT_CONVERGENCE_SECONDS | 120 | потолок, за который каждая сессия должна быть усыновлена или дренирована после rollout. Не повышайте его, чтобы красный прогон стал зелёным (см. пробел осиротевшей сессии в CLAUDE.md) |
ROLLOUT_EVIDENCE_DIR | mktemp -d | куда make rollout-test пишет доказательства (логи pods, события, выборки реплик); propriedades.yml задаёт её и загружает папку |
EVIDENCIA_INTERVALO | 2 | секунды между выборками сборщиков доказательств; spec его сокращает (AT-174) |
RESTORE_MUTACAO | не задана | tabela-faltando превращает make test-restore в make test-restore-mutacao, намеренную поломку, которую он должен поймать (AT-126) |
RESTORE_KEEP_JOB | не задана | 1 сохраняет Jobs резервного копирования/восстановления после make test-restore для осмотра |
PROJECT_WORKSPACES_ROOT_NO_POD | /data/project-workspaces | где make test-reprojecao-artefatos-k8s ищет папку артефактов внутри pod api |
KUBE_VERSION | 1.31.0 | версия Kubernetes, против которой make k8s-validate проверяет отрендеренные оверлеи |
Полный инвентарь
Таблицы выше объясняют, что делает каждая переменная. Этот раздел —
инвентарь: извлекаемый из кода при каждом pnpm docs:generate, он
существует, чтобы новая переменная не осталась нигде не документированной
незаметно для всех.
⚠️ Блок сгенерирован
pnpm docs:generate. Не редактируйте вручную — следующая сборка его перезапишет.
Инвентарь, извлечённый из кода: переменных, читаемых во время выполнения, — 182. У всех есть описание в таблицах выше.
Каждый источник помечен как продукт — то, что оператор установки задаёт в .env развёртывания, — или инструменты: их читают только CI и разработчики продукта. Переменной инструментов не место в .env оператора.
api — переменных: 67 · продукт
API_JSON_BODY_LIMIT(apps/api/src/main.ts)API_PUBLIC_URL(apps/api/src/application/use-cases/auth/start-social-login.use-case.ts)ARTIFACT_PROJECTOR_INTERVAL_MS(apps/api/src/application/artifact-projection/artifact-projector.ts)AUTH_ACCESS_TOKEN_TTL_MS(apps/api/src/infrastructure/security/ed25519-access-token-issuer.ts)AUTH_EMAIL_TOKEN_TTL_MS(apps/api/src/application/use-cases/auth/auth-config.ts)AUTH_IP_ATTEMPT_THRESHOLD(apps/api/src/application/use-cases/auth/auth-config.ts)AUTH_JWT_SECRET(apps/api/src/infrastructure/security/auth-key-material.ts)AUTH_JWT_SECRET_PREVIOUS(apps/api/src/infrastructure/security/auth-key-material.ts)AUTH_LOCKOUT_ENABLED(apps/api/src/infrastructure/persistence/drizzle/drizzle-login-throttle.ts)AUTH_LOCKOUT_IP_THRESHOLDS(apps/api/src/infrastructure/persistence/drizzle/drizzle-login-throttle.ts)AUTH_LOCKOUT_THRESHOLDS(apps/api/src/infrastructure/persistence/drizzle/drizzle-login-throttle.ts)AUTH_LOCKOUT_WINDOW_MS(apps/api/src/infrastructure/persistence/drizzle/drizzle-login-throttle.ts)AUTH_MAIL_LOG_TOKENS(apps/api/src/infrastructure/mail/log-mail-sender.ts)AUTH_REFRESH_ABSOLUTE_TTL_MS(apps/api/src/application/use-cases/auth/auth-config.ts)AUTH_REFRESH_TOKEN_TTL_MS(apps/api/src/application/use-cases/auth/auth-config.ts)AUTH_REGISTRATION_ENABLED(apps/api/src/application/use-cases/auth/auth-config.ts)AUTH_RESET_TOKEN_TTL_MS(apps/api/src/application/use-cases/auth/auth-config.ts)AUTH_SET_PASSWORD_TTL_MS(apps/api/src/application/use-cases/auth/auth-config.ts)AUTH_TOKEN_PEPPER(apps/api/src/infrastructure/security/auth-key-material.ts)BRABO_FORCE_SEED(apps/api/src/scripts/provisionar-usuario.ts)BRABO_PROJECTS_BASE(apps/api/src/infrastructure/filesystem/project-workspaces-root.ts)BRABO_SEED_PASSWORD(apps/api/src/db/seed.ts)BRABO_SERVICE_TOKEN(apps/api/src/infrastructure/security/service-token.ts)BRABO_SERVICE_TOKEN_PREVIOUS(apps/api/src/infrastructure/security/service-token.ts)BRABO_VERSION(apps/api/src/tracing.ts)BROKER_URL(apps/api/src/infrastructure/http-clients/container-broker.client.ts)CREDENTIALS_MASTER_KEY(apps/api/src/infrastructure/security/envelope-encryption.service.ts)CREDENTIALS_MASTER_KEY_PREVIOUS(apps/api/src/infrastructure/security/envelope-encryption.service.ts)CUSTO_RECONCILIACAO_INTERVAL_MS(apps/api/src/application/use-cases/llm/reconciliar-custo-estimado.use-case.ts)DATABASE_URL(apps/api/src/db/migrate.ts)ENGINE_PUBLIC_URL(apps/api/src/application/use-cases/runner/request-runner-ticket.use-case.ts)ENGINE_URL(apps/api/src/application/use-cases/runner/request-runner-ticket.use-case.ts)GIT_LOCAL_REPOS_ROOT(apps/api/src/infrastructure/git/local-git-provider.ts)GIT_OAUTH_STATE_SECRET(apps/api/src/infrastructure/security/oauth-state-secret.ts)GITHUB_OAUTH_CLIENT_ID(apps/api/src/infrastructure/git/github-oauth-client.ts)GITHUB_OAUTH_CLIENT_SECRET(apps/api/src/infrastructure/git/github-oauth-client.ts)GITLAB_OAUTH_CLIENT_ID(apps/api/src/infrastructure/git/gitlab-oauth-client.ts)GITLAB_OAUTH_CLIENT_SECRET(apps/api/src/infrastructure/git/gitlab-oauth-client.ts)GRAPH_PROJECTOR_INTERVAL_MS(apps/api/src/application/graph-projection/graph-projector.ts)HUGGINGFACE_API_TOKEN(apps/api/src/infrastructure/huggingface/huggingface-client.ts)HUGGINGFACE_HUB_URL(apps/api/src/infrastructure/huggingface/huggingface-client.ts)LOG_LEVEL(apps/api/src/infrastructure/observability/logger.config.ts)MAIL_TRANSPORT(apps/api/src/infrastructure/mail/smtp-config.ts)METRICS_GAUGE_INTERVAL_MS(apps/api/src/infrastructure/observability/domain-gauges.collector.ts)MIGRATIONS_FOLDER(apps/api/src/db/migrate.ts)NEO4J_PASSWORD(apps/api/src/infrastructure/graph/neo4j-config.ts)NEO4J_URI(apps/api/src/infrastructure/graph/neo4j-config.ts)NEO4J_USER(apps/api/src/infrastructure/graph/neo4j-config.ts)NINE_ROUTER_BASE_URL(apps/api/src/infrastructure/llm/nine-router-provider.ts)NODE_ENV(apps/api/src/infrastructure/graph/neo4j-config.ts)OLLAMA_HOST(apps/api/src/infrastructure/llm/ollama-provider.ts)OTEL_DIAG_LOG(apps/api/src/tracing.ts)OTEL_EXPORTER_OTLP_ENDPOINT(apps/api/src/tracing.ts)OTEL_SERVICE_NAME(apps/api/src/tracing.ts)PORT(apps/api/src/main.ts)PROJECT_WORKSPACES_ROOT(apps/api/src/infrastructure/filesystem/project-workspaces-root.ts)RATE_LIMIT_ENABLED(apps/api/src/interfaces/http/shared/rate-limit.guard.ts)RATE_LIMIT_IP(apps/api/src/interfaces/http/shared/rate-limit.guard.ts)RATE_LIMIT_USER(apps/api/src/interfaces/http/shared/rate-limit.guard.ts)RATE_LIMIT_WINDOW_MS(apps/api/src/infrastructure/observability/domain-gauges.collector.ts)SMTP_FROM(apps/api/src/infrastructure/mail/smtp-config.ts)SMTP_HOST(apps/api/src/infrastructure/mail/smtp-config.ts)SMTP_PASSWORD(apps/api/src/infrastructure/mail/smtp-config.ts)SMTP_PORT(apps/api/src/infrastructure/mail/smtp-config.ts)SMTP_SECURE(apps/api/src/infrastructure/mail/smtp-config.ts)SMTP_USER(apps/api/src/infrastructure/mail/smtp-config.ts)WEB_ORIGIN(apps/api/src/infrastructure/mail/smtp-mail-sender.ts)
engine — переменных: 63 · продукт
ANAMNESE_BUDGET_MICROS(apps/engine/config/runtime.exs)ANAMNESE_ENABLED(apps/engine/config/runtime.exs)ANAMNESE_INITIAL_WINDOW_DAYS(apps/engine/config/runtime.exs)ANAMNESE_INTERVAL_SECONDS(apps/engine/config/runtime.exs)ANAMNESE_MAX_ITERATIONS(apps/engine/config/runtime.exs)ANAMNESE_MAX_PAYLOAD_CHARS(apps/engine/config/runtime.exs)ANAMNESE_MAX_PROMPT_EVENTS(apps/engine/config/runtime.exs)ANAMNESE_MIN_EVENTS(apps/engine/config/runtime.exs)API_URL(apps/engine/config/runtime.exs)BRABO_SERVICE_TOKEN(apps/engine/config/runtime.exs)BRABO_SERVICE_TOKEN_PREVIOUS(apps/engine/config/runtime.exs)CONTEXT_COMPACTION_THRESHOLD(apps/engine/config/runtime.exs)DATABASE_URL(apps/engine/config/dev.exs)DEFAULT_CONTEXT_WINDOW(apps/engine/config/runtime.exs)DNS_CLUSTER_QUERY(apps/engine/config/runtime.exs)ECTO_IPV6(apps/engine/config/runtime.exs)GATE_RESCUE_INTERVAL_SECONDS(apps/engine/config/runtime.exs)GATE_RESCUE_STALE_AFTER_SECONDS(apps/engine/config/runtime.exs)GRAPH_INSTRUCTION_TEMPLATES_ENABLED(apps/engine/config/runtime.exs)GRAPH_TEMPLATES_ENABLED(apps/engine/config/runtime.exs)LLM_TURN_TIMEOUT_MS(apps/engine/config/runtime.exs)MIX_TEST_PARTITION(apps/engine/config/test.exs)MODEL_SYNC_INTERVAL_SECONDS(apps/engine/config/runtime.exs)OTEL_EXPORTER_OTLP_ENDPOINT(apps/engine/config/runtime.exs)PHX_HOST(apps/engine/config/runtime.exs)PHX_SERVER(apps/engine/config/runtime.exs)POOL_SIZE(apps/engine/config/runtime.exs)PORT(apps/engine/config/runtime.exs)POSTGRES_HOST(apps/engine/config/test.exs)POSTGRES_PASSWORD(apps/engine/config/test.exs)POSTGRES_USER(apps/engine/config/test.exs)PROJECT_WORKSPACES_ROOT(apps/engine/config/runtime.exs)PSYCHOLOGIST_BUDGET_MICROS_LEVE(apps/engine/config/runtime.exs)PSYCHOLOGIST_BUDGET_MICROS_PESADA(apps/engine/config/runtime.exs)PSYCHOLOGIST_ENABLED(apps/engine/config/runtime.exs)PSYCHOLOGIST_MAX_ITERATIONS_LEVE(apps/engine/config/runtime.exs)PSYCHOLOGIST_MAX_ITERATIONS_PESADA(apps/engine/config/runtime.exs)PSYCHOLOGIST_MAX_PAYLOAD_CHARS(apps/engine/config/runtime.exs)PSYCHOLOGIST_MAX_PROMPT_EVENTS_LEVE(apps/engine/config/runtime.exs)PSYCHOLOGIST_MAX_PROMPT_EVENTS_PESADA(apps/engine/config/runtime.exs)PSYCHOLOGIST_RAG_TOP_K(apps/engine/config/runtime.exs)PSYCHOLOGIST_TRIAGE_THRESHOLD(apps/engine/config/runtime.exs)READ_FILE_MAX_BYTES(apps/engine/config/runtime.exs)SEARCH_WORKSPACE_MAX_BYTES(apps/engine/config/runtime.exs)SEARCH_WORKSPACE_MAX_HITS(apps/engine/config/runtime.exs)SECOPS_SCAN_TIMEOUT_MS(apps/engine/config/runtime.exs)SECRET_KEY_BASE(apps/engine/config/runtime.exs)SESSION_CONVERSATION_IDLE_TIMEOUT_MS(apps/engine/config/runtime.exs)SESSION_HEARTBEAT_TIMEOUT_MS(apps/engine/config/runtime.exs)SHUTDOWN_DRAIN_TIMEOUT_MS(apps/engine/config/runtime.exs)SOME_APP_SSL_CERT_PATH(apps/engine/config/runtime.exs)SOME_APP_SSL_KEY_PATH(apps/engine/config/runtime.exs)START_ANAMNESE(apps/engine/config/runtime.exs)START_GATE_RESCUE(apps/engine/config/runtime.exs)START_MODEL_SYNC(apps/engine/config/runtime.exs)START_OUTBOX_DRAIN(apps/engine/config/runtime.exs)TERMINAL_ACTION_TIMEOUT_MS(apps/engine/config/runtime.exs)TERMINAL_OUTPUT_MAX_BYTES(apps/engine/config/runtime.exs)TOOL_LOOP_MAX_ITERATIONS(apps/engine/config/runtime.exs)TOOL_LOOP_MAX_ITERATIONS_EXECUCAO(apps/engine/config/runtime.exs)TOOL_LOOP_MAX_ITERATIONS_GATE(apps/engine/config/runtime.exs)TRANSPORT_MAX_BODY_BYTES(apps/engine/config/runtime.exs)WEB_ORIGIN(apps/engine/config/runtime.exs)
web — переменных: 4 · продукт
VITE_API_URL(apps/web/src/lib/runtime-config.ts)VITE_BRABO_VERSION(apps/web/src/lib/runtime-config.ts)VITE_ENGINE_URL(apps/web/src/lib/runtime-config.ts)VITE_LOG_LEVEL(apps/web/src/lib/runtime-config.ts)
broker — переменных: 7 · продукт
API_URL(apps/broker/src/config.ts)BRABO_PROJECTS_HOST_BASE(apps/broker/src/config.ts)BRABO_SERVICE_TOKEN(apps/broker/src/config.ts)BRABO_SERVICE_TOKEN_PREVIOUS(apps/broker/src/config.ts)BROKER_PORT(apps/broker/src/config.ts)NODE_ENV(apps/broker/src/config.ts)PROJECT_WORKSPACES_HOST_ROOT(apps/broker/src/config.ts)
api/scripts — переменных: 15 · инструменты
DATABASE_URL(apps/api/scripts/demo-repo-bootstrap.ts)DEMO_ANAMNESE_TIMEOUT_MS(apps/api/scripts/demo-anamnese.ts)DEMO_MODEL(apps/api/scripts/demo-anamnese.ts)DEMO_MODEL_LEVE(apps/api/scripts/demo-psicologo.ts)DEMO_MODEL_PESADO(apps/api/scripts/demo-psicologo.ts)DEMO_OLLAMA_COPY_FROM(apps/api/scripts/demo-psicologo.ts)DEMO_PSICOLOGO_TIMEOUT_MS(apps/api/scripts/demo-psicologo.ts)DEMO_QA_MODEL(apps/api/scripts/demo-pr-gates-area-qa.ts)DEMO_TIMEOUT_MS(apps/api/scripts/demo-dev-agent-real.ts)DEMO_WORKFLOWS_MODEL(apps/api/scripts/demo-infra-workflows-github.ts)GIT_LOCAL_REPOS_ROOT(apps/api/scripts/demo-repo-bootstrap.ts)GOLDEN_SET_QA_MODEL(apps/api/scripts/seed-golden-set-qa.ts)NOME(apps/api/scripts/demo-pr-gates.ts)OLLAMA_HOST(apps/api/scripts/demo-psicologo.ts)RATE_LIMIT_WINDOW_MS(apps/api/scripts/relatorio-seguranca-runtime.ts)
e2e — переменных: 4 · инструменты
E2E_API_URL(e2e/suporte/api.ts)E2E_PASSWORD(e2e/suporte/api.ts)E2E_USER(e2e/suporte/api.ts)E2E_WEB_URL(e2e/playwright.config.ts)
deploy/k8s — переменных: 22 · инструменты
API_URL(deploy/k8s/rollout-test.sh)BRABO_CLUSTER_NAME(deploy/k8s/bootstrap.sh)BRABO_CLUSTER_TOOL(deploy/k8s/bootstrap.sh)BRABO_KEEP_CLUSTER(deploy/k8s/bootstrap.sh)BRABO_NAMESPACE(deploy/k8s/hpa-test.sh)BRABO_SKIP_BUILD(deploy/k8s/bootstrap.sh)BRABO_SKIP_OBSERVABILITY(deploy/k8s/bootstrap.sh)BRABO_SMOKE_PASSWORD(deploy/k8s/bootstrap.sh)ENGINE_URL(deploy/k8s/smoke.sh)EVIDENCIA_INTERVALO(deploy/k8s/rollout-evidencia.sh)HPA_JOBS(deploy/k8s/hpa-test.sh)HPA_TIMEOUT(deploy/k8s/hpa-test.sh)KUBE_VERSION(deploy/k8s/validate.sh)PROJECT_WORKSPACES_ROOT_NO_POD(deploy/k8s/test-reprojecao-artefatos.sh)RESTORE_KEEP_JOB(deploy/k8s/test-restore.sh)RESTORE_MUTACAO(deploy/k8s/test-restore.sh)ROLLOUT_CONVERGENCE_SECONDS(deploy/k8s/rollout-test.sh)ROLLOUT_EVIDENCE_DIR(deploy/k8s/rollout-test.sh)ROLLOUT_SESSIONS(deploy/k8s/rollout-test.sh)SMOKE_USER(deploy/k8s/bootstrap.sh)TAG(deploy/k8s/bootstrap.sh)WEB_URL(deploy/k8s/smoke.sh)
TODO(humano): проверки схемы переменных при загрузке нет (что-то вроде
zod/envalidв api илиNimbleOptionsв engine). Сегодня неверное числовое значение молча становитсяNaN, а опечатка в имени переменной падает в умолчание без предупреждения. Единственные исключения — помеченные 🔒, которые явно падают в production.