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

FASE 30 — локальный агент машины

Этот документ — срез фазы: к чему она стремится, от чего отказывается и в каком порядке. Решения живут в ADR 0154 и в ADR 0155 — если здесь что-то расходится с ними, действует ADR.

К чему стремится фаза​

Установить Brabo одной строкой и пользоваться. Сегодня FASE 29 почти это и даёт: install.sh проверяет собственное происхождение, поднимает compose, устанавливает бинарник runner через install -m 0755 и записывает базу. И затем останавливается в двух точках, о которых продукт уже сам заявлял письменно.

Первая — локальный агент. Последняя строка установщика говорит, что он не сопрягает агент с проектом и что ключ устройства по-прежнему приходит с экрана. Желающий пользоваться установленным бинарником возвращается в браузер и в итоге вставляет в терминал команду, которую собирает apps/web/src/lib/runner-bootstrap.ts:398. Комментарий рядом объясняет, почему экран не может этого избежать, — «веб-страница не запускает бинарник ни на чьей машине», — и он прав. Чего он не покрывает: установщик — не веб-страница: он работает в терминале, с TTY, и ADR 0150 отверг curl | sh именно для того, чтобы можно было задавать вопросы.

Вторая — никто не может войти. Измерено в этой фазе: в сгенерированном .env нет ни одной почтовой переменной, MAIL_TRANSPORT сваливается в log (docker/docker-compose.install.yml:200), а регистрация требует подтвердить e-mail — что, как уже фиксировал docblock apps/api/src/scripts/provisionar-usuario.ts, «с log-only MailSender само не закрывается». Установка одной строкой заканчивается словом «Готово» и оставляет человека выуживать ссылку подтверждения из docker compose logs api.

Обе проблемы решаются в одном месте, и именно это делает фазу одной.

Что измерение изменило в срезе​

FASE 29 вывела runner на машину из рамок с помощью измерения: «один runner на проект навязан в пяти независимых местах». При повторном измерении с файл:строка три из пяти менять не нужно — и это уменьшает фазу вдвое.

#гдечто навязываетэта фаза
1terminal_channel.ex:204топик terminal:<projectId>не трогает
2runner_socket.ex:73id socket с project_idне трогает
3Engine.Runners.SocketTicketticket несёт project_idне трогает
4apps/api/src/db/schema/auth.ts:325-326project_id NOT NULLстановится nullable
5apps/runner/src/servico.ts:264,391unit brabo-runner-<projectId>становится одной на машину

Причина — в ADR 0154: пункты 1, 2 и 3 описывают соединение, а не процесс. Процесс, открывающий N соединений — по одному на проект, — удовлетворяет им байт в байт и сохраняет отказ второму runner в том же проекте, что является реальной гарантией, а не случайностью. Остаётся то, что принадлежит процессу: учётные данные (4) и unit (5).

В engine в этой фазе ничего не меняется. Это была самая полезная находка измерения, и именно она сохраняет RunnerReadiness (RN-507), зеркало (RN-516) и workspace_create (RN-532) байт в байт.

Сессии​

По одному результату на каждую. Порядок задаёт колонка «зависит от».

#результатзависит отсобственные запреты
1этот документ, ADR 0154/0155 и диапазон RN—не трогает apps/, docker/, scripts/, install.sh; не редактирует принятый ADR
2ЗАКРЫТА — api: project_id nullable, ключ машины принимается в runner-ticket, GET runner/projects (RN-543)1не трогает engine; не ослабляет роль ни одного маршрута
3ЗАКРЫТА — runner: N соединений, по одному на проект, обнаруживаемые через маршрут (RN-544)2не трогает RunnerReadiness, зеркало и workspace_create
4ЗАКРЫТА — runner: unit на машину в service install, сосуществующая с unit на проект (RN-545)3не удаляет unit на проект; не меняет Restart=on-abnormal
5ЗАКРЫТА — api: POST /internal/first-account, подтверждённая учётная запись + личный workspace, 409 при любом существующем пользователе (RN-546)1не создаёт публичный маршрут; не трогает обычную регистрацию
6ЗАКРЫТА — install.sh: согласие, учётная запись, ключ машины и service install (RN-547)4, 5нигде не записывает пароль; без TTY сообщает и выходит с 0
7ЗАКРЫТА — web: экран проекта распознаёт уже сопряжённый агент машины (RN-548)3не удаляет поток ADR 0118
8ЗАКРЫТА — E2E на чистой машине, docmap, docs:check (RN-549)6, 7не ослабляет gate ради прохождения E2E

