analyst(ET): auto-commit from analyst run_id=692

This commit is contained in:
2026-06-15 11:28:17 +03:00
committed by deployer
parent 55e9483fb8
commit 425ecb7585
4 changed files with 447 additions and 0 deletions

View File

@@ -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` (заполняет архитектор).

View File

@@ -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/` остаётся зелёным.

View File

@@ -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 (регресс-доказательство) |

View File

@@ -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