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