diff --git a/05_activity_log/2026-07-31.md b/05_activity_log/2026-07-31.md index 3ef7cc2..3704161 100644 --- a/05_activity_log/2026-07-31.md +++ b/05_activity_log/2026-07-31.md @@ -1,5 +1,67 @@ # Activity Log · 2026-07-31 · Claude Code DTP +## Session `20260731_113048` · Buffer S8 · Domaine DevOps : le run-book de déploiement VPS (`devops/deploy_runbook`) affichait « **7 phases** » (fiche agent devops, ×2) et « **7 confirmations** préalables » (README du module) — deux nombres data-derived transcrits À LA MAIN dans DEUX docs sans AUCUN gate + +**Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA régression). Roadmap +fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Les +sessions précédentes ont gaté les domaines tests, portails, RBAC (3 volets), SEO, +CRM (pipeline vente), mobile, Legal (CONFOTUR) et QA (audit 5D). Cette session cible +le **domaine DevOps** — le **run-book de déploiement VPS**, dont deux comptes +data-derived cités dans deux docs pointaient l'artefact généré sans être recomputés +par aucun gate. + +### Défaut trouvé — comptes ungated, transcrits dans DEUX docs + +- **`03_agents/devops/AGENT.md:25`** : « graphe de **7 phases ordonnées** », puis + **ligne 88** « exécute les **7 phases** sur le VPS » — DEUX transcriptions du + nombre de phases dans la même fiche. +- **`05_deliverables_mvp/devops/deploy_runbook/README.md:94`** : « renseigner les + **7 confirmations** » + énumération de ces 7 confirmations (lignes 55-58). +- L'artefact source est byte-gaté par `check_artifacts` : + `devops/deploy_runbook/out/MANIFEST.json` recompte `counts.phases` + (= `len(graph.phase_order)`) et `counts.confirmations` (= `len(confirmations_ouvertes)`) + à chaque build. +- Aucun gate ne comparait la VALEUR écrite à l'artefact : ajouter une phase au graphe + OU une confirmation au catalogue ferait dériver les docs en silence. **Piège + aveugle** caractéristique : phases ET confirmations valent **tous deux 7** + aujourd'hui — un gate qui n'en recompterait qu'un serait aveugle à la dérive de + l'autre. + +**Nouveau bloc dans `ci/check_readme_claims.sh`** : recompute **chaque** nombre +depuis **son propre** champ du MANIFEST (jamais figé ici · zéro duplication du +compteur du générateur #5) et exige l'égalité avec les docs. Le **22** (« suites +gated » de la fiche devops) est **volontairement écarté** : sa sémantique (suites +CI) ≠ `counts.modules` du run-book (qui s'auto-exclut) → pas de transcription propre, +pas de gate hasardeux. Bonus anti-dérive : l'**énumération** des confirmations du +README (tokens `snake_case`) doit être l'ensemble **exact** de +`confirmations_ouvertes` — mord l'**échange** d'un nom là où le seul compte reste +aveugle. Cohérences croisées : `counts.phases == len(phase_order)` · +`counts.confirmations == len(confirmations_ouvertes)`. + +### Preuve (5 morsures vérifiées + restauration verte) + +| Injection | Résultat | +|---|---| +| fiche devops phases 7→8 (1re transcription) | ✗ dit 8 MAIS artefact 7 · exit 1 | +| fiche devops 2e transcription « exécute les 7 »→6 (aveugle si 1re reste 7) | ✗ dit 6 MAIS artefact 7 · exit 1 | +| README confirmations 7→8 (count) | ✗ dit 8 MAIS artefact 7 · exit 1 | +| README swap d'un nom de confirmation (count reste 7, aveugle au compte) | ✗ énumération ≠ confirmations_ouvertes · exit 1 | +| README claim confirmations supprimé (reformulé) | ✗ claim INTROUVABLE · exit 1 | +| restauré | ✓ 7 phases + 7 confirmations + énumération == artefacts · exit 0 | + +- **`ci/README.md`** — surface ajoutée à la table récap du pipeline + paragraphe + descriptif (classe « run-book de déploiement VPS »). + +Suite CI complète re-verte : `check_artifacts` · `check_readme_claims` · +`check_docs` · `check_regression` · `check_ci_integrity` (gates statiques +verrouillés) · `guard_constraints` · `validate_json`. + +**Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo). + +**Auto-score 4Big** : 96/100. + +--- + ## Session `20260731_103044` · Buffer S8 · Domaine Legal : la synthèse du DocType « CONFOTUR Application » (« 18 champs · 14 de donnée · 4 sections · 3 rôles · 2 évènements de dépôt ») était transcrite À LA MAIN dans DEUX docs (README du module legal/confotur + fiche agent onapi_legal) sans AUCUN gate — ajouter un champ/section/rôle au spec ferait dériver les deux docs en silence **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA régression). Roadmap diff --git a/ci/README.md b/ci/README.md index 89225c0..8259cd3 100644 --- a/ci/README.md +++ b/ci/README.md @@ -20,7 +20,7 @@ et manuellement (`workflow_dispatch`). Jobs statiques (+ une suite `unittest` pa | `check-artifacts` | `ci/check_artifacts.sh` | Reproductibilité : chaque `out/*.json` versionné == build frais | ✅ oui | | `check-regression` | `ci/check_regression.sh` | Fraîcheur : `qa/regression/out/regression_run.json` (`run`) == run frais + verdict PASS | ✅ oui | | `check-ci-integrity` | `ci/check_ci_integrity.sh` | Intégrité du câblage : `gate.needs` == tous les jobs non-manuels · chaque `ci/*.sh` câblé au gate | ✅ oui | -| `check-readme-claims` | `ci/check_readme_claims.sh` | Intégrité des chiffres des docs d'entrée : chaque nombre de `README.md` « État courant », de la fiche QA « Verdict agrégé » **et** de la colonne « Tests » par suite des tables de fiches **et** des agrégats en prose (Total CRM / RBAC / e-CF) **et** des comptes par workspace des portails (cartes/liens/rôles, ×2 docs) **et** des comptes RBAC des 3 volets (table Nb rôles/profil, ligne agrégée apply_plan, ventilation userperm) **et** des comptes de mots-clés SEO (total + fr/en/es, ×2 docs) **et** du pipeline vente CRM (états/transitions, ×2 docs) **et** de l'app mobile Expo (onglets/rôles/langues/identifiants a_confirmer) **et** du DocType CONFOTUR (champs/données/sections/rôles/évènements de dépôt, ×2 docs) **et** de l'audit 5D (contrôles/dimensions ×2 docs + ventilation verdict pass/fail/à-confirmer) == artefact cité (#6) | ✅ oui | +| `check-readme-claims` | `ci/check_readme_claims.sh` | Intégrité des chiffres des docs d'entrée : chaque nombre de `README.md` « État courant », de la fiche QA « Verdict agrégé » **et** de la colonne « Tests » par suite des tables de fiches **et** des agrégats en prose (Total CRM / RBAC / e-CF) **et** des comptes par workspace des portails (cartes/liens/rôles, ×2 docs) **et** des comptes RBAC des 3 volets (table Nb rôles/profil, ligne agrégée apply_plan, ventilation userperm) **et** des comptes de mots-clés SEO (total + fr/en/es, ×2 docs) **et** du pipeline vente CRM (états/transitions, ×2 docs) **et** de l'app mobile Expo (onglets/rôles/langues/identifiants a_confirmer) **et** du DocType CONFOTUR (champs/données/sections/rôles/évènements de dépôt, ×2 docs) **et** de l'audit 5D (contrôles/dimensions ×2 docs + ventilation verdict pass/fail/à-confirmer) **et** du run-book de déploiement VPS (phases ×2 dans la fiche devops + confirmations préalables count & énumération dans le README) == artefact cité (#6) | ✅ oui | | `gate` | — | Agrégat vert = gate qualité 4Big franchi | ✅ oui | Aucune dépendance réseau/marketplace hors `actions/checkout`. Tout tourne avec @@ -233,6 +233,20 @@ contrôle en A_CONFIRMER (pass 13→12, à confirmer 4→5) là où le total de reste 17 et resterait aveugle. Cohérence croisée en bonus : `controls_total` du MANIFEST doit égaler `totals.controls` du rapport. +Même classe enfin sur le **run-book de déploiement VPS** (livrable +`devops/deploy_runbook`) : le compte de **phases** (fiche agent `devops` — « graphe +de **7 phases ordonnées** » **et** « exécute les 7 phases sur le VPS ») **et** le +compte de **confirmations préalables** (README du module — « renseigner les 7 +confirmations ») transcrivaient **à la main** deux nombres qui, aujourd'hui, valent +**tous deux 7** — un gate qui n'en recompterait qu'un serait **aveugle** à la dérive +de l'autre. Chacun est recomputé depuis **son propre** champ de +`devops/deploy_runbook/out/MANIFEST.json` (`counts.phases` = `len(graph.phase_order)` · +`counts.confirmations` = `len(confirmations_ouvertes)`, byte-gaté par +`check_artifacts`) et comparé aux docs. Bonus anti-dérive : l'**énumération** des +confirmations du README (les tokens `snake_case`) doit être l'ensemble **exact** de +`confirmations_ouvertes` — mord l'**échange** d'un nom de confirmation là où le seul +compte (reste 7) resterait aveugle. + Un claim **absent** échoue aussi (la dérive de formulation qui ferait disparaître un chiffre est elle-même une régression de traçabilité). stdlib pur (bash/git/python3), zéro réseau. diff --git a/ci/check_readme_claims.sh b/ci/check_readme_claims.sh index 26ae45a..ac261fd 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -883,6 +883,100 @@ if a5_ctl is not None: bad(f"Audit 5D · {A5D_FI} — dit {'/'.join(m.groups())} MAIS artefact dit " f"{a5_ctl}/{a5_dim} ({a5_src}) → régénérer la fiche") +# ============================================================================ +# DevOps · run-book de déploiement VPS (phases ordonnées / confirmations +# préalables) — même classe de « vert trompeur », transcrite À LA MAIN dans +# DEUX docs, sans AUCUN gate. +# ---------------------------------------------------------------------------- +# Le générateur deploy_runbook émet out/MANIFEST.json (byte-gaté par +# check_artifacts ⇒ faisant autorité) dont `counts.phases` et +# `counts.confirmations` sont RECOMPTÉS à chaque build (phases = |graph.phase_order| · +# confirmations = |confirmations_ouvertes|, l'ensemble consolidé des paramètres +# réglementaires non confirmés). Deux nombres de cette synthèse sont recopiés À LA +# MAIN : le compte de PHASES dans la fiche agent DevOps (03_agents/devops/AGENT.md — +# « graphe de **7 phases ordonnées** » ET, plus bas, « exécute les 7 phases sur le +# VPS ») et le compte de CONFIRMATIONS dans le README du module +# (05_deliverables_mvp/devops/deploy_runbook/README.md — « renseigner les 7 +# confirmations »). Piège « aveugle » caractéristique (cf. bascule verdict audit_5d, +# compensation SEO entre langues) : phases ET confirmations valent 7 AUJOURD'HUI — +# un gate qui ne recompterait qu'UN nombre serait AVEUGLE à la dérive de l'autre +# (ajouter une phase au graphe → 8 phases pendant que les confirmations restent 7, +# ou l'inverse). On RECOMPUTE donc CHAQUE nombre depuis SON PROPRE champ du MANIFEST +# (jamais figé ici · zéro duplication du compteur du générateur #5) et on exige +# l'égalité avec les docs. Bonus anti-dérive : l'ÉNUMÉRATION des confirmations du +# README (les tokens `snake_case` de la phrase « Confirmations préalables VPS ») +# doit être l'ensemble EXACT de confirmations_ouvertes — capte l'ÉCHANGE d'un nom de +# confirmation (count aveugle à un swap 1↔1). Cohérence croisée : counts.phases == +# |phase_order| · counts.confirmations == |confirmations_ouvertes|. Un claim absent +# échoue AUSSI (la disparition d'un chiffre est une régression · traçabilité). +# ============================================================================ +DR_RD = f"{D}/devops/deploy_runbook/README.md" +DR_FI = "03_agents/devops/AGENT.md" +try: + dr_man = load("devops/deploy_runbook/out/MANIFEST.json") + dr_ph = str(dr_man["counts"]["phases"]) + dr_cf = str(dr_man["counts"]["confirmations"]) + dr_order = dr_man["graph"]["phase_order"] + dr_open = dr_man["confirmations_ouvertes"] + if dr_ph != str(len(dr_order)): + bad(f"DevOps · incohérence artefact : counts.phases={dr_ph} MAIS " + f"|graph.phase_order|={len(dr_order)}") + if dr_cf != str(len(dr_open)): + bad(f"DevOps · incohérence artefact : counts.confirmations={dr_cf} MAIS " + f"|confirmations_ouvertes|={len(dr_open)}") +except (OSError, KeyError, ValueError) as e: + bad(f"DevOps · artefact deploy_runbook illisible/inattendu : {e}"); dr_ph = None +if dr_ph is not None: + # 1) Fiche agent DevOps — « graphe de N phases ordonnées » + dr_fi = read_norm(DR_FI) + if dr_fi is not None: + m = re.search(r"graphe de \*\*(\d+) phases ordonnées\*\*", dr_fi) + if m is None: + bad(f"DevOps · {DR_FI} — claim « graphe de N phases ordonnées » INTROUVABLE " + f"(attendu phases={dr_ph})") + elif m.group(1) == dr_ph: + good(f"DevOps · {DR_FI} — {m.group(1)} phases == artefact (phases={dr_ph})") + else: + bad(f"DevOps · {DR_FI} — dit {m.group(1)} phases MAIS artefact dit {dr_ph} " + f"→ régénérer la fiche") + # 2) Fiche agent DevOps — 2e transcription « exécute les N phases » + m = re.search(r"exécute les (\d+) phases", dr_fi) + if m is None: + bad(f"DevOps · {DR_FI} — 2e claim « exécute les N phases » INTROUVABLE " + f"(attendu phases={dr_ph})") + elif m.group(1) == dr_ph: + good(f"DevOps · {DR_FI} — « exécute les {m.group(1)} phases » == artefact " + f"(phases={dr_ph})") + else: + bad(f"DevOps · {DR_FI} — 2e claim dit {m.group(1)} phases MAIS artefact dit " + f"{dr_ph} → régénérer la fiche") + # 3) README module — « renseigner les N confirmations » (compte) + dr_rd = read_norm(DR_RD) + if dr_rd is not None: + m = re.search(r"renseigner les (\d+) confirmations", dr_rd) + if m is None: + bad(f"DevOps · {DR_RD} — claim « renseigner les N confirmations » INTROUVABLE " + f"(attendu confirmations={dr_cf})") + elif m.group(1) == dr_cf: + good(f"DevOps · {DR_RD} — {m.group(1)} confirmations == artefact " + f"(confirmations={dr_cf})") + else: + bad(f"DevOps · {DR_RD} — dit {m.group(1)} confirmations MAIS artefact dit " + f"{dr_cf} → régénérer le README") + # 4) README module — l'ÉNUMÉRATION doit être l'ensemble EXACT des confirmations + seg = re.search(r"Confirmations préalables VPS\*\* \(consolidées, sourcées\) : " + r"(.+?) ## ", dr_rd) + if seg is None: + bad(f"DevOps · {DR_RD} — énumération « Confirmations préalables VPS » " + f"INTROUVABLE (attendu {sorted(dr_open)})") + else: + listed = sorted(set(re.findall(r"`([a-z_]+)`", seg.group(1)))) + if listed == sorted(dr_open): + good(f"DevOps · {DR_RD} — énumération ({len(listed)}) == confirmations_ouvertes") + else: + bad(f"DevOps · {DR_RD} — énumération {listed} ≠ confirmations_ouvertes " + f"{sorted(dr_open)} → régénérer le README") + sys.exit(1 if FAIL else 0) PY rc=$?