analyst(ET): auto-commit from analyst run_id=708
This commit is contained in:
186
docs/work-items/ORCH-114/01-brd.md
Normal file
186
docs/work-items/ORCH-114/01-brd.md
Normal file
@@ -0,0 +1,186 @@
|
||||
---
|
||||
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
|
||||
---
|
||||
|
||||
# 01 — BRD (бизнес-требования): ORCH-114 — Ownership-lease для side-effectful переходов стадий + умное восстановление при старте
|
||||
|
||||
Work Item: **ORCH-114** · Repo: **orchestrator** · Стадия: analysis
|
||||
|
||||
> **Багфикс-трек → ЭСКАЛАЦИЯ В ПОЛНЫЙ ЦИКЛ (`escalate: full-cycle`).** Задача пришла под меткой
|
||||
> `Bug` (укороченный маршрут ORCH-019, пропуск `architecture`), но дефект **системный и
|
||||
> архитектурный**: вводится глобальный инвариант владения переходом, durable-механизм, переживающий
|
||||
> рестарт процесса, и compare-and-swap на запись стадии. Это требует **ADR** (выбор механизма:
|
||||
> lease / heartbeat / transition-epoch / CAS) и затрагивает поведение всего конвейера и нескольких
|
||||
> фоновых акторов. Поэтому выпускается **полный** analysis-пакет; оператор снимает багфикс-трек
|
||||
> эндпоинтом `POST /bug-fast-track/escalate?work_item=ORCH-114` → задача уходит в `architecture`
|
||||
> (ADR-001 D5 ORCH-019).
|
||||
|
||||
---
|
||||
|
||||
## 1. Бизнес-контекст и проблема
|
||||
|
||||
ORCH-114 — **системный наследник** инцидент-цепочки ORCH-110 / ORCH-111 / ORCH-112 / ORCH-113.
|
||||
Каждый предшественник закрыл **точечный** симптом, но **корневой класс** остался открыт:
|
||||
**у side-effectful переходов стадий нет единого владения (ownership)**.
|
||||
|
||||
### Корень (установленный факт, верифицировано кодом)
|
||||
|
||||
`stage_engine.advance_stage(...)` — единая точка перехода между стадиями и исполнения тяжёлых
|
||||
под-гейтов ребра `deploy-staging → deploy` (security → merge-gate re-test → coverage →
|
||||
image-freshness) и под-гейта `deploy → done` (`_handle_merge_verify`: `merge_pr`, ratchet
|
||||
coverage-baseline, запись proof-of-merge). При этом:
|
||||
|
||||
- **Запись стадии не атомарна по предусловию.** `db.update_task_stage(task_id, stage)` —
|
||||
«голый» `UPDATE tasks SET stage=? WHERE id=?` **без** `WHERE stage=?` (нет compare-and-swap, нет
|
||||
epoch/version-колонки). Любой второй вызов безусловно перезатирает результат первого.
|
||||
- **`advance_stage` ре-ентерабельна без защиты.** Внутри неё нет ни in-memory-лока на `task_id`,
|
||||
ни durable-маркера «переход в процессе». Два конкурентных вызова для одной задачи оба читают
|
||||
`stage='deploy-staging'`, оба прогоняют ВСЕ под-гейты, оба пишут `deploy`, оба ставят
|
||||
следующего агента.
|
||||
- **Минимум 5 путей входят в переход независимо:** (1) монитор агента (`launcher._try_advance_stage`,
|
||||
auto-advance по `exit_code==0`), (2) Plane-webhook (`webhooks/plane._try_advance_stage`,
|
||||
Approved / Confirm Deploy), (3) reconciler F-1 (`advance_if_gate_passed → advance_stage`,
|
||||
`finished_agent=None`), (4) job-reaper (`job_reaper._gate_driven_advance → launcher._try_advance_stage`),
|
||||
(5) deploy-finalizer Phase C (`run_deploy_finalizer → advance_stage(finished_agent="deployer")`).
|
||||
Ни один не проверяет, не находится ли **другой** актор уже внутри того же перехода.
|
||||
|
||||
### Почему предшественники не закрыли класс
|
||||
|
||||
| Задача | Что закрыла | Что осталось открытым |
|
||||
|--------|-------------|------------------------|
|
||||
| **ORCH-110** | merge-gate re-test: ложный rollback по инфра-таймауту + tree-kill осиротевших pytest | Только merge-gate re-test; не вводит владения переходом |
|
||||
| **ORCH-112** | гигиена общего deploy-checkout (грязь блокировала `git pull`) | Только чистка артефактов; не про конкурентные переходы |
|
||||
| **ORCH-113** | reaper не пере-исполняет **живую** финализацию `deploy-staging` (Tier-2) | **process-local in-memory** реестр (`finalizer_liveness`), **только reaper**, **только Tier-2**, **только `deploy-staging`**; **теряется при рестарте**; **НЕ** покрывает reconciler / webhook / restart-recovery |
|
||||
|
||||
Таким образом, **остаточный кросс-путь** (ORCH-113 §ограничения сам это фиксирует): живой монитор
|
||||
внутри `advance_stage(deploy-staging)` — и параллельно reaper (при выключенном liveness-флаге или
|
||||
**после рестарта**, когда in-memory реестр пуст), либо reconciler F-1, либо webhook-путь — повторно
|
||||
входят в тот же переход. Результат — **двойные** эффекты (security/merge/coverage/image-freshness/
|
||||
прод-деплой) и **противоречивые** исходы (один путь откатывает на `development`, другой доводит до
|
||||
`done`). Именно это наблюдалось в инциденте ORCH-111 (job 1914 / PR #130): повторный re-test покраснел
|
||||
и дал ложный откат `deploy-staging → development` с ложным developer-retry, **одновременно** с успешной
|
||||
финализацией и мержем оригинального монитора.
|
||||
|
||||
### Особый разрез — рестарт процесса (self-hosting)
|
||||
|
||||
Прод-контейнер `orchestrator` рестартится при self-деплое. Если процесс умирает **в середине**
|
||||
финализации, in-memory `finalizer_liveness._OWNED` исчезает, `requeue_running_jobs` переводит
|
||||
`running → queued`, и переход может быть **пере-исполнен с нуля** без знания, что часть необратимых
|
||||
шагов (мерж в `main`, ratchet baseline, прод-деплой) уже применена. **Durable**-сигнал владения,
|
||||
переживающий рестарт, отсутствует — это ключевая дельта ORCH-114 над ORCH-113.
|
||||
|
||||
---
|
||||
|
||||
## 2. Объём (scope)
|
||||
|
||||
### В объёме
|
||||
- Единый **инвариант владения** side-effectful переходом/финализацией: в любой момент времени
|
||||
переход конкретной задачи исполняет **не более одного** актора.
|
||||
- **Compare-and-swap (CAS)** / transition-epoch на запись стадии: писатель применяет переход
|
||||
только если предусловие (текущая стадия / эпоха) не изменилось с момента чтения; проигравший —
|
||||
аборт **без** побочных эффектов.
|
||||
- **Durable** механизм владения (lease/heartbeat/epoch — выбор за архитектором), переживающий
|
||||
рестарт процесса.
|
||||
- Осведомлённость **job-reaper** и **startup-requeue** о живой / устаревшей финализации (обобщение
|
||||
ORCH-113 за пределы Tier-2 / `deploy-staging` / in-memory).
|
||||
- **Reconciler F-1** и **webhook**-пути: skip/defer при активном lease перехода.
|
||||
- **Умное восстановление при старте**: после смерти процесса в середине финализации система сходится
|
||||
к **единственному** согласованному исходу (без двойных необратимых эффектов и без противоречий
|
||||
rollback↔done).
|
||||
- **Наблюдаемость**: read-only блок в `GET /queue` + алерт на форсированный/устаревший реклейм lease.
|
||||
- **Регресс-тесты**: `deploy-staging`-ребро, deploy-finalizer (Phase C), restart-recovery.
|
||||
|
||||
### Вне объёма
|
||||
- Изменение состава/порядка стадий (`STAGE_TRANSITIONS`), реестра `QG_CHECKS`, семантики/имён
|
||||
`check_*`, машинных вердикт-ключей (`verdict:`/`result:`/`deploy_status:`/`staging_status:`/
|
||||
`security_status:`/`coverage_status:`) — **байт-в-байт не трогаются**.
|
||||
- Повторная починка частных симптомов ORCH-110/112 (merge-retest tree-kill, checkout-hygiene) —
|
||||
они уже закрыты; ORCH-114 их **переиспользует**, не переписывает.
|
||||
- Переход на `uvicorn --workers>1` / мульти-процессную модель (остаётся одно-процессной; durable-lease
|
||||
лишь делает инвариант корректным и на этот случай, но миграция модели — отдельная задача).
|
||||
- Выбор конкретного механизма (lease vs heartbeat vs epoch), точная форма хранения (доп. таблица vs
|
||||
доп. колонки) и порядок старта демонов — **решает архитектор** в `06-adr/` (это требования к
|
||||
свойствам, не к реализации).
|
||||
|
||||
---
|
||||
|
||||
## 3. Заинтересованные стороны
|
||||
- **Owner / оператор self-hosting** — заказчик; страдает от ложных откатов, двойных деплоев и ручного
|
||||
разбора расхождений состояния.
|
||||
- **Все проекты в общем инстансе** (orchestrator + enduro-trails) — групповой риск: расхождение
|
||||
состояния и ложный freeze репо клинят общую очередь.
|
||||
- **Принимает результат:** Owner; технически — финальная стадия конвейера (CI/гейты), не агент сам.
|
||||
|
||||
---
|
||||
|
||||
## 4. Бизнес-требования (BR)
|
||||
|
||||
| ID | Требование (проверяемое) | Покрытие |
|
||||
|----|---------------------------|----------|
|
||||
| **BR-1** | Side-effectful переход/финализацию задачи в любой момент исполняет **не более одного** актора (единое владение). | FR-1 / AC-1 |
|
||||
| **BR-2** | Запись стадии для side-effectful переходов защищена **compare-and-swap / epoch**: проигравший гонку писатель не мутирует стадию и **не выполняет** побочных эффектов. | FR-2 / AC-1, AC-2 |
|
||||
| **BR-3** | **job-reaper** осведомлён о живой vs устаревшей финализации на **всех** релевантных путях (не только Tier-2/`deploy-staging`): defer при живом владении, реклейм мёртвого/устаревшего владельца в **ограниченное** время. | FR-3 / AC-4, AC-5 |
|
||||
| **BR-4** | **Startup-requeue / восстановление при старте** учитывает незавершённую финализацию через durable-состояние: не пере-исполняет уже применённый необратимый шаг. | FR-4 / AC-6 |
|
||||
| **BR-5** | **Reconciler F-1** и **webhook**-пути продвижения **пропускают/откладывают** переход, пока активен lease владения для задачи. | FR-5 / AC-7, AC-8 |
|
||||
| **BR-6** | После смерти процесса в середине финализации система сходится к **единственному** согласованному исходу: **нет** двойного `merge_pr` / ratchet baseline / image-rebuild / инициации прод-деплоя и **нет** противоречия rollback↔done. | FR-1…FR-4 / AC-1, AC-6 |
|
||||
| **BR-7** | Состояние владения переходом **наблюдаемо**: read-only блок в `GET /queue` + алерт при форсированном/устаревшем реклейме. | FR-6 / AC-12 |
|
||||
| **BR-8** | Поставляются **регресс-тесты** на конкурентный двойной эффект (`deploy-staging`), deploy-finalizer (Phase C) и restart-recovery; обязательный регресс воспроизводит исходный класс (красный до фикса, зелёный после). | FR-7 / AC-1, AC-6, тест-план |
|
||||
| **BR-9** | Механизм **обратим**: kill-switch возвращает поведение **байт-в-байт** к состоянию до ORCH-114. | FR-7 / AC-9 |
|
||||
|
||||
---
|
||||
|
||||
## 5. Нефункциональные требования (NFR)
|
||||
|
||||
| ID | Требование |
|
||||
|----|------------|
|
||||
| **NFR-1** | **never-raise.** Любая ошибка механизма владения изолируется. Горячий путь claim/guard — **fail-open** (не заклинить общую очередь всех проектов, AC-8 ORCH-088); решения, критичные для безопасности прода/необратимости — **fail-closed**. |
|
||||
| **NFR-2** | **Kill-switch + область раската** по образцу leaf-гейтов (`serial_gate`/`coverage_gate`/`finalizer_liveness`): глобальный флаг + при необходимости CSV-скоуп репо (пусто → self-hosting only). При выключенном флаге — нулевая регрессия (enduro не затронут). |
|
||||
| **NFR-3** | **Инварианты конвейера не тронуты:** `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / машинные вердикт-ключи / схемы существующих таблиц — байт-в-байт. Любое новое хранилище — **аддитивно и идемпотентно** (`CREATE TABLE IF NOT EXISTS` / `_ensure_column`). |
|
||||
| **NFR-4** | **Durable / restart-safe.** Сигнал владения переживает рестарт процесса (ключевая дельта над in-memory `finalizer_liveness` ORCH-113); после рестарта восстановление детерминированно решает «дорешать vs уже применено». |
|
||||
| **NFR-5** | **Self-hosting безопасность.** Механизм владения сам по себе **никогда** не рестартит прод-контейнер, не пушит/force-push в `main`, не трогает detached deploy-процесс (NFR-3 ORCH-090/112). |
|
||||
| **NFR-6** | **Сквозной бюджет reaper сохранён:** инвариант ORCH-065/109/110/113 `reaper_max_running_s (5400) > Σ(deploy-staging gate-work ≈4460) + grace`. Lease **не** удлиняет финализацию за backstop без согласованной правки бюджета; устаревший/мёртвый владелец добивается Tier-3 в ограниченное время. |
|
||||
| **NFR-7** | **Идемпотентность.** Повторный заход в уже применённый переход — **no-op** (по epoch / SHA-in-main / lease), никогда не второй побочный эффект. |
|
||||
| **NFR-8** | **Обратная совместимость.** При флаге off / репо вне области — путь старта, claim и переходы байт-в-байт прежние (enduro и текущий orchestrator). |
|
||||
|
||||
---
|
||||
|
||||
## 6. Допущения и ограничения
|
||||
- **Одно-процессная модель сейчас** (один uvicorn-воркер без `--workers`), но требование NFR-4
|
||||
(durable) делает инвариант корректным и при будущем рестарте/мульти-процессности — без переписывания.
|
||||
- **Источник истины планировщика — локальная БД** (offline hot-path, NFR-2/ORCH-026/088): механизм
|
||||
владения не должен вносить сетевых зависимостей в горячий claim.
|
||||
- **Переиспользуются существующие durable-примитивы:** атомарный `reap_running_job` (rowcount-guard),
|
||||
`claim_next_job` (rowcount-guard), `requeue_running_jobs`, merge-lease (ORCH-043). ORCH-114 **достраивает**
|
||||
владение поверх них, а не дублирует.
|
||||
- **`finalizer_liveness` (ORCH-113)** — отправная точка: ORCH-114 обобщает её до durable, кросс-путевого
|
||||
владения; решение «расширить / заменить / надстроить» принимает архитектор.
|
||||
- Точные **D-решения** (durable shape, эпоха vs lease-таблица, набор покрываемых рёбер сверх
|
||||
`deploy-staging`/`deploy→done`, порядок старта демонов) — за архитектором (`06-adr/`, `10-tech-risks.md`).
|
||||
|
||||
## 7. Критерии успеха
|
||||
Кратко (детальные PASS/FAIL — `03-acceptance-criteria.md`):
|
||||
- Конкурентный/после-рестартовый повторный вход в side-effectful переход **не** даёт двойных эффектов
|
||||
и противоречивых исходов; ровно один актор владеет и доводит переход.
|
||||
- CAS/epoch на запись стадии: проигравший — чистый аборт.
|
||||
- reaper / startup / reconciler / webhook осведомлены о живом lease (defer) и о мёртвом (реклейм в
|
||||
ограниченное время).
|
||||
- Полный `pytest tests/` зелёный; новые регресс-тесты (двойной эффект, restart-recovery) зелёные;
|
||||
при выключенном kill-switch — поведение байт-в-байт прежнее.
|
||||
|
||||
## 8. Риски
|
||||
Краткий перечень (детали — `10-tech-risks.md`, заполняет архитектор):
|
||||
- **Дедлок / over-block:** слишком «жёсткое» владение может заклинить легитимный путь (reaper не
|
||||
добьёт зависший финализатор) → требование NFR-6 (bounded reclaim) и fail-open на hot-path.
|
||||
- **Бюджет vs lease:** lease, удерживаемый дольше `reaper_max_running_s`, конфликтует со сквозным
|
||||
бюджетом → согласование с ORCH-065/109/110/113.
|
||||
- **Durable-состояние и гонки на рестарте:** некорректный «умный recovery» может сам стать источником
|
||||
двойного применения → обязательный restart-recovery регресс (BR-8).
|
||||
- **Скрытые пути перехода** (gitea-webhook `handle_push`/`handle_ci_status`/`handle_pr` пишут стадию
|
||||
**в обход** `advance_stage` через прямой `update_task_stage`) → охват CAS должен учитывать и их
|
||||
(архитектор фиксирует границу).
|
||||
143
docs/work-items/ORCH-114/02-trz.md
Normal file
143
docs/work-items/ORCH-114/02-trz.md
Normal file
@@ -0,0 +1,143 @@
|
||||
---
|
||||
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 / репо вне области — нулевая регрессия.
|
||||
177
docs/work-items/ORCH-114/03-acceptance-criteria.md
Normal file
177
docs/work-items/ORCH-114/03-acceptance-criteria.md
Normal file
@@ -0,0 +1,177 @@
|
||||
---
|
||||
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
|
||||
---
|
||||
|
||||
# 03 — Критерии приёмки (Acceptance Criteria): ORCH-114 — Ownership-lease для side-effectful переходов + умное восстановление при старте
|
||||
|
||||
Work Item: **ORCH-114** · Repo: **orchestrator** · Стадия: analysis
|
||||
|
||||
Формат: каждый критерий имеет **PASS** (что должно быть истинно для приёмки) и **FAIL** (что
|
||||
считается провалом). Reviewer/CI проверяет их буквально по файлам и тестам репозитория.
|
||||
|
||||
---
|
||||
|
||||
## AC-1 — Обязательный регресс: нет двойного эффекта при конкурентном входе в переход
|
||||
|
||||
**Условие:** два актора одновременно входят в `advance_stage(deploy-staging)` для одной задачи
|
||||
(живой монитор-финализатор + второй путь: reaper / reconciler F-1 / webhook). Тест-двойники для
|
||||
`merge_pr` / coverage-ratchet / image-rebuild / deploy-init считают число вызовов.
|
||||
- **PASS:** side-effectful шаги (merge_pr, ratchet baseline, image-rebuild, инициация прод-деплоя,
|
||||
enqueue следующего агента) выполняются **ровно один раз**; персистится **ровно один** согласованный
|
||||
исход стадии; второй актор получает «lost-race»/defer и **не** выполняет побочных эффектов. Тест
|
||||
**красный до фикса, зелёный после** (воспроизводит класс инцидента ORCH-111).
|
||||
- **FAIL:** любой side-effectful шаг вызван дважды; либо два противоречивых исхода (один откатил на
|
||||
`development`, другой довёл до `done`); либо тест не воспроизводит проблему до фикса.
|
||||
|
||||
---
|
||||
|
||||
## AC-2 — Compare-and-swap на запись стадии
|
||||
|
||||
**Условие:** CAS-вариант записи стадии вызывается двумя писателями с одинаковым ожидаемым предусловием;
|
||||
первый применяется, второй приходит со «устаревшим» ожиданием.
|
||||
- **PASS:** первый writer применяет переход (rowcount=1); второй получает «lost-race» (rowcount=0),
|
||||
стадия **не** мутируется повторно; при выключенном kill-switch CAS вырождается в прежний безусловный
|
||||
`update_task_stage` (байт-в-байт).
|
||||
- **FAIL:** второй writer перезатирает стадию; либо CAS меняет семантику записи при выключенном флаге.
|
||||
|
||||
---
|
||||
|
||||
## AC-3 — Жизненный цикл владения: acquire / release / реклейм
|
||||
|
||||
**Условие:** актор начинает side-effectful финализацию.
|
||||
- **PASS:** владение захватывается на границе финализации и освобождается в `try/finally` — в т.ч.
|
||||
при исключении и при откате (rollback); durable-запись владения видна другому актору; после release
|
||||
владение реклеймится/свободно.
|
||||
- **FAIL:** владение не освобождается при исключении/откате (lease «течёт» и клинит задачу); либо не
|
||||
захватывается на границе.
|
||||
|
||||
---
|
||||
|
||||
## AC-4 — Reaper откладывает реклейм при живом владении (все пути, не только Tier-2/deploy-staging)
|
||||
|
||||
**Условие:** durable-владение активно (живой финализатор), reaper делает тик.
|
||||
- **PASS:** reaper **defer** (лог + счётчик, без повторного `advance_stage`) пока владение живо и в
|
||||
пределах бюджета; область defer обобщена за пределы Tier-2/`deploy-staging` ORCH-113 на релевантные
|
||||
пути; атомарный `reap_running_job` rowcount-guard сохранён.
|
||||
- **FAIL:** reaper повторно исполняет финализацию при живом владельце; либо defer ограничен только
|
||||
прежней узкой областью, оставляя кросс-путь открытым.
|
||||
|
||||
---
|
||||
|
||||
## AC-5 — Reaper добивает мёртвое/устаревшее владение в ограниченное время
|
||||
|
||||
**Условие:** владелец провально мёртв/завис (lease устарел), финализация не прогрессирует.
|
||||
- **PASS:** reaper реклеймит задачу в пределах Tier-3 backstop `reaper_max_running_s` (маркер владения
|
||||
backstop не обходит); задача не остаётся навсегда заклиненной; сквозной инвариант
|
||||
`reaper_max_running_s > Σ(deploy-staging gate-work) + grace` сохранён.
|
||||
- **FAIL:** мёртвое владение блокирует задачу бессрочно; либо нарушен бюджетный инвариант ORCH-065/
|
||||
109/110/113.
|
||||
|
||||
---
|
||||
|
||||
## AC-6 — Умное восстановление при рестарте процесса
|
||||
|
||||
**Условие:** процесс убит **в середине** финализации `deploy-staging`/`deploy` (in-memory состояние
|
||||
потеряно); процесс перезапущен (`requeue_running_jobs` + цикл).
|
||||
- **PASS:** восстановление через durable-владение/эпоху + авторитетные факты (SHA-in-main ORCH-071/073,
|
||||
маркер `INITIATED`) детерминированно сходится к **единственному** согласованному исходу: незавершённое
|
||||
дорешается, **уже применённый** необратимый шаг (мерж/ratchet/прод-деплой) **не** применяется повторно.
|
||||
- **FAIL:** после рестарта переход исполняется заново с двойным необратимым эффектом; либо возникает
|
||||
противоречие rollback↔done; либо задача застревает нетерминальной с удержанным lease.
|
||||
|
||||
---
|
||||
|
||||
## AC-7 — Reconciler F-1 пропускает переход при активном lease
|
||||
|
||||
**Условие:** lease перехода активен; reconciler F-1 сканирует задачу.
|
||||
- **PASS:** F-1 **defer/skip** (silent, наблюдаемо), не вызывает `advance_stage`, по образцу skip-guard'ов
|
||||
escalated/Blocked/task-deps; fail-safe: неопределённость lease → консервативный skip.
|
||||
- **FAIL:** F-1 продвигает стадию параллельно живому владельцу.
|
||||
|
||||
---
|
||||
|
||||
## AC-8 — Webhook-путь пропускает переход при активном lease
|
||||
|
||||
**Условие:** lease перехода активен; приходит Plane-webhook (Approved / Confirm Deploy) на ту же задачу.
|
||||
- **PASS:** webhook-путь продвижения **defer**, не дублирует переход/финализацию при живом владельце;
|
||||
поздний легитимный сигнал не теряется (повторно отработает после release или станет идемпотентным no-op).
|
||||
- **FAIL:** webhook повторно входит в переход параллельно владельцу и даёт двойной эффект.
|
||||
|
||||
---
|
||||
|
||||
## AC-9 — Kill-switch off → поведение байт-в-байт прежнее
|
||||
|
||||
**Условие:** новый kill-switch выключен.
|
||||
- **PASS:** lease не пишется/не читается; CAS вырождается в прежний безусловный `update_task_stage`;
|
||||
reaper/reconciler/webhook/startup ведут себя как до ORCH-114; существующий `pytest tests/` зелёный
|
||||
без правок ожиданий; enduro не затронут.
|
||||
- **FAIL:** при выключенном флаге наблюдается любое отличие от до-ORCH-114 поведения.
|
||||
|
||||
---
|
||||
|
||||
## AC-10 — never-raise + fail-open (hot-path) / fail-closed (prod-safety)
|
||||
|
||||
**Условие:** механизм владения сталкивается с ошибкой (БД-сбой/повреждённая запись lease/исключение).
|
||||
- **PASS:** ни одна публичная функция владения не роняет конвейер; горячий путь claim/guard —
|
||||
**fail-open** (общая очередь всех проектов не клинится); решения, критичные для необратимости/прода —
|
||||
**fail-closed**; на ошибке — WARNING + безопасный дефолт.
|
||||
- **FAIL:** ошибка механизма роняет claim/конвейер; либо hot-path заклинивает очередь; либо
|
||||
prod-критичное решение фейлит «открыто».
|
||||
|
||||
---
|
||||
|
||||
## AC-11 — Инварианты конвейера не тронуты
|
||||
|
||||
**Условие:** аудит диффа против `src/stages.py`, `src/qg/checks.py`, схемы БД.
|
||||
- **PASS:** `STAGE_TRANSITIONS` / реестр `QG_CHECKS` / семантика и имена `check_*` / машинные
|
||||
вердикт-ключи (`verdict:`/`result:`/`deploy_status:`/`staging_status:`/`security_status:`/
|
||||
`coverage_status:`) — **байт-в-байт**; любое новое хранилище аддитивно/идемпотентно
|
||||
(`CREATE TABLE IF NOT EXISTS` / `_ensure_column`), схемы существующих таблиц неизменны.
|
||||
- **FAIL:** изменены состав/порядок стадий, реестр/семантика гейтов, вердикт-ключи или существующие
|
||||
столбцы/индексы.
|
||||
|
||||
---
|
||||
|
||||
## AC-12 — Наблюдаемость
|
||||
|
||||
**Условие:** `GET /queue` при включённом флаге.
|
||||
- **PASS:** присутствует аддитивный read-only блок владения (держатели/возраст/defer-счётчики/реклеймы);
|
||||
существующие ключи `/queue` не сломаны; форсированный/устаревший реклейм даёт Telegram-алерт с
|
||||
кликабельным номером.
|
||||
- **FAIL:** блок отсутствует/ломает существующий вывод; реклейм происходит молча без наблюдаемости.
|
||||
|
||||
---
|
||||
|
||||
## AC-13 — Self-hosting безопасность
|
||||
|
||||
**Условие:** аудит механизма владения на побочные действия.
|
||||
- **PASS:** механизм **никогда** не рестартит прод-контейнер `orchestrator`, не пушит/force-push в
|
||||
`main`, не трогает detached deploy-процесс; деплой орка по-прежнему только через staging-гейт (8501)
|
||||
и `Confirm Deploy`.
|
||||
- **FAIL:** механизм владения инициирует рестарт прода/мутацию `main`/вмешательство в detached-деплой.
|
||||
|
||||
---
|
||||
|
||||
## Сводная матрица AC ↔ FR/BR/NFR
|
||||
|
||||
| AC | Покрывает | Тип проверки |
|
||||
|----|-----------|--------------|
|
||||
| AC-1 | BR-1, BR-6, BR-8 / FR-1, FR-2 | integration (regression, red→green) |
|
||||
| AC-2 | BR-2 / FR-2 | unit |
|
||||
| AC-3 | BR-1 / FR-1 | unit |
|
||||
| AC-4 | BR-3 / FR-3 | integration |
|
||||
| AC-5 | BR-3, NFR-6 / FR-3 | unit/integration |
|
||||
| AC-6 | BR-4, BR-6, NFR-4, NFR-7 / FR-1…FR-4 | integration (restart-recovery) |
|
||||
| AC-7 | BR-5 / FR-5 | unit/integration |
|
||||
| AC-8 | BR-5 / FR-5 | unit/integration |
|
||||
| AC-9 | BR-9, NFR-8 / FR-7 | regression (kill-switch off) |
|
||||
| AC-10 | NFR-1 / FR-7 | unit |
|
||||
| AC-11 | NFR-3 / FR-6 (negative) | structural audit |
|
||||
| AC-12 | BR-7 / FR-6 | unit/integration |
|
||||
| AC-13 | NFR-5 / FR-1 | structural audit |
|
||||
107
docs/work-items/ORCH-114/04-test-plan.yaml
Normal file
107
docs/work-items/ORCH-114/04-test-plan.yaml
Normal file
@@ -0,0 +1,107 @@
|
||||
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
|
||||
Reference in New Issue
Block a user