Files
orchestrator/docs/work-items/ORCH-101/02-trz.md
claude-bot 69aa6eacde
All checks were successful
CI / test (push) Successful in 1m9s
analyst(ET): auto-commit from analyst run_id=602
2026-06-10 20:02:14 +03:00

254 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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:332336` | `"HOME": "/home/slin"`; `deploy-finalizer@mva154.local` | то же (единый источник с A2; имя актора может остаться per-actor) |
| A4 | `src/post_deploy.py:575579` | `"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` (соседние строки 533535 уже так делают) |
| A8 | `src/disk_watchdog.py:95` | fallback `["/repos", "/app/data"]` — зеркало дефолта `settings.disk_monitor_paths` | допустимое config-backed зеркало; не блокер. Требование: значения остаются синхронными с конфиг-дефолтом (одна точка истины желательна — на усмотрение архитектора) |
| A9 | `src/projects.py:4255` | `_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 (A1A4, 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` отработала и артефакты `0104` созданы; полный прогон до `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 прочитаны при правке помеченных блоков.