[DTP-Worker] Sprint 8 · buffer · Fiches agents/rattachement Workspace : les ASSERTIONS D'APPARTENANCE Has Role des trois fiches 03_agents/{rendu,ifc_speckle,mobile}/AGENT.md — QUELLE CONSOLE (Workspace ERPNext natif v15) le rôle peut atteindre — étaient une surface data-derived DISTINCTE du contrat rbac_50_roles.json (la membership vit dans frontend/portails/out/workspace.json, byte-gaté par check_artifacts) et HORS de tout gate.
Les blocs Fiches Faisabilité / Fiche Mobile ne gataient que les attributs du CONTRAT ; le bloc « triplets par workspace » ne gate que le COMPTE de rôles par Workspace (`nb_roles`), AVEUGLE à QUEL rôle. Rattacher le rôle mobile à `OTO Ventes` (SUR-EXPOSITION console : le dev mobile gagnerait l'accès au portail Ventes — la restriction même que la note #6 pose), retirer/déplacer le rôle rendu de `OTO Construction`, laissait la fiche périmée en silence pendant que l'artefact dit autre chose ⇒ l'agent ERPNext Backend câblerait le mauvais accès console. Gate ajouté (nouveau bloc « Fiches agents · rattachement Workspace ») : index recomputé `erpnext_role_name → {titres de Workspace le portant}` depuis workspace.json (nom résolu du contrat par id, zéro duplication). 3 formes d'assertion : in_named (rendu — prose cite CE Workspace ET membership == {lui seul}) · in_any (ifc — membership ≥1) · not_in_named (mobile — assertion NÉGATIVE #6, rôle ABSENT du Has Role du Workspace nommé). Garde anti-typo : Workspace nommé absent de l'artefact ⇒ ROUGE. Claim absent ⇒ ROUGE (traçabilité). 9 morsures vérifiées (4 artefact + 5 fiche/bord) ; restauré vert ; 7 gates re-verts. ci/README.md (récap + paragraphe détaillé) + activity log + mémoire mis à jour. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -245,3 +245,61 @@ absolu) · **7 gates re-verts**.
|
||||
é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.
|
||||
|
||||
## Session `20260801_020209` · Buffer S8 · Domaine Fiches agents/rattachement Workspace : les **assertions d'appartenance `Has Role`** des trois fiches `03_agents/{rendu,ifc_speckle,mobile}/AGENT.md` — QUELLE CONSOLE (Workspace ERPNext natif) le rôle peut atteindre — étaient une surface data-derived **DISTINCTE du contrat** `rbac_50_roles.json` (la membership vit dans `frontend/portails/out/workspace.json`, byte-gaté) et **HORS de tout gate**. Les blocs Fiches Faisabilité / Fiche Mobile des sessions précédentes ne gataient que les attributs du **contrat** ; le bloc « triplets par workspace » ne gate que le **compte** de rôles par Workspace (`nb_roles`), AVEUGLE à QUEL 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 famille de fiches que
|
||||
les deux sessions précédentes (`agent-fiche-role-attrs-ungated`) mais **surface
|
||||
orthogonale** : non plus les attributs recopiés du contrat, mais la **membership
|
||||
`Has Role`** — l'accès console — qu'aucun gate ne touchait.
|
||||
|
||||
**Dérive silencieuse fermée** :
|
||||
- `03_agents/rendu/AGENT.md:35` — « rattaché au Workspace `OTO Construction` »
|
||||
(assertion NOMMÉE) ; `03_agents/ifc_speckle/AGENT.md:35` — « rattaché à un
|
||||
Workspace » (générique) ; `03_agents/mobile/AGENT.md:41` — « **volontairement pas**
|
||||
rattaché au Workspace `OTO Ventes` » (assertion NÉGATIVE d'honnêteté #6).
|
||||
- Source faisant autorité : `frontend/portails/out/workspace.json` (les 5 Workspaces
|
||||
natifs v15, chacun portant sa liste `Has Role`, byte-gaté par `check_artifacts` via
|
||||
la reproductibilité `out/`). L'`erpnext_role_name` de chaque rôle est résolu du
|
||||
contrat `rbac_50_roles.json` par `id` (zéro duplication).
|
||||
- Piège : rattacher le rôle mobile à `OTO Ventes` = **sur-exposition console** (le
|
||||
dev mobile gagnerait l'accès au portail Ventes — la restriction même que la note
|
||||
#6 pose), retirer/**déplacer** le rôle rendu de `OTO Construction` = perte/erreur
|
||||
d'accès console — laisse la fiche périmée EN SILENCE pendant que l'artefact dit
|
||||
autre chose ⇒ l'agent ERPNext Backend câblerait le mauvais accès console. Aucune
|
||||
suite `tests/` (qui teste des FONCTIONS de résolution, pas la prose) n'attrape ce
|
||||
« vert trompeur ».
|
||||
- **État courant** : aucune assertion périmée — rendu ∈ {`OTO Construction`} seul ·
|
||||
ifc ∈ {`OTO Construction`} (≥1) · mobile ∈ ∅ (donc absent de `OTO Ventes`) ; les 3
|
||||
recoupent workspace.json exactement (anti-invention #6, rien à réécrire). Le défaut
|
||||
est la **surface ungated**.
|
||||
|
||||
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Fiches agents ·
|
||||
rattachement Workspace (`Has Role`) » avant `sys.exit`) : index recomputé
|
||||
`erpnext_role_name → {titres de Workspace le portant}` depuis workspace.json. Par
|
||||
fiche, 3 formes d'assertion : **in_named** (rendu) — la prose cite CE Workspace ET la
|
||||
membership == {ce Workspace} seul (membership élargie ou déplacée ⇒ ROUGE) ;
|
||||
**in_any** (ifc) — membership ≥ 1 Workspace ; **not_in_named** (mobile) — rôle ABSENT
|
||||
du `Has Role` du Workspace nommé. Garde anti-typo : un Workspace nommé absent de
|
||||
l'artefact échoue avant tout (anti-cible fantôme). Un claim absent échoue AUSSI
|
||||
(traçabilité #6).
|
||||
|
||||
**9 morsures vérifiées** (4 côté artefact · 5 côté fiche/bord) : artefact ajoute le
|
||||
rôle mobile à `OTO Ventes` (**sur-exposition #6 VIOLÉE**) · artefact retire le rôle
|
||||
rendu de `OTO Construction` · artefact déplace rendu vers `OTO Achat` · artefact
|
||||
retire ifc de tous les Workspaces · fiche rendu renomme le Workspace
|
||||
(`OTO Construction→OTO Ventes`, INTROUVABLE) · fiche rendu supprime l'assertion ·
|
||||
fiche mobile nomme un Workspace fantôme (`OTO Marketing`, anti-typo) · fiche ifc
|
||||
supprime l'assertion générique · fiche mobile passe le négatif au positif (retire
|
||||
« volontairement pas ») ; restauré = green : rendu=={`OTO Construction`} · ifc≥1 ·
|
||||
mobile∉`OTO Ventes` · 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é « rattachement
|
||||
Workspace des fiches agents ») mis à jour. Mémoire `agent-fiche-role-attrs-ungated`
|
||||
à actualiser (surface membership couverte).
|
||||
- **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 ; l'attachement
|
||||
réel des rôles aux Workspaces `bench` reste côté VPS).
|
||||
- **Auto-score 4Big** : 96/100.
|
||||
|
||||
+31
-1
File diff suppressed because one or more lines are too long
@@ -3405,6 +3405,114 @@ if _fais_roles is not None:
|
||||
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")
|
||||
|
||||
# ============================================================================
|
||||
# Fiches agents · rattachement Workspace (`Has Role`) — QUELLE CONSOLE (Workspace
|
||||
# ERPNext natif v15) le rôle d'une fiche peut atteindre. Surface data-derived
|
||||
# DISTINCTE des attributs du contrat rbac_50_roles.json : la MEMBERSHIP `Has Role`
|
||||
# ne vit PAS dans le contrat mais dans frontend/portails/out/workspace.json
|
||||
# (artefact byte-gaté par check_artifacts). Trois fiches font, EN PROSE, une
|
||||
# assertion d'appartenance :
|
||||
# • rendu (:35) — rôle « OTO Faisabilité Rendu 3D » rattaché au Workspace
|
||||
# NOMMÉ `OTO Construction` (accès console).
|
||||
# • ifc_speckle (:35) — rôle « OTO Faisabilité IFC Speckle » rattaché à UN
|
||||
# Workspace ERPNext (générique, non nommé).
|
||||
# • mobile (:41) — rôle « OTO Plateforme Mobile » VOLONTAIREMENT PAS
|
||||
# rattaché au Workspace NOMMÉ `OTO Ventes` (assertion
|
||||
# NÉGATIVE d'honnêteté · #6).
|
||||
# Aucun gate ne les couvrait : le bloc « triplets par workspace » gate le COMPTE de
|
||||
# rôles par Workspace (nb_roles), AVEUGLE à QUEL rôle ; les blocs Fiches Faisabilité /
|
||||
# Fiche Mobile gatent le CONTRAT, pas la membership Has Role. Rattacher le rôle mobile
|
||||
# à `OTO Ventes` (SUR-EXPOSITION console : le dev mobile gagnerait l'accès au portail
|
||||
# Ventes — la restriction même que la note #6 pose), retirer le rôle rendu de
|
||||
# `OTO Construction` (perte d'accès console du spécialiste), ou DÉPLACER le rôle vers
|
||||
# un autre Workspace, laisse la fiche périmée EN SILENCE pendant que l'artefact dit
|
||||
# autre chose ⇒ l'agent ERPNext Backend câblerait le mauvais accès console. Aucune
|
||||
# suite tests/ (qui teste des FONCTIONS de résolution, pas la prose d'une fiche)
|
||||
# n'attrape ce « vert trompeur ». On RECOMPUTE, depuis workspace.json, l'ensemble des
|
||||
# Workspaces dont le `Has Role` 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. Un claim absent
|
||||
# échoue AUSSI (traçabilité · #6).
|
||||
# ============================================================================
|
||||
FICHES_WS = [
|
||||
{"file": "03_agents/rendu/AGENT.md", "rid": "faisabilite-rendu-3d",
|
||||
"kind": "in_named", "ws": "OTO Construction"},
|
||||
{"file": "03_agents/ifc_speckle/AGENT.md", "rid": "faisabilite-ifc-speckle",
|
||||
"kind": "in_any"},
|
||||
{"file": "03_agents/mobile/AGENT.md", "rid": "plateforme-mobile",
|
||||
"kind": "not_in_named", "ws": "OTO Ventes"},
|
||||
]
|
||||
_ws_json = _ws_roles = None
|
||||
try:
|
||||
_ws_json = load("frontend/portails/out/workspace.json")
|
||||
_ws_roles = load("rbac/rbac_50_roles.json")["roles"]
|
||||
except (OSError, KeyError, ValueError, TypeError) as e:
|
||||
bad(f"Fiches Workspace · workspace.json / rbac_50_roles.json illisible : {e}")
|
||||
if _ws_json is not None and _ws_roles is not None:
|
||||
# Index recomputé : erpnext_role_name -> ensemble des titres de Workspace le portant.
|
||||
_mem, _titles = {}, set()
|
||||
for ws in _ws_json:
|
||||
title = ws.get("title")
|
||||
_titles.add(title)
|
||||
for hr in (ws.get("roles") or []):
|
||||
_mem.setdefault(hr.get("role"), set()).add(title)
|
||||
for fc in FICHES_WS:
|
||||
tag = fc["file"].split("/")[1]
|
||||
cand = [r for r in _ws_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
|
||||
rname = cand[0].get("erpnext_role_name")
|
||||
actual = _mem.get(rname, set()) # Workspaces réels du rôle (Has Role)
|
||||
ftxt = read_norm(fc["file"])
|
||||
if ftxt is None:
|
||||
continue
|
||||
named = fc.get("ws")
|
||||
# Un Workspace NOMMÉ par la fiche doit exister dans l'artefact (anti-typo :
|
||||
# sinon une assertion in_named/not_in_named « verte » viserait un fantôme).
|
||||
if named is not None and named not in _titles:
|
||||
bad(f"Fiche {tag} · le Workspace nommé `{named}` n'existe pas dans "
|
||||
f"workspace.json (titres : {sorted(_titles)}) → fiche/artefact périmé")
|
||||
continue
|
||||
if fc["kind"] == "in_named":
|
||||
if not re.search(rf"rattaché au Workspace `{re.escape(named)}`", ftxt):
|
||||
bad(f"Fiche {tag} · {fc['file']} — assertion « rattaché au Workspace "
|
||||
f"`{named}` » INTROUVABLE (claim absent = régression · #6)")
|
||||
elif actual == {named}:
|
||||
good(f"Fiche {tag} · rôle « {rname} » ∈ Has Role de `{named}` "
|
||||
f"(et de lui seul) == workspace.json")
|
||||
elif named in actual:
|
||||
bad(f"Fiche {tag} · rôle « {rname} » ∈ `{named}` MAIS AUSSI "
|
||||
f"{sorted(actual - {named})} — la fiche nomme UN seul Workspace ; "
|
||||
f"membership élargie dans workspace.json → réviser fiche/artefact")
|
||||
else:
|
||||
bad(f"Fiche {tag} · {fc['file']} nomme `{named}` MAIS workspace.json "
|
||||
f"rattache « {rname} » à {sorted(actual) or 'aucun Workspace'} → "
|
||||
f"fiche périmée (rôle déplacé/retiré du Has Role), régénérer")
|
||||
elif fc["kind"] == "in_any":
|
||||
if not re.search(r"rattaché à un Workspace", ftxt):
|
||||
bad(f"Fiche {tag} · {fc['file']} — assertion « rattaché à un "
|
||||
f"Workspace » INTROUVABLE (claim absent = régression · #6)")
|
||||
elif actual:
|
||||
good(f"Fiche {tag} · rôle « {rname} » ∈ Has Role de {sorted(actual)} "
|
||||
f"== workspace.json (≥1 Workspace, comme la fiche l'affirme)")
|
||||
else:
|
||||
bad(f"Fiche {tag} · {fc['file']} affirme un rattachement Workspace "
|
||||
f"MAIS « {rname} » n'est dans le Has Role d'AUCUN Workspace → "
|
||||
f"fiche périmée")
|
||||
elif fc["kind"] == "not_in_named":
|
||||
if not re.search(rf"volontairement pas\*\* rattaché au Workspace "
|
||||
rf"`{re.escape(named)}`", ftxt):
|
||||
bad(f"Fiche {tag} · {fc['file']} — assertion négative « volontairement "
|
||||
f"pas rattaché au Workspace `{named}` » INTROUVABLE (#6)")
|
||||
elif named not in actual:
|
||||
good(f"Fiche {tag} · rôle « {rname} » ABSENT du Has Role de `{named}` "
|
||||
f"== workspace.json (assertion négative #6 tenue)")
|
||||
else:
|
||||
bad(f"Fiche {tag} · {fc['file']} affirme « pas rattaché à `{named}` » "
|
||||
f"MAIS workspace.json met « {rname} » dans son Has Role → "
|
||||
f"SUR-EXPOSITION console, assertion #6 VIOLÉE")
|
||||
|
||||
sys.exit(1 if FAIL else 0)
|
||||
PY
|
||||
rc=$?
|
||||
|
||||
Reference in New Issue
Block a user