--- work_item: ORCH-090 stage: analysis author_agent: analyst status: ready-for-review created_at: 2026-06-09 model_used: claude-opus-4-8 --- # 01 — BRD (бизнес-требования): ORCH-090 — Механизм отмены задачи: статус STOP в Plane (остановка + полный сброс) Work Item: **ORCH-090** · Repo: **orchestrator** · Стадия: analysis ## 1. Бизнес-контекст и проблема Сегодня в оркестраторе **нет штатного способа отменить/остановить задачу**. Оператор вынужден выполнять разрозненные ручные действия: убить процесс агента, дождаться исчерпания ретраев job, удалить ветку/worktree/строку task из БД и вручную сбросить статус в Plane. Это медленно, ошибкоопасно и не воспроизводимо (инцидент 09.06 с ORCH-087 — оператор делал всё это руками). Вторая, связанная проблема — **дыра ручного релонча**: `src/webhooks/plane.py::handle_status_start` при ручном переводе задачи в рабочий статус (через «To Analyse» / In Progress) **повторно ставит в очередь агента текущей стадии на той же ветке** (`has_active_job_for_task` → иначе `enqueue_job(stage_agent, …)`). Это означает, что попытка оператора «подтолкнуть» задачу сменой статуса может незаметно релончить агента — именно этот механизм усугубил сегодняшний инцидент. Требуется единый, декларативный механизм: **перевод задачи в новый Plane-статус STOP → оркестратор немедленно останавливает всю работу по задаче и полностью сбрасывает её прогресс**. Повторный запуск возможен ТОЛЬКО через «To Analyse» (с нуля). Никакой другой статус пайплайн не запускает. **Установленные факты (по текущему коду, не изобретать):** - Машина стадий — `src/stages.py::STAGE_TRANSITIONS`; терминальная стадия только `done` (`cancelled`-стадии нет). - Plane-маппинг — `src/plane_sync.py`: `_PLANE_NAME_TO_KEY` уже содержит `"Cancelled" → "cancelled"`, `_DEFAULT_STATES` содержит UUID `cancelled`; имени «STOP» в маппинге сейчас нет. - Остановка процесса агента уже реализована как graceful-каскад в `src/agents/launcher.py::_watchdog` (SIGTERM → grace `agent_kill_grace_seconds` → SIGKILL); PID задачи хранится в `jobs.pid`. - Статусы job в `jobs` — `queued | running | done | failed`; статуса `cancelled` нет. - Терминал-скип для реконсилятора/мониторов уже учитывает `done` и `cancelled` (`src/reconciler.py::_is_terminal_state`, ORCH-068/086). - Запуск пайплайна с нуля — `handle_status_start → start_pipeline` (создаёт ветку + docs + analyst). ## 2. Объём (scope) ### В объёме - Новый Plane-статус **STOP** как сигнал отмены задачи (распознавание в диспетчере статусов). - **Остановка задачи (G1):** graceful-стоп активного агента (SIGTERM), отмена всех job'ов задачи (queued/running → терминальный «cancelled»-исход), исчерпание ретраев (запрет авто-requeue), снятие таймеров/мониторов (post-deploy monitor, brd-review clock и т.п.). - **Полный сброс прогресса (G2):** удаление/архив рабочей ветки и worktree, очистка незавершённого прогресса задачи в БД так, чтобы повторный старт шёл строго через `start_pipeline` (с нуля). Docs-артефакты задачи — сохранить/забэкапить (не теряем аналитику). - **Закрытие дыры релонча (G3/G4):** перевод в любой промежуточный рабочий статус (Development/Architecture/Review/Deploying/…) вручную **не** запускает агента; единственный вход к запуску пайплайна — «To Analyse» (старт с нуля). - **Идемпотентность и fail-safe (G5):** STOP на уже остановленной/завершённой задаче — no-op; STOP во время критичной операции (merge/deploy) — корректное прерывание без порчи `main`/прода. - Kill-switch фичи; наблюдаемость (лог + Telegram + блок в `GET /queue`). - Обновление документации (CLAUDE.md, architecture/README.md, CHANGELOG.md) и инфра-предусловие (создать статус STOP на доске Plane). ### Вне объёма - Автоматическая отмена задач по таймауту/эвристике — STOP только по явному человеческому сигналу. - Возобновление задачи «с середины» после STOP — сознательно НЕ поддерживается (только перезапуск с нуля через To Analyse). - Изменение семантики Rejected (откат на стадию назад) — STOP это отдельный путь, не Rejected. - Изменение состава/семантики `STAGE_TRANSITIONS` exit-гейтов и `QG_CHECKS` / `check_*`. - Откат уже задеплоенного в прод кода (rollback) — STOP не выполняет rollback; он лишь прерывает незавершённую работу безопасно. - Кросс-проектная отмена пакета задач (отменяется одна задача за сигнал). ## 3. Заинтересованные стороны - **Заказчик / владелец продукта:** Слава (идея STOP-статуса). - **Оператор оркестратора** (Стрим и др.) — главный потребитель: получает кнопку «отменить» вместо ручной хирургии по БД/процессам. - **Затрагиваемые проекты:** orchestrator (self-hosting) и enduro-trails (общая прод-БД/очередь) — изменения должны быть аддитивны и не задевать enduro при выключенном/неприменимом флаге. - **Принимает результат:** reviewer/tester по критериям приёмки (`03`/`04`). ## 4. Бизнес-требования (BR) - **BR-1 (STOP останавливает работу)** — перевод задачи в Plane-статус STOP → оркестратор останавливает всю работу по задаче: (a) активному агенту посылается SIGTERM (graceful, с последующим жёстким kill по существующему grace-каскаду); (b) все job'ы задачи (queued и running) переводятся в терминальный «отменённый» исход и не выбираются claim'ом; (c) ретраи исчерпываются (никакого авто-requeue после STOP); (d) таймеры/мониторы задачи (post-deploy monitor, brd-review clock, merge-lease defer и т.п.) снимаются. Контракт фичи — **never-raise**. - **BR-2 (STOP = полный сброс)** — после STOP задача НЕ продолжается с середины. Рабочая ветка+worktree удаляются/архивируются; незавершённый прогресс задачи в БД очищается или помечается так, что повторный запуск идёт через `start_pipeline` с нуля (свежая ветка от актуального `origin/main`, новый аналитик). Docs-артефакты (`01..17`) — сохранить/забэкапить. - **BR-3 (единственный вход — To Analyse)** — единственный Plane-статус, запускающий пайплайн — «To Analyse» (старт с нуля). После STOP повторный «To Analyse» создаёт задачу заново. - **BR-4 (закрыть дыру релонча)** — ручной перевод задачи в любой промежуточный рабочий статус (Architecture/Development/Review/Testing/Deploying/Awaiting Deploy/…) **не** запускает агента соответствующей стадии. Текущее поведение `handle_status_start`, релончащее агента текущей стадии на той же ветке, должно быть устранено/загейчено так, чтобы пайплайн стартовал только из «To Analyse». - **BR-5 (идемпотентность)** — STOP на задаче, которая уже остановлена (cancelled), уже `done` или не существует, — **no-op** (без ошибок, без побочных эффектов, без повторного kill). - **BR-6 (безопасное прерывание критичных операций)** — STOP во время merge/deploy не оставляет `main` в half-merged состоянии и не роняет/не рестартит прод-контейнер. Если критичный шаг уже необратимо запущен (детач-деплой/слияние в процессе), STOP не должен его «разорвать» с порчей — допустимо дождаться/пропустить необратимый шаг и зафиксировать честный итог (детали безопасной точки прерывания — архитектору). - **BR-7 (STOP ≠ Rejected)** — STOP это полная остановка+сброс задачи, а не откат на предыдущую стадию. Существующий путь Rejected (`handle_verdict(approved=False)` → `_rollback_stage`) не меняется и не смешивается с STOP. - **BR-8 (наблюдаемость)** — каждое срабатывание STOP прозрачно: лог, Telegram-уведомление (с кликабельным номером задачи), Plane-коммент (best-effort), отражение в live-карточке и read-only блок в `GET /queue`. ## 5. Нефункциональные требования (NFR) - **NFR-1 (нулевая регрессия + kill-switch)** — фича под флагом включения (по образцу `serial_gate_enabled`/`merge_gate_enabled`); при выключенном флаге поведение оркестратора строго как сейчас. `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / семантика существующих статусов — без изменений. - **NFR-2 (общая прод-БД, аддитивность)** — любые изменения схемы БД — только аддитивные и идемпотентные (`CREATE TABLE IF NOT EXISTS` / `_ensure_column`); enduro-trails не затрагивается. - **NFR-3 (self-hosting safety)** — STOP не должен убить сам оркестратор / прод и не портить `main`. Прерывание merge/deploy — fail-safe (не оставлять half-merge; не рестартить прод). - **NFR-4 (restart-safe)** — состояние «задача отменена» durable (БД); после рестарта контейнера отменённая задача не «оживает» и не релончится reconciler'ом/reaper'ом (переиспользовать терминал-скип `done`/`cancelled`). - **NFR-5 (never-raise)** — обработчик STOP и закрытие дыры релонча не должны валить вебхук-поток; ошибка на единице работы логируется и не прерывает обработку других задач/проектов. - **NFR-6 (offline-устойчивость горячего пути)** — закрытие дыры релонча и терминал-скип не должны добавлять обязательных сетевых вызовов в горячий claim-цикл. ## 6. Допущения и ограничения - На доске Plane проекта ORCH будет создан статус **STOP** (инфра-предусловие); до его создания фича в режиме fail-safe (нет статуса → нет STOP-действия, ничего не ломается). - Логический ключ `cancelled` и его UUID/группа уже присутствуют в `plane_sync` — STOP может переиспользовать «cancelled»-семантику терминал-скипа (точное соответствие имя→ключ и группа-`cancelled` — решение архитектора). - Существующий graceful kill-каскад агента (`_watchdog`: SIGTERM→grace→SIGKILL) переиспользуется для остановки активного агента; новый механизм kill не изобретается. - Терминал-скип `done`/`cancelled` в `reconciler`/мониторах уже есть и должен покрыть STOP-отменённые задачи (NFR-4) — переиспользовать, не дублировать. - Архитектурные решения (хранилище статуса отмены, точка безопасного прерывания merge/deploy, удаление vs архив ветки, точные точки врезки в `plane.py`) — зона архитектора (`06-adr/`). ## 7. Критерии успеха STOP-статус, выставленный на задаче, приводит к: остановленному агенту, отменённым job'ам без авто-requeue, снятым таймерам/мониторам, удалённой/заархивированной ветке+worktree, durable-статусу «отменена» (переживает рестарт), сохранённым docs-артефактам. Ручной перевод в промежуточный рабочий статус более не релончит агента; пайплайн стартует только из «To Analyse». STOP идемпотентен и безопасен при merge/deploy. Детальные PASS/FAIL — в `03-acceptance-criteria.md`. ## 8. Риски - Гонка «STOP во время merge/deploy» → риск half-merge/порчи `main` (mitigation: безопасная точка прерывания, fail-safe — детали архитектору). - Закрытие дыры релонча может задеть легитимный сценарий resume после «Needs Input» → нужно сохранить намеренные сценарии возврата к работе, не ломая их (уточнить с архитектором, какой путь заменяет релонч). - Очистка прогресса в БД при общей прод-БД → риск задеть enduro/другие задачи (mitigation: строго per-task, аддитивно). - Детали — `10-tech-risks.md` (заполняет архитектор).