diff --git a/docs/work-items/ORCH-101/01-brd.md b/docs/work-items/ORCH-101/01-brd.md new file mode 100644 index 0000000..d1dd914 --- /dev/null +++ b/docs/work-items/ORCH-101/01-brd.md @@ -0,0 +1,175 @@ +--- +work_item: ORCH-101 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-10 +model_used: claude-opus-4-8 +--- + +# 01 — BRD (бизнес-требования): ORCH-101 — ORCH-10-common: расхардкод + секреты + smoke (фундамент тиража) + +Work Item: **ORCH-101** · Repo: **orchestrator** · Стадия: analysis + +## 1. Бизнес-контекст и проблема + +Эпик **ORCH-10** (домен 📈 D5 «Масштаб», `docs/epics/self-evolution.md`, D5.3) — тираж платформы +для раздачи **текущей** функциональности нескольким заказчикам **на тест**. Решения Славы 10.06: + +- **Два типа тиража, оба stateless** (наши задачи/данные НЕ переносятся, чистый старт): + - **Тип A (Lite):** перенос орк + watchdog на новую инфру; окружение донастраивается по чёткой + инструкции. + - **Тип B (Bundled):** весь стек (Plane/Gitea/орк) одним комплектом. +- **ORCH-101 = 10-common** — общий фундамент **обоих** типов. Без него ни Lite, ни Bundled не поедут. + +**Проблема.** Платформа фактически прибита гвоздями к хосту `mva154`: + +1. **Хардкоды хоста/окружения.** Аудит репо (стадия analysis, см. реестр в `02-trz.md` §3.1) + подтвердил: в `src/**` есть код, который **обходит конфиг** — внешний URL Gitea + `http://git.mva154.duckdns.org` зашит в `src/plane_sync.py`, `HOME=/home/slin` и git-идентичность + `*@mva154.local` зашиты в трёх местах (`launcher`/`self_deploy`/`post_deploy`); в + `docker-compose.yml` зашиты пути `/home/slin/...`, gid docker-группы `999`, uid `1000:1000`, + node-пути; в `Dockerfile` — `useradd … -d /home/slin slin` и порт CMD; в + `scripts/orchestrator-deploy-hook.sh` — `REPO=/home/slin/repos/orchestrator`. На новом хосте + (другой пользователь, другие пути, другой gid docker, другие URL) платформа не заведётся без + правки кода — это блокер тиража. +2. **Секреты.** Боевые секреты (`.env`) копировать на чужую инфраструктуру нельзя (общий + webhook-secret = чужой хост сможет слать нам валидные вебхуки; утечка токенов). Механизма + «сгенерировать НОВЫЙ комплект секретов на новом хосте» нет; `.env.example` не имеет чек-листа + «что обязан заполнить оператор». +3. **Верификация.** Нет процедуры убедиться, что развёрнутая копия платформы **жива**: «завести + тестовый проект + задачу → конвейер доехал». Без неё каждый тираж — слепой деплой. + +**Ценность:** после ORCH-101 платформа разворачивается на новой инфре конфигурацией (env), с +собственными секретами и проверяется воспроизводимым smoke-прогоном. Это критический путь всего +эпика ORCH-10. + +## 2. Объём (scope) + +### В объёме + +1. **Расхардкод** — вынести все хардкоды хоста/окружения в конфиг/env **с дефолтами, равными + текущим значениям**: IP/hostname (`82.22.50.71`, `mva154`, `*.duckdns.org`), пути + (`/home/slin/...`, `/repos`), порты (8500/8501/3000/8091), gid docker `999`, uid `1000`, имена + контейнеров/сервисов/образов, URL Plane/Gitea, git-идентичность агентов. Зоны: + `src/**`, `watchdog/**`, `docker-compose.yml`, `Dockerfile`, `scripts/`, `.env.example`. + Аудит — **весь репо** (нормативный реестр находок — `02-trz.md` §3.1). +2. **Секреты** — механизм генерации **новых** секретов на новом хосте (webhook-секреты — + криптослучайная генерация на месте; внешние токены Plane/Gitea/Telegram — выпуск на целевых + системах по чек-листу). Полный шаблон `.env.example` с плейсхолдерами + чек-лист «что заполнить». + Боевые секреты не покидают текущий хост. +3. **Smoke-верификация** — документированная воспроизводимая процедура (и/или скрипт-обвязка): + «завести тестовый проект + задачу → конвейер доехал», с чёткими критериями PASS/FAIL. +4. **Анти-регресс** — структурный тест (grep-тест по образцу `tests/test_agent_prompts_canon.py`), + запрещающий возврат хост-хардкодов в `src/**`. +5. **Доки** — deployment-раздел (параметризация, карта новых env-ключей, чек-лист секретов, + smoke-процедура) + обновление карты env в `docs/operations/INFRA.md` + `CHANGELOG.md`. + +### Вне объёма + +- **Сама инструкция тиража Типа A (Lite) end-to-end** и **сборка комплекта Типа B (Bundled)** — + отдельные задачи эпика ORCH-10; 10-common даёт им фундамент. +- **Перенос данных/задач/истории** — тираж stateless по решению Славы 10.06 (чистый старт). +- **Мультитенантность** (D5.6), горизонтальный воркер-пул (D5.4), IaC-автоматизация + (Terraform/Ansible) — не сейчас. +- **Изменение конвейера**: `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict ключи / + схема БД — не трогаются (задача конфигурационная, не процессная). +- **Расхардкод исторических документов** `docs/**`, `memory/**`, `CHANGELOG.md` — историческая + правда не переписывается; аудит судит **код и инфра-файлы**, не архив. +- **`tests/**`** — тестам разрешены литералы как фикстуры/ожидания. +- **Onboarding-kit** (`onboarding/`, ORCH-009) — уже параметризован плейсхолдерами `{{NAME}}`; + правится только при необходимости согласовать новые env-ключи. + +## 3. Заинтересованные стороны + +- **Слава (Owner)** — заказчик эпика ORCH-10; принимает BRD (гейт Approved) и прод-деплой + (Confirm Deploy). +- **Будущие заказчики-тестеры** — потребители тиража Типов A/B (получают платформу на своей инфре). +- **Стрим** — оператор тиража: выполняет инструкцию, генерирует секреты, гоняет smoke. +- **Последующие задачи эпика ORCH-10 (Lite/Bundled)** — прямые потребители фундамента. +- **Агенты конвейера / self-hosting** — изменения катятся через общий прод-инстанс; регресс на + текущем хосте недопустим (обслуживает и enduro-trails). + +## 4. Бизнес-требования (BR) + +- **BR-1 (расхардкод src)** — в `src/**` и `watchdog/**` не остаётся хост-специфичных литералов + (IP/hostname/пути/порты/URL/идентичности), **обходящих конфиг**: каждое такое значение читается + из `Settings` (env `ORCH_*` / `WATCHDOG_*`) с дефолтом, равным текущему боевому значению. + Дефолты в `src/config.py` / `watchdog/config.py` — легитимное место значений по умолчанию. +- **BR-2 (расхардкод инфра-файлов)** — `docker-compose.yml`, `Dockerfile`, + `scripts/orchestrator-deploy-hook.sh` параметризованы (compose-интерполяция `${VAR:-default}`, + `ARG`, env-override соответственно); без заданных переменных конфигурация резолвится в текущие + значения 1:1. +- **BR-3 (секреты)** — существует механизм получения **нового** комплекта секретов на новом хосте: + генерируемые локально (webhook-секреты) создаются криптослучайно; выпускаемые внешними системами + (токены Plane/Gitea/Telegram) перечислены в чек-листе с указанием, где их выпустить. + `.env.example` покрывает 100% обязательных ключей. Копирование боевых секретов не требуется ни + на одном шаге. +- **BR-4 (smoke)** — существует документированная воспроизводимая процедура верификации тиража: + «развёрнутый инстанс → тестовый проект + задача → конвейер доехал», с момента старта до явного + PASS/FAIL-критерия; воспроизводимость подтверждена прогоном на текущей инфре (staging-песочница + 8501 / sandbox-проект). +- **BR-5 (zero-regression)** — на текущем хосте поведение 1:1: все новые параметры имеют дефолты = + сегодняшним значениям; пустой/неизменённый `.env` даёт байт-в-байт текущее поведение; полный + `pytest tests/ -q` зелёный. +- **BR-6 (анти-регресс)** — возврат хост-хардкода в `src/**` ломает CI: структурный тест + сканирует код (вне комментариев/докстрингов) на запрещённые литералы. +- **BR-7 (доки)** — deployment-раздел + карта env + чек-лист секретов + smoke-процедура + опубликованы в `docs/operations/`; `CHANGELOG.md` обновлён (правило агентов №2). + +## 5. Нефункциональные требования (NFR) + +- **NFR-1 (self-hosting safety)** — задача не перезапускает и не роняет прод-контейнер + `orchestrator`; правки `docker-compose.yml`/`Dockerfile` инертны до следующего штатного деплоя + через конвейер (staging 8501 → Confirm Deploy). Никаких push/force-push в `main` мимо PR. +- **NFR-2 (обратимость)** — «kill-switch природа» параметризации: отсутствие новых env-переменных + = текущее поведение. Отдельный функциональный kill-switch не требуется — дефолты и есть откат. +- **NFR-3 (секреты вне гита)** — реальные значения только в `.env`/`.env.staging` на хосте + (правило агентов №8); в гит попадают только шаблоны/плейсхолдеры; генератор секретов никогда не + перезаписывает существующий `.env` молча. +- **NFR-4 (инварианты соседних задач, правило №9)** — параметризация не ослабляет зафиксированные + инварианты: ORCH-058 «freshness-путь целится ТОЛЬКО в staging, никогда в прод 8500»; ORCH-040 + «uid 1000 + group_add 999 (МИНА 1 — не удалять) + HOME согласован с маунтами»; INV-4 «мерж только + через Gitea PR-merge API». Затронутые маркеры читаются перед правкой. +- **NFR-5 (стабильность анти-регресс теста)** — grep-тест детерминирован и не флапает: судит код, + исключает комментарии/докстринги/`tests/**`/`docs/**`; список запрещённых литералов + централизован в самом тесте. +- **NFR-6 (конвейер не трогаем)** — `STAGE_TRANSITIONS`, состав `QG_CHECKS`, семантика `check_*`, + machine-verdict ключи, схема БД — байт-в-байт прежние. + +## 6. Допущения и ограничения + +- Целевой хост тиража: Linux + Docker + Compose; Plane и Gitea доступны (для Типа A — донастройка + по инструкции Lite-задачи; их URL/токены подаются через env). Claude CLI присутствует на хосте + (пути маунтов параметризуются). +- Тираж — **single-tenant**: один инстанс платформы на заказчика; общая мультитенантная топология + вне объёма. +- Имя self-hosting репо платформы (`orchestrator`) — платформенная конвенция; делать ли его + конфигурируемым (`SELF_HOSTING_REPO`) — решение архитектора (см. `02-trz.md` §3.4, вопрос А-2). +- Встроенный fallback-реестр проектов (`src/projects.py::_DEFAULT_PROJECTS`) содержит UUID'ы Plane + текущего хоста; на новом хосте обязателен `ORCH_PROJECTS_JSON` — фиксируется в чек-листе, сами + UUID'ы в дефолте безвредны (не сматчатся) и остаются как документированный fallback. +- Историческая документация и комментарии кода могут упоминать `mva154`/пути — это не хардкоды + поведения и аудитом кода не считаются. + +## 7. Критерии успеха + +Платформа разворачивается на чужой инфре **без правки кода** — только env/конфиг; секреты +выпускаются заново по чек-листу; smoke-прогон даёт явный PASS; на текущем хосте — ноль изменений +поведения и зелёный полный регресс. Детальные условия PASS/FAIL — `03-acceptance-criteria.md` +(AC-1…AC-8); прослеживаемость BR ↔ AC — в сводной матрице там же. + +## 8. Риски + +Кратко (детально — `10-tech-risks.md`, заполняет архитектор): + +- **Р-1: скрытый регресс при параметризации горячих путей** (`launcher` env агентов, compose + volumes) — митигируется дефолтами = текущим значениям + полным регрессом + staging-прогоном. +- **Р-2: ослабление инварианта ORCH-058** при конфигуризации staging-порта (`_STAGING_PORT`) — + митигируется guard-условием «staging-порт ≠ прод-порт» и решением архитектора (возможно, + оставить константой с обоснованием). +- **Р-3: флап анти-регресс grep-теста** (ложные срабатывания на комментарии) — митигируется + правилами исключений NFR-5. +- **Р-4: неполный аудит** (пропущенный хардкод всплывает на первом реальном тираже) — митигируется + нормативным реестром §3.1 ТЗ + smoke-процедурой как последней линией обнаружения. +- **Р-5: расползание скоупа в Lite/Bundled** — митигируется явным «вне объёма» §2. diff --git a/docs/work-items/ORCH-101/02-trz.md b/docs/work-items/ORCH-101/02-trz.md new file mode 100644 index 0000000..1846960 --- /dev/null +++ b/docs/work-items/ORCH-101/02-trz.md @@ -0,0 +1,253 @@ +--- +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 прочитаны при правке помеченных блоков. diff --git a/docs/work-items/ORCH-101/03-acceptance-criteria.md b/docs/work-items/ORCH-101/03-acceptance-criteria.md new file mode 100644 index 0000000..e5585a4 --- /dev/null +++ b/docs/work-items/ORCH-101/03-acceptance-criteria.md @@ -0,0 +1,131 @@ +--- +work_item: ORCH-101 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-10 +model_used: claude-opus-4-8 +--- + +# 03 — Критерии приёмки (Acceptance Criteria): ORCH-101 — ORCH-10-common: расхардкод + секреты + smoke + +Work Item: **ORCH-101** · Repo: **orchestrator** · Стадия: analysis + +Формат: каждый критерий имеет **PASS** (что должно быть истинно для приёмки) и **FAIL** +(что считается провалом). Любой машинный/ручной reviewer проверяет их буквально по файлам +репозитория. AC-1…AC-4 — дословно из бизнес-запроса (уточнены до проверяемости); AC-5…AC-8 — +детализация скоупа (секреты/инфра/анти-регресс/инварианты). + +--- + +## AC-1 — Ноль хардкодов хоста/путей/портов в `src/**` + +**Условие:** в коде `src/**` и `watchdog/**` (вне комментариев/докстрингов) нет хост-специфичных +литералов, обходящих конфиг; всё читается из env/конфига с дефолтами. Проверка — grep-тестом +(`tests/test_no_host_hardcodes.py`) и ревью по реестру `02-trz.md` §3.1. +- **PASS:** + - блокеры A1–A4, A7 реестра закрыты: `grep -rn "git.mva154.duckdns.org\|/home/slin\|mva154.local" src/ watchdog/` не находит ни одного вхождения в исполняемом коде (докстринги/комментарии — допустимы); + - `src/plane_sync.py::notify_stage_change` строит ссылки Branch/PR из `settings.gitea_public_url` (fallback `gitea_url`) и `settings.gitea_owner`; + - env-словари акторов (`launcher` ×2, `self_deploy`, `post_deploy`) берут HOME и git-идентичность из `settings`; + - A5 (`_STAGING_PORT`) и A6 (`SELF_HOSTING_REPO`) либо конфигуризованы, либо явно обоснованы в ADR задачи как платформенные константы (решение архитектора зафиксировано); + - структурный тест `tests/test_no_host_hardcodes.py` существует, его allowlist пуст (или каждая запись обоснована комментарием), тест зелёный. +- **FAIL:** хотя бы один литерал `82.22.50.71` / `/home/slin` / `mva154` / `duckdns` в исполняемом коде `src/**`/`watchdog/**`; ИЛИ ссылка в Plane-комментарии всё ещё строится от захардкоженного `http://git.mva154.duckdns.org`; ИЛИ A5/A6 не конфигуризованы и не обоснованы в ADR; ИЛИ анти-регресс тест отсутствует/красный. + +--- + +## AC-2 — Без регресса: на текущем хосте поведение 1:1 + +**Условие:** дефолты всех новых параметров равны текущим боевым значениям; pytest зелёный. +- **PASS:** + - каждый новый `Settings`-ключ / compose-переменная / `ARG` / shell-default имеет дефолт, равный значению, зашитому до задачи (`/home/slin`, `claude-bot@mva154.local`-адреса, gid 999, uid 1000, порты 8500/8501, пути node/claude-code и т.д.); + - `docker compose config` без заданных переменных окружения резолвится в эквивалент текущей конфигурации (volumes/user/group_add/environment/command совпадают по значениям); + - значения существующих дефолтов `src/config.py` (реестр §3.1 E) не изменены; + - полный `pytest tests/ -q` зелёный; + - `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict ключи / схема БД — без изменений (диф не затрагивает их семантику). +- **FAIL:** хотя бы один дефолт отличается от текущего боевого значения; ИЛИ `docker compose config` при пустом окружении даёт иную эффективную конфигурацию; ИЛИ любой существующий тест красный; ИЛИ диф меняет машину стадий/реестр QG/схему БД. + +--- + +## AC-3 — Smoke-процедура задокументирована и воспроизводима + +**Условие:** существует документированная процедура «развёрнутый инстанс → тестовый проект + +задача → конвейер доехал» с явными PASS/FAIL-критериями; воспроизводимость подтверждена. +- **PASS:** + - в `docs/operations/` есть раздел/документ (предложение ТЗ: `REPLICATION.md`) с пошаговой smoke-процедурой: health-check инстанса → тестовый проект → тестовая задача → подтверждение продвижения конвейера (минимум: `analysis` отработала, артефакты `01–04` созданы; расширенный режим — до `done`); + - каждый шаг имеет явный ожидаемый результат (PASS/FAIL), итог — однозначный вердикт; + - процедура не требует переноса данных/секретов с боевого хоста (stateless); + - воспроизводимость подтверждена хотя бы одним прогоном на текущей инфре (staging-песочница 8501 / sandbox-проект) — результат зафиксирован в артефактах задачи (например, `13-test-report.md` / `15-staging-log.md`); + - если введена скрипт-обвязка (`scripts/…`) — она запускается (`--help`/dry-run без ошибок) и покрыта тестом из `04-test-plan.yaml`. +- **FAIL:** процедуры нет; ИЛИ шаги без явных критериев («посмотреть, что всё ок»); ИЛИ процедура требует копирования боевых данных/секретов; ИЛИ заявленный прогон не зафиксирован; ИЛИ скрипт-обвязка падает на запуске. + +--- + +## AC-4 — Доки (deployment-раздел) + CHANGELOG + +**Условие:** документация обновлена в том же PR (правило агентов №2; reviewer проверяет — №6). +- **PASS:** + - deployment-раздел (см. AC-3) дополнительно содержит: карту всех новых env-переменных (имя, назначение, дефолт), процедуру/чек-лист секретов (см. AC-5), границы «10-common vs Lite vs Bundled»; + - карта переменных окружения в `docs/operations/INFRA.md` дополнена новыми ключами; + - `CHANGELOG.md` содержит запись ORCH-101; + - `CLAUDE.md`/`README.md` обновлены, если фактический объём изменений того требует (новые операторские способности/ограничения). +- **FAIL:** новый env-ключ отсутствует в карте env; ИЛИ нет записи в `CHANGELOG.md`; ИЛИ deployment-раздел не покрывает секреты/smoke; ИЛИ README выдаёт решённое за открытое (правило №6). + +--- + +## AC-5 — Секреты: генерация новых, не копирование боевых + +**Условие:** механизм выпуска нового комплекта секретов на новом хосте существует и безопасен. +- **PASS:** + - webhook-секреты (`ORCH_PLANE_WEBHOOK_SECRET`, `ORCH_GITEA_WEBHOOK_SECRET`) генерируются криптослучайно (≥ 32 байт энтропии; повторный запуск даёт другие значения); + - механизм никогда не перезаписывает существующий `.env` молча; + - чек-лист перечисляет все внешние токены (`ORCH_PLANE_API_TOKEN`, `ORCH_PLANE_BOT_*`, `ORCH_GITEA_TOKEN`, `ORCH_TELEGRAM_BOT_TOKEN`) с указанием, где их выпустить и куда вписать; явно сказано «боевые секреты не копируются»; + - `.env.example` покрывает 100% ключей, обязательных для старта (включая новые из AC-1/AC-2), секретные значения — только плейсхолдеры; реальные секреты в гит не попадают (`.gitleaks`/security-гейт зелёный); + - `.env.staging.example` согласован (если затронут). +- **FAIL:** секрет генерируется детерминированно/слабо; ИЛИ запуск механизма затирает существующий `.env`; ИЛИ в `.env.example` отсутствует обязательный ключ; ИЛИ в гит закоммичено реальное секретное значение; ИЛИ процедура предписывает копирование боевого секрета. + +--- + +## AC-6 — Инфра-файлы параметризованы + +**Условие:** `docker-compose.yml`, `Dockerfile`, `scripts/orchestrator-deploy-hook.sh` не требуют +правки под новый хост — только переменные. +- **PASS:** + - реестр §3.1 B/C/D закрыт: пути `/home/slin/...`, gid `999`, uid `1000:1000`, node/claude-code-пути, ssh-user, staging-порт в `command:`, `REPO=` в deploy-hook — параметризованы (`${VAR:-default}` / `ARG` / `"${REPO:-…}"`) с дефолтами = текущим значениям; + - связка «uid/gid/HOME/маунты `.claude`+`.ssh`/`useradd`» меняется согласованной группой переменных (инвариант ORCH-040 сохранён, `group_add` docker-gid не удалён); + - структурный тест параметризации (TC-06/TC-12 из `04-test-plan.yaml`) зелёный. +- **FAIL:** хотя бы одно значение реестра B/C/D осталось непараметризованным; ИЛИ группа ORCH-040 рассогласована (HOME ≠ маунт-таргеты при дефолтах); ИЛИ `group_add` удалён; ИЛИ структурный тест красный. + +--- + +## AC-7 — Анти-регресс защита от возврата хардкодов + +**Условие:** возврат хост-хардкода в `src/**` ломает CI. +- **PASS:** `tests/test_no_host_hardcodes.py` существует; сканирует `src/**`+`watchdog/**` на централизованный список запрещённых литералов (минимум: `82.22.50.71`, `/home/slin`, `mva154`, `duckdns`); исключает комментарии/докстринги/`tests/**`/`docs/**`; детерминирован (повторные прогоны стабильны); демонстрационно ловит подсадку литерала (негативная самопроверка в самом тесте или в тестах теста). +- **FAIL:** тест отсутствует; ИЛИ не ловит подсаженный в код литерал из списка; ИЛИ флапает; ИЛИ список литералов размазан по нескольким местам без единой точки правки. + +--- + +## AC-8 — Self-hosting безопасность и инварианты соседних задач + +**Условие:** задача не дестабилизирует общий прод и не ослабляет зафиксированные инварианты. +- **PASS:** + - в рамках задачи прод-контейнер `orchestrator` не перезапускается; прод-деплой — только штатно (staging 8501 → ручной `Confirm Deploy`); + - инвариант ORCH-058 сохранён: freshness/staging-путь не может быть переконфигурирован в прод-таргет (guard «staging-порт ≠ прод-порт» при конфигуризации A5 — либо A5 остался константой по ADR); + - INV-4 сохранён (никаких push/force-push в `main` мимо PR-merge API); + - маркеры `ORCH-NNN` в правленых блоках сохранены/обновлены корректно (правило №9). +- **FAIL:** диф содержит рестарт/останов прод-контейнера вне штатного деплой-пути; ИЛИ конфигурацией можно нацелить staging-rebuild на прод-порт; ИЛИ нарушен INV-4; ИЛИ правка помеченного блока стёрла инвариант соседнего ADR. + +--- + +## Сводная матрица AC ↔ BR/FR + +| AC | Покрывает | +|----|-----------| +| AC-1 | BR-1 / FR-1, FR-2, FR-6 | +| AC-2 | BR-5 / FR-1 (реестр E), FR-2, FR-3, NFR-6 | +| AC-3 | BR-4 / FR-5 | +| AC-4 | BR-7 / FR-7 | +| AC-5 | BR-3 / FR-4, NFR-3 | +| AC-6 | BR-2 / FR-3, NFR-4 | +| AC-7 | BR-6 / FR-6, NFR-5 | +| AC-8 | NFR-1, NFR-2, NFR-4 / FR-3 | diff --git a/docs/work-items/ORCH-101/04-test-plan.yaml b/docs/work-items/ORCH-101/04-test-plan.yaml new file mode 100644 index 0000000..5bf9a95 --- /dev/null +++ b/docs/work-items/ORCH-101/04-test-plan.yaml @@ -0,0 +1,146 @@ +work_item: ORCH-101 +stage: analysis +author_agent: analyst +status: ready-for-review +created_at: 2026-06-10 +model_used: claude-opus-4-8 +title: "ORCH-10-common: расхардкод + секреты + smoke — план тестов" +framework: pytest +scope: > + Покрывается: расхардкод src/**+watchdog/** (чтение хост-значений из Settings), + параметризация docker-compose.yml/Dockerfile/deploy-hook (структурные проверки), + полнота .env.example, поведение генератора секретов, запускаемость smoke-обвязки, + анти-регресс grep-тест и его негативная самопроверка, zero-regression (полный + регресс tests/). Вне покрытия: реальный e2e-тираж на новом железе (заменён + воспроизводимым прогоном smoke-процедуры на staging-песочнице 8501 — AC-3), + задачи Lite/Bundled эпика ORCH-10, перенос данных (stateless по решению 10.06). +notes: > + Имена новых тест-модулей — предложение analyst; developer может переименовать, + сохранив покрытие TC. Дефолты всех новых параметров обязаны равняться текущим + боевым значениям (AC-2) — тесты фиксируют это явно. Анти-регресс тест судит + исполняемый код (комментарии/докстринги исключены) — образец структурных тестов: + tests/test_agent_prompts_canon.py. Полный регресс tests/ должен оставаться + зелёным; STAGE_TRANSITIONS/QG_CHECKS/check_*/machine-verdict не меняются — + новые тесты входят в существующие гейты (check_ci_green) автоматически. + +tests: + - id: TC-01 + type: unit + description: > + Анти-регресс скан: в исполняемом коде src/** и watchdog/** нет запрещённых + литералов (82.22.50.71, /home/slin, mva154, duckdns); комментарии/докстринги + исключаются; список литералов централизован; allowlist пуст либо каждая + запись обоснована (AC-1, AC-7 / FR-6). + module: tests/test_no_host_hardcodes.py + expected: PASS + + - id: TC-02 + type: unit + description: > + Негативная самопроверка сканера: подсаженный во временный/фиктивный модуль + запрещённый литерал детектируется (сканер реально ловит, не вечно-зелёный) + (AC-7 / FR-6, NFR-5). + module: tests/test_no_host_hardcodes.py + expected: PASS + + - id: TC-03 + type: unit + description: > + plane_sync.notify_stage_change строит ссылки Branch/PR из + settings.gitea_public_url (fallback gitea_url) + settings.gitea_owner; + литерал git.mva154.duckdns.org и owner admin из кода удалены; при + переопределённых настройках ссылка меняется соответственно (monkeypatch, + без сети) (AC-1 / FR-1 A1, FR-2). + module: tests/test_host_config_keys.py + expected: PASS + + - id: TC-04 + type: unit + description: > + Env агент-процесса в agents/launcher (оба места запуска): HOME и + GIT_AUTHOR/COMMITTER_NAME/EMAIL берутся из settings; дефолты равны прежним + значениям (/home/slin, claude-bot@mva154.local); переопределение env-ключей + меняет словарь окружения (AC-1, AC-2 / FR-1 A2, FR-2). + module: tests/test_host_config_keys.py + expected: PASS + + - id: TC-05 + type: unit + description: > + Env self_deploy (deploy-finalizer) и post_deploy (monitor): HOME и + git-идентичность из settings, дефолты = прежним значениям; литералы + /home/slin и *@mva154.local из кода удалены (AC-1, AC-2 / FR-1 A3–A4, FR-2). + module: tests/test_host_config_keys.py + expected: PASS + + - id: TC-06 + type: unit + description: > + Структурная проверка docker-compose.yml: хост-значения реестра ТЗ §3.1 B + (пути /home/slin/*, node/claude-code-пути, gid 999, uid 1000:1000, ssh-user, + staging-порт в command) выражены интерполяцией ${VAR:-default}, и дефолты + равны текущим значениям; group_add docker-gid присутствует во всех трёх + сервисах (инвариант ORCH-040 «МИНА 1») (AC-2, AC-6 / FR-3). + module: tests/test_infra_parametrization.py + expected: PASS + + - id: TC-07 + type: unit + description: > + Структурная проверка Dockerfile: uid/gid/username/home параметризованы через + ARG с дефолтами 1000/1000/slin//home/slin; решение по CMD-порту соответствует + ADR задачи (ARG либо обоснованный дефолт 8500) (AC-2, AC-6 / FR-3, §3.4 А-3). + module: tests/test_infra_parametrization.py + expected: PASS + + - id: TC-08 + type: unit + description: > + Полнота .env.example: каждый обязательный для старта ключ присутствует, + включая ВСЕ новые ключи FR-2/FR-3 (сверка с Settings.model_fields по + выбранному архитектором контракту); секретные ключи содержат только + плейсхолдеры, не реальные значения; deploy-hook принимает REPO через + env-override (AC-5, AC-6 / FR-1 D1, FR-4.4). + module: tests/test_infra_parametrization.py + expected: PASS + + - id: TC-09 + type: unit + description: > + Генератор секретов: два запуска дают различные значения; длина/энтропия + webhook-секретов >= 32 байт; существующий .env никогда не перезаписывается + молча (запуск при существующем файле -> отказ/явное подтверждение); + вывод согласован с ключами .env.example (AC-5 / FR-4, NFR-3). + module: tests/test_secrets_gen.py + expected: PASS + + - id: TC-10 + type: integration + description: > + Smoke-обвязка и процедура: deployment-док с пошаговой smoke-процедурой + существует и содержит явные PASS/FAIL-критерии каждого шага; если введён + скрипт — он запускается без ошибок в безопасном режиме (--help/dry-run, без + сети/LLM-расходов); карта env в INFRA.md дополнена; CHANGELOG.md содержит + запись ORCH-101 (AC-3, AC-4 / FR-5, FR-7). + module: tests/test_replication_smoke.py + expected: PASS + + - id: TC-11 + type: unit + description: > + Инвариант ORCH-058 при исходе А-1: если staging-порт стал конфигуром — + дефолт 8501, guard «staging-порт != прод-порт» отвергает совпадение + (freshness-путь невозможно нацелить на прод); если остался константой — + тест фиксирует константу 8501 и наличие обоснования в ADR задачи + (AC-8 / FR-1 A5, NFR-4). + module: tests/test_host_config_keys.py + expected: PASS + + - id: TC-12 + type: integration + description: > + Полный регресс: pytest tests/ -q зелёный на дефолтной конфигурации (пустой + env) — поведение платформы на текущем хосте 1:1; существующие тесты не + правились под задачу (кроме согласованных структурных) (AC-2 / BR-5, NFR-6). + module: tests/ + expected: PASS