feat(replication): расхардкод хоста + секреты нового хоста + smoke-runbook
All checks were successful
CI / test (push) Successful in 57s
CI / test (pull_request) Successful in 55s

Фундамент тиража 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>
This commit is contained in:
2026-06-10 20:50:43 +03:00
parent 26bdd783d6
commit f1635ddb39
26 changed files with 1583 additions and 86 deletions

View File

@@ -245,7 +245,13 @@ def build_deploy_command(repo: str, work_item_id: str | None, branch: str) -> li
result_sentinel = os.path.join(host_dir, RESULT)
hook_log = os.path.join(host_dir, "hook.log")
# ORCH-101 (D7): REPO is passed EXPLICITLY (same source the `cd` below uses)
# so the hook's env-override actually works on a parametrised host; the
# hook's own default only serves manual operator runs. The exit-code
# contract of the hook (0/1/2, ORCH-036) is untouched — this is one
# additional env assignment in the prefix.
env_assignments = (
f"REPO={shlex.quote(settings.deploy_host_repo_path)} "
f"SOURCE_IMAGE={shlex.quote(settings.deploy_prod_source_image)} "
f"TARGET_SERVICE={shlex.quote(settings.deploy_prod_target_service)} "
f"TARGET_PORT={int(settings.deploy_prod_target_port)} "
@@ -327,13 +333,17 @@ def write_deploy_log(repo: str, work_item_id: str, branch: str, exit_code, statu
return False
# Best-effort commit + push (the gate also falls back to origin/main).
# ORCH-101 (A3): HOME + email domain from Settings; the actor NAME stays the
# platform literal `deploy-finalizer` (D2 — distinguishable system-actor
# commits, not host-specific). Defaults = the previous hardcoded values.
_email = f"deploy-finalizer@{settings.git_email_domain}"
git_env = {
**os.environ,
"HOME": "/home/slin",
"HOME": settings.agent_home_dir,
"GIT_AUTHOR_NAME": "deploy-finalizer",
"GIT_AUTHOR_EMAIL": "deploy-finalizer@mva154.local",
"GIT_AUTHOR_EMAIL": _email,
"GIT_COMMITTER_NAME": "deploy-finalizer",
"GIT_COMMITTER_EMAIL": "deploy-finalizer@mva154.local",
"GIT_COMMITTER_EMAIL": _email,
}
try:
subprocess.run(["git", "-C", wt, "add", rel],