diff --git a/docs/work-items/ORCH-106/01-brd.md b/docs/work-items/ORCH-106/01-brd.md new file mode 100644 index 0000000..5ae497a --- /dev/null +++ b/docs/work-items/ORCH-106/01-brd.md @@ -0,0 +1,150 @@ +--- +work_item: ORCH-106 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-12 +model_used: claude-opus-4-8 +track: bug +escalate: none +--- + +# 01 — Bug-report (облегчённый BRD): ORCH-106 — Fix onboard_project.py: add color field for state creation + +Work Item: **ORCH-106** · Repo: **orchestrator** · Стадия: analysis · Трек: **🐞 Bug** (укороченный маршрут, ORCH-019) + +> Багфикс-трек: стадия `architecture` пропускается. Это **облегчённый** пакет (короткий +> bug-report + обязательный план регресс-теста), но все 4 файла analysis присутствуют +> (гейт `check_analysis_complete` не меняется). Эскалация в полный цикл — **не требуется** +> (см. §8). + +--- + +## 1. Симптом + +CLI онбординга `scripts/onboard_project.py` (режим `apply`) **не может создать статусы Plane** +для нового проекта. На каждом статусе, отсутствующем в проекте (в том числе **STOP**), шаг +`plane.state:` падает с исходом `ERROR`: запрос `POST .../states/` к Plane CE возвращает +**HTTP 400**. + +Практический эффект: turnkey-онбординг (ORCH-009) и Bundled-bootstrap (ORCH-103, +`onboard_project.py apply+verify`) **не доводят доску до 22 статусов** автоматически — оператор +вынужден создавать статусы вручную (теряется ключевое обещание «один проход»). Без статусов +группы `cancelled` (STOP, ORCH-090) и `Confirm Deploy` (ORCH-059) затронутый проект остаётся без +fail-closed-кнопок отмены/подтверждения деплоя. + +## 2. Шаги воспроизведения + +1. Иметь доступный Plane CE с валидным API-токеном (env `ORCH_PLANE_*`). +2. Запустить онбординг нового (либо неполностью сконфигурированного) проекта: + `python scripts/onboard_project.py apply --project ...` (любой путь, доходящий до + секции «2. Plane: статусы», строки ~760-779 `scripts/onboard_project.py`). +3. **Факт:** в отчёте каждый отсутствующий статус — `plane.state:STOP … ERROR` (и аналогично для + прочих), причина — `Client error '400 Bad Request'` от `POST …/projects//states/`. +4. **Ожидание:** статус создаётся, шаг — `CREATED`; `POST …/states/` возвращает `200/201`. + +> Детерминированный (без сети) повтор для CI — на уровне формируемого тела запроса: тело, +> которое `create_state` передаёт в `_post`, **не содержит** ключа `color` (см. §3). Это и есть +> корневой наблюдаемый дефект, проверяемый регресс-тестом (`04-test-plan.yaml`). + +## 3. Локализация + +| Что | Где | +|-----|-----| +| Дефектный метод | `scripts/onboard_project.py` → `PlaneClient.create_state` (строки **424-428**) | +| Дефектная строка | тело POST: `{"name": name, "group": group}` — **отсутствует поле `color`** | +| Точка отправки | `PlaneClient._post` (строки 382-389): `200/201` → ок; `401/403/404/405/501` → `ManualStep`; **прочее (включая 400) → `resp.raise_for_status()` → исключение** | +| Точка проявления | секция «2. Plane: статусы», цикл по `_PLANE_NAME_TO_KEY` (строки 767-779): исключение ловится `except Exception → report.add(..., ERROR, str(e))` | +| Таблица групп (контекст) | `STATE_GROUPS` (строки 81-104) — имена→группы, в т.ч. `STOP→cancelled` | + +## 4. Причина (root cause) + +Эндпоинт создания статуса в Plane CE (`POST /workspaces//projects//states/`) **требует +непустое поле `color`** (валидный hex-цвет) в теле запроса. `create_state` его не отправляет → +серверная валидация отклоняет запрос с **HTTP 400**. Поскольку `_post` не относит `400` к +«ручным» статусам (`401/403/404/405/501`), срабатывает `raise_for_status()`, исключение +поднимается в цикл онбординга и фиксируется как `ERROR`. + +Сопутствующий факт: соседний `create_label` (строки 430-434) и `create_project` (418-422) +работают, т.к. их CE-эндпоинты не требуют `color`; проблема **локальна** для создания статусов. + +## 5. Направление исправления (требование, не реализация) + +Тело запроса, формируемое `create_state`, должно содержать **валидное непустое поле `color`** +(hex-строка, принимаемая Plane CE), чтобы `POST …/states/` возвращал `200/201` и статус +создавался. Конкретный способ (единый дефолт-цвет на все статусы либо палитра по группам/именам) +и значение цвета — **решает developer** (для багфикс-трека `architecture` пропущена; это +точечная правда тела запроса, не архитектурное решение). + +> ⚠️ Зона аналитика — требование и его проверяемость, не выбор реализации. Минимально достаточно: +> любой валидный hex-цвет, единый или пер-статусный. + +## 6. Объём (scope) + +### В объёме +- Добавить валидное поле `color` в тело POST, формируемое `PlaneClient.create_state` + (`scripts/onboard_project.py`). +- Обязательный регресс-тест: тело `create_state` несёт непустой валидный `color` (red до фикса, + green после) — см. `04-test-plan.yaml`. + +### Вне объёма +- Изменение публичной сигнатуры `create_state(project_id, name, group)` (color добавляется + **внутри** тела, не как новый обязательный аргумент) — чтобы не ломать call-site (строка 773) и + существующие тесты/моки. +- Любые правки рантайма `src/**`, `STAGE_TRANSITIONS`, `QG_CHECKS`, `check_*`, схемы БД, compose, + Dockerfile. +- Цвета/палитра статусов как «дизайн» — не задача; нужен лишь валидный для CE цвет. +- Поведение `create_label`/`create_project` — не трогается. + +## 7. Заинтересованные стороны / влияние + +- **Заказчик:** оператор платформы, разворачивающий новый проект (ORCH-009 turnkey, ORCH-103 + Bundled). Боль — ручное досоздание 22 статусов, обещание «один проход» нарушено. +- **Затронуто:** `scripts/onboard_project.py` (CLI вне рантайма), runbook'и онбординга/тиража + (ссылаются на `apply+verify`). Действующий прод-конвейер и проект enduro-trails **не + затронуты** (скрипт — операторский, активируется только явным запуском). +- **Принимает результат:** reviewer/tester по критериям `03` и плану `04`. + +## 8. Решение по треку (bug-track, ORCH-019) + +Баг **простой и точечный**: правка тела запроса одного метода (`create_state`), без ADR, без +макетов, без затрагивания рантайма/гейтов. → Остаётся на **облегчённом багфикс-треке**, стадия +`architecture` пропускается. **`escalate: none`** — эскалация в полный цикл (`POST +/bug-fast-track/escalate`) **не требуется**. + +## 9. Бизнес-требования (BR) + +- **BR-1** — `onboard_project.py apply` создаёт статусы Plane без ошибки 400: тело + `POST …/states/` содержит валидное непустое поле `color`. +- **BR-2** — Сценарий из описания задачи воспроизводится зелёным: создание статуса с именем + **STOP** и цветом возвращает `200/201` (на живом Plane CE) / тело несёт `color` + (детерминированно в CI). +- **BR-3** — Все статусы онбординга (весь `_PLANE_NAME_TO_KEY`, включая STOP) создаются единообразно + с непустым `color`. + +## 10. Нефункциональные требования (NFR) + +- **NFR-1 (совместимость)** — публичная сигнатура `create_state(project_id, name, group)` и + call-site (строка 773) не меняются; существующие онбординг-тесты + (`tests/test_onboarding_script.py` и пр.) остаются зелёными. +- **NFR-2 (изоляция рантайма)** — правка только в операторском CLI `scripts/onboard_project.py` + (+ тест); `src/**`/`STAGE_TRANSITIONS`/`QG_CHECKS`/схема БД/compose — байт-в-байт. Self-hosting + безопасно: скрипт не деплоит/не рестартит прод/не трогает `main`. +- **NFR-3 (валидность цвета)** — отправляемый `color` — валидная hex-строка, принимаемая Plane CE + (не пустая, не `null`). +- **NFR-4 (полный регресс зелёный)** — `pytest tests/ -q` остаётся зелёным. + +## 11. Допущения и ограничения + +- Plane CE требует именно `color` (hex); поле обязательно и непустое — установленный факт из + описания задачи и наблюдаемого 400. +- Живая проверка `200` против реального Plane CE — ручной шаг приёмки (CI работает на уровне тела + запроса, без сети — детерминированность). +- Точное значение/палитра цвета — на усмотрение developer; аналитик фиксирует лишь «валидный + непустой hex». + +## 12. Критерии успеха + +Резюме: после фикса онбординг создаёт все статусы (включая STOP) без 400; регресс-тест на наличие +валидного `color` в теле `create_state` зелёный; существующий набор тестов не сломан. Детальные +PASS/FAIL — `03-acceptance-criteria.md`. diff --git a/docs/work-items/ORCH-106/02-trz.md b/docs/work-items/ORCH-106/02-trz.md new file mode 100644 index 0000000..c0eb46d --- /dev/null +++ b/docs/work-items/ORCH-106/02-trz.md @@ -0,0 +1,90 @@ +--- +work_item: ORCH-106 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-12 +model_used: claude-opus-4-8 +track: bug +--- + +# 02 — ТЗ (TRZ, bug-shaped): ORCH-106 — Fix onboard_project.py: add color field for state creation + +Work Item: **ORCH-106** · Repo: **orchestrator** · Стадия: analysis · Трек: **🐞 Bug** + +> Облегчённое ТЗ багфикс-трека: фиксирует **конкретное изменение тела запроса**, выведенное из +> bug-report (`01-brd.md`) и фактического кода. Архитектурного обоснования нет (стадия +> `architecture` пропущена для bug-трека) — изменение точечное. + +## 1. Сводка изменения + +В `PlaneClient.create_state` (`scripts/onboard_project.py`, строки 424-428) тело запроса +`POST …/projects//states/` дополняется обязательным для Plane CE полем **`color`** (валидная +непустая hex-строка). Сейчас тело — `{"name": name, "group": group}`; после фикса — +`{"name": name, "group": group, "color": }`. Поведение `_post`, цикл онбординга, +сигнатуры и прочие методы не меняются. Добавляется обязательный регресс-тест. + +## 2. Задействованные модули / пути + +| Путь | Действие | +|------|----------| +| `scripts/onboard_project.py` | изменить — `PlaneClient.create_state`: добавить `color` в тело POST | +| `tests/test_onboarding_script.py` *(или новый `tests/test_onboard_state_color.py`)* | создать/дополнить — регресс-тест на наличие валидного `color` в теле `create_state` | + +> Выбор файла теста — на усмотрение developer; ключевое требование — тест экзерсайзит **реальный** +> `PlaneClient.create_state` (через перехват `_post`/`httpx.post`), а не `FakePlane` из существующего +> набора (мок не строит реальное тело и баг не ловит). + +## 3. Функциональные требования + +### FR-1 — `color` в теле создания статуса (BR-1, BR-3) +`create_state` ДОЛЖЕН включать в тело POST ключ `color` с валидной непустой hex-строкой для +**каждого** создаваемого статуса. Инвариант: ни один путь `create_state` не отправляет тело без +`color` (либо с пустым/`null` `color`). + +### FR-2 — Сценарий STOP (BR-2) +Создание статуса с именем **STOP** (группа `cancelled`, `STATE_GROUPS["STOP"]`) формирует тело с +непустым валидным `color`; на живом Plane CE такой `POST …/states/` возвращает `200/201`. + +### FR-3 — Сохранение контракта метода (NFR-1) +Публичная сигнатура `create_state(project_id, name, group)` НЕ меняется (color добавляется внутри +тела). Call-site `plane.create_state(project_id, name, group)` (строка 773) и существующие +тест-моки (`FakePlane.create_state(self, project_id, name, group)`) остаются валидны без правок. + +## 4. Изменения API + +**Исходящий вызов к Plane CE** — тело `POST /workspaces//projects//states/` получает +дополнительное поле `color`. Эндпоинтов **самого оркестратора** (FastAPI `src/**`) изменение **не +вводит и не меняет**. Новых/изменённых orchestrator-endpoint'ов — **Нет.** + +## 5. Изменения схемы БД + +**Нет.** SQLite-схема, таблицы, миграции — не затрагиваются (правка в операторском CLI). + +## 6. Требования к новым/изменённым QG checks + +**Нет.** `STAGE_TRANSITIONS`, реестр `QG_CHECKS`, `check_*`, machine-verdict ключи +(`verdict:`/`result:`/`deploy_status:`/`staging_status:`/`security_status:`/`coverage_status:`) — +байт-в-байт прежние. Багфикс-трек срезает только аналитику/архитектуру, не гейты (NFR-1 ORCH-019). + +## 7. Артефакты pipeline, создаваемые/обновляемые + +- Создаются analysis-доки `01-brd.md`, `02-trz.md`, `03-acceptance-criteria.md`, + `04-test-plan.yaml` (этот пакет). +- На дальнейших стадиях: `12-review.md` (reviewer), `13-test-report.md` (tester), + `14-deploy-log.md` (deployer). ADR (`06-adr/`) **не создаётся** — bug-трек, `architecture` + пропущена. +- Документация: при необходимости отметить факт в `CHANGELOG.md`; правка скрипта онбординга не + меняет нормативные шаги runbook'ов (LITE_SETUP/BUNDLED_SETUP/ONBOARDING) по существу — решает + developer/reviewer. + +## 8. Совместимость / регресс + +- **Обратная совместимость:** сигнатура и call-site неизменны; существующие онбординг-тесты + зелёные (NFR-1). +- **Область раската:** только операторский CLI `scripts/onboard_project.py`; активируется лишь + явным человеческим запуском (kill-switch не нужен — паттерн ORCH-009/102/103). +- **Self-hosting безопасность:** скрипт не деплоит/не рестартит прод-контейнер/не пушит `main` + (NFR-2). Idempotent-семантика `apply` (ensure без delete) сохраняется: повторный прогон по + существующему статусу остаётся `SKIPPED`. +- **Обратимость:** изменение тривиально откатывается (одна строка тела запроса). diff --git a/docs/work-items/ORCH-106/03-acceptance-criteria.md b/docs/work-items/ORCH-106/03-acceptance-criteria.md new file mode 100644 index 0000000..7a22295 --- /dev/null +++ b/docs/work-items/ORCH-106/03-acceptance-criteria.md @@ -0,0 +1,82 @@ +--- +work_item: ORCH-106 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-12 +model_used: claude-opus-4-8 +track: bug +--- + +# 03 — Критерии приёмки (Acceptance Criteria, bug-shaped): ORCH-106 + +Work Item: **ORCH-106** · Repo: **orchestrator** · Стадия: analysis · Трек: **🐞 Bug** + +Формат: каждый критерий имеет **PASS** (что должно быть истинно для приёмки) и **FAIL** +(что считается провалом). Reviewer/tester проверяют буквально по файлам репозитория и прогону тестов. + +--- + +## AC-1 — `create_state` отправляет валидное поле `color` + +**Условие:** тело POST, формируемое `PlaneClient.create_state` (`scripts/onboard_project.py`), +содержит ключ `color` с непустым значением. +- **PASS:** в теле присутствует ключ `color`, значение — непустая строка (`color is not None and + color != ""`). Для STOP (и любого имени) тело = `{"name", "group", "color", …}`. +- **FAIL:** тело не содержит `color`, либо `color` пустой/`null`. + +--- + +## AC-2 — `color` — валидный hex-цвет + +**Условие:** значение `color` — валидная hex-строка, принимаемая Plane CE. +- **PASS:** значение соответствует hex-формату цвета (например, `^#?[0-9A-Fa-f]{6}$` — конкретный + формат на усмотрение developer, но валидный для CE и непустой). +- **FAIL:** значение не является валидным hex-цветом (произвольный мусор, пустая строка, не строка). + +--- + +## AC-3 — Сценарий STOP создаётся без 400 (BR-2) + +**Условие:** создание статуса с именем **STOP** проходит. +- **PASS (детерминированно, CI):** регресс-тест подтверждает, что `create_state(project_id, + "STOP", "cancelled")` формирует тело с непустым валидным `color`. +- **PASS (живая проверка, ручной шаг приёмки):** реальный `POST …/projects//states/` с + `name=STOP` и `color` возвращает **`200/201`** (онбординг-отчёт: `plane.state:STOP … CREATED`). +- **FAIL:** живой `POST …/states/` для STOP возвращает `400`; или шаг отчёта — `ERROR`. + +--- + +## AC-4 — Все статусы онбординга единообразно с `color` (BR-3) + +**Условие:** цикл создания статусов (секция «2. Plane: статусы», по `_PLANE_NAME_TO_KEY`) для +каждого создаваемого статуса формирует тело с непустым валидным `color`. +- **PASS:** ни один статус из набора онбординга (включая STOP, `Confirm Deploy`, `Done`, + `Cancelled`, …) не отправляется без `color`. +- **FAIL:** хотя бы один статус отправляется с отсутствующим/пустым `color`. + +--- + +## AC-5 — Нулевая регрессия и изоляция рантайма (NFR-1, NFR-2, NFR-4) + +**Условие:** правка не ломает существующее поведение и не выходит за рамки CLI онбординга. +- **PASS:** + - Публичная сигнатура `create_state(project_id, name, group)` и call-site (строка 773) не + изменены; существующие онбординг-тесты (`tests/test_onboarding_*.py`) зелёные. + - `git diff` не затрагивает `src/**`, `STAGE_TRANSITIONS`, `QG_CHECKS`, `check_*`, схему БД, + `docker-compose*.yml`, `Dockerfile` (изменены только `scripts/onboard_project.py` и тест). + - Полный прогон `pytest tests/ -q` — зелёный. +- **FAIL:** сломан хоть один существующий тест; изменён рантайм/гейты/схема БД; изменена сигнатура + `create_state`. + +--- + +## Сводная матрица AC ↔ FR/BR + +| AC | Покрывает | +|----|-----------| +| AC-1 | BR-1 / FR-1 | +| AC-2 | NFR-3 / FR-1 | +| AC-3 | BR-2 / FR-2 | +| AC-4 | BR-3 / FR-1 | +| AC-5 | NFR-1 / NFR-2 / NFR-4 / FR-3 | diff --git a/docs/work-items/ORCH-106/04-test-plan.yaml b/docs/work-items/ORCH-106/04-test-plan.yaml new file mode 100644 index 0000000..7e276e4 --- /dev/null +++ b/docs/work-items/ORCH-106/04-test-plan.yaml @@ -0,0 +1,68 @@ +work_item: ORCH-106 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-12 +model_used: claude-opus-4-8 +track: bug +title: "Регресс-тест: create_state шлёт валидный color (onboard_project.py)" +framework: pytest +scope: > + Покрывается ТОЛЬКО тело POST, формируемое PlaneClient.create_state в + scripts/onboard_project.py (наличие непустого валидного hex-поля color) + неизменность + существующего поведения онбординга. Вне покрытия: реальные сетевые вызовы к Plane CE + (детерминированный CI без сети — перехват _post/httpx.post), выбор палитры/значения цвета, + правки рантайма src/**. +notes: > + Обязательный регресс-тест багфикс-трека (ORCH-019): TC-01 ДОЛЖЕН быть КРАСНЫМ на коде до фикса + (тело без color) и ЗЕЛЁНЫМ после фикса. Тесты детерминированы и без сети: инстанцируется + РЕАЛЬНЫЙ PlaneClient, его _post (или httpx.post) монкипатчится для перехвата (url, payload); + FakePlane из существующего набора НЕ годится для регресса (мок не строит реальное тело). + Полный регресс `pytest tests/ -q` должен оставаться зелёным. + +tests: + - id: TC-01 + type: unit + description: > + [ОБЯЗАТЕЛЬНЫЙ РЕГРЕСС, red→green] Реальный PlaneClient.create_state(project_id, "STOP", + "cancelled") формирует тело POST с ключом 'color', значение — непустая строка. Перехват + через монкипатч PlaneClient._post: assert 'color' in payload and payload['color']. + КРАСНЫЙ до фикса (тело {'name','group'}), ЗЕЛЁНЫЙ после. + module: tests/test_onboarding_script.py + expected: PASS + + - id: TC-02 + type: unit + description: > + Значение payload['color'], отправляемое create_state, является валидной hex-строкой цвета + (соответствует hex-формату, напр. ^#?[0-9A-Fa-f]{6}$). Защита от непустого, но невалидного + значения. + module: tests/test_onboarding_script.py + expected: PASS + + - id: TC-03 + type: unit + description: > + Тело сохраняет существующие ключи: payload содержит корректные 'name' и 'group' (для STOP — + group 'cancelled' из STATE_GROUPS) ВМЕСТЕ с новым 'color'; добавление color не вытесняет + name/group. + module: tests/test_onboarding_script.py + expected: PASS + + - id: TC-04 + type: integration + description: > + Прогон секции создания статусов онбординга по нескольким статусам (включая STOP, Done, + Confirm Deploy): для КАЖДОГО перехваченное тело create_state несёт непустой валидный color; + ни один статус не отправляется без color. + module: tests/test_onboarding_script.py + expected: PASS + + - id: TC-05 + type: unit + description: > + Нулевая регрессия контракта: публичная сигнатура create_state(project_id, name, group) + неизменна; существующие онбординг-тесты (FakePlane mutations ('create_state', name, group), + call-site строка 773) остаются зелёными. + module: tests/test_onboarding_script.py + expected: PASS