--- 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.` / отдельная таблица 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=` и `POST /serial-gate/resume?work_item=` (по образцу существующего `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).