107 lines
11 KiB
Markdown
107 lines
11 KiB
Markdown
---
|
||
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/` остаётся зелёным.
|