# ONBOARDING — turnkey-онбординг нового проекта (ORCH-009) > RUNBOOK. Полный чеклист подключения нового проекта к оркестратору одним проходом. > Исполнитель — оператор; инструмент — CLI `scripts/onboard_project.py` > (режимы `plan` — дефолт/dry-run, `apply`, `verify`). Каждый шаг, который CLI выполнить > не может, помечен **🖐 РУЧНОЙ ШАГ** и снабжён командой проверки результата. > Архитектура решения — см. «Ссылки» внизу. Запуск CLI — из корня чекаута репо orchestrator, в venv с `requirements.txt`: ```bash python3 scripts/onboard_project.py plan \ --name "My Project" --description "зачем проект" \ --repo my-project --prefix MP \ --stack "Python 3.12 + FastAPI" --test-cmd "pytest tests/ -q" \ --prod-port 8600 --staging-port 8601 \ --webhook-url https://openclaw.mva154.duckdns.org/orchestrator/webhook/gitea ``` `plan` печатает полный план **без единой мутации** (ни сети-POST, ни записи на диск); `apply` — идемпотентный ensure (существующее → `skipped(exists)`, ничего не удаляется); exit-коды: `0` — чисто, `2` — есть `manual-step`/gap, `1` — ошибка. --- ## 0. Предусловия Все значения — в `.env` на хосте (секреты в гит не попадают): | Переменная | Зачем | Проверка | |-----------|-------|----------| | `ORCH_PLANE_API_TOKEN` (+`ORCH_PLANE_API_URL`, `ORCH_PLANE_WORKSPACE_SLUG`) | создание/чтение проекта, статусов, лейблов | `curl -s -H "X-API-Key: $TOKEN" $URL/api/v1/workspaces/$SLUG/projects/ \| head -c 200` | | `ORCH_GITEA_TOKEN` (+`ORCH_GITEA_URL`) | создание репо + webhook | `curl -s -H "Authorization: token $TOKEN" $URL/api/v1/user \| head -c 200` | | `ORCH_GITEA_WEBHOOK_SECRET` | HMAC webhook (переиспользуется, один на все репо) | есть строка в `.env`; нет → `apply` сгенерирует и выведет | | `ORCH_PROJECTS_JSON` | текущий реестр — источник merged-вывода | `grep ORCH_PROJECTS_JSON .env` | Токен Plane должен иметь право создавать проекты в workspace; токен Gitea — создавать репо и hooks под выбранным owner (`--gitea-owner`, дефолт из конфига). --- ## 1. Слой Plane: проект + статусы + лейблы Выполняет `apply` (или вручную при недоступности API CE — каждый отказ CLI помечает `manual-step`, не падает). 1. **Проект**: создаётся с `identifier = --prefix`. Уже существует → передай `--plane-project-id ` (ensure распознает и пропустит). 2. **Статусы — точные канонические имена** (22, источник — `plane_sync._PLANE_NAME_TO_KEY`; опечатка = тихая деградация fail-closed веток): | Статус | Группа | | Статус | Группа | |--------|--------|-|--------|--------| | Backlog | `backlog` | | In Review | `started` | | Todo | `unstarted` | | Blocked | `started` | | To Analyse | `unstarted` | | Approved | `started` | | In Progress | `started` | | Rejected | `started` | | Analysis | `started` | | **Confirm Deploy** | `started` | | Architecture | `started` | | Needs Input | `started` | | Development | `started` | | Done | `completed` | | Code-Review | `started` | | Cancelled | `cancelled` | | Review | `started` | | **STOP** | **`cancelled`** | | Testing | `started` | | Awaiting Deploy | `started` | | Deploying | `started` | | Monitoring after Deploy | `started` | ⚠️ Код-критично: `STOP` обязан быть в группе `cancelled` (иначе ветка отмены молча не активируется); в терминальных группах (`completed`/`cancelled`) — ТОЛЬКО Done/Cancelled/STOP, иначе terminal-detection ложно сочтёт живую задачу терминальной. 3. **Лейблы**: `autoApprove`, `autoDeploy`, `Bug` (имена — из конфига оркестратора; их отсутствие = fail-safe ручной режим / полный цикл). 4. **🖐 РУЧНОЙ ШАГ — порядок статусов на доске**: drag-and-drop в UI (API не управляет порядком). Проверка: открой доску проекта — колонки в порядке конвейера. 5. **Workspace-webhook**: уже **существует** (один на весь workspace, создан на уровне workspace заранее) — CLI его НЕ создаёт, только напоминает проверить: ```bash docker exec -e PGPASSWORD=plane plane-app-plane-db-1 psql -U plane -d plane -c \ "SELECT id, url, is_active FROM webhooks;" ``` --- ## 2. Слой Gitea: репо + per-repo webhook 1. **Репо** `--gitea-owner/--repo`: создаётся пустым (`auto_init=false`; ветку `main` создаст initial push следующего слоя). Существует → `skipped(exists)`. 2. **Per-repo webhook**: `events: push/pull_request/status`, `content_type: json`, `branch_filter: *`, URL = `--webhook-url`. **Секрет переиспользуется** из `ORCH_GITEA_WEBHOOK_SECRET` (приёмник валидирует ОДИН глобальный секрет на все репо; новый секрет сломал бы HMAC существующих вебхуков). Секрета нет в env → CLI сгенерирует и выведет строку для `.env` — **🖐 РУЧНОЙ ШАГ**: добавить её в `.env` (в гит не коммитить). Формат и проверка — `docs/operations/SETUP_WEBHOOKS.md`. Проверка: ```bash curl -s -H "Authorization: token $ORCH_GITEA_TOKEN" \ "$ORCH_GITEA_URL/api/v1/repos///hooks" | python3 -m json.tool ``` 3. **Branch protection `main` НЕ включать** (ADR D10): required-approvals/status-checks ломают PR-merge API merge-актора конвейера (ложные HOLD). Защита держится конвенцией + скоупом токенов. --- ## 3. Слой kit: материализация + initial push 1. `apply` рендерит kit (`onboarding/repo-skeleton/`, плейсхолдеры `{{NAME}}` из `onboarding/placeholders.json`) во временный каталог, докладывает live-copy канона (`docs/_templates/` 16 скелетов + `docs/_standards/` 3 стандарта — verbatim из текущего чекаута, BR-2 «канон не форкается») и пушит **ТОЛЬКО в свежесозданный/пустой репо** (единственный разрешённый push; коммит `feat: onboarding skeleton (ORCH-009 kit)`). 2. Репо непустой → шаг помечается `manual-step`: **🖐 РУЧНОЙ ШАГ** — занеси недостающие файлы обычным PR с ревью; поверх существующего контента ничего не пушится (BR-9). 3. После рендера не должно остаться ни одного `{{...}}`: CLI падает на этом сам; повторная проверка — `verify` (скан плейсхолдеров в файлах репо). --- ## 4. Регистрация в реестре оркестратора > ⚠️ **САМЫЙ ВАЖНЫЙ РУЧНОЙ СЛОЙ.** CLI `.env` прода НЕ правит и контейнер НЕ рестартит > (инвариант NFR-2) — он только печатает готовую строку. 1. **🖐 РУЧНОЙ ШАГ — env**: возьми из отчёта `apply` строку `ORCH_PROJECTS_JSON=[...полный merged-массив...]` (существующие записи verbatim + новая в конец; строка уже провалидирована фактическим парсером реестра) и замени ею строку в `.env` оркестратора на хосте. Вставляется атомарно одной строкой — ручное слияние JSON не нужно. 2. **🖐 РУЧНОЙ ШАГ — управляемый рестарт оркестратора**: реестр строится при импорте, нужна перезагрузка процесса. **Self-hosting предупреждение: прод-контейнер один на ВСЕ проекты — рестарт = групповое окно** (встаёт конвейер всех проектов). Выполняй осознанно: дождись тихого окна (`GET /queue` — нет бегущих job), затем штатный рестарт по `docs/operations/INFRA.md`. Проверка после рестарта: ```bash curl -s http://localhost:8500/health curl -s http://localhost:8500/queue | python3 -m json.tool | head -30 # реестр жив, конвейер пуст/цел ``` 3. TTL-self-heal статусов Plane (300с) рестарта НЕ требует: статусы/лейблы, созданные после регистрации, подхватятся сами. --- ## 5. Верификация 1. **`verify`-режим CLI** (read-only): ```bash python3 scripts/onboard_project.py verify --name ... --repo ... --prefix ... \ --plane-project-id --stack ... --test-cmd ... --prod-port ... --staging-port ... \ --webhook-url https://openclaw.mva154.duckdns.org/orchestrator/webhook/gitea ``` Проверяет: запись реестра парсится и совпадает по полям; все 22 статуса резолвятся (включая fail-closed `Confirm Deploy`/`STOP`); лейблы на месте; webhook существует и активен; kit-файлы в репо (6 промптов, `AGENTS.md`, `INFRA.md`, `_templates`/`_standards`); нет неразрешённых плейсхолдеров. Любой gap → exit `2` с перечнем. 2. **Smoke на песочнице (ADR D8)** — контур: **staging-оркестратор (порт 8501, изолированная БД `./data/staging`)** + одноразовый sandbox-проект (рекомендуемые имена: проект `onboarding-smoke`, префикс `SMK`, репо `onboarding-smoke`): 1. Онборди sandbox самим CLI (слои 1–3 этого runbook). 2. **🖐 РУЧНОЙ ШАГ**: зарегистрируй sandbox в `ORCH_PROJECTS_JSON` **`.env.staging`** (не прода!) и перезапусти staging-контейнер (он свободен от прод-инварианта): `docker compose --profile staging up -d orchestrator-staging`. 3. Создай тестовую задачу в sandbox-проекте → доведи до стадии analysis в песочнице. 4. Критерий PASS: агент по своему промпту **прочитал доку проекта** (следы чтения `CLAUDE.md`/`AGENTS.md` в выводе) и **записал артефакты** в `docs/work-items/SMK-…/` по канону `PIPELINE_DOCS.md`. 5. Запротоколируй прогон в «Журнале smoke-прогонов» (ниже). Для приёмки ORCH-009 первый протокол обязателен. --- ## 6. Откат CLI ничего не удаляет (BR-9) — откат всегда ручной и осознанный: | Что создано | Как откатить | Проверка | |-------------|--------------|----------| | Plane-проект (+статусы/лейблы) | удалить проект в UI Plane | проект исчез из списка workspace | | Gitea-репо (+webhook) | удалить репо в UI/API Gitea (webhook умрёт вместе с ним) | `GET /api/v1/repos//` → 404 | | Строка реестра | убрать запись из `ORCH_PROJECTS_JSON` в `.env` + управляемый рестарт (см. слой 4, то же групповое окно) | `GET /queue` — проекта нет в реестре | | Sandbox-артефакты smoke | удалить sandbox-проект/репо после прогона (или архивировать) | см. выше | --- ## Журнал smoke-прогонов | Дата | Оператор | Параметры (проект/префикс/репо) | Контур | Результат (PASS/FAIL) | Протокол | |------|----------|----------------------------------|--------|------------------------|----------| | — | — | — (первый прогон фиксируется при приёмке ORCH-009) | staging 8501 | — | — | --- ## Ссылки - Архитектура решения: `docs/work-items/ORCH-009/06-adr/ADR-001-turnkey-onboarding-kit-and-cli.md` (D1…D11); сквозной ADR — `docs/architecture/adr/adr-0035-turnkey-project-onboarding.md`. - Устройство набора шаблонов и словарь плейсхолдеров: `onboarding/README.md`. - Формат вебхуков: `docs/operations/SETUP_WEBHOOKS.md`; топология и рестарты — `docs/operations/INFRA.md`.