Files
orchestrator/docs/work-items/ORCH-082/01-brd.md

120 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 соблюдён.