[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
+58
View File
@@ -2606,3 +2606,61 @@ verrouillés) · `guard_constraints` · `validate_json`.
`validate_json`).
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo).
- **Auto-score 4Big** : 96/100.
## 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)
- **Constat + DÉFAUT RÉEL capté** : le README `devops/deploy_runbook` porte la table
« ## Plan de phases généré » (README:45-53) — les **7 phases ordonnées** que
l'agent DevOps suit phase par phase au portage VPS. Ses colonnes d'identité sont
data-derived de `out/deploy_runbook.json` : `#`=`order`, `Responsable`=`responsable`
(`worker`/`vps`/`worker+vps`), **Modules**=l'ensemble des modules gated déployés,
`Dépend de`=les n°s d'ordre des `depends_on`. Le bloc DevOps existant de
`check_readme_claims.sh` ne gate QUE **deux comptes agrégés** (phases ×2 dans la
fiche `devops` · confirmations dans le README) — ces quatre colonnes étaient un
**WILDCARD**. Contrairement aux surfaces buffer précédentes (« aucune valeur
périmée »), ici la table AVAIT **DÉRIVÉ** : elle **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** (`counts.modules
== modules_mapped == 22`). Vert trompeur caractéristique : l'agent DevOps suivant la
table aurait **sauté 2 modules** au déploiement.
- **Data-derived** : `out/deploy_runbook.json` (7 phases · `order`/`id`/`responsable`/
`depends_on`/`modules[]`) et sa source `deploy_spec.json.module_phase` (22 modules,
autorité byte-gatée par `check_artifacts` · couverture bijective prouvée vs jobs CI).
`check_artifacts` ne prouve QUE `deploy_runbook==build` — l'identité des LIGNES de la
table (qui déploie quoi · dans quel ordre · après quoi) restait non gatée.
- **Preuve reproduite** : retirer `mobile/app_config` de la phase 5 du README →
`check_readme_claims` **exit 0** avant le gate (le bloc ne voyait que phases=7 &
confirmations=7). Un responsable périmé (phase VPS attribuée au worker) ou une
dépendance périmée (Frontend avant RBAC) est un hazard réel qu'aucune suite `tests/`
(qui teste des fonctions, pas la table commitée) n'attrape.
- **Correction d'abord (artefact = autorité · #6)** : la table étant STALE, README
corrigé pour matcher l'artefact byte-gaté — phase 5 `+mobile/app_config` (& libellé
« + config mobile »), phase 7 `+qa/acceptance`. Consommateur régénéré : le
`qa/audit_4big/out/quality_report.json` enregistre la taille en octets de chaque
README en `evidence` (5414→5468 pour deploy_runbook) — rebuild `audit_4big` (score/
verdict/totaux INCHANGÉS, seule l'évidence-taille bouge · reproducibility gate).
- **Gate ajouté** (`ci/check_readme_claims.sh`, sous-bloc « 2bis) deploy_runbook —
TABLE Plan de phases ») : (1) **IDENTITÉ par phase** — chaque ligne porte EXACTEMENT
`#`=`order` + `Responsable`=`responsable` + **Modules** (ensemble de code-spans) ==
modules de la phase + `Dépend de`=n°s d'ordre des `depends_on` (deps extraits par
digits ⇒ robuste au séparateur) ; colonne « Phase » libre (paraphrase). (2)
**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 — le défaut même capté). Cohérences croisées
(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). Un claim absent échoue AUSSI.
- **8 morsures vérifiées** : README phase5 omet `mobile/app_config` (défaut original,
`manquants=[mobile/app_config]`) · README phase7 omet `qa/acceptance` (défaut
original) · README phase5 responsable `worker+vps`→`vps` (câblage) · README phase6
`Dépend de 4,5`→`4` (dépendance manquante) · module FANTÔME `ghost/mod` en phase5
(`en_trop`) · ligne FANTÔME `#8` (8≠7) · phase 6 SUPPRIMÉE (INTROUVABLE + 6≠7) ·
artefact phase2 responsable `vps`→`worker` (README stale). Restauré = green :
7 lignes == 7 phases · union Modules 22==22 · responsables/deps/modules == artefact ·
ordres 1..7 contigus · exit 0.
- **État courant** : le défaut STALE est **corrigé** (README byte-aligné sur
l'artefact) ET la surface est désormais **gatée**. `ci/README.md` (ligne synthèse +
détail « 2ᵉ surface `devops/deploy_runbook` ») mis à jour · **7 gates verts**
(`check_readme_claims` · `check_artifacts` · `check_docs` · `check_ci_integrity` ·
`check_regression` · `guard_constraints` · `validate_json`).
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo).
- **Auto-score 4Big** : 96/100.
@@ -48,9 +48,9 @@ chiffrée dans le catalogue de confirmations.
| 2 | vps | DocTypes custom | `crm/dossier_vente`, `legal/confotur` | 1 |
| 3 | worker+vps | RBAC (Role + DocPerm + UP + Role Profile) | `rbac/fixtures_gen`, `rbac/userperm_gen`, `rbac/roleprofile_gen`, `rbac/apply_plan` | 2 |
| 4 | worker+vps | Workflow vente + règles métier | `crm/workflow_vente`, `crm/commissions`, `fiscal/ecf_dgii` | 2, 3 |
| 5 | worker+vps | Frontend desk (Workspaces + chat OTOIA) | `frontend/portails`, `frontend/chat_otoia` | 3 |
| 5 | worker+vps | Frontend desk (Workspaces + chat OTOIA) + config mobile | `frontend/portails`, `frontend/chat_otoia`, `mobile/app_config` | 3 |
| 6 | worker+vps | Contenu & publication | `publiciste`, `faisabilite/generator`, `faisabilite/bancable`, `seo`, `demo/scenarios` | 4, 5 |
| 7 | vps | Vérification QA post-déploiement | `qa/audit_5d`, `qa/audit_4big`, `qa/regression` | 4, 5, 6 |
| 7 | vps | Vérification QA post-déploiement | `qa/acceptance`, `qa/audit_5d`, `qa/audit_4big`, `qa/regression` | 4, 5, 6 |
**Confirmations préalables VPS** (consolidées, sourcées) : `custom_modules`,
`custom_doctypes`, `taux_commission` (Direction · audit_5d D1.1), `rnc_emisor`
@@ -816,7 +816,7 @@
"criterion": "DOC",
"weight": 20,
"passed": true,
"evidence": "README.md (5414 octets)"
"evidence": "README.md (5468 octets)"
},
{
"criterion": "CONTRAT",
+19 -1
View File
File diff suppressed because one or more lines are too long
+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