diff --git a/docs/work-items/ORCH-104/01-brd.md b/docs/work-items/ORCH-104/01-brd.md new file mode 100644 index 0000000..a9d755a --- /dev/null +++ b/docs/work-items/ORCH-104/01-brd.md @@ -0,0 +1,187 @@ +--- +work_item: ORCH-104 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-12 +model_used: claude-opus-4-8 +--- + +# 01 — BRD (бизнес-требования): ORCH-104 — Установочный скрипт для Lite + +Work Item: **ORCH-104** · Repo: **orchestrator** (self-hosting) · Стадия: analysis +Тип: FEATURE — UX/онбординг тиража (Type A эпика ORCH-10), поверх ORCH-102 (LITE_SETUP.md) + +> ⚠️ **Объём заморожен (2026-06-12).** Один интерактивный установщик, автоматизирующий +> ручной маршрут `docs/deployment/LITE_SETUP.md`. Это **scripts + docs + tests**-изменение: +> рантайм/конвейер (`src/**`, `STAGE_TRANSITIONS`, `QG_CHECKS`, схема БД) — **не трогаются** +> (см. §2 «Вне объёма», NFR-1). Установщик НЕ форкает каноны: секреты — кирпичом +> `gen_secrets.py`, онбординг — кирпичом `onboard_project.py`, env — из `.env.example`. + +--- + +## 1. Бизнес-контекст и проблема + +### 1.1. Цель +Максимально упростить установку оркестратора в Lite-режиме на чужой хост: свести +тринадцатишаговую ручную инструкцию к **одному запускаемому файлу**, который сам сканирует +систему, подсказывает чего не хватает, запрашивает у оператора только действительно +неизвестные данные (токены/URL) и доводит инсталляцию до работающего конвейера. + +### 1.2. Корневая боль (установленный факт) +`docs/deployment/LITE_SETUP.md` (ORCH-102) — золотой источник Lite-тиража, но это **ручной +runbook из 13 разделов**. Оператор вручную: проверяет предусловия хоста (§2), клонирует код (§3), +копирует `.env.example`, гоняет `gen_secrets.py`, **заполняет ~20 ключей `.env`** по четырём +группам (Plane/Gitea/Telegram/хост-порты, §4), настраивает webhook Plane (иногда **прямым SQL в +Postgres**, §5.4 путь Б), выпускает токен и webhook Gitea (§6), ставит claude CLI (§7), заводит +**два** Telegram-бота (§8), поднимает compose (§9), регистрирует проект `onboard_project.py` (§10), +гоняет smoke (§11). Это долго, легко ошибиться (опечатка в секрете → 401 HMAC; неверный uid → +worktree не пишется; занятый порт; пропущенный fail-closed статус `Confirm Deploy`/`STOP`), и +ошибка часто всплывает поздно — на `docker compose up`, а не в момент ввода. + +### 1.3. Почему именно установщик (а не «ещё инструкция») +Ручной маршрут уже задокументирован и стабилен (ORCH-102). Ценность ORCH-104 — **автоматизация +happy-path и ранняя валидация**: авто-детект всего детектируемого (uid/gid/docker-gid, свободные +порты, node/claude, **существующие инсталляции Plane/Gitea**), живая проверка каждого введённого +секрета ДО записи в `.env`, идемпотентный безопасный повтор. LITE_SETUP.md остаётся справочником +и фолбэком для траблшутинга — установщик его не заменяет, а исполняет. + +### 1.4. Установленные факты (переиспользуемые кирпичи — НЕ изобретать) +- **`scripts/bootstrap_bundle.py` (ORCH-103)** — ближайший прецедент: step-движок `check→ensure`, + режимы `plan`/`apply`/`verify`, exit-коды `0/2/1`, честные `manual_checkpoint` с + `input()`/`getpass()`, stdlib-only, секреты не печатаются, **delete-операций нет вообще**, + каноны переиспользуются субпроцессами. Установщик Lite — его «connect-only»-сородич: bundle + *поднимает* Plane/Gitea, Lite *подключается* к уже существующим. +- **`scripts/gen_secrets.py` (ORCH-101)** — выпуск webhook-секретов (`secrets.token_hex(32)`), + отказ перезаписи без `--force`. Единственный легитимный источник секретов. +- **`scripts/onboard_project.py` (ORCH-009)** — регистрация проекта (22 статуса с точными + именами, лейблы `autoApprove`/`autoDeploy`/`Bug`, репо+webhook), `plan`/`apply`/`verify`, + идемпотентно, exit `0/2/1`. +- **`.env.example` / `.env.watchdog.example`** — канон 100% ключей старта (дефолт каждого ключа = + боевому значению, ORCH-101); установщик рендерит `.env` из них, не выдумывая ключи. +- **`docker-compose.yml`** — сам по себе является Lite-подмножеством (дефолтный `up -d` поднимает + ровно `orchestrator` + `orchestrator-watchdog`; `orchestrator-staging` за профилем `staging`). + +### 1.5. Решения владельца (Owner decisions) +Предложены Владельцу как рекомендованные дефолты 2026-06-12; приняты для старта анализа, +пересматриваемы на review. + +| ID | Решение | Дата | +|----|---------|------| +| D-1 | **Установка зависимостей = детект + управляемая установка.** Установщик сканирует, показывает чего не хватает, печатает **точную команду** под обнаруженный дистрибутив и предлагает выполнить безопасные с явного согласия (`y/N`). Системные пакеты под root — только с подтверждением, **никогда молча**. (Совпадает с self-hosting-этосом и каноном `bootstrap_bundle.py`.) | 2026-06-12 | +| D-2 | **Plane/Gitea = только подключение (Lite = Type A).** Установщик **детектит существующие** инсталляции; при нескольких — показывает нумерованный список и даёт выбрать; ручной ввод URL — всегда фолбэк. Если их нет вовсе → инструктирует поставить самостоятельно либо указывает на Bundled-тираж (ORCH-103). Установщик их **не поднимает**. | 2026-06-12 | +| D-3 | **Режимы = интерактивный мастер + `plan`/`apply`/`verify`.** Канон `bootstrap_bundle.py`: `plan` (дефолт, ноль мутаций) / `apply` / `verify`; exit `0/2/1`; без TTY → fail-closed exit 2 с инструкцией; идемпотентный повтор. Скрипт пригоден и для повторного/CI-прогона. | 2026-06-12 | + +--- + +## 2. Объём (scope) + +### В объёме +- **FR-1** — Единый entry-point `scripts/install_lite.py` с режимами `plan`/`apply`/`verify`. +- **FR-2** — Скан-предусловий хоста (детект docker/compose/git/python3/node/claude CLI/портов/ + uid/gid/docker-gid/каталогов) с понятным списком «чего не хватает». +- **FR-3** — Управляемая установка недостающих зависимостей (детект дистрибутива → точная команда → + выполнение безопасных с согласия; D-1). +- **FR-4** — Детект существующих инсталляций Plane/Gitea на хосте + выбор при нескольких + ручной + фолбэк (D-2). +- **FR-5** — Интерактивный сбор данных оператора (Plane/Gitea/Telegram токены/URL) с **живой + верификацией каждого ДО записи** и скрытым вводом секретов. +- **FR-6** — Выпуск webhook-секретов строго кирпичом `gen_secrets.py`. +- **FR-7** — Сборка `.env` / `.env.watchdog` из канона `.env.example` / `.env.watchdog.example` + (идемпотентный рендер, права 600, без молчаливой перезаписи). +- **FR-8** — Подъём `orchestrator` + `orchestrator-watchdog` (`docker compose up`) + ожидание + готовности. +- **FR-9** — Регистрация проекта строго кирпичом `onboard_project.py apply`/`verify` + запись + `ORCH_PROJECTS_JSON`. +- **FR-10** — Health-верификация (`/health` / `/queue` / `/metrics`) + итоговая сводка PASS/FAIL. +- **FR-11** — Синхронизация документации (указатель LITE_SETUP.md на установщик; CLAUDE/README/ + overview/CHANGELOG). + +### Вне объёма +- ❌ **Любые изменения рантайма/конвейера** — `src/**`, `STAGE_TRANSITIONS`, `QG_CHECKS`, `check_*`, + machine-verdict ключи, схема БД (NFR-1; установщик вне процесса орка и вне конвейера QG). +- ❌ **Подъём самих Plane/Gitea** — это Bundled-тираж (ORCH-103); Lite только подключается (D-2). +- ❌ **Полный безусловный авто-install системных пакетов под root без согласия** (D-1 отвергает). +- ❌ **Teardown / удаление** — установщик не несёт delete-операций (NFR-4); снос — документированная + процедура. +- ❌ **Замена LITE_SETUP.md** — runbook остаётся золотым источником и фолбэком (дополняем, не + выкидываем). +- ❌ **Форк канонов** — секреты/онбординг/env/compose не реимплементируются (NFR-7). +- ❌ **Автоматизация интерактивного логина claude CLI** — это поток Anthropic; остаётся + верифицируемым manual-step (OQ-6). + +--- + +## 3. Заинтересованные стороны +- **Заказчик/инициатор:** Владелец (Слава) — цель «максимально упростить установку для пользователей». +- **Прямой пользователь:** внешний оператор/заказчик платформы, разворачивающий Lite на своём хосте. +- **Затрагиваемые:** сопровождающие LITE_SETUP.md (норматив синхронизации, BR-9); агенты + пайплайна (reviewer — проверка doc-sync; tester — прогон новых тестов). +- **Принимает результат:** reviewer (стадия review) + tester (стадия testing) по критериям §03. + +--- + +## 4. Бизнес-требования (BR) + +| ID | Требование | Связь | +|----|------------|-------| +| **BR-1** | Один запускаемый файл проводит оператора от «код склонирован» до «работающий Lite-конвейер», автоматизируя LITE_SETUP.md §2–§11. | FR-1, AC-1 | +| **BR-2** | Установщик сканирует хост и выдаёт чёткий список отсутствующих предусловий (что есть / чего не хватает). | FR-2, AC-2 | +| **BR-3** | Для каждого недостающего предусловия — точная команда установки под обнаруженный дистрибутив + предложение выполнить безопасные с явного согласия (никогда молча, D-1). | FR-3, AC-3 | +| **BR-4** | Данные, которые знает только оператор (токены/URL Plane, Gitea, Telegram), запрашиваются интерактивно в момент установки, **каждое — с живой проверкой ДО записи**; секреты вводятся скрыто и не логируются. | FR-5, AC-5, AC-6 | +| **BR-5** | Установщик детектит существующие инсталляции Plane и Gitea; при нескольких — показывает список и даёт выбрать; ручной ввод URL — всегда доступный фолбэк (D-2). | FR-4, AC-4 | +| **BR-6** | Каноны не форкаются: webhook-секреты — `gen_secrets.py`, регистрация проекта — `onboard_project.py`, env — из `.env.example`/`.env.watchdog.example`, стек — из `docker-compose.yml`. | FR-6, FR-7, FR-9, AC-7 | +| **BR-7** | Идемпотентность и наблюдаемость: режимы `plan`(дефолт)/`apply`/`verify`; повтор пропускает завершённые шаги; exit `0/2/1`; без TTY → fail-closed exit 2 с инструкцией; финальная сводка PASS/FAIL. | FR-1, FR-10, AC-8, AC-11 | +| **BR-8** | Self-hosting-безопасность: установщик — только scripts/docs/tests; никогда не правит `src/**`/конвейер/схему; исполняется на хосте заказчика; говорит только с локальным хостом и собственными Plane/Gitea/Telegram заказчика; delete-операций нет. | NFR-1, NFR-4, AC-9, AC-10 | +| **BR-9** | Норматив сопровождения: установщик автоматизирует LITE_SETUP.md → оба держатся в синхроне (меняешь шаги установки → обнови LITE_SETUP.md в том же PR; держит анти-дрейф тест). | FR-11, AC-12 | + +--- + +## 5. Нефункциональные требования (NFR) + +| ID | Требование | +|----|------------| +| **NFR-1** | **Рантайм байт-в-байт.** `src/**`, `STAGE_TRANSITIONS`, `QG_CHECKS`, `check_*`, machine-verdict ключи, схема БД — не трогаются. Kill-switch не нужен: активация = только явный запуск оператором (паттерн ORCH-009/102/103). | +| **NFR-2** | **Гигиена секретов.** Значения секретов никогда не печатаются в stdout/логи; `.env`/`.env.watchdog` пишутся правами 600; существующие секреты не перетираются без явного согласия/`--force`. | +| **NFR-3** | **Never-raise в детекте.** Сбой любой эвристики обнаружения/пробы деградирует на ручной ввод и не роняет установщик и не блокирует прогон (best-effort, fail-safe). | +| **NFR-4** | **Нет delete-операций** нигде в скрипте (teardown — только документированная процедура; зеркало ORCH-103 D9). | +| **NFR-5** | **Идемпотентный ensure.** Каждый шаг `check→ensure`; повтор безопасен; валидный существующий конфиг пропускается; дублей проекта/webhook не создаётся. | +| **NFR-6** | **stdlib-only.** Никаких новых зависимостей платформы — работает на голом `python3` целевого хоста ДО первого `docker compose up` (как `gen_secrets.py`/`bootstrap_bundle.py`). Каноны-знания — только субпроцессами кирпичей. | +| **NFR-7** | **Детерминированные анти-дрейф тесты.** В unit-тестах — без сети/docker/subprocess/LLM; чистые функции изолированы; HTTP/процессы — через инъекцию фейков. | +| **NFR-8** | **Кросс-дистрибутив.** Детект и команды установки работают на распространённых Linux (Debian/Ubuntu `apt`, RHEL/Fedora `dnf`); неизвестный дистрибутив → деградация на «инструктировать». | + +--- + +## 6. Допущения и ограничения +- **Контур Lite** (LITE_SETUP §1–§2): Linux x86_64, у оператора есть root/sudo для установки + системных пакетов (или он ставит их сам). Вне контура — вне гарантии. +- **Plane и Gitea — собственные инсталляции заказчика** (Type A). Установщик к ним подключается, + не управляет их жизненным циклом. +- **Plane CE не даёт API первичной инициализации/иногда webhook** (LITE_SETUP §5.4): такие шаги + остаются честными manual-step с верификацией результата (контракт `manual_checkpoint`). +- **Интерактивный логин claude CLI** не автоматизируется (поток Anthropic) — manual-step (OQ-6). +- **Платформенные конвенции (не менять):** репо обязан называться `orchestrator` + (`SELF_HOSTING_REPO`); имена compose-сервисов/профиля — константы; `ORCH_STAGING_PORT` ≠ + прод-порт (fail-closed guard ORCH-058). + +--- + +## 7. Критерии успеха (резюме) +Оператор на свежем хосте запускает один файл, отвечает на минимум вопросов (только то, что +система не может определить сама), и получает поднятый Lite-контур с зарегистрированным проектом и +зелёными `/health` `/queue` `/metrics`. Повторный запуск безопасен и пропускает сделанное. Рантайм +не изменён. Детальные PASS/FAIL — в `03-acceptance-criteria.md`. + +--- + +## 8. Риски (кратко; детали — 10-tech-risks.md, заполняет архитектор) +- **R-1** — Эвристика детекта Plane/Gitea ложно-положительна/отрицательна (нестандартные имена + контейнеров/порты) → митигируется ручным фолбэком (NFR-3) и живой верификацией URL (BR-4). +- **R-2** — Авто-install системных пакетов небезопасен/дистрибутиво-зависим → митигируется D-1 + (только с согласия, точная команда, неизвестный дистрибутив → инструктировать). +- **R-3** — Дрейф установщик ↔ LITE_SETUP.md при будущих правках → митигируется BR-9 + анти-дрейф + тестом. +- **R-4** — Случайная утечка секрета в лог/перетирание `.env` → митигируется NFR-2 (маскирование, + 600, без silent overwrite), покрыто AC-6. +- **R-5** — Дублирование логики `bootstrap_bundle.py` (parse_env/render_env/manual_checkpoint) → + архитектурный вопрос «общий модуль vs самодостаточный файл» (OQ-4), не блокирует анализ. diff --git a/docs/work-items/ORCH-104/02-trz.md b/docs/work-items/ORCH-104/02-trz.md new file mode 100644 index 0000000..85a0e1b --- /dev/null +++ b/docs/work-items/ORCH-104/02-trz.md @@ -0,0 +1,193 @@ +--- +work_item: ORCH-104 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-12 +model_used: claude-opus-4-8 +--- + +# 02 — ТЗ (TRZ): ORCH-104 — Установочный скрипт для Lite + +Work Item: **ORCH-104** · Repo: **orchestrator** · Стадия: analysis + +> ТЗ описывает **конкретные требования к реализации**, выведенные из BRD и фактического кода. +> Архитектурное обоснование (структура скрипта, точные эвристики детекта, общий код с +> `bootstrap_bundle.py`, финальное имя файла) — задача архитектора (`06-adr/`). Открытые вопросы — +> §12. + +## 1. Сводка изменения +Добавляется **один интерактивный установщик** `scripts/install_lite.py`, автоматизирующий ручной +маршрут `docs/deployment/LITE_SETUP.md` §2–§11 для Lite-тиража (Type A). Скрипт сканирует хост, +детектит/предлагает доустановить зависимости, обнаруживает существующие Plane/Gitea (выбор при +нескольких), интерактивно собирает и **живо верифицирует** токены/URL, выпускает секреты кирпичом +`gen_secrets.py`, собирает `.env`/`.env.watchdog` из канон-`.example`, поднимает `orchestrator` + +`orchestrator-watchdog`, регистрирует проект кирпичом `onboard_project.py` и проверяет health. +Изменение **аддитивно** и **вне рантайма**: `src/**`/`STAGE_TRANSITIONS`/`QG_CHECKS`/схема БД — не +трогаются; kill-switch не нужен (активация — только явный запуск). Каноны не форкаются. + +## 2. Задействованные модули / пути + +| Путь | Действие | +|------|----------| +| `scripts/install_lite.py` | **создать** — интерактивный установщик Lite (entry-point; имя — OQ-1) | +| `scripts/gen_secrets.py` | **переиспользовать** (subprocess-кирпич), без изменений | +| `scripts/onboard_project.py` | **переиспользовать** (subprocess-кирпич, `apply`/`verify`), без изменений | +| `.env.example`, `.env.watchdog.example` | **переиспользовать** как канон-источник рендера, без изменений | +| `docker-compose.yml` | **переиспользовать** (`docker compose up -d --build`), без изменений | +| `scripts/bootstrap_bundle.py` | **эталон-паттерн** (parse_env/render_env/preflight_verdict/manual_checkpoint/exit-коды) — не изменяется; общий код — OQ-4 | +| `docs/deployment/LITE_SETUP.md` | **обновить** — указатель на установщик как рекомендованный путь + синхронизация шагов | +| `tests/test_install_lite_script.py` | **создать** — unit (чистые функции) + структурные анти-дрейф тесты | +| `tests/test_lite_setup_doc.py` | **обновить** — ассерт ссылки на установщик; существующие ассерты зелёные | +| `CLAUDE.md`, `CHANGELOG.md`, `docs/architecture/README.md`, `docs/overview/` | **обновить** (docs golden-source, правило агентов №2) | + +## 3. Функциональные требования + +### FR-1 — Entry-point и режимы +Скрипт `scripts/install_lite.py`, запускаемый из корня чекаута: `argparse`, позиционный `mode ∈ +{plan, apply, verify}` (дефолт `plan`). Exit-коды (контракт): **0** — успех; **2** — остановка на +manual-step / незавершённое предусловие / нет TTY; **1** — ошибка. `plan` — ноль мутаций (печать +плана + read-only preflight-диагностика). `apply` — полный прогон step-движком `check→ensure` +(повтор = каскад skip; «resume» = повторный запуск). `verify` — read-only пост-проверка +(health-контракты + `onboard_project.py verify`). Привязка: BR-1, BR-7. + +### FR-2 — Скан предусловий хоста (preflight) +Read-only снимок хоста (по образцу `bootstrap_bundle.collect_facts`/`preflight_verdict`): наличие +`docker`, `docker compose` v2, `git`, `python3`, `node`, `claude` CLI + читаемость кред; свободность +портов (прод-порт `ORCH_DEPLOY_PROD_TARGET_PORT` дефолт 8500, при self-hosting-вилке staging 8501); +`uid`/`gid`/`docker-gid` и владелец каталога репозиториев; наличие ssh-каталога. Чистая функция- +вердикт возвращает `(blockers, warnings)`; человекочитаемый список «есть/нет». Привязка: BR-2. + +### FR-3 — Управляемая установка зависимостей (D-1) +Для каждого блокера-зависимости: детект пакетного менеджера (`apt`/`dnf` по наличию бинаря / +`/etc/os-release`) → **точная команда** установки (чистая функция «дистрибутив+пакет → команда»); +печать команды; для безопасных — предложение выполнить с согласия (`y/N`, `input()`); отказ/нет +TTY/неизвестный дистрибутив → печать инструкции и `exit 2` (никакой молчаливой root-мутации). +Привязка: BR-3, AC-3. + +### FR-4 — Детект существующих Plane/Gitea + выбор (D-2) +Best-effort обнаружение (never-raise, NFR-3): кандидаты из `docker ps` (имена/образы, похожие на +Plane: `plane-*`/`makeplane`/`proxy`; Gitea: `gitea/gitea`/`gitea-*`) и из слушающих портов +(типовые Plane 80/8080/443, Gitea 3000). По кандидату — проба живости (Plane: `GET /api/instances/`; +Gitea: `GET /api/v1/version`). Чистая функция формирует ранжированный список кандидатов. Поведение: +0 кандидатов → запрос ручного URL; ≥2 → нумерованный список + выбор (`input()` индекс), вне +диапазона → ручной ввод; 1 → предложить с подтверждением. Выбор наполняет `ORCH_PLANE_*` / +`ORCH_GITEA_*`. Привязка: BR-5, AC-4. + +### FR-5 — Интерактивный сбор данных + живая верификация +Honest-checkpoint контракт (как `bootstrap_bundle.manual_checkpoint`): для каждого требуемого +секрета/параметра — печать откуда взять (ссылка на LITE_SETUP §5–§8), скрытый ввод секрета +(`getpass`), **верификация ДО записи**: Plane — `GET /api/v1/workspaces//projects/` с +`X-API-Key`; Gitea — `GET /api/v1/user` с `Authorization: token`; Telegram — `GET /bot/getMe`. +Провал → повтор (до N) или `exit 2` с подсказкой, значение **не пишется**. Авто-детект и +пред-заполнение всего детектируемого (uid/gid/docker-gid/порты/пути/node/claude/выбранные URL) — +оператор только подтверждает. Привязка: BR-4, AC-5. + +### FR-6 — Выпуск webhook-секретов кирпичом `gen_secrets.py` +`ORCH_PLANE_WEBHOOK_SECRET` / `ORCH_GITEA_WEBHOOK_SECRET` выпускаются **строго** субпроцессом +`gen_secrets.py` (никакого собственного `secrets.token_hex` в установщике — анти-форк, AC-7); если +уже присутствуют в `.env` и валидны — пропуск (не перетирать без `--force`). Привязка: BR-6, NFR-2. + +### FR-7 — Сборка `.env` / `.env.watchdog` +Идемпотентный рендер из канона (`render_env`-паттерн): существующий файл — обновить ключи- +override, отсутствующий — отрендерить из `.env.example` / `.env.watchdog.example`; комментарии +канона сохранены; неизвестные ключи — управляемым блоком в конец; запись правами **600**; значения +секретов в stdout/лог **не попадают**; молчаливой перезаписи нет. Watchdog-ключи (`WATCHDOG_TG_*`) +кладутся **только** в `.env.watchdog` (файл-носитель, LITE_SETUP §4.3). Привязка: BR-6, NFR-2. + +### FR-8 — Подъём стека + готовность +`docker compose up -d --build` ровно `orchestrator` + `orchestrator-watchdog` (staging НЕ +поднимается — за профилем). Ожидание готовности поллингом `GET /health` (таймаут). Перед записью +`ORCH_PROJECTS_JSON` стек уже жив. Привязка: BR-1. + +### FR-9 — Регистрация проекта кирпичом `onboard_project.py` +Сбор параметров проекта (имя/repo/prefix/стек/тест-команда/порты/webhook-URL — флаги или интерактивно), +вызов `onboard_project.py apply` затем `verify` субпроцессом; парс merged-`ORCH_PROJECTS_JSON` из +отчёта и запись в `.env`; ручные пункты отчёта (manual-step) пробрасываются оператору. **Никакого +собственного создания статусов/лейблов/репо** (анти-форк, 22 статуса — только онбординг-кирпич). +Привязка: BR-6, AC-7. + +### FR-10 — Health-верификация + сводка +После `apply` (и в `verify`): `GET /health` → 200, `GET /queue` / `GET /metrics` → валидный JSON. +Итоговая сводка по шагам (`ok`/`skipped`/`manual-step`) + общий вердикт; любой FAIL → `exit 1` с +диагностикой (хвост `docker logs` / снапшот). Привязка: BR-7, AC-11. + +### FR-11 — Синхронизация документации +`LITE_SETUP.md` дополняется указателем «рекомендованный путь — `install_lite.py`; ручной маршрut +ниже как фолбэк/референс». Обновляются `CLAUDE.md` (раздел тиража), `docs/architecture/README.md` +(Type A), `docs/overview/` (если затронута витрина), `CHANGELOG.md`. Привязка: BR-9, AC-12. + +## 4. Изменения API +**Нет.** Установщик — вне процесса орка; обращается только к существующим read-only эндпоинтам +(`/health`, `/queue`, `/metrics`) как HTTP-клиент и к собственным Plane/Gitea/Telegram заказчика. +Новых/изменённых эндпоинтов оркестратора не вводится. + +## 5. Изменения схемы БД +**Нет.** Установщик не касается БД оркестратора (её создаёт сам орк пустой при первом старте, +stateless-инвариант LITE_SETUP §12). + +## 6. Требования к новым/изменённым QG checks +**Нет.** `QG_CHECKS` / `check_*` / `STAGE_TRANSITIONS` / machine-verdict ключи — байт-в-байт не +трогаются (INV-1). Установщик не участвует в решении ни одного гейта. + +## 7. Конфигурация +Новых **рантайм**-ключей `config.py` / kill-switch — **нет** (NFR-1; активация = явный запуск). +Установщик читает/пишет только `.env` / `.env.watchdog` (канон ключей — `.env.example` / +`.env.watchdog.example`, ORCH-101). CLI-флаги установщика (имена — OQ-7): режим + параметры проекта +для `onboard_project.py` (`--repo`/`--prefix`/`--stack`/…), возможный `--force` (перевыпуск +секретов), возможный `--non-interactive`/значения из env для CI. + +## 8. Наблюдаемость +- Прогресс-лог по шагам (`ok`/`skipped`/`manual-step`/`error`) — **без значений секретов** (только + имена ключей/пути файлов, NFR-2). +- Итоговая сводка PASS/FAIL + код выхода `0/2/1`. +- `manual_checkpoint` печатает точную инструкцию и верифицирует результат (молчаливый пропуск + запрещён); без TTY → `exit 2` с той же инструкцией. + +## 9. Артефакты pipeline (создаются/обновляются) +- `scripts/install_lite.py` (новый исполняемый артефакт). +- `tests/test_install_lite_script.py` (новый), `tests/test_lite_setup_doc.py` (обновление). +- `docs/work-items/ORCH-104/06-adr/ADR-001-.md` (архитектор) + опц. сквозной + `docs/architecture/adr/adr-NNNN-*.md`. +- `docs/deployment/LITE_SETUP.md`, `CLAUDE.md`, `docs/architecture/README.md`, `docs/overview/`, + `CHANGELOG.md` — обновления (BR-9). + +## 10. Совместимость / регресс +Аддитивно: новый файл-скрипт + новый тест + правки docs. Существующие кирпичи (`gen_secrets.py`, +`onboard_project.py`) и compose — байт-в-байт. Полный регресс `pytest tests/ -q` остаётся зелёным. +Обратимость — тривиальная (удаление нового файла/теста). Область раската — только хосты заказчиков +Lite; **наш прод не затронут** (установщик исполняется на чужом хосте, говорит только с локальным +хостом и инфраструктурой заказчика). + +## 11. Инварианты (не нарушать) +- **INV-1** — `src/**` / `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict ключи / + схема БД — байт-в-байт. +- **INV-2** — Каноны не форкаются: секреты — `gen_secrets.py`; статусы/лейблы/репо/webhook — + `onboard_project.py`; env — из `.env.example`/`.env.watchdog.example`; стек — из `docker-compose.yml`. +- **INV-3** — Нет delete-операций (никаких `docker … rm`, `rm -rf`, удаления веток, force-push). +- **INV-4** — Секреты не печатаются; `.env`/`.env.watchdog` — права 600; без молчаливой перезаписи. +- **INV-5** — Никогда не трогает наш прод / `main` / force-push; говорит только с локальным хостом и + собственными Plane/Gitea/Telegram заказчика. +- **INV-6** — Платформенные конвенции: репо `orchestrator`; имена compose-сервисов/профиля — + константы; staging за профилем; `ORCH_STAGING_PORT` ≠ прод-порт (guard ORCH-058) — установщик + уважает, не форкает. +- **INV-7** — stdlib-only (NFR-6). + +## 12. Открытые вопросы для архитектора (OQ — не блокируют анализ) +- **OQ-1** — Финальное имя/путь: `scripts/install_lite.py` (понятно конечному оператору) vs + `scripts/bootstrap_lite.py` (симметрия с `bootstrap_bundle.py`). Рекомендация анализа — + `install_lite.py`. +- **OQ-2** — Точные эвристики детекта Plane/Gitea (паттерны имён/образов контейнеров, набор + портов/URL-проб, ранжирование уверенности). +- **OQ-3** — Какие зависимости считать «безопасными для авто-выполнения с согласия» (напр. + `pip install -r requirements.txt` в venv — да; `apt install docker` под sudo — только consent; + claude CLI через npm); владелец distro-команд-карты. +- **OQ-4** — Общий код с `bootstrap_bundle.py` (вынести `parse_env`/`render_env`/`manual_checkpoint` + в общий stdlib-модуль) vs самодостаточный один файл (ради «1 установочный файл» и stdlib-only). + Trade-off DRY ↔ простота/одно-файловость. +- **OQ-5** — Драйвить ли путь Б Plane-webhook (raw-SQL, LITE_SETUP §5.4) автоматически (как + `bootstrap_bundle.step_plane_webhook`) или всегда оставлять верифицируемым manual-step. +- **OQ-6** — Подтвердить: интерактивный логин claude CLI остаётся manual-step с верификацией + (`claude --version` + читаемость кред), не автоматизируется. +- **OQ-7** — Набор CLI-флагов/env для не-интерактивного (CI) прогона: какие входы принимают + флаги/env vs только prompt. diff --git a/docs/work-items/ORCH-104/03-acceptance-criteria.md b/docs/work-items/ORCH-104/03-acceptance-criteria.md new file mode 100644 index 0000000..ccf8ca1 --- /dev/null +++ b/docs/work-items/ORCH-104/03-acceptance-criteria.md @@ -0,0 +1,171 @@ +--- +work_item: ORCH-104 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-12 +model_used: claude-opus-4-8 +--- + +# 03 — Критерии приёмки (Acceptance Criteria): ORCH-104 — Установочный скрипт для Lite + +Work Item: **ORCH-104** · Repo: **orchestrator** · Стадия: analysis + +Формат: каждый критерий имеет **PASS** (что должно быть истинно для приёмки) и **FAIL** (что +считается провалом). Reviewer/tester проверяют их буквально по файлам репозитория и поведению +скрипта (фейки HTTP/процессов — в тестах; сеть/docker в unit не нужны). + +> Область: автоматизация LITE_SETUP.md §2–§11 одним установщиком (scripts+docs+tests). Подъём самих +> Plane/Gitea (Bundled), teardown, правки рантайма — вне области (см. 01-brd §2). + +--- + +## AC-1 — Единый entry-point и режимы + +**Условие:** `python3 scripts/install_lite.py` (дефолт-режим `plan`) запущен из корня чекаута. +- **PASS:** печатается нормативный план шагов + read-only preflight-диагностика; **ноль мутаций** + (никаких записей в `.env`/`.env.watchdog`, никаких `docker compose up`); exit `0` (предусловия + чисты) либо `2` (есть блокеры). Поддержаны режимы `plan`/`apply`/`verify` (argparse). +- **FAIL:** режим `plan` что-то пишет/поднимает; нет одного из режимов; неизвестный код выхода + (не из `{0,1,2}`). + +--- + +## AC-2 — Скан предусловий выявляет нехватку + +**Условие:** на хосте отсутствует одно из предусловий (напр. `node` или свободный прод-порт). +- **PASS:** `plan`/preflight перечисляет отсутствующее как блокер с понятной строкой и подсказкой + (что доустановить / какой порт занят); при всех присутствующих — блокеров нет, разрешён `apply`. +- **FAIL:** отсутствующая зависимость не отмечена; ложный блокер при фактически выполненном + предусловии; preflight роняет скрипт исключением. + +--- + +## AC-3 — Управляемая установка зависимостей (D-1) + +**Условие:** недостающая зависимость, для которой определён пакетный менеджер хоста (`apt`/`dnf`). +- **PASS:** установщик печатает **точную команду** установки под обнаруженный дистрибутив; авто- + выполнение — только после явного согласия (`y/N`); при отказе / отсутствии TTY / неизвестном + дистрибутиве — печатает инструкцию и завершает `exit 2`, **не выполнив ни одной root-мутации + молча**. +- **FAIL:** скрипт ставит системный пакет без подтверждения; команда не соответствует дистрибутиву; + неизвестный дистрибутив приводит к падению вместо инструкции. + +--- + +## AC-4 — Детект существующих Plane/Gitea и выбор (D-2) + +**Условие:** на хосте присутствует ≥2 кандидата Plane (или Gitea); отдельный прогон — 0 кандидатов. +- **PASS:** при ≥2 — установщик показывает **нумерованный список** обнаруженных инсталляций и + принимает выбор оператора; выбранный URL наполняет `ORCH_PLANE_*`/`ORCH_GITEA_*`. При 0 — + запрашивает ручной ввод URL (фолбэк). Выбор/ввод проходит живую верификацию (AC-5) до записи. +- **FAIL:** при нескольких инсталляциях выбор не предложен (молча берётся первая/случайная); нет + ручного фолбэка при 0 кандидатов; сбой детекта роняет установщик (нарушение never-raise NFR-3). + +--- + +## AC-5 — Живая верификация введённых данных ДО записи + +**Условие:** оператор вводит токен/URL Plane, Gitea или Telegram. +- **PASS:** введённое значение проверяется онлайн перед записью (Plane `GET …/projects/`; Gitea + `GET /api/v1/user`; Telegram `getMe`); неверное → отклоняется (повтор до N либо `exit 2` с + подсказкой) и **в `.env` не пишется**; верное → принимается и пишется. Секреты вводятся скрыто + (`getpass`). +- **FAIL:** неверный секрет записан в `.env`; верификация отсутствует/после записи; секрет виден на + экране при вводе. + +--- + +## AC-6 — Гигиена секретов + +**Условие:** прогон `apply`, затем повторный `apply`. +- **PASS:** ни одно значение секрета не появляется в stdout/логах (только имена ключей/пути); + `.env` и `.env.watchdog` создаются правами **600**; повторный прогон **не перетирает** уже + присутствующие значения секретов без `--force`/явного согласия. +- **FAIL:** секрет утёк в вывод/лог; права файла шире 600; повтор молча перезаписал существующий + секрет. + +--- + +## AC-7 — Каноны переиспользуются, не форкаются + +**Условие:** ревизия `scripts/install_lite.py` и его прогон. +- **PASS:** webhook-секреты выпускаются вызовом `gen_secrets.py`; проект регистрируется вызовом + `onboard_project.py apply`+`verify`; `.env` рендерится из `.env.example`. Структурный тест + подтверждает ссылки на оба кирпича и **отсутствие форка** канона статусов Plane (нет + захардкоженных 22 имён статусов) и собственной генерации секретов. +- **FAIL:** установщик сам генерирует webhook-секреты / сам создаёт статусы-лейблы-репо / сам + выдумывает ключи env вместо рендера из канона. + +--- + +## AC-8 — Идемпотентный apply + +**Условие:** два последовательных `apply` (фейки Plane/Gitea/процессов в тесте). +- **PASS:** второй прогон помечает завершённые шаги как `skipped`; дублей проекта/webhook не + создаётся; значения `.env` стабильны; exit-код консистентен (`0`, либо `2` если остался + manual-step). +- **FAIL:** повтор создаёт дубли / перевыпускает секреты / падает на уже сделанном шаге. + +--- + +## AC-9 — Рантайм не затронут + +**Условие:** диф PR ORCH-104. +- **PASS:** изменены только `scripts/**`, `docs/**`, `tests/**` (+ `CLAUDE.md`/`CHANGELOG.md`); + `src/**`, `STAGE_TRANSITIONS`, `QG_CHECKS`, `check_*`, machine-verdict ключи, схема БД — + **байт-в-байт** (снапшот-тест инвариантов зелёный, не изменён этим PR). +- **FAIL:** PR трогает `src/**` / реестр стадий / гейты / схему БД; снапшот-тест инвариантов + изменён или падает. + +--- + +## AC-10 — stdlib-only, без delete-операций, без импорта платформы + +**Условие:** структурный анализ `scripts/install_lite.py` (AST/токенайз/grep). +- **PASS:** только stdlib-импорты (из разрешённого набора); нет `from src …`/`import src`; нет + деструктивных операций (`docker … rm`, `rm -rf`, удаление веток, `git push --force`); каноны- + знания — только субпроцессами кирпичей. +- **FAIL:** найден сторонний/платформенный импорт; найдена delete/force-операция; найден захардкод + хост-литералов (переиспользуется `FORBIDDEN`-набор `tests/test_no_host_hardcodes.py`). + +--- + +## AC-11 — Health-гейт и итоговый вердикт + +**Условие:** успешный `apply`, затем `verify`. +- **PASS:** `GET /health` → 200, `GET /queue` и `GET /metrics` → валидный JSON; итоговая сводка + печатает PASS; любой FAIL шага даёт `exit 1` с диагностикой. `verify` (read-only) повторяет + health-контракты + `onboard_project.py verify`. +- **FAIL:** скрипт рапортует успех при неотвечающем `/health`; нет финальной сводки; FAIL не + даёт `exit 1`. + +--- + +## AC-12 — Синхронизация документации + +**Условие:** ревизия `docs/deployment/LITE_SETUP.md` и анти-дрейф теста. +- **PASS:** LITE_SETUP.md содержит указатель на установщик как рекомендованный путь (ручной + маршрут сохранён фолбэком); `tests/test_lite_setup_doc.py` ассертит наличие ссылки и **остаётся + зелёным** во всех существующих проверках (13 разделов, кирпичи, key-sync); обновлены + `CLAUDE.md`/`CHANGELOG.md` (и витрина `docs/overview/`, если затронута). +- **FAIL:** установщик добавлен, но LITE_SETUP.md/`CLAUDE.md` о нём молчат; анти-дрейф тест не + обновлён/красный (reviewer-ось обзорных доков, ORCH-079/011 → finding ≥P1). + +--- + +## Сводная матрица AC ↔ BR/FR/NFR +| AC | Покрывает | Тип проверки | +|----|-----------|--------------| +| AC-1 | BR-1 / FR-1 | integration (plan zero-mutation) | +| AC-2 | BR-2 / FR-2 | unit (preflight verdict) | +| AC-3 | BR-3 / FR-3 / D-1 | unit + integration (consent / no-TTY) | +| AC-4 | BR-5 / FR-4 / D-2 | unit (discovery/select) | +| AC-5 | BR-4 / FR-5 | integration (verify-before-write) | +| AC-6 | BR-8 / NFR-2 | unit + integration (mask / 600 / no-overwrite) | +| AC-7 | BR-6 / FR-6 / FR-9 / INV-2 | structural (reuse bricks, anti-fork) | +| AC-8 | BR-7 / NFR-5 | integration (idempotency) | +| AC-9 | BR-8 / NFR-1 / INV-1 | structural (invariant snapshot) | +| AC-10 | BR-8 / NFR-4 / NFR-6 / INV-3 / INV-7 | structural (AST/grep) | +| AC-11 | BR-7 / FR-10 | integration (health gate) | +| AC-12 | BR-9 / FR-11 | structural (doc anti-drift) | diff --git a/docs/work-items/ORCH-104/04-test-plan.yaml b/docs/work-items/ORCH-104/04-test-plan.yaml new file mode 100644 index 0000000..904009a --- /dev/null +++ b/docs/work-items/ORCH-104/04-test-plan.yaml @@ -0,0 +1,150 @@ +work_item: ORCH-104 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-12 +model_used: claude-opus-4-8 +title: "Установочный скрипт для Lite — интерактивный установщик scripts/install_lite.py" +framework: pytest +scope: > + FR-1..FR-11 / AC-1..AC-12. Покрывается поведение установщика (чистые функции + step-движок + с инъекцией фейков HTTP/процессов) и анти-дрейф (stdlib-only, no-delete, reuse-bricks, doc-sync, + invariant snapshot). ВНЕ объёма тестов: реальная установка пакетов, реальный docker compose up, + реальные Plane/Gitea/Telegram (unit/integration детерминированы, без сети/docker/LLM — NFR-7). +notes: > + Зеркало паттернов tests/test_bootstrap_script.py и tests/test_lite_setup_doc.py: модуль грузится + через importlib; side-effects (subprocess/HTTP/getpass/input) инъектируются фейками; чистые + функции (parse_env/render_env/preflight_verdict/distro-команда/discovery/select) тестируются + изолированно. Полный регресс `pytest tests/ -q` обязан оставаться зелёным. Имена функций ниже — + ориентир по образцу bootstrap_bundle.py; финальные сигнатуры уточняет архитектор/разработчик. + +tests: + # ---- FR-1 / AC-1: entry-point и режимы ---- + - id: TC-01 + type: integration + description: "plan-режим (дефолт) печатает план + preflight и НЕ делает мутаций (нет записи .env, нет compose up); exit 0 при чистых предусловиях, 2 при блокерах." + module: tests/test_install_lite_script.py + expected: PASS + - id: TC-02 + type: unit + description: "build_plan() возвращает нормативный список шагов Lite в правильном порядке (preflight→deps→discovery→inputs→secrets→env→up→onboard→health)." + module: tests/test_install_lite_script.py + expected: PASS + + # ---- FR-2 / AC-2: скан предусловий ---- + - id: TC-03 + type: unit + description: "preflight-вердикт: отсутствие docker/compose/node, занятый прод-порт, mismatch uid:gid владельца repos-dir → блокеры; полностью укомплектованный хост → блокеров нет." + module: tests/test_install_lite_script.py + expected: PASS + + # ---- FR-3 / AC-3 / D-1: управляемая установка зависимостей ---- + - id: TC-04 + type: unit + description: "distro→команда: apt-хост даёт apt-команду установки, dnf-хост — dnf-команду; неизвестный дистрибутив → деградация на текстовую инструкцию (без команды-мутации)." + module: tests/test_install_lite_script.py + expected: PASS + - id: TC-05 + type: integration + description: "remediation: при отказе оператора (ответ 'N') и при отсутствии TTY установщик НЕ выполняет установку, печатает инструкцию и возвращает exit 2 (никакой молчаливой root-мутации)." + module: tests/test_install_lite_script.py + expected: PASS + + # ---- FR-4 / AC-4 / D-2: детект Plane/Gitea + выбор ---- + - id: TC-06 + type: unit + description: "discovery: по фейковым фактам docker ps/портов с 2 кандидатами Plane возвращается ранжированный список из 2; 0 кандидатов → пустой список (триггерит ручной фолбэк)." + module: tests/test_install_lite_script.py + expected: PASS + - id: TC-07 + type: unit + description: "select_candidate: валидный индекс выбирает кандидата; индекс вне диапазона/пустой ввод → режим ручного ввода URL (never-raise)." + module: tests/test_install_lite_script.py + expected: PASS + - id: TC-08 + type: unit + description: "detect never-raise: исключение в пробе docker/порта/URL деградирует в 'кандидатов нет' и не роняет установщик (NFR-3)." + module: tests/test_install_lite_script.py + expected: PASS + + # ---- FR-5 / AC-5: живая верификация ввода ---- + - id: TC-09 + type: integration + description: "verify-before-write: фейк Plane/Gitea, отвечающий 401, отклоняет токен — значение НЕ пишется в .env; ответ 200 принимает и пишет. Telegram getMe ok:false → отклонение." + module: tests/test_install_lite_script.py + expected: PASS + + # ---- FR-6 / FR-7 / AC-6 / AC-7: секреты и env ---- + - id: TC-10 + type: unit + description: "parse_env round-trip: KEY=value строки → словарь, комментарии/пустые игнорируются." + module: tests/test_install_lite_script.py + expected: PASS + - id: TC-11 + type: unit + description: "render_env: ключи-override обновляются в каноне, комментарии сохранены, неизвестные ключи дописываются управляемым блоком; идемпотентно (повторный рендер стабилен)." + module: tests/test_install_lite_script.py + expected: PASS + - id: TC-12 + type: integration + description: "секрет-гигиена: после прогона ни одно секрет-значение не встречается в собранном stdout/логе (маскирование); записанные .env/.env.watchdog имеют права 600." + module: tests/test_install_lite_script.py + expected: PASS + - id: TC-13 + type: integration + description: "no-silent-overwrite: при уже заполненных секретах повторный apply без --force их не перетирает (значения стабильны); webhook-секреты выпускаются вызовом gen_secrets.py, а не собственным кодом." + module: tests/test_install_lite_script.py + expected: PASS + + # ---- FR-9 / AC-7: онбординг кирпичом ---- + - id: TC-14 + type: integration + description: "onboard reuse: установщик вызывает onboard_project.py apply+verify субпроцессом (фейк), парсит merged ORCH_PROJECTS_JSON из отчёта и пишет его в .env; ручные пункты отчёта пробрасываются (exit 2)." + module: tests/test_install_lite_script.py + expected: PASS + + # ---- FR-1 / FR-10 / AC-8 / AC-11: идемпотентность, health, no-TTY ---- + - id: TC-15 + type: integration + description: "идемпотентность: два apply подряд (фейки) → второй помечает шаги skipped, без дублей проекта/webhook, значения .env стабильны." + module: tests/test_install_lite_script.py + expected: PASS + - id: TC-16 + type: integration + description: "no-TTY fail-closed: stdin не tty → manual_checkpoint печатает инструкцию и поднимает остановку → exit 2 (никакого зависания на input)." + module: tests/test_install_lite_script.py + expected: PASS + - id: TC-17 + type: integration + description: "health-гейт: фейк отвечает 200 на /health и валидным JSON на /queue,/metrics → сводка PASS, exit 0; /health не 200 → exit 1 с диагностикой (нет ложного success)." + module: tests/test_install_lite_script.py + expected: PASS + + # ---- AC-9 / AC-10 / INV: структурные анти-дрейф ---- + - id: TC-18 + type: unit + description: "stdlib-only + no-src-import: AST-скан install_lite.py — импорты только из разрешённого stdlib-набора; нет 'from src'/'import src' (зеркало test_bootstrap_script)." + module: tests/test_install_lite_script.py + expected: PASS + - id: TC-19 + type: unit + description: "no-delete-ops: AST/regex-скан не находит деструктивных операций (docker … rm/down -v, rm -rf, git push --force, удаление веток) и хост-хардкодов (FORBIDDEN-набор test_no_host_hardcodes)." + module: tests/test_install_lite_script.py + expected: PASS + - id: TC-20 + type: unit + description: "reuse-bricks / anti-fork: скрипт ссылается на gen_secrets.py и onboard_project.py; не несёт захардкоженного канона имён статусов Plane (FORBIDDEN_STATUS_NEEDLES) и собственной генерации секретов." + module: tests/test_install_lite_script.py + expected: PASS + - id: TC-21 + type: unit + description: "invariant snapshot: STAGE_TRANSITIONS/QG_CHECKS не изменены этим PR (существующий снапшот-тест зелёный; ORCH-104 их не трогает)." + module: tests/test_install_lite_script.py + expected: PASS + + # ---- AC-12: синхронизация документации ---- + - id: TC-22 + type: unit + description: "doc-sync: tests/test_lite_setup_doc.py ассертит, что LITE_SETUP.md ссылается на install_lite.py как рекомендованный путь; все существующие проверки дока (13 разделов, кирпичи, env key-sync, секрет-гигиена) остаются зелёными." + module: tests/test_lite_setup_doc.py + expected: PASS