ADR 0155 — Первая учётная запись рождается в терминале, а локальный агент поднимается сопряжённым
Статус
Accepted 2026-09-27 (AT-108), после реализации — тем же путём, что
ADR 0055. Родился
Proposed в сессии 1 ФАЗЫ 30 (2026-09-11) и был отработан RN-543..552 в
сессиях 2–8 (E2E на чистой машине, RN-549, — сквозное доказательство), с
закрытием, описанным в
historico-de-fases.md и в
fase-30-runner-por-maquina.md.
Там, где текст ниже расходится с кодом, решение НЕ переписывалось:
расхождение названо в «Примечаниях к принятию», в конце.
Второе из двух решений ФАЗЫ 30. Зависит от ADR 0154: без идентичности машины здесь нечего регистрировать.
Контекст
install.sh завершается с поднятым compose и бинарником runner на диске.
Человек открывает браузер и видит экран входа в установке, где никого не
существует.
И путь выхода оттуда сломан в новой установке. Измерено:
.env, который генерирует установщик, не содержит ни одной переменной e-mail (install.sh, heredoc, записывающий одиннадцать ключей);MAIL_TRANSPORTсводится к значению по умолчаниюlog(docker/docker-compose.install.yml:200), и compose в комментарии рядом говорит, что "o comportamento continua log-only mesmo aqui";- регистрация требует подтверждения e-mail, и docblock
apps/api/src/scripts/provisionar-usuario.tsуже фиксирует последствие прямым текстом: "o fluxo normal de registro exige verificar e-mail — que com oMailSenderlog-only não fecha sozinho".
То есть кто устанавливает одной строкой, регистрируется, ничего не получает, и
единственный выход — выуживать ссылку из docker compose logs api.
Установка, которая заканчивается словами «Готово» и никого не впускает.
provisionarUsuario существует ровно для этой дыры — создаёт пользователя с
уже подтверждённым паролем — и сегодня у него два вызывающих:
демонстрационный seed и smoke. Ни один из них не является настоящей
установкой.
Решение
Установщик создаёт первую учётную запись, с явным согласием в терминале, и поднимает локального агента уже сопряжённым.
1. Только ПЕРВАЯ учётная запись и только когда нет ни одной
Шаг выполняется, когда в установке нет ни одного пользователя. Если есть хоть один, установщик не предлагает — это не создатель учётных записей, это завершение новой установки. При миграции (RN-530) пользователи приходят в restore, и шаг молчит по тому же критерию.
Маршрут внутренний и аутентифицирован BRABO_SERVICE_TOKEN — тем же секретом,
который установщик только что сгенерировал и записал в .env с режимом 600, —
и отказывает, если пользователь уже существует. Публичный маршрут «создать
первого owner» не появляется: публичный маршрут, создающий owner, — это гонка
между тем, кто установил, и тем, кто просканировал порт.
2. Без подтверждения e-mail, и основание — то, что даёт полномочия
Учётная запись рождается подтверждённой, через provisionarUsuario. Это не
ослабляет обычную регистрацию, которая остаётся байт в байт: подтверждение
e-mail доказывает «этот человек контролирует этот ящик», а тот, кто
запускает установщик, уже доказал нечто более сильное — контролирует
машину, .env с пятью секретами и демон Docker. Требовать от него более
слабое доказательство по каналу, о котором установка знает, что он выключен,
— театр.
Установщик говорит об этом в выводе, вместо того чтобы человек сам обнаружил, что его учётная запись отличается от остальных.
3. Согласие явное, и без TTY шаг не происходит
Та же мерка, что в RN-511 и consentir-base.mjs: шаг спрашивает e-mail и
пароль, а без интерактивного терминала сообщает, а не решает. Не
существует пути, при котором учётная запись рождалась бы с паролем, выбранным
скриптом, — ни сгенерированным, ни по умолчанию.
Пароль читается без эха и подтверждается. Минимальный отказ (длина) — тот же, что в домене, вызываемый тем же кодом; установщик не несёт второй мерки пароля, которая бы разошлась.
4. Ключ машины регистрируется там же, и сервис поднимается с ним
После создания учётной записи установщик регистрирует ключ устройства машины
(ADR 0154), записывает приватный ключ с kid внутри (RN-475, дефект, из-за
которого автоматический режим никогда не аутентифицировался) и устанавливает
сервис — unit на машину из ADR 0154, пункт 4.
Пара генерируется на машине; приватный ключ никуда не едет. Это тот же
дизайн, что в браузере (ADR 0118), по той же причине, и он сохраняет верной
фразу, которой RN-519 отказывает представлению maintainer: «приватный ключ
устройства никогда не покидает браузер» — становится «никогда не покидает
машину».
Уточнено в реализации (RN-552). Этот пункт не говорил, какими учётными данными установщик регистрирует ключ, и обе кандидатуры были реальны. Маршрут —
POST /internal/machine-device-keys— родился с service token, тем же, что для первой учётной записи: это тот же установщик, в ту же минуту, и токен доказывает контроль над МАШИНОЙ, а именно это и хочет доказать этот шаг. Альтернатива — учётные данные только что созданного пользователя — противоречила бы пункту 5 этого ADR («установщик ни за кого не входит») и дала бы паролю, прочитанному в TTY, второе использование, тогда как Последствия обещают, что он используется и отбрасывается. Цена — утёкший service token начинает мочь ФАБРИКОВАТЬ долговременные учётные данные доступа — объявлена вdocs/security-surface.md, и в маршруте живут три ограничения: в теле нетuserId(владелец — ЕДИНСТВЕННЫЙ пользователь установки,409при нуле или более чем одном), регистрация ЗАМЕНЯЕТ активные ключи машины владельца (переустановленная машина проходит; тысячи ключей не существует), а JWK сdотклоняется по имени, а не записывается. Уточнено, а не опровергнуто, и письменно, потому что этот ADR ещёProposed: принятый ADR никогда не редактируется, новый ссылается на старый.
5. Сервис поднимается без проекта, и это нормальное состояние
Установленный агент запрашивает GET runner/projects (ADR 0154, пункт 3) и
ничего не находит, потому что проекта ещё нет. Он ждёт, и это не ошибка:
не выдаёт отказа, не выходит с ненулевым кодом, а service status говорит
«работает, нет проектов в режиме runner» — одно из четырёх состояний, которые
RN-518 уже различает, а не пятое.
Когда человек создаст первый проект в режиме runner в web, агент уже там.
Это и есть то «привязать папку позже», к которому стремится фаза, и оно
происходит через workspace_create (RN-532), без того чтобы кто-то возвращался
в терминал.
Что этот ADR явно отвергает
- Создание учётной записи, когда одна уже существует. См. пункт 1.
- Публичный маршрут первого owner. См. пункт 1.
- Пароль, сгенерированный скриптом, или по умолчанию. См. пункт 3. Установщик, выбирающий пароль, производит установки с одинаковым паролем.
- Ослабление подтверждения e-mail обычной регистрации. Пункт 2 создаёт путь для названного случая; другой он не трогает.
- Самостоятельное включение SMTP. Установщик не спрашивает почтовый сервер
и не записывает
MAIL_*:MAIL_TRANSPORT=logостаётся объявленным значением по умолчанию, здесь как и в продакшене. Он лишь перестаёт зависеть от него, чтобы установка завершилась. - Запрос учётных данных LLM. Это вне рамок: установка завершается пригодной к использованию, а ключ provider — решение того, кто будет тратить (RN-058).
Последствия
- Установка одной строкой начинает по-настоящему завершаться: она заканчивается учётной записью, возможным входом и работающим локальным агентом. Сегодня она заканчивается входом, который никто не может пройти, не читая лог контейнера.
- Установщик начинает обращаться с паролем. Он читается без эха,
используется и отбрасывается — никогда не записывается в
.env, никогда в маркер, никогда в лог. Маркер ($XDG_STATE_HOME/brabo/) получает e-mail созданного owner, который является идентификацией, а не секретом, и больше ничего. - Второй путь создания пользователя начинает существовать в продакшене. Он узкий (первая учётная запись, внутренний маршрут, service token) и исчезает, как только в установке появляются люди, — но это новая поверхность, и она объявлена.
provisionarUsuarioполучает третьего вызывающего, и первого, который не является автоматизацией тестов. Его docblock сегодня говорит, что он существует для seed и smoke; он начнёт говорить ещё и для установки.- ФАЗА 29 становится согласованной сама с собой. Она отвергла установщик одной строкой «пока артефакты не подписаны» и подписала до установки; эта фаза закрывает другой конец — установить не значит доставить байты, значит сделать пригодным к использованию.
Примечания к принятию (2026-09-27)
Сверено с кодом при принятии. Решение остаётся как есть; это пункты, в которых текст и код расходятся, и где код их разрешил:
- Пункт 2 и Последствия — «учётная запись рождается подтверждённой, через
provisionarUsuario», который «получает третьего вызывающего». Текст не учёл, что скриптprovisionarUsuarioОТКАЗЫВАЕТСЯ работать сNODE_ENV=production(apps/api/src/scripts/provisionar-usuario.ts:37), а установщик работает именно в продакшене. Решением было извлечь ядро — тройку записей, благодаря которой учётная запись рождается подтверждённой, — вProvisionarUsuarioUseCase(apps/api/src/application/use-cases/auth/provisionar-usuario.use-case.ts:43) и оставить отказ в СКРИПТЕ, который он и защищает (известный пароль, созданный без участия человека). Нового вызывающего получил use case, через маршрут первой учётной записи (apps/api/src/application/use-cases/auth/criar-primeira-conta.use-case.ts:85); у скрипта остались два прежних, seed и smoke, иBRABO_FORCE_SEEDНЕ является путём установщика (RN-546). - Пункт 4 — какими учётными данными регистрируется ключ машины. Уже
уточнено в самом тексте, пока он был
Proposed: service token, черезPOST /internal/machine-device-keys(RN-552). Пара рождается в терминале двумя командами,brabo-runner device-key createиfinish --id, потому чтоkid— это id записи на сервере и существует только после неё (RN-551). - Пункт 5 — «не выходит с ненулевым кодом». Ожидание при нуле проектов
существует (RN-550, исправившая выход с 0 из RN-544 именно за противоречие
этому пункту), но у него есть потолок в ДЕСЯТЬ подряд запросов без полезного
ответа, и тогда агент выходит с 1, называя число
(
apps/runner/src/espera-de-projetos.ts:75, выход вapps/runner/src/index.ts:1547). Пустой список не считается отказом — только api, который не отвечает. - Пункт 5 — «
service statusговорит "работает, нет проектов в режиме runner"».statusне знает о проекте: он отвечает с диска и от менеджера, четырьмя состояниями RN-518 (apps/runner/src/servico.ts:246), а ожидающая unit машины отображается какrodando. «Нет проектов в режиме runner» говорит ЛОГ самого агента (apps/runner/src/index.ts:1529). Пункт об отсутствии пятого состояния выполняется; текст, приписанныйstatus, — нет.