work_item: ORCH-065
description: >
  Тест-план для фикса zombie jobs (job-reaper), залипшего merge-lease
  (проактивный реклейм dead/stale lease) и идемпотентной финализации merge.
  Все новые фоновые механизмы — never-raise, restart-safe, kill-switch.
  Модуль reaper и точные имена функций уточнит архитектор; в module указаны
  кандидатные пути (tests/test_job_reaper.py / tests/test_merge_lease_reclaim.py).

tests:
  # ---- A. Job-reaper ----
  - id: TC-01
    type: unit
    description: >
      Reaper переводит running-job с мёртвым исполнителем в корректный статус
      без рестарта процесса (pid не существует / exit_code записан, job всё ещё
      running). Покрывает AC-1.
    module: tests/test_job_reaper.py
    expected: PASS

  - id: TC-02
    type: unit
    description: >
      Анти-ложноположительность: running-job с ЖИВЫМ процессом в пределах
      agent_timeout НЕ реапится (ни по одному тику, ни в пределах reaper_max_running_s).
      Покрывает AC-3.
    module: tests/test_job_reaper.py
    expected: PASS

  - id: TC-03
    type: unit
    description: >
      Устойчивость: job помечается зомби только после reaper_dead_ticks
      последовательных тиков смерти pid (>=2), а не на первом тике. Покрывает FR-1.3.
    module: tests/test_job_reaper.py
    expected: PASS

  - id: TC-04
    type: unit
    description: >
      Backstop по потолку: job, висящий running дольше reaper_max_running_s,
      реапится даже если liveness определить нельзя. Покрывает FR-1.1/AC-1.
    module: tests/test_job_reaper.py
    expected: PASS

  - id: TC-05
    type: unit
    description: >
      Корректный исход: exit_code==0 -> успешное завершение без дублирования
      stage-advance; неуспех при attempts<max -> queued; исчерпан бюджет -> failed
      + Telegram. Покрывает AC-4.
    module: tests/test_job_reaper.py
    expected: PASS

  - id: TC-06
    type: unit
    description: >
      Атомарность reap-UPDATE с guard status='running': параллельная обработка
      одной строки (стартовый requeue_running_jobs + reaper) не приводит к двойному
      reap. Покрывает AC-5.
    module: tests/test_job_reaper.py
    expected: PASS

  - id: TC-07
    type: unit
    description: >
      Kill-switch reaper_enabled=false -> reaper не трогает ни одной строки
      (строго прежнее поведение). Покрывает AC-14.
    module: tests/test_job_reaper.py
    expected: PASS

  - id: TC-08
    type: unit
    description: >
      never-raise: ошибка БД/ОС внутри одного тика reaper не пробрасывается и не
      останавливает поток (изоляция per-job, образец reconciler). Покрывает AC-9.
    module: tests/test_job_reaper.py
    expected: PASS

  - id: TC-09
    type: integration
    description: >
      Очередь разблокируется: после reap зомби-строки при max_concurrency=1
      следующий queued-job успешно claim'ится (claim_next_job + count_running_jobs).
      Покрывает AC-2.
    module: tests/test_queue.py
    expected: PASS

  # ---- B. Stale/dead merge-lease reclaim ----
  - id: TC-10
    type: unit
    description: >
      Реклейм lease с мёртвым pid (os.kill(pid,0) -> ProcessLookupError)
      проактивно, не дожидаясь TTL и чужого acquire. Покрывает AC-6.
    module: tests/test_merge_lease_reclaim.py
    expected: PASS

  - id: TC-11
    type: unit
    description: >
      Реклейм по TTL (age >= merge_lock_timeout_s) сохранён, с logger.warning.
      Покрывает AC-7. (расширяет существующий stale-lease сценарий merge_gate.)
    module: tests/test_merge_lease_reclaim.py
    expected: PASS

  - id: TC-12
    type: unit
    description: >
      Живой lease (pid жив, age < TTL) НЕ освобождается — защита легитимного merge.
      Покрывает AC-8.
    module: tests/test_merge_lease_reclaim.py
    expected: PASS

  - id: TC-13
    type: unit
    description: >
      Условность: реклейм реален только для merge_gate_repos/self-hosting; для
      прочих репо no-op. Покрывает AC-9.
    module: tests/test_merge_lease_reclaim.py
    expected: PASS

  - id: TC-14
    type: unit
    description: >
      never-raise: ошибка чтения/удаления lease-файла не валит реклейм-поток.
      Покрывает AC-9.
    module: tests/test_merge_lease_reclaim.py
    expected: PASS

  - id: TC-15
    type: unit
    description: >
      Kill-switch lease_reclaim_enabled=false -> проактивный реклейм отключён,
      остаётся лишь прежний ленивый TTL-реклейм в acquire_merge_lease.
      Покрывает AC-14.
    module: tests/test_merge_lease_reclaim.py
    expected: PASS

  # ---- C. Идемпотентная финализация merge ----
  - id: TC-16
    type: unit
    description: >
      Идемпотентность финализации: повторный вызов при уже слитом PR / уже
      актуальном main (branch_is_behind_main == False) — no-op, без ошибки и без
      второго merge. Покрывает AC-11.
    module: tests/test_merge_gate.py
    expected: PASS

  - id: TC-17
    type: integration
    description: >
      Восстановление: сценарий "rebase+re-test зелёные, merge не состоялся,
      процесс умер" -> job доводится до queued reaper'ом и merge докатывается
      штатным путём без повторного дорогого re-test, когда безопасно. Покрывает AC-10.
    module: tests/test_merge_gate_race.py
    expected: PASS

  # ---- D/E. Инварианты и наблюдаемость ----
  - id: TC-18
    type: integration
    description: >
      GET /queue содержит блок reaper (enabled, interval, last_run_ts,
      reaped_total, last_reaped, lease_reclaimed_total). Покрывает AC-15.
    module: tests/test_queue.py
    expected: PASS

  - id: TC-19
    type: unit
    description: >
      Контракты неизменны: STAGE_TRANSITIONS и реестр QG_CHECKS не получили новых
      стадий/чеков; check_branch_mergeable сигнатурно не изменён. Покрывает AC-13.
    module: tests/test_config.py
    expected: PASS

  - id: TC-20
    type: unit
    description: >
      Новые настройки reaper_*/lease_reclaim_* присутствуют в config с дефолтами и
      читаются из ORCH_* env. Покрывает §5 ТЗ / AC-14.
    module: tests/test_config.py
    expected: PASS

  - id: TC-21
    type: unit
    description: >
      Стартовый реклейм stale/dead lease вызывается в lifespan рядом с
      requeue_running_jobs (smoke на регистрацию старт/стоп reaper-потока).
      Покрывает FR-2.1 / AC-6.
    module: tests/test_job_reaper.py
    expected: PASS

regression:
  - command: pytest tests/ -q
    expected: PASS
    note: >
      Полный прогон не должен ломать существующие тесты merge_gate / queue /
      reconciler / deploy.
