Перейти к основному содержимому
Перевод от 2026-10-16; оригинал с тех пор изменился.

Конфигурация

Вся конфигурация задаётся переменными окружения. Файла конфигурации приложения нет — есть 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_PROFILEScontainer-broker — читается Compose из --env-file, так что up -d --wait поднимает брокер без флага
BROKER_URLhttp://broker:8090 — сторона api; без неё api никогда не вызывает брокер
DOCKER_GIDgid сокета, видимый изнутри контейнера, измеренный самим образом брокера; сбой измерения — именованный отказ, никогда умолчание 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_PORT11434 или первый свободный порт до 11443, когда что-то (нативный Ollama) уже слушает там; весь занятый диапазон — именованный отказ, без записи чего-либо
COMPOSE_PROFILESllm, вместе с container-broker в ОДНОЙ строке, только когда приняты оба (container-broker,llm) — две строки не складываются, побеждает последняя

Без них проверка семантического дубликата пропускается. Включение позже — в runbook.

api​

Основные​

переменнаяумолчаниекогда ломается
DATABASE_URLpostgres://brabo:brabo@localhost:5432/braboбез неё ничего не поднимается
PORT3000—
API_JSON_BODY_LIMIT10mbпотолок тела JSON, которое принимает api, применяемый к парсеру, зарегистрированному в main.ts, до любого маршрута. Существует, чтобы изменение транспортного потолка engine (TRANSPORT_MAX_BODY_BYTES, 8 MiB) не требовало нового deploy — умолчание оставляет запас над ним. Слишком маленький — и ход с большим контекстом возвращается как 413
NODE_ENV—production включает строгий CORS и проверку ключей
API_PUBLIC_URLhttp://localhost:3000используется в callbacks OAuth git; неверная = сломанный callback
ENGINE_URLhttp://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_VERSIONdevстановится 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_PORT5173 (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_MS90000015 мин
AUTH_REFRESH_TOKEN_TTL_MS120960000014 дней
AUTH_REFRESH_ABSOLUTE_TTL_MS2592000000абсолютный потолок семейства, считая от входа — без него ротация даёт вечную сессию
AUTH_REGISTRATION_ENABLEDtrueлюбое значение, отличное от "false", оставляет регистрацию открытой
AUTH_LOCKOUT_ENABLEDtrueто же соглашение
AUTH_LOCKOUT_WINDOW_MS900000скользящее окно подсчёта
AUTH_LOCKOUT_THRESHOLDS5:30,8:300,12:900лестница корзины по email, сбои:секунды
AUTH_LOCKOUT_IP_THRESHOLDS20:30,30:120лестница корзины по IP, мягче и с коротким потолком
AUTH_EMAIL_TOKEN_TTL_MS172800000подтверждение email, 48 ч
AUTH_RESET_TOKEN_TTL_MS3600000сброс пароля, 1 ч
AUTH_SET_PASSWORD_TTL_MS604800000установка первого пароля (мигрированный пользователь), 7 дней — дольше сброса, потому что получатель его не запрашивал
AUTH_IP_ATTEMPT_THRESHOLD60потолок попыток по IP на маршрутах auth
AUTH_MAIL_LOG_TOKENSfalseтолько 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_TRANSPORTloglog (умолчание) или smtp. Любое другое значение падает в log
SMTP_HOST 🔒—хост SMTP-провайдера. Только с MAIL_TRANSPORT=smtp: в production api отказывается стартовать, если его нет, если он из одних пробелов или если это значение-пример, опубликованное в .env.example (RN-114)
SMTP_PORT587порт провайдера — 587 это STARTTLS, 465 — неявный TLS (SMTP_SECURE=true)
SMTP_SECUREfalsetrue включает неявный 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_PASSWORDbrabo12345678пароль засеянных пользователей (owner@brabo.dev, dev@brabo.dev), создаваемых с уже подтверждённым email
BRABO_FORCE_SEED—разблокирует seed при NODE_ENV=production, где по умолчанию он отказывается запускаться. Не задавайте её в реальном окружении: аккаунт рождается с известным и подтверждённым паролем

Seed идемпотентен и не трогает пароль уже существующих. Повторный запуск после того, как кто-то сменил свой пароль, его не откатывает.

Rate limit​

Скользящее окно в Postgres — Redis нет (ADR 0027).

