---
work_item: ORCH-090
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-09
model_used: claude-opus-4-8
---

# ADR-0026: Системное терминальное состояние `cancelled` — STOP-отмена задачи

Сквозной (cross-cutting) ADR. Детальное решение задачи —
`docs/work-items/ORCH-090/06-adr/ADR-001-stop-cancel-task.md`.

## Статус
Proposed

## Контекст

ORCH-090 вводит Plane-статус **STOP** — единый декларативный механизм отмены задачи (остановка
агента + полный сброс прогресса). Самое́ кросс-каттинговое следствие — появление **нового
системного терминального состояния `cancelled`** (стадия `tasks.stage='cancelled'` + терминальный
job-статус `jobs.status='cancelled'`). До ORCH-090 «терминальность задачи» в горячем планировщике
была захардкожена как **`stage == 'done'`** (единственный сток в `STAGE_TRANSITIONS`), и это
определение разъехалось между подсистемами:

- `src/reconciler.py` **уже** трактует `stage in ("done","cancelled")` как терминал-скип
  (ORCH-086 D2 предвосхитил `cancelled`; стр. 196) и `_is_terminal_state` по группе Plane
  `{completed, cancelled}` (ORCH-068, стр. 398–415).
- `src/serial_gate.py` (ORCH-088) и `src/task_deps.py` (ORCH-026) считают задачу «незавершённой»
  по `stage != 'done'` — **без** `cancelled`. Если ввести `cancelled`-стадию, не тронув их,
  отменённая задача навсегда будет «активной»/«незавершённой зависимостью» и **заклинит очередь
  репо**.

Этот ADR фиксирует `cancelled` как первоклассное терминальное состояние, равноправное `done`, и
перечисляет ВСЕ точки, где системный предикат терминальности должен его признавать.

## Решение

### Инвариант
**«Задача терминальна» ⇔ `stage ∈ {done, cancelled}`.** Это единое определение для всех
подсистем планировщика/мониторинга. `cancelled` — терминальный **сток** (не новое ребро
конвейера): exit-гейты рёбер `STAGE_TRANSITIONS` и реестр `QG_CHECKS`/`check_*` **не меняются**.

### Точки, признающие `cancelled` терминальным (исчерпывающе)
1. `src/stages.py::STAGE_TRANSITIONS` — добавить сток
   `"cancelled": {"next": None, "agent": None, "qg": None}` (параллельно `done`).
2. `src/serial_gate.py` — `repo_has_other_unfinished` и claim-фрагмент `t2.stage != 'done'`,
   snapshot: `stage != 'done'` → `stage NOT IN ('done','cancelled')`. **(маркер ORCH-088)**
3. `src/task_deps.py` — dep-gate и `is_task_ready`: `stage != 'done'` →
   `stage NOT IN ('done','cancelled')`. **(маркер ORCH-026)**
4. `src/reconciler.py` — уже покрыто скипом `stage in ("done","cancelled")` (стр. 196);
   `get_active_tasks_for_reconcile` опционально сузить до `NOT IN ('done','cancelled')`.
5. `src/job_reaper.py` / `src/queue_worker.py` — перед авто-requeue dead/running-job'а сверять
   терминал задачи: `stage in ("done","cancelled")` → job помечается `cancelled`, не реквью'ится.
6. `src/post_deploy.py` / `stage_engine.run_post_deploy_monitor` — монитор не тикает по
   отменённой задаче (терминал-проверка/маркер `done`).

### Новые терминальные исходы
- **Job:** `jobs.status='cancelled'` — нигде не реквью'ится; `claim_next_job` выбирает только
  `status='queued'` (изменений в claim нет). `mark_job` стампит `finished_at` для `cancelled`.
- **Задача:** `tasks.stage='cancelled'` + аддитивные колонки `cancelled_at`,
  `cancel_requested_at` (отложенная отмена в критическом окне merge/deploy). Натуральные ключи
  `plane_id`/`work_item_id` тумбстонятся (`#cancelled-<id>`) для переиспользования «To Analyse»
  с нуля; `plane_issue_id` сохраняется (аудит). Детали — 08-data-requirements.md.

### Точки врезки STOP (компоненты)
- `plane.py` — маршрут `stop` (fail-closed, не в `_DEFAULT_STATES`) → `handle_stop`; гейт релонча
  ограничен стадией `analysis`.
- `stage_engine.cancel_task` — оркестрация отмены (graceful SIGTERM, cancel-jobs, worktree+branch,
  tombstone, notify); безопасное прерывание merge/deploy (D7 локального ADR).
- leaf `src/cancel.py` — чистая логика (`applies`/`in_critical_window`/`snapshot`), never-raise.
- `src/gitea.py` — `delete_remote_branch` (never-raise; только feature-ветка, `main` неприкосновенен).
- `GET /queue` — read-only блок `stop`.

### Флаги / совместимость
- Kill-switch `stop_status_enabled` + scope `stop_status_repos` (CSV, пусто → все репо).
- При `stop_status_enabled=False`: STOP-обработка и гейт релонча инертны; расширение
  терминал-набора `cancelled` безвредно при отсутствии отменённых задач → **нулевая регрессия**.
- `STAGE_TRANSITIONS` (exit-гейты) / `QG_CHECKS` / `check_*` / семантика
  Approved/Rejected/Confirm Deploy / merge-gate (ORCH-043) / merge-verify (ORCH-071/073) /
  image-freshness (ORCH-058) / post-deploy (ORCH-021) / serial-gate FIFO (ORCH-088) / auto-label
  (ORCH-089) — **без изменений**.
- Миграции БД — только аддитивные/идемпотентные (`_ensure_column`); enduro не затронут (NFR-2).

## Последствия
- **+** Единое, консистентное определение терминальности — устранён латентный рассинхрон
  `done`-only между планировщиком и реконсилятором.
- **+** STOP безопасен для self-hosting: не трогает `main`/прод, отложенная отмена в критическом
  окне.
- **−** Терминальность теперь читается из набора `{done, cancelled}`, а не из скаляра `'done'` —
  будущие подсистемы обязаны использовать набор. Митигейшн: этот ADR + маркер `ORCH-090` в
  изменённых местах + тесты.
- **Откат:** `stop_status_enabled=False`; полный revert — снять врезки и вернуть предикаты к
  `stage != 'done'`.

## Эволюция маркеров `cancelled`-терминала
Места, признающие `cancelled` терминальным (см. список выше), несут маркер `ORCH-090`. Правка
любого из них — сверяться с этим ADR (анти-археология: 3+ маркеров → одна ссылка сюда,
TRACEABILITY.md).

## Ссылки
- Детальный ADR: `docs/work-items/ORCH-090/06-adr/ADR-001-stop-cancel-task.md`
- Data: `docs/work-items/ORCH-090/08-data-requirements.md`
- Связанные: adr-0017 (serial-gate), adr-0015 (task-deps), adr-0007 (self-deploy),
  adr-0006 (merge-gate), adr-0018 (auto-label)
