Files
orchestrator/tests/test_replication_smoke.py
claude-bot f1635ddb39
All checks were successful
CI / test (push) Successful in 57s
CI / test (pull_request) Successful in 55s
feat(replication): расхардкод хоста + секреты нового хоста + smoke-runbook
Фундамент тиража 10-common (эпик ORCH-10): платформа разворачивается на
новой инфре без правки кода — только env/конфиг. Каждый дефолт = боевому
значению (пустой .env => поведение 1:1, kill-switch-природа, NFR-2);
STAGE_TRANSITIONS/QG_CHECKS/check_*/machine-verdict/схема БД не тронуты.

- config: agent_home_dir / agent_git_name / git_email_domain / staging_port
  (ADR-001 D2/D4); код-блокеры A1-A4 закрыты: plane_sync ссылки из
  gitea_public_url+gitea_owner, launcher - единый agent_git_env() (x2 места),
  self_deploy/post_deploy - HOME+домен из Settings (имена системных акторов -
  платформенные литералы)
- image_freshness: staging_port из конфига + fail-closed guard
  staging_port == прод-порт -> отказ ДО ssh/build (инвариант ORCH-058 AC-9
  стал исполняемым); REPO= передаётся хуку явно обоими инвокерами (D7)
- SELF_HOSTING_REPO - нормативная платформенная константа (D3, пин-тест)
- compose: полная ${VAR:-default}-интерполяция (реестр B, карта D6); группа
  ORCH-040 uid/gid/HOME/маунты двигается согласованно (build.args APP_*);
  group_add "МИНА 1" сохранён x3; оба app-сервиса с явным command:
- Dockerfile: ARG APP_UID/APP_GID/APP_USER/APP_HOME (CMD exec-form 8500
  сознательно не тронут - D5); deploy-hook: REPO="${REPO:-...}" (D1 реестра)
- секреты: stdlib scripts/gen_secrets.py (token_hex(32); печать по умолчанию;
  --write никогда не перезаписывает существующий .env молча, exit=2;
  перезапись только --force); .env.example дополнен до полноты ключей старта
- доки: новый docs/operations/REPLICATION.md (карта env, чек-лист секретов,
  smoke-процедура с PASS/FAIL, границы 10-common/Lite/Bundled), INFRA.md,
  README, CLAUDE.md, CHANGELOG
- анти-регресс: tests/test_no_host_hardcodes.py (tokenize-сканер запрещённых
  литералов, config-модули - структурное исключение, allowlist пуст,
  негативная самопроверка) + test_host_config_keys / test_infra_parametrization
  / test_secrets_gen / test_replication_smoke; согласованные структурные
  правки test_orch040_compose (судит резолв дефолтов) и
  test_deploy_hook_rollback_sim (REPO через env-override = контракт D7)

Полный регресс: 1764 passed.

Refs: ORCH-101

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 20:50:43 +03:00

100 lines
4.7 KiB
Python
Raw Permalink 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.
"""ORCH-101 (TC-10, AC-3/AC-4): репликационная документация и smoke-процедура.
Структурные проверки: deployment-док `docs/operations/REPLICATION.md`
существует и несёт пошаговую smoke-процедуру с явными PASS/FAIL-критериями,
карту env-переменных, чек-лист секретов и границы 10-common vs Lite vs
Bundled; карта env в INFRA.md дополнена; CHANGELOG.md содержит запись
ORCH-101; гайд stateless (не предписывает перенос боевых данных/секретов).
Скрипт-обвязка ORCH-101 не вводилась (ADR-001 D9 — чистый runbook), поэтому
запускаемость проверяется только для генератора секретов (--help, без
сети/LLM).
"""
import subprocess
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
REPLICATION = REPO_ROOT / "docs/operations/REPLICATION.md"
INFRA = REPO_ROOT / "docs/operations/INFRA.md"
CHANGELOG = REPO_ROOT / "CHANGELOG.md"
def _replication_text() -> str:
assert REPLICATION.is_file(), "docs/operations/REPLICATION.md отсутствует (AC-3)"
return REPLICATION.read_text(encoding="utf-8")
def test_replication_doc_has_smoke_procedure_with_pass_fail():
text = _replication_text()
# Каждый шаг smoke имеет явный критерий; маркеры вердикта присутствуют.
assert "PASS" in text and "FAIL" in text
# Ключевые кирпичи процедуры (D9): health-check, onboarding-CLI, очередь.
for brick in ("/health", "/queue", "onboard_project.py", "docker compose config"):
assert brick in text, f"smoke-кирпич {brick} не упомянут в REPLICATION.md"
# Минимальный сигнал «конвейер доехал»: стадия analysis + артефакты 0104.
assert "analysis" in text
assert "01" in text and "04" in text
def test_replication_doc_covers_secrets_checklist():
text = _replication_text()
assert "gen_secrets.py" in text
for token in (
"ORCH_PLANE_WEBHOOK_SECRET",
"ORCH_GITEA_WEBHOOK_SECRET",
"ORCH_PLANE_API_TOKEN",
"ORCH_GITEA_TOKEN",
"ORCH_TELEGRAM_BOT_TOKEN",
):
assert token in text, f"секрет {token} не покрыт чек-листом"
# Нормативная строка stateless-тиража (AC-5): боевые секреты не копируются.
assert "не копиру" in text.lower()
def test_replication_doc_has_env_map_and_boundaries():
text = _replication_text()
for var in (
"ORCH_AGENT_HOME_DIR",
"ORCH_AGENT_GIT_NAME",
"ORCH_GIT_EMAIL_DOMAIN",
"ORCH_STAGING_PORT",
"ORCH_DOCKER_GID",
"ORCH_RUN_UID",
"ORCH_HOST_REPOS_DIR",
):
assert var in text, f"переменная {var} отсутствует в карте env REPLICATION.md"
# Границы тиража (анти-скоуп-крип Р-5) + платформенные конвенции (D3/D4).
assert "Lite" in text and "Bundled" in text
assert "orchestrator" in text # конвенция имени репо платформы
# Чек-лист обязывает задать реестр проектов на новом хосте (A9).
assert "ORCH_PROJECTS_JSON" in text
def test_replication_doc_is_stateless():
text = _replication_text().lower()
# Процедура не предписывает перенос БД/данных с боевого хоста.
assert "перенос" not in text or "не предполагает перенос" in text or "без перенос" in text
def test_infra_env_map_extended():
text = INFRA.read_text(encoding="utf-8")
for var in ("ORCH_AGENT_HOME_DIR", "ORCH_STAGING_PORT", "ORCH_DOCKER_GID"):
assert var in text, f"{var} отсутствует в карте env INFRA.md (AC-4)"
assert "REPLICATION.md" in text # перекрёстная ссылка на deployment-док
def test_changelog_has_orch_101_entry():
text = CHANGELOG.read_text(encoding="utf-8")
assert "ORCH-101" in text
def test_gen_secrets_runs_in_safe_mode():
"""Обвязка запускается без ошибок в безопасном режиме (--help: без сети,
без LLM, без записи файлов)."""
r = subprocess.run(
[sys.executable, str(REPO_ROOT / "scripts/gen_secrets.py"), "--help"],
capture_output=True, text=True, timeout=30,
)
assert r.returncode == 0, r.stderr