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