# 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", )`. **Фильтр идентичен `merge_pr`/ORCH-073 FR-3** — авто-docs-PR (`base != main`) НЕ считается код-PR. 2. Иначе `POST …/pulls` (`head=branch`, `base=main`, заголовок/тело — авто) → `201` → `("created", )`. 3. Идемпотентность: если параллельно PR уже создан и Gitea вернёт ошибку «PR exists» — повторный `GET` подтверждает существующий PR и возвращает `("existed", …)`, **дубль не плодится** (AC-2). 4. Любая иная ошибка HTTP/parse/сети → `("failed", )`. **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: `. Сообщение 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».