9.3 KiB
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_finalizer → merge_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 — первая задача после серии ручных
восстановлений
main08.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 (бизнес-уровень)
- Root cause задокументирован (
06-adr/архитектором, ссылка из ADR на этот BRD). - После фикса задача с веткой без PR не зависает: конвейер создаёт PR идемпотентно и доводит до
done(при честном merge). - Защита ORCH-073 цела (регресс-тест на «код не в main» → HOLD).
- Логи различают created/existed/failed.
pytestзелёный; never-raise соблюдён.