feat(merge-verify): guarantee idempotent open code-PR before merge_pr (ORCH-082)

Close the missing invariant "by merge-verify time the branch has an open
code-PR". The pipeline created a PR only on the developer path with a fresh
worktree commit (launcher._ensure_pr), so a branch (e.g. after a manual main
restore) could reach the deploy->done merge-verify under-gate PR-less ->
merge_pr returned "no open PR" -> a FALSE HOLD (ORCH-074 incident).

- merge_gate.ensure_open_pr(repo, branch) -> (status, detail): idempotent
  leaf-actor (never-raise). GET open PRs filtered head==branch AND base==main
  (identical to merge_pr/ORCH-073 FR-3 — auto docs-PR is not a code-PR) ->
  existed; else POST -> created; 409/422 race -> re-GET -> existed (no dup);
  any other error -> failed.
- stage_engine._handle_merge_verify: врезка after validated_revision and
  BEFORE merge_pr. created|existed -> proceed; failed -> honest HOLD via new
  _hold_pr_create_failed (note "pr-create-failed-hold", text distinguishable
  from the not-merged HOLD; task stays on deploy, NO rollback).
- launcher._ensure_pr delegated to ensure_open_pr (single PR-creation path,
  shared head==branch & base==main filter); the developer-only trigger is
  unchanged.
- ORCH-073 protection untouched & authoritative: merge is confirmed ONLY by
  verify_merged_to_main (SHA-in-main) + check_main_regression. Real un-merged
  code still HOLDs.
- Kill-switch ORCH_MERGE_VERIFY_AUTOCREATE_PR_ENABLED (default true); scope =
  merge_verify_applies (self-hosting / merge_verify_repos); non-self -> no-op;
  false -> ORCH-074 behaviour 1:1. No DB migration; main never push/force-push.
- Append ORCH-082 marker to MAIN_REGRESSION_MARKERS (append-only convention).
- conftest defaults the autocreate flag OFF (mirrors merge_verify_enabled) so
  unrelated deploy->done tests stay 1:1 (no network).

Tests: tests/test_orch082_ensure_pr.py (TC-01..05),
tests/test_orch082_merge_verify_autocreate.py (TC-06..12). Docs: README
merge-verify block (ORCH-082), CHANGELOG, .env.example.

Refs: ORCH-082

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 00:48:43 +03:00
committed by stream
parent 74269b467c
commit 0ab6a33ef5
9 changed files with 557 additions and 29 deletions

View File

@@ -442,6 +442,22 @@ class Settings(BaseSettings):
# merge_verify_repos), so non-self repos are a no-op.
regression_guard_enabled: bool = True
# ORCH-082 (ADR-001 Р-5): guarantee an open code-PR BEFORE the deterministic
# merge_pr inside the merge-verify under-gate. The pipeline never guaranteed the
# branch had an open PR (head==branch, base==main) at merge time — PRs are created
# ONLY on the developer path with a fresh worktree commit (launcher._ensure_pr),
# so a branch (e.g. after a manual main restore / a bounce with no new commits)
# could reach merge-verify PR-less -> merge_pr returns "no open PR" -> a FALSE HOLD
# that ORCH-073 fail-closed correctly catches but should never have to. The
# idempotent leaf-actor merge_gate.ensure_open_pr creates/finds the code-PR ДО
# merge_pr; ORCH-073's SHA-in-main proof is untouched and stays authoritative.
# merge_verify_autocreate_pr_enabled -> kill-switch (env
# ORCH_MERGE_VERIFY_AUTOCREATE_PR_ENABLED). False -> exactly the pre-ORCH-082
# behaviour (no auto-create; "no open PR" -> HOLD as before). Reuses the
# merge_verify_applies scope (self-hosting / merge_verify_repos) — no separate
# *_repos, since auto-create is semantically inseparable from merge-verify.
merge_verify_autocreate_pr_enabled: bool = True
# Telegram notifications
telegram_bot_token: str = ""
telegram_chat_id: str = ""