Files
orchestrator/docs/work-items/ORCH-104/06-adr/ADR-001-setup-lite-interactive-installer.md
claude-bot 302a891aff
All checks were successful
CI / test (push) Successful in 59s
architect(ET): auto-commit from architect run_id=639
2026-06-11 20:42:39 +03:00

49 KiB
Raw Blame History

work_item, stage, author_agent, status, created_at, model_used
work_item stage author_agent status created_at model_used
ORCH-104 architecture architect proposed 2026-06-11 claude-opus-4-8

ADR-001: Интерактивный installer Lite-тиража — scripts/setup_lite.py (wizard поверх канона LITE_SETUP)

Work Item: ORCH-104 — Установочный скрипт Lite-тиража (интерактивный installer) Стадия: architecture Сквозная регистрация: docs/architecture/adr/adr-0040-lite-interactive-installer.md (дополняет серию канонов тиража adr-0035…0038: Lite получает исполняемый инструмент; вводится норматив сопровождения «док + скрипт в одном PR» и wizard-контракт consent-gated мутаций, обязательный для будущих операторских CLI платформы).

Статус

Proposed

Контекст

Эпик ORCH-10 закрыт по обоим типам: Type A — Lite документом (ORCH-102: docs/deployment/LITE_SETUP.md, 13 нормативных разделов, ~30+ ручных fenced-команд), Type B — Bundled комплектом + скриптом (ORCH-103: deploy/bundled/ + scripts/bootstrap_bundle.py). Асимметрия: у Lite нет инструмента — порог входа высокий (BRD §1.1: оператор вручную собирает значения, которые машина определяет сама; ошибки конфигурации проявляются поздно). Бизнес-запрос Владельца (BRD §1.2): один установочный файл, который выполняет установку, запрашивает данные в момент установки, сканирует систему с офером доустановки и даёт выбор при нескольких инсталляциях.

Факты, сверенные с репо (не изобретать):

  • Паттерн step-движка готов: scripts/bootstrap_bundle.py (ORCH-103 D5) — режимы plan (дефолт)/apply/verify, check→ensure (повторный запуск = каскад skip), exit 0/2/1, manual_checkpoint(title, instructions, verify, max_tries=3) (без TTY — немедленный exit 2), чистые функции parse_env/render_env/preflight_verdict/build_plan под unit-тестами, _ensure_venv (host-venv для onboard-кирпича, probe import httpx, pydantic), _psql (SQL через stdin — секреты не в argv), инвариант APPLY_STEPS == build_plan().
  • Кирпичи: scripts/gen_secrets.py (--write PATH x-mode: существующий файл → exit 2, перезапись только --force; secrets.token_hex(32) → 64 hex); scripts/onboard_project.py (plan|apply|verify, обязательные --name/--repo/--prefix/--stack/--test-cmd/--prod-port/ --staging-port/--webhook-url, опции --gitea-owner/--env-file/--json, exit 0/2/1).
  • Анти-дрейф канона: tests/test_lite_setup_doc.py пиннит кортеж SECTIONS — заголовки ## 1.## 13. в порядке маршрута (TC-01), «каждый раздел §2§13 несёт fenced-команду» проверяется только для SECTIONS[1:] (§1 — без требования команд), каждый упомянутый в доке ORCH_*/WATCHDOG_*-токен обязан существовать в .env.example (_ENV_TOKEN_RE), fenced-блоки сканируются на FORBIDDEN-литералы и секрет-эвристику (hex≥32 / alnum≥40).
  • Структурная гигиена скриптов: tests/test_bootstrap_script.py — ast-скан stdlib-allowlist, FORBIDDEN_DELETE_NEEDLES (volume rm, rm -rf, down -v, compose down, rmtree, os.remove, .unlink), FORBIDDEN_STATUS_NEEDLES (Backlog, To Analyse, Confirm Deploy, …) — собственный канон статусов в скрипте запрещён.
  • Сеть нашего контура: все три сервиса корневого docker-compose.ymlnetwork_mode: host → из контейнера орка http://127.0.0.1:<published-port> достигает сервисов хоста; URL-кандидаты discovery на published-портах валидны для API-ключей.
  • Целевой режим резолва модели/эффорта, статусов и секретов — только кирпичи/конфиг (ORCH-41/74, plane_sync._PLANE_NAME_TO_KEY, gen_secrets): скрипт канон-знаний не дублирует.

