Files
oto-enterprise-os-dtp/05_activity_log/2026-08-01.md
T
Claude Code DTP Worker 30892dfecb [DTP-Worker] Sprint 8 · buffer · Mobile/app_config : le SDK MAJEUR Expo (« Expo 54 ») ANCRÉ sur la roadmap Sprint 5 l.56 « Rebuild Expo 54 »
La version MAJEURE du SDK Expo que l'app compagnon cible (`expoSdkMajor`) est data-derived : `out/app_config.json.expo.extra.expoSdkMajor = 54` (byte-gaté par check_artifacts ⇒ faisant autorité), recopié dans `out/MANIFEST.json.app.expo_sdk_major`, dérivé de `mobile_spec.json[app].expo_sdk_major` dont la SOURCE FAISANT AUTORITÉ est la roadmap Sprint 5 l.56 « Rebuild Expo 54 » (le spec le DÉCLARE via `expo_sdk_source`). Le README:4/19/35/72 recopie « Expo 54 » CINQ fois À LA MAIN — dont la ligne de source « Expo SDK **54** | roadmap Sprint 5 l.56 ». Le bloc « Mobile · récap de l'app Expo » amont ne gate QUE le quadruplet onglets/rôles/langues/identifiants a_confirmer (comptes de MANIFEST.counts), AVEUGLE au majeur du SDK. Piège #6 : BUMPER le SDK (Expo 55 sort · eas build cible 55) dans le spec/artefact SANS toucher au README (ou l'inverse), ou DÉRIVER la roadmap de l'artefact, laisse la prose PÉRIMÉE en silence pendant que le graphe byte-gaté dit autre chose → l'agent Mobile lancerait `eas build` sur le MAUVAIS SDK (le rebuild même que la ligne roadmap prescrit) — « vert trompeur » qu'aucune suite tests/ (FONCTIONS de génération, jamais la prose ni l'ancre à la roadmap) n'attrape. Même patron d'ANCRAGE que la marque SEO §Entités (Helios RD ancrée sur CLAUDE.md) ou les roadmap_line de la recette, mais sur une valeur dont la roadmap — non CLAUDE.md — est l'INPUT faisant autorité.

Gate ajouté (bloc « Mobile · le SDK MAJEUR Expo … ANCRÉ sur la roadmap Sprint 5 l.56 ») : le majeur re-dérivé de app_config.json byte-gaté, puis (a) cohérence interne artefact ⇔ MANIFEST ⇔ spec ; (b) ancrage roadmap `Rebuild Expo <N>` == artefact (ancre vive) ; (c) le spec DÉCLARE l'ancrage (expo_sdk_source cite roadmap + Expo N) ; (d) TOUTE mention prose « Expo [SDK] N » == artefact (aucune périmée) ; (e) la ligne de source du tableau cite l'ancre roadmap. Un claim absent échoue AUSSI.

7 morsures vérifiées (README 54→55 prose périmée · artefact 54→55 README+roadmap périmés = le vrai silent green · roadmap Rebuild Expo 54→55 artefact périmé · MANIFEST 54→55 chaîne incohérente · spec expo_sdk_source sans roadmap · README ligne source sans roadmap · README retire toute mention Expo N), restauré vert (git checkout --, JAMAIS git clean), 7 gates re-verts. ci/README.md (table récap + paragraphe détaillé) mis à jour.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-01 07:14:31 +00:00

66 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.

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:193for 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 attributrole_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.

Session 20260801_033217 · Buffer S8 · Domaine Fiches agents/colonne « Job CI » : chaque table de livrables de 03_agents/*/AGENT.md porte une colonne Job CI qui NOMME entre backticks le job Gitea Actions exécutant la suite du module (rbac-tests, fiscal-ecf-tests, frontend-portails-tests…). Ce nom est saisi à la main ; la source de vérité est la clé jobs: de .gitea/workflows/ci.yml. Ce lien n'était couvert par AUCUN gate : check_ci_integrity.sh prouve que chaque job est CÂBLÉ dans gate.needs (INV-A/B) et les blocs amont de check_readme_claims recomputent les comptes de tests par suite — mais RIEN ne vérifiait que le nom ÉCRIT dans la fiche DÉSIGNE un job réel.

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 de « vert trompeur » que les cellules « Tests » par suite ou la colonne « Type » des Role Profile — appliqué à la surface data-derived distincte : le NOM de job cité par la fiche, jamais confronté à la définition réelle du workflow CI.

Dérive silencieuse fermée :

  • 03_agents/*/AGENT.md — colonne « Job CI » : 33 tokens *-tests répartis sur 13 fiches, chacun un nom de job Gitea Actions recopié à la main.
  • Source faisant autorité : la section jobs: de .gitea/workflows/ci.yml.
  • Piège : renommer un job dans ci.yml (rbac-testsrbac-role-tests) ou mal recopier demo-scenario-tests en demo-scenarios-tests laisse la fiche pointer un job fantôme — le CI reste vert (le vrai job tourne sous son nouveau nom), la doc d'identité de l'agent ment en silence. check_ci_integrity ne regarde QUE le câblage gate.needs, aveugle au NOM cité par la prose.

