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

# ADR-0051: Ось «пауза» serial-gate — park-сигнал без блокировки FIFO

Сквозной (cross-cutting) ADR. Детальное решение задачи —
`docs/work-items/ORCH-124/06-adr/ADR-001-serial-gate-pause-without-blocking.md`.

Статус: **Proposed** · Дата: 2026-06-16 · Источник: **ORCH-124** (bug → escalate full-cycle)

## Контекст

ORCH-088 (serial-gate, adr-0017) определяет «активную задачу репо» **исключительно по машинной стадии**
`tasks.stage NOT IN ('done','cancelled')` (после ORCH-090/adr-0026 — с учётом терминала `cancelled`).
Plane-статусы Backlog/Blocked/Needs-Input — **слой B (индикация), ORCH-066** — не меняют `tasks.stage`
(слой A); у таблицы `tasks` нет колонки статуса. ⇒ приостановленная оператором задача неотличима от
активно исполняемой и держит FIFO-гейт (`t2.id < jobs.task_id`) закрытым для более поздних analyst-job
того же репо.

**Инцидент ORCH-116/ORCH-123:** ORCH-116 поставили на паузу, чтобы пропустить срочный фикс ORCH-123, но
serial-gate держал analyst-job ORCH-123 в `queued`. Единственные обходы (терминальный `cancel`, довод до
`done`, глобальное `serial_gate_enabled=false`) — грубые.

Горячий путь `serial_gate.build_claim_clause` врезан в `claim_next_job` — **offline SQL** — и сетевого
чтения Plane-статуса (как делает reconciler ORCH-060) позволить не может. Нужен **DB-резолвимый** сигнал
паузы.

## Решение

### Инвариант: «пауза» — ОТДЕЛЬНАЯ ОСЬ планировщика, ортогональная «терминальности»

Вводится **per-task park-сигнал** — аддитивная нуллабельная колонка **`tasks.paused_at TEXT`**
(NULL = не на паузе) — и **новая ось планировщика «пауза»**, независимая от оси «терминальность».

| Ось | Предикат | Кто использует | Меняется ORCH-124? |
|-----|----------|----------------|--------------------|
| **Терминальность** (adr-0026) | `stage IN ('done','cancelled')` | `serial_gate` + `task_deps` + `stages.py` | **НЕТ — байт-в-байт** |
| **Пауза** (новая, ORCH-124) | `paused_at IS NOT NULL` | **только** FIFO «active» предикат `serial_gate` | да (аддитивно) |

**serial-gate «активная задача» ⇔ `stage NOT IN ('done','cancelled') AND paused_at IS NULL`.** Это
**осознанная, задокументированная** дивергенция serial-gate от чисто-терминального предиката (требование
гармонизации adr-0026): пауза выводит предшественника из FIFO-учёта serial-gate, **не делая его
терминальным**.

### Что НЕ меняется (анти-регресс adr-0026)

- **`task_deps`** (adr-0015) и **`stages.py::STAGE_TRANSITIONS`** колонку `paused_at` **не читают** —
  остаются чисто терминальными. Явно объявленная зависимость (`job_deps`) на **приостановленную** задачу
  **по-прежнему блокирует** зависимый job. Пауза («пропустите меня в FIFO») и dependency («B нужен
  результат A») — разные оси; пауза НЕ обходит dependency и НЕ обходит per-repo `repo_freeze`.
- `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict / схемы существующих таблиц — без
  изменений. Пауза — не стадия и не Quality Gate, а признак планировщика очереди.

### Точки, признающие ось «пауза» (исчерпывающе)

1. `src/serial_gate.py::build_claim_clause` — терм `AND t2.paused_at IS NULL` внутри `active_clause`
   (под под-флагом). **(маркер ORCH-124, рядом с ORCH-088/ORCH-090)**
2. `src/serial_gate.py::repo_has_active_task` / `_per_repo_snapshot` — тот же предикат + наблюдаемость
   (ключ `paused`, `reason` ожидания).
3. `src/db.py` — колонка `tasks.paused_at` (`_ensure_column`) + хелперы `set_task_paused`/
   `clear_task_paused`/`is_task_paused`.
4. `src/main.py` — операторские эндпоинты `POST /serial-gate/pause|resume` (по образцу
   `POST /serial-gate/unfreeze`).

### Анти-stale-base при возобновлении (ORCH-088 не регрессирует)

Пауза «демотирует» задачу в FIFO; свежесть базы при resume обеспечивают **существующие** механизмы — новой
rebase-машинерии нет: отложенный срез ветки (ORCH-088, для паузнутой-в-`analysis`) + безусловный pre-merge
`auto_rebase_onto_main` под merge-lease (ORCH-026/093) + merge-gate re-test (ORCH-110) для уже
материализованной ветки. Нормальная задача (`paused_at IS NULL`) по-прежнему держит гейт.

### Флаги / совместимость

- Независимый под-флаг `serial_gate_pause_enabled` (env `ORCH_SERIAL_GATE_PAUSE_ENABLED`, дефолт `True`) —
  зеркало `serial_gate_freeze_enabled`. `False` ⇒ pause-терм опущен из SQL, эндпоинты no-op ⇒ serial-gate
  байт-в-байт как ORCH-088/090. Область — переиспользует `serial_gate_repos` (новый `*_repos` не вводится).
- Дефолт `True` безопасен: пока ни одна задача не на паузе, `paused_at` везде `NULL` ⇒ истинный no-op
  (enduro не затронут).
- never-raise: pause-терм в `build_claim_clause` сохраняет **fail-OPEN**; freeze — **fail-CLOSED**.
- Миграция — только аддитивная/идемпотентная (`_ensure_column`); общая прод-БД безопасна (NFR-3).

## Последствия

- **+** Чистая операторская «пауза без блокировки», отличная от cancel (терминал) и от kill-switch;
  durable, offline, webhook-независимая; закрывает инцидент ORCH-116/ORCH-123.
- **+** Единый, явно описанный двухосевой предикат планировщика (терминальность ⊥ пауза) — устранён риск
  будущего рассинхрона.
- **−** Появилась вторая ось «активности» serial-gate — будущие подсистемы планировщика обязаны помнить:
  serial-gate «активна» = `не терминальна И не на паузе`, но **терминал** (`task_deps`/`stages.py`) ось
  «пауза» НЕ включает. Митигейшн: этот ADR + маркер `ORCH-124` в изменённых местах + тесты.
- **Откат:** `ORCH_SERIAL_GATE_PAUSE_ENABLED=false` (serial-gate 1:1 как ORCH-088/090; колонка `paused_at`
  инертна).

## Эволюция маркеров

Горячий SQL serial-gate несёт теперь 3 маркера (`ORCH-088` FIFO-гейт, `ORCH-090` терминал `cancelled`,
`ORCH-124` ось паузы) — правка любого из них сверяется с этим сводным ADR (анти-археология: 3+ маркеров →
одна ссылка сюда, `docs/_standards/TRACEABILITY.md`).

## Ссылки
- Детальный ADR: `docs/work-items/ORCH-124/06-adr/ADR-001-serial-gate-pause-without-blocking.md`
- Данные: `docs/work-items/ORCH-124/08-data-requirements.md`
- Связанные: adr-0017 (serial-gate ORCH-088), adr-0026 (терминал `{done,cancelled}` ORCH-090),
  adr-0015 (task-deps), adr-0027 (merge-актор rebase/retry ORCH-093), adr-0042 (merge-gate re-test ORCH-110)
