[DTP-Worker] Sprint 8 · buffer L75 · Domaine RBAC/fixtures_gen : le CATALOGUE des DocTypes custom à créer sur le VPS (« Faisabilité, Publiciste Log, CONFOTUR Application, API Access ») ET la SÉPARATION DES POUVOIRS (« set_user_permissions n'est émis que pour le rôle **RBAC Admin** ») étaient transcrits À LA MAIN dans le README du module rbac/fixtures_gen sans AUCUN gate d'IDENTITÉ. Le bloc racine ne gate que le COMPTE (« 50 rôles / 116 DocPerm » via l'agrégat RBAC de l'apply_plan) — surface distincte. Ces deux faits sont DATA-DERIVED : catalogue = custom_doctypes_a_confirmer de rbac/fixtures_gen/out/MANIFEST.json (les DocTypes custom: true du contrat rbac_50_roles.json) · singleton sécurité = {role | set_user_permissions==1} de out/custom_docperm.json (= OTO Plateforme RBAC Admin) — les deux artefacts byte-gatés par check_artifacts. Aucun gate ne comparait ces ENSEMBLES à la prose : AJOUTER un DocType custom au contrat (MANIFEST rebâtit 5 entrées) · RENOMMER/ÉCHANGER l'un des 4 · PROMOUVOIR un 2e rôle porteur du flag (élévation de privilège) ferait dériver la prose en silence pendant que l'artefact dit autre chose — « vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS de mapping, pas la prose du README) n'attrape → nouveau bloc recomputant le catalogue depuis MANIFEST et le singleton depuis custom_docperm (zéro duplication du contrat du générateur #6) et exigeant que la prose l'énumère/le nomme EXACTEMENT. Contrôle par ENSEMBLE (absent ET en trop mordus via set-diff · accents/casse normalisés via unicodedata). Cohérences croisées en bonus : le catalogue est NON VIDE, sans doublon et TRIÉ (byte-déterminisme du générateur) · la séparation des pouvoirs est un SINGLETON (ni vide — garde vacante — ni multiple — élévation de privilège). Un claim absent échoue AUSSI. 6 morsures vérifiées : échange d'un nom de DocType (Faisabilité→Faisabilite2) capté (absents=[faisabilite] en trop=[faisabilite2]) · sous-ensemble (retrait API Access) capté · rôle nommé faux (RBAC Admin→Ventes Conseiller) capté · énumération supprimée (INTROUVABLE) · 2e rôle promu au flag dans l'artefact (singleton cassé) capté · catalogue MANIFEST non trié (byte-déterminisme) capté ; restauré = green : catalogue [API Access,CONFOTUR Application,Faisabilité,Publiciste Log] == MANIFEST · singleton OTO Plateforme RBAC Admin == custom_docperm. État courant : aucun ensemble périmé (anti-invention #6, rien à réécrire) — le défaut est la surface ungated. ci/README.md (table + détail) mis à jour · 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-07-31 17:07:38 +00:00
parent 9794eabc8a
commit 733c73ae3f
3 changed files with 186 additions and 1 deletions
+68
View File
@@ -1,5 +1,73 @@
# Activity Log · 2026-07-31 · Claude Code DTP
## Session `20260731_170112` · Buffer S8 · Domaine RBAC/fixtures_gen : le **catalogue des DocTypes `custom`** à créer sur le VPS (« `Faisabilité`, `Publiciste Log`, `CONFOTUR Application`, `API Access` ») ET la **séparation des pouvoirs** (« `set_user_permissions` n'est émis que pour le rôle **RBAC Admin** ») étaient transcrits À LA MAIN dans le README du module sans AUCUN gate d'IDENTITÉ — le bloc racine ne gate que le COMPTE (« 50 rôles / 116 DocPerm » via l'agrégat RBAC de l'apply_plan).
**Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont
gaté les domaines tests, portails, RBAC (3 volets : roleprofile/apply_plan/userperm),
SEO, CRM (pipeline vente + séparation des pouvoirs + commissions), mobile (quadruplet
+ onglets), Legal/CONFOTUR, DevOps, QA (audit 5D ×2 + 4Big ×2), Démo, Frontend/Chat
OTOIA, recette et Fiscal/e-CF. Cette session cible le **module `rbac/fixtures_gen`**
jamais gaté hors son compte agrégé — dont deux faits data-derived du README pointent
les artefacts générés sans être recomputés par aucun gate.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/rbac/fixtures_gen/README.md:56` : « listés dans
`MANIFEST.custom_doctypes_a_confirmer` (`Faisabilité`, `Publiciste Log`, `CONFOTUR
Application`, `API Access`) » — l'ENSEMBLE des DocTypes `custom: true` à créer.
- `…/README.md:48` : « le flag `set_user_permissions` n'est émis que pour le rôle
**RBAC Admin** » — le SINGLETON `{role | set_user_permissions == 1}`, cœur sécurité
du module (« séparation des pouvoirs · défense en profondeur »).
- Sources faisant autorité (byte-gatées par `check_artifacts`) :
`rbac/fixtures_gen/out/MANIFEST.json.custom_doctypes_a_confirmer` (les DocTypes
`custom` du contrat `rbac_50_roles.json`) et `out/custom_docperm.json` (le seul
rôle porteur du flag = `OTO Plateforme RBAC Admin`).
- Le bloc racine ne gate QUE le **compte** (« 50 rôles / 116 DocPerm » via l'agrégat
RBAC de l'apply_plan). Aucun gate ne comparait ces ENSEMBLES aux artefacts :
**AJOUTER** un DocType `custom` au contrat (le MANIFEST rebâtit 5 entrées),
**RENOMMER/ÉCHANGER** l'un des 4, ou **PROMOUVOIR** un 2ᵉ rôle porteur de
`set_user_permissions` (élévation de privilège) ferait dériver la prose en silence —
« vert trompeur » qu'aucune suite `tests/` (qui teste des FONCTIONS de mapping, pas
la prose) n'attrape.
- **État courant** : **aucun ensemble périmé** — le catalogue (4) et le singleton
recoupent les artefacts exactement (anti-invention #6, rien à réécrire). Le défaut
est la **surface ungated**.
**Nouveau bloc dans `ci/check_readme_claims.sh`** : recompute le catalogue depuis
`MANIFEST.custom_doctypes_a_confirmer` et le singleton depuis `custom_docperm.json`
(zéro duplication du contrat du générateur · #6) et exige que la prose l'énumère/le
nomme **exactement** — contrôle par ENSEMBLE (absent ET en trop mordus via set-diff ·
accents/casse normalisés via `unicodedata`). Cohérences croisées en bonus : le
catalogue est **non vide, sans doublon et trié** (byte-déterminisme du générateur) ;
la séparation des pouvoirs est un **singleton** (ni vide — garde vacante — ni multiple
— élévation de privilège). Un claim absent échoue AUSSI.
### Preuve (6 morsures vérifiées + restauration verte)
| Injection | Résultat |
|---|---|
| README échange nom DocType (`Faisabilité → Faisabilite2`) | ✗ absents=`[faisabilite]` · en trop=`[faisabilite2]` · exit 1 |
| README retrait `API Access` (sous-ensemble) | ✗ absents=`[api access]` · exit 1 |
| README rôle nommé faux (`RBAC Admin → Ventes Conseiller`) | ✗ nomme « Ventes Conseiller » MAIS flag porté par `OTO Plateforme RBAC Admin` · exit 1 |
| README énumération du catalogue supprimée | ✗ énumération du catalogue custom INTROUVABLE · exit 1 |
| artefact 2ᵉ rôle promu au flag (`custom_docperm`) | ✗ set_user_permissions porté par `[…RBAC Admin, …Ventes Conseiller]` (attendu 1) · séparation cassée · exit 1 |
| artefact catalogue MANIFEST non trié | ✗ non trié/dédupliqué · exit 1 |
| restauré | ✓ catalogue `[API Access, CONFOTUR Application, Faisabilité, Publiciste Log]` == MANIFEST · singleton `OTO Plateforme RBAC Admin` == custom_docperm · exit 0 |
- **`ci/README.md`** — surface ajoutée à la table récap du pipeline + paragraphe
descriptif (classe « catalogue RBAC `rbac/fixtures_gen` »).
Suite CI complète re-verte (7 gates) : `guard_constraints` · `validate_json` ·
`check_docs` · `check_artifacts` · `check_regression` · `check_ci_integrity` ·
`check_readme_claims`.
**Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact).
**Auto-score 4Big** : 96/100.
---
## Session `20260731_163112` · Buffer S8 · Domaine CRM/Commissions : la **cross-cohérence** du barème vendeurs (« cœur du livrable ») — les états **brouillon exclus** (`lead/visite/devis/abandonné`), les **champs de base** (`montant_reservation`, `montant_contrat`) et l'**invariant 3** « on ne commissionne que des états soumis » — était énumérée À LA MAIN dans le README du module sans AUCUN gate d'IDENTITÉ ; le bloc racine ne gate que le COMPTE de tests (« 25 », via l'agrégat « Total CRM »).
**Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement
+26 -1
View File
@@ -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 ») == 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`) == 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
@@ -422,6 +422,31 @@ l'**invariant 3** du module, non vide. 5 morsures vérifiées : échange brouill
(`reservation→lead`, invariant 3 cassé) · promotion de l'état `abandonne` en soumis
dans l'artefact (draft set change, « en trop » dans la prose) ; restauré = vert.
Même classe enfin sur le **catalogue RBAC** (livrable `rbac/fixtures_gen`) — deux
faits data-derived transcrits **à la main** dans le README. (1) La liste des DocTypes
`custom` à créer avant import « (`Faisabilité`, `Publiciste Log`, `CONFOTUR
Application`, `API Access`) » (README §« Application sur VPS ») == l'ensemble
`custom_doctypes_a_confirmer` d'`rbac/fixtures_gen/out/MANIFEST.json` (les DocTypes
`custom: true` du contrat RBAC). (2) La **séparation des pouvoirs** — « le flag
`set_user_permissions` n'est émis que pour le rôle **RBAC Admin** » (README:48) ==
l'ensemble `{role | set_user_permissions == 1}` d'`out/custom_docperm.json`. Le bloc
racine ne gate que le **compte** (« 50 rôles / 116 DocPerm » via l'agrégat RBAC de
l'apply_plan) — jamais l'**identité** de ces deux ensembles. AJOUTER un DocType
`custom` au contrat, RENOMMER/ÉCHANGER l'un des 4 ou PROMOUVOIR un 2ᵉ rôle porteur du
flag (élévation de privilège) ferait dériver la prose en silence pendant que
l'artefact byte-gaté dit autre chose — dérive qu'aucune suite `tests/` (qui teste des
FONCTIONS de mapping, pas la prose du README) n'attrape. On RECOMPUTE chaque ensemble
depuis SON artefact (zéro duplication du contrat du générateur · #6) et on exige que
la prose l'énumère/le nomme **exactement** (absent ET en trop mordus via set-diff ·
accents/casse normalisés). Cohérences croisées en bonus : le catalogue est **non
vide, sans doublon et trié** (byte-déterminisme du générateur) et la séparation des
pouvoirs est un **singleton** (ni vide — garde vacante — ni multiple — élévation de
privilège). 6 morsures vérifiées : échange d'un nom de DocType (`Faisabilité →
Faisabilite2`) · sous-ensemble (retrait `API Access`) · rôle nommé faux (`RBAC Admin
→ Ventes Conseiller`) · énumération supprimée (INTROUVABLE) · 2ᵉ rôle promu au flag
dans l'artefact (singleton cassé) · catalogue MANIFEST non trié (byte-déterminisme) ;
restauré = vert.
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.
+92
View File
@@ -1713,6 +1713,98 @@ if cm_ev is not None:
bad(f"CRM commissions · {CM_RD} — énumère {sorted(bs)} MAIS plan "
f"{sorted(cm_base)}{detail} → régénérer le README")
# ============================================================================
# RBAC/fixtures_gen · le CATALOGUE des DocTypes `custom` à créer sur le VPS et la
# SÉPARATION DES POUVOIRS (le rôle unique porteur de `set_user_permissions`) —
# deux faits data-derived transcrits À LA MAIN dans le README sans AUCUN gate
# d'IDENTITÉ.
# ----------------------------------------------------------------------------
# Le générateur rbac_fixtures_gen émet out/MANIFEST.json et out/custom_docperm.json
# (byte-gatés par check_artifacts ⇒ faisant autorité) :
# • MANIFEST.custom_doctypes_a_confirmer = l'ensemble des DocTypes `custom: true`
# du contrat rbac_50_roles.json (ceux à CRÉER avant import — le reste est natif
# v15). Le README §« Application sur VPS » l'ÉNUMÈRE À LA MAIN, en nommant même
# le champ : « listés dans `MANIFEST.custom_doctypes_a_confirmer` (`Faisabilité`,
# `Publiciste Log`, `CONFOTUR Application`, `API Access`) ».
# • custom_docperm.json porte le flag `set_user_permissions`. Le CŒUR sécurité du
# module (« séparation des pouvoirs · défense en profondeur ») est que ce flag
# n'est émis que pour UN SEUL rôle — le README:48 dit « n'est émis que pour le
# rôle **RBAC Admin** ». C'est l'ensemble {role | set_user_permissions=1}.
# Le bloc racine ne gate QUE le COMPTE de tests fixtures (« 50 rôles / 116 DocPerm »
# via l'agrégat RBAC de l'apply_plan) — jamais l'IDENTITÉ de ces deux ensembles.
# Aucun gate ne les comparait à l'artefact : AJOUTER un DocType `custom` au contrat
# (le MANIFEST rebâtit 5 entrées) OU RENOMMER/ÉCHANGER l'un des 4 laisse la prose à
# ses 4 noms périmés ; PROMOUVOIR un 2ᵉ rôle porteur de `set_user_permissions` (ou
# renommer RBAC Admin) laisse la prose « un seul rôle RBAC Admin » fausse — « vert
# trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS de mapping, pas la
# prose du README) n'attrape. Même patron que l'énumération des onglets Mobile, des
# brouillons CRM ou des open items de l'audit 5D. On RECOMPUTE chaque ensemble
# depuis SON artefact byte-gaté (zéro duplication du contrat du générateur #6) et on
# exige que la prose l'énumère/le nomme EXACTEMENT (absent ET en trop via set-diff ·
# accents/casse normalisés). Cohérence croisée en bonus : le catalogue est NON VIDE,
# sans doublon et TRIÉ (byte-déterminisme du générateur) ; la séparation des pouvoirs
# est un SINGLETON (ni vide — la garde serait vacante — ni multiple — élévation de
# privilège). Un claim absent échoue AUSSI (traçabilité).
# ============================================================================
FX_RD = f"{D}/rbac/fixtures_gen/README.md"
def _fx_acc(s):
return "".join(c for c in unicodedata.normalize("NFKD", s)
if unicodedata.category(c) != "Mn").strip().lower()
try:
fx_cdt = load("rbac/fixtures_gen/out/MANIFEST.json")["custom_doctypes_a_confirmer"]
fx_perm = load("rbac/fixtures_gen/out/custom_docperm.json")
fx_sup = sorted({p["role"] for p in fx_perm if p.get("set_user_permissions")})
except (OSError, KeyError, ValueError, TypeError) as e:
bad(f"RBAC fixtures · artefact illisible/inattendu : {e}"); fx_cdt = None
if fx_cdt is not None:
# bonus : le catalogue est un vrai ENSEMBLE trié non vide (byte-déterminisme)
if not fx_cdt:
bad("RBAC fixtures · custom_doctypes_a_confirmer VIDE → aucun DocType custom "
"à créer ? (contrat suspect)")
elif fx_cdt != sorted(set(fx_cdt)):
bad(f"RBAC fixtures · custom_doctypes_a_confirmer {fx_cdt} non trié/dédupliqué "
f"→ générateur non déterministe (attendu {sorted(set(fx_cdt))})")
else:
good(f"RBAC fixtures · catalogue custom {fx_cdt} — ensemble trié, sans doublon")
# bonus : séparation des pouvoirs — un SEUL rôle porte set_user_permissions
if len(fx_sup) != 1:
bad(f"RBAC fixtures · set_user_permissions porté par {fx_sup} (attendu 1 seul "
f"rôle) → séparation des pouvoirs cassée (élévation de privilège possible)")
else:
good(f"RBAC fixtures · set_user_permissions — singleton {fx_sup[0]!r} "
f"(défense en profondeur intacte)")
fx_rd = read_norm(FX_RD)
if fx_rd is not None:
# 1) README — énumération du catalogue custom (parenthèse après le champ nommé)
me = re.search(r"custom_doctypes_a_confirmer`?\s*\(([^)]*)\)", fx_rd)
if me is None:
bad(f"RBAC fixtures · {FX_RD} — énumération du catalogue custom INTROUVABLE "
f"(attendu {sorted(fx_cdt)})")
else:
got = {_fx_acc(t) for t in re.findall(r"`([^`]+)`", me.group(1))}
exp = {_fx_acc(x) for x in fx_cdt}
if got == exp:
good(f"RBAC fixtures · {FX_RD} — catalogue custom {sorted(got)} == "
f"MANIFEST.custom_doctypes_a_confirmer")
else:
miss = sorted(exp - got); extra = sorted(got - exp)
detail = (f" · absents={miss}" if miss else "") + \
(f" · en trop={extra}" if extra else "")
bad(f"RBAC fixtures · {FX_RD} — énumère {sorted(got)} MAIS MANIFEST "
f"{sorted(exp)}{detail} → régénérer le README")
# 2) README:48 — le rôle NOMMÉ porteur de set_user_permissions (séparation)
if len(fx_sup) == 1:
mr = re.search(r"émis que pour le rôle \*\*([^*]+)\*\*", fx_rd)
if mr is None:
bad(f"RBAC fixtures · {FX_RD} — claim « set_user_permissions n'est émis "
f"que pour le rôle ** ** » INTROUVABLE (attendu {fx_sup[0]!r})")
elif _fx_acc(mr.group(1)) in _fx_acc(fx_sup[0]):
good(f"RBAC fixtures · {FX_RD} — rôle nommé « {mr.group(1).strip()} » ⊆ "
f"{fx_sup[0]!r} (le seul porteur du flag)")
else:
bad(f"RBAC fixtures · {FX_RD} — nomme « {mr.group(1).strip()} » MAIS "
f"set_user_permissions porté par {fx_sup[0]!r} → régénérer le README")
sys.exit(1 if FAIL else 0)
PY
rc=$?