Files
orchestrator/docs/work-items/ORCH-110/02-trz.md

13 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

02 — ТЗ (TRZ): ORCH-110 — merge-gate local re-test timeout: устранение ложного отката + утечки процессов

Work Item: ORCH-110 · Repo: orchestrator · Стадия: analysis

ТЗ описывает конкретные требования к реализации, выведенные из BRD и фактического кода. Архитектурное обоснование, выбор вариантов и контракт merge-gate — задача архитектора (06-adr, основание escalate: full-cycle). Здесь — поведение/контракты/инварианты и привязка к модулям, НЕ выбор механизма.

1. Сводка изменения

Устранить ложный откат deploy-staging → development, возникающий когда локальный re-test merge-gate падает по таймауту (инфра/ресурс), при зелёном tester PASS и зелёном CI. Изменение бьёт по двум корням и одному контракту: (1) утечка осиротевших pytest-процессов из оркестратор-спавненных прогонов re-test/coverage (источник CPU-голодания) → гарантировать tree-kill дерева подпроцесса при таймауте/kill; (2) классификация инфра-таймаута как транзиента (повтор/defer/инфра-alert), а не код-фейла (откат + расход developer-retry); (3) контракт необходимости локального re-test относительно зелёного CI и состояния branch vs origin/main. Сопутствующе — согласование бюджета re-test с реальным временем сюита. Всё — аддитивно, под kill-switch, never-raise, скоуп self-hosting, с сохранением исходной защиты merge-gate от семантического конфликта (красный re-test по-прежнему откатывает).

2. Задействованные модули / пути

Путь Действие
src/merge_gate.py изменить — retest_branch: жизненный цикл подпроцесса (tree-kill при таймауте/kill); классификация исхода «timeout» как транзиента (контракт возврата)
src/coverage_gate.py изменить — measure_coverage: тот же tree-kill при таймауте (сиблинг-источник утечки, BR-3)
src/qg/checks.py изменить — check_branch_mergeable: различать «timeout/infra» от «red re-test» в возвращаемом контракте (без смены имени/семантики зарегистрированного check_*)
src/stage_engine.py изменить — _handle_merge_gate / маршрутизация исхода: инфра-таймаут → defer/повтор/инфра-alert (по образцу _handle_merge_gate_defer), НЕ _handle_merge_gate_rollback; красный re-test → прежний rollback
src/config.py изменить — флаг(и) толерантности к инфра-таймауту + (опц.) согласование merge_retest_timeout_s; уважить сквозные инварианты merge_lock_timeout_s / reaper_max_running_s / coverage_run_timeout_s
docs/architecture/README.md, CLAUDE.md, CHANGELOG.md обновить — описание поведения merge-gate re-test (golden source наравне с кодом)
tests/test_* создать — покрытие по 04-test-plan.yaml

Точный набор новых символов/флагов и механизм tree-kill (process-group start_new_session+killpg, либо иной) — решение архитектора. ТЗ фиксирует что должно выполняться, не как.

3. Функциональные требования

FR-1 — Толерантность к инфра-таймауту re-test (нет ложного отката) [BR-1, BR-2]

Когда merge-gate локальный re-test завершается специфически по таймауту (а не детерминированно красным результатом), исход ДОЛЖЕН классифицироваться как транзиент/инфра, не код-фейл. Путь восстановления НЕ ДОЛЖЕН быть тем же _handle_merge_gate_rollback (откат на development + инкремент developer-retry), который при зелёных CI/tester ведёт к «Manual intervention needed». Допустимая реакция (выбор — архитектор): ограниченный повтор re-test и/или defer (по образцу существующего _handle_merge_gate_defer для merge-lock busy) и/или отдельный инфра-alert. Прецеденты толерантности к инфра: ORCH-061 (staging infra tolerance), ORCH-093 (transient vs terminal классификация merge-POST).

FR-2 — Tree-kill оркестратор-спавненных тест-процессов [BR-3]

merge_gate.retest_branch и coverage_gate.measure_coverage ДОЛЖНЫ гарантировать, что при таймауте (а также при любом kill/прерывании прогона) завершается всё дерево подпроцесса pytest, включая внуков, а не только прямой потомок. После таймаута ни один оркестратор-спавненный pytest-процесс не должен оставаться живым и грузить CPU. Контракт возврата retest_branch ((False, "re-test timeout after <T>s")) сохраняется; меняется лишь побочный эффект — отсутствие утечки. Существующий каскад launcher SIGTERM→grace→SIGKILL (stop_process) — образец на уровне агентов; для этих subprocess-прогонов требуется эквивалентная гарантия на уровне группы процессов.

FR-3 — Согласованность бюджета re-test [BR-4, NFR-6]

