Files
oto-enterprise-os-dtp/05_activity_log/2026-08-01.md
T
Claude Code DTP Worker 3a91c9d52f [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>
2026-08-01 02:10:19 +00:00

24 KiB
Raw Blame History

Activity Log · 2026-08-01 · Claude Code DTP

Session 20260801_000201 · Buffer S8 · Domaine RBAC/userperm_gen : la table « Mapping scope_donnees → mécanisme » — la FONCTION d'enforcement ROW-LEVEL (quel mécanisme Frappe natif applique CHAQUE portée de données + si un User Permission template est émis), CŒUR sécurité du module — était transcrite EN PROSE (README:33-38) SANS AUCUN gate d'IDENTITÉ. Le bloc RBAC « 3 volets » existant ne gate QUE la ventilation par mécanisme (« 28 entite · 16 groupe · 2 own · 4 equipe »), un COMPTE aveugle à QUEL mécanisme applique QUELLE portée.

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 patron d'IDENTITÉ que le mapping RBAC fixtures_gen (séparation des pouvoirs set_user_permissions) ou la cross-cohérence PERMISSIONS Legal/CONFOTUR (role_id→actions) — appliqué à la surface data-derived distincte du même README rbac/userperm_gen : la table qui associe chaque portée de données à son mécanisme d'enforcement, jamais gatée hors son compte agrégé.

Dérive silencieuse fermée :

  • 05_deliverables_mvp/rbac/userperm_gen/README.md:33-38 — table « Mapping scope_donnees → mécanisme » : nomme, PAR portée, le mechanism Frappe natif (owndocperm_if_owner · entiteuser_permission_company · groupenone_consolidated · equipevps_confirm_team) ET le verdict « Template émis ? » (oui pour entite seul, sinon non (null)).
  • Source faisant autorité (byte-gatée par check_artifacts) : out/user_permission_plan.json — chaque entrée porte scope_donnees + mechanism + user_permission_template (null ou objet). Le mapping est une fonction : chaque portée → exactement un mécanisme ; template émis SSI entite/user_permission_company.
  • Piège : le bloc de ventilation est AVEUGLE à l'identité de ces couples. RÉAFFECTER une portée à un mécanisme plus permissif (entitenone_consolidated : le row-level enforcement ABANDONNÉ, sur-exposition des données inter-entités — la restriction même que le module pose), RENOMMER un mécanisme ou BASCULER le verdict « Template émis ? » laisse le README périmé pendant que l'artefact dit autre chose → l'agent ERPNext Backend câblerait le mauvais mécanisme (le risque même que la table veut prévenir). Aucune suite tests/ (qui teste des FONCTIONS de mapping/résolution, pas la prose) n'attrape ce « vert trompeur ».
  • État courant : aucune ligne périmée — les 4 couples portée→mécanisme et leurs verdicts recoupent l'artefact exactement (anti-invention #6, rien à réécrire). Le défaut est la surface ungated.

Gate ajouté (ci/check_readme_claims.sh, nouveau bloc « RBAC/userperm_gen Mapping » après le bloc CRM gardes) : (1) par portée — le mechanism (1er token backtické de la colonne) ET le verdict « Template émis ? » (**oui** / non (null)) recomputés de user_permission_plan.json, prose exigée EXACTE ; (2) identité d'ensemble — portées de la table == portées de l'artefact (set-diff : ni fantôme ni manquante). Cohérences croisées en bonus (mordent un plan INTERNEMENT incohérent) : chaque portée mappe UN SEUL mécanisme (fonction, pas relation) · « Template émis » UNIFORME sur les entrées d'une portée · template émis EXACTEMENT pour user_permission_company (l'invariant « template SSI entite » du README:36/61) · ensemble des portées NON VIDE. Un claim absent échoue AUSSI (traçabilité).

7 morsures vérifiées : README réaffecte entitenone_consolidated (mécanisme périmé · élévation de portée) · README bascule entite « Template émis » oui→non (verdict périmé) · README échange une portée groupefamille (absents=[groupe] + en trop=[famille] + ligne INTROUVABLE) · README retire la ligne own (absents=[own]) · table entière supprimée (les 4 absents + toutes lignes INTROUVABLES) · artefact réaffecte toutes les entrées entitenone_consolidated (README périmé sur mécanisme ET verdict) · artefact rend une entrée own porteuse d'un template (« Template émis » NON uniforme [False,True] · invariant SSI cassé) ; restauré = green : 4 portées · table == artefact · par-ligne exact · fonction/uniforme/SSI verts · exit 0. Working tree byte-restauré (git checkout --, JAMAIS git clean) · 7 gates re-verts.

  • ci/README.md (table récap du pipeline + paragraphe détaillé « 2ᵉ surface rbac/userperm_gen ») 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).
  • Auto-score 4Big : 96/100.

