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
на проект навязан в пяти независимых местах». При повторном измерении с
файл:строка три из пяти менять не нужно — и это уменьшает фазу
вдвое.
| # | где | что навязывает | эта фаза |
|---|---|---|---|
| 1 | terminal_channel.ex:204 | топик terminal:<projectId> | не трогает |
| 2 | runner_socket.ex:73 | id socket с project_id | не трогает |
| 3 | Engine.Runners.SocketTicket | ticket несёт project_id | не трогает |
| 4 | apps/api/src/db/schema/auth.ts:325-326 | project_id NOT NULL | становится nullable |
| 5 | apps/runner/src/servico.ts:264,391 | unit 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: больше установок на чужих машинах.