fix(serial-gate): pause-without-blocking via per-task park signal (ORCH-124) #144
Reference in New Issue
Block a user
Delete Branch "feature/ORCH-124-bug-serial-gate-treats-backlog"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
ORCH-124 — Serial-gate «пауза без блокировки» (per-task park-сигнал)
Тип:
fix(меткаBug, эскалирован в full-cycle) · Стадия: development · Гейт:check_ci_greenПроблема (инцидент ORCH-116/ORCH-123)
serial_gateопределял «активную задачу репо» исключительно по машинной стадииtasks.stage NOT IN ('done','cancelled'). Plane-статусы Backlog/Blocked/Needs-Input(слой B индикации, ORCH-066) не меняют
tasks.stage(слой A) ⇒ приостановленныйпредшественник был неотличим от активного и держал FIFO-гейт закрытым против срочного
успешника (ORCH-116 поставлен на паузу, чтобы пропустить фикс ORCH-123 — фикс не
стартовал, пока ORCH-116 формально не
done).Решение (ADR-001 / adr-0051)
Явный per-task park-сигнал — аддитивная колонка
tasks.paused_at TEXT— и новаяортогональная ось планировщика «пауза». serial-gate «активна» ⇔
stage NOT IN ('done','cancelled') AND paused_at IS NULLво всех 3 точках под под-флагом.{done,cancelled}— байт-в-байт (adr-0026 не регрессирует):task_deps/stages.pyколонкуpaused_atне читают ⇒ паузнутая зависимость иrepo_freezeпо-прежнему блокируют (пауза их не обходит — разные оси).
pre-merge
auto_rebase_onto_main+ merge-gate re-test ORCH-026/093/110); новойrebase-машинерии нет. Нормальная задача (
paused_at IS NULL) держит гейт.serial_gate_pause_enabled(дефолтTrue=истинный no-op до явной паузы), never-raise, hot-claim fail-OPEN сохранён.
STAGE_TRANSITIONS/QG_CHECKS/check_*/ machine-verdict ключи / схемысуществующих таблиц — байт-в-байт не тронуты.
Точки врезки
src/db.pytasks.paused_at(_ensure_column) +set_task_paused/clear_task_paused/is_task_pausedsrc/serial_gate.py_pause_layer_enabled(); pause-терм в 3 точках; ключpaused+reasonв снапшотеsrc/config.py,.env.exampleserial_gate_pause_enabled(envORCH_SERIAL_GATE_PAUSE_ENABLED, дефолтTrue)src/main.pyPOST /serial-gate/pause|resume?work_item=<id>(по образцуunfreeze)tests/test_orch124_serial_gate_pause.pyCHANGELOG.md[Unreleased]Доки (
docs/architecture/README.md/internals.md, ADRadr-0051) — уже в ветке (architect).Проверки
tests/test_orch124_serial_gate_pause.py— 15 passed (TC-01 красный до фикса, зелёный после).test_serial_gate*.py,test_orch026_*) — зелёный.pytest tests/— все зелёные, кроме одного пред-существующего, не связанного сORCH-124 падения
test_orch123_staging_runner_exec.py::test_r2_held_deploy_staging_not_rolled_back(cross-test contamination: падает и на чистой базе с застэшенными изменениями, проходит в изоляции;
затрагивает staging-гейт ORCH-123, которого ORCH-124 не касается).
Refs: ORCH-124
Fixes incident ORCH-116/ORCH-123: serial_gate defined a repo's "active task" purely by machine stage (tasks.stage NOT IN ('done','cancelled')). Plane statuses Backlog/Blocked/Needs-Input (layer-B indication, ORCH-066) do NOT change tasks.stage (layer A), so a paused predecessor was indistinguishable from an active one and held the FIFO gate closed against an urgent successor — the urgent fix could not start until the paused task was formally done. Introduces an explicit, durable, DB-resolvable per-task "park" signal — additive nullable column tasks.paused_at (pattern of cancelled_at/track) — and a new ORTHOGONAL scheduler "pause" axis. The serial-gate "active task" predicate becomes `stage NOT IN ('done','cancelled') AND paused_at IS NULL` across all three points (build_claim_clause / repo_has_active_task / _per_repo_snapshot). The terminal set {done,cancelled} in serial_gate/task_deps/stages.py is byte-for-byte unchanged (adr-0026 not regressed): task_deps/stages.py do NOT read paused_at, so a paused declared dependency and an active repo_freeze STILL block (pause never bypasses them — different axes). Anti-stale-base on resume relies on the existing deferred branch cut (ORCH-088) + pre-merge auto_rebase_onto_main + merge-gate re-test (ORCH-026/093/110) — no new rebase machinery. Additive, under an independent sub-flag, never-raise, restart-safe; hot-claim fail-OPEN and freeze fail-CLOSED preserved. STAGE_TRANSITIONS / QG_CHECKS / check_* / machine-verdict keys / existing table schemas are byte-for-byte untouched (this is a queue-scheduler + observability change, not a Quality Gate). - src/db.py: additive tasks.paused_at column (_ensure_column) + set/clear/is helpers - src/serial_gate.py: _pause_layer_enabled() + pause-term in the 3 points; `paused` list + per-job `reason` (freeze>dependency>active-task>null) in the /queue snapshot - src/config.py + .env.example: serial_gate_pause_enabled (default True = true no-op) - src/main.py: POST /serial-gate/pause|resume?work_item=<id> (by образцу unfreeze) - tests/test_orch124_serial_gate_pause.py: TC-01 mandatory incident regress + TC-02..15 - CHANGELOG.md: [Unreleased] entry ADR: docs/work-items/ORCH-124/06-adr/ADR-001-serial-gate-pause-without-blocking.md Cross-cutting: docs/architecture/adr/adr-0051-serial-gate-pause-without-blocking.md Refs: ORCH-124 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>