19 KiB
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.