feat(serial-gate): per-repo serial gate + deferred branch cut + rollback-freeze (ORCH-088) #88

Merged
admin merged 7 commits from feature/ORCH-088-orch-88-10-20 into main 2026-06-09 11:31:58 +03:00
Owner

ORCH-088 — Per-repo serial gate (Этап 1, serial e2e)

Закрывает логический stale-анализ: ветка задачи N+1 срезалась на входе в анализ от main, ещё не содержащего код предшественника N. Новая задача репо не входит в analysis (analyst-job не выбирается, ветка не режется), пока в репо есть более ранняя незавершённая задача (FIFO) ИЛИ репо заморожен.

Что сделано (по ADR-001)

  • Gate-в-claim (db.claim_next_job) — leaf src/serial_gate.py::build_claim_clause (fail-OPEN, санитизация repo-CSV). Только analyst-job, только локальная БД (offline hot-path).
  • Отложенный срез ветки (AC-6): start_pipeline не создаёт Gitea-ветку/docs для применимого репо; релокация в launcher._materialize_deferred_branch на момент claim analyst-job (база = свежий origin/main).
  • Durable per-repo freeze (FR-5): аддитивная repo_freeze; post-deploy DEGRADEDset_repo_freeze + Telegram; снятие — POST /serial-gate/unfreeze?repo=…. is_repo_frozen fail-CLOSED.
  • Наблюдаемость: блок serial_gate в GET /queue.
  • Конфиг: serial_gate_enabled / serial_gate_repos (пусто = все репо) / serial_gate_freeze_enabled.

⚠️ Отклонение от ADR (FIFO-уточнение, FR-2)

ADR-001 D1 фиксировал псевдо-SQL t2.id != jobs.task_id. При != пакет одновременно созданных свежих задач (все в analysis) взаимно блокируется → дедлок всей serial-очереди (воспроизведено). Реализовано t2.id < jobs.task_id — допускает самую раннюю задачу и сериализует остальные (строго по одной, FIFO по jobs.id), сохраняя AC-1 и не блокируя rework-analyst (R-7). Подробности — в коде/CHANGELOG/README. ADR не правился (артефакт стадии architecture).

Инварианты

STAGE_TRANSITIONS / QG_CHECKS / check_* — без изменений. Аддитивно, под kill-switch, never-raise, restart-safe; миграции идемпотентны; прод-контейнер не рестартится. При выключенном флаге — нулевая регрессия (enduro не затронут).

Тесты

TC-01..TC-22: tests/test_serial_gate.py, test_serial_gate_e2e.py, test_serial_gate_freeze.py, test_serial_gate_branch.py, test_queue_endpoint.py. Полный прогон — 1114 passed. Два пред-ORCH-088 routing-теста (test_plane_webhook, test_status_trigger) пиннуты на kill-switch-off (тестируют routing, не branch-timing).

Docs

README (раздел serial gate / /queue / API / БД + статус-футер), CLAUDE.md, CHANGELOG.md, .env.example.

Refs: ORCH-088

🤖 Generated with Claude Code

