120 lines
9.3 KiB
Markdown
120 lines
9.3 KiB
Markdown
# 01 — BRD: ORCH-082 (ORCH-81)
|
||
|
||
**Конвейер не создаёт PR для ветки → деплой стопорится на merge-verify (HOLD)**
|
||
|
||
- Work Item: **ORCH-082** (Plane-заголовок «ORCH-81»)
|
||
- Repo: `orchestrator` (self-hosting)
|
||
- Тип: **Багфикс / надёжность конвейера**
|
||
- Приоритет: **HIGH** — блокирует автономный деплой
|
||
- Зона: создание PR (reviewer/developer/deployer пути), `src/merge_gate.py`, `src/stage_engine.py` (`_handle_merge_verify`), `src/agents/launcher.py` (`_ensure_pr`)
|
||
|
||
---
|
||
|
||
## 1. Контекст и проблема
|
||
|
||
При деплое **ORCH-074** (08.06, статус «Confirm Deploy») детерминированный finalizer
|
||
(`run_deploy_finalizer` → под-гейт `_handle_merge_verify`) вызвал
|
||
`merge_gate.merge_pr(repo, branch)` и получил **`ok=False` («no open PR»)**: в Gitea для
|
||
ветки `feature/ORCH-074-…` **не существовало открытого PR** с `head.ref==branch` и
|
||
`base.ref=="main"`.
|
||
|
||
Защита **ORCH-073** (fail-closed по «SHA-в-main») отработала **корректно**: задача удержана
|
||
на стадии `deploy` (НЕ `done`), Plane → Blocked, Telegram-alert, ложно-зелёного `done` не
|
||
произошло. Это **правильное** поведение для случая «merge реально невозможен».
|
||
|
||
**Дефект не в защите, а в инварианте до неё:** автономный конвейер **не гарантировал**, что к
|
||
моменту merge у ветки существует открытый PR. PR на сегодня создаётся ровно в одном месте —
|
||
`launcher._ensure_pr`, вызываемом **только** на пути `agent == "developer"` и **только** когда
|
||
в этом конкретном run был непустой git-diff, успешный commit и успешный push (см. root-cause
|
||
ниже). Любой сценарий, где developer-run не произвёл свежий коммит, оставляет ветку **без PR**,
|
||
и задача неминуемо застревает на merge-verify.
|
||
|
||
### Workaround, применённый вручную (НЕ фикс)
|
||
PR #79 создан вручную через Gitea API (`mergeable=True`) → штатно перезапущен
|
||
`run_deploy_finalizer` → `merge_pr` честно влил код в `main` → задача `done`. Это разовое ручное
|
||
вмешательство, **не** устранение причины.
|
||
|
||
### Почему это системный пробел, а не разовый сбой
|
||
Так как создание PR **не гарантировано конвейером**, любая следующая задача с тем же стечением
|
||
обстоятельств (developer-run без нового коммита; тихо упавший вызов создания PR; ветка
|
||
восстановлена/пересоздана вручную) застрянет на merge-verify тем же образом. Автономность
|
||
деплоя (цель ORCH-54) этим заблокирована.
|
||
|
||
---
|
||
|
||
## 2. Root cause (предварительный аудит кода — подтвердить логами G1)
|
||
|
||
PR создаётся **исключительно** функцией `AgentLauncher._ensure_pr` (`src/agents/launcher.py`),
|
||
которая вызывается из `_monitor_agent` по цепочке условий:
|
||
|
||
```
|
||
exit_code == 0
|
||
→ есть worktree-изменения (git status --porcelain непусто)
|
||
→ git commit succeeded
|
||
→ git push succeeded
|
||
→ agent == "developer" ←── ТОЛЬКО здесь вызывается self._ensure_pr(...)
|
||
```
|
||
|
||
Отсюда минимум три структурных способа остаться без PR:
|
||
|
||
- **R-A (условное создание).** Если developer-run завершился без изменений (`git status`
|
||
пустой) — ветка уже была закоммичена/запушена в прошлый run, бойнс REQUEST_CHANGES без новых
|
||
правок, повторный прогон, или ручное восстановление ветки — `_ensure_pr` **не вызывается
|
||
вовсе**. PR не появится никогда. (Соответствует гипотезе ТЗ №2.)
|
||
- **R-B (тихий сбой создания).** `_ensure_pr` ловит любое исключение
|
||
(`except Exception → logger.error → return None`): транзиентная ошибка Gitea на шаге
|
||
`POST …/pulls` теряется без ретрая и без эскалации. Конвейер «думает», что developer
|
||
отработал, и едет дальше. (Гипотеза ТЗ №1 — silent fail.)
|
||
- **R-C (разъехавшееся состояние ветки/PR).** ORCH-074 — первая задача после серии ручных
|
||
восстановлений `main` 08.06. PR мог быть закрыт/пересоздан, либо у ветки остался только
|
||
авто-docs-PR (`base != main`), который `merge_pr`/`pr_already_merged` корректно НЕ считают
|
||
кодовым PR. (Гипотеза ТЗ №4.)
|
||
|
||
Идемпотентность (гипотеза №3): сам `_ensure_pr` идемпотентен на чтении (сначала `GET …open&head`,
|
||
создаёт только если пусто), но он не запускается вне «свежий developer-коммит», поэтому
|
||
идемпотентность не достигает merge-стадии — никакой флаг «PR создан» в БД не хранится.
|
||
|
||
**Вывод:** гарантия «к моменту merge у ветки есть открытый код-PR» в конвейере **отсутствует**.
|
||
|
||
---
|
||
|
||
## 3. Бизнес-цели
|
||
|
||
| ID | Цель |
|
||
|----|------|
|
||
| **G1** | Установить и задокументировать точную причину отсутствия PR на ORCH-074 (код-аудит + логи run_id 396/398). |
|
||
| **G2** | Гарантировать инвариант: к моменту merge-verify у ветки **есть** открытый код-PR; если его нет — finalizer/deployer создаёт его сам, **идемпотентно**, ПЕРЕД `merge_pr`, вместо HOLD на ручное вмешательство. |
|
||
| **G3** | Явно логировать факт PR: **PR-created / PR-existed / PR-create-failed** (наблюдаемость). |
|
||
|
||
## 4. Не-цели (явные границы)
|
||
|
||
- НЕ ослаблять защиту ORCH-073: fail-closed по «SHA-в-main» остаётся. Реальная невозможность
|
||
merge → по-прежнему HOLD + alert.
|
||
- НЕ авто-мержить без PR (PR — обязательный артефакт ревью/слияния).
|
||
- НЕ создавать PR в неподходящий момент — только на ребре `deploy → done`, ПОСЛЕ прохождения
|
||
всех гейтов (security/merge-gate/staging/image-freshness уже пройдены).
|
||
- НЕ менять `STAGE_TRANSITIONS`, реестр `QG_CHECKS`, схему БД, контракты `check_deploy_status`,
|
||
exit-коды хука.
|
||
|
||
## 5. Заинтересованные стороны
|
||
- **Owner** (homenet542) — автономность деплоя орка.
|
||
- Все проекты на инстансе (enduro-trails) — общий прод/очередь: ложный HOLD self-задачи не
|
||
должен требовать ручного вмешательства, а реальный дефект merge — обязан удерживаться.
|
||
|
||
## 6. Бизнес-риски и допущения
|
||
- **Грабли (из ORCH-073):** у ветки может быть несколько PR (код-PR + авто docs-PR). Создание/
|
||
выбор PR обязан фильтровать `head.ref==branch` И `base.ref=="main"`, иначе слияние/верификация
|
||
схватят не тот PR.
|
||
- **Допущение:** merge-verify исполняется ПОСЛЕ всех гейтов, поэтому создание PR именно здесь не
|
||
обходит ревью и безопасно по времени.
|
||
- **Контракт надёжности:** весь новый путь — **never-raise**; ошибка создания PR (Gitea
|
||
недоступна) → честный HOLD + alert, а не исключение в `advance_stage`.
|
||
|
||
## 7. Definition of Done (бизнес-уровень)
|
||
1. Root cause задокументирован (`06-adr/` архитектором, ссылка из ADR на этот BRD).
|
||
2. После фикса задача с веткой без PR не зависает: конвейер создаёт PR идемпотентно и доводит до
|
||
`done` (при честном merge).
|
||
3. Защита ORCH-073 цела (регресс-тест на «код не в main» → HOLD).
|
||
4. Логи различают created/existed/failed.
|
||
5. `pytest` зелёный; never-raise соблюдён.
|