# 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 <sha> origin/main`.
   Первая ветка (`pr_already_merged`) и есть дыра.
2. **`pr_already_merged` засчитывает ЛЮБОЙ merged PR ветки.**
   `src/merge_gate.py::pr_already_merged` делает `GET /pulls?state=all&head=<branch>` и
   возвращает `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==<feature-branch>`).
- **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 подрывает само ядро
автономности конвейера.
