162 lines
13 KiB
Markdown
162 lines
13 KiB
Markdown
# 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 <branch>` → ненулевой код).
|
||
|
||
### 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 <branch>` (ТОЛЬКО ветка задачи,
|
||
НИКОГДА `main`). Вернуть `(True, ...)`.
|
||
- Контракт never-raise: любая git/OS-ошибка → `(False, "<reason>")`, не исключение.
|
||
|
||
### 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: <tail>")`.
|
||
|
||
### 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, "<reason>")`.
|
||
|
||
> **Опционально (за архитектором):** флаг `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`.
|