[DTP-Worker] Sprint 8 · buffer · DevOps/CI : gate d'IDENTITÉ des identifiants d'infrastructure VPS (IP 153.75.250.214 + conteneurs Docker erpnext-backend-1/erpnext-frontend-1) — recomputés de CLAUDE.md §VPS · direction copie→source sur toute doc .md du mandat (hors journaux) · enveloppe conteneur DÉRIVÉE des stems service (backend/frontend) et non du préfixe compose ⇒ un renommage erpnext-*→oto-* mord · migration serveur cascade sur les 10 copies

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-08-01 15:15:24 +00:00
parent 9963279879
commit 290bd2ec02
3 changed files with 185 additions and 1 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
+110
View File
@@ -5802,6 +5802,116 @@ if _ecf_ev is not None:
good(f"Fiscal e-CF évènements · FormaPago défaut = {_fp_code} "
f"({_lbl}) · Cardnet #10 == ecf_plan")
# ============================================================================
# CLAUDE.md §VPS · IDENTITÉ des IDENTIFIANTS D'INFRASTRUCTURE (IP + conteneurs
# Docker) — même classe de « vert trompeur » que l'endpoint RunPod, côté serveur.
# ----------------------------------------------------------------------------
# CLAUDE.md §VPS FIXE l'infrastructure de production du mandat :
# • l'IP du VPS `153.75.250.214`
# • les DEUX conteneurs Docker `erpnext-backend-1` / `erpnext-frontend-1`
# Ces identifiants sont RECOPIÉS À LA MAIN dans quantité de docs — README.md,
# le master prompt, AGENTS_EXISTING_ASSETS.md, la fiche `mobile` (backend cible de
# l'API), RBAC_50_ROLES_SPEC.md (run-book d'import), tests/README.md (base URL
# Playwright), ci/README.md (instance Gitea) — sans AUCUN ancrage vers CLAUDE.md.
# Aucun gate ne couvrait ce lien : check_docs ne voit qu'un lien Markdown,
# guard_constraints qu'un terme interdit, check_artifacts qu'un out/ — une IP ni un
# nom de conteneur n'est aucun des trois. Or une MIGRATION serveur (nouvelle IP) ou
# un RENOMMAGE du projet compose (`erpnext_*` → `oto_*`) est un évènement ops
# COURANT : CLAUDE.md §VPS se met à jour, les ~10 copies se PÉRIMENT en silence →
# un agent câblerait l'API mobile / les tests e2e / le hand-off d'import vers un
# serveur MORT ou un conteneur FANTÔME (l'invention même que #6 interdit). Aucune
# suite tests/ (FONCTIONS des modules, jamais la prose infra) ne l'attrape — classe
# notée en mémoire (`agent-fiche-role-attrs-ungated`, surface infra ~ RunPod).
#
# On RE-DÉRIVE l'IP + l'ENSEMBLE des conteneurs de CLAUDE.md §VPS (jamais une liste
# à la main · #6). Direction copie→source : sur TOUTE doc .md du mandat (hors
# CLAUDE.md = la source, hors 05_activity_log/ + daily_reports/ = journaux
# historiques figés, à ne pas réécrire), tout token IPv4 DOIT == l'IP canonique et
# tout token conteneur `erpnext-<x>-1` DOIT ∈ l'ensemble canonique. Le corpus ne
# porte QUE ces valeurs (aucune autre IPv4, aucun autre conteneur — vérifié) : un
# écart = une copie périmée. Le write-path interdit `/var/www/html/static` relève
# de guard_constraints (#interdits), non de ce gate d'identité. Garde
# anti-évaporation : l'IP ET ≥1 conteneur canonique doivent rester cités quelque
# part (sinon la surface d'identité du VPS a silencieusement disparu · #6).
# ============================================================================
VPS_SRC = "CLAUDE.md"
try:
_vps_cm = open(VPS_SRC, encoding="utf-8").read()
except OSError as e:
bad(f"VPS infra · {VPS_SRC} illisible : {e}"); _vps_cm = None
if _vps_cm is not None:
_vps_sec = re.search(r"^## VPS\s*\n(.*?)(?=^## |\Z)", _vps_cm, re.S | re.M)
if _vps_sec is None:
bad(f"VPS infra · section « ## VPS » INTROUVABLE dans {VPS_SRC} → ancre "
f"d'infra cassée, impossible de recomputer IP/conteneurs")
else:
_vps_body = _vps_sec.group(1)
_ip_m = re.search(r"\bIP\s+(\d{1,3}(?:\.\d{1,3}){3})\b", _vps_body)
_dk_m = re.search(r"Docker\s*\(([^)]+)\)", _vps_body)
if _ip_m is None or _dk_m is None:
bad(f"VPS infra · §VPS ne livre pas l'IP et/ou les conteneurs Docker "
f"(IP={_ip_m and _ip_m.group(1)} · Docker={_dk_m and _dk_m.group(1)}) "
f"→ format de §VPS modifié, recompute impossible")
else:
vps_ip = _ip_m.group(1)
vps_containers = {c.strip() for c in _dk_m.group(1).split(",")
if re.fullmatch(r"[a-z0-9]+-[a-z]+-1", c.strip())}
# Enveloppe de forme DÉRIVÉE des stems de service canoniques
# (backend/frontend), PAS du préfixe compose littéral `erpnext-` : un
# RENOMMAGE du projet compose (`erpnext-backend-1` → `oto-backend-1`)
# dans une copie DOIT être vu comme fantôme — l'ancrer sur `erpnext-`
# le rendrait invisible (le token renommé ne matcherait plus).
_stems = sorted({m.group(1) for c in vps_containers
if (m := re.fullmatch(r"[a-z0-9]+-([a-z]+)-1", c))})
if not vps_containers or not _stems:
bad(f"VPS infra · §VPS « Docker (…) » ne contient aucun conteneur de "
f"forme `<projet>-<service>-1` (lu {_dk_m.group(1)!r}) → format modifié")
else:
_cont_shape = re.compile(r"[a-z0-9]+-(?:" + "|".join(_stems) + r")-1")
_ip_shape = re.compile(r"\b\d{1,3}(?:\.\d{1,3}){3}\b")
vps_files = [f for f in subprocess.run(
["git", "ls-files", "*.md"],
capture_output=True, text=True).stdout.split()
if f != "CLAUDE.md"
and not f.startswith("05_activity_log/")
and not f.startswith(f"{D}/daily_reports/")]
ip_seen = cont_seen = 0
for vf in vps_files:
try:
vtxt = open(vf, encoding="utf-8").read()
except OSError as e:
bad(f"VPS infra · {vf} illisible : {e}"); continue
# (a) IPv4 : toute IP citée == IP canonique du §VPS
ips = set(_ip_shape.findall(vtxt))
if ips:
ip_seen += 1
stale_ip = sorted(ips - {vps_ip})
if stale_ip:
bad(f"VPS infra · {vf} cite l'IP périmée {stale_ip} MAIS "
f"CLAUDE.md §VPS dit `{vps_ip}` → serveur migré / IP "
f"mal recopiée (mettre la doc à jour)")
else:
good(f"VPS infra · {vf} · IP `{vps_ip}` == §VPS")
# (b) conteneurs Docker : tout `erpnext-<x>-1` ∈ ensemble canonique
conts = set(_cont_shape.findall(vtxt))
if conts:
cont_seen += 1
phantom = sorted(conts - vps_containers)
if phantom:
bad(f"VPS infra · {vf} cite le(s) conteneur(s) fantôme(s) "
f"{phantom} MAIS §VPS dit {sorted(vps_containers)} → "
f"projet compose renommé / nom mal recopié")
else:
good(f"VPS infra · {vf} · conteneur(s) {sorted(conts)} "
f"⊆ §VPS {sorted(vps_containers)}")
if ip_seen == 0:
bad(f"VPS infra · AUCUNE doc du mandat ne cite l'IP `{vps_ip}` — la "
f"surface d'identité du VPS s'est évaporée (régression #6)")
if cont_seen == 0:
bad(f"VPS infra · AUCUNE doc ne cite de conteneur Docker "
f"`erpnext-<x>-1` — surface d'identité conteneur évaporée (#6)")
sys.exit(1 if FAIL else 0)
PY
rc=$?