254 lines
24 KiB
Markdown
254 lines
24 KiB
Markdown
---
|
||
work_item: ORCH-101
|
||
stage: analysis
|
||
author_agent: analyst
|
||
status: ready-for-review
|
||
created_at: 2026-06-10
|
||
model_used: claude-opus-4-8
|
||
---
|
||
|
||
# 02 — ТЗ (TRZ): ORCH-101 — ORCH-10-common: расхардкод + секреты + smoke (фундамент тиража)
|
||
|
||
Work Item: **ORCH-101** · Repo: **orchestrator** · Стадия: analysis
|
||
|
||
> ТЗ описывает **что** должно измениться и **где** (модули/контракты/артефакты), выведено из BRD и
|
||
> фактического кода (аудит выполнен по репо на ветке задачи). **Как** (точные имена новых
|
||
> конфиг-ключей, механика генератора секретов, форма smoke-скрипта) — решает архитектор в `06-adr/`.
|
||
|
||
---
|
||
|
||
## 1. Сводка изменения
|
||
|
||
Три слоя одного фундамента тиража (эпик ORCH-10, оба типа A/B):
|
||
|
||
1. **Расхардкод:** все хост-специфичные значения, которые сегодня **обходят конфиг** (зашиты в код
|
||
`src/**`, в `docker-compose.yml`, `Dockerfile`, `scripts/orchestrator-deploy-hook.sh`),
|
||
переводятся на чтение из `Settings` (env `ORCH_*`) / compose-интерполяцию `${VAR:-default}` /
|
||
`ARG` / env-override — **с дефолтами, равными текущим боевым значениям** (zero-regression).
|
||
Нормативный реестр находок — §3.1 (результат аудита всего репо).
|
||
2. **Секреты:** механизм выпуска **нового** комплекта секретов на новом хосте (генерация
|
||
webhook-секретов + чек-лист внешних токенов) + полнота `.env.example`.
|
||
3. **Smoke:** документированная воспроизводимая процедура «тестовый проект + задача → конвейер
|
||
доехал» с PASS/FAIL-критериями (+ анти-регресс grep-тест против возврата хардкодов).
|
||
|
||
Конвейер (`STAGE_TRANSITIONS`/`QG_CHECKS`/`check_*`/verdict-ключи/схема БД) — не меняется.
|
||
|
||
---
|
||
|
||
## 2. Задействованные модули / пути
|
||
|
||
| Путь | Действие |
|
||
|------|----------|
|
||
| `src/config.py` | изменить: новые `Settings`-ключи (HOME агент-процессов, git-идентичность акторов, внешний Gitea-URL уже есть — `gitea_public_url`; прочее по §3.1), дефолты = текущим значениям |
|
||
| `src/plane_sync.py` | изменить: `notify_stage_change` — убрать литерал `gitea_base = "http://git.mva154.duckdns.org"` и owner `admin` из путей ссылок → `settings.gitea_public_url` (fallback `gitea_url`) + `settings.gitea_owner` |
|
||
| `src/agents/launcher.py` | изменить: env агент-процесса (×2 места) — `HOME` и git author/committer (`claude-bot@mva154.local`) из конфига |
|
||
| `src/self_deploy.py` | изменить: env detached-процесса — `HOME`, `deploy-finalizer@mva154.local` из конфига |
|
||
| `src/post_deploy.py` | изменить: env монитора — `HOME`, `post-deploy-monitor@mva154.local` из конфига |
|
||
| `src/image_freshness.py` | изменить ИЛИ обосновать: `_STAGING_PORT = 8501` (см. §3.4 А-1, инвариант ORCH-058) |
|
||
| `src/qg/checks.py` | изменить ИЛИ обосновать: `SELF_HOSTING_REPO = "orchestrator"` (см. §3.4 А-2) |
|
||
| `src/fs_normalize.py` | изменить: fallback-подсказку `sudo chown -R 1000:1000 /repos/_wt` строить из `settings` (uid/worktrees_dir) |
|
||
| `src/projects.py` | не менять логику; `_DEFAULT_PROJECTS` остаётся документированным fallback (UUID'ы чужого Plane безвредны); фиксация в чек-листе «на новом хосте обязателен `ORCH_PROJECTS_JSON`» |
|
||
| `watchdog/config.py` | проверить/синхронизировать: уже env-driven (`WATCHDOG_*`); дефолт `http://127.0.0.1:8500/metrics` согласовать с параметризацией прод-порта |
|
||
| `docker-compose.yml` | изменить: интерполяция `${VAR:-default}` для всех хост-значений (§3.1 B) |
|
||
| `Dockerfile` | изменить: `ARG` для uid/gid/username/home; CMD-порт — по решению архитектора (§3.4 А-3) |
|
||
| `scripts/orchestrator-deploy-hook.sh` | изменить: `REPO=/home/slin/repos/orchestrator` → env-override `REPO="${REPO:-…}"` |
|
||
| `scripts/` (новый) | создать: генератор секретов и/или smoke-обвязка — форма за архитектором (§3.2, §3.3) |
|
||
| `.env.example` | изменить: новые ключи, секции секретов с плейсхолдерами, чек-лист «что заполнить» |
|
||
| `.env.staging.example` | изменить: согласовать с новыми ключами (при необходимости) |
|
||
| `tests/test_no_host_hardcodes.py` (новый) | создать: структурный анти-регресс grep-тест (FR-8) |
|
||
| `tests/` (новые) | создать: тесты параметризации/секретов/smoke по `04-test-plan.yaml` |
|
||
| `docs/operations/` | создать deployment-раздел тиража (предложение: `REPLICATION.md`; имя финализирует архитектор) + обновить карту env в `INFRA.md` |
|
||
| `CHANGELOG.md`, `CLAUDE.md`, `README.md` | изменить: запись изменения; паспорт/обзорные доки — по фактическому объёму (правило агентов №2/№6) |
|
||
|
||
---
|
||
|
||
## 3. Функциональные требования
|
||
|
||
### FR-1 — Нормативный реестр хардкодов (результат аудита) и их устранение
|
||
|
||
Привязка: BR-1, BR-2. Аудит выполнен по всему репо (grep по классам: IP/hostname, пути, порты,
|
||
gid/uid, URL, идентичности). Реестр ниже **нормативен**: developer закрывает каждую строку;
|
||
расхождение «нашёл ещё» — дополняет реестр в PR, а не игнорирует.
|
||
|
||
#### 3.1. Реестр
|
||
|
||
**A. `src/**` / `watchdog/**` — код, обходящий конфиг (блокеры AC-1):**
|
||
|
||
| # | Файл:строка | Хардкод | Требование |
|
||
|---|---|---|---|
|
||
| A1 | `src/plane_sync.py:1064` | `gitea_base = "http://git.mva154.duckdns.org"` в `notify_stage_change` + owner `admin` в построении ссылок Branch/PR | читать `settings.gitea_public_url` (fallback `settings.gitea_url`; loopback-семантика — как в `notifications._build_plane_issue_link`) + `settings.gitea_owner`; никаких новых ключей не требуется |
|
||
| A2 | `src/agents/launcher.py:594, 825` | `"HOME": "/home/slin"`; `GIT_AUTHOR/COMMITTER_EMAIL: claude-bot@mva154.local` (×2 места) | HOME и git-идентичность из конфига (новые ключи, дефолты = текущим значениям) |
|
||
| A3 | `src/self_deploy.py:332–336` | `"HOME": "/home/slin"`; `deploy-finalizer@mva154.local` | то же (единый источник с A2; имя актора может остаться per-actor) |
|
||
| A4 | `src/post_deploy.py:575–579` | `"HOME": "/home/slin"`; `post-deploy-monitor@mva154.local` | то же |
|
||
| A5 | `src/image_freshness.py:61` | `_STAGING_PORT = 8501` (модульная константа; намеренный анти-prod-инвариант ORCH-058 AC-9) | конфигуризовать с дефолтом 8501 **с сохранением инварианта** (guard «staging-порт ≠ прод-порт») ИЛИ оставить константой с явным обоснованием в ADR — решение архитектора (§3.4 А-1) |
|
||
| A6 | `src/qg/checks.py:517` | `SELF_HOSTING_REPO = "orchestrator"` (узел: на него опираются все `*_repos`-leaf'ы «empty CSV → self-hosting only») | конфиг-ключ с дефолтом `orchestrator` ИЛИ нормативная платформенная константа («репо платформы в тираже обязан называться `orchestrator`», фиксируется в deployment-доке) — решение архитектора (§3.4 А-2) |
|
||
| A7 | `src/fs_normalize.py:539` | fallback-строка подсказки `sudo chown -R 1000:1000 /repos/_wt` | строить из `settings.fs_target_uid` / `settings.worktrees_dir` (соседние строки 533–535 уже так делают) |
|
||
| A8 | `src/disk_watchdog.py:95` | fallback `["/repos", "/app/data"]` — зеркало дефолта `settings.disk_monitor_paths` | допустимое config-backed зеркало; не блокер. Требование: значения остаются синхронными с конфиг-дефолтом (одна точка истины желательна — на усмотрение архитектора) |
|
||
| A9 | `src/projects.py:42–55` | `_DEFAULT_PROJECTS`: UUID'ы Plane текущего хоста | НЕ блокер (документированный fallback «out of the box», чужие UUID не сматчатся). Требование: чек-лист тиража обязывает задать `ORCH_PROJECTS_JSON` |
|
||
| A10 | `watchdog/config.py:100,144` | дефолт `http://127.0.0.1:8500/metrics` | уже env-driven (`WATCHDOG_METRICS_URL`); дефолт легитимен; согласовать с А-3 (прод-порт), если тот станет параметром |
|
||
|
||
> Паттерн `getattr(settings, "x", <литерал>)` (A7/A8 и др.) — **config-backed fallback**, не
|
||
> хардкод-блокер: значение управляется конфигом. Блокер — только код, который конфиг **обходит**.
|
||
|
||
**B. `docker-compose.yml` — хост-специфика (блокеры AC-6):**
|
||
|
||
| # | Место | Хардкод |
|
||
|---|---|---|
|
||
| B1 | volumes ×3 сервисов | `/home/slin/repos:/repos`(+`:ro`), `/home/slin/.claude`, `/home/slin/.claude.json`, `/home/slin/.orchestrator-ssh:/home/slin/.ssh` |
|
||
| B2 | volumes ×2 сервисов | `/usr/lib/node_modules/@anthropic-ai/claude-code:/opt/claude-code:ro`, `/usr/bin/node:/usr/bin/node:ro` |
|
||
| B3 | `group_add` ×3 | `"999"` (gid docker-группы хоста) |
|
||
| B4 | `user:` ×2 | `"1000:1000"` (uid:gid хоста) |
|
||
| B5 | environment | `ORCH_HOST_REPOS_DIR=/home/slin/repos` ×2, `ORCH_DEPLOY_HOST_REPO_PATH=/home/slin/repos/orchestrator`, `DEPLOY_SSH_USER=slin` ×2, `ORCH_DEPLOY_SSH_USER=slin`, `DEPLOY_HOOK_SCRIPT=/home/slin/bin/enduro-deploy-hook.sh` ×2 (legacy enduro) |
|
||
| B6 | `orchestrator-staging.command` | порт `8501` |
|
||
|
||
Требование: compose-интерполяция `${VAR:-default}` (источник — `.env`, штатный механизм Compose);
|
||
`docker compose config` без заданных переменных резолвится в текущие значения 1:1. Целевая
|
||
семантика «HOME внутри контейнера согласован с маунтами `.claude`/`.ssh` и `useradd` Dockerfile»
|
||
(инвариант ORCH-040) сохраняется как согласованная группа переменных.
|
||
|
||
**C. `Dockerfile`:**
|
||
|
||
| # | Место | Хардкод |
|
||
|---|---|---|
|
||
| C1 | `useradd -u 1000 -g 1000 -m -d /home/slin -s /bin/bash slin` | uid/gid/home/username |
|
||
| C2 | `CMD … --port 8500` | прод-порт |
|
||
|
||
Требование: C1 → `ARG` с дефолтами (1000/1000//home/slin/slin). C2 — по решению архитектора
|
||
(§3.4 А-3): порт уже переопределяем через compose `command:`; допустимо оставить CMD-дефолт 8500.
|
||
|
||
**D. `scripts/`:**
|
||
|
||
| # | Место | Хардкод |
|
||
|---|---|---|
|
||
| D1 | `orchestrator-deploy-hook.sh:38` | `REPO=/home/slin/repos/orchestrator` (единственный непараметризованный путь скрипта; остальное уже env-override) |
|
||
|
||
Требование: `REPO="${REPO:-/home/slin/repos/orchestrator}"` (паттерн остальных переменных скрипта).
|
||
`scripts/staging_check.py` — уже env-driven (`ORCH_PLANE_API_URL`/`ORCH_GITEA_URL`/`--base-url`),
|
||
изменений не требует.
|
||
|
||
**E. Уже параметризовано (НЕ трогать, фиксация аудита):** дефолты `src/config.py`
|
||
(`plane_api_url:8091`, `gitea_url:3000`, `repos_dir`, `host_repos_dir`, `worktrees_dir`, `runs_dir`,
|
||
`db_path`, `deploy_ssh_user`, `deploy_host_repo_path`, `deploy_prod_target_port:8500`,
|
||
`post_deploy_base_url`, `disk_monitor_paths`, `claude_bin`) — легитимные значения по умолчанию,
|
||
управляемые env; менять их значения запрещено (BR-5).
|
||
|
||
### FR-2 — Чтение хост-значений из конфига в `src/**`
|
||
|
||
Привязка: BR-1. Каждый блокер класса A (A1–A4, A7; A5/A6 — по исходу §3.4) читает значение через
|
||
`settings` (существующие либо новые ключи `Settings` с env `ORCH_*`). Требования к новым ключам:
|
||
|
||
- дефолт = текущее боевое значение (BR-5): `/home/slin`, `claude-bot`, `*@mva154.local`-адреса;
|
||
- описательный комментарий в `config.py` по образцу существующих блоков (назначение + env-имя);
|
||
- ключи отражены в `.env.example` и карте env `INFRA.md`;
|
||
- точные имена ключей и группировка (например, единый `agent_home_dir` + per-actor идентичности
|
||
или общий email-домен) — решение архитектора; ТЗ фиксирует контракт: **ни один из пяти акторов
|
||
(агенты CLI ×2 места, self-deploy finalizer, post-deploy monitor, fs-подсказка) не содержит
|
||
литералов `/home/slin` / `mva154` после изменения**.
|
||
|
||
### FR-3 — Параметризация инфра-файлов
|
||
|
||
Привязка: BR-2. Реестр B/C/D закрыт; механика — compose-интерполяция / `ARG` / shell-default.
|
||
Инварианты: ORCH-040 (uid 1000 + `group_add` docker-gid + HOME-согласованность маунтов — группа
|
||
переменных меняется согласованно, «МИНА 1» `group_add` не удаляется), ORCH-058 (staging-сервис
|
||
никогда не резолвится в прод-таргет). Поведение `docker compose config` при пустом окружении —
|
||
эквивалент текущего файла (проверяется тестом TC-06).
|
||
|
||
### FR-4 — Механизм секретов нового хоста
|
||
|
||
Привязка: BR-3. Состав:
|
||
|
||
1. **Инвентаризация** (фиксация аудита): генерируемые локально — `ORCH_PLANE_WEBHOOK_SECRET`,
|
||
`ORCH_GITEA_WEBHOOK_SECRET`; выпускаемые внешними системами — `ORCH_PLANE_API_TOKEN`,
|
||
`ORCH_PLANE_BOT_*` (7 шт., опциональны: fallback на `ORCH_PLANE_API_TOKEN`), `ORCH_GITEA_TOKEN`,
|
||
`ORCH_TELEGRAM_BOT_TOKEN` (+ несекретный `ORCH_TELEGRAM_CHAT_ID`).
|
||
2. **Генерация:** webhook-секреты создаются криптослучайно (стандарт: `secrets`-модуль Python /
|
||
эквивалент, длина ≥ 32 байт энтропии) на целевом хосте; форма (отдельный
|
||
`scripts/gen_secrets.py`, режим onboarding-CLI или документированная команда) — решение
|
||
архитектора. Требования к поведению: повторный запуск даёт другие значения; существующий `.env`
|
||
никогда не перезаписывается молча (NFR-3); вывод согласован с ключами `.env.example`.
|
||
3. **Чек-лист** в deployment-доке: для каждого секрета — где выпустить (Plane UI/API, Gitea UI,
|
||
BotFather), куда вписать, как проверить. Явное правило: «боевые секреты текущего хоста не
|
||
копируются».
|
||
4. **Полнота `.env.example`:** каждый обязательный для старта ключ присутствует (с плейсхолдером
|
||
или дефолтом), включая новые ключи FR-2/FR-3; секретные значения — только плейсхолдеры.
|
||
|
||
### FR-5 — Smoke-верификация тиража
|
||
|
||
Привязка: BR-4. Документированная процедура (deployment-раздел) + опциональная скрипт-обвязка
|
||
(форма — архитектор; кандидаты-кирпичи уже в репо: `scripts/onboard_project.py` `plan/apply/verify`,
|
||
`scripts/staging_check.py`, `GET /health` / `/queue` / `/metrics`):
|
||
|
||
1. **Шаги:** поднять инстанс (env+секреты заполнены) → `GET /health` ок → завести тестовый
|
||
проект (onboarding-CLI `verify` или sandbox-проект) → создать тестовую задачу → убедиться, что
|
||
конвейер «доехал» (задача продвигается по стадиям; минимальный обязательный сигнал — стадия
|
||
`analysis` отработала и артефакты `01–04` созданы; полный прогон до `done` — расширенный режим).
|
||
2. **Критерии:** каждый шаг имеет явный PASS/FAIL; итог процедуры — однозначный вердикт.
|
||
3. **Воспроизводимость:** процедура прогоняется на текущей инфре (staging-песочница 8501 +
|
||
sandbox-проект) без нового железа — это и приёмочная проверка AC-3.
|
||
4. **Stateless:** процедура нигде не предполагает перенос данных/БД с боевого хоста.
|
||
|
||
### FR-6 — Анти-регресс структурный тест
|
||
|
||
Привязка: BR-6. Новый `tests/test_no_host_hardcodes.py` (образец — `tests/test_agent_prompts_canon.py`):
|
||
|
||
- сканирует `src/**/*.py` и `watchdog/**/*.py` на запрещённые литералы: `82.22.50.71`,
|
||
`/home/slin`, `mva154`, `duckdns` (список централизован в тесте; расширяем);
|
||
- судит **код**: строки-комментарии и докстринги исключаются (NFR-5; механика исключения —
|
||
прагматичная построчная/AST — за developer);
|
||
- `tests/**`, `docs/**`, `.env.example` — вне зоны сканирования;
|
||
- допускает точечный явный allowlist (файл:литерал) с комментарием-обоснованием — пустой на
|
||
момент сдачи задачи (все блокеры A закрыты).
|
||
|
||
### FR-7 — Документация
|
||
|
||
Привязка: BR-7. Deployment-раздел в `docs/operations/` (предложение: `REPLICATION.md`): карта
|
||
новых env-переменных (включая compose-интерполяцию), процедура секретов (FR-4), smoke-процедура
|
||
(FR-5), границы «10-common vs Lite vs Bundled». Обновления: `INFRA.md` (карта env),
|
||
`CHANGELOG.md`; `CLAUDE.md`/`README.md` — по фактическому объёму изменений (правило №2/№6).
|
||
|
||
---
|
||
|
||
### 3.4. Вопросы архитектору (решаются в `06-adr/`, не в ТЗ)
|
||
|
||
- **А-1:** `_STAGING_PORT = 8501` (`image_freshness`) — конфиг-ключ с guard'ом «≠ прод-порт» или
|
||
нормативная константа? Инвариант ORCH-058 AC-9 («никогда не прод 8500») обязан сохраниться.
|
||
- **А-2:** `SELF_HOSTING_REPO = "orchestrator"` — конфиг-ключ или платформенная конвенция тиража
|
||
(репо платформы всегда `orchestrator`)? На него завязаны все `*_repos`-leaf'ы.
|
||
- **А-3:** CMD-порт Dockerfile (8500) — `ARG` или остаётся (порт уже переопределяем compose
|
||
`command:`)?
|
||
- **А-4:** форма генератора секретов (отдельный скрипт / режим `onboard_project.py` /
|
||
документированная команда) и форма smoke (чистый runbook / runbook + скрипт).
|
||
- **А-5:** группировка новых конфиг-ключей FR-2 (единый HOME-ключ + per-actor email'ы vs общий
|
||
email-домен).
|
||
|
||
---
|
||
|
||
## 4. Изменения API
|
||
|
||
Нет. Существующие эндпоинты (`/health`, `/queue`, `/metrics`) переиспользуются smoke-процедурой
|
||
read-only; новые эндпоинты не вводятся.
|
||
|
||
## 5. Изменения схемы БД
|
||
|
||
Нет.
|
||
|
||
## 6. Требования к новым/изменённым QG checks
|
||
|
||
Нет. `QG_CHECKS`/`check_*`/machine-verdict ключи не меняются. Новый структурный тест (FR-6) —
|
||
обычный pytest: попадает в существующие гейты `check_ci_green`/`check_tests_passed`/merge-gate
|
||
re-test автоматически, без регистрации нового QG.
|
||
|
||
## 7. Совместимость / регресс
|
||
|
||
- **Zero-regression (BR-5):** все новые параметры — с дефолтами, равными сегодняшним боевым
|
||
значениям; пустой/неизменённый `.env` ⇒ байт-в-байт текущее поведение; `docker compose config`
|
||
без переменных ⇒ эквивалент текущего файла. Полный `pytest tests/ -q` зелёный.
|
||
- **Обратимость (NFR-2):** откат = не задавать новые переменные. Функциональный kill-switch не
|
||
требуется (дефолты и есть прежнее поведение); вводить новый флаг ради флага запрещено.
|
||
- **Область раската:** изменения инертны для enduro-trails (значения те же); compose/Dockerfile
|
||
вступают в силу только при следующем штатном деплое через конвейер (staging 8501 → ручной
|
||
Confirm Deploy) — прод-контейнер в рамках задачи не рестартуется (NFR-1).
|
||
- **Инварианты соседних маркеров (правило №9):** ORCH-040 (uid/gid/HOME/«МИНА 1»), ORCH-058
|
||
(freshness → только staging), ORCH-036/059 (self-deploy фазы, Confirm Deploy), INV-4 (мерж только
|
||
через PR-merge API) — сохраняются; соответствующие ADR прочитаны при правке помеченных блоков.
|