[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:
Claude Code DTP Worker
2026-08-01 11:43:06 +00:00
parent c4487fdcf7
commit dce409f0f6
3 changed files with 174 additions and 1 deletions
+64
View File
@@ -1219,3 +1219,67 @@ schema.org/hreflang amont — zéro duplication #6) :
aucune écriture dans un `out/` ⇒ 0 dérive d'artefact ; l'injection réelle des aucune écriture dans un `out/` ⇒ 0 dérive d'artefact ; l'injection réelle des
balises hreflang/JSON-LD dans les pages reste côté agent Frontend/SEO / VPS). balises hreflang/JSON-LD dans les pages reste côté agent Frontend/SEO / VPS).
- **Auto-score 4Big** : 96/100. - **Auto-score 4Big** : 96/100.
## Session `20260801_113314` · Buffer S8 · RBAC/RBAC_50_ROLES_SPEC §4 — colonne « Entité » de la « Cartographie rôles ↔ entités » ancrée sur CLAUDE.md §Entités rbac_50_roles.json
**Contexte** : Sprint 8 · buffer (roadmap fonctionnellement close ; poursuite de la
série anti-dérive CLAUDE.md #6, double classe « ANCRAGE à CLAUDE.md »
([[claude-md-constant-anchor-gate]]) **et** « identité d'ensemble recomputée du
contrat RBAC » [[rbac-spec-portail-count-gate]]). Même patron que les entités CONFOTUR
`entite_porteuse` (session 053228, §Entités) et les comptes SPEC §3/§7 — appliqué à
une **section encore ungated de la MÊME SPEC** : la table §4.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/rbac/RBAC_50_ROLES_SPEC.md` §4 « Cartographie rôles ↔ entités » —
la **colonne de tête** liste les entités porteuses (`entite_principale`) : les **7
entités NOMMÉES** du mandat (`WAF` (holding) · `WA SRL` · `AC Arias Cuevas` ·
`Consortium ECR DR` (paymaster RH) · `Helios RD` (marque publique) · `Ploutos`
(patrimoine/monétaire) · `9060 QC` (World Activities Cdn.)) + le périmètre consolidé
« **Groupe** (consolidé) ».
- **Deux sources faisant autorité** : (i) **CLAUDE.md §Entités** — l'orthographe + le
descripteur parenthétique de chaque entité nommée ; (ii) `rbac_50_roles.json`
(byte-gaté via les 3 MANIFEST) — l'ENSEMBLE des `entite_principale` réellement portées
par les 50 rôles (distinct = 11 Helios RD · 11 WA SRL · 10 AC Arias Cuevas · 8 Groupe ·
5 9060 QC · 3 WAF · 1 Ploutos · 1 Consortium ECR DR = 50).
- Piège : 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 (menu périmé), 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 » qu'aucune suite
`tests/` (FONCTIONS RBAC, jamais la prose de la SPEC) n'attrape.
- **État courant** : aucune ligne périmée — §4 == distinct contrat (8 entités) · les 7
descripteurs == CLAUDE.md §Entités · aucune entité décorative (anti-invention #6, rien
à réécrire). Le défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc `1sexies` après le §7, réutilise
`src_roles`/`spec_rd`/`SPEC_RD` déjà en scope · extraction fraîche de CLAUDE.md §Entités
— `_nfc` défini plus bas, non requis, entités ASCII) :
- **(a) ENSEMBLE** — entités de la colonne §4 (`| **Entité** (desc)? |`) == distinct
`entite_principale` du contrat (set-diff : `manquantes` = portées mais hors §4 ·
`fantômes` = listées mais jamais portées).
- **(b) ANCRAGE** — chaque entité NOMMÉE de CLAUDE.md §Entités (tokens en gras) est une
ligne §4 ET son descripteur parenthétique == CLAUDE.md **EXACT** (absence comprise :
`WA SRL`/`AC Arias Cuevas` = sans descripteur des deux côtés).
- **(c)** chaque entité nommée du mandat est PORTÉE ≥1× par le contrat (`cm_ents ⊆
contrat`, aucune décorative).
- Section `## 4.` INTROUVABLE = claim absent = régression #6.
**8 morsures vérifiées** (4 côté contrat/CLAUDE.md · 4 côté SPEC) : contrat +rôle porté
par entité `Fantasia` absente de §4 (`(a)` manquante) · contrat `Ploutos→Ploutos SRL`
sur tous les rôles (`(a)` fantôme+manquante · `(c)` `Ploutos` décorative) · CLAUDE.md
`9060 QC→9061 QC` (`(b)` absent · `(c)` décorative) · CLAUDE.md descripteur
`(holding)→(holding co)` (`(b)` mismatch) · SPEC §4 `AC Arias Cuevas→AC Arias` (`(a)`
fantôme+manquante · `(b)` absent) · SPEC §4 descripteur `marque publique→marque privée`
(`(b)` mismatch) · SPEC §4 retire la ligne « Groupe » (`(a)` manquante) · SPEC supprime
l'en-tête `## 4.` (INTROUVABLE) ; restauré = green : 9 ✓ (`(a)` 8 entités · `(b)` 7
descripteurs · `(c)` 7 portées) · exit 0. Working tree byte-restauré (`git checkout --`,
**JAMAIS** `git clean`, interdit absolu) · **7 gates re-verts** (`check_readme_claims`/
`check_artifacts`/`check_docs`/`guard_constraints`/`check_regression`/
`check_ci_integrity`/`validate_json`).
- `ci/README.md` (clause de la ligne récap du pipeline + paragraphe détaillé « Colonne
Entité de la §4 ancrée sur CLAUDE.md §Entités ») mis à jour.
- **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 ; le seed réel des
rôles/entités `bench` reste côté VPS).
- **Auto-score 4Big** : 96/100.
+31 -1
View File
File diff suppressed because one or more lines are too long
+79
View File
@@ -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"(profils=portails distincts / métier=hors plateforme / rôles) "
f"→ corriger la SPEC") 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 ------------ # 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: