# 02 — Техническое задание (ТЗ) **Work Item:** ORCH-043 **Тема:** merge-gate + auto-rebase + re-test (безопасная параллель в одном репо) **Автор:** Analyst > ТЗ описывает ТРЕБУЕМОЕ поведение и конкретные точки изменения кода. Окончательный > выбор места встройки в конвейер (новая стадия vs гейт существующего перехода vs шаг > перед слиянием) и детали reconciliation — **за архитектором** (ADR в `06-adr/`). > Если ТЗ окажется нереализуемым — вернуть на стадию `analysis`, не комментировать задним числом. --- ## 1. Задействованные модули `src/` | Модуль | Роль в изменении | |--------|------------------| | `src/merge_gate.py` (**новый**) | Ядро фичи: ancestor-check, auto-rebase, re-test, merge-lock. Чистые функции + git-операции в worktree. | | `src/qg/checks.py` | Новый QG-check `check_branch_mergeable` (merge-gate) + регистрация в `QG_CHECKS`. Переиспользует паттерн `check_tests_local` (pytest в worktree) и `_repo_path`. | | `src/stages.py` | Встройка merge-gate в `STAGE_TRANSITIONS` (точное место — за архитектором; см. §6). | | `src/stage_engine.py` | Ветка отката merge-gate → `development` в `_handle_qg_failure_rollbacks` + диспетчеризация нового check в `_run_qg`. | | `src/git_worktree.py` | Возможные хелперы: проверка «behind origin/main», rebase, push `--force-with-lease`. Не ломать сигнатуры `ensure_worktree` / `get_worktree_path`. | | `src/config.py` | Новые `settings`: тайм-аут re-test, вкл/выкл гейта, политика отстающей ветки, тайм-аут lock. | | `src/agents/launcher.py` | Если merge-gate встраивается как шаг перед слиянием на стадии `deploy` — точка, где deployer запускается, может потребовать координации с lock (за архитектором). | | `tests/` | Новые тесты (см. `04-test-plan.yaml`) + обновление snapshot-тестов реестра/стадий. | ## 2. Функциональные требования к `src/merge_gate.py` Предлагаемый публичный контракт (имена финализирует архитектор; поведение обязательно): ### 2.1 `branch_is_behind_main(repo, branch) -> bool` - `git fetch origin main` в main-clone/worktree (best-effort, never-raise → трактуем как «не удалось определить» и НЕ пропускаем слияние вслепую). - Ветка считается отстающей, если `origin/main` **не** является предком HEAD ветки (`git merge-base --is-ancestor origin/main ` → ненулевой код). ### 2.2 `auto_rebase_onto_main(repo, branch) -> (ok: bool, reason: str)` - Выполняется в изолированном worktree ветки (`ensure_worktree`), НЕ в общем clone. - Догнать ветку до `origin/main` (rebase либо merge — выбор архитектора; критично: результат содержит весь `origin/main` и историю/изменения ветки). - **Текстовый конфликт** → отменить операцию (`git rebase --abort` / `git merge --abort`), worktree оставить чистым, вернуть `(False, "rebase conflict: <файлы>")`. - **Чистый догон** → `git push --force-with-lease origin ` (ТОЛЬКО ветка задачи, НИКОГДА `main`). Вернуть `(True, ...)`. - Контракт never-raise: любая git/OS-ошибка → `(False, "")`, не исключение. ### 2.3 `retest_branch(repo, branch) -> (ok: bool, reason: str)` - Прогнать тест-набор проекта в worktree догнанной ветки. Канон — как в `check_tests_local`: `python -m pytest` (точная команда/каталог — за архитектором, согласованно с CI-конфигом `.gitea/workflows/`). - Тайм-аут `settings.merge_retest_timeout_s`; превышение → `(False, "re-test timeout")`. - Возврат: `(True, "re-test green")` при коде 0, иначе `(False, "re-test failed: ")`. ### 2.4 Merge-lock (сериализация, BR-5) - Реализовать межзадачную сериализацию «догон+re-test+слияние» в пределах одного `repo`. - Допустимые реализации (выбор архитектора): файловый lock в `repos_dir`, advisory-lock, либо строка-замок в SQLite. Требования: restart-safe, с тайм-аутом `settings.merge_lock_timeout_s`, корректное освобождение при ошибке/падении. - Под локом: повторно сверить «не отстаёт» ПОСЛЕ захвата (double-check), т.к. `main` мог уйти, пока ждали lock. ## 3. Новый QG-check (`src/qg/checks.py`) ``` check_branch_mergeable(repo, work_item_id, branch) -> tuple[bool, str] ``` Поведение (детерминированно, без участия LLM): 1. Захватить merge-lock для `repo` (с тайм-аутом). Не удалось → `(False, "merge-lock busy")`. 2. Если ветка не отстаёт от `origin/main` → `(True, "branch up-to-date with main")`. 3. Иначе `auto_rebase_onto_main`: - конфликт → `(False, "rebase conflict: ...")`; - успех → `retest_branch`: - зелёный → `(True, "rebased onto main, re-test green")`; - красный/тайм-аут → `(False, "re-test failed after rebase: ...")`. 4. Освободить lock в `finally`. - Зарегистрировать в `QG_CHECKS` под ключом `"check_branch_mergeable"`. - Контракт never-raise (как у соседних чеков): исключение → `(False, "")`. > **Опционально (за архитектором):** флаг `settings.merge_gate_enabled`; при `False` > чек возвращает `(True, "merge-gate disabled")` (безопасный no-op для постепенного > раскатывания, по образцу условного staging-гейта ORCH-35). ## 4. Изменения схемы БД - **Не требуется** для базовой реализации (lock через файл/advisory). - ЕСЛИ архитектор выберет lock через SQLite — добавить таблицу/строку-замок миграцией, совместимой с текущей инициализацией `src/db.py` (никаких ломающих изменений `tasks`, `agent_runs`, `jobs`, `events`). Это решение фиксируется в ADR. ## 5. Изменения API - Новых HTTP-эндпоинтов **не требуется**. - Допустимо (не обязательно) расширить `GET /status` или `GET /queue` индикатором «merge-gate: rebasing/re-testing/locked» для наблюдаемости — на усмотрение архитектора, без изменения существующих контрактов ответов. ## 6. Точки встройки в конвейер (требование + кандидаты) **Требование:** merge-gate отрабатывает как можно ближе к фактическому слиянию в `main` и ДО него. Слияние ветки в `main` НЕ должно происходить в обход гейта. Кандидаты (окончательно — ADR архитектора): - **(A)** Гейт на переходе `deploy-staging → deploy` или новый под-гейт перед слиянием: deployer вливает PR на стадии `deploy`, поэтому проверка «догнать+re-test» логично встаёт непосредственно перед запуском deployer. - **(B)** Новая стадия `merge-gate` между `deploy-staging` и `deploy` с агентом=None и `qg="check_branch_mergeable"`. - **(C)** Перенести само слияние в `main` из ответственности deployer-агента в детерминированный шаг оркестратора, защищённый merge-gate (более крупное изменение). При любом варианте, меняющем `STAGE_TRANSITIONS` или `QG_CHECKS`: - обновить `docs/architecture/README.md` (таблица стадий + реестр QG, §«Конвейер»); - обновить snapshot-тесты `tests/test_qg_registry_snapshot.py` (`_EXPECTED_QGS`, `_EXPECTED_TRANSITIONS`) — осознанно, в этом же PR; - сохранить порядок ключей `STAGE_TRANSITIONS` (от него зависит `get_previous_stage`). ## 7. Откаты (интеграция со `stage_engine`) В `_handle_qg_failure_rollbacks` добавить ветку для merge-gate FAIL по образцу `check_staging_status` / `check_deploy_status`: - `update_task_stage(task_id, "development")`, `set_issue_blocked(work_item_id)`; - комментарий в Plane (`plane_add_comment`, author="deployer" или системный) с причиной (конфликт rebase / красный re-test) — дословный `reason` гейта; - Telegram-алерт (`send_telegram`); - учитывать `MAX_DEVELOPER_RETRIES`, не плодить бесконечные заворот-циклы. - В `_run_qg` добавить диспетчеризацию `check_branch_mergeable` с сигнатурой `(repo, work_item_id, branch)` (как у артефактных чеков). ## 8. Изменения конфигурации (`src/config.py`, env-префикс `ORCH_`) | Setting | Назначение | Дефолт (предложение) | |---------|-----------|----------------------| | `merge_gate_enabled: bool` | Глобальный вкл/выкл гейта | `True` | | `merge_retest_timeout_s: int` | Тайм-аут повторного прогона тестов | `600` | | `merge_lock_timeout_s: int` | Тайм-аут ожидания merge-lock | `300` | | `merge_gate_repos: str` | (опц.) ограничить гейт списком репо; пусто = все | `""` | Значения и имена финализирует архитектор; задокументировать в `.env.example` и `docs/architecture/README.md`. ## 9. Требования к наблюдаемости / документации (golden source) - Обновить `docs/architecture/README.md`: описание merge-gate, auto-rebase, re-test, merge-lock; при изменении стадий/реестра — соответствующие таблицы. - Обновить `CHANGELOG.md`. - Завести ADR `docs/work-items/ORCH-043/06-adr/ADR-001-merge-gate.md` (механизм догона, выбор rebase vs merge, реализация lock, место встройки). - Все ветки кода — с лог-сообщениями (`logger.info/warning/error`) по образцу соседних гейтов, чтобы поведение читалось в `/app/data/runs` и логах сервиса. ## 10. Нефункциональные требования - **Безопасность self-hosting:** никогда не push в `main`; force только `--force-with-lease` по ветке задачи; прод-контейнер `orchestrator` не рестартить/не ронять. - **Изоляция:** все git-операции — в worktree ветки (`ensure_worktree`), не в общем clone, чтобы не словить S-4-гонку параллельных задач. - **Идемпотентность/restart-safe:** lock и гейт корректно ведут себя при рестарте сервиса. - **Never-raise** контракт у всех новых чеков/парсеров (как в текущем `src/qg/checks.py`). - **Совместимость:** не менять сигнатуры/поведение существующих QG-чеков и вебхуков. ## 11. Артефакты pipeline, которые должны быть созданы/обновлены - `src/merge_gate.py` (новый), изменения в `src/qg/checks.py`, `src/stages.py`, `src/stage_engine.py`, `src/config.py`, при необходимости `src/git_worktree.py`. - Новые тесты в `tests/` + обновлённые snapshot-тесты. - `docs/architecture/README.md`, `CHANGELOG.md`, `.env.example`, `docs/work-items/ORCH-043/06-adr/ADR-001-merge-gate.md`.