переменнаяумолчаниечто делает
RATE_LIMIT_ENABLEDtrueлюбое значение, отличное от "false", оставляет его включённым
RATE_LIMIT_WINDOW_MS60000размер окна
RATE_LIMIT_USER300запросов на пользователя за окно
RATE_LIMIT_IP600запросов на 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-workspacesworktrees агентов проекта в режиме 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_HOSThttp://localhost:11434—
OLLAMA_REQUEST_TIMEOUT_MS300000потолок бездействия сокета Ollama, а не общей длительности. У локальной модели задержка до первого токена другого порядка, отсюда собственная переменная; см. окружение инференса
LLM_REQUEST_TIMEOUT_MS300000тот же потолок бездействия для 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_URLhttps://huggingface.coбазовый URL Hub, с которым говорит поиск. Переопределение для тестов (фальшивый HTTP-сервер на эфемерном порту); в production не задаётся
HUGGINGFACE_REQUEST_TIMEOUT_MS15000потолок бездействия запроса поиска к 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_USERneo4j (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_MS2000период poller, опустошающего очередь graph_projection в outbox и пишущего handoffs/гипотезы/профили/взаимодействия в граф (RN-416)
ARTIFACT_PROJECTOR_INTERVAL_MS2000период poller, опустошающего очередь artifact_projection в outbox и пишущего артефакты каждого агента как Markdown в папку docs/ проекта (RN-523). ПРОИЗВОДНАЯ проекция: источник — event log, и папку можно удалить и восстановить

Наблюдаемость​

переменнаяумолчаниепримечание
OTEL_EXPORTER_OTLP_ENDPOINT—отсутствие выключает ЭКСПОРТ, а не инструментирование (ADR 0035). Span по-прежнему создаётся, и trace_id по-прежнему появляется в логе — это даёт корреляцию в разработке без коллектора. Без неё span отбрасывается в конце, а не покидает процесс
OTEL_SERVICE_NAMEbrabo-api—
OTEL_DIAG_LOG—1 включает диагностический лог самого OTel
LOG_LEVELinfo в production, debug вневместе с NODE_ENV определяет и ФОРМАТ: вне production лог читаемый (pino-pretty в процессе, с деревом слоёв); в production — одна строка JSON на событие
METRICS_GAUGE_INTERVAL_MS15000период сбора доменных gauges

engine​

Основные​

переменнаяумолчаниекогда ломается
DATABASE_URLecto://brabo:brabo@localhost:5432/braboобратите внимание на схему ecto://, а не postgres://
POSTGRES_HOST / _USER / _PASSWORDlocalhost / brabo / braboиспользуются, когда DATABASE_URL не собран
POOL_SIZE—исчерпанный pool блокирует Oban, и очередь перестаёт потребляться
PORT4000—
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_URLhttp://localhost:3000engine обращается к api обратно отсюда
ECTO_IPV6——
SKIP_MIGRATIONS—используется Job миграции

Кластер и остановка​

переменнаяумолчаниепримечание
DNS_CLUSTER_QUERY—headless Service, формирующий кластер Erlang. Без неё каждая реплика — остров, и каждый rollout дренирует всё
SHUTDOWN_DRAIN_TIMEOUT_MS45000окно preStop. Повышается вместе с terminationGracePeriodSeconds, никогда в одиночку
SESSION_HEARTBEAT_TIMEOUT_MS30000сколько сессия ждёт heartbeat вкладки, прежде чем спросить api, есть ли незавершённая работа
SESSION_CONVERSATION_IDLE_TIMEOUT_MS28800000 (8ч)потолок простаивающего разговора: разговорный агент, ждущий пользователя, удерживает сессию сверх heartbeat на это время, считая от конца своего хода; после этого сессия закрывается как closed с причиной conversation_idle_timeout (RN-581). Отображена для engine в трёх composes (dev, production, установка) с тем же умолчанием; не записана в deploy/k8s/ намеренно — там ничто не перехватывает окружение, так что отсутствие и есть умолчание, а её изменение — элемент env: в патче engine оверлея
RELEASE_NAME / RELEASE_NODE—идентичность узла в распределении

Harness​

переменнаяумолчаниепримечание
TOOL_LOOP_MAX_ITERATIONS8потолок итераций цикла разговорного агента. Исчерпан — агент завершает работу артефактом блокировки
TOOL_LOOP_MAX_ITERATIONS_EXECUCAO60потолок dev agents. Больше, потому что они исследуют репозиторий перед записью — и потому что task_budget_micros сдерживает расход снизу
TOOL_LOOP_MAX_ITERATIONS_GATE60потолок субагентов QA, по той же причине
DEFAULT_CONTEXT_WINDOW128000используется, когда модель не объявляет окно (разговорные агенты никогда не объявляют); был 8192 до RN-702
CONTEXT_COMPACTION_THRESHOLD0.7доля окна, запускающая компактизацию
LLM_TURN_TIMEOUT_MS3000005 мин на ход
TOOL_ROUTER_TIMEOUT_MS2000потолок в мс ОДНОГО вызова маршрутизатора инструментов Jev (ADR 0179, RN-625). Читается api, а не engine. После потолка шаг падает на весь каталог инструментов с motivoDaQueda: timeout; ход никогда не падает из-за Jev. 0 из 328 измеренных запросов превысили 2 000 мс. Ключ вкл/выкл задаётся на workspace (PUT workspaces/:id/tool-router), а не переменной окружения
TERMINAL_ACTION_TIMEOUT_MS120000потолок команды терминала (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_BYTES32768потолок в БАЙТАХ вывода команды (RN-074). Вывод остаётся в истории цикла и едет в каждом следующем ходе; без потолка find по большому дереву обрушивает всё выполнение с 413 от provider
READ_FILE_MAX_BYTES32768потолок в БАЙТАХ содержимого, прочитанного read_file (RN-141) — тот же класс переполнения, что RN-074, через дверь read_file вместо терминала; независимая переменная, то же значение по совпадению контекста
SEARCH_WORKSPACE_MAX_BYTES32768потолок в БАЙТАХ итогового текста search_workspace (RN-150) — тот же класс переполнения, что RN-074/RN-141, через дверь поиска; независимая переменная
SEARCH_WORKSPACE_MAX_HITS500потолок ЧИСЛА совпадений, которые search_workspace собирает до сборки ответа (RN-150) — прекращает обход/чтение содержимого, как только достигнут потолок, чтобы не платить I/O на дереве со слишком многими совпадениями лишь ради последующей обрезки по байтам
SECOPS_SCAN_TIMEOUT_MS1800003 мин на сканер SecOps
TRANSPORT_MAX_BODY_BYTES8388608 (8 MiB)ТРАНСПОРТНЫЙ потолок, который компактизация контекста соблюдает поверх окна модели (RN-412) — эффективное окно min(context_window, этот потолок в токенах), так что компактизация срабатывает ДО того, как тело превысит HTTP-лимит api, а не только когда модель «забыла бы»
GRAPH_INSTRUCTION_TEMPLATES_ENABLEDfalseвключает источник :graph в InstructionFiles — сегодня только идентичность ux-designer разрешает шаблон графа до inline-текста (RN-413). СОБСТВЕННОЕ имя, не GRAPH_TEMPLATES_ENABLED ниже — они бы столкнулись с противоположными умолчаниями, если бы делили ключ

Психолог​

переменнаяумолчаниепримечание
PSYCHOLOGIST_ENABLEDfalseГЛОБАЛЬНАЯ пауза НОВЫХ раундов (автоматических и по запросу) — продуктовое решение пользователя от 2026-08-10, не баг, та же форма флага, что ANAMNESE_ENABLED ниже (который был включён обратно в RN-680; этот — нет). Ничего существующего не удаляет. Включение требует перезапуска engine (RN-117). Ни один ключ загрузки с ним не парен: автоматический триггер Психолога — закрытие сессии, а не тик
PSYCHOLOGIST_TRIAGE_THRESHOLD20число событий в сессии, разделяющее лёгкий и тяжёлый анализ
PSYCHOLOGIST_MAX_ITERATIONS_LEVE / _PESADA4 / 8—
PSYCHOLOGIST_BUDGET_MICROS_LEVE / _PESADA50000 / 300000USD 0,05 и USD 0,30 за анализ
PSYCHOLOGIST_MAX_PROMPT_EVENTS_LEVE / _PESADA50 / 400сколько событий входит в prompt
PSYCHOLOGIST_MAX_PAYLOAD_CHARS600обрезка payload каждого события
PSYCHOLOGIST_RAG_TOP_K3сколько релевантных фрагментов rag_search входит в контекст, вычитаемых из потолка недавних событий выше (RN-417)
GRAPH_TEMPLATES_ENABLEDfalseвключает разрешение psychologist-kickoff/anamnese-kickoff/context-manager-summarize как шаблона графа — ОБЩИЙ ключ для Психолога, Анамнеза и prompt резюмирования ContextManager (RN-417), не путать с GRAPH_INSTRUCTION_TEMPLATES_ENABLED выше. Выключен (умолчание) — API графа даже не вызывается; включён — любой сбой падает на inline-текст без ошибки

Анамнез​

переменнаяумолчаниепримечание
ANAMNESE_ENABLEDtrueпродуктовый флаг: может ли произойти НОВЫЙ раунд (периодический и по запросу). false — ГЛОБАЛЬНАЯ пауза, ничего не удаляющая (RN-115); её изменение требует перезапуска engine. Был false с 2026-08-10 (пауза пользователя), пока владелец не включил Анамнез обратно с исправлениями 2026-10-01 (RN-680): раунд без подходящего субъекта — эффективного участника проекта с собственным взаимодействием в окне — не делает вызова LLM. Одного его недостаточно для ПЕРИОДИЧЕСКОГО раунда: START_ANAMNESE (ключ загрузки, ниже) тоже должен быть true, и они отвечают на разные вопросы
ANAMNESE_INTERVAL_SECONDS90015 мин между раундами
ANAMNESE_MIN_EVENTS10ниже этого не запускается — избегает построения профиля по шуму
ANAMNESE_INITIAL_WINDOW_DAYS30окно первого раунда
ANAMNESE_MAX_ITERATIONS6—
ANAMNESE_BUDGET_MICROS200000USD 0,20 за раунд
ANAMNESE_MAX_PROMPT_EVENTS500—
ANAMNESE_MAX_PAYLOAD_CHARS600—

Ограничители нагрузки​

переменнаяумолчаниепримечание
START_OUTBOX_DRAINtrueключ загрузки 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_ANAMNESEtrueограничитель НАГРУЗКИ для тестов/dev: не даёт kickoff/0 даже быть вызванным при загрузке, но ничего продуктового не решает — не путать с ANAMNESE_ENABLED (продукт: ГЛОБАЛЬНАЯ пауза, переживает любое значение этой). Выключение предотвращает новые постановки в очередь, не очищает очередь. Накопленные jobs выполнятся при следующей загрузке — очередь нужно вычистить. См. окружение инференса
START_MODEL_SYNCtrueпериодический тик синхронизации каталога моделей. Выключение ничего не замораживает: кнопка «Обновить каталог» на экране настроек вызывает тот же use case (RN-043)
MODEL_SYNC_INTERVAL_SECONDS21600 (6ч)каталог provider меняется в масштабе дней, а каждый раунд тратит один вызов API на provider — отсюда щедрое умолчание
START_GATE_RESCUEtrueпериодический тик спасения цикла gate (Engine.Gates.GateRescuer, RN-140). Выключение не меняет загрузку: спасение выполняется там один раз в любом случае
GATE_RESCUE_INTERVAL_SECONDS300 (5 мин)застрявший gate блокирует весь PR пользователя — интервал намного короче, чем у Анамнеза/синхронизации моделей, а каждый тик стоит лишь одного почти всегда пустого запроса
GATE_RESCUE_STALE_AFTER_SECONDS900 (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_DIRbackup_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_DAILY7хранение по количеству, а не по возрасту
BACKUP_KEEP_WEEKLY4—
GIT_REPOS_DIR/data/git-reposbare-репозитории проектов provider local. Отсутствует ⇒ шаг пропускается с сообщением (CronJob Kubernetes не монтирует этот том)
RESTORE_DB—имя базы-назначения восстановления
RESTORE_PREFIXdaily/weekly/ для восстановления из еженедельной копии
RESTORE_GIT_PREFIXgit-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_PORT3001тот же порт, что использует Grafana кластера; они не сосуществуют по той же причине, по которой не сосуществуют pnpm dev и make deploy-local
PROMETHEUS_PORT9090порт, который runbook уже использует в kubectl port-forward кластера
LOKI_PORT3100только для прямых запросов; обычный путь — через 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_URLhttp://broker:8090 в compose dev; пусто в production и в установке (читается api)Пусто — НОРМАЛЬНОЕ состояние: читающий наблюдаемое состояние контейнера говорит тогда «не наблюдалось», а не наследует зарегистрированное (RN-486), и api отвечает brokerConfigurado: false, так что создание проекта предлагает только runner (RN-573). Локально брокер поднимается по умолчанию, так что compose dev по умолчанию направляет её на сервис, который он поднимает (RN-599) — значение в .env всё равно побеждает. Composes production и установки намеренно без умолчания: там брокер под profile, и включающий его записывает URL
BROKER_PORT8090Порт, на котором слушает брокер. Он НИЧЕГО не публикует на хосте — только 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_URLhttp://api:3000Откуда брокер ЧИТАЕТ решение Архитектора. Он не получает спецификацию контейнера; он сам приходит за ней (RN-485)
PROJECT_WORKSPACES_HOST_ROOT—Корень папок проектов на ХОСТЕ, а не внутри какого-либо контейнера. Без неё start отказывает, называя эту переменную, а остальные четыре операции продолжают работать. Не путайте с PROJECT_WORKSPACES_ROOT, это путь внутри контейнеров: -v разрешается DAEMON по файловой системе хоста, и путь изнутри api заставил бы его создать и смонтировать ПУСТУЮ папку
DOCKER_GID999 (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_UID1000ваш id -u. НЕ читается из ${UID} оболочки — в bash он только для чтения и по умолчанию не экспортируется в окружение, так что файл compose всегда видел бы его пустым
DEV_GID1000ваш 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_MODELqwen2.5-coder:7bмодель, которую использует каждый демонстрационный скрипт, если одна из нижеследующих не перекрывает её для конкретной роли
DEMO_QA_MODELDEMO_MODELтолько агент QA, в демонстрациях gates PR — чтобы судить моделью, отличной от той, что написала код
DEMO_WORKFLOWS_MODELDEMO_MODELтолько агент Infra в демонстрации workflows GitHub
DEMO_MODEL_LEVEqwen2.5-coder:7bлёгкая модель Психолога (та, что использует дешёвый проход)
DEMO_MODEL_PESADOqwen-psicologo-pesado:latestтяжёлая модель Психолога. Это вариант локального Modelfile, а не опубликованный тег — отсюда нижняя
DEMO_OLLAMA_COPY_FROMqwen2.5-coder:7bбаза, из которой демонстрация копирует, когда нужно создать тяжёлую модель в локальном Ollama
DEMO_TIMEOUT_MS1800000 (30 мин); 900000 в демонстрации dev agentсколько скрипт ждёт завершения выполнения, прежде чем сдаться
DEMO_ANAMNESE_TIMEOUT_MS900000 (15 мин)тот же срок для демонстрации Анамнеза
DEMO_PSICOLOGO_TIMEOUT_MS600000 (10 мин)тот же срок для демонстрации Психолога
GOLDEN_SET_QA_MODELqwen2.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_URLhttp://localhost:3000куда HTTP-поддержка набора обращается к api
E2E_WEB_URLhttp://localhost:8088baseURL, который открывает Playwright, и origin, от которого зависит утверждение о CSRF
E2E_USERowner@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_NAMEbraboимя кластера k3d/kind, который bootstrap создаёт или переиспользует
BRABO_CLUSTER_TOOLk3dkind переключает 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_NAMESPACEbrabonamespace, с которым говорит каждый проверочный скрипт
API_URL / ENGINE_URL / WEB_URLhttp://localhost:3000 / :4000 / :8088где проверочные скрипты достигают сервисов кластера — порты, которые публикует load balancer k3d. Те же имена, что у продуктовых переменных выше, другой читатель: здесь это лишь цель скриптов
SMOKE_USERowner@brabo.devаккаунт, под которым входят smoke и проверки
BRABO_SMOKE_PASSWORDлокальный пароль seed, зафиксированный в скриптахпароль этого аккаунта — а также то, что bootstrap передаёт pod seed как BRABO_SEED_PASSWORD
HPA_JOBS60сколько зондирующих jobs make hpa-test ставит в очередь, чтобы engine масштабировался
HPA_TIMEOUT240секунды, которые make hpa-test ждёт scale-up
ROLLOUT_SESSIONS5сколько сессий make rollout-test открывает до rollout
ROLLOUT_CONVERGENCE_SECONDS120потолок, за который каждая сессия должна быть усыновлена или дренирована после rollout. Не повышайте его, чтобы красный прогон стал зелёным (см. пробел осиротевшей сессии в CLAUDE.md)
ROLLOUT_EVIDENCE_DIRmktemp -dкуда make rollout-test пишет доказательства (логи pods, события, выборки реплик); propriedades.yml задаёт её и загружает папку
EVIDENCIA_INTERVALO2секунды между выборками сборщиков доказательств; 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_VERSION1.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.