130 lines
13 KiB
Markdown
130 lines
13 KiB
Markdown
---
|
||
work_item: ORCH-110
|
||
stage: analysis
|
||
author_agent: analyst
|
||
status: ready-for-review
|
||
created_at: 2026-06-15
|
||
model_used: claude-opus-4-8
|
||
escalate: 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, бюджеты).
|