12 KiB
work_item, stage, author_agent, status, created_at, model_used
| work_item | stage | author_agent | status | created_at | model_used |
|---|---|---|---|---|---|
| ORCH-093 | analysis | analyst | ready-for-review | 2026-06-09 | claude-opus-4-8 |
01 — BRD (бизнес-требования): ORCH-093 — merge-актор не ретраит транзиентные ошибки Gitea (405/5xx) → ложный HOLD + мусорные PR
Work Item: ORCH-093 · Repo: orchestrator · Стадия: analysis
1. Бизнес-контекст и проблема
Тип: BUG (надёжность self-deploy merge-фазы). Найдено по инциденту ORCH-063 (09.06).
Инцидент-первоисточник (ORCH-063, 09.06). Прод-деплой self-hosting прошёл, staging OK, но при
мерже PR в main Gitea вернул HTTP 405 {"message":"Please try again later"} — транзиентная икота
(Gitea пересчитывал mergeable сразу после пуша). PR #98 был open + mergeable=True, конфликтов
не было. Однако merge-актор merge_gate.merge_pr() — one-shot: на любой не-200/201 он сразу
вернул (False, "merge failed: HTTP 405"). Сработала корректная защита ORCH-071/081 «deploy
succeeded but not merged» → задача удержана на deploy (НЕ done), алерт, потребовался ручной
домерж (повтор merge_pr вручную → смержилось с первого раза). Защита отработала верно, но
транзиент не должен был требовать человека.
Два дефекта, оба верифицированы по коду прода src/merge_gate.py:
- ДЕФЕКТ 1 —
merge_prне ретраит транзиентные HTTP-ошибки.merge_gate.merge_pr()(src/merge_gate.py~700) делает одинPOST /pulls/{index}/merge; на любой не-200/201 (включая405 "try again later",5xx,409/422«ещё считается mergeable») сразуreturn False, "merge failed: HTTP {code}"— без ретрая. Сравни: у Claude-агентов есть transient-breaker (429/overloadретраится), у merge-актора такого механизма нет → инфра-икота Gitea = ложный HOLD. - ДЕФЕКТ 2 —
ensure_open_prплодит мусорные PR на уже влитой ветке. При повторном прогоне финализатора после ручного мержа: PR #98 ужеmerged+closed→ensure_open_pr(src/merge_gate.py~605) не находит открытого code-PR → создаёт новый пустой PR #99 (ветка уже вmain, diff пустой). Пришлось закрывать вручную.
Боль: ложные HOLD при инфра-икоте Gitea требуют ручного вмешательства в автономный конвейер (эпик ORCH-088 — пакетный автономный прогон) и оставляют мусорные пустые PR.
2. Объём (scope)
В объёме
merge_prретраит транзиентные ошибки мержа (405/«try again», 408,5xx, таймаут/сетевые, а также409/422когда PR всё ещё mergeable) с ограниченным числом попыток и backoff — перед тем как вернутьFalse.- Различение «mergeable, но Gitea временно отказал» (ретраить) vs «реальный конфликт / не-mergeable» (НЕ ретраить, честный быстрый HOLD).
ensure_open_pr/ merge-verify не создаёт новый PR, если ветка уже полностью вmain(нет коммитовorigin/main..branch) — возвращает исход «already-in-main»; финализатор сразу доводит доdoneбез мусорного PR.- Конфигурируемость (число ретраев, backoff, kill-switch на ретрай-поведение); разумные дефолты.
- Обновление
.env.example,CHANGELOG.md, merge-gate-раздела документации.
Вне объёма
- ❌ Снятие/ослабление защиты ORCH-071/081 «deploy succeeded but not merged» — она корректна; задача лишь снижает ложные срабатывания на транзиентах.
- ❌ Ретрай реального конфликта / не-mergeable — это законный HOLD, нужен человек.
- ❌ Любые прямые
push/force-pushвmain(инвариант INV-4 ORCH-071/073 — мерж только через Gitea PR-merge API). - ❌ Изменение
STAGE_TRANSITIONS, составаQG_CHECKS, схемы БД. - ❌ Изменение SHA-in-main-доказательства мержа (
verify_merged_to_main) как источника истины.
3. Заинтересованные стороны
- Заказчик / оператор автономного конвейера (Owner, Стрим) — меньше ручных домержей, чище список PR в Gitea.
- Self-hosting репо
orchestrator— основной потребитель merge-verify under-gate (ORCH-071); изменение в первую очередь касается self-deploy merge-фазы. - Все проекты на общем инстансе — косвенно: меньше зависших на
deployзадач, держащих merge-lease и клинящих serial-gate репо (ORCH-088). - Reviewer / tester — принимают результат по AC и зелёному
pytest.
4. Бизнес-требования (BR)
- BR-1 — При транзиентной ошибке мержа (
405/«Please try again later»,408,5xx, таймаут/сетевая ошибка)merge_prповторяетPOST …/mergeдоNраз с backoff, прежде чем вернуть(False, …); успешный повтор внутри бюджета →(True, …), мерж выполнен. - BR-2 —
merge_prразличает «PR mergeable, Gitea временно отказал» (ретраить) и «реальный конфликт / PR не mergeable» (НЕ ретраить). Различение опирается на код ответа и полеmergeablePR (GET /pulls/{n}). Неоднозначный409/422классифицируется поmergeable. - BR-3 — Терминальные ошибки (
404нет PR / реальный конфликт /403) НЕ ретраятся —merge_prвозвращает(False, …)быстро; честный HOLD (защита ORCH-071/081) сохраняется. - BR-4 — При исчерпании ретраев
merge_prвозвращает(False, …)с понятным reason; защита «deploy succeeded but not merged» срабатывает как прежде (HOLD + алерт). - BR-5 — Если ветка уже полностью в
main(нет коммитовorigin/main..branch),ensure_open_prНЕ создаёт PR — возвращает исход «already-in-main»; merge-verify доводит задачу доdoneбез мусорного пустого PR. - BR-6 — Поведение ретрая конфигурируемо: число попыток, backoff и kill-switch; дефолты разумны
(≈3 попытки, backoff 2–5 с) и задокументированы в
.env.example. - BR-7 — При выключенном ретрай-kill-switch поведение
merge_prидентично текущему (one-shot) — нулевая регрессия.
5. Нефункциональные требования (NFR)
- NFR-1 (never-raise) — Контракт never-raise
merge_pr/ensure_open_prсохранён: любая HTTP/parse/сетевая ошибка →(False, …)/("failed"|"already-in-main", …), исключение никогда не пробрасывается в_handle_merge_verify/advance_stage. - NFR-2 (self-hosting safety / INV-4) — Никаких прямых
push/force-pushвmain; мерж только через Gitea PR-merge API. Прод-контейнерorchestratorне перезапускается этой задачей. - NFR-3 (обратимость / kill-switch) — Ретрай-поведение полностью отключаемо одним флагом → откат к нынешнему one-shot без изменения кода.
- NFR-4 (ограниченность) — Суммарное время ретраев ограничено (
N× backoff_max) и не может «подвесить» monitor-поток, исполняющий merge-verify; backoff с верхним потолком. - NFR-5 (идемпотентность) — Повторный прогон финализатора на уже влитой ветке безопасен и
бесследен (нет дублей PR, нет дублей мержа — переиспользуется
pr_already_merged). - NFR-6 (наблюдаемость) — Каждый ретрай и его причина логируются (по образцу
check_ci_green:attempt i/N); исход (успех/исчерпание/терминал) различим в логе.
6. Допущения и ограничения
- Gitea-код
405 {"message":"Please try again later"}— транзиент (Gitea пересчитываетmergeableсразу после пуша);5xx/таймаут/сетевая — транзиент. 409(conflict) и422(unprocessable) двойственны: либо реальный конфликт, либо «ещё не пересчитан mergeable». Источник различения — полеmergeableизGET /pulls/{n}(а не только код):mergeable==True→ транзиент (ретраить),mergeable==False→ реальный конфликт (НЕ ретраить).404(нет PR) обрабатывается раньше шагом «no open PR» и/или трактуется как терминал.- Образец паттерна ретрая уже есть в репо:
check_ci_green(src/qg/checks.py, attempts + interval- backoff) и transient-breaker агентов (
backoff_base_seconds/backoff_max_seconds/transient_max_attemptsвconfig.py).
- backoff) и transient-breaker агентов (
- Merge-verify under-gate (ORCH-071) реален только для self-hosting (
merge_verify_applies); на прочих репо мерж делает LLM-deployer — там изменениеmerge_prне задействуется. - Изменение точечное в
src/merge_gate.py+ флаги вsrc/config.py;STAGE_TRANSITIONS,QG_CHECKS, схема БД не трогаются.
7. Критерии успеха
merge_pr переживает транзиентную икоту Gitea (405/5xx/таймаут/«not mergeable yet») за счёт
ограниченного ретрая с backoff и больше не даёт ложного HOLD; реальный конфликт по-прежнему даёт
быстрый честный HOLD; ensure_open_pr не создаёт мусорных PR на уже влитой ветке; поведение
конфигурируемо и отключаемо; never-raise сохранён; pytest tests/ -q зелёный; доки и .env.example
обновлены. Детальные PASS/FAIL — 03-acceptance-criteria.md.
8. Риски
- Слишком агрессивный ретрай реального конфликта → задержка честного HOLD (митигируется BR-2/BR-3:
классификация по
mergeable). - Ошибочная классификация транзиента как терминала (или наоборот) при неполном ответе Gitea
(
mergeable=None) — нужна осторожная дефолт-политика. - Гонка
ensure_open_pralready-in-main vs параллельный мерж.
Детали и оценка — 10-tech-risks.md (заполняет архитектор).