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

ADR 0193 — Git с учётными данными выполняется на ХОСТЕ runner; код выполняется в контейнере

Статус​

Accepted. 2026-10-01 (AT-116, история HS-037, эпик EP-010, раунд 36; решение владельца от 01.10: «операции git с учётными данными на ХОСТЕ, код в контейнере»). Закрывает половину, которую RN-558 оставила объявленной открытой, и ссылается, не редактируя их, на ADR 0130 (порт Docker без свободного -e), ADR 0145 (RunnerReadiness и три предусловия режима runner) и ADR 0056 (учётные данные git только в ОКРУЖЕНИИ дочернего процесса). Доказательство — AT-111.

Контекст​

Измерено в dev 2026-10-01, до изменения:

  • ЕДИНСТВЕННЫЙ exec engine, несущий env, — аутентифицированный git fetch из Engine.Actions.Workspace.RunnerGit.fetch!/3 (GitAuth.env_de_auth/1, преобразованный в map; nil для provider без токена). TerminalExecutor вызывает RunnerRouter.exec/4 и никогда не передаёт env. В режиме runner нет ни push, ни clone с учётными данными через exec: clone при создании папки путешествует в workspace_create (RN-532), который уже выполняется на ХОСТЕ (criarPastaDoProjeto).
  • RunnerReadiness (ADR 0145) требует ЗАРЕГИСТРИРОВАННОГО running контейнера до любой операции RunnerGit, а в проекте runner эта запись существует лишь потому, что ТОТ ЖЕ runner поднял контейнер, — что устанавливает estado.containerAtivo. Когда он установлен, tratarExec (apps/runner/src/index.ts) направлял КАЖДУЮ команду в docker exec, у которого нет поля env (ADR 0130). Со времён RN-558 эта пара (env присутствует, контейнер активен) ОТКЛОНЯЛАСЬ с меткой credencial-nao-atravessa-o-container: ОБЫЧНЫЙ путь аутентифицированного fetch заканчивался названным отказом, никогда успехом.
  • Папка ОДНА И ТА ЖЕ с обеих сторон: tratarContainerStart монтирует estado.dir (корень, подтверждённый runner) в /work (DockerViaCli.start, ${raizDoProjeto}:/work:rw, PONTO_DE_MONTAGEM в packages/docker-port/src/docker-port.ts), а cwd каждого exec проверяется внутри estado.dir (validarCwdDentroDaRaiz) и переводится в /work/... только когда идёт в контейнер. .git, который fetch обновляет на хосте, — тот, который видит контейнер.

Два запрещённых выхода остаются запрещёнными: свободный -e в DockerPort (ADR 0130 отказался) и флаг «пропустить контейнер» в RunnerReadiness (он обрушил бы третье предусловие ADR 0145 для всего exec).

