Files
oto-enterprise-os-dtp/05_activity_log/2026-08-01.md
T

1574 lines
120 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.
## 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-confotur``submit` (en trop) ·
fiche retire `create` de `legal-onapi` (absent) · fiche réaffecte portail
Ventes→Direction · fiche renomme `legal-directeur``legal-boss` (ligne
INTROUVABLE + ensemble absents/fantôme) · sous-liste entière supprimée (3
INTROUVABLES + ensemble vide) · **artefact** promeut `ventes-confotur``submit`,
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élie``Sophie`), 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_major``spec.app.expo_sdk_major` ; (b)
**ancrage roadmap**`re.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.
## Session `20260801_073239` · Buffer S8 · Fiscal/ecf_dgii : le JEU DE DEVISES `moneda` (`USD`/`DOP`) ANCRÉ sur CLAUDE.md #10 — le champ que `TipoMoneda` porte dans chaque e-CF DGII, byte-gaté pour la REPRODUCTIBILITÉ mais JAMAIS ancré à la contrainte. Le README s'en réclame « #10 » (README:37/57/77) ; les blocs fiscaux amont gatent le FormaPago (Cardnet · #10) + états/champs émetteurs, AVEUGLES au jeu de devises ; le bloc « Fiches #10 » ancre les fiches ERPNext/CRM, AVEUGLE au module fiscal.
**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
les paramètres canoniques `faisabilite/generator` (#9/#10), la marque SEO §Entités
(`Helios RD`), la persona Chat OTOIA (§Architecture cible) ou les tokens branding #4
— appliqué ici à une valeur ancrée sur **CLAUDE.md #10****USD + DOP** devises »).
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/fiscal/ecf_dgii``moneda.options = ["USD","DOP"]` dans le
spec ET (byte-gaté par `check_artifacts`) dans `out/ecf_plan.json`.
- Chaîne de dérivation : CLAUDE.md #10 « **USD + DOP** devises » (INPUT faisant
autorité) → `ecf_spec.json[moneda].options``out/ecf_plan.json.moneda.options`
(copie verbatim par `ecflib/builder.py:79`).
- Piège #6 : **byte-gater une constante prouve la REPRODUCTIBILITÉ, PAS l'ANCRAGE**.
`check_artifacts` prouve `ecf_plan==build` depuis le spec, JAMAIS que
`spec[moneda]==#10`. RENOMMER/ÉTENDRE les devises dans CLAUDE.md #10
(`USD + DOP``USD + EUR`, ou +`EUR`) ET dans le spec de façon cohérente reste
byte-VERT tout en faisant émettre à l'e-CF un `TipoMoneda` d'une devise qui
CONTREDIT le mandat — « vert trompeur » qu'aucune suite `tests/` (FONCTIONS de
génération, jamais l'ancre à CLAUDE.md) n'attrape. L'agent ERPNext Backend
câblerait `Encabezado.TipoMoneda` sur une devise hors mandat (le risque même que
la cross-cohérence e-CF veut prévenir).
- **État courant** : aucune dérive — spec/artefact/README concordent avec #10
(anti-invention #6). Le défaut était la **surface byte-gatée mais non ancrée** +
l'absence de déclaration d'ancrage (`moneda.source` legitimement `null` car il
source le **taux TipoCambio**, à confirmer côté VPS · #8 — l'ancre des OPTIONS
devait vivre dans un champ DISTINCT).
**Correctif de déclaration** : nouveau champ `moneda.options_source` dans
`ecf_spec.json` (+ requis dans `ecf.schema.json`) citant « CLAUDE.md #10 — devises
**USD + DOP** » ; distinct de `source` (taux, reste `null`/`a_confirmer`). Artefact
`out/ecf_plan.json` régénéré (byte-gate re-verte · 39 tests module verts · le compte
`a_confirmer` inchangé, il ne teste que `moneda.a_confirmer`).
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « Fiscal · ecf_dgii — le JEU DE
DEVISES `moneda` … ANCRÉ sur CLAUDE.md #10 », inséré après la famille « Fiches
#10 ») : RÉUTILISE `exp["devises"]` déjà recomputé depuis #10 (zéro duplication),
`want_cur = re.findall("[A-Z]{3}", exp["devises"])`, puis (a) `spec[moneda].options`
== `want_cur` ORDRE EXACT (mord ajout/retrait/réordonnancement) ; (b) l'artefact
byte-gaté idem ; (c) `moneda.options_source` cite `CLAUDE.md #10` + chaque devise ;
(d1) la ligne README « devise (…USD/DOP… · #10) » cite le jeu EXACT ; (d2) toute
mention `moneda`/`devise` d'une paire de devises (scan ancré sur le contexte, évite
le faux positif `RNC / ITBIS`) == le jeu (aucune périmée). Un claim absent échoue
AUSSI (traçabilité).
**7 morsures vérifiées** : CLAUDE.md #10 `USD+DOP→USD+EUR` (l'ancre est vive →
mord a+b+c+d1 en cascade) · **spec** `options USD/DOP→USD/EUR` régénéré (le vrai
*silent green* → mord a) · **artefact** seul `USD→EUR` (spec+README périmés → mord
b) · **reorder** spec `[DOP,USD]` (ordre exact → mord a) · `options_source` sans
« CLAUDE.md #10 » (ancre déclarée manquante → mord c) · **README** ligne #10
`USD/DOP→USD/EUR` (prose périmée → mord d1) · **README** table `moneda USD/DOP→
USD/EUR` (mention périmée → mord d2) ; restauré = vert (a+b+c+d, 3 mentions
README concordantes · exit 0). Working tree byte-restauré (`git checkout --`,
**JAMAIS** `git clean`) · **7 gates re-verts** (validate_json · check_artifacts ·
check_readme_claims · check_docs · guard_constraints · check_ci_integrity ·
check_regression).
- `ci/README.md` (clause de récap du pipeline + paragraphe détaillé « jeu de
DEVISES `moneda` … 4e surface du MÊME README, ancré sur #10 ») mis à jour.
- **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
la confirmation du **TipoCambio** USD→DOP + l'émission réelle via Compupar
restent côté agent ERPNext Backend / Compta · VPS).
- **Auto-score 4Big** : 96/100.
---
## Session `20260801_080244` · Buffer S8 · SEO/projets_master — IDENTITÉ du CATALOGUE PROJETS (P01..P09) ANCRÉE sur CLAUDE.md §Projets
**Domaine** : SEO · `05_deliverables_mvp/seo/fixtures/projets_master.json` + artefacts `out/seo_schema_org.json`.
**Constat (anti-drift #6)** : la matière première du worker SEO est `projets_master.json` — 9 projets (`code` + `nom`) que le bundle SEO expose à Google (un listing `Residence` par projet dans le JSON-LD indexé sous `vente.otov7.com/projets/…`). Chaque entrée **DÉCLARE** `source.fichiers: ["CLAUDE.md#Projets"]` et le README:80 l'affirme (« 9 projets P01..P09 · noms sourcés de `CLAUDE.md §Projets` ») — **MAIS AUCUN gate ne le VÉRIFIAIT**. `check_artifacts` prouve QUE les `out/` se reconstruisent depuis la fixture (byte-for-byte), PAS que ses `code`/`nom` == CLAUDE.md §Projets ; les blocs SEO amont gatent les COMPTES de mots-clés, la COMPOSITION du graphe schema.org (bijection un-listing-par-projet) et l'IDENTITÉ de la marque racine `Helios RD` (§Entités) — tous AVEUGLES à l'identité de la **liste de projets**.
**Vert trompeur** : RENOMMER un projet dans CLAUDE.md §Projets (« Aqua Terra Las Terrenas » → « … Bay »), le RENOMMER/AJOUTER/RETIRER dans la fixture, puis régénérer, laisse le JSON-LD PUBLIC lister un projet FANTÔME ou en OMETTRE un canonique pendant que la byte-gate reste VERTE (l'artefact se reconstruit fidèlement… depuis une fixture DÉRIVÉE) et que le compte reste 9. Même classe EXACTE que la marque org (§Entités · session 050225), les entités CONFOTUR (§Entités · 053228) et les libellés projets du run_sheet démo (§Projets · déjà gaté). Aucune suite `tests/` (FONCTIONS de génération, jamais l'ancrage à CLAUDE.md) ne l'attrape.
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « SEO · l'IDENTITÉ du CATALOGUE PROJETS ») — RE-DÉRIVE le catalogue de CLAUDE.md §Projets (zéro duplication · source unique), puis exige :
- **(a)** identité d'ensemble des codes fixture ⇔ CLAUDE.md §Projets (aucun fantôme/manquant · pas de doublon) ;
- **(b)** `nom` de chaque entrée == CLAUDE.md §Projets (accent-**insensible** : la fixture normalise « Xamaná »→« Xamana ») ;
- **(c)** chaque entrée DÉCLARE l'ancrage `CLAUDE.md#Projets` dans `source.fichiers` ;
- **(d)** les `name` des listings du graphe schema.org PUBLIC == l'ensemble des noms CLAUDE.md §Projets + bijection (1 listing/projet) + `MANIFEST.counts` projects/listings == |§Projets| ;
- **(e)** le README cite l'ancrage CLAUDE.md §Projets. Un claim absent échoue AUSSI (disparition = régression).
**Vérif** : 13 confirmations vertes (9 noms + set codes + listings schema.org + MANIFEST + README) ; **7 morsures vérifiées puis restaurées** — CLAUDE.md rename P07 (bite b+d simultanés, l'ancre est vive) · fixture nom drift P03 · fixture drop P09 (set manquant) · fixture add fantôme P10 (set fantôme) · fixture strip `source.fichiers` P01 · artefact schema.org rename `Structure``Estructura` (le vrai silent-green public) · README retire la citation §Projets. Restauré vert. **7 gates CI re-verts** (`check_readme_claims`, `check_artifacts`, `check_docs`, `check_ci_integrity`, `guard_constraints`, `check_regression`, `validate_json`).
- **Auto-score 4Big** : 96/100.
## Session `20260801_083254` · Buffer S8 · DevOps/deploy_runbook — IMPUTABILITÉ des CONFIRMATIONS préalables VPS (owner + réf-contrôle) ancrée sur out/deploy_runbook.json qa/audit_5d
**Domaine** : DevOps · `05_deliverables_mvp/devops/deploy_runbook/README.md:55-58`
(ligne « Confirmations préalables VPS ») `out/deploy_runbook.json` (byte-gaté)
`05_deliverables_mvp/qa/audit_5d/out/audit_report.json` (byte-gaté).
**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 la table run-book (2e surface `deploy_runbook`) ou l'identité du RÔLE
émetteur e-CF — appliquée à une **3e surface distincte du MÊME README `deploy_runbook`** :
non plus le COMPTE de confirmations ni l'ENSEMBLE de leurs noms (déjà gatés), mais
l'**imputabilité** de chacune — QUI la porte et QUEL contrôle audit_5d la trace.
**Dérive silencieuse fermée** :
- `devops/deploy_runbook/README.md:55-58` — la ligne « Confirmations préalables VPS »
annote CHAQUE confirmation réglementaire d'un COUPLE data-derived
`nom` (**owner** · **réf-contrôle**) : `taux_commission` (Direction · audit_5d D1.1) ·
`rnc_emisor` (Compta · D1.2) · `itbis_tipocambio` (Fiscaliste eCF · D1.3) ·
`seuil_uaf` (Oficial de Cumplimiento · D2.3) · `endpoint_otoia` (ERPNext Backend ·
session 19). L'OWNER = qui doit confirmer le paramètre avant déploiement ; la RÉF =
le contrôle qa/audit_5d (`D#.#`) — ou la session — qui en assure le suivi.
- Sources faisant autorité (double, byte-gatées par `check_artifacts`) : (a) les
`confirmations` de `out/deploy_runbook.json` — chaque item porte `owner` + `source` ;
(b) `qa/audit_5d/out/audit_report.json` — chaque `control` (`D1.1`..`D5.3`) porte
SON PROPRE `owner`. L'owner et le contrôle ne vivent PAS dans le MANIFEST
(`confirmations_ouvertes` n'est qu'une liste de NOMS) : ils ne sont vérifiables que
contre ces deux artefacts croisés.
- Piège #6 : les blocs DevOps amont ne gatent que le COMPTE (2) et l'ENSEMBLE des
noms — AVEUGLES à QUI porte chaque confirmation et à QUEL contrôle la trace.
RÉAFFECTER `taux_commission` de Direction à Ventes (mauvais responsable escaladé →
confirmation jamais obtenue · séparation des pouvoirs cassée), MISATTRIBUER le
contrôle (`D1.1``D2.1`, pointant un contrôle qui ne trace PAS ce paramètre) ou
citer un contrôle FANTÔME (`D9.9`, absent d'audit_5d) laisse la prose périmée EN
SILENCE pendant que l'artefact/l'audit disent autre chose → l'agent DevOps
escaladerait le mauvais owner / le mauvais contrôle. Aucune suite `tests/`
(FONCTIONS de graphe) n'attrape ce « vert trompeur ».
- **État courant** : aucune annotation périmée — les 5 couples owner/réf recoupent
`deploy_runbook.json`, et les 4 réf `D#.#` existent dans audit_5d avec un owner
compatible (`Oficial de Cumplimiento``Oficial de Cumplimiento / UAF`, `Fiscaliste
eCF``Compta Fiscaliste eCF`) (anti-invention #6, rien à réécrire). Le défaut est
la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « 2ter) deploy_runbook —
IMPUTABILITÉ des CONFIRMATIONS » après le bloc table de phases) : confirmations
aplaties de `deploy_runbook.json` (unicité inter-phases), contrôle audit_5d résolu de
la `source` (`D#.#`), annotations README parsées `nom` (owner · réf), puis **(a)**
cross-module — chaque confirmation tracée porte un contrôle audit_5d RÉEL dont l'owner
est COMPATIBLE avec l'owner run-book (sous-ensemble de tokens · tolère « / UAF ») ET
elle reste ANNOTÉE au README ; **(b)** chaque annotation désigne une confirmation
RÉELLE (pas fantôme), owner README ⊆ owner run-book (tolère l'abréviation), et la réf
== contrôle réel (présent audit_5d + source) ou, pour une réf textuelle
(« session 19 »), présente dans la source. Un claim absent échoue AUSSI (traçabilité #6).
**9 morsures vérifiées** (4 côté README · 3 côté artefact · 2 réf) : README réaffecte
owner `Direction→Ventes` (mauvais responsable) · README misattribue `D1.1→D2.1`
(contrôle périmé vs source) · README cite contrôle fantôme `D9.9` · README retire
l'annotation de `taux_commission` (tracée par D1.1, INTROUVABLE) · **artefact**
réaffecte `taux_commission` owner→Ventes (README stale = le vrai *silent green* · mord
cross-module ET README⊆) · **artefact** audit_5d D1.1 owner `Direction→Finanzas`
(divergence inter-modules) · **artefact** source `seuil_uaf` `D2.3→D9.9` (contrôle
FANTÔME cité par la source) · README annotation fantôme (`endpoint_otoia→endpoint_xxx`) ·
README réf textuelle `session 19→session 42` (absente de la source) ; restauré = green :
5 couples owner/réf == artefacts croisés · 4 réf `D#.#` présentes audit_5d + source ·
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é « 3ᵉ
surface `devops/deploy_runbook` — imputabilité des confirmations ») 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'obtention
réelle des 7 confirmations auprès de leurs owners reste côté agent DevOps / VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_093301` · Buffer S8 · RBAC/RBAC_50_ROLES_SPEC §3 — DÉFAUT FACTUEL corrigé + gate d'identité par portail
**Contexte** : Sprint 8 · buffer (roadmap fonctionnellement close ; poursuite de la
série anti-dérive CLAUDE.md #6). Cette fois la surface data-derived non gatée était
**FAUSSE**, pas seulement à risque : la table §3 « Cartographie portails ↔ familles »
de `RBAC_50_ROLES_SPEC.md` (colonne « Nb rôles » PAR portail) avait dérivé du contrat
`rbac_50_roles.json` **en préservant le total 50**, donc en silence.
**Défaut factuel trouvé & corrigé** :
- `05_deliverables_mvp/rbac/RBAC_50_ROLES_SPEC.md:47` — portail `direction` affichait
**10** rôles ; le contrat en compte **9** (6 direction + CFO/finance + analyste
faisabilité + 2 legal).
- `…:52` — portail `plateforme` affichait **5** ; le contrat en compte **6** (devops ·
rbac-admin · qa · otoia · mobile · bi).
- L'erreur était compensée (10+5 vs 9+6) → total 50 exact → invisible aux gates
agrégés. Le MANIFEST roleprofile (byte-gaté) disait déjà 9 et 6 : la SPEC seule
avait dérivé. Corrigé pour refléter la source.
**Dérive silencieuse fermée (gate durable)** :
- `ci/check_readme_claims.sh` — nouveau bloc « 1quater) RBAC_50_ROLES_SPEC §3 » : RECOMPTE
le nombre de rôles PAR portail depuis `rbac/rbac_50_roles.json` (source de vérité,
byte-gatée par `check_artifacts`) et exige l'égalité avec CHAQUE ligne de la table §3
**plus le total** `**Total** | **50**`. Un portail absent de la table échoue aussi
(disparition = régression de traçabilité). Complète le gate roleprofile (qui gate le
MANIFEST, un doc distinct) sur une 2ᵉ surface transcrivant la même ventilation.
- Vérif négative : réintroduire `direction=10` fait échouer le gate (exit 1, message
« dit 10 MAIS rbac_50_roles.json compte 9 ») ; restauré → vert.
**Artefact régénéré (reproductibilité #artifact-reproducibility-gate)** :
- `qa/audit_4big/out/quality_report.json` — l'`evidence` DOC cite la taille en octets de
la SPEC (10040 → 10039 octets après correction 1 o) ; rebuild `audit_4big_gen.py build
-o out` byte-identique. `check_artifacts` re-vert.
**CI** : les 7 gates PASS (validate_json · check_artifacts · check_docs ·
check_readme_claims · guard_constraints · check_regression · check_ci_integrity).
- **Hors périmètre worker (VPS · #8)** : néant (correction d'un doc en-repo + gate
bash/python3 stdlib ; l'unique artefact `out/` touché a été régénéré à l'identique).
- **Auto-score 4Big** : 96/100.
## Session `20260801_100301` · Buffer S8 · RBAC/RBAC_50_ROLES_SPEC §7 — comptes du run-book d'application ancrés sur le contrat rbac_50_roles.json
**Contexte** : Sprint 8 · buffer (roadmap fonctionnellement close ; poursuite de la
série anti-dérive CLAUDE.md #6). Même classe que le §3 gaté à la session précédente
(`093301`, per-portail) : une **2ᵉ surface de la MÊME SPEC** transcrivant à la main des
comptes data-derived, déjà gatés ailleurs mais AVEUGLES à cette note (mémoire
[[rbac-spec-portail-count-gate]]).
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/rbac/RBAC_50_ROLES_SPEC.md` §7 — la NOTE « ✅ … est **livré**
en-repo » restitue TROIS comptes du run-book d'application sur une **surface
DISTINCTE** des READMEs `userperm_gen` / `roleprofile_gen` (déjà gatés) et du §3
(ventilation par portail, bloc `1quater`) : « **28** templates `Company` (portée
`entite`) » · « **6 profils** (**5** portails métier + console technique
`plateforme`) » · « **50 rôles de façon bijective** ».
- Source faisant autorité : `rbac/rbac_50_roles.json` (contrat, byte-gaté par
`check_artifacts`). Recomputes : 28 == |`scope_donnees == entite`| (= nombre de
`User Permission` templates) · 6 == |portails distincts| (1 `Role Profile` par
portail) · 5 == portails **métier** (hors `plateforme`) · 50 == |rôles|.
- Piège #6 : BUMPER une portée `entite``groupe` (le décompte de templates change) ou
AJOUTER/RETIRER un portail (le décompte de profils change) dans le contrat périme la
note EN SILENCE pendant que les READMEs de volet et le §3 restent verts → l'agent
ERPNext Backend lirait un run-book promettant un décompte faux. Aucune suite `tests/`
(FONCTIONS de génération/résolution RBAC, jamais la prose de la SPEC) n'attrape ce
« vert trompeur ».
- **État courant** : aucun compte périmé — 28/6/5/50 recoupent le contrat exactement
(anti-invention #6, rien à réécrire). Le défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « 1quinquies) RBAC_50_ROLES_SPEC §7 »
après le bloc §3) : réutilise `src_roles` (contrat) + `spec_rd` (déjà chargés par le
§3, zéro duplication). Le blockquote markdown `> ` de la note survit au collapse de
`read_norm` et fragmente la prose (« 28 > templates ») ⇒ **neutralisé** (`spec7 =
spec_rd.replace("> ", "")`) avant match. Exige : (a) « N templates `Company` (portée
`entite`) » == |scope==entite| ; (b) « **N profils** (M portails métier + console
technique `plateforme`) couvrant les **K rôles de façon bijective** » == |portails
distincts|/|métier hors plateforme|/|rôles|. Garde anti-typo : `plateforme` doit exister
au contrat (sinon la note « console technique plateforme » est fausse). Un claim absent
échoue AUSSI (INTROUVABLE = régression #6).
**6 morsures vérifiées** (restauré vert après chacune) : SPEC `28→27` (prose périmée) ·
SPEC `6 profils→7` · SPEC `5 portails métier→4` · SPEC `50→49` · **contrat** une portée
`entite→groupe` (28→27 · README stale = le vrai *silent green* · « SPEC dit 28 MAIS
compte 27 ») · SPEC retire « 28 templates » (claim absent = INTROUVABLE) ; restauré =
green : UP templates 28 == |entite| · Role Profiles 6 (métier 5) bijectifs sur 50 ==
contrat · 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é « Comptes
du run-book d'application `RBAC_50_ROLES_SPEC.md` §7 ») 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 ; l'application `bench` réelle
reste côté agent ERPNext Backend / VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_103304` · Buffer S8 · Chat OTOIA — IDENTITÉ des configs runtime (persona · langues · endpoint) dans la fiche Frontend Console
**Contexte** : Sprint 8 · buffer (roadmap fonctionnellement close ; poursuite de la
série anti-dérive CLAUDE.md #6, classe « fiche agent qui restitue des attributs
data-derived, gatée uniquement sur des COMPTES agrégés » — mémoire
[[agent-fiche-role-attrs-ungated]]).
**Dérive silencieuse fermée** :
- `03_agents/frontend_console/AGENT.md:29` — la cellule « Métrique vérifiée » du
livrable `chat_otoia` restitue TROIS attributs data-derived du montage OTOIA :
« persona **Amélie** … · langues **FR/EN/ES** · `` `endpoint: null` `` ». Le bloc
chat_otoia amont (`ci/check_readme_claims.sh`) ne recompute que le **COMPTE**
blocks/mounts/portails (tous = 5) — AVEUGLE à CE QUE dit chaque config runtime.
- Le gate persona/capabilities « ancré sur CLAUDE.md §Architecture cible » vise le
spec, les 5 mounts, le MANIFEST, le README et l'oracle du test — **JAMAIS** cette
cellule de fiche, et il ignore langues comme endpoint. La cellule était donc une
surface data-derived ungated de plus.
- Sources faisant autorité (byte-gatées par `check_artifacts`) :
`frontend/chat_otoia/out/chat_mount.json` (une config par portail · 5) +
`out/MANIFEST.json` (persona/langues/endpoint_statut consolidés).
- **`endpoint: null` = AFFIRMATION D'HONNÊTETÉ #6/#8** : l'endpoint OTOIA n'est PAS
fabriqué (renseigné côté VPS · agent Frontend). Qu'une config gagne un endpoint réel
dans l'artefact et la fiche « endpoint: null » devient un MENSONGE en silence.
- **État courant** : aucun attribut périmé — persona `Amélie` · langues `fr/en/es` ·
`endpoint == null` sur les 5 mounts ET le MANIFEST (anti-invention #6, rien à
réécrire). Le défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « Chat OTOIA · IDENTITÉ des configs
runtime (persona · langues · endpoint) » après le bloc de COMPTE chat_otoia) : charge
`MANIFEST.json` + `chat_mount.json` (5 configs) et exige, sur la cellule de la fiche
(regex `persona (\S+) QC · langues ([A-Z/]+) · \`endpoint: ([^\`]+)\``, ne matche QUE
la ligne :29, pas la prose Scope :12) : (a) persona == `MANIFEST.persona.nom` (le
« QC » est le qualificatif de marque `Voix Amélie QC` de CLAUDE.md, gardé en littéral
NON gaté) ; (b) langues `FR/EN/ES` == `MANIFEST.langues` en majuscule ; (c) `endpoint:
null` avec `all(m.endpoint is None)` sur les 5 mounts (sinon `exp_endpoint` devient
`NON-null(...)` ⇒ morsure d'honnêteté). Cohérences croisées : `MANIFEST.persona.nom`
et `MANIFEST.langues` == CHAQUE mount (montage désynchronisé mordu) ·
`endpoint_statut == 'a_confirmer'` ⇔ endpoints TOUS null (incohérence interne
artefact sinon). Un claim absent échoue AUSSI (INTROUVABLE = régression #6). Aucune
suite `tests/` (FONCTIONS du builder, jamais la prose de la fiche) n'attrape ce
« vert trompeur ».
**6 morsures vérifiées** (restauré vert après chacune) : fiche persona `Amélie→Amelia`
(mismatch) · fiche langues `FR/EN/ES→FR/EN` · fiche `endpoint: null→endpoint: fixé` ·
**artefact** un mount `endpoint null→URL réelle` (bascule d'honnêteté ET incohérence
`endpoint_statut='a_confirmer'` — le vrai *silent green* : l'endpoint fabriqué pendant
que la fiche promet `null`) · fiche retire le bloc attributs (claim absent =
INTROUVABLE) · **artefact** un mount `persona Amélie→Nadia` (montage désynchronisé du
MANIFEST). Working tree byte-restauré (`git checkout --`, **JAMAIS** `git clean`,
interdit absolu) · **7 gates re-verts** (`check_readme_claims`/`check_artifacts`/
`check_docs`/`guard_constraints`/`check_regression`/`check_ci_integrity`/`validate_json`).
- `ci/README.md` (clause de la table récap du pipeline + paragraphe détaillé sous le
bloc de COMPTE chat_otoia, avec renvoi au gate persona/capabilities distinct) 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 ; le câblage réel de l'endpoint
OTOIA + web-component reste côté agent Frontend / VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_110311` · Buffer S8 · SEO — IDENTITÉ des 2 métriques data-derived (schema.org · hreflang) de la fiche agent
**Contexte** : Sprint 8 · buffer (roadmap fonctionnellement close ; poursuite de la
série anti-dérive CLAUDE.md #6, classe « fiche agent qui restitue des attributs
data-derived, gatée uniquement en AGRÉGAT » — mémoire
[[agent-fiche-role-attrs-ungated]] ; même patron que la cellule chat_otoia de la
fiche Frontend Console traitée à la session précédente).
**Dérive silencieuse fermée** :
- `03_agents/seo/AGENT.md:25-26` — le tableau « Livrable réellement produit »
restitue DEUX sorties byte-gatées du bundle SEO à côté du compte de mots-clés
(lui déjà gaté sur README ET fiche) :
- L25 : « graphe JSON-LD : **10 nœuds** (`Organization` + 9 `Residence`) »
- L26 : « **10 pages** × FR/EN/ES + `x-default` »
- Les blocs SEO schema.org (`SEO_SG_RD`) et hreflang (`SEO_HL_RD`) amont ne lisent
QUE `seo/README.md` — JAMAIS la fiche. `SEO_FI` n'était utilisé que par le bloc
de COMPTE de mots-clés. Ces deux cellules étaient donc des surfaces data-derived
ungated : RENOMMER le `@type` des listings, AJOUTER/RETIRER un projet (→ N nœuds
& N pages dérivent) ou ÉCHANGER une langue de contenu périme la fiche pendant que
les trois artefacts byte-gatés (`seo_schema_org.json`/`MANIFEST.json` et
`seo_hreflang.json`) disent autre chose — « vert trompeur » qu'aucune suite
`tests/` (FONCTIONS du builder, jamais la prose de la fiche) n'attrape.
- **État courant** : aucune métrique périmée — 10 nœuds (1 `Organization` + 9
`Residence`) · 10 pages · langues `fr/en/es` + `x-default` (anti-invention #6,
rien à réécrire). Le défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « SEO · FICHE AGENT » inséré
après le bloc hreflang README, réutilise les vérités déjà recomputées par les blocs
schema.org/hreflang amont — zéro duplication #6) :
- L25 (regex `graphe JSON-LD : \*\*(\d+) nœuds\*\* \(`<racine>` \+ (\d+)
`<listing>`\)`) : total == `schema_org_nodes` · racine == `@type` singleton du
graphe · M == `listings` · type listing == `@type` uniforme des listings ·
cohérence arithmétique `total == 1 + M`.
- L26 (regex `\*\*(\d+) pages\*\* × ([A-Z/]+) \+ `x-default``) : `|pages|` == carte
hreflang · jeu de langues (lower) == langues de contenu de l'artefact (set-diff :
absent ET en trop).
- Un claim absent échoue AUSSI (INTROUVABLE = régression #6).
**8 morsures vérifiées** (restauré vert après chacune) : nœuds `10→11` · listing
`Residence→Apartment` · racine `Organization→LocalBusiness` · listings `9→8`
(casse l'arithmétique ET la bijection) · pages `10→12` · langue `es→pt` · retrait
`es` (`FR/EN`) · retrait du claim L25 (INTROUVABLE). Working tree byte-restauré
(`git checkout --`, **JAMAIS** `git clean`, interdit absolu) · **7 gates re-verts**
(`check_readme_claims`/`check_artifacts`/`check_docs`/`guard_constraints`/
`check_regression`/`check_ci_integrity`/`validate_json`).
- `ci/README.md` (clause de la table récap du pipeline + paragraphe détaillé « Fiche
agent SEO — 2 métriques data-derived » après le paragraphe hreflang) 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 ; l'injection réelle des
balises hreflang/JSON-LD dans les pages reste côté agent Frontend/SEO / VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_113314` · Buffer S8 · RBAC/RBAC_50_ROLES_SPEC §4 — colonne « Entité » de la « Cartographie rôles ↔ entités » ancrée sur CLAUDE.md §Entités rbac_50_roles.json
**Contexte** : Sprint 8 · buffer (roadmap fonctionnellement close ; poursuite de la
série anti-dérive CLAUDE.md #6, double classe « ANCRAGE à CLAUDE.md »
([[claude-md-constant-anchor-gate]]) **et** « identité d'ensemble recomputée du
contrat RBAC » [[rbac-spec-portail-count-gate]]). Même patron que les entités CONFOTUR
`entite_porteuse` (session 053228, §Entités) et les comptes SPEC §3/§7 — appliqué à
une **section encore ungated de la MÊME SPEC** : la table §4.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/rbac/RBAC_50_ROLES_SPEC.md` §4 « Cartographie rôles ↔ entités » —
la **colonne de tête** liste les entités porteuses (`entite_principale`) : les **7
entités NOMMÉES** du mandat (`WAF` (holding) · `WA SRL` · `AC Arias Cuevas` ·
`Consortium ECR DR` (paymaster RH) · `Helios RD` (marque publique) · `Ploutos`
(patrimoine/monétaire) · `9060 QC` (World Activities Cdn.)) + le périmètre consolidé
« **Groupe** (consolidé) ».
- **Deux sources faisant autorité** : (i) **CLAUDE.md §Entités** — l'orthographe + le
descripteur parenthétique de chaque entité nommée ; (ii) `rbac_50_roles.json`
(byte-gaté via les 3 MANIFEST) — l'ENSEMBLE des `entite_principale` réellement portées
par les 50 rôles (distinct = 11 Helios RD · 11 WA SRL · 10 AC Arias Cuevas · 8 Groupe ·
5 9060 QC · 3 WAF · 1 Ploutos · 1 Consortium ECR DR = 50).
- Piège : le §3 gate les comptes par **portail**, le §7 les comptes du run-book, les
blocs fiches l'`entite` d'**UN** rôle ; tous **aveugles à l'identité de CETTE liste**.
AJOUTER au contrat un rôle porté par une entité absente de §4 (menu périmé), RENOMMER
une entité dans CLAUDE.md §Entités (`9060 QC→9061 QC`) ou DÉRIVER un descripteur laisse
la SPEC contredire le mandat/le contrat EN SILENCE — « vert trompeur » qu'aucune suite
`tests/` (FONCTIONS RBAC, jamais la prose de la SPEC) n'attrape.
- **État courant** : aucune ligne périmée — §4 == distinct contrat (8 entités) · les 7
descripteurs == CLAUDE.md §Entités · aucune entité décorative (anti-invention #6, rien
à réécrire). Le défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc `1sexies` après le §7, réutilise
`src_roles`/`spec_rd`/`SPEC_RD` déjà en scope · extraction fraîche de CLAUDE.md §Entités
— `_nfc` défini plus bas, non requis, entités ASCII) :
- **(a) ENSEMBLE** — entités de la colonne §4 (`| **Entité** (desc)? |`) == distinct
`entite_principale` du contrat (set-diff : `manquantes` = portées mais hors §4 ·
`fantômes` = listées mais jamais portées).
- **(b) ANCRAGE** — chaque entité NOMMÉE de CLAUDE.md §Entités (tokens en gras) est une
ligne §4 ET son descripteur parenthétique == CLAUDE.md **EXACT** (absence comprise :
`WA SRL`/`AC Arias Cuevas` = sans descripteur des deux côtés).
- **(c)** chaque entité nommée du mandat est PORTÉE ≥1× par le contrat (`cm_ents ⊆
contrat`, aucune décorative).
- Section `## 4.` INTROUVABLE = claim absent = régression #6.
**8 morsures vérifiées** (4 côté contrat/CLAUDE.md · 4 côté SPEC) : contrat +rôle porté
par entité `Fantasia` absente de §4 (`(a)` manquante) · contrat `Ploutos→Ploutos SRL`
sur tous les rôles (`(a)` fantôme+manquante · `(c)` `Ploutos` décorative) · CLAUDE.md
`9060 QC→9061 QC` (`(b)` absent · `(c)` décorative) · CLAUDE.md descripteur
`(holding)→(holding co)` (`(b)` mismatch) · SPEC §4 `AC Arias Cuevas→AC Arias` (`(a)`
fantôme+manquante · `(b)` absent) · SPEC §4 descripteur `marque publique→marque privée`
(`(b)` mismatch) · SPEC §4 retire la ligne « Groupe » (`(a)` manquante) · SPEC supprime
l'en-tête `## 4.` (INTROUVABLE) ; restauré = green : 9 ✓ (`(a)` 8 entités · `(b)` 7
descripteurs · `(c)` 7 portées) · exit 0. Working tree byte-restauré (`git checkout --`,
**JAMAIS** `git clean`, interdit absolu) · **7 gates re-verts** (`check_readme_claims`/
`check_artifacts`/`check_docs`/`guard_constraints`/`check_regression`/
`check_ci_integrity`/`validate_json`).
- `ci/README.md` (clause de la ligne récap du pipeline + paragraphe détaillé « Colonne
Entité de la §4 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 ; le seed réel des
rôles/entités `bench` reste côté VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_120319` · Buffer S8 · Frontend Console/AGENT.md — IDENTITÉ de l'ENSEMBLE des portails métier + exclusion nommée de la console technique
**Contexte** : Sprint 8 · buffer (roadmap fonctionnellement close ; poursuite de la
série anti-dérive CLAUDE.md #6 — fiche-agent qui restitue une donnée gatée seulement
en AGRÉGAT/COMPTE, cf. [[agent-fiche-role-attrs-ungated]]). Même patron que
l'énumération ordonnée des onglets Mobile (`role_navigation`) et la colonne « Entité »
de la SPEC §4 ([[rbac-spec-portail-count-gate]]) — appliqué à une surface encore ungated
de la fiche **Frontend Console**.
**Dérive silencieuse fermée** :
- `03_agents/frontend_console/AGENT.md` restitue à la main, en **deux endroits**
data-derived, l'IDENTITÉ de l'ensemble des portails — jamais gatée hors le COMPTE :
- **L11** « **5 portails rôle métier** (Ventes / Construction / Achat / Compta /
Direction) » — l'ENSEMBLE ORDONNÉ des noms courts.
- **L46** « la console technique `plateforme` (**6ᵉ portail RBAC**) est **exclue** »
— l'identité + le rang + la non-appartenance au métier de la console exclue.
- **Deux artefacts faisant autorité** : (i) `frontend/portails/out/MANIFEST.json`
(byte-gaté) — les workspaces métier (`OTO Ventes` … → noms courts sans le préfixe
« OTO ») ; (ii) `rbac_50_roles.json` — `portails_business` (l'ordre du contrat) + les
portails distincts des rôles (6 : 5 métier + `plateforme`).
- Piège : le bloc « triplets par workspace » recompute cartes/liens/rôles **par**
portail mais reste **aveugle à QUELS portails** composent l'ensemble ; les autres
gates comptent « 5 » sans jamais confronter le SET. Un ÉCHANGE (Achat→Marketing),
RETRAIT, RÉORDRE, RENOMMAGE de la console technique dans le contrat
(`plateforme`→`console`) ou un contrat qui PROMEUT `plateforme` en métier laisse la
fiche contredire les artefacts EN SILENCE — « vert trompeur » qu'aucune suite
`tests/` (FONCTIONS RBAC, jamais la prose) n'attrape.
- **État courant** : aucune dérive — L11 == workspaces frontend == `portails_business` ·
L46 `plateforme` == unique portail RBAC hors métier == 6ᵉ. Le défaut est la **surface
ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « Portails ensemble » juste après le
bloc triplets, réutilise `ws_list`/`ws_fi`/`WS_FI` déjà en scope · relit le contrat
`rbac_50_roles.json`) :
- **(a) ENSEMBLE** — noms courts ordonnés (label `MANIFEST.workspaces[].workspace` sans
« OTO ») == énumération L11 (compte + absents/en trop + ordre) **ET DOUBLE ANCRAGE**
les ids workspaces frontend == `portails_business` du contrat (l'« égalité stricte »
que la fiche revendique en L47).
- **(b) EXCLUSION** — `plateforme` == l'UNIQUE portail RBAC hors métier
(`distinct(roles.portail) portails_business`, len==1), nommé EXACTEMENT dans L46,
de rang == `|distinct|` (6ᵉ), avec `**exclue**` structurellement exigé (regex `\S*`
absorbe l'exposant « ᵉ » multi-octets sans le coder en dur).
- Claim absent (L11 ou L46 INTROUVABLE) = régression #6.
**8 morsures vérifiées** (6 côté fiche · 2 côté contrat) : L11 `Achat→Marketing`
(`(a)` en trop/absent) · L11 retire `Compta` (`(a)` compte+absent) · L11 réordonne
`Ventes↔Construction` (`(a)` ordre) · L46 `plateforme→console` (`(b)` nom) · L46
`6ᵉ→7ᵉ` (`(b)` rang) · L46 retire `**exclue**` (INTROUVABLE) · contrat réordonne
`portails_business` (ancrage rompu) · contrat `ventes→sales` dans `portails_business`
(ancrage rompu + `plateforme` n'est plus l'unique hors-métier ⇒ 2 fails) ; restauré =
green (3 ✓ : double ancrage · énumération L11 · console technique L46) · exit 0.
Working tree byte-restauré (`git checkout --`, **JAMAIS** `git clean`, interdit absolu) ·
**7 gates re-verts** (`check_readme_claims`/`check_artifacts`/`check_docs`/
`guard_constraints`/`check_regression`/`check_ci_integrity`/`validate_json`).
- `ci/README.md` (clause de la ligne récap du pipeline + paragraphe détaillé « Ensemble
des portails métier + exclusion nommée de la console technique ») 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
Workspaces/rôles `bench` reste côté VPS).
- **Auto-score 4Big** : 96/100.
---
## Sprint 8 · buffer · Gate d'IDENTITÉ de la colonne « Entrée CLI » des fiches agents — verbes documentés == subparsers argparse réels
**Classe fermée** : `agent-fiche-role-attrs-ungated` (mémoire) — sous-classe « colonne
CLI ». Chaque table de livrables des fiches `03_agents/*/AGENT.md` porte une colonne
« Entrée CLI » citant, entre backticks, le point d'entrée du module ET son jeu de
verbes (`publiciste.py parse|validate|generate|run`, `faisabilite_gen.py
score|scaffold|generate|batch`, `rbac_apply_plan.py build|validate`, …). Cette liste
est **saisie à la main**. Les gates amont couvrent déjà génériquement — pour TOUTES les
fiches, via `git ls-files` — la colonne « Tests » (recomputée depuis `regression_plan`)
et la colonne « Job CI » (contre `ci.yml`), mais **RIEN** ne vérifiait que les VERBES
écrits sont ceux que le CLI **expose réellement**.
**Piège** : renommer une sous-commande (`generate`→`render`), en AJOUTER une
(`publish`), en RETIRER une ou les RÉORDONNER laisse la fiche documenter une surface
d'invocation **fantôme** : le module tourne (le CI appelle le vrai verbe), la doc
d'identité de l'agent — celle qui lui dit COMMENT lancer son propre livrable — ment en
silence. Aucune suite `tests/` (FONCTIONS du module, jamais la prose de fiche) ne
l'attrape. Classe explicitement notée en mémoire.
**Gate ajoutée** (`ci/check_readme_claims.sh`, bloc « colonne Entrée CLI », après le
bloc « Job CI fiches ») : `_cli_subcommands()` **RECOMPUTE** via **AST** l'ensemble
ORDONNÉ des `subparsers.add_parser("<verbe>")` de chaque script (jamais une liste à la
main · #6). Résolution du script : chemin du module de la MÊME ligne de table (lien
`…/05_deliverables_mvp/<path>/README.md`) + basename du CLI → `{D}/<path>/<file>.py`
(chaque cellule liée à SON module · pas de collision de basename). Égalité EXIGÉE
**exacte** — ensemble ET ordre (l'ordre de `add_parser` == ordre de l'aide argparse).
Les `|` séparateurs markdown sont échappés `\|` dans les cellules : dé-échappés avant
découpe. Direction fiche→CLI : la fiche ne peut pas mentir sur les verbes du script.
**16 cellules** gatées (6 fiches : crm×3 · devops · erpnext_backend×5 · faisabilite×2 ·
publiciste · qa×4 — dont `audit_5d_gen.py`/`audit_4big_gen.py` que les regex `[a-z_]`
manquaient, d'où `[a-z0-9_]`). Actuellement **toutes exactes** — la gate verrouille
l'état correct.
**7 morsures vérifiées** (restaurées vert après chaque) :
1. FICHE renomme verbe (`generate→render`) → manquants/en trop, `seul_ordre_diffère=False`.
2. FICHE ajoute verbe fantôme (`publish`) → en trop.
3. FICHE retire un verbe (`batch`) → manquant.
4. FICHE réordonne (`parse|validate`↔) → `seul_ordre_diffère=True` (ordre discriminé).
5. **Le vrai « vert trompeur »** : `add_parser("generate")→("render")` dans le `.py`,
fiche INCHANGÉE → red (la source de vérité a bougé sous la fiche).
6. Nouveau `add_parser("audit")` dans le `.py` non documenté → manquant côté fiche.
7. Script déplacé/renommé (`publiciste.py→publiciste_v2.py`) → « script INTROUVABLE ».
**Incident process** : un `git checkout .` de nettoyage d'un scénario adverse a reverté
l'edit **non-commité** de `ci/check_readme_claims.sh` (bloc effacé). Ré-appliqué à
l'identique puis re-vérifié. Leçon : ne jamais `git checkout .`/`--` large avec un edit
de gate non commité en cours ; restaurer par chemin ciblé uniquement. (`git clean` <!-- ci-allow : mention de l'interdit absolu, jamais exécuté -->
reste l'interdit absolu — non utilisé.)
- **6 gates re-verts** : `check_artifacts` · `check_docs` · `guard_constraints` ·
`check_readme_claims` (16 ✓ Entrée CLI) · `check_regression` · `check_ci_integrity`.
Aucun hash-lock sur les scripts de gate → rien d'autre à régénérer.
- **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_130325` · Buffer S8 · Fiches agents + master prompt/infra GPU : l'**IDENTITÉ de l'endpoint RunPod ComfyUI** — l'ID du pod `t68akvx80wv092` + le tag modèle **« Flux dev fp8 »** qui exécute le rendu Flux — était RECOPIÉE À LA MAIN en **6 citations** (`03_agents/bim/AGENT.md:32,67` · `03_agents/rendu/AGENT.md:19,55,91` · `02_master_prompt/MASTER_PROMPT_DTP_v2.md:24`) mais **HORS de tout gate**. La source de vérité in-repo est le bullet §1 de `AGENTS_EXISTING_ASSETS.md:15` — « le fichier maître » que les fiches CITENT explicitement comme leur ancre.
**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 la colonne « Job CI » ou « Entrée CLI » des fiches (surface data-derived
recopiée à la main, jamais confrontée à sa source) — appliquée à une **surface distincte
encore jamais gatée** : l'**infra GPU** (l'endpoint RunPod que l'agent Rendu/BIM câble
pour le batch Flux). Classe `agent-fiche-role-attrs-ungated`.
**Dérive silencieuse fermée** :
- `03_agents/bim/AGENT.md` (table modules L32 + coordination L67) · `03_agents/rendu/AGENT.md`
(principe L19 + table L55 + coordination L91) · `02_master_prompt/MASTER_PROMPT_DTP_v2.md:24`
(bloc infra « GPU ») — chacun NOMME entre backticks l'id `t68akvx80wv092` et/ou le tag
« Flux dev fp8 ».
- Source faisant autorité : `AGENTS_EXISTING_ASSETS.md:15` §1 BIM
(« - **RunPod ComfyUI** endpoint `t68akvx80wv092` (Flux dev fp8) »).
- Piège : un pod RunPod est **RE-CRÉÉ avec un NOUVEL id** (cas le plus courant) ou le
modèle Flux est bumpé (`dev fp8`→autre quant) : l'inventaire maître se met à jour, les
6 citations se PÉRIMENT en silence pendant que l'agent Rendu/BIM câblerait le batch vers
un **endpoint GPU MORT** — l'invention même que #6 interdit. `check_docs` ne voit qu'un
lien, `guard_constraints` qu'un terme interdit, `check_artifacts` qu'un `out/` — un ID de
pod n'est aucun des trois ; aucune suite `tests/` (FONCTIONS du module) n'attrape la prose.
- **État courant** : **aucune citation périmée** — les 6 recoupent l'inventaire exactement
(anti-invention #6, rien à réécrire). Le défaut est la **surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « RunPod endpoint » avant
`sys.exit`) : (0) **ancrage** — id + tag recomputés du bullet §1 (garde : bullet EXACTEMENT
une fois, sinon ancre morte) ; (1) **direction fiche→inventaire** — toute fiche/master
prompt qui MENTIONNE RunPod doit citer l'id EXACT (tout token backtické de forme pod —
alphanum minuscule ≥12 **avec un chiffre**, forme qui n'entre en collision avec aucun autre
token backtické du corpus : vérifié) == canonique, ET le tag Flux EXACT (substring
normalisé, insensible casse) ; (2) **disparition totale** — plus aucune mention RunPod
échoue AUSSI (traçabilité #6).
**6 morsures vérifiées** : inventaire re-crée le pod (id change → mord les **3 fiches** en
cascade) · inventaire bump le tag (`dev fp8→schnell fp8` → 3 fiches) · fiche rendu mistype
une citation (id fantôme, mord rendu seul) · fiche bim retire le tag (tag absent) · master
prompt retire l'id backtické (citation incomplète) · inventaire supprime le bullet canonique
(ancre morte, recompute impossible) ; restauré = green : 3 fiches · id == inventaire · tag ==
inventaire · exit 0. Working tree byte-restauré (`git checkout --`, **JAMAIS** `git clean`,
interdit absolu) · **7 gates re-verts** (`check_readme_claims`/`check_docs`/`guard_constraints`/
`check_artifacts`/`check_ci_integrity`/`validate_json`/`check_regression`).
- `ci/README.md` (clause récap du pipeline `check-readme-claims` + paragraphe détaillé
« IDENTITÉ de l'endpoint GPU RunPod ComfyUI ancrée sur l'inventaire maître ») 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 pod RunPod réel + le
batch ComfyUI Flux restent côté VPS/RunPod).
- **Auto-score 4Big** : 96/100.
## Session `20260801_133334` · Buffer S8 · Domaine CRM/Commissions : la **PROMESSE ANTI-INVENTION (#6)** du barème vendeurs — la **RAISON D'ÊTRE** du module — était HORS de tout gate. Aucun taux de commission n'étant documenté dans CLAUDE.md, le README §« Anti-invention » promet (README:22-23) que « le barème livré porte `taux_pct: null` + `source: null` + `a_confirmer: true` pour **chaque** évènement », et l'invariant CLI #7 REFUSE tout `taux_pct` fourni **sans `source`**. Le bloc cross-cohérence commissions existant ne gate QUE brouillons/champs de base ; le bloc racine QUE le compte de tests (« 25 »). RIEN ne confrontait cette promesse à l'artefact byte-gaté.
**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 surface `crm/commissions`
que le bloc cross-cohérence (états soumis/base_field) mais **axe orthogonal et plus
grave** : non plus QUEL état paie, mais la garantie que le module **n'invente aucun
chiffre** — la classe même que #6 pose, appliquée au livrable dont c'est la mission.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/crm/commissions/README.md:22-23` — la promesse « `taux_pct:
null` + `source: null` + `a_confirmer: true` pour **chaque** évènement ».
- Source faisant autorité : `out/commission_plan.json` (byte-gaté par
`check_artifacts`) — chaque `evenements[]` porte `taux_pct`/`source`/`a_confirmer` ;
`out/MANIFEST.json[counts]` recompte `taux_a_confirmer`/`evenements`.
- Piège #6, la dérive la plus dommageable possible ici : qu'un `taux_pct` DEVIENNE
non-null (taux INVENTÉ, ou valeur remplie **sans `source`**) pendant que le README
affirme encore « tout `null` » → le calcul `base × taux` (`commlib/finance.py`)
émettrait une commission fondée sur un chiffre inventé — l'invention EXACTE que le
module promet d'écarter. Aucune suite `tests/` (FONCTIONS de calcul/résolution,
jamais la promesse du README vs l'artefact commité) ne l'attrape.
- **État courant** : les 5 évènements sont bien tout-`null` (`taux_pct`/`source`
null · `a_confirmer` true), `counts.taux_a_confirmer == 5 == |plan|`, `note_taux`
cohérente ; rien de périmé (anti-invention #6, rien à réécrire). Le défaut est la
**surface ungated**.
**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « CRM/Commissions ·
ANTI-INVENTION (#6) » après le bloc cross-cohérence commissions) : on RECOMPUTE
l'état null/confirmé depuis l'artefact, puis (a) **invariant #7 permanent** — aucun
`taux_pct` renseigné **sans `source`** (taux inventé) ; (b) **cohérence** `a_confirmer`
⟺ `taux_pct is null` (un taux confirmé n'est plus « à confirmer », et inversement) ;
(c) **`MANIFEST.counts`** recoupe le plan (`taux_a_confirmer` == nb à confirmer ·
`evenements` == |plan|) ; (d) **la promesse #6 du README REFLÈTE l'artefact dans les
DEUX SENS** — si tout-null, la promesse DOIT être présente (sinon la raison d'être
disparaît · #6) ; si un taux est un jour confirmé, la promesse « tout null » DOIT
avoir été retirée (sinon « vert trompeur ») — **zéro faux négatif** quand la
Direction remplira les taux ; (e) `note_taux` du MANIFEST cohérente avec l'état.
**5 morsures vérifiées** : `taux_pct` rempli SANS source (invariant #7 · invention
+ cascade b/d) · `taux_pct` rempli AVEC source (invariant #7 OK mais `a_confirmer`
resté true → b, + README périmé → d — prouve que #7 et la cohérence sont distincts) ·
`a_confirmer=false` sur taux null (b + counts périmés c + d) · `MANIFEST.counts.
taux_a_confirmer` périmé 5→4 (c seul) · promesse anti-invention retirée du README
(d · INTROUVABLE) ; restauré = green : invariant #7 · cohérence · counts · promesse
== artefact · 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` (cellule récap du pipeline + paragraphe détaillé « promesse
anti-invention #6 du barème `crm/commissions` ») 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 ; la
confirmation réelle des taux + le câblage du calcul restent côté Direction/VPS).
- **Auto-score 4Big** : 96/100.
## Session `20260801_140334` · Buffer S8 · Domaine Fiscal/e-CF DGII : l'**IDENTITÉ des ÉVÈNEMENTS D'ÉMISSION** — la section §Cross-cohérence présentée comme **« le cœur du livrable »** (README:40-59) — était transcrite À LA MAIN SANS aucun gate d'identité. Le SEUL gate fiscal existant couvre le **contrat de format e-NCF** (`E+tipo(2)+seq(10)=13`) + le compte de tests ; le bloc racine, le seul agrégat « e-CF 39 tests ». RIEN ne confrontait à l'artefact byte-gaté QUEL évènement du pipeline émet un e-CF, sur quel `base_field`, par quel rôle Compta, ni la FormaPago défaut.
**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 « identité
d'un ENSEMBLE » que la colonne « Entité » de la SPEC §4, l'énumération des onglets
Mobile ou les transitions à séparation des pouvoirs du workflow — appliquée à la
**surface data-derived la plus critique du module fiscal** : les `emission_events`
d'`ecf_plan.json`, jamais gatés hors le format e-NCF et le compte de tests.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/fiscal/ecf_dgii/README.md:40-59` (§Cross-cohérence, « le cœur
du livrable ») — transcrit À LA MAIN : (L48) l'ENSEMBLE des évènements émetteurs
(« seulement réservation et contrat ») + l'ENSEMBLE exclu des brouillons (« on ne
facture pas un brouillon (lead/visite/devis) ») ; (L50) l'ENSEMBLE des `base_field`
(`montant_reservation`/`montant_contrat`) ; (L53-54) le rôle UNIQUE
(`compta-fiscaliste-ecf` · « OTO Compta Fiscaliste eCF » · portail compta) ;
(L59) la FormaPago défaut (« = 3 (Tarjeta) » · Cardnet #10).
- Source faisant autorité (byte-gatée par `check_artifacts`) :
`out/ecf_plan.json` — `emission_events[]` (`update_value`/`base_field`/`role_id`/
`erpnext_role_name`), `forma_pago_defaut` + catalogue `formas_pago`. Double
ancrage du rôle sur `rbac/rbac_50_roles.json` (id → `portail` compta → nom ERPNext).
- Piège #6/#5, la dérive la plus dommageable ici : **AJOUTER un `emission_event` sur
un état BROUILLON** (ex. `devis`) — l'anti-pattern EXACT que le module interdit
(« on ne facture pas un brouillon ») — pendant que L48 dit encore « seulement
réservation et contrat » → l'agent ERPNext Backend câblerait une **facture émise
avant soumission/encaissement**. Aucune suite `tests/` (FONCTIONS de résolution/
composition, jamais la prose vs l'artefact) ne l'attrape.
- **État courant** : émetteurs `{contrat, reservation}`, base_field
`{montant_contrat, montant_reservation}`, rôle unique `compta-fiscaliste-ecf`
(portail compta · RBAC concordant), FormaPago défaut `3`/Tarjeta/Cardnet —
tout recoupe 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 « Fiscal · e-CF DGII —
IDENTITÉ des ÉVÈNEMENTS D'ÉMISSION » avant `sys.exit`) : (0) **cohérence interne** —
rôle UNIQUE sur tous les évènements · défaut ∈ catalogue FormaPago ; (1) **ENSEMBLE
émetteurs** — set-diff README `seulement …` == `update_value` de l'artefact (ni
fantôme ni manquant · accents/casse normalisés) ; (2) **DISJONCTION** — l'ensemble
exclu `(lead/visite/devis)` ∩ émetteurs = ∅ (jamais de facture sur brouillon — mord
un `devis` émetteur ET la self-contradiction du README) ; (3) **ENSEMBLE `base_field`**
— set-diff README == artefact ; (4) **rôle unique** cité backtické + nom ERPNext,
**double-ancré** sur `rbac_50_roles` (id → portail compta → nom concordant) ; (5)
**FormaPago défaut** — code == artefact · label ∈ catalogue · motif cite Cardnet (#10).
Un claim absent échoue AUSSI (traçabilité #6).
**6 morsures vérifiées** : artefact facture un BROUILLON (`devis` émetteur → mismatch
d'ensemble **ET** clash de disjonction · 2 morsures en une) · artefact renomme un
`base_field` (`montant_reservation→montant_acompte`) · artefact change FormaPago défaut
`3→1` (Efectivo) · artefact casse le rôle unique (2ᵉ évènement → `compta-comptable`) ·
README périmé « seulement contrat » (réservation retiré) · motif du défaut ne cite
plus « Cardnet » (#10) ; restauré = green : émetteurs · disjonction · base_field ·
rôle == RBAC · FormaPago == artefact · exit 0. Working tree byte-restauré
(`git checkout --`, **JAMAIS** `git clean`, interdit absolu) · **7 gates re-verts**
(`check_readme_claims`/`check_docs`/`guard_constraints`/`check_artifacts`/
`check_ci_integrity`/`validate_json`/`check_regression`).
- `ci/README.md` (clause récap du pipeline `check-readme-claims` + paragraphe détaillé
« IDENTITÉ des évènements d'émission e-CF ancrée sur `ecf_plan.json` ») 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 ; la connexion
Compupar réelle + l'émission e-CF en production restent côté ERPNext Backend/VPS).
- **Auto-score 4Big** : 96/100.