[DTP-Worker 20260801_190412] Sprint 8 · buffer · Fiches agents/RBAC : gate d'APPARTENANCE à roles_allowed du chat OTOIA — 3e surface d'appartenance (DISTINCTE du contrat rbac_50_roles.json ET du Has Role workspace.json) : QUI peut INVOQUER l'assistant OTOIA d'un portail — asserée en prose par rendu:36 (∈ roles_allowed construction, triple ancrage) + mobile:41 (∉ tout, assertion négative #6), recomputée de chat_otoia/out/chat_mount.json (byte-gaté) ; 6 morsures vérifiées, 7 gates re-verts
This commit is contained in:
@@ -2073,3 +2073,23 @@ spec == rapport · exit 0. Working tree byte-restauré (`git checkout --`, **JAM
|
||||
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; la confirmation
|
||||
des 4 open items réglementaires reste côté métier/VPS).
|
||||
- **Auto-score 4Big** : 96/100.
|
||||
|
||||
## Session `20260801_190412` · Buffer S8 · Domaine Fiches agents/RBAC : l'**appartenance à `roles_allowed` du chat OTOIA** — QUI peut **invoquer** l'assistant OTOIA embarqué dans un portail — asserée EN PROSE par 2 fiches (`03_agents/rendu/AGENT.md:36` positive · `03_agents/mobile/AGENT.md:41` négative) était **HORS de tout gate d'appartenance**. C'est une **3e surface d'appartenance** DISTINCTE des deux déjà gatées : le **contrat** `rbac_50_roles.json` (attributs du rôle, blocs Fiches Faisabilité/Mobile) et le **`Has Role`** `workspace.json` (accès console, bloc Fiches Workspace). Le contrôle d'invocation du chat vit dans un 3e artefact — `frontend/chat_otoia/out/chat_mount.json` (`roles_allowed` par portail) — que le bloc Chat OTOIA amont ne gate que sur l'IDENTITÉ persona/capabilities + la COMPOSITION (5 mounts ⇔ 5 portails), AVEUGLE à QUEL rôle figure dans quelle `roles_allowed`.
|
||||
|
||||
**Tâche** : **Sprint 8 · buffer** (QA/DevOps — série anti-dérive CLAUDE.md #6, roadmap fonctionnellement close). **Même patron EXACT** que le bloc de membership `Has Role` (juste au-dessus dans `check_readme_claims.sh`) — appliqué à un **artefact + une sémantique DIFFÉRENTS** : accès console (`Has Role`) ≠ droit d'invocation du chat (`roles_allowed`).
|
||||
|
||||
**Dérive silencieuse fermée** :
|
||||
- `03_agents/rendu/AGENT.md:36` — POSITIVE : rôle « OTO Faisabilité Rendu 3D » ∈ `roles_allowed` du portail NOMMÉ `construction` (déclenchement des rendus assistés).
|
||||
- `03_agents/mobile/AGENT.md:41` — NÉGATIVE d'honnêteté (#6) : rôle « OTO Plateforme Mobile » (famille `plateforme`) dans AUCUNE `roles_allowed` (le chat ne couvre que les portails métier).
|
||||
- Source faisant autorité : `frontend/chat_otoia/out/chat_mount.json[].roles_allowed` (byte-gaté par `check_artifacts`), `erpnext_role_name` résolu du contrat par `id` (zéro duplication).
|
||||
- Piège : ajouter le rôle mobile à une `roles_allowed` = **SUR-EXPOSITION** (le dev mobile gagnerait le droit de piloter l'IA d'un portail métier — l'escalade même que #6 refuse) ; retirer le rôle rendu de la `roles_allowed` construction = perte du déclenchement assisté ; DÉPLACER le rôle vers un autre portail — laisse la fiche périmée EN SILENCE pendant que l'artefact dit autre chose. Aucune suite `tests/` (qui teste des FONCTIONS de montage, pas la prose d'une fiche) n'attrape ce « vert trompeur ».
|
||||
- **État courant** : aucune valeur périmée — rendu ∈ construction (et lui seul), mobile ∉ tout (anti-invention #6, rien à réécrire). Le défaut est la **surface ungated**.
|
||||
|
||||
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Fiches agents · appartenance à `roles_allowed` du chat OTOIA » après le bloc Fiches Workspace) : index recomputé `erpnext_role_name → {portails autorisant}`, puis (a) **in_named_portail** (rendu) — le portail nommé est data-derived (== `portail` du rôle au contrat, **triple ancrage** : nommé == contrat == unique portail autorisant), membership == {ce portail}, prose citant « invoquer le chat OTOIA » ; (b) **not_in_any** (mobile) — rôle ABSENT de TOUTE `roles_allowed`. Portail nommé fantôme (absent des mounts) = échec (anti-typo). Claim absent = échec (traçabilité #6). Invariant vérifié en amont : chaque rôle ∈ ≤1 `roles_allowed`, == son `portail` contrat.
|
||||
|
||||
**6 morsures vérifiées** (3 côté artefact · 3 côté fiche/bord) : artefact retire rendu de `construction` (∉ aucun portail) · artefact ajoute mobile à `ventes` (SUR-EXPOSITION #6 VIOLÉE) · artefact ajoute AUSSI rendu à `compta` (droit élargi, fiche nomme 1 seul) · fiche rendu nomme le mauvais portail (`construction→compta`, INTROUVABLE) · fiche mobile retire le token `roles_allowed` (INTROUVABLE) ; restauré = green : rendu ∈ `{construction}` · mobile ∉ tout · exit 0. Working tree byte-restauré (`git checkout --`, **JAMAIS** `git clean`, interdit absolu) · **7 gates re-verts** (`validate_json`/`check_docs`/`guard_constraints`/`check_artifacts`/`check_ci_integrity`/`check_readme_claims`/`check_regression`).
|
||||
|
||||
- `ci/README.md` (recap `check-readme-claims` + paragraphe détaillé « 3e surface d'appartenance · `roles_allowed` du chat OTOIA ») mis à jour.
|
||||
- **Note memory** : la classe « agent-fiche-role-attrs-ungated » est désormais CLOSE (attributs contrat = Fiches Faisabilité/Mobile ; `Has Role` = Fiches Workspace ; `roles_allowed` chat = ce bloc). Candidat propre restant : la fiche `ifc_speckle:34` (attributs contrat déjà gatés) + la row acceptance S3 « viewer 3D » (déjà gatée QA).
|
||||
- **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 montage réel du chat reste côté runner/VPS).
|
||||
- **Auto-score 4Big** : 96/100.
|
||||
|
||||
+31
-1
File diff suppressed because one or more lines are too long
@@ -5215,6 +5215,118 @@ if _ws_json is not None and _ws_roles is not None:
|
||||
f"MAIS workspace.json met « {rname} » dans son Has Role → "
|
||||
f"SUR-EXPOSITION console, assertion #6 VIOLÉE")
|
||||
|
||||
# ============================================================================
|
||||
# Fiches agents · appartenance à `roles_allowed` du chat OTOIA — QUI peut
|
||||
# INVOQUER l'assistant OTOIA embarqué dans un portail. Surface data-derived
|
||||
# DISTINCTE de la membership Workspace `Has Role` (QUELLE console le rôle atteint,
|
||||
# gatée juste au-dessus) ET des attributs du contrat rbac_50_roles.json (gatés par
|
||||
# les blocs Fiches Faisabilité / Fiche Mobile) : la liste `roles_allowed` ne vit
|
||||
# PAS dans le contrat ni dans workspace.json mais dans
|
||||
# frontend/chat_otoia/out/chat_mount.json (une config runtime par portail,
|
||||
# artefact byte-gaté par check_artifacts). Deux fiches font, EN PROSE, une
|
||||
# assertion d'appartenance à CE contrôle d'accès :
|
||||
# • rendu (:36) — POSITIVE : le rôle « OTO Faisabilité Rendu 3D » fait partie
|
||||
# des `roles_allowed` du portail NOMMÉ `construction` (droit
|
||||
# d'invoquer le chat pour déclencher les rendus assistés).
|
||||
# • mobile (:41) — NÉGATIVE d'honnêteté (#6) : le rôle « OTO Plateforme Mobile »
|
||||
# (famille `plateforme`) n'est VOLONTAIREMENT dans AUCUNE liste
|
||||
# `roles_allowed` — le chat ne couvre que les portails métier.
|
||||
# Aucun gate ne les couvrait : le bloc Chat OTOIA amont gate l'IDENTITÉ de la
|
||||
# persona/capabilities (ancrée CLAUDE.md) et la COMPOSITION (5 mounts ⇔ 5 portails),
|
||||
# AVEUGLE à QUEL rôle figure dans quelle `roles_allowed` ; le bloc Fiches Workspace
|
||||
# gate la membership `Has Role`, un artefact + une sémantique DIFFÉRENTS (accès
|
||||
# console ≠ droit d'invocation du chat). Ajouter le rôle mobile à une `roles_allowed`
|
||||
# (SUR-EXPOSITION : le dev mobile gagnerait le droit de piloter l'IA d'un portail
|
||||
# métier — l'escalade même que la note #6 refuse), retirer le rôle rendu de la
|
||||
# `roles_allowed` construction (perte du déclenchement assisté), ou le DÉPLACER vers
|
||||
# un autre portail, laisse la fiche périmée EN SILENCE pendant que l'artefact dit
|
||||
# autre chose ⇒ l'agent ERPNext Backend câblerait le mauvais contrôle d'accès chat.
|
||||
# Aucune suite tests/ (qui teste des FONCTIONS de montage/résolution, pas la prose
|
||||
# d'une fiche) n'attrape ce « vert trompeur ». On RECOMPUTE, depuis chat_mount.json,
|
||||
# l'ensemble des portails dont la `roles_allowed` porte l'erpnext_role_name du rôle
|
||||
# (résolu du contrat par id, zéro duplication) et on confronte l'assertion de la
|
||||
# fiche. Triple ancrage bonus (POSITIVE) : le portail NOMMÉ par la fiche == le
|
||||
# `portail` du rôle dans le contrat == l'unique portail dont la `roles_allowed`
|
||||
# le porte. Un claim absent échoue AUSSI (traçabilité · #6).
|
||||
# ============================================================================
|
||||
FICHES_CHAT = [
|
||||
{"file": "03_agents/rendu/AGENT.md", "rid": "faisabilite-rendu-3d",
|
||||
"kind": "in_named_portail"},
|
||||
{"file": "03_agents/mobile/AGENT.md", "rid": "plateforme-mobile",
|
||||
"kind": "not_in_any"},
|
||||
]
|
||||
_chat_mounts = _chat_roles = None
|
||||
try:
|
||||
_chat_mounts = load("frontend/chat_otoia/out/chat_mount.json")
|
||||
_chat_roles = load("rbac/rbac_50_roles.json")["roles"]
|
||||
except (OSError, KeyError, ValueError, TypeError) as e:
|
||||
bad(f"Fiches Chat · chat_mount.json / rbac_50_roles.json illisible : {e}")
|
||||
if _chat_mounts is not None and _chat_roles is not None:
|
||||
# Index recomputé : erpnext_role_name -> ensemble des portails dont la
|
||||
# `roles_allowed` le porte (le droit d'invoquer le chat de CE portail).
|
||||
_allow, _portails = {}, set()
|
||||
for _mnt in _chat_mounts:
|
||||
_pt = _mnt.get("portail")
|
||||
_portails.add(_pt)
|
||||
for _rn in (_mnt.get("roles_allowed") or []):
|
||||
_allow.setdefault(_rn, set()).add(_pt)
|
||||
if not _chat_mounts:
|
||||
bad("Fiches Chat · aucun mount dans chat_mount.json → artefact suspect")
|
||||
for fc in FICHES_CHAT:
|
||||
tag = fc["file"].split("/")[1]
|
||||
cand = [r for r in _chat_roles if r.get("id") == fc["rid"]]
|
||||
if len(cand) != 1:
|
||||
bad(f"Fiche {tag} · le contrat porte {len(cand)} rôle(s) `{fc['rid']}` "
|
||||
f"(attendu exactement 1) → contrat changé de forme, vérifier")
|
||||
continue
|
||||
r0 = cand[0]
|
||||
rname = r0.get("erpnext_role_name")
|
||||
actual = _allow.get(rname, set()) # portails réels autorisant ce rôle
|
||||
ftxt = read_norm(fc["file"])
|
||||
if ftxt is None:
|
||||
continue
|
||||
if fc["kind"] == "in_named_portail":
|
||||
# Le portail NOMMÉ par la fiche est data-derived : c'est le `portail` du
|
||||
# rôle dans le contrat (triple ancrage). Il DOIT exister comme mount
|
||||
# (anti-typo : sinon l'assertion « verte » viserait un portail fantôme).
|
||||
named = r0.get("portail")
|
||||
if named not in _portails:
|
||||
bad(f"Fiche {tag} · le rôle `{fc['rid']}` a portail `{named}` (contrat) "
|
||||
f"MAIS aucun mount chat pour ce portail (mounts : {sorted(_portails)}) "
|
||||
f"→ contrat/artefact périmé")
|
||||
continue
|
||||
# La fiche doit NOMMER ce portail dans son assertion roles_allowed + chat.
|
||||
if not re.search(rf"`roles_allowed` \(portail {re.escape(named)}\)", ftxt):
|
||||
bad(f"Fiche {tag} · {fc['file']} — assertion « `roles_allowed` (portail "
|
||||
f"{named}) » INTROUVABLE (claim absent = régression · #6)")
|
||||
elif not re.search(r"autorisés à \*\*invoquer le chat OTOIA\*\*", ftxt):
|
||||
bad(f"Fiche {tag} · {fc['file']} — la fiche nomme le portail mais pas "
|
||||
f"le droit « invoquer le chat OTOIA » → assertion incomplète (#6)")
|
||||
elif actual == {named}:
|
||||
good(f"Fiche {tag} · rôle « {rname} » ∈ roles_allowed du portail "
|
||||
f"`{named}` (et de lui seul) == chat_mount.json")
|
||||
elif named in actual:
|
||||
bad(f"Fiche {tag} · rôle « {rname} » ∈ roles_allowed de `{named}` MAIS "
|
||||
f"AUSSI {sorted(actual - {named})} — la fiche nomme UN seul portail ; "
|
||||
f"droit d'invocation élargi dans chat_mount.json → réviser fiche/artefact")
|
||||
else:
|
||||
bad(f"Fiche {tag} · {fc['file']} nomme le portail `{named}` MAIS "
|
||||
f"chat_mount.json autorise « {rname} » sur "
|
||||
f"{sorted(actual) or 'aucun portail'} → fiche périmée (rôle "
|
||||
f"retiré/déplacé de roles_allowed), régénérer")
|
||||
elif fc["kind"] == "not_in_any":
|
||||
if not re.search(r"volontairement pas\*\* rattaché.*?`roles_allowed` du chat "
|
||||
r"OTOIA", ftxt):
|
||||
bad(f"Fiche {tag} · {fc['file']} — assertion négative « volontairement pas "
|
||||
f"… `roles_allowed` du chat OTOIA » INTROUVABLE (#6)")
|
||||
elif not actual:
|
||||
good(f"Fiche {tag} · rôle « {rname} » ABSENT de TOUTE roles_allowed "
|
||||
f"== chat_mount.json (assertion négative #6 tenue)")
|
||||
else:
|
||||
bad(f"Fiche {tag} · {fc['file']} affirme « pas dans roles_allowed » MAIS "
|
||||
f"chat_mount.json autorise « {rname} » sur {sorted(actual)} → "
|
||||
f"SUR-EXPOSITION du chat OTOIA, assertion #6 VIOLÉE")
|
||||
|
||||
# ============================================================================
|
||||
# Publiciste/branding · TOKENS DESIGN CANONIQUES « ancrés sur CLAUDE.md #4 »
|
||||
# — les 4 valeurs de la marque luxury (2 couleurs + 2 typographies), jamais gatées.
|
||||
|
||||
Reference in New Issue
Block a user