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