# 03-Acceptance Criteria — ORCH-086

Каждый критерий формулирует чёткое условие PASS/FAIL. Проверяется автотестами (`tests/test_reconciler.py`) и код-ревью.

## AC-1 — ET-002 (терминальная) больше не генерирует «разблокирована»
**Дано:** F-1 (`reconcile_gate_once`) обрабатывает задачу enduro, чья стадия в БД оркестратора НЕ-`done` (дрейф), а текущий статус в Plane — терминальный (`Done`, группа `completed`); гейт стадии зелёный (для enduro — no-op `True`).
- **PASS:** `_note_unblock` НЕ вызывается → `send_telegram` НЕ вызывается ни при обычном тике, ни при первом проходе после старта (свежий процесс/чистый `_unblock_dedup`).
- **FAIL:** уведомление «… разблокирована (потерян webhook)» отправлено хотя бы раз.

## AC-2 — терминальные задачи (done/cancelled) не доходят до `_note_unblock`
**Дано:** задача терминальна в Plane (группа `completed` или `cancelled`) ИЛИ её стадия в БД ∈ `{done, cancelled}`.
- **PASS:** F-1 делает ранний скип (нет `advance` / нет `_note_unblock`); `skipped_terminal_total` увеличен на 1 на каждую такую задачу за тик.
- **FAIL:** терминальная задача доходит до `advance_if_gate_passed`→`_note_unblock`, либо `skipped_terminal_total` не растёт.
- **Грабли (R1):** условие должно срабатывать для ОБОИХ проектов — enduro (терминал по группе `completed`/`cancelled`, либо fallback-ключ `done`/`cancelled`) и orchestrator (свой набор статусов). Тест покрывает оба пути терминал-детекта: (а) по группе, (б) fallback по логическому ключу при пустых `groups`.

## AC-3 — `_note_unblock` на всех путях получает `state_uuid` → dedup покрывает все вызовы
**Дано:** легитимный unblock реально застрявшей НЕ-терминальной задачи на пути F-1 (гейт зелёный, стадия сменилась).
- **PASS:** `_note_unblock` вызван с непустым `state_uuid`; повторный вызов для того же `issue_id`+`state_uuid` (например на следующем тике до фактической смены статуса) подавляется dedup-guard'ом → `deduped_total` растёт, второго `send_telegram` нет.
- **FAIL:** F-1 зовёт `_note_unblock` без `state_uuid` (2 аргумента) → dedup не работает → повторные уведомления.

## AC-4 — легитимный unblock реально застрявшей задачи ПО-ПРЕЖНЕМУ уведомляет (анти-регресс)
**Дано:** НЕ-терминальная задача (Plane-статус рабочий, не `done`/`cancelled`/`blocked`/`needs_input`), реально застрявшая (прошла grace, нет active-job), гейт зелёный → F-1 её продвигает (`result.advanced=True`).
- **PASS:** `_note_unblock` вызван ОДИН раз; при `reconcile_notify_unblock=True` отправлен ровно один Telegram; `unblocked_total` += 1.
- **FAIL:** уведомление подавлено (полезный алерт задушен) ИЛИ отправлено более одного раза за одну смену стадии.

## AC-5 — pytest зелёный; never-raise в тике сохранён
- **PASS:** `pytest tests/ -q` зелёный; при исключении внутри терминал-детекта/`fetch_issue_state`/`_reconcile_gate_task` тик НЕ падает (изоляция per-task), и ложное уведомление при ошибке НЕ отправляется (консервативно).
- **FAIL:** падение тика, незелёный pytest, либо исключение терминал-детекта приводит к ложной отправке.

## AC-6 — без регрессий смежного поведения (контрактный)
- **PASS:** F-2 (plane-side) терминал-скип/dedup/счётчики работают как в ORCH-068; `STAGE_TRANSITIONS`, `QG_CHECKS`, схема БД, сигнатуры `advance_stage`/`advance_if_gate_passed`/`_note_unblock`, форма `status()`/`GET /queue` — не изменены; новые config-флаги не введены; `reconcile_skip_blocked_enabled` по-прежнему гейтит только Guard 2 (терминал-скип ему не подчинён). Документация (`README.md`, `CHANGELOG.md`) обновлена в том же PR.
- **FAIL:** любое из перечисленного нарушено.
