work_item: ORCH-082
title: "Гарантированный идемпотентный код-PR перед merge-verify (фикс ложного HOLD)"
strategy: >
  Юнит-тесты на новый идемпотентный актор merge_gate.ensure_open_pr (мок Gitea HTTP)
  и интеграционные тесты на врезку в stage_engine._handle_merge_verify (мок merge_gate
  + verify), включая регресс ORCH-073. Все пути — never-raise. Gitea и git мокаются,
  сеть не дёргается.

tests:
  # ---- ensure_open_pr: идемпотентный PR-актор (FR-1) ----
  - id: TC-01
    type: unit
    description: "ensure_open_pr: открытого код-PR нет -> POST создаёт PR -> ('created', N); фильтр base==main применён"
    module: tests/test_orch082_ensure_pr.py
    expected: PASS

  - id: TC-02
    type: unit
    description: "ensure_open_pr: открытый PR head==branch И base==main уже есть -> ('existed', N), POST не вызывается (нет дубля)"
    module: tests/test_orch082_ensure_pr.py
    expected: PASS

  - id: TC-03
    type: unit
    description: "Грабли мультиPR: у ветки только docs-PR (base!=main) -> он НЕ считается код-PR -> создаётся PR на main (AC-6)"
    module: tests/test_orch082_ensure_pr.py
    expected: PASS

  - id: TC-04
    type: unit
    description: "ensure_open_pr never-raise: Gitea POST/GET кидает HTTP/timeout -> ('failed', reason), исключение не всплывает (AC-7)"
    module: tests/test_orch082_ensure_pr.py
    expected: PASS

  - id: TC-05
    type: unit
    description: "Идемпотентность гонки: POST вернул 'PR exists' -> повторный GET подтверждает существующий -> ('existed', N), дубль не создан"
    module: tests/test_orch082_ensure_pr.py
    expected: PASS

  # ---- _handle_merge_verify: врезка ensure-PR (FR-2/FR-3) ----
  - id: TC-06
    type: integration
    description: "merge-verify: PR отсутствовал -> ensure_open_pr создаёт -> merge_pr -> verify True -> deploy->done БЕЗ ложного HOLD (AC-3)"
    module: tests/test_orch082_merge_verify_autocreate.py
    expected: PASS

  - id: TC-07
    type: integration
    description: "Регресс ORCH-073: PR создан/влит, но verify_merged_to_main=False (код не в main) -> HOLD + set_issue_blocked, НЕ done, без отката (AC-4)"
    module: tests/test_orch082_merge_verify_autocreate.py
    expected: PASS

  - id: TC-08
    type: integration
    description: "ensure_open_pr -> 'failed' (Gitea down) -> честный HOLD+alert, текст отличается от 'not merged', advance_stage не падает (AC-7)"
    module: tests/test_orch082_merge_verify_autocreate.py
    expected: PASS

  - id: TC-09
    type: integration
    description: "Kill-switch merge_verify_autocreate_pr_enabled=False -> ensure_open_pr не вызывается, 'no open PR' -> прежний HOLD 1:1 (AC-8)"
    module: tests/test_orch082_merge_verify_autocreate.py
    expected: PASS

  - id: TC-10
    type: integration
    description: "Условность: non-self репо (merge_verify_applies=False) -> врезка no-op, авто-создание не выполняется (AC-9)"
    module: tests/test_orch082_merge_verify_autocreate.py
    expected: PASS

  - id: TC-11
    type: integration
    description: "Идемпотентный повторный проход (reaper/reconciler): PR уже existed, merge_pr=already-merged -> verify True -> done, без дублей PR (AC-2/FR-5)"
    module: tests/test_orch082_merge_verify_autocreate.py
    expected: PASS

  # ---- Наблюдаемость (G3 / AC-5) ----
  - id: TC-12
    type: unit
    description: "Логи различают created/existed/failed; HOLD-сообщение create-failed != HOLD-сообщение not-merged (caplog, AC-5)"
    module: tests/test_orch082_merge_verify_autocreate.py
    expected: PASS

  # ---- Регресс существующего merge-verify контракта ----
  - id: TC-13
    type: integration
    description: "Happy-path ORCH-071/073 не изменён: merge_pr ok + verify True + регресс-гард ok -> done, merged_to_main: true во frontmatter"
    module: tests/test_merge_verify.py
    expected: PASS
