# 02 — ТЗ: ORCH-082 (ORCH-81)

**Гарантированный идемпотентный код-PR перед merge-verify + наблюдаемость**

> Машина стадий, реестр `QG_CHECKS`, схема БД, exit-коды хука, контракты
> `check_deploy_status`/`_parse_deploy_status`, защита ORCH-073 (SHA-в-main) — **НЕ меняются**.
> Изменение — точечная врезка «ensure PR» в под-гейт merge-verify + новый идемпотентный
> PR-актор в `merge_gate` + структурное логирование.

---

## 1. Задействованные модули `src/`

| Модуль | Роль в задаче | Характер изменения |
|--------|---------------|--------------------|
| `src/merge_gate.py` | leaf-логика merge-актора (`merge_pr`, `verify_merged_to_main`, `pr_already_merged`) | **+ новый идемпотентный актор** `ensure_open_pr(repo, branch) -> (status, detail)` (never-raise). |
| `src/stage_engine.py` | под-гейт `_handle_merge_verify` на ребре `deploy → done` | **врезка:** вызвать `ensure_open_pr` ПЕРЕД `merge_pr`; на `failed` → честный HOLD+alert; логировать исход. |
| `src/agents/launcher.py` | `_ensure_pr` (текущий единственный создатель PR) | **усилить наблюдаемость** (различать created/existed/failed) — опционально переиспользовать новый актор `merge_gate.ensure_open_pr`, чтобы создание PR было единым кодом. Поведение «создавать только у developer» НЕ ужесточать без необходимости. |
| `src/config.py` | флаги | **+ kill-switch** `merge_verify_autocreate_pr_enabled` (дефолт `True`), область — та же `merge_verify_applies` (self-hosting / `merge_verify_repos`). |
| `docs/architecture/README.md`, `CHANGELOG.md` | golden source | обновить (раздел ORCH-071/073 merge-verify — дописать про авто-создание PR). |

> Точная сигнатура `ensure_open_pr`, имя/дефолт kill-switch и место врезки — за архитектором
> (ADR). Ниже — функциональные требования к поведению, не финальный дизайн.

## 2. Функциональные требования

### FR-1 — Идемпотентный PR-актор `merge_gate.ensure_open_pr(repo, branch)`
Возвращает структурированный исход (например `("existed"|"created"|"failed", detail)`):
1. `GET …/pulls?state=open` → если есть PR с **`head.ref==branch` И `base.ref=="main"`** →
   `("existed", <number>)`. **Фильтр идентичен `merge_pr`/ORCH-073 FR-3** — авто-docs-PR
   (`base != main`) НЕ считается код-PR.
2. Иначе `POST …/pulls` (`head=branch`, `base=main`, заголовок/тело — авто) → `201` →
   `("created", <number>)`.
3. Идемпотентность: если параллельно PR уже создан и Gitea вернёт ошибку «PR exists» —
   повторный `GET` подтверждает существующий PR и возвращает `("existed", …)`, **дубль не
   плодится** (AC-2).
4. Любая иная ошибка HTTP/parse/сети → `("failed", <reason>)`. **Never-raise.**

### FR-2 — Врезка в `_handle_merge_verify` (ребро `deploy → done`)
Внутри существующего `_handle_merge_verify`, ПОСЛЕ `merge_verify_applies(repo)`-гейта и
резолва `validated_revision`, но **ПЕРЕД** `merge_pr`:
- если `merge_verify_autocreate_pr_enabled` → вызвать `ensure_open_pr(repo, branch)`;
- `status == "created"|"existed"` → продолжить штатно к `merge_pr` → `verify_merged_to_main`;
- `status == "failed"` → **честный HOLD + alert** (как сегодняшний not-merged путь:
  `note_not_merged_alert` + `set_issue_blocked` + Plane-коммент + Telegram; задача остаётся на
  `deploy`, НЕ `done`, БЕЗ отката на development) с сообщением, отражающим «PR создать не
  удалось» (а не «PR не влит»).
- kill-switch off → текущее поведение 1:1 (никакого создания PR).