Session 20260801_003201 · Buffer S8 · Domaine Faisabilité/bancable : les 4 figures « Génération réelle (fixture) » du README (40 unités · valeur catalogue USD 8,560,000 / DOP 505,040,000 · point d'équilibre 21 unités (⌈52 % × 40⌉)) — les chiffres DATA-DERIVED que le dossier bancable (l'artefact que voit le banquier) recopie — étaient HORS de tout gate. Le module n'écrit son out/ que sur disque (« out/ non commité ») ⇒ check_artifacts est aveugle (aucun artefact byte-gaté) et AUCUN gate ne le touchait.

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 esprit que les paramètres canoniques faisabilite/generator ancrés sur CLAUDE.md #9/#10 (le 52 % réutilisé ici) — appliqué à une surface encore jamais gatée : un module dont la source de vérité n'est PAS un out/*.json mais la fixture commitée + le générateur.

Dérive silencieuse fermée :

  • 05_deliverables_mvp/faisabilite/bancable/README.md:103-105 — ligne « Génération réelle (fixture) » : 40 unités (= Σ quantités typologies) · USD 8,560,000 (= Σ qté×prix_usd) · DOP 505,040,000 (= Σ qté×prix_dop) · 21 unités (⌈52 % × 40⌉) (= ⌈point_equilibre_pct × Σ unités⌉).
  • Source faisant autorité : fixtures/brief_bancable.json (COMMITÉE) + la génération réelle python3 bancable_gen.py validate (émet le manifeste sur stdout, figures_calculees[]). Le 52 % est ancré sur CLAUDE.md #9 (« Point équilibre 52% pré-vente »), pas recopié.
  • Piège : la seule vérification existante (tests/) teste des FONCTIONS (l'arithmétique de recoupement _check_derived_arithmetic) avec des oracles HARDCODÉS dans le test — une copie de plus, jamais comparée à la PROSE du README. Éditer une typologie de la fixture (quantité/prix), en ajouter/retirer une, ou déplacer le point d'équilibre de CLAUDE.md #9, laisse les 4 chiffres du README périmés pendant que le générateur produit autre chose → le banquier lit un dossier faux (l'invention même que #6 interdit).
  • État courant : aucun chiffre périmé — les 4 figures recoupent la génération réelle exactement (anti-invention #6, rien à réécrire). Le défaut est la surface ungated (module entièrement hors CI).

Gate ajouté (ci/check_readme_claims.sh, nouveau bloc « Faisabilité/bancable figures » avant sys.exit) : (1) recompute indépendant des 4 figures depuis la fixture commitée ; (2) cohérence croisée — le manifeste GÉNÉRÉ (bancable_gen validate) == le recompute (mord un générateur/fixture incohérents) ; (3) prose — un seul pattern d'identité exige les 4 valeurs EXACTES, avec séparateur de milliers pour USD/DOP, + la formule ⌈pct × Σ unités⌉ dont le pct == CLAUDE.md #9 et le total == Σ unités. Un claim absent échoue AUSSI (traçabilité). Le out/ n'étant pas commité, le gate exécute le générateur (stdlib pur, subprocess, encoding="utf-8") au lieu de lire un artefact byte-gaté — même patron que le bloc faisabilite/generator qui lit model.py.

7 morsures vérifiées : README USD 8,560,000→9,560,000 (figure périmée) · README retire le séparateur de milliers (8,560,000→8560000) · README breakeven 21→20 (formule périmée) · README formule 52 %→55 % (dérive CLAUDE.md #9) · README supprime la ligne entière (INTROUVABLE · claim absent) · fixture Studio qté 12→13 (5 morsures : units/USD/DOP/breakeven README périmés + manifeste) · fixture 2 Chambres prix_usd 320000→300000 (catalogue USD périmé) ; restauré = green : 4 figures == génération réelle · manifeste == recompute · pct 52 % == CLAUDE.md #9 · exit 0. Working tree byte-restauré via git checkout -- (JAMAIS git clean, interdit absolu) · 7 gates re-verts.

  • ci/README.md (ligne récap du pipeline check-readme-claims) mis à jour.
  • Hors périmètre worker (VPS · #8) : néant (gate bash/python3 stdlib en-repo ; aucune écriture dans un out/ ⇒ 0 dérive d'artefact ; conversion PDF + rebuild Portail Bancables restent côté VPS).
  • Auto-score 4Big : 96/100.

Session 20260801_010204 · Buffer S8 · Domaine Mobile/fiche agent : la fiche d'identité du rôle RBAC plateforme-mobile (03_agents/mobile/AGENT.md:36) — le SEUL ancrage in-repo réellement vérifiable de l'agent mobile (builds/soumissions stores étant hors-repo, #8) — recopiait EN PROSE, DEPUIS le contrat rbac_50_roles.json, TOUS les attributs data-derived du rôle (erpnext_role_name · nom_fr · famille/portail · entite_principale · niveau · scope_donnees · modules · permission API Access custom R/W · description) SANS AUCUN gate d'IDENTITÉ. Le README mobile/app_config n'est gaté que sur des COMPTES agrégés (« 5 onglets · 44 rôles couverts · 3 langues · 13 identifiants »), 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 patron d'IDENTITÉ que la table gardes CRM/workflow_vente (rôle qui garde chaque transition) ou le mapping RBAC userperm_gen (portée→mécanisme) — appliqué à la surface distincte de la fiche agent mobile : la carte que l'agent ERPNext Backend lit pour seeder le rôle (seed_mobile_rbac.py), jamais gatée hors les comptes agrégés de app_config.

Dérive silencieuse fermée :

  • 03_agents/mobile/AGENT.md:36 — ligne « Points de contact réellement commités », cellule « Ce qu'il fixe » : nomme, PAR attribut, le rôle ERPNext OTO Plateforme Mobile (« Développeur Mobile » · famille/portail plateforme · entité 9060 QC · niveau 2 · scope groupe · modules Core+Website · perm API Access custom R/W · description « Maintient l'app Expo/React Native et l'API mobile ; gère les builds EAS et les soumissions stores »).
  • Source faisant autorité : 05_deliverables_mvp/rbac/rbac_50_roles.json (contrat RBAC, schéma-validé · les 3 MANIFEST RBAC recomputés en dérivent et sont byte-gatés par check_artifacts) → le rôle d'id plateforme-mobile porte chacun de ces champs. Autres blocs du gate lisent déjà ce contrat directement (userperm, workflow_vente, confotur).
  • Piège : la seule vérification existante (tests/) teste des FONCTIONS de résolution RBAC, jamais la PROSE d'une fiche agent. Éditer le contrat — élever le scope_donnees (groupeentite : sur-portée inter-entités, la restriction même que le rôle pose), changer le niveau/l'entite_principale, renommer le rôle, ajouter/retirer un module ou élargir la permission (R/WR/W/D) — laisse la fiche périmée pendant que le contrat dit autre chose → l'agent ERPNext Backend câblerait le mauvais rôle (le risque même que la fiche veut prévenir).
  • État courant : aucun attribut périmé — les 9 attributs + la description 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 « Fiche Mobile » avant sys.exit) : (1) par attributerpnext_role_name · nom_fr · portail · entite_principale · niveau · scope_donnees · modules · permission recomputés du contrat, prose EXACTE exigée (regex par attribut, lookaheads anti-préfixe pour niveau/modules/perm) ; (2) permission — le sigle des verbes est canonique et ordonné (C/R/W/D/S/X/A) : élargir OU rétrécir la perm change le sigle attendu (un préfixe R/W ne satisfait plus R/W/D, ni l'inverse) ; verbe inconnu ⇒ ROUGE (étendre le gate) ; (3) description recoupée verbatim (emphase markdown ** et point final neutralisés). Cohérences croisées bonus (mordent un contrat INTERNEMENT incohérent) : famille == portail (bijection portail) · permission bien custom · role_id présent EXACTEMENT une fois. Un claim absent échoue AUSSI (traçabilité : ligne de rôle INTROUVABLE).

12 morsures vérifiées (6 côté contrat = fiche périmée · 6 côté fiche = fiche fausse) : contrat scope groupe→entite (sur-portée) · contrat niveau 2→3 · contrat +delete (sigle R/W→R/W/D) · contrat description réécrite · contrat rôle renommé (OTO Plateforme Mobile→OTO Mobile Dev) · fiche modules Website→Selling · fiche entité 9060 QC→WAF · fiche perm R/W→R/W/D (sur-revendication) · fiche perm R/W→R (sous-revendication) · fiche modules over-claim +Selling et under-claim Core seul · ligne de rôle entièrement supprimée (INTROUVABLE) ; restauré = green : 9 attributs + description == contrat · famille==portail · perm custom · role_id singleton · 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é « Fiche d'identité du rôle RBAC mobile ») 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 du rôle bench reste côté VPS).
  • 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 attributerpnext_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.

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.