architect(ET): auto-commit from architect run_id=585

This commit is contained in:
2026-06-10 15:00:00 +03:00
committed by orchestrator-deployer
parent 11551572e9
commit ed04f71fd1
5 changed files with 565 additions and 0 deletions

View File

@@ -122,6 +122,42 @@ F1b (рамка C-1: наблюдатель отделён от наблюдае
`docs/work-items/ORCH-100/06-adr/ADR-001-sidecar-watchdog.md`,
`docs/work-items/ORCH-100/07-infra-requirements.md`.
## Turnkey-онбординг проектов (ORCH-009 — design)
Операторская способность развернуть **новый** проект одним проходом: Plane-проект (статусы с
точными именами + лейблы под машинные контракты) → Gitea-репо (+per-repo webhook) → каркас репо
(kit) → запись реестра → верификация. Реализуется **вне рантайма и вне конвейера**: `src/**`
байт-в-байт (`STAGE_TRANSITIONS`/`QG_CHECKS`/`check_*`/machine-verdict/схема БД — не тронуты),
kill-switch не нужен (активация — только явный запуск CLI человеком). Эталон — сам репозиторий
orchestrator (каноны ORCH-52b/c/d/e); enduro-trails эталоном не является.
- **Kit `onboarding/repo-skeleton/`** — параметризуемый каркас нового репо: 6 промптов агентов
канона 52d/92 (язык — канон орка: 5 ru + deployer en, ADR-001 D2 ORCH-092), паспорт `CLAUDE.md`,
`AGENTS.md` (точка входа агентов: карта доков + правила), `CONTRIBUTING.md`, `README`/`CHANGELOG`,
скелет `docs/` с обязательным `operations/INFRA.md`, `.env.example`. Плейсхолдеры `{{NAME}}` +
stdlib-рендер (без новых зависимостей); словарь — `onboarding/placeholders.json`. **Канон не
форкается (BR-2):** `docs/_templates/` + `docs/_standards/` не хранятся в kit — копируются live
из чекаута орка в момент материализации.
- **CLI `scripts/onboard_project.py`** — `plan` (дефолт, GET-only, ноль мутаций) / `apply`
(идемпотентный ensure, без delete-операций) / `verify` (round-trip реестра через фактический
`projects._parse_projects_json`, резолв всех статусов включая fail-closed `Confirm Deploy`/`STOP`,
лейблы, webhook, полнота kit, скан неразрешённых плейсхолдеров). Имена статусов — read-only
импорт `plane_sync._PLANE_NAME_TO_KEY` (22, нулевой дрейф); канонические группы фиксированы ADR
(код-критично: `STOP`→`cancelled` ORCH-090; терминальные группы только у Done/Cancelled/STOP —
иначе terminal-detection ORCH-068 ложно терминалит). Gitea-webhook переиспользует глобальный
`ORCH_GITEA_WEBHOOK_SECRET`; initial push — **только** в свежесозданный пустой репо (INV-4 не
затрагивается). Скрипт никогда не рестартит прод / не правит `.env` / ничего не удаляет;
регистрация в реестре = операторские env + управляемый рестарт (runbook). Недоступное в
Plane CE API → `manual-step` (fail-safe).
- **Runbook `docs/operations/ONBOARDING.md`** — чеклист всех слоёв, явные ручные шаги, smoke на
**staging-контуре** (8501, изолированная БД) с одноразовым sandbox-проектом, откат.
- **Анти-дрейф:** структурные канон-тесты kit (аналог `tests/test_agent_prompts_canon.py`) +
снапшот-тест `STAGE_TRANSITIONS`/`QG_CHECKS`.
Подробнее: [adr-0035](adr/adr-0035-turnkey-project-onboarding.md), детально —
`docs/work-items/ORCH-009/06-adr/ADR-001-turnkey-onboarding-kit-and-cli.md` (D1…D11),
`docs/work-items/ORCH-009/07-infra-requirements.md`.
## Конвейер и Quality Gates
```

View File

@@ -0,0 +1,80 @@
---
work_item: ORCH-009
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-10
model_used: claude-opus-4-8
---
# adr-0035: Turnkey-онбординг проектов — kit + операторский CLI + runbook (ORCH-009)
## Статус
Proposed
## Контекст
Подключение нового проекта к оркестратору — ручная археология по разрозненным докам и памяти;
каждый пропущенный шаг даёт **тихую деградацию**: без промптов в репо конвейер проекта не работает
вовсе (launcher резолвит `.openclaw/agents/<role>.md` относительно worktree репо задачи); без
точных имён статусов Plane ветки `Confirm Deploy` (ORCH-059) / `STOP` (ORCH-090) молча не
активируются (fail-closed); без лейблов `autoApprove`/`autoDeploy`/`Bug` авто-режимы (ORCH-089)
и багфикс-трек (ORCH-019) молча выключены (fail-safe). Эталон онбординга — **сам репозиторий
orchestrator** (каноны ORCH-52b/c/d/e кодифицированы в `docs/_templates/`, `docs/_standards/`,
`.openclaw/agents/`). Домен D5.2 эпика саморазвития: способность разворачивать новый проект
одним проходом.
## Решение
Способность реализуется **вне рантайма и вне конвейера**`src/**` байт-в-байт не меняется
(`STAGE_TRANSITIONS`/`QG_CHECKS`/`check_*`/machine-verdict/схема БД/контракт `projects.py`
нетронуты), kill-switch не нужен (активация — только явный запуск операторского CLI):
1. **Onboarding-kit `onboarding/repo-skeleton/`** — параметризуемый каркас нового репо:
6 промптов агентов канона 52d/92 (5 XML-секций, «❌→✅», эмиссия схемы 52c, verdict-ключи
байт-в-байт; язык — канон орка: 5 ru + deployer en), паспорт `CLAUDE.md`, `AGENTS.md`
(точка входа агентов), `CONTRIBUTING.md`, `README.md`, `CHANGELOG.md`, скелет `docs/` с
обязательным `operations/INFRA.md`, `.env.example`. Плейсхолдеры `{{NAME}}` + stdlib-рендер
(без новых pip-зависимостей); словарь — `onboarding/placeholders.json` (биекция со
вхождениями в kit держится тестами). **Канон не форкается:** `docs/_templates/` +
`docs/_standards/` НЕ хранятся в kit — копируются live из чекаута орка в момент материализации.
2. **Операторский CLI `scripts/onboard_project.py`**`plan` (дефолт, GET-only, ни одной
мутации) / `apply` (идемпотентный ensure, без delete-операций) / `verify`. Шаги: Plane-проект →
22 статуса с точными именами из `plane_sync._PLANE_NAME_TO_KEY` (read-only импорт — нулевой
дрейф; канонические группы фиксированы: `STOP``cancelled`, терминальные группы только у
Done/Cancelled/STOP — иначе terminal-detection ORCH-068 ложно терминалит) → лейблы → Gitea-репо
(+per-repo webhook `push`/`pull_request`/`status`; HMAC-секрет **переиспользуется** из
`ORCH_GITEA_WEBHOOK_SECRET` — приёмник один на все репо) → материализация kit + initial push
**только в свежесозданный пустой репо** (INV-4 не затрагивается) → merged-вывод
`ORCH_PROJECTS_JSON`, провалидированный фактическим `projects._parse_projects_json`
(round-trip). Недоступное в Plane CE API → `manual-step` со ссылкой на runbook (fail-safe).
Скрипт **никогда** не рестартит прод, не правит `.env`, не пушит в существующие репо, ничего
не удаляет.
3. **Runbook `docs/operations/ONBOARDING.md`** — полный чеклист: предусловия (токены) → скрипт →
операторские шаги (env + управляемый рестарт с self-hosting-предупреждением; UI-only Plane) →
верификация (`verify` + smoke) → откат. Smoke-контур — **staging (8501, изолированная БД)** +
одноразовый sandbox-проект (`SMK`); протокол — «Журнал smoke-прогонов» в runbook.
Анти-дрейф — структурные тесты kit (аналог `tests/test_agent_prompts_canon.py`) + снапшот-тест
`STAGE_TRANSITIONS`/`QG_CHECKS` (контроль ненарушения `src`). Branch protection `main` новых репо
**не включается** (ломала бы PR-merge API merge-актора — ложные HOLD класса ORCH-093).
## Последствия
- **+** Новый проект разворачивается одним проходом проверяемо: все слои (Plane-контракты,
webhook, промпты, дока, реестр) закрыты скриптом+runbook; тихие деградации ловит `verify`.
- **+** Нулевой риск рантайма: изменение docs/templates/scripts/tests-only; регресс
enduro/orchestrator невозможен по построению; общая БД не читается и не пишется скриптом.
- **+** Единый эталон без форка: новые репо получают живой канон момента онбординга;
обновления канона в них едут обычными PR с reviewer-gate.
- **** Регистрация в реестре остаётся операторской (env + управляемый рестарт — Ф-3,
сознательное ограничение NFR-2); разрыв «создано, но не зарегистрировано» виден через `verify`.
- **** Закрытый список read-only импортов из `src` (`projects._parse_projects_json`,
`plane_sync._PLANE_NAME_TO_KEY`, поля `config.settings`) — связь с приватными именами;
поломка при рефакторинге видимая (тесты), расширение списка — только через ADR.
- **Ограничение:** способность ≠ исполнение: онбординг конкретного заказчика — операторская
эксплуатация (вне ORCH-009); тиражирование на новый хост — ORCH-10 (вне объёма).
Детально: `docs/work-items/ORCH-009/06-adr/ADR-001-turnkey-onboarding-kit-and-cli.md`
(D1…D11 — раскладка, плейсхолдеры, copy-vs-template split, импорт src, группы статусов,
webhook-секрет, формат реестра, smoke-контур, языковая политика, branch protection, форма CLI).

View File

@@ -0,0 +1,341 @@
---
work_item: ORCH-009
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-10
model_used: claude-opus-4-8
---
# ADR-001: Turnkey-онбординг проектов — kit `onboarding/` + операторский CLI + runbook
Work Item: **ORCH-009** — Онбординг проектов в оркестратор (turnkey: Plane + репо + агенты + инфра)
Стадия: **architecture**
Связь: BRD `01-brd.md`, ТЗ `02-trz.md`, AC `03-acceptance-criteria.md`, тест-план `04-test-plan.yaml`,
инфра `07-infra-requirements.md`, риски `10-tech-risks.md`.
Сквозная регистрация: **`docs/architecture/adr/adr-0035-turnkey-project-onboarding.md`**
(решение кросс-каттинговое: новая способность уровня всего оркестратора — масштабирование на
новые проекты, домен D5.2 эпика саморазвития).
## Статус
Proposed
---
## Контекст
Онбординг нового проекта сегодня — ручная археология по `SETUP_WEBHOOKS.md`/`INFRA.md`/памяти;
любой пропуск даёт тихую деградацию (BRD §1.2): без промптов в репо конвейер проекта не работает
вовсе (Ф-1: launcher резолвит `.openclaw/agents/<role>.md` **относительно worktree репо задачи**);
без точных имён статусов ветки `Confirm Deploy`/`STOP` молча не активируются (fail-closed,
`src/plane_sync.py:130-165`); без лейблов авто-режимы/багфикс-трек молча выключены (fail-safe,
`src/labels.py`/`src/bug_fast_track.py`).
Ограничения, заданные анализом и проверенные по коду:
- **NFR-1:** `src/**` не меняется; `STAGE_TRANSITIONS`/`QG_CHECKS`/`check_*`/machine-verdict
ключи/схема БД/контракт `projects.py` — байт-в-байт. Задача docs/templates/scripts/tests-only.
- **Ф-2:** агент видит только worktree своего репо → каноны обязаны быть **скопированы** в новый
репо (ссылка на репо орка агенту недоступна).
- **Ф-3:** реестр строится при импорте из `ORCH_PROJECTS_JSON` (`src/projects.py::_load_projects`);
регистрация = правка `.env` + **операторский** управляемый рестарт.
- **Ф-6:** Plane-webhook — workspace-level, уже существует (в CE создаётся SQL-ом, внешнего API
нет); Gitea-webhook — per-repo, через API (`push`/`pull_request`/`status`, HMAC).
- **Ф-7:** живой канон — `docs/_templates/` (16 скелетов), `docs/_standards/` (3 стандарта),
`.openclaw/agents/*.md` (канон 52d/92).
- Эталон онбординга = **сам репозиторий orchestrator** (актуализация Владельца 10.06);
enduro-trails эталоном не является.
ТЗ оставило архитектору 8 открытых вопросов (OQ-1…OQ-8) — все закрываются ниже (D1…D11).
---
## Решение
### Сводка
Три артефакта + тесты, всё **вне конвейера и вне рантайма**:
1. **Onboarding-kit** `onboarding/repo-skeleton/` — параметризуемый каркас нового репо
(6 промптов канона 52d/92, паспорт `CLAUDE.md`, `AGENTS.md`, `CONTRIBUTING.md`, скелет `docs/`
с обязательным `operations/INFRA.md`); словарь плейсхолдеров — `onboarding/placeholders.json`.
2. **Операторский CLI** `scripts/onboard_project.py``plan` (дефолт, GET-only) / `apply`
(идемпотентный ensure) / `verify`; Plane (проект+статусы+лейблы) → Gitea (репо+webhook) →
материализация kit (рендер + live-copy канона) + initial push → генерация записи реестра →
отчёт `created/skipped(exists)/manual-step`.
3. **Runbook** `docs/operations/ONBOARDING.md` — полный чеклист, явные ручные шаги
(env + управляемый рестарт; UI-only Plane), верификация (verify + smoke на staging), откат.
Никакого нового кода в горячих путях; kill-switch не нужен (способность активируется только
явным запуском CLI человеком — TRZ §7).
### D1 — Раскладка: top-level `onboarding/` (OQ-1)
**Решение: `onboarding/` в корне репо** — ровно как предложено ТЗ:
```
onboarding/
README.md ← устройство kit: словарь плейсхолдеров, правило «канон не форкается»,
copy-vs-template карта (D3), как запускать тесты kit
placeholders.json ← словарь плейсхолдеров (single source of truth, D2)
repo-skeleton/ ← дерево зеркалит целевой репо (FR-1)
.openclaw/agents/{analyst,architect,developer,reviewer,tester,deployer}.md
CLAUDE.md AGENTS.md CONTRIBUTING.md README.md CHANGELOG.md .env.example
docs/ARCHITECTURE.md docs/PIPELINE.md docs/PRODUCT_VISION.md
docs/operations/INFRA.md
docs/architecture/adr/README.md ← стаб реестра сквозных ADR (дир непустая, самоописуема)
docs/work-items/.gitkeep docs/history/.gitkeep
```
Отвергнуто:
- **`docs/_onboarding/`** — смешивает kit (продукт-артефакт, исходник для ЧУЖИХ репо) с
документацией самого орка; шаблоны промптов под `docs/` рядом с живыми `docs/_templates/`
провоцируют путаницу «какая копия живая» (прямо риск R-1/R-5 BRD) и ложные срабатывания
doc-тулинга.
- **`scripts/onboarding/`** — смешивает данные (дерево skeleton) с исполняемым кодом; `scripts/`
в этом репо — плоские утилиты (`staging_check.py`, deploy-hook).
Top-level каталог делает границу физической: **всё под `onboarding/` предназначено новому репо,
ничто под `onboarding/` не исполняется рантаймом орка.** Структурные тесты канона гоняются по
`onboarding/repo-skeleton/.openclaw/agents/*.md` отдельно от живых промптов орка (TC-03…08 ↔
существующий `tests/test_agent_prompts_canon.py` не пересекаются).
### D2 — Механизм подстановки: `{{NAME}}` + stdlib, без новых зависимостей (OQ-2)
**Решение: синтаксис `{{PLACEHOLDER_NAME}}`** (верхний регистр, `[A-Z][A-Z0-9_]*`), подстановка —
простой проход `str.replace` по словарю; после рендера — обязательный скан
`re.compile(r"\{\{[A-Z][A-Z0-9_]*\}\}")` на неразрешённые плейсхолдеры (ошибка в apply/verify,
PASS-условие AC-5/TC-09).
- **`string.Template` отвергнут:** kit-шаблоны (INFRA.md, `.env.example`, промпты) содержат
shell-сниппеты с `$VAR`/`${VAR}` — синтаксис `$` коллидирует и потребовал бы экранирования по
всему kit (хрупко, нечитабельно).
- **Jinja2 отвергнут:** новая pip-зависимость (ТЗ §9 запрещает без обоснования) + условная логика
в шаблонах = второй язык программирования в kit → выше риск дрейфа. Kit обязан быть тупым.
- Синтаксис `{{…}}` визуально различим, greppable; в Markdown/YAML kit-файлов естественно не
встречается, остаточные случаи ловит скан.
**Словарь — `onboarding/placeholders.json`** (машиночитаемый single source of truth; формат:
`{ "NAME": {"description": …, "required": bool, "default": …|null, "example": …} }`):
| Плейсхолдер | Смысл | Обяз. |
|---|---|---|
| `{{PROJECT_NAME}}` | человекочитаемое имя проекта | да |
| `{{PROJECT_DESCRIPTION}}` | 12 фразы «зачем проект» (README/PRODUCT_VISION) | да |
| `{{REPO}}` | имя Gitea-репо (== каталог под `/repos`) | да |
| `{{GITEA_OWNER}}` | owner/org репо в Gitea | да |
| `{{WORK_ITEM_PREFIX}}` | префикс work-item (`ET`/`ORCH`-аналог) | да |
| `{{PLANE_PROJECT_ID}}` | uuid Plane-проекта (известен после Plane-шага apply) | да |
| `{{STACK}}` | стек проекта (описательно) | да |
| `{{TEST_CMD}}` | команда тестов (напр. `pytest -q`) | да |
| `{{PROD_PORT}}` / `{{STAGING_PORT}}` | порты прод/staging | да |
Расширение словаря = правка `placeholders.json` + kit + тестов в одном PR. Тесты держат
**биекцию**: каждый плейсхолдер, встречающийся в kit, объявлен в словаре, и каждый объявленный —
используется (нет мёртвых/опечаточных).
### D3 — Copy-vs-template split (OQ-3, BR-2)
| Класс | Файлы | Механизм |
|---|---|---|
| **Live-copy канона** (НЕ хранится в kit) | `docs/_templates/**` (16), `docs/_standards/**` (3) | копируются скриптом **verbatim из рабочего чекаута репо орка в момент материализации** |
| **Параметризуемые шаблоны** (хранятся в kit) | 6 промптов, `CLAUDE.md`, `AGENTS.md`, `CONTRIBUTING.md`, `README.md`, `CHANGELOG.md`, `docs/ARCHITECTURE.md`, `docs/PIPELINE.md`, `docs/PRODUCT_VISION.md`, `docs/operations/INFRA.md`, `docs/architecture/adr/README.md`, `.env.example` | рендер `{{…}}` (D2) |
| **Скелет-каркас** | `docs/work-items/.gitkeep`, `docs/history/.gitkeep` | копия как есть |
- Канон копируется **байт-в-байт, без переписывания**: ORCH-примеры внутри стандартов
(`PIPELINE_DOCS.md` цитирует ORCH-088 и т.п.) остаются примерами — это не «утечка», а
иллюстрация (утечкой считается орк-литерал там, где должен быть параметр — AC-5/TC-10:
префикс work-item, порты 8500/8501, self-hosting-правила в паспорте/промптах).
- Повторный `apply` существующие файлы в целевом репо **не перезаписывает** (идемпотентность
BR-9): обновление канона в уже-онбордженных репо едет их обычными PR с reviewer-gate;
новые онбординги автоматически получают свежий канон (live-copy, ТЗ §7).
- Источник live-copy — чекаут, из которого запущен скрипт; скрипт проверяет наличие обоих
каталогов и (в verify) количество скелетов ≥ 16 / стандартов ≥ 3.
### D4 — Скрипту разрешён read-only импорт `src` — закрытый список (OQ-4)
**Решение: импортировать, не снапшотить.** Закрытый список импортов:
| Импорт | Зачем |
|---|---|
| `src.projects._parse_projects_json`, `src.projects.ProjectConfig` | round-trip валидация генерируемой записи реестра фактическим парсером (AC-6/TC-12) |
| `src.plane_sync._PLANE_NAME_TO_KEY` | точные канонические имена 22 статусов байт-в-байт (AC-7/TC-13) |
| `src.config.settings` (read-only поля) | имена лейблов `auto_approve_label`/`auto_deploy_label`/`bug_fast_track_label` (дефолты `autoApprove`/`autoDeploy`/`Bug`), URL/токены Plane/Gitea из env |
- **Почему не снапшот:** дублирование констант = гарантированный дрейф (R-2); AC-6 и так требует
фактический парсер; снапшот потребовал бы отдельного «теста синхронизации», который и есть
признание дрейфа. Импорт даёт нулевой дрейф **по построению**.
- Импорт безопасен: `src.projects``src.config` (pydantic-settings с дефолтами, инстанцируется
без env); `src.plane_sync` module-level считает только строки из settings; `httpx` — уже
зависимость проекта (`requirements.txt`), **новых pip-зависимостей нет**.
- Импорт приватных имён (`_parse_projects_json`, `_PLANE_NAME_TO_KEY`) — сознательная,
санкционированная ТЗ связь (ТЗ §2 разрешает явно). **Список закрыт:** любой новый импорт из
`src` — только через обновление этого ADR. Контроль ненарушения `src` — снапшот-тест TC-21
(`STAGE_TRANSITIONS`/`QG_CHECKS`) + AC-12 (diff).
- Скрипт запускается из корня чекаута орка (runbook-предусловие); `sys.path`-шим в начале файла
(паттерн `scripts/staging_check.py`).
### D5 — Plane-провижининг: канонические статусы + группы + fail-safe (OQ-5, BR-7)
**Ensure-семантика:** `GET states` → создать недостающие по точным именам (ключи
`_PLANE_NAME_TO_KEY`, 22 имени); существующие (включая CE-дефолтные Backlog/Todo/In Progress/
Done/Cancelled нового проекта) — `skipped(exists)` по совпадению имени. Аналогично лейблы:
`autoApprove`/`autoDeploy`/`Bug` (имена — из `settings`, D4).
**Канонические группы статусов** (Plane: `backlog|unstarted|started|completed|cancelled`) —
фиксируются этим ADR; код-критичные констрейнты выделены:
| Статус | Группа | Констрейнт |
|---|---|---|
| Backlog | `backlog` | |
| Todo, To Analyse | `unstarted` | |
| In Progress, Analysis, Architecture, Development, Code-Review, Review, Testing, Awaiting Deploy, Deploying, Monitoring after Deploy, Needs Input, In Review, Blocked, Approved, Confirm Deploy | `started` | **рабочие/гейтовые статусы НЕ в терминальных группах** — иначе terminal-detection ORCH-068 (`{uuid→group}`, группы `completed`/`cancelled` = терминал) ложно сочтёт живую задачу терминальной |
| Rejected | `started` | reject = rework-петля в анализ, задача жива → НЕ `cancelled` |
| Done | `completed` | терминал |
| Cancelled | `cancelled` | терминал |
| **STOP** | **`cancelled`** | **требование ORCH-090** (fail-closed: без статуса/группы ветка cancel не активируется) |
**Fail-safe (CE-пробелы):** код орка использует только GET states — доступность POST
project/states/labels в Plane CE не гарантирована. Любой недоступный вызов (403/404/405/501/
нереализовано) → шаг помечается **`manual-step`** со ссылкой на соответствующий раздел runbook
(точное имя статуса + группа для ручного создания в UI), скрипт не падает (AC-7/TC-14).
Заведомо ручные шаги: порядок статусов на доске (drag-and-drop, UI-only), workspace-webhook
(существует, Ф-6 — verify печатает команду проверки, не создаёт).
### D6 — Gitea-провижининг: репо + webhook + initial push только в пустой репо (BR-9)
- **Репо:** `POST /api/v1/...` под `{{GITEA_OWNER}}`, `auto_init=false` (репо рождается пустым;
`main` создаёт initial push). Существует → `skipped(exists)`.
- **Webhook (per-repo):** events `push`/`pull_request`/`status`, `content_type: json`,
`branch_filter: "*"`, URL = внешний URL орка `/webhook/gitea` (формат `SETUP_WEBHOOKS.md`).
**Секрет: приёмник `src/webhooks/gitea.py` валидирует ОДИН глобальный
`ORCH_GITEA_WEBHOOK_SECRET` на все репо** → скрипт **переиспользует** существующий секрет из
env (никогда не генерит новый при наличии — новый сломал бы HMAC всех вебхуков); секрет
отсутствует в env → сгенерить `secrets.token_hex(20)` + вывести оператору для `.env`
(первичная настройка). В логах/отчёте секрет всегда маскируется (NFR-3).
- **Initial push:** материализованный kit коммитится (`feat: onboarding skeleton (ORCH-009 kit)`)
и пушится в `main` **только если репо свежесоздан/пуст** (Gitea `empty: true`); непустой репо →
`manual-step` (kit-файлы НИКОГДА не пушатся поверх существующего контента). Это единственный
разрешённый push: новый пустой репо до регистрации в реестре не является участником конвейера →
**INV-4 (мерж только через PR-merge API) не затрагивается** (ТЗ §9).
### D7 — Запись реестра: полный merged-массив, скрипт `.env` не трогает (BR-8)
**Решение: скрипт выводит (а) standalone-запись нового проекта и (б) полный merged-массив
`ORCH_PROJECTS_JSON`** = существующие записи verbatim + новая в конец. Источник существующих:
текущий env / `--env-file` (дефолт — `.env` в корне чекаута, если есть); источника нет → только
standalone-запись + инструкция. Перед выводом merged-массив прогоняется через
`projects._parse_projects_json` (round-trip: поля новой записи совпадают, существующие не
потеряны/не искажены — AC-6/TC-12).
- **Почему full-array, а не диф:** оператор вставляет одну строку в `.env` атомарно — ручное
слияние JSON в env-строке (экранирование, запятые) и есть источник ошибок R-4.
- Скрипт **не правит** `.env` прода и **не рестартит** контейнер (NFR-2): печатает строку +
инструкцию «добавь в `.env` → управляемый рестарт оркестратора (self-hosting: групповое окно,
выполнять осознанно)» со ссылкой на runbook. `verify` после рестарта показывает разрыв
«создано, но не зарегистрировано» (R-4).
### D8 — Песочница для smoke: staging-контур 8501 + одноразовый SMK-проект (OQ-6, AC-13)
**Решение: smoke выполняется на staging-контуре** (`orchestrator-staging`, 8501, изолированная БД
`./data/staging`) с **одноразовым** sandbox: Plane-проект `onboarding-smoke` (префикс `SMK`) +
Gitea-репо `onboarding-smoke`, онбордженные самим скриптом. Регистрация — в `ORCH_PROJECTS_JSON`
**staging-окружения** (`.env.staging`) + рестарт staging (свободен, в отличие от прод-инварианта).
Прогон: тестовая задача SMK → стадия `analysis` → проверить следы чтения паспорта/`AGENTS.md` и
артефакты `docs/work-items/SMK-…/` по канону `PIPELINE_DOCS.md`.
- **Прод-контур отвергнут:** smoke-задача писала бы конвейерные строки в общую прод-БД и жила бы
в общей очереди с enduro/ORCH — шум и риск в общем инстансе (дух NFR-2).
- Протокол прогона — раздел **«Журнал smoke-прогонов»** в `ONBOARDING.md` (дата, параметры,
PASS/FAIL по чек-листу AC-13); для приёмки ORCH-009 первый протокол обязателен, ссылка на него —
из `13-test-report.md` задачи. Судьба sandbox-артефактов: архив/удаление вручную по разделу
«Откат» runbook (скрипт не удаляет ничего — BR-9).
### D9 — Языковая политика kit-промптов: канон орка (OQ-7, AC-4)
**Решение: 5 ru + deployer en** — ровно языковая раскладка канона орка, нормативная по
ADR-001 D2 ORCH-092 (deployer — самый safety-critical промпт, en-раскладка минимизирует
регресс-поверхность байт-точных verdict-ключей/команд). Kit наследует канон без отступлений;
per-project отступление возможно позже **только** решением в собственном ADR нового проекта
(правило фиксируется в `onboarding/README.md` и шаблоне `CONTRIBUTING.md`). Проверяется TC-08.
### D10 — Branch protection `main` нового репо: НЕ включать (OQ-8)
**Решение: не включать.** Merge-актор конвейера — Gitea PR-merge API под токеном орка
(INV-4; `src/merge_gate.py`, ORCH-093): required-approvals/required-status-checks дали бы
405/409-класс отказов `merge_pr` → ложные HOLD (ровно класс инцидента ORCH-063). Сам орк живёт
без protection — защита `main` держится конвенцией (агенты не пушат `main`; мерж только через
PR API) и скоупом токенов. Решение фиксируется в runbook; пересмотр — при мультитенант-hardening
(D5.6, вне объёма).
### D11 — Форма CLI и тестируемость без сети (BR-11, NFR-5)
**Один файл `scripts/onboard_project.py`** (операторская UX: один очевидный энтрипойнт; паттерн
`scripts/staging_check.py`), внутри — слои:
- **Чистое ядро:** `build_plan(params, observed) -> Plan` — без I/O; `Plan` = упорядоченный список
шагов закрытого списка BR-1: `plane.project → plane.states(22) → plane.labels(3) → gitea.repo →
gitea.webhook → kit.materialize+push → registry.emit`. Рендер kit — чистая функция
`render(text, params)` (D2), в plan-режиме выполняется **in-memory** (ни одной записи на диск —
AC-8/TC-16); материализация на диск (temp-dir → git init/commit/push) — только в `apply`.
- **Тонкие клиенты** `PlaneClient`/`GiteaClient` (httpx; единственные точки сети) — инжектируются
→ в тестах мокаются целиком (NFR-5: ноль сетевых вызовов, TC-13…18).
- **Режимы:** `plan` (дефолт) — только GET-пробы текущего состояния + полный план без единой
мутации; `apply` — ensure-исполнение (идемпотентно, без delete-операций вовсе); `verify`
GET-пробы + локальные проверки (registry round-trip, резолв всех логических ключей включая
`confirm_deploy`/`stop`, лейблы, webhook активен, kit-файлы в репо, скан неразрешённых
плейсхолдеров).
- **Отчёт:** человекочитаемый + `--json`; статус каждого шага
`created | skipped(exists) | manual-step | planned | error`; exit-коды: `0` — чисто, `2` — есть
`manual-step`/gap в verify, `1` — ошибка. Каждый шаг логируется (BR-11).
---
## Альтернативы (сводно)
- **`docs/_onboarding/` / `scripts/onboarding/`** — отвергнуто (D1): смешение kit с живой докой
орка / данных с кодом.
- **Jinja2 / `string.Template`** — отвергнуто (D2): новая зависимость и логика в шаблонах /
коллизия `$` с shell-сниппетами.
- **Снапшот констант `src` + тест синхронизации** — отвергнуто (D4): узаконенный дрейф; импорт
даёт нулевой дрейф по построению.
- **Генерация нового webhook-секрета per-repo** — отвергнуто (D6): приёмник валидирует один
глобальный секрет; новый сломал бы HMAC существующих вебхуков.
- **Диф-вывод реестра** — отвергнуто (D7): ручное слияние JSON-в-env — источник ошибок R-4.
- **Smoke на прод-контуре** — отвергнуто (D8): запись в общую прод-БД/очередь.
- **Branch protection `main`** — отвергнуто (D10): ломает PR-merge API актора (ложные HOLD).
## Последствия
- **+** Turnkey-способность D5.2: один проход + runbook вместо археологии; тихие деградации
(статусы/лейблы/промпты) закрываются проверяемо (`verify` + структурные тесты).
- **+** Нулевой риск рантайма: `src/**` байт-в-байт, нового кода в горячих путях нет, kill-switch
не нужен; регресс enduro/orchestrator невозможен по построению.
- **+** Анти-дрейф структурный: live-copy канона (BR-2) + единые канон-тесты kit (NFR-4) +
биекция словаря плейсхолдеров.
- **** Операторские шаги остаются ручными (env + управляемый рестарт; UI-only Plane): осознанное
ограничение NFR-2 (никакой автоматики рестартов) — митигировано runbook + verify (видимый разрыв).
- **** Импорт приватных имён `src` связывает скрипт с внутренними идентификаторами — митигировано
закрытым списком (D4) и тем, что рефакторинг имён мгновенно валит импорт в тестах (видимая,
не тихая поломка).
- **** Kit-шаблоны промптов требуют сопровождения при эволюции канона — митигировано общими
структурными требованиями тестов (расхождение ловит CI, NFR-4).
- **Откат:** удалить `onboarding/`, `scripts/onboard_project.py`, `docs/operations/ONBOARDING.md`,
тесты — репо в исходном состоянии (ТЗ §7); внешние сущности (sandbox/созданные проекты) —
вручную по разделу «Откат» runbook.
## Ссылки
- BRD: `docs/work-items/ORCH-009/01-brd.md` · TRZ: `02-trz.md` · AC: `03-acceptance-criteria.md`
· Test plan: `04-test-plan.yaml`
- Сверено по коду: `src/projects.py` (`ProjectConfig`, `_parse_projects_json`, `_load_projects`),
`src/plane_sync.py:94-165` (`_DEFAULT_STATES`, `_PLANE_NAME_TO_KEY` — 22 имени, fail-closed
`Confirm Deploy`/`STOP`), `src/qg/checks.py::check_architecture_done`, `src/config.py`
(`auto_*_label`/`bug_fast_track_label`), `requirements.txt` (httpx уже есть)
- Операции: `docs/operations/SETUP_WEBHOOKS.md` (формат Gitea-webhook; Plane workspace-webhook —
SQL-only), `docs/operations/INFRA.md`
- Стандарты: `docs/_standards/PIPELINE_DOCS.md` (§4 ADR-naming), `HANDOFF_PROTOCOL.md`,
`TRACEABILITY.md`
- ADR: adr-0001 (registry), adr-0017/0018 (паттерны условности), adr-0021/0022 (канон промптов/
трассировка), adr-0026 (STOP, группа `cancelled`), ORCH-092 `ADR-001` D2 (язык deployer),
сквозной **adr-0035-turnkey-project-onboarding**

View File

@@ -0,0 +1,66 @@
---
work_item: ORCH-009
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-10
model_used: claude-opus-4-8
---
# 07 — Инфра-требования: ORCH-009 — Turnkey-онбординг проектов
Work Item: **ORCH-009** · Repo: **orchestrator** · Стадия: architecture
> Топология оркестратора **не меняется** (NFR-1/NFR-2: `src/**` и compose не трогаются).
> Файл фиксирует **предусловия исполнения способности** (токены/доступы/контуры) и инфра-границы
> операторского скрипта. Детали решений — `06-adr/ADR-001-turnkey-onboarding-kit-and-cli.md`.
## I-1. Топология / окружения
- **Прод (`orchestrator`, 8500):** не затрагивается. Скрипт не создаёт/не останавливает/не
рестартит контейнеры; в общую БД не пишет (читает только файлы чекаута и внешние API).
- **Staging (`orchestrator-staging`, 8501, БД `./data/staging`):** контур smoke-прогона (ADR D8).
Регистрация sandbox-проекта — в `.env.staging`; рестарт staging — штатный, свободный
(прод-инвариант на него не распространяется).
- **Новые внешние сущности** (создаются скриптом в `apply`): Plane-проект, Gitea-репо +
per-repo webhook. Аддитивно: существующие проекты/репо не модифицируются (BR-9).
- **Запуск скрипта:** хост mva154, из корня чекаута репо orchestrator. Среда исполнения —
venv с `requirements.txt` (httpx уже в зависимостях; новых pip-зависимостей нет) **или**
`docker compose exec orchestrator python scripts/onboard_project.py …` (read-only к рантайму,
без рестартов). Канонический способ фиксирует runbook `docs/operations/ONBOARDING.md`.
## I-2. Переменные окружения / секреты
**Новых env-переменных не вводится.** Используются существующие (предусловия запуска):
| Переменная | Роль в онбординге |
|---|---|
| `ORCH_PLANE_API_TOKEN` (+ `ORCH_PLANE_API_URL`, `ORCH_PLANE_WORKSPACE_SLUG`) | создание/чтение Plane-проекта, статусов, лейблов; токен с правом создания проектов в workspace |
| `ORCH_GITEA_TOKEN` (+ Gitea base URL) | создание репо (под `{{GITEA_OWNER}}`), per-repo webhook; токен с правом create-repo + hooks |
| `ORCH_GITEA_WEBHOOK_SECRET` | **переиспользуется** для webhook нового репо (приёмник валидирует один глобальный секрет, ADR D6); отсутствует → скрипт генерит и печатает оператору для `.env` |
| `ORCH_PROJECTS_JSON` | источник существующих записей для merged-вывода (ADR D7); **применение новой строки — операторский шаг** |
- Секреты — только в `.env`/`.env.staging` на хосте, в гит не попадают (правило #8 CLAUDE.md);
в логах/отчётах скрипта секреты маскируются (NFR-3).
- Kit несёт собственный `.env.example` нового проекта (дескрипторы без значений) — канон секретов
транслируется в онбордируемые репо.
## I-3. Деплой / рестарт
- **Скрипт НИКОГДА не рестартит/не останавливает прод-контейнер** (NFR-2, self-hosting инвариант).
- Регистрация проекта в реестре (Ф-3): правка `.env` (строка `ORCH_PROJECTS_JSON` из отчёта
скрипта) + **управляемый операторский рестарт** оркестратора — групповое окно для ВСЕХ проектов
общего инстанса; runbook помечает шаг self-hosting-предупреждением и командой проверки
(`GET /queue`, резолв статусов нового проекта).
- TTL-self-heal статусов Plane (ORCH-068, 300с) рестарта не требует: статусы/лейблы, созданные
после регистрации, подхватываются без вмешательства.
- Деплой самой задачи ORCH-009 — штатный конвейер: изменение docs/scripts/tests-only, образ
пересобирается стандартно, staging-гейт (8501) обязателен как обычно.
## I-4. CI/CD
- `.gitea/workflows/`**без изменений**: новые тесты (`tests/test_onboarding_kit.py`,
`test_onboarding_script.py`, `test_onboarding_invariants.py`) подхватываются существующим
pytest-шагом; все детерминированы, без сети (NFR-5).
- Инфра-предусловий в образе нет: скрипт — операторский CLI вне рантайма, в образ ничего
дополнительно не запекается.

View File

@@ -0,0 +1,42 @@
---
work_item: ORCH-009
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-10
model_used: claude-opus-4-8
---
# 10 — Технические риски: ORCH-009 — Turnkey-онбординг проектов
Work Item: **ORCH-009** · Repo: **orchestrator** · Стадия: architecture
> Информационный (гейтом не парсится). Детализирует риски BRD §8 (R-1…R-5) до уровня решений
> `06-adr/ADR-001`; митигейшены привязаны к D-решениям и TC тест-плана.
## Реестр рисков
| ID | Риск | Вер. | Влия. | Митигейшн |
|----|------|------|-------|-----------|
| TR-1 | **Drift канона** (R-1): копия `_templates`/`_standards` в новых репо разъезжается с живым каноном орка; kit-промпты отстают от эволюции канона 52d | Сред. | Сред. | BR-2/D3: live-copy в момент онбординга, второй редактируемой копии канона нет; NFR-4: структурные канон-тесты kit (TC-03…08) ловят расхождение в CI; обновление онбордженных репо — их обычные PR |
| TR-2 | **Тихая деградация Plane-контрактов** (R-2): опечатка в имени статуса/лейбла или неверная **группа** → fail-closed/fail-safe ветки (`Confirm Deploy`, `STOP`, авто-лейблы, `Bug`) молча не работают; рабочий статус в группе `completed`/`cancelled` → terminal-detection ORCH-068 ложно терминалит живую задачу | Сред. | Выс. | D4: имена импортируются из `_PLANE_NAME_TO_KEY` (нулевой дрейф по построению, TC-13); D5: канонические группы зафиксированы таблицей ADR с код-критичными констрейнтами (STOP→`cancelled`, терминальные группы только Done/Cancelled/STOP); `verify` резолвит ВСЕ логические ключи включая `confirm_deploy`/`stop` |
| TR-3 | **Скрипт с боевыми токенами** (R-3): ошибка = разрушительное действие на общих Plane/Gitea | Низ. | Выс. | BR-9/D11: `plan` (GET-only) — дефолт; delete-операций в коде нет вовсе (TC-18); аддитивный ensure (TC-17); push только в свежесозданный пустой репо (`empty: true`, D6); существующие сущности не модифицируются |
| TR-4 | **Разрыв «создано, но не зарегистрировано»** (R-4): оператор не применил env+рестарт → проект невидим для орка | Сред. | Сред. | D7: merged-массив одной строкой (без ручного слияния JSON); runbook: явный операторский шаг с self-hosting-предупреждением + команда проверки; `verify` показывает разрыв (TC-12, AC-11) |
| TR-5 | **Утечка орк-специфики в kit** (R-5): новый репо получает ORCH-префикс, порты 8500/8501, self-hosting-правила орка | Сред. | Сред. | D2: скан неразрешённых плейсхолдеров после рендера; TC-10: явный тест на утечки; биекция словаря `placeholders.json` ↔ kit (мёртвые/опечаточные плейсхолдеры не живут) |
| TR-6 | **Поломка HMAC существующих вебхуков**: генерация нового per-repo секрета при едином глобальном `ORCH_GITEA_WEBHOOK_SECRET` приёмника | Низ. | Выс. | D6: секрет **переиспользуется** из env (новый генерится только при полном отсутствии — первичная настройка); секрет маскируется в логах/отчёте (NFR-3) |
| TR-7 | **Связь скрипта с приватными именами `src`** (`_parse_projects_json`, `_PLANE_NAME_TO_KEY`): рефакторинг src валит скрипт | Низ. | Низ. | D4: закрытый список импортов (расширение — только через ADR); поломка видимая, не тихая — импорт падает в тестах (TC-12/13) на том же PR, что рефакторит src; снапшот TC-21 гардит сам src |
| TR-8 | **Plane CE API-пробелы** (OQ-5): POST project/states/labels недоступен в CE → провижининг неполон | Сред. | Низ. | D5: fail-safe деградация в `manual-step` со ссылкой на runbook (имя+группа для UI-создания), не падение (TC-14); `verify` подтверждает итоговую полноту независимо от способа создания |
| TR-9 | **Smoke загрязняет общий контур**: прогон способности в проде = строки в общей БД/очереди | Низ. | Сред. | D8: smoke только на staging (8501, изолированная БД, `.env.staging`); sandbox-сущности одноразовые, снос вручную по разделу «Откат» runbook |
## Сводный вывод
Доминирующий класс — **операционные риски исполнения способности** (TR-2/TR-3/TR-4): они
митигированы структурно (импорт констант вместо копий, GET-only дефолт, отсутствие delete-операций,
verify-режим), а не дисциплиной. Рисков для прод-конвейера самой задачи **нет по построению**:
`src/**` байт-в-байт (AC-12/TC-21), нового кода в горячих путях нет, kill-switch не требуется —
способность активируется только явным запуском операторского CLI.
Эскалация `arch:major-change` **не требуется**: ни новой стадии, ни нового рантайм-компонента,
ни изменения БД — это docs/templates/scripts/tests-only способность (новая стадия/компонент
конвейера не вводится). Возврат в анализ не требуется: ТЗ выполнимо без нарушения принципов.
Остаточный риск для прод-конвейера (self-hosting): **низкий**.