Files

11 KiB
Raw Permalink Blame History

BRD — ORCH-053: Sweeper потерянных webhook (реконсиляция застрявших стадий)

Work Item ID: ORCH-053 Стадия: analysis → (architecture) Тип: надёжность конвейера (проектирование + реализация). Self-hosting (ORCH).

1. Проблема (бизнес-контекст)

Продвижение задач между стадиями конвейера завязано исключительно на входящие webhook-события:

  • Plane (work_item.updated → статус In Progress / Approved / Rejected) — единственный триггер старта задачи, advance и rollback (src/webhooks/plane.py).
  • Gitea (CI-status success/failure, push, PR reviewed/merged) — триггер development→review, architecture→development, review→testing, deploy→done (src/webhooks/gitea.py).

Если входящее событие потеряно (502 на падающем/ребилдящемся инстансе, Plane/Gitea не повторяют доставку, сетевой сбой, sha→branch не разрезолвился, вебхук был временно выключен) — статус в источнике истины (Plane / зелёный CI) уже изменился, а задача в оркестраторе не сдвинулась. Задача висит молча, без какого-либо механизма восстановления.

Живой инцидент (ORCH-044, 06.06): dev-агент отработал (exit 0, CI позеленел), но Gitea webhook о CI-success не продвинул задачу (не дошёл / не сматчился sha→branch). Задача висела бы на development молча навсегда — спасли только ручным дёрганьем гейта check_ci_green. Это системная дыра, а не разовый сбой; сейчас её ловит ручной heartbeat-watchdog Стрима (костыль).

Что уже есть и почему недостаточно

Механизм Что покрывает Почему не закрывает дыру
requeue_running_jobs() (startup) зависшие jobs при рестарте про jobs, не про застрявший stage-переход
orphan-recovery (main.py) agent_runs без finished_at job-уровень, не stage
ORCH-5 events de-dup (delivery_id) защита от дублей webhook обратной защиты от потери нет
ORCH-045 ci_poll в check_ci_green поллит CI 12×10с только если гейт уже вызван webhook'ом; не пришёл webhook → гейт не вызывается

Общий принцип всех существующих механизмов — restart-safe resilience на уровне jobs. Нет ни одного механизма, реконсилирующего рассинхрон «источник истины ≠ стадия задачи».

2. Цель

Задача не должна застревать молча из-за потерянного входящего события. Ввести фоновый периодический sweeper / reconciler, который сам находит «зависшие» задачи и доигрывает пропущенный переход — через те же штатные гейты и обработчики, что и webhook (никакой параллельной логики продвижения). Убрать необходимость в ручном heartbeat-watchdog.

3. Заинтересованные стороны

  • Owner / Стрим (Слава) — перестаёт ловить зависания вручную.
  • Все проекты на инстансе (enduro-trails + orchestrator) — конвейер не встаёт молча.
  • Self-hosting (ORCH) — особенно при ребилде прода (ORCH-51): вебхуки, прилетевшие на падающий инстанс, подбираются реконсиляцией после старта.

4. Объём (Scope)

В объёме — две взаимодополняющие ветки реконсиляции (обе обязательны):

F-1. Gate-side sweeper (реконсиляция застрявшей стадии по локальной БД)

Периодический проход по таблице tasks: найти задачи, у которых (а) stage != done, (б) нет активных job'ов в очереди, (в) с момента updated_at прошло больше per-stage порога → пере-проверить QG текущей стадии и, если passed — продвинуть штатным путём (stage_engine.advance_stage(..., finished_agent=None), тот же путь, что использует webhook). Закрывает потерю Gitea CI/PR-вебхуков (ORCH-044).

F-2. Plane-side reconciler (реконсиляция потерянного Plane status-webhook)

