18 KiB
work_item, stage, author_agent, status, created_at, model_used, escalate
| work_item | stage | author_agent | status | created_at | model_used | escalate |
|---|---|---|---|---|---|---|
| ORCH-110 | analysis | analyst | ready-for-review | 2026-06-15 | claude-opus-4-8 | full-cycle |
01 — BRD (бизнес-требования): ORCH-110 — BUG: merge-gate local re-test timeout causes false rollback after green CI
Work Item: ORCH-110 · Repo: orchestrator · Стадия: analysis
Багфикс-трек → эскалация в полный цикл (
escalate: full-cycle). Задача помеченаBug, но сама баг-карточка требует «отдельный анализ вариантов и контрактов merge-gate» (см. «Ограничение» ниже) — это решение с несколькими проектными альтернативами и нетривиальными инвариантами self-hosting, которому нужен ADR. По правилу ORCH-019 (ADR-001 D5) выпускается полный analysis-пакет, а трек эскалируется (POST /bug-fast-track/escalate?work_item=ORCH-110) → задача проходит стадиюarchitecture. Прецедент — родственная задача ORCH-111 («bug → escalate full-cycle»).
1. Бизнес-контекст и проблема
Оркестратор — self-hosting инструмент: его прод-контейнер обслуживает конвейер всех проектов и
дорабатывает сам себя. На ребре deploy-staging → deploy исполняется детерминированный под-гейт
merge-gate (check_branch_mergeable, ORCH-043): он догоняет ветку до текущего origin/main
(auto_rebase_onto_main) и затем локально пере-прогоняет весь тест-сюит (retest_branch →
python -m pytest tests/ -q) в worktree, чтобы поймать семантический конфликт слияния (ветка
зелёная по своей базе, но ломает уехавший main).
Установленные факты инцидента (ORCH-109, PR #129):
- tester завершился
result: PASS; полный регресс —1899 passedза516.70s; - CI Gitea по HEAD — зелёный (push + pull_request success); PR после rebase — open,
mergeable=true; - merge-gate локальный re-test упал по таймауту:
re-test timeout after 600s(merge_retest_timeout_s = 600); - на хосте обнаружены старые зависшие pytest-процессы
tests/test_install_lite_script.py, жившие > 2 суток и грузившие CPU; прибиты вручную 2026-06-14.
Цепочка отказа. Зависшие осиротевшие pytest-процессы (CPU-голодание) → тот же сюит, что у tester
шёл 516.70s (запас до 600s ≈ 16%), под нагрузкой превысил 600s → check_branch_mergeable вернул
(False, "re-test timeout after 600s") → _handle_merge_gate_rollback: откат deploy-staging → development + developer-retry. Каждый из 3 retry повторно падал по тому же CPU-голоданию → финальный
alert «Merge-gate still failing after 3 developer retries (re-test timeout after 600s)» → задача
застряла, потребовалось ручное вмешательство.
Корень (подтверждён по коду):
- Утечка осиротевших процессов.
merge_gate.retest_branchиcoverage_gate.measure_coverageзапускаютsubprocess.run([... pytest ...], timeout=...)без изоляции группы процессов (start_new_session/preexec_fn). ПриTimeoutExpiredPython убивает только прямого потомка; внуки pytest репарентируются на PID 1 (tini жнёт зомби, но не убивает живых сирот) и живут сутками, грузя CPU. Это источник CPU-голодания (ровно симптом из фактов). - Нет толерантности к инфра-таймауту. Re-test таймаут (ресурсная/инфра-причина)
классифицируется идентично красному re-test (реальный дефект кода): оба → откат на
development+ расход developer-retry. Разработчик не может «починить» CPU-голодание → retry сгорают вхолостую и упираются в alert «Manual intervention needed». - Тонкий бюджет. Бюджет re-test
600sпрактически равен фактическому времени сюита (516.70s); запас не растёт вместе с сюитом (ср. ORCH-109, где по той же причине были подняты бюджеты агентов developer/reviewer). - Контракт необходимости re-test. На ветке, уже актуальной к
origin/main(rebase — no-op), и с зелёным CI по этому же HEAD локальный полный re-test пере-проверяет ровно тот коммит, что CI уже подтвердил, — становясь избыточной единственной точкой ложного отказа.
2. Объём (scope)
В объёме
- Поведение merge-gate при таймауте локального re-test: классификация и путь восстановления
(толерантность к инфра-таймауту вместо ложного отката на
development). - Жизненный цикл подпроцессов, которые оркестратор запускает САМ для проверок: re-test merge-gate
(
merge_gate.retest_branch) и coverage-run (coverage_gate.measure_coverage) — гарантия отсутствия осиротевших процессов после таймаута/kill. - Согласованность бюджета re-test с фактическим временем полного сюита (адекватный запас) с учётом сквозных инвариантов reaper/lease.
- Контракт необходимости локального re-test merge-gate (когда он реально нужен относительно
зелёного CI и состояния
branch vs origin/main) — анализ вариантов под решение архитектора. - Наблюдаемость инфра-таймаута (отличить «инфра-таймаут, повтор/defer» от «дефект кода → developer»).
Вне объёма
- Алерт sidecar-watchdog на осиротевший тест-процесс — это ORCH-111 (
proc_blocking, наблюдатель только сигналит, никогда не убивает, C-1). ORCH-110 — комплементарная сторона (предотвращение утечки + толерантность), дубля детекции не вводит. - Ручное умерщвление уже зависших хост-процессов — операционная мера (выполнена 2026-06-14), не код.
- Любые правки
STAGE_TRANSITIONS/ реестраQG_CHECKS/check_*-семантики / machine-verdict ключей / схемы БД (инвариант NFR-1). - Изменение конкретного теста
tests/test_install_lite_script.py(его поведение — отдельный предмет; здесь важен класс «оркестратор-спавненный pytest не должен переживать свой бюджет»). - Поведение не-self-hosting репозиториев (enduro-trails) — нулевая регрессия.
- Изменение хука прод-деплоя/рестарт прод-контейнера (self-hosting безопасность).
3. Заинтересованные стороны
- Owner / оператор self-hosting — страдает от ручного разбора застрявших задач и зависших процессов; заказчик исправления.
- Конвейер всех проектов — общий прод-контейнер: утечка CPU деградирует обслуживание enduro.
- Пакетный автономный режим (эпик ORCH-088) — ложные откаты и manual-gate'ы ломают цель «10–20 задач за ночь без вмешательства».
- Принимает результат: reviewer → tester → deployer штатного конвейера.
4. Бизнес-требования (BR)
- BR-1 — Зелёный путь без ручного вмешательства. При зелёном tester
PASSи зелёном CI задача не должна требовать ручного вмешательства из-за инфраструктурного/локального re-test таймаута (прямое «Ожидаемое поведение» баг-карточки). - BR-2 — Инфра-таймаут ≠ дефект кода. Таймаут локального re-test merge-gate (ресурсная/инфра
причина) не должен трактоваться как код-фейл: путь восстановления не должен сжигать
developer-retry и приводить к «Manual intervention after N developer retries», если CI и tester
были зелёными. Реакция на таймаут — ограниченный повтор/defer и/или отдельный инфра-сигнал, не
безусловный откат на
development. - BR-3 — Нет осиротевших процессов. Подпроцессы pytest, запущенные самим оркестратором для re-test и coverage-run, должны полностью завершаться (всё дерево, включая внуков) при таймауте/kill. Ни один оркестратор-спавненный pytest не должен переживать свой бюджет и продолжать грузить CPU.
- BR-4 — Адекватный бюджет re-test. Бюджет времени re-test должен иметь достаточный запас над фактическим временем полного сюита, чтобы здоровый сюит при штатной нагрузке не падал по таймауту; бюджет конфигурируем и со-эволюционирует с ростом сюита.
- BR-5 — Контракт необходимости re-test. Merge-gate должен различать «ветка реально отстала
от уехавшего
origin/mainи была ребейзнута» (риск семантического конфликта → re-test оправдан) и «ветка уже актуальна / rebase — no-op, CI по этому HEAD зелёный» (re-test избыточен). Локальный re-test не должен быть избыточной единственной точкой ложного отказа на коммите, уже подтверждённом CI. Конкретный контракт (skip/scope/trust-CI-SHA) выбирает архитектор и фиксирует в ADR. - BR-6 — Сохранение защиты от семантического конфликта. Толерантность к таймауту не должна
ослаблять исходную цель merge-gate (ORCH-043): детерминированно красный re-test (реальный сбой
теста, а не таймаут) по-прежнему обязан откатывать на
development. Послабление применяется ТОЛЬКО к таймауту/инфра, никогда к красному результату. - BR-7 — Наблюдаемость. Состояние «инфра-таймаут» должно быть видимым (лог + Telegram с
кликабельным номером + read-only в
GET /queue) и отличимым от код-фейл-отката; согласовано с сигналом ORCH-111 (без дубля).
5. Нефункциональные требования (NFR)
- NFR-1 — Инварианты конвейера неприкосновенны.
STAGE_TRANSITIONS/ реестрQG_CHECKS/ семантикаcheck_*/ machine-verdict ключи (verdict:/result:/deploy_status:/staging_status:/security_status:/coverage_status:) / схема БД — байт-в-байт прежние. Исправление — аддитивное (врезка/leaf-логика), не новая стадия и не новый зарегистрированный QG. - NFR-2 — Kill-switch + нулевая регрессия. Новое поведение под флагом; при выключенном флаге —
поведение байт-в-байт как до ORCH-110 (таймаут → прежний откат). Скоуп — self-hosting
(
orchestrator); enduro не затронут. - NFR-3 — Self-hosting безопасность. Исправление никогда не пушит/force-push в
main(INV-4; merge только через Gitea PR-merge API), не рестартит прод-контейнер, не трогает detached-деплой. - NFR-4 — never-raise. Любая ошибка в новом пути → безопасный дефолт + WARNING; исключение
никогда не уходит в
advance_stage/monitor-поток (контракт merge-gate сохранён). - NFR-5 — Ограниченность (anti-loop). Любой повтор/defer таймаута строго ограничен по числу попыток и суммарному времени; исчерпание → чёткий инфра-alert, отличный от «developer must fix», а не бесконечный bounce и не молчаливое зависание.
- NFR-6 — Сквозные инварианты времени. Любое изменение бюджета re-test должно уважать
существующие соотношения:
merge_lock_timeout_s(TTL merge-lease),reaper_max_running_s(Tier-3 backstop reaper, ORCH-065/109),coverage_run_timeout_s— без рассинхрона.
6. Допущения и ограничения
- Ограничение из баг-карточки (дословно): «Решение намеренно не описано в этой баге; нужен отдельный анализ вариантов и контрактов merge-gate». → Аналитик фиксирует требования и тест-план; варианты и контракт merge-gate прорабатывает архитектор (06-adr) — основание эскалации в полный цикл.
- Допущение: tini (PID 1) жнёт зомби, но не убивает живых осиротевших процессов (подтверждено поведением инцидента) — отсюда требование tree-kill (BR-3).
- Допущение: таймаут merge-gate re-test в зелёном инциденте вызван внешним CPU-голоданием, а не реальным зависанием теста ветки; но решение обязано остаться fail-safe к случаю реального зависшего теста (см. Риск R-2 / BR-6).
- Среда верификации — staging-контур (8501), обязательная страховка перед прод-деплоем self.
7. Критерии успеха
Резюме: зелёный tester PASS + зелёный CI + актуальная ветка → задача доходит до deploy без
ложного отката на development и без manual-gate из-за инфра-таймаута; оркестратор-спавненные
pytest-процессы не переживают свой бюджет; реальный красный re-test по-прежнему откатывает на
development; инварианты конвейера и self-hosting не тронуты. Детальные PASS/FAIL —
03-acceptance-criteria.md.
8. Риски
- R-1 — Над-толерантность маскирует реальный зависший тест (бесконечный/долгий) как «инфра» → смягчение: строгая ограниченность (NFR-5) + отдельный инфра-alert + сохранение красно-откат-пути (BR-6).
- R-2 — Поднятие бюджета без правки tree-kill лишь отодвигает отказ (сюит растёт) → исправление должно бить корень (BR-3), бюджет (BR-4) — вторично.
- R-3 — Рассинхрон сквозных таймаутов (reaper/lease) при изменении бюджета (NFR-6).
- R-4 — Дубль/конфликт с сигналом ORCH-111 (
proc_blocking) → координация: ORCH-110 предотвращает/толерирует, ORCH-111 наблюдает; разные слои. - Детальная оценка и митигации —
10-tech-risks.md(заполняет архитектор).