[DTP-Worker 20260802_063519] Sprint 8 · buffer · RBAC SPEC §2 : ancrage de l'encadré « Défense en profondeur » (NOM du détenteur de set_user_permissions · scope · famille) sur l'artefact byte-gaté

Extension du bloc « RBAC fixtures » (points 4 & 5) : la 3e surface prose du fait
de sécurité set_user_permissions (SPEC §2 encadré + §6 row) était NON gatée. Le
test test_rbac impose scope/famille à tout détenteur mais jamais le NOM du rôle ni
la prose §2. Réutilise fx_sup (singleton déjà byte-gaté · pas de doublon d'unicité,
#5) comme nom-autorité + src_roles pour scope/famille.

Prouvé M6 SILENT-GREEN : rename détenteur dans le contrat + régen artefacts =>
check_artifacts VERT mais §2 name-gate RED (autorité = artefact, pas cohérence SPEC).
6 morsures adversariales, 6 gates re-verts exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-08-02 06:48:01 +00:00
parent 215c9e6f25
commit 335d449346
2 changed files with 120 additions and 0 deletions
+66
View File
@@ -4516,6 +4516,72 @@ if fx_cdt is not None:
(f" · en trop={extra6}" if extra6 else "")
bad(f"RBAC fixtures · {SPEC_RD} §6 — énumère {sorted(got6)} MAIS "
f"MANIFEST {sorted(exp6)}{detail6} → régénérer la SPEC")
# 4) RBAC_50_ROLES_SPEC.md §2 — encadré « Défense en profondeur » : 3ᵉ surface prose
# du MÊME fait de sécurité. Les points (2)/(3) ancrent la prose du README fixtures
# (« émis que pour le rôle **NAME** ») + le catalogue §6 ; le §2 « Modèle RBAC »
# ferme sur « **réservé** au seul rôle `<NAME>` (`scope_donnees = groupe`,
# `famille = plateforme`) — séparation des pouvoirs vérifiée par test automatisé » :
# TROIS attributs (NOM · scope · famille) qu'AUCUN gate ne touchait. Le test
# `test_rbac::test_set_user_permissions_reserve_admin` impose scope/famille à TOUT
# détenteur mais JAMAIS le NOM du rôle ni cette prose §2 → renommer le détenteur
# (contrat + régénération fixtures/out + audit 4Big) laisse le §2 citer un rôle
# DISPARU, VERT (check_artifacts ne voit qu'un HASH de TAILLE du doc, la SPEC n'est
# pas un out/*.json). On RÉUTILISE fx_sup (singleton déjà byte-gaté ci-dessus · zéro
# re-dérivation de l'unicité #5) comme NOM-autorité et src_roles (contrat byte-gaté)
# pour scope/famille. La glose « (gestion des habilitations) » N'est PAS gatée.
if spec_rd is not None and len(fx_sup) == 1:
_su_hn = fx_sup[0]
_su_role = next((r for r in (src_roles or [])
if r.get("erpnext_role_name") == _su_hn), None)
_su_def = re.search(r"\*\*Défense en profondeur\*\*(.*?)"
r"vérifiée par test automatis", spec_rd)
if _su_def is None:
bad(f"RBAC fixtures · {SPEC_RD} §2 — encadré « **Défense en profondeur** … "
f"vérifiée par test automatisé » INTROUVABLE (anti-évaporation #6)")
else:
_su_reg = _su_def.group(1)
_su_toks = re.findall(r"`([^`]+)`", _su_reg)
# ancrage : le pouvoir lui-même doit être nommé dans l'encadré
if "set_user_permissions" not in _su_toks:
bad(f"RBAC fixtures · {SPEC_RD} §2 — encadré ne nomme pas "
f"`set_user_permissions` → mal ancré")
# (a) NOM du détenteur cité == le seul porteur du flag (byte-gaté)
if _fx_acc(_su_hn) in {_fx_acc(t) for t in _su_toks}:
good(f"RBAC fixtures · {SPEC_RD} §2 — détenteur nommé « {_su_hn} » == le "
f"seul porteur de set_user_permissions (custom_docperm.json)")
else:
_su_named = [t for t in _su_toks
if "=" not in t and t != "set_user_permissions"]
bad(f"RBAC fixtures · {SPEC_RD} §2 — nomme {_su_named} MAIS "
f"set_user_permissions réservé à {_su_hn!r} → régénérer la SPEC "
f"(rôle renommé côté contrat ?)")
# (b)/(c) scope + famille cités == attributs du détenteur (contrat byte-gaté)
if _su_role is None:
bad(f"RBAC fixtures · {SPEC_RD} §2 — détenteur {_su_hn!r} INTROUVABLE dans "
f"rbac_50_roles.json (fixtures ↔ contrat désynchronisés)")
else:
for _su_lbl in ("scope_donnees", "famille"):
_su_m = re.search(rf"{_su_lbl}\s*=\s*(\w+)", _su_reg)
_su_want = _su_role.get(_su_lbl)
if _su_m is None:
bad(f"RBAC fixtures · {SPEC_RD} §2 — `{_su_lbl} = …` INTROUVABLE "
f"dans l'encadré")
elif _su_m.group(1) == _su_want:
good(f"RBAC fixtures · {SPEC_RD} §2 — {_su_lbl} `{_su_m.group(1)}` "
f"== détenteur (rbac_50_roles.json)")
else:
bad(f"RBAC fixtures · {SPEC_RD} §2 — encadré dit {_su_lbl}="
f"`{_su_m.group(1)}` MAIS le détenteur {_su_hn!r} a "
f"`{_su_want}` → aligner sur le contrat")
# 5) §6 row « Séparation des pouvoirs » cite le pouvoir réservé (ancre présente)
_su_m6 = re.search(r"## 6\..*?(?=## 7\.)", spec_rd)
if _su_m6 and "Séparation des pouvoirs" in _su_m6.group(0) \
and "set_user_permissions" in _su_m6.group(0):
good(f"RBAC fixtures · {SPEC_RD} §6 — row « Séparation des pouvoirs » cite "
f"bien `set_user_permissions` réservé (ancre présente)")
else:
bad(f"RBAC fixtures · {SPEC_RD} §6 — row « Séparation des pouvoirs » / "
f"`set_user_permissions` réservé INTROUVABLE → ancre évaporée (#6)")
# ============================================================================
# SEO · la COMPOSITION du graphe schema.org (JSON-LD) — un nœud racine