Files
orchestrator/docs/operations/PHANTOM_MERGE_RUNBOOK.md

6.7 KiB
Raw Permalink Blame History

Runbook — диагностика «фантомного merge» (ORCH-071)

Когда применять. Задача дошла до done (или прод задеплоен «зелёным»), но есть подозрение, что её ветка не влита в main — следующая задача срежет ветку от устаревшего main и потеряет код предшественника (постмортем docs/history/LESSONS_2026-06-08_phantom-merge.md). Этот runbook даёт 4 проверки для однозначной локализации фантома.

С ORCH-071 такой исход блокируется автоматически: под-гейт deploy → done (stage_engine._handle_merge_verify) сначала детерминированно вливает PR (merge_gate.merge_pr, Gitea PR-merge API), затем верифицирует merge (merge_gate.verify_merged_to_main) и НЕ пускает задачу в done, пока merge не подтверждён (alert + HOLD). Этот runbook — для ручной перепроверки/инцидентов (в т.ч. при выключенном kill-switch ORCH_MERGE_VERIFY_ENABLED=false).

Подставьте значения:

OWNER=admin                       # settings.gitea_owner
REPO=orchestrator                 # репозиторий
BRANCH=feature/ORCH-071-slug      # ветка задачи
GITEA=http://localhost:3000       # settings.gitea_url
TOKEN=<gitea_token>               # settings.gitea_token
FILE=src/stage_engine.py          # любой файл, гарантированно изменённый задачей

Проверка 1 — Gitea API: список PR + флаги merged

Показывает, считает ли сам Gitea PR влитым.

curl -s -H "Authorization: token $TOKEN" \
  "$GITEA/api/v1/repos/$OWNER/$REPO/pulls?state=all" \
  | python3 -c 'import sys,json; \
[print(p["number"], p["state"], "merged="+str(p.get("merged")), p["head"]["ref"]) \
 for p in json.load(sys.stdin)]'
  • Фантом НЕ подтверждён (всё хорошо): строка ветки $BRANCH имеет merged=True.
  • Фантом подтверждён (по этому критерию): PR ветки state=open / merged=False (или PR отсутствует), при том что задача в done / прод задеплоен.

Проверка 2 — md5 прод-файлов vs git show origin/main:<file>

Сверяет содержимое файла на проде с тем, что лежит в origin/main.

# в прод-контейнере (или через docker exec orchestrator):
md5sum "/app/$FILE"

# содержимое того же файла из origin/main (на хосте, в клоне репо):
git -C /home/slin/repos/$REPO fetch origin main -q
git -C /home/slin/repos/$REPO show "origin/main:$FILE" | md5sum
  • Совпало: прод соответствует main (фантома нет ИЛИ задача не меняла этот файл — возьмите файл из проверки 3/diff'а ветки).
  • Разошлось: прод собран из ветки, а main его не получил → косвенный признак фантома.

Проверка 3 — git merge-base ветки vs main

Главный детерминированный критерий: является ли HEAD ветки предком origin/main.

git -C /home/slin/repos/$REPO fetch origin -q
SHA=$(git -C /home/slin/repos/$REPO rev-parse "origin/$BRANCH")
git -C /home/slin/repos/$REPO merge-base --is-ancestor "$SHA" origin/main \
  && echo "MERGED: ветка влита в main" \
  || echo "NOT MERGED: ветка НЕ предок origin/main (ФАНТОМ)"

Это ровно та проверка, что выполняет merge_gate.verify_merged_to_main (rc=0 → влито).

  • MERGED: фантома нет.
  • NOT MERGED: фантом подтверждён — main не содержит коммитов задачи.

Проверка 4 — таймлайн деплой-логов

Восстанавливает порядок событий: был ли merge до/после деплоя, и был ли он вообще.

# Вердикт деплоя + новое поле merge-верификации (ORCH-071):
git -C /home/slin/repos/$REPO show "origin/$BRANCH:docs/work-items/<WI>/14-deploy-log.md" \
  | sed -n '1,12p'   # frontmatter: deploy_status:, merged_to_main:

# Наблюдаемость под-гейта в живом сервисе:
curl -s "$GITEA_HEALTH/queue" | python3 -c \
  'import sys,json; print(json.load(sys.stdin)["merge_verify"])'
# -> {"enabled":..., "merge_verified_total":..., "not_merged_alerts_total":..., "last_alert_wi":...}

# Журнал хоста по деплою (sentinel-каталог задачи):
ls -la /home/slin/repos/.deploy-state-$REPO/<WI>/
cat   /home/slin/repos/.deploy-state-$REPO/<WI>/hook.log
  • deploy_status: SUCCESS + merged_to_main: false → деплой прошёл, merge — нет (это и есть класс ORCH-071; задача должна быть удержана на deploy, не done).
  • not_merged_alerts_total растёт / last_alert_wi == <WI> → под-гейт уже поднял alert.

Критерий «фантом подтверждён»

Фантомный merge считается подтверждённым, если выполняется ХОТЯ БЫ ОДНО из:

  1. Проверка 1: PR ветки state=open / merged=False (или PR нет), а задача в done.
  2. Проверка 3: merge-base --is-ancestor вернул NOT MERGED (HEAD ветки не предок origin/main).
  3. Проверка 4: 14-deploy-log.md имеет deploy_status: SUCCESS при merged_to_main: false.

Проверка 2 — вспомогательная (зависит от того, менял ли файл задачей), используется для подтверждения проверок 1/3.

Что делать при подтверждённом фантоме

  1. Влить PR вручную через Gitea (PR-merge API / UI) — НИКОГДА не git push/--force в main (INV-4).
  2. Повторить approve задачи (re-drive) — под-гейт переоценит: merge подтвердится → задача уйдёт в done.
  3. Если фантом случился при выключенном kill-switch — включить ORCH_MERGE_VERIFY_ENABLED=true.