# adr-0006: Merge-gate — догон `main` + re-test + сериализация слияний

- **Статус:** proposed
- **Дата:** 2026-06-06
- **Задача:** ORCH-043
- **Детальный ADR:** `docs/work-items/ORCH-043/06-adr/ADR-001-merge-gate.md`

## Контекст
Ветка валидируется относительно того `main`, из которого создана, а не относительно `main`
на момент слияния. Параллельная задача могла влиться раньше → **семантический конфликт
слияния** (git мержит без текстового конфликта, но `main` сломан). Для self-hosting это
красный `main` инструмента, обслуживающего все проекты. Слияние в `main` делает
deployer-агент в начале стадии `deploy`; замена механизма PR-merge — вне объёма.

## Решение
Детерминированный merge-gate (`check_branch_mergeable`, без LLM) на ребре
`deploy-staging → deploy`, ДО запуска deployer'а, который мержит. `STAGE_TRANSITIONS` не
меняется (минимальный blast-radius); в `QG_CHECKS` добавлен `check_branch_mergeable`.

- **Догон:** ветка отстаёт ⇔ `origin/main` не предок HEAD → `rebase origin/main` в worktree
  + `push --force-with-lease` (ТОЛЬКО ветка задачи; `main` — никогда). Текстовый конфликт →
  `rebase --abort` → откат на `development`.
- **Re-test:** `python -m pytest tests/` в worktree догнанной ветки, тайм-аут
  `merge_retest_timeout_s`. Красный/тайм-аут → откат на `development`.
- **Сериализация (BR-5):** файловый **merge-lease** на репо
  (`<repos_dir>/.merge-lease-<repo>.json`), живёт от гейта до фактического merge.
  Acquire **неблокирующий** (anti-deadlock при `max_concurrency=1`): busy → **defer**
  (re-enqueue deployer с задержкой через `available_at`), не rollback. Release — на
  PR-merged вебхуке / `deploy→done` / откате / по возрасту (crash-реклейм). Restart-safe.
- **Условность (как ORCH-35):** реален для `orchestrator`; прочие репо — no-op. Флаги
  `merge_gate_enabled` / `merge_gate_repos` для поэтапного раската.

## Альтернативы
- **Новая стадия `merge-gate`** (кандидат B) — «пустая» стадия без агента не имеет триггера
  (`advance_stage` срабатывает только на завершении агента/вебхуке); потребовала бы chaining
  в движке (не restart-safe) или синтетический job-тип. Отклонено.
- **Перенос merge в детерминированный шаг оркестратора** (кандидат C) — запрещён объёмом
  (замена механизма PR-merge вне scope). Отклонено.
- **Блокирующий lock** — дедлок при одном worker-слоте. Отклонено в пользу defer.

## Последствия
- Сценарий «две зелёные ветки ломают `main`» закрыт: re-test против актуального `main` +
  сериализация слияний.
- Плата: merge-gate — «скрытый» под-гейт ребра (нет в `STAGE_TRANSITIONS`); сериализация
  опирается на PR-merged вебхук со страховкой реклеймом по возрасту; defer перепрогоняет
  staging; длинный re-test держит worker-слот.
- Сквозное изменение конвейера → `arch:major-change`; прод-деплой ORCH-043 строго через
  staging-гейт (8501).

## Связи
adr-0001 (`is_self_hosting_repo`), adr-0003 (условный staging-гейт — образец условности),
adr-0002 (очередь / `available_at` для defer), ORCH-2 (worktree-изоляция), ORCH-046
(дословный reason в `task_desc` при откате).
