--- verdict: APPROVED # APPROVED | REQUEST_CHANGES — строго одно из двух, UPPERCASE work_item: ORCH-114 stage: review author_agent: reviewer status: approved created_at: 2026-06-15 model_used: claude-opus-4-8 type: review work_item_id: ORCH-114 version: 2 --- # Review ORCH-114 — Durable transition-ownership lease + expected-stage CAS ## Summary Повторное ревью (цикл 2). Прошлый вердикт был `REQUEST_CHANGES` из-за **частичной незавершённости документации golden-source** (два P1 + три P2/P3). Кода-корректность и инварианты конвейера уже тогда были без P0/P1. В этом цикле developer **закрыл все ранее поднятые findings**: - **P1 `.env.example`** → закрыт: добавлены `ORCH_TRANSITION_LEASE_ENABLED=true` / `ORCH_TRANSITION_LEASE_REPOS=` с подробной нормативной шапкой (рядом с блоком reaper-флагов). - **P1 `CLAUDE.md`** → закрыт: добавлена полноценная паспорт-секция «Единое владение side-effectful переходами: durable-lease + expected-stage CAS (ORCH-114)» (механизм, два слоя, инвариант, флаги, ADR-ссылки) — по образцу ORCH-112/113. - **P2 витрина `docs/overview/`** → закрыта: `tech-data-model.md` (строка таблицы `transition_lease`), `tech-observability.md` (упоминание блока `/queue`), `tech-architecture.md` (компонент `Transition-lease`). - **P2 расхождение код↔ADR D4 (CAS на rollback-записях)** → закрыто: введён общий хелпер `_rollback_stage_cas`, и все четыре in-region rollback-записи (`_handle_merge_gate_rollback`/ `_handle_security_gate`/`_handle_coverage_gate`/`_handle_image_freshness`) теперь пишут `development` через тот же expected-stage CAS. Реализация совпала с собственным ADR D4; добавлены целевые тесты (TC-11 `test_tc11_inregion_rollback_writes_use_cas`, `test_tc11_rollback_cas_lost_aborts_without_overwriting_done`). - **P2 изоляция тестов** → закрыта: убран module-level `os.environ.setdefault("ORCH_DB_PATH")`, `test_webhooks.py` пинит собственный реестр per-test, добавлен autouse-fixture `_disable_transition_lease` (kill-switch off для всего suite по образцу `_disable_merge_verify`). Архитектура реализована **в точном соответствии с ADR-001 D1–D10**: durable-lease на входе (`acquire`/`release` в `try/finally`) + expected-stage CAS на записи (включая 6 путей в обход `advance_stage`), liveness по `pid`+`boot_id` без heartbeat, реклейм при рестарте (`recover_on_startup` в `main.lifespan`), reaper/reconciler/webhook defer при живом владельце, Tier-3 backstop добивает зависшего. `src/transition_lease.py` — чистый never-raise leaf (импортирует только `db`+`config`, лениво `merge_gate.pid_alive`/`qg.checks`/`notifications`). **Инварианты конвейера (AC-11):** `src/stages.py` и `src/qg/checks.py` **не тронуты** (нет в диффе), `STAGE_TRANSITIONS`/`QG_CHECKS` встречаются в src-диффе только в комментарии; machine-verdict ключи не тронуты; БД аддитивна (одна таблица `transition_lease`, `CREATE TABLE IF NOT EXISTS`, без epoch-колонки). hot-path `claim_next_job` lease не консультирует (AC-10/fail-open). **Багфикс-трек (ORCH-019/BR-4):** задача `bug→escalate full-cycle`. Регресс-тест-фиксатор **присутствует**: `test_tc01_concurrent_entry_no_double_effect` (зелёный с lease) + `test_tc01_red_before_fix_demonstration` (красный при kill-switch off — второй актор переисполняет все под-гейты, воспроизводя класс ORCH-111). Требование выполнено. **Прогоны (verified):** - `pytest tests/test_orch114_transition_ownership.py tests/test_webhooks.py` — **50 passed** (именно та комбинация двух модулей, что в прошлом цикле давала 4 падения — изоляция подтверждённо починена). - `pytest tests/` (полный) — **2052 passed** в детерминированном порядке → AC-9 (байт-в-байт при kill-switch off) и CI-green выполняются операционно; ORCH-113-тесты в наборе зелёные (контракт предшественника не сломан, ORCH-078). ## Findings ### P0 — Blocker - _(нет)_ ### P1 — Must fix - _(нет)_ ### P2 — Should fix - _(нет)_ ### P3 — Nice to have - [ ] **Merge-lease не освобождается на (практически недостижимой) ветке CAS-lost в rollback'е coverage/image-freshness.** В `_handle_coverage_gate`/`_handle_image_freshness` `release_merge_lease` стоит **после** `_rollback_stage_cas`, поэтому при проигранном CAS (`return True`) штатное освобождение merge-lease пропускается. Под держимым transition-lease эта ветка практически недостижима (единственный владелец → CAS почти всегда выигрывает; чтобы проиграть, нужен аномальный bypass-писатель, сдвинувший стадию с `deploy-staging`). Даже в этом крайнем случае утечка **ограничена** собственным TTL+reclaim merge-lease (ORCH-043/065). Корректность недвоения не нарушена. При желании — продублировать holder-aware `release_merge_lease` до CAS-проверки (или задокументировать намеренный аборт). Не блокер. _Ссылка: ADR-001 D1/D4, ORCH-027/058 rollback._ - [ ] **`reconciler`/`plane._try_advance_stage` зовут `is_held_by_live_owner(task_id)` без предварительного `applies(repo)`** (в отличие от reaper). Для out-of-scope, но включённого репо (enduro при дефолте) это безвредный лишний `SELECT` (строк нет → `False`). Функционально корректно; ради нулевого оверхеда можно добавить дешёвый `applies`-гард. (Перенос P3 из цикла 1 — остаётся косметикой.) ## Документация **Обновлено и проверено (полно):** - `.env.example` — `ORCH_TRANSITION_LEASE_ENABLED` / `ORCH_TRANSITION_LEASE_REPOS` (канон ключей старта, ORCH-101). - `CLAUDE.md` — паспорт-секция ORCH-114. - `docs/overview/` — `tech-data-model.md` / `tech-observability.md` / `tech-architecture.md` (витрина системы, ORCH-011). - `CHANGELOG.md` — подробная запись ORCH-114 в `[Unreleased]`. - `docs/architecture/README.md` + `internals.md` — компонент, секция, таблица БД, API-эндпоинт `POST /transition-lease/release`, блок `/queue`. - ADR: `docs/work-items/ORCH-114/06-adr/ADR-001-…` + сквозной `docs/architecture/adr/adr-0045-…` — полные, со сверкой по коду. - Work-item доки `02-trz`/`03-acceptance-criteria`/`04-test-plan`/`08-data-requirements`/ `10-tech-risks` — на месте. **Необновлённой документации при изменении `src/` не выявлено.** Пунктов README «Известные ограничения», закрываемых этим PR, нет. ## Verdict rationale Все P0/P1/P2 прошлого цикла закрыты; новых P0/P1 не выявлено по всем четырём осям (ТЗ/AC, ADR, качество кода + обязательный регресс-тест багфикс-трека, документация golden-source). Остаются только два P3-наблюдения (косметика/недостижимая граница), не блокирующие приёмку. Полный `pytest tests/` зелёный (2052 passed), инварианты конвейера и схемы существующих таблиц — байт-в-байт. → **APPROVED**.