Files

9.3 KiB
Raw Permalink Blame History

01 — BRD: ORCH-082 (ORCH-81)

Конвейер не создаёт PR для ветки → деплой стопорится на merge-verify (HOLD)

  • Work Item: ORCH-082 (Plane-заголовок «ORCH-81»)
  • Repo: orchestrator (self-hosting)
  • Тип: Багфикс / надёжность конвейера
  • Приоритет: HIGH — блокирует автономный деплой
  • Зона: создание PR (reviewer/developer/deployer пути), src/merge_gate.py, src/stage_engine.py (_handle_merge_verify), src/agents/launcher.py (_ensure_pr)

1. Контекст и проблема

При деплое ORCH-074 (08.06, статус «Confirm Deploy») детерминированный finalizer (run_deploy_finalizer → под-гейт _handle_merge_verify) вызвал merge_gate.merge_pr(repo, branch) и получил ok=False («no open PR»): в Gitea для ветки feature/ORCH-074-… не существовало открытого PR с head.ref==branch и base.ref=="main".

Защита ORCH-073 (fail-closed по «SHA-в-main») отработала корректно: задача удержана на стадии deploy (НЕ done), Plane → Blocked, Telegram-alert, ложно-зелёного done не произошло. Это правильное поведение для случая «merge реально невозможен».

Дефект не в защите, а в инварианте до неё: автономный конвейер не гарантировал, что к моменту merge у ветки существует открытый PR. PR на сегодня создаётся ровно в одном месте — launcher._ensure_pr, вызываемом только на пути agent == "developer" и только когда в этом конкретном run был непустой git-diff, успешный commit и успешный push (см. root-cause ниже). Любой сценарий, где developer-run не произвёл свежий коммит, оставляет ветку без PR, и задача неминуемо застревает на merge-verify.

Workaround, применённый вручную (НЕ фикс)

PR #79 создан вручную через Gitea API (mergeable=True) → штатно перезапущен run_deploy_finalizermerge_pr честно влил код в main → задача done. Это разовое ручное вмешательство, не устранение причины.

Почему это системный пробел, а не разовый сбой

Так как создание PR не гарантировано конвейером, любая следующая задача с тем же стечением обстоятельств (developer-run без нового коммита; тихо упавший вызов создания PR; ветка восстановлена/пересоздана вручную) застрянет на merge-verify тем же образом. Автономность деплоя (цель ORCH-54) этим заблокирована.


2. Root cause (предварительный аудит кода — подтвердить логами G1)

PR создаётся исключительно функцией AgentLauncher._ensure_pr (src/agents/launcher.py), которая вызывается из _monitor_agent по цепочке условий:

exit_code == 0
  → есть worktree-изменения (git status --porcelain непусто)
    → git commit succeeded
      → git push succeeded
        → agent == "developer"   ←── ТОЛЬКО здесь вызывается self._ensure_pr(...)

Отсюда минимум три структурных способа остаться без PR:

  • R-A (условное создание). Если developer-run завершился без изменений (git status пустой) — ветка уже была закоммичена/запушена в прошлый run, бойнс REQUEST_CHANGES без новых правок, повторный прогон, или ручное восстановление ветки — _ensure_pr не вызывается вовсе. PR не появится никогда. (Соответствует гипотезе ТЗ №2.)
  • R-B (тихий сбой создания). _ensure_pr ловит любое исключение (except Exception → logger.error → return None): транзиентная ошибка Gitea на шаге POST …/pulls теряется без ретрая и без эскалации. Конвейер «думает», что developer отработал, и едет дальше. (Гипотеза ТЗ №1 — silent fail.)
  • R-C (разъехавшееся состояние ветки/PR). ORCH-074 — первая задача после серии ручных восстановлений main 08.06. PR мог быть закрыт/пересоздан, либо у ветки остался только авто-docs-PR (base != main), который merge_pr/pr_already_merged корректно НЕ считают кодовым PR. (Гипотеза ТЗ №4.)

Идемпотентность (гипотеза №3): сам _ensure_pr идемпотентен на чтении (сначала GET …open&head, создаёт только если пусто), но он не запускается вне «свежий developer-коммит», поэтому идемпотентность не достигает merge-стадии — никакой флаг «PR создан» в БД не хранится.

Вывод: гарантия «к моменту merge у ветки есть открытый код-PR» в конвейере отсутствует.


3. Бизнес-цели

ID Цель
G1 Установить и задокументировать точную причину отсутствия PR на ORCH-074 (код-аудит + логи run_id 396/398).
G2 Гарантировать инвариант: к моменту merge-verify у ветки есть открытый код-PR; если его нет — finalizer/deployer создаёт его сам, идемпотентно, ПЕРЕД merge_pr, вместо HOLD на ручное вмешательство.
G3 Явно логировать факт PR: PR-created / PR-existed / PR-create-failed (наблюдаемость).

4. Не-цели (явные границы)

  • НЕ ослаблять защиту ORCH-073: fail-closed по «SHA-в-main» остаётся. Реальная невозможность merge → по-прежнему HOLD + alert.
  • НЕ авто-мержить без PR (PR — обязательный артефакт ревью/слияния).
  • НЕ создавать PR в неподходящий момент — только на ребре deploy → done, ПОСЛЕ прохождения всех гейтов (security/merge-gate/staging/image-freshness уже пройдены).
  • НЕ менять STAGE_TRANSITIONS, реестр QG_CHECKS, схему БД, контракты check_deploy_status, exit-коды хука.

5. Заинтересованные стороны

  • Owner (homenet542) — автономность деплоя орка.
  • Все проекты на инстансе (enduro-trails) — общий прод/очередь: ложный HOLD self-задачи не должен требовать ручного вмешательства, а реальный дефект merge — обязан удерживаться.

6. Бизнес-риски и допущения

  • Грабли (из ORCH-073): у ветки может быть несколько PR (код-PR + авто docs-PR). Создание/ выбор PR обязан фильтровать head.ref==branch И base.ref=="main", иначе слияние/верификация схватят не тот PR.
  • Допущение: merge-verify исполняется ПОСЛЕ всех гейтов, поэтому создание PR именно здесь не обходит ревью и безопасно по времени.
  • Контракт надёжности: весь новый путь — never-raise; ошибка создания PR (Gitea недоступна) → честный HOLD + alert, а не исключение в advance_stage.

7. Definition of Done (бизнес-уровень)

  1. Root cause задокументирован (06-adr/ архитектором, ссылка из ADR на этот BRD).
  2. После фикса задача с веткой без PR не зависает: конвейер создаёт PR идемпотентно и доводит до done (при честном merge).
  3. Защита ORCH-073 цела (регресс-тест на «код не в main» → HOLD).
  4. Логи различают created/existed/failed.
  5. pytest зелёный; never-raise соблюдён.