Периодический опрос Plane API по проектам реестра (projects.py): issues в статусах, требующих действия (In Progress / Approved / Rejected). Сверить с локальной tasks и доиграть через существующие обработчики webhooks/plane.py:

  • In Progress + нет задачи в БД → создать+запустить (handle_status_start/start_pipeline);
  • Approved + стадия не сдвинута → advance (handle_verdict(approved=True));
  • Rejected + не откатана → rollback (handle_verdict(approved=False)).

F-3. Усиление sha→branch резолва в Gitea-вебхуке

В handle_ci_status добавить надёжный fallback (поиск task по БД), чтобы исходный webhook реже терялся из-за неразрезолвленного branch. Sweeper работает от задачи (repo+branch известны из БД) и обходит эту хрупкость по определению.

F-4. Наблюдаемость

Лог (и опц. Telegram) каждый раз, когда sweeper разблокировал застрявшую задачу — чтобы видеть частоту срабатывания дыры (метрика потерянных webhook). Опц. вывод счётчика в /queue или /reconcile. Не спамить, когда всё синхронно.

Вне объёма

  • Буфер недоставленных webhook (это ORCH-51; sweeper — резервная сетка к нему).
  • Изменение состава стадий/гейтов (STAGE_TRANSITIONS, QG_CHECKS).
  • Изменение логики самих гейтов и обработчиков (только переиспользование).
  • Новый исполняемый деплой (ORCH-36).

5. Ключевые требования (бизнес-уровень)

  1. Источник истины — гейт/Plane, а не событие. Sweeper дёргает ровно те же функции продвижения, что и webhook. Параллельной логики продвижения быть не должно.
  2. Идемпотентность (критично). Задержавшийся или дублированный webhook + sweeper НЕ создают двойную задачу / двойной запуск / двойной advance. Тот же guard, что у webhook: нет активного job + стадия совпадает + atomic claim как в queue_worker.
  3. Безопасность активной работы. Sweeper НЕ трогает задачи с активными (queued/running) job'ами — они легитимно в работе, не потеряны.
  4. Per-stage grace. Разные стадии имеют разное нормальное время (analysis ~815 мин vs deploy). Порог застревания настраивается, чтобы не дёргать гейт у задачи, где агент законно работает.
  5. Restart-safe. Sweeper — фоновый поток, стартует с приложением, переживает рестарт (как queue_worker). Без потери состояния.
  6. Self-hosting safety. Sweeper не должен ронять/рестартить прод-контейнер; kill-switch в конфиге для поэтапного раската и аварийного отключения.
  7. Без шума. Когда всё синхронно — никаких действий и нотификаций.
  8. Документация = golden source. README/architecture, ADR, CHANGELOG обновляются в том же PR.

6. Эффект

  • Потерянный webhook больше не = молча застрявшая задача.
  • Ручной heartbeat-watchdog Стрима больше не нужен для ловли зависаний (AC-5 в эпике).
  • Резервная сетка к ORCH-51 при ребилде прода.

7. Связи

  • Дополняет ORCH-51 (потеря webhook при рестарте — буфер; sweeper — реконсиляция).
  • Дополняет ORCH-36 (если deploy-webhook потеряется — sweeper добьёт deploy→done).
  • ORCH-1b — та же философия resilience: транзиентный сбой не убивает задачу.
  • Эпик: звено ORCH-54 (автономное внедрение). Параллельна ORCH-36 (разные файлы), но max_concurrency=1 → встанет в очередь.

8. Риски (кратко; подробно — 10-tech-risks архитектора)

  • Гонка sweeper ↔ живой webhook → двойной запуск. Митигируется atomic claim + active-job guard + grace-период (не конкурировать с задержавшимся webhook).
  • Spam нотификаций при персистентно красном гейте на каждом тике. Митигируется: действие/нотификация только на изменении состояния (advance), не на каждый тик.
  • Нагрузка на Plane API при опросе каждые N сек. Митигируется интервалом + фильтром по статусам + per-project.
  • Self-hosting: sweeper правит инструмент, обслуживающий и другие проекты. Kill-switch обязателен.