[DTP-Worker] Sprint 8 · buffer · RBAC/SPEC §7 : gate d'IDENTITÉ des comptes du run-book d'application (28 UP templates · 6 Role Profiles / 5 métier · 50 rôles bijectifs) ancrés sur rbac_50_roles.json

Surface data-derived non gatée : la NOTE §7 «  … livré en-repo » de
RBAC_50_ROLES_SPEC.md restitue 3 comptes du run-book (28 templates Company /
6 profils (5 métier + plateforme technique) / 50 rôles bijectifs) sur une
surface DISTINCTE des READMEs userperm_gen/roleprofile_gen déjà gatés et du
§3 (per-portail). Nouveau bloc 1quinquies dans check_readme_claims.sh :
recompute depuis le contrat byte-gaté (|scope==entite|, |portails distincts|,
|métier hors plateforme|, |roles|), blockquote « > » neutralisé avant match.
6 morsures vérifiées (dont contrat entite→groupe = silent green), restauré
vert, 7 gates PASS. ci/README.md (récap + paragraphe détaillé) mis à jour.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-08-01 10:11:39 +00:00
parent 7ce0f7b1e7
commit 7e8bb9a0de
3 changed files with 134 additions and 1 deletions
+55
View File
@@ -1051,3 +1051,58 @@ check_readme_claims · guard_constraints · check_regression · check_ci_integri
- **Hors périmètre worker (VPS · #8)** : néant (correction d'un doc en-repo + gate - **Hors périmètre worker (VPS · #8)** : néant (correction d'un doc en-repo + gate
bash/python3 stdlib ; l'unique artefact `out/` touché a été régénéré à l'identique). bash/python3 stdlib ; l'unique artefact `out/` touché a été régénéré à l'identique).
- **Auto-score 4Big** : 96/100. - **Auto-score 4Big** : 96/100.
## Session `20260801_100301` · Buffer S8 · RBAC/RBAC_50_ROLES_SPEC §7 — comptes du run-book d'application ancrés sur le contrat rbac_50_roles.json
**Contexte** : Sprint 8 · buffer (roadmap fonctionnellement close ; poursuite de la
série anti-dérive CLAUDE.md #6). Même classe que le §3 gaté à la session précédente
(`093301`, per-portail) : une **2ᵉ surface de la MÊME SPEC** transcrivant à la main des
comptes data-derived, déjà gatés ailleurs mais AVEUGLES à cette note (mémoire
[[rbac-spec-portail-count-gate]]).
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/rbac/RBAC_50_ROLES_SPEC.md` §7 — la NOTE « ✅ … est **livré**
en-repo » restitue TROIS comptes du run-book d'application sur une **surface
DISTINCTE** des READMEs `userperm_gen` / `roleprofile_gen` (déjà gatés) et du §3
(ventilation par portail, bloc `1quater`) : « **28** templates `Company` (portée
`entite`) » · « **6 profils** (**5** portails métier + console technique
`plateforme`) » · « **50 rôles de façon bijective** ».
- Source faisant autorité : `rbac/rbac_50_roles.json` (contrat, byte-gaté par
`check_artifacts`). Recomputes : 28 == |`scope_donnees == entite`| (= nombre de
`User Permission` templates) · 6 == |portails distincts| (1 `Role Profile` par
portail) · 5 == portails **métier** (hors `plateforme`) · 50 == |rôles|.
- Piège #6 : BUMPER une portée `entite``groupe` (le décompte de templates change) ou
AJOUTER/RETIRER un portail (le décompte de profils change) dans le contrat périme la
note EN SILENCE pendant que les READMEs de volet et le §3 restent verts → l'agent
ERPNext Backend lirait un run-book promettant un décompte faux. Aucune suite `tests/`
(FONCTIONS de génération/résolution RBAC, jamais la prose de la SPEC) n'attrape ce
« vert trompeur ».
- **État courant** : aucun compte périmé — 28/6/5/50 recoupent le contrat exactement
(anti-invention #6, rien à réécrire). Le défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « 1quinquies) RBAC_50_ROLES_SPEC §7 »
après le bloc §3) : réutilise `src_roles` (contrat) + `spec_rd` (déjà chargés par le
§3, zéro duplication). Le blockquote markdown `> ` de la note survit au collapse de
`read_norm` et fragmente la prose (« 28 > templates ») ⇒ **neutralisé** (`spec7 =
spec_rd.replace("> ", "")`) avant match. Exige : (a) « N templates `Company` (portée
`entite`) » == |scope==entite| ; (b) « **N profils** (M portails métier + console
technique `plateforme`) couvrant les **K rôles de façon bijective** » == |portails
distincts|/|métier hors plateforme|/|rôles|. Garde anti-typo : `plateforme` doit exister
au contrat (sinon la note « console technique plateforme » est fausse). Un claim absent
échoue AUSSI (INTROUVABLE = régression #6).
**6 morsures vérifiées** (restauré vert après chacune) : SPEC `28→27` (prose périmée) ·
SPEC `6 profils→7` · SPEC `5 portails métier→4` · SPEC `50→49` · **contrat** une portée
`entite→groupe` (28→27 · README stale = le vrai *silent green* · « SPEC dit 28 MAIS
compte 27 ») · SPEC retire « 28 templates » (claim absent = INTROUVABLE) ; restauré =
green : UP templates 28 == |entite| · Role Profiles 6 (métier 5) bijectifs sur 50 ==
contrat · exit 0. Working tree byte-restauré (`git checkout --`, **JAMAIS** `git clean`,
interdit absolu) · **7 gates re-verts** (`check_readme_claims`/`check_ci_integrity`/
`check_docs`/`guard_constraints`/`check_artifacts`/`check_regression`/`validate_json`).
- `ci/README.md` (clause de la table récap du pipeline + paragraphe détaillé « Comptes
du run-book d'application `RBAC_50_ROLES_SPEC.md` §7 ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
aucune écriture dans un `out/` ⇒ 0 dérive d'artefact ; l'application `bench` réelle
reste côté agent ERPNext Backend / VPS).
- **Auto-score 4Big** : 96/100.
+23 -1
View File
File diff suppressed because one or more lines are too long
+56
View File
@@ -869,6 +869,62 @@ if src_roles is not None and spec_rd is not None:
bad(f"RBAC SPEC §3 · total — {SPEC_RD} dit {mt.group(1)} MAIS " bad(f"RBAC SPEC §3 · total — {SPEC_RD} dit {mt.group(1)} MAIS "
f"rbac_50_roles.json compte {exp_tot} → corriger la SPEC") f"rbac_50_roles.json compte {exp_tot} → corriger la SPEC")
# 1quinquies) RBAC_50_ROLES_SPEC.md §7 — comptes du run-book d'application --------
# La NOTE §7 (« ✅ … livré en-repo ») restitue, sur une SURFACE DISTINCTE des READMEs
# userperm_gen / roleprofile_gen (déjà gatés), TROIS comptes data-derived du run-book
# d'application : « 28 templates `Company` (portée `entite`) », « 6 profils (5 portails
# métier + console technique `plateforme`) » et « 50 rôles de façon bijective ». Ces
# nombres sont recopiés À LA MAIN dans la SPEC ; le bloc §3 ne gate QUE la ventilation
# par portail, et les gates userperm/roleprofile ne regardent QUE leurs propres READMEs
# — AVEUGLES à cette note. BUMPER une portée `entite`→`groupe` dans le contrat (le
# nombre de `User Permission` templates change), AJOUTER/RETIRER un portail (le nombre
# de `Role Profile` change) ou casser la bijection laisse la note PÉRIMÉE en silence
# pendant que le contrat dit autre chose → l'agent ERPNext Backend lirait un run-book
# qui promet un décompte faux. On RECOMPUTE les 3 comptes depuis rbac_50_roles.json
# (source de vérité, byte-gatée par check_artifacts) : 28 == |scope==entite|, 6 ==
# |portails distincts| (= 1 Role Profile par portail), 5 == portails métier (hors
# `plateforme`, la console technique), 50 == |rôles|. Un claim absent échoue AUSSI.
if src_roles is not None and spec_rd is not None:
# La note §7 vit dans un blockquote (`> `) : les marqueurs survivent au collapse
# de read_norm et fragmentent la prose (« 28 > templates »). On les retire pour
# ce bloc (aucun `> ` littéral hors blockquote dans cette SPEC ; les flèches sont
# des `→`). Le §3 (tables sans blockquote) n'en a pas besoin.
spec7 = spec_rd.replace("> ", "")
up_entite = sum(1 for r in src_roles if r.get("scope_donnees") == "entite")
portails = {r.get("portail") for r in src_roles}
metier = portails - {"plateforme"}
if "plateforme" not in portails:
bad("RBAC SPEC §7 · portail technique `plateforme` ABSENT du contrat "
"rbac_50_roles.json → la note « console technique plateforme » est fausse")
exp_up = str(up_entite)
m_up = re.search(r"(\d+) templates `Company` \(portée `entite`\)", spec7)
if m_up is None:
bad(f"RBAC SPEC §7 · « N templates Company (portée entite) » INTROUVABLE "
f"dans {SPEC_RD} (attendu {exp_up})")
elif m_up.group(1) == exp_up:
good(f"RBAC SPEC §7 · UP templates {m_up.group(1)} == |scope==entite| "
f"rbac_50_roles.json ({exp_up})")
else:
bad(f"RBAC SPEC §7 · {SPEC_RD} dit {m_up.group(1)} templates Company MAIS "
f"rbac_50_roles.json compte {exp_up} rôles `entite` → corriger la SPEC")
exp_prof, exp_metier, exp_bij = str(len(portails)), str(len(metier)), str(len(src_roles))
m_pr = re.search(
r"\*\*(\d+) profils\*\* \((\d+) portails métier \+ console technique "
r"`plateforme`\) couvrant les \*\*(\d+) rôles de façon bijective\*\*", spec7)
if m_pr is None:
bad(f"RBAC SPEC §7 · « N profils (M portails métier …) couvrant les K rôles "
f"de façon bijective » INTROUVABLE dans {SPEC_RD} "
f"(attendu {exp_prof}/{exp_metier}/{exp_bij})")
elif m_pr.groups() == (exp_prof, exp_metier, exp_bij):
good(f"RBAC SPEC §7 · Role Profiles {m_pr.group(1)} (métier {m_pr.group(2)}) "
f"bijectifs sur {m_pr.group(3)} rôles == rbac_50_roles.json "
f"({exp_prof}/{exp_metier}/{exp_bij})")
else:
bad(f"RBAC SPEC §7 · {SPEC_RD} dit {'/'.join(m_pr.groups())} MAIS "
f"rbac_50_roles.json compte {exp_prof}/{exp_metier}/{exp_bij} "
f"(profils=portails distincts / métier=hors plateforme / rôles) "
f"→ corriger la SPEC")
# 2) apply_plan — ligne « Génération réelle » agrégeant les 3 volets ------------ # 2) apply_plan — ligne « Génération réelle » agrégeant les 3 volets ------------
AP_RD = f"{D}/rbac/apply_plan/README.md" AP_RD = f"{D}/rbac/apply_plan/README.md"
try: try: