From 7934081bc2a45dbecee66860b50c19f566f5153a Mon Sep 17 00:00:00 2001 From: Claude Code DTP Worker Date: Fri, 31 Jul 2026 19:37:27 +0000 Subject: [PATCH] =?UTF-8?q?[DTP-Worker]=20Sprint=208=20=C2=B7=20buffer=20?= =?UTF-8?q?=C2=B7=20Domaine=20RBAC/roleprofile=5Fgen=20(2e=20surface)=20:?= =?UTF-8?q?=20le=20CLASSEMENT=20m=C3=A9tier/technique=20des=206=20`Role=20?= =?UTF-8?q?Profile`=20(colonne=20=C2=AB=20Type=20=C2=BB=20de=20la=20table?= =?UTF-8?q?=20=C2=AB=20Profils=20g=C3=A9n=C3=A9r=C3=A9s=20=C2=BB=20+=20pro?= =?UTF-8?q?se=20=C2=AB=206=20profils=20(5=20m=C3=A9tier=20+=201=20techniqu?= =?UTF-8?q?e)=20=C2=BB)=20=C3=A9tait=20NON=20GAT=C3=89.=20Le=20bloc=20RBAC?= =?UTF-8?q?=20existant=20ne=20gatait=20que=20le=20COMPTE=20`Nb=20r=C3=B4le?= =?UTF-8?q?s`=20par=20ligne=20+=20total=20bijectif=2050=20=E2=80=94=20sa?= =?UTF-8?q?=20colonne=20=C2=AB=20Type=20=C2=BB=20=C3=A9tait=20un=20WILDCAR?= =?UTF-8?q?D=20(`[^|]*`),=20et=20le=20split=20m=C3=A9tier/technique=20en?= =?UTF-8?q?=20prose=20n'=C3=A9tait=20gat=C3=A9=20nulle=20part.=20Preuve=20?= =?UTF-8?q?reproduite=20:=20classer=20le=20portail=20technique=20`platefor?= =?UTF-8?q?me`=20en=20=C2=AB=20m=C3=A9tier=20=C2=BB=20dans=20le=20README?= =?UTF-8?q?=20=E2=86=92=20check=5Freadme=5Fclaims=20EXIT=200=20(le=20gate?= =?UTF-8?q?=20ne=20voyait=20que=206=3D=3D6).=20Ce=20classement=20est=20DAT?= =?UTF-8?q?A-DERIVED=20:=20le=20flag=20bool=C3=A9en=20`metier`=20par=20pro?= =?UTF-8?q?fil=20de=20`MANIFEST.profiles`=20+=20le=20split=20=3D=20`counts?= =?UTF-8?q?.portails=5Fmetier`(5)/`portails=5Ftechniques`(1)/`role=5Fprofi?= =?UTF-8?q?les`(6),=20MANIFEST=20recomput=C3=A9=20du=20contrat=20`rbac=5F5?= =?UTF-8?q?0=5Froles.json`=20=C3=A0=20chaque=20build=20(byte-gat=C3=A9=20p?= =?UTF-8?q?ar=20check=5Fartifacts).=20Un=20=C2=AB=20m=C3=A9tier=20vs=20tec?= =?UTF-8?q?hnique=20=C2=BB=20faux=20d=C3=A9tournerait=20le=20Sprint=204=20?= =?UTF-8?q?(=C2=AB=205=20portails=20r=C3=B4le=20=C2=BB=20=3D=20les=205=20m?= =?UTF-8?q?=C3=A9tier=20=C2=B7=20`plateforme`=20=3D=20console=20technique)?= =?UTF-8?q?.=20PI=C3=88GE=20:=20check=5Fartifacts=20ne=20prouve=20QUE=20MA?= =?UTF-8?q?NIFEST=3D=3Dbuild=20(byte-for-byte)=20et=20la=20boucle=20Nb=20r?= =?UTF-8?q?=C3=B4les=20existante=20n'it=C3=A8re=20que=20MANIFEST=E2=86=92R?= =?UTF-8?q?EADME=20(capte=20le=20MANQUANT)=20=E2=80=94=20donc=20RENVERSER?= =?UTF-8?q?=20un=20flag=20dans=20la=20prose=20=C2=B7=20MENTIR=20sur=20le?= =?UTF-8?q?=20split=20=C2=B7=20AJOUTER=20une=20ligne=20fant=C3=B4me=20d'un?= =?UTF-8?q?=20profil=20invent=C3=A9=20restait=20=C2=AB=20vert=20trompeur?= =?UTF-8?q?=20=C2=BB=20qu'aucune=20suite=20tests/=20(qui=20teste=20des=20F?= =?UTF-8?q?ONCTIONS=20de=20builder,=20pas=20la=20prose=20du=20README)=20n'?= =?UTF-8?q?attrape=20=E2=86=92=20gate=20=C3=A9tendu=20(bloc=20roleprofile?= =?UTF-8?q?=20de=20ci/check=5Freadme=5Fclaims.sh)=20:=20(1)=20colonne=20?= =?UTF-8?q?=C2=AB=20Type=20=C2=BB=20recomput=C3=A9e=20du=20flag=20`metier`?= =?UTF-8?q?,=20exig=C3=A9e=20EXACTE=20par=20profil=20(fin=20du=20wildcard)?= =?UTF-8?q?=20;=20(2)=20prose=20=C2=AB=20N=20profils=20(X=20m=C3=A9tier=20?= =?UTF-8?q?+=20Y=20technique)=20=C2=BB=20=3D=3D=20comptes=20MANIFEST=20;?= =?UTF-8?q?=20(3)=20identit=C3=A9=20d'ensemble=20=E2=80=94=20nombre=20de?= =?UTF-8?q?=20lignes=20de=20profil=20de=20la=20table=20=3D=3D=20|profiles|?= =?UTF-8?q?=20(aucune=20ligne=20FANT=C3=94ME=20absente=20du=20contrat=20;?= =?UTF-8?q?=20compl=C3=A8te=20le=20sens=20MANIFEST=E2=86=92README)=20;=20(?= =?UTF-8?q?4)=20coh=C3=A9rences=20crois=C3=A9es=20mordant=20un=20MANIFEST?= =?UTF-8?q?=20INTERNEMENT=20incoh=C3=A9rent=20:=20`portails=5Fmetier=20+?= =?UTF-8?q?=20portails=5Ftechniques=20=3D=3D=20role=5Fprofiles=20=3D=3D=20?= =?UTF-8?q?|profiles|`=20ET=20ces=20comptes=20recomptent=20les=20flags=20`?= =?UTF-8?q?metier`=20r=C3=A9els.=205=20morsures=20v=C3=A9rifi=C3=A9es=20:?= =?UTF-8?q?=20README=20classe=20plateforme=20m=C3=A9tier=20(technique?= =?UTF-8?q?=E2=86=92m=C3=A9tier)=20=C2=B7=20README=20classe=20ventes=20tec?= =?UTF-8?q?hnique=20(m=C3=A9tier=E2=86=92technique)=20=C2=B7=20prose=20spl?= =?UTF-8?q?it=205=E2=86=924=20m=C3=A9tier=20(6/4/1=20MAIS=206/5/1)=20?= =?UTF-8?q?=C2=B7=20ligne=20FANT=C3=94ME=20OTO=20Portail=20Fantome=20(7?= =?UTF-8?q?=E2=89=A06)=20=C2=B7=20MANIFEST=20portails=5Fmetier=205?= =?UTF-8?q?=E2=86=926=20sans=20toucher=20aux=20flags=20(incoh=C3=A9rence?= =?UTF-8?q?=20interne,=20flags=20r=C3=A9els=3D5)=20;=20restaur=C3=A9=20=3D?= =?UTF-8?q?=20green=20:=206=20Types=20=3D=3D=20MANIFEST=20=C2=B7=20split?= =?UTF-8?q?=206/5/1=20=C2=B7=206=20lignes=20=C2=B7=20comptes=20coh=C3=A9re?= =?UTF-8?q?nts=20=C2=B7=20exit=200.=20=C3=89tat=20courant=20:=20aucun=20cl?= =?UTF-8?q?assement=20p=C3=A9rim=C3=A9=20(anti-invention=20#6,=20rien=20?= =?UTF-8?q?=C3=A0=20r=C3=A9=C3=A9crire)=20=E2=80=94=20le=20d=C3=A9faut=20e?= =?UTF-8?q?st=20la=20surface=20ungated.=20ci/README.md=20(table=20+=20d?= =?UTF-8?q?=C3=A9tail=20=C2=AB=202e=20surface=20roleprofile=5Fgen=20=C2=BB?= =?UTF-8?q?)=20mis=20=C3=A0=20jour=20=C2=B7=207=20gates=20re-verts.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- 05_activity_log/2026-07-31.md | 35 +++++++++++++++++++ ci/README.md | 15 ++++++-- ci/check_readme_claims.sh | 66 +++++++++++++++++++++++++++++++---- 3 files changed, 108 insertions(+), 8 deletions(-) diff --git a/05_activity_log/2026-07-31.md b/05_activity_log/2026-07-31.md index f2054ad..6d79d5d 100644 --- a/05_activity_log/2026-07-31.md +++ b/05_activity_log/2026-07-31.md @@ -2215,3 +2215,38 @@ verrouillés) · `guard_constraints` · `validate_json`. `check_ci_integrity` · `check_regression` · `guard_constraints` · `validate_json`). - **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo). - **Auto-score 4Big** : 96/100. + +## Sprint 8 · buffer · RBAC roleprofile_gen (2e surface) · classement métier/technique +- **Constat** : le README `rbac/roleprofile_gen` porte une table « Profils générés » + (6 `Role Profile` : nom · portail · **Type** métier/technique · Nb rôles). Le bloc + RBAC existant ne gatait que le COMPTE `Nb rôles` par ligne + total bijectif — sa + colonne « Type » était un **WILDCARD** (`[^|]*`). 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`). Aveugle aussi au **split** en prose + « 6 profils (5 métier + 1 technique) » (README:93) — gaté nulle part. +- **Data-derived** : le classement = flag booléen `metier` par profil de + `MANIFEST.profiles` ; le split = `counts.portails_metier`(5) / `portails_techniques`(1) + / `role_profiles`(6) — MANIFEST recomputé de `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). +- **Gate ajouté** (`ci/check_readme_claims.sh`, bloc roleprofile) : (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 d'un profil inventé absent du contrat ; complète le sens MANIFEST→README de + la boucle Nb rôles qui capte le MANQUANT) ; (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) + capté · README classe `ventes` technique (métier→technique) capté · prose split + 5→4 métier capté (6/4/1 MAIS 6/5/1) · ligne FANTÔME `OTO Portail Fantome` (7≠6) + captée · MANIFEST `portails_metier` 5→6 sans toucher aux flags (incohérence interne) + capté (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 (colonne wildcardée). `ci/README.md` (table + détail + « 2e surface roleprofile_gen ») mis à jour · **7 gates re-verts** + (`check_readme_claims` · `check_artifacts` · `check_docs` · `check_ci_integrity` · + `check_regression` · `guard_constraints` · `validate_json`). +- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo). +- **Auto-score 4Big** : 96/100. diff --git a/ci/README.md b/ci/README.md index 16985a5..720df61 100644 --- a/ci/README.md +++ b/ci/README.md @@ -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 + énumération des transitions à séparation des pouvoirs `{action | allow_self_approval=0}` recomputée de `workflow.json`) **et** de l'app mobile Expo (onglets/rôles/langues/identifiants a_confirmer + énumération ordonnée des onglets recomputée de `role_navigation`, bijection onglet⇔portail) **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 + énumération des open items `control→dimension→propriétaire` recomputée de `audit_report.open_items`) **et** du run-book de déploiement VPS (phases ×2 dans la fiche devops + confirmations préalables count & énumération dans le README) **et** du run-sheet de démo (nombre de modules cités dans le diagramme du README) **et** du montage Chat OTOIA par portail (`Custom Block`/configs runtime/portails, tous = 5 aujourd'hui, ×2 docs) **et** du module de recette `qa/acceptance` (couple livrables de sprint + métriques succès MVP, ×2 docs de module au-delà du README d'entrée) **et** du barème de l'auditeur 4Big (nombre de critères en prose + poids par critère de la table + « generator (N critères) », recomputés de `quality_report.criteria`) **et** de la carte de renormalisation par archétype de l'auditeur 4Big (critères EXCLUS de `data_room`/`parser`/`contract`, recomputés des `checks` par archétype de `quality_report.modules`) **et** du contrat de format de l'e-NCF fiscal (`E + tipoeCF(2) + secuencia(10) = 13` : préfixe/longueurs des segments/longueur totale, ×3 README + fiche `erpnext_backend`, recomputés du bloc `e_ncf` d'`ecf_plan.json` + cohérences croisées `1+tipo+seq==longueur` & `|regex digits|==tipo+seq`) **et** de la cross-cohérence du barème de commissions `crm/commissions` (états brouillon exclus recomputés de `{doc_status=0}` de `workflow.json` + champs de base recomputés de `{base_field}` de `commission_plan.json` + invariant 3 « commissionne seulement des états soumis ») **et** du catalogue RBAC `rbac/fixtures_gen` (DocTypes `custom` à créer énumérés == `MANIFEST.custom_doctypes_a_confirmer` + séparation des pouvoirs : le rôle unique porteur de `set_user_permissions` == `custom_docperm.json`) **et** de la composition du graphe schema.org SEO (les `@type` du `@graph` — `Organization` racine + une `Residence` par projet — recomputés de `seo_schema_org.json` + bijection listing⇔projet & absence d'`offers`) **et** de la carte hreflang SEO (les langues de contenu `alternate` `FR/EN/ES` + `x-default` recomputées de `seo_hreflang.json` + cohérences croisées : carte uniforme sur toutes les pages · `x-default` présent partout · langue par défaut ∈ langues de contenu · `x-default`.href == `canonical` == href de la langue par défaut) **et** du catalogue de projets du DocType `crm/dossier_vente` (le Select `projet` — les 9 projets `P01..P09` avec libellés — **ancré sur** la section « ## Projets » de `CLAUDE.md`, recomputé de CLAUDE.md : ensemble + ordre == options du DocType byte-gaté, plage `P01..P09` + mot d'ancrage dans le README, catalogue contigu/sans-doublon en bonus) **et** du catalogue des DocTypes `custom` à créer du portail `frontend/portails` (2e surface : le §hand-off VPS énumère `CONFOTUR Application`/`Faisabilité`/`Publiciste Log` == `MANIFEST.doctypes_custom_a_creer` recomputé, set-diff normalisé + cohérences croisées : catalogue trié/sans-doublon/non-vide · égal à l'UNION des `doctypes_custom` par workspace) == 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 + énumération des transitions à séparation des pouvoirs `{action | allow_self_approval=0}` recomputée de `workflow.json`) **et** de l'app mobile Expo (onglets/rôles/langues/identifiants a_confirmer + énumération ordonnée des onglets recomputée de `role_navigation`, bijection onglet⇔portail) **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 + énumération des open items `control→dimension→propriétaire` recomputée de `audit_report.open_items`) **et** du run-book de déploiement VPS (phases ×2 dans la fiche devops + confirmations préalables count & énumération dans le README) **et** du run-sheet de démo (nombre de modules cités dans le diagramme du README) **et** du montage Chat OTOIA par portail (`Custom Block`/configs runtime/portails, tous = 5 aujourd'hui, ×2 docs) **et** du module de recette `qa/acceptance` (couple livrables de sprint + métriques succès MVP, ×2 docs de module au-delà du README d'entrée) **et** du barème de l'auditeur 4Big (nombre de critères en prose + poids par critère de la table + « generator (N critères) », recomputés de `quality_report.criteria`) **et** de la carte de renormalisation par archétype de l'auditeur 4Big (critères EXCLUS de `data_room`/`parser`/`contract`, recomputés des `checks` par archétype de `quality_report.modules`) **et** du contrat de format de l'e-NCF fiscal (`E + tipoeCF(2) + secuencia(10) = 13` : préfixe/longueurs des segments/longueur totale, ×3 README + fiche `erpnext_backend`, recomputés du bloc `e_ncf` d'`ecf_plan.json` + cohérences croisées `1+tipo+seq==longueur` & `|regex digits|==tipo+seq`) **et** de la cross-cohérence du barème de commissions `crm/commissions` (états brouillon exclus recomputés de `{doc_status=0}` de `workflow.json` + champs de base recomputés de `{base_field}` de `commission_plan.json` + invariant 3 « commissionne seulement des états soumis ») **et** du catalogue RBAC `rbac/fixtures_gen` (DocTypes `custom` à créer énumérés == `MANIFEST.custom_doctypes_a_confirmer` + séparation des pouvoirs : le rôle unique porteur de `set_user_permissions` == `custom_docperm.json`) **et** de la composition du graphe schema.org SEO (les `@type` du `@graph` — `Organization` racine + une `Residence` par projet — recomputés de `seo_schema_org.json` + bijection listing⇔projet & absence d'`offers`) **et** de la carte hreflang SEO (les langues de contenu `alternate` `FR/EN/ES` + `x-default` recomputées de `seo_hreflang.json` + cohérences croisées : carte uniforme sur toutes les pages · `x-default` présent partout · langue par défaut ∈ langues de contenu · `x-default`.href == `canonical` == href de la langue par défaut) **et** du catalogue de projets du DocType `crm/dossier_vente` (le Select `projet` — les 9 projets `P01..P09` avec libellés — **ancré sur** la section « ## Projets » de `CLAUDE.md`, recomputé de CLAUDE.md : ensemble + ordre == options du DocType byte-gaté, plage `P01..P09` + mot d'ancrage dans le README, catalogue contigu/sans-doublon en bonus) **et** du catalogue des DocTypes `custom` à créer du portail `frontend/portails` (2e surface : le §hand-off VPS énumère `CONFOTUR Application`/`Faisabilité`/`Publiciste Log` == `MANIFEST.doctypes_custom_a_creer` recomputé, set-diff normalisé + cohérences croisées : catalogue trié/sans-doublon/non-vide · égal à l'UNION des `doctypes_custom` par workspace) **et** du classement métier/technique des `Role Profile` de `rbac/roleprofile_gen` (2e surface : la colonne « Type » de la table — auparavant un WILDCARD — recompute le flag `metier` par profil de `MANIFEST.profiles` + prose « N profils (X métier + Y technique) » == `counts.role_profiles/portails_metier/portails_techniques` + identité d'ensemble : lignes de profil de la table == `|profiles|` (aucun profil fantôme) + cohérences croisées : `portails_metier + portails_techniques == role_profiles` et recomptent les flags réels) == 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 @@ -172,7 +172,18 @@ tous les volets en une phrase) et la ventilation « 28 `entite` · 16 `groupe` `own` · 4 `equipe` ». Chaque nombre est recomputé depuis le `out/MANIFEST.json` du volet (recalculé du contrat `rbac_50_roles.json` à chaque build, byte-gaté par `check_artifacts`) : ajouter un rôle/DocType ferait dériver **les trois** READMEs -en silence sinon. +en silence sinon. **2ᵉ surface `rbac/roleprofile_gen`** : la table « Profils +générés » ne gatait que le **compte** `Nb rôles` par ligne — sa colonne « Type » +(`métier`/`technique`) était un **wildcard** (`[^|]*`), donc un portail technique +classé « métier » (ou l'inverse) passait **vert**. On recompute le classement +depuis le flag booléen `metier` de `MANIFEST.profiles` et on exige la colonne +« Type » **exacte** par profil, on gate la prose « **6 profils** (5 métier + 1 +technique) » contre `counts.role_profiles/portails_metier/portails_techniques`, on +exige que le **nombre de lignes de profil** de la table == `|profiles|` (aucune +ligne fantôme d'un profil inventé absent du contrat), et en bonus deux +cohérences croisées qui mordent un MANIFEST **internement** incohérent : +`portails_metier + portails_techniques == role_profiles` **et** ces comptes +recomptent les flags `metier` réels des profils. Même classe enfin côté **SEO** : le total et la ventilation par langue des mots-clés (« **258** mots-clés (fr=87 · en=87 · es=84) ») étaient transcrits **à diff --git a/ci/check_readme_claims.sh b/ci/check_readme_claims.sh index be8a973..6b5f8cf 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -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"