49 KiB
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), exit0/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-кирпича, probeimport httpx, pydantic),_psql(SQL через stdin — секреты не в argv), инвариантAPPLY_STEPS == build_plan(). - Кирпичи:
scripts/gen_secrets.py(--write PATHx-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.yml—network_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.md⇄setup_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_bundleapplyмутирует без per-action вопросов (его согласие = выбор режима), поэтому там безопасен только plan-default; у setup_lite каждая мутация отдельно согласуется (FR-1), поэтому apply-default безопасен по построению:- Фаза 0
apply≡plan: read-only скан + discovery + печать плана; первый вопрос оператору задаётся только после неё («Продолжить установку? [y/N]» — дефолт-ответ N); - Ранний guard
.env: существующий немаркированный.env/.env.watchdog(D6) → отказ exit 2 ДО первого вопроса установки (NFR-7: живой хост защищён); - Per-action consent: каждая мутация (установка пакета, запись файла, SQL,
up, рестарт) — отдельное согласие с печатью точной команды; отказ → честный MANUAL с эквивалентной командой, не молчаливый пропуск; - non-TTY:
applyбез--yes→ немедленный честный exit 2 с подсказкой (никаких зависаний и мутаций — D10).
- Фаза 0
- Тест-контракт (новый модуль, D12):
parse_args([]).mode == "apply"— сознательно зеркальный кtest_plan_is_default_modebootstrap'а ассерт с комментарием-обоснованием; набор режимов закрыт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_STEPSORCH-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).
D4 — Скан предусловий и оферы установки: per-package consent, закрытый набор менеджеров (OQ-4)
- Вердикты — чистая функция
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.jsonuid'ом будущего контейнера — §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-URLhttp://127.0.0.1:<port>(валидно: корневой compose —network_mode: host, контейнер орка видит published-порты хоста через loopback) и публичный URLhttp://<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_envbootstrap: строки/комментарии канона сохраняются, значения подставляются; принцип 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_secretsbootstrap); свежий выпуск, боевые секреты не используются (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) — офер автоматизации, исполняется ТОЛЬКО при всех предусловиях:
- Plane-инсталляция — docker, и Postgres-контейнер выбран и подтверждён пользователем
(кандидаты — D5:
postgres*-образы того же compose-проекта); - явное согласие с показом точного SQL (INSERT канона §5.4) ДО исполнения;
- пароль БД Plane — скрытый ввод (или env-prefill, D10); в argv не попадает —
SQL и
PGPASSWORDпередаются через stdin/env субпроцесса (паттерн_psqlbootstrap); - идемпотентность: сначала
SELECT count(*)по URL приёмника (deleted_at IS NULL) — уже зарегистрирован → skip (паттерн_existsbootstrap); - обязательная пост-верификация
SELECT url, is_active→ нет строки → шаг НЕ PASS.
- Plane-инсталляция — docker, и Postgres-контейнер выбран и подтверждён пользователем
(кандидаты — D5:
- Только 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/cX-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_onboardbootstrap_bundle), НЕ «печать готовой команды»: печать разорвала бы «одну команду» на самом ценном шаге. Усиление против bundle — сначалаplan: вывод плана кирпича показывается пользователю → согласие →apply→verify(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(строкаinstructionsORCH_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,_psqlstdin,step_onboard→instructionsORCH_PROJECTS_JSON=,APPLY_STEPS == build_plan()),scripts/gen_secrets.py(--writex-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(канон имён ключей)