## ORCH-088 — Per-repo serial gate (Этап 1, serial e2e) Закрывает **логический** stale-анализ: ветка задачи N+1 срезалась на входе в анализ от `main`, ещё не содержащего код предшественника N. Новая задача репо не входит в `analysis` (analyst-job не выбирается, ветка не режется), пока в репо есть **более ранняя** незавершённая задача (FIFO) ИЛИ репо заморожен. ### Что сделано (по ADR-001) - **Gate-в-claim** (`db.claim_next_job`) — leaf `src/serial_gate.py::build_claim_clause` (fail-OPEN, санитизация repo-CSV). Только analyst-job, только локальная БД (offline hot-path). - **Отложенный срез ветки** (AC-6): `start_pipeline` не создаёт Gitea-ветку/docs для применимого репо; релокация в `launcher._materialize_deferred_branch` на момент claim analyst-job (база = свежий `origin/main`). - **Durable per-repo freeze** (FR-5): аддитивная `repo_freeze`; post-deploy `DEGRADED` → `set_repo_freeze` + Telegram; снятие — `POST /serial-gate/unfreeze?repo=…`. `is_repo_frozen` fail-CLOSED. - **Наблюдаемость**: блок `serial_gate` в `GET /queue`. - **Конфиг**: `serial_gate_enabled` / `serial_gate_repos` (пусто = все репо) / `serial_gate_freeze_enabled`. ### ⚠️ Отклонение от ADR (FIFO-уточнение, FR-2) ADR-001 D1 фиксировал псевдо-SQL `t2.id != jobs.task_id`. При `!=` пакет одновременно созданных свежих задач (все в `analysis`) **взаимно блокируется** → дедлок всей serial-очереди (воспроизведено). Реализовано `t2.id < jobs.task_id` — допускает самую раннюю задачу и сериализует остальные (строго по одной, FIFO по `jobs.id`), сохраняя AC-1 и не блокируя rework-analyst (R-7). Подробности — в коде/CHANGELOG/README. ADR не правился (артефакт стадии architecture). ### Инварианты `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` — без изменений. Аддитивно, под kill-switch, never-raise, restart-safe; миграции идемпотентны; прод-контейнер не рестартится. При выключенном флаге — нулевая регрессия (enduro не затронут). ### Тесты TC-01..TC-22: `tests/test_serial_gate.py`, `test_serial_gate_e2e.py`, `test_serial_gate_freeze.py`, `test_serial_gate_branch.py`, `test_queue_endpoint.py`. Полный прогон — **1114 passed**. Два пред-ORCH-088 routing-теста (`test_plane_webhook`, `test_status_trigger`) пиннуты на kill-switch-off (тестируют routing, не branch-timing). ### Docs README (раздел serial gate / `/queue` / API / БД + статус-футер), `CLAUDE.md`, `CHANGELOG.md`, `.env.example`. Refs: ORCH-088 🤖 Generated with [Claude Code](https://claude.com/claude-code)
admin added 6 commits 2026-06-09 11:24:50 +03:00
Этап 1 (serial e2e) пакетного автономного режима. Новая задача репо не входит
в analysis (analyst-job не выбирается, ветка не режется), пока в репо есть более
ранняя незавершённая задача (FIFO, t2.id < jobs.task_id) ИЛИ репо заморожен.

- src/serial_gate.py — новый leaf (never-raise): build_claim_clause (fail-OPEN),
  is_repo_frozen (fail-CLOSED), set/clear_repo_freeze, serial_gate_applies, snapshot.
- src/db.py — идемпотентная миграция repo_freeze + serial_gate-фрагмент в claim_next_job.
- src/webhooks/plane.py + src/agents/launcher.py — отложенный срез ветки: start_pipeline
  не создаёт Gitea-ветку/docs для применимого репо; релокация в _materialize_deferred_branch
  на момент claim analyst-job (база = свежий origin/main с кодом предшественника, AC-6).
- src/stage_engine.py — post-deploy DEGRADED → durable per-repo freeze + Telegram-алерт.
- src/main.py — блок serial_gate в GET /queue + POST /serial-gate/unfreeze.
- src/config.py — serial_gate_enabled / serial_gate_repos / serial_gate_freeze_enabled.

FIFO-уточнение реализации (FR-2): ADR-001 D1 фиксировал t2.id != jobs.task_id; при !=
пакет одновременно созданных свежих задач взаимно блокировался бы (дедлок). t2.id <
jobs.task_id допускает самую раннюю задачу и сериализует остальные, сохраняя AC-1/R-7.

STAGE_TRANSITIONS / QG_CHECKS / check_* — без изменений. Аддитивно, под kill-switch,
never-raise, restart-safe; при выключенном флаге — нулевая регрессия (enduro не затронут).

Тесты: TC-01..TC-22 (test_serial_gate*.py + test_queue_endpoint.py); полный прогон 1114 зелёных.
Docs: README (serial gate / /queue / API / БД), CLAUDE.md, CHANGELOG.md, .env.example.

Refs: ORCH-088
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tester(ET): auto-commit from tester run_id=439
All checks were successful
CI / test (push) Successful in 34s
CI / test (pull_request) Successful in 32s
dd4aaebe84
admin force-pushed feature/ORCH-088-orch-88-10-20 from 31891354c8 to dd4aaebe84 2026-06-09 11:24:50 +03:00 Compare
admin merged commit 7d61c820a7 into main 2026-06-09 11:31:58 +03:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: admin/orchestrator#88