Files

7.8 KiB
Raw Permalink Blame History

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 найден)

  1. Деньги/токены НЕ тратятся: jobs / agent_runs за 4ч пусты; advance_stage для done = no-op; handle_verdict для done-задачи ничего не меняет. Это «дешёвый», но шумный и подрывающий доверие баг (livelock + ложный alert-fatigue).
  2. Механизм:
    • _reconcile_plane_project (src/reconciler.py ~241) тянет list_issues_by_state(pid, [in_progress, approved, rejected]).
    • На enduro-trails статусы «схлопнуты»: после ORCH-066 терминальный Done алиасится под UUID approved (см. ниже п.4) → ET-002 (Plane=Done) попадает в actionable-выборку.
    • В _reconcile_plane_issue (~295) срабатывает ветка new_state == approved and task is not Nonehandle_verdict(approved) (no-op, задача уже done) + безусловный _note_unblock.
    • _note_unblock (~317) вызывается сразу после _dispatch, не проверяя фактическое изменение состояния — хотя его docstring обещает «fires only on an actual state change, never per idle tick». Инвариант нарушен.
  3. Два независимых дефекта складываются:
    • D1 (выборка): терминальные статусы (Done/Cancelled) не исключены из actionable-набора F-2; на «схлопывающих» проектах Done не отличается от approved по голому UUID.
    • D2 (нотификация): _note_unblock срабатывает безусловно после no-op dispatch, а не только при подтверждённом state change.
  4. Почему 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).