--- 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).