Files
orchestrator/docs/work-items/ORCH-068/02-trz.md

9.2 KiB
Raw Blame History

ТЗ — ORCH-068: устранить livelock reconciler F-2 + спам unblock done-задачи

Документ описывает ТРЕБОВАНИЯ к изменению поведения (что и где), а не выбор реализации. Конкретный механизм (state.group vs явный allowlist терминалов; TTL vs flush-on-unknown) — решение архитектора в ADR.

1. Затронутые модули src/

Модуль Роль в баге Требуемое изменение
src/reconciler.py F-2 _reconcile_plane_project / _reconcile_plane_issue / _note_unblock Исключить терминальные статусы из actionable-выборки; _note_unblock только при подтверждённом state change; дедуп.
src/plane_sync.py get_project_states, list_issues_by_state, _STATES_CACHE Дать способ различать терминальные/review статусы (группа состояния); устранить вечно-устаревший кэш (TTL/flush).
src/config.py флаги (если нужны) новые kill-switch/настройки TTL — с дефолтами, не меняющими прод-инварианты.
src/main.py (/queue) наблюдаемость (опц.) отразить дедуп/skip-терминалов в снимке reconcile.

НЕ трогать: src/stages.py (STAGE_TRANSITIONS), src/qg/checks.py (QG_CHECKS), схему БД, контракты handle_status_start / handle_verdict, F-1 (reconcile_gate_once), F-3.

2. Требования к F-2 (src/reconciler.py)

TR-1. Исключить терминальные статусы из actionable-выборки

_reconcile_plane_project НЕ должен подавать задачи в терминальном Plane-статусе (Done и прочие completed-группы, Cancelled) ни в list_issues_by_state, ни в последующее сравнение веток.

  • Требование проектно-независимое: работает на enduro И orchestrator, независимо от того, «схлопывает» ли проект статусы по UUID.
  • Различение Done/Cancelled (completed) от approved/rejected (review) НЕ должно опираться только на голый UUID, если проект их алиасит. Допустимый ориентир — группа состояния Plane (state.group: completed/started/unstarted/backlog/cancelled) либо явный набор логических ключей терминалов. Выбор — за архитектором.

TR-2. _note_unblock — только при реальном state change

_note_unblock (лог + Telegram + инкремент unblocked_total) ВЫЗЫВАЕТСЯ ТОЛЬКО когда диспетчеризованный обработчик фактически изменил состояние задачи (advance / replayed transition, реально сдвинувший стадию). No-op dispatch (задача уже в целевом состоянии) → нотификация НЕ отправляется.

  • Сейчас _dispatch (asyncio.run(coro_fn(...))) отбрасывает результат, а _note_unblock зовётся безусловно. Требуется механизм подтверждения изменения (напр. сравнение стадии задачи до/после dispatch через get_task_by_plane_id, либо проброс сигнала из обработчика). Конкретику выбирает архитектор; контракт обработчиков handle_* менять НЕ обязательно (предпочтительно сравнение состояния до/после на стороне reconciler).
  • Восстановить соответствие docstring _note_unblock: «Fires only on an actual state change … never per idle tick».

TR-3. Дедуп / идемпотентность нотификаций

Reconciler НЕ должен слать повторное уведомление о той же задаче, если её состояние не менялось с прошлого тика. TR-1+TR-2 закрывают основной кейс (done более не входит в выборку и не нотифицируется); TR-3 — дополнительная страховка (best-effort), чтобы любой будущий no-op путь не дал повторного спама.

3. Требования к статус-кэшу (src/plane_sync.py) — secondary

TR-4. Устаревший _STATES_CACHE обновляется без рестарта

После появления нового Plane-статуса процесс не должен бесконечно держать устаревший набор. Допустимые подходы (выбор архитектора, можно комбинировать):

  • TTL на запись кэша (напр. ORCH_PLANE_STATES_TTL_S, дефолт разумный, 0/неуст. = прежнее поведение для совместимости);
  • flush-on-unknown: при детекте неизвестного статуса в вебхуке/реконсилере — вызвать существующий reload_project_states(pid) и перезапросить;
  • админ-эндпоинт/сигнал для ручного flush без рестарта. reload_project_states() уже существует — переиспользовать как примитив сброса, новую логику сброса не дублировать.

4. Изменения API

  • Новых обязательных endpoint'ов нет.
  • Опционально (TR-4, на усмотрение архитектора): admin-эндпоинт сброса кэша статусов (напр. POST /admin/plane-states/reload) — если выбран этот вариант flush. Должен быть защищён/идемпотентен; документировать в README таблице API.
  • Снимок GET /queue (блок reconcile) — без ломающих изменений; допустимо добавить поля наблюдаемости (skip-терминалов / dedup-счётчик).

5. Изменения схемы БД

Нет. Дедуп TR-3 реализуется in-memory (best-effort, как существующие счётчики unblocked_total/last_unblocked, AC-11 ORCH-053 допускает их сброс при рестарте) либо через сравнение живого состояния Plane/БД. Миграции не требуются.

6. Требования к новым QG checks

Нет. Реестр QG_CHECKS не меняется.

7. Инварианты (обязаны сохраниться)

  • INV-1. Source of truth F-2 — Plane (НЕ меняем).
  • INV-2. never-raise на единицу работы (per-issue / per-project / per-tick) сохранён.
  • INV-3. Kill-switch ORCH_RECONCILE_ENABLED (+ ORCH_RECONCILE_PLANE_ENABLED гасит только F-2) — работают.
  • INV-4. F-1 / F-3 поведение не изменено.
  • INV-5. 0 jobs / 0 токенов для синхронизированных задач (как сейчас) сохранено.
  • INV-6. Легитимная разблокировка реально-потерянного approved/in_progress webhook продолжает работать (нет регресса F-2).
  • INV-7. Self-hosting: тик reconciler НИКОГДА не рестартит/не роняет прод-контейнер.

8. Артефакты pipeline, которые надо обновить в ТОМ ЖЕ PR

  • CLAUDE.md — если меняется наблюдаемое поведение reconciler/кэша (раздел про reconciler/правила).
  • docs/architecture/README.md — секция «Reconciler … (ORCH-053)»: уточнить исключение терминалов + дедуп; при TR-4 — секция «Plane Sync».
  • docs/architecture/adr/adr-0007-reconciler.md (или новый per-WI ADR docs/work-items/ORCH-068/06-adr/ADR-001-…) — зафиксировать решение по терминалам/группе состояния и по кэшу.
  • CHANGELOG.md — запись о фиксе (fix:).
  • .env.example / .env.staging — если введён новый флаг (TTL/kill-switch).

9. Замечания по приёмке/тестированию

  • Регресс-тест ОБЯЗАН покрывать: задача в Done (синхронизирована) → тик F-2 = 0 нотификаций, на enduro И orchestrator проектах (terminal не зависит от алиасинга).
  • Тест НЕ должен делать реальных сетевых вызовов — мокать list_issues_by_state / get_project_states / send_telegram / _dispatch (handle_*).