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