[DTP-Worker] Sprint 8 · buffer · Domaine RBAC/roleprofile_gen (2e surface) : le CLASSEMENT métier/technique des 6 Role Profile (colonne « Type » de la table « Profils générés » + prose « 6 profils (5 métier + 1 technique) ») était NON GATÉ. Le bloc RBAC existant ne gatait que le COMPTE Nb rôles par ligne + total bijectif 50 — sa colonne « Type » était un WILDCARD ([^|]*), et le split métier/technique en prose n'était gaté nulle part. Preuve reproduite : classer le portail technique plateforme en « métier » dans le README → check_readme_claims EXIT 0 (le gate ne voyait que 6==6). Ce classement est DATA-DERIVED : le flag booléen metier par profil de MANIFEST.profiles + le split = counts.portails_metier(5)/portails_techniques(1)/role_profiles(6), MANIFEST recomputé du contrat rbac_50_roles.json à chaque build (byte-gaté par check_artifacts). Un « métier vs technique » faux détournerait le Sprint 4 (« 5 portails rôle » = les 5 métier · plateforme = console technique). PIÈGE : check_artifacts ne prouve QUE MANIFEST==build (byte-for-byte) et la boucle Nb rôles existante n'itère que MANIFEST→README (capte le MANQUANT) — donc RENVERSER un flag dans la prose · MENTIR sur le split · AJOUTER une ligne fantôme d'un profil inventé restait « vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS de builder, pas la prose du README) n'attrape → gate étendu (bloc roleprofile de ci/check_readme_claims.sh) : (1) colonne « Type » recomputée du flag metier, exigée EXACTE par profil (fin du wildcard) ; (2) prose « N profils (X métier + Y technique) » == comptes MANIFEST ; (3) identité d'ensemble — nombre de lignes de profil de la table == |profiles| (aucune ligne FANTÔME absente du contrat ; complète le sens MANIFEST→README) ; (4) cohérences croisées mordant un MANIFEST INTERNEMENT incohérent : portails_metier + portails_techniques == role_profiles == |profiles| ET ces comptes recomptent les flags metier réels. 5 morsures vérifiées : README classe plateforme métier (technique→métier) · README classe ventes technique (métier→technique) · prose split 5→4 métier (6/4/1 MAIS 6/5/1) · ligne FANTÔME OTO Portail Fantome (7≠6) · MANIFEST portails_metier 5→6 sans toucher aux flags (incohérence interne, flags réels=5) ; restauré = green : 6 Types == MANIFEST · split 6/5/1 · 6 lignes · comptes cohérents · exit 0. État courant : aucun classement périmé (anti-invention #6, rien à réécrire) — le défaut est la surface ungated. ci/README.md (table + détail « 2e surface roleprofile_gen ») mis à jour · 7 gates re-verts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -673,17 +673,30 @@ rp_rd = read_norm(RP_RD)
|
||||
if rp_man is not None and rp_rd is not None:
|
||||
for p in rp_man["profiles"]:
|
||||
name, portail, exp = p["role_profile"], p["portail"], str(p["nb_roles"])
|
||||
# métier vs technique : le flag DATA-DERIVED (booléen `metier` du MANIFEST)
|
||||
# doit être transcrit EXACTEMENT dans la colonne « Type ». Sans ce gate la
|
||||
# colonne était un WILDCARD (`[^|]*`) → un portail technique classé « métier »
|
||||
# (ou l'inverse) passait « vert » alors que l'artefact dit autre chose.
|
||||
exp_type = "métier" if p["metier"] else "technique"
|
||||
pat = (rf"\| `{re.escape(name)}` \| {re.escape(portail)} \| "
|
||||
r"[^|]*\| (\d+) \|")
|
||||
r"([^|]+?) \| (\d+) \|")
|
||||
m = re.search(pat, rp_rd)
|
||||
if m is None:
|
||||
bad(f"RBAC roleprofile · {name} — ligne INTROUVABLE dans {RP_RD} "
|
||||
f"(attendu nb_roles={exp})")
|
||||
elif m.group(1) == exp:
|
||||
good(f"RBAC roleprofile · {name} — Nb rôles {m.group(1)} == MANIFEST ({exp})")
|
||||
f"(attendu type={exp_type} nb_roles={exp})")
|
||||
else:
|
||||
bad(f"RBAC roleprofile · {name} — {RP_RD} dit {m.group(1)} MAIS "
|
||||
f"MANIFEST dit {exp} → régénérer le README")
|
||||
got_type, got_n = m.group(1).strip(), m.group(2)
|
||||
if got_n == exp:
|
||||
good(f"RBAC roleprofile · {name} — Nb rôles {got_n} == MANIFEST ({exp})")
|
||||
else:
|
||||
bad(f"RBAC roleprofile · {name} — {RP_RD} dit {got_n} MAIS "
|
||||
f"MANIFEST dit {exp} → régénérer le README")
|
||||
if got_type == exp_type:
|
||||
good(f"RBAC roleprofile · {name} — Type « {got_type} » == MANIFEST "
|
||||
f"(metier={p['metier']})")
|
||||
else:
|
||||
bad(f"RBAC roleprofile · {name} — {RP_RD} classe « {got_type} » MAIS "
|
||||
f"MANIFEST dit « {exp_type} » (metier={p['metier']}) → régénérer")
|
||||
exp_tot = str(rp_man["counts"]["roles_couverts"])
|
||||
mt = re.search(r"\*\*Total\*\* \| \*\*(\d+)\*\*", rp_rd)
|
||||
if mt is None:
|
||||
@@ -694,6 +707,47 @@ if rp_man is not None and rp_rd is not None:
|
||||
else:
|
||||
bad(f"RBAC roleprofile · total — {RP_RD} dit {mt.group(1)} MAIS MANIFEST "
|
||||
f"dit {exp_tot} → régénérer le README")
|
||||
# 1bis) IDENTITÉ de l'ensemble des profils (pas seulement leurs comptes) --------
|
||||
# La boucle ci-dessus est MANIFEST→README (chaque profil du contrat a une ligne :
|
||||
# capte le MANQUANT). Le sens README→MANIFEST (une ligne FANTÔME dans la table,
|
||||
# profil inventé absent du contrat) reste aveugle → on compte les lignes de profil
|
||||
# de la table et on exige == |profiles|. Ensemble ∩ boucle = identité d'ensemble.
|
||||
exp_prof = len(rp_man["profiles"])
|
||||
rows = re.findall(r"\| `OTO Portail [^|`]+` \| [^|]+ \| (?:métier|technique) \| \d+ \|",
|
||||
rp_rd)
|
||||
if len(rows) == exp_prof:
|
||||
good(f"RBAC roleprofile · table — {len(rows)} lignes de profil == "
|
||||
f"MANIFEST ({exp_prof}) : aucun profil FANTÔME")
|
||||
else:
|
||||
bad(f"RBAC roleprofile · table — {RP_RD} porte {len(rows)} lignes de profil "
|
||||
f"MAIS MANIFEST en compte {exp_prof} → ligne en trop/manquante ?")
|
||||
# 1ter) prose « N profils (X métier + Y technique) » == comptes MANIFEST --------
|
||||
# + cohérences croisées : les comptes portails_metier/techniques doivent RECOMPTER
|
||||
# les flags `metier` des profils, et leur somme == role_profiles == |profiles|
|
||||
# (mord un MANIFEST INTERNEMENT incohérent : compte de flag ≠ flags réels).
|
||||
c = rp_man["counts"]
|
||||
cm, ct, cp = c["portails_metier"], c["portails_techniques"], c["role_profiles"]
|
||||
real_m = sum(1 for p in rp_man["profiles"] if p["metier"])
|
||||
real_t = sum(1 for p in rp_man["profiles"] if not p["metier"])
|
||||
if cm == real_m and ct == real_t and cm + ct == cp == exp_prof:
|
||||
good(f"RBAC roleprofile · cohérence comptes — portails_metier={cm} + "
|
||||
f"techniques={ct} == role_profiles={cp} == |flags métier réels|")
|
||||
else:
|
||||
bad(f"RBAC roleprofile · cohérence comptes — MANIFEST incohérent : "
|
||||
f"portails_metier={cm} (flags réels={real_m}) · techniques={ct} "
|
||||
f"(réels={real_t}) · role_profiles={cp} · |profiles|={exp_prof}")
|
||||
mp = re.search(r"\*\*(\d+) profils\*\* \((\d+) métier \+ (\d+) technique\)", rp_rd)
|
||||
if mp is None:
|
||||
bad(f"RBAC roleprofile · split métier/technique — prose « N profils "
|
||||
f"(X métier + Y technique) » INTROUVABLE dans {RP_RD} "
|
||||
f"(attendu {cp} profils · {cm} métier + {ct} technique)")
|
||||
elif (mp.group(1), mp.group(2), mp.group(3)) == (str(cp), str(cm), str(ct)):
|
||||
good(f"RBAC roleprofile · split métier/technique — {mp.group(1)} profils "
|
||||
f"({mp.group(2)} métier + {mp.group(3)} technique) == MANIFEST")
|
||||
else:
|
||||
bad(f"RBAC roleprofile · split métier/technique — {RP_RD} dit "
|
||||
f"{mp.group(1)}/{mp.group(2)}/{mp.group(3)} MAIS MANIFEST dit "
|
||||
f"{cp}/{cm}/{ct} → régénérer le README")
|
||||
|
||||
# 2) apply_plan — ligne « Génération réelle » agrégeant les 3 volets ------------
|
||||
AP_RD = f"{D}/rbac/apply_plan/README.md"
|
||||
|
||||
Reference in New Issue
Block a user