From f36528705e9972c8286c127c4d75ebbf3c0bf8bd Mon Sep 17 00:00:00 2001 From: claude-bot Date: Tue, 9 Jun 2026 23:01:19 +0300 Subject: [PATCH] analyst(ET): auto-commit from analyst run_id=518 --- docs/work-items/ORCH-094/01-brd.md | 155 ++++++++++++++++++ docs/work-items/ORCH-094/02-trz.md | 129 +++++++++++++++ .../ORCH-094/03-acceptance-criteria.md | 94 +++++++++++ docs/work-items/ORCH-094/04-test-plan.yaml | 97 +++++++++++ 4 files changed, 475 insertions(+) create mode 100644 docs/work-items/ORCH-094/01-brd.md create mode 100644 docs/work-items/ORCH-094/02-trz.md create mode 100644 docs/work-items/ORCH-094/03-acceptance-criteria.md create mode 100644 docs/work-items/ORCH-094/04-test-plan.yaml diff --git a/docs/work-items/ORCH-094/01-brd.md b/docs/work-items/ORCH-094/01-brd.md new file mode 100644 index 0000000..6653f3b --- /dev/null +++ b/docs/work-items/ORCH-094/01-brd.md @@ -0,0 +1,155 @@ +--- +work_item: ORCH-094 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-09 +model_used: claude-opus-4-8 +--- + +# 01 — BRD (бизнес-требования): ORCH-094 — терминальная (done) задача флаппит deploy-статусы в Plane (Awaiting↔Monitoring), не держит Done + +Work Item: **ORCH-094** · Repo: **orchestrator** · Стадия: analysis + +## 1. Бизнес-контекст и проблема + +**Тип:** BUG — рассинхрон БД↔Plane / «зомби»-цикл post-deploy-статуса (self-hosting). + +**Симптом (верифицирован живьём 09.06 на ORCH-061):** +Задача ORCH-061 в БД оркестратора = `done` с 07.06 (task 47; фикс задеплоен в прод; конвейер её +не трогает — 0 активных job'ов). При этом карточка задачи в **Plane не держит Done**: непрерывно +флаппит `Monitoring after Deploy ⟷ Awaiting Deploy` парами (туда-обратно за ~2 сек), каждые +несколько минут. Накоплено 273 активности. Доходило до абсурда: 09.06 14:56 встала в `Done` → +15:48 её выдернуло обратно `Done → Awaiting Deploy`. Воспроизводится детерминированно: ручной +sync 061→Done (PATCH 200, 16:47) → через ~60 сек снова `Done → Awaiting Deploy → Monitoring` +(16:48). Само **не затихает**. + +**Установленные факты (по логам/БД прода + чтение кода ветки):** +- **Сам оркестратор не инициирует переходы из своих штатных стадийных обработчиков для done-задачи.** + В момент флаппа лог орка показывает только **входящие** webhook-и Plane + (`issue … updated to state … (Awaiting Deploy) → no pipeline action`, затем `(Monitoring) → + no pipeline action`). Обработчик `webhooks/plane.py::handle_issue_updated` для статусов + Awaiting/Monitoring логирует «no pipeline action» и **сам статус не переотправляет** (echo-loop + обработчика исключён). +- **Actor всех 273 переходов** = `daf4d3f4-55df-4016-9095-0cf9ddd8fd28` — бот-актор оркестратора + (тот же токен, под которым орк делает гигиену доски / sync). То есть PATCH-и шлёт **что-то под + токеном орка**, не привязанное к активной task/job в БД. +- В БД орка **нет активного post-deploy-monitor** для task 47 (pdm активен только у текущей + 063/task 74). `orchestrator-staging` (8501) — не источник (task 061 в его БД отсутствует). +- В коде ветки **единственные три писателя** deploy-статусов — `src/stage_engine.py`: + `set_issue_monitoring` (строка 404, на переходе `deploy → done` для self-hosting), + `set_issue_awaiting_deploy` (строка 1218, Phase A), `set_issue_deploying` (строка 1316, Phase B). + Все три — **внутри стадийных обработчиков** (`advance_stage` / `_handle_self_deploy_phase_*`), + ни один не сидит в фоновом цикле, независимом от таблицы `jobs`. +- `notifications.py::_live_plane_branch_override` **только читает** живой Plane-статус (для рендера + карточки) — писателем не является. +- Реконсилятор: F-1 пропускает задачи со `stage in ('done','cancelled')` (terminal-skip ORCH-086); + F-2 опрашивает issue **только** в статусах `[to_analyse, approved, rejected]` — статусы + `Monitoring`/`Awaiting` он не перебирает. **Механизма «привести done-задачу, застрявшую на + deploy-статусе, обратно к Done» (идемпотентного схождения) — нет.** + +**Боль:** карточка вводит наблюдателя в заблуждение («задача деплоится», хотя она в проде и done), +шумит активностью (273 события на одной задаче), **вечно жжёт API-вызовы Plane** флаппом и +маскирует реальное состояние доски. Конвейер технически не нарушен (задача в проде), поэтому +приоритет **MEDIUM**, но дефект бессрочный и самовоспроизводящийся. + +**Родственные задачи:** ORCH-091 (врущие/застывшие статусы карточки), ORCH-068/086 (терминал-скип +как защита инвариантов). ORCH-094 распространяет идею терминал-скипа на deploy-статусы и закрывает +источник флаппа. + +## 2. Объём (scope) + +### В объёме +- **G1 — устранить источник** PATCH-ей deploy-статуса на задачу, у которой в БД `stage=done` и нет + активного job'а. Терминальная (done) задача в Plane должна стабильно держать `Done` и не получать + `Awaiting`/`Monitoring`. +- **G2 — идемпотентность sync/setter'ов:** если БД=`done`, любой sync/монитор/реконсилятор/прямой + вызов приводит Plane к `Done` (не к промежуточному deploy-статусу) — терминал-скип/схождение, + распространённые на статусы `Monitoring`/`Awaiting` (как ORCH-068/086 для других статусов). +- **G3 — детерминированный конец post-deploy-monitor:** монитор завершается чётко (HEALTHY / N тиков + → Done) и не оставляет «зомби»-таймеров, переживающих завершение задачи/рестарт; тики монитора + привязаны к активному job'у в БД (нет job → нет тиков, нет статус-PATCH). +- **G4 — наблюдаемость:** лог однозначно показывает, **кто и почему** ставит deploy-статус + (caller/функция + причина), для будущей диагностики таких флаппов. +- Инструментальная локализация фактического актора флаппа на проде (воспроизведение на 061) и его + документирование (что это было) — в рамках выполнения задачи (developer/architect). + +### Вне объёма +- Изменение конвейера стадий (`STAGE_TRANSITIONS`), состава `QG_CHECKS`, семантики machine-verdict + ключей (`deploy_status:`/`staging_status:`/…) — **не трогать**. +- Изменение рабочего deploy-цикла для **реально деплоящейся** задачи (Phase A→B→C, post-deploy + HEALTHY-окно) — поведение должно сохраниться 1:1 (регресс, AC-4). +- Поведение для не-self-hosting репозиториев (enduro-trails) — нулевая регрессия. +- Архитектурное решение «где именно поставить гард» (на уровне setter'а в `plane_sync` vs на уровне + вызывающего в `stage_engine` vs реконсилятор) — определяет **архитектор** в `06-adr/`. + +## 3. Заинтересованные стороны + +- **Заказчик/репортёр:** Слава (владелец) — обнаружил на ORCH-061 09.06. +- **Затрагивает:** всех наблюдателей доски Plane проекта ORCH (ложная индикация); лимиты Plane API + (вечный флапп жжёт вызовы под общим бот-токеном). +- **Принимает результат:** Owner / CI на финальной стадии конвейера. +- **Особый риск:** self-hosting — правка идёт в инструмент, обслуживающий прод всех проектов из + общего инстанса; рабочий deploy-цикл нельзя сломать. + +## 4. Бизнес-требования (BR) + +- **BR-1** — Терминальная задача (БД `stage=done`, 0 активных job'ов), выставленная в Plane=`Done`, + **остаётся `Done`** и не получает авто-переходов в `Awaiting Deploy`/`Monitoring after Deploy`. +- **BR-2** — Любой источник синхронизации (реконсилятор, монитор, прямой вызов setter'а deploy-статуса) + для задачи с БД=`done` приводит Plane к **`Done` идемпотентно**, а не к промежуточному deploy-статусу; + повторные срабатывания не качают маятник. +- **BR-3** — Post-deploy-monitor имеет **детерминированный конец** (HEALTHY / исчерпание N тиков → Done, + или DEGRADED → Blocked+freeze) и после завершения **не производит ни одного** последующего + статус-PATCH для этой задачи; не оставляет таймера/состояния, переживающего завершение или рестарт. +- **BR-4** — Тики post-deploy-monitor **привязаны к активному job'у** в таблице `jobs`: нет активного + job'а для задачи → нет тиков → нет статус-PATCH. «Зомби»-монитор (тики без соответствующего активного + job'а) исключён. +- **BR-5** — Для **реально деплоящейся** задачи (063-подобной) deploy-окно + `Awaiting → Deploying → Monitoring → Done` работает в точности как раньше (нет регресса). +- **BR-6** — Каждый вызов, выставляющий deploy-статус, оставляет в логе однозначную запись **кто + (функция/путь) и почему** ставит статус (наблюдаемость для будущей диагностики флаппов). +- **BR-7** — Фактический источник флаппа на проде локализован и **задокументирован** (что это было) + в `06-adr/` и/или `CHANGELOG.md`. + +## 5. Нефункциональные требования (NFR) + +- **NFR-1 — never-raise:** вся новая логика (гарды/терминал-скип/идемпотентность) не бросает + исключений в горячих путях; сетевая ошибка Plane при сверке статуса → безопасная деградация + (не флаппить и не падать), а не блокировка конвейера всех проектов. +- **NFR-2 — self-hosting безопасность:** не перезапускать/не ронять прод-контейнер; не трогать + `main`/force-push/прод-деплой; правка не меняет рабочий критический путь self-deploy. +- **NFR-3 — обратимость:** поведение под kill-switch (или иным обратимым флагом) — при выключении + возврат к прежнему поведению; нулевая регрессия для не-self репозиториев. +- **NFR-4 — restart-safe:** состояние монитора/гардов корректно после рестарта контейнера (нет + «воскрешения» тиков для уже завершённой задачи). +- **NFR-5 — `pytest tests/ -q` зелёный**; `STAGE_TRANSITIONS` / `QG_CHECKS` / machine-verdict ключи / + схема БД (если без миграции) — без изменений или строго аддитивно. + +## 6. Допущения и ограничения + +- Допущение: статусы `Monitoring after Deploy` / `Awaiting Deploy` существуют в Plane-проекте ORCH + как реальные статусы (иначе alias-fallback маппит их на базовые UUID — это часть диагностики + терминал-детекта). +- Допущение: бот-токен орка (`daf4d3f4-…`) — единственный актор переходов; внешняя Plane-automation + под другим токеном считается отдельной гипотезой и проверяется при локализации (H-внешнее). +- Ограничение: установленные факты выше **не изобретать** — они верифицированы на проде; точный + актор флаппа требует инструментального воспроизведения (фикс — после локализации). +- Ограничение: правка строго в зоне self-hosting deploy/post-deploy/sync; конвейер и гейты неизменны. + +## 7. Критерии успеха + +Терминальная задача стабильно держит `Done` ≥10 мин без авто-переходов (AC-1); любой sync для done +идемпотентно сходится к `Done` (AC-2); post-deploy-monitor завершается детерминированно и не +оставляет тиков/таймеров (AC-3); рабочий deploy-цикл 063-подобной задачи не регрессирует (AC-4); +never-raise + зелёный pytest + источник флаппа задокументирован (AC-5). Детальные PASS/FAIL — в +`03-acceptance-criteria.md`. + +## 8. Риски + +- Гард терминал-скипа поставлен слишком широко → подавит легитимный `Monitoring` у реально + деплоящейся задачи (регресс AC-4). Митигировать тонкой привязкой к БД `stage=done` + активность job. +- Фактический актор флаппа окажется внешней Plane-automation (вне кода орка) → код-фикс не закроет + G1 полностью; нужно зафиксировать в ADR и, при необходимости, защититься идемпотентным схождением + к Done (BR-2) как буфером. +- Детали — `10-tech-risks.md` (заполняет архитектор). diff --git a/docs/work-items/ORCH-094/02-trz.md b/docs/work-items/ORCH-094/02-trz.md new file mode 100644 index 0000000..3c54c5e --- /dev/null +++ b/docs/work-items/ORCH-094/02-trz.md @@ -0,0 +1,129 @@ +--- +work_item: ORCH-094 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-09 +model_used: claude-opus-4-8 +--- + +# 02 — ТЗ (TRZ): ORCH-094 — устранение флаппа deploy-статусов у терминальной (done) задачи + +Work Item: **ORCH-094** · Repo: **orchestrator** · Стадия: analysis + +> ТЗ описывает **конкретные изменения к реализации**, выведенные из BRD и фактического кода ветки. +> Архитектурное обоснование (ГДЕ ставить гард: setter `plane_sync` vs caller `stage_engine` vs +> реконсилятор) — задача архитектора (`06-adr/`). Здесь — ЧТО должно выполняться и ГДЕ искать. + +## 1. Сводка изменения + +Задача с БД `stage=done` и 0 активных job'ов в Plane стабильно держит `Done`: нужно (а) закрыть +источник, который шлёт ей PATCH-и deploy-статусов (`Awaiting Deploy`/`Monitoring after Deploy`), +(б) сделать выставление любого **deploy-фазового** статуса **терминал-aware / идемпотентным** — +для задачи, чья БД-стадия терминальна (`done`/`cancelled`), любой sync/монитор/прямой вызов +сходится к `Done`, а не к промежуточному статусу, (в) гарантировать детерминированный конец +post-deploy-monitor с привязкой тиков к активному job'у (нет job → нет тиков), (г) добавить +наблюдаемость «кто/почему ставит deploy-статус». + +Изменение **аддитивное, под обратимым флагом, never-raise**, в зоне self-hosting deploy/post-deploy/sync. +`STAGE_TRANSITIONS` / `QG_CHECKS` / machine-verdict ключи — **не трогаются**. + +## 2. Задействованные модули / пути + +| Путь | Действие | Зачем | +|------|----------|-------| +| `src/plane_sync.py` | изменить | Сеттеры `set_issue_awaiting_deploy` (~954), `set_issue_deploying` (~964), `set_issue_monitoring` (~974), `set_issue_done` (~913) — кандидат на единый терминал-aware гард (FR-2). Терминал-детект статуса (группа/UUID, ORCH-068) уже здесь. | +| `src/stage_engine.py` | изменить | Три писателя deploy-статуса: `advance_stage` стр. 404 (`set_issue_monitoring` на `deploy→done`), `_handle_self_deploy_phase_a` стр. 1218, `_handle_self_deploy_phase_b` стр. 1316. `run_post_deploy_monitor` (~1698–1850) — детерминированный конец, привязка к job. `arm_monitor`-вызов (~431). Логирование caller/причины (FR-4). | +| `src/post_deploy.py` | изменить (вероятно) | `arm_monitor` (~388–411), маркеры `armed`/`series`/`done`, `enqueue_job("post-deploy-monitor", …)` — гарантия отсутствия «зомби»-тиков и привязки к активному job (FR-3). | +| `src/reconciler.py` | изменить (вероятно) | F-2 опрашивает только `[to_analyse, approved, rejected]` (стр. ~387). Нет схождения «done-задача на deploy-статусе → Done». Добавить идемпотентное схождение/терминал-детект для deploy-статусов (FR-1/FR-2) ИЛИ убедиться, что гард в setter'е делает это излишним. | +| `src/config.py` | изменить | Kill-switch/флаг новой логики (FR-5). | +| `src/webhooks/plane.py` | прочитать (диагностика) | `handle_issue_updated` (~129–180): подтверждено, что для `Awaiting`/`Monitoring` логирует «no pipeline action» и не переотправляет — echo-loop исключён; править не требуется (если локализация не покажет иное). | +| `tests/test_*` | создать/изменить | Анти-регресс по FR-1…FR-5 (см. `04-test-plan.yaml`). | +| `CHANGELOG.md`, `docs/architecture/README.md`, `CLAUDE.md` | изменить | Документация = golden source; зафиксировать фикс + локализованный источник флаппа (BR-7). | + +> **Трассировка маркеров (CLAUDE.md прав. 9):** перед правкой строк с маркерами `ORCH-066`/`ORCH-068`/ +> `ORCH-086`/`ORCH-036`/`ORCH-059`/`ORCH-071`/`ORCH-088` прочитать их `06-adr/` и не сломать инвариант +> (особенно: deploy→done ставит `Monitoring`, монитор-close ставит `Done`; терминал-скип реконсилятора; +> post-deploy DEGRADED → freeze ORCH-088). + +## 3. Функциональные требования + +### FR-1 — Источник флаппа локализован и устранён +Инструментально воспроизвести флапп на ORCH-061 (или эквивалентной терминальной задаче), определить +**фактического актора** (функция/путь под бот-токеном орка ИЛИ внешняя Plane-automation) и устранить +его так, чтобы терминальная задача не получала deploy-статус-PATCH-ей. +- Зацепки (BR diagnostics): единственные code-писатели — `stage_engine.py:404/1218/1316`; реконсилятор + F-1 done-skip есть, F-2 эти статусы не перебирает; live-overlay `notifications.py` — read-only. +- Если актор — внешняя Plane-automation (вне кода орка), это **фиксируется в ADR** (BR-7) и закрывается + буфером FR-2 (идемпотентное схождение к Done гасит маятник на стороне орка). +- Привязка: BR-1, BR-7. + +### FR-2 — Терминал-aware идемпотентность выставления deploy-статуса +Любая попытка выставить **deploy-фазовый** статус (`Awaiting Deploy`/`Deploying`/`Monitoring after +Deploy`) для задачи, чья БД-стадия **терминальна** (`stage IN ('done','cancelled')`), должна вместо +этого привести Plane к `Done` (для `done`) либо к корректному терминалу (для `cancelled`) — +идемпотентно. Повторные вызовы не качают маятник: уже-`Done` → no-op. +- Гард — терминал-aware (по БД-стадии задачи, не по живому Plane-статусу), чтобы НЕ подавлять + легитимный `Monitoring` у реально деплоящейся (нетерминальной) задачи (BR-5/AC-4). +- Реализация-кандидат (решает архитектор): единая точка в setter'ах `plane_sync` (требует доступа к + БД-стадии по `work_item_id`) ИЛИ в caller'ах `stage_engine`/`reconciler`. ТЗ требует **результат**: + done-задача сходится к Done из любого пути. +- never-raise: невозможность определить стадию/сетевая ошибка → безопасная деградация (не флаппить). +- Привязка: BR-1, BR-2. + +### FR-3 — Детерминированный конец post-deploy-monitor + привязка тиков к активному job +- Монитор завершается детерминированно: HEALTHY+исчерпание `post_deploy_budget` тиков → `set_issue_done` + + маркер `done`; DEGRADED → штатный путь (Blocked/freeze ORCH-088); после завершения — **ни одного** + последующего статус-PATCH (маркер `done` — идемпотентный страж, ~стр. 1729). +- Тик монитора **обязан** проверять, что задача не терминальна и для неё есть основание тикать (нет + активного основания/job → тик no-op, новый тик не ставится в очередь). «Зомби»-тик (тик без + соответствующего активного job'а/при БД=done) → немедленный no-op без статус-PATCH. +- Гарантировать, что `arm_monitor` не может быть вызван/перезапущен для задачи, уже находящейся в `done`, + способом, который заново ставит `Monitoring` (повторный `deploy→done` re-drive). +- restart-safe: после рестарта контейнера нет воскрешения тиков для завершённого окна. +- Привязка: BR-3, BR-4, NFR-4. + +### FR-4 — Наблюдаемость выставления deploy-статуса +Каждый вызов, выставляющий deploy-фазовый статус, логирует структурно: **work_item, caller +(функция/путь), целевой статус, причина/триггер, БД-стадия задачи на момент вызова**. Достаточно, +чтобы по логу однозначно определить «кто и почему» при будущем флаппе. Терминал-aware-подавление +(FR-2) тоже логируется (что подавили и почему). +- Привязка: BR-6, G4. + +### FR-5 — Обратимость и совместимость +- Новая логика — под kill-switch/флагом в `config.py` (env-override); `False` → прежнее поведение + 1:1 (нулевая регрессия). +- Условность self-hosting, как ORCH-035/036/043/088: для не-self репозиториев — no-op / прежнее + поведение. +- Привязка: NFR-3, BR-5. + +## 4. Изменения API + +Нет новых внешних эндпоинтов конвейера. Допустимо (на усмотрение архитектора) аддитивное read-only +поле наблюдаемости в `GET /queue` (напр. блок `post_deploy`/`deploy_status_guard` со счётчиками +подавлений), по образцу существующих блоков `serial_gate`/`reconcile`/`reaper`. Не обязательно. + +## 5. Изменения схемы БД + +Ожидается **без миграции схемы**: терминал-aware гард читает существующую `tasks.stage`; привязка +тиков к job — существующая таблица `jobs`; состояние монитора — существующие sentinel-файлы +(`post_deploy.py`). Если архитектор сочтёт необходимым durable-счётчик/маркер — строго аддитивно +(`_ensure_column`, по образцу ORCH-088/090), без изменения существующих колонок. + +## 6. Требования к новым/изменённым QG checks + +**Нет.** `QG_CHECKS` и `check_*` (включая `check_deploy_status`/`check_staging_status`) — **не +трогаются**; machine-verdict ключи (`deploy_status:`/`staging_status:`/…) — байт-в-байт. ORCH-094 — +фикс индикации/идемпотентности sync, не гейт. + +## 7. Совместимость / регресс + +- **Kill-switch** (FR-5): выключение → прежнее поведение 1:1. +- **Регресс деплоя (AC-4):** рабочий цикл 063-подобной задачи `Awaiting→Deploying→Monitoring→Done` + сохраняется — гард срабатывает строго на терминальной БД-стадии, нетерминальная задача проходит + как раньше. +- **Не-self репозитории:** условность self-hosting → нулевая регрессия (enduro-trails). +- **`STAGE_TRANSITIONS`/`QG_CHECKS`/machine-verdict/схема БД** — без изменений (или строго аддитивно). +- **never-raise / self-hosting безопасность:** не трогать `main`/force-push/прод-контейнер/детач-деплой. +- **Артефакты pipeline:** обновляются `CHANGELOG.md`, обзорные доки (`README.md`/`docs/architecture/ + README.md`), `CLAUDE.md`; `06-adr/ADR-NNN-…` с локализованным источником флаппа (BR-7). diff --git a/docs/work-items/ORCH-094/03-acceptance-criteria.md b/docs/work-items/ORCH-094/03-acceptance-criteria.md new file mode 100644 index 0000000..2fe1c9a --- /dev/null +++ b/docs/work-items/ORCH-094/03-acceptance-criteria.md @@ -0,0 +1,94 @@ +--- +work_item: ORCH-094 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-09 +model_used: claude-opus-4-8 +--- + +# 03 — Критерии приёмки (Acceptance Criteria): ORCH-094 — флапп deploy-статусов у терминальной (done) задачи + +Work Item: **ORCH-094** · Repo: **orchestrator** · Стадия: analysis + +Формат: каждый критерий имеет **PASS** (что должно быть истинно для приёмки) и **FAIL** (что +считается провалом). Reviewer/CI проверяет их буквально по файлам репозитория и/или прод-проверкой. + +--- + +## AC-1 — Терминальная задача стабильно держит Done + +**Условие:** задача с БД `stage=done` и 0 активных job'ов, выставленная в Plane=`Done`, наблюдается +≥10 минут (воспроизводящий тест на 061-подобной фикстуре и/или прод-проверка на ORCH-061). +- **PASS:** за окно наблюдения **ни одного** авто-перехода в `Awaiting Deploy`/`Monitoring after + Deploy`; статус остаётся `Done`. В тесте: после выставления `Done` ни один кодовый путь орка не + порождает PATCH deploy-статуса для этой задачи. +- **FAIL:** зафиксирован хотя бы один авто-переход done-задачи в `Awaiting`/`Monitoring`, либо флапп + продолжается. + +--- + +## AC-2 — Идемпотентное схождение к Done для done-задачи + +**Условие:** для задачи с БД `stage IN ('done','cancelled')` инициируется любой источник sync +(реконсилятор-тик, монитор-тик, прямой вызов setter'а deploy-статуса). +- **PASS:** результат — `Done` (для `done`) / корректный терминал (для `cancelled`); промежуточный + deploy-статус (`Awaiting`/`Deploying`/`Monitoring`) **не** выставляется; повторный вызов на + уже-`Done` — no-op (без PATCH-маятника). Подавление логируется (что/почему). +- **FAIL:** sync для done-задачи выставляет промежуточный deploy-статус, либо повторные вызовы + качают `Done ⟷ deploy-статус`. + +--- + +## AC-3 — Детерминированный конец post-deploy-monitor, без «зомби»-тиков + +**Условие:** post-deploy-monitor отрабатывает свой жизненный цикл (HEALTHY до исчерпания +`post_deploy_budget` тиков, либо DEGRADED). +- **PASS:** по достижении HEALTHY/N-тиков → `set_issue_done` + маркер `done`; **после завершения — + 0 последующих статус-PATCH** для этой задачи (тест: монитор отработал → последующих + `set_issue_*`-вызовов нет). Тик при БД=`done`/отсутствии активного основания → немедленный no-op + без PATCH. После рестарта контейнера тики завершённого окна не воскресают. +- **FAIL:** после завершения монитора фиксируется хотя бы один статус-PATCH; либо «зомби»-тик + выполняется без активного job'а/при БД=done и шлёт статус; либо `arm_monitor` повторно ставит + `Monitoring` уже-done-задаче. + +--- + +## AC-4 — Регресс: рабочий deploy-цикл реально деплоящейся задачи + +**Условие:** реально деплоящаяся 063-подобная задача проходит self-deploy. +- **PASS:** последовательность статусов `Awaiting Deploy → Deploying → Monitoring after Deploy → + Done` работает в точности как до ORCH-094; Phase A/B/C, merge-gate, post-deploy HEALTHY-окно, + freeze-на-DEGRADED (ORCH-088) — не затронуты; терминал-aware гард (FR-2) **не** подавляет + легитимный `Monitoring` у нетерминальной задачи. +- **FAIL:** любой шаг рабочего deploy-цикла нетерминальной задачи изменён/подавлён/сломан. + +--- + +## AC-5 — Наблюдаемость, безопасность, документация, зелёный pytest + +**Условие:** реализация завершена. +- **PASS:** + - Лог однозначно показывает **кто (функция/путь) и почему** ставит deploy-статус, и что/почему + подавлено терминал-aware гардом (FR-4). + - never-raise: новая логика не бросает исключений в горячих путях; сетевая ошибка Plane → безопасная + деградация. Не трогаются `main`/force-push/прод-контейнер/детач-деплой. + - `STAGE_TRANSITIONS` / `QG_CHECKS` / machine-verdict ключи — без изменений; новая логика под + kill-switch (`False` → прежнее поведение 1:1); не-self репозитории не затронуты. + - `pytest tests/ -q` зелёный; добавлены тесты по `04-test-plan.yaml`. + - **Источник флаппа задокументирован** (что это было) в `06-adr/ADR-NNN-…` + `CHANGELOG.md`; + обновлены `CLAUDE.md` / `docs/architecture/README.md` (golden source). +- **FAIL:** нет логирования caller/причины; new-логика бросает/без флага; тронуты гейты/verdict-ключи; + красный pytest; источник флаппа не задокументирован; затронут не-self репозиторий. + +--- + +## Сводная матрица AC ↔ FR/BR + +| AC | Покрывает | +|----|-----------| +| AC-1 | BR-1 / FR-1 | +| AC-2 | BR-2 / FR-2 | +| AC-3 | BR-3, BR-4 / FR-3 | +| AC-4 | BR-5 / FR-2, FR-5 | +| AC-5 | BR-6, BR-7 / FR-4, FR-5, NFR-1…NFR-5 | diff --git a/docs/work-items/ORCH-094/04-test-plan.yaml b/docs/work-items/ORCH-094/04-test-plan.yaml new file mode 100644 index 0000000..2bf6883 --- /dev/null +++ b/docs/work-items/ORCH-094/04-test-plan.yaml @@ -0,0 +1,97 @@ +work_item: ORCH-094 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-09 +model_used: claude-opus-4-8 +title: "Тест-план: терминальная (done) задача не флаппит deploy-статусы, держит Done" +framework: pytest +scope: > + Покрывается: терминал-aware идемпотентность выставления deploy-статусов + (Awaiting/Deploying/Monitoring) для задач с БД stage=done/cancelled; детерминированный + конец post-deploy-monitor и отсутствие "зомби"-тиков/статус-PATCH после завершения; + привязка тиков монитора к активному job; наблюдаемость caller/причины; обратимость + (kill-switch) и регресс рабочего deploy-цикла реально деплоящейся задачи. + Вне покрытия: изменение STAGE_TRANSITIONS/QG_CHECKS/machine-verdict ключей (не трогаются); + поведение не-self репозиториев (проверяется как нулевая регрессия). Точный актор флаппа на + проде локализуется инструментально (developer) и фиксируется в ADR — на это отдельный + smoke/прод-чек, не unit. +notes: > + Полный регресс tests/ должен оставаться зелёным (pytest tests/ -q). Setter'ы Plane и сетевые + вызовы — мокать (никаких реальных PATCH в Plane из тестов). Регресс = любой авто-переход + done-задачи в deploy-статус, либо статус-PATCH после завершения монитора, либо подавление + легитимного Monitoring у нетерминальной задачи. Тесты опираются на фикстуры задач со стадиями + done/deploy и на счётчики вызовов set_issue_* (через мок). + +tests: + - id: TC-01 + type: unit + description: "deploy-статус для задачи с БД stage=done сходится к Done: попытка set_issue_monitoring/awaiting/deploying при terminal-стадии выставляет Done (или no-op, если уже Done), а не промежуточный статус." + module: tests/test_deploy_status_terminal_guard.py + expected: PASS + + - id: TC-02 + type: unit + description: "Идемпотентность: повторный вызов терминал-aware setter'а на уже-Done задаче — no-op (0 дополнительных PATCH), маятник Done<->deploy-статус не возникает." + module: tests/test_deploy_status_terminal_guard.py + expected: PASS + + - id: TC-03 + type: unit + description: "Нетерминальная задача (stage=deploy) не подавляется: set_issue_monitoring/awaiting/deploying проходит штатно (регресс AC-4)." + module: tests/test_deploy_status_terminal_guard.py + expected: PASS + + - id: TC-04 + type: unit + description: "Kill-switch выключен -> прежнее поведение 1:1 (терминал-aware гард не вмешивается); включён -> done-задача сходится к Done." + module: tests/test_deploy_status_terminal_guard.py + expected: PASS + + - id: TC-05 + type: unit + description: "never-raise: при невозможности определить БД-стадию / сетевой ошибке Plane сеттер деградирует безопасно (не флаппит, не бросает исключение)." + module: tests/test_deploy_status_terminal_guard.py + expected: PASS + + - id: TC-06 + type: unit + description: "post-deploy-monitor: после завершения окна (HEALTHY, ticks==budget -> set_issue_done + маркер done) последующих статус-PATCH для задачи нет (0 set_issue_* вызовов)." + module: tests/test_post_deploy_monitor_termination.py + expected: PASS + + - id: TC-07 + type: unit + description: "post-deploy-monitor тик при БД stage=done / отсутствии активного основания -> немедленный no-op без статус-PATCH и без постановки следующего тика ('зомби'-тик исключён)." + module: tests/test_post_deploy_monitor_termination.py + expected: PASS + + - id: TC-08 + type: unit + description: "arm_monitor не пере-арминг для задачи, уже находящейся в done: повторный deploy->done re-drive не выставляет Monitoring заново (маркер armed/done -> no-op)." + module: tests/test_post_deploy_monitor_termination.py + expected: PASS + + - id: TC-09 + type: unit + description: "Наблюдаемость: каждый вызов выставления deploy-статуса логирует work_item, caller/путь, целевой статус, причину и БД-стадию; подавление терминал-aware гардом тоже логируется." + module: tests/test_deploy_status_observability.py + expected: PASS + + - id: TC-10 + type: integration + description: "Реконсилятор/sync для задачи с БД=done и Plane=Monitoring приводит к Done идемпотентно (а не к промежуточному deploy-статусу) и не качает маятник на повторных тиках." + module: tests/test_reconciler_done_deploy_convergence.py + expected: PASS + + - id: TC-11 + type: integration + description: "Регресс рабочего deploy-цикла: реально деплоящаяся (нетерминальная) 063-подобная задача проходит Awaiting -> Deploying -> Monitoring -> Done без подавления (Phase A/B/C, post-deploy HEALTHY-окно как раньше)." + module: tests/test_self_deploy_cycle_regression.py + expected: PASS + + - id: TC-12 + type: integration + description: "Не-self репозиторий (enduro-подобный): нулевая регрессия — терминал-aware гард deploy-статусов инертен (условность self-hosting)." + module: tests/test_deploy_status_terminal_guard.py + expected: PASS