# 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 (`own`→`docperm_if_owner` · `entite`→`user_permission_company` · `groupe`→`none_consolidated` · `equipe`→`vps_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 (`entite` → `none_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 `entite`→`none_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 `groupe`→`famille` (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 `entite`→ `none_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` (`groupe`→`entite` : 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/W`→`R/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 attribut** — `erpnext_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 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. ## 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. ## Session `20260801_023211` · Buffer S8 · Domaine Publiciste/branding : les **4 TOKENS DESIGN CANONIQUES** de la marque luxury — fond dark `#0a0a12` + accent doré `#f0b429`, titres **Fraunces** + corps **Cormorant Garamond** (contrainte NON-NÉGOCIABLE **CLAUDE.md #4**) — étaient RECOPIÉS à **QUATRE** endroits **tous NON gatés** : (a) les CONSTANTES `lib/branding.py` (`COLOR_BG`/`COLOR_ACCENT`/`FONT_DISPLAY`/`FONT_BODY`), la source unique importée par le générateur & substituée dans le gabarit (`{{COLOR_BG}}…`), hors `out/` donc **invisible à check_artifacts** ; (b) le **docstring** du même fichier ; (c) le README (ligne du gabarit) ; (d) le **tuple d'oracle HARDCODÉ** de `tests/test_publiciste.py`. **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 EXACT** que les paramètres canoniques `faisabilite/generator` **ancrés sur CLAUDE.md #9/#10** (`CANONICAL` ↔ contrainte) — appliqué à la **contrainte #4** (identité visuelle) et à son consommateur `publiciste/branding`, module **entièrement hors CI** jusqu'ici (grep `branding|0a0a12|f0b429|Fraunces` dans `ci/` = 0 avant cette session). **Dérive silencieuse fermée** : - `05_deliverables_mvp/publiciste/lib/branding.py:13-20` — les 4 CONSTANTES · `05_deliverables_mvp/publiciste/lib/branding.py:3-4` — le docstring qui les cite · `05_deliverables_mvp/publiciste/README.md:43` — « Gabarit dark+doré (Fraunces + Cormorant Garamond) » · `05_deliverables_mvp/publiciste/tests/test_publiciste.py:193` — `for token in ("#0a0a12", "#f0b429", "Fraunces", "Cormorant Garamond")`. - Source faisant autorité : **CLAUDE.md #4** (ligne 18) — la contrainte constitutionnelle, comme #9/#10 le sont pour le générateur de faisabilité. - Piège : le SEUL contrôle existant (`test_marque_luxury`) assert que le RENDU HTML CONTIENT ces 4 marqueurs — mais ils sont **HARDCODÉS dans le test**, une copie de plus **jamais comparée à CLAUDE.md**. Si #4 change (accent `#f0b429`→`#c9a227`) et qu'on aligne `branding.py`, l'oracle du test reste périmé (rouge trompeur) ; si RIEN ne bouge, tout reste vert alors que le site public `vente.otov7.com` émettrait la mauvaise couleur — invention silencieuse de la classe même que #6 interdit, qu'aucune suite `tests/` (qui teste des FONCTIONS, pas l'ancrage à CLAUDE.md) n'attrape. - **État courant** : **aucun token périmé** — les 4 constantes + docstring + README + oracle du test recoupent CLAUDE.md #4 exactement (anti-invention #6, rien à réécrire). Le défaut est la **surface ungated** (module hors CI). **Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Publiciste/branding » avant `sys.exit`) : (0) **ancrage** — les 4 tokens recomputés de la ligne #4 de CLAUDE.md (2 couleurs backtickées + 2 fontes de la parenthèse `(… + …)`) ; (a) `branding.py` CONSTANTES == #4 (hex **insensible à la casse**, fontes à l'exact strip) ; (b) **docstring** (texte AVANT les constantes) cite les 4 EXACTS ; (c) README cite les 2 fontes ; (d) **oracle du test** — le tuple `for token in (...)` == les 4 tokens de #4 (set-diff : absent ET en trop). Un claim absent échoue AUSSI (traçabilité #6). **6 morsures vérifiées** : `branding.py` accent `#f0b429→#e0a420` (constante périmée) · `branding.py` display `Fraunces→Playfair Display` · **CLAUDE.md #4** accent `→#c9a227` (mord **SIMULTANÉMENT** les 3 copies aval : constante + docstring + oracle du test — prouve la valeur de l'ANCRAGE) · docstring retire l'accent de la prose (claim absent) · README échange une fonte (`Cormorant Garamond→Georgia`) · oracle du test retire `Fraunces` ; restauré = green : 4 tokens sains · constantes == #4 · docstring 4 tokens · README 2 fontes · oracle == #4 · 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é « Tokens design canoniques `publiciste/branding` ancrés sur CLAUDE.md #4 ») 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 déploiement réel du site `vente.otov7.com` reste côté VPS). - **Auto-score 4Big** : 96/100. ## Session `20260801_030214` · Buffer S8 · Domaine Fiscal/e-CF DGII : l'**IDENTITÉ du RÔLE émetteur** e-CF — le SEUL rôle habilité à ÉMETTRE un comprobante fiscal électronique — nommé **en prose** par le bullet `role_id` du README (`fiscal/ecf_dgii/README.md:51-54`) via TROIS attributs data-derived (`role_id` `compta-fiscaliste-ecf` · `erpnext_role_name` `OTO Compta Fiscaliste eCF` · `portail` `compta`) était **HORS de tout gate d'IDENTITÉ**. Le bloc **e-NCF** gate le FORMAT de l'identifiant ; le bloc **cross-cohérence** (2e surface, session précédente) gate états émetteurs/champs de base/FormaPago ; mais AUCUN ne comparait l'IDENTITÉ de ce rôle à ses deux sources faisant autorité. **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 d'IDENTITÉ** que la Fiche Mobile / les Fiches Faisabilité (`agent-fiche-role-attrs-ungated`) ou les gardes CRM/`workflow_vente` (rôle→transition) — appliquée à une **3e surface distincte du MÊME README `fiscal/ecf_dgii`** : le rôle émetteur nommé dans la section « Cross-cohérence », jamais gaté hors le FORMAT e-NCF et les ensembles états/champs. **Dérive silencieuse fermée** : - `05_deliverables_mvp/fiscal/ecf_dgii/README.md:53-54` — bullet `role_id` : « **et** appartenir au portail `compta` — le rôle dédié `compta-fiscaliste-ecf` (_OTO Compta Fiscaliste eCF_) ». - Sources faisant autorité (double) : (a) `out/ecf_plan.json` (byte-gaté par `check_artifacts`) — CHAQUE `emission_events[]` porte `role_id` + `erpnext_role_name` ; (b) `rbac/rbac_50_roles.json` (contrat schéma-validé · les 3 MANIFEST RBAC en dérivent, byte-gatés) — le rôle d'`id` `compta-fiscaliste-ecf` porte `portail: "compta"`. Le `portail` n'est PAS dans l'artefact ; il n'est vérifiable QUE contre le contrat. - Piège : la seule vérification existante (`tests/`) teste des **FONCTIONS** (`ncf`/résolution RBAC), jamais la PROSE. RÉAFFECTER l'émission à un autre rôle (dans le plan), le RENOMMER, ou DÉPLACER `compta-fiscaliste-ecf` du portail `compta` vers un autre portail (dans le contrat : l'e-CF serait émis **HORS Compta** — la séparation même que la cross-cohérence pose) laisse la prose périmée EN SILENCE pendant que l'artefact/le contrat disent autre chose → l'agent ERPNext Backend câblerait le mauvais rôle émetteur (le risque même que #6 interdit). - **État courant** : **aucun attribut périmé** — les 3 recoupent artefact+contrat exactement (anti-invention #6, rien à réécrire). Le défaut est la **surface ungated**. **Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Fiscal rôle émetteur » après le bloc FormaPago) : (1) **par attribut** — `role_id`/`erpnext_role_name` recomputés de `emission_events` + `portail`/nom Frappe recomputés du contrat, prose EXACTE exigée (regex par claim) ; (2) **cohérences croisées bonus** (mordent un plan/contrat INTERNEMENT incohérent) : tous les `emission_events` pointent UN SEUL rôle (fonction · non vide) · ce `role_id` existe EXACTEMENT une fois au contrat · le nom résolu dans l'artefact == le contrat (zéro-duplication) · `portail == compta` (séparation des pouvoirs). Un claim absent échoue AUSSI (traçabilité #6). **6 morsures vérifiées** : README renomme le `role_id` (`compta-fiscaliste-ecf→…-xxx`, prose périmée) · README renomme le nom Frappe · README déplace le portail (`compta→ventes`) · **artefact** réaffecte les DEUX `emission_events` à `ventes-directeur` (5 mords en cascade : nom≠contrat + portail HORS Compta + les 3 claims de prose) · **contrat** déplace le rôle vers `ventes` (portail HORS Compta + prose portail périmée) · **artefact** diverge du contrat sur le nom (`OTO Compta X`, zéro-dup cassé) ; restauré = green : 3 claims == artefact/ 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é « identité du RÔLE émetteur e-CF, 3e surface du MÊME README `fiscal/ecf_dgii` ») 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 + le câblage Compupar restent côté VPS). - **Auto-score 4Big** : 96/100.