[DTP-Worker] Sprint 8 · buffer L75 · Domaine QA/Audit 5D : la synthèse de l'audit de conformité (« 17 contrôles · 5 dimensions ») était transcrite À LA MAIN dans DEUX docs (README du module qa/audit_5d + cellule de la fiche agent qa, même couple du même catalogue de contrôles) sans AUCUN gate, et la ventilation du verdict (« 13 PASS · 0 FAIL · 4 à confirmer ») du seul README non plus — ajouter un contrôle/une dimension au catalogue OU faire basculer un contrôle en A_CONFIRMER ferait dériver les docs en silence pendant que les DEUX artefacts byte-gatés par check_artifacts (qa/audit_5d/out/MANIFEST.json = controls_total+dimensions · out/audit_report.json = totals.{pass,fail,a_confirmer}, recomptés du catalogue à chaque build) disent autre chose → nouveau bloc dans check_readme_claims recomputant le couple (contrôles+dimensions) depuis MANIFEST et la ventilation (pass/fail/à-confirmer) depuis audit_report.totals, exigeant l'égalité avec les deux docs + cohérence croisée controls_total==totals.controls (4 morsures vérifiées : dérive contrôles README · bascule 13→12/4→5 captée là où le total de contrôles reste 17 et resterait aveugle · dérive dimensions fiche agent · claim verdict disparu = red)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
+14
-1
@@ -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.
|
||||
|
||||
@@ -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=$?
|
||||
|
||||
Reference in New Issue
Block a user