---
work_item: ORCH-124
stage: analysis
author_agent: analyst
status: ready-for-review
created_at: 2026-06-16
model_used: claude-opus-4-8
escalate: full-cycle
---

# 02 — ТЗ (TRZ): ORCH-124 — wait/terminal-семантика serial-gate (пауза без блокировки)

Work Item: **ORCH-124** · Repo: **orchestrator** · Стадия: analysis

> ТЗ описывает **требования к изменениям**, выведенные из BRD и фактического кода. **Выбор
> механизма** «паузы без блокировки» (release-on-status / per-task hold-флаг / task_deps) и его
> архитектурное обоснование — задача **архитектора** (`06-adr`, эскалация в full-cycle). Ниже —
> что должно стать истинным и какие модули это затронет, без предписания «как именно».

## 1. Сводка изменения
Сейчас serial-gate (`src/serial_gate.py`) считает «активной» любую задачу репо со стадией вне
`{done,cancelled}` — в трёх точках (`build_claim_clause` / `repo_has_active_task` /
`_per_repo_snapshot`). Поскольку Plane-статусы Backlog/Blocked/Needs-Input (слой B индикации) **не
меняют `tasks.stage`** (слой A), приостановленная задача неотличима от активной и держит FIFO-гейт
закрытым для более поздних analyst-job. Требуется ввести **явный, durable, DB-резолвимый** признак
«пауза/park» и научить определение «активной задачи» его учитывать, **сохранив** анти-stale-base
ORCH-088 при возобновлении (R-1). Машинные гейты не трогаются — это правка **планировщика очереди**.

## 2. Задействованные модули / пути
| Путь | Действие |
|------|----------|
| `src/serial_gate.py` | изменить — определение «активной задачи» во всех 3 точках (`build_claim_clause`, `repo_has_active_task`, `_per_repo_snapshot`); причина ожидания в снапшоте |
| `src/db.py` | изменить (вероятно) — `claim_next_job` (учёт нового предиката в горячем SQL) + (если выбран DB-сигнал) аддитивная колонка/таблица `_ensure_column`/`CREATE TABLE IF NOT EXISTS` + read-only/мутатор-хелперы |
| `src/config.py` | изменить — под-флаг(и) семантики паузы (kill-switch), область репо (по образцу `serial_gate_*`) |
| `src/main.py` | изменить (вероятно) — операторский эндпоинт pause/resume **или** расширение блока `serial_gate` в `GET /queue` причиной ожидания |
| `src/webhooks/plane.py` и/или `src/plane_sync.py` | изменить (если механизм = release-on-status) — захват намерения паузы из смены Plane-статуса в durable DB-сигнал (не на hot-path) |
| `tests/test_serial_gate*.py` (новый `tests/test_orch124_serial_gate_pause.py`) | создать/дополнить — кейсы паузы + регресс инцидента |
| `docs/architecture/README.md`, `CHANGELOG.md`, `docs/work-items/ORCH-124/06-adr/` | обновить — раздел serial-gate + ADR (архитектор) |

> Точный набор модулей зависит от выбранного механизма (ADR). Минимально-необходимый набор —
> `serial_gate.py` (3 точки) + `db.py` (hot-path) + `config.py` (флаг) + тесты; остальное — по решению
> архитектора.

## 3. Функциональные требования

### FR-1 — Признак паузы исключает задачу из «активных» в горячем SQL (BR-1, NFR-2)
`build_claim_clause()`: подзапрос `EXISTS (... t2.stage NOT IN ('done','cancelled'))` должен
**дополнительно** исключать приостановленные задачи-предшественники. Предикат — **чисто SQL по
локальной БД** (никакой сети). Форма исключения — функция выбранного DB-сигнала (доп. колонка
`tasks.<paused>` / отдельная таблица hold / `task_deps`): архитектор фиксирует точную SQL-форму в ADR.
Инвариант: job'ы уже активной задачи (`agent != 'analyst'`) проходят как раньше; самоблокировки
собственной строки (R-7 ORCH-088) нет.

### FR-2 — Зеркало и снапшот согласованы с FR-1 (BR-1/BR-4)
`repo_has_active_task()` и `_per_repo_snapshot()` используют **тот же** предикат «активна», что и
`build_claim_clause` — приостановленный предшественник не попадает в `active_task`. Все три точки
правятся согласованно (анти-дрейф: они должны давать один ответ на один вход).

### FR-3 — Явное durable намерение паузы (BR-2, BR-5, NFR-2)
Должен существовать **явный** оператор-инициируемый сигнал «park/pause» задачи, **durable**
(переживает рестарт) и **DB-резолвимый**. Распауза (resume) — обратная операция, восстанавливающая
участие задачи в serial-gate (BR-5). Сигнал **отличен** от `cancel`/`done` (не терминальный) и от
глобального kill-switch. Конкретная форма (новый эндпоинт `POST /serial-gate/pause|resume`, или
маппинг Plane-статуса в DB-сигнал через webhook, или декларация `task_deps`) — **решение архитектора**.

