16 KiB
work_item, stage, author_agent, status, created_at, model_used, escalate
| work_item | stage | author_agent | status | created_at | model_used | escalate |
|---|---|---|---|---|---|---|
| ORCH-113 | analysis | analyst | ready-for-review | 2026-06-15 | claude-opus-4-8 | full-cycle |
01 — BRD (бизнес-требования): ORCH-113 — BUG: job-reaper не должен повторно запускать финализацию deploy-staging, пока жив исходный finalizer
Work Item: ORCH-113 · Repo: orchestrator · Стадия: analysis
Багфикс-трек → эскалация в полный цикл (
escalate: full-cycle). Задача помеченаBug, но сама баг-карточка явно требует «анализ контракта reaper, статусаrunning/finalizing, длительности grace и идемпотентности edge-гейтов» (см. «Ограничение» в бизнес-запросе) — это решение с несколькими проектными альтернативами (liveness-heartbeat finalizer'а / явный sub-statefinalizing/ per-stage grace / ownership-lease на edge-гейты) и нетривиальными инвариантами self-hosting, затрагивающее задокументированный сквозной инвариант ORCH-065 (контракт живости reaper,adr-0011). По правилу ORCH-019 (ADR-001 D5) выпускается полный analysis-пакет, а трек эскалируется (POST /bug-fast-track/escalate?work_item=ORCH-113) → задача проходит стадиюarchitecture. Прецедент — родственные задачи того же инцидент-кластера: ORCH-110 / ORCH-111 («bug → escalate full-cycle»).
1. Бизнес-контекст и проблема
Оркестратор — self-hosting инструмент: его прод-контейнер обслуживает конвейер всех проектов из
одного инстанса с общей БД и общей очередью и дорабатывает сам себя. Фоновый демон job-reaper
(src/job_reaper.py, ORCH-065) — страховочный слой: он добивает «зомби»-job'ы, чей монитор умер,
не записав терминальный статус. Его Tier-2-ветка (процесс агента завершился — agent_runs.exit_code
записан, — но job всё ещё running) неоднозначна: это одновременно «монитор умер посреди
финализации» И «живой монитор ещё финализирует». Reaper разрешает неоднозначность таймером —
finalization grace reaper_finalize_grace_s = 300 (db.py:1345-1348, job_reaper.py:36-44): если
exit_code записан дольше grace — трактует ситуацию как мёртвый монитор и сам до-водит стадию.
Корневая ошибка контракта: grace=300с построен на задокументированном допущении, что после записи
finished_at монитор делает лишь «git commit/push (+PR), БАГ-8-проверку и сетевые Plane-комментарии —
секунды…десятки секунд, и ТОЛЬКО ПОТОМ _try_advance_stage». Для ребра deploy-staging → deploy
это неверно: _try_advance_stage (launcher._monitor_agent, строка 998) синхронно, в потоке
монитора, прогоняет весь набор тяжёлых детерминированных edge-под-гейтов —
security → merge-gate (полный локальный re-test, бюджет merge_retest_timeout_s = 900) →
coverage (ещё один полный pytest --cov) → image-freshness (docker-rebuild + пересоздание
staging). Это минуты, и весь этот объём идёт ПОСЛЕ штампа finished_at (launcher:861) и
ДО _finalize_job (launcher:1003). finished_age_s reaper меряет именно от finished_at
(db.py:1360) — то есть от начала финализации. Итог: пока живой монитор ещё внутри edge-гейтов,
reaper по истечении 300с считает его мёртвым и независимо запускает тот же тяжёлый набор edge-гейтов
повторно.
Установленные факты инцидента (ORCH-111, deployer job 1914, run_id 683):
- deployer завершился
exit_code=0и записал15-staging-log.mdсоstaging_status: SUCCESS; - после выхода агента job
1914оставаласьrunning, т.к. монитор ещё финализировал стадию; - финализация
deploy-stagingзаняла дольше 300с (полный локальный re-test + coverage); - reaper после
reaper_finalize_grace_s=300трактовал это как умерший monitor и повторно вызвал gate-driven advance (_reap_exit0→_gate_driven_advance→_try_advance_stage→advance_stage); - edge-гейты
deploy-stagingисполнились повторно:security-gate,merge-gate, rebase, полный локальный re-test; - один из повторных re-test стал красным:
3 failed, 1916 passed, 1 warning, 14 errors in 444.79s; - задача была откатана
deploy-staging → development(+ ложный developer-retry), хотя параллельно исходный prod-deploy/finalizer дошёл доdeploy_status: SUCCESS, PR #130 был смержен, задача перешлаdeploy → done.
Симптом: две ветки системы расходятся по состоянию одной задачи — одна повторно откатывает
deploy-staging, другая успешно завершает deploy. Гонка + ложный rollback + ложный developer-retry +
шумные алерты + несогласованное состояние Plane/БД.
Почему существующие гарды reaper не спасли: атомарный claim-before-act
(reap_running_job(... WHERE status='running'), job_reaper.py:280) защищает строку job от
двойного терминального флипа, но не защищает побочное исполнение edge-гейтов: reaper вызывает
_gate_driven_advance → advance_stage, который и прогоняет тяжёлые под-гейты, до/независимо от
монитора. Гонка — в side-effectful исполнении edge-гейтов, а не в флипе строки. Дешёвая
read-only пред-проверка _gate_is_green('deploy-staging') читает лишь check_staging_status
(frontmatter 15-staging-log.md = SUCCESS, зелёный) → reaper уверенно идёт в тяжёлый advance.
Tier-3 backstop (reaper_max_running_s = 5400) при этом не срабатывает — баг чисто в Tier-2 grace.
2. Объём (scope)
В объёме
- Reaper не должен повторно исполнять тяжёлую финализацию
deploy-staging/merge-gate (security / merge-gate / локальный re-test / coverage / image-freshness), пока исходный monitor/finalizer ещё жив или пока edge-гейты для этого job/stage уже исполняются. - Повторная обработка завершившегося-но-ещё-
runningjob наdeploy-stagingдолжна быть идемпотентной: без второго локального re-test/merge-gate для того же job/stage без строгого владения состоянием. - Согласование Tier-2 grace (
reaper_finalize_grace_s) с фактической wall-clock-длительностью финализацииdeploy-stagingИЛИ замена таймерного критерия живости на сигнал, переживающий «долгую, но живую» финализацию. - Сохранение основной функции reaper (ORCH-065): реально мёртвый finalizer на
deploy-stagingпо-прежнему добивается за ограниченное время.
Вне объёма
- Изменение
STAGE_TRANSITIONS/QG_CHECKS/ семантики любогоcheck_*/ machine-verdict ключей / схемы существующих таблиц (правки — только аддитивные). - Инфра-толерантность merge-gate к таймауту re-test и tree-kill осиротевших pytest-процессов — это ORCH-110 (союзная задача того же инцидента; не дублировать).
- Починка конкретных «мигающих» тестов, давших
3 failed … 14 errors. - Полный редизайн reaper или модели финализации монитора.
- Выбор механизма решения (heartbeat / sub-state
finalizing/ per-stage grace / ownership-lease) — это архитектурное решение (06-adr), не зона аналитика.
3. Заинтересованные стороны
- Owner / Слава — заказчик исправления, держатель инвариантов self-hosting.
- Конвейер всех проектов (orchestrator self-hosting + enduro-trails) — общий инстанс/БД/очередь: ложный rollback и гонка состояния касаются стабильности платформы в целом.
- Операторы — получатели алертов; именно их будят ложные «merge-gate FAILED / rolled back».
- Архитектор — принимает решение по механизму владения/живости (06-adr) после эскалации.
4. Бизнес-требования (BR)
- BR-1 — Reaper не должен запускать второй прогон edge-гейтов ребра
deploy-staging → deploy(security / merge-gate / re-test / coverage / image-freshness) для job, чей исходный monitor/finalizer ещё жив. - BR-2 — Повторная обработка завершившегося-но-
runningjob наdeploy-stagingидемпотентна: не более одного локального re-test/merge-gate на пару (job, stage) без строгого владения состоянием; второй актор, не владеющий состоянием, не исполняет побочных шагов. - BR-3 — Критерий живости Tier-2 должен учитывать реальную wall-clock-длительность
финализации
deploy-staging(включающую полный набор edge-гейтов), ИЛИ живость должна определяться сигналом, который переживает долгую-но-живую финализацию (не однимfinished_age_s). - BR-4 — Реально мёртвый монитор (краш посреди финализации
deploy-staging) по-прежнему должен добиваться reaper'ом за ограниченное время — основная функция ORCH-065 сохраняется; фикс не превращает reaper в no-op дляdeploy-staging. - BR-5 — После согласования у задачи — единственное консистентное состояние: никакого
ложного отката
deploy-staging → developmentи никакого ложного developer-retry после фактически успешного deploy; ветки системы сходятся, не расходятся.
5. Нефункциональные требования (NFR)
- NFR-1 — Контракт reaper сохранён: never-raise на единицу работы, kill-switch, fail-safe; reaper остаётся наблюдателем-страховкой, не Quality Gate'ом.
- NFR-2 —
STAGE_TRANSITIONS/QG_CHECKS/ каждыйcheck_*/ machine-verdict ключи / схема существующих таблиц — байт-в-байт; любые БД-правки — только аддитивные (_ensure_column/CREATE TABLE IF NOT EXISTS). - NFR-3 — Self-hosting-безопасно: фикс никогда не рестартит/не роняет прод-контейнер и
никогда не пушит/force-push'ит
main. - NFR-4 — Обратная совместимость и обратимость: поведение reaper для не-
deploy-stagingстадий и путь добивания мёртвого монитора сохранены; выключенный kill-switch → строго прежнее поведение; раскат обратим. - NFR-5 — Restart-safe: in-memory состояние reaper сбрасывается при рестарте (это покрыто
стартовым
requeue_running_jobs); любой новый маркер владения/живости должен быть либо durable, либо безопасно восстановимым после рестарта. - NFR-6 — Сквозной инвариант ORCH-065/109/110 сохранён:
reaper_max_running_s (5400) > Σ(deploy-staging gate-work) + grace(Tier-3 backstop). Любая правка grace/таймаутов не должна его нарушить.
6. Допущения и ограничения
- Задача помечена
Bug; ввиду архитектурной природы — эскалация в полный цикл (нужен ADR + анализ тех-рисков архитектором: 06-adr / 07 / 08 / 10). - Инстанс общий для всех проектов (общая БД/очередь) — фикс не должен вносить регрессию для enduro-trails и не-self репо.
- Выбор конкретного механизма владения/живости — за архитектором; настоящий BRD фиксирует требования и инварианты, а не реализацию.
- Источник истины о «жив ли finalizer» сегодня отсутствует: pid агента в Tier-2 уже мёртв в обоих
случаях (
proc.wait()вернулся), а живости потока-монитора/финализатора система не наблюдает — это и есть пробел, который закрывает фикс.
7. Критерии успеха
Reaper при живом finalizer'е deploy-staging не запускает второй прогон edge-гейтов и не откатывает
задачу; повторная обработка идемпотентна; мёртвый finalizer по-прежнему добивается; после фикса нет
ложного rollback/developer-retry и расхождения состояния; инварианты ORCH-065/NFR-2 целы; полный
регресс tests/ зелёный. Детальные PASS/FAIL — 03-acceptance-criteria.md.
8. Риски
- Гонка/расхождение состояния (наблюдалось): повторный откат после успешного deploy. Высокий.
- Над-толерантность: слишком «доверять живости» → реально мёртвый finalizer не добивается (регресс ORCH-065). Сдерживается BR-4 + Tier-3 backstop.
- Нарушение сквозного бюджета при правке grace/таймаутов (NFR-6).
Детальная проработка и контрмеры —
10-tech-risks.md(заполняет архитектор).