Что закрыла сессия 2 и что она оставила заявленным​

Половина api работает: runner_device_keys.project_id — nullable, PatAuthGuard разрешает роль относительно запрошенного проекта, когда учётные данные не называют никакого, и GET runner/projects существует. Две вещи, которых измерение среза не предвидело и которые реализация заставила решить, обе зафиксированы в RN-543:

  • Список RN-519 стал возвращать оба вида, помеченные. Ключ машины, не попадающий ни в один список, был бы невидимым и постоянным — дефект, который закрыла RN-519, возродившийся в новом виде.
  • Отзыв получил МНОЖЕСТВЕННУЮ цель. revogar ключа машины не имеет projectId, который можно передать engine; отказ от разрыва вновь открыл бы RN-520 в N соединениях. Один {проект, пользователь} на каждый проект владельца в режиме runner, без изменения engine.

И то, что остаётся заявленным, но не сделанным: ни один маршрут api пока не СОЗДАЁТ ключ машины — регистрирует его install.sh (ADR 0155 пункт 4), в сессии 6. Маршрут родится в PR, где появится первый реальный вызывающий.

Что закрыла сессия 3 и три решения, которые ей пришлось принять​

Локальный агент действительно открывает N соединений, и измерение среза подтвердилось: в engine ничего не изменилось, а обработчики runner (tratarExec, tratarMirrorSync, tratarWorkspaceCreate) не изменились ни на строку — схему определили четыре поля НА ПРОЕКТ в EstadoDoRunner, ставшие N состояниями с docker и base, разделяемыми по значению. Старый режим остался байт в байт, а новые ворота узки: учётные данные машины и база, на которую дано согласие.

Три вещи, которых срез не предвидел, и RN-544 их фиксирует:

  • Отвергнутый проект не роняет остальные. Потолок попыток и отказ в join перестали принадлежать ПРОЦЕССУ и стали принадлежать ПРОЕКТУ; только когда не остаётся ни одного, процесс выходит с 1, с исходом каждого. В режиме одного проекта прежнее поведение остаётся идентичным по обратному рассуждению: при ОДНОМ соединении обслуживать больше нечего.
  • Список запрашивается только при старте. Повторный опрос привёл бы к тому, что список, вернувшийся МЕНЬШИМ — удалён? преобразован? временная 500? — ронял бы живое соединение из-за неоднозначности. Процесс сообщает об этом при запуске, и жест — переподключиться.
  • Пустой список — НОРМАЛЬНОЕ состояние, с выходом 0. Это состояние любой новой установки, и Restart=on-abnormal намеренно её не перезапускает. (Пересмотрено при разделении сессии 6, RN-550: при НУЛЕ соединений агент ОСТАЁТСЯ РАБОТАТЬ и повторно запрашивает — половина «только при старте» остаётся нетронутой для тех, у кого есть живое соединение.)

И то, что остаётся заявленным, но не сделанным: apiUrl в режиме машины берётся из --api-url/BRABO_API_URL/значения по умолчанию и никогда из brabo-runner.config.json, который привязан к ПРОЕКТУ, — флаг пишет unit на машину из сессии 4. Unit остаётся на проект: эта сессия изменила ПРОЦЕСС, а не сервис.

