Files
oto-enterprise-os-dtp/05_activity_log/2026-08-01.md
T
Claude Code DTP Worker e53d60fc0b [DTP-Worker] Sprint 8 · buffer · Legal/CONFOTUR : IDENTITÉ des ENTITÉS porteuses du DocType CONFOTUR Application ANCRÉE sur CLAUDE.md §Entités — le champ Select entite_porteuse (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), DÉRIVÉES via options_source de confotur_spec.json[entites] et BYTE-GATÉES pour la REPRODUCTIBILITÉ par check_artifacts (l'artefact 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. Piège #6 : 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 pendant que la byte-gate reste VERTE (l'artefact se reconstruit fidèlement… depuis un spec dérivé) — « vert trompeur » de la MÊME classe que la marque SEO org (§Entités) et les tokens branding (#4). 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, jamais l'ancre à CLAUDE.md) ne l'attrape. Gate ajouté (bloc « Legal · CONFOTUR — IDENTITÉ des ENTITÉS porteuses ») : les 7 entités re-dérivées des tokens en gras de la section « ## Entités » de CLAUDE.md, puis (a) options de l'artefact == 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" · (e) README cite l'ancre CLAUDE.md + nomme chaque entité (nom complet OU 1er token · abréviation « AC » tolérée · #6). 7 morsures vérifiées (CLAUDE.md rename → 4 bites a/b/c/e simultanées, l'ancre est vive · artefact drop option · artefact reorder · spec entites renommé · spec source sans CLAUDE.md · spec options_source hardcoded · README retire une entité), restauré vert, 7 gates re-verts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-01 05:41:06 +00:00

682 lines
54 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
## 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-tests``rbac-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-tests``seo-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** `:29`**Nom 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 Vente``OTO 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é
(`name``OTO 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 (`Cardnet``Azul`) 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 `Cardnet``Azul` → mord erpnext SEUL (crm ne cite pas le paiement) ✓
2. CLAUDE.md #10 devises `USD + DOP``USD + EUR` → mord les DEUX fiches ✓
3. CLAUDE.md #10 `(pas Stripe)``(pas PayPal)` → mord l'exclusion erpnext ✓
4. fiche crm `Letter US``Letter 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és****Helios 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.name``Helios DR` → mord (a) anchor ✓
2. **CLAUDE.md §Entités** rename `Helios RD``Helios 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` `DO``US` → 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 QC``9061 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 QC``9061 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 `Ploutos``Ploutos SA` → mord (b) ✓
5. **spec** `entites_source` retire « CLAUDE.md » → mord (c) ✓
6. **spec** champ `options_source``hardcoded` → 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.