### FR-4 — Анти-stale-base при возобновлении (BR-3, R-1 — критично)
Решение обязано **не регрессировать** ORCH-088: нормально исполняемая задача держит гейт; а
возобновлённая ранее приостановленная задача не должна приводить к stale-базе у успешника, который
прошёл вперёд. Механизм разрешения (демотирование в FIFO + обязательный rebase при resume / явный
yield с принятием rebase / иное) фиксируется архитектором в ADR. ТЗ требует лишь: после распаузы
ни одна из задач не остаётся на устаревшей базе незаметно.

### FR-5 — Корректная причина ожидания (BR-4)
Блок `serial_gate` в `GET /queue` для ожидающего успешника различает причины: `active-task`
(идёт работа) / `paused-predecessor` (предшественник на паузе — не должно случаться после фикса, но
наблюдаемо) / `freeze` / `dependency`. Аддитивно к существующим ключам снапшота (`active_task` /
`waiting` / `frozen` / `frozen_reason` / `frozen_at`).

### FR-6 — Явные блоки сохранены (BR-6)
`repo_freeze` (durable per-repo freeze) и `task_deps` (`job_deps`) продолжают блокировать claim ровно
как сейчас. «Пауза» НЕ должна обходить freeze/dependency — это разные оси (freeze = весь репо;
dependency = конкретная пара; пауза = «пропустите меня»).

### FR-7 — Условность и нейтральность (NFR-3)
Поведение под выключателем (существующий `serial_gate_enabled` либо новый под-флаг паузы) → байт-в-байт
до ORCH-124. Область репо — по образцу `serial_gate_repos` (CSV; пусто → текущая область serial-gate,
т.е. все репо). enduro не затронут при нейтральном поведении.

## 4. Изменения API
Вероятно (на усмотрение архитектора, ADR):
- **Новые операторские эндпоинты** `POST /serial-gate/pause?work_item=<id>` и
  `POST /serial-gate/resume?work_item=<id>` (по образцу существующего `POST /serial-gate/unfreeze`),
  если механизм = явный per-task hold. Возвращают новое состояние (paused/active) задачи.
- **Расширение** `GET /queue` → блок `serial_gate` дополняется причиной ожидания (FR-5) и (если есть)
  списком приостановленных задач репо. Существующие ключи не меняются (BC).

Если механизм = release-on-status, новых ручных эндпоинтов может не быть (намерение — смена Plane-статуса,
захватываемая webhook'ом в durable DB-сигнал); тогда раздел сводится к расширению `GET /queue`.

## 5. Изменения схемы БД
Вероятно **аддитивно** (на усмотрение архитектора):
- Колонка `tasks.paused_at TEXT` (NULL = не на паузе), идемпотентно через `_ensure_column` — паттерн
  существующих `tasks.cancelled_at` / `tasks.cancel_requested_at` / `tasks.track`; **или**
- Отдельная таблица hold (по образцу `repo_freeze`: `work_item_id`/`task_id`, `paused_at`,
  `cleared_at IS NULL` = активна), `CREATE TABLE IF NOT EXISTS`.
Схемы **существующих** таблиц (`tasks`/`jobs`/`job_deps`/`repo_freeze`) — без деструктивных изменений.
Если механизм = task_deps, новой схемы может не понадобиться вовсе. Финальное решение — ADR +
`08-data-requirements.md` (архитектор).

## 6. Требования к новым/изменённым QG checks
**Нет.** `STAGE_TRANSITIONS` / состав `QG_CHECKS` / семантика и имена `check_*` / machine-verdict ключи
(`verdict:`/`result:`/`deploy_status:`/`staging_status:`/`security_status:`) — **байт-в-байт не
трогаются**. ORCH-124 — правка планировщика очереди (claim) и наблюдаемости, **не** Quality Gate и
**не** новая стадия конвейера.

## 7. Совместимость / регресс
- **Обратная совместимость:** аддитивно; выключатель → поведение до ORCH-124 байт-в-байт (NFR-3,
  FR-7). Существующие тесты serial-gate (`tests/test_serial_gate*.py`) остаются зелёными.
- **Kill-switch / область:** переиспользовать `serial_gate_enabled` (kill-switch) + при необходимости
  ввести под-флаг семантики паузы (независимый тумблер, по образцу `serial_gate_freeze_enabled`) и
  область `*_repos` (CSV).
- **Обратимость:** выставить под-флаг паузы в `False` → serial-gate работает как ORCH-088/090
  (приостановленные снова считаются активными — т.е. возврат к текущему багу, но это осознанный
  rollback-режим, не дефолт).
- **never-raise / fail-direction (NFR-1):** сохранить fail-OPEN на hot-claim build и fail-CLOSED на
  freeze; новая ветка паузы не инвертирует эти направления.
- **Гармонизация предиката (NFR-4):** не сломать `task_deps`/`stages.py` терминальное множество
  `{done,cancelled}` (ORCH-090/adr-0026); расхождение serial-gate (паузой) — только осознанно и
  задокументированно в ADR.
- **Артефакты pipeline:** обновляются `docs/architecture/README.md` (раздел serial-gate ORCH-088 +
  семантика паузы), `CHANGELOG.md`, новый `docs/work-items/ORCH-124/06-adr/ADR-001-*.md` (архитектор),
  при необходимости `08-data-requirements.md` (архитектор). Reviewer проверяет обновление доки (правило
  агентов §6).