Бюджет merge_retest_timeout_s ДОЛЖЕН иметь достаточный запас над фактическим временем полного сюита (наблюдаемо: 600s бюджет vs 516.70s факт ≈ 16%). Бюджет остаётся конфигурируемым; при его изменении ДОЛЖНЫ соблюдаться сквозные инварианты: reaper_max_running_s > max(agent_timeout, бюджеты) + grace (ORCH-065/109) и согласование с merge_lock_timeout_s (TTL merge-lease держится на время re-test). Малформный/непозитивный конфиг → безопасный дефолт + WARNING (never-break).

FR-4 — Контракт необходимости локального re-test [BR-5, BR-6]

Merge-gate ДОЛЖЕН различать риск-кейсы и применять re-валидацию пропорционально реальному риску слияния:

  • ветка реально отстала от уехавшего origin/main и ребейзнута → семантический риск → re-test оправдан (текущая цель ORCH-043 сохраняется);
  • ветка уже актуальна / rebase — no-op, и CI по этому самому HEAD зелёный → локальный полный re-test пере-проверяет ровно подтверждённый CI коммит и не должен быть единственной точкой ложного отказа. Конкретный контракт (например: пропуск re-test при «не-behind + зелёный CI по HEAD», сокращённый scope, доверие SHA, подтверждённому CI, и т. п.) — выбор архитектора в ADR (ядро запрошенного баг-карточкой «анализа контрактов merge-gate»). Инвариант BR-6: детерминированно красный re-test (реальный сбой теста) обязан и далее откатывать на development — послабление применяется ТОЛЬКО к таймауту/инфра.

FR-5 — Сохранение инвариантов и kill-switch [NFR-1, NFR-2, NFR-3, NFR-4]

Изменение аддитивно: STAGE_TRANSITIONS / реестр QG_CHECKS / семантика check_* / machine-verdict ключи / схема БД — без изменений; merge-gate остаётся под-гейтом-врезкой, не новой стадией/QG. Под kill-switch: выключенный флаг → байт-в-байт прежнее поведение (таймаут → откат). Скоуп self-hosting (orchestrator); enduro — no-op. never-raise; INV-4 (никогда push/force-push main; merge только через Gitea PR API) и запрет рестарта прод-контейнера — соблюдены.

FR-6 — Наблюдаемость и ограниченность [BR-7, NFR-5]

Состояние «инфра-таймаут» ДОЛЖНО логироваться, уведомляться в Telegram (кликабельный номер задачи) и быть видимым read-only (например, расширение блока merge/merge_verify в GET /queue), отличимо от код-фейл-отката. Любой повтор/defer строго ограничен (число попыток + суммарное время); исчерпание → инфра-alert (не «developer must fix»). Координация с ORCH-111 (proc_blocking) — без дубля: ORCH-110 предотвращает/толерирует, ORCH-111 наблюдает.

4. Изменения API

Новых обязательных эндпоинтов не требуется. Допустимо (when-applicable, на усмотрение архитектора) read-only расширение существующего снимка GET /queue (блок merge-gate) полями наблюдаемости инфра-таймаута/повторов. Никаких новых управляющих эндпоинтов.

5. Изменения схемы БД

Нет. Счётчики повторов/defer — по образцу существующих (_merge_defer_count / _developer_retry_count поверх jobs/agent_runs) либо in-memory/sentinel; новые таблицы/колонки не вводятся (NFR-1).

6. Требования к новым/изменённым QG checks

Нет нового зарегистрированного QG. check_branch_mergeable остаётся в реестре QG_CHECKS с тем же именем и семантикой PASS/FAIL; меняется лишь различение причины FAIL (timeout/infra vs red) в возвращаемом reason и маршрутизация исхода во врезке _handle_merge_gate (advance_stage). STAGE_TRANSITIONS и состав QG_CHECKS — байт-в-байт.

7. Совместимость / регресс

  • Обратная совместимость: kill-switch off → поведение байт-в-байт как до ORCH-110 (таймаут → rollback на development), включая текст alert'ов.
  • Область раската: self-hosting orchestrator (как ORCH-035/043/058/071); прочие репо — no-op, путь LLM-deployer/прежний merge не затронут.
  • Обратимость: чисто аддитивная логика под флагом; откат = выключить флаг.
  • Self-hosting: без рестарта прод-контейнера; merge только через Gitea PR API; никаких операций с main (INV-4).
  • Анти-регресс целей merge-gate: красный re-test → прежний rollback (BR-6); защита от семантического конфликта/фантомного merge (ORCH-043/071/073) — не ослаблена.
  • Трассировка маркеров (ORCH-078): правки в merge_gate.py/coverage_gate.py/qg/checks.py затрагивают блоки с маркерами ORCH-043/071/073/093/027/065/109 — перед изменением сверить их 06-adr и не сломать зафиксированные инварианты (lease, never-raise, fail-open/closed, бюджеты).