---
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}}` | 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**
