diff --git a/docs/work-items/ORCH-026/01-brd.md b/docs/work-items/ORCH-026/01-brd.md new file mode 100644 index 0000000..2a3e99a --- /dev/null +++ b/docs/work-items/ORCH-026/01-brd.md @@ -0,0 +1,135 @@ +# 01-BRD — Управление зависимостями задач (B ждёт A) в очереди + +**Work Item:** ORCH-026 +**Repo:** orchestrator (self-hosting) +**Branch:** feature/ORCH-026-b-a +**Стадия:** analysis +**Источник:** предложение Стрим, одобрено Славой (2026-06-04); дополнение Слава+Стрим 2026-06-08 (инцидент эрозии `main`) + +--- + +## 1. Контекст и проблема + +### 1.1 Первопричина (мотивация СЕЙЧАС — инцидент 08.06) +Эрозия `main` 08.06 (потеря кода ORCH-067/069, фантом-merge) родилась НЕ из логических +зависимостей, а из **некоординированного параллелизма**: несколько self-hosting задач +(ORCH-067/069/071) одновременно срезали ветки от `main` и правили общие файлы +(`CHANGELOG.md`, `notifications.py`, `config.py`). Последствия: + +- CHANGELOG-конфликты на `auto_rebase` → откаты `deploy-staging → development` (дорого: + ORCH-069 = 3 попытки = $3.98); +- тихое затирание кода соседа при merge ветки, срезанной от устаревшего `main` (фантом). + +**ORCH-073** закрыл ПОСЛЕДСТВИЯ (3 рубежа: CHANGELOG `merge=union` + SHA-in-main verify + +регресс-гард маркеров). ORCH-026 должен закрыть **ПЕРВОПРИЧИНУ**: задачи одного репо не +должны мешать друг другу в `main`. + +### 1.2 Исходный скоуп (плоская очередь ORCH-1) +Очередь (`src/queue_worker.py`, ORCH-1) — плоская: `jobs` упорядочены по `id` (FIFO), +гейтятся только `available_at` и `max_concurrency`. Нельзя выразить «задача B не стартует, +пока не готова A». Декомпозиция эпиков (ORCH-025) порождает заведомо зависимые подзадачи. + +### 1.3 Что уже есть (опора, НЕ переписывать) +- **ORCH-1** — персистентная очередь (`jobs`), atomic claim, `available_at`-defer, restart-safe. +- **ORCH-065** — `merge-lease` (`src/merge_gate.py`): per-repo файловый лиз + `.merge-lease-.json`, неблокирующий acquire, holder-aware release, проактивный + реклейм мёртвого/устаревшего держателя. **Сейчас лиз держится только на ребре + `deploy-staging → deploy`** (от merge-gate до фактического merge). +- **ORCH-043** — merge-gate: `branch_is_behind_main`, `auto_rebase_onto_main` (rebase + **только когда ветка отстаёт или при конфликте**), `retest_branch`. +- **ORCH-073** — merge-verify: `verify_merged_to_main` (SHA-in-main), `check_main_regression`. +- **Plane-статусы** `Blocked` / `Needs Input` + `set_issue_blocked` (`src/plane_sync.py`). +- **Telegram live-tracker** (`src/notifications.py`) — одна карточка на задачу, уже умеет + показывать статус `Blocked`. + +--- + +## 2. Цель (бизнес-результат) + +Задачи одного репозитория перестают повреждать `main` друг друга, а очередь умеет +выражать логические зависимости между задачами — БЕЗ потери параллелизма между разными +репозиториями и без риска для self-hosting прода. + +--- + +## 3. Два уровня требований (объединить в одной задаче; приоритет — Уровень A) + +### Уровень A — Сериализация merge/деплоя внутри ОДНОГО репо (КРИТИЧНО, корень эрозии) +Закрывает первопричину инцидента 08.06. + +- **A-1.** В рамках ОДНОГО репо merge-в-`main` + деплой должны быть **сериализованы**: пока + задача A не слита в `main` (и для self-hosting — не задеплоена), задача B того же репо НЕ + доходит до своего merge/деплоя от устаревшего `main`. +- **A-2.** B перед своим merge-gate **обязана ребейзнуться на СВЕЖИЙ `main`** (где уже есть + A) — **proactive pre-merge rebase**, а не только при текстовом конфликте (как сейчас в + ORCH-043). Цель: B всегда несёт актуальный код предшественников → структурный анти-фантом + на уровне планировщика (дополняет рубежи ORCH-073, не заменяет). +- **A-3.** Сериализация — **только внутри одного репо**. Задачи РАЗНЫХ репо (orchestrator vs + enduro-trails) параллелятся свободно (общая БД/очередь — пропускная способность не падает). +- **A-4.** Механизм — минимально-инвазивный и **restart-safe** (как ORCH-1/065): переживает + рестарт прод-контейнера, не оставляет навсегда захваченных ресурсов (опора на проактивный + реклейм ORCH-065). +- **A-5.** **Совместимость с self-hosting safety:** не ронять/не рестартить прод-контейнер + вне штатного deploy; гейт `Confirm Deploy` (ORCH-059) сохранён; никаких push/force-push в + `main`. +- **A-6.** Защита от взаимоблокировки: B при занятой сериализации **defer** (повторная + постановка с задержкой через `available_at`), а НЕ откат на `development` и НЕ вечное + ожидание; bounded defer-бюджет (анти-livelock, как `merge_defer_max_attempts`). + +### Уровень B — Декларативные зависимости (исходный скоуп ORCH-26) +- **B-1.** Задача может объявить связь `blocked-by` / `blocks` (depends-on). +- **B-2.** Планировщик очереди (ORCH-1) **не запускает** заблокированную задачу, пока все её + depends-on не достигли терминального состояния (`done`). +- **B-3.** **Защита от дедлоков:** циклические зависимости детектируются; задача в цикле не + «пропадает молча» — выставляется `Blocked` + alert (Telegram/Plane). +- **B-4.** **Видимость:** заблокированная задача видна — Plane-статус `Blocked` и/или + ожидание в Telegram-карточке (что и кого ждёт). + +--- + +## 4. Открытые вопросы для архитектора (НЕ решаются на этапе анализа) + +> Аналитик фиксирует требования; выбор механизма — за архитектором (ADR в `06-adr/`). + +1. **Где хранить связи (Уровень B):** Plane relations (родное, видимо в UI, но требует + сетевого запроса и зависит от Plane) vs таблица в БД (`job_deps`/поля `tasks`, надёжно и + offline, но дубль источника) vs **гибрид** (Plane — источник декларации, БД — кэш для + планировщика). Рекомендация анализа: гибрид с offline-fallback (см. §6). +2. **Механизм сериализации (Уровень A):** глобальный per-repo merge-lock vs FIFO merge-queue + vs **обязательный pre-merge rebase + расширение окна merge-lease** (от «момента merge» до + «main-updated»). Выбрать минимально-инвазивный, restart-safe, переиспользующий ORCH-065/043. +3. **Граница окна сериализации для self-hosting:** для не-self репо «merged в main» = конец + окна; для self (orchestrator) деплой асинхронный (Phase B/C, ORCH-036/071) — нужно решить, + до какого события держать лиз (до `merged_to_main: true` / до `done`). +4. **Совместимость B и A:** depends-on (B) на уровне постановки в очередь vs merge-сериализация + (A) на уровне merge-gate — разные точки конвейера; убедиться, что не конфликтуют. + +--- + +## 5. Вне скоупа (Non-goals) +- Изменение машины стадий `STAGE_TRANSITIONS` (сериализация/зависимости — врезки/гейты, не + новые стадии — паттерн ORCH-043/058/071). +- Приоритизация/перепланирование задач по весам (только зависимости и сериализация). +- Кросс-репо зависимости (A-3 явно запрещает кросс-репо сериализацию; кросс-репо логические + зависимости — возможный follow-up, не v1). +- Отмена/замена рубежей ORCH-073 — ORCH-026 их **дополняет** на уровне планировщика. + +--- + +## 6. Заинтересованные стороны +- **Owner (Слава)** — одобряет BRD; держатель self-hosting прод-риска. +- **Стрим** — автор предложения. +- **Конвейер агентов** — потребитель: developer/deployer работают с веткой, которую затрагивает + сериализация; reviewer проверяет обновление доки. + +--- + +## 7. Критерии успеха (бизнес-уровень) +- Две зелёные задачи одного репо больше не способны затереть код друг друга в `main` на уровне + планировщика (без участия рубежей-последствий ORCH-073). +- Задача может объявить зависимость; заблокированная задача не стартует раньше времени и видна + наблюдателю. +- Пропускная способность разных репо не деградирует. +- Прод-контейнер orchestrator не падает и не рестартится вне штатного `Confirm Deploy`. + +Точные PASS/FAIL — `03-acceptance-criteria.md`. diff --git a/docs/work-items/ORCH-026/02-trz.md b/docs/work-items/ORCH-026/02-trz.md new file mode 100644 index 0000000..8ca41e3 --- /dev/null +++ b/docs/work-items/ORCH-026/02-trz.md @@ -0,0 +1,134 @@ +# 02-ТЗ — Управление зависимостями задач (B ждёт A) в очереди + +**Work Item:** ORCH-026 · **Repo:** orchestrator · **Стадия:** analysis + +> ТЗ фиксирует ТРЕБОВАНИЯ к изменениям (модули, контракты, артефакты). Конкретный механизм +> сериализации и место хранения связей — решение архитектора (ADR в `06-adr/`); ниже отмечены +> как «КАНДИДАТ / решает архитектор». Аналитик не предлагает архитектуру. + +--- + +## 1. Задействованные модули `src/` + +| Модуль | Роль в задаче | Уровень | +|--------|---------------|---------| +| `src/queue_worker.py` | Планировщик: `_drain_once` / `claim_next_job` — точка учёта зависимостей и сериализации при выборе job. | A + B | +| `src/db.py` | Очередь `jobs` / `tasks`; `claim_next_job`, `enqueue_job`, `count_running_jobs`. Кандидат на хранение связей и блокировки claim. | A + B | +| `src/merge_gate.py` | merge-lease (ORCH-065), `branch_is_behind_main` / `auto_rebase_onto_main` (ORCH-043) — опора для proactive pre-merge rebase и расширения окна сериализации. | A | +| `src/qg/checks.py` | `check_branch_mergeable` (под-гейт ребра `deploy-staging → deploy`) — точка форсированного pre-merge rebase. | A | +| `src/stage_engine.py` | `advance_stage` — врезки гейтов; точка интеграции сериализации/верификации. | A | +| `src/webhooks/plane.py` | `handle_work_item_created` / `start_pipeline` — приём задачи; точка чтения relations (если источник — Plane). | B | +| `src/plane_sync.py` | `set_issue_blocked`, `get_project_states` (`blocked`/`needs_input`), relations API. | B | +| `src/notifications.py` | live-карточка: индикация `Blocked` / «ждёт ORCH-NNN». | B | +| `src/config.py` | Новые kill-switch + scope-настройки (паттерн `*_enabled` / `*_repos`). | A + B | +| `src/reconciler.py` / `src/job_reaper.py` | Не ломать: skip заблокированных задач (как уже делается для Blocked/Needs-Input, ORCH-060/068); реклейм ресурсов сериализации. | A + B | + +--- + +## 2. Требования к изменениям — Уровень A (сериализация merge/деплоя) + +### 2.1 Proactive pre-merge rebase (A-2) +- На ребре `deploy-staging → deploy`, ДО фактического merge (в составе `check_branch_mergeable` + или соседнего под-гейта), ветка задачи **всегда** догоняется на свежий `origin/main` — + **не только при `branch_is_behind_main`/конфликте**. +- Переиспользовать `merge_gate.auto_rebase_onto_main` (rebase + `push --force-with-lease` + ТОЛЬКО ветки задачи). Текстовый конфликт → существующий контракт: `rebase --abort` → откат на + `development` (как ORCH-043). +- **Инвариант:** никаких push/force-push в `main`. + +### 2.2 Расширение окна merge-lease (A-1, A-3, A-4) +- **КАНДИДАТ (решает архитектор):** держать per-repo merge-lease (ORCH-065) не только «на + момент merge», а на окно **«merge → main-updated»** (для self — до подтверждения + `merged_to_main: true` / `done`), чтобы B не дошла до своего merge, пока A не в `main`. +- Acquire — **неблокирующий** (как сейчас): занято → **defer** задачи B через + `enqueue_job(available_at_delay_s=...)`, bounded бюджет (анти-livelock; ср. + `merge_defer_max_attempts`). Откат на `development` НЕ применять для defer. +- Release — holder-aware (как `release_merge_lease`), на merged-вебхуке / `deploy→done` / + откате / по проактивному реклейму (ORCH-065 `reclaim_stale_lease`). +- Сериализация **строго per-repo** (`.merge-lease-.json`) — кросс-репо параллелизм не + затрагивается (A-3). + +### 2.3 Условность и безопасность (A-5) +- Реально только для применимых репо: kill-switch + CSV-scope (паттерн `merge_gate_repos` / + `merge_verify_repos`; пусто → только self-hosting `orchestrator`). +- `STAGE_TRANSITIONS`, `Confirm Deploy` (ORCH-059), exit-коды deploy-хука, БАГ-8, + terminal-sync — **без изменений**. +- Контракт **never-raise** для всех новых функций (как соседи в `merge_gate.py`). + +--- + +## 3. Требования к изменениям — Уровень B (декларативные зависимости) + +### 3.1 Декларация связи (B-1) +- **КАНДИДАТ хранения (решает архитектор, см. BRD §4.1):** + - вариант Plane relations: читать `blocked-by` через Plane API в `handle_work_item_created`; + - вариант БД: новая таблица `job_deps(task_id, depends_on_task_id)` или поле в `tasks` + (idempotent `_ensure_column` миграция, как ORCH-065 `jobs.pid`); + - гибрид: Plane — декларация, БД — кэш для планировщика (offline-устойчивость). +- Миграция БД (если выбран вариант с таблицей/колонкой) — **только аддитивная** + (`CREATE TABLE IF NOT EXISTS` / `_ensure_column`), безопасная на живой прод-БД с общими + данными enduro-trails. + +### 3.2 Гейт планировщика (B-2) +- При выборе job (`claim_next_job` / `_drain_once`) задача с незавершёнными depends-on + **не клеймится** (аналог `available_at`-gate): пропускается до тех пор, пока все depends-on + не `done`. Не должна занимать слот `max_concurrency`. +- Реализация — **leaf-функция** с чистой логикой «готова ли задача к запуску» (тестируемо + юнитами, never-raise), по образцу `staging_verdict.py` / `post_deploy.py`. + +### 3.3 Защита от дедлоков (B-3) +- Детектор циклов в графе depends-on (DFS/обнаружение цикла) — чистая функция, юнит-тестируемая. +- Цикл → задача(и) НЕ запускается молча: `set_issue_blocked` + alert (Telegram/Plane) с + указанием цикла. Не блокировать поток других задач. + +### 3.4 Видимость (B-4) +- Заблокированная задача: Plane-статус `Blocked` (`set_issue_blocked`) и/или строка ожидания в + Telegram-карточке («⏳ ждёт ORCH-NNN»). Использовать существующий механизм карточки + (`notifications.update_task_tracker`), контракт never-raise / silent. +- `reconciler` F-1 уже пропускает Blocked/Needs-Input (ORCH-060/068) — убедиться, что новые + заблокированные-по-зависимости задачи тоже пропускаются (не «разблокируются» ошибочно). + +--- + +## 4. Изменения API (endpoints) +- **Новые HTTP endpoints не требуются.** +- **Наблюдаемость:** расширить снимок `GET /queue` блоком о зависимостях/сериализации + (по образцу блоков `reconcile` / `reaper` / `post_deploy` / `merge_verify`): кол-во + заблокированных задач, держатель merge-lease, defer-счётчики, обнаруженные циклы. Read-only, + никогда не источник истины для решений. + +## 5. Изменения схемы БД +- **КАНДИДАТ (если выбран БД/гибрид для Уровня B):** аддитивная таблица `job_deps` или колонка + в `tasks` (см. §3.1). Только `CREATE TABLE IF NOT EXISTS` / `_ensure_column`. Без изменения + существующих колонок `jobs`/`tasks`. Restart-safe, безопасно на общей прод-БД. +- Уровень A (сериализация) — **без изменения схемы БД** (merge-lease файловый, как ORCH-065). + +## 6. Требования к новым QG checks +- **Новый зарегистрированный QG-чек НЕ вводится** (паттерн ORCH-071/058: под-гейт — врезка в + `advance_stage` или расширение `check_branch_mergeable`, а не новая запись в `QG_CHECKS`). +- Реестр `QG_CHECKS` — без изменений. + +## 7. Конфигурация (`src/config.py`) +Новые настройки по паттерну `*_enabled` (kill-switch) + `*_repos` (CSV scope, пусто → +self-hosting). КАНДИДАТ-имена (финализирует архитектор): +- Уровень A: `merge_serialize_enabled` / `merge_serialize_repos` (или расширение + `merge_gate_*`); опционально `premerge_rebase_always` (вкл proactive rebase). +- Уровень B: `task_deps_enabled` / `task_deps_source` (`plane|db|hybrid`). +Дефолты — обратная совместимость (для не-self репо — прежнее поведение). + +## 8. Артефакты pipeline (создать/обновить В ТОМ ЖЕ PR) +- `06-adr/ADR-001-*.md` — решение по сериализации (A) и хранению зависимостей (B). +- Обновить `docs/architecture/README.md` (раздел про очередь/merge-gate/сериализацию). +- Обновить `CLAUDE.md` (паспорт: конвейер/инварианты, если меняется поведение очереди). +- Обновить `CHANGELOG.md` (`## [Unreleased]`). +- Если вводится таблица БД — отразить в `08-data-requirements.md` (создаёт архитектор). +- `07-infra-requirements.md` — если требуется новый Plane-статус/настройка relations. + +## 9. Инварианты (НЕ нарушать) +1. `STAGE_TRANSITIONS`, реестр `QG_CHECKS`, `check_deploy_status`/`check_staging_status`, + `Confirm Deploy` (ORCH-059), БАГ-8, terminal-sync — без изменений. +2. Никаких push/force-push в `main`; force только `--force-with-lease` на ветку задачи. +3. Сериализация — строго per-repo; кросс-репо параллелизм сохранён. +4. never-raise во всех новых функциях; restart-safe состояние. +5. ORCH-026 дополняет рубежи ORCH-073, не заменяет. +6. Прод-контейнер orchestrator не рестартится вне штатного `Confirm Deploy`. diff --git a/docs/work-items/ORCH-026/03-acceptance-criteria.md b/docs/work-items/ORCH-026/03-acceptance-criteria.md new file mode 100644 index 0000000..7acda59 --- /dev/null +++ b/docs/work-items/ORCH-026/03-acceptance-criteria.md @@ -0,0 +1,107 @@ +# 03-Критерии приёмки — ORCH-026 + +**Work Item:** ORCH-026 · **Repo:** orchestrator · **Стадия:** analysis + +Каждый критерий — проверяемое условие PASS/FAIL. Маппинг на тесты — `04-test-plan.yaml`. + +--- + +## Уровень A — Сериализация merge/деплоя внутри одного репо + +### AC-A1 — Сериализация merge внутри репо +- **PASS:** пока задача A применимого репо удерживает окно merge (merge-lease не освобождён / + `main` ещё не обновлён), задача B того же репо НЕ доходит до фактического merge — она + **defer**-ится (повторная постановка через `available_at`), а не мержится от устаревшего `main`. +- **FAIL:** B мержится/деплоится, пока A не в `main`; или B откатывается на `development` вместо + defer. + +### AC-A2 — Proactive pre-merge rebase +- **PASS:** перед merge ветка задачи **всегда** догоняется на свежий `origin/main` (вызывается + rebase), даже когда текстового конфликта нет и ветка формально не «behind» по старой проверке; + после rebase ветка содержит код предшественника (A). +- **FAIL:** rebase запускается только при конфликте/`branch_is_behind_main`, и B мержится без + кода A. + +### AC-A3 — Кросс-репо параллелизм сохранён +- **PASS:** задача в `orchestrator` и задача в `enduro-trails` доходят до merge/деплоя + параллельно — сериализация одного репо не блокирует другой (lease/гейт строго per-repo). +- **FAIL:** задача одного репо ждёт освобождения ресурса, удерживаемого задачей ДРУГОГО репо. + +### AC-A4 — Restart-safe +- **PASS:** после рестарта прод-контейнера состояние сериализации восстанавливается корректно; + мёртвый держатель merge-lease проактивно реклеймится (ORCH-065), конвейер не встаёт навсегда. +- **FAIL:** рестарт оставляет навсегда захваченный lease → конвейер всех проектов встаёт. + +### AC-A5 — Self-hosting safety +- **PASS:** прод-контейнер orchestrator НЕ рестартится/не падает вне штатного `Confirm Deploy` + (ORCH-059); нет push/force-push в `main`; `STAGE_TRANSITIONS` и реестр `QG_CHECKS` не изменены. +- **FAIL:** любой незапрошенный рестарт прода, прямой push в `main`, или изменение машины стадий. + +### AC-A6 — Anti-deadlock / anti-livelock при defer +- **PASS:** при занятой сериализации B defer-ится с задержкой и bounded бюджетом; исчерпание + бюджета → эскалация (alert/Blocked), не бесконечный цикл и не откат. +- **FAIL:** B уходит в вечный defer-цикл, либо немедленно откатывается на `development`. + +### AC-A7 — Условность (не-self репо без регресса) +- **PASS:** при выключенном kill-switch и для репо вне scope поведение конвейера 1:1 как до + ORCH-026 (нулевая регрессия для enduro-trails). +- **FAIL:** не-self репо меняет поведение merge/деплоя. + +--- + +## Уровень B — Декларативные зависимости + +### AC-B1 — Декларация зависимости +- **PASS:** задача может объявить `blocked-by`/`depends-on` (через выбранный источник — + Plane relations / БД / гибрid), и связь корректно считывается планировщиком. +- **FAIL:** связь не считывается / теряется. + +### AC-B2 — Гейт планировщика (B не стартует до A) +- **PASS:** задача с незавершённым depends-on **не клеймится** воркером (не запускается агент, + слот `max_concurrency` не занимается), пока все depends-on не достигли `done`; как только A + становится `done` — B становится claimable. +- **FAIL:** B запускается раньше завершения A; или занимает слот, простаивая. + +### AC-B3 — Детект дедлоков (циклы) +- **PASS:** циклическая зависимость (A→B→A и длиннее) детектируется детерминированно; задача(и) + в цикле → `Blocked` + alert (Telegram/Plane) с указанием цикла; поток остальных задач не + блокируется. +- **FAIL:** цикл приводит к молчаливому вечному ожиданию или к падению воркера. + +### AC-B4 — Видимость заблокированной задачи +- **PASS:** заблокированная задача видна — Plane-статус `Blocked` и/или строка ожидания в + Telegram-карточке (что/кого ждёт); инвариант «одна карточка на задачу» сохранён. +- **FAIL:** заблокированная задача невидима наблюдателю. + +### AC-B5 — Совместимость с reconciler/reaper +- **PASS:** `reconciler` F-1 НЕ «разблокирует» задачу, заблокированную по зависимости (как уже + делает для Blocked/Needs-Input, ORCH-060/068); reaper не реапит корректно ожидающую задачу. +- **FAIL:** reconciler продвигает заблокированную задачу мимо её depends-on. + +--- + +## Общие (оба уровня) + +### AC-G1 — never-raise +- **PASS:** любая ошибка (git/сеть/БД/Plane) в новой логике не пробрасывается в `advance_stage`/ + воркер; деградирует консервативно (defer/skip/fail-closed), конвейер не падает. +- **FAIL:** необработанное исключение роняет воркер/монитор-поток. + +### AC-G2 — Kill-switch +- **PASS:** глобальный kill-switch выключает фичу целиком → поведение 1:1 как до ORCH-026. +- **FAIL:** при выключенном флаге поведение изменено. + +### AC-G3 — Документация обновлена (golden source) +- **PASS:** в ТОМ ЖЕ PR обновлены `docs/architecture/README.md`, `CLAUDE.md` (если изменилось + поведение очереди), `CHANGELOG.md`, заведён ADR в `06-adr/`. Reviewer проверяет. +- **FAIL:** код изменён, документация — нет (→ REQUEST_CHANGES). + +### AC-G4 — Миграция БД безопасна (если применимо) +- **PASS:** миграция только аддитивная (`CREATE TABLE IF NOT EXISTS`/`_ensure_column`), + идемпотентна, безопасна на живой общей прод-БД; существующие данные enduro-trails не затронуты. +- **FAIL:** деструктивная миграция / изменение существующих колонок. + +### AC-G5 — Тесты зелёные +- **PASS:** новые unit+integration тесты (`04-test-plan.yaml`) проходят; существующий + `pytest tests/ -q` остаётся зелёным (нет регресса merge-gate/merge-verify/reconciler/reaper). +- **FAIL:** красный pytest или регресс существующих тестов. diff --git a/docs/work-items/ORCH-026/04-test-plan.yaml b/docs/work-items/ORCH-026/04-test-plan.yaml new file mode 100644 index 0000000..9da9e73 --- /dev/null +++ b/docs/work-items/ORCH-026/04-test-plan.yaml @@ -0,0 +1,169 @@ +work_item: ORCH-026 +description: > + План тестов для управления зависимостями задач (Уровень B) и сериализации + merge/деплоя внутри одного репо (Уровень A). Стек: pytest. Имена модулей/функций — + кандидаты; финализирует архитектор/разработчик. Все новые функции — never-raise. + +tests: + # ---------------- Уровень A: сериализация merge/деплоя ---------------- + - id: TC-A01 + type: unit + description: > + Proactive pre-merge rebase: ветка догоняется на свежий origin/main ДАЖЕ когда + branch_is_behind_main вернул бы False (нет конфликта). Проверить, что rebase + вызывается всегда перед merge (AC-A2). + module: tests/test_orch026_premerge_rebase.py + expected: PASS + + - id: TC-A02 + type: unit + description: > + Расширенное окно merge-lease: пока A держит lease (окно merge→main-updated), + acquire для B того же репо возвращает busy → defer (не откат). holder-aware + release не удаляет чужой lease (AC-A1, AC-A6). + module: tests/test_orch026_merge_serialize.py + expected: PASS + + - id: TC-A03 + type: unit + description: > + Сериализация строго per-repo: lease/гейт orchestrator не влияет на задачу + enduro-trails — обе claimable параллельно (AC-A3). + module: tests/test_orch026_merge_serialize.py + expected: PASS + + - id: TC-A04 + type: unit + description: > + Restart-safe + проактивный реклейм: мёртвый держатель lease (pid не жив) + реклеймится reclaim_stale_lease; конвейер не встаёт навсегда (AC-A4). + module: tests/test_orch026_merge_serialize.py + expected: PASS + + - id: TC-A05 + type: unit + description: > + Anti-livelock defer: B defer-ится с available_at-задержкой и bounded бюджетом; + исчерпание → эскалация (Blocked/alert), не бесконечный цикл (AC-A6). + module: tests/test_orch026_merge_serialize.py + expected: PASS + + - id: TC-A06 + type: unit + description: > + Условность/kill-switch: при выключенном флаге и для репо вне scope поведение + merge/деплоя 1:1 как до ORCH-026 — no-op (AC-A7, AC-G2). + module: tests/test_orch026_conditionality.py + expected: PASS + + - id: TC-A07 + type: unit + description: > + Self-hosting safety: новая логика никогда не делает push/force-push в main; + force только --force-with-lease на ветку задачи; STAGE_TRANSITIONS не изменены + (AC-A5). + module: tests/test_orch026_conditionality.py + expected: PASS + + - id: TC-A08 + type: integration + description: > + Сквозной сценарий: две задачи одного репо проходят deploy-staging→deploy; B не + доходит до merge, пока A не в main; после A→done B ребейзится на свежий main + (несёт код A) и мержится. main не теряет код A (AC-A1/AC-A2). + module: tests/test_orch026_serialize_integration.py + expected: PASS + + # ---------------- Уровень B: декларативные зависимости ---------------- + - id: TC-B01 + type: unit + description: > + Чтение/декларация связи blocked-by из выбранного источника (Plane/БД/гибрид); + связь корректно резолвится в depends_on_task_id (AC-B1). never-raise при + недоступности источника → консервативно (нет связи или fail-closed по решению ADR). + module: tests/test_orch026_task_deps.py + expected: PASS + + - id: TC-B02 + type: unit + description: > + Гейт готовности (leaf-функция): задача с незавершённым depends-on НЕ ready; + все depends-on в done → ready. Чистая логика, юнит-тестируемая (AC-B2). + module: tests/test_orch026_task_deps.py + expected: PASS + + - id: TC-B03 + type: unit + description: > + Детект циклов: A→B→A (и длиннее) детектируется детерминированно; ацикличный + граф → циклов нет. Чистая функция (AC-B3). + module: tests/test_orch026_dep_cycles.py + expected: PASS + + - id: TC-B04 + type: unit + description: > + Цикл → set_issue_blocked + alert (Telegram/Plane), без падения воркера и без + блокировки потока других задач (AC-B3, AC-G1). + module: tests/test_orch026_dep_cycles.py + expected: PASS + + - id: TC-B05 + type: unit + description: > + claim_next_job не клеймит заблокированную задачу (не занимает слот + max_concurrency); как только depends-on done — задача становится claimable (AC-B2). + module: tests/test_orch026_task_deps.py + expected: PASS + + - id: TC-B06 + type: unit + description: > + Видимость: заблокированная задача отражается в Plane-статусе Blocked и/или + строке ожидания Telegram-карточки; инвариант «одна карточка на задачу» сохранён + (AC-B4). notifications never-raise / silent. + module: tests/test_orch026_dep_visibility.py + expected: PASS + + - id: TC-B07 + type: unit + description: > + reconciler F-1 НЕ разблокирует задачу, заблокированную по зависимости (как для + Blocked/Needs-Input); reaper не реапит корректно ожидающую (AC-B5). + module: tests/test_orch026_task_deps.py + expected: PASS + + - id: TC-B08 + type: integration + description: > + Сквозной сценарий: B объявлена blocked-by A; при постановке в очередь B не + стартует, пока A не done; после A→done воркер запускает B. Telegram/Plane + показывают Blocked у B до разблокировки (AC-B1/B2/B4). + module: tests/test_orch026_deps_integration.py + expected: PASS + + # ---------------- Общие / миграция / регресс ---------------- + - id: TC-G01 + type: unit + description: > + Аддитивная миграция БД (если выбран вариант с таблицей/колонкой): идемпотентна, + безопасна на существующей БД с данными, не меняет существующие колонки (AC-G4). + module: tests/test_orch026_migration.py + expected: PASS + + - id: TC-G02 + type: unit + description: > + Наблюдаемость GET /queue: новый блок (заблокированные задачи / держатель lease / + defer-счётчики / циклы) присутствует и read-only; не источник истины. + module: tests/test_orch026_queue_observability.py + expected: PASS + + - id: TC-G03 + type: integration + description: > + Регресс: полный pytest tests/ -q остаётся зелёным — merge-gate (ORCH-043), + merge-verify (ORCH-073), reconciler (ORCH-053/068), reaper (ORCH-065) не + деградировали (AC-G5). + module: tests/ + expected: PASS