--- work_item: ORCH-090 stage: analysis author_agent: analyst status: ready-for-review created_at: 2026-06-09 model_used: claude-opus-4-8 --- # 02 — ТЗ (TRZ): ORCH-090 — Механизм отмены задачи: статус STOP в Plane (остановка + полный сброс) Work Item: **ORCH-090** · Repo: **orchestrator** · Стадия: analysis > ТЗ описывает **что** и **где** должно измениться (модули/контракты/артефакты), выведенное из BRD > и фактического кода. **Как** (хранилище статуса отмены, точка безопасного прерывания merge/deploy, > удаление vs архив ветки, точные точки врезки) — решает архитектор в `06-adr/`. ТЗ фиксирует > требования и границы, не предлагает архитектурное решение. --- ## 1. Сводка изменения Ввести обработку нового Plane-статуса **STOP** как сигнала отмены задачи. При его получении оркестратор: (1) останавливает активного агента (graceful SIGTERM через существующий каскад), (2) отменяет все job'ы задачи и исчерпывает ретраи, (3) снимает таймеры/мониторы, (4) удаляет/ архивирует рабочую ветку+worktree и сбрасывает незавершённый прогресс в БД до состояния «отменена» (durable), сохраняя docs-артефакты. Параллельно закрывается **дыра релонча**: ручной перевод в промежуточный рабочий статус больше не запускает агента — единственный вход к запуску пайплайна остаётся «To Analyse» (`start_pipeline`). Всё — аддитивно, под kill-switch, never-raise, restart-safe. `STAGE_TRANSITIONS`, `QG_CHECKS`, `check_*` и семантика существующих статусов — **не меняются**. --- ## 2. Задействованные модули / пути | Путь | Действие | |------|----------| | `src/webhooks/plane.py` | изменить: добавить распознавание/маршрутизацию STOP (`handle_issue_updated`) → новый обработчик `handle_stop` (имя — на усмотрение архитектора); **загейтить/убрать релонч агента** в `handle_status_start` (промежуточные статусы не запускают агента; пайплайн — только из `To Analyse`/`start_pipeline`) | | `src/agents/launcher.py` | изменить: предоставить/переиспользовать остановку активного процесса задачи (SIGTERM каскад `_watchdog`; `jobs.pid`), пометку «не релончить» (исчерпание `max_attempts`/запрет авто-requeue для отменённой задачи) | | `src/queue_worker.py` / `src/db.py` | изменить: отмена job'ов задачи (queued/running → терминальный «cancelled»-исход); claim не выбирает отменённые; helper'ы выборки job'ов задачи; (возможно) новый терминальный статус job `cancelled` ИЛИ переиспользование `failed`+флаг — выбор архитектора; durable-пометка задачи «отменена» в `tasks` | | `src/git_worktree.py` | изменить/переиспользовать: удаление/архив рабочей ветки и worktree отменённой задачи (`remove_worktree`; удаление/архив Gitea-ветки) — never-raise | | `src/plane_sync.py` | изменить: маппинг Plane-статуса STOP (`_PLANE_NAME_TO_KEY` / `_DEFAULT_STATES`); переиспользовать группу `cancelled` для терминал-скипа; сеттер статуса (best-effort) | | `src/stages.py` | при необходимости — терминальная трактовка отменённой задачи (НЕ менять exit-гейты рёбер; добавление `cancelled`-стадии — решение архитектора, см. §5) | | `src/reconciler.py` | переиспользовать терминал-скип `done`/`cancelled` (`_is_terminal_state`) — отменённая задача не реконсилируется/не релончится | | `src/job_reaper.py` | согласовать: reaper не «оживляет» отменённые job'ы (терминальный исход не requeue'ится) | | `src/stage_engine.py` | согласовать: снятие таймеров/мониторов (post-deploy monitor, brd-review clock) и безопасное прерывание merge/deploy при STOP | | `src/notifications.py` | переиспользовать `send_telegram`/`update_task_tracker` для алерта/карточки отмены (never-raise, кликабельный номер) | | `src/config.py` | изменить: новый kill-switch `stop_status_enabled` (+ при необходимости область репо/доп-флаги) по образцу `serial_gate_enabled` | | `src/main.py` | изменить: read-only блок наблюдаемости отмены в `GET /queue` (аддитивно) | | `docs/architecture/README.md`, `CLAUDE.md`, `CHANGELOG.md` | обновить в том же PR (golden source) | | `tests/` | добавить тесты (см. `04-test-plan.yaml`) | > Чистую логику распознавания/решения по STOP желательно вынести в leaf-модуль (по образцу > `src/serial_gate.py` / `src/labels.py`, never-raise) — окончательно решает архитектор. --- ## 3. Функциональные требования ### FR-1 — Распознавание и маршрутизация STOP (BR-1, BR-5) - `handle_issue_updated` (`webhooks/plane.py`) распознаёт перевод задачи в логический статус STOP (через `_PLANE_NAME_TO_KEY`/группа `cancelled`) и маршрутизирует в обработчик отмены. - Обработчик идемпотентен: если задача уже отменена / `done` / отсутствует → no-op (BR-5). - Контракт — never-raise: ошибка обработки STOP логируется, вебхук-поток не падает (NFR-5). ### FR-2 — Остановка активного агента (BR-1a) - Для running-job'а задачи послать активному процессу SIGTERM (graceful) через существующий каскад `launcher._watchdog` (SIGTERM → grace `agent_kill_grace_seconds` → SIGKILL); PID берётся из `jobs.pid`. - Если активного процесса нет (idle/queued) — шаг no-op. ### FR-3 — Отмена job'ов и исчерпание ретраев (BR-1b, BR-1c) - Все job'ы задачи (`status IN (queued, running)`) переводятся в **терминальный отменённый исход** так, что `claim_next_job` их больше не выбирает и `_finalize_*`/reaper не делает авто-requeue. - Запрет авто-requeue: после STOP `attempts` считаются исчерпанными (либо отдельный терминальный статус job `cancelled`, либо `failed`+маркер — выбор архитектора). Reaper (`job_reaper.py`) и `_finalize_permanent` не должны возвращать отменённый job в `queued`. ### FR-4 — Снятие таймеров и мониторов (BR-1d) - При STOP снимаются/обнуляются связанные с задачей таймеры и фоновые наблюдатели: post-deploy monitor (ORCH-021), brd-review clock (ORCH-087), отложенные defer'ы merge-lease/serial-gate. - Терминал-скип `done`/`cancelled` (`reconciler._is_terminal_state`, ORCH-068/086) применяется к отменённой задаче, чтобы реконсилятор/мониторы её не трогали (NFR-4). ### FR-5 — Полный сброс прогресса (BR-2) - Рабочая ветка и worktree задачи удаляются/архивируются (`git_worktree.remove_worktree` + удаление/ архив Gitea-ветки; never-raise). `main` не трогается, force-push в `main` запрещён. - Незавершённый прогресс задачи в БД приводится к durable-состоянию «отменена» так, что повторный запуск возможен ТОЛЬКО через `start_pipeline` с нуля (новая ветка от свежего `origin/main`, новый analyst). Конкретика «очистить строку vs пометить cancelled» — архитектору; инвариант: возобновления «с середины» не происходит. - **Docs-артефакты задачи (`01..17`) сохраняются/бэкапятся** — не удаляются вместе с прогрессом. ### FR-6 — Закрытие дыры релонча (BR-3, BR-4) - `handle_status_start` (или эквивалентная точка) **не должен релончить агента текущей стадии** при ручном переводе в промежуточный рабочий статус (Architecture/Development/Review/Testing/ Deploying/Awaiting Deploy/Monitoring/…). - Запуск пайплайна остаётся возможен **только** через статус «To Analyse» → `start_pipeline` (создание ветки + docs + enqueue analyst). Любой намеренный сценарий «вернуть задачу в работу» (например, после Needs Input) должен быть пересмотрен так, чтобы НЕ опираться на авто-релонч агента сменой рабочего статуса (точный заменяющий механизм — архитектору). ### FR-7 — Безопасное прерывание критичных операций (BR-6, NFR-3) - STOP во время merge/deploy не оставляет `main` в half-merged состоянии и не рестартит/не роняет прод-контейнер. Если необратимый шаг (detached self-deploy / слияние PR) уже запущен — STOP не «разрывает» его с порчей: допускается дать необратимому шагу завершиться/зафиксировать честный исход, после чего применить отмену. Точка безопасного прерывания и обработка merge-lease — ADR. ### FR-8 — Наблюдаемость (BR-8) - Каждое срабатывание STOP: `logger.info/warning` (что остановлено/сброшено), Telegram-алерт (`send_telegram`, кликабельный номер `plane_issue_link`), Plane-коммент (best-effort), обновление live-карточки (`update_task_tracker`, never-raise), read-only блок отмены в `GET /queue`. --- ## 4. Изменения API - **Новых обязательных публичных endpoint'ов нет.** Триггер STOP — смена статуса Plane (webhook), не REST. (По аналогии с ORCH-088 возможен опциональный админ-эндпоинт принудительной отмены — на усмотрение архитектора; если вводится, описать в ADR и таблице API README.) - `GET /queue` — **аддитивно**: новый read-only блок (например `stop`/`cancel`) — флаг `enabled`, счётчик отменённых задач/job'ов, последние отмены. Существующие ключи не меняются; never-raise. - Внешний контракт вебхука `POST /webhook/plane` — не меняется (новая ветка обработки статуса внутри `handle_issue_updated`). --- ## 5. Изменения схемы БД > Только **аддитивные, идемпотентные** миграции (общая прод-БД; enduro не трогать). > `CREATE TABLE IF NOT EXISTS` / `_ensure_column`. - **Статус job «отменён» (FR-3):** требуется терминальный исход, который не requeue'ится. Варианты (выбор — архитектор): новый статус `jobs.status='cancelled'` ИЛИ переиспользование `failed` + аддитивный маркер. Требование к выбранному варианту: claim/finalize/reaper не возвращают его в `queued`; restart-safe. - **Состояние задачи «отменена» (FR-5, NFR-4):** durable-признак, что задача отменена и не возобновляется с середины. Варианты: добавление терминальной стадии `cancelled` в `tasks.stage` (учитывается терминал-скипом `done`/`cancelled`, уже поддержан reconciler'ом) ИЛИ аддитивная колонка/таблица. `STAGE_TRANSITIONS` (exit-гейты рёбер) при этом **не меняются** — отмена это терминальное состояние, не новое ребро конвейера. - `QG_CHECKS`, `check_*`, `job_deps`, `agent_runs`-контракт, `repo_freeze` — **без изменений**. --- ## 6. Требования к новым/изменённым QG checks - **Новых QG-проверок не вводить.** STOP — это решение диспетчера статусов/планировщика (отмена), а не Quality Gate стадии. Реестр `QG_CHECKS` и `check_*` не меняются (по образцу `task_deps` ORCH-026 и `serial_gate` ORCH-088 — логика в обработчике/claim, не новый QG). --- ## 7. Совместимость / регресс - **Kill-switch:** новый флаг `stop_status_enabled` (env `ORCH_STOP_STATUS_ENABLED`) по образцу `serial_gate_enabled`; `False` → STOP-обработка и закрытие дыры релонча ведут себя нейтрально (поведение строго как сейчас, нулевая регрессия). При необходимости — область репо (`stop_status_repos`, CSV) с дефолтом «все репо» (отмена осмысленна и для enduro). - **Аддитивность БД (NFR-2):** только идемпотентные миграции; enduro при выключенном/неприменимом флаге не затрагивается. - **Инварианты (не нарушать):** `STAGE_TRANSITIONS`, реестр `QG_CHECKS`, `check_*`, exit-коды deploy-хука, merge-gate (ORCH-043), merge-verify (ORCH-071/073), image-freshness (ORCH-058), post-deploy контракт (ORCH-021), serial-gate (ORCH-088), auto-label (ORCH-089), семантика Rejected/Approved/Confirm Deploy — **без изменений**. - **Self-hosting safety (NFR-3):** STOP не рестартит/не роняет прод-контейнер; не push/force-push в `main`; merge/deploy прерываются fail-safe (без half-merge). - **never-raise (NFR-5):** обработчик STOP и закрытие релонча не валят вебхук-поток; ошибка на единице работы изолирована. - **Артефакты pipeline (создать/обновить в том же PR):** `docs/work-items/ORCH-090/06-adr/ADR-001-…` (решение архитектора), `docs/architecture/README.md` (раздел «STOP / отмена задачи (ORCH-090)», обновление описания `GET /queue`, раздела статусной модели и при новой таблице/колонке — раздела «База данных»), `CLAUDE.md` (абзац о STOP в статусной модели), `CHANGELOG.md` (`feat:`); при новой таблице/колонке — `docs/work-items/ORCH-090/08-data-requirements.md`; при админ-эндпоинте — таблица API в README. --- ## 8. Открытые вопросы для архитектора (не блокируют анализ) - OQ-1: Имя Plane-статуса — отдельный «STOP» (новый key) vs переиспользование существующего «Cancelled» (key `cancelled` уже в `_PLANE_NAME_TO_KEY`). Влияет на маппинг и группу терминал-скипа. - OQ-2: Статус отменённого job — новый `cancelled` vs `failed`+маркер. - OQ-3: Состояние отменённой задачи — терминальная стадия `cancelled` vs аддитивная колонка/таблица. - OQ-4: Сброс прогресса — удалить строку task (полный re-create через To Analyse) vs пометить cancelled и при To Analyse создавать новую задачу. - OQ-5: Удаление vs архив рабочей ветки (и Gitea-ветки) — что безопаснее для аудита. - OQ-6: Точка безопасного прерывания merge/deploy (FR-7) и обработка удерживаемого merge-lease. - OQ-7: Чем заменить легитимный «resume после Needs Input», который сейчас опирается на релонч в `handle_status_start` (FR-6), чтобы не сломать намеренный сценарий возврата к работе.