diff --git a/docs/work-items/ORCH-104/01-brd.md b/docs/work-items/ORCH-104/01-brd.md new file mode 100644 index 0000000..5953cc5 --- /dev/null +++ b/docs/work-items/ORCH-104/01-brd.md @@ -0,0 +1,201 @@ +--- +work_item: ORCH-104 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-11 +model_used: claude-opus-4-8 +--- + +# 01 — BRD: ORCH-104 — Установочный скрипт Lite-тиража (интерактивный installer) + +Work Item: **ORCH-104** · Repo: **orchestrator** (self-hosting) · Стадия: analysis +Заказчик: Слава (поставщик платформы); конечный потребитель — внешний оператор Lite-тиража + +--- + +## 1. Бизнес-контекст и проблема + +### 1.1. Текущее состояние +Type A эпика ORCH-10 («Lite»: заказчик разворачивает у себя только `orchestrator` + +`orchestrator-watchdog`, подключая СВОИ Plane/Gitea/Telegram/LLM) закрыт задачей ORCH-102 +**документом**: `docs/deployment/LITE_SETUP.md` — golden source из 13 нормативных разделов, +~30+ ручных fenced-команд и ~20 обязательных ключей нового хоста (§4.2 LITE_SETUP). +Маршрут полный и честный (каждый шаг несёт «Проверка:»/PASS/FAIL), но **порог входа высокий**: + +- оператор вручную собирает и переносит в `.env` значения, которые машина определяет сама + (uid/gid владельца каталога репо, gid группы docker, пути node / дистрибутива claude-code, + занятость портов); +- ошибки конфигурации (символ в секрете, рассинхрон тройки прод-порта, ключи watchdog не в том + файле-носителе, branch protection на `main`) проявляются **поздно** — на smoke или первой + задаче, а не в момент ввода; +- каверзные шаги (webhook Plane CE без внешнего API — §5.4; два независимых Telegram-бота — + §8; статусы строго через onboarding-CLI — §5.3) требуют дисциплины чтения дока. + +### 1.2. Бизнес-запрос (источник — описание задачи Plane) +Нужен **один установочный файл**, который: +1. на автомате выполняет установку Lite; +2. информацию, которую знает только пользователь (токен Plane и т.п.), **запрашивает в момент + установки**; +3. **сканирует систему**, говорит чего не хватает и **предлагает установить**; +4. если на хосте **несколько инсталляций** (например, Plane) — **показывает их и предлагает + выбрать**. + +Цель — максимально упростить установку для пользователей. + +### 1.3. Прецедент в платформе (переиспользовать, не изобретать) +- **Bundled (ORCH-103)** уже имеет установочный скрипт `scripts/bootstrap_bundle.py`: + режимы `plan` (дефолт) / `apply` / `verify`, step-движок check→ensure (повторный запуск = + каскад skip), exit-коды `0/2/1`, python stdlib-only, manual-checkpoint с обязательной + верификацией, **ни одной delete-операции**, секреты никогда не печатаются. **Lite такого + инструмента не имеет** — только док. ORCH-104 закрывает этот разрыв тем же выверенным + паттерном, добавляя три новых способности: интерактивный сбор данных, скан предусловий с + офером установки, discovery нескольких инсталляций с выбором. +- **Кирпичи готовы:** `scripts/gen_secrets.py` (webhook-секреты; x-mode «существующий файл → + отказ exit 2», перезапись только `--force`) и `scripts/onboard_project.py` + (`plan`/`apply`/`verify`: Plane-проект + 22 статуса + лейблы, Gitea-репо + webhook, kit, + merged-`ORCH_PROJECTS_JSON`). Канон статусов/лейблов **не форкается** — только кирпич. + +### 1.4. Установленные факты (проверено по репо, не изобретать) +- Маршрут Lite = LITE_SETUP.md §2–§12; кирпичи-каноны: REPLICATION.md (карта env §2, секреты + §3, smoke §4), ONBOARDING.md (статусы §1), SETUP_WEBHOOKS.md, `.env.example` / + `.env.watchdog.example` (канон 100% ключей). +- Контур Lite: **Linux x86_64, Docker Engine + Compose v2, git, python3, node** (§2 LITE_SETUP); + вне контура — вне гарантии. +- Webhook Plane CE **не экспонирован во внешнем `/api/v1`** — настраивается через UI (если + сборка показывает) или прямым SQL в Postgres Plane (§5.4 LITE_SETUP). +- Branch protection на `main` **НЕ включать** (норматив §6.4, ADR D10 ORCH-009 / INV-4). +- Telegram-канала **два и они независимы** (C-1 ORCH-100); токен орка для watchdog + переиспользовать запрещено; sidecar читает только `.env.watchdog` (ловушка файла-носителя). +- Тираж **stateless**: данные/БД/секреты боевого хоста не переносятся; секреты — только + свежевыпущенные (§12 LITE_SETUP, REPLICATION §3/§5). +- Скрипт живёт в репо → `git clone` чекаута остаётся единственным ручным предшагом + (разрешает «курицу и яйцо» §3: к моменту запуска скрипта чекаут уже есть). +- Структуру LITE_SETUP.md пиннит анти-дрейф тест `tests/test_lite_setup_doc.py` + («13 разделов в порядке», кирпичи, env-ключи ⊂ `.env.example`) — правка дока требует + синхронной правки теста в том же PR. + +--- + +## 2. Объём (scope) + +### 2.1. В объёме +- **Новый операторский CLI** в `scripts/` (один файл; кандидат имени — `setup_lite.py`, + финализирует архитектор), автоматизирующий маршрут LITE_SETUP §2–§12. +- **Скан предусловий** хоста (§2 + §7 LITE_SETUP) с честными вердиктами и **офером установки** + недостающего (с явного согласия; неподдерживаемый дистрибутив → manual-step). +- **Discovery инсталляций Plane/Gitea** на хосте (через локальный Docker): ≥2 → показать и + предложить выбрать; 1 → подставить по умолчанию; 0 → ручной ввод + честная подсказка. +- **Интерактивный сбор** обязательных ключей нового хоста (§4.2) с **немедленной верификацией** + каждого введённого значения фактическим вызовом (Plane API / Gitea API / Telegram getMe). +- **Автодетект хост-параметров** (uid/gid, docker gid, пути node/claude-code, порты) — то, что + машина может узнать сама, у пользователя не спрашивается. +- **Сборка `.env` / `.env.watchdog`** от канонов `.env.example` / `.env.watchdog.example`; + webhook-секреты — кирпичом `gen_secrets.py`; существующие файлы молча не перетираются. +- **Запуск Lite-контура** (ровно орк + watchdog) и health-проверки; stateless-проверка чистоты. +- **Регистрация проекта заказчика** строго кирпичом `onboard_project.py` (plan→apply→verify). +- **Итоговый отчёт** PASS/FAIL/MANUAL + exit-коды `0/2/1`; **идемпотентный повторный запуск** + (resume после manual-step). +- **Обновление `docs/deployment/LITE_SETUP.md`** (скрипт = рекомендованный быстрый путь; ручной + маршрут сохраняется как канон-fallback) + синхронные анти-дрейф тесты. + +### 2.2. Вне объёма (явно, не делать) +- **Установка/администрирование Plane и Gitea** — Lite-рамка: это продукты заказчика + (§1 LITE_SETUP); кейс «нет инфраструктуры вовсе» закрыт Bundled (ORCH-103). Скрипт только + обнаруживает и подключает. +- Любые правки **`src/**`**, корневого `docker-compose.yml`, `Dockerfile`, схемы БД, + `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` — рантайм байт-в-байт. +- Teardown / uninstall / миграция данных (stateless-канон; delete-операций в скрипте нет). +- Поддержка не-Linux платформ (контур Lite не расширяется). +- Автоматизация интерактивного OAuth-логина claude CLI (логин — по инструкции Anthropic, + вручную; скрипт только верифицирует результат). +- Самодостаточный «скачиватель» до чекаута (`curl | bash`): канал дистрибуции чекаута + согласуется с поставщиком (§3 LITE_SETUP) и не фиксирован платформой. +- Изменение `bootstrap_bundle.py` (Bundled) и `onboard_project.py` (кирпич переиспользуется + как есть). + +--- + +## 3. Заинтересованные стороны +- **Внешний оператор/заказчик Lite** — главный потребитель: ставит платформу одной командой, + отвечая на вопросы по ходу; не обязан заранее читать 13 разделов. +- **Поставщик платформы (Слава/Owner)** — снижение стоимости каждого внедрения и числа + обращений «не взлетело»; принимает результат. +- **Платформа/конвейер** — не затрагиваются: операторский инструмент вне рантайма; прод и + enduro-trails не подвержены риску (активация — только явный запуск человеком). + +--- + +## 4. Бизнес-требования (BR) + +| ID | Требование | Связь | +|----|------------|-------| +| BR-1 | **Один входной файл:** установка Lite выполняется одной командой из корня чекаута на голом python3; каждый шаг маршрута LITE_SETUP §2–§12 либо выполняется скриптом, либо явно выдаётся как ручной чекпоинт с верификацией результата. Молчаливых пропусков нет. | FR-1/FR-10, AC-1 | +| BR-2 | **Скан системы:** скрипт определяет недостающие предусловия (docker/compose/git/python3/node/claude-code/uid-gid/docker-group/ssh/порты), честно перечисляет их и предлагает установить/исправить; установка — только с явного согласия пользователя; неподдерживаемое окружение → manual-step с готовыми командами. | FR-2, AC-2 | +| BR-3 | **Запрос данных в момент установки:** все обязательные ключи нового хоста (§4.2 LITE_SETUP) запрашиваются интерактивно тогда, когда нужны; каждый токен/URL немедленно верифицируется фактическим вызовом; ввод секретов скрытый, значения секретов нигде не печатаются. | FR-4, AC-4 | +| BR-4 | **Discovery с выбором:** при нескольких инсталляциях Plane/Gitea на хосте скрипт показывает кандидатов и предлагает выбрать; при одной — подставляет её по умолчанию; при нуле — ручной ввод и честная подсказка (Lite не ставит Plane/Gitea; «нет инфраструктуры» = маршрут Bundled). Вариант «ввести вручную» доступен всегда. | FR-3, AC-3 | +| BR-5 | **Безопасность повторного запуска и чужих сред:** повторный запуск идемпотентен (каскад skip, resume после manual-step); существующие `.env`/`.env.watchdog` не перетираются молча; скрипт не содержит delete-операций, не трогает `main`, не рестартит чужие контейнеры; каждая мутация — с явного согласия. | FR-1/FR-6, AC-5/AC-11 | +| BR-6 | **Результат — работающий Lite-контур:** ровно два контейнера (`orchestrator`, `orchestrator-watchdog`), `/health`–`/queue`–`/metrics` зелёные, stateless-чистота подтверждена, проект заказчика зарегистрирован кирпичом onboarding, smoke-инструкция выдана; итог прогона — однозначный вердикт + exit-код `0/2/1`. | FR-8/FR-9/FR-10, AC-9/AC-10 | +| BR-7 | **Канон не форкается:** скрипт автоматизирует маршрут LITE_SETUP.md и использует существующие кирпичи (`gen_secrets.py`, `onboard_project.py`); сам канон-знание (статусы, ключи, нормативы) не дублирует; изменение маршрута → обновление LITE_SETUP.md в том же PR (норматив NFR-5 ORCH-102); соответствие закреплено анти-дрейф тестами. | FR-11, AC-14 | +| BR-8 | **Рантайм не тронут:** `src/**`, корневой compose, `Dockerfile`, `STAGE_TRANSITIONS`/`QG_CHECKS`/схема БД — байт-в-байт; kill-switch не нужен (активация — только явный запуск оператором; паттерн ORCH-009/102/103). | NFR-7, AC-13 | + +--- + +## 5. Нефункциональные требования (NFR) + +| ID | Требование | +|----|------------| +| NFR-1 | **stdlib-only:** скрипт работает на голом python3 целевого хоста ДО любого venv/`docker compose up`; модули платформы (`src/**`) не импортируются; закрепляется ast-сканом в тестах (паттерн `test_bootstrap_script.py`). | +| NFR-2 | **Безопасность среды заказчика:** никаких delete/необратимых операций; мутации — только локальный хост и только с явного согласия; БД Plane заказчика мутируется (webhook, Path Б §5.4) исключительно по явному согласию с обязательной верификацией; branch protection скрипт сам НЕ удаляет (только честный FAIL с лечением). | +| NFR-3 | **Секрет-гигиена:** значения секретов не печатаются и не логируются (только имена ключей/пути файлов); ввод — скрытый; секреты — только свежевыпущенные (stateless, REPLICATION §3); боевые значения исходного хоста не используются даже как подсказки-дефолты. | +| NFR-4 | **Честность шагов:** каждый шаг — явный PASS/FAIL/MANUAL со ссылкой на соответствующий § LITE_SETUP; «молчаливый пропуск запрещён» (паттерн ORCH-103). | +| NFR-5 | **Тестируемость без TTY/сети/docker:** вся решающая логика (вердикты предусловий, классификация discovery, когерентность портов, рендер env, аргументы onboard, step-движок) — чистые функции; интерактив — за инжектируемым I/O; pytest-прогон детерминирован. | +| NFR-6 | **Идемпотентность/resume:** step-движок check→ensure; повторный запуск пропускает выполненное и продолжает с первого незавершённого шага; manual-step → exit 2 → «resume» = просто повторный запуск (паттерн bootstrap_bundle). | +| NFR-7 | **Self-hosting безопасность:** случайный запуск на хосте с живым продом не ломает ничего: ранний отказ по существующему `.env` (без force), read-only режим диагностики, никакого воздействия на уже бегущие контейнеры без согласия. | +| NFR-8 | **Поддержка канона:** LITE_SETUP.md, CHANGELOG.md, витрина `docs/overview/` (если затронуто описание тиража) обновляются в том же PR (правило агентов №2); анти-дрейф тесты дока остаются зелёными. | + +--- + +## 6. Допущения и ограничения +- Контур — как у Lite: Linux x86_64, Docker Engine + Compose v2; скрипт контур не расширяет. +- Plane/Gitea — инсталляции заказчика, сетево доступны с хоста оркестратора; их установка — + вне задачи. +- Discovery гарантируется для **docker-инсталляций** (контейнеры с опознаваемыми + образами/метками compose); native/k8s-инсталляции честно уходят в ручной ввод URL — + это не FAIL discovery. +- Запуск — из корня чекаута репо `orchestrator`; clone чекаута — ручной предшаг (канал + дистрибуции — договорённость с поставщиком, §3 LITE_SETUP). +- Язык UX скрипта — русский (паттерн `gen_secrets.py`/`bootstrap_bundle.py`); сообщения + ссылаются на разделы LITE_SETUP.md как на полный канон. +- Интерактивный OAuth-логин claude CLI принципиально не автоматизируем скриптом — + верифицируется результат (читаемость кредов uid'ом контейнера). + +--- + +## 7. Критерии успеха (резюме; детали — 03-acceptance-criteria.md) +- AC-1 одна команда, режимы и идемпотентный повтор; +- AC-2 скан предусловий с честными вердиктами и офером установки; +- AC-3 discovery 0/1/много с выбором и ручным fallback; +- AC-4 интерактивный сбор с немедленной верификацией и секрет-гигиеной; +- AC-5/AC-6 корректная сборка `.env` (обязательные ключи, свежие секреты, когерентные порты, + отказ перетирания); +- AC-7/AC-8 нормативы C-1 (два бота) и §6.4 (branch protection) машинно охраняются; +- AC-9 Lite-контур поднят и здоров; AC-10 проект зарегистрирован кирпичом; +- AC-11/AC-12 exit-коды, resume, stdlib-only/no-delete гигиена; +- AC-13 рантайм байт-в-байт; AC-14 документация обновлена синхронно. + +--- + +## 8. Риски (кратко; детали — 10-tech-risks.md, заполняет архитектор) +- **R-1 Запуск на боевом хосте по ошибке** → ранний guard существующего `.env`/живого + инстанса; read-only диагностика дефолтно безопасна. +- **R-2 Офер установки пакетов** — вмешательство в систему заказчика → только явное согласие, + печать точной команды; неопределимый пакетный менеджер → manual-step. +- **R-3 SQL-вставка webhook в Postgres Plane** (Path Б §5.4) инвазивна → только согласие + + подтверждённый пользователем контейнер БД + верификация; UI-путь предпочтителен. +- **R-4 Дрейф скрипта от канона LITE_SETUP** (двойной источник истины) → норматив same-PR + + анти-дрейф тесты (ключи ⊂ `.env.example`, кирпичи, упоминание скрипта в доке). +- **R-5 Интерактивность против автоматизации** (CI/non-TTY: риск зависания) → инжектируемый + I/O, честный отказ/неинтерактивная альтернатива в non-TTY. +- **R-6 Ложноположительный discovery** (чужой контейнер опознан как Plane) → выбор всегда за + пользователем, ручной ввод всегда доступен, токен-верификация всё равно обязательна. diff --git a/docs/work-items/ORCH-104/02-trz.md b/docs/work-items/ORCH-104/02-trz.md new file mode 100644 index 0000000..6a7d1f0 --- /dev/null +++ b/docs/work-items/ORCH-104/02-trz.md @@ -0,0 +1,271 @@ +--- +work_item: ORCH-104 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-11 +model_used: claude-opus-4-8 +--- + +# 02 — ТЗ (TRZ): ORCH-104 — Установочный скрипт Lite-тиража (интерактивный installer) + +Work Item: **ORCH-104** · Repo: **orchestrator** · Стадия: analysis + +> ТЗ описывает **что** должно измениться и **где** (модули/контракты/артефакты). **Как** +> (имя/режимы скрипта, эвристики discovery, степень автоматизации отдельных шагов) — решает +> архитектор в `06-adr/`. Тип изменения — **scripts + docs + tests** (паттерн ORCH-009/103): +> рантайм `src/**` байт-в-байт. + +--- + +## 1. Сводка изменения + +Ввести **единый операторский установочный скрипт Lite-тиража**: один файл в `scripts/`, +который интерактивно проводит внешнего оператора по маршруту `docs/deployment/LITE_SETUP.md` +§2–§12 — сканирует предусловия хоста и предлагает доустановить недостающее, обнаруживает +инсталляции Plane/Gitea (при нескольких — даёт выбрать), запрашивает обязательные ключи в +момент установки с немедленной верификацией, автодетектит хост-параметры, собирает +`.env`/`.env.watchdog` от канонов, поднимает Lite-контур, регистрирует проект заказчика +кирпичом `onboard_project.py` и выдаёт итоговый вердикт PASS/MANUAL/FAIL с exit-кодами +`0/2/1`. Паттерн — `bootstrap_bundle.py` (ORCH-103): step-движок check→ensure, stdlib-only, +no-delete, manual-checkpoint с верификацией, идемпотентный повтор. Канон LITE_SETUP.md не +форкается — скрипт становится в нём рекомендованным быстрым путём, ручной маршрут сохраняется. + +--- + +## 2. Задействованные модули / пути + +| Путь | Действие | Роль в задаче | +|------|----------|---------------| +| `scripts/setup_lite.py` *(имя-кандидат; финал — архитектор)* | **создать** | единый установочный CLI: step-движок, скан, discovery, интерактивный сбор, сборка конфигов, запуск, отчёт | +| `scripts/gen_secrets.py` | переиспользовать, **не менять** | кирпич выпуска webhook-секретов (субпроцесс — паттерн AC-7 ORCH-103: канон-знания только субпроцессами кирпичей) | +| `scripts/onboard_project.py` | переиспользовать, **не менять** | кирпич регистрации проекта: Plane-проект + 22 статуса + лейблы, Gitea-репо + webhook, merged-`ORCH_PROJECTS_JSON` | +| `docs/deployment/LITE_SETUP.md` | **обновить** | скрипт = рекомендованный быстрый путь; ручной маршрут остаётся каноном-fallback; размещение не ломает пиннинг «13 разделов в порядке» ЛИБО тест обновляется синхронно | +| `tests/test_setup_lite_script.py` *(имя-кандидат)* | **создать** | анти-дрейф + unit чистых функций (по образцу `tests/test_bootstrap_script.py`) | +| `tests/test_lite_setup_doc.py` | обновить **при необходимости** | если меняется пиннингуемая структура LITE_SETUP.md (разделы/кирпичи) | +| `CHANGELOG.md` | обновить | запись `feat:` | +| `docs/overview/` | обновить **при необходимости** | если витрина описывает маршрут Lite-тиража (таблица соответствия — в индексе витрины, правило агентов №2/ORCH-011) | +| `src/**`, `docker-compose.yml`, `Dockerfile`, `.env.example`, `.env.watchdog.example` | **НЕ менять** | каноны-источники: скрипт их только читает (шаблоны env, состав сервисов) | + +--- + +## 3. Функциональные требования + +### FR-1 — Единая точка входа, режимы, идемпотентность (BR-1, BR-5) +- Один файл; запуск `python3 scripts/setup_lite.py …` из корня чекаута репо `orchestrator` + на голом python3 (до venv и до `docker compose up`). +- Обязательны как минимум: **read-only режим диагностики** (ноль мутаций: скан предусловий + + discovery + план шагов — аналог `plan` ORCH-103) и **установочный интерактивный режим** + (аналог `apply`); желателен `verify` (read-only пост-проверка). Дефолтный режим и имена — + решение архитектора (OQ-1) с учётом паттерна D5 ORCH-009/103 («plan — дефолт») и + бизнес-цели «одна команда». +- Step-движок **check→ensure**: каждый шаг сперва проверяет «уже сделано?» и при PASS + пропускается → повторный запуск идемпотентен; «resume» после manual-step = просто повторный + запуск (паттерн `bootstrap_bundle.run_apply`). +- **Exit-коды (контракт):** `0` — все шаги PASS; `2` — остановка на manual-step / + незавершённое предусловие; `1` — ошибка. +- Каждая мутация хоста — с **явного согласия** пользователя (per-action consent); отказ от + согласия → честный MANUAL-шаг с печатью эквивалентной команды, не молчаливый пропуск. + +### FR-2 — Скан предусловий с офером установки (BR-2; LITE_SETUP §2, §7) +- Проверяемый перечень (каждый пункт — отдельный вердикт `OK | MISSING | WARN | MANUAL`): + 1. ОС/арх: `uname -sm` = Linux x86_64 (иное → WARN «вне контура Lite»); + 2. `docker --version`, `docker compose version` (v2), `git --version`, `python3 --version`, + `node --version`; + 3. дистрибутив claude-code (`npm root -g` → каталог `@anthropic-ai/claude-code`) и + аутентификация CLI (читаемость `~/.claude/.credentials.json` uid'ом из п.5); + 4. группа docker (`getent group docker` → gid для `ORCH_DOCKER_GID`); + 5. uid/gid пользователя-владельца и каталога репозиториев (`ORCH_HOST_REPOS_DIR`, + инвариант ORCH-040: владелец = `ORCH_RUN_UID:ORCH_RUN_GID`); + 6. каталог ssh-ключей (`ORCH_HOST_SSH_DIR`): существование/ключи; + 7. свободность портов (прод/staging, дефолты 8500/8501). +- Для каждого `MISSING`: определить пакетный менеджер хоста (например apt/dnf/yum/zypper — + точный набор фиксирует архитектор) → предложить **конкретную команду установки** → выполнить + её ТОЛЬКО с явного согласия; для node+claude-code — офер `npm install -g + @anthropic-ai/claude-code`; для ssh-ключей — офер `ssh-keygen` (+ печать pubkey как + manual-step «добавить в Gitea», §2.4/§6.2). +- Неопределимый дистрибутив/менеджер, отказ пользователя, sudo-недоступность → честный + **MANUAL** с готовыми командами и ссылкой на § LITE_SETUP; молчаливый пропуск запрещён. +- Интерактивный OAuth-логин claude CLI скрипт **не выполняет** — только верифицирует + результат (§7.2) и выдаёт manual-step. + +### FR-3 — Discovery инсталляций Plane/Gitea (BR-4) +- Источник — **локальный Docker**: перечисление контейнеров, группировка в «инсталляции» по + метке compose-проекта (`com.docker.compose.project`), опознание по именам образов + (Plane: `makeplane/*`-семейство; Gitea: `gitea/*`) и published-портам; точные + эвристики/паттерны — ADR. Из кандидата выводятся **предлагаемые** URL + (`ORCH_PLANE_API_URL`/`ORCH_PLANE_WEB_URL`; `ORCH_GITEA_URL`/`ORCH_GITEA_PUBLIC_URL`). +- Поведение по числу найденных: **0** → ручной ввод URL + честная подсказка «Lite не + устанавливает Plane/Gitea; нет инфраструктуры — см. Bundled (BUNDLED_SETUP.md)»; **1** → + префилл по умолчанию (с подтверждением); **≥2** → нумерованный список (проект, образы, + порты) + выбор. Пункт **«ввести вручную»** доступен всегда. +- **Best-effort, never-block:** docker недоступен / ошибка перечисления / не-docker + инсталляция (native/k8s) → ручной ввод, не FAIL. +- Discovery заполняет только кандидаты URL; **токены всегда вводит пользователь** (FR-4); + выбранный кандидат всё равно проходит верификацию FR-4. + +### FR-4 — Интерактивный сбор обязательных ключей с немедленной верификацией (BR-3) +- Покрывается карта обязательных ключей нового хоста — **§4.2 LITE_SETUP** (группы: Plane, + Gitea, webhook-секреты, Telegram, `ORCH_PROJECTS_JSON`, хост-параметры, порты). +- Секретные значения вводятся **скрыто** (`getpass`-класс ввода) и **никогда не печатаются** + (ни в stdout, ни в лог; только имена ключей) — паттерн NFR-3 ORCH-103. +- Немедленная верификация каждого введённого значения фактическим вызовом: + - Plane: `GET $ORCH_PLANE_API_URL/api/v1/workspaces//projects/` c `X-API-Key` → 200; + - Gitea: `GET $ORCH_GITEA_URL/api/v1/user` c токеном → 200 (+ владелец → `ORCH_GITEA_OWNER`); + - Telegram: `getMe` → `"ok":true`; helper определения chat-id через `getUpdates`; + - публичный URL оркестратора (для webhook'ов) — синтаксическая валидация + предупреждение, + что достижимость со стороны Plane/Gitea проверится на smoke. +- Неуспех верификации → re-prompt с диагнозом (ограниченное число попыток — паттерн + `manual_checkpoint(max_tries=3)`), затем честный MANUAL/остановка exit 2 — не бесконечный + цикл и не молчаливое принятие. +- **non-TTY** (нет интерактива): честный отказ с подсказкой ЛИБО неинтерактивная альтернатива + (флаги/answers-file — механизм решает архитектор, OQ-2); зависание недопустимо. + +### FR-5 — Автодетект хост-параметров и когерентность портов (BR-2, BR-6) +- Автоматически определяются и НЕ спрашиваются у пользователя (только подтверждение): + `ORCH_RUN_UID`/`ORCH_RUN_GID` (uid/gid владельца `ORCH_HOST_REPOS_DIR` / текущего + пользователя), `ORCH_DOCKER_GID` (`getent group docker`), `ORCH_HOST_NODE_BIN` + (`which node`), `ORCH_HOST_CLAUDE_CODE_DIR` (`npm root -g`), `ORCH_AGENT_HOME_DIR` / + `ORCH_HOST_CLAUDE_DIR` / `ORCH_HOST_CLAUDE_JSON` (HOME пользователя из §2.2), + `ORCH_DEPLOY_HOST_REPO_PATH` (корень чекаута). +- Порты: busy-check (`ss`/socket) прод- и staging-портов; занят → предложить альтернативу. + Смена прод-порта → **синхронно** согласовать тройку `ORCH_DEPLOY_PROD_TARGET_PORT` ⇄ + `WATCHDOG_METRICS_URL` ⇄ `ORCH_POST_DEPLOY_BASE_URL` (§2.5/§4.2). `ORCH_STAGING_PORT` == + прод-порт → **отказ fail-closed** (инвариант ORCH-058, усилен ORCH-101). + +### FR-6 — Сборка `.env` / `.env.watchdog` (BR-5; LITE_SETUP §4) +- `.env` собирается **от канона `.env.example`** (принцип ORCH-101: дефолт = боевое значение; + записываются только собранные отличия нового хоста). `.env.watchdog` — от + `.env.watchdog.example` (ключи `WATCHDOG_TG_BOT_TOKEN`/`WATCHDOG_TG_CHAT_ID` кладутся + **только туда** — ловушка файла-носителя §4.3). +- Webhook-секреты — **кирпичом `gen_secrets.py`** (свежий выпуск; боевые секреты не + используются — stateless §12 / REPLICATION §3). +- **Существующий `.env`/`.env.watchdog` → отказ** (exit 2, внятное сообщение); перезапись — + только явный force-флаг (паттерн `gen_secrets --force` / `--force-secrets` ORCH-103). + Подсказки-дефолты промптов — из `.env.example`/автодетекта, **никогда** из боевых значений. +- После сборки — резолв-проверка `docker compose config` (PASS/FAIL, §4 «Проверка»). + +### FR-7 — Подключения и машинная охрана нормативов (BR-3, BR-6) +- **Plane (§5):** наличие workspace/доступность API верифицируются (FR-4); статусы/лейблы + вручную НЕ создаются (только кирпич onboarding, §5.3). **Webhook (§5.4, каверза Plane CE):** + Path A (UI) = manual-checkpoint с инструкцией и верификацией; Path Б (SQL в Postgres Plane) + = только с **явного согласия** + подтверждённый пользователем контейнер БД Plane + + пост-верификация (`SELECT url, is_active FROM webhooks`); выбор степени автоматизации — ADR + (OQ-3). Молчаливый пропуск запрещён. +- **Gitea (§6):** токен верифицирован (`/api/v1/user`); **branch protection на `main`:** + непустой `branch_protections` → **FAIL шага с лечением** (норматив §6.4 / ADR D10 ORCH-009 / + INV-4); скрипт правила **сам не удаляет** (no-delete) — только инструкция. Per-repo webhook + и репо создаёт кирпич onboarding (FR-9), не сам скрипт. +- **Telegram (§8):** два независимых бота; **идентичные токены орка и watchdog → отказ шага** + (C-1 ORCH-100 «ЗАПРЕЩЕНО» — машинная проверка, не примечание). +- **LLM (§7):** верификация дистрибутива/нод-бинаря/читаемости кредов uid'ом контейнера; + наличие `ORCH_AGENT_MODEL_DEFAULT`/`ORCH_AGENT_EFFORT_DEFAULT` в собранном `.env` (§7.3). + +### FR-8 — Запуск Lite-контура и health (BR-6; LITE_SETUP §9, §12) +- С согласия: `docker compose up -d --build`; проверка состава: запущены **ровно** + `orchestrator` + `orchestrator-watchdog`, `orchestrator-staging` НЕ поднят (строго за + `profiles: [staging]`). +- Health-чек контрактов: `/health` → 200 `"status":"ok"`; `/queue` → штатный JSON; + `/metrics` → `"schema_version": 1` (порт — фактический из конфига). +- Stateless-проверка чистоты (§12): счётчики jobs нулевые, ни одной задачи чужих проектов; + нарушение → FAIL с лечением «пересобрать `data/` с нуля». + +### FR-9 — Регистрация проекта заказчика кирпичом onboarding (BR-6, BR-7; LITE_SETUP §10) +- Скрипт собирает параметры проекта (имя/описание/repo/prefix/stack/test-cmd/порты/ + webhook-url из публичного URL оркестратора) и строит аргументы вызова + `onboard_project.py` **детерминированной чистой функцией** (тестируемо без сети). +- Последовательность: `plan` → показать пользователю → согласие → `apply` → `verify` + (субпроцессы; exit 2 кирпича = остались ручные шаги → транслировать как MANUAL). + Степень автоматизации vs «печать готовой команды» — ADR (OQ-6; прецедент драйва — + `bootstrap_bundle.step_onboard`). +- `ORCH_PROJECTS_JSON` из отчёта `apply` вписывается в `.env` (с согласия); затем управляемый + рестарт **только собственного свежеподнятого** контура по процедуре §10: проверка тихого + окна (`/queue` без running-job) → `docker compose up -d --force-recreate orchestrator` → + пост-проверка `/health`+`/queue`. + +### FR-10 — Итоговый отчёт (BR-1, BR-6) +- Финальная сводная таблица всех шагов: PASS/FAIL/MANUAL; для каждого MANUAL — что сделать и + ссылка на § LITE_SETUP; для FAIL — диагноз и лечение (зеркало §13 траблшутинга). +- Smoke первой задачи (§11: issue → «To Analyse» → артефакты `01–04`) выдаётся как + завершающая manual-инструкция (вердикт «тираж PASS» — за оператором). + +### FR-11 — Документация: канон не форкается (BR-7) +- `docs/deployment/LITE_SETUP.md`: скрипт вводится как **рекомендованный быстрый путь** + (врезка/подраздел в начале маршрута); ручной маршрут §2–§13 сохраняется как канон и + fallback для MANUAL-шагов. Размещение либо не ломает пиннинг + `test_doc_exists_with_all_13_sections_in_order`, либо тест обновляется в том же PR. +- Норматив сопровождения (NFR-5 ORCH-102) расширяется симметрично: «меняешь шаги тиража → + обнови LITE_SETUP.md **и установочный скрипт** в том же PR». + +--- + +## 4. Изменения API +**Нет.** Эндпоинты оркестратора не добавляются и не меняются; скрипт — потребитель +существующих read-only контрактов (`/health`, `/queue`, `/metrics`) и внешних API +(Plane/Gitea/Telegram). + +## 5. Изменения схемы БД +**Нет.** БД оркестратора не затрагивается (создаётся пустой при первом старте — штатно); +БД Plane заказчика — только опциональная webhook-вставка Path Б §5.4 (FR-7, с согласия). + +## 6. Требования к новым/изменённым QG checks +**Нет.** Скрипт — операторский инструмент вне рантайма и вне конвейера; +`STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict ключи — байт-в-байт. + +## 7. Совместимость / регресс +- **Kill-switch не нужен:** активация — только явный запуск CLI человеком (паттерн + ORCH-009/102/103); рантайм не несёт ни одной новой кодовой ветки. +- Полный существующий регресс `pytest tests/ -q` остаётся зелёным; `test_lite_setup_doc.py` — + зелёный (с синхронным обновлением при правке структуры дока). +- Обратимость: задача добавляет файлы `scripts/` + `tests/` + правку дока; откат = revert PR, + поведение платформы не меняется в обе стороны. +- Скрипт не вносит хост-литералов в `src/**` (вне скана `test_no_host_hardcodes.py` по + определению, т.к. `src/**` не трогается). + +## 8. Конфигурация +Новых ключей `Settings`/`.env.example` **не вводится**. Скрипт читает каноны +`.env.example`/`.env.watchdog.example` как шаблоны и пишет целевые `.env`/`.env.watchdog` +(FR-6). Платформенные константы (`SELF_HOSTING_REPO="orchestrator"`, имена сервисов, layout +контейнера) не параметризуются (норматив REPLICATION §1). + +## 9. Наблюдаемость +- Структурный stdout-лог прогона (шаг → вердикт → диагноз) + итоговая таблица (FR-10). +- Exit-код = машинный итог прогона (`0/2/1`) — пригоден для обвязки/CI заказчика. +- Опциональный файл-отчёт прогона — на усмотрение архитектора (ADR). + +## 10. Артефакты pipeline (создать/обновить в ТОМ ЖЕ PR) +- `docs/work-items/ORCH-104/06-adr/ADR-001-.md` — решения: имя/режимы скрипта, + эвристики discovery, политика оферов установки, автоматизация webhook Path Б и onboarding, + механизм non-TTY (архитектор). +- `docs/deployment/LITE_SETUP.md` — быстрый путь (FR-11). +- `tests/test_setup_lite_script.py` (+ правка `tests/test_lite_setup_doc.py` при + необходимости). +- `CHANGELOG.md` — запись `feat:`; витрина `docs/overview/` — если затронуто описание тиража. + +## 11. Инварианты (не нарушать) +- `src/**`, корневой `docker-compose.yml`, `Dockerfile`, `STAGE_TRANSITIONS`, `QG_CHECKS`, + `check_*`, machine-verdict ключи, схема БД — **байт-в-байт**. +- **stdlib-only**; модули платформы не импортируются; канон-знания — только субпроцессами + кирпичей (`gen_secrets.py`, `onboard_project.py`). +- **No-delete:** ни одной удаляющей операции (файлы, контейнеры, ветки, правила) — лечение + всегда инструкцией. +- **Никогда** не push/force-push/иные операции в `main` (INV-4); не рестартить чужие/боевые + контейнеры; мутации — только с явного согласия. +- Секреты: свежие, скрытый ввод, не печатаются; существующие `.env*` молча не перетираются. +- Stateless: данные/задачи/секреты исходного хоста не переносятся ни одним шагом. + +## 12. Открытые вопросы для архитектора (не блокируют анализ) +- **OQ-1:** имя скрипта и набор/дефолт режимов: строгий паттерн D5 (`plan` — дефолт, + мутации только в `apply`) vs «запуск без аргументов = wizard» (бизнес-цель «одна команда»). + Возможный компромисс: wizard-дефолт, где фаза скана всегда read-only, а мутации — за + per-action consent. +- **OQ-2:** механизм неинтерактивного прогона (флаги / answers-file / env-переменные) и + точное поведение в non-TTY. +- **OQ-3:** webhook Plane: автоматизировать ли Path Б (SQL) или строго manual-checkpoint; + критерий выбора/подтверждения контейнера Postgres Plane. +- **OQ-4:** политика оферов установки: исполнять команды пакетного менеджера из скрипта + (с согласия) vs только печатать (безопаснее); набор поддерживаемых менеджеров. +- **OQ-5:** размещение быстрого пути в LITE_SETUP.md (врезка в §1 vs новый раздел с правкой + пиннинга «13 разделов») и судьба нумерации. +- **OQ-6:** степень драйва onboarding: субпроцесс plan→apply→verify изнутри скрипта + (прецедент `bootstrap_bundle.step_onboard`) vs печать готовой команды. diff --git a/docs/work-items/ORCH-104/03-acceptance-criteria.md b/docs/work-items/ORCH-104/03-acceptance-criteria.md new file mode 100644 index 0000000..915216d --- /dev/null +++ b/docs/work-items/ORCH-104/03-acceptance-criteria.md @@ -0,0 +1,206 @@ +--- +work_item: ORCH-104 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-11 +model_used: claude-opus-4-8 +--- + +# 03 — Критерии приёмки (Acceptance Criteria): ORCH-104 — Установочный скрипт Lite-тиража + +Work Item: **ORCH-104** · Repo: **orchestrator** · Стадия: analysis + +Формат: каждый критерий — чёткое условие **PASS/FAIL**. Поведенческие критерии проверяются +детерминированными pytest-тестами (чистые функции / инжектируемый I/O / tmp-файлы — без +реальных TTY/сети/docker); структурные — по файлам репозитория. Имя скрипта ниже — +`scripts/setup_lite.py` (кандидат; если архитектор финализирует иное имя, критерии читаются +с фактическим именем 1:1). + +--- + +## AC-1 — Единая точка входа, режимы, идемпотентный повтор + +**Условие:** скрипт существует и реализует контракт FR-1. +- **PASS:** один файл в `scripts/`; запускается на голом python3 из корня чекаута; существует + read-only режим диагностики (ноль мутаций ФС/docker/сети-мутаций); step-движок check→ensure: + на «уже выполненном» хосте шаги дают каскад skip (повторный запуск не перевыполняет + мутации); manual-step останавливает прогон с exit 2, повторный запуск продолжает с первого + незавершённого шага. +- **FAIL:** установка по-прежнему требует ручного прохода LITE_SETUP без скрипта; повторный + запуск перевыполняет мутации или ломает состояние; read-only режима нет. + +--- + +## AC-2 — Скан предусловий: честные вердикты + офер установки + +**Условие:** функция вердиктов предусловий (FR-2) на фикстурах фактов хоста. +- **PASS:** полный набор фактов (всё установлено) → все пункты `OK`, блокеров нет; факт + «docker отсутствует» → вердикт `MISSING` с конкретной командой установки и запросом + согласия (инжектированный отказ → шаг `MANUAL` с печатью команды, мутация не выполняется); + неопределимый пакетный менеджер → `MANUAL` с готовыми командами и ссылкой на § LITE_SETUP; + не-Linux/не-x86_64 → `WARN` «вне контура Lite». Ни один пункт перечня FR-2 не пропускается + молча. +- **FAIL:** отсутствующее предусловие даёт ложный `OK`/молчаливый пропуск; установка + выполняется без явного согласия; неподдерживаемое окружение роняет скрипт вместо MANUAL. + +--- + +## AC-3 — Discovery Plane/Gitea: 0 / 1 / много, ручной ввод всегда + +**Условие:** классификатор discovery (FR-3) на фикстурах перечня docker-контейнеров. +- **PASS:** фикстура с двумя независимыми Plane-инсталляциями (разные compose-проекты) → + ровно 2 кандидата, пользователю показан нумерованный список и выбор применён; одна + инсталляция → её URL предложен по умолчанию (с подтверждением); ноль → ручной ввод URL + + подсказка про Bundled; ошибка/недоступность docker → ручной ввод без падения (never-block); + пункт «ввести вручную» присутствует при любом числе кандидатов; посторонние образы в + кандидаты не попадают. +- **FAIL:** при ≥2 кандидатах выбор сделан автоматически без пользователя; при 0 кандидатах + скрипт падает/блокируется; чужой контейнер опознан как кандидат без возможности отказаться. + +--- + +## AC-4 — Интерактивный сбор: немедленная верификация + секрет-гигиена + +**Условие:** цикл запроса значения (FR-4) с инжектированным I/O и замоканной верификацией. +- **PASS:** неуспешная верификация (например, Plane-токен → 401) даёт re-prompt с диагнозом; + после исчерпания лимита попыток — честный MANUAL/остановка exit 2 (не бесконечный цикл); + успешная верификация → значение принято; секретные значения запрашиваются скрытым вводом и + не появляются ни в stdout-транскрипте, ни в отчёте (только имена ключей); non-TTY-запуск + без неинтерактивной альтернативы → честный отказ с подсказкой, не зависание. +- **FAIL:** значение секрета напечатано/залогировано; неверный токен принят без верификации; + бесконечный цикл re-prompt; зависание в non-TTY. + +--- + +## AC-5 — Сборка `.env`/`.env.watchdog`: канон, свежие секреты, отказ перетирания + +**Условие:** рендер конфигов (FR-6) на tmp-каталоге. +- **PASS:** собранный `.env` содержит все группы обязательных ключей §4.2 LITE_SETUP + (Plane, Gitea, webhook-секреты, Telegram, `ORCH_PROJECTS_JSON` — допустим как отложенный + MANUAL до FR-9, хост-параметры, порты), а собранный `.env.watchdog` — оба ключа + `WATCHDOG_TG_*` (и они НЕ требуются в `.env`); webhook-секреты — свежевыпущенные кирпичом + `gen_secrets` (64 hex, при повторном выпуске значения различаются); существующий + `.env`/`.env.watchdog` → отказ с exit 2 без force-флага, файл не изменён байт-в-байт; + подсказки-дефолты промптов не содержат боевых значений исходного хоста. +- **FAIL:** молчаливая перезапись существующего файла; обязательный ключ отсутствует без + явной MANUAL-отметки; секреты скопированы/захардкожены вместо свежего выпуска. + +--- + +## AC-6 — Порты: busy-check, когерентная тройка, staging ≠ prod + +**Условие:** логика портов (FR-5). +- **PASS:** занятый прод-порт → предложена альтернатива; смена прод-порта → в собранном + `.env` синхронно согласованы `ORCH_DEPLOY_PROD_TARGET_PORT` ⇄ `WATCHDOG_METRICS_URL` ⇄ + `ORCH_POST_DEPLOY_BASE_URL`; ввод `ORCH_STAGING_PORT` равного прод-порту → отказ + fail-closed (значение не принято). +- **FAIL:** тройка рассинхронизирована; совпадение staging/prod принято молча. + +--- + +## AC-7 — Норматив C-1: два независимых Telegram-бота + +**Условие:** шаг Telegram (FR-7) получает одинаковые токены для орка и watchdog. +- **PASS:** шаг отказывает с явным объяснением запрета (C-1 ORCH-100) и требует другой токен; + различные токены (оба `getMe` ok) → PASS шага. +- **FAIL:** одинаковые токены приняты молча. + +--- + +## AC-8 — Норматив §6.4: branch protection на `main` — честный FAIL без удаления + +**Условие:** шаг Gitea (FR-7) при непустом списке `branch_protections` репо (замокан). +- **PASS:** шаг даёт FAIL с лечением (текст норматива §6.4: правила удалить, иначе ложные + HOLD); скрипт НЕ выполняет удаление правил сам (no-delete); пустой список → PASS шага. +- **FAIL:** непустые правила пропущены молча; скрипт сам удалил правила. + +--- + +## AC-9 — Запуск Lite-контура: ровно два контейнера + health + +**Условие:** шаг запуска (FR-8) на замоканных `compose`/HTTP-примитивах. +- **PASS:** последовательность вызовов соответствует §9: `up -d --build` только после + согласия; проверка состава констатирует ровно `orchestrator` + `orchestrator-watchdog` + (поднятый `orchestrator-staging` или третий сервис → FAIL шага); health-чек требует + `/health` 200 `"status":"ok"`, `/queue` JSON, `/metrics` `schema_version: 1`; + stateless-проверка отмечает чужие задачи/ненулевые счётчики как FAIL с лечением §12. +- **FAIL:** контур поднят без согласия; состав не проверяется; нездоровый инстанс получает + PASS. + +--- + +## AC-10 — Регистрация проекта: строго кирпич onboarding + +**Условие:** шаг регистрации (FR-9). +- **PASS:** аргументы `onboard_project.py` построены чистой функцией из собранных ответов + (unit-проверяемо); последовательность `plan` → согласие → `apply` → `verify`; exit 2 + кирпича транслируется как MANUAL (не как успех); `ORCH_PROJECTS_JSON` из отчёта `apply` + вписывается в `.env`; рестарт собственного контура — только после проверки тихого окна + (`/queue` без running-job). Скрипт сам НЕ создаёт статусы/лейблы/репо/webhook мимо кирпича. +- **FAIL:** скрипт дублирует канон-знания кирпича (свой список статусов/лейблов); `apply` + вызван без показа плана/согласия; реестр не доведён до `.env` без MANUAL-отметки. + +--- + +## AC-11 — Контракт exit-кодов и resume + +**Условие:** прогоны step-движка на фикстурах (все шаги ok / есть manual / есть ошибка). +- **PASS:** все PASS → exit 0; остановка на manual-step / незавершённое предусловие → exit 2; + ошибка → exit 1; после exit 2 повторный запуск продолжает с первого незавершённого шага. +- **FAIL:** коды перепутаны/неразличимы; manual-step считается успехом. + +--- + +## AC-12 — Гигиена скрипта (структурно, по образцу test_bootstrap_script.py) + +**Условие:** статический анализ файла скрипта. +- **PASS:** импорты — только python stdlib (ast-скан); модули платформы (`src.*`) не + импортируются; delete-операций нет (эвристический скан: `rm -rf`, `compose down -v`, + `DELETE`-вызовы API, `git push --delete` и т.п.); импорт модуля не имеет side effects; + значения секретов не печатаются (нет f-string/print с переменными секретов); канонические + кирпичи (`gen_secrets.py`, `onboard_project.py`, `LITE_SETUP.md`) упомянуты/используются. +- **FAIL:** любой пункт нарушен. + +--- + +## AC-13 — Рантайм байт-в-байт + +**Условие:** диф задачи. +- **PASS:** `src/**`, корневой `docker-compose.yml`, `Dockerfile`, `.env.example`, + `.env.watchdog.example`, схема БД, `STAGE_TRANSITIONS`/`QG_CHECKS`/`check_*` — не изменены; + полный регресс `pytest tests/ -q` зелёный. +- **FAIL:** любой из перечисленных путей/контрактов изменён. + +--- + +## AC-14 — Документация синхронна (канон не форкается) + +**Условие:** состояние доков в том же PR. +- **PASS:** `docs/deployment/LITE_SETUP.md` вводит скрипт как рекомендованный быстрый путь и + сохраняет ручной маршрут; `tests/test_lite_setup_doc.py` зелёный (при изменении + пиннингуемой структуры — обновлён в том же PR); `CHANGELOG.md` несёт запись; витрина + `docs/overview/` обновлена, если её описание тиража затронуто. +- **FAIL:** скрипт добавлен, а LITE_SETUP.md не упоминает его / doc-тест красный / + CHANGELOG пуст. + +--- + +## Сводная матрица AC ↔ BR/FR + +| AC | FR | BR | Тип проверки | +|----|----|----|--------------| +| AC-1 | FR-1 | BR-1, BR-5 | unit (step-движок, парсер режимов) + структурный | +| AC-2 | FR-2 | BR-2 | unit (вердикты на фикстурах фактов) | +| AC-3 | FR-3 | BR-4 | unit (классификатор discovery на фикстурах docker) | +| AC-4 | FR-4 | BR-3 | unit (инжектируемый I/O, замоканная верификация) | +| AC-5 | FR-6 | BR-3, BR-5 | unit + tmp-ФС | +| AC-6 | FR-5 | BR-2, BR-6 | unit (чистая функция когерентности) | +| AC-7 | FR-7 | BR-6 | unit | +| AC-8 | FR-7 | BR-6, BR-7 | unit (замоканный Gitea API) | +| AC-9 | FR-8 | BR-6 | unit (замоканные compose/HTTP) | +| AC-10 | FR-9 | BR-6, BR-7 | unit (builder аргументов, трансляция exit-кодов) | +| AC-11 | FR-1, FR-10 | BR-1, BR-5 | unit (контракт кодов, resume) | +| AC-12 | — | BR-5, BR-7, NFR-1/2/3 | структурный (ast/эвристики) | +| AC-13 | — | BR-8 | структурный + полный регресс | +| AC-14 | FR-11 | BR-7 | структурный (doc-тесты) | diff --git a/docs/work-items/ORCH-104/04-test-plan.yaml b/docs/work-items/ORCH-104/04-test-plan.yaml new file mode 100644 index 0000000..4a9b401 --- /dev/null +++ b/docs/work-items/ORCH-104/04-test-plan.yaml @@ -0,0 +1,197 @@ +work_item: ORCH-104 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-11 +model_used: claude-opus-4-8 +title: "Установочный скрипт Lite-тиража: интерактивный installer (setup_lite)" +framework: pytest +scope: > + Новый операторский CLI scripts/setup_lite.py (имя-кандидат) + обновление + docs/deployment/LITE_SETUP.md + анти-дрейф тесты. Вне покрытия: реальные + TTY/сеть/docker/пакетные менеджеры (всё мокается/инжектируется), установка + Plane/Gitea (вне объёма Lite), сквозной прогон на живом хосте (ручной smoke + по LITE_SETUP §11 силами оператора/deploy-staging). +notes: > + Принципы (паттерн tests/test_bootstrap_script.py): вся решающая логика — чистые + функции (вердикты предусловий, классификатор discovery, когерентность портов, + рендер env, builder аргументов onboarding, step-движок), тестируемые без + TTY/сети/docker; интерактив — через инжектируемый I/O (скриптованные ответы); + файловые сценарии — на tmp_path; структурная гигиена — ast/эвристики по файлу + скрипта. Полный существующий регресс tests/ обязан остаться зелёным; имя модуля + скрипта в тестах — фактическое (если архитектор финализирует иное имя). + +tests: + # ---------- AC-1 / FR-1: точка входа, режимы, step-движок ---------- + - id: TC-01 + type: unit + description: "Парсер CLI: существует read-only режим диагностики (ноль мутаций) и установочный режим; набор режимов закрыт; контракт соответствует ADR (паттерн plan/apply/verify ORCH-103)" + module: tests/test_setup_lite_script.py + expected: PASS + + - id: TC-02 + type: unit + description: "Step-движок check→ensure: шаг с уже истинным check пропускается (skip) без вызова ensure; повторный прогон по фикстуре 'всё выполнено' — каскад skip, ни одной повторной мутации" + module: tests/test_setup_lite_script.py + expected: PASS + + - id: TC-03 + type: unit + description: "Resume: прогон останавливается на manual-step (exit 2); повторный запуск продолжает с первого незавершённого шага, выполненные не перевыполняются" + module: tests/test_setup_lite_script.py + expected: PASS + + # ---------- AC-2 / FR-2: скан предусловий и офер установки ---------- + - id: TC-04 + type: unit + description: "Вердикты предусловий: полный набор фактов хоста (docker/compose v2/git/python3/node/claude-code/креды/docker-группа/uid-gid/ssh/порты) → все OK, блокеров нет" + module: tests/test_setup_lite_script.py + expected: PASS + + - id: TC-05 + type: unit + description: "Факт 'docker отсутствует' → вердикт MISSING с конкретной командой установки под детектированный пакетный менеджер; инжектированный отказ от согласия → шаг MANUAL, команда напечатана, мутация НЕ выполнена" + module: tests/test_setup_lite_script.py + expected: PASS + + - id: TC-06 + type: unit + description: "Неопределимый пакетный менеджер → честный MANUAL с готовыми командами и ссылкой на § LITE_SETUP (не молчаливый пропуск, не падение); uname != Linux x86_64 → WARN 'вне контура Lite'" + module: tests/test_setup_lite_script.py + expected: PASS + + # ---------- AC-3 / FR-3: discovery Plane/Gitea ---------- + - id: TC-07 + type: unit + description: "Классификатор discovery: фикстура docker-перечня с ДВУМЯ Plane-инсталляциями (разные compose-проекты) → ровно 2 кандидата с проектом/образами/портами; выбор пользователя применяется; пункт 'ввести вручную' присутствует" + module: tests/test_setup_lite_script.py + expected: PASS + + - id: TC-08 + type: unit + description: "Discovery: одна инсталляция → её URL префилл по умолчанию (с подтверждением); ноль инсталляций → ручной ввод + подсказка про Bundled; посторонние образы (не Plane/не Gitea) в кандидаты не попадают" + module: tests/test_setup_lite_script.py + expected: PASS + + - id: TC-09 + type: unit + description: "Discovery best-effort: ошибка/недоступность docker при перечислении → ручной ввод URL без падения и без блокировки прогона (never-block)" + module: tests/test_setup_lite_script.py + expected: PASS + + # ---------- AC-4 / FR-4: интерактивный сбор + верификация + секрет-гигиена ---------- + - id: TC-10 + type: unit + description: "Цикл запроса с инжектированным I/O: верификация токена падает (401, мок) → re-prompt с диагнозом; после лимита попыток → MANUAL/остановка exit 2, НЕ бесконечный цикл; успешная верификация → значение принято" + module: tests/test_setup_lite_script.py + expected: PASS + + - id: TC-11 + type: unit + description: "Секрет-гигиена: секретные значения запрашиваются скрытым вводом и отсутствуют в stdout-транскрипте и итоговом отчёте (печатаются только имена ключей)" + module: tests/test_setup_lite_script.py + expected: PASS + + - id: TC-12 + type: unit + description: "non-TTY без неинтерактивной альтернативы → честный отказ с подсказкой (детерминированный exit), НЕ зависание на ожидании ввода" + module: tests/test_setup_lite_script.py + expected: PASS + + # ---------- AC-5 / FR-6: сборка .env / .env.watchdog ---------- + - id: TC-13 + type: integration + description: "Рендер на tmp_path: собранный .env содержит все группы обязательных ключей §4.2 LITE_SETUP; .env.watchdog содержит WATCHDOG_TG_BOT_TOKEN/WATCHDOG_TG_CHAT_ID (файл-носитель §4.3); webhook-секреты свежие 64-hex и различаются между прогонами" + module: tests/test_setup_lite_script.py + expected: PASS + + - id: TC-14 + type: integration + description: "Существующий .env (и отдельно .env.watchdog) на tmp_path → отказ exit 2 без force-флага, файл байт-в-байт не изменён; с явным force — перезапись выполняется" + module: tests/test_setup_lite_script.py + expected: PASS + + - id: TC-15 + type: unit + description: "Подсказки-дефолты промптов берутся из .env.example/автодетекта и не содержат боевых значений исходного хоста (ни секретов, ни хост-литералов: переиспользовать FORBIDDEN-набор test_no_host_hardcodes)" + module: tests/test_setup_lite_script.py + expected: PASS + + # ---------- AC-6 / FR-5: порты ---------- + - id: TC-16 + type: unit + description: "Когерентность портов: смена прод-порта → синхронно согласованы ORCH_DEPLOY_PROD_TARGET_PORT ⇄ WATCHDOG_METRICS_URL ⇄ ORCH_POST_DEPLOY_BASE_URL; занятый порт (мок busy-check) → предложена альтернатива" + module: tests/test_setup_lite_script.py + expected: PASS + + - id: TC-17 + type: unit + description: "ORCH_STAGING_PORT == прод-порт → отказ fail-closed (значение не принято; инвариант ORCH-058/101)" + module: tests/test_setup_lite_script.py + expected: PASS + + # ---------- AC-7..AC-8 / FR-7: машинная охрана нормативов ---------- + - id: TC-18 + type: unit + description: "Telegram C-1: одинаковые токены бота орка и watchdog-бота → отказ шага с объяснением запрета; различные валидные (getMe ok, мок) → PASS шага" + module: tests/test_setup_lite_script.py + expected: PASS + + - id: TC-19 + type: unit + description: "Gitea branch protection (мок API): непустой branch_protections на main → FAIL шага с лечением §6.4, БЕЗ попытки удаления правил скриптом; пустой список → PASS" + module: tests/test_setup_lite_script.py + expected: PASS + + - id: TC-20 + type: unit + description: "Webhook Plane Path Б (SQL): выполняется только при явном согласии — инжектированный отказ → MANUAL-чекпоинт с инструкцией UI-пути, мутирующий вызов НЕ произведён (мок); после согласия — обязательная пост-верификация" + module: tests/test_setup_lite_script.py + expected: PASS + + # ---------- AC-9 / FR-8: запуск и health ---------- + - id: TC-21 + type: unit + description: "Шаг запуска (моки compose/HTTP): up только после согласия; состав 'ровно orchestrator + orchestrator-watchdog' → PASS, поднятый staging/третий сервис → FAIL шага; health требует /health 200 ok + /queue JSON + /metrics schema_version 1; чужие задачи в /queue → FAIL stateless-проверки" + module: tests/test_setup_lite_script.py + expected: PASS + + # ---------- AC-10 / FR-9: onboarding-кирпич ---------- + - id: TC-22 + type: unit + description: "Builder аргументов onboard_project.py — чистая функция от собранных ответов (имя/repo/prefix/stack/test-cmd/порты/webhook-url); последовательность plan→согласие→apply→verify; exit 2 кирпича транслируется как MANUAL; скрипт не несёт собственного канона статусов/лейблов" + module: tests/test_setup_lite_script.py + expected: PASS + + # ---------- AC-11 / FR-1, FR-10: exit-коды ---------- + - id: TC-23 + type: unit + description: "Контракт exit-кодов: все шаги PASS → 0; manual-step/незавершённое предусловие → 2; ошибка → 1; коды — именованные константы" + module: tests/test_setup_lite_script.py + expected: PASS + + # ---------- AC-12: структурная гигиена скрипта ---------- + - id: TC-24 + type: unit + description: "ast-скан: импорты скрипта — только python stdlib; модули платформы (src.*) не импортируются; канонические кирпичи gen_secrets.py/onboard_project.py и LITE_SETUP.md упомянуты" + module: tests/test_setup_lite_script.py + expected: PASS + + - id: TC-25 + type: unit + description: "Эвристический скан: delete-операций нет (rm -rf, compose down -v, DELETE-вызовы API, push --delete и т.п.); import модуля скрипта не имеет side effects (ничего не пишет/не запускает)" + module: tests/test_setup_lite_script.py + expected: PASS + + # ---------- AC-13..AC-14: рантайм и документация ---------- + - id: TC-26 + type: integration + description: "Рантайм байт-в-байт: полный существующий регресс pytest tests/ -q зелёный; src/**, корневой docker-compose.yml, Dockerfile, .env.example, .env.watchdog.example задачей не изменены" + module: tests/ (полный регресс) + expected: PASS + + - id: TC-27 + type: unit + description: "LITE_SETUP.md вводит установочный скрипт как рекомендованный быстрый путь (упоминание файла скрипта в доке) и сохраняет ручной маршрут; все проверки test_lite_setup_doc.py зелёные (при изменении пиннингуемой структуры тест обновлён в том же PR)" + module: tests/test_lite_setup_doc.py + expected: PASS