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