[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:
+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