9.2 KiB
ТЗ — 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 ADRdocs/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_*).