Files
orchestrator/docs/architecture/adr/adr-0017-serial-gate.md

5.8 KiB
Raw Permalink Blame History

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 — масштаб автономности: накидать вечером 1020 задач и получить к утру пакет, последовательно проведённый через весь конвейер (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).