ADR 0056 — Engine работает в удалённом репозитории
Статус
Принят — реализован и доказан тестом в Фазе B backlog (RN-076).
Открытие при реализации, зафиксированное потому, что оно меняет размер проблемы:
двум из четырёх потребителей учётные данные никогда не были нужны.
Engine.Gates.Diff и Engine.Harness.ProjectContext используют лишь ИМЯ ветки
по умолчанию — они останавливались на удалённом provider как побочный ущерб функции, которая
возвращала больше, чем они просили. Разделение default_branch/1 и
remoto_de_trabalho/1 разблокировало обоих без какого-либо токена.
Контекст
Engine.Projects.ProjectRepository.get_local_repo_path/1 возвращает
{:error, {:unsupported_provider, "github"}} для всего, что не local.
От него зависят четыре потребителя, и все останавливаются вместе:
| потребитель | что перестаёт работать |
|---|---|
Engine.Dev.WorktreeManager | у dev agent нет worktree — он не пишет код |
Engine.Actions.TerminalExecutor | без workspace любая команда падает |
Engine.Gates.Diff | у QA и SecOps нет diff, который они судят |
Engine.Harness.ProjectContext | агент собирает контекст без репозитория |
Корень — асимметрия: api говорит с GitHub по HTTP — создала репозиторий, закоммитила файлы bootstrap, создала ветки, — тогда как engine работает в файловой системе и знает только локальный bare repo. Проект на GitHub проходит разговорную половину и bootstrap и останавливается на половине построения.
Именно это сегодня не даёт FASE 13b существовать как написано: она требует измеренного выполнения в проекте, ПРИНЯТОМ из fork через удалённый GithubProvider, а это выполнение никак не может дойти до первой команды.
Что уже готово и не требует изменений
Engine.Actions.Workspace.init_from_bare!/3 делает git init + remote add origin <origem> + fetch + checkout. Это уже универсально: то, что <origem>
является локальным путём, — случайность provider local, а не замысла. Замена
источника на URL решает весь путь без его переписывания.
Не хватает, следовательно, не git-сантехники, а учётных данных.
Решение
1. Engine запрашивает у api рабочий remote и никогда не хранит учётные данные
Токен живёт зашифрованным в user_credentials, с envelope encryption, а мастер-ключ
принадлежит api. Engine не получает мастер-ключ и не сохраняет
токен: он запрашивает у api по уже существующему каналу /internal/* с service
token рабочий remote проекта в момент, когда ему нужно
получить или отправить.
Передача мастер-ключа engine расширила бы радиус поражения самого чувствительного секрета продукта ради одного HTTP-вызова меньше.
2. Токен НИКОГДА не попадает в .git/config
Это самое важное решение, и оно — прямое следствие ADR 0055.
Очевидный путь — remote add origin https://x-access-token:TOKEN@github.com/…
— записывает учётные данные открытым текстом внутри папки проекта. А
RN-075 только что дала dev agent
автоодобряемое чтение внутри этой папки: cat .git/config вернул бы токен
без какого-либо одобрения, и он уехал бы к LLM-provider на
следующем ходе, внутри истории цикла.
Область путей защищает от чтения агентом за пределами проекта. Она не защищает — и не может защитить — от секрета, который сам продукт поместил внутрь.
Поэтому: origin хранит чистый URL, а аутентификация внедряется на каждый
вызов, живя только во время использующего её процесса git. Никаких учётных данных в
файле, ни в .git/config, ни в сохранённом helper.
3. Учётные данные — владельца (OWNER) workspace
То же правило, что RN-058 уже установила для ключа LLM, по той же причине: платит и авторизует владелец workspace, а не агент и не тот, кто открыл сессию.
4. provider: local остаётся прежним
Это не обратная совместимость из вежливости: локальный provider — то, что используют
контрактные тесты, и то, благодаря чему pnpm dev работает вообще без учётных
данных. Рабочий remote локального проекта — это путь bare repo, а
остальной путь разницы не знает.
5. Сбой учётных данных — это infra, и он называется
Отсутствующий, просроченный токен или токен без прав на репозиторий отклоняется с источником
infra — не modelo, не código. Это правило CLAUDE.md об источнике
сбоя, а находка T этого раунда показывает, что оно нарушается именно на
путях ошибок, которые никто не задействует.
Последствия
Что разблокируется. Проект на удалённом provider получает worktree, терминал, diff для gate и контекст — то есть половину построения. Это предусловие FASE 13b: без этого не существует измеренного выполнения в удалённом репозитории.
Что становится дороже. Engine начинает зависеть от api для работы в
файловой системе. fetch теперь может упасть из-за сети, просроченного токена
или недоступности api — три режима сбоя, которых у локального bare repo не было.
Отсюда пункт 5 — решение, а не деталь.
Чего этот ADR НЕ решает. Изоляция остаётся открытой проблемой
ADR 0055: токена больше нет
на диске, но агент по-прежнему работает в том же контейнере, что и
монорепозиторий Brabo. env процесса git в окне, пока он
работает, — меньшая поверхность, чем постоянный файл, но не нулевая.
Рассмотренные альтернативы
Дать мастер-ключ engine. Убирает HTTP-вызов и удваивает число мест, откуда может утечь любой секрет продукта. Отвергнуто.
Токен в URL origin. Так делает почти каждый туториал, и это ровно
то, что ADR 0055 сделал опасным: запись секрета туда, где у агента есть
автоодобряемое чтение. Отвергнуто — и зафиксировано здесь, потому что соблазн
"упростить" до этого вернётся.
Зеркалировать remote в локальный bare и синхронизировать. Оставило бы engine прежним, но создало бы второй источник истины репозитория с тихим расхождением, когда кто-то отправляет напрямую в provider. Отвергнуто.
Оставить только local и отложить. Это текущее состояние, и именно оно мешает FASE 13b.
Ссылки
- Находка N из achados-execucao-real.md, Фаза B backlog.
- ADR 0055 — область, которая делает решение 2 обязательным.
- RN-058 — чьи учётные данные тратит агент.
apps/engine/lib/engine/projects/project_repository.ex,apps/engine/lib/engine/actions/workspace.ex,apps/engine/lib/engine/actions/git_executor.ex.