[DTP-Worker] Sprint 8 · buffer · RBAC/SPEC §4 : gate d'IDENTITÉ de la colonne « Entité » de la « Cartographie rôles ↔ entités » (7 entités nommées + Groupe consolidé) doublement ancré sur CLAUDE.md §Entités (descripteurs) ⨯ rbac_50_roles.json (ENSEMBLE des entite_principale)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+31
-1
File diff suppressed because one or more lines are too long
@@ -925,6 +925,85 @@ if src_roles is not None and spec_rd is not None:
|
||||
f"(profils=portails distincts / métier=hors plateforme / rôles) "
|
||||
f"→ corriger la SPEC")
|
||||
|
||||
# 1sexies) RBAC_50_ROLES_SPEC.md §4 — colonne « Entité » ancrée sur CLAUDE.md §Entités
|
||||
# La table §4 « Cartographie rôles ↔ entités » liste, en colonne de tête, les entités
|
||||
# porteuses (`entite_principale`) du RBAC : les 7 entités NOMMÉES du mandat (WAF · WA
|
||||
# SRL · AC Arias Cuevas · Consortium ECR DR · Helios RD · Ploutos · 9060 QC) + le
|
||||
# périmètre consolidé « Groupe ». Cette colonne a DEUX sources faisant autorité — (i)
|
||||
# CLAUDE.md §Entités (l'orthographe + le descripteur parenthétique de chaque entité
|
||||
# nommée) et (ii) rbac_50_roles.json (l'ENSEMBLE des `entite_principale` réellement
|
||||
# portées par les 50 rôles) — mais AUCUN gate ne la touchait : le §3 gate les comptes
|
||||
# par PORTAIL, le §7 les comptes du run-book, les blocs fiches l'`entite` d'UN rôle ;
|
||||
# tous AVEUGLES à l'identité de CETTE liste. AJOUTER au contrat un rôle porté par une
|
||||
# entité absente de §4, RENOMMER une entité dans CLAUDE.md §Entités (9060 QC→9061 QC)
|
||||
# ou DÉRIVER un descripteur laisse la SPEC contredire le mandat/le contrat EN SILENCE
|
||||
# — « vert trompeur » de la MÊME classe que les entités CONFOTUR (§Entités) ou la
|
||||
# marque SEO org, qu'aucune suite tests/ (FONCTIONS RBAC, jamais la prose de la SPEC)
|
||||
# n'attrape.
|
||||
# GATE : (a) ENSEMBLE des entités de §4 == distinct `entite_principale` du contrat
|
||||
# (set-diff : ni fantôme ni manquante) ; (b) ANCRAGE — chaque entité NOMMÉE de
|
||||
# CLAUDE.md §Entités est une ligne de §4 avec son descripteur parenthétique EXACT
|
||||
# (absence comprise) ; (c) chaque entité nommée du mandat est PORTÉE ≥1× par le
|
||||
# contrat (cm_ents ⊆ contrat). Un claim absent échoue AUSSI (traçabilité #6).
|
||||
if src_roles is not None and spec_rd is not None:
|
||||
try:
|
||||
_cm4 = open("CLAUDE.md", encoding="utf-8").read()
|
||||
_sec4 = re.search(r"^## Entités\s*\n(.*?)(?=^## )", _cm4, re.S | re.M)
|
||||
if not _sec4:
|
||||
raise ValueError("section « ## Entités » introuvable")
|
||||
# (entité → descripteur parenthétique | None), dans l'ordre du mandat
|
||||
cm4 = [(m.group(1).strip(), (m.group(2).strip() if m.group(2) else None))
|
||||
for m in re.finditer(r"\*\*([^*]+?)\*\*(?:\s*\(([^)]+)\))?", _sec4.group(1))]
|
||||
if not cm4:
|
||||
raise ValueError("aucune entité en gras dans §Entités")
|
||||
if len({e for e, _ in cm4}) != len(cm4):
|
||||
raise ValueError(f"entités non uniques : {[e for e, _ in cm4]}")
|
||||
except (OSError, ValueError) as e:
|
||||
bad(f"RBAC SPEC §4 · CLAUDE.md §Entités illisible/inattendu : {e}"); cm4 = None
|
||||
_m4 = re.search(r"## 4\..*?(?=## 5\.)", spec_rd)
|
||||
if _m4 is None:
|
||||
bad(f"RBAC SPEC §4 · section « ## 4. » INTROUVABLE dans {SPEC_RD} "
|
||||
f"(claim absent = régression #6)"); sec4 = None
|
||||
else:
|
||||
sec4 = _m4.group(0)
|
||||
if cm4 is not None and sec4 is not None:
|
||||
# Colonne de tête : « | **Entité** (desc)? | » (le header non-gras et le
|
||||
# séparateur ne matchent pas ; la colonne « Exemples de rôles » n'a pas de gras)
|
||||
spec4 = [(m.group(1).strip(), (m.group(2).strip() if m.group(2) else None))
|
||||
for m in re.finditer(r"\| \*\*([^*|]+?)\*\*(?: \(([^)|]+)\))? \|", sec4)]
|
||||
spec4_names = {n for n, _ in spec4}
|
||||
contract_ents = {r.get("entite_principale") for r in src_roles}
|
||||
# (a) ENSEMBLE §4 == distinct entite_principale du contrat
|
||||
_miss = sorted(contract_ents - spec4_names) # portées mais absentes de §4
|
||||
_phan = sorted(spec4_names - contract_ents) # listées mais jamais portées
|
||||
if _miss or _phan:
|
||||
bad(f"RBAC SPEC §4 · (a) colonne Entité ≠ distinct `entite_principale` du "
|
||||
f"contrat — manquantes={_miss} · fantômes={_phan} → corriger la SPEC "
|
||||
f"(ou régénérer si le contrat a changé)")
|
||||
else:
|
||||
good(f"RBAC SPEC §4 · (a) {len(spec4_names)} entités == distinct "
|
||||
f"`entite_principale` rbac_50_roles.json (ni fantôme ni manquante)")
|
||||
# (b) ANCRAGE — chaque entité NOMMÉE de CLAUDE.md est une ligne §4 · descripteur exact
|
||||
spec4_map = dict(spec4)
|
||||
for _e, _d in cm4:
|
||||
if _e not in spec4_map:
|
||||
bad(f"RBAC SPEC §4 · (b) entité canonique `{_e}` (CLAUDE.md §Entités) "
|
||||
f"ABSENTE de la colonne §4 → SPEC périmée")
|
||||
elif spec4_map[_e] != _d:
|
||||
bad(f"RBAC SPEC §4 · (b) `{_e}` — descripteur §4 {spec4_map[_e]!r} ≠ "
|
||||
f"CLAUDE.md §Entités {_d!r} → aligner sur le mandat")
|
||||
else:
|
||||
good(f"RBAC SPEC §4 · (b) `{_e}` ({_d or 'sans descripteur'}) == "
|
||||
f"CLAUDE.md §Entités")
|
||||
# (c) chaque entité nommée du mandat est PORTÉE ≥1× par le contrat
|
||||
_unused = sorted(e for e, _ in cm4 if e not in contract_ents)
|
||||
if _unused:
|
||||
bad(f"RBAC SPEC §4 · (c) entités du mandat JAMAIS portées par un rôle : "
|
||||
f"{_unused} → entité décorative (contrat ou §Entités à revoir)")
|
||||
else:
|
||||
good(f"RBAC SPEC §4 · (c) les {len(cm4)} entités nommées du mandat sont "
|
||||
f"portées ≥1× par le contrat")
|
||||
|
||||
# 2) apply_plan — ligne « Génération réelle » agrégeant les 3 volets ------------
|
||||
AP_RD = f"{D}/rbac/apply_plan/README.md"
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user