Files
orchestrator/docs/architecture/adr/adr-0014-merge-verify-sha-source-of-truth.md

6.9 KiB
Raw Permalink Blame History

adr-0014: SHA-в-main — единственный критерий merge-verify + регресс-гард целостности main

  • Статус: accepted
  • Дата: 2026-06-08
  • Задача: ORCH-073 (BUG CRITICAL — эрозия main)
  • Amends: adr-0013 (ORCH-071) — меняет КРИТЕРИЙ подтверждения merge.
  • Детальный ADR: docs/work-items/ORCH-073/06-adr/ADR-001-merge-verify-sha-truth-and-regression-guard.md
  • Постмортем: docs/history/LESSONS_2026-06-08_phantom-merge.md

Контекст

adr-0013 (ORCH-071) ввёл под-гейт merge-verify на ребре deploy → done, но допускал подтверждение merge по ИЛИ-критерию: verify_merged_to_main возвращал True, если pr_already_merged(repo, branch) ЛИБО SHA — предок origin/main. pr_already_merged засчитывал любой merged PR ветки, включая авто docs-PR (staging/deploy-логи). У одной feature-ветки в main сливались только docs-PR, а code-PR — нет → pr_already_merged=True → verify CONFIRMEDdone, хотя кода в main не было. Накопительно потеряны ORCH-067 (ссылки plane_issue_link) и ORCH-069 (qg0_title_max). Вторичный усилитель — CHANGELOG-ребейзы, откатывающие ветку и тащащие устаревший код-сосед. Восстановление кода (G1) выполнено вручную restore-PR #76; этот ADR устраняет корень навсегда.

Решение

  1. SHA-в-main — единственный критерий (FR-1). verify_merged_to_main(repo, branch, sha) подтверждает merge ТОЛЬКО прямым фактом git merge-base --is-ancestor <sha> origin/main (после git fetch origin main). OR-ветка pr_already_merged удалена из верификатора. Пустой sha / любая git-ошибка → False (fail-closed: alert + HOLD). never-raise (INV-1).
  2. pr_already_merged → idempotency-guard, различающий code-PR/docs-PR (FR-2). Засчитывает merged PR только при head.ref==<feature-branch> И base.ref=="main" (явный фильтр в цикле, не ненадёжный query-параметр head). Используется лишь как защита merge_pr от второго merge, НЕ как подтверждение done.
  3. merge_pr сливает именно code-ветку (FR-3). Выбор открытого PR по head.ref==branch И base.ref=="main"; merge только Gitea POST /pulls/{index}/merge, никогда push/force-push в main. Источник истины «слилось» — FR-1.
  4. Регресс-гард целостности main (FR-5). Новая merge_gate.check_main_regression, вызываемая в _handle_merge_verify ПОСЛЕ подтверждённого SHA-в-main и ДО done: проверяет, что origin/main содержит декларативный набор маркеров ключевых функций ранее-merged задач (git grep -c <marker> origin/main -- <path> > 0). Маркер отсутствует → alert «main regressed» + HOLD (НЕ done, БЕЗ авто-отката на development — инфра-дефект, ALERT-only как ORCH-021/071). Набор — append-only константа MAIN_REGRESSION_MARKERS в merge_gate.py (расширяется каждой значимой задачей). Fail-open на git-ошибке самого грепа (регресс утверждается только при детерминированном count==0); первичный фейл-клозед — SHA-в-main. Kill-switch regression_guard_enabled (дефолт true); non-self → no-op.
  5. .gitattributes CHANGELOG.md merge=union (FR-4). В корне репо; авто-слияние правок ## [Unreleased] без конфликта → auto_rebase_onto_main не откатывает ветку и не тащит устаревший код-сосед. docs/**/*.md под union НЕ ставится (union только для append-only; доки переписываются построчно).

Инварианты

never-raise на verify/merge/регресс-гарде (ошибка → alert/HOLD, не падение); прод 8500 не рестартится/не падает в рамках merge; merge только Gitea PR-API без force-push в main; ручной Confirm Deploy (ORCH-059) сохранён; идемпотентность по «SHA-в-main», а не по «любому merged PR»; non-self репо (enduro) — merge/verify/регресс-гард без изменений. STAGE_TRANSITIONS, реестр QG_CHECKS, check_deploy_status, схема БД, внешние HTTP-эндпоинты — без изменений.

Альтернативы

  • Сохранить PR-флаг как со-критерий verify (с фильтром head/base) — отклонено: PR можно слить и тут же откатить ребейзом-соседом; надёжен только факт «SHA в main».
  • docs/**/*.md merge=union — отклонено: тихая дубликация строк в переписываемых доках.
  • Регресс-гард с авто-откатом / хранением маркеров в БД/Plane — отклонено (Не-цель «не менять схему БД/Plane»; реакция ALERT-only).
  • Fail-closed на marker-grep — отклонено: ложный HOLD при git-сбое; marker-grep вторичен.

Последствия

Невозможно «done + прод задеплоен, а code-PR не в main». Ложно-зелёный по docs-PR устранён в корне. CHANGELOG-конфликты больше не откатывают ветку. Регресс соседнего кода ловится отдельным гардом. Минус: при недоступной Gitea/git verify консервативно False → возможен ложный HOLD+alert (снимается повтором; fail-closed для done приоритетен). Набор маркеров требует дисциплины — значимая задача дописывает свой маркер.

Связи

  • Amends adr-0013 (ORCH-071), наследует adr-0006 (merge-gate), adr-0011 (job-reaper/lease).
  • Детально: docs/work-items/ORCH-073/06-adr/ADR-001-merge-verify-sha-truth-and-regression-guard.md.