Files
orchestrator/docs/architecture/adr/adr-0013-merge-verify-gate.md

5.2 KiB
Raw Permalink Blame History

adr-0013: Merge-в-main + пост-деплой верификация как условие done (фикс фантомного merge)

  • Статус: accepted
  • Дата: 2026-06-08
  • Задача: ORCH-071 (CRITICAL bug)
  • Детальный ADR: docs/work-items/ORCH-071/06-adr/ADR-001-merge-verify-gate.md
  • Постмортем: docs/history/LESSONS_2026-06-08_phantom-merge.md

Контекст

Для self-hosting репо orchestrator стадия deploy идёт детерминированным путём (_handle_self_deploy_phase_b → initiate_deploy → run_deploy_finalizer), а LLM-агент deployer НЕ запускается. Фактический merge PR в main исторически делал только агент deployer → на self-hosting пути нет шага merge-в-main вообще. Detached host-деплой лишь retag'ает образ + рестартит 8500; done достигается по deploy_status: SUCCESS без верификации main. «Зелёный» деплой (образ из рабочей ветки) маскирует отсутствие merge → следующая задача срезает ветку от устаревшего main и теряет код предшественника. Накопительно потеряны ORCH-022/059/066/068. Вторичный фактор: Phase B рестартит прод → merge внутри живого процесса гонялся бы с рестартом (урок №3).

Решение

Детерминированный merge-актор + пост-merge верификация как под-гейт ребра deploy → done, врезанный в единственную функцию перехода advance_stage (симметрично edge-под-гейтам security/merge-gate/image-freshness). STAGE_TRANSITIONS, check_deploy_status/_parse_deploy_status, реестр QG_CHECKS, схема БД — не меняются.

  • Врезка _handle_merge_verify в advance_stage (current_stage=="deploy" и next_stage=="done", ПОСЛЕ зелёного check_deploy_status, ДО update_task_stage). Гейтит ВСЕ пути к done единообразно: run_deploy_finalizer (Phase C), reconciler F-1, job-reaper — все идут через advance_stage. Закрывает дыру: reconciler F-1 иначе протолкнул бы done в обход merge.
  • Merge в Phase C (после рестарта), НЕ в Phase B. Phase C finalizer — restart-surviving (reserved-job deploy-finalizer, claim воркером нового контейнера, re-drive reaper'ом). Merge физически строго ПОСЛЕ рестарта → рестарт его не убивает (G3 вторым вариантом — «шаг, переживающий рестарт»).
  • Merge-актор merge_gate.merge_prpr_already_merged (no-op повтор, ORCH-065) → иначе Gitea POST /repos/{owner}/{repo}/pulls/{index}/merge. Никогда push/force-push в main. never-raise.
  • Верификатор merge_gate.verify_merged_to_mainPR.merged==true ИЛИ git merge-base --is-ancestor <validated_sha> origin/main. never-raise → False («не подтверждено»).
  • Не подтверждено → alert «deploy succeeded but not merged» (Telegram+Plane) + HOLD (set_issue_blocked, задача НЕ done, БЕЗ авто-отката на development — not-merged есть инфра-дефект, реакция ALERT-only как ORCH-021 self-hosting). Подтверждено → штатный deploy → done (терминал-sync / post-deploy monitor как сегодня) + merged_to_main: true во frontmatter 14-deploy-log.md (наблюдаемость, deploy_status: нетронут).
  • Идемпотентность (INV-5): pr_already_merged перед merge; verify зелёный для уже-слитого PR; повтор без дубль-merge/ложного отката.
  • Условность (как ORCH-35/43/58): merge_verify_enabled (kill-switch, дефолт true) + merge_verify_repos (пусто → только self-hosting). Non-self репо — no-op, merge остаётся за агентом deployer.

Инварианты

never-raise на verify/merge (ошибка → alert, не падение конвейера); не рестартить/не ронять прод 8500; ручной approve прод-деплоя сохранён (Confirm Deploy, ORCH-059); только PR-merge API Gitea; restart-safe (sentinel + jobs, без миграции БД).

Последствия

Невозможно «done + прод задеплоен, а PR open». Минусы: при недоступной Gitea verify консервативно False → возможен ложный HOLD+alert (снимается повтором; fail-closed для done приоритетен); HOLD требует ручного вмешательства. Диагностика фантома — runbook docs/operations/PHANTOM_MERGE_RUNBOOK.md (G4).