# 01 — BRD: ORCH-073 — CRIT: эрозия main (код ORCH-067/069 затёрт ребейзами, не доехал) - **Work Item:** ORCH-073 - **Тип:** BUG CRITICAL — целостность `main`, накопительный регресс/эрозия - **Репозиторий:** orchestrator (self-hosting) - **Ветка:** `feature/ORCH-073-crit-main-orch-067-069` - **Связь:** усиливает/чинит ORCH-071 (merge-verify); НЕ покрыт ORCH-071. ## 1. Бизнес-проблема Код успешно «задеплоенных» и переведённых в `done` задач **ORCH-067** (tracker bump, Plane-статусы, кликабельные ссылки `plane_issue_link`) и **ORCH-069** (`qg0_title_max`) **физически отсутствовал в `origin/main`**, хотя обе прошли весь конвейер, Confirm Deploy, merge-verify `CONFIRMED` и стали `done`. В `main` попадали только их **docs-коммиты** (staging-log / verdict через отдельные авто docs-PR), но НЕ код feature-веток. Внешнее проявление (нашёл Слава, 08.06): «ссылок на задачу в Plane нет», карточка Telegram показывает сырой номер задачи вместо кликабельной ссылки — потому что код ссылок есть в ветке ORCH-067, но не в `main`. **Накопительный характер:** каждая новая задача срезает ветку от УСТАРЕВШЕГО `main` и при merge тихо (без конфликт-маркеров) затирает код предшественника. Уже потеряны ORCH-067 и ORCH-069; без системного фикса теряется код каждой следующей задачи с правкой `CHANGELOG.md`. ## 2. Подтверждённый root cause (git-аудит 08.06, не гипотеза) 1. **`verify_merged_to_main` подтверждает merge по ложному признаку.** `src/merge_gate.py::verify_merged_to_main` возвращает `True`, если выполнено **ЛИБО** `pr_already_merged(repo, branch)`, **ЛИБО** `git merge-base --is-ancestor origin/main`. Первая ветка (`pr_already_merged`) и есть дыра. 2. **`pr_already_merged` засчитывает ЛЮБОЙ merged PR ветки.** `src/merge_gate.py::pr_already_merged` делает `GET /pulls?state=all&head=` и возвращает `True`, если **хоть один** PR `merged==True`. У одной ветки несколько PR (code-PR + авто docs-PR со staging/deploy-логами). Сливается docs-PR → функция говорит «already-merged» → `verify_merged_to_main`=`True` → merge-verify `CONFIRMED` → `done`, хотя code-PR НЕ слит. **Ложно-зелёный.** 3. **CHANGELOG.md-ребейзы — вторичный усилитель.** Merge-gate `auto_rebase_onto_main` при конфликте `CHANGELOG.md` откатывает `deploy-staging → development`; повторный ребейз ветки от старого `main` несёт устаревшие версии файлов (`notifications.py`/`config.py`/`webhooks/plane.py`), которые при merge тихо затирают соседний код (фантом-эффект, как в ORCH-071, без конфликт-маркеров). > Уточнение для архитектора: в ТЗ упомянута «инвертированная проверка `merge-base --is-ancestor > origin/main HEAD` (merge_gate.py ~76)» — это `branch_is_behind_main` (детектор «ветка > свежая»), он корректен для своей цели. Фактический дефект merge-verify — это OR-ветка > `pr_already_merged` в `verify_merged_to_main` (строка ~649), которая засчитывает docs-PR. ## 3. Состояние на момент анализа (G1) Аудит `origin/main` показал, что **восстановительный PR #76** (`restore(main): re-merge ORCH-067 + ORCH-069 (ORCH-073)`) уже вернул код в `main`: - `plane_issue_link` присутствует (`src/notifications.py`), `qg0_title_max` присутствует (`src/config.py`, `src/webhooks/plane.py`), `verify_merged_to_main` присутствует. Таким образом **G1 (восстановление кода) фактически выполнено** ручным restore-PR. Задача ORCH-073 должна **подтвердить и зафиксировать** это в критериях приёмки (AC-1) и сосредоточиться на **системном фиксе навсегда** (G2–G5 / FR-1…FR-5), иначе регресс повторится. ## 4. Цели (Goals) - **G1.** КОД ORCH-067 и ORCH-069 присутствует в `origin/main` одновременно с ORCH-071 (подтвердить restore-PR #76, зафиксировать маркеры > 0). Pytest зелёный. Прод задеплоен. - **G2 (FR-2/FR-3).** `merge`/`pr_already_merged` различают **code-PR** и **docs-PR** — merge засчитывается только за PR с кодом ветки (`base==main`, `head==`). - **G3 (FR-1, ядро).** `verify_merged_to_main` подтверждает merge **ТОЛЬКО** по факту «deployed SHA — предок `origin/main`». PR-флаги вспомогательны, не достаточны. - **G4 (FR-4).** Защита от CHANGELOG-затирания: `.gitattributes` с `CHANGELOG.md merge=union` (+ опц. `docs/*.md merge=union` для append-only). - **G5 (FR-5, регресс-гард навсегда).** После деплоя — sanity-проверка целостности `main`: deployed SHA в `main` И набор маркеров ранее-merged задач не уменьшился. Откат соседнего кода → alert «main regressed», задача НЕ `done`. ## 5. Не-цели (Out of scope) - Не менять Plane / схему БД. - Не отменять self-hosting safety (не ронять прод, merge только через PR-API, без force-push в `main`). - Не менять ручной гейт `Confirm Deploy`. - Не менять поведение merge/verify для non-self репозиториев (enduro-trails) — обратная совместимость. ## 6. Инварианты - **INV-1.** never-raise на верификации (alert, не падение). - **INV-2.** self-hosting safety: прод не падает; merge только PR-API, без force-push в `main`. - **INV-3.** ручной `Confirm Deploy` сохранён. - **INV-4.** Идемпотентность: повторный прогон / reaper не делает второй merge; idempotency опирается на «SHA-в-main», а не на «любой merged PR». - **INV-5.** Обратная совместимость non-self (enduro): поведение merge/verify без изменений. ## 7. Заинтересованные стороны - **Owner / Слава** — потребитель (видит кликабельные ссылки в карточке; доверие к merge-verify). - **Все проекты на инстансе** (enduro-trails) — общий `main`/очередь/БД; регресс орка = групповой риск. ## 8. Срочность КРИТИКАЛ. Без FR-1/FR-4/FR-5 каждая новая задача с правкой `CHANGELOG.md` продолжает терять код предшественников (уже потеряны 067, 069). Ложно-зелёный merge-verify подрывает само ядро автономности конвейера.