Что закрыла сессия 4 и три решения, которые ей пришлось принять​

Пятая и последняя связь из таблицы выше закрыта: service install --machine устанавливает brabo-runner.service / dev.brabo.runner, без суффикса, и поднимает процесс, созданный сессией 3. Unit на проект не была удалена и не переименована, Restart=on-abnormal остался байт в байт (его замена отменила бы и решение сессии 3 о выходе с 0 при пустом списке), и ничего в api, engine или web не было затронуто.

Одно измерение изменило механизм, и оно противоречит букве пункта 5 ADR 0154. ADR написал «service install без --project устанавливает unit машины»; у resolverProjeto два источника, и обычный путь сегодня — запускать install вообще без флагов изнутри папки, которую настроил браузер, — отвечает brabo-runner.config.json. Если следовать букве, ADR молча преобразовал бы установку тех, кто уже пользуется продуктом, а это противоположно тому, что обещает тот же пункт 5. Поэтому дискриминатором стал флаг --machine, явный opt-in, с обоими источниками проекта нетронутыми.

Три решения, которые срез оставлял открытыми, зафиксированы в RN-545:

  • status отвечает о запрошенном виде, и код не суммирует. Четыре состояния и четыре кода (RN-088) не превращаются в восемь; другой вид появляется в ТЕКСТЕ, как присутствие, прочитанное с диска, с указанием, что его состояние у менеджера не запрашивалось. Без --machine и без разрешимого проекта ответ касается машины — единственной unit, имени которой не нужен аргумент.
  • install ОТКАЗЫВАЕТ, когда другой вид уже установлен. Ничего не удаляет, ничего не записывает и называет uninstall каждой найденной unit. Без --force: обе вместе были бы двумя процессами, оспаривающими один проект, и сервер отказал бы одному из них. Гарантия пункта 5 (уже установленная unit продолжает работать) остаётся нетронутой — отказ касается создания перекрытия сейчас.
  • uninstall без названного вида ничего не удаляет и перечисляет то, что существует. Он намеренно не наследует значение по умолчанию от status: прочитать не тот вид стоит одной неверной строки, удалить не тот — сервиса и ключа.

Два собственных отказа install --machine не были в срезе и измерены: папка не может содержать brabo-runner.config.json (сервис поднялся бы в режиме ПРОЕКТА, молча, обслуживая только один), а база должна иметь согласие (без неё агент выходит в uso() при первой загрузке). Unit замораживает XDG_CONFIG_HOME — именно она определяет, где живёт файл базы, и ни один из двух менеджеров не передаёт окружение shell, — но никогда значение базы.

И то, что остаётся заявленным, но не сделанным: install --machine не знает, действительно ли ключ в этой папке — ключ машины (на диске оба вида — один и тот же файл, а знает сервер), поэтому папка с ключом проекта устанавливает unit без ошибки, и отказ появляется при первой загрузке. И никто пока не СОЗДАЁТ ключ машины — это сессия 6.

Что закрыла сессия 5 и решение, которого не было в ADR 0155​

Сессия 5 закрылась решением, с которым ADR 0155 не сталкивался, и оно важно для того, кто возьмёт сессию 6: provisionarUsuario отказывается запускаться с NODE_ENV=production, а установщик работает именно там. Вместо BRABO_FORCE_SEED ядро стало ProvisionarUsuarioUseCase, а отказ остался в скрипте — он защищает от ИЗВЕСТНОГО пароля, созданного БЕЗ участия человека, а маршрут относится к другой категории. Сессия 5 также добавила то, чего ADR не говорил, а RN-410 требует: личный workspace рождается в той же транзакции, иначе установка закончилась бы входом, который проходит, и дашбордом, где «Новый проект» негде создать.

Что закрыла сессия 7 и три решения, которые ей пришлось принять​

