5.2 KiB
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_pr—pr_already_merged(no-op повтор, ORCH-065) → иначе GiteaPOST /repos/{owner}/{repo}/pulls/{index}/merge. Никогда push/force-push вmain. never-raise. - Верификатор
merge_gate.verify_merged_to_main—PR.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во frontmatter14-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).