[DTP-Worker] Sprint 8 · buffer · Faisabilité/fiches agents : les DEUX FICHES D'IDENTITÉ des rôles RBAC faisabilite-rendu-3d (03_agents/rendu/AGENT.md:34) et faisabilite-ifc-speckle (03_agents/ifc_speckle/AGENT.md:34) — la carte que l'agent ERPNext Backend lit pour SEEDER le rôle porteur des rendus / de l'export IFC→GLB — recopiaient EN PROSE, DEPUIS le contrat rbac_50_roles.json, les attributs data-derived du rôle (erpnext_role_name · nom_fr/modules côté rendu · portail · entite_principale · niveau · les DEUX permissions_cibles File R/W/create + Faisabilité R ou R/W) SANS AUCUN gate d'IDENTITÉ. Le README frontend/portails n'est gaté que sur des COMPTES de rôles par Workspace, aveugles à l'identité de CE rôle.

Même classe que la Fiche Mobile (session précédente) — surface 03_agents/*/AGENT.md transcrivant à la main les attributs d'un rôle depuis le contrat, gatée (si tant est) seulement sur des comptes agrégés. Gate ajouté (bloc « Fiches Faisabilité », config-table par fiche : fichier · role_id · attributs restitués) : par attribut recomputé du contrat, prose EXACTE exigée ; permissions rendues en ORDRE CANONIQUE (read→R · write→W · create/delete/… en toutes lettres — élargir/rétrécir change la chaîne, `R/W/create` ne satisfait plus `R/W/create/delete`) ; verbe inconnu ⇒ ROUGE ; claim absent échoue AUSSI.

#6 (honnêteté) vérifié AVANT édition : la DESCRIPTION de ces deux fiches est une PARAPHRASE éditoriale (le contrat porte « (… — CLAUDE.md) » / « les modèles » que la fiche condense) — donc NON gatée verbatim (ne rien réécrire de correct pour un gate), seuls les attributs structurés (transcrits à l'exact) sont contraints. État courant : aucun attribut structuré périmé (12 recomputes == contrat).

11 morsures vérifiées (5 contrat + 6 fiche) : rendu Faisabilité +write (R→R/W) · rendu niveau 2→3 · rendu WA SRL→WAF · rendu rôle renommé · ifc Faisabilité R/W→R · fiche rendu File R/W/create→…/delete · fiche ifc R/W→R/W/create · fiche rendu module Faisabilité→Selling · fiche ifc WA SRL→WAF · fiche rendu nom_fr retiré (absent) · ligne contact ifc supprimée (INTROUVABLE) ; restauré vert, 7 gates re-verts. ci/README.md + activity log MAJ.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-08-01 01:38:49 +00:00
parent 3bce40ea80
commit bc58809e7d
3 changed files with 188 additions and 1 deletions
+62
View File
@@ -183,3 +183,65 @@ absolu) · **7 gates re-verts**.
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; le seed réel édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; le seed réel
du rôle `bench` reste côté VPS). du rôle `bench` reste côté VPS).
- **Auto-score 4Big** : 96/100. - **Auto-score 4Big** : 96/100.
## Session `20260801_013208` · Buffer S8 · Domaine Faisabilité/fiches agents : les **deux FICHES D'IDENTITÉ** des rôles RBAC `faisabilite-rendu-3d` (`03_agents/rendu/AGENT.md:34`) et `faisabilite-ifc-speckle` (`03_agents/ifc_speckle/AGENT.md:34`) — la carte que l'agent ERPNext Backend lit pour **seeder** le rôle porteur des rendus / de l'export IFC→GLB — recopiaient EN PROSE, DEPUIS le contrat `rbac_50_roles.json`, les attributs data-derived du rôle (`erpnext_role_name` · `nom_fr`/`modules` côté rendu · `portail` · `entite_principale` · `niveau` · les **deux** `permissions_cibles` `File` R/W/create + `Faisabilité` R ou R/W) SANS AUCUN gate d'IDENTITÉ. Le README `frontend/portails` n'est gaté que sur des COMPTES de rôles par Workspace, AVEUGLES à l'identité de CE rôle.
**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). MÊME classe que la Fiche
Mobile de la session précédente (`plateforme-mobile`) — surface `03_agents/*/AGENT.md`
qui transcrit à la main les attributs d'un rôle depuis `rbac_50_roles.json`, gatée (si
tant est) seulement sur des comptes agrégés — appliquée aux **deux fiches faisabilité
construction** encore ungated. Mémoire `agent-fiche-role-attrs-ungated` mise à jour.
**Dérive silencieuse fermée** :
- `03_agents/rendu/AGENT.md:34` — cellule « Ce qu'il fixe » du rôle
`faisabilite-rendu-3d` : `OTO Faisabilité Rendu 3D` · « Spécialiste Rendu 3D » ·
portail `construction` · `WA SRL` · niveau 2 · module `OTOV7 Faisabilité` ·
perms `File` R/W/create + `Faisabilité` R.
- `03_agents/ifc_speckle/AGENT.md:34` — cellule du rôle `faisabilite-ifc-speckle` :
`OTO Faisabilité IFC Speckle` · portail `construction` · `WA SRL` · niveau 2 ·
perms `File` R/W/create + `Faisabilité` **R/W** (écriture en plus vs rendu).
- Source faisant autorité : `05_deliverables_mvp/rbac/rbac_50_roles.json` (contrat
RBAC schéma-validé · les 3 MANIFEST RBAC en dérivent, byte-gatés par
`check_artifacts`). Plusieurs blocs du gate lisent déjà ce contrat directement.
- Piège : la seule vérification existante (`tests/`) teste des **FONCTIONS** de
résolution RBAC, jamais la PROSE d'une fiche. Élargir `Faisabilité` R→R/W (le rôle
Rendu 3D gagnerait l'écriture sur un DocType qu'il ne doit que LIRE), changer
`niveau`/`entite_principale`, renommer le rôle ou permuter un module laisse la fiche
périmée pendant que le contrat dit autre chose → l'agent câblerait le mauvais rôle.
- **Constat #6 (honnêteté)** : la **DESCRIPTION** de ces deux fiches est une
**PARAPHRASE éditoriale** (le contrat porte « (… — CLAUDE.md) » / « les modèles »
que la fiche condense) — vérifié programmatiquement AVANT toute édition. Elle n'est
donc **PAS** gatée verbatim (ne rien réécrire de correct pour satisfaire un gate) ;
seuls les attributs **structurés**, eux transcrits à l'exact, sont contraints.
- **État courant** : aucun attribut structuré périmé — les 12 recomputes recoupent
le contrat exactement (anti-invention #6, rien à réécrire). Le défaut est la
**surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Fiches Faisabilité »
avant `sys.exit`) : table de config par fiche (fichier · role_id · liste des attributs
RESTITUÉS). Pour chaque fiche : (1) **par attribut**`erpnext_role_name` · `nom_fr` ·
`portail` · `entite_principale` · `niveau` · `modules` · les deux permissions
recomputés du contrat, prose EXACTE exigée (regex par attribut, lookaheads
anti-préfixe niveau/module/perms) ; (2) **permissions** — chaîne
« \`Doctype\` R/W/create + \`Autre\` R » rendue en **ordre canonique** (read→R ·
write→W · create/delete/submit/… en toutes lettres) : élargir OU rétrécir change la
chaîne attendue (`R/W/create` ne satisfait plus `R/W/create/delete`) ; verbe inconnu ⇒
ROUGE. Un claim absent échoue AUSSI (ligne de contact INTROUVABLE = régression #6).
**11 morsures vérifiées** (5 côté contrat = fiche périmée · 6 côté fiche = fiche
fausse) : contrat rendu `Faisabilité +write` (sigle `R→R/W`) · contrat rendu
`niveau 2→3` · contrat rendu `WA SRL→WAF` · contrat rendu rôle renommé · contrat ifc
`Faisabilité R/W→R` · fiche rendu perm `File R/W/create→…/delete` (over-claim) · fiche
ifc perm `R/W→R/W/create` (over-claim) · fiche rendu module `Faisabilité→Selling` ·
fiche ifc `WA SRL→WAF` · fiche rendu `nom_fr` retiré (claim absent) · ligne de contact
ifc supprimée (INTROUVABLE) ; restauré = green : 12 attributs == contrat · exit 0.
Working tree byte-restauré (`git checkout --`, **JAMAIS** `git clean`, interdit
absolu) · **7 gates re-verts**.
- `ci/README.md` (table récap du pipeline + paragraphe détaillé « deux fiches
d'identité des rôles RBAC faisabilité ») 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 `bench` reste côté VPS).
- **Auto-score 4Big** : 96/100.
+29 -1
View File
File diff suppressed because one or more lines are too long
+97
View File
@@ -3308,6 +3308,103 @@ if mob_role is not None and mob_txt is not None:
bad(f"Fiche Mobile · {MOB} — description « {_plain(md.group(1))} » " bad(f"Fiche Mobile · {MOB} — description « {_plain(md.group(1))} » "
f"MAIS contrat « {exp_desc} » → description périmée, régénérer") f"MAIS contrat « {exp_desc} » → description périmée, régénérer")
# ============================================================================
# Fiches Faisabilité (03_agents/rendu/AGENT.md · 03_agents/ifc_speckle/AGENT.md) ·
# les FICHES D'IDENTITÉ des rôles RBAC `faisabilite-rendu-3d` et
# `faisabilite-ifc-speckle` — même classe de « vert trompeur » que la Fiche Mobile.
# ----------------------------------------------------------------------------
# La ligne « Contact in-repo (vérifié) » de chaque fiche (rendu:34 · ifc_speckle:34)
# recopie À LA MAIN, DEPUIS le contrat rbac_50_roles.json (source de vérité RBAC,
# schéma-validé · byte-gaté par les 3 MANIFEST recomputés), les attributs data-derived
# du rôle : son `erpnext_role_name`, son `portail`, son `entite_principale`, son
# `niveau`, ses `modules` (rendu), son `nom_fr` (rendu) et ses `permissions_cibles`
# (deux doctypes chacune : `File` R/W/create + `Faisabilité` R ou R/W). C'est la carte
# que l'agent ERPNext Backend lit pour seeder le rôle porteur des rendus / de l'export
# IFC→GLB. AUCUN gate ne comparait ces valeurs au contrat : le README frontend/portails
# n'est gaté que sur des COMPTES de rôles par Workspace, AVEUGLES à l'identité de CE
# rôle. Éditer le contrat — élever le `scope_donnees`, changer le `niveau`,
# l'`entite_principale`, renommer le rôle, ajouter/retirer un module ou élargir la
# permission (`Faisabilité` R→R/W : le rôle Rendu 3D gagnerait l'écriture sur le
# DocType Faisabilité qu'il ne doit que LIRE) — laisse la fiche périmée en silence
# pendant que le contrat dit autre chose ⇒ l'agent câblerait le mauvais rôle
# (l'invention même que #6 interdit). Aucune suite tests/ (qui teste des FONCTIONS de
# résolution RBAC, pas la prose d'une fiche) n'attrape ce « vert trompeur ». On
# RECOMPUTE chaque attribut RESTITUÉ par la fiche depuis le contrat (zéro duplication)
# et on exige que la fiche l'énonce EXACTEMENT. Les verbes de permission sont rendus
# dans un ORDRE CANONIQUE (read→R · write→W · create/delete/submit/… en toutes
# lettres, ordre fixe) : élargir OU rétrécir une permission change la chaîne attendue
# (`R/W` ne satisfait plus `R/W/create`, ni l'inverse) ; verbe inconnu ⇒ ROUGE
# (étendre le gate). Un claim absent échoue AUSSI (traçabilité). NB : contrairement
# à la Fiche Mobile, la DESCRIPTION de ces deux fiches est une PARAPHRASE éditoriale
# (le contrat porte « (… — CLAUDE.md) » / « les modèles » que la fiche condense) —
# elle n'est donc PAS gatée verbatim (ne rien réécrire de correct pour un gate · #6) ;
# seuls les attributs STRUCTURÉS, eux transcrits à l'exact, sont contraints.
# ============================================================================
_CANON_VERB = [("read", "R"), ("write", "W"), ("create", "create"),
("delete", "delete"), ("submit", "submit"), ("cancel", "cancel"),
("amend", "amend"), ("print", "print"), ("report", "report")]
FICHES_FAIS = [
{"file": "03_agents/rendu/AGENT.md", "rid": "faisabilite-rendu-3d",
"attrs": ["role", "nom_fr", "portail", "entite", "niveau", "modules", "perms"]},
{"file": "03_agents/ifc_speckle/AGENT.md", "rid": "faisabilite-ifc-speckle",
"attrs": ["role", "portail", "entite", "niveau", "perms"]},
]
try:
_fais_roles = load("rbac/rbac_50_roles.json")["roles"]
except (OSError, KeyError, ValueError, TypeError) as e:
bad(f"Fiches Faisabilité · rbac_50_roles.json illisible/inattendu : {e}")
_fais_roles = None
if _fais_roles is not None:
for fc in FICHES_FAIS:
rid, tag = fc["rid"], fc["file"].split("/")[1]
cand = [r for r in _fais_roles if r.get("id") == rid]
ftxt = read_norm(fc["file"])
if ftxt is None:
continue
if len(cand) != 1:
bad(f"Fiche {tag} · le contrat porte {len(cand)} rôle(s) `{rid}` "
f"(attendu exactement 1) → contrat changé de forme, vérifier")
continue
r = cand[0]
seg = re.search(rf"→ rôle `{re.escape(rid)}` \|(.+?)\|", ftxt)
if seg is None:
bad(f"Fiche {tag} · {fc['file']} — ligne de contact du rôle `{rid}` "
f"INTROUVABLE (un claim absent est une régression · #6)")
continue
cell = seg.group(1)
# permissions_cibles → chaîne « `Doctype` R/W/create + `Autre` R », ordre canon
_perm_parts, _unknown = [], set()
for p in (r.get("permissions_cibles") or []):
acts = set(p.get("actions", []))
_unknown |= acts - {k for k, _ in _CANON_VERB}
sig = "/".join(tok for k, tok in _CANON_VERB if k in acts)
_perm_parts.append(f"`{p.get('doctype')}` {sig}")
perm_str = " + ".join(_perm_parts)
if _unknown:
bad(f"Fiche {tag} · rôle `{rid}` — verbe(s) inconnu(s) "
f"{sorted(_unknown)} dans le contrat → étendre le gate")
mods = r.get("modules") or []
mod_pat = (rf"module `{re.escape(mods[0])}`(?![+`\w])" if len(mods) == 1
else r"modules " + r"\+".join(f"`{re.escape(m)}`" for m in mods))
_PATS = {
"role": (rf"rôle ERPNext \*\*`{re.escape(r['erpnext_role_name'])}`\*\*",
r["erpnext_role_name"]),
"nom_fr": (rf"« {re.escape(r['nom_fr'])} »", r["nom_fr"]),
"portail": (rf"portail {re.escape(r['portail'])}(?![\w])", r["portail"]),
"entite": (rf"(?<![\w]){re.escape(r['entite_principale'])}(?![\w])",
r["entite_principale"]),
"niveau": (rf"niveau {r['niveau']}(?!\d)", str(r["niveau"])),
"modules": (mod_pat, "+".join(mods)),
"perms": (rf"perms {re.escape(perm_str)}(?![\w/])", perm_str),
}
for a in fc["attrs"]:
pat, exp = _PATS[a]
if re.search(pat, cell):
good(f"Fiche {tag} · rôle `{rid}` — {a} « {exp} » == contrat")
else:
bad(f"Fiche {tag} · {fc['file']} — {a} : la fiche ne cite pas "
f"« {exp} » du contrat rbac_50_roles.json (périmé ?) → régénérer")
sys.exit(1 if FAIL else 0) sys.exit(1 if FAIL else 0)
PY PY
rc=$? rc=$?