Метка вида, которую сессия 2 добавила в список, — «без них в списке ключ машины был бы невидим на любом экране», — получила потребителя. Панель onboarding runner начинает распознавать активный ключ МАШИНЫ и при его наличии перестаёт требовать сопряжения: не хватает не сопряжения, а работающего агента. Ничего в api, engine или runner не затронуто.

Три решения, которые срез оставлял открытыми, и во что превратилось каждое (RN-548 содержит всё обоснование):

  • Где это появляется: на ПАНЕЛИ, а значит, сразу во всех трёх местах её монтирования — но не с одинаковым результатом. Распознавание идёт по ПРОЕКТУ, потому что маршрут — GET /projects/:projectId/runner-device-keys: у TerminalPanel и FolderBrowserModal проект есть всегда, а у NewProjectWizard — только после досрочного создания (RN-437). Без projectId спрашивать некого. AmbienteDoProjeto был РАССМОТРЕН и отвергнут: его docblock уже заявляет, что присутствие runner — знание из первых рук канала, и строка о зарегистрированном ключе там была бы третьим прокси, конкурирующим с более сильным сигналом, — причём о МАШИНЕ, а не о проекте.
  • С ключом и без живого соединения — самый частый случай — экран говорит две вещи, которые знает, и называет две, которых не знает. Объявляет сопряжение (с именами ключей и датой последнего использования), а рядом: зарегистрированный ключ — не работающий агент и список относится к вашей УЧЁТНОЙ ЗАПИСИ, а не к этому браузеру. Это мерка workspaceVerifiedAt, применённая на шаг раньше, включая тон accent и никогда success. Жест, который он предлагает, — проверить СЕРВИС на сопряжённой машине, а за «сейчас» по-прежнему отвечает EsperaDoRunner, переиспользованный.
  • Вид проявляется через свою ЦЕНУ, а не как ярлык. Панель не перечисляет ключи и не отзывает — она говорит, что ключ машины обслуживает все проекты владельца и что его отзыв роняет локальный агент во всех них.

И то, что остаётся заявленным, но не сделанным: никто пока не СОЗДАЁТ ключ машины (это install.sh, сессия 6), поэтому сегодня экран можно проверить только с ключом, зарегистрированным вручную. Предлагаемая команда — brabo-runner service status --project <id>, существующая сегодня форма, — форма для машины придёт с unit сессии 4 (закрыто позже, AT-106: при распознанном ключе машины панель предлагает brabo-runner service status --machine; с ключом проекта остаётся --project <id>). Активный ключ ПРОЕКТА панель не меняет: это родственный дефект, на уровень ниже. А экрана для перечисления/отзыва ключей по-прежнему нет.

Что закрыло разделение сессии 6 — и почему это не сессия 8​

Сессия 6 затронула бы apps/api, apps/runner и install.sh разом, а результат на три workspace — это то, где сессия теряется. Она была разделена на три: две части ниже, непересекающиеся и параллельные, и install.sh после них, как оркестратор и ничего больше, — сессия 6 остаётся открытой, с этим меньшим срезом. Ни одна из этих двух не является нумерованной сессией фазы, и в особенности ни одна не является сессией 8, которая — E2E и остаётся открытой в таблице выше.

Две недостающие части на стороне агента, и обе происходят из ADR 0155.

Первая — ПЕРЕСМОТР сессии 3, и решение — АСИММЕТРИЯ (RN-550): при нуле соединений агент перестаёт выходить и начинает ЖДАТЬ, повторно запрашивая GET runner/projects через 15с/30с/60с, пока не появится первый проект; при живом соединении список по-прежнему читается только при старте, байт в байт. Довод сессии 3 остаётся целым там, где он действует, — вернувшийся МЕНЬШИМ список неоднозначен, а разрыв живого соединения из-за неоднозначности меняет верное состояние на догадку, — а при нуле соединений рвать нечего. Пал довод exit 0 («активный сервис, который ничего не делает»), потому что процесс стал что-то делать и говорить об этом. Измерено в этой сессии: Restart=on-abnormal не смотрит на код выхода — он перезапускает по сигналу, watchdog или таймауту, — поэтому в обеих unit ничего не меняется, а эффект в том, что unit машины действительно находится в active (running). У ожидания потолка нет; у сбоя есть (десять запросов подряд, выход 1 с указанием числа).

