From 425ecb75853122b2202ed129c526078a51b6b64b Mon Sep 17 00:00:00 2001 From: claude-bot Date: Mon, 15 Jun 2026 11:28:17 +0300 Subject: [PATCH] analyst(ET): auto-commit from analyst run_id=692 --- docs/work-items/ORCH-113/01-brd.md | 167 ++++++++++++++++++ docs/work-items/ORCH-113/02-trz.md | 106 +++++++++++ .../ORCH-113/03-acceptance-criteria.md | 98 ++++++++++ docs/work-items/ORCH-113/04-test-plan.yaml | 76 ++++++++ 4 files changed, 447 insertions(+) create mode 100644 docs/work-items/ORCH-113/01-brd.md create mode 100644 docs/work-items/ORCH-113/02-trz.md create mode 100644 docs/work-items/ORCH-113/03-acceptance-criteria.md create mode 100644 docs/work-items/ORCH-113/04-test-plan.yaml diff --git a/docs/work-items/ORCH-113/01-brd.md b/docs/work-items/ORCH-113/01-brd.md new file mode 100644 index 0000000..03bb862 --- /dev/null +++ b/docs/work-items/ORCH-113/01-brd.md @@ -0,0 +1,167 @@ +--- +work_item: ORCH-113 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-15 +model_used: claude-opus-4-8 +escalate: 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-state +> `finalizing` / 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 **уже исполняются**. +- Повторная обработка завершившегося-но-ещё-`running` job на `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** — Повторная обработка завершившегося-но-`running` job на `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` (заполняет архитектор). diff --git a/docs/work-items/ORCH-113/02-trz.md b/docs/work-items/ORCH-113/02-trz.md new file mode 100644 index 0000000..77635d3 --- /dev/null +++ b/docs/work-items/ORCH-113/02-trz.md @@ -0,0 +1,106 @@ +--- +work_item: ORCH-113 +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-113 — BUG: job-reaper не должен повторно запускать финализацию `deploy-staging`, пока жив исходный finalizer + +Work Item: **ORCH-113** · Repo: **orchestrator** · Стадия: analysis + +> ТЗ описывает **требования к изменению**, выведенные из BRD и фактического кода. Конкретный +> механизм (heartbeat живости / sub-state `finalizing` / per-stage grace / ownership-lease на +> edge-гейты), точные имена символов/колонок/флагов и порядок врезок — **архитектурное решение** +> (06-adr), не зона аналитика. Модули-плейсхолдеры ниже выровнены под манифест `PIPELINE_DOCS.md`. + +## 1. Сводка изменения + +Сделать повторную обработку reaper'ом завершившегося-но-ещё-`running` job на стадии `deploy-staging` +**идемпотентной и владеющей состоянием**: пока исходный monitor/finalizer **жив** (или edge-гейты для +пары `(job, stage)` уже исполняются), reaper **не должен** независимо запускать второй прогон edge- +под-гейтов ребра `deploy-staging → deploy` (security / merge-gate / локальный re-test / coverage / +image-freshness) и **не должен** на этом основании откатывать задачу. Корень — ошибочное допущение +Tier-2 finalization-grace (`reaper_finalize_grace_s=300`), что финализация после штампа `finished_at` +длится «секунды…десятки секунд»; для `deploy-staging` она длится **минуты** (полный re-test + coverage ++ rebuild), потому что `_try_advance_stage` (тяжёлые edge-гейты) выполняется **после** штампа +`finished_at` и **до** `_finalize_job`. Фикс должен дать reaper'у способ отличить «живой, долго +финализирующий монитор» от «мёртвого монитора» и обеспечить строгое владение исполнением edge-гейтов. + +## 2. Задействованные модули / пути +| Путь | Действие | +|------|----------| +| `src/job_reaper.py` | изменить — Tier-2-ветка `_reap_job` (строки ~197-209), `_reap_known_outcome` / `_reap_exit0` / `_gate_driven_advance`: ввести проверку живости/владения перед side-effectful re-drive `advance_stage` для `deploy-staging`; при живом finalizer'е — **defer**, не reap | +| `src/agents/launcher.py` | изменить (вероятно) — `_monitor_agent`: место/порядок штампа `finished_at` (строка ~861) относительно `_try_advance_stage` (строка ~998) и/или эмиссия durable-сигнала «finalizer жив/финализирует» перед запуском тяжёлых edge-гейтов | +| `src/db.py` | изменить (вероятно) — `get_running_jobs` (строки ~1337-1367, источник `finished_age_s`) и/или аддитивная колонка владения/живости (`_ensure_column`, паттерн `tasks.cancelled_at`); `reap_running_job` — без изменения контракта атомарного флипа | +| `src/config.py` | изменить (вероятно) — kill-switch фикса и/или per-stage/finalize-aware grace; сохранить сквозной инвариант `reaper_max_running_s > Σ gate-work + grace` (NFR-6) | +| `src/stage_engine.py` | **только чтение/ссылка** — `advance_stage` ребро `deploy-staging` (строки ~321-383): эталон того, какие edge-гейты дублируются. Изменение нежелательно; идемпотентность предпочтительно решать на стороне reaper/launcher | + +> Точный набор затронутых модулей и распределение логики (reaper-only vs launcher-сигнал vs db-колонка) +> архитектор фиксирует в 06-adr. Аналитик фиксирует, что центр тяжести правки — `src/job_reaper.py`. + +## 3. Функциональные требования + +### FR-1 — Распознавание живости финализирующего монитора (BR-1, BR-3) +Reaper в Tier-2 для стадии `deploy-staging` должен распознавать ситуацию «процесс агента завершён, +но monitor/finalizer ещё **жив** и исполняет edge-гейты» и **не** трактовать её как мёртвый монитор по +одному лишь `finished_age_s >= reaper_finalize_grace_s`. Сигнал живости должен переживать долгую +(минуты) финализацию `deploy-staging`. **Инвариант:** живой finalizer **никогда** не reap'ается. + +### FR-2 — Идемпотентность и строгое владение edge-гейтами (BR-2) +Для пары `(job, stage)` на `deploy-staging` тяжёлый прогон edge-гейтов (security / merge-gate / +локальный re-test / coverage / image-freshness) исполняется **не более одного раза одновременно**: +актор, не владеющий состоянием, **не** запускает второй `advance_stage`/re-test/merge-gate. Текущий +атомарный claim-before-act (`reap_running_job ... WHERE status='running'`) защищает только флип строки +job — требование расширяет защиту на **side-effectful исполнение edge-гейтов**. + +### FR-3 — Согласование grace/бюджета (BR-3, NFR-6) +Длительность finalization-grace (или заменяющий её сигнал живости) должна покрывать фактический +wall-clock финализации `deploy-staging` = Σ(security + merge re-test `merge_retest_timeout_s=900` + +coverage + image rebuild). Любая правка grace/таймаутов сохраняет сквозной инвариант ORCH-065/109/110: +`reaper_max_running_s (5400) > Σ(deploy-staging gate-work) + grace`. + +### FR-4 — Сохранение добивания мёртвого finalizer'а (BR-4) +Реально **мёртвый** monitor/finalizer на `deploy-staging` (краш посреди финализации, сигнал живости +отсутствует/протух) по-прежнему добивается reaper'ом за ограниченное время по существующему контракту +(retry в пределах бюджета, иначе `failed` + Telegram; Tier-3 backstop как крайний предохранитель). +Reaper не становится no-op для `deploy-staging`. + +### FR-5 — Отсутствие расхождения состояния (BR-5) +Исключить одновременный ложный откат `deploy-staging → development` и успешное завершение +`deploy → done` для одной задачи. После согласования у задачи — единственное консистентное +терминальное/стадийное состояние; ложный developer-retry не инкрементируется. Под-гейт merge-verify +(`deploy → done`, ORCH-071) остаётся единственным choke-point'ом в `done` — он **не** ослабляется. + +## 4. Изменения API +Новых публичных эндпоинтов **не требуется**. Допустима **аддитивная read-only** наблюдаемость в +`GET /queue` (например, отметка «deploy-staging finalize in-progress / deferred-by-liveness» в блоке +`reaper`) — без изменения существующих ключей ответа. + +## 5. Изменения схемы БД +Возможна **аддитивная** колонка владения/живости finalizer'а (например, durable-таймштамп +«finalizing heartbeat» или owner-токен на job/agent_run), вводимая идемпотентно через `_ensure_column` +(паттерн `tasks.cancelled_at` / `tasks.track`). Существующие таблицы/колонки/индексы и их семантика — +**без изменений**. Точная необходимость и форма — за архитектором (06-adr / 08-data-requirements). + +## 6. Требования к новым/изменённым QG checks +**Нет.** `QG_CHECKS` и любой `check_*` (включая `check_staging_status`, `check_branch_mergeable`, +`check_coverage_gate`, `check_security_gate`, `check_staging_image_fresh`, `check_deploy_status`) — не +трогаются ни по составу, ни по семантике, ни по machine-verdict ключам. Багфикс — свойство страховочного +демона/финализатора, **не** Quality Gate. + +## 7. Совместимость / регресс +- **Kill-switch:** фикс под выделенным флагом; `False` → строго прежнее поведение reaper (нулевая + регрессия). +- **Область:** поведение для **не-`deploy-staging`** стадий и путь добивания мёртвого монитора — + сохранены; self-hosting-only/скоуп репо согласовать с существующими leaf-паттернами (если вводится + per-repo разрез). +- **Обратимость:** раскат обратим выключением флага (+ откатом значений grace/таймаутов к дефолтам). +- **Never-raise / restart-safe / self-hosting (NFR-1/3/5):** любой сбой нового пути живости/владения + деградирует безопасно (reaper-тик продолжается); новый durable-маркер восстановим после рестарта; + фикс не рестартит прод и не пушит `main`. +- **Сквозной инвариант (NFR-6):** `reaper_max_running_s > Σ gate-work + grace` сохранён. +- **Анти-регресс:** структурный тест-гард (см. `04-test-plan.yaml`), полный `tests/` остаётся зелёным. diff --git a/docs/work-items/ORCH-113/03-acceptance-criteria.md b/docs/work-items/ORCH-113/03-acceptance-criteria.md new file mode 100644 index 0000000..80a8c1c --- /dev/null +++ b/docs/work-items/ORCH-113/03-acceptance-criteria.md @@ -0,0 +1,98 @@ +--- +work_item: ORCH-113 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-15 +model_used: claude-opus-4-8 +escalate: full-cycle +--- + +# 03 — Критерии приёмки (Acceptance Criteria): ORCH-113 — BUG: job-reaper не должен повторно запускать финализацию `deploy-staging`, пока жив исходный finalizer + +Work Item: **ORCH-113** · Repo: **orchestrator** · Стадия: analysis + +Формат: каждый критерий имеет **PASS** (что должно быть истинно для приёмки) и **FAIL** (что считается +провалом). Любой машинный/ручной reviewer проверяет их буквально по файлам репозитория и тестам. + +--- + +## AC-1 — Нет второго прогона edge-гейтов при живом finalizer'е + +**Условие:** job на `deploy-staging` с `exit_code=0`, `finished_age_s >= reaper_finalize_grace_s`, но +исходный monitor/finalizer ещё **жив** (сигнал живости присутствует). +- **PASS:** reaper **не** вызывает `_gate_driven_advance`/`advance_stage` для этого job; второй прогон + security / merge-gate / локального re-test / coverage / image-freshness **не** запускается; reaper + логирует defer. +- **FAIL:** reaper запускает повторный `advance_stage` / любой edge-под-гейт, пока finalizer жив. + +--- + +## AC-2 — Идемпотентность и строгое владение состоянием + +**Условие:** монитор финализирует `deploy-staging` и параллельно срабатывает reaper-тик для того же +job/stage. +- **PASS:** тяжёлый прогон edge-гейтов (merge-gate / локальный re-test) исполняется **ровно один раз** + для пары `(job, stage)`; актор без владения состоянием не выполняет побочных шагов (мерж/re-test/ + rollback). +- **FAIL:** второй локальный re-test/merge-gate запускается для того же job/stage без владения + состоянием (двойное исполнение edge-гейтов). + +--- + +## AC-3 — Мёртвый finalizer по-прежнему добивается + +**Условие:** monitor/finalizer на `deploy-staging` реально **умер** посреди финализации (сигнал +живости отсутствует/протух), job завис в `running`. +- **PASS:** reaper за ограниченное время добивает job по существующему контракту (retry в пределах + бюджета, иначе `failed` + Telegram; Tier-3 backstop как предохранитель); reaper для `deploy-staging` + **не** превращён в no-op. +- **FAIL:** мёртвый finalizer на `deploy-staging` не добивается reaper'ом (зомби-job блокирует очередь). + +--- + +## AC-4 — Нет ложного отката и расхождения состояния после успешного deploy + +**Условие:** сценарий инцидента ORCH-111 — долгая (> grace) финализация `deploy-staging` при зелёном +`staging_status: SUCCESS`, deploy/finalizer параллельно доходит до `deploy_status: SUCCESS` / merge PR. +- **PASS:** задача **не** откатывается `deploy-staging → development` параллельной reaper-веткой; + developer-retry **не** инкрементируется ложно; у задачи единственное консистентное + терминальное/стадийное состояние (сходимость, не расхождение). +- **FAIL:** задача откатана `deploy-staging → development` и/или начислен ложный developer-retry, в то + время как deploy фактически успешен; ветки состояния расходятся. + +--- + +## AC-5 — Инварианты и контракт reaper сохранены + +**Условие:** аудит диффа и поведения при выключенном kill-switch. +- **PASS:** `STAGE_TRANSITIONS` / `QG_CHECKS` / каждый `check_*` / machine-verdict ключи / схема + существующих таблиц — **байт-в-байт**; БД-правки только аддитивные (`_ensure_column` / + `CREATE TABLE IF NOT EXISTS`); reaper остаётся never-raise per unit; выключенный флаг → прежнее + поведение; правки не рестартят прод и не пушат `main`; сквозной инвариант + `reaper_max_running_s > Σ gate-work + grace` (ORCH-065/109/110) сохранён. +- **FAIL:** изменены `STAGE_TRANSITIONS`/`QG_CHECKS`/семантика `check_*`/machine-verdict ключи или + схема существующих таблиц; reaper может бросить исключение из тика; флаг `False` меняет поведение; + нарушен сквозной бюджетный инвариант. + +--- + +## AC-6 — Обязательный регресс-тест и зелёный полный прогон + +**Условие:** запуск тест-сюита репозитория. +- **PASS:** добавлен регресс-тест инцидента ORCH-111 (TC-05 в `04-test-plan.yaml`), **красный** на + коде до фикса и **зелёный** после; полный `pytest tests/ -q` зелёный. +- **FAIL:** регресс-теста нет, либо он зелёный и до фикса (не воспроизводит баг), либо полный регресс + `tests/` красный. + +--- + +## Сводная матрица AC ↔ FR/BR +| AC | Покрывает | +|----|-----------| +| AC-1 | BR-1 / BR-3 / FR-1 | +| AC-2 | BR-2 / FR-2 | +| AC-3 | BR-4 / FR-4 | +| AC-4 | BR-5 / FR-5 | +| AC-5 | NFR-1 / NFR-2 / NFR-3 / NFR-6 / FR-3 | +| AC-6 | BR-1…BR-5 (регресс-доказательство) | diff --git a/docs/work-items/ORCH-113/04-test-plan.yaml b/docs/work-items/ORCH-113/04-test-plan.yaml new file mode 100644 index 0000000..23bee70 --- /dev/null +++ b/docs/work-items/ORCH-113/04-test-plan.yaml @@ -0,0 +1,76 @@ +work_item: ORCH-113 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-15 +model_used: claude-opus-4-8 +escalate: full-cycle +title: "job-reaper не повторяет финализацию deploy-staging при живом finalizer'е: живость + идемпотентность + строгое владение" +framework: pytest +scope: > + Покрывает: распознавание живого финализирующего монитора в Tier-2 reaper на стадии deploy-staging + (не reap по одному finished_age_s); идемпотентность и строгое владение исполнением edge-гейтов + (не более одного локального re-test/merge-gate на (job, stage)); сохранение добивания РЕАЛЬНО + мёртвого finalizer'а; отсутствие ложного отката deploy-staging -> development и расхождения состояния + после успешного deploy; сохранение инвариантов (STAGE_TRANSITIONS/QG_CHECKS/check_*/machine-verdict/ + схема существующих таблиц байт-в-байт; never-raise; kill-switch; сквозной бюджет ORCH-065/109/110). + Вне покрытия: инфра-толерантность merge-gate к таймауту re-test и tree-kill осиротевших процессов + (ORCH-110); починка конкретных мигающих тестов; поведение enduro/не-self репо (только проверяется + отсутствие регрессии / no-op). +notes: > + TC-05 — ОБЯЗАТЕЛЬНЫЙ регресс-тест инцидента ORCH-111 (deployer job 1914 / run_id 683): КРАСНЫЙ на + коде до фикса (reaper при живом долгом finalizer'е deploy-staging независимо запускает второй прогон + edge-гейтов и откатывает задачу), ЗЕЛЁНЫЙ после фикса. Подпроцессы (pytest re-test / coverage / + docker), сеть, Plane и Gitea — мокаются; «живой/мёртвый finalizer» и «долгая финализация > grace» + моделируются управляемо, без обращения наружу. Полный регресс tests/ должен оставаться зелёным. + Точные имена символов/колонок/флагов уточняет архитектор (06-adr); модули-плейсхолдеры выровнены под + манифест PIPELINE_DOCS. + +tests: + - id: TC-01 + type: unit + description: "Tier-2 reaper на deploy-staging: exit_code=0 и finished_age_s >= grace, но finalizer ЖИВ (сигнал живости присутствует) -> reaper НЕ вызывает _gate_driven_advance/advance_stage; второй прогон edge-гейтов не запускается; логируется defer (AC-1/FR-1)." + module: tests/test_orch113_reaper_finalizer_liveness.py + expected: PASS + + - id: TC-02 + type: unit + description: "Строгое владение: при попытке повторной обработки того же (job, stage) актор без владения состоянием НЕ исполняет merge-gate/локальный re-test/advance (claim/ownership проигран -> ноль побочных эффектов), AC-2/FR-2." + module: tests/test_orch113_reaper_finalizer_liveness.py + expected: PASS + + - id: TC-03 + type: unit + description: "Мёртвый finalizer на deploy-staging (сигнал живости отсутствует/протух) -> reaper по-прежнему добивает job за ограниченное время по существующему контракту (retry в пределах бюджета, иначе failed+Telegram; Tier-3 backstop срабатывает) — reaper не no-op для deploy-staging (AC-3/FR-4)." + module: tests/test_orch113_reaper_finalizer_liveness.py + expected: PASS + + - id: TC-04 + type: integration + description: "Идемпотентность под гонкой: монитор финализирует deploy-staging и параллельно срабатывает reaper-тик -> тяжёлый прогон edge-гейтов (merge-gate/re-test) исполняется РОВНО ОДИН раз для (job, stage); нет второго re-test и нет ложного rollback (AC-2/AC-4/FR-2/FR-5)." + module: tests/test_orch113_reaper_finalizer_liveness.py + expected: PASS + + - id: TC-05 + type: integration + description: "ОБЯЗАТЕЛЬНЫЙ регресс ORCH-111: долгая (> grace) финализация deploy-staging при staging_status=SUCCESS, deploy/finalizer параллельно доходит до успеха/merge PR -> reaper НЕ откатывает deploy-staging -> development и НЕ инкрементирует developer-retry; у задачи единственное консистентное состояние. КРАСНЫЙ до фикса, ЗЕЛЁНЫЙ после (AC-4/FR-5)." + module: tests/test_orch113_reaper_finalizer_liveness.py + expected: PASS + + - id: TC-06 + type: unit + description: "Регресс-гард совместимости: kill-switch выключен ИЛИ стадия не deploy-staging -> поведение reaper байт-в-байт прежнее (Tier-2 grace, claim-before-act, dead-pid/Tier-3 пути неизменны), NFR-4/AC-5." + module: tests/test_orch113_reaper_finalizer_liveness.py + expected: PASS + + - id: TC-07 + type: unit + description: "Сквозной инвариант бюджета (ORCH-065/109/110): при дефолтном конфиге reaper_max_running_s (5400) > Σ(deploy-staging gate-work) + grace; любая правка grace/таймаутов фикса инвариант не нарушает (NFR-6/AC-5)." + module: tests/test_orch113_reaper_finalizer_liveness.py + expected: PASS + + - id: TC-08 + type: unit + description: "never-raise: сбой/исключение в новом пути распознавания живости/владения деградирует безопасно — reaper-тик не падает, прочие job обрабатываются, прод не трогается, main не пушится (NFR-1/NFR-3/AC-5)." + module: tests/test_orch113_reaper_finalizer_liveness.py + expected: PASS