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

ADR 0186 — Handoff от PO к Arquiteto принимается без клика, когда backlog покрыт и репозиторий локальный

Статус​

Accepted. 2026-09-30 (AT-314, история HS-079, раунд 32; решение владельца от 30.09: "Handoff oferecido pelo PO (LLM) pode provisionar o repositório local sem clique?" — да). Поверх ADR 0165, который поместил рождение репозитория в принятие handoff к Arquiteto и имел неявной предпосылкой, что это принятие — всегда клик человека. Не редактирует его: добавляет второго автора того же принятия, с закрытыми условиями, и оставляет место подготовки ровно там, куда его поставил 0165.

Контекст​

Измерено в коде dev 2026-09-30 (исследование AT-309):

  • PO завершает backlog и вызывает offer_handoff(to_agent: "arquiteto") (po_server.ex, kickoff: "Cubra TODAS as regras com ao menos uma história. Quando o backlog estiver pronto, ofereça um handoff ao arquiteto"). Предложение рождается offered и ждёт клика "Aceitar handoff e iniciar Arquiteto".
  • Этот клик подготавливает репозиторий (AcceptHandoffUseCase, RN-582). Для репозитория local — единственного, который создаёт принятие, потому что это единственный provider без учётных данных, — клик ничего не выбирает: нет хостинга для решения и нет секрета для разрешения. Это шаг подтверждения на пути без альтернативы.
  • Покрытие backlog уже имеет ОДИН подсчёт в продукте, computeCoverage (domain/backlog/coverage.ts), используемый вкладкой Backlog (GetCoverageUseCase) и инструментом чтения PO (ListBusinessRulesUseCase, RN-164): правило покрыто, когда хотя бы одна история ссылается на него в businessRuleIds.
  • Таблица исследования насчитала ~11 кликов-решений (+N продвижений) от brief до выполнения; этот — один из тех, что защищают лишь сами себя.

Решение​

  1. Когда. Предложение po → arquiteto принимается системой в ТОМ ЖЕ запросе, в котором engine его создаёт (POST /internal/sessions/:id/handoffs), после транзакции предложения и никогда не внутри неё, тогда и только тогда, когда:
    • предложение в состоянии offered;
    • backlog покрыт — объективное определение, измеряемое по тому, что записывает PO: у проекта есть хотя бы одно бизнес-правило (artifact.business_rule) и ни одного правила без истории, ссылающейся на него в businessRuleIds, по ТОМУ ЖЕ computeCoverage, что во вкладке Backlog. Ноль правил — не покрыто: "нечего покрывать" не доказывает, что PO работал;
    • репозиторий, которого касается принятие, локальный и без учётных данных: у проекта нет репозитория (принятие подготовит local) или имеющийся — local, и у проекта нет зарегистрированного git-подключения (OAuth) в project_git_connections;
    • тот, кто открыл сессию, всё ещё имеет в проекте роль, которую требует человеческий маршрут принятия (developer, действующая роль RN-471). Решение — чистый предикат decidirAceiteAutomatico (domain/sessions/aceite-automatico-do-handoff.ts); у каждого "нет" есть названная причина, и именно её возвращает ответ engine.
  2. Принимает по-прежнему AcceptHandoffUseCase. Новый use case (AceitarHandoffAutomaticamenteUseCase) лишь ЧИТАЕТ и РЕШАЕТ; при "да" вызывает обычное принятие, которое подготавливает репозиторий ДО activateAgent (RN-582, ADR 0165). Второго пути подготовки нет.
  3. Проверяемо. handoff.accepted автоматического принятия записывается с СИСТЕМНЫМ актором {kind: 'system', id: 'handoff-auto-accept'} и payload automatico: true, emNomeDe (тот, кто открыл сессию, — от её имени подготавливается репозиторий, provisionedBy) и criterio (regras, cobertas, repositorio). Следующий agent.activated несёт ТОГО ЖЕ актора. Человеческое принятие не меняется: актор user, без этих полей.
  4. Сбой не поднимается наверх. Предложение уже зафиксировано, когда выполняется автоматическое принятие, а engine ждёт его ответа; исключение там превратилось бы в "сбой предложения handoff" по поводу существующего предложения. Поэтому сбой становится НОВЫМ событием handoff.auto_accept_failed (системный актор, origem: 'infra', error), а ответ говорит falhou. Карточка предложения остаётся на месте, когда принятие не успело отметить accepted.
  5. Модель предупреждается. Ответ внутреннего маршрута получает aceiteAutomatico: {aceito, criterio?, motivo?} и, при aceito: true, status: 'accepted'; offer_handoff engine говорит PO, что Arquiteto уже активирован и что не следует предлагать снова (повторное предложение дало бы 409 agente_ja_ativo, ADR 0182).

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

  • Обычный путь (новый проект, локальный репозиторий, PO, покрывающий правила) теряет один клик; лента показывает принятие с критерием ("o backlog cobre as N regras de negócio e o repositório é local").
  • Удалённый репозиторий или репозиторий с учётными данными по-прежнему требует клика, как просил владелец. Ручной handoff (ADR 0109) тоже: он не проходит через внутренний маршрут.
  • Handoff, предложенный LLM, начинает производить эффект git ( git init --bare provider local и bootstrap Gitflow) без человеческого клика. Это объявленная цена, и именно её одобрил владелец; абсолютные пределы (push, PR, deploy, merge в защищённую ветку — RN-418) не сдвигаются, потому что ничего из этого не происходит при локальной подготовке.
  • Предложение другим адресатам и предложение Arquiteto другим агентом НЕ получают автоматического принятия. Расширение на другую пару — новый ADR.
  • Ответ engine задерживается на столько, сколько занимают локальная подготовка и startAgent, в пределах стандартного receive_timeout Req. Не измерено в реальной установке в этой сессии.
  • Не измерено: engine не компилировался и не тестировался в этой сессии (без Elixir в окружении); новая клаузула offer_handoff и её тест ExUnit были написаны и оставлены для CI.