Вторая закрывает пробел, который заявили сессии 2, 3 и 4, — «никто пока не СОЗДАЁТ ключ машины», — в той половине, что принадлежит агенту (RN-551): brabo-runner device-key create|finish генерирует пару Ed25519 на этой машине. Решение здесь — СРЕЗ: CLI не говорит с api, потому что регистрирует установщик с BRABO_SERVICE_TOKEN, а передача этого секрета агенту расширила бы его возможности далеко за пределы нужного — каждая сторона хранит свой секрет, и ни одна не видит чужого. Отсюда схема в два шага: kid — это id РЕГИСТРАЦИИ и существует только после неё, поэтому create записывает .parcial, который читатель игнорирует, а finish записывает настоящее имя. Файл, который читает runner, никогда не существует без kid — дефект RN-475, невозможный по построению.

То, что остаётся заявленным, но не сделанным: МАРШРУТ, регистрирующий публичный ключ машины, и install.sh, связывающий три команды, остаются сессией 6.

И половина API того же разделения (RN-552): POST /internal/machine-device-keys, engine-service, тело { name, publicKeyJwk } и ответ с id, который становится kid приватного ключа (RN-475). Учётные данные выбраны, и цена заявлена: service token, потому что требование учётных данных только что созданного пользователя заставило бы установщик открыть на машине ровно ту живую сессию, открывать которую ADR 0155 пункт 5 отказался, — и дало бы второе применение паролю, который RN-546 намеренно отбрасывает. Цена: утёкший BRABO_SERVICE_TOKEN становится способен изготовить долговременные учётные данные доступа, а не только говорить с внутренними маршрутами.

Три ограничения живут в маршруте, а не в тексте: в теле нет userId (владелец — ЕДИНСТВЕННЫЙ пользователь установки, а ноль или больше одного — это 409 без записи — условие RN-546 в другой форме, касающееся УСТАНОВКИ и никогда аргумента); регистрация ЗАМЕНЯЕТ активные ключи машины владельца в той же транзакции, поэтому переустановленная машина проходит, а тысяча ключей невозможна по условию, без числа, которое устареет; и JWK с d отвергается по имени, причём мерка формы стала ОДНОЙ, в домене, вызываемой обоими регистраторами.

То, что остаётся заявленным, но не сделанным: в установке без ПРОЕКТА ни один экран не достаёт до ключа машины — перечисление и отзыв оба идут через /projects/:projectId/..., — и именно поэтому регистрация заменяет, а не оставляет осиротевший: осиротевший ключ там был бы живым и недостижимым.

Что закрыла сессия 6 и четыре решения, которые ей пришлось принять​

С меньшим срезом сессия 6 стала оркестрацией и ничем больше: ни одна строка apps/ не изменилась, ни один маршрут не родился, а четыре части, которые связывает install.sh, уже существовали и были протестированы. Дыра, которую фаза открыла в начале, — «в новой установке никто не может войти», — закрывается здесь, и обещание пункта 5 ADR 0155 начинает выполняться на деле: агент поднимается без проекта, ждёт и подхватывает первый, который человек создаст в web.

