[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:
@@ -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=$?
|
||||
|
||||
Reference in New Issue
Block a user