architect(ET): auto-commit from architect run_id=639
All checks were successful
CI / test (push) Successful in 59s
All checks were successful
CI / test (push) Successful in 59s
This commit is contained in:
@@ -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)
|
||||
|
||||
Единая точка входа «бизнес + тех» для трёх аудиторий (заказчик / менеджер / разработчик) —
|
||||
|
||||
121
docs/architecture/adr/adr-0040-lite-interactive-installer.md
Normal file
121
docs/architecture/adr/adr-0040-lite-interactive-installer.md
Normal 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`.
|
||||
@@ -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` (канон имён ключей)
|
||||
65
docs/work-items/ORCH-104/07-infra-requirements.md
Normal file
65
docs/work-items/ORCH-104/07-infra-requirements.md
Normal 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).
|
||||
47
docs/work-items/ORCH-104/10-tech-risks.md
Normal file
47
docs/work-items/ORCH-104/10-tech-risks.md
Normal 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). Возврат в анализ не требуется:
|
||||
ТЗ выполнимо без нарушения принципов архитектуры.
|
||||
Reference in New Issue
Block a user