Четыре решения, полностью зафиксированные в RN-547:

  • Когда сбоит промежуточное звено, ничего не откатывается, обо всём сообщается — и скрипт всегда выходит с 0. Уже выполненные звенья полезны сами по себе (с учётной записью человек входит, с ключом машина сопряжена), откат потребовал бы удалить учётную запись и отозвать ключ, а маршрута для этого нет и не должно быть такого, который установщик вызвал бы сам. Цена — установка, которая может закончиться на полпути; чего она никогда не делает — это заканчивается на полпути МОЛЧА: каждый сбой — именованная строка в итоговом блоке, с командой для повтора.
  • Идемпотентность — это КОД HTTP, и 409 — не сбой. Второй запуск или миграция, чьё восстановление принесло пользователей: шаг умолкает, объясняя почему, и не попадает в список незавершённого. 000 — curl, не поговоривший с api, — обрабатывается отдельно, иначе «api не поднялась» читалось бы как «api отказала». А шаг агента выполняется только тогда, когда ЭТОТ запуск создал учётную запись: при уже существующем пользователе регистрация ключа машины отчеканила бы долговременные учётные данные для того, кто в этом прогоне ничего не просил.
  • name ключа — это hostname. RN-551 заявила, что CLI не даёт имён — это поле регистрации, и имя даёт тот, кто регистрирует. Открывающему список ключей нужно знать, К КАКОЙ машине идти, а hostname — это имя, которым человек уже называет свои машины.
  • .parcial, остающийся, когда регистрация не завершилась, ОСТАЁТСЯ и называется. Удалить его означало бы, что установщик решает, будто знает то, чего не знает: таймаут после записи api неотличим от таймаута до неё, и файл может быть единственной копией приватной половины уже зарегистрированного ключа.

Две вещи изменились, хотя их не было в срезе. Пароль отправляется через STDIN curl, а service token — в --config с правами 600, — /proc/<pid>/cmdline доступен на чтение любому пользователю машины, и пароль в argv был бы открыт на время запроса. И --help лгал: он печатает заголовок, являющийся комментарием, а правило, запрещающее говорить установщику о «сессиях фазы», читало только строки кода, — поэтому «ESTA VERSÃO NÃO INSTALA NADA» пережило месяцы после того, как скрипт стал поднимать compose и устанавливать runner. Исправлено, с тестом по ВЫВОДУ --help, а фиксированный диапазон строк, молча его обрезавший, стал «до первой строки, не являющейся комментарием».

И то, что остаётся заявленным, но не сделанным: установка от начала до конца на чистой машине — настоящий compose, настоящая api, настоящий бинарник runner, записанная unit и ожидающий агент — это E2E сессии 8, и доказать это может только он. В тестах этой сессии brabo-runner — shell-дублёр, а api — тестовый сервер: доказывается СВЯЗЫВАНИЕ, но никогда интеграция. И install --machine по-прежнему не знает, действительно ли ключ в этой папке — ключ машины (пробел RN-545), — разница в том, что теперь существует путь, который СОЗДАЁТ там ключ машины, так что отказ при первой загрузке перестал быть вероятным исходом.

Что закрыла сессия 8 — и три решения, которые ей пришлось принять​

Последняя в фазе и единственная, способная ответить на вопрос, к которому стремится вся фаза. Сессия 6 письменно заявила, что её тесты доказывают СВЯЗЫВАНИЕ и никогда интеграцию — brabo-runner как shell-дублёр, ненастоящая api, — и именно этот пробел закрывается здесь.

Harness родился не в этой сессии: .github/workflows/install-e2e.yml существует с FASE 29 (RN-534) и уже запускал опубликованный установщик на эфемерном runner Actions, который буквально является чистой машиной. То, что он доказывал, останавливалось на .env. Сессия его расширила, и теперь он доказывает пять звеньев RN-547 против настоящих api и бинарника, агента, работающего в ожидании, и первый проект в режиме Runner, подхваченный без того, чтобы кто-то возвращался в терминал.

