---
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), чтобы не сломать намеренный сценарий возврата к работе.
