24 KiB
work_item, stage, author_agent, status, created_at, model_used
| work_item | stage | author_agent | status | created_at | model_used |
|---|---|---|---|---|---|
| ORCH-101 | analysis | analyst | ready-for-review | 2026-06-10 | 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):
- Расхардкод: все хост-специфичные значения, которые сегодня обходят конфиг (зашиты в код
src/**, вdocker-compose.yml,Dockerfile,scripts/orchestrator-deploy-hook.sh), переводятся на чтение изSettings(envORCH_*) / compose-интерполяцию${VAR:-default}/ARG/ env-override — с дефолтами, равными текущим боевым значениям (zero-regression). Нормативный реестр находок — §3.1 (результат аудита всего репо). - Секреты: механизм выпуска нового комплекта секретов на новом хосте (генерация
webhook-секретов + чек-лист внешних токенов) + полнота
.env.example. - 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и карте envINFRA.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. Состав:
- Инвентаризация (фиксация аудита): генерируемые локально —
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). - Генерация: webhook-секреты создаются криптослучайно (стандарт:
secrets-модуль Python / эквивалент, длина ≥ 32 байт энтропии) на целевом хосте; форма (отдельныйscripts/gen_secrets.py, режим onboarding-CLI или документированная команда) — решение архитектора. Требования к поведению: повторный запуск даёт другие значения; существующий.envникогда не перезаписывается молча (NFR-3); вывод согласован с ключами.env.example. - Чек-лист в deployment-доке: для каждого секрета — где выпустить (Plane UI/API, Gitea UI, BotFather), куда вписать, как проверить. Явное правило: «боевые секреты текущего хоста не копируются».
- Полнота
.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):
- Шаги: поднять инстанс (env+секреты заполнены) →
GET /healthок → завести тестовый проект (onboarding-CLIverifyили sandbox-проект) → создать тестовую задачу → убедиться, что конвейер «доехал» (задача продвигается по стадиям; минимальный обязательный сигнал — стадияanalysisотработала и артефакты01–04созданы; полный прогон доdone— расширенный режим). - Критерии: каждый шаг имеет явный PASS/FAIL; итог процедуры — однозначный вердикт.
- Воспроизводимость: процедура прогоняется на текущей инфре (staging-песочница 8501 + sandbox-проект) без нового железа — это и приёмочная проверка AC-3.
- 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или остаётся (порт уже переопределяем composecommand:)? - А-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 прочитаны при правке помеченных блоков.