91 lines
6.8 KiB
Markdown
91 lines
6.8 KiB
Markdown
# BRD: Reconciler не должен трогать escalated / max-retries задачи
|
||
|
||
Work Item ID: ORCH-060
|
||
Стадия: analysis → architecture
|
||
Связано: ORCH-053 (reconciler), ORCH-046 (retry-счётчик), ORCH-047 (BLOCKED-вердикт)
|
||
|
||
## 1. Контекст и проблема
|
||
|
||
ORCH-053 ввёл фоновый reconciler (`src/reconciler.py`) — sweeper, доигрывающий
|
||
пропущенные webhook-переходы. Слой F-1 (`reconcile_gate_once` →
|
||
`_reconcile_gate_task`) для каждой не-терминальной задачи (`stage != 'done'`) без
|
||
активного job и старше grace делает read-only пред-оценку канонического QG; если
|
||
гейт зелёный → `advance_if_gate_passed` → `advance_stage(..., finished_agent=None)`.
|
||
|
||
**Дефект.** Задача, исчерпавшая лимит developer-ретраев
|
||
(`_developer_retry_count(task_id) >= MAX_DEVELOPER_RETRIES = 3`), **escalated** —
|
||
но эскалация в обработчиках Gitea (`src/webhooks/gitea.py:280` для CI-failure,
|
||
`:371` для review REQUEST_CHANGES) выполняет ТОЛЬКО `notify_error(...)`:
|
||
|
||
- стадия НЕ меняется (остаётся `development`);
|
||
- терминального маркера в БД нет (нет `blocked`-флага в таблице `tasks`);
|
||
- активного job нет.
|
||
|
||
Для reconciler такая задача неотличима от «застрявшей из-за потерянного webhook».
|
||
Если CI к этому моменту зелёный (типичный кейс: разработчик починил CI, но reviewer
|
||
продолжал слать REQUEST_CHANGES → ушли в лимит), F-1 каждые `reconcile_interval_s`
|
||
(120 с) видит зелёный `check_ci_green` и **разблокирует** задачу `development → review`.
|
||
Reviewer снова REQUEST_CHANGES → откат на `development` → снова эскалация (стадия
|
||
не меняется). Следующий тик — снова разблокировка. Бесконечный цикл.
|
||
|
||
**Реальный инцидент (наблюдение 06–07.06.2026).** ET-013 разблокирована
|
||
reconciler'ом **10 раз за ночь**, в итоге всё равно escalated — бесполезный поллинг
|
||
каждые 2 минуты, лишние запуски агентов (токены, деньги), шум в Telegram
|
||
(`reconcile_notify_unblock`), нагрузка на конвейер общего инстанса (self-hosting:
|
||
один инстанс обслуживает ORCH + enduro-trails).
|
||
|
||
Симметричный риск: задача, которую человек/агент явно перевёл в Plane-статус
|
||
**Blocked** или **Needs Input** (ручной гейт), не должна автоматически
|
||
разблокироваться reconciler'ом до вмешательства человека.
|
||
|
||
## 2. Бизнес-цель
|
||
|
||
Reconciler (F-1) обязан **пропускать** (не трогать) задачи, которые:
|
||
1. исчерпали лимит developer-ретраев (`_developer_retry_count >= MAX_DEVELOPER_RETRIES`), и/или
|
||
2. находятся в явном «человеческом»/терминальном Plane-статусе **Blocked** / **Needs Input**.
|
||
|
||
Такие задачи ждут ручного вмешательства; автоматический sweeper их игнорирует.
|
||
|
||
## 3. Заинтересованные стороны
|
||
|
||
- **Owner проекта** — прекращение «фантомной» активности и шума по escalated-задачам.
|
||
- **Другие проекты на инстансе (enduro-trails)** — снижение паразитной нагрузки общей очереди.
|
||
- **Агенты-разработчики оркестратора** — корректная семантика терминального состояния.
|
||
|
||
## 4. Объём (Scope)
|
||
|
||
### Входит
|
||
- Гард в F-1 (`_reconcile_gate_task` / `advance_if_gate_passed`), который ДО
|
||
оценки гейта и вызова `advance_stage` пропускает escalated-задачи
|
||
(retry-count >= лимит) — детерминированно, без сети.
|
||
- Гард, пропускающий задачи в Plane-статусе Blocked / Needs Input.
|
||
- Тесты (unit) на оба условия + регресс happy-path и отсутствия спама/нотификаций.
|
||
- Обновление документации: `docs/architecture/README.md` (описание F-1),
|
||
per-work-item ADR, `CHANGELOG.md`.
|
||
|
||
### Не входит
|
||
- Изменение порога `MAX_DEVELOPER_RETRIES` или логики самой эскалации в `gitea.py`.
|
||
- Изменение F-2 plane-side по существу (F-2 уже реагирует только на
|
||
in_progress/approved/rejected, то есть Blocked/Needs Input им не доигрываются —
|
||
достаточно регресс-теста, фиксирующего это поведение).
|
||
- Реестры `STAGE_TRANSITIONS` / `QG_CHECKS`, схема прочих стадий.
|
||
|
||
## 5. Допущения и ограничения
|
||
|
||
- **Инвариант reconciler (ORCH-053):** схема БД и реестры не меняются. Решение
|
||
должно либо обойтись без миграции, либо архитектор обязан явно обосновать
|
||
необходимость нового столбца как терминального маркера.
|
||
- **Never-raise:** гард не должен ломать тик; любая ошибка вычисления условия →
|
||
безопасный фоллбэк (не трогать задачу — консервативно).
|
||
- **self-hosting:** нельзя ронять/рестартить прод-контейнер; изменение — чисто
|
||
логика sweeper'а, деплой через staging (8501) по канону.
|
||
- Источник истины по retry — `agent_runs` (как у `_developer_retry_count`).
|
||
|
||
## 6. Критерий успеха (бизнес)
|
||
|
||
После выката на конкретной escalated-задаче (как ET-013): за ночь — **0**
|
||
строк `reconciler: <wi> ... разблокирована`, **0** повторных запусков агентов,
|
||
**0** Telegram-нотификаций разблокировки; задача спокойно ждёт человека в
|
||
`development`/Blocked. При этом штатные «честно застрявшие» задачи
|
||
(retry < лимита, не Blocked) reconciler по-прежнему доигрывает.
|