analyst(ET): auto-commit from analyst run_id=638
All checks were successful
CI / test (push) Successful in 58s

This commit is contained in:
2026-06-11 20:19:18 +03:00
parent 831e9ed8d2
commit 94a3f399f2
4 changed files with 875 additions and 0 deletions

View File

@@ -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) → выбор всегда за
пользователем, ручной ввод всегда доступен, токен-верификация всё равно обязательна.

View File

@@ -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/<slug>/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» → артефакты `0104`) выдаётся как
завершающая 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-<slug>.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 печать готовой команды.

View File

@@ -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-тесты) |

View File

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