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

# 08 — Требования к данным: ORCH-090 — Механизм отмены задачи (STOP)

Work Item: **ORCH-090** · Repo: **orchestrator** · Стадия: architecture

> Общая прод-БД (orchestrator + enduro). Все изменения — **только аддитивные и идемпотентные**
> (`_ensure_column`); существующие таблицы-контракты не переопределяются (NFR-2, AC-9).

## Изменения схемы БД

### Таблица `tasks` — аддитивные колонки (через `_ensure_column`)
| Колонка | Тип | Назначение |
|---------|-----|------------|
| `cancelled_at` | `TEXT` | durable-метка времени отмены (аудит/наблюдаемость). NULL для неотменённых. |
| `cancel_requested_at` | `TEXT` | durable-метка «отмена запрошена, но отложена» (STOP в критическом окне merge/deploy, ADR-001 D7). Снимается при доведении отмены до конца. |

Никаких `ALTER` существующих колонок. `init_db` идемпотентен (повторный вызов — no-op).

### Без DDL-изменений (расширение допустимых значений TEXT)
- **`jobs.status`** — добавляется значение `cancelled` к набору `queued|running|done|failed`.
  Колонка уже `TEXT`; DDL не меняется. `claim_next_job` выбирает только `status='queued'` →
  `cancelled` исключён нативно.
- **`tasks.stage`** — добавляется терминальное значение `cancelled` (сток, параллельно `done`).
  Колонка уже `TEXT DEFAULT 'created'`; DDL не меняется. `STAGE_TRANSITIONS` exit-гейты рёбер
  **не меняются** — `cancelled` это терминальное состояние, не новое ребро.

### Без изменений
`job_deps`, `agent_runs`, `repo_freeze`, `tracker_messages`, индексы — контракты нетронуты.
`QG_CHECKS` / `check_*` — без изменений.

## Новые/изменённые сущности

### Тумбстон натуральных ключей отменённой задачи (ADR-001 D4)
На cancel выполняется UPDATE отменённой строки `tasks`:
- `plane_id := plane_id || '#cancelled-' || id`
- `work_item_id := work_item_id || '#cancelled-' || id`
- `stage := 'cancelled'`, `cancelled_at := datetime('now')`
- `plane_issue_id` — **сохраняется нетронутым** (аудит-связь с issue Plane).

Цель: освободить натуральные ключи, чтобы повторный «To Analyse» создал свежую задачу
(`get_task_by_plane_id(plane_id)` → `None`; anti-dup `create_task_atomic` /
`ensure_unique_work_item_id` не коллизируют), сохранив строку для аудита. Формат суффикса
`#cancelled-<id>` детерминирован и парсится.

### Отмена job'ов (ADR-001 D3)
`cancel_jobs_for_task(task_id)` — guarded UPDATE
`SET status='cancelled', finished_at=datetime('now') WHERE task_id=? AND status IN ('queued','running')`.
Терминальный исход, нигде не реквью'ящийся.

## Совместимость данных / миграции

- **Аддитивность/идемпотентность:** только `_ensure_column` (no-op если колонка есть) и
  расширение наборов TEXT-значений; деструктивных/несовместимых миграций нет (AC-9). Повторная
  `init_db` после рестарта не падает.
- **Restart-safe (NFR-4):** durable терминал = `tasks.stage='cancelled'` (уже понимается
  терминал-скипом реконсилятора, стр. 196). После рестарта `requeue_running_jobs` флипает только
  `running` → отменённые job'ы (`cancelled`) не оживают; отменённая задача не реконсилируется.
- **Влияние на общую прод-БД:** изменения строго per-task; enduro не затрагивается, при
  `stop_status_enabled=False` или отсутствии отменённых задач — поведение БД 1:1 как сейчас.
- **Кросс-каттинг (adr-0026):** предикат «задача незавершена» в `serial_gate`/`task_deps`
  расширяется `stage != 'done'` → `stage NOT IN ('done','cancelled')`, иначе отменённая задача
  заклинит очередь репо. Чтение БД (offline hot-path) не приобретает новых сетевых вызовов (NFR-6).
