[DTP-Worker 20260801_230453] Sprint 8 · buffer · QA/Audit 5D : gate d'IDENTITÉ du VERDICT GLOBAL — le LABEL de conclusion de l'audit (PASS / PASS_WITH_OPEN_ITEMS / FAIL), la HEADLINE du livrable, était HORS de tout gate : le bloc audit_5d existant recompute la ventilation chiffrée (13 PASS · 0 FAIL · 4 à confirmer), les open items, les 5 dimensions et les livrables audités — jamais le label lui-même. Or il est data-derived (audit_report.json[verdict] == MANIFEST.json[verdict], byte-gatés), recalculé par builder.py (FAIL si ≥1 FAIL · sinon PASS_WITH_OPEN_ITEMS si ≥1 A_CONFIRMER · sinon PASS) et transcrit à la main à 2 endroits du README (la RÈGLE l.39 + le VERDICT COURANT l.49). Nouveau bloc : on MIROITE la règle et on exige (a) HONNÊTETÉ interne audit_report.verdict == verdict(triplet) — un PASS forcé pendant que 4 params restent a_confirmer (CŒUR #6 : ne jamais forcer un PASS) est mordu ; (b) MANIFEST.verdict == audit_report.verdict ; (c) label « Verdict courant » README == artefact ; (d) la règle « Verdict global » énumère EXACTEMENT le codomaine {FAIL, PASS_WITH_OPEN_ITEMS, PASS} du générateur. 5 morsures vérifiées (2 README · 2 artefact + M3 cascade a+b+c · 1 disparition), 7 gates re-verts

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-08-01 23:13:00 +00:00
parent a40bfc51c1
commit 15812c1dff
3 changed files with 149 additions and 1 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
+88
View File
@@ -2272,6 +2272,94 @@ 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")
# ----------------------------------------------------------------------------
# Audit 5D · IDENTITÉ du VERDICT GLOBAL — le LABEL de conclusion (`PASS` /
# `PASS_WITH_OPEN_ITEMS` / `FAIL`), pas seulement sa ventilation chiffrée.
# ----------------------------------------------------------------------------
# Le bloc de ventilation ci-dessus recompute le TRIPLET chiffré (pass/fail/à
# confirmer) mais reste AVEUGLE au LABEL de verdict lui-même — la HEADLINE de tout
# l'audit. Ce label est data-derived (out/audit_report.json[verdict] == out/
# MANIFEST.json[verdict], byte-gatés par check_artifacts ⇒ faisant autorité),
# recalculé par le générateur (qalib/builder.py) via la règle EXACTE : `FAIL` si
# ≥1 FAIL · sinon `PASS_WITH_OPEN_ITEMS` si ≥1 A_CONFIRMER · sinon `PASS`. Le README
# le transcrit À LA MAIN à DEUX endroits — la RÈGLE (« Verdict global : `FAIL` si …
# sinon `PASS_WITH_OPEN_ITEMS` … sinon `PASS` ») ET le VERDICT COURANT
# (« ## Verdict courant \n `PASS_WITH_OPEN_ITEMS` — **13 PASS · 0 FAIL · 4 à
# confirmer** »). AUCUN gate ne le vérifiait : le triplet peut coller à l'artefact
# pendant que le LABEL courant ment (écrire `PASS` alors que 4 open items
# subsistent = violation #6 « ne jamais forcer un PASS », le CŒUR même du livrable —
# l'audit prétend propre un dossier avec 4 params réglementaires non confirmés) ;
# ou la RÈGLE documentée dérive du codomaine réel du générateur (un label renommé
# dans builder.py, la prose périmée). check_artifacts ne prouve QUE
# audit_report==build (byte) ; aucune suite tests/ (FONCTIONS, pas la prose du
# README) n'attrape ces deux dérives. On MIROITE la règle du générateur et on
# exige : (a) HONNÊTETÉ interne — audit_report.verdict == verdict recalculé du
# triplet (l'audit ne peut afficher un verdict incohérent avec ses propres
# chiffres) ; (b) CROISÉ — MANIFEST.verdict == audit_report.verdict ; (c) le LABEL
# « Verdict courant » du README == audit_report.verdict ; (d) la RÈGLE du README
# énumère EXACTEMENT le codomaine {FAIL, PASS_WITH_OPEN_ITEMS, PASS} (un label
# renommé côté code sans mise à jour de la règle est mordu). Un claim absent échoue
# AUSSI (disparition = régression).
# ============================================================================
A5V_DOMAIN = {"FAIL", "PASS_WITH_OPEN_ITEMS", "PASS"}
def _a5_verdict_from_totals(t):
# miroir EXACT de qalib/builder.py : FAIL prime, puis open items, sinon PASS
if t["fail"]:
return "FAIL"
if t["a_confirmer"]:
return "PASS_WITH_OPEN_ITEMS"
return "PASS"
try:
a5v_rep = load("qa/audit_5d/out/audit_report.json")
a5v_verdict = a5v_rep["verdict"]
a5v_tot = a5v_rep["totals"]
a5v_man_verdict = load("qa/audit_5d/out/MANIFEST.json")["verdict"]
except (OSError, KeyError, ValueError, TypeError) as e:
bad(f"Audit 5D · verdict — artefact illisible/inattendu : {e}"); a5v_verdict = None
if a5v_verdict is not None:
# (a) honnêteté interne : le verdict de l'artefact découle de ses propres totals
a5v_exp = _a5_verdict_from_totals(a5v_tot)
if a5v_verdict != a5v_exp:
bad(f"Audit 5D · verdict — audit_report dit « {a5v_verdict} » MAIS ses totals "
f"(fail={a5v_tot['fail']} · à_confirmer={a5v_tot['a_confirmer']}) imposent "
f"« {a5v_exp} » (verdict FORCÉ, incohérent — viole #6)")
else:
good(f"Audit 5D · verdict — audit_report « {a5v_verdict} » == recalcul du triplet "
f"(fail={a5v_tot['fail']} · à_confirmer={a5v_tot['a_confirmer']})")
# (b) croisé artefacts : MANIFEST == rapport
if a5v_man_verdict != a5v_verdict:
bad(f"Audit 5D · verdict — MANIFEST dit « {a5v_man_verdict} » MAIS audit_report "
f"dit « {a5v_verdict} » (artefacts désynchronisés)")
else:
good(f"Audit 5D · verdict — MANIFEST « {a5v_man_verdict} » == audit_report")
a5v_rd = read_norm(A5D_RD)
if a5v_rd is not None:
# (c) LABEL « Verdict courant » du README == verdict de l'artefact
m = re.search(r"## Verdict courant\s+`([A-Z_]+)`\s*—\s*\*\*\d+ PASS", a5v_rd)
if m is None:
bad(f"Audit 5D · {A5D_RD} — label « Verdict courant » INTROUVABLE "
f"(attendu « {a5v_verdict} »)")
elif m.group(1) == a5v_verdict:
good(f"Audit 5D · {A5D_RD} — « Verdict courant » = « {m.group(1)} » == artefact")
else:
bad(f"Audit 5D · {A5D_RD} — « Verdict courant » = « {m.group(1)} » MAIS "
f"artefact dit « {a5v_verdict} » → régénérer le README")
# (d) la RÈGLE documentée énumère EXACTEMENT le codomaine du générateur
seg = re.search(r"Verdict global\s*:\s*(.+?)(?: > | ## )", a5v_rd)
if seg is None:
bad(f"Audit 5D · {A5D_RD} — règle « Verdict global : … » INTROUVABLE "
f"(codomaine {sorted(A5V_DOMAIN)} non vérifiable)")
else:
labels = set(re.findall(r"`([A-Z_]+)`", seg.group(1)))
if labels == A5V_DOMAIN:
good(f"Audit 5D · {A5D_RD} — règle « Verdict global » énumère le codomaine "
f"{sorted(A5V_DOMAIN)} == générateur")
else:
miss = sorted(A5V_DOMAIN - labels); extra = sorted(labels - A5V_DOMAIN)
bad(f"Audit 5D · {A5D_RD} — règle « Verdict global » énumère {sorted(labels)} "
f"≠ codomaine générateur {sorted(A5V_DOMAIN)} "
f"(manquants={miss or '—'} en_trop={extra or '—'}) → aligner la règle")
# ============================================================================
# Audit 5D (2e surface du MÊME README) · ÉNUMÉRATION des open items du « Verdict
# courant » (control → dimension → propriétaire) — même classe de « vert trompeur ».