# Acceptance Criteria — ORCH-053 Work Item ID: ORCH-053 Формат: каждый критерий имеет явное условие PASS/FAIL. Критерий считается выполненным, только если соответствующие тесты из `04-test-plan.yaml` зелёные. ## AC-1 — Реконсиляция застрявшей стадии (gate-side, F-1) - **Дано:** task на стадии `development`, без активных job'ов, `updated_at` старше grace, гейт `check_ci_green` для её branch — зелёный (CI прошёл, но webhook потерян, как ORCH-044). - **Когда:** срабатывает фоновый проход `reconcile_gate_once()`. - **PASS:** задача продвинута `development → review`, заenqueuen `reviewer` (через `advance_stage(..., finished_agent=None)`), `tasks.updated_at` обновлён. - **FAIL:** задача осталась на `development`, либо продвижение пошло параллельной логикой (не через `advance_stage`). ## AC-2 — Источник истины — гейт, не событие - **PASS:** продвижение в F-1 выполняется исключительно вызовом `stage_engine.advance_stage(...)`; в `reconciler.py` НЕТ собственного `update_task_stage`+`enqueue_job` для advance стадии (только переиспользование). - **FAIL:** в reconciler продублирована логика advance/rollback. ## AC-3 — Идемпотентность: sweeper не трогает задачи с активным job - **Дано:** task с `queued` или `running` job (`has_active_job_for_task == True`). - **PASS:** sweeper пропускает задачу — ни advance, ни enqueue, ни нотификации. - **FAIL:** sweeper дёргает гейт / создаёт второй job для такой задачи. ## AC-4 — Идемпотентность: задержавшийся/дублированный webhook + sweeper не двоят - **Дано:** issue в Plane = In Progress, задержавшийся Plane-webhook ещё не обработан. - **Когда:** F-2 реконсилирует И затем (или одновременно) приходит реальный webhook. - **PASS:** создаётся **ровно одна** задача (один task row, один branch/worktree, один стартовый analyst-job). Повторный путь видит существующую задачу/активный job и не двоит. - **FAIL:** созданы две задачи / два стартовых job / два worktree на один `plane_id`. ## AC-5 — Per-stage grace соблюдается - **Дано:** task на стадии, чей `updated_at` свежее grace этой стадии (агент легитимно работает, напр. analysis 8 мин при grace 1800с). - **PASS:** sweeper НЕ трогает задачу (не дёргает гейт). - **PASS (граница):** как только `age(updated_at) >= grace_for_stage(stage)` и нет активного job — задача становится кандидатом. - **FAIL:** sweeper дёргает гейт у задачи в пределах grace. ## AC-6 — Plane In Progress без задачи → запуск (F-2) - **Дано:** issue в Plane = In Progress (статус сменён руками, webhook потерян), в `tasks` задачи нет, прошёл grace. - **PASS:** sweeper вызывает `handle_status_start`/`start_pipeline` → задача создана, заenqueuen analyst — как если бы пришёл webhook. - **FAIL:** задача не создана; либо создана дублирующей логикой, минуя `handle_status_start`. ## AC-7 — Plane Approved без advance → advance (F-2) - **Дано:** issue = Approved, task существует и стадия НЕ сдвинута, нет активного job, прошёл grace. - **PASS:** sweeper вызывает `handle_verdict(approved=True)` → штатный advance. - **FAIL:** нет advance, либо advance вне `handle_verdict`/`advance_stage`. ## AC-8 — Plane Rejected без rollback → rollback (F-2) - **Дано:** issue = Rejected, task существует и не откатана, нет активного job, прошёл grace. - **PASS:** sweeper вызывает `handle_verdict(approved=False)` → штатный rollback на предыдущую стадию. - **FAIL:** нет rollback, либо rollback вне штатного пути. ## AC-9 — Нет спама нотификаций на красном гейте - **Дано:** застрявшая задача, у которой гейт стабильно **красный** (напр. CI failure), нет активного job, прошёл grace. - **Когда:** sweeper проходит несколько тиков подряд. - **PASS:** `notify_qg_failure`/Telegram НЕ вызывается на каждом тике (≤1 раз / без повторов); задача не продвигается. - **FAIL:** на каждом тике летит нотификация о провале гейта. ## AC-10 — Тишина при синхронности - **Дано:** все задачи синхронны (нет застрявших; статусы Plane совпадают с локальными). - **PASS:** проход не выполняет действий, не пишет INFO-логов о разблокировке, не шлёт нотификаций. - **FAIL:** sweeper генерирует шум/действия при полностью синхронном состоянии. ## AC-11 — Restart-safe фоновый поток - **PASS:** reconciler стартует в `main.lifespan` (daemon-поток), корректно останавливается (`stop()`), переживает рестарт сервиса без потери (нет состояния в памяти, критичного для корректности; всё перечитывается из БД/Plane). - **FAIL:** reconciler не стартует автоматически, висит при shutdown, или дублирует действия после рестарта. ## AC-12 — Наблюдаемость разблокировки (F-4) - **Дано:** sweeper разблокировал застрявшую задачу. - **PASS:** в лог пишется явная строка вида `reconciler: разблокирована (потерян webhook)`; при `reconcile_notify_unblock=True` — Telegram-уведомление. - **FAIL:** разблокировка происходит молча (невозможно измерить частоту дыры). ## AC-13 — Kill-switch - **Дано:** `reconcile_enabled=False` (env `ORCH_RECONCILE_ENABLED=false`). - **PASS:** фоновый поток reconciler не выполняет проходов (или не стартует); система работает как до ORCH-053. `reconcile_plane_enabled=False` гасит только F-2, F-1 работает. - **FAIL:** sweeper активен при выключенном флаге. ## AC-14 — Усиленный sha→branch резолв (F-3) - **Дано:** Gitea CI-status webhook без `branches` и со `sha`, не разрезолвившимся через `git branch -r --contains`. - **PASS:** добавленный БД-fallback однозначно находит task (по repo + активной development-стадии) и продвигает; неоднозначность логируется на уровне INFO; существующая success/failure-семантика гейта не изменена. - **FAIL:** регресс существующего резолва, либо ложный матч при неоднозначности. ## AC-15 — Never-raise в тике - **Дано:** обработка одной задачи/issue кидает исключение (битые данные, ошибка API). - **PASS:** исключение изолировано, проход продолжает остальные задачи; поток не падает. - **FAIL:** одно исключение роняет весь проход / поток reconciler. ## AC-16 — F-1 не продвигает analysis по локальному состоянию - **Дано:** task на `analysis`, артефакты на диске присутствуют, но Plane НЕ в статусе Approved (BRD не одобрен человеком), нет активного job, прошёл grace. - **PASS:** F-1 (gate-side) НЕ продвигает analysis→architecture (advance стадии analysis отдан F-2, которая сверяется с реальным статусом Plane Approved). - **FAIL:** sweeper автопродвинул неодобренный BRD. ## AC-17 — Документация обновлена (golden source) - **PASS:** в PR обновлены `docs/architecture/README.md`, заведён `docs/work-items/ORCH-053/06-adr/ADR-001-*.md`, обновлён `CHANGELOG.md`, упомянут kill-switch в `docs/operations/INFRA.md`. - **FAIL:** код изменён, документация — нет (Reviewer обязан вернуть REQUEST_CHANGES).