--- 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-`) для переиспользования «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)