ТЗ (02-trz.md §12) оставило архитектору OQ-1…OQ-6. Ниже — пакет решений D1…D12. Тип изменения — scripts + docs + tests: рантайм src/** байт-в-байт (BR-8/AC-13).

Решение

Сводка

Новый операторский CLI scripts/setup_lite.py (имя финализировано, зеркалит docs/deployment/LITE_SETUP.md): python stdlib-only wizard, автоматизирующий маршрут LITE_SETUP §2§12. Режимы — семейные plan / apply / verify, но дефолт — apply (интерактивная установка; осознанное отступление от plan-default семейства — D2): бизнес-цель «одна команда» — суть задачи, а безопасность дефолта обеспечена структурно (фаза 0 ≡ plan, ранний guard .env, per-action consent на каждую мутацию, non-TTY → exit 2 до мутаций). Step-движок check→ensure, 10 нормативных шагов (D3), exit 0/2/1, resume = повторный запуск без state-файла (реальность — единственный источник истины; коллизию «resume против guard существующего .env» решает маркер-строка managed-файла — D6). Скан предусловий с офером доустановки per-package consent'ом (D4), discovery docker-инсталляций Plane/Gitea строго по image-префиксам с выбором пользователя (D5), интерактивный сбор ключей §4.2 с немедленной верификацией и секрет-гигиеной (getpass, env-prefill + --yes для headless — D10), webhook Plane CE: Path A (UI) — рекомендация, Path Б (SQL) — офер под пятью предусловиями (D8), машинная охрана нормативов C-1 / §6.4 / ORCH-058 (D9), onboarding строго кирпичом plan→согласие→apply→verify (D11). Канон не форкается: LITE_SETUP получает подраздел ### 1.1. Быстрый путь (пиннинг «13 разделов» цел байт-в-байт — D12).

D1 — Имя, место, рамка: scripts/setup_lite.py, один файл, stdlib-only (OQ-1-часть)

  • scripts/setup_lite.py — один файл в scripts/ рядом с кирпичами; имя зеркалит док-канон (LITE_SETUP.mdsetup_lite.py — мнемоническая пара «инструкция ⇄ инструмент»). Альтернатива bootstrap_lite.py отвергнута: bootstrap в платформе уже значит «доводка Bundled-стека» (ORCH-103); у Lite семантика иная — установка поверх инфраструктуры заказчика.
  • python stdlib-only (NFR-1): запуск python3 scripts/setup_lite.py из корня чекаута на голом python3 ДО venv и до docker compose up; импорты — только stdlib-allowlist (ast-скан, паттерн test_bootstrap_script.py::STDLIB_ALLOWED); src.* не импортируется; канон-знания — только субпроцессами кирпичей (gen_secrets.py, onboard_project.py).
  • Язык UX — русский (паттерн gen_secrets/bootstrap_bundle); каждое сообщение шага ссылается на соответствующий § LITE_SETUP как на полный канон (NFR-4).
  • Рамка Lite не расширяется: Linux x86_64 + Docker/Compose v2; установка Plane/Gitea — вне объёма (подсказка «нет инфраструктуры → Bundled»); teardown/uninstall отсутствуют.

D2 — Режимы и дефолт: plan/apply/verify, дефолт — apply-wizard (OQ-1)

  • Закрытый набор режимов — семейная лексика ORCH-009/103: plan, apply, verify (никаких новых слов wizard/install: оператор, выучивший bootstrap_bundle, понимает setup_lite мгновенно; TC-01 «набор режимов закрыт»).
  • Контракты plan и verify — 1:1 с семейством: plan — строгий read-only (скан предусловий + discovery + автодетект + печать плана шагов; ноль мутаций ФС/docker/сети; exit 0 — блокеров нет / 2 — есть); verify — read-only пост-проверка (/health+/queue+ /metrics, состав «ровно орк+watchdog», stateless-чистота §12, onboard_project.py verify субпроцессом при доступном venv). Оба — полноценные non-TTY-режимы (CI-пригодны).
  • Дефолт без аргументов — apply (отступление от plan-default семейства, осознанное и тестируемое): бизнес-запрос Владельца — «один установочный файл, который на автомате выполняет установку» (BRD §1.2 п.1); plan-default воспроизвёл бы порог, который задача призвана убрать. Семейный инвариант сохранён в его сути — «запуск без аргументов не мутирует без явного согласия» — но другим механизмом: у bootstrap_bundle apply мутирует без per-action вопросов (его согласие = выбор режима), поэтому там безопасен только plan-default; у setup_lite каждая мутация отдельно согласуется (FR-1), поэтому apply-default безопасен по построению:
    1. Фаза 0 applyplan: read-only скан + discovery + печать плана; первый вопрос оператору задаётся только после неё («Продолжить установку? [y/N]» — дефолт-ответ N);
    2. Ранний guard .env: существующий немаркированный .env/.env.watchdog (D6) → отказ exit 2 ДО первого вопроса установки (NFR-7: живой хост защищён);
    3. Per-action consent: каждая мутация (установка пакета, запись файла, SQL, up, рестарт) — отдельное согласие с печатью точной команды; отказ → честный MANUAL с эквивалентной командой, не молчаливый пропуск;
    4. non-TTY: apply без --yes → немедленный честный exit 2 с подсказкой (никаких зависаний и мутаций — D10).
  • Тест-контракт (новый модуль, D12): parse_args([]).mode == "apply"сознательно зеркальный к test_plan_is_default_mode bootstrap'а ассерт с комментарием-обоснованием; набор режимов закрыт choices=("plan", "apply", "verify").
  • Флаги: --force (разрешить перезапись существующих НЕмаркированных .env* — см. D6; печать пути + согласие всё равно запрашивается), --yes (headless-consent, D10). Параметры проекта заказчика (для onboarding-шага) — опциональные --project-*-флаги как альтернатива интерактивному вводу (точный список — developer, зеркало sandbox-флагов bootstrap_bundle).

D3 — Step-движок: 10 нормативных шагов, check→ensure, без state-файла (FR-1)

  • Нормативный план apply (механика — паттерн build_plan()/APPLY_STEPS ORCH-103; инвариант «APPLY_STEPS == build_plan()» — под тестом):

    # Шаг § LITE_SETUP Суть
    1 scan §2, §7 read-only скан предусловий + автодетект хост-параметров + ранний guard .env (D6)
    2 prereqs §2, §7 доустановка MISSING per-package consent'ом / MANUAL (D4)
    3 discovery §5, §6 обнаружение инсталляций Plane/Gitea, выбор / ручной ввод (D5)
    4 collect §4.2, §5§8 интерактивный сбор ключей с немедленной верификацией (D9, D10)
    5 render-env §4 сборка .env/.env.watchdog от канонов + gen_secrets + docker compose config (D6, D7)
    6 plane-webhook §5.4 Path A manual-checkpoint / Path Б офер SQL (D8)
    7 gitea-guards §6 branch_protections == [] (FAIL+лечение), ssh-pubkey manual-step (D9)
    8 up §9 docker compose up -d --build с согласия; состав «ровно орк+watchdog»; health-чек
    9 onboard §10 кирпич plan→согласие→apply→verify; ORCH_PROJECTS_JSON.env; управляемый рестарт (D11)
    10 report §11, §12 stateless-проверка; итоговая таблица PASS/FAIL/MANUAL; smoke-инструкция ссылкой на §11
  • check→ensure: каждый шаг сперва проверяет «уже сделано?» по реальности (файл существует и маркирован; токен валиден API-пробой; контейнеры бегут; webhook существует) и при PASS пропускается. State-файл НЕ вводится (альтернатива отвергнута: новый артефакт = новая поверхность дрейфа/устаревания; реальность — единственный источник истины; resume после manual-step = просто повторный запуск — каскад skip доводит до первого незавершённого шага, NFR-6; паттерн bootstrap_bundle).

  • Exit-коды — именованные константы EXIT_OK=0 / EXIT_MANUAL=2 / EXIT_ERROR=1 (контракт FR-1/AC-11; исключения ManualStop/SetupError — паттерн bootstrap).

  • Smoke-инструкция шага 10 — ссылкой на LITE_SETUP §11, без текста статусов: скрипт не несёт имён Plane-статусов вообще (новый тест зеркалит FORBIDDEN_STATUS_NEEDLES — D12); вердикт «тираж PASS» остаётся за оператором (FR-10).

  • Итоговая таблица (FR-10): шаг → PASS/FAIL/MANUAL/skip; для MANUAL — что сделать + § дока; для FAIL — диагноз и лечение (зеркало §13). Опциональный файл-отчёт НЕ вводится (stdout + exit-код достаточны; меньше артефактов — меньше утечек, NFR-3).

  • Вердикты — чистая функция prereq_verdicts(facts) -> [(item, OK|MISSING|WARN|MANUAL, detail)] от read-only снимка фактов хоста (паттерн collect_facts/preflight_verdict): ОС/арх (uname -sm; не-Linux/не-x86_64 → WARN «вне контура Lite», не FAIL), docker, compose v2, git, python3, node, дистрибутив claude-code (npm root -g@anthropic-ai/claude-code), аутентификация CLI (читаемость ~/.claude/.credentials.json uid'ом будущего контейнера — §7.2), группа docker (getent group docker), uid/gid владельца каталога репо (§2.2), ssh-каталог (§2.4), свободность портов (§2.5). Ни один пункт перечня FR-2 не пропускается молча (AC-2).
  • Оферы установки — исполняются скриптом, но только с явного per-package согласия (бизнес-запрос п.3 «предлагает установить»; BRD R-2): печать точной команды ДО исполнения → согласие → исполнение → re-check фактом (повторная проверка версии/наличия; для compose обязательно docker compose version — нативные репо дистрибутивов могут нести v1!) → не сошлось → честный MANUAL со ссылкой на официальную инструкцию. Отказ от согласия → MANUAL с той же командой (TC-05).
  • Закрытый набор пакетных менеджеров: apt-get / dnf / yum / zypper (детект по наличию бинаря, в этом порядке; apt-get, не apt — стабильный CLI). Неопределимый менеджер (например, pacman/alpine) → MANUAL с готовыми generic-командами и ссылкой на § LITE_SETUP — не падение (TC-06). Точные имена пакетов per-менеджер — developer (карта- константа в скрипте); политика зафиксирована здесь.
  • Sudo-честность: требуется root, а его нет (не root и sudo недоступен) → MANUAL с командой под sudo; скрипт не пытается эскалировать привилегии сам.
  • Спец-случаи: node+claude-code — офер npm install -g @anthropic-ai/claude-code (node — пакетом менеджера); ssh-ключи — офер ssh-keygen -t ed25519 + печать pubkey как manual-step «добавить в Gitea» (§2.4/§6.2); OAuth-логин claude CLI не автоматизируется — только верификация результата + manual-step (§7.2, допущение BRD §6).
  • Автодетект хост-параметров (FR-5) — спрашивается только подтверждение, не значения: ORCH_RUN_UID/ORCH_RUN_GID (владелец ORCH_HOST_REPOS_DIR / текущий пользователь), ORCH_DOCKER_GID, 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 (корень чекаута).

D5 — Discovery Plane/Gitea: строго image-префиксы, выбор всегда за пользователем (FR-3)

  • Источник — только локальный Docker: перечисление контейнеров (docker ps с форматом, дающим имя/образ/published-порты/метку com.docker.compose.project); группировка контейнеров в «инсталляции» по метке compose-проекта (без метки → группа-одиночка по имени контейнера).
  • Опознание — строго по префиксам образов (анти-ложноположительность R-6; имена контейнеров/проектов НЕ используются как признак): Plane — образы makeplane/*; Gitea — gitea/gitea* и docker.gitea.com/gitea*. Посторонние образы в кандидаты не попадают (AC-3). Список префиксов — константа скрипта (расширение = осознанная правка под тестом).
  • URL-кандидаты из published-портов: Plane — порт контейнера-входа (образ makeplane/plane-proxy; нет proxy → инсталляция показывается без URL-префилла); Gitea — published-порт на контейнерный 3000. Предлагаются: API-URL http://127.0.0.1:<port> (валидно: корневой compose — network_mode: host, контейнер орка видит published-порты хоста через loopback) и публичный URL http://<hostname>:<port> (hostname — ввод/подтверждение оператора; для webhook'ов нужен адрес, достижимый со стороны Plane/Gitea).
  • Поведение по числу кандидатов (BR-4): 0 → ручной ввод URL + честная подсказка «Lite не устанавливает Plane/Gitea; нет инфраструктуры — маршрут Bundled (BUNDLED_SETUP.md)»; 1 → префилл по умолчанию с подтверждением; ≥2 → нумерованный список (compose-проект, образы, порты) + выбор. Пункт «ввести вручную» доступен всегда (включая случай 1 и ≥2).
  • Best-effort, never-block: docker недоступен / ошибка перечисления / native- или k8s-инсталляция → ручной ввод URL, не FAIL и не падение (TC-09). Discovery заполняет только кандидаты URL; токены всегда вводит пользователь (FR-4), выбранный кандидат всё равно проходит токен-верификацию.
  • Постгрес-кандидат для Path Б (D8): из выбранной Plane-инсталляции — контейнеры с образом postgres* ТОГО ЖЕ compose-проекта; подтверждение пользователя обязательно.

D6 — Сборка .env/.env.watchdog: рендер от канонов + маркер managed-файла (FR-6; решает «resume против guard»)

  • Рендер от канонов .env.example/.env.watchdog.example чистой функцией (паттерн render_env bootstrap: строки/комментарии канона сохраняются, значения подставляются; принцип ORCH-101 «дефолт = боевое значение» — записываются только собранные отличия). WATCHDOG_TG_BOT_TOKEN/WATCHDOG_TG_CHAT_ID кладутся только в .env.watchdog (ловушка файла-носителя §4.3); подсказки-дефолты промптов — из .env.example/автодетекта, никогда из боевых значений (TC-15: FORBIDDEN-набор переиспользуется).
  • Маркер managed-файла — ключ к resume: первая строка каждого собранного скриптом файла — фиксированный комментарий-маркер # managed by scripts/setup_lite.py (ORCH-104). Семантика guard'а на шаге scan:
    • файла нет → штатный путь (рендер на шаге 5);
    • файл есть, маркера нет → это чужой/живой конфиг → отказ exit 2 без --force, файл байт-в-байт не тронут (NFR-7/AC-5/TC-14; --force = явная перезапись с согласием);
    • файл есть, маркер есть → собран нами ранее → resume-ensure: дозаполнение недостающих ключей, существующие значения не перетираются (NFR-6; без маркера resume после manual-step на шаге 6+ был бы невозможен — guard отбивал бы собственный артефакт).
  • Webhook-секреты — строго кирпичом: субпроцесс gen_secrets.py --write <tmpdir>/fragment → парс фрагмента → перенос двух ключей (паттерн step_secrets bootstrap); свежий выпуск, боевые секреты не используются (stateless §12). Запись live-файлов — права 600, содержимое не печатается (NFR-3).
  • Проверка шага: docker compose config → PASS/FAIL (§4 «Проверка»; ошибка интерполяции → диагноз «ищите незакрытую кавычку/невалидный JSON в ORCH_PROJECTS_JSON»).

D7 — Порты: busy-check, когерентная тройка одной функцией, staging ≠ prod fail-closed (FR-5)

  • Busy-check прод/staging-портов сокетом (паттерн _port_busy); занят → предложить альтернативу (ввод с дефолтом «порт+N»).
  • Когерентность тройки — механически, одной чистой функцией port_overrides(prod_port) -> {ORCH_DEPLOY_PROD_TARGET_PORT, WATCHDOG_METRICS_URL, ORCH_POST_DEPLOY_BASE_URL} — рассинхрон структурно невозможен (TC-16; ловушка §2.5/§4.2 закрывается кодом, не дисциплиной).
  • ORCH_STAGING_PORT == прод-порт → отказ fail-closed на вводе (значение не принято, re-prompt; инвариант ORCH-058, усиленный ORCH-101; TC-17).

D8 — Webhook Plane CE: Path A (UI) — рекомендация, Path Б (SQL) — офер под предусловиями (OQ-3)

Каверза Plane CE: webhook не экспонирован во внешнем /api/v1 (§5.4). В Bundled bootstrap выполняет SQL-вставку безусловно — он владеет инсталляцией (сам её создал, знает контейнер и креды). В Lite Plane — продукт заказчика: контейнер/пароль БД скрипту неизвестны, инсталляция может быть не-docker, мутация чужой прод-БД инвазивна (BRD R-3, NFR-2: «UI-путь предпочтителен»). Решение — двухпутёвый шаг с приоритетом UI:

  • Path A (UI) — дефолт-рекомендация: manual_checkpoint-контракт (печать точной инструкции §5.4 с подставленными URL приёмника и именем секрета — значение секрета НЕ печатается, только имя ключа) → подтверждение оператора → верификация. Верификация Path A: при доступных координатах БД (введены для Path Б/проверки) — механический SELECT url, is_active FROM webhooks; иначе — честная деградация: шаг фиксируется MANUAL («подтверждено оператором, сквозная проверка доставки — smoke §11»), НЕ выдаётся за механически проверенный PASS (NFR-4).
  • Path Б (SQL INSERT) — офер автоматизации, исполняется ТОЛЬКО при всех предусловиях:
    1. Plane-инсталляция — docker, и Postgres-контейнер выбран и подтверждён пользователем (кандидаты — D5: postgres*-образы того же compose-проекта);
    2. явное согласие с показом точного SQL (INSERT канона §5.4) ДО исполнения;
    3. пароль БД Plane — скрытый ввод (или env-prefill, D10); в argv не попадает — SQL и PGPASSWORD передаются через stdin/env субпроцесса (паттерн _psql bootstrap);
    4. идемпотентность: сначала SELECT count(*) по URL приёмника (deleted_at IS NULL) — уже зарегистрирован → skip (паттерн _exists bootstrap);
    5. обязательная пост-верификация SELECT url, is_active → нет строки → шаг НЕ PASS.
  • Только INSERT/SELECT — никаких UPDATE/DELETE (no-delete распространяется на чужую БД); slug и прочие подстановки валидируются (^[a-z0-9-]+$ для slug; UUID — uuid.uuid4() локально; секрет — через psql-переменную/stdin, не конкатенация в argv) — анти-SQL-инъекция на пользовательском вводе (отличие от bundle, где значения самогенерированные).
  • Любой сбой Path Б / отказ согласия / не-docker Plane → fail-safe в Path A (manual-checkpoint c инструкцией UI-пути; мутирующий вызов не произведён — TC-20).

D9 — Машинная охрана нормативов: C-1, §6.4, верификация каждого ввода (FR-4/FR-7)

  • Немедленная верификация каждого введённого значения фактическим вызовом (FR-4): Plane-токен — GET /api/v1/workspaces/<slug>/projects/ c X-API-Key → 200; Gitea-токен — GET /api/v1/user → 200 (+ логин владельца → префилл ORCH_GITEA_OWNER); Telegram — getMe"ok":true + helper chat-id через getUpdates; публичный URL оркестратора — синтаксическая валидация + предупреждение «достижимость со стороны Plane/Gitea проверится на smoke». Неуспех → re-prompt с диагнозом, лимит 3 попытки (паттерн manual_checkpoint(max_tries=3)) → MANUAL/exit 2, не бесконечный цикл (TC-10).
  • C-1 (ORCH-100) — машинно: токен watchdog-бота байт-в-байт равен токену орка → отказ шага с объяснением запрета и требованием другого токена (TC-18); оба токена — скрытый ввод.
  • §6.4 — branch protection: GET /api/v1/repos/<owner>/<repo>/branch_protections непуст → FAIL шага с лечением (текст норматива: правила удалить, иначе ложные HOLD — §13.7); скрипт правила сам не удаляет (no-delete; TC-19). Проверка — для репо проекта заказчика после onboarding (шаг 9) и/или по введённому owner/repo.
  • §7.3: наличие непустых ORCH_AGENT_MODEL_DEFAULT/ORCH_AGENT_EFFORT_DEFAULT в собранном .env (дефолты канона .env.example достаточны — проверка, не ввод).

D10 — non-TTY и headless: env-prefill + явный --yes; answers-file отвергнут (OQ-2)

  • Интерактив — за инжектируемым I/O (NFR-5): примитивы ask(key, secret=False) / consent(action) принимают источники ввода/вывода параметрами → unit-тесты со скриптованными ответами без TTY (TC-10…TC-12).
  • Env-prefill — неинтерактивная альтернатива без новой лексики: ask() ПЕРЕД промптом проверяет переменную окружения процесса с тем же каноническим именем ключа (ORCH_PLANE_API_TOKEN, WATCHDOG_TG_BOT_TOKEN, …) — значение найдено → принимается без промпта, но верификация выполняется как обычно (неверный токен из env → в TTY re-prompt, в headless exit 2). Словарь имён один — .env.example; никакого нового формата.
  • --yes — headless-consent: явный флаг = заранее данное согласие на consent-вопросы (аналог семантики «выбор режима apply = согласие» bootstrap_bundle). non-TTY матрица:
    • apply без --yes → немедленный честный exit 2 с перечнем того, что потребует интерактива, и подсказкой (plan/verify доступны; TTY или --yes+env-prefill) — зависание исключено (TC-12);
    • apply --yes → consent'ы подтверждены флагом; значения — только из env-prefill; отсутствует обязательное значение → exit 2 (не молчаливый дефолт); manual-step → exit 2 (resume — повторный запуск);
    • plan/verify — работают в non-TTY полноценно (exit по результату).
  • Answers-file отвергнут: секреты в файле-вне-.env = новая поверхность утечки (NFR-3), новый формат = новый канон под сопровождение; env-prefill покрывает кейс без этих издержек.

D11 — Onboarding: строго кирпич, plan→согласие→apply→verify, управляемый рестарт (OQ-6)

  • Драйв субпроцессом (прецедент step_onboard bootstrap_bundle), НЕ «печать готовой команды»: печать разорвала бы «одну команду» на самом ценном шаге. Усиление против bundle — сначала plan: вывод плана кирпича показывается пользователю → согласие → applyverify (FR-9/TC-22).
  • Запуск кирпича — host-venv (канон ONBOARDING; кирпичу нужны httpx/pydantic): идемпотентный ensure venv — паттерн _ensure_venv (probe импорта → pip install -r requirements.txt); сам setup_lite от venv не зависит (stdlib-only).
  • Аргументы — детерминированная чистая функция build_onboard_args(answers, mode) -> list от собранных ответов (--name/--description/--repo/--gitea-owner/--prefix/--stack/--test-cmd/ --prod-port/--staging-port/--webhook-url + --env-file <корневой .env> + --json) — unit-тестируемо без сети (AC-10). Webhook-url строится от публичного URL оркестратора (https://<orchestrator-public-host>/webhook/gitea).
  • Трансляция исходов: exit 2 кирпича (остались ручные шаги) → MANUAL итогового отчёта (не успех); exit 1 → FAIL. ORCH_PROJECTS_JSON — из JSON-отчёта apply (строка instructions ORCH_PROJECTS_JSON=… — фактический контракт кирпича) → запись в .env с согласия.
  • Управляемый рестарт — только собственного свежеподнятого контура (§10): проверка тихого окна (GET /queue без running-job) → docker compose up -d --force-recreate orchestrator → пост-проверка /health+/queue. Чужие/боевые контейнеры не трогаются (NFR-7; рестартуется контур, поднятый шагом 8 этого же прогона).
  • Собственная реализация статусов/лейблов/репо/webhook в setup_lite запрещена (BR-7; 22 статуса остаются за plane_sync._PLANE_NAME_TO_KEY через кирпич; структурный тест — D12).

D12 — Документация и анти-дрейф: §1.1 в LITE_SETUP (пиннинг цел), новый тест-модуль (OQ-5, FR-11)

  • Размещение быстрого пути — подраздел ### 1.1. Быстрый путь: setup_lite.py внутри ## 1. Рамка Lite (+ упоминание в шапке-цитате дока). Заголовки ## 1.## 13. не меняются → пиннинг test_doc_exists_with_all_13_sections_in_order и нумерация ссылок (§5.4, §13.7 и т.д. из других доков/скриптов) сохраняются байт-в-байт; ручной маршрут §2§13 — канон и fallback для MANUAL-шагов. Fenced-блок §1.1 (команда запуска + режимы) обязан проходить существующие сканы дока: только плейсхолдеры, упоминаемые env-токены — только существующие в .env.example (_ENV_TOKEN_RE-ассерт). Альтернативы отвергнуты: ## 0/14-й раздел/перенумерация — ломают пиннинг и внешние ссылки на §-номера без выгоды.
  • Footer-норматив LITE_SETUP расширяется (FR-11): «меняешь шаги тиража → обнови этот док и scripts/setup_lite.py в том же PR» (симметрично NFR-5 ORCH-102).
  • tests/test_setup_lite_script.py (имя финализировано; по образцу test_bootstrap_script.py + тест-план TC-01…TC-25): ast-скан stdlib-only и отсутствия src.*; зеркала обоих needle-наборов (FORBIDDEN_DELETE_NEEDLES — delete-операций нет; FORBIDDEN_STATUS_NEEDLES — собственного канона статусов нет); упоминание кирпичей gen_secrets.py/onboard_project.py/LITE_SETUP.md; unit чистых функций (вердикты предусловий, классификатор discovery, port_overrides, рендер env c маркером, builder аргументов onboard, step-движок/resume, exit-контракт, дефолт-режим apply, закрытость режимов); секрет-гигиена (инжектированный транскрипт без значений секретов); import без side effects.
  • tests/test_lite_setup_doc.py — только аддитивно: новый тест «LITE_SETUP.md упоминает setup_lite.py (быстрый путь) и сохраняет ручной маршрут» (TC-27); существующие ассерты, включая кортеж SECTIONS, не правятся (пиннинг структуры не меняется — D12 п.1).
  • Синхронно в том же PR (правило агентов №2 / NFR-8): CHANGELOG.md (feat:); витрина docs/overview/README.md (строка маршрута «Развернуть у себя» — упомянуть установочный скрипт; business.md — при необходимости); паспорт CLAUDE.md (секция ORCH-104); docs/architecture/README.md — обновлён на этой стадии (блок Type A дополнен инсталлером).

Что НЕ меняется

src/**, корневой docker-compose.yml, Dockerfile, .env.example, .env.watchdog.example, scripts/gen_secrets.py, scripts/onboard_project.py, scripts/bootstrap_bundle.py, deploy/**, onboarding/**, промпты .openclaw/agents/**; STAGE_TRANSITIONS, состав QG_CHECKS, семантика check_*, machine-verdict ключи, схема БД — байт-в-байт (BR-8/AC-13). Kill-switch не вводится: активация — только явный запуск CLI человеком на целевом хосте (паттерн ORCH-009/102/103); в нашем контуре артефакт инертен. Прод-контейнер в рамках задачи не рестартуется (выкат — штатно: staging 8501 → Confirm Deploy).

Альтернативы

  • plan-дефолт (строгое следование D5 ORCH-009/103) — отвергнуто для этого инструмента: воспроизводит порог входа, который задача убирает (бизнес-запрос «одна команда» — BRD §1.2); безопасность дефолта достигается per-action consent + ранним guard + non-TTY exit 2 (D2), а не выбором режима. plan сохранён как полноценный строгий read-only режим.
  • Отдельное имя режима wizard/install — отвергнуто: новая лексика против выученной семейной (plan/apply/verify); дешевле объяснить «apply интерактивен», чем вводить слово.
  • State-файл прогресса для resume — отвергнуто: новый артефакт = поверхность дрейфа/устаревания; check→ensure по реальности уже даёт resume (паттерн bootstrap); коллизия с guard'ом .env решена маркером (D6) без состояния.
  • Answers-file для headless — отвергнуто: секреты на диске вне .env (NFR-3), новый формат под сопровождение; env-prefill с каноническими именами + --yes покрывает кейс (D10).
  • Безусловная автоматизация SQL-вставки webhook (как в bundle) — отвергнуто: в Lite БД Plane — чужая прод-инсталляция; без подтверждённого контейнера/согласия/пост-верификации это R-3; UI-путь — рекомендация, SQL — офер (D8).
  • Только печать команд установки пакетов (никогда не исполнять) — отвергнуто: ломает бизнес-запрос п.3 («предлагает установить»); per-package consent + re-check дают тот же уровень безопасности с лучшим UX (D4).
  • Discovery по именам контейнеров/compose-проектов — отвергнуто: ложноположительные срабатывания на чужих контейнерах (R-6); строго image-префиксы + выбор за пользователем (D5).
  • Новый раздел LITE_SETUP (## 0/14-й/перенумерация) — отвергнуто: ломает пиннинг «13 разделов в порядке» и внешние ссылки на §-номера; подраздел §1.1 даёт то же без издержек (D12).
  • Опциональный файл-отчёт прогона — отвергнуто (TRZ §9 оставлял на усмотрение): stdout + exit-код покрывают наблюдаемость; файл с транскриптом — лишняя поверхность для случайного попадания чувствительных значений.

Последствия

  • + Порог входа Lite падает с «прочитай 13 разделов и выполни ~30 команд» до «запусти один файл и отвечай на вопросы»; ошибки конфигурации ловятся в момент ввода (немедленная верификация), а не на smoke.
  • + Нулевой дрейф канонов: статусы/секреты/onboarding — только кирпичами; маршрут — только LITE_SETUP (ссылки из каждого шага); форма закреплена анти-дрейф тестами с зеркалами уже существующих needle-наборов.
  • + Рантайм байт-в-байт, наш прод недостижим по построению: ранний guard немаркированного .env (живой хост), no-delete, никаких операций с main, рестарт — только собственного свежеподнятого контура.
  • Дефолт apply отличается от plan-default семейства — осознанная цена бизнес-цели; митигировано структурными гарантиями D2 (фаза 0 ≡ plan, consent, non-TTY exit 2) и явным тест-ассертом с комментарием-обоснованием.
  • Двойной источник маршрута (док + скрипт) — поверхность дрейфа; митигировано нормативом «док + скрипт в одном PR» (footer LITE_SETUP, adr-0040) и анти-дрейф тестами (кирпичи, env-ключи ⊂ канона, упоминание скрипта в доке).
  • Оферы установки пакетов вмешиваются в систему заказчика — митигировано per-package consent с печатью точной команды, закрытым набором менеджеров, re-check'ом и честным MANUAL.
  • Path Б мутирует чужую Plane-БД — митигировано пятью предусловиями D8 (docker + подтверждённый контейнер + согласие с показом SQL + идемпотентный INSERT + пост-верификация) и приоритетом UI-пути.
  • Откат: удалить scripts/setup_lite.py, tests/test_setup_lite_script.py, вернуть §1.1 и footer LITE_SETUP.md, аддитивный тест в test_lite_setup_doc.py, записи CHANGELOG/CLAUDE.md/README/overview — состояние репо 1:1 (scripts+docs+tests, без миграций); поведение платформы не меняется в обе стороны.

Ссылки

  • BRD: docs/work-items/ORCH-104/01-brd.md
  • TRZ: docs/work-items/ORCH-104/02-trz.md (OQ-1…OQ-6 → D1…D12)
  • Acceptance: docs/work-items/ORCH-104/03-acceptance-criteria.md; тест-план: 04-test-plan.yaml
  • Сквозной ADR: docs/architecture/adr/adr-0040-lite-interactive-installer.md
  • Риски: docs/work-items/ORCH-104/10-tech-risks.md; инфра: 07-infra-requirements.md
  • Предшественники: adr-0037 (ORCH-102 — канон LITE_SETUP/13 разделов), adr-0038 (ORCH-103 — bootstrap-паттерн: режимы/step-движок/manual-checkpoint/no-delete), adr-0036 (ORCH-101 — «дефолт=боевое», gen_secrets, guard ORCH-058), adr-0035 (ORCH-009 — onboarding-CLI, D10 branch protection)
  • Сверено по коду/репо: scripts/bootstrap_bundle.py (manual_checkpoint(max_tries=3), render_env/parse_env, _ensure_venv, _psql stdin, step_onboardinstructions ORCH_PROJECTS_JSON=, APPLY_STEPS == build_plan()), scripts/gen_secrets.py (--write x-mode/exit 2/--force, token_hex(32)), scripts/onboard_project.py (CLI: plan|apply|verify, обязательные флаги, --env-file/--json), tests/test_lite_setup_doc.py (кортеж SECTIONS, _ENV_TOKEN_RE, FORBIDDEN-импорт, секрет-эвристика, «команды только в SECTIONS[1:]»), tests/test_bootstrap_script.py (STDLIB_ALLOWED, FORBIDDEN_DELETE_NEEDLES, FORBIDDEN_STATUS_NEEDLES), docker-compose.yml (network_mode: host ×3), docs/deployment/LITE_SETUP.md (§2§13), .env.example (канон имён ключей)