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