### FR-3 — Защита ORCH-073 цела (регресс-инвариант)
Создание PR **не подменяет** проверку слияния. После `ensure_open_pr` + `merge_pr` верификация
остаётся **только** `verify_merged_to_main` (SHA-в-main, ORCH-073 FR-1) + регресс-гард
(`check_main_regression`). Если код реально не оказался в `main` — HOLD сохраняется. Создание PR
лишь устраняет **ложный** HOLD «no open PR», который конвейер обязан был предотвратить.

### FR-4 — Наблюдаемость (G3)
В лог писать однозначный исход на каждом из мест работы с PR:
- `merge-verify ensure_open_pr -> created PR #N` /
- `… -> existed PR #N` /
- `… -> failed: <reason>`.
Сообщение HOLD при `failed` обязано отличаться текстом от HOLD «not merged» (оператор должен
видеть, что причина — невозможность создать PR, а не невозможность слить уже созданный).
Желательно — пометка исхода в `14-deploy-log.md` (best-effort, frontmatter `deploy_status:`
нетронут).

### FR-5 — Идемпотентность повторного прохода
Повторный заход в merge-verify (reaper / reconciler / повторный approve) при уже существующем
PR → `ensure_open_pr` возвращает `("existed", …)`, `merge_pr` → `already-merged`/штатно — **без
дублей PR и без побочных эффектов** (INV-5/AC-9 ORCH-073 сохранены).

## 3. Изменения API (HTTP / внутренние)
- **Внешний HTTP API сервиса — без изменений** (новых endpoint нет).
- **Исходящие вызовы Gitea:** новый `POST /api/v1/repos/{owner}/{repo}/pulls` из контекста
  merge-verify (тот же вызов, что уже делает `_ensure_pr`); чтение — существующий
  `GET …/pulls?state=open`.
- **Внутренний контракт `merge_gate`:** новая публичная функция `ensure_open_pr` (leaf,
  never-raise), вызывается из `stage_engine._handle_merge_verify` (и опционально из
  `launcher._ensure_pr`).

## 4. Изменения схемы БД
**Нет.** Состояние идемпотентности выводится из самого Gitea (наличие открытого PR), миграции
не требуются. (Согласуется с restart-safe-моделью merge-verify.)

## 5. Требования к новым QG checks
**Новых зарегистрированных QG-checks нет.** Это под-гейт-врезка в `advance_stage`
(`_handle_merge_verify`), как и сам ORCH-071 merge-verify — не отдельный `QG_CHECKS`-элемент.
Реестр `QG_CHECKS` не трогается.

## 6. Конфигурация / kill-switch
- `merge_verify_autocreate_pr_enabled: bool = True` (env `ORCH_MERGE_VERIFY_AUTOCREATE_PR_ENABLED`).
  `False` → ровно прежнее поведение (нет авто-создания PR; «no open PR» → HOLD как раньше).
- Область действия — `merge_gate.merge_verify_applies(repo)`: реально только для self-hosting /
  `merge_verify_repos`; прочие репо — no-op.

## 7. Артефакты pipeline (создать/обновить)
- `docs/work-items/ORCH-082/06-adr/ADR-001-*.md` — архитектор (root cause G1 + дизайн ensure-PR).
- `12-review.md`, `13-test-report.md`, `14/15/16-*` — последующие стадии.
- Обновить `docs/architecture/README.md` (блок ORCH-071/073) и `CHANGELOG.md` — в ТОМ ЖЕ PR
  (правило агентов №2/№6).

## 8. Инварианты (не нарушать)
- `STAGE_TRANSITIONS`, `QG_CHECKS`, схема БД, `check_deploy_status`/`_parse_deploy_status`,
  exit-коды хука, terminal-sync, merge-gate (ORCH-043), image-freshness (ORCH-058) — **без
  изменений**.
- Контракт **never-raise** на всём пути merge-verify (INV-1 ORCH-073).
- Слияние только через PR (`POST /pulls/{index}/merge`); `main` никогда не push/force-push.
- Защита ORCH-073 (SHA-в-main + регресс-гард) приоритетна: при конфликте «создать PR» проигрывает
  «не дать ложно-зелёный done».