Решение​

  1. Операция git с учётными данными выполняется на ХОСТЕ runner, даже при активном контейнере. Команда dev agent по-прежнему идёт в docker exec. Учётные данные доходят до дочернего процесса путём, который всегда работал: apps/runner/src/exec.ts сливает env поверх process.env.

  2. Дискриминатор — ЯВНАЯ МЕТКА, поставленная engine, а не env. Payload exec получает gitCredenciado: true, опциональный, и ставит его RunnerRouter.exec_git_credenciado/5, вызываемый ОДНОЙ точкой (RunnerGit.despachar/5, из fetch!/3, только когда есть учётные данные). Функция требует непустой env через guard клаузы. Runner выполняет на хосте ТОЛЬКО при СОВПАДЕНИИ (метка — буквальное true и непустой env).

    Почему не один env, который уже существовал: тогда любой exec, который пришёл бы с env, ушёл бы из контейнера — env стал бы дверью выхода из сдерживания, которое ADR 0145 поставил для кода. С меткой выход — это НАЗВАННОЕ решение единственного вызывающего engine, и env без метки не получает хост. Метки самой по себе тоже недостаточно: без учётных данных нечего защищать, и команда следует привычной маршрутизации.

  3. Ни один порт ADR 0130 не меняется. У DockerPort.exec по-прежнему нет поля env; образ, сеть, монтирование и broker не затрагиваются. RunnerReadiness остаётся байт в байт: метка меняет ГДЕ выполняет runner, никогда ЛИ engine отправляет, — предусловие зарегистрированного running контейнера продолжает действовать и для fetch.

  4. Отказ RN-558 продолжает существовать и сужается до одного случая. Пара (env присутствует, контейнер активен) БЕЗ метки по-прежнему отклоняется с той же меткой протокола и источником politica. Ни один вызывающий engine сегодня не производит такую пару; на практике она возникает из-за РАСХОЖДЕНИЯ версий — brabo-runner, предшествующий этому ADR, не читает метку, направляет fetch в контейнер и отказывает как раньше. Поэтому сообщение Engine.Runners.CredencialDeGit.mensagem/2 начинает говорить, как это исправить (обновить runner или остановить контейнер), вместо «недостающая половина не существует». Текст отказа runner тоже меняется (говорит «SEM a marca de git credenciado»), и fixture apps/runner/fixtures/exec-result-recusa-de-credencial.json меняется вместе с ним.

  5. Инвариант лога сохраняется. Ни отказ, ни лог пути на хосте не упоминают имя или значение переменной env — отказ даёт КОЛИЧЕСТВО, а лог хоста добавляет только [git credenciado, no host]. Текст команды по-прежнему логируется как всегда (он несёт helper из ADR 0056, который ссылается на переменные по имени, но никогда по значению, — здесь это не меняется).

Последствия​

  • АУТЕНТИФИЦИРОВАННЫЙ fetch/clone удалённого репозитория в режиме runner начинает работать с поднятым контейнером, что является обычным путём. RN-558 перестаёт срабатывать на нём.
  • Старый runner + новый engine: runner игнорирует метку и отказывает с меткой RN-558; engine говорит «обновите runner». Новый runner + старый engine: engine не присылает метку, и runner отказывает как раньше. Ни одна комбинация не возвращается к тихому отбрасыванию.
  • Git с учётными данными выполняется с пользователем и git ХОСТА, а не контейнера. Файлы, которые fetch записывает в .git, рождаются с владельцем процесса runner — который является владельцем смонтированной папки. Не измерено: контейнер проекта runner, чей --user не совпадает с этим владельцем, увидел бы обновлённый .git с другим владельцем; сегодня spec, которую api присылает runner (EspecificacaoDeContainerParaRunner), не имеет usuario, поэтому контейнер работает с пользователем образа, а эффект не проверялся на настоящем Docker.
  • RN-558 перечисляла этот вариант как тот, что «сломал бы инвариант о том, что работа происходит внутри контейнера». Решение владельца принимает его с этой записанной ценой: из контейнера выходит РАЗГОВОР git с удалённым репозиторием (fetch с учётными данными), никогда код проекта, — npm install, тесты и сборка dev agent продолжают выполняться в docker exec. То, что приносит fetch, — объект git, и он становится рабочим файлом только в checkout/worktree add, которые не несут учётных данных и следуют привычной маршрутизации.
  • Хосту нужен установленный git. Он уже был нужен: workspace_create (RN-532) и путь без контейнера всегда выполняли git на хосте.
  • Метка — вторая константа протокола в паре exec, рядом с env. Она путешествует только когда равна true (payload обычного терминала остаётся байт в байт прежним), и runner принимает только boolean true — любое другое значение — отсутствие.
  • Вне охвата: push с учётными данными через exec runner сегодня не существует; если он появится, то войдёт через ту же функцию (exec_git_credenciado/5), никогда через второй путь метки.
  • Доказательство (AT-111) заменяет отказ успехом с обеих сторон: apps/runner/src/index-handlers.spec.ts (при активном контейнере и метке настоящий helper git получает учётные данные, и настоящий git fetch origin успешно выполняется на хосте, без docker.exec; env без метки по-прежнему отклоняется) и apps/engine/test/engine_web/channels/credencial_no_runner_test.exs (через настоящий TerminalChannel fetch идёт с меткой, и цепочка заканчивается {:ok, _}). Тесты ExUnit не запускались в окружении этой поставки (repo.hex.pm отвечал 403); их доказывает CI.