Три решения, полностью зафиксированные в RN-549:

  • Сигнал «агент работает» — это СТРОКА, которую он пишет, а не состояние менеджера. perguntarEstado отображает activating в rodando — и обоснованно (RN-088: «поднимается» — не «остановлен»), — поэтому systemctl is-active и service status --machine отвечают работает для процесса, который умрёт через две секунды. Строка RN-550 существует только после того, как агент прочитал ключ с диска, получил ticket, поговорил с api и получил ПУСТОЙ список: она доказывает сопряжение и ожидание разом, и это ЗАПИСАННЫЙ ФАКТ, а не выборка состояния, — поэтому ожидание её с потолком не является гонкой. status идёт после неё, как подтверждение, но никогда вместо.
  • Исход доказывается через API, а не через браузер. Субъект — АГЕНТ, и наблюдает он GET /runner/projects. Браузер создал бы проект через тот же маршрут с лишним слоем, который сбоит по посторонним причинам, — и не сделал бы утверждение сильнее, потому что оно по-прежнему является строкой journal. Браузерный слой существует (e2e/, ADR 0120), отвечает на другой вопрос и предполагает работающий compose production: привлечение его сделало бы E2E установщика зависимым от второго harness на машине, которая должна быть чистой.
  • Половина, запускаемая в PR, — это ТЕСТ, а не workflow. E2E остаётся вне pull_request — открыть эту дверь означало бы дать установщику способ пропустить проверку происхождения, от чего ADR 0150 отказывается. Но workflow, запускаемый на теге, — это workflow, ошибка которого проявляется на теге: scripts/dev/install-e2e.spec.ts стережёт две формы его тихого загнивания — ослабленный gate и переписанную фразу install.sh, из-за которой утверждения не проваливаются, а ИСЧЕЗАЮТ.

Обнаружились два дефекта, оба невидимые, потому что workflow никогда не запускался. Первый был в harness и исправлен: файл ответов TTY начинался с nao-migrar, что на чистой машине попадает в вопрос о МАРКЕРЕ — он не совпадает с s|S|sim|SIM, скрипт говорил «Nada foi gravado» и выходил с 0, и утверждение о маркере провалилось бы. Второй — НАХОДКА и остался находкой: опубликованный install.sh ничего не поднимает сам на чистой машине — он использует docker/docker-compose.install.yml, который не является ассетом Release, не входит в подписанный checksums.txt и нигде им не скачивается (вместе с ./postgres/init.sql это три файла). Тот, кто следует curl … install.sh из runbook, падает на "no such file or directory" уже после проверки подписи и записи .env. Workflow приносит их вручную, по тегу, в шаге, который называет это находкой; исправление — это выбор между подписанным ассетом и клоном, и это отдельный результат.

И то, что остаётся заявленным, но не сделанным: этот E2E не запускается в PR, который его написал, — первый реальный запуск — это следующий финальный тег, и ни одна проверка PR не доказывает ни строки его. Вне рамок, по решению: второй запуск путём МИГРАЦИИ (RN-530), macOS (полный шаг — Linux; launchd и Docker, которого нет у hosted runner) и браузер.

Зарезервированный диапазон: RN-543..555. ADR 0154 и 0155. RN-541 уже выделена в несмерженной ветке — пропуск намеренный, по критерию, который FASE 29 зафиксировала, разрешив три коллизии по дате.

Что фаза дала в целом​

