Files
orchestrator/docs/architecture/adr/adr-0026-stop-cancel-task.md

107 lines
8.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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, стр. 398415).
- `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)