work_item: ORCH-053
description: >
  Тесты sweeper/reconciler потерянных webhook. Вся сеть (Plane API, Gitea API, QG)
  мокируется (monkeypatch), как в существующих tests/. Telegram заглушён autouse-фикстурой
  conftest. Используется временная SQLite БД (ORCH_DB_PATH / фикстура setup_db по образцу
  test_webhooks.py / test_queue.py). Реальные агенты/CLI не запускаются.

tests:
  # ---- F-1: gate-side sweeper -------------------------------------------------
  - id: TC-01
    type: unit
    description: >
      reconcile_gate_once продвигает застрявшую development-задачу: нет активных job,
      updated_at старше grace, check_ci_green замокан в (True, "CI green") →
      advance_stage вызван, стадия стала review, заenqueuen reviewer.
    module: tests/test_reconciler.py
    expected: PASS

  - id: TC-02
    type: unit
    description: >
      Источник истины — гейт: reconciler НЕ содержит собственного update_task_stage/
      enqueue_job для advance — продвижение идёт строго через stage_engine.advance_stage
      (проверка через мок/spy advance_stage, вызван с finished_agent=None).
    module: tests/test_reconciler.py
    expected: PASS

  - id: TC-03
    type: unit
    description: >
      Задача с активным job (has_active_job_for_task=True) пропускается: гейт не дёргается,
      advance_stage не вызывается, нотификаций нет.
    module: tests/test_reconciler.py
    expected: PASS

  - id: TC-04
    type: unit
    description: >
      Per-stage grace: задача с updated_at свежее grace своей стадии не трогается;
      ровно на границе age>=grace и без активного job — становится кандидатом.
    module: tests/test_reconciler.py
    expected: PASS

  - id: TC-05
    type: unit
    description: >
      grace_for_stage читает reconcile_grace_overrides_json (per-stage), при отсутствии
      ключа — reconcile_grace_default_s; невалидный JSON → дефолт, не падает.
    module: tests/test_reconciler.py
    expected: PASS

  - id: TC-06
    type: unit
    description: >
      Нет спама: при стабильно красном гейте (check_ci_green=(False,...)) несколько проходов
      подряд НЕ вызывают notify_qg_failure повторно на каждом тике; задача не продвигается.
    module: tests/test_reconciler.py
    expected: PASS

  - id: TC-07
    type: unit
    description: >
      Тишина при синхронности: когда все задачи done / имеют активный job / в пределах grace —
      проход не вызывает advance_stage и не пишет INFO-логов о разблокировке.
    module: tests/test_reconciler.py
    expected: PASS

  - id: TC-08
    type: unit
    description: >
      AC-16: задача на analysis с артефактами на диске, но Plane НЕ Approved — F-1
      (reconcile_gate_once) НЕ продвигает analysis→architecture.
    module: tests/test_reconciler.py
    expected: PASS

  - id: TC-09
    type: unit
    description: >
      Never-raise: если обработка одной задачи кидает исключение (advance_stage замокан на
      raise), проход ловит его и продолжает обрабатывать остальные задачи; поток не падает.
    module: tests/test_reconciler.py
    expected: PASS

  - id: TC-10
    type: unit
    description: >
      Kill-switch: при reconcile_enabled=False reconcile_gate_once/plane_once не выполняют
      действий (no-op); при reconcile_plane_enabled=False гасится только F-2.
    module: tests/test_reconciler.py
    expected: PASS

  # ---- F-2: plane-side reconciler --------------------------------------------
  - id: TC-11
    type: unit
    description: >
      In Progress без задачи: list_issues_by_state возвращает issue в In Progress, в БД задачи
      нет → reconcile_plane_once вызывает handle_status_start (мок) ровно один раз с корректным
      issue_data (id/state/project).
    module: tests/test_reconciler_plane.py
    expected: PASS

  - id: TC-12
    type: unit
    description: >
      Approved без advance: issue=Approved, task существует, нет активного job → вызван
      handle_verdict(approved=True) (мок) один раз.
    module: tests/test_reconciler_plane.py
    expected: PASS

  - id: TC-13
    type: unit
    description: >
      Rejected без rollback: issue=Rejected, task существует, нет активного job → вызван
      handle_verdict(approved=False) (мок) один раз.
    module: tests/test_reconciler_plane.py
    expected: PASS

  - id: TC-14
    type: unit
    description: >
      Идемпотентность F-2: issue в требующем-действия статусе, но у task есть активный job →
      handle_status_start/handle_verdict НЕ вызываются (живой webhook в работе).
    module: tests/test_reconciler_plane.py
    expected: PASS

  - id: TC-15
    type: integration
    description: >
      AC-4 анти-дубль на создании: одновременная реконсиляция + обработка реального In Progress
      webhook для одного plane_id создают ровно ОДИН task row и один стартовый analyst-job
      (реальная временная БД, мок Gitea/Plane сетевых вызовов).
    module: tests/test_reconciler_plane.py
    expected: PASS

  - id: TC-16
    type: unit
    description: >
      list_issues_by_state never-raise: при ошибке Plane API (httpx бросает / non-2xx) →
      возвращает [], тик не падает; при успехе — обходит пагинацию и фильтрует по state.
    module: tests/test_reconciler_plane.py
    expected: PASS

  - id: TC-17
    type: unit
    description: >
      F-2 опрашивает все проекты реестра projects.PROJECTS и резолвит state-uuid через
      get_project_states per-project (enduro + orchestrator), не хардкодит uuid.
    module: tests/test_reconciler_plane.py
    expected: PASS

  # ---- F-3: sha→branch резолв -------------------------------------------------
  - id: TC-18
    type: unit
    description: >
      handle_ci_status: при отсутствии branches и неразрезолвленном sha срабатывает БД-fallback
      и однозначно находит единственную development-задачу repo; продвижение идёт штатно.
    module: tests/test_gitea_sha_resolve.py
    expected: PASS

  - id: TC-19
    type: unit
    description: >
      handle_ci_status: при неоднозначности (несколько development-задач repo) БД-fallback не
      делает ложный матч (branch остаётся неразрезолвленным, лог INFO), success/failure-семантика
      гейта не изменена.
    module: tests/test_gitea_sha_resolve.py
    expected: PASS

  # ---- F-4 / интеграция фонового потока --------------------------------------
  - id: TC-20
    type: unit
    description: >
      Наблюдаемость: при разблокировке reconciler пишет явную лог-строку с work_item_id и
      stage; при reconcile_notify_unblock=True вызывается send_telegram (замокан).
    module: tests/test_reconciler.py
    expected: PASS

  - id: TC-21
    type: integration
    description: >
      Restart-safe поток: Reconciler.start() поднимает daemon-поток, stop() завершает его
      в пределах таймаута; повторный start идемпотентен (не плодит второй поток).
    module: tests/test_reconciler.py
    expected: PASS

  - id: TC-22
    type: unit
    description: >
      Конфиг: новые поля reconcile_* присутствуют в Settings с заявленными дефолтами и
      читаются из env с префиксом ORCH_ (по образцу tests/test_config.py).
    module: tests/test_config.py
    expected: PASS

  - id: TC-23
    type: unit
    description: >
      Регресс реестров: STAGE_TRANSITIONS и QG_CHECKS не изменены ORCH-053
      (snapshot-тест проходит как раньше).
    module: tests/test_qg_registry_snapshot.py
    expected: PASS