Восемь сессий плюс две части, вышедшие из разделения сессии 6 и не являющиеся нумерованными сессиями. То, что фаза обещала в начале, — установить Brabo одной строкой и пользоваться — стало происходить:

  • Идентичность локального агента перестала быть привязанной к ПРОЕКТУ и стала привязанной к МАШИНЕ. Обе связи, которые, по измерению среза, нужно было изменить, изменились: runner_device_keys.project_id — nullable (RN-543), а unit — одна на машину (RN-545). Остальные три не изменились, и измерение, которое их спасло, — самая полезная находка фазы: топик, id socket и ticket описывают СОЕДИНЕНИЕ, и N соединений удовлетворяют им байт в байт.
  • В engine ничего не изменилось. RunnerReadiness (RN-507), зеркало (RN-516) и workspace_create (RN-532) остались байт в байт, как и обработчики runner.
  • Новая установка перестала рождаться без кого-либо внутри. MAIL_TRANSPORT по-прежнему log, и установщик по-прежнему не включает SMTP — изменилось то, что первая учётная запись рождается в ТЕРМИНАЛЕ, уже подтверждённой (RN-546/RN-547), с личным workspace в той же транзакции. Выуживание ссылки подтверждения из docker compose logs api перестало быть путём.
  • Агент поднимается сопряжённым и ЖДЁТ. Пара Ed25519 рождается на машине (RN-551), передаётся только публичный ключ (RN-552), id регистрации становится kid (инвариант RN-475, теперь ненарушимый по построению), а при нуле проектов процесс остаётся работать, повторно запрашивая (RN-550), вместо выхода.
  • Экран распознаёт уже сопряжённых (RN-548), никогда не утверждая, что агент работает, — зарегистрированный ключ доказывает сопряжение, а не живой процесс.
  • И всё в целом измерено от начала до конца (RN-549), против настоящих api и бинарника, на чистой машине.

То, чего фаза не закрыла, — сразу ниже, в «Пробелах, которых фаза касается и не решает», и ни один из них не изменился в размере по пути — кроме ЭКРАНА перечисления/отзыва ключей, который намеренно стал больше: фаза добавила вид ключа для перечисления и не построила экран. Добавьте находку о compose, измеренную сессией 8, и пробел install --machine, который по-прежнему не знает, действительно ли ключ в этой папке — ключ машины, — на диске оба вида являются одним и тем же файлом, а знает сервер.

Чего эта фаза НЕ касается​

Заявлено в обоих ADR и повторено здесь, потому что это придаёт смысл остальному:

  • decide.ts и абсолютные потолки — ни один не получает исключения, даже настраиваемого
  • proposed_action как источник любого внешнего эффекта агента; создание папки и сопряжение агента остаются конфигурацией с согласия, а не действием агента (линия RN-516)
  • неизменяемость event log
  • RunnerReadiness (RN-507) и три его предусловия — и никогда флаг «пропустить контейнер», по причине пункта 4 ADR 0147
  • зеркало (RN-515/516/517) и workspace_create (RN-532): оба привязаны к проекту и передаются в разрешении join того соединения
  • обычная регистрация с подтверждением e-mail и значение по умолчанию MAIL_TRANSPORT=log
  • поток настройки через браузер (ADR 0118)

Пробелы, которых фаза касается и не решает​

  • Отзыв по-прежнему достигает {проект, пользователь}, но никогда {ключ} (RN-520) — и становится дороже: отзыв роняет агент во всех проектах этой машины. FASE 29 заявила эту цену заранее; здесь она наступает. Закрытие требует смены цели отзыва, а это отдельное направление.
  • Нет экрана, где перечислять и отзывать ключи устройств. RN-519 открыла GET в api; web так и не получил экрана. Фаза добавляет вид ключа для перечисления и не строит экран — пробел становится больше, и он заявлен. RN-548 начинает ПОТРЕБЛЯТЬ этот список на панели onboarding, что его не закрывает: читать, чтобы распознать, — не то же, что перечислять, чтобы отозвать.
  • Единая точка отказа. Один процесс на машину вместо N: падение сервиса выключает агент во всех проектах. Это цена схемы, а не небрежность.
  • Учётные данные git по-прежнему отбрасываются в docker exec в режиме runner (ADR 0145) — эта фаза их не касается, и ничто здесь не ухудшает.
  • BRB-031 закрывается решением сопровождающего, а не как следствие: фаза создаёт путь без chmod и сохраняет путь браузера. Выбор вывести второй из употребления — отдельное решение.
  • BRB-017 (обязательство GPL образа engine, P1) остаётся там, где был, и эта фаза делает его конкретнее по той же причине, что и FASE 29: больше установок на чужих машинах.