analyst(ET): auto-commit from analyst run_id=386
This commit is contained in:
135
docs/work-items/ORCH-026/01-brd.md
Normal file
135
docs/work-items/ORCH-026/01-brd.md
Normal 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`.
|
||||
134
docs/work-items/ORCH-026/02-trz.md
Normal file
134
docs/work-items/ORCH-026/02-trz.md
Normal 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`.
|
||||
107
docs/work-items/ORCH-026/03-acceptance-criteria.md
Normal file
107
docs/work-items/ORCH-026/03-acceptance-criteria.md
Normal 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 или регресс существующих тестов.
|
||||
169
docs/work-items/ORCH-026/04-test-plan.yaml
Normal file
169
docs/work-items/ORCH-026/04-test-plan.yaml
Normal 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
|
||||
Reference in New Issue
Block a user