Files
orchestrator/docs/work-items/ORCH-114/02-trz.md

144 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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
---
# 02 — ТЗ (TRZ): ORCH-114 — Ownership-lease для side-effectful переходов стадий + умное восстановление при старте
Work Item: **ORCH-114** · Repo: **orchestrator** · Стадия: analysis
> ТЗ описывает **конкретные требования к реализации**, выведенные из BRD (`01-brd.md`) и фактического
> кода. **Выбор механизма** (durable lease / heartbeat / transition-epoch / форма хранения, набор
> покрываемых рёбер) и архитектурное обоснование — задача архитектора (`06-adr/`). Здесь — *что*
> должно быть истинно и *какие модули* затрагиваются, не *как* именно.
## 1. Сводка изменения
Вводится **единый инвариант владения** side-effectful переходом стадии: запись стадии и исполнение
тяжёлых под-гейтов/финализации защищаются **durable-механизмом владения** (lease/epoch) + **CAS** на
запись стадии, так что в любой момент переход конкретной задачи доводит **ровно один** актор, а
конкурентный/после-рестартовый повторный вход (reaper / reconciler / webhook / startup-requeue)
**откладывается или становится no-op** вместо повторного применения необратимых эффектов
(merge_pr / coverage-ratchet / image-rebuild / инициация прод-деплоя / противоречивый rollback↔done).
Аддитивно, под kill-switch, never-raise; `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / вердикт-ключи
/ схемы существующих таблиц — не трогаются (обобщает и делает durable процесс-локальный
`finalizer_liveness` ORCH-113).
## 2. Задействованные модули / пути
| Путь | Действие | Назначение в ORCH-114 |
|------|----------|------------------------|
| `src/stage_engine.py` | изменить | `advance_stage`: захват владения на границе side-effectful перехода/финализации; CAS на запись стадии; release в `try/finally`; проигравший — чистый аборт. Покрыть `_handle_merge_verify`, под-гейты `deploy-staging→deploy`, `run_deploy_finalizer` (Phase C), `advance_if_gate_passed` (F-1). |
| `src/db.py` | изменить | CAS-вариант записи стадии (запись только при совпадении ожидаемой текущей стадии/эпохи; rowcount-результат). Durable-хелперы владения (acquire / heartbeat-touch / release / reclaim / snapshot) — форму хранилища задаёт архитектор. |
| `src/finalizer_liveness.py` | изменить/обобщить | Отправная точка: обобщить process-local реестр до **durable, кросс-путевого** владения (или надстроить durable-слой поверх). Сохранить контракт never-raise + kill-switch. |
| `src/job_reaper.py` | изменить | Tier-2/Tier-3 осведомлены о durable-владении на **всех** релевантных путях (не только Tier-2/`deploy-staging`): defer при живом, реклейм мёртвого/устаревшего в ограниченное время (NFR-6). |
| `src/queue_worker.py` | изменить | `requeue_running_jobs` / стартовое восстановление сверяется с durable-владением: не пере-исполнять уже применённый необратимый шаг (умное восстановление). `claim_next_job` — не вносить сетевых зависимостей. |
| `src/reconciler.py` | изменить | F-1 (`advance_if_gate_passed`) — defer при активном lease перехода (по образцу skip-guard'ов escalated/Blocked/deps). |
| `src/webhooks/plane.py` | изменить | Пути продвижения (`_try_advance_stage`, Approved / Confirm Deploy) — defer при активном lease. |
| `src/webhooks/gitea.py` | изменить (учесть) | Прямые записи стадии в обход `advance_stage` (`handle_push`/`handle_ci_status`/`handle_pr`) должны попадать под тот же CAS-инвариант либо явно исключаться архитектором (граница в ADR). |
| `src/main.py` | изменить | Порядок старта демонов / точка восстановления; read-only блок в `GET /queue`; опц. operator-эндпоинт реклейма. |
| `src/config.py` | изменить | Kill-switch(и) + бюджеты/таймауты владения + (опц.) CSV-скоуп репо. |
| `tests/test_orch114_transition_ownership.py` | создать | Покрытие FR-1…FR-7 (см. `04-test-plan.yaml`). |
## 3. Функциональные требования
### FR-1 — Единое владение side-effectful переходом (BR-1, BR-6)
На границе, где начинается side-effectful финализация/переход (минимум: под-гейты
`deploy-staging→deploy`, `_handle_merge_verify` на `deploy→done`, Phase C `run_deploy_finalizer`),
актор **захватывает владение** задачей. Пока владение активно, другой актор не исполняет тот же
переход. Release — детерминированно в `try/finally` (в т.ч. на исключении/откате). Владение
**durable** (NFR-4): переживает рестарт и доступно для проверки другому актору/новому процессу.
### FR-2 — Compare-and-swap / epoch на запись стадии (BR-2)
Запись стадии для side-effectful переходов выполняется только если предусловие (ожидаемая текущая
стадия и/или эпоха перехода) не изменилось с момента чтения. Реализуется через CAS-вариант
`update_task_stage` (`UPDATE … SET stage=?[, epoch=epoch+1] WHERE id=? AND stage=?[ AND epoch=?]`,
решение по форме — архитектор). Проигравший гонку писатель получает «lost-race» результат, **не**
мутирует стадию и **не** выполняет ни одного побочного эффекта (merge_pr / ratchet / rebuild /
deploy-init / enqueue следующего агента). Инвариант распространяется и на пути, пишущие стадию в
обход `advance_stage` (gitea-webhook), — либо CAS, либо явное исключение (граница в ADR).
### FR-3 — Reaper, осведомлённый о владении на всех путях (BR-3, NFR-6)
Job-reaper перед реклеймом сверяется с durable-владением **не только** в Tier-2 для `deploy-staging`
(текущая область ORCH-113), а на всех релевантных тирах/рёбрах: **живой** владелец → **defer**
(лог + счётчик, без повторного advance); **мёртвый/устаревший** владелец → реклейм в ограниченное
время (Tier-3 backstop `reaper_max_running_s` добивает зависшего; маркер владения backstop не
обходит инвариант бюджета). Сохранить атомарный `reap_running_job` rowcount-guard.
### FR-4 — Умное восстановление при старте (BR-4, BR-6, NFR-7)
Стартовое восстановление (`requeue_running_jobs` + последующий цикл) использует durable-владение/эпоху,
чтобы **детерминированно** различить: (a) финализация не начиналась / безопасно перезапустить →
re-drive; (b) необратимый шаг уже применён (мерж в `main` / ratchet / прод-деплой инициирован) →
**сойтись к done/консистентному исходу без повторного применения**. Источник истины для «уже
применено» — авторитетные durable-факты (SHA-in-main ORCH-071/073, маркер `INITIATED` self-deploy,
durable-lease/эпоха), а не in-memory состояние.
### FR-5 — Skip/defer в reconciler и webhook (BR-5)
Reconciler F-1 (`advance_if_gate_passed`) и webhook-пути продвижения (`plane._try_advance_stage`,
Approved/Confirm Deploy) при **активном** lease перехода для задачи **откладывают** действие
(silent skip + наблюдаемость), по образцу существующих skip-guard'ов F-1 (escalated / Blocked /
task-deps). Fail-safe: неопределённость состояния lease → консервативный skip (не дублировать).
### FR-6 — Наблюдаемость (BR-7)
Аддитивный read-only блок в `GET /queue` (по образцу `serial_gate`/`merge_gate`/`reaper`):
держатели lease, возраст владения, defer-счётчики, форсированные/устаревшие реклеймы. Алерт
(`send_telegram`, кликабельный номер) на форсированный/устаревший реклейм. Опц. запись в
lessons-journal (ORCH-098, `source="auto"`). Опц. operator-эндпоинт ручного реклейма (по образцу
`POST /serial-gate/unfreeze`).
### FR-7 — Конфигурация, обратимость, never-raise (BR-9, NFR-1, NFR-2, NFR-8)
Все публичные функции владения — never-raise (ошибка → безопасный дефолт + WARNING). Kill-switch
возвращает поведение **байт-в-байт** к до-ORCH-114 (lease не пишется/не читается, CAS вырождается в
прежний безусловный `update_task_stage`). Hot-path — fail-open; prod-safety — fail-closed.
## 4. Изменения API
- **`GET /queue`** — аддитивный read-only блок владения переходом (имя ключа уточнит архитектор,
напр. `transition_ownership` / `transition_lease`). Существующие ключи `/queue` — байт-в-байт.
- **(Опционально, по решению архитектора)** `POST /transition-lease/release?work_item=<id>`
операторский ручной реклейм застрявшего владения (паттерн `POST /serial-gate/unfreeze`).
- `GET /metrics` (ORCH-099) — при необходимости аддитивное поле без бампа `schema_version` (sidecar
обязан толерировать незнакомые ключи). Прочие эндпоинты — не трогаются.
## 5. Изменения схемы БД
**Требование (форму выбирает архитектор, `06-adr/` + `08-data-requirements.md`):** для durable-владения
(NFR-4) и CAS/epoch (FR-2) требуется **аддитивное, идемпотентное** durable-состояние. Кандидаты
(не предписание):
- доп. аддитивная таблица владения (`CREATE TABLE IF NOT EXISTS`, паттерн `repo_freeze`/
`coverage_baseline`/`lessons`) с `(task_id/job_id, owner, run_id, stage, acquired_at, heartbeat_at,
expires_at)`; **либо**
- аддитивные колонки на `tasks`/`jobs` (`_ensure_column`, паттерн `tasks.track`/`tasks.cancelled_at`),
включая возможную `epoch/version`-колонку для CAS.
**Жёсткие ограничения (NFR-3):** только аддитивно/идемпотентно; **схемы существующих таблиц
(`tasks`/`jobs`/`agent_runs` и пр.) — байт-в-байт**; никаких изменений существующих столбцов/индексов,
ломающих обратную совместимость; restart-safe инициализация в `init_db()`.
## 6. Требования к новым/изменённым QG checks
**Нет.** `QG_CHECKS` / `check_*` / `_parse_*` / машинные вердикт-ключи — **не трогаются**. Владение
переходом — свойство **движка переходов и фоновых акторов**, а **не** Quality Gate и **не** стадия
(аналогично тому, как merge-lease/serial-gate/finalizer-liveness — врезки/leaf'ы, а не QG). Никаких
новых стадий/рёбер в `STAGE_TRANSITIONS`.
## 7. Совместимость / регресс
- **Kill-switch** (новый флаг в `config.py`, env `ORCH_*`): `False` → CAS вырождается в прежний
безусловный `update_task_stage`, lease не пишется/не читается, reaper/reconciler/webhook ведут себя
как до ORCH-114 — **байт-в-байт** (включая зелёный существующий `pytest tests/`).
- **Область раската:** по образцу leaf-гейтов; durable-lease минимально применяется к self-hosting
рёбрам (`deploy-staging`/`deploy→done`, где живут необратимые эффекты); generic-CAS инертен при
отсутствии гонки (нулевая стоимость на не-затронутых переходах). Точную область фиксирует архитектор.
- **Обратимость:** механизм аддитивен и изолирован; откат = выключить kill-switch (durable-таблица/
колонки остаются инертными).
- **never-raise / fail-open / fail-closed:** hot-path claim/guard — fail-open (не клинить общую
очередь, AC-8 ORCH-088); prod-safety/необратимость — fail-closed; любой сбой механизма — WARNING +
безопасный дефолт.
- **Сквозной бюджет:** lease согласован с `reaper_max_running_s`/`reaper_finalize_grace_s` (ORCH-065/
109/110/113) — не удлиняет финализацию за backstop; устаревший владелец добивается в ограниченное время.
- **Маркеры трассировки (ORCH-078):** правки в блоках с маркерами ORCH-065/109/110/111/113 (reaper,
finalizer-liveness, merge-gate) сверяются с их ADR перед изменением; новый код помечается `ORCH-114`.
- **enduro-trails:** при флаге off / репо вне области — нулевая регрессия.