From 772ccab013274b6dc106d0a0d09766a7b3ff52f3 Mon Sep 17 00:00:00 2001 From: Slava Date: Mon, 8 Jun 2026 09:20:22 +0300 Subject: [PATCH] =?UTF-8?q?docs(history):=20CRITICAL=20postmortem=20?= =?UTF-8?q?=E2=80=94=20phantom=20merge=20(deploy=20without=20main-merge),?= =?UTF-8?q?=20see=20ORCH-071?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LESSONS_2026-06-08_phantom-merge.md | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 docs/history/LESSONS_2026-06-08_phantom-merge.md diff --git a/docs/history/LESSONS_2026-06-08_phantom-merge.md b/docs/history/LESSONS_2026-06-08_phantom-merge.md new file mode 100644 index 0000000..ebc40d2 --- /dev/null +++ b/docs/history/LESSONS_2026-06-08_phantom-merge.md @@ -0,0 +1,47 @@ +# Lessons Learned — 2026-06-08: «Фантомный merge» — прод деплоится, но код не сливается в main + +## Severity: CRITICAL (потеря целостности main, накопительная потеря кода между задачами) + +## Резюме +Self-deploy (Phase B) собирал прод-образ из ВЕТКИ задачи и рапортовал `finalize SUCCESS` + `post-deploy HEALTHY`, но git-merge ветки в `main` НЕ происходил. PR оставался `open`. Следующая задача срезала свою ветку от устаревшего main → теряла код незалитых предшественников. Накопительно потеряны в main: **ORCH-022, ORCH-059, ORCH-066, ORCH-068** (PR#67/68/69/70 — все open, merged=False). Последний реально слитый — ORCH-065 (PR#66). + +## Как обнаружено +Симптом: ORCH-067 переведён в `To Analyse`, но конвейер не стартовал (`no pipeline action`). Причина — прод слушал старый триггер `in_progress`, а не `to_analyse` (ORCH-066). При разборе выяснилось: код ORCH-066 не в проде, хотя он «деплоился». + +Решающее наблюдение оператора (Слава): «спам ET-002 начался СРАЗУ после деплоя 66 → значит код деплоился». Это вскрыло механизм: код 66 БЫЛ в проде 22:17–05:32, потом стёрт деплоем 068 (срезан от старого main без 66). + +## Доказательная база (как подтверждали — воспроизводимый метод) +1. **PR-статус (Gitea API):** PR#67(022)/68(059)/69(066)/70(068) = open, merged=False. PR#66(065) = merged=True (последний честный). +2. **md5-сверка файлов прод vs origin/main vs ветка:** + - `src/reconciler.py`, `src/plane_sync.py`: prod md5 == ветка ORCH-068 != main → прод = снимок ветки 068, НЕ main. + - `src/webhooks/plane.py`: prod == main == ветка-068 (ветка 068 этот файл не трогала → видна старая база без to_analyse). +3. **git merge-base:** ветка ORCH-068 срезана от `bb03350` (ORCH-065), не от кода 066. История ветки-068 по 066 содержит только `docs staging`, кода (`to_analyse`) нет. +4. **Таймлайн логов:** деплой 22:17 (ветка-066, сломанный reconciler) → спам ET-002 начался; деплой 05:32 (ветка-068, база 065 без 66) → спам прекратился (0 после 05:33). Подтверждает: прод-образ = снимок ВЕТКИ, меняется при каждом деплое, теряет незалитое. + +## Root cause (гипотеза → нужен код-аудит self_deploy/merge_gate) +Self-deploy Phase B инициирует прод-деплой из worktree ветки (BUILD-ONCE из validated commit). Шаг git-merge ветки в main: +- ЛИБО не вызывается на self-hosting пути (Phase B уходит в detached host-процесс, finalizer пишет SUCCESS-маркеры, но merge отдельно и молча скипается/падает), +- ЛИБО регресс фикса ORCH-065 (idempotent merge / merge-lease reclaim): guard `pr_already_merged` или lease-reclaim ошибочно считает PR уже слитым / не докатывает merge после рестарта контейнера (а Phase B ИМЕННО рестартит контейнер → процесс, державший merge-lease, умирает до завершения merge). +Симптоматически ORCH-065 был последним успешным merge — деградация началась СРАЗУ после него или из-за взаимодействия его механики с self-deploy-рестартом. + +## Почему конвейер не заметил +- `finalize SUCCESS` и `post-deploy HEALTHY` маркеры пишутся НЕЗАВИСИМО от факта merge. Пайплайн считает задачу done по этим маркерам, git-состояние main не верифицируется. +- Прод здоров (образ из ветки рабочий) → health-check зелёный → нет сигнала о проблеме. +- Дыра видна только при сравнении main с прод ИЛИ когда следующая задача теряет код предыдущей (что и случилось с 67). + +## Уроки +1. **Деплой ОБЯЗАН верифицировать, что код реально в main ПОСЛЕ деплоя.** finalize SUCCESS без проверки `git merge-base origin/main == deployed_commit` (или PR.merged==true) — фальшивый зелёный. Добавить post-merge верификацию: deployed SHA должен быть предком origin/main. +2. **Маркер «deployed» != «merged».** Нельзя считать задачу завершённой по staging/post-deploy-маркерам, если PR не закрыт merge. Гейт: задача → done ТОЛЬКО при PR.merged==true. +3. **Self-deploy рестартит контейнер → любой держатель merge-lease/незавершённый git-шаг умирает.** Merge ДОЛЖЕН завершиться и быть подтверждён ДО рестарта прод-контейнера, либо merge выносится в шаг, переживающий рестарт (как requeue_running_jobs, но для merge-в-main). +4. **Срез ветки от main делает целостность main критичной.** Если main отстаёт — каждая новая задача наследует дыру. main = единственный источник для новых веток, его рассинхрон с прод = накопительная потеря. +5. **Метод диагностики (сохранить как runbook):** при подозрении на рассинхрон — (a) Gitea API PR list merged-флаги, (b) md5 prod-файлов vs `git show origin/main:`, (c) merge-base ветки vs main, (d) таймлайн деплой-логов. Эти 4 проверки однозначно локализуют фантом. + +## Действия +- Восстановление main: интеграционная ветка `integ/restore-main-2026-06-08` — последовательный merge 022→059→066→068 (docs union-resolved, reconciler-конфликт 066⊕068 разрешён: каркас 068 livelock-fix + триггер to_analyse 066), полный pytest, затем merge в main + передеплой. +- Заведён критбаг ORCH-071: «фантомный merge — self-deploy без верификации merge в main» (root-fix: post-deploy verify + done-гейт по PR.merged + merge до рестарта). +- ORCH-070 (Confirm Deploy trigger) частично ДУБЛИРУЕТ ORCH-059 (handle_confirm_deploy уже написан в 059) — после долива 059 пересмотреть scope 070 (остаётся только display-слой статусов Monitoring after Deploy). + +## Связанные +- ORCH-065 (последний честный merge; подозрение на регресс его merge-механики) +- ORCH-066/068 (потерянный код), ORCH-059 (Confirm Deploy trigger, тоже потерян) +- Урок 2026-06-08 confirm-deploy-deadtrigger (симптом того же корня)