analyst(ET): auto-commit from analyst run_id=763
All checks were successful
CI / test (push) Successful in 1m9s
All checks were successful
CI / test (push) Successful in 1m9s
This commit is contained in:
185
docs/work-items/ORCH-124/01-brd.md
Normal file
185
docs/work-items/ORCH-124/01-brd.md
Normal file
@@ -0,0 +1,185 @@
|
||||
---
|
||||
work_item: ORCH-124
|
||||
stage: analysis
|
||||
author_agent: analyst
|
||||
status: ready-for-review
|
||||
created_at: 2026-06-16
|
||||
model_used: claude-opus-4-8
|
||||
escalate: full-cycle
|
||||
---
|
||||
|
||||
# 01 — BRD / Bug-report: ORCH-124 — serial gate treats Backlog/Blocked/Needs-Input paused tasks as active and blocks urgent successors
|
||||
|
||||
Work Item: **ORCH-124** · Repo: **orchestrator** · Стадия: analysis · Трек: **Bug → эскалация в full-cycle**
|
||||
|
||||
> ⚠️ **`escalate: full-cycle` (ADR-001 D5 ORCH-019).** Метка задачи — `Bug`, но по сути это
|
||||
> **архитектурный** дефект: требуется **определить семантику wait/terminal состояний serial-gate**
|
||||
> и выбрать механизм «пауза без блокировки» (release-on-status / явный per-task hold-флаг /
|
||||
> переиспользование `task_deps`). Любой вариант пересекается с **корневым инвариантом ORCH-088
|
||||
> (анти-stale-base)** и с гармонизированным терминальным предикатом ORCH-090 (`adr-0026`,
|
||||
> `serial_gate` + `task_deps` + `stages.py`). Это не «однострочная» правка — нужен ADR с явным
|
||||
> разрешением конфликта свойств (см. §8 и `10-tech-risks.md` архитектора). Поэтому выпускается
|
||||
> **полный** analysis-пакет (а не облегчённый bug-пакет). Оператор снимает багфикс-трек:
|
||||
> `POST /bug-fast-track/escalate?work_item=ORCH-124` → задача пойдёт через стадию `architecture`
|
||||
> (architect выпустит ADR для семантики паузы serial-gate).
|
||||
|
||||
---
|
||||
|
||||
## 1. Бизнес-контекст и проблема
|
||||
|
||||
### Симптом (наблюдаемое — установленный факт инцидента)
|
||||
Во время инцидента **ORCH-116/ORCH-123**: задачу **ORCH-116** намеренно поставили на паузу
|
||||
(перевели в Plane-статус Blocked/Backlog), чтобы вперёд пропустить срочный фикс **ORCH-123**.
|
||||
Однако `serial_gate` **по-прежнему считал ORCH-116 активной задачей** (`active_task`) и держал
|
||||
analyst-job ORCH-123 в очереди (`queued`) — срочный фикс не мог стартовать, пока ORCH-116
|
||||
формально не `done`/`cancelled`.
|
||||
|
||||
### Причина симптома (установленный факт — верифицировано по коду)
|
||||
`serial_gate` определяет «активную задачу репо» **исключительно по машинной стадии**
|
||||
`tasks.stage NOT IN ('done','cancelled')` — в трёх местах `src/serial_gate.py`:
|
||||
- `build_claim_clause()` — горячий SQL-фрагмент в `db.claim_next_job`:
|
||||
`EXISTS (SELECT 1 FROM tasks t2 WHERE t2.repo = jobs.repo AND t2.id < jobs.task_id AND
|
||||
t2.stage NOT IN ('done','cancelled'))`;
|
||||
- `repo_has_active_task()` — Python-зеркало для наблюдаемости;
|
||||
- `_per_repo_snapshot()` — выбор `active_task` для блока `serial_gate` в `GET /queue`.
|
||||
|
||||
При этом **Plane-статусы Backlog / Blocked / Needs Input — это слой B (индикация), ORCH-066**, и они
|
||||
**не меняют `tasks.stage` (слой A)**. Сеттеры `set_issue_blocked` / `set_issue_needs_input`
|
||||
(`src/plane_sync.py`) делают только `PATCH` Plane-статуса; машинная стадия задачи остаётся прежней
|
||||
(`analysis` / `development` / `deploy-staging` …). Подтверждение из кода: у таблицы `tasks` **нет
|
||||
колонки статуса** (комментарий `src/reconciler.py:322`: «`tasks` has no status column, so the live
|
||||
Plane state is the source of truth»). Следовательно для serial-gate приостановленная задача неотличима
|
||||
от активно исполняемой — её стадия не входит в `{done,cancelled}`, значит она «активна» и блокирует
|
||||
FIFO всех более поздних analyst-job того же репо.
|
||||
|
||||
### Почему это важно (бизнес-боль)
|
||||
- **Срочный фикс не запускается**, пока более ранняя задача поставлена на паузу. Единственные
|
||||
существующие способы «разблокировать» — терминально `cancel`/довести до `done`, либо целиком
|
||||
выключить serial-gate (`ORCH_SERIAL_GATE_ENABLED=false`) для всех репо. Все три — грубые.
|
||||
- У оператора **нет чистого механизма «пауза без блокировки»** с явным намерением — отдельного от
|
||||
отмены (терминал) и от глобального выключения гейта.
|
||||
- На пакетном автономном прогоне (эпик ORCH-088) это превращает любую «отложенную» задачу в стоп-кран
|
||||
очереди репо.
|
||||
|
||||
### Прецедент в коде (контекст для архитектора, не решение)
|
||||
Reconciler уже **умеет** уважать wait-состояния: ORCH-060 Guard 2 (`reconciler._is_blocked_or_needs_input`,
|
||||
Variant A) **сетевым** запросом Plane-статуса пропускает Blocked/Needs-Input (и активные
|
||||
ORCH-066-ожидания) и не «оживляет» их. Но reconciler — фоновый тик и **может** позволить себе сетевой
|
||||
вызов; `serial_gate.build_claim_clause` врезан в `claim_next_job` (**offline hot-path**) и сетевого
|
||||
вызова позволить **не может** (NFR-2 ниже). Это центральное расхождение, которое и порождает баг:
|
||||
сигнал паузы есть в Plane, но не доступен горячему SQL гейта.
|
||||
|
||||
## 2. Объём (scope)
|
||||
|
||||
### В объёме
|
||||
- Определить **семантику wait/terminal** для serial-gate: какие состояния задачи-предшественника
|
||||
НЕ должны держать FIFO-гейт закрытым для более поздней задачи.
|
||||
- Дать оператору **явный, durable, DB-резолвимый** механизм «пауза без блокировки» (или формально
|
||||
переиспользовать существующий: freeze / task_deps), с чётким намерением, отличным от cancel.
|
||||
- Поправить определение «активной задачи» во **всех трёх** точках `serial_gate.py`, чтобы
|
||||
приостановленная задача не считалась `active_task`.
|
||||
- Корректная **причина ожидания** в блоке `serial_gate` снапшота `GET /queue`
|
||||
(active task / paused-predecessor / freeze / dependency).
|
||||
- Тесты: предшественник Blocked/Backlog/Needs-Input + срочный успешник; регресс-тест инцидента.
|
||||
|
||||
### Вне объёма
|
||||
- Изменения `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict ключей — **не трогаем**
|
||||
(маршрутизация очереди — свойство планировщика, не Quality Gate).
|
||||
- Введение нового **машинного** статуса в `STAGE_TRANSITIONS` (это не новая стадия конвейера).
|
||||
- Изменение поведения reconciler ORCH-060 (его networked-skip уже корректен; гармонизация — на
|
||||
усмотрение архитектора, но переписывать его не требуется).
|
||||
- Автоматическое управление паузой по данным вне явного намерения оператора (никакого эвристического
|
||||
«само-распаузивания»).
|
||||
- Конкретный **выбор механизма** (release-on-status vs per-task hold-флаг vs task_deps) — это решение
|
||||
**архитектора** (ADR), а не аналитика.
|
||||
|
||||
## 3. Заинтересованные стороны
|
||||
- **Оператор/владелец конвейера (Слава)** — заказчик: нуждается в чистой паузе, чтобы пропускать
|
||||
срочные фиксы без отмены и без выключения гейта.
|
||||
- **Self-hosting orchestrator** — затрагивается напрямую (serial-gate активен для всех репо).
|
||||
- **enduro-trails** — затрагивается косвенно (общая БД/очередь); регрессия недопустима при
|
||||
выключенном/нейтральном поведении.
|
||||
- **Архитектор** — принимает решение о механизме и семантике (ADR), разрешает конфликт §8.
|
||||
- Принимает результат — reviewer + tester по критериям `03-acceptance-criteria.md`.
|
||||
|
||||
## 4. Бизнес-требования (BR)
|
||||
- **BR-1** — Перевод задачи-предшественника в состояние паузы/ожидания (Backlog / Blocked /
|
||||
Needs Input) **больше не должен случайно блокировать** более позднюю срочную задачу того же репо в
|
||||
serial-gate. Проверяемо: analyst-job успешника становится claimable.
|
||||
- **BR-2** — У оператора есть **чистый механизм «пауза без блокировки»** с явным намерением,
|
||||
**отличный** от `cancel` (терминал) и от глобального выключения гейта. Намерение — durable
|
||||
(переживает рестарт процесса/контейнера).
|
||||
- **BR-3** — Пауза снимает гейт **только по явному намерению**. **Нормально исполняемая** задача
|
||||
(реально идёт работа) **по-прежнему держит** гейт — анти-stale-base гарантия ORCH-088 не
|
||||
регрессирует (см. §8 — конфликт свойств, разрешает архитектор).
|
||||
- **BR-4** — Снапшот `serial_gate` в `GET /queue` показывает **корректную причину** ожидания
|
||||
успешника: активная задача / приостановленный предшественник / freeze / dependency.
|
||||
- **BR-5** — При **возобновлении** (распаузе) задачи serial-ordering корректно восстанавливается:
|
||||
возобновлённая задача снова участвует в гейте (либо держит его, либо явно ре-входит в FIFO с
|
||||
обязательством rebase) — нет «вечного обхода» и нет потерянного намерения.
|
||||
- **BR-6** — Существующие гарантии serial-gate сохранены: FIFO по более ранним незавершённым
|
||||
задачам, durable per-repo `freeze` (`repo_freeze`), cross-repo параллелизм, явные `task_deps` —
|
||||
по-прежнему блокируют, где должны.
|
||||
|
||||
## 5. Нефункциональные требования (NFR)
|
||||
- **NFR-1 (never-raise / fail-safe)** — Контракт leaf `serial_gate` сохранён: каждая публичная
|
||||
функция деградирует консервативно. Сохранить два направления отказа ORCH-088: hot-claim build →
|
||||
**fail-OPEN** (`""`-фрагмент, не заклинить очередь всех проектов, AC-8 ORCH-088); freeze-решение →
|
||||
**fail-CLOSED** (прод-безопасность, AC-9 ORCH-088). Новая логика паузы не должна инвертировать эти
|
||||
направления.
|
||||
- **NFR-2 (чистота hot-path)** — Гейт-в-claim остаётся **offline SQL-предикатом**; **никаких сетевых
|
||||
вызовов** (в т.ч. Plane API) в `claim_next_job`. Сигнал «пауза» обязан быть **DB-резолвимым**
|
||||
(durable колонка/таблица), а не считываться из Plane на горячем пути (в отличие от reconciler).
|
||||
- **NFR-3 (обратная совместимость / kill-switch / область)** — Изменение аддитивно и обратимо;
|
||||
выключатель (существующий `serial_gate_enabled` либо новый под-флаг) → байт-в-байт прежнее поведение
|
||||
до ORCH-124. `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict ключи / **схемы
|
||||
существующих таблиц** — без изменений (новая колонка/таблица — только аддитивно, паттерн
|
||||
`_ensure_column` / `CREATE TABLE IF NOT EXISTS`). enduro не затронут при нейтральном поведении.
|
||||
- **NFR-4 (гармонизация предиката)** — Любой новый предикат «активна/терминальна/пауза» должен
|
||||
оставаться согласованным с гармонизированным терминальным множеством `{done,cancelled}` в
|
||||
`serial_gate` + `task_deps` + `stages.py` (ORCH-090 / adr-0026), либо архитектор явно описывает,
|
||||
почему serial-gate расходится (паузой), не ломая `task_deps`.
|
||||
- **NFR-5 (self-hosting безопасность)** — Никакого рестарта/падения прод-контейнера, мутации `main`,
|
||||
force-push; только чтение/запись своих таблиц и принятие решения о claim. Hot-path не должен
|
||||
замедляться сетью или тяжёлым запросом.
|
||||
|
||||
## 6. Допущения и ограничения
|
||||
- У таблицы `tasks` **сегодня нет колонки статуса**; Plane-статус (Backlog/Blocked/Needs Input) —
|
||||
слой B индикации, в БД не отражён. Значит «пауза» для горячего пути требует **нового durable
|
||||
DB-сигнала** (колонка `tasks` или отдельная таблица), либо переиспользования уже DB-резолвимого
|
||||
механизма (`repo_freeze` / `task_deps`).
|
||||
- `repo_freeze` существует, но **freeze'ит весь репо** (блокирует всех успешников) — это
|
||||
противоположность «пропустить срочного успешника», поэтому как есть не годится для BR-1 (но годится
|
||||
как явный блок для BR-6).
|
||||
- `task_deps` (`job_deps`) — явные декларации зависимостей, уже DB-резолвимы и консультируются в
|
||||
`claim_next_job` (`NOT EXISTS`); кандидат на «explicit intent», на усмотрение архитектора.
|
||||
- Reconciler ORCH-060 различает Blocked/Needs-Input **сетевым** запросом Plane — прецедент семантики,
|
||||
но **не переиспользуем** на hot-path (NFR-2).
|
||||
- Серый кейс: Needs Input во время `analysis` — нормальное короткое ожидание ответа; решение, считать
|
||||
ли его «паузой для гейта», за архитектором (важно не превратить штатное короткое ожидание в обход
|
||||
анти-stale-base).
|
||||
|
||||
## 7. Критерии успеха
|
||||
Кратко (детальные PASS/FAIL — `03-acceptance-criteria.md`):
|
||||
- Приостановленный предшественник (Backlog/Blocked/Needs-Input по явному намерению) не блокирует
|
||||
срочного успешника; нормально идущая задача — блокирует; freeze/dependency блокируют, где должны;
|
||||
`GET /queue` показывает корректную причину; всё под kill-switch; машинные инварианты байт-в-байт;
|
||||
регресс-тест инцидента красный до фикса и зелёный после.
|
||||
|
||||
## 8. Риски
|
||||
Кратко (детально — `10-tech-risks.md`, заполняет архитектор):
|
||||
- **R-1 (ключевой конфликт свойств) — пауза vs анти-stale-base (ORCH-088).** Если «пауза» освобождает
|
||||
гейт, успешник срежет ветку от `main`, ещё **не** содержащего код предшественника. Когда
|
||||
приостановленный предшественник позже возобновится и смержится — его база/ветка могут стать stale.
|
||||
ORCH-088 был построен ровно чтобы это предотвратить. Архитектор обязан разрешить конфликт явно
|
||||
(напр.: пауза «демотирует» задачу в FIFO и обязывает rebase при возобновлении; либо явный per-task
|
||||
«yield» с принятием rebase). **Аналитик фиксирует конфликт, не выбирает решение.**
|
||||
- **R-2** — Случайное/неявное освобождение гейта (баг в детекте намерения) ослабит сериализацию для
|
||||
всех — требуется строго **явное** намерение оператора.
|
||||
- **R-3** — Рассинхрон «Plane-статус ↔ DB-сигнал паузы»: если механизм опирается на webhook о смене
|
||||
статуса, потерянный webhook оставит задачу «активной» в БД (или наоборот). Нужен durable,
|
||||
идемпотентный, восстановимый сигнал.
|
||||
- **R-4** — Регрессия гармонизированного предиката `{done,cancelled}` в `task_deps`/`stages.py`, если
|
||||
serial-gate изменит понимание «активности» неаккуратно.
|
||||
- **R-5** — fail-direction: ошибка в новой ветке не должна инвертировать fail-OPEN (claim) /
|
||||
fail-CLOSED (freeze) контракты ORCH-088.
|
||||
133
docs/work-items/ORCH-124/02-trz.md
Normal file
133
docs/work-items/ORCH-124/02-trz.md
Normal file
@@ -0,0 +1,133 @@
|
||||
---
|
||||
work_item: ORCH-124
|
||||
stage: analysis
|
||||
author_agent: analyst
|
||||
status: ready-for-review
|
||||
created_at: 2026-06-16
|
||||
model_used: claude-opus-4-8
|
||||
escalate: full-cycle
|
||||
---
|
||||
|
||||
# 02 — ТЗ (TRZ): ORCH-124 — wait/terminal-семантика serial-gate (пауза без блокировки)
|
||||
|
||||
Work Item: **ORCH-124** · Repo: **orchestrator** · Стадия: analysis
|
||||
|
||||
> ТЗ описывает **требования к изменениям**, выведенные из BRD и фактического кода. **Выбор
|
||||
> механизма** «паузы без блокировки» (release-on-status / per-task hold-флаг / task_deps) и его
|
||||
> архитектурное обоснование — задача **архитектора** (`06-adr`, эскалация в full-cycle). Ниже —
|
||||
> что должно стать истинным и какие модули это затронет, без предписания «как именно».
|
||||
|
||||
## 1. Сводка изменения
|
||||
Сейчас serial-gate (`src/serial_gate.py`) считает «активной» любую задачу репо со стадией вне
|
||||
`{done,cancelled}` — в трёх точках (`build_claim_clause` / `repo_has_active_task` /
|
||||
`_per_repo_snapshot`). Поскольку Plane-статусы Backlog/Blocked/Needs-Input (слой B индикации) **не
|
||||
меняют `tasks.stage`** (слой A), приостановленная задача неотличима от активной и держит FIFO-гейт
|
||||
закрытым для более поздних analyst-job. Требуется ввести **явный, durable, DB-резолвимый** признак
|
||||
«пауза/park» и научить определение «активной задачи» его учитывать, **сохранив** анти-stale-base
|
||||
ORCH-088 при возобновлении (R-1). Машинные гейты не трогаются — это правка **планировщика очереди**.
|
||||
|
||||
## 2. Задействованные модули / пути
|
||||
| Путь | Действие |
|
||||
|------|----------|
|
||||
| `src/serial_gate.py` | изменить — определение «активной задачи» во всех 3 точках (`build_claim_clause`, `repo_has_active_task`, `_per_repo_snapshot`); причина ожидания в снапшоте |
|
||||
| `src/db.py` | изменить (вероятно) — `claim_next_job` (учёт нового предиката в горячем SQL) + (если выбран DB-сигнал) аддитивная колонка/таблица `_ensure_column`/`CREATE TABLE IF NOT EXISTS` + read-only/мутатор-хелперы |
|
||||
| `src/config.py` | изменить — под-флаг(и) семантики паузы (kill-switch), область репо (по образцу `serial_gate_*`) |
|
||||
| `src/main.py` | изменить (вероятно) — операторский эндпоинт pause/resume **или** расширение блока `serial_gate` в `GET /queue` причиной ожидания |
|
||||
| `src/webhooks/plane.py` и/или `src/plane_sync.py` | изменить (если механизм = release-on-status) — захват намерения паузы из смены Plane-статуса в durable DB-сигнал (не на hot-path) |
|
||||
| `tests/test_serial_gate*.py` (новый `tests/test_orch124_serial_gate_pause.py`) | создать/дополнить — кейсы паузы + регресс инцидента |
|
||||
| `docs/architecture/README.md`, `CHANGELOG.md`, `docs/work-items/ORCH-124/06-adr/` | обновить — раздел serial-gate + ADR (архитектор) |
|
||||
|
||||
> Точный набор модулей зависит от выбранного механизма (ADR). Минимально-необходимый набор —
|
||||
> `serial_gate.py` (3 точки) + `db.py` (hot-path) + `config.py` (флаг) + тесты; остальное — по решению
|
||||
> архитектора.
|
||||
|
||||
## 3. Функциональные требования
|
||||
|
||||
### FR-1 — Признак паузы исключает задачу из «активных» в горячем SQL (BR-1, NFR-2)
|
||||
`build_claim_clause()`: подзапрос `EXISTS (... t2.stage NOT IN ('done','cancelled'))` должен
|
||||
**дополнительно** исключать приостановленные задачи-предшественники. Предикат — **чисто SQL по
|
||||
локальной БД** (никакой сети). Форма исключения — функция выбранного DB-сигнала (доп. колонка
|
||||
`tasks.<paused>` / отдельная таблица hold / `task_deps`): архитектор фиксирует точную SQL-форму в ADR.
|
||||
Инвариант: job'ы уже активной задачи (`agent != 'analyst'`) проходят как раньше; самоблокировки
|
||||
собственной строки (R-7 ORCH-088) нет.
|
||||
|
||||
### FR-2 — Зеркало и снапшот согласованы с FR-1 (BR-1/BR-4)
|
||||
`repo_has_active_task()` и `_per_repo_snapshot()` используют **тот же** предикат «активна», что и
|
||||
`build_claim_clause` — приостановленный предшественник не попадает в `active_task`. Все три точки
|
||||
правятся согласованно (анти-дрейф: они должны давать один ответ на один вход).
|
||||
|
||||
### FR-3 — Явное durable намерение паузы (BR-2, BR-5, NFR-2)
|
||||
Должен существовать **явный** оператор-инициируемый сигнал «park/pause» задачи, **durable**
|
||||
(переживает рестарт) и **DB-резолвимый**. Распауза (resume) — обратная операция, восстанавливающая
|
||||
участие задачи в serial-gate (BR-5). Сигнал **отличен** от `cancel`/`done` (не терминальный) и от
|
||||
глобального kill-switch. Конкретная форма (новый эндпоинт `POST /serial-gate/pause|resume`, или
|
||||
маппинг Plane-статуса в DB-сигнал через webhook, или декларация `task_deps`) — **решение архитектора**.
|
||||
|
||||
### FR-4 — Анти-stale-base при возобновлении (BR-3, R-1 — критично)
|
||||
Решение обязано **не регрессировать** ORCH-088: нормально исполняемая задача держит гейт; а
|
||||
возобновлённая ранее приостановленная задача не должна приводить к stale-базе у успешника, который
|
||||
прошёл вперёд. Механизм разрешения (демотирование в FIFO + обязательный rebase при resume / явный
|
||||
yield с принятием rebase / иное) фиксируется архитектором в ADR. ТЗ требует лишь: после распаузы
|
||||
ни одна из задач не остаётся на устаревшей базе незаметно.
|
||||
|
||||
### FR-5 — Корректная причина ожидания (BR-4)
|
||||
Блок `serial_gate` в `GET /queue` для ожидающего успешника различает причины: `active-task`
|
||||
(идёт работа) / `paused-predecessor` (предшественник на паузе — не должно случаться после фикса, но
|
||||
наблюдаемо) / `freeze` / `dependency`. Аддитивно к существующим ключам снапшота (`active_task` /
|
||||
`waiting` / `frozen` / `frozen_reason` / `frozen_at`).
|
||||
|
||||
### FR-6 — Явные блоки сохранены (BR-6)
|
||||
`repo_freeze` (durable per-repo freeze) и `task_deps` (`job_deps`) продолжают блокировать claim ровно
|
||||
как сейчас. «Пауза» НЕ должна обходить freeze/dependency — это разные оси (freeze = весь репо;
|
||||
dependency = конкретная пара; пауза = «пропустите меня»).
|
||||
|
||||
### FR-7 — Условность и нейтральность (NFR-3)
|
||||
Поведение под выключателем (существующий `serial_gate_enabled` либо новый под-флаг паузы) → байт-в-байт
|
||||
до ORCH-124. Область репо — по образцу `serial_gate_repos` (CSV; пусто → текущая область serial-gate,
|
||||
т.е. все репо). enduro не затронут при нейтральном поведении.
|
||||
|
||||
## 4. Изменения API
|
||||
Вероятно (на усмотрение архитектора, ADR):
|
||||
- **Новые операторские эндпоинты** `POST /serial-gate/pause?work_item=<id>` и
|
||||
`POST /serial-gate/resume?work_item=<id>` (по образцу существующего `POST /serial-gate/unfreeze`),
|
||||
если механизм = явный per-task hold. Возвращают новое состояние (paused/active) задачи.
|
||||
- **Расширение** `GET /queue` → блок `serial_gate` дополняется причиной ожидания (FR-5) и (если есть)
|
||||
списком приостановленных задач репо. Существующие ключи не меняются (BC).
|
||||
|
||||
Если механизм = release-on-status, новых ручных эндпоинтов может не быть (намерение — смена Plane-статуса,
|
||||
захватываемая webhook'ом в durable DB-сигнал); тогда раздел сводится к расширению `GET /queue`.
|
||||
|
||||
## 5. Изменения схемы БД
|
||||
Вероятно **аддитивно** (на усмотрение архитектора):
|
||||
- Колонка `tasks.paused_at TEXT` (NULL = не на паузе), идемпотентно через `_ensure_column` — паттерн
|
||||
существующих `tasks.cancelled_at` / `tasks.cancel_requested_at` / `tasks.track`; **или**
|
||||
- Отдельная таблица hold (по образцу `repo_freeze`: `work_item_id`/`task_id`, `paused_at`,
|
||||
`cleared_at IS NULL` = активна), `CREATE TABLE IF NOT EXISTS`.
|
||||
Схемы **существующих** таблиц (`tasks`/`jobs`/`job_deps`/`repo_freeze`) — без деструктивных изменений.
|
||||
Если механизм = task_deps, новой схемы может не понадобиться вовсе. Финальное решение — ADR +
|
||||
`08-data-requirements.md` (архитектор).
|
||||
|
||||
## 6. Требования к новым/изменённым QG checks
|
||||
**Нет.** `STAGE_TRANSITIONS` / состав `QG_CHECKS` / семантика и имена `check_*` / machine-verdict ключи
|
||||
(`verdict:`/`result:`/`deploy_status:`/`staging_status:`/`security_status:`) — **байт-в-байт не
|
||||
трогаются**. ORCH-124 — правка планировщика очереди (claim) и наблюдаемости, **не** Quality Gate и
|
||||
**не** новая стадия конвейера.
|
||||
|
||||
## 7. Совместимость / регресс
|
||||
- **Обратная совместимость:** аддитивно; выключатель → поведение до ORCH-124 байт-в-байт (NFR-3,
|
||||
FR-7). Существующие тесты serial-gate (`tests/test_serial_gate*.py`) остаются зелёными.
|
||||
- **Kill-switch / область:** переиспользовать `serial_gate_enabled` (kill-switch) + при необходимости
|
||||
ввести под-флаг семантики паузы (независимый тумблер, по образцу `serial_gate_freeze_enabled`) и
|
||||
область `*_repos` (CSV).
|
||||
- **Обратимость:** выставить под-флаг паузы в `False` → serial-gate работает как ORCH-088/090
|
||||
(приостановленные снова считаются активными — т.е. возврат к текущему багу, но это осознанный
|
||||
rollback-режим, не дефолт).
|
||||
- **never-raise / fail-direction (NFR-1):** сохранить fail-OPEN на hot-claim build и fail-CLOSED на
|
||||
freeze; новая ветка паузы не инвертирует эти направления.
|
||||
- **Гармонизация предиката (NFR-4):** не сломать `task_deps`/`stages.py` терминальное множество
|
||||
`{done,cancelled}` (ORCH-090/adr-0026); расхождение serial-gate (паузой) — только осознанно и
|
||||
задокументированно в ADR.
|
||||
- **Артефакты pipeline:** обновляются `docs/architecture/README.md` (раздел serial-gate ORCH-088 +
|
||||
семантика паузы), `CHANGELOG.md`, новый `docs/work-items/ORCH-124/06-adr/ADR-001-*.md` (архитектор),
|
||||
при необходимости `08-data-requirements.md` (архитектор). Reviewer проверяет обновление доки (правило
|
||||
агентов §6).
|
||||
136
docs/work-items/ORCH-124/03-acceptance-criteria.md
Normal file
136
docs/work-items/ORCH-124/03-acceptance-criteria.md
Normal file
@@ -0,0 +1,136 @@
|
||||
---
|
||||
work_item: ORCH-124
|
||||
stage: analysis
|
||||
author_agent: analyst
|
||||
status: ready-for-review
|
||||
created_at: 2026-06-16
|
||||
model_used: claude-opus-4-8
|
||||
escalate: full-cycle
|
||||
---
|
||||
|
||||
# 03 — Критерии приёмки (Acceptance Criteria): ORCH-124 — wait/terminal-семантика serial-gate
|
||||
|
||||
Work Item: **ORCH-124** · Repo: **orchestrator** · Стадия: analysis
|
||||
|
||||
Формат: каждый критерий имеет **PASS** (что должно быть истинно для приёмки) и **FAIL** (что считается
|
||||
провалом). Reviewer/tester проверяет их буквально по файлам репозитория и тестам.
|
||||
|
||||
---
|
||||
|
||||
## AC-1 — Приостановленный предшественник не блокирует срочного успешника (регресс инцидента)
|
||||
|
||||
**Условие:** репо имеет более раннюю задачу A (`A.id < B.id`) в состоянии паузы (Blocked/Backlog/
|
||||
Needs-Input по явному намерению оператора) и более позднюю срочную задачу B с queued analyst-job.
|
||||
- **PASS:** `build_claim_clause()` НЕ блокирует analyst-job задачи B; `claim_next_job()` выбирает его;
|
||||
B входит в `analysis`. Тест воспроизводит инцидент ORCH-116/ORCH-123 (красный до фикса, зелёный
|
||||
после).
|
||||
- **FAIL:** analyst-job B остаётся `queued`, потому что приостановленная A всё ещё считается активной.
|
||||
|
||||
---
|
||||
|
||||
## AC-2 — Нормально исполняемая задача по-прежнему держит гейт (анти-stale-base, без регресса ORCH-088)
|
||||
|
||||
**Условие:** репо имеет более раннюю задачу A, **реально исполняемую** (не на паузе, стадия вне
|
||||
`{done,cancelled}`) и более позднюю задачу B с queued analyst-job.
|
||||
- **PASS:** analyst-job B **остаётся заблокированным** (FIFO ORCH-088 цел); B стартует только когда A
|
||||
становится `done`/`cancelled` (или явно поставлена на паузу оператором).
|
||||
- **FAIL:** B стартует поверх ещё не завершённой активной A → возврат stale-base дефекта, который
|
||||
закрывал ORCH-088.
|
||||
|
||||
---
|
||||
|
||||
## AC-3 — Пауза снимает гейт только по явному durable намерению
|
||||
|
||||
**Условие:** способ перевода задачи в «паузу» для serial-gate.
|
||||
- **PASS:** освобождение гейта происходит **только** при явном операторском сигнале (эндпоинт
|
||||
pause / выбранный механизм ADR), сигнал **durable** (переживает рестарт процесса/контейнера) и
|
||||
**DB-резолвимый**. Никакого эвристического само-распаузивания.
|
||||
- **FAIL:** гейт снимается без явного намерения (например, по умолчанию для любой не-`done` задачи) ИЛИ
|
||||
намерение теряется после рестарта.
|
||||
|
||||
---
|
||||
|
||||
## AC-4 — Анти-stale-base при возобновлении (R-1 разрешён архитектором)
|
||||
|
||||
**Условие:** приостановленная A возобновлена после того, как успешник B уже прошёл вперёд.
|
||||
- **PASS:** ни A, ни B не остаются незаметно на устаревшей базе — выбранный механизм (демотирование A
|
||||
в FIFO + обязательный rebase / явный yield с rebase / иное по ADR) гарантирует, что возобновлённая
|
||||
задача строится/мержится на актуальном `origin/main`. Поведение зафиксировано тестом по контракту ADR.
|
||||
- **FAIL:** возобновлённая A или прошедший B приводит к stale-ветке/затиранию кода без сигнала.
|
||||
|
||||
---
|
||||
|
||||
## AC-5 — Явные блоки (freeze + dependency) сохранены
|
||||
|
||||
**Условие:** активный `repo_freeze` для репо ИЛИ объявленная незавершённая зависимость в `job_deps`.
|
||||
- **PASS:** claim успешника по-прежнему **заблокирован** freeze'ем (до ручного
|
||||
`POST /serial-gate/unfreeze`) и/или незавершённой зависимостью (`task_deps`) — пауза их не обходит.
|
||||
- **FAIL:** «пауза» позволяет обойти freeze или объявленную зависимость.
|
||||
|
||||
---
|
||||
|
||||
## AC-6 — Корректная причина ожидания в `GET /queue`
|
||||
|
||||
**Условие:** блок `serial_gate` в снапшоте `GET /queue`.
|
||||
- **PASS:** для ожидающего успешника видна корректная причина ожидания (`active-task` /
|
||||
`paused-predecessor` / `freeze` / `dependency`); приостановленный предшественник НЕ показывается как
|
||||
`active_task`. Существующие ключи снапшота не сломаны (BC).
|
||||
- **FAIL:** снапшот показывает приостановленную задачу как `active_task` ИЛИ причина ожидания неверна/
|
||||
отсутствует.
|
||||
|
||||
---
|
||||
|
||||
## AC-7 — Hot-path остаётся offline (без сети)
|
||||
|
||||
**Условие:** путь `db.claim_next_job` → `serial_gate.build_claim_clause`.
|
||||
- **PASS:** определение «активна/пауза» резолвится **только** локальной БД (SQL); ни одного сетевого
|
||||
вызова (Plane API и т.п.) на горячем пути claim. Проверяемо тестом (claim работает без сети / без
|
||||
Plane).
|
||||
- **FAIL:** claim делает сетевой вызов для определения паузы.
|
||||
|
||||
---
|
||||
|
||||
## AC-8 — Машинные инварианты байт-в-байт; kill-switch off → прежнее поведение
|
||||
|
||||
**Условие:** реестры и выключатель.
|
||||
- **PASS:** `STAGE_TRANSITIONS` / состав `QG_CHECKS` / имена и семантика `check_*` / machine-verdict
|
||||
ключи / схемы существующих таблиц — без изменений (структурный тест). При выключенном под-флаге паузы
|
||||
serial-gate ведёт себя байт-в-байт как ORCH-088/090.
|
||||
- **FAIL:** изменён любой machine-verdict ключ / состав `QG_CHECKS` / `STAGE_TRANSITIONS`, либо
|
||||
выключенный флаг меняет поведение.
|
||||
|
||||
---
|
||||
|
||||
## AC-9 — never-raise и сохранённые fail-directions
|
||||
|
||||
**Условие:** ошибки внутри новой логики паузы.
|
||||
- **PASS:** все публичные функции `serial_gate` never-raise; hot-claim build по-прежнему **fail-OPEN**
|
||||
(`""`-фрагмент при ошибке), freeze-решение по-прежнему **fail-CLOSED**; новая ветка паузы не
|
||||
инвертирует эти направления. Проверяемо тестом (инъекция ошибки → claim не падает, очередь не
|
||||
заклинивает).
|
||||
- **FAIL:** ошибка в ветке паузы роняет claim/worker ИЛИ инвертирует fail-direction.
|
||||
|
||||
---
|
||||
|
||||
## AC-10 — Возобновление восстанавливает участие в гейте (BR-5)
|
||||
|
||||
**Условие:** распауза ранее приостановленной задачи.
|
||||
- **PASS:** после resume задача снова участвует в serial-gate согласно выбранной семантике (держит
|
||||
гейт как активная либо ре-входит в FIFO); нет «вечного обхода».
|
||||
- **FAIL:** возобновлённая задача навсегда остаётся вне гейта / её намерение паузы «залипает».
|
||||
|
||||
---
|
||||
|
||||
## Сводная матрица AC ↔ FR/BR
|
||||
| AC | Покрывает |
|
||||
|----|-----------|
|
||||
| AC-1 | BR-1 / FR-1, FR-2 |
|
||||
| AC-2 | BR-3 / FR-4 |
|
||||
| AC-3 | BR-2 / FR-3 |
|
||||
| AC-4 | BR-3, BR-5 / FR-4 |
|
||||
| AC-5 | BR-6 / FR-6 |
|
||||
| AC-6 | BR-4 / FR-5 |
|
||||
| AC-7 | NFR-2 / FR-1 |
|
||||
| AC-8 | NFR-3, NFR-4 / FR-7 |
|
||||
| AC-9 | NFR-1 / FR-7 |
|
||||
| AC-10 | BR-5 / FR-3 |
|
||||
112
docs/work-items/ORCH-124/04-test-plan.yaml
Normal file
112
docs/work-items/ORCH-124/04-test-plan.yaml
Normal file
@@ -0,0 +1,112 @@
|
||||
work_item: ORCH-124
|
||||
stage: analysis
|
||||
author_agent: analyst
|
||||
status: ready-for-review
|
||||
created_at: 2026-06-16
|
||||
model_used: claude-opus-4-8
|
||||
title: "Serial-gate wait/pause semantics — paused predecessor must not block urgent successor"
|
||||
framework: pytest
|
||||
scope: >
|
||||
Покрывает определение "активной задачи" в serial-gate (build_claim_clause /
|
||||
repo_has_active_task / _per_repo_snapshot), durable явный сигнал паузы и его учёт в
|
||||
горячем SQL claim, сохранность анти-stale-base ORCH-088, явных блоков (freeze/task_deps),
|
||||
наблюдаемости GET /queue, never-raise/fail-directions и kill-switch. Вне покрытия:
|
||||
изменения STAGE_TRANSITIONS/QG_CHECKS/check_* (их НЕТ), сетевые вызовы Plane API на hot-path
|
||||
(запрещены — проверяется их ОТСУТСТВИЕ), реальный прод-деплой/рестарт.
|
||||
notes: >
|
||||
TC-01 — ОБЯЗАТЕЛЬНЫЙ регресс-тест инцидента ORCH-116/ORCH-123: красный до фикса, зелёный
|
||||
после. Точные имена флагов/колонок/эндпоинтов и SQL-форма предиката зависят от выбранного
|
||||
механизма (ADR архитектора) — тест-план фиксирует ПОВЕДЕНИЕ, не реализацию. Тесты
|
||||
работают на временной SQLite-БД без сети/Plane/прода (паттерн tests/test_serial_gate*.py).
|
||||
Полный регресс tests/ должен оставаться зелёным (особенно test_serial_gate*.py).
|
||||
|
||||
tests:
|
||||
- id: TC-01
|
||||
type: integration
|
||||
description: "РЕГРЕСС (обязательный): репо с более ранней приостановленной задачей A + более поздняя срочная B → claim_next_job выбирает analyst-job B (гейт открыт). Красный до фикса, зелёный после. Воспроизводит ORCH-116/ORCH-123."
|
||||
module: tests/test_orch124_serial_gate_pause.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-02
|
||||
type: unit
|
||||
description: "Предшественник в Backlog (по явному намерению) не считается активным: build_claim_clause не блокирует analyst-job успешника."
|
||||
module: tests/test_orch124_serial_gate_pause.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-03
|
||||
type: unit
|
||||
description: "Предшественник в Needs-Input (по явному намерению) не блокирует успешника — параллельно AC-1 для иного wait-состояния."
|
||||
module: tests/test_orch124_serial_gate_pause.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-04
|
||||
type: unit
|
||||
description: "АНТИ-РЕГРЕСС ORCH-088: нормально исполняемый предшественник (не на паузе, стадия вне done/cancelled) ПО-ПРЕЖНЕМУ блокирует analyst-job успешника (FIFO цел)."
|
||||
module: tests/test_orch124_serial_gate_pause.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-05
|
||||
type: unit
|
||||
description: "Пауза требует явного durable намерения: задача без операторского сигнала паузы остаётся активной (гейт держится); сигнал паузы DB-резолвим."
|
||||
module: tests/test_orch124_serial_gate_pause.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-06
|
||||
type: unit
|
||||
description: "Durable: сигнал паузы переживает пересоздание соединения/рестарт (читается из БД, не из памяти процесса)."
|
||||
module: tests/test_orch124_serial_gate_pause.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-07
|
||||
type: unit
|
||||
description: "Возобновление (resume) восстанавливает участие задачи в serial-gate (держит гейт / ре-входит в FIFO согласно ADR); нет вечного обхода."
|
||||
module: tests/test_orch124_serial_gate_pause.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-08
|
||||
type: unit
|
||||
description: "Явные блоки сохранены: активный repo_freeze продолжает блокировать claim успешника; пауза freeze не обходит."
|
||||
module: tests/test_orch124_serial_gate_pause.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-09
|
||||
type: unit
|
||||
description: "Явные блоки сохранены: незавершённая объявленная зависимость (job_deps/task_deps) продолжает блокировать claim; пауза dependency не обходит."
|
||||
module: tests/test_orch124_serial_gate_pause.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-10
|
||||
type: unit
|
||||
description: "GET /queue snapshot: приостановленный предшественник НЕ показывается как active_task; причина ожидания успешника корректна (active-task/paused-predecessor/freeze/dependency); существующие ключи snapshot сохранены."
|
||||
module: tests/test_orch124_serial_gate_pause.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-11
|
||||
type: unit
|
||||
description: "Согласованность трёх точек: build_claim_clause, repo_has_active_task и _per_repo_snapshot дают один и тот же вердикт 'активна' на одном входе (анти-дрейф)."
|
||||
module: tests/test_orch124_serial_gate_pause.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-12
|
||||
type: unit
|
||||
description: "Hot-path offline: claim_next_job + build_claim_clause резолвят паузу без сетевого вызова (Plane API не вызывается); claim работает при недоступном Plane."
|
||||
module: tests/test_orch124_serial_gate_pause.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-13
|
||||
type: unit
|
||||
description: "never-raise / fail-directions: инъекция ошибки в логику паузы → build_claim_clause fail-OPEN ('' фрагмент, очередь не клинит), freeze-решение fail-CLOSED; ни одна публичная функция не бросает."
|
||||
module: tests/test_orch124_serial_gate_pause.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-14
|
||||
type: unit
|
||||
description: "Kill-switch / нейтральность: при выключенном под-флаге паузы (и/или serial_gate_enabled) поведение serial-gate байт-в-байт как ORCH-088/090; вне области репо — не затронуто (enduro)."
|
||||
module: tests/test_orch124_serial_gate_pause.py
|
||||
expected: PASS
|
||||
|
||||
- id: TC-15
|
||||
type: unit
|
||||
description: "Структурный анти-регресс: STAGE_TRANSITIONS, состав QG_CHECKS, имена check_* и machine-verdict ключи не изменены; схемы существующих таблиц tasks/jobs/job_deps/repo_freeze не сломаны."
|
||||
module: tests/test_orch124_serial_gate_pause.py
|
||||
expected: PASS
|
||||
Reference in New Issue
Block a user