Files
orchestrator/docs/work-items/ORCH-124/01-brd.md
claude-bot fef5ba15d5
All checks were successful
CI / test (push) Successful in 1m9s
analyst(ET): auto-commit from analyst run_id=763
2026-06-16 17:56:23 +03:00

19 KiB
Raw Blame History

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

01 — BRD / Bug-report: ORCH-124 — serial gate treats Backlog/Blocked/Needs-Input paused tasks as active and blocks urgent successors

Work Item: ORCH-124 · Repo: orchestrator · Стадия: analysis · Трек: Bug → эскалация в full-cycle

⚠️ escalate: full-cycle (ADR-001 D5 ORCH-019). Метка задачи — Bug, но по сути это архитектурный дефект: требуется определить семантику wait/terminal состояний serial-gate и выбрать механизм «пауза без блокировки» (release-on-status / явный per-task hold-флаг / переиспользование task_deps). Любой вариант пересекается с корневым инвариантом ORCH-088 (анти-stale-base) и с гармонизированным терминальным предикатом ORCH-090 (adr-0026, serial_gate + task_deps + stages.py). Это не «однострочная» правка — нужен ADR с явным разрешением конфликта свойств (см. §8 и 10-tech-risks.md архитектора). Поэтому выпускается полный analysis-пакет (а не облегчённый bug-пакет). Оператор снимает багфикс-трек: POST /bug-fast-track/escalate?work_item=ORCH-124 → задача пойдёт через стадию architecture (architect выпустит ADR для семантики паузы serial-gate).


1. Бизнес-контекст и проблема

Симптом (наблюдаемое — установленный факт инцидента)

Во время инцидента ORCH-116/ORCH-123: задачу ORCH-116 намеренно поставили на паузу (перевели в Plane-статус Blocked/Backlog), чтобы вперёд пропустить срочный фикс ORCH-123. Однако serial_gate по-прежнему считал ORCH-116 активной задачей (active_task) и держал analyst-job ORCH-123 в очереди (queued) — срочный фикс не мог стартовать, пока ORCH-116 формально не done/cancelled.

Причина симптома (установленный факт — верифицировано по коду)

serial_gate определяет «активную задачу репо» исключительно по машинной стадии tasks.stage NOT IN ('done','cancelled') — в трёх местах src/serial_gate.py:

  • build_claim_clause() — горячий SQL-фрагмент в db.claim_next_job: EXISTS (SELECT 1 FROM tasks t2 WHERE t2.repo = jobs.repo AND t2.id < jobs.task_id AND t2.stage NOT IN ('done','cancelled'));
  • repo_has_active_task() — Python-зеркало для наблюдаемости;
  • _per_repo_snapshot() — выбор active_task для блока serial_gate в GET /queue.

При этом Plane-статусы Backlog / Blocked / Needs Input — это слой B (индикация), ORCH-066, и они не меняют tasks.stage (слой A). Сеттеры set_issue_blocked / set_issue_needs_input (src/plane_sync.py) делают только PATCH Plane-статуса; машинная стадия задачи остаётся прежней (analysis / development / deploy-staging …). Подтверждение из кода: у таблицы tasks нет колонки статуса (комментарий src/reconciler.py:322: «tasks has no status column, so the live Plane state is the source of truth»). Следовательно для serial-gate приостановленная задача неотличима от активно исполняемой — её стадия не входит в {done,cancelled}, значит она «активна» и блокирует FIFO всех более поздних analyst-job того же репо.

Почему это важно (бизнес-боль)

  • Срочный фикс не запускается, пока более ранняя задача поставлена на паузу. Единственные существующие способы «разблокировать» — терминально cancel/довести до done, либо целиком выключить serial-gate (ORCH_SERIAL_GATE_ENABLED=false) для всех репо. Все три — грубые.
  • У оператора нет чистого механизма «пауза без блокировки» с явным намерением — отдельного от отмены (терминал) и от глобального выключения гейта.
  • На пакетном автономном прогоне (эпик ORCH-088) это превращает любую «отложенную» задачу в стоп-кран очереди репо.

Прецедент в коде (контекст для архитектора, не решение)

Reconciler уже умеет уважать wait-состояния: ORCH-060 Guard 2 (reconciler._is_blocked_or_needs_input, Variant A) сетевым запросом Plane-статуса пропускает Blocked/Needs-Input (и активные ORCH-066-ожидания) и не «оживляет» их. Но reconciler — фоновый тик и может позволить себе сетевой вызов; serial_gate.build_claim_clause врезан в claim_next_job (offline hot-path) и сетевого вызова позволить не может (NFR-2 ниже). Это центральное расхождение, которое и порождает баг: сигнал паузы есть в Plane, но не доступен горячему SQL гейта.

