# 10 — Технические риски: ORCH-088 (Serial gate)

Work Item: **ORCH-088** · Repo: **orchestrator** · Стадия: architecture
Связь: ADR `06-adr/ADR-001-serial-gate.md`, ТЗ `02-trz.md` §11-12, BRD §8.

Оценка: **Вероятность** (Н/С/В) × **Влияние** (Н/С/В). Self-hosting: «Влияние В» = риск для конвейера
ВСЕХ проектов (общий прод/БД/очередь).

| ID | Риск | Вер. | Влия. | Митигейшн |
|----|------|------|-------|-----------|
| **R-1** | **Stale-base сохраняется**, если ветка режется на входе (`_create_gitea_branch` в `start_pipeline`) до завершения предшественника — гейт только claim не лечит (BRD R-1, AC-6 FAIL). | С | В | Relocation среза ветки на claim analyst-job (ADR D1): `start_pipeline` не создаёт ветку для применимого репо; `ensure_worktree` режет от свежего `origin/main` уже после открытия gate. Тест AC-6: `git merge-base --is-ancestor <validated_sha A> <base B>`. |
| **R-2** | Gate, ошибочно **fail-closed на транзиентной ошибке БД** в hot-claim, заклинивает очередь ВСЕХ проектов (enduro встаёт). | С | В | `build_claim_clause` обёрнут в try/except → ошибка построения = пустой фрагмент = claim без gate (**fail-open**, ADR D10/AC-8). Freeze fail-closed применяется только в Python-слое, не в тотальном сбое hot-claim. Unit-тест: исключение в построении clause → claim не падает, выбирает job. |
| **R-3** | **«Вечный freeze» / залипшая Blocked-задача** останавливает пакет незаметно (D6 — Blocked держит gate). | С | С | Наблюдаемость `GET /queue` блок `serial_gate` (`active_task`, `waiting`, `frozen`+reason); Telegram-алерт при выставлении freeze и карточка «⏳ ждёт …»; ручное снятие — простой эндпоинт `POST /serial-gate/unfreeze` (D4). Оператор видит причину застоя. |
| **R-4** | **Async→sync релокация** `_create_gitea_branch`/`_create_initial_docs`: эти вызовы async (httpx) в `start_pipeline`, а launcher `_spawn` — sync. Неверная обёртка → исключение/блок event-loop. | С | С | Developer оборачивает Gitea-API для sync-контекста (httpx sync client / `asyncio.run` в отдельном пути). Контракт launcher never-raise: сбой материализации ветки → лог + job в retry, прод не трогается. Тест: claim analyst-job создаёт ветку и worktree без падения. |
| **R-5** | **Потребитель ожидает материализованную ветку до запуска analyst** (между `start_pipeline` и claim ветки нет): Telegram-карточка / Plane-sync / reconciler могут предполагать существование ветки. | Н | С | Проверено: трекер/Plane-sync используют branch как строку имени, не git-ref. Перед разработкой — аудит читателей `tasks.branch`/Gitea-ветки на стадии до analyst. `start_pipeline` по-прежнему пишет `branch` в task-row (имя), не материализуя ref. |
| **R-6** | **SQL-инъекция / поломка clause** через `serial_gate_repos` CSV при встраивании в `IN (...)`. | Н | С | Санитизация repo-токенов `^[A-Za-z0-9._-]+$` в `build_claim_clause` (ADR D8); невалидный токен дропается. CSV — операторский конфиг (не пользовательский ввод), риск низкий, но гард обязателен. Unit-тест на мусорный CSV. |
| **R-7** | **Rework-analyst блокирует сам себя**: rejection-path `start_pipeline` re-enqueue analyst активной задачи; наивный gate «есть незавершённая задача репо» удержал бы её навсегда. | С | В | Условие `t2.id != jobs.task_id` (ADR D1) — учитываются только **другие** задачи. Unit-тест: rework-analyst задачи A при единственной незавершённой A — claim проходит. |
| **R-8** | **Freeze не учитывает уже-`done` задачу**: деградировавшая задача к моменту DEGRADED уже `stage='done'` (BR-7) ⇒ обычный gate её не удержит, следующая стартует до выставления freeze (гонка). | Н | С | Freeze — **отдельный durable сигнал** (`repo_freeze`), не зависит от `stage` (ADR D2/D3). `set_repo_freeze` вызывается в DEGRADED-ветке монитора; до снятия gate закрыт безусловно. Возможная узкая гонка «`done`→claim next до записи freeze» приемлема Этапом 1 (следующий тик уже видит freeze); при необходимости — выставлять предупредительный freeze в начале окна мониторинга (вне скопа). |
| **R-9** | **Default-all область** неожиданно сериализует enduro (меняет поведение при включении флага). | С | Н | Осознанное решение (ADR D5, ТЗ §8): enduro выигрывает от serial e2e; `max_concurrency=1` и так ограничивает параллелизм. Оператор может сузить `ORCH_SERIAL_GATE_REPOS=orchestrator`. «Нулевая регрессия» гарантирована при **выключенном** kill-switch (NFR-4). |
| **R-10** | **Миграция на общей прод-БД** (`repo_freeze`) роняет init при неудачном порядке/блокировке. | Н | В | `CREATE TABLE IF NOT EXISTS` + idempotent index; выполняется в существующем init-пути схемы; аддитивно, не трогает enduro-строки. Прод-контейнер не рестартится механизмом gate (NFR-6). |

---

## Сводный вывод
Архитектурно безопасных блокеров нет. Критические векторы — **R-1** (закрыт relocation среза ветки),
**R-2/R-7** (закрыты fail-open hot-claim и `t2.id != jobs.task_id`). Все механизмы аддитивны, под
kill-switch, never-raise, не рестартят прод. Главный операционный риск — **R-3** (ручной freeze),
смягчён наблюдаемостью и алертами. Реализация — стандартный путь стадии development без эскалации
`arch:major-change` (нет новой стадии/QG/смены БД-контракта; новая таблица аддитивна).
