[DTP-Worker] Sprint 8 · buffer · DevOps/deploy_runbook (2e surface) : la TABLE « Plan de phases généré » — le GRAPHE de portage VPS ordonné (# · Responsable · Modules · Dépend de) — était un WILDCARD et avait DÉRIVÉ : elle omettait mobile/app_config (phase 5) ET qa/acceptance (phase 7), 20/22 modules listés pendant que la couverture prouvée était bijective 22/22. README corrigé sur l'artefact byte-gaté + gate d'identité par phase (8 morsures).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-07-31 23:40:44 +00:00
parent 082b057639
commit e5f6459e85
5 changed files with 176 additions and 4 deletions
+96
View File
@@ -1560,6 +1560,102 @@ if dr_ph is not None:
bad(f"DevOps · {DR_RD} — énumération {listed} ≠ confirmations_ouvertes "
f"{sorted(dr_open)} → régénérer le README")
# 2bis) deploy_runbook — IDENTITÉ de la TABLE « Plan de phases généré » -----------
# Le bloc ci-dessus ne gate que DEUX comptes agrégés (phases · confirmations). La
# table « ## Plan de phases généré » énumère, PAR phase, QUATRE données data-derived
# de out/deploy_runbook.json (byte-gaté par check_artifacts) : le n° d'ordre (#), le
# RESPONSABLE (worker/vps/worker+vps), l'ensemble des MODULES gated déployés dans la
# phase, et « Dépend de » (les n°s d'ordre des `depends_on`). C'est le GRAPHE DE
# PORTAGE VPS ORDONNÉ que l'agent DevOps suit phase par phase — un module OUBLIÉ de
# la colonne Modules (donc jamais déployé), un responsable ou une dépendance périmés
# (ex. Frontend avant RBAC) sont un hazard réel, invisible à check_artifacts (qui ne
# prouve QUE deploy_runbook==build) ET au bloc de comptes (aveugle à l'identité des
# lignes ; ces colonnes étaient un WILDCARD). Même classe que la table run-book de
# rbac/apply_plan et la table demo/scenarios. Preuve reproduite : la table omettait
# `mobile/app_config` (phase 5) ET `qa/acceptance` (phase 7) — 20 modules listés sur
# 22 — pendant que le MANIFEST prouvait la couverture BIJECTIVE 22/22 vs CI : vert
# trompeur, l'agent DevOps aurait sauté 2 modules. On RECOMPUTE #/responsable/
# modules/deps depuis deploy_runbook.json (zéro duplication) et on exige que la table
# les énonce EXACTEMENT. Identité d'ensemble : {ordres des lignes} == {ordres de
# l'artefact} (aucune phase FANTÔME/MANQUANTE) ET l'UNION des cellules Modules == les
# 22 modules de l'artefact (aucun module OUBLIÉ/en trop). La colonne « Phase » reste
# libre (paraphrase). Cohérences croisées en bonus (mordent un plan INTERNEMENT
# incohérent) : ordres contigus 1..N sans doublon · responsable ∈ {worker, vps,
# worker+vps} · toute dépendance pointe en ARRIÈRE (n° < n° de la phase ⇒ pas de
# cycle/renvoi avant).
try:
dr_plan = load("devops/deploy_runbook/out/deploy_runbook.json")
except (OSError, KeyError, ValueError) as e:
bad(f"DevOps · run-book illisible : {e}"); dr_plan = None
if dr_plan is not None:
id2order = {p["id"]: p["order"] for p in dr_plan}
ALLOWED_RESP = {"worker", "vps", "worker+vps"}
orders = sorted(p["order"] for p in dr_plan)
all_modules = sorted(x["module"] for p in dr_plan for x in p["modules"])
if orders == list(range(1, len(dr_plan) + 1)):
good(f"DevOps · run-book — ordres contigus 1..{len(dr_plan)} (aucun trou/doublon)")
else:
bad(f"DevOps · run-book — ordres NON contigus {orders} "
f"(attendu 1..{len(dr_plan)}) → plan incohérent")
# Lignes lues en RAW : la table « Plan de phases généré » est la SEULE à 5
# colonnes dont la 1ʳᵉ est numérique (l'autre table est `Fichier | Rôle`).
try:
dr_tbl = open(DR_RD, encoding="utf-8").read().splitlines()
except OSError as e:
bad(f"DevOps · {DR_RD} illisible : {e}"); dr_tbl = []
table_orders, rows_by_order = [], {}
for ln in dr_tbl:
cells = [c.strip() for c in ln.strip().strip("|").split("|")]
if len(cells) == 5 and re.fullmatch(r"\d+", cells[0]):
o = int(cells[0]); table_orders.append(o); rows_by_order[o] = cells
if sorted(table_orders) == orders and len(table_orders) == len(set(table_orders)):
good(f"DevOps · run-book — {len(table_orders)} lignes == {len(dr_plan)} phases "
f"(aucune FANTÔME/MANQUANTE)")
else:
bad(f"DevOps · run-book — lignes table {sorted(table_orders)} ≠ phases artefact "
f"{orders} (ligne en trop/manquante ou doublon)")
table_modules = sorted(m for r in rows_by_order.values()
for m in re.findall(r"`([^`]+)`", r[3]))
if table_modules == all_modules:
good(f"DevOps · run-book — union des Modules ({len(table_modules)}) == "
f"{len(all_modules)} modules de l'artefact (aucun OUBLIÉ/en trop)")
else:
miss = sorted(set(all_modules) - set(table_modules))
extra = sorted(set(table_modules) - set(all_modules))
bad(f"DevOps · run-book — colonne Modules ≠ artefact : manquants={miss or '—'} "
f"en_trop={extra or '—'} → régénérer le README")
for p in sorted(dr_plan, key=lambda x: x["order"]):
o, resp = p["order"], p["responsable"]
deps = sorted(id2order[x] for x in p["depends_on"])
mods = sorted(x["module"] for x in p["modules"])
if resp not in ALLOWED_RESP:
bad(f"DevOps · phase {o} — responsable artefact « {resp} » "
f"hors {sorted(ALLOWED_RESP)} (plan incohérent)")
if any(d >= o for d in deps):
bad(f"DevOps · phase {o} — dépendance en AVANT {deps} (≥ {o}) : graphe incohérent")
row = rows_by_order.get(o)
if row is None:
bad(f"DevOps · phase {o} — ligne INTROUVABLE dans {DR_RD} "
f"(attendu responsable={resp} modules={mods} deps={deps or '—'})"); continue
got_resp = row[1]
got_mods = sorted(re.findall(r"`([^`]+)`", row[3]))
got_deps = sorted(int(x) for x in re.findall(r"\d+", row[4]))
if got_resp == resp:
good(f"DevOps · phase {o} — responsable « {got_resp} » == artefact")
else:
bad(f"DevOps · phase {o} — {DR_RD} dit responsable « {got_resp} » MAIS "
f"artefact dit « {resp} » → régénérer le README")
if got_mods == mods:
good(f"DevOps · phase {o} — Modules ({len(got_mods)}) == artefact")
else:
bad(f"DevOps · phase {o} — {DR_RD} Modules {got_mods} MAIS artefact dit "
f"{mods} → régénérer le README")
if got_deps == deps:
good(f"DevOps · phase {o} — Dépend de {got_deps or '—'} == artefact")
else:
bad(f"DevOps · phase {o} — {DR_RD} « Dépend de » = {got_deps or '—'} MAIS "
f"artefact dit {deps or '—'} → régénérer le README")
# ============================================================================
# Démo · run-sheet de pitch (nombre de modules cités) — même classe de « vert
# trompeur », transcrit À LA MAIN dans le diagramme d'architecture du README du