2. Объём (scope)

В объёме

  • Определить семантику wait/terminal для serial-gate: какие состояния задачи-предшественника НЕ должны держать FIFO-гейт закрытым для более поздней задачи.
  • Дать оператору явный, durable, DB-резолвимый механизм «пауза без блокировки» (или формально переиспользовать существующий: freeze / task_deps), с чётким намерением, отличным от cancel.
  • Поправить определение «активной задачи» во всех трёх точках serial_gate.py, чтобы приостановленная задача не считалась active_task.
  • Корректная причина ожидания в блоке serial_gate снапшота GET /queue (active task / paused-predecessor / freeze / dependency).
  • Тесты: предшественник Blocked/Backlog/Needs-Input + срочный успешник; регресс-тест инцидента.

Вне объёма

  • Изменения STAGE_TRANSITIONS / QG_CHECKS / check_* / machine-verdict ключей — не трогаем (маршрутизация очереди — свойство планировщика, не Quality Gate).
  • Введение нового машинного статуса в STAGE_TRANSITIONS (это не новая стадия конвейера).
  • Изменение поведения reconciler ORCH-060 (его networked-skip уже корректен; гармонизация — на усмотрение архитектора, но переписывать его не требуется).
  • Автоматическое управление паузой по данным вне явного намерения оператора (никакого эвристического «само-распаузивания»).
  • Конкретный выбор механизма (release-on-status vs per-task hold-флаг vs task_deps) — это решение архитектора (ADR), а не аналитика.

3. Заинтересованные стороны

  • Оператор/владелец конвейера (Слава) — заказчик: нуждается в чистой паузе, чтобы пропускать срочные фиксы без отмены и без выключения гейта.
  • Self-hosting orchestrator — затрагивается напрямую (serial-gate активен для всех репо).
  • enduro-trails — затрагивается косвенно (общая БД/очередь); регрессия недопустима при выключенном/нейтральном поведении.
  • Архитектор — принимает решение о механизме и семантике (ADR), разрешает конфликт §8.
  • Принимает результат — reviewer + tester по критериям 03-acceptance-criteria.md.

4. Бизнес-требования (BR)

  • BR-1 — Перевод задачи-предшественника в состояние паузы/ожидания (Backlog / Blocked / Needs Input) больше не должен случайно блокировать более позднюю срочную задачу того же репо в serial-gate. Проверяемо: analyst-job успешника становится claimable.
  • BR-2У оператора есть чистый механизм «пауза без блокировки» с явным намерением, отличный от cancel (терминал) и от глобального выключения гейта. Намерение — durable (переживает рестарт процесса/контейнера).
  • BR-3 — Пауза снимает гейт только по явному намерению. Нормально исполняемая задача (реально идёт работа) по-прежнему держит гейт — анти-stale-base гарантия ORCH-088 не регрессирует (см. §8 — конфликт свойств, разрешает архитектор).
  • BR-4 — Снапшот serial_gate в GET /queue показывает корректную причину ожидания успешника: активная задача / приостановленный предшественник / freeze / dependency.
  • BR-5 — При возобновлении (распаузе) задачи serial-ordering корректно восстанавливается: возобновлённая задача снова участвует в гейте (либо держит его, либо явно ре-входит в FIFO с обязательством rebase) — нет «вечного обхода» и нет потерянного намерения.
  • BR-6 — Существующие гарантии serial-gate сохранены: FIFO по более ранним незавершённым задачам, durable per-repo freeze (repo_freeze), cross-repo параллелизм, явные task_deps — по-прежнему блокируют, где должны.

