# adr-0017: Per-repo serial gate (пакетный автономный режим, serial e2e)

Статус: **proposed** · Дата: 2026-06-09 · Источник: **ORCH-088** (Этап 1)
Детально: `docs/work-items/ORCH-088/06-adr/ADR-001-serial-gate.md`.

## Контекст
Цель эпика ORCH-088 — масштаб автономности: накидать вечером 10–20 задач и получить к утру пакет,
последовательно проведённый через весь конвейер (analysis → … → deploy → done). Корневая проблема —
**stale-анализ**: ветка задачи N+1 срезается на входе в анализ (`start_pipeline._create_gitea_branch`)
от `main`, ещё не содержащего код предшественника N. Физическое код-затирание уже закрыто (ORCH-026
auto_rebase + merge-lease); остаётся **логический** разрыв. Plane API v1 не имеет bulk/relations ⇒
очередь/зависимости хранятся у оркестратора (gate по локальной БД).

## Решение
**Per-repo serial gate** — новая задача репо не входит в `analysis` (не режет ветку, не запускает
analyst), пока в том же репо есть незавершённая задача (`stage != 'done'`) или репо заморожен.
Три механизма, аддитивно, под kill-switch, с областью репо, never-raise, restart-safe:

1. **Gate-в-claim** (`db.claim_next_job`) — analyst-job (`jobs.agent='analyst'`) применимого репо не
   выбирается, если `EXISTS` другая незавершённая задача репо ИЛИ активна строка `repo_freeze`. По
   образцу `task_deps` `NOT EXISTS` (ORCH-026); только локальная БД (offline hot-path). Job'ы уже
   активной задачи проходят свободно; rework-analyst не блокирует себя (`t2.id != jobs.task_id`).
2. **Отложенный срез ветки** — для применимого репо `start_pipeline` создаёт task-row + enqueue
   analyst, но **не** создаёт Gitea-ветку/docs; срез релоцируется на момент claim analyst-job
   (launcher), когда `origin/main` уже содержит предшественника (`done` ⇔ SHA-в-main, ORCH-071/073).
   `ensure_worktree` режет от свежего `origin/main` ⇒ AC-6 структурно. Идемпотентно (409 = no-op).
3. **Durable per-repo freeze** (`repo_freeze`) — post-deploy `DEGRADED`/rollback (ORCH-021) →
   `set_repo_freeze` + Telegram-алерт; gate закрыт безусловно до **ручного** снятия
   (`POST /serial-gate/unfreeze`). Деградировавшая задача уже `done` (BR-7) ⇒ нужен отдельный сигнал.

Чистая логика — leaf `src/serial_gate.py` (never-raise). Флаги `serial_gate_enabled` (kill-switch),
`serial_gate_repos` (CSV; **пусто ⇒ все репо**, в отличие от self-hosting-only ORCH-35/43/58),
`serial_gate_freeze_enabled`. Наблюдаемость — блок `serial_gate` в `GET /queue`.

## Альтернативы
- **Гейт в `start_pipeline` + re-trigger при `done`** — больше состояния/путей, риск зависших задач;
  relocation на claim переиспользует restart-safe `jobs`-очередь.
- **Freeze как колонка `tasks`** — неверная семантика (freeze per-repo, задача уже `done`).
- **Self-hosting-only область** — лишает enduro анти-stale-base (FR-3).
- **Отдельная таблица очереди ожидания** — избыточно; `jobs(queued)`+gate достаточно.
- **Снятие freeze Plane-жестом** — перегрузка статусов (анти-паттерн ORCH-059).

## Последствия
- **+** AC-6 закрыт структурно; AC-2/AC-3 «бесплатны» (ожидание = `queued` job без ветки);
  переиспользование проверенных паттернов; cross-repo параллелизм сохранён; `STAGE_TRANSITIONS` /
  `QG_CHECKS` / `check_*` / merge-gate / merge-verify / image-freshness / post-deploy / deploy-хук /
  `max_concurrency` — **без изменений**.
- **NFR-1:** hot-claim тотальный сбой → **fail-open** (не заклинить очередь всех проектов); freeze в
  Python-слое → **fail-closed** (безопасность прода).
- **−** Срез ветки/docs мигрируют из async в sync-путь launcher (обёртка); Blocked-задача держит пакет
  (Этап 1, осознанно); freeze снимается только вручную.
- Откат: `serial_gate_enabled=False` ⇒ claim/старт 1:1 как до ORCH-088; таблица `repo_freeze` инертна.
- **Вне скопа** (Этап 1): merge-очередь FIFO, pre-merge rebase как отдельная фича, фазы A/B/C,
  любой параллелизм задач внутри одного репо, зависимость от ORCH-83.

## Связи
- Переиспользует: adr-0002 (очередь ORCH-1), adr-0015 (claim-gate/auto_rebase/merge-lease ORCH-026),
  adr-0010 (post-deploy monitor — источник DEGRADED), adr-0013/0014 (merge-verify ⇒ `done`⇔SHA-в-main).
- Новая аддитивная таблица `repo_freeze` (`docs/work-items/ORCH-088/08-data-requirements.md`).
