work_item: ORCH-114
stage: analysis
author_agent: analyst
status: ready-for-review
created_at: 2026-06-15
model_used: claude-opus-4-8
escalate: full-cycle
title: "Ownership-lease для side-effectful переходов стадий + умное восстановление при старте"
framework: pytest
scope: >
  Покрывается: единое владение side-effectful переходом (FR-1), CAS/epoch на запись стадии (FR-2),
  осведомлённость reaper о живом/мёртвом владении на всех путях (FR-3), умное восстановление при
  рестарте (FR-4), skip/defer в reconciler F-1 и webhook (FR-5), наблюдаемость (FR-6), kill-switch
  и never-raise (FR-7). Вне покрытия: переход на uvicorn --workers>1, частные симптомы ORCH-110/112
  (уже закрыты и переиспользуются), изменение STAGE_TRANSITIONS/QG_CHECKS/check_*.
notes: >
  TC-01 — ОБЯЗАТЕЛЬНЫЙ регресс класса инцидента ORCH-111: красный до фикса, зелёный после.
  Все side-effectful вызовы (merge_pr / coverage-ratchet / image-rebuild / deploy-init) проверяются
  через тест-двойники со счётчиком вызовов — без сети/реального git/прода/ssh. Restart-recovery
  моделируется сбросом in-memory состояния + повторным прогоном стартового восстановления над durable
  состоянием БД. Полный регресс tests/ должен оставаться зелёным; при выключенном kill-switch
  поведение байт-в-байт прежнее.

tests:
  - id: TC-01
    type: integration
    description: "ОБЯЗАТЕЛЬНЫЙ РЕГРЕСС. Два конкурентных входа в advance_stage(deploy-staging) одной задачи (живой финализатор + reaper/reconciler/webhook): каждый side-effectful шаг (merge_pr/ratchet/rebuild/deploy-init/enqueue) вызывается ровно один раз; персистится один согласованный исход; второй актор — lost-race/defer без побочных эффектов. Красный до фикса, зелёный после."
    module: tests/test_orch114_transition_ownership.py
    expected: PASS

  - id: TC-02
    type: unit
    description: "CAS-запись стадии: первый writer применяет (rowcount=1), второй с устаревшим предусловием получает lost-race (rowcount=0) и не мутирует стадию (FR-2/AC-2)."
    module: tests/test_orch114_transition_ownership.py
    expected: PASS

  - id: TC-03
    type: unit
    description: "Жизненный цикл владения: acquire на границе финализации; release в try/finally при нормальном завершении, при исключении и при откате (lease не течёт); durable-запись видна другому актору (FR-1/AC-3)."
    module: tests/test_orch114_transition_ownership.py
    expected: PASS

  - id: TC-04
    type: integration
    description: "Reaper defer при живом владении на путях за пределами Tier-2/deploy-staging ORCH-113: повторный advance не выполняется, атомарный reap_running_job rowcount-guard сохранён, ведётся счётчик defer (FR-3/AC-4)."
    module: tests/test_orch114_transition_ownership.py
    expected: PASS

  - id: TC-05
    type: unit
    description: "Reaper добивает мёртвое/устаревшее владение в пределах Tier-3 backstop reaper_max_running_s; маркер владения backstop не обходит; задача не клинится бессрочно (FR-3/AC-5/NFR-6)."
    module: tests/test_orch114_transition_ownership.py
    expected: PASS

  - id: TC-06
    type: integration
    description: "Умное восстановление при рестарте: процесс убит в середине финализации, in-memory сброшен; стартовое восстановление над durable-состоянием + авторитетными фактами (SHA-in-main, INITIATED) сходится к единственному исходу без повторного необратимого эффекта (FR-4/AC-6)."
    module: tests/test_orch114_transition_ownership.py
    expected: PASS

  - id: TC-07
    type: integration
    description: "Reconciler F-1 (advance_if_gate_passed) делает defer/skip при активном lease перехода; fail-safe: неопределённость lease → консервативный skip (FR-5/AC-7)."
    module: tests/test_orch114_transition_ownership.py
    expected: PASS

  - id: TC-08
    type: integration
    description: "Webhook-путь (plane._try_advance_stage, Approved/Confirm Deploy) делает defer при активном lease; поздний легитимный сигнал не теряется (повтор после release / идемпотентный no-op) (FR-5/AC-8)."
    module: tests/test_orch114_transition_ownership.py
    expected: PASS

  - id: TC-09
    type: integration
    description: "Kill-switch off: lease не пишется/не читается, CAS вырождается в прежний безусловный update_task_stage, reaper/reconciler/webhook/startup — байт-в-байт до ORCH-114; существующий pytest tests/ зелёный (FR-7/AC-9/NFR-8)."
    module: tests/test_orch114_transition_ownership.py
    expected: PASS

  - id: TC-10
    type: unit
    description: "never-raise + fail-open/fail-closed: ошибка/повреждённая запись lease/исключение БД не роняют конвейер; hot-path claim/guard fail-open; prod-safety решение fail-closed; WARNING + безопасный дефолт (FR-7/AC-10/NFR-1)."
    module: tests/test_orch114_transition_ownership.py
    expected: PASS

  - id: TC-11
    type: unit
    description: "Структурный аудит инвариантов: STAGE_TRANSITIONS / QG_CHECKS / имена-семантика check_* / вердикт-ключи байт-в-байт; новое хранилище аддитивно/идемпотентно (CREATE TABLE IF NOT EXISTS / _ensure_column), схемы существующих таблиц неизменны (NFR-3/AC-11)."
    module: tests/test_orch114_transition_ownership.py
    expected: PASS

  - id: TC-12
    type: integration
    description: "Наблюдаемость: GET /queue несёт аддитивный read-only блок владения (держатели/возраст/defer/реклеймы), существующие ключи не сломаны; форсированный/устаревший реклейм даёт Telegram-алерт с кликабельным номером (FR-6/AC-12)."
    module: tests/test_orch114_transition_ownership.py
    expected: PASS

  - id: TC-13
    type: unit
    description: "Self-hosting безопасность: механизм владения не инициирует рестарт прод-контейнера, не пушит/force-push в main, не трогает detached deploy-процесс (NFR-5/AC-13)."
    module: tests/test_orch114_transition_ownership.py
    expected: PASS

  - id: TC-14
    type: integration
    description: "Полный регресс конвейера: pytest tests/ остаётся зелёным; deploy-staging-ребро и deploy-finalizer (Phase C) проходят при включённом механизме без двойных эффектов в одно-акторном happy-path (BR-8)."
    module: tests/
    expected: PASS
