7.8 KiB
BRD — ORCH-068: BUG reconciler livelock (спам unblock done-задачи)
1. Контекст и предыстория
Reconciler (src/reconciler.py, ORCH-053) — фоновый поток, который доигрывает пропущенные webhook-переходы. Ветвь F-2 (plane-side) опрашивает Plane per-project и реплеит In Progress / Approved / Rejected через штатные handle_status_start / handle_verdict. При фактической разблокировке вызывается _note_unblock → лог + Telegram.
ORCH-066 (мердж feature/ORCH-066-plane, прод 2026-06-07 ~22:16 UTC) ввела новую статусную модель Plane (маппинг стадия↔статус, новые имена Done, Monitoring after Deploy и т.п.). Это спровоцировало регрессию в F-2.
2. Проблема (бизнес-симптом)
С 22:17 UTC (рестарт прод-контейнера после деплоя ORCH-066) reconciler каждые ~120с шлёт в Telegram:
reconciler: ET-002 done разблокирована (потерян webhook)
для задачи ET-002 (enduro-trails), которая в Done с 2026-05-21. На момент анализа — 191+ сообщений подряд, поток не прекращается (ночной спам, найден Славой 2026-06-08 01:22 UTC).
Ключевой факт: ET-002 полностью синхронизирована — БД stage=done, Plane state=Done. Reconciler обязан молчать (инвариант «silence when in sync», AC-9/AC-10 из ORCH-053), но шлёт уведомления вхолостую.
3. Диагностика (проведена, root cause найден)
- Деньги/токены НЕ тратятся:
jobs/agent_runsза 4ч пусты;advance_stageдля done = no-op;handle_verdictдля done-задачи ничего не меняет. Это «дешёвый», но шумный и подрывающий доверие баг (livelock + ложный alert-fatigue). - Механизм:
_reconcile_plane_project(src/reconciler.py~241) тянетlist_issues_by_state(pid, [in_progress, approved, rejected]).- На enduro-trails статусы «схлопнуты»: после ORCH-066 терминальный
Doneалиасится под UUIDapproved(см. ниже п.4) → ET-002 (Plane=Done) попадает в actionable-выборку. - В
_reconcile_plane_issue(~295) срабатывает веткаnew_state == approved and task is not None→handle_verdict(approved)(no-op, задача уже done) + безусловный_note_unblock. _note_unblock(~317) вызывается сразу после_dispatch, не проверяя фактическое изменение состояния — хотя его docstring обещает «fires only on an actual state change, never per idle tick». Инвариант нарушен.
- Два независимых дефекта складываются:
- D1 (выборка): терминальные статусы (
Done/Cancelled) не исключены из actionable-набора F-2; на «схлопывающих» проектах Done не отличается от approved по голому UUID. - D2 (нотификация):
_note_unblockсрабатывает безусловно после no-op dispatch, а не только при подтверждённом state change.
- D1 (выборка): терминальные статусы (
- Почему
get_project_statesсхлопывает: функция строит маппинг по именам статусов из Plane API, затем недостающие ключи добивает из_DEFAULT_STATES(enduro-значения). После ORCH-066 набор статусов enduro изменился — голый UUID перестал однозначно различатьDone(completed-группа) иapproved(review). Группа состояния (state.group) при этом различает их корректно, но в коде не используется.
4. Связанный баг (BUG КЭША СТАТУСОВ, найден 2026-06-07 при деплое ORCH-066)
_STATES_CACHE (src/plane_sync.py ~134) кэширует статусы Plane на весь lifetime процесса. После создания нового Plane-статуса (напр. Confirm Deploy) боевой процесс держит устаревший набор → webhook на новый статус даёт «no pipeline action» (Phase B не триггерится). Лечилось только рестартом орка. Примитив сброса уже есть — reload_project_states() — но он нигде не вызывается автоматически.
Оба бага — следствие хрупкости статусной модели после ORCH-066. Решение: вести их в одном work item (см. scope ниже), окончательное разделение — на усмотрение архитектора.
5. Цели (Goals)
- G1. Reconciler НЕ шлёт «разблокирована» для синхронизированной done/cancelled задачи (восстановить инвариант silence-when-in-sync).
- G2.
_note_unblockсрабатывает только при реальном state change (соблюдён AC-9/AC-10). - G3. Дедуп: нет повторного спама по той же задаче без изменения её состояния.
- G4. Корректное различение терминальных (
Done/Cancelled) и review-статусов (approved/rejected) даже на проектах, «схлопывающих» их по UUID — на всех проектах (enduro И orchestrator). - G5 (secondary). Устаревший
_STATES_CACHEобновляется без рестарта процесса (TTL / flush-on-unknown / endpoint).
6. Не-цели (Out of scope)
- N1. Менять source-of-truth: ориентир F-2 на Plane остаётся корректным по дизайну (таблица
tasksбез status-колонки; статусы двигает человек в Plane). Идею F-2 НЕ переписываем — баг в маппинге/нотификации, не в концепции. - N2. Менять реестры
STAGE_TRANSITIONS/QG_CHECKS, схему БД, контракты гейтов. - N3. Менять поведение F-1 (gate-side) и F-3.
- N4. Полный авто-approve деплоя (ORCH-54).
7. Затронутые стороны
- Все проекты на одном инстансе (enduro-trails + orchestrator, общая БД/очередь) — баг проявился на ET-002, но фикс выборки терминалов обязан быть проектно-независимым.
- Self-hosting: правка идёт в работающий прод-инструмент → обязательна страховка staging (8501), запрет на рестарт прод-контейнера в рамках задачи.
8. Критерий успеха (бизнес)
Тик reconciler для синхронизированной done/cancelled задачи = 0 уведомлений, 0 jobs, 0 токенов. Telegram-спам прекращён. Легитимная разблокировка (реально потерянный approved/in_progress webhook) по-прежнему работает (нет регресса F-2).