5. Нефункциональные требования (NFR)

  • NFR-1 (never-raise / fail-safe) — Контракт leaf serial_gate сохранён: каждая публичная функция деградирует консервативно. Сохранить два направления отказа ORCH-088: hot-claim build → fail-OPEN (""-фрагмент, не заклинить очередь всех проектов, AC-8 ORCH-088); freeze-решение → fail-CLOSED (прод-безопасность, AC-9 ORCH-088). Новая логика паузы не должна инвертировать эти направления.
  • NFR-2 (чистота hot-path) — Гейт-в-claim остаётся offline SQL-предикатом; никаких сетевых вызовов (в т.ч. Plane API) в claim_next_job. Сигнал «пауза» обязан быть DB-резолвимым (durable колонка/таблица), а не считываться из Plane на горячем пути (в отличие от reconciler).
  • NFR-3 (обратная совместимость / kill-switch / область) — Изменение аддитивно и обратимо; выключатель (существующий serial_gate_enabled либо новый под-флаг) → байт-в-байт прежнее поведение до ORCH-124. STAGE_TRANSITIONS / QG_CHECKS / check_* / machine-verdict ключи / схемы существующих таблиц — без изменений (новая колонка/таблица — только аддитивно, паттерн _ensure_column / CREATE TABLE IF NOT EXISTS). enduro не затронут при нейтральном поведении.
  • NFR-4 (гармонизация предиката) — Любой новый предикат «активна/терминальна/пауза» должен оставаться согласованным с гармонизированным терминальным множеством {done,cancelled} в serial_gate + task_deps + stages.py (ORCH-090 / adr-0026), либо архитектор явно описывает, почему serial-gate расходится (паузой), не ломая task_deps.
  • NFR-5 (self-hosting безопасность) — Никакого рестарта/падения прод-контейнера, мутации main, force-push; только чтение/запись своих таблиц и принятие решения о claim. Hot-path не должен замедляться сетью или тяжёлым запросом.

6. Допущения и ограничения

  • У таблицы tasks сегодня нет колонки статуса; Plane-статус (Backlog/Blocked/Needs Input) — слой B индикации, в БД не отражён. Значит «пауза» для горячего пути требует нового durable DB-сигнала (колонка tasks или отдельная таблица), либо переиспользования уже DB-резолвимого механизма (repo_freeze / task_deps).
  • repo_freeze существует, но freeze'ит весь репо (блокирует всех успешников) — это противоположность «пропустить срочного успешника», поэтому как есть не годится для BR-1 (но годится как явный блок для BR-6).
  • task_deps (job_deps) — явные декларации зависимостей, уже DB-резолвимы и консультируются в claim_next_job (NOT EXISTS); кандидат на «explicit intent», на усмотрение архитектора.
  • Reconciler ORCH-060 различает Blocked/Needs-Input сетевым запросом Plane — прецедент семантики, но не переиспользуем на hot-path (NFR-2).
  • Серый кейс: Needs Input во время analysis — нормальное короткое ожидание ответа; решение, считать ли его «паузой для гейта», за архитектором (важно не превратить штатное короткое ожидание в обход анти-stale-base).

7. Критерии успеха

Кратко (детальные PASS/FAIL — 03-acceptance-criteria.md):

  • Приостановленный предшественник (Backlog/Blocked/Needs-Input по явному намерению) не блокирует срочного успешника; нормально идущая задача — блокирует; freeze/dependency блокируют, где должны; GET /queue показывает корректную причину; всё под kill-switch; машинные инварианты байт-в-байт; регресс-тест инцидента красный до фикса и зелёный после.

8. Риски

Кратко (детально — 10-tech-risks.md, заполняет архитектор):

  • R-1 (ключевой конфликт свойств) — пауза vs анти-stale-base (ORCH-088). Если «пауза» освобождает гейт, успешник срежет ветку от main, ещё не содержащего код предшественника. Когда приостановленный предшественник позже возобновится и смержится — его база/ветка могут стать stale. ORCH-088 был построен ровно чтобы это предотвратить. Архитектор обязан разрешить конфликт явно (напр.: пауза «демотирует» задачу в FIFO и обязывает rebase при возобновлении; либо явный per-task «yield» с принятием rebase). Аналитик фиксирует конфликт, не выбирает решение.
  • R-2 — Случайное/неявное освобождение гейта (баг в детекте намерения) ослабит сериализацию для всех — требуется строго явное намерение оператора.
  • R-3 — Рассинхрон «Plane-статус ↔ DB-сигнал паузы»: если механизм опирается на webhook о смене статуса, потерянный webhook оставит задачу «активной» в БД (или наоборот). Нужен durable, идемпотентный, восстановимый сигнал.
  • R-4 — Регрессия гармонизированного предиката {done,cancelled} в task_deps/stages.py, если serial-gate изменит понимание «активности» неаккуратно.
  • R-5 — fail-direction: ошибка в новой ветке не должна инвертировать fail-OPEN (claim) / fail-CLOSED (freeze) контракты ORCH-088.