analyst(ET): auto-commit from analyst run_id=386

This commit is contained in:
2026-06-08 18:32:55 +03:00
committed by stream
parent 520bcafa73
commit 518d7d18c8
4 changed files with 545 additions and 0 deletions

View File

@@ -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-<repo>.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`.

View File

@@ -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-<repo>.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`.

View File

@@ -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 или регресс существующих тестов.

View File

@@ -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