# 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 — первая задача после серии ручных восстановлений `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 соблюдён.