# ADR-0016: ensure_open_pr — гарантированный код-PR перед merge-verify (ORCH-082)

## Статус
Accepted — амендмент к [adr-0013](adr-0013-merge-verify-gate.md) и
[adr-0014](adr-0014-merge-verify-sha-source-of-truth.md). Детально:
`docs/work-items/ORCH-082/06-adr/ADR-001-ensure-open-pr-before-merge-verify.md`.

## Контекст
Merge-verify (ORCH-071/073) — под-гейт ребра `deploy → done`: детерминированно мержит код-PR в
`main` (`merge_pr`) и подтверждает merge **только** по «SHA-в-main» (`verify_merged_to_main`,
ORCH-073). На деплое ORCH-074 (08.06) `merge_pr` вернул `("False", "no open PR")`: у ветки **не
было** открытого PR с `head==branch` И `base=="main"`. Защита ORCH-073 верно удержала задачу
(HOLD, не ложный `done`), но это лечило **следствие**.

Первопричина (код-аудит): PR создаётся в конвейере **единственной** функцией
`launcher._ensure_pr`, вызываемой **только** на developer-пути и **только** при свежем
worktree-коммите. Любой сценарий без свежего developer-коммита (бойнс без правок, повторный
прогон, **ручное восстановление ветки/`main`** — случай ORCH-074) оставляет ветку без код-PR.
Инвариант «к merge-verify у ветки есть открытый код-PR» в конвейере **отсутствовал** → блокер
автономного деплоя (ORCH-54).

## Решение
Аддитивно обеспечить инвариант **внутри того же под-гейта**, ПЕРЕД `merge_pr`, не трогая машину
стадий:

1. **Новый leaf-актор `merge_gate.ensure_open_pr(repo, branch) -> (status, detail)`** (never-raise):
   `GET …/pulls?state=open` с фильтром **`head.ref==branch` И `base.ref=="main"`** (идентичен
   `merge_pr`/ORCH-073 FR-3 — авто-docs-PR не считается код-PR) → `("existed", N)`; иначе
   `POST …/pulls` → `("created", N)`; гонка «PR exists» → повторный GET → `existed` (без дублей);
   любая ошибка → `("failed", reason)`.
2. **Врезка в `_handle_merge_verify`** ПОСЛЕ резолва `validated_revision` и ПЕРЕД `merge_pr`:
   `created|existed` → штатно к `merge_pr`; `failed` → честный HOLD+alert через новый helper
   `_hold_pr_create_failed` (текст «PR создать не удалось» — отличим от not-merged HOLD), задача
   остаётся на `deploy`, БЕЗ отката на development.
3. **Kill-switch `merge_verify_autocreate_pr_enabled`** (дефолт `True`); область —
   `merge_verify_applies` (self-hosting / `merge_verify_repos`). `False` → поведение ORCH-074 1:1.
4. **`launcher._ensure_pr`** рекомендуется делегировать в `ensure_open_pr` (единый код создания
   PR), сохранив прежний триггер «только developer-путь».

## Последствия
- **Защита ORCH-073 неприкосновенна и приоритетна:** подтверждение merge остаётся ТОЛЬКО
  `verify_merged_to_main` (SHA-в-main) + `check_main_regression`. Создание PR устраняет лишь
  **ложный** HOLD «no open PR», но не маскирует реально невлитый код (тот → HOLD как прежде).
- **Без миграций:** идемпотентность выводится из Gitea (наличие открытого PR), схема БД не меняется
  — restart-safe; повторный заход (reaper/reconciler/re-approve) → `existed`, дублей нет.
- **Инварианты целы:** `STAGE_TRANSITIONS`, `QG_CHECKS`, схема БД, `check_deploy_status`,
  exit-коды хука, merge-gate (ORCH-043), image-freshness (ORCH-058) — без изменений; `main` не
  push/force-push; never-raise на всём пути.
- **Наблюдаемость:** один однозначный исход в логах на проход — created / existed / failed; HOLD по
  failed текстуально отличим от HOLD not-merged.
- **Минус:** код-PR может создаваться после прохождения гейтов — безопасно, т.к. гейты валидируют
  код ветки, а merge-verify идёт ПОСЛЕ всех гейтов; PR — лишь механизм слияния, ревью не обходится.
