From 08561579d10bd3c3cfe8846d486494bda8a97466 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Wed, 10 Jun 2026 15:00:00 +0300 Subject: [PATCH] architect(ET): auto-commit from architect run_id=585 --- docs/architecture/README.md | 36 ++ .../adr-0035-turnkey-project-onboarding.md | 80 ++++ .../ADR-001-turnkey-onboarding-kit-and-cli.md | 341 ++++++++++++++++++ .../ORCH-009/07-infra-requirements.md | 66 ++++ docs/work-items/ORCH-009/10-tech-risks.md | 42 +++ 5 files changed, 565 insertions(+) create mode 100644 docs/architecture/adr/adr-0035-turnkey-project-onboarding.md create mode 100644 docs/work-items/ORCH-009/06-adr/ADR-001-turnkey-onboarding-kit-and-cli.md create mode 100644 docs/work-items/ORCH-009/07-infra-requirements.md create mode 100644 docs/work-items/ORCH-009/10-tech-risks.md diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 278f7bc..b302f9c 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -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 ``` diff --git a/docs/architecture/adr/adr-0035-turnkey-project-onboarding.md b/docs/architecture/adr/adr-0035-turnkey-project-onboarding.md new file mode 100644 index 0000000..35dbce8 --- /dev/null +++ b/docs/architecture/adr/adr-0035-turnkey-project-onboarding.md @@ -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/.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). diff --git a/docs/work-items/ORCH-009/06-adr/ADR-001-turnkey-onboarding-kit-and-cli.md b/docs/work-items/ORCH-009/06-adr/ADR-001-turnkey-onboarding-kit-and-cli.md new file mode 100644 index 0000000..421418a --- /dev/null +++ b/docs/work-items/ORCH-009/06-adr/ADR-001-turnkey-onboarding-kit-and-cli.md @@ -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/.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}}` | 1–2 фразы «зачем проект» (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** diff --git a/docs/work-items/ORCH-009/07-infra-requirements.md b/docs/work-items/ORCH-009/07-infra-requirements.md new file mode 100644 index 0000000..f929985 --- /dev/null +++ b/docs/work-items/ORCH-009/07-infra-requirements.md @@ -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 вне рантайма, в образ ничего + дополнительно не запекается. diff --git a/docs/work-items/ORCH-009/10-tech-risks.md b/docs/work-items/ORCH-009/10-tech-risks.md new file mode 100644 index 0000000..5023bd8 --- /dev/null +++ b/docs/work-items/ORCH-009/10-tech-risks.md @@ -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): **низкий**.