From 9ac7bdb2dec45395700bbf8b48ff642b92c3ac3e Mon Sep 17 00:00:00 2001 From: Claude Code DTP Worker Date: Fri, 31 Jul 2026 11:06:21 +0000 Subject: [PATCH] =?UTF-8?q?[DTP-Worker]=20Sprint=208=20=C2=B7=20buffer=20L?= =?UTF-8?q?75=20=C2=B7=20Domaine=20QA/Audit=205D=20:=20la=20synth=C3=A8se?= =?UTF-8?q?=20de=20l'audit=20de=20conformit=C3=A9=20(=C2=AB=2017=20contr?= =?UTF-8?q?=C3=B4les=20=C2=B7=205=20dimensions=20=C2=BB)=20=C3=A9tait=20tr?= =?UTF-8?q?anscrite=20=C3=80=20LA=20MAIN=20dans=20DEUX=20docs=20(README=20?= =?UTF-8?q?du=20module=20qa/audit=5F5d=20+=20cellule=20de=20la=20fiche=20a?= =?UTF-8?q?gent=20qa,=20m=C3=AAme=20couple=20du=20m=C3=AAme=20catalogue=20?= =?UTF-8?q?de=20contr=C3=B4les)=20sans=20AUCUN=20gate,=20et=20la=20ventila?= =?UTF-8?q?tion=20du=20verdict=20(=C2=AB=2013=20PASS=20=C2=B7=200=20FAIL?= =?UTF-8?q?=20=C2=B7=204=20=C3=A0=20confirmer=20=C2=BB)=20du=20seul=20READ?= =?UTF-8?q?ME=20non=20plus=20=E2=80=94=20ajouter=20un=20contr=C3=B4le/une?= =?UTF-8?q?=20dimension=20au=20catalogue=20OU=20faire=20basculer=20un=20co?= =?UTF-8?q?ntr=C3=B4le=20en=20A=5FCONFIRMER=20ferait=20d=C3=A9river=20les?= =?UTF-8?q?=20docs=20en=20silence=20pendant=20que=20les=20DEUX=20artefacts?= =?UTF-8?q?=20byte-gat=C3=A9s=20par=20check=5Fartifacts=20(qa/audit=5F5d/o?= =?UTF-8?q?ut/MANIFEST.json=20=3D=20controls=5Ftotal+dimensions=20=C2=B7?= =?UTF-8?q?=20out/audit=5Freport.json=20=3D=20totals.{pass,fail,a=5Fconfir?= =?UTF-8?q?mer},=20recompt=C3=A9s=20du=20catalogue=20=C3=A0=20chaque=20bui?= =?UTF-8?q?ld)=20disent=20autre=20chose=20=E2=86=92=20nouveau=20bloc=20dan?= =?UTF-8?q?s=20check=5Freadme=5Fclaims=20recomputant=20le=20couple=20(cont?= =?UTF-8?q?r=C3=B4les+dimensions)=20depuis=20MANIFEST=20et=20la=20ventilat?= =?UTF-8?q?ion=20(pass/fail/=C3=A0-confirmer)=20depuis=20audit=5Freport.to?= =?UTF-8?q?tals,=20exigeant=20l'=C3=A9galit=C3=A9=20avec=20les=20deux=20do?= =?UTF-8?q?cs=20+=20coh=C3=A9rence=20crois=C3=A9e=20controls=5Ftotal=3D=3D?= =?UTF-8?q?totals.controls=20(4=20morsures=20v=C3=A9rifi=C3=A9es=20:=20d?= =?UTF-8?q?=C3=A9rive=20contr=C3=B4les=20README=20=C2=B7=20bascule=2013?= =?UTF-8?q?=E2=86=9212/4=E2=86=925=20capt=C3=A9e=20l=C3=A0=20o=C3=B9=20le?= =?UTF-8?q?=20total=20de=20contr=C3=B4les=20reste=2017=20et=20resterait=20?= =?UTF-8?q?aveugle=20=C2=B7=20d=C3=A9rive=20dimensions=20fiche=20agent=20?= =?UTF-8?q?=C2=B7=20claim=20verdict=20disparu=20=3D=20red)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- 05_activity_log/2026-07-31.md | 51 ++++++++++++++++++++++ ci/README.md | 15 ++++++- ci/check_readme_claims.sh | 81 +++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 1 deletion(-) diff --git a/05_activity_log/2026-07-31.md b/05_activity_log/2026-07-31.md index b7bc373..3ef7cc2 100644 --- a/05_activity_log/2026-07-31.md +++ b/05_activity_log/2026-07-31.md @@ -1297,3 +1297,54 @@ verrouillés) · `guard_constraints`. **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo). **Auto-score 4Big** : 96/100. + +--- + +## Sprint 8 · buffer L75 · Gate chiffres audit 5D (contrôles/dimensions ×2 docs + ventilation verdict) + +**Contexte** : poursuite de la fermeture des surfaces de « vert trompeur » non +gatées dans `check_readme_claims.sh`. Après RBAC, SEO, CRM pipeline, Mobile et +Legal CONFOTUR, restait la **synthèse de l'audit de conformité 5D** (livrable +`qa/audit_5d`), transcrite À LA MAIN dans DEUX docs sans aucun gate. + +**Dérive silencieuse fermée** : +- Le README du module (`05_deliverables_mvp/qa/audit_5d/README.md`) ouvre par + « **17 contrôles** répartis en **5 dimensions (5D)** » ET affiche la ventilation + du verdict « **13 PASS · 0 FAIL · 4 à confirmer** ». +- La fiche agent QA (`03_agents/qa/AGENT.md`) recopie le même couple « 17 contrôles + / 5 dimensions ». +- Les DEUX artefacts source sont byte-gatés par `check_artifacts` (recomptés du + catalogue de contrôles à chaque build) : `out/MANIFEST.json` (`controls_total`, + `dimensions`) ET `out/audit_report.json` (`totals.{pass,fail,a_confirmer}`). +- Aucun gate ne comparait la VALEUR écrite à l'artefact : ajouter un contrôle/une + dimension au catalogue, ou faire **basculer** un contrôle en A_CONFIRMER, ferait + dériver les deux docs (et la ligne verdict) en silence. + +**Nouveau bloc dans `ci/check_readme_claims.sh`** : recompute le couple +(contrôles + dimensions) depuis le MANIFEST et la ventilation (pass/fail/à-confirmer) +depuis `audit_report.totals` — **jamais** figés en dur ici, zéro duplication du +compteur du générateur (#6) — et exige l'égalité avec les deux docs. La ventilation +est recomputée SÉPARÉMENT car une bascule (pass 13→12, à confirmer 4→5) est **aveugle** +au seul total de contrôles (reste 17). Cohérence croisée en bonus : +`MANIFEST.controls_total == audit_report.totals.controls`. + +### Preuve (4 morsures vérifiées + restauration verte) + +| Injection | Résultat | +|---|---| +| README contrôles 17→18 | ✗ dit 18/5 MAIS artefact 17/5 · exit 1 | +| README verdict 13 PASS→12 / 4→5 à confirmer (bascule aveugle au total) | ✗ dit 12/0/5 MAIS totals 13/0/4 · exit 1 | +| fiche QA dimensions 5→6 | ✗ dit 17/6 MAIS artefact 17/5 · exit 1 | +| claim README verdict supprimé (reformulé) | ✗ ventilation verdict INTROUVABLE · exit 1 | +| restauré | ✓ 17/5 + 13/0/4 == artefacts · exit 0 | + +- **`ci/README.md`** — surface ajoutée à la table récap du pipeline + paragraphe + descriptif (classe « audit 5D »). + +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. diff --git a/ci/README.md b/ci/README.md index 8679bca..89225c0 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) == 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) == 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 @@ -220,6 +220,19 @@ depot_events}`, recomptés du `confotur_spec.json` à chaque build, byte-gaté p une dérive de n'importe lequel (ajouter un champ/une section/un rôle au spec) là où un seul nombre resterait aveugle. +Même classe enfin sur la **synthèse de l'audit 5D** (livrable `qa/audit_5d`) : la +phrase d'ouverture du README du module (« **17 contrôles** répartis en **5 dimensions +(5D)** ») **et** la cellule de la fiche agent `qa` (« 17 contrôles / 5 dimensions ») +transcrivaient **à la main** le même couple, dérivé du catalogue de contrôles. Ce +couple est recomputé depuis `qa/audit_5d/out/MANIFEST.json` (`controls_total` + +`len(dimensions)`, byte-gaté par `check_artifacts`) et comparé aux **deux** docs. La +**ventilation du verdict** portée par le seul README (« **13 PASS · 0 FAIL · 4 à +confirmer** ») est recomputée à part depuis `qa/audit_5d/out/audit_report.json` +(`totals.{pass,fail,a_confirmer}`, aussi byte-gaté) : elle mord une **bascule** d'un +contrôle en A_CONFIRMER (pass 13→12, à confirmer 4→5) là où le total de contrôles +reste 17 et resterait aveugle. Cohérence croisée en bonus : `controls_total` du +MANIFEST doit égaler `totals.controls` du rapport. + 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 3dda7a6..26ae45a 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -802,6 +802,87 @@ if conf_exp is not None: bad(f"Legal confotur · {path_f} — dit {'/'.join(m.groups())} MAIS " f"MANIFEST dit {'/'.join(conf_exp)} ({conf_src}) → régénérer le doc") +# ============================================================================ +# Audit 5D · synthèse de l'audit de conformité (contrôles / dimensions / verdict +# pass-fail-à confirmer) — même classe de « vert trompeur », transcrite À LA MAIN +# dans DEUX docs. +# ---------------------------------------------------------------------------- +# Le générateur audit_5d émet DEUX artefacts byte-gatés par check_artifacts +# (⇒ faisant autorité) : out/MANIFEST.json (controls_total, `dimensions`, +# `audited_artifacts`, `open_items`, verdict — recomptés du catalogue de contrôles +# à chaque build) ET out/audit_report.json dont `totals` porte la ventilation +# {controls, pass, fail, a_confirmer} recalculée en rejouant les 17 contrôles sur +# les hand-off amont. Deux nombres de cette synthèse sont recopiés À LA MAIN à +# DEUX endroits : la phrase d'ouverture du README du module (05_deliverables_mvp/ +# qa/audit_5d/README.md — « **17 contrôles** répartis en **5 dimensions (5D)** ») +# ET la cellule de la fiche agent QA (03_agents/qa/AGENT.md — « 17 contrôles / 5 +# dimensions »). La ventilation du verdict (« **13 PASS · 0 FAIL · 4 à confirmer** ») +# n'est portée QUE par le README, mais se PÉRIME dès qu'un contrôle bascule en +# A_CONFIRMER (pass 13→12, à confirmer 4→5) alors que le TOTAL de contrôles reste +# 17 — dérive AVEUGLE au seul compte de contrôles, d'où sa recomputation séparée +# depuis audit_report.totals. Aucun gate ne couvrait ces claims : ajouter un +# contrôle/une dimension au catalogue, ou faire basculer un contrôle, ferait +# dériver LES DEUX docs (et la ligne verdict) en silence pendant que les artefacts +# disent autre chose — même piège que les mots-clés SEO, les triplets par workspace +# des portails ou le quintuplet CONFOTUR. On RECOMPUTE ici (contrôles + dimensions +# depuis le MANIFEST · pass/fail/à confirmer depuis audit_report.totals · déjà +# byte-gatés · zéro duplication du compteur du générateur) et on exige l'égalité. +# Un claim absent échoue AUSSI (traçabilité). Cohérence croisée en bonus : le +# `controls_total` du MANIFEST doit égaler `totals.controls` du rapport. +# ============================================================================ +A5D_RD = f"{D}/qa/audit_5d/README.md" +A5D_FI = "03_agents/qa/AGENT.md" +try: + a5_man = load("qa/audit_5d/out/MANIFEST.json") + a5_tot = load("qa/audit_5d/out/audit_report.json")["totals"] + a5_ctl = str(a5_man["controls_total"]) + a5_dim = str(len(a5_man["dimensions"])) + if a5_ctl != str(a5_tot["controls"]): + bad(f"Audit 5D · incohérence artefacts : MANIFEST controls_total={a5_ctl} " + f"MAIS audit_report totals.controls={a5_tot['controls']}") + a5_pass, a5_fail = str(a5_tot["pass"]), str(a5_tot["fail"]) + a5_conf = str(a5_tot["a_confirmer"]) +except (OSError, KeyError, ValueError) as e: + bad(f"Audit 5D · artefact illisible/inattendu : {e}"); a5_ctl = None +if a5_ctl is not None: + a5_src = f"contrôles={a5_ctl} dimensions={a5_dim}" + # 1) README module — phrase d'ouverture (contrôles + dimensions) + a5_rd = read_norm(A5D_RD) + if a5_rd is not None: + m = re.search(r"\*\*(\d+) contrôles\*\* répartis en \*\*(\d+) dimensions \(5D\)\*\*", + a5_rd) + if m is None: + bad(f"Audit 5D · {A5D_RD} — synthèse contrôles/dimensions INTROUVABLE " + f"(attendu {a5_src})") + elif m.groups() == (a5_ctl, a5_dim): + good(f"Audit 5D · {A5D_RD} — {'/'.join(m.groups())} == artefact ({a5_src})") + else: + bad(f"Audit 5D · {A5D_RD} — dit {'/'.join(m.groups())} MAIS artefact dit " + f"{a5_ctl}/{a5_dim} ({a5_src}) → régénérer le README") + # 2) README module — ventilation du verdict (pass / fail / à confirmer) + a5_vsrc = f"pass={a5_pass} fail={a5_fail} à_confirmer={a5_conf}" + m = re.search(r"\*\*(\d+) PASS · (\d+) FAIL · (\d+) à confirmer\*\*", a5_rd) + if m is None: + bad(f"Audit 5D · {A5D_RD} — ventilation verdict INTROUVABLE (attendu {a5_vsrc})") + elif m.groups() == (a5_pass, a5_fail, a5_conf): + good(f"Audit 5D · {A5D_RD} — verdict {'/'.join(m.groups())} == " + f"audit_report.totals ({a5_vsrc})") + else: + bad(f"Audit 5D · {A5D_RD} — dit {'/'.join(m.groups())} MAIS totals disent " + f"{a5_pass}/{a5_fail}/{a5_conf} ({a5_vsrc}) → régénérer le README") + # 3) Fiche agent QA — même couple contrôles/dimensions, 2e transcription + a5_fi = read_norm(A5D_FI) + if a5_fi is not None: + m = re.search(r"(\d+) contrôles / (\d+) dimensions", a5_fi) + if m is None: + bad(f"Audit 5D · {A5D_FI} — claim « N contrôles / M dimensions » INTROUVABLE " + f"(attendu {a5_src})") + elif m.groups() == (a5_ctl, a5_dim): + good(f"Audit 5D · {A5D_FI} — {'/'.join(m.groups())} == artefact ({a5_src})") + else: + 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") + sys.exit(1 if FAIL else 0) PY rc=$?