analyst(ET): auto-commit from analyst run_id=182
All checks were successful
CI / test (push) Successful in 14s
All checks were successful
CI / test (push) Successful in 14s
This commit is contained in:
114
docs/work-items/ORCH-043/01-brd.md
Normal file
114
docs/work-items/ORCH-043/01-brd.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# 01 — Business Requirements Document (BRD)
|
||||
|
||||
**Work Item:** ORCH-043
|
||||
**Тема:** Безопасная параллель в одном репо: merge-gate + auto-rebase + re-test
|
||||
**Проект:** orchestrator (self-hosting)
|
||||
**Автор:** Analyst
|
||||
**Дата:** 2026-06-06
|
||||
|
||||
---
|
||||
|
||||
## 1. Контекст и проблема
|
||||
|
||||
Оркестратор ведёт несколько work item **параллельно**, каждый в своём изолированном
|
||||
git worktree / ветке (`feature/ORCH-NNN-slug`, ORCH-2/S-4). Все ветки одного проекта
|
||||
исходят из общего `origin/main` и в конце конвейера **вливаются обратно в `main`**.
|
||||
|
||||
Текущий конвейер валидирует ветку **относительно того состояния `main`, из которого
|
||||
она была создана**, а не относительно `main` на момент слияния:
|
||||
|
||||
- `check_ci_green` (стадия `development`) — CI зелёный **на ветке** (Gitea commit status ветки).
|
||||
- `check_tests_passed` (стадия `testing`) — вердикт тестировщика по коду **ветки**.
|
||||
- На стадии `deploy` ветка вливается в `main` (слияние выполняет deployer-агент,
|
||||
см. `src/webhooks/gitea.py` — комментарий про «deployer merges the PR at the START of its run»).
|
||||
|
||||
**Между «ветка проверена» и «ветка влита» `main` мог уйти вперёд** из-за слияния другой
|
||||
параллельной задачи. Возникает **семантический (логический) конфликт слияния**: git
|
||||
сливает ветки без текстового конфликта, но объединённый код `main` сломан — тесты,
|
||||
которые были зелёными на ветке, на обновлённом `main` падают.
|
||||
|
||||
### Почему это критично именно здесь (self-hosting)
|
||||
Проект ORCH правит инструмент, который СЕЙЧАС работает в проде и обслуживает другие
|
||||
проекты (enduro-trails) из одного инстанса с общей БД и общей очередью (см. `CLAUDE.md`,
|
||||
`docs/operations/INFRA.md`). Сломанный `main` оркестратора = встал конвейер ВСЕХ проектов.
|
||||
Две параллельные ORCH-задачи, каждая «зелёная» по отдельности, при последовательном
|
||||
слиянии способны положить прод.
|
||||
|
||||
### Сценарий-иллюстрация
|
||||
1. Задачи A и B ответвлены от `main@C0`.
|
||||
2. A проходит конвейер, вливается → `main@C1`.
|
||||
3. B тестировалась против `C0`; её CI зелёный относительно `C0`. Git-слияние B в `C1`
|
||||
проходит без текстового конфликта, но `C1` содержит изменения A, ломающие B.
|
||||
4. `main` становится красным. Конвейер всех проектов деградирует.
|
||||
|
||||
---
|
||||
|
||||
## 2. Цель
|
||||
|
||||
Гарантировать, что ветка вливается в `main` **только если она проверена против
|
||||
актуального `origin/main`**. Перед слиянием ветка автоматически догоняет `main`
|
||||
(auto-rebase) и **повторно тестируется** (re-test); зелёный результат на актуальном
|
||||
`main` — обязательное условие слияния (merge-gate). Слияния в `main` одного репозитория
|
||||
**сериализуются**, чтобы окно гонки не воспроизводилось между двумя гейтами.
|
||||
|
||||
## 3. Заинтересованные стороны
|
||||
- **Owner / разработчики** — не хотят красный `main` и ручные разборы конфликтов.
|
||||
- **Все проекты на инстансе** — зависят от живого прод-оркестратора.
|
||||
- **Агенты конвейера** — получают детерминированный гейт вместо ручной координации.
|
||||
|
||||
## 4. Объём (Scope)
|
||||
|
||||
### В объёме
|
||||
1. **Merge-gate** — детерминированный гейт перед слиянием в `main`: пропускает
|
||||
слияние только если ветка не отстаёт от `origin/main` И повторная проверка зелёная.
|
||||
2. **Auto-rebase** — если ветка отстаёт от `origin/main`, автоматически догнать `main`
|
||||
(rebase/merge ветки на актуальный `origin/main`) в worktree и запушить результат.
|
||||
3. **Re-test** — после auto-rebase повторно прогнать тест-набор на догнанной ветке;
|
||||
зелёный результат — условие прохода гейта.
|
||||
4. **Сериализация слияний** — в пределах одного репозитория одновременно «догон+слияние»
|
||||
выполняет только одна задача (merge-lock), иначе гонка воспроизводится.
|
||||
5. **Откаты при неуспехе** — текстовый конфликт rebase ИЛИ красный re-test → возврат
|
||||
задачи на `development` (по образцу существующих откатов) с понятным комментарием.
|
||||
6. **Конфигурируемость** — пороги/тайм-ауты re-test и поведение гейта вынесены в `settings`.
|
||||
|
||||
### Вне объёма
|
||||
- Изменение логики стадий `analysis` / `architecture` / `review`.
|
||||
- Замена самого механизма слияния PR в Gitea (UI/настройки репозитория).
|
||||
- Реальные прод-деплои (остаются за `scripts/orchestrator-deploy-hook.sh`).
|
||||
- Кросс-репозиторная сериализация (гейт защищает `main` каждого репо отдельно).
|
||||
|
||||
## 5. Бизнес-требования (BR)
|
||||
|
||||
| ID | Требование |
|
||||
|----|------------|
|
||||
| BR-1 | Перед слиянием ветки в `main` оркестратор обязан проверить, что ветка содержит последний `origin/main` (не отстаёт). |
|
||||
| BR-2 | Если ветка отстаёт — оркестратор автоматически догоняет её до `origin/main` без участия человека (auto-rebase). |
|
||||
| BR-3 | После догона тест-набор повторно прогоняется; слияние разрешено только при зелёном результате (re-test). |
|
||||
| BR-4 | Текстовый конфликт при auto-rebase или красный re-test НЕ приводит к слиянию: задача откатывается на `development` для ручного фикса. |
|
||||
| BR-5 | В пределах одного репозитория «догон+проверка+слияние» сериализуются: две задачи не могут одновременно пройти merge-gate и влиться. |
|
||||
| BR-6 | Гейт детерминированный (Python/гит-команды + код тестов), а не доверие LLM-агенту. |
|
||||
| BR-7 | Гейт обязателен минимум для self-hosting репозитория `orchestrator`; применим к любому репо с параллельными задачами. |
|
||||
| BR-8 | Все события гейта (догон, re-test, проход/откат) логируются и отражаются комментарием в Plane, без рассинхрона стадий. |
|
||||
|
||||
## 6. Критерии успеха
|
||||
- Воспроизводимый ранее сценарий «две зелёные ветки ломают `main`» более не приводит
|
||||
к красному `main`: вторая ветка либо догоняется и проходит re-test, либо откатывается.
|
||||
- Прод-контейнер `orchestrator` не перезапускается и не падает в рамках задачи.
|
||||
- Реестр гейтов и стадий остаётся консистентным (snapshot-тесты обновлены осознанно).
|
||||
|
||||
## 7. Риски и ограничения
|
||||
- **Гонка между двумя гейтами** — снимается merge-lock (BR-5); без него фикс неполон.
|
||||
- **Долгий re-test** — нужен тайм-аут и понятный откат, а не вис задачи.
|
||||
- **Force-push догнанной ветки** — допустим только `--force-with-lease` и только по
|
||||
own-ветке задачи; никогда по `main`.
|
||||
- **Self-hosting** — любые изменения не должны ронять/рестартить прод-оркестратор;
|
||||
обязательная страховка стадией `deploy-staging` (порт 8501) сохраняется.
|
||||
- Окончательное место встройки в конвейер (новая стадия / гейт существующего перехода /
|
||||
шаг перед слиянием) — **решение архитектора** (ADR), BRD фиксирует требуемое поведение.
|
||||
|
||||
## 8. Связанные артефакты
|
||||
- `02-trz.md` — техническое задание (модули, гейт, конфиг, точки встройки).
|
||||
- `03-acceptance-criteria.md` — критерии приёмки PASS/FAIL.
|
||||
- `04-test-plan.yaml` — план тестов.
|
||||
- Контекст кода: `src/qg/checks.py`, `src/stage_engine.py`, `src/git_worktree.py`,
|
||||
`src/agents/launcher.py`, `src/webhooks/gitea.py`, `src/stages.py`, `src/config.py`.
|
||||
161
docs/work-items/ORCH-043/02-trz.md
Normal file
161
docs/work-items/ORCH-043/02-trz.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# 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`.
|
||||
105
docs/work-items/ORCH-043/03-acceptance-criteria.md
Normal file
105
docs/work-items/ORCH-043/03-acceptance-criteria.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# 03 — Критерии приёмки (Acceptance Criteria)
|
||||
|
||||
**Work Item:** ORCH-043 — merge-gate + auto-rebase + re-test
|
||||
**Автор:** Analyst
|
||||
|
||||
Каждый критерий имеет однозначное условие PASS/FAIL. Все критерии должны быть PASS.
|
||||
|
||||
---
|
||||
|
||||
## AC-1 — Ветка актуальна: гейт пропускает без догона
|
||||
- **Дано:** ветка содержит последний `origin/main` (не отстаёт).
|
||||
- **Когда:** выполняется `check_branch_mergeable(repo, work_item_id, branch)`.
|
||||
- **PASS:** возвращает `(True, ...)` с причиной «up-to-date», auto-rebase НЕ запускается,
|
||||
ветка не пушится повторно.
|
||||
- **FAIL:** возвращает `False`, либо выполняет ненужный rebase/push.
|
||||
|
||||
## AC-2 — Ветка отстаёт + чистый догон + зелёный re-test → проход
|
||||
- **Дано:** ветка отстаёт от `origin/main`; rebase проходит без текстового конфликта;
|
||||
тест-набор на догнанной ветке зелёный.
|
||||
- **Когда:** выполняется merge-gate.
|
||||
- **PASS:** ветка догнана до `origin/main`, запушена `--force-with-lease`, re-test зелёный,
|
||||
гейт возвращает `(True, ...)`.
|
||||
- **FAIL:** гейт возвращает `False` при чистом догоне и зелёном re-test, либо `main` тронут,
|
||||
либо push выполнен НЕ через `--force-with-lease`.
|
||||
|
||||
## AC-3 — Текстовый конфликт rebase → откат на development, без слияния
|
||||
- **Дано:** auto-rebase упирается в текстовый конфликт.
|
||||
- **Когда:** выполняется merge-gate.
|
||||
- **PASS:** rebase отменён (worktree чист), гейт возвращает `(False, "rebase conflict...")`,
|
||||
задача переведена на `development`, в Plane — комментарий с причиной, слияния в `main` нет.
|
||||
- **FAIL:** ветка осталась в конфликтном состоянии, или задача продвинулась к слиянию,
|
||||
или `main` изменён.
|
||||
|
||||
## AC-4 — Красный re-test после догона → откат на development, без слияния
|
||||
- **Дано:** rebase чистый, но тесты на догнанной ветке падают.
|
||||
- **Когда:** выполняется merge-gate.
|
||||
- **PASS:** гейт возвращает `(False, "re-test failed after rebase...")`, задача на
|
||||
`development`, комментарий в Plane, слияния нет.
|
||||
- **FAIL:** гейт вернул `True`, либо слияние произошло при красном re-test.
|
||||
|
||||
## AC-5 — Сериализация слияний (merge-lock)
|
||||
- **Дано:** две задачи одного `repo` одновременно подходят к merge-gate.
|
||||
- **Когда:** обе пытаются пройти гейт.
|
||||
- **PASS:** «догон+re-test+слияние» выполняет одновременно только одна задача; вторая
|
||||
ждёт освобождения lock (в пределах `merge_lock_timeout_s`), после чего повторно
|
||||
сверяет «не отстаёт» и при необходимости догоняется. Воспроизводимый сценарий
|
||||
«две зелёные ветки ломают main» НЕ приводит к красному `main`.
|
||||
- **FAIL:** обе задачи параллельно проходят гейт и вливаются, воспроизводя гонку.
|
||||
|
||||
## AC-6 — Re-test тайм-аут управляем
|
||||
- **Дано:** re-test превышает `settings.merge_retest_timeout_s`.
|
||||
- **PASS:** прогон прерывается, гейт возвращает `(False, "re-test timeout...")`, задача
|
||||
не виснет, идёт штатный откат.
|
||||
- **FAIL:** задача висит дольше тайм-аута или падает с необработанным исключением.
|
||||
|
||||
## AC-7 — Никогда не push/merge в main напрямую из гейта
|
||||
- **PASS:** код merge-gate не выполняет `git push ... main` и не форс-пушит `main`;
|
||||
force-операции — только `--force-with-lease` по ветке задачи.
|
||||
- **FAIL:** найден любой push/force-push в `main` из логики гейта.
|
||||
|
||||
## AC-8 — Изоляция в worktree
|
||||
- **PASS:** все git-операции гейта идут в worktree ветки (`get_worktree_path` /
|
||||
`ensure_worktree`), а не в общем `/repos/<repo>` clone.
|
||||
- **FAIL:** rebase/тесты выполняются в общем clone, создавая S-4-гонку.
|
||||
|
||||
## AC-9 — Контракт never-raise
|
||||
- **Дано:** недоступен git/сеть, бит worktree, отсутствует ветка и т.п.
|
||||
- **PASS:** `check_branch_mergeable` и функции `merge_gate.py` возвращают `(False, "<reason>")`
|
||||
(или безопасный фоллбэк), НИКОГДА не пробрасывают исключение в `advance_stage`.
|
||||
- **FAIL:** любое необработанное исключение всплывает из гейта.
|
||||
|
||||
## AC-10 — Реестр QG и снапшоты консистентны
|
||||
- **PASS:** `"check_branch_mergeable"` зарегистрирован в `QG_CHECKS` и callable;
|
||||
`tests/test_qg_registry_snapshot.py` (`_EXPECTED_QGS`, при изменении стадий —
|
||||
`_EXPECTED_TRANSITIONS`) обновлены и зелёные; порядок ключей `STAGE_TRANSITIONS`
|
||||
сохранён (не сломан `get_previous_stage`).
|
||||
- **FAIL:** дрейф реестра/стадий без обновления снапшотов; красные snapshot-тесты.
|
||||
|
||||
## AC-11 — Интеграция отката в stage_engine
|
||||
- **PASS:** в `_handle_qg_failure_rollbacks` есть ветка merge-gate FAIL → `development`
|
||||
с уведомлениями (Plane + Telegram) и учётом `MAX_DEVELOPER_RETRIES`; `_run_qg`
|
||||
корректно диспетчеризует новый чек.
|
||||
- **FAIL:** FAIL гейта не приводит к откату, или нет уведомления, или зацикливание заворотов.
|
||||
|
||||
## AC-12 — Условный no-op / выключение (если реализовано)
|
||||
- **Дано:** `settings.merge_gate_enabled = False` (или репо вне `merge_gate_repos`).
|
||||
- **PASS:** гейт возвращает `(True, "merge-gate disabled")`, конвейер работает как прежде.
|
||||
- **FAIL:** гейт блокирует/ломает конвейер при выключенном флаге.
|
||||
|
||||
## AC-13 — Документация обновлена (golden source)
|
||||
- **PASS:** обновлены `docs/architecture/README.md` (merge-gate/auto-rebase/re-test,
|
||||
при изменении — таблицы стадий/реестра), `CHANGELOG.md`, `.env.example` (новые
|
||||
`ORCH_*` настройки); создан ADR `06-adr/ADR-001-merge-gate.md`.
|
||||
- **FAIL:** функционал изменён, документация/ADR/CHANGELOG не обновлены (Reviewer →
|
||||
REQUEST_CHANGES).
|
||||
|
||||
## AC-14 — Безопасность self-hosting
|
||||
- **PASS:** в рамках задачи прод-контейнер `orchestrator` (8500) не рестартился и не падал;
|
||||
изменения не трогают `.env*`, `docker-compose.yml`, прод-инфраструктуру; страховка
|
||||
стадией `deploy-staging` сохранена.
|
||||
- **FAIL:** любой рестарт/падение прод-оркестратора или правка прод-инфры в рамках задачи.
|
||||
|
||||
## AC-15 — Зелёный регресс
|
||||
- **PASS:** `pytest tests/ -q` зелёный целиком (новые тесты ORCH-043 + существующий набор).
|
||||
- **FAIL:** любой упавший/сломанный существующий тест.
|
||||
163
docs/work-items/ORCH-043/04-test-plan.yaml
Normal file
163
docs/work-items/ORCH-043/04-test-plan.yaml
Normal file
@@ -0,0 +1,163 @@
|
||||
work_item: ORCH-043
|
||||
title: "merge-gate + auto-rebase + re-test — безопасная параллель в одном репо"
|
||||
framework: pytest
|
||||
notes: >
|
||||
Тесты на git-операции используют локальные временные репозитории (init bare "origin"
|
||||
+ рабочая ветка), мокают сеть/Plane/Telegram (как в tests/test_qg.py:
|
||||
ORCH_DB_PATH/ORCH_REPOS_DIR в tmp, httpx замокан). Каталог тестов/команда pytest для
|
||||
re-test должны совпадать с CI-конфигом проекта. Финальные имена функций/модулей сверять
|
||||
с реализацией архитектора.
|
||||
|
||||
tests:
|
||||
# ---- merge_gate core: ancestor / behind detection ----
|
||||
- id: TC-01
|
||||
type: unit
|
||||
description: "branch_is_behind_main → True, когда origin/main ушёл вперёд относительно ветки"
|
||||
module: tests/test_merge_gate.py
|
||||
expected: PASS
|
||||
- id: TC-02
|
||||
type: unit
|
||||
description: "branch_is_behind_main → False, когда ветка уже содержит весь origin/main"
|
||||
module: tests/test_merge_gate.py
|
||||
expected: PASS
|
||||
- id: TC-03
|
||||
type: unit
|
||||
description: "branch_is_behind_main never-raise: недоступный git/clone → безопасный возврат, не исключение"
|
||||
module: tests/test_merge_gate.py
|
||||
expected: PASS
|
||||
|
||||
# ---- auto-rebase ----
|
||||
- id: TC-04
|
||||
type: unit
|
||||
description: "auto_rebase_onto_main: чистый догон → (True), ветка содержит origin/main, push выполнен через --force-with-lease"
|
||||
module: tests/test_merge_gate.py
|
||||
expected: PASS
|
||||
- id: TC-05
|
||||
type: unit
|
||||
description: "auto_rebase_onto_main: текстовый конфликт → rebase отменён (worktree чист), (False, 'rebase conflict...'), main не тронут"
|
||||
module: tests/test_merge_gate.py
|
||||
expected: PASS
|
||||
- id: TC-06
|
||||
type: unit
|
||||
description: "auto_rebase_onto_main НЕ пушит и не форс-пушит main ни при каком исходе (проверка вызванных git-команд)"
|
||||
module: tests/test_merge_gate.py
|
||||
expected: PASS
|
||||
|
||||
# ---- re-test ----
|
||||
- id: TC-07
|
||||
type: unit
|
||||
description: "retest_branch: pytest rc=0 → (True, 're-test green')"
|
||||
module: tests/test_merge_gate.py
|
||||
expected: PASS
|
||||
- id: TC-08
|
||||
type: unit
|
||||
description: "retest_branch: pytest rc!=0 → (False, 're-test failed...') с хвостом вывода"
|
||||
module: tests/test_merge_gate.py
|
||||
expected: PASS
|
||||
- id: TC-09
|
||||
type: unit
|
||||
description: "retest_branch: превышен merge_retest_timeout_s → (False, 're-test timeout...'), без виса"
|
||||
module: tests/test_merge_gate.py
|
||||
expected: PASS
|
||||
|
||||
# ---- merge-lock / сериализация ----
|
||||
- id: TC-10
|
||||
type: unit
|
||||
description: "merge-lock: второй захват того же repo не проходит, пока lock удержан; освобождается в finally/после ошибки"
|
||||
module: tests/test_merge_gate.py
|
||||
expected: PASS
|
||||
- id: TC-11
|
||||
type: unit
|
||||
description: "merge-lock restart-safe: устаревший/осиротевший lock не блокирует навсегда (тайм-аут merge_lock_timeout_s)"
|
||||
module: tests/test_merge_gate.py
|
||||
expected: PASS
|
||||
|
||||
# ---- QG check_branch_mergeable ----
|
||||
- id: TC-12
|
||||
type: unit
|
||||
description: "check_branch_mergeable: ветка актуальна → (True, 'up-to-date'), rebase не вызывался"
|
||||
module: tests/test_qg_merge_gate.py
|
||||
expected: PASS
|
||||
- id: TC-13
|
||||
type: unit
|
||||
description: "check_branch_mergeable: отстаёт + чистый rebase + зелёный re-test → (True)"
|
||||
module: tests/test_qg_merge_gate.py
|
||||
expected: PASS
|
||||
- id: TC-14
|
||||
type: unit
|
||||
description: "check_branch_mergeable: конфликт rebase → (False, 'rebase conflict...')"
|
||||
module: tests/test_qg_merge_gate.py
|
||||
expected: PASS
|
||||
- id: TC-15
|
||||
type: unit
|
||||
description: "check_branch_mergeable: красный re-test после догона → (False, 're-test failed after rebase...')"
|
||||
module: tests/test_qg_merge_gate.py
|
||||
expected: PASS
|
||||
- id: TC-16
|
||||
type: unit
|
||||
description: "check_branch_mergeable never-raise: внутренняя ошибка → (False, reason), не исключение; lock освобождён"
|
||||
module: tests/test_qg_merge_gate.py
|
||||
expected: PASS
|
||||
- id: TC-17
|
||||
type: unit
|
||||
description: "merge_gate_enabled=False (или репо вне merge_gate_repos) → (True, 'merge-gate disabled') no-op"
|
||||
module: tests/test_qg_merge_gate.py
|
||||
expected: PASS
|
||||
|
||||
# ---- реестр QG / стадии ----
|
||||
- id: TC-18
|
||||
type: unit
|
||||
description: "'check_branch_mergeable' присутствует в QG_CHECKS и callable"
|
||||
module: tests/test_qg_registry_snapshot.py
|
||||
expected: PASS
|
||||
- id: TC-19
|
||||
type: unit
|
||||
description: "snapshot STAGE_TRANSITIONS/_EXPECTED_QGS обновлён осознанно и совпадает; порядок ключей сохранён (get_previous_stage не сломан)"
|
||||
module: tests/test_qg_registry_snapshot.py
|
||||
expected: PASS
|
||||
|
||||
# ---- интеграция со stage_engine (откаты) ----
|
||||
- id: TC-20
|
||||
type: integration
|
||||
description: "_run_qg диспетчеризует check_branch_mergeable с сигнатурой (repo, work_item_id, branch)"
|
||||
module: tests/test_stage_engine.py
|
||||
expected: PASS
|
||||
- id: TC-21
|
||||
type: integration
|
||||
description: "merge-gate FAIL → advance_stage откатывает задачу на 'development', set_issue_blocked, комментарий Plane, Telegram-алерт (моки)"
|
||||
module: tests/test_stage_engine.py
|
||||
expected: PASS
|
||||
- id: TC-22
|
||||
type: integration
|
||||
description: "merge-gate FAIL уважает MAX_DEVELOPER_RETRIES — нет бесконечного цикла заворотов"
|
||||
module: tests/test_stage_engine.py
|
||||
expected: PASS
|
||||
- id: TC-23
|
||||
type: integration
|
||||
description: "merge-gate PASS → задача продвигается к слиянию/деплою, рассинхрона стадий нет"
|
||||
module: tests/test_stage_engine.py
|
||||
expected: PASS
|
||||
|
||||
# ---- сквозной сценарий гонки ----
|
||||
- id: TC-24
|
||||
type: integration
|
||||
description: >
|
||||
Воспроизведение бизнес-сценария: A и B от main@C0; A влита (main@C1);
|
||||
B проходит merge-gate → догоняется до C1 и re-test зелёный → безопасное слияние;
|
||||
при красном re-test B откатывается, main остаётся зелёным
|
||||
module: tests/test_merge_gate_race.py
|
||||
expected: PASS
|
||||
|
||||
# ---- конфигурация ----
|
||||
- id: TC-25
|
||||
type: unit
|
||||
description: "Новые ORCH_* настройки (merge_gate_enabled, merge_retest_timeout_s, merge_lock_timeout_s, merge_gate_repos) читаются с дефолтами и env-override"
|
||||
module: tests/test_config.py
|
||||
expected: PASS
|
||||
|
||||
# ---- регресс ----
|
||||
- id: TC-26
|
||||
type: integration
|
||||
description: "Полный набор pytest tests/ -q зелёный (существующие гейты/вебхуки/стадии не сломаны)"
|
||||
module: tests/
|
||||
expected: PASS
|
||||
Reference in New Issue
Block a user