Gate ajouté (ci/check_readme_claims.sh, bloc « Fiches agents · colonne Job CI ») : on RECOMPUTE l'ensemble des jobs depuis la clé jobs: de ci.yml (la section on: — push/pull_request/workflow_dispatch — est AVANT jobs: et donc naturellement exclue ; jamais une liste à la main) et on exige que chaque token *-tests cité dans une fiche y figure. Direction fiche→ci.yml (le consommateur) : ci.yml peut définir des jobs non cités (légitime), mais une fiche ne peut pas citer un job absent. La disparition totale de la colonne échoue AUSSI (le recensement qui s'évapore est lui-même une régression).

Morsures vérifiées (2) : (1) token de fiche seo-testsseo-suite-tests (job fantôme, mord les 2 citations dans seo/AGENT.md) ; (2) job ci.yml rbac-tests renommé rbac-role-tests → mord SIMULTANÉMENT les 4 fiches qui le citent (erpnext_backend/ifc_speckle/mobile/rendu). État courant : 33 références == jobs ci.yml, exit 0. Working tree byte-restauré (cp depuis backup + git checkout implicite — JAMAIS git clean, interdit absolu). Sibling gates re-verts : check_ci_integrity/check_docs/ guard_constraints/check_artifacts = PASS.

  • ci/README.md (table récap du pipeline + paragraphe détaillé « Colonne Job CI des fiches agents == section jobs: de ci.yml ») 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 ; aucun déclenchement de workflow CI réel — analyse statique du YAML uniquement).
  • Auto-score 4Big : 96/100.

Session 20260801_040219 · Buffer S8 · Domaine CRM/DocType porteur : le NOM du DocType porteur OTO Dossier Vente — le point d'attache du pipeline vente (le Workflow s'y branche via son document_type) — était RECOPIÉ À LA MAIN à plusieurs endroits (titre + ligne « Nom du DocType » du README crm/dossier_vente, fiche 03_agents/crm/AGENT.md:25, fiche 03_agents/onapi_legal/AGENT.md:50) mais AUCUN gate ne couvrait le NOM. Le bloc CRM pipeline existant ne recompute que le COMPTE d'états (9), aveugle à l'identité ; aucune suite tests/ (FONCTIONS de graphe) n'attrape la prose.

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 les fiches de rôle RBAC (agent-fiche-role-attrs-ungated) ou l'identité du rôle émetteur e-CF — appliquée à une surface distincte : le NOM d'un DocType porteur, transcrit à ≥4 endroits et asserté en prose égal à workflow.document_type, jamais confronté à l'artefact byte-gaté.

Dérive silencieuse fermée :

  • 05_deliverables_mvp/crm/dossier_vente/README.md:1 (titre « # DocType porteur · OTO Dossier Vente ») et :29Nom du DocType = document_type du workflow (OTO Dossier Vente) »).
  • 03_agents/crm/AGENT.md:25 — « DocType porteur OTO Dossier Vente : … sans lui le pipeline n'a rien à quoi s'attacher ».
  • 03_agents/onapi_legal/AGENT.md:50 — « cible = workflow.document_type (OTO Dossier Vente) ».
  • Source faisant autorité : crm/dossier_vente/out/doctype_oto_dossier_vente.json (champ name, = MANIFEST.doctype_name, byte-gaté par check_artifacts). L'attache est confirmée par crm/workflow_vente/out/workflow.json[0].document_type (byte-gaté).
  • Piège : RENOMMER le DocType dans doctype_spec.json (OTO Dossier VenteOTO Dossier de Vente) reconstruit les DEUX artefacts de façon COHÉRENTE (le document_type du workflow suit, les tests restent verts), MAIS les mentions en prose se PÉRIMENT en silence → l'agent ERPNext Backend importerait un DocType sous un nom pendant que la fiche/le README en nomment un autre, et le Workflow s'attacherait à un DocType FANTÔME (la rupture même que « sans lui le pipeline n'a rien à quoi s'attacher » veut prévenir).
  • État courant : aucun nom périmé — les 4 mentions + les 2 artefacts recoupent exactement (anti-invention #6, rien à réécrire). Le défaut est la surface ungated.

Gate ajouté (ci/check_readme_claims.sh, nouveau bloc « CRM · IDENTITÉ du DocType porteur » après le bloc CRM pipeline états) : on RECOMPUTE le nom depuis doctype_oto_dossier_vente.json[name] et on exige que chaque prose le nomme EXACTEMENT (regex backtické par mention). Cohérences croisées bonus (mordent un artefact INTERNEMENT incohérent) : DocType.name == MANIFEST.doctype_name (cohérence interne) · == workflow.document_type (l'attache porteur↔workflow — sinon le Workflow vise un DocType fantôme). Un claim absent échoue AUSSI (traçabilité #6).

7 morsures vérifiées (3 côté artefact · 4 côté prose) : nom artefact renommé (nameOTO Dossier de Vente) → mord 6 lignes en cascade (incohérence interne + attache rompue + les 4 mentions périmées) · workflow.document_type divergent → attache ROMPUE seule · MANIFEST.doctype_name divergent → incohérence interne seule · fiche crm/AGENT.md:25 renommée · fiche onapi_legal:50 renommée · ligne « Nom du DocType » périmée · titre du README périmé ; restauré = green : 6 ✓ · exit 0. Working tree byte-restauré (git checkout --, JAMAIS git clean, interdit absolu) · 7 gates re-verts (check_readme_claims/check_ci_integrity/ check_docs/guard_constraints/check_artifacts/check_regression/validate_json).

  • ci/README.md (ligne récap du pipeline + paragraphe détaillé « IDENTITÉ du DocType porteur crm/dossier_vente ») 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 ; l'import réel du DocType puis du Workflow reste côté agent ERPNext Backend / VPS).
  • Auto-score 4Big : 96/100.

Session 20260801_043224 · Buffer S8 · Fiches agents/citation CLAUDE.md #10 : la prose « Anti-invention » de DEUX fiches — 03_agents/erpnext_backend/AGENT.md:57 (devises USD + DOP · format Letter US · paiements Cardnet, pas Stripe) et 03_agents/crm/AGENT.md:37 (devises USD + DOP · format Letter US) — RECOPIE À LA MAIN les valeurs canoniques de la contrainte NON-NÉGOCIABLE CLAUDE.md #10 en AFFIRMANT « cités depuis CLAUDE.md #10, jamais réinventés », mais AUCUN gate ne liait cette citation à #10.

Tâche : Sprint 8 · buffer (DevOps CI/CD · QA). Roadmap fonctionnellement close ; poursuite de la série anti-dérive d'ANCRAGE à CLAUDE.md (classe claude-md-constant-anchor-gate). Même patron EXACT que le bloc « paramètres canoniques » faisabilite/generator (model.py CANONICAL ↔ #9/#10) et publiciste/branding (4 tokens ↔ #4) — appliqué à une SURFACE distincte : la prose des fiches agents qui se réclame de #10.

Dérive silencieuse fermée :

  • 03_agents/erpnext_backend/AGENT.md:57 + 03_agents/crm/AGENT.md:37 — prose « Anti-invention (#6) » citant en gras les tokens #10 (devises/format/paiement).
  • Source faisant autorité : la contrainte NON-NÉGOCIABLE CLAUDE.md #10 (**USD + DOP** devises · **Letter US** format · **Cardnet** paiements (pas Stripe)).
  • Le seul contrôle amont (bloc « paramètres canoniques ») n'ancre QUE faisabilite/generator/genlib/model.py CANONICAL + son README — AVEUGLE à ces fiches. Aucune suite tests/ (FONCTIONS, jamais l'ancre à CLAUDE.md) n'attrape la prose.
  • Piège #6 : RENOMMER le prestataire (CardnetAzul) ou changer une devise/format dans CLAUDE.md #10 laisse ces citations PÉRIMÉES en silence, contredisant le mandat pendant que la prose se réclame de « CLAUDE.md #10 » — « vert trompeur ».

Gate ajouté (bloc « Fiches agents · CITATION des constantes CLAUDE.md #10 ») : réutilise les valeurs exp déjà recomputées de #9/#10 (zéro duplication) + le prestataire EXCLU ((pas X)) recomputé de #10 ; config table par-fiche du SOUS-ENSEMBLE de tokens qu'elle cite (crm omet le paiement) ; exige de chaque fiche l'ancre « CLAUDE.md #10 » + chaque token en gras **<valeur>** == #10 (casse tolérée) + l'exclusion pour erpnext.

5 morsures vérifiées (restauré vert après chacune, 7 gates re-verts) :

  1. CLAUDE.md #10 CardnetAzul → mord erpnext SEUL (crm ne cite pas le paiement) ✓
  2. CLAUDE.md #10 devises USD + DOPUSD + EUR → mord les DEUX fiches ✓
  3. CLAUDE.md #10 (pas Stripe)(pas PayPal) → mord l'exclusion erpnext ✓
  4. fiche crm Letter USLetter USA → mord CRM (format) ✓
  5. fiche erpnext supprime l'ancre CLAUDE.md #10 → mord INTROUVABLE ✓

Session 20260801_050225 · Buffer S8 · SEO/schema.org · IDENTITÉ du nœud racine Organization ancrée sur CLAUDE.md §Entités : le nom de marque Helios RD (la marque publique · CLAUDE.md §Entités « Helios RD (marque publique) sous WAG ») et son url == base_url du site, portés par le nœud racine du graphe JSON-LD 05_deliverables_mvp/seo/out/seo_schema_org.json, étaient byte-gatés pour la REPRODUCTIBILITÉ (check_artifacts prouve que l'artefact se reconstruit depuis seo_spec.json) mais JAMAIS ANCRÉS à CLAUDE.md. Le bloc SEO schema.org amont gate la COMPOSITION (@type + bijection un-listing-par-projet), aveugle à l'IDENTITÉ de la marque que Google indexe.

Tâche : Sprint 8 · buffer (DevOps CI/CD · QA). Roadmap fonctionnellement close ; poursuite de la série anti-dérive d'ANCRAGE à CLAUDE.md (classe claude-md-constant-anchor-gate). Même patron EXACT que les tokens branding publiciste/branding ancrés sur CLAUDE.md #4 et les paramètres canoniques faisabilite/generator ancrés sur #9/#10 (CANONICAL ↔ contrainte) — appliqué à une surface distincte : l'identité de marque émise dans le JSON-LD public de vente.otov7.com, dont la reproductibilité byte était gatée mais pas l'ancrage.

Dérive silencieuse fermée :

  • 05_deliverables_mvp/seo/out/seo_schema_org.json @graph — nœud racine Organization : name: "Helios RD" + url: "https://vente.otov7.com" + @id: ".../#organization" ; chaque nœud Residence (listing) porte brand.@id == org.@id + address.addressCountry == "DO".
  • 05_deliverables_mvp/seo/seo_spec.json[schema_org] — l'INPUT du générateur : organization.name == "Helios RD", country_code == "DO", et un bullet source qui DÉCLARE l'ancrage « CLAUDE.md §Entites (Helios RD = marque publique sous WAG) ».
  • Source faisant autorité : CLAUDE.md §EntitésHelios RD (marque publique) sous WAG ») pour la marque + MANIFEST.base_url (byte-gaté) pour le base_url + le country_code du spec pour le géo-ciblage.
  • Piège #6 : la byte-gate prouve que l'artefact se reconstruit fidèlement… depuis un spec dérivé, PAS que le nom == CLAUDE.md. RENOMMER la marque dans seo_spec.json (ou dans CLAUDE.md §Entités) puis régénérer laisse le JSON-LD public émettre un nom de marque qui CONTREDIT le mandat pendant que check_artifacts reste VERT — « vert trompeur ». Aucune suite tests/ (FONCTIONS de génération, jamais l'ancrage à CLAUDE.md) ne l'attrape.
  • État courant : aucune valeur périmée — name/url/@id/rattachements/ addressCountry/déclaration du spec recoupent CLAUDE.md+MANIFEST+spec exactement (anti-invention #6, rien à réécrire). Le défaut est la surface ungated.

Gate ajouté (ci/check_readme_claims.sh, nouveau bloc « SEO org » après le bloc composition schema.org) : la marque est re-dérivée de CLAUDE.md §Entités (parenthèse « (marque publique) », exigée présente 1× exactement · zéro duplication), puis : (a) Organization.name == marque CLAUDE.md ; (b) Organization.url == MANIFEST.base_url et @id sous base_url ; (c) chaque listing rattaché à CETTE Organization (brand.@id == org.@id) et sous base_url ; (d) addressCountry UNIFORME == country_code du spec (code ISO 2 lettres, géo-ciblage appliqué à l'identique) ; (e) le spec DÉCLARE l'ancrage (organization.name == marque + source cite CLAUDE.md + la marque). Racine Organization exigée singleton en pré-requis.

6 morsures vérifiées (restauré vert après chacune, 7 gates re-verts) :

  1. artefact Organization.nameHelios DR → mord (a) anchor ✓
  2. CLAUDE.md §Entités rename Helios RDHelios Dominicana → mord SIMULTANÉMENT (a) artefact ET (e) spec — l'ancre est vive ✓
  3. artefact Organization.url divergent → mord (b) url/@id ✓
  4. artefact un addressCountry DOUS → mord (d) uniformité géo ✓
  5. spec organization.source supprime l'ancre CLAUDE.md → mord (e) déclaration ✓
  6. artefact un listing brand.@id#phantom → mord (c) rattachement ✓

Restauré = green : Helios RD == CLAUDE.md · url == base_url · 9 listings rattachés sous base_url · addressCountry uniforme DO · spec déclare l'ancrage · exit 0. Working tree byte-restauré (git checkout --, JAMAIS git clean, interdit absolu) · 7 gates re-verts (check_readme_claims/check_ci_integrity/ check_docs/guard_constraints/check_artifacts/check_regression/validate_json).

  • ci/README.md (clause de la table récap du pipeline + paragraphe détaillé « Identité du nœud racine Organization ancrée sur CLAUDE.md §Entités ») 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 ; l'injection réelle du <script type="application/ld+json"> dans les pages vente.otov7.com reste côté agent Frontend/SEO / VPS).
  • Auto-score 4Big : 96/100.

Session 20260801_053228 · Buffer S8 · Legal/CONFOTUR · IDENTITÉ des ENTITÉS porteuses ancrée sur CLAUDE.md §Entités : le champ Select entite_porteuse du DocType CONFOTUR Application — le menu déroulant qui fixe QUELLE entité juridique porte chaque dossier d'incitation touristique — offre pour options les 7 entités canoniques du mandat (WAF · WA SRL · AC Arias Cuevas · Consortium ECR DR · Helios RD · Ploutos · 9060 QC). Ces options sont DÉRIVÉES via options_source de confotur_spec.json[entites] et byte-gatées pour la REPRODUCTIBILITÉ par check_artifacts (l'artefact doctype_confotur_application.json se reconstruit depuis le spec) mais JAMAIS ANCRÉES à CLAUDE.md §Entités, leur source faisant autorité. Les blocs CONFOTUR amont ne gatent que le COMPTE de champs/rôles et la cross-cohérence des PERMISSIONS, AVEUGLES à l'identité de cette liste d'entités.

Tâche : Sprint 8 · buffer (DevOps CI/CD · QA). Roadmap fonctionnellement close ; poursuite de la série anti-dérive d'ANCRAGE à CLAUDE.md (classe claude-md-constant-anchor-gate). Même patron EXACT que la marque SEO org ancrée sur CLAUDE.md §Entités (session précédente 050225) et les tokens branding publiciste/branding ancrés sur #4 — appliqué à une surface distincte du MÊME §Entités : le déroulant que l'agent ERPNext Backend importe (bench import-fixtures), dont la reproductibilité byte était gatée mais pas l'ancrage.

Dérive silencieuse fermée :

  • 05_deliverables_mvp/legal/confotur/out/doctype_confotur_application.json — champ entite_porteuse (Select · reqd), options = les 7 entités séparées par \n.
  • 05_deliverables_mvp/legal/confotur/confotur_spec.json — l'INPUT : entites (la liste) + entites_source (DÉCLARE « CLAUDE.md #Entites (…) ») + le champ dont options_source: "entites" (wiring : le déroulant DÉRIVE de la liste).
  • 05_deliverables_mvp/legal/confotur/README.md:37-38 — prose « entite_porteuse (Select) : entités CLAUDE.md (WAF · WA SRL · AC · Consortium ECR DR · Helios RD · Ploutos · 9060 QC) » (abréviation éditoriale « AC » pour « AC Arias Cuevas »).
  • Source faisant autorité : CLAUDE.md §Entités — les 7 tokens en gras **...** de la section « ## Entités », dans l'ordre.
  • Piège #6 : la byte-gate prouve que l'artefact se reconstruit fidèlement… depuis un spec dérivé, PAS que la liste == CLAUDE.md. RENOMMER une entité dans CLAUDE.md §Entités (9060 QC9061 QC) OU dans confotur_spec.json[entites] puis régénérer laisse le DocType offrir un menu qui CONTREDIT/omet une entité canonique du mandat pendant que check_artifacts reste VERT — « vert trompeur ». L'agent ERPNext Backend importerait un DocType dont le déroulant liste une entité FANTÔME ou en omet une (la restriction même que reqd pose). Aucune suite tests/ (FONCTIONS de génération) ne l'attrape.
  • État courant : aucune valeur périmée — options artefact / entites du spec / déclaration / README recoupent CLAUDE.md §Entités exactement (anti-invention #6, rien à réécrire). Le défaut est la surface ungated.

Gate ajouté (ci/check_readme_claims.sh, nouveau bloc « Legal · CONFOTUR — IDENTITÉ des ENTITÉS porteuses » avant sys.exit) : les 7 entités sont re-dérivées des tokens en gras de la section « ## Entités » de CLAUDE.md (uniques, non vides), puis : (a) options du Select de l'artefact == entités CLAUDE.md (ORDRE exact) ; (b) spec[entites] == CLAUDE.md (l'INPUT ancré) ; (c) le spec DÉCLARE l'ancrage (entites_source cite CLAUDE.md + chaque entité) ; (d) wiring intact (options_source == "entites" — sinon le déroulant est DÉCROCHÉ de la liste ancrée) ; (e) README cite l'ancre « CLAUDE.md » + nomme chaque entité par nom complet OU 1er token (l'abréviation « AC » tolérée · #6, pas de réécriture d'une prose correcte pour satisfaire un gate). Un claim absent échoue AUSSI (traçabilité #6).

7 morsures vérifiées (restauré vert après chacune, 7 gates re-verts) :

  1. CLAUDE.md §Entités 9060 QC9061 QC → mord SIMULTANÉMENT (a) artefact + (b) spec + (c) déclaration + (e) README = 4 bites — l'ancre est vive ✓
  2. artefact retire la dernière option → mord (a) ✓
  3. artefact permute 2 options → mord (a) ORDRE ✓
  4. spec entites renomme PloutosPloutos SA → mord (b) ✓
  5. spec entites_source retire « CLAUDE.md » → mord (c) ✓
  6. spec champ options_sourcehardcoded → mord (d) wiring ✓
  7. README retire l'entité Ploutos de la ligne → mord (e) ✓

Restauré = green : options == CLAUDE.md (7, ordre) · spec == CLAUDE.md · spec déclare l'ancrage · wiring == "entites" · README nomme les 7 · exit 0. Working tree byte-restauré (git checkout --, JAMAIS git clean, interdit absolu) · 7 gates re-verts (check_readme_claims/check_ci_integrity/check_docs/ guard_constraints/check_artifacts/check_regression/validate_json).

  • ci/README.md (clause de la table récap du pipeline + paragraphe détaillé « Entités porteuses du DocType CONFOTUR ancrées sur CLAUDE.md §Entités ») 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 ; l'import réel du DocType CONFOTUR Application reste côté agent ERPNext Backend / VPS).
  • Auto-score 4Big : 96/100.

Session 20260801_060234 · Buffer S8 · Legal/CONFOTUR — cross-cohérence PERMISSIONS répliquée dans la FICHE AGENT 03_agents/onapi_legal/AGENT.md:43-45 : la MÊME table role→portail→actions que le README du module, jamais gatée

Tâche : Sprint 8 · buffer (DevOps CI/CD · QA). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Classe explicitement notée en mémoire (agent-fiche-role-attrs-ungated) : une fiche 03_agents/*/AGENT.md peut restituer la MÊME donnée data-derived qu'un README déjà gaté, sur une surface distincte que le gate README ne voit pas.

Dérive silencieuse fermée :

  • 03_agents/onapi_legal/AGENT.md:43-45 — §Permissions de la fiche de l'agent ONAPI/Legal, annoncée « mot pour mot les permissions_cibles RBAC des 3 rôles » : ventes-confotur (portail Ventes) → read/write/create/print · legal-onapi (portail Direction) → read/write/create · legal-directeur (portail Direction) → read/write/submit/report. Convention DIFFÉRENTE du README du module (/ espacé · **submit** en gras).
  • Source faisant autorité : legal/confotur/out/MANIFEST.json (roles_rbac_utilises[] : role_id · portail · permissions, recomputé du contrat rbac_50_roles.json à chaque build · byte-gaté par check_artifacts).
  • Piège : le bloc « Legal confotur perms · cross-cohérence » existant ne gate QUE la §Permissions du README du MODULE (legal/confotur/README.md), AVEUGLE à la fiche. PROMOUVOIR ventes-confotur à submit (élévation de privilège · casse la séparation des pouvoirs dont is_submittable est déduit), RETIRER une action, RÉAFFECTER un portail, RENOMMER un rôle ou AJOUTER une ligne fantôme dans la FICHE laisse celle-ci périmée pendant que l'artefact (et le README, lui gaté) disent autre chose → l'agent ONAPI/Legal lirait sa PROPRE doc d'identité mentant sur le jeu de permissions qu'il porte (le risque même que « mot pour mot » promet d'écarter) — « vert trompeur » qu'aucune suite tests/ (FONCTIONS de mapping RBAC, jamais la prose de fiche) n'attrape.
  • État courant : aucune ligne périmée — les 3 couples portée→actions et leurs portails recoupent l'artefact exactement (anti-invention #6). Le défaut est la surface ungated.

Gate ajouté (ci/check_readme_claims.sh, nouveau bloc « Legal · CONFOTUR — cross-cohérence PERMISSIONS répliquée dans la FICHE AGENT » après le bloc cross-cohérence du README) : RÉUTILISE conf_perm_by_id/conf_portail_by_id déjà dérivés du MANIFEST par le bloc README (ZÉRO duplication du contrat #6) ; regex tolérant l'espacement / et le gras ** ; PAR rôle le portail (casse normalisée) ET le jeu d'actions exigés EXACTS (set-diff : absent ET en trop) ; identité d'ensemble fiche⇔MANIFEST (aucun rôle fantôme ni manquant). Un claim absent échoue AUSSI (traçabilité).

6 morsures vérifiées : fiche promeut ventes-confotursubmit (en trop) · fiche retire create de legal-onapi (absent) · fiche réaffecte portail Ventes→Direction · fiche renomme legal-directeurlegal-boss (ligne INTROUVABLE + ensemble absents/fantôme) · sous-liste entière supprimée (3 INTROUVABLES + ensemble vide) · artefact promeut ventes-confotursubmit, fiche stale (le vrai « silent green » : source change, fiche fige) ; restauré = green : 3 rôles portail+actions == MANIFEST · identité d'ensemble · exit 0. Working tree byte-restauré (git checkout --, JAMAIS git clean) · 7 gates re-verts.

  • ci/README.md (table récap du pipeline · clause « 3e surface » CONFOTUR) 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_063234 · Buffer S8 · Chat OTOIA/persona+capabilities : l'IDENTITÉ de la persona (Amélie / voix multilingual_v2) et des 4 CAPABILITIES OTOIA (aec.py + knowledge.py + prompt_engine.py + chat.py) du montage Chat par portail frontend/chat_otoia — portées par out/chat_mount.json (5 configs runtime) + out/MANIFEST.json, byte-gatés pour la reproductibilité (check_artifacts prouve la reconstruction depuis chat_spec.json) mais JAMAIS ANCRÉES à CLAUDE.md §Architecture cible, leur source faisant autorité (« Voix Amélie QC (multilingual_v2) » l.30 · « OTOIA capabilities : aec.py + knowledge.py + prompt_engine.py + chat.py » l.28).

Tâche : Sprint 8 · buffer (DevOps CI/CD · QA). Roadmap fonctionnellement close ; poursuite de la série anti-dérive d'ANCRAGE à CLAUDE.md (classe claude-md-constant-anchor-gate). Même patron EXACT que les tokens branding publiciste/branding ancrés sur CLAUDE.md #4, la marque SEO Organization ancrée sur §Entités, les paramètres canoniques faisabilite/generator ancrés sur #9/#10 — appliqué à une surface distincte : la persona + les capabilities émises dans CHAQUE portail rôle, dont la reproductibilité byte était gatée mais pas l'ancrage.

Dérive silencieuse fermée :

  • 05_deliverables_mvp/frontend/chat_otoia/out/chat_mount.json — les 5 mounts, chacun persona: {nom: "Amélie", voix: "multilingual_v2"} + capabilities: [aec.py, knowledge.py, prompt_engine.py, chat.py] ; idem out/MANIFEST.json ; l'INPUT chat_spec.json (persona + capabilities[].module) ; la table README:28-29 (« Persona Amélie / multilingual_v2 » · « Capabilities aec/knowledge/prompt_engine/chat.py » toutes deux annotées « CLAUDE.md §Architecture cible »).
  • Source faisant autorité : CLAUDE.md §Architecture cible (l.28 capabilities · l.30 « Voix Amélie QC (multilingual_v2) »).
  • Piège #6 : le SEUL contrôle d'identité vit dans tests/ (test_persona_amelie_sourcee / test_capabilities_sourcees_sans_ajout) mais son ORACLE est HARDCODÉ (self.assertEqual(spec["persona"]["nom"], "Amélie") · caps == ["aec.py", …]) — une copie de plus, jamais comparée à CLAUDE.md. Renommer la persona (AmélieSophie), changer la voix, ou renommer/RETIRER une capability dans chat_spec.json (ou dans CLAUDE.md) reconstruit l'artefact fidèlement (byte-gate VERTE) sans toucher l'oracle du test (tests VERTS) → le chat monté dans CHAQUE portail annoncerait une persona/voix/capabilities qui CONTREDIT le mandat — « vert trompeur » qu'aucune suite tests/ (FONCTIONS de génération, jamais l'ancre à CLAUDE.md) n'attrape.
  • État courant : aucune valeur périmée — persona + capabilities recoupent CLAUDE.md exactement (anti-invention #6, rien à réécrire). Le défaut est l'ancrage ungated.

Gate ajouté (ci/check_readme_claims.sh, nouveau bloc « Chat OTOIA · persona + capabilities ancrées CLAUDE.md » avant sys.exit) : persona+capabilities re-dérivées de CLAUDE.md §Architecture cible (regex Voix … QC (…) + OTOIA capabilities : … split +, NFC), puis : (a) chat_spec.json persona/capabilities == CLAUDE.md (ordre exact) ; (b) le spec DÉCLARE l'ancrage (persona.source + chaque capability.source citent CLAUDE.md) ; (c) CHACUN des 5 mounts + le MANIFEST portent persona/capabilities == CLAUDE.md ; (d) README cite l'ancre §Architecture cible + nomme persona (Amélie+multilingual_v2) + chaque capability (notation compacte matchée par radical) ; (e) l'ORACLE du test == CLAUDE.md (set-diff). Un claim absent échoue AUSSI (traçabilité #6).

8 morsures vérifiées : CLAUDE.md Amélie→Sophie (mord SIMULTANÉMENT spec/mount/README/oracle — 5 fails, l'ancre est vive) · CLAUDE.md retire chat.py (3 fails) · spec aec.py→exfil.py (capability injectée) · spec persona.source sans CLAUDE.md · UN SEUL mount à voix altérée (byte-gate aveugle) · README retire le radical prompt_engine · oracle du test persona Amélie→Bob · oracle du test capabilities réordonné/tronqué ; restauré = green : 5 sous-checks (a/b/c/d/e) verts · exit 0. Working tree byte-restauré (git checkout --, JAMAIS git clean, interdit absolu) · 7 gates re-verts · unittest module chat_otoia re-vert.

  • ci/README.md (table récap du pipeline + paragraphe détaillé « Persona & capabilities du Chat OTOIA ancrées sur CLAUDE.md §Architecture cible ») 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 ; l'import réel des Custom Block + le câblage de l'endpoint OTOIA restent côté agent ERPNext Backend / VPS).
  • Auto-score 4Big : 96/100.

Session 20260801_070237 · Buffer S8 · Mobile/app_config : le SDK MAJEUR Expo (« Expo 54 ») — la version majeure que l'app compagnon cible, ANCRÉE sur la roadmap Sprint 5 l.56 « Rebuild Expo 54 » — était transcrite À LA MAIN cinq fois dans le README SANS AUCUN gate d'IDENTITÉ ni d'ANCRAGE. Le bloc « Mobile · récap de l'app Expo » existant ne gate QUE le quadruplet onglets/rôles/langues/identifiants a_confirmer (comptes de MANIFEST.counts), AVEUGLE au majeur du SDK.

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'ANCRAGE que la marque SEO §Entités (Helios RD ancrée sur CLAUDE.md) ou les roadmap_line de la recette qa/acceptance — appliqué ici à une valeur ancrée sur la ROADMAP (source faisant autorité de la version Expo, CLAUDE.md ne la spécifiant pas).

Dérive silencieuse fermée :

  • 05_deliverables_mvp/mobile/app_config/README.md:4/19/35/72 — « Expo 54 » (×4 mentions) + la ligne de source « Expo SDK 54 | roadmap Sprint 5 l.56 ».
  • Chaîne de dérivation (byte-gatée par check_artifacts) : roadmap Sprint 5 l.56 « Rebuild Expo 54 » (INPUT faisant autorité) → mobile_spec.json[app] (expo_sdk_major = 54 + expo_sdk_source cite la roadmap) → out/app_config.json.expo.extra.expoSdkMajor = 54 + out/MANIFEST.json .app.expo_sdk_major = 54.
  • Piège #6 : BUMPER le SDK (Expo 55 sort · eas build cible 55) dans le spec/artefact SANS toucher au README (ou l'inverse), ou DÉRIVER la roadmap de l'artefact, laisse la prose PÉRIMÉE en silence pendant que le graphe byte-gaté dit autre chose → l'agent Mobile lancerait eas build sur le MAUVAIS SDK (le rebuild même que la ligne roadmap prescrit). Aucune suite tests/ (FONCTIONS de génération, jamais la prose ni l'ancre à la roadmap) n'attrape ce « vert trompeur ».
  • État courant : aucune mention périmée — les 4 « Expo 54 », le spec, le MANIFEST et la roadmap concordent (anti-invention #6, rien à réécrire). Le défaut est la surface ungated + non ancrée à la roadmap.

Gate ajouté (ci/check_readme_claims.sh, nouveau bloc « Mobile · le SDK MAJEUR Expo … ANCRÉ sur la roadmap Sprint 5 l.56 », inséré avant sys.exit) : le majeur sdk re-dérivé de app_config.json byte-gaté, puis (a) cohérence interne artefact ⇔ MANIFEST.app.expo_sdk_majorspec.app.expo_sdk_major ; (b) ancrage roadmapre.search("Rebuild Expo (\d+)") sur 04_roadmap/ROADMAP_8_WEEKS_OR_LESS.md == sdk (ancre vive) ; (c) le spec DÉCLARE l'ancrage (expo_sdk_source cite « roadmap » + « Expo N ») ; (d) toute mention prose « Expo [SDK] N » (regex Expo(?:\s+SDK)?\s*\*{0,2}(\d+)) == sdk (aucune périmée) ; (e) la ligne de source du tableau cite l'ancre roadmap à côté du majeur. Un claim absent échoue AUSSI (traçabilité).

7 morsures vérifiées : README Expo SDK **54**→**55** (prose périmée) · artefact 54→55 (README + roadmap périmés = le vrai silent green) · roadmap Rebuild Expo 54→55 (artefact périmé · ancre qui dérive) · MANIFEST 54→55 (chaîne interne incohérente) · spec expo_sdk_source sans « roadmap » (ancrage déclaré manquant) · README ligne de source sans « roadmap » (ancrage prose manquant) · README retire TOUTE mention « Expo N » (claim absent) ; restauré = green : cohérence interne · ancre roadmap vive · spec déclare · 4 mentions == 54 · ligne source cite roadmap · 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é « SDK majeur Expo de l'app mobile ancré sur la roadmap Sprint 5 l.56 ») 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 ; eas build/ eas submit App Store #32 / Play Store restent côté agent Mobile · VPS).
  • Auto-score 4Big : 96/100.