architect(ET): auto-commit from architect run_id=639
All checks were successful
CI / test (push) Successful in 59s

This commit is contained in:
2026-06-11 20:42:39 +03:00
parent 94a3f399f2
commit 302a891aff
5 changed files with 725 additions and 0 deletions

View File

@@ -245,6 +245,28 @@ Type B). Анти-дрейф — `tests/test_bundle_compose.py` / `test_bundled_
[adr-0038](adr/adr-0038-bundled-replication-canon.md), детально —
`docs/work-items/ORCH-103/06-adr/ADR-001-bundled-stack-compose-and-bootstrap.md`.
**Интерактивный installer Lite (ORCH-104 — design).** Lite получает инструмент, симметричный
Bundled-bootstrap'у: **`scripts/setup_lite.py`** — python stdlib-only wizard, автоматизирующий
маршрут LITE_SETUP §2§12 (скан предусловий с офером доустановки per-package consent'ом;
discovery docker-инсталляций Plane/Gitea строго по image-префиксам с выбором пользователя;
интерактивный сбор ключей §4.2 с немедленной верификацией каждого значения и секрет-гигиеной;
автодетект хост-параметров; сборка `.env`/`.env.watchdog` от канонов + `gen_secrets.py`;
webhook Plane CE: UI-путь — рекомендация, SQL-путь — офер под предусловиями с обязательной
пост-верификацией; машинная охрана C-1/§6.4/ORCH-058; подъём ровно орк+watchdog;
onboarding строго кирпичом plan→согласие→apply→verify; итог PASS/FAIL/MANUAL, exit 0/2/1).
Режимы — семейные `plan`/`apply`/`verify`, но **дефолт — `apply`-wizard** (бизнес-цель «одна
команда»); безопасность дефолта — структурно, не режимом: фаза 0 ≡ plan (read-only), ранний
guard чужого `.env` (**маркер managed-файла**: без маркера → отказ exit 2, с маркером →
resume-ensure), per-action consent на каждую мутацию, non-TTY без явного `--yes` → exit 2
(headless — env-prefill каноническими именами ключей, не answers-file). Step-движок
check→ensure без state-файла (resume = повторный запуск); no-delete; канон не форкается —
LITE_SETUP получает подраздел §1.1 «Быстрый путь» (пиннинг «13 разделов» цел), норматив
сопровождения расширен: «меняешь шаги тиража → обнови док **и скрипт** в том же PR».
Анти-дрейф — `tests/test_setup_lite_script.py` (зеркала delete/status-needle-наборов, unit
чистых функций) + аддитивный тест в `test_lite_setup_doc.py`. Рантайм/конвейер — байт-в-байт;
kill-switch не нужен. Подробнее: [adr-0040](adr/adr-0040-lite-interactive-installer.md),
детально — `docs/work-items/ORCH-104/06-adr/ADR-001-setup-lite-interactive-installer.md`.
## Витрина системы `docs/overview/` (ORCH-011 — design)
Единая точка входа «бизнес + тех» для трёх аудиторий (заказчик / менеджер / разработчик) —

View File

@@ -0,0 +1,121 @@
---
work_item: ORCH-104
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-11
model_used: claude-opus-4-8
---
# adr-0040: Интерактивный installer Lite-тиража — `scripts/setup_lite.py` (ORCH-104)
## Статус
Proposed
## Контекст
Эпик ORCH-10 закрыт по обоим типам, но асимметрично: Type B — Bundled имеет **инструмент**
(`scripts/bootstrap_bundle.py`, ORCH-103/adr-0038), Type A — Lite — только **документ**
(`docs/deployment/LITE_SETUP.md`, ORCH-102/adr-0037: 13 разделов, ~30+ ручных команд, ~20
обязательных ключей). Порог входа Lite высок: оператор вручную собирает значения, которые
машина определяет сама; ошибки конфигурации проявляются поздно (на smoke, не в момент ввода).
Бизнес-запрос Владельца: один установочный файл — выполняет установку, запрашивает данные в
момент установки, сканирует систему с офером доустановки, при нескольких инсталляциях
Plane/Gitea даёт выбрать.
Сквозной характер: серия канонов тиража (adr-0035 onboarding → adr-0036 10-common →
adr-0037 Lite-док → adr-0038 Bundled) дополняется слоем «Lite-инструмент»; вводятся нормативы,
обязательные для будущих операторских CLI платформы и любого агента, меняющего шаги тиража.
Детальный пакет решений (D1…D12, исходы OQ-1…OQ-6 ТЗ) — work-item ADR:
`docs/work-items/ORCH-104/06-adr/ADR-001-setup-lite-interactive-installer.md`.
## Решение
1. **Новый операторский CLI `scripts/setup_lite.py`** (имя зеркалит LITE_SETUP.md): python
stdlib-only wizard, автоматизирующий маршрут LITE_SETUP §2§12 — скан предусловий хоста с
офером доустановки (per-package consent, закрытый набор менеджеров apt-get/dnf/yum/zypper,
re-check после установки, иначе MANUAL), discovery docker-инсталляций Plane/Gitea (строго
image-префиксы `makeplane/*`/`gitea/gitea*`; 0 → ручной ввод + подсказка про Bundled, 1 →
префилл, ≥2 → выбор; «ввести вручную» всегда; never-block), интерактивный сбор ключей §4.2
с немедленной верификацией каждого значения фактическим вызовом (Plane/Gitea API, Telegram
getMe; лимит 3 попытки → MANUAL), автодетект хост-параметров, сборка `.env`/`.env.watchdog`
от канонов-example (webhook-секреты — строго кирпич `gen_secrets.py`), подъём ровно
орк+watchdog + health, регистрация проекта строго кирпичом `onboard_project.py`
(plan→согласие→apply→verify), итоговый отчёт PASS/FAIL/MANUAL + exit `0/2/1`.
2. **Wizard-контракт consent-gated мутаций (норматив для интерактивных операторских CLI):**
режимы — семейные `plan`/`apply`/`verify`, но **дефолт — `apply`** (бизнес-цель «одна
команда»); отступление от plan-default семейства безопасно по построению и допускается
ТОЛЬКО при четырёх структурных гарантиях: (а) фаза 0 apply ≡ plan (read-only скан + печать
плана + явный вопрос «продолжить?»), (б) ранний guard чужих конфигов до первого вопроса,
(в) per-action consent на каждую мутацию с печатью точной команды (отказ → честный MANUAL),
(г) non-TTY без явного `--yes` → немедленный exit 2 (зависание/мутации исключены).
Инструменты без per-action consent (bootstrap_bundle) обязаны сохранять plan-default.
3. **Маркер managed-файла** — норматив примирения идемпотентного resume с защитой чужих
конфигов: первая строка собранного скриптом `.env*` — фиксированный комментарий
`# managed by scripts/setup_lite.py (ORCH-104)`; существующий файл без маркера → отказ
exit 2 (без `--force`), с маркером → resume-ensure (дозаполнение без перетирания значений).
4. **Headless-канал — env-prefill + явный `--yes`** (не answers-file: секреты на диске вне
`.env` запрещены): значения берутся из переменных окружения процесса с каноническими
именами ключей `.env.example`; верификация обязательна и в headless; отсутствие
обязательного значения → exit 2, не молчаливый дефолт.
5. **Webhook Plane CE (Lite-инсталляция заказчика):** Path A (UI) — рекомендация
(manual-checkpoint с верификацией либо честный MANUAL «проверка — smoke §11»); Path Б
(SQL INSERT в Postgres Plane) — **офер** строго при предусловиях: docker-Plane +
подтверждённый пользователем контейнер БД + согласие с показом SQL + идемпотентный INSERT +
обязательная пост-верификация; только INSERT/SELECT; ввод валидируется (анти-инъекция);
сбой → fail-safe в Path A. (Отличие от Bundled adr-0038, где bootstrap владеет инсталляцией
и пишет безусловно.)
6. **Машинная охрана нормативов тиража:** C-1 ORCH-100 (идентичные токены орка/watchdog →
отказ шага), §6.4/D10 ORCH-009 (непустые `branch_protections` на `main` → FAIL с лечением,
удаление — никогда), guard ORCH-058 (`ORCH_STAGING_PORT == прод-порт` → fail-closed),
когерентная тройка прод-порта — одной чистой функцией.
7. **Канон не форкается; норматив сопровождения расширен:** LITE_SETUP.md получает подраздел
`### 1.1. Быстрый путь` (пиннинг «13 разделов в порядке» цел байт-в-байт; ручной маршрут —
канон и fallback для MANUAL-шагов); footer-норматив NFR-5: «меняешь шаги тиража → обнови
LITE_SETUP.md **и `scripts/setup_lite.py`** в том же PR». Канон-знания в скрипте запрещены
структурно: кирпичи — только субпроцессами; имён Plane-статусов в скрипте нет вообще
(зеркало `FORBIDDEN_STATUS_NEEDLES`); smoke — ссылкой на §11.
8. **Анти-дрейф — постоянная CI-гарантия:** `tests/test_setup_lite_script.py` (ast stdlib-only,
зеркала delete/status-needle-наборов, кирпичи упомянуты, unit чистых функций: вердикты
скана, классификатор discovery, когерентность портов, рендер env с маркером, builder
onboard-аргументов, step-движок/resume, exit-контракт, дефолт `apply`, секрет-гигиена
транскрипта) + аддитивный тест в `tests/test_lite_setup_doc.py` (скрипт упомянут в доке;
существующие ассерты и кортеж `SECTIONS` не правятся).
### Что НЕ меняется
`src/**`, корневой `docker-compose.yml`, `Dockerfile`, `.env.example`, `.env.watchdog.example`,
кирпичи `gen_secrets.py`/`onboard_project.py`/`bootstrap_bundle.py`, `deploy/**`,
`onboarding/**`; `STAGE_TRANSITIONS`, состав `QG_CHECKS`, семантика `check_*`, machine-verdict
ключи, схема БД — байт-в-байт. Kill-switch не вводится (активация — только явный запуск
оператора; паттерн ORCH-009/102/103). Прод-контейнер в рамках задачи не рестартуется.
## Альтернативы
- **plan-дефолт (строгий D5 ORCH-009/103)** — отвергнуто для wizard-инструмента: воспроизводит
порог, который задача убирает; безопасность дана гарантиями п.2, а не выбором режима.
- **State-файл прогресса / answers-file / файл-отчёт** — отвергнуты: новые артефакты = новые
поверхности дрейфа и утечки; реальность как источник resume + env-prefill + stdout покрывают
те же кейсы.
- **Безусловная SQL-автоматизация webhook (как Bundled)** — отвергнуто: в Lite БД Plane — чужая
прод-инсталляция (см. п.5).
- **Новый раздел LITE_SETUP / перенумерация** — отвергнуто: ломает пиннинг «13 разделов» и
внешние §-ссылки; подраздел §1.1 даёт то же бесплатно.
## Последствия
- Порог входа Lite: «прочитай 13 разделов» → «запусти один файл»; ошибки конфигурации ловятся
в момент ввода; число обращений «не взлетело» падает.
- Платформа получает переиспользуемые нормативы: wizard-контракт consent-gated мутаций (п.2),
маркер managed-файла (п.3), headless-канал env-prefill+`--yes` (п.4) — для будущих
операторских CLI.
- Цена: двойной источник маршрута (док+скрипт) — под нормативом same-PR и анти-дрейф тестами;
дефолт `apply` расходится с семейством — зафиксировано явным тест-ассертом с обоснованием.
- Откат: удалить скрипт, тест-модуль, §1.1/footer и аддитивный тест — состояние 1:1
(scripts+docs+tests, без миграций).
## Связи
adr-0037 (ORCH-102 — канон LITE_SETUP: автоматизируемый маршрут и пиннинг структуры),
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: 22 статуса, D10 branch protection), adr-0033 (ORCH-100 —
C-1 независимые Telegram-каналы). Детально —
`docs/work-items/ORCH-104/06-adr/ADR-001-setup-lite-interactive-installer.md`,
`07-infra-requirements.md`, `10-tech-risks.md`.

View File

@@ -0,0 +1,470 @@
---
work_item: ORCH-104
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-11
model_used: 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.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_bundle `apply` мутирует
без per-action вопросов (его согласие = выбор режима), поэтому там безопасен только
plan-default; у setup_lite **каждая мутация отдельно согласуется** (FR-1), поэтому
apply-default безопасен по построению:
1. **Фаза 0 `apply` ≡ `plan`:** 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).
### 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.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`**: вывод плана кирпича показывается пользователю → согласие → `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` (строка
`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_onboard` → `instructions`
`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` (канон имён ключей)

View File

@@ -0,0 +1,65 @@
---
work_item: ORCH-104
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-11
model_used: claude-opus-4-8
---
# 07 — Инфра-требования: ORCH-104 — Установочный скрипт Lite-тиража (интерактивный installer)
Work Item: **ORCH-104** · Repo: **orchestrator** · Стадия: architecture
> When-applicable: топология **нашего** прода не меняется — файл создан для
> аудитопригодности с явными `N/A` и фиксацией предусловий **целевого хоста заказчика**
> (на котором скрипт исполняется). Решения — `06-adr/ADR-001-setup-lite-interactive-installer.md`.
## I-1. Топология / окружения
- **Наш прод/staging (mva154):** `N/A` — задача scripts+docs+tests; контейнеры, порты, сеть,
тома, compose-файлы не меняются. В нашем контуре скрипт инертен (активация — только явный
запуск человеком; запуска на mva154 не предполагается вовсе).
- **Целевой хост заказчика (где скрипт работает):** контур Lite без изменений — Linux x86_64,
Docker Engine + Compose v2, git, python3, node (LITE_SETUP §2). Скрипт сам **сканирует** эти
предусловия и предлагает доустановить недостающее (per-package consent, ADR D4); результат
установки — ровно базовый Lite-контур: `orchestrator` + `orchestrator-watchdog`
(`orchestrator-staging` НЕ поднимается — строго за `profiles: [staging]`).
- **Discovery-предположение:** Plane/Gitea заказчика обнаруживаются только как
docker-инсталляции локального хоста (image-префиксы `makeplane/*`, `gitea/gitea*`,
`docker.gitea.com/gitea*` — ADR D5); native/k8s-инсталляции — честный ручной ввод URL
(не FAIL). Сетевая достижимость Plane/Gitea с хоста оркестратора — предусловие заказчика
(BRD §6).
## I-2. Переменные окружения / секреты
- **Новых ключей `Settings`/`.env.example`/`.env.watchdog.example` НЕТ** (TRZ §8) — каноны
только читаются как шаблоны рендера.
- Скрипт **пишет** целевые `.env`/`.env.watchdog` на хосте заказчика: от канонов-example,
права `600`, первая строка — маркер managed-файла `# managed by scripts/setup_lite.py
(ORCH-104)` (ADR D6: немаркированный существующий файл → отказ exit 2 без `--force`).
- Секреты: webhook-секреты — только свежий выпуск кирпичом `gen_secrets.py` (субпроцесс);
внешние токены (Plane/Gitea/Telegram ×2) — скрытый ввод оператора с немедленной верификацией;
значения нигде не печатаются и не логируются (NFR-3). Боевые секреты исходного хоста не
используются даже как подсказки-дефолты (stateless, LITE_SETUP §12).
- Headless-prefill: переменные окружения процесса с каноническими именами ключей + явный
`--yes` (ADR D10); answers-file не вводится.
## I-3. Деплой / рестарт
- **Рестарт нашего прод-контейнера НЕ требуется и не выполняется** (self-hosting инвариант):
изменение — файлы `scripts/` + `tests/` + docs; выкат задачи — штатный конвейер
(deploy-staging 8501 → `Confirm Deploy`).
- На **хосте заказчика** скрипт выполняет `docker compose up -d --build` и управляемый
`--force-recreate orchestrator` **только собственного свежеподнятого** контура, после
проверки тихого окна `GET /queue` (ADR D11); чужие/уже бегущие контейнеры не трогаются без
согласия (NFR-7); delete-операций нет вообще.
## I-4. CI/CD
- `.gitea/workflows/`**без изменений**. Новый структурный/unit тест-модуль
`tests/test_setup_lite_script.py` + аддитивный тест в `tests/test_lite_setup_doc.py`
попадают в существующий прогон `pytest tests/ -q` (детерминированы: без TTY/сети/docker —
инжектируемый I/O, моки, tmp_path; NFR-5).
- Инфра-предусловий прод-образа нет (скрипт не входит в рантайм-контейнер как зависимость;
pip-зависимости не добавляются — stdlib-only).

View File

@@ -0,0 +1,47 @@
---
work_item: ORCH-104
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-11
model_used: claude-opus-4-8
---
# 10 — Технические риски: ORCH-104 — Установочный скрипт Lite-тиража (интерактивный installer)
Work Item: **ORCH-104** · Repo: **orchestrator** · Стадия: architecture
> Информационный (гейтом не парсится). Риски реализации и эксплуатации интерактивного
> installer'а; решения — `06-adr/ADR-001-setup-lite-interactive-installer.md` (D1…D12).
> Развивает реестр BRD §8 (R-1…R-6).
## Реестр рисков
| ID | Риск | Вер. | Влия. | Митигейшн |
|----|------|------|-------|-----------|
| TR-1 | **Случайный запуск на хосте с живым продом** (R-1): дефолт-режим — `apply`-wizard, не `plan` | Низ. | Выс. | Структурные гарантии D2: фаза 0 `apply``plan` (read-only), ранний guard НЕмаркированного `.env` → exit 2 ДО первого вопроса (D6), per-action consent с дефолт-ответом «нет», non-TTY → exit 2 до мутаций (D10); `--force` требует явного набора + согласия; на нашем mva154 запуск не предполагается вовсе |
| TR-2 | **Оферы установки пакетов** (R-2) — вмешательство в систему заказчика; нативные репо могут дать не то (compose v1, старый docker) | Сред. | Сред. | Per-package consent с печатью точной команды ДО исполнения; закрытый набор менеджеров apt-get/dnf/yum/zypper; **re-check фактом после установки** (в т.ч. `docker compose version` → v2), не сошлось → MANUAL с официальной инструкцией; нет менеджера/sudo/отказ → честный MANUAL (D4) |
| TR-3 | **SQL-вставка webhook в чужую Plane-БД** (R-3): инвазивная мутация прод-инсталляции заказчика; пользовательский ввод в SQL | Сред. | Выс. | Path A (UI) — дефолт-рекомендация; Path Б — только при 5 предусловиях D8 (docker-Plane + подтверждённый пользователем postgres-контейнер + согласие с показом SQL + идемпотентный INSERT + обязательная пост-верификация SELECT); только INSERT/SELECT (no UPDATE/DELETE); секрет/пароль — stdin/env, не argv; валидация slug `^[a-z0-9-]+$` (анти-инъекция); любой сбой → fail-safe в Path A |
| TR-4 | **Дрейф скрипта от канона LITE_SETUP** (R-4): два источника маршрута расходятся | Сред. | Сред. | Канон-знания не дублируются (кирпичи субпроцессами, статусы — за `plane_sync` через onboarding, smoke — ссылкой на §11); footer-норматив «док + скрипт в одном PR» (FR-11/adr-0040); анти-дрейф: упоминание скрипта в доке, env-ключи ⊂ `.env.example`, зеркала needle-наборов (D12) |
| TR-5 | **Зависание/недетерминизм в non-TTY** (R-5): CI/обвязка заказчика виснет на ожидании ввода | Низ. | Сред. | `isatty`-гейт: `apply` без `--yes` в non-TTY → немедленный exit 2 с подсказкой; headless — только env-prefill + явный `--yes`; `plan`/`verify` — полноценные non-TTY режимы; весь интерактив за инжектируемым I/O (детерминированные тесты TC-12) (D10) |
| TR-6 | **Ложноположительный discovery** (R-6): чужой контейнер опознан как Plane/Gitea; неверный URL уезжает в конфиг | Низ. | Сред. | Опознание строго по image-префиксам (не по именам); выбор всегда за пользователем + пункт «ввести вручную» всегда; токен-верификация выбранного URL обязательна (FR-4) — неверный кандидат не пройдёт `collect` (D5/D9) |
| TR-7 | **Утечка секретов** через транскрипт wizard'а / отчёт / файлы | Низ. | Выс. | Скрытый ввод (getpass-класс); значения не печатаются и не логируются (только имена ключей); итоговая таблица без значений; live-файлы `600`; файл-отчёт прогона сознательно НЕ вводится; анти-дрейф: тест транскрипта (TC-11) + секрет-эвристика на fenced-блоках дока (D3/D6/D10) |
| TR-8 | **Коллизия resume ⟷ guard существующего `.env`**: после manual-step повторный запуск отбивается собственным артефактом ЛИБО guard ослабляется и перетирает чужой конфиг | Сред. | Выс. | Маркер managed-файла (первая строка): без маркера → отказ exit 2 (чужой конфиг), с маркером → resume-ensure без перетирания значений (D6); семантика покрыта unit-тестами (TC-03/TC-14) |
| TR-9 | **Разнообразие хостов**: экзотический дистрибутив/пакетный менеджер/нестандартный docker — скан даёт неверный вердикт | Сред. | Низ. | Вердикты — чистые функции от фактов (тестируемы на фикстурах); неопределимое → честный MANUAL с generic-командами и ссылкой на § LITE_SETUP (никогда не падение); не-Linux/не-x86_64 → WARN «вне контура» (рамка Lite не расширяется) (D4) |
| TR-10 | **Дрейф интерфейсов кирпичей** (`gen_secrets.py`/`onboard_project.py`): смена флагов/формата отчёта молча ломает installer | Низ. | Сред. | Контракты кирпичей закреплены их собственными тестами (`test_secrets_gen`, onboarding-тесты); builder аргументов — чистая функция под unit-тестом (ломается громко в CI); exit-коды кирпичей транслируются честно (2 → MANUAL, 1 → FAIL) (D11); норматив same-PR симметричен |
## Сводный вывод
Доминирующий класс — **эксплуатационные риски целевого хоста заказчика** (TR-2/TR-3/TR-9):
наш прод они не затрагивают и гасятся per-action consent'ом, контрактом manual-checkpoint,
честными MANUAL-деградациями и обязательной верификацией каждого ввода. Специфика ORCH-104
против предшественников — два новых внимательных места: **дефолт-режим `apply`** (TR-1; закрыт
структурными гарантиями D2, а не дисциплиной) и **маркер managed-файла** (TR-8; единственный
механизм, примиряющий идемпотентный resume с защитой чужого `.env`).
Рисков для прод-конвейера self-hosting **нет по построению** (BR-8: рантайм байт-в-байт,
kill-switch не нужен — активация только явным запуском оператора; полный регресс и все
существующие анти-дрейф тесты остаются зелёными, пиннинг «13 разделов» LITE_SETUP не меняется).
Эскалация `arch:major-change` не требуется: новых стадий/компонентов рантайма/смены БД нет —
задача целиком в слое дистрибуции (паттерн ORCH-101/102/103). Возврат в анализ не требуется:
ТЗ выполнимо без нарушения принципов архитектуры.