[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
+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))} » "
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)
PY
rc=$?