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

18 KiB
Raw Blame History

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_branchpython -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)» → задача застряла, потребовалось ручное вмешательство.

Корень (подтверждён по коду):

  1. Утечка осиротевших процессов. merge_gate.retest_branch и coverage_gate.measure_coverage запускают subprocess.run([... pytest ...], timeout=...) без изоляции группы процессов (start_new_session/preexec_fn). При TimeoutExpired Python убивает только прямого потомка; внуки pytest репарентируются на PID 1 (tini жнёт зомби, но не убивает живых сирот) и живут сутками, грузя CPU. Это источник CPU-голодания (ровно симптом из фактов).
  2. Нет толерантности к инфра-таймауту. Re-test таймаут (ресурсная/инфра-причина) классифицируется идентично красному re-test (реальный дефект кода): оба → откат на development + расход developer-retry. Разработчик не может «починить» CPU-голодание → retry сгорают вхолостую и упираются в alert «Manual intervention needed».
  3. Тонкий бюджет. Бюджет re-test 600s практически равен фактическому времени сюита (516.70s); запас не растёт вместе с сюитом (ср. ORCH-109, где по той же причине были подняты бюджеты агентов developer/reviewer).
  4. Контракт необходимости 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'ы ломают цель «1020 задач за ночь без вмешательства».
  • Принимает результат: 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 (заполняет архитектор).