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

## Session `20260801_230453` · Buffer S8 · QA/Audit 5D : gate d'IDENTITÉ du **VERDICT GLOBAL** — le LABEL de conclusion de l'audit

**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
complète ; la valeur marginale reste le durcissement anti-dérive (CLAUDE.md #6)
des surfaces prose data-derived encore non gatées. Les 7 gates verts au départ.

**Surface trouvée (aveugle)** : le **verdict global** de l'audit 5D — le LABEL
`PASS` / `PASS_WITH_OPEN_ITEMS` / `FAIL` qui est la **HEADLINE de tout le
livrable** — était **HORS de tout gate**. Le bloc audit_5d existant recompute la
**ventilation chiffrée** (« **13 PASS · 0 FAIL · 4 à confirmer** », pass/fail/à
confirmer d'`audit_report.totals`), l'énumération des open items, les 5 dimensions
et les livrables audités — mais **jamais le label de verdict lui-même**. Or ce
label est data-derived (`out/audit_report.json[verdict]` == `out/MANIFEST.json[verdict]`
== `PASS_WITH_OPEN_ITEMS`, byte-gatés), recalculé par `qalib/builder.py` via la
règle **`FAIL` si ≥1 FAIL · sinon `PASS_WITH_OPEN_ITEMS` si ≥1 A_CONFIRMER · sinon
`PASS`**, et transcrit à la main à **deux** endroits du README : la RÈGLE
(`Verdict global : …`, l.39) et le VERDICT COURANT (`## Verdict courant` l.49). Le
risque #6 est le **CŒUR** du livrable : écrire `PASS` alors que 4 params
réglementaires restent `a_confirmer` = l'audit « force » un PASS et prétend propre
un dossier qui ne l'est pas — exactement ce que l'audit reproche aux autres. La
ventilation chiffrée peut rester exacte pendant que le label ment ; ni
`check_artifacts` (byte only) ni les suites `tests/` (FONCTIONS, pas la prose)
n'attrapent.

**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « Audit 5D · IDENTITÉ du VERDICT
GLOBAL », inséré juste après la ventilation chiffrée) : on **MIROITE** la règle du
générateur et on exige — **(a)** HONNÊTETÉ interne : `audit_report.verdict` ==
verdict recalculé du triplet (fail>0→FAIL · a_confirmer>0→PASS_WITH_OPEN_ITEMS ·
sinon PASS) ⇒ un verdict FORCÉ incohérent avec ses propres chiffres est mordu ;
**(b)** CROISÉ : `MANIFEST.verdict` == `audit_report.verdict` ; **(c)** le LABEL
« Verdict courant » du README == `audit_report.verdict` (ancré sur le heading +
la ventilation `**N PASS`, pas sur la règle l.39) ; **(d)** la RÈGLE « Verdict
global » énumère EXACTEMENT le **codomaine** `{FAIL, PASS_WITH_OPEN_ITEMS, PASS}`
du générateur (un label renommé côté `builder.py` sans mise à jour de la règle est
mordu). Claim absent = échec (disparition = régression).

**5 morsures vérifiées** (working tree byte-restauré après chacune via
`git checkout -- <chemin précis>`, JAMAIS `git checkout .` / `git clean`) :
1. README label `PASS_WITH_OPEN_ITEMS`→`PASS` → ✗ (c) « Verdict courant = PASS MAIS
   artefact PASS_WITH_OPEN_ITEMS » ;
2. README règle l.39 `PASS_WITH_OPEN_ITEMS`→`PASS_OPEN` → ✗ (d) « codomaine
   [FAIL, PASS, PASS_OPEN] ≠ [FAIL, PASS, PASS_WITH_OPEN_ITEMS] » ;
3. `audit_report.verdict`→`PASS` (à_confirmer reste 4) → ✗ (a) « verdict FORCÉ,
   incohérent — viole #6 » **+** cascade ✗ (b) + ✗ (c) (3 fails : l'honnêteté est
   la morsure la plus forte) ;
4. `MANIFEST.verdict`→`PASS` → ✗ (b) « MANIFEST PASS MAIS audit_report
   PASS_WITH_OPEN_ITEMS (désynchronisés) » ;
5. README label supprimé (l.49) → ✗ (c) « Verdict courant INTROUVABLE ».
Restauré = green : (a)(b)(c)(d) tous ✓.

- `ci/README.md` : la sous-clause « audit 5D » de la row `check-readme-claims`
  étendue de l'IDENTITÉ du VERDICT GLOBAL (label + honnêteté interne + croisé
  MANIFEST + codomaine de la règle documentée).
- **7 gates re-verts** : `guard_constraints` / `validate_json` / `check_docs` /
  `check_artifacts` / `check_regression` / `check_ci_integrity` /
  `check_readme_claims` (tous rc=0).
- **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_210434` · Buffer S8 · Domaine CRM/commissions : l'**IDENTITÉ du CHAMP DEVISE** — la clause `devise_field` = le champ `devise` (`Select` **USD/DOP** · #10) du DocType Dossier Vente, transcrite **en prose** dans la §« Cross-cohérence » (`crm/commissions/README.md:54`) — était **HORS de tout gate**. Les trois blocs commissions déjà en place gatent les états soumis / brouillons exclus, les champs de base Currency, l'identité des rôles payés (portail `ventes`) et la promesse anti-taux #6 — mais **aucun** ne touchait ce couple champ↔devises (l'**invariant CLI #8** du générateur).

**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). **4e surface distincte du
MÊME livrable `crm/commissions`** (après états/champs, anti-taux, rôles payés) — le
« **sur quelle devise** » du calcul `base × taux`. Surface DISTINCTE du bloc « Fiches
#10 » (prose ERPNext/CRM) et du bloc e-CF `TipoMoneda` (options du spec fiscal) :
ici c'est le champ `devise` du DocType Dossier Vente + la clé racine `devise_field`
du plan, ancrés sur **CLAUDE.md #10** (« **USD + DOP** devises »).

**Dérive silencieuse fermée** :
- `05_deliverables_mvp/crm/commissions/README.md:54` — clause « `devise_field` = le
  champ `devise` (`Select` **USD/DOP** · #10) du DocType » : NOMME le champ (`devise`),
  son fieldtype (`Select`), ses options (`USD/DOP`) et tague `#10`.
- Sources faisant autorité : (a) `out/commission_plan.json` (byte-gaté par
  `check_artifacts`) — clé RACINE `devise_field` (le champ du Dossier Vente qui porte
  la devise d'un montant commissionné) ; (b) `crm/dossier_vente/doctype_spec.json`
  (l'INPUT du générateur) — le champ `devise` de fieldtype `Select` + ses `options` ;
  (c) **CLAUDE.md #10** (ligne 24 : « **USD + DOP** devises ») — les 2 devises
  canoniques. L'invariant CLI #8 (`commissions_gen.py:151-158`) exige les trois
  simultanément ; le gate re-dérive INDÉPENDAMMENT.
- Piège : la seule vérification existante (`tests/`) teste des **FONCTIONS**
  finance/résolution, jamais la PROSE vs l'artefact. RENOMMER le champ (`devise`→
  `moneda`), changer son fieldtype (`Select`→`Data` : énumération PERDUE, montant en
  monnaie libre) ou altérer ses options (`USD/DOP`→`USD/EUR` : devise HORS #10 → le
  calcul commissionnerait dans une monnaie non prévue) laisse la prose périmée EN
  SILENCE. `devise_field` n'apparaissait NULLE PART dans `ci/` (grep = 0).
- **État courant** : **aucune valeur périmée** — `plan.devise_field == "devise"` ==
  champ DocType `devise` (`Select`, options `[USD, DOP]`) == CLAUDE.md #10 ; la prose
  README recoupe tout exactement (anti-invention #6, rien à réécrire). Le défaut est
  la **surface ungated**.

**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « CRM/Commissions ·
IDENTITÉ du CHAMP DEVISE » après le bloc rôles payés) : (0) **ANCRAGE** — les 2
devises depuis le segment gras « USD + DOP » de CLAUDE.md #10 ; (a) **CROSS-ARTEFACT**
— `plan.devise_field == "devise"` == fieldname du champ DocType, fieldtype `Select`,
`options` (ORDRE inclus) == devises #10 ; (b) **PROSE** — README:54 nomme le champ
`devise`, le dit `Select`, cite `USD/DOP` == #10 et tague `#10`. Un claim absent
échoue AUSSI (traçabilité #6). Direction plan/DocType/#10 → README.

**7 morsures vérifiées** (4 côté README · 1 DocType · 1 CLAUDE.md · 1 plan) : README
champ `devise→moneda` (prose périmée) · README `Select→Data` (type périmé) · README
`USD/DOP→USD/EUR` (devises périmées) · README tag `· #10` retiré (claim INTROUVABLE) ·
**DocType** `options USD→EUR` (hors #10, cross-artefact) · **CLAUDE.md #10**
`USD+DOP→USD+EUR` (ancrage VIVANT · mord le cross-artefact) · **plan** `devise_field
devise→moneda` (invariant #8) ; restauré = green : plan.devise_field == DocType ==
#10 · prose == artefacts · 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é
  « Quatrième surface du MÊME livrable `crm/commissions` · identité du champ devise »)
  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 création du
  champ `devise` du DocType `bench` reste côté VPS).
- **Auto-score 4Big** : 96/100.

## Session `20260801_183412` · Buffer S8 · Domaine Legal/CONFOTUR : les **deux champs du DocType `CONFOTUR Application` DÉRIVÉS du pipeline vente** — `dossier_vente` (Link → cible `workflow.document_type` = `OTO Dossier Vente`) et `estado` (Select → options = les états `confotur_*` du **workflow vente**), transcrits **en prose** dans la §« Cœur du livrable : cross-cohérence » (`legal/confotur/README.md:33-36`) — étaient **HORS de tout gate d'IDENTITÉ**. Les blocs CONFOTUR amont gatent le **compte** de champs/rôles, la cross-cohérence des **PERMISSIONS** (rôle→actions) et l'ancrage des **ENTITÉS** (`entite_porteuse`→CLAUDE.md §Entités) — mais **aucun** ne touchait `dossier_vente`/`estado`.

**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 EXACTE** que
l'ancrage `entite_porteuse` du MÊME README (session `20260801` entités CONFOTUR) —
appliqué à la **surface distincte** des deux AUTRES champs data-derived du même
DocType, jamais gatés hors comptes/permissions/entités.

**Dérive silencieuse fermée** :
- `05_deliverables_mvp/legal/confotur/README.md:35-36` — bullet `dossier_vente`
  (Link) : « cible = `workflow.document_type` (= `OTO Dossier Vente`) » (valeur
  NOMMÉE en clair) · `:33-34` — bullet `estado` (Select) : « options = les états
  `confotur_*` du **workflow vente** … dérivées — jamais réécrites en dur ».
- Source faisant autorité : `crm/workflow_vente/workflow_vente_spec.json` — l'**INPUT**
  que le builder confotur LIT (déclaré `workflow_source` dans le MANIFEST), d'où il
  tire `document_type` (= cible du Link) et les `update_value` d'états commençant par
  `confotur` (= options du Select). L'artefact `out/doctype_confotur_application.json`
  porte `dossier_vente.options`/`estado.options`, byte-gaté par `check_artifacts`.
- Piège : la seule vérification existante (`tests/`) teste des **FONCTIONS** de
  génération, jamais la PROSE du README. RENOMMER le DocType porteur côté workflow
  (`OTO Dossier Vente`→`OTO Dossier de Vente`) ou AJOUTER/RENOMMER un état CONFOTUR
  régénère l'artefact confotur (`check_artifacts` reste VERT — il se reconstruit
  depuis le spec workflow) MAIS laisse la prose README « (= `OTO Dossier Vente`) »
  périmée EN SILENCE → l'agent ONAPI/Legal câblerait une cible de Link / un menu
  d'états que le pipeline ne porte plus (l'invention #6 interdit).
- **État courant** : **aucune valeur périmée** — `dossier_vente` Link → « OTO
  Dossier Vente » == workflow `document_type` · `estado` options
  `[confotur_depose, confotur_approuve]` == états `confotur_*` du workflow (ordre
  pipeline) · la prose README recoupe l'artefact/le workflow exactement
  (anti-invention #6, rien à réécrire). Le défaut est la **surface ungated**.

**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Confotur champs
dérivés » avant `sys.exit`) : (0) **traçabilité** — MANIFEST `workflow_source` cite
bien `crm/workflow_vente/workflow_vente_spec.json` ; (a) **cross-artefact** —
`dossier_vente.options` (Link) == workflow `document_type` ; (b) **prose** — la
valeur README « (= `…`) » == workflow `document_type` ; (c) **cross-artefact** —
`estado.options` == `update_value` des états `confotur_*` du workflow (ORDRE
pipeline) ; (c′) toute option `estado` porte le préfixe `confotur` (le README
l'affirme) ; (d) **prose** — le bullet `estado` DÉCLARE la dérivation (`confotur_*`
+ workflow vente). Un claim absent échoue AUSSI (traçabilité #6). Direction
README/artefact→workflow spec.

**6 morsures vérifiées** : README `dossier_vente` valeur `OTO Dossier Vente→OTO
Dossier de Vente` (prose périmée, b) · README bullet `estado` supprimé (claim
INTROUVABLE, d) · **artefact** `estado.options` +`brouillon` (options ≠ workflow, c
ET hors-préfixe, c′) · **artefact** `dossier_vente.options→OTO Autre Doc` (Link ≠
document_type, a) · **MANIFEST** `workflow_source` déréférencé (dérivation non
déclarée, 0) · **spec workflow** `document_type→OTO Dossier de Vente` (mord
SIMULTANÉMENT l'artefact a ET la prose b — l'ancre est vive) ; restauré = green :
6 checks verts · 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é
  « Champs `dossier_vente`/`estado` du DocType CONFOTUR dérivés du workflow 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 `bench import-fixtures` reste côté VPS).
- **Auto-score 4Big** : 96/100.

## Session `20260801_173409` · Buffer S8 · Fiches agents/pied de page voix OTOIA : le **PIED DE PAGE CANONIQUE** « - Voix **Amélie QC** (**multilingual_v2**) pour toute interaction OTOIA » — répété à l'identique dans **11 des 13 fiches** `03_agents/*/AGENT.md` (toutes sauf `faisabilite`/`publiciste`, qui n'orchestrent pas OTOIA) — RECOPIE À LA MAIN les **trois** tokens de la contrainte **CLAUDE.md §Architecture cible** « Voix Amélie QC (multilingual_v2) » : prénom persona (`Amélie`), qualificatif de marque (`QC`), modèle de voix ElevenLabs (`multilingual_v2`). Cette surface était **HORS de tout gate**.

**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 persona/capabilities
`frontend/chat_otoia` ancrées sur §Architecture cible, la marque SEO org (§Entités) et
les tokens branding (#4) — appliqué à une **surface distincte encore ungated** : les 11
pieds de page de fiches, jamais confrontés à CLAUDE.md.

**Dérive silencieuse fermée** :
- `03_agents/{bim,crm,devops,erpnext_backend,frontend_console,ifc_speckle,mobile,
  onapi_legal,qa,rendu,seo}/AGENT.md` — ligne « - Voix Amélie QC (multilingual_v2)
  pour toute interaction OTOIA ».
- Source faisant autorité : **CLAUDE.md §Architecture cible** (ligne 30) « Voix
  Amélie QC (multilingual_v2) ».
- Piège : le bloc « Chat OTOIA · IDENTITÉ de la persona » RE-DÉRIVE bien persona+voix
  de CLAUDE.md mais ne confronte QUE le module `chat_otoia` (spec + `out/` + son propre
  README + l'oracle du test) — **AVEUGLE aux 11 pieds de page** ; le bloc « configs
  runtime » note même explicitement que le « QC » est « gardé en littéral — non gaté
  ici ». Le jour où ElevenLabs déprécie `multilingual_v2` (→`multilingual_v3`) ou qu'on
  renomme la persona/le qualificatif dans CLAUDE.md, ces 11 fiches annonceraient EN
  SILENCE une voix/persona que le mandat ne prescrit plus, chacune affirmant orchestrer
  « toute interaction OTOIA » avec le mauvais modèle. Aucune suite `tests/` (FONCTIONS
  du builder, jamais la prose des fiches) n'attrape ce « vert trompeur ».
- **État courant** : **aucune valeur périmée** — les 11 pieds de page recoupent
  CLAUDE.md §Architecture cible 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 · pied de
page voix » après le bloc persona/capabilities Chat OTOIA) : on RE-DÉRIVE le triplet
(persona · qualif · voix) de CLAUDE.md §Architecture cible **INDÉPENDAMMENT** du parse
persona amont (dont la regex fige `QC` en dur — un renommage du qualificatif doit mordre
ICI, pas seulement via l'INTROUVABLE du bloc persona) ; pour CHAQUE fiche git-trackée
portant le pied de page, on exige ses **3 tokens == CLAUDE.md**. Une fiche avec prose
« interaction OTOIA » mais pied de page malformé échoue ; la **disparition totale** du
recensement échoue AUSSI (surface évaporée = régression #6). Direction fiche→CLAUDE.md.

**6 morsures vérifiées** : CLAUDE.md `multilingual_v2→v3` (mord les 11 fiches +, en
cascade, le bloc persona amont — 15 fails, l'ancre est vive) · CLAUDE.md qualif `QC→BC`
(mord les 11 pieds de page — bloc INDÉPENDANT, ne s'appuie pas sur le parse persona qui
fige QC) · fiche `seo` voix `v2→v1` (1 fail) · fiche `crm` persona `Amélie→Sophie`
(1 fail) · fiche `bim` pied de page malformé, parenthèses retirées (1 fail INTROUVABLE) ·
pied de page retiré de TOUTES les fiches (surface évaporée, 1 fail) ; restauré = green :
11 pieds de page == CLAUDE.md · 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é « Pied de
  page Voix Amélie QC (multilingual_v2) des fiches agents ancré 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 ; le runtime réel
  de la voix OTOIA reste côté VPS).
- **Auto-score 4Big** : 96/100.

## Session `20260801_170404` · Buffer S8 · Domaine QA/audit_4big : la colonne **« Fait vérifié »** de la table du barème (`qa/audit_4big/README.md:24,26`) — les **DEUX SEUILS STRUCTURELS** que le scorer applique réellement (`DOC` « ≥ **400 octets** » · `TESTS` « ≥ **8 méthodes** `def test_*` »), transcrits À LA MAIN — étaient **HORS de tout gate**. Le bloc BARÈME existant recompute la 2e colonne (**poids** : DOC 20 · CONTRAT 20 · TESTS 25 · CLI 15 · HANDOFF 20) mais son regex (`| \`DOC\` | 20 |`) est **AVEUGLE à la 3e cellule** ; ces deux nombres sont DATA-DERIVED de `quality_spec.json[thresholds]` (`min_doc_bytes`/`min_test_methods`).

**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 le
bloc BARÈME (poids) et la carte de renormalisation par archétype du MÊME README —
appliqué à une **3e surface distincte du MÊME README `qa/audit_4big`** : les seuils
de la colonne « Fait vérifié », jamais gatés hors leur poids voisin.

**Dérive silencieuse fermée** :
- `05_deliverables_mvp/qa/audit_4big/README.md:24` — cellule DOC « un `*.md`
  (README/SPEC) ≥ **400 octets** à la racine » (= `thresholds.min_doc_bytes`) ·
  `:26` — cellule TESTS « ≥ **8 méthodes** `def test_*` dans `tests/` » (=
  `thresholds.min_test_methods`).
- Source faisant autorité : `05_deliverables_mvp/qa/audit_4big/quality_spec.json`
  `[thresholds]` — l'**INPUT** du générateur, byte-gaté indirectement par
  `check_artifacts` qui prouve que `out/quality_report.json` se **rebuild** depuis
  lui. Les seuils ne sont PAS émis en clair dans le report ; seule l'**evidence
  `TESTS`** de chaque module porte « (seuil N) » (émise par `crit_tests`), ce qui en
  fait un **ancrage croisé** du seuil réellement APPLIQUÉ.
- Piège : la seule vérification existante (`tests/`) teste des **FONCTIONS** de
  scoring (`crit_doc`/`crit_tests` avec seuil paramétré), jamais la PROSE du README.
  Élever `min_test_methods` (8→12 : un module de 8-11 tests tomberait sous `FAIL` —
  la barre même que le critère pose) ou changer `min_doc_bytes` dans le spec rebuild
  le report SANS TOUCHER au README → prose « ≥ 8 méthodes »/« ≥ 400 octets » périmée
  en silence, l'auditeur documentant un barème qu'il **n'applique plus** (l'invention
  #6 interdit).
- **État courant** : **aucun seuil périmé** — les 2 cellules recoupent le spec
  exactement, et le seuil TESTS appliqué (evidence report) == spec sur les 22 modules
  (anti-invention #6, rien à réécrire). Le défaut est la **surface ungated**.

**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « README audit_4big ·
SEUILS STRUCTURELS » après le bloc renormalisation par archétype) : (1) **cross-check
d'ancrage** — le seuil `TESTS` APPLIQUÉ (regex `(seuil N)` sur l'evidence de CHAQUE
module du report) == `spec.min_test_methods` pour tous (mord une divergence
spec↔report) ; (2) **prose DOC** — cellule « ≥ N octets » recomputée du spec, exacte ;
(3) **prose TESTS** — cellule « ≥ N méthodes » recomputée du spec, exacte. Un claim
absent échoue AUSSI (traçabilité #6).

**5 morsures vérifiées** : README `DOC 400→450` (prose périmée) · README `TESTS 8→10`
(prose périmée) · README ligne DOC vidée du seuil (claim INTROUVABLE) · **spec**
`min_test_methods 8→12` + rebuild (README périmé ET cross-check evidence[8]≠spec[12]
spec↔report) · **spec** `min_doc_bytes 400→500` + rebuild (README DOC périmé) ;
restauré = green : seuil TESTS appliqué == spec (8) · DOC « ≥ 400 octets » == spec ·
TESTS « ≥ 8 méthodes » == spec · 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 `check-readme-claims` + paragraphe détaillé
  « troisième surface · colonne Fait vérifié / seuils structurels ») 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 branchement
  du gate 4Big sur le pipeline de release reste côté VPS).
- **Auto-score 4Big** : 96/100.

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

## Session `20260801_143341` · Buffer S8 · Domaine Faisabilité/generator : les **TROIS copies aval** des 6 MARQUEURS CANONIQUES (`3 %`/`8.5 %`/`52 %` · `USD+DOP`/`Letter US`/`Cardnet`, contraintes NON-NÉGOCIABLES **CLAUDE.md #9/#10**) — l'**oracle d'enforcement** `genlib/renderer.py::_CANONICAL_MARKERS` (la liste que `check_canonical()` PARCOURT pour PROUVER que chaque rendu de faisabilité porte les 6 marqueurs), les **DEUX docstrings** de `genlib/scorer.py` (barème axe 4 + `_score_parametres_canoniques`) et l'**oracle du test** `tests/test_generator.py` (`for marker in [...]`) — étaient **HORS de tout ancrage**. Le bloc « paramètres canoniques » existant n'ancre QUE `model.py::CANONICAL` + le README à #9/#10 ; son propre commentaire NOMMAIT pourtant le défaut (« ces marqueurs sont HARDCODÉS DANS LE TEST, une copie de plus, JAMAIS comparée à CLAUDE.md ») sans le fermer.

**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 tuple d'oracle du
test `publiciste/branding` ancré sur #4 (une copie hardcodée dans un test, gatée) —
appliqué aux **trois copies aval encore ungated du MÊME module** `faisabilite/generator`
dont le bloc canonique n'ancrait que 2 des 5 copies.

**Dérive silencieuse fermée** :
- `05_deliverables_mvp/faisabilite/generator/genlib/renderer.py:275` —
  `_CANONICAL_MARKERS = ["3 %", "8.5 %", "52 %", "USD + DOP", "Cardnet", "Letter US"]`,
  la LISTE que `check_canonical()` parcourt pour vérifier que le volet ingénierie
  du rendu porte les 6 marqueurs. C'est l'**enforcement même** de l'« axe 4
  honnêteté ».
- `05_deliverables_mvp/faisabilite/generator/genlib/scorer.py:7` (barème axe 4) **et**
  `:109` (`_score_parametres_canoniques`) — deux docstrings énumérant
  « 3 % / 8.5 % / 52 % · USD+DOP · Letter US · Cardnet ».
- `05_deliverables_mvp/faisabilite/generator/tests/test_generator.py:103` —
  `for marker in ["3 %", "8.5 %", "52 %", "USD + DOP", "Cardnet", "Letter US"]` :
  l'oracle du test lui-même, celui que le commentaire du bloc amont dénonçait.
- Source faisant autorité : **CLAUDE.md #9/#10** (les 6 valeurs), déjà recomputées
  en `exp` par le bloc « paramètres canoniques » — RÉUTILISÉES ici (zéro duplication).
- Piège #6 : l'oracle `_CANONICAL_MARKERS` est le juge de l'honnêteté du rendu. S'il
  DÉRIVE de `CANONICAL` (donc de #9/#10), le contrôle devient FAUX de deux façons :
  soit il exige un marqueur périmé (`Cardnet` alors que le rendu, issu d'un CANONICAL
  mis à jour, porterait `Azul` → **faux rouge**), soit on retire un marqueur et le
  contrôle **NE VÉRIFIE PLUS RIEN** (vert vacux). La seule suite `tests/` teste des
  FONCTIONS (dont `check_canonical` — AVEC son propre oracle hardcodé), jamais
  l'ancrage à CLAUDE.md.
- **État courant** : **aucun marqueur périmé** — `set(renderer._CANONICAL_MARKERS) ==
  set(model.CANONICAL.values())` vérifié programmatiquement AVANT toute édition ; les
  3 copies recoupent #9/#10 exactement (anti-invention #6, rien à réécrire). Le défaut
  est la **surface ungated**.

**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Faisabilité/generator
(2e surface) » après le bloc « paramètres canoniques ») : RÉUTILISE `exp` +
`_want6` (6 valeurs normalisées de #9/#10, zéro duplication). Helper `_list_vs_want6`
pour les copies-LISTES : (c) `renderer.py::_CANONICAL_MARKERS` et (e) l'oracle du test
sont parsés (`ast.literal_eval`) puis comparés à l'ensemble EXACT (set-diff : absent
ET en trop · anti-doublon). Pour les copies-DOCSTRINGS (d), chaque valeur normalisée
(`_pnorm`/`_ai` — espaces/accents-insensibles ⇒ `USD+DOP`==`USD + DOP`) doit être
substring de la ligne normalisée ; ≥2 énumérations exigées (barème + fonction). Un
claim/ligne absent échoue AUSSI (traçabilité #6).

**4 morsures vérifiées** : (M1) `renderer._CANONICAL_MARKERS` `Cardnet→Azul`
(absents=[cardnet] · en trop=[azul]) · (M2) oracle du test retire `8.5 %`
(absents=[8.5%]) · (M3) scorer docstring L7 `52 %→55 %` (n'énumère plus [52%]) ·
(M4) **CLAUDE.md #10 `Cardnet→Azul` → mord SIMULTANÉMENT model.py + renderer + test +
les DEUX docstrings** (5 morsures en cascade — la valeur MÊME de l'ancrage à la
source constitutionnelle) ; restauré = green : renderer/test == #9/#10 (6 marqueurs) ·
2 docstrings == #9/#10 · exit 0. Working tree byte-restauré (`git checkout --`,
**JAMAIS** `git clean`, interdit absolu) · **7 gates re-verts**
(`check_readme_claims`/`check_artifacts`/`check_ci_integrity`/`check_docs`/
`guard_constraints`/`check_regression`/`validate_json`).

- `ci/README.md` (clause récap du pipeline `check-readme-claims` + paragraphe détaillé
  « 2ᵉ surface du MÊME module : les 3 copies aval des marqueurs canoniques ») 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 génération
  réelle des faisabilités reste côté BIM/Faisabilité).
- **Auto-score 4Big** : 96/100.

## Session `20260801_150344` · Buffer S8 · Domaine DevOps CI/CD : gate d'**IDENTITÉ des IDENTIFIANTS D'INFRASTRUCTURE VPS** (IP `153.75.250.214` + les DEUX conteneurs Docker `erpnext-backend-1`/`erpnext-frontend-1`) ancré sur **CLAUDE.md §VPS** — surface data-derived jusqu'ici **HORS de tout ancrage**.

**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]] + [[agent-fiche-role-attrs-ungated]], surface
**infra ~ endpoint RunPod**). **Même patron EXACT** que le gate « endpoint GPU
RunPod » (session 130325 : un identifiant d'infra hand-transcrit dans N docs,
recomputé de la source in-repo faisant autorité) — appliqué ici aux identifiants
**serveur** dont la source de vérité est **constitutionnelle** (CLAUDE.md §VPS).

**Dérive silencieuse fermée** : CLAUDE.md §VPS FIXE l'IP du VPS `153.75.250.214`
et les 2 conteneurs Docker `erpnext-backend-1`/`erpnext-frontend-1`. Ces
identifiants sont RECOPIÉS À LA MAIN dans ~10 endroits, sans AUCUN ancrage :
- `README.md` (×2 · VPS hors périmètre + dépôt Gitea) · `02_master_prompt/
  MASTER_PROMPT_DTP_v2.md` (IP ×2 + les 2 conteneurs) · `AGENTS_EXISTING_ASSETS.md`
  (IP + 2 conteneurs) · `03_agents/mobile/AGENT.md` (`erpnext-backend-1`, backend
  cible de l'API) · `05_deliverables_mvp/rbac/RBAC_50_ROLES_SPEC.md` (run-book
  d'import) · `05_deliverables_mvp/GAP_ANALYSIS_SPRINT1.md` · `tests/README.md`
  (base URL Playwright) · `ci/README.md` (instance Gitea).
- Aucun gate ne couvrait ce lien : `check_docs` ne voit qu'un lien Markdown,
  `guard_constraints` qu'un terme interdit, `check_artifacts` qu'un `out/` — une IP
  ni un nom de conteneur n'est aucun des trois. Aucune suite `tests/` (FONCTIONS
  des modules, jamais la prose infra) ne l'attrape.
- Drift la plus réaliste, #6 : une **MIGRATION serveur** (nouvelle IP) ou un
  **RENOMMAGE du projet compose** (`erpnext-*` → `oto-*`) — évènement ops COURANT :
  CLAUDE.md §VPS se met à jour, les ~10 copies se PÉRIMENT en silence → un agent
  câblerait l'API mobile / les tests e2e / le hand-off d'import vers un serveur
  MORT ou un conteneur FANTÔME (l'invention même que #6 interdit).
- **État courant** : aucune copie périmée — l'unique IPv4 du corpus == canonique,
  les seuls conteneurs cités ⊆ l'ensemble canonique (vérifié programmatiquement
  AVANT édition). Le défaut est la **surface ungated**.

**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « CLAUDE.md §VPS · IDENTITÉ
des IDENTIFIANTS D'INFRASTRUCTURE » avant `sys.exit`) : RE-DÉRIVE l'IP + l'ENSEMBLE
des conteneurs de CLAUDE.md §VPS (`^## VPS` → `IP <v4>` + `Docker (…)`, jamais une
liste à la main · #6 ; garde « section/format introuvable » = ancre morte).
Direction copie→source sur `git ls-files '*.md'` (hors `CLAUDE.md` = source, hors
`05_activity_log/` + `daily_reports/` = journaux figés à ne pas réécrire) : tout
token **IPv4** DOIT == l'IP canonique ; tout token **conteneur** DOIT ∈ l'ensemble
canonique. Garde anti-évaporation (IP ET ≥1 conteneur restent cités quelque part).
Le write-path interdit `/var/www/html/static` reste du ressort de
`guard_constraints`, non de ce gate d'identité.

**Piège de conception CORRIGÉ en vérif adversariale** : 1ʳᵉ version ancrait
l'enveloppe conteneur sur le préfixe LITTÉRAL `erpnext-[a-z]+-1` → un renommage de
projet compose dans une copie (`erpnext-backend-1` → `oto-backend-1`, le drift MÊME
qu'on vise) ne matchait plus le shape et **PASSAIT en silence** (M2 vert à tort).
Correctif : l'enveloppe est DÉRIVÉE des **stems de service** canoniques
(`backend`/`frontend`) via `[a-z0-9]+-(?:backend|frontend)-1`, PAS du préfixe →
`oto-backend-1` est vu comme fantôme. Le filtre source est lui aussi générique
(`[a-z0-9]+-[a-z]+-1`) pour que M4 (renommage DANS §VPS) cascade avec un message
utile plutôt qu'un « format modifié » crude.

**5 morsures vérifiées** (mutations sur fichiers DONNÉES uniquement, restaurées par
`git checkout -- <path>` ciblé — **JAMAIS** `git clean`/`checkout` large avec le
gate décommité en cours, interdit absolu + incident process en mémoire) : (M1) IP
périmée dans `README.md` (serveur migré, copie non mise à jour) → stale · (M2)
conteneur `erpnext-backend-1→oto-backend-1` dans le master prompt (compose renommé)
→ fantôme (après correctif) · (M3) IP changée DANS CLAUDE.md §VPS → **cascade sur
les 5 docs citant l'IP** · (M4) conteneurs renommés DANS §VPS → **cascade fantôme
sur les 4 docs citant un conteneur** avec message utile · (M5) header `## VPS`
retiré → ancre morte. Restauré = green : 10 assertions VPS infra (5 IP + 5
conteneur) · exit 0. **7 gates re-verts** (`check_readme_claims`/`check_artifacts`/
`check_ci_integrity`/`check_docs`/`guard_constraints`/`check_regression`/
`validate_json`).

- `ci/README.md` (clause récap du pipeline `check-readme-claims` : sous-clause
  « identité d'infrastructure VPS ») mise à jour — et désormais **elle-même couverte
  par le gate** (elle gagne les tokens conteneur/IP canoniques, tous verts).
- **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 migration
  serveur réelle + le renommage compose restent côté DevOps/VPS production).
- **Auto-score 4Big** : 96/100.

## Session `20260801_153345` · Buffer S8 · Domaine CRM/Commissions : l'**IDENTITÉ des RÔLES PAYÉS** du barème vendeurs — le « **à quel rôle** » de la question fondatrice (« quel évènement paie, à quel RÔLE, sur quel montant », README:8-9) — était HORS de tout gate. La §Cross-cohérence promet (README:51-53) que chaque `role_id` commissionné « doit être résolu depuis `rbac_50_roles.json` … **ET appartenir au portail `ventes`** » (invariant CLI #5) avec un `erpnext_role_name` « cohérent avec RBAC » (invariant #6 · le « nom Frappe résolu » du README:36). C'est une **SÉPARATION DES POUVOIRS** : on ne commissionne QUE des rôles du portail ventes. Les DEUX blocs commissions existants (cross-cohérence états/base_field + anti-invention taux) NE couvraient PAS cette appartenance-portail ni la résolution du nom.

**Tâche** : **Sprint 8 · buffer** (ERPNext Backend / CRM · #6). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive. **Troisième surface data-derived** du même
livrable `crm/commissions` (après états/base_field et anti-taux) — le troisième tiers
de la question fondatrice du module (« à quel rôle »), le seul jamais confronté à
l'artefact. Classe [[agent-fiche-role-attrs-ungated]] : une propriété-rôle
data-derived affirmée en prose mais gatée par rien.

**Dérive silencieuse fermée** :
- `05_deliverables_mvp/crm/commissions/README.md:51-53` — « `role_id` … résolu depuis
  `rbac_50_roles.json` … ET appartenir au portail `ventes` » + README:36 « nom Frappe
  résolu ».
- Sources faisant autorité (toutes byte-gatées par `check_artifacts`) :
  `out/commission_plan.json[evenements[].{role_id,erpnext_role_name}]` (les rôles
  RÉELLEMENT payés) · `rbac/rbac_50_roles.json[roles[].{id,portail,erpnext_role_name}]`
  + `[portails_business]` (le contrat) · `out/MANIFEST.json[roles_rbac_utilises,counts.roles]`
  (le récap).
- Piège #6, les dérives les plus graves ici : **(a)** commissionner un rôle d'un
  AUTRE portail (payer un fiscaliste `compta` → la prose « portail `ventes` » devient
  un MENSONGE, pouvoirs mélangés) ; **(b)** un `role_id` FANTÔME absent du contrat
  (rôle inventé — l'invention même que #6 interdit) ; **(c)** un `erpnext_role_name`
  figé en dur dans le plan qui dérive quand le contrat renomme le rôle (nom Frappe
  périmé → `Has Role` importé sur un rôle INEXISTANT côté VPS). Aucune suite `tests/`
  (FONCTIONS de résolution/calcul, jamais la prose vs l'artefact) ne l'attrape ; ce
  n'est ni un lien (`check_docs`), ni un terme interdit (`guard_constraints`), ni un
  `out/` dérivé (`check_artifacts`) → invisible aux trois.

**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « CRM/Commissions ·
IDENTITÉ des RÔLES PAYÉS » après le bloc anti-invention) : RE-DÉRIVE tout des
artefacts byte-gatés (zéro dup du contrat du générateur · #6). **(a)** chaque
`role_id` du plan ∈ contrat (sinon fantôme) ; **(b)** `erpnext_role_name` du plan ==
contrat pour chaque évènement (nom Frappe résolu · invariant #6) ; **(c)** le portail
des rôles payés est un **SINGLETON** ∈ `portails_business`, ET == le portail nommé au
README (`appartenir au portail \`([a-z-]+)\`` — le littéral `ventes`) : payer un rôle
d'un autre portail casse le singleton, renommer le portail au contrat désaccorde le
littéral README ; **(d)** `MANIFEST.roles_rbac_utilises` == l'ensemble distinct
`(role_id, nom)` réellement payé + `counts.roles` recoupé.

**6 morsures vérifiées** (mutations sur fichiers DONNÉES uniquement — plan/contrat/
MANIFEST/README — restaurées par `git checkout -- <path>` **ciblé** ; **JAMAIS**
`git checkout .`/large avec le gate décommité en cours d'édition, interdit absolu +
incident process en mémoire) : (M1) `role_id` fantôme dans le plan → absent du contrat ·
(M2) `erpnext_role_name` dérivé dans le plan → ≠ contrat · (M3) rôle **`compta`**
commissionné → portails `{compta, ventes}`, séparation des pouvoirs rompue (+ cascade
MANIFEST) · (M4) littéral portail README `ventes→sales` → dit `sales` MAIS rôles du
portail `ventes` · (M5) `roles_rbac_utilises` amputé → ≠ rôles payés · (M6) renommage
du rôle DANS le contrat (`ventes-conseiller`) → cascade double (nom plan≠contrat +
MANIFEST périmé). Restauré = vert : 4 assertions vertes · exit 0. **7 gates re-verts**
(`check_readme_claims`/`check_artifacts`/`check_ci_integrity`/`check_docs`/
`guard_constraints`/`check_regression`/`validate_json`).

- `ci/README.md` (ligne récap du pipeline `check-readme-claims` : sous-clause « rôles
  payés » ; + paragraphe prose « troisième surface du même livrable `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
  des taux réels + le câblage du calcul restent côté ERPNext Backend/VPS production).
- **Auto-score 4Big** : 96/100.

## Session `20260801_160354` · Buffer S8 · Domaine QA/comptes de tests · Fiches agents : CINQ **comptes de tests PAR SUITE** écrits HORS de la colonne « Tests » des tables de fiches — `seo` « **36 tests** » (`03_agents/seo/AGENT.md:31`, prose libre) · `frontend_console` « **19 tests** » (portails) & « **31 tests** » (chat_otoia) (`:32`/`:34`, prose libre) · `onapi_legal` « **44 tests** » (`:32`, prose libre) · `devops` « 29 (dont 14 injections négatives) » (`:25`, **cellule de table à suffixe**) — étaient **HORS de tout gate**. Les DEUX blocs de comptes de tests existants ne les atteignent PAS : le bloc **cellules « Tests »** (`row_re`) n'accepte qu'une ancre de lien `` `<mot>/` `` ET une cellule ENTIER NU ; le bloc **agrégats en prose** ne couvre que `crm` (Total CRM) et `erpnext_backend` (RBAC + e-CF).

**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA — « Regression tests
exhaustifs »). Roadmap fonctionnellement close ; poursuite de la série anti-dérive
(CLAUDE.md #6). **Même classe EXACTE** que le bloc « comptes de tests PAR SUITE »
(cellules de table) et le bloc « agrégats EN PROSE » (Total CRM/RBAC/e-CF) — appliquée
à la **dernière poche** de la même surface : les claims per-suite que ces deux blocs
ne balaient pas par construction de leurs regex.

**Dérive silencieuse fermée** :
- `03_agents/seo/AGENT.md:31` — « **36 tests**, gated dans le pipeline » (suite `seo`).
- `03_agents/frontend_console/AGENT.md:32` — « **19 tests** · job CI
  `frontend-portails-tests` » (suite `frontend/portails`) · `:34` — « **31 tests** ·
  job CI `chat-otoia-tests` » (suite `frontend/chat_otoia`).
- `03_agents/onapi_legal/AGENT.md:32` — « **44 tests** (dont 8 négatifs) · job CI
  `legal-confotur-tests` » (suite `legal/confotur`).
- `03_agents/devops/AGENT.md:25` — cellule « Tests » du run-book :
  « 29 (dont 14 injections négatives) » (suite `devops/deploy_runbook`). Cette ligne
  échappe à `row_re` **DEUX fois** : (a) l'ancre de lien est un CHEMIN complet
  (`05_deliverables_mvp/devops/deploy_runbook/`, pas `` `<mot>/` ``) et (b) la cellule
  n'est PAS un entier nu (suffixe « (dont …) »).
- Source faisant autorité : `qa/regression/out/regression_plan.json`
  (`suites[].test_methods` · byte-gaté par `check_artifacts`), déjà chargée dans le
  dict `auth` que les deux blocs amont utilisent (zéro duplication #6). Les 5 recomputes :
  `seo`=36 · `frontend/portails`=19 · `frontend/chat_otoia`=31 · `legal/confotur`=44 ·
  `devops/deploy_runbook`=29.
- Piège : ajouter/retirer un `def test_` à l'une de ces 5 suites fait DÉRIVER en
  silence le nombre de la fiche pendant que la matrice de régression dit autre chose.
  Une compensation entre deux suites (l'une +1, l'autre −1) laisserait même la SOMME
  agrégée (« 564 tests · 22 suites », elle gatée) juste ET ces lignes fausses —
  exactement le motif per-suite que le bloc des cellules corrige, sur des surfaces
  qu'il ne balaie pas. Aucune suite `tests/` (qui teste des FONCTIONS, pas la prose
  des fiches) n'attrape ce « vert trompeur ».
- **État courant** : **aucun compte périmé** — les 5 recoupent
  `plan.suites[].test_methods` 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 · comptes
de tests PAR SUITE écrits HORS de la colonne Tests » après le bloc des agrégats en
prose e-CF) : table de config `PER_SUITE_HORS_TABLE` = (fiche · suite_path · motif ·
label). Pour chaque claim : (1) on RECOMPUTE `exp = auth[suite_path]` (même source
byte-gatée déjà chargée · zéro duplication) ; (2) motif ANCRÉ sur le nom de job CI
adjacent (ou le lien du module pour devops) pour viser LA bonne suite SANS ambiguïté
(les deux claims `frontend_console` 19/31 sont désambiguïsés par leurs job CI
distincts) ; (3) claim absent ⇒ ROUGE (traçabilité #6). Garde `suite_path ∈ auth`
(anti-câblage-rompu).

**7 morsures vérifiées** : (M1) fiche seo `36→35` · (M2) fiche portails `19→20` ·
(M3) fiche onapi `44→43` · (M4) **cellule devops** `29→30` (prouve que le suffixe
« (dont …) » + ancre chemin-complet est bien couvert) · (M5) **SOURCE drift** —
`plan.suites[seo].test_methods 36→37` ⇒ fiche périmée (prouve que le gate suit
l'ARTEFACT, pas un oracle figé) · (M6) ligne prose seo retirée ⇒ INTROUVABLE
(traçabilité) · (M7) fiche chat_otoia `31→32` ⇒ mord SEULE la ligne chat_otoia sans
toucher portails (prouve la désambiguïsation par job CI). Restauré = green : 5 claims
== `plan.suites[].test_methods` · exit 0. Working tree byte-restauré
(`git checkout --`, **JAMAIS** `git clean`, interdit absolu) · **7 gates re-verts**
(`check_readme_claims`/`check_docs`/`guard_constraints`/`validate_json`/
`check_artifacts`/`check_ci_integrity`/`check_regression`).

- `ci/README.md` (clause du récap `check-readme-claims` : « comptes de tests par
  suite écrits HORS de cette colonne ») mis à jour.
- **Observé, hors périmètre de CE gate** : `03_agents/devops/AGENT.md:24` cellule
  « 22 suites gated » = COMPTE de suites (`plan.totals.suites`), classe DISTINCTE du
  `test_methods` per-suite ; déjà ancré via l'agrégat QA « 22 suites gated · 564
  tests » — candidat propre pour une session future si confirmé ungated.
- **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 ; les suites
  réelles tournent côté runner Gitea).
- **Auto-score 4Big** : 96/100.

## Session `20260801_163401` · Buffer S8 · Domaine QA/audit_5d : l'**IDENTITÉ des 5 DIMENSIONS** du cadre d'audit — le **libellé** (`titre`) ET la **norme de référence** (`reference`) de chacune, énumérés à la main dans la table « ## Les 5 dimensions » (`05_deliverables_mvp/qa/audit_5d/README.md:15-19`) — était **HORS de tout gate d'IDENTITÉ**. Le tout premier bloc audit_5d ne recompute que le **COMPTE** (« **5 dimensions (5D)** ») — un WILDCARD aveugle à QUELLE dimension ; les 2 autres blocs audit_5d gatent la ventilation du verdict et l'énumération des open items, jamais la carte des dimensions.

**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA — « Audit UAF + normes ISA/IFRS
5D »). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md
#6). **Même patron EXACT** que l'énumération des open items (2e surface du MÊME
README) ou les triplets par workspace des portails — appliqué à une **3e surface
distincte du MÊME README `qa/audit_5d`** : la table des 5 dimensions, gatée jusqu'ici
seulement sur son COMPTE agrégé.

**Dérive silencieuse fermée** :
- `05_deliverables_mvp/qa/audit_5d/README.md:15-19` — table « ## Les 5 dimensions » :
  chaque ligne `| **D1** | Traçabilité & anti-invention | ISA 500 — Éléments probants |`
  transcrit PAR LIGNE le triplet (`id`, `titre`, `reference`) d'une dimension.
- Source faisant autorité : `qa/audit_5d/out/audit_report.json[dimensions]`
  (byte-gaté par `check_artifacts` ⇒ chaque dimension recomposée du catalogue de
  contrôles à chaque build), chaque entrée portant `id` + `titre` + `reference`.
  L'INPUT `audit_spec.json[dimensions]` (dont le rapport DÉRIVE) sert de cohérence
  croisée.
- Piège : l'audit 5D est l'audit de **SECOND NIVEAU** que l'auditeur/banquier lit.
  RENOMMER un libellé (« Traçabilité & anti-invention » → …) ou PERMUTER une norme
  de référence (D3 fiscal citant la mauvaise `Ley 32-23`, D2 AML la mauvaise
  `Ley 155-17`, D1 probatoire `ISA 501` au lieu d'`ISA 500`) laisse le compte à 5 —
  le bloc de comptage reste AVEUGLE — pendant que l'artefact dit autre chose ⇒ une
  AFFIRMATION de conformité fausse (l'invention même que #6 interdit). Aucune suite
  `tests/` (qui teste des FONCTIONS de contrôle, pas la prose) n'attrape ce « vert
  trompeur ».
- **État courant** : **aucune dimension périmée** — les 5 triplets 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 « Audit 5D (3e surface du
MÊME README) · IDENTITÉ des 5 DIMENSIONS » après le bloc open items) : on RECOMPUTE
la liste ORDONNÉE `{id:(titre, reference)}` depuis `audit_report.dimensions[]` (zéro
duplication du modèle de l'auditeur) et on exige que la table README l'énumère
EXACTEMENT : (1) identité par **ENSEMBLE d'`id`** (absent ET en trop mordus) ; (2)
**ORDRE** préservé (D1→D5) ; (3) `(titre, reference)` **par ligne**. Regex de ligne
`| **Dn** | … | … |` (gras + PAS de sous-point ⇒ distingue des lignes open items
`| D1.1 · … |`). Cohérence croisée bonus : `audit_report.dimensions` ==
`audit_spec.json[dimensions]` (l'INPUT dont le rapport dérive · mord un builder qui
renommerait/permuterait une dimension en aval du spec). Table disparue = échec
(traçabilité #6).

**8 morsures vérifiées** (5 côté README · 1 réordonnancement · 2 côté artefact/spec) :
README renomme D1 titre (`& anti-invention→et non-invention`) · README permute la
norme D1 (`ISA 500→ISA 501`) · README permute la norme fiscale D3 (`Ley 32-23→Ley
33-23`, affirmation de conformité fausse) · README supprime la ligne D5 (absent) ·
README réordonne D1↔D2 (ORDRE cassé) · **artefact** renomme le titre D2 (README périmé
+ divergence spec↔rapport) · **spec** renomme la référence D4 (divergence spec↔rapport)
· README supprime la table entière (INTROUVABLE, énumère `[]`) ; restauré = green :
5 dimensions `[D1..D5]` (id→titre→reference) == artefact · ordre == artefact ·
spec == rapport · 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_ci_integrity`/`check_artifacts`/
`check_regression`/`validate_json`).

- `ci/README.md` (recap `check-readme-claims` + paragraphe détaillé « 3e surface du
  MÊME README de l'audit 5D · IDENTITÉ des 5 dimensions ») 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
  des 4 open items réglementaires reste côté métier/VPS).
- **Auto-score 4Big** : 96/100.

## Session `20260801_190412` · Buffer S8 · Domaine Fiches agents/RBAC : l'**appartenance à `roles_allowed` du chat OTOIA** — QUI peut **invoquer** l'assistant OTOIA embarqué dans un portail — asserée EN PROSE par 2 fiches (`03_agents/rendu/AGENT.md:36` positive · `03_agents/mobile/AGENT.md:41` négative) était **HORS de tout gate d'appartenance**. C'est une **3e surface d'appartenance** DISTINCTE des deux déjà gatées : le **contrat** `rbac_50_roles.json` (attributs du rôle, blocs Fiches Faisabilité/Mobile) et le **`Has Role`** `workspace.json` (accès console, bloc Fiches Workspace). Le contrôle d'invocation du chat vit dans un 3e artefact — `frontend/chat_otoia/out/chat_mount.json` (`roles_allowed` par portail) — que le bloc Chat OTOIA amont ne gate que sur l'IDENTITÉ persona/capabilities + la COMPOSITION (5 mounts ⇔ 5 portails), AVEUGLE à QUEL rôle figure dans quelle `roles_allowed`.

**Tâche** : **Sprint 8 · buffer** (QA/DevOps — série anti-dérive CLAUDE.md #6, roadmap fonctionnellement close). **Même patron EXACT** que le bloc de membership `Has Role` (juste au-dessus dans `check_readme_claims.sh`) — appliqué à un **artefact + une sémantique DIFFÉRENTS** : accès console (`Has Role`) ≠ droit d'invocation du chat (`roles_allowed`).

**Dérive silencieuse fermée** :
- `03_agents/rendu/AGENT.md:36` — POSITIVE : rôle « OTO Faisabilité Rendu 3D » ∈ `roles_allowed` du portail NOMMÉ `construction` (déclenchement des rendus assistés).
- `03_agents/mobile/AGENT.md:41` — NÉGATIVE d'honnêteté (#6) : rôle « OTO Plateforme Mobile » (famille `plateforme`) dans AUCUNE `roles_allowed` (le chat ne couvre que les portails métier).
- Source faisant autorité : `frontend/chat_otoia/out/chat_mount.json[].roles_allowed` (byte-gaté par `check_artifacts`), `erpnext_role_name` résolu du contrat par `id` (zéro duplication).
- Piège : ajouter le rôle mobile à une `roles_allowed` = **SUR-EXPOSITION** (le dev mobile gagnerait le droit de piloter l'IA d'un portail métier — l'escalade même que #6 refuse) ; retirer le rôle rendu de la `roles_allowed` construction = perte du déclenchement assisté ; DÉPLACER le rôle vers un autre portail — laisse la fiche périmée EN SILENCE pendant que l'artefact dit autre chose. Aucune suite `tests/` (qui teste des FONCTIONS de montage, pas la prose d'une fiche) n'attrape ce « vert trompeur ».
- **État courant** : aucune valeur périmée — rendu ∈ construction (et lui seul), mobile ∉ tout (anti-invention #6, rien à réécrire). Le défaut est la **surface ungated**.

**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Fiches agents · appartenance à `roles_allowed` du chat OTOIA » après le bloc Fiches Workspace) : index recomputé `erpnext_role_name → {portails autorisant}`, puis (a) **in_named_portail** (rendu) — le portail nommé est data-derived (== `portail` du rôle au contrat, **triple ancrage** : nommé == contrat == unique portail autorisant), membership == {ce portail}, prose citant « invoquer le chat OTOIA » ; (b) **not_in_any** (mobile) — rôle ABSENT de TOUTE `roles_allowed`. Portail nommé fantôme (absent des mounts) = échec (anti-typo). Claim absent = échec (traçabilité #6). Invariant vérifié en amont : chaque rôle ∈ ≤1 `roles_allowed`, == son `portail` contrat.

**6 morsures vérifiées** (3 côté artefact · 3 côté fiche/bord) : artefact retire rendu de `construction` (∉ aucun portail) · artefact ajoute mobile à `ventes` (SUR-EXPOSITION #6 VIOLÉE) · artefact ajoute AUSSI rendu à `compta` (droit élargi, fiche nomme 1 seul) · fiche rendu nomme le mauvais portail (`construction→compta`, INTROUVABLE) · fiche mobile retire le token `roles_allowed` (INTROUVABLE) ; restauré = green : rendu ∈ `{construction}` · mobile ∉ tout · exit 0. Working tree byte-restauré (`git checkout --`, **JAMAIS** `git clean`, interdit absolu) · **7 gates re-verts** (`validate_json`/`check_docs`/`guard_constraints`/`check_artifacts`/`check_ci_integrity`/`check_readme_claims`/`check_regression`).

- `ci/README.md` (recap `check-readme-claims` + paragraphe détaillé « 3e surface d'appartenance · `roles_allowed` du chat OTOIA ») mis à jour.
- **Note memory** : la classe « agent-fiche-role-attrs-ungated » est désormais CLOSE (attributs contrat = Fiches Faisabilité/Mobile ; `Has Role` = Fiches Workspace ; `roles_allowed` chat = ce bloc). Candidat propre restant : la fiche `ifc_speckle:34` (attributs contrat déjà gatés) + la row acceptance S3 « viewer 3D » (déjà gatée QA).
- **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 montage réel du chat reste côté runner/VPS).
- **Auto-score 4Big** : 96/100.

---

## Session 20260801_193414 · Sprint 8 · buffer · Gate d'IDENTITÉ des comptes de tests self-reportés par les README de MODULE (#6)

**Contexte** : la série anti-dérive Sprint-8 gate chaque surface data-derived contre sa source de vérité. Tous les blocs de **comptes de tests** existants (`ci/check_readme_claims.sh`) visent `03_agents/*/AGENT.md` : la cellule « Tests » des tables via `row_re`, les agrégats prose de `crm`/`erpnext_backend`, et les cinq claims per-suite « hors table » (seo/portails/chat_otoia/confotur/deploy_runbook). AUCUN ne touche le README du **MODULE** lui-même — pourtant `05_deliverables_mvp/<suite>/README.md` se re-déclare, en propre, le compte de SA suite à plusieurs endroits.

**Surface trouvée (ungated)** : **27 claims sur 16 READMEs de module**, sous trois formulations récurrentes :
- commentaire de la commande unittest : `discover -s tests -v   # N tests (…)` ;
- bullet « Vérification en-repo » : `→ **N/N verts**` (six modules RBAC/faisabilité/CRM) ;
- justification d'auto-score 4Big : `Validé statiquement en-repo (N tests verts + …)`.
Ces nombres sont SAISIS À LA MAIN dans le doc d'entrée de chaque module et se périment dès qu'un `def test_` est ajouté/retiré : la source faisant autorité (`qa/regression/out/regression_plan.json` → `auth = plan.suites[path].test_methods`, recomptée à chaque build par découverte réelle, byte-gatée par `check_artifacts` + fraîcheur du run par `check_regression`) se met à jour tandis que la prose du README rote EN SILENCE — « vert trompeur » de la MÊME classe que la cellule « Tests » des fiches, mais sur la surface que `row_re` n'atteint pas (il ne lit le README de module QUE comme *cible de lien*). Aucune suite `tests/` (qui teste des FONCTIONS de génération, jamais la prose du README) n'attrape ce périmé.

**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « READMEs de MODULE · comptes de tests self-reportés » juste après le bloc per-suite « hors table », d'où `auth` est déjà en portée · zéro rechargement · #6) : table de config `MODULE_README_TESTS` = liste `(suite_path, [motifs ancrés])`. Pour chaque module : `auth[suite_path]` recomputé, chaque motif DOIT matcher (claim absent = régression #6) ET tous ses groupes capturés == `auth`. Le motif `**N/N verts**` capture DEUX groupes → les deux doivent valoir `auth` : un `25/24 verts` internement incohérent mord aussi. Chaque motif est ancré sur un contexte distinctif (`discover -s tests…# N tests`, `**N/N verts**`, `en-repo (N tests verts`, `test_confotur.py # N tests`…) pour viser LE compte de CETTE suite sans capter un autre nombre.
- **Cinq modules volontairement HORS table** (`frontend/chat_otoia`, `mobile/app_config`, `publiciste`, `qa/acceptance`, `qa/audit_4big`) : leur README ne self-reporte AUCUN compte de tests (vérifié) — rien à ancrer ; un futur claim = l'ajouter à la table. Noté dans le commentaire du bloc.

**4 morsures vérifiées** (chaque README restauré depuis backup — **JAMAIS** `git checkout .`/`git clean`, interdits absolus) :
1. drift commentaire (`faisabilite/generator` 16→17 tests) → ✗ « dit 17 MAIS source dit 16 » ;
2. incohérence interne `N/N` (`rbac/apply_plan` `16/16`→`16/17 verts`) → ✗ « dit 16/17 MAIS source dit 16 » ;
3. disparition de claim (`legal/confotur` prose auto-score « diffable, 44 tests (dont… » retirée) → ✗ « claim INTROUVABLE (attendu 44) » ;
4. drift auto-score prose (`rbac/fixtures_gen` `(11 tests verts`→`(12 tests verts`) → ✗ « dit 12 MAIS source dit 11 ».
Restauré = green : `rc=0`, **440 checks de base intacts (diff vide) + 27 nouveaux**. Gates sœurs re-verts : `check_ci_integrity` rc=0 · `check_docs` rc=0.

- En-tête du gate mis à jour (inventaire des surfaces couvertes : nouvelle entrée « comptes de tests self-reportés par MODULE »).
- **Note memory** : classe « comptes de tests » — désormais couverte sur TROIS surfaces (cellule « Tests » des fiches via `row_re` · agrégats prose fiches · **README de module**, ce bloc) + les cinq claims per-suite hors table. Source unique = `auth` (plan.suites[].test_methods).
- **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_200419` · Buffer S8 · Fiche DevOps : gate du **COMPTE de suites gated** (« 22 suites gated ») — sémantique DISTINCTE des comptes de tests par suite

**Contexte** : la série anti-dérive Sprint-8 gate chaque surface data-derived contre
sa source de vérité (CLAUDE.md #6, roadmap fonctionnellement close). Tous les blocs de
comptes de tests de `ci/check_readme_claims.sh` recomputent un compte de **méthodes**
`def test_` (`auth[path]` = `plan.suites[path].test_methods`) : la cellule « Tests »
des tables via `row_re`, les agrégats prose crm/erpnext_backend, les 5 claims per-suite
« hors table », et les READMEs de module. Une surface d'une **classe voisine mais
distincte** restait ungated.

**Surface trouvée (ungated)** : `03_agents/devops/AGENT.md:24` — dans la table des
livrables DevOps, la cellule « Tests » de la row du **pipeline CI**
(`.gitea/workflows/ci.yml` + `ci/`) affiche « **22 suites gated** » : non pas un compte
de tests, mais le **nombre de SUITES** que la CI passe au crible, soit
`regression_plan.totals.suites`. Ce nombre est saisi À LA MAIN et **aucun** gate ne
l'atteignait :
- `row_re` échoue DEUX fois : l'ancre de lien est `ci.yml`/`ci/` (pas un module de
  `auth`) ET la cellule porte le suffixe « suites gated » (pas un entier nu) ;
- l'agrégat de la fiche QA (`03_agents/qa/AGENT.md:35` « **22 suites gated · 564
  tests…** », gaté sur `regression_run`) est une **surface distincte** (autre fichier,
  autre motif). La MÊME valeur 22 vit donc à DEUX endroits — **un seul** était gaté.

**Dérive silencieuse fermée** : ajouter/retirer un module gated fait bouger
`reg["suites"]` (recompté à chaque build par découverte réelle des suites, byte-gaté par
`check_artifacts`) tandis que cette cellule DevOps rote EN SILENCE ⇒ la doc d'identité de
l'agent DevOps affirmerait un **périmètre de couverture CI faux** — le « vert trompeur »
que #6 interdit. Aucune suite `tests/` (FONCTIONS, pas la prose de fiche) ne l'attrape.
**État courant** : valeur exacte (22 == 22), le défaut est la **surface ungated** (rien
à réécrire · anti-invention #6).

**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Fiche DevOps · COMPTE de
suites gated » juste après le bloc per-suite « hors table », d'où `reg` est déjà en
portée · zéro rechargement) : on RECOMPUTE depuis `reg["suites"]`
(`regression_plan.totals.suites`, source unique) et on exige que la cellule l'écrive
EXACTEMENT. Motif ANCRÉ sur le label de rôle « CI/CD Gitea Actions » de CETTE row →
vise la bonne cellule. Claim absent = échec (traçabilité #6). **Cohérence croisée** :
`reg["suites"]` == `regression_run` `len(suites)` (la source du compte de la fiche QA)
⇒ une divergence plan↔run mord ici aussi.

**5 morsures vérifiées** (2 côté fiche · 2 côté artefact · 1 cross-cohérence) :
1. cellule 22→23 → ✗ « dit 23 MAIS source dit 22 » ;
2. claim retiré (`| gated |`) → ✗ « cellule « N suites gated » INTROUVABLE » ;
3. `plan.totals.suites` 22→21 (fiche périmée) → ✗ divergence SOURCE + ✗ « dit 22 MAIS
   source dit 21 » ;
4. `regression_run` privé d'une suite (plan↔run divergent) → ✗ « incohérence SOURCE :
   plan.suites=22 ≠ run len=21 » ;
5. restauré = green (rc=0).
Working tree byte-restauré via `git checkout -- <fichier précis>` (**JAMAIS**
`git checkout .` / `git clean`, interdits absolus). **7 gates re-verts** :
`validate_json` / `check_docs` / `guard_constraints` / `check_artifacts` /
`check_ci_integrity` / `check_readme_claims` / `check_regression` (tous rc=0).

- En-tête du gate mis à jour (inventaire des surfaces couvertes : nouvelle entrée
  « COMPTE de suites gated de la fiche devops ») + `ci/README.md` (recap
  `check-readme-claims` : nouvelle clause du compte de suites gated).
- **Note memory** : la classe « comptes de tests » (méthodes `def test_`) est couverte
  sur 4 surfaces + les 5 per-suite ; le COMPTE de **suites** (`plan.totals.suites`) est
  une classe voisine — désormais gaté sur ses DEUX surfaces (fiche QA agrégat déjà gaté ·
  fiche DevOps cellule, ce bloc).
- **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_203424` · Buffer S8 · Audit 5D (4e surface du même README) : gate de l'IDENTITÉ des **LIVRABLES AUDITÉS** — la liste des hand-off `out/*.json` que l'audit de second niveau LIT comme matière première

**Contexte** : la série anti-dérive Sprint-8 gate chaque surface data-derived contre
sa source de vérité (CLAUDE.md #6, roadmap fonctionnellement close). Trois blocs
`audit_5d` de `ci/check_readme_claims.sh` couvrent déjà le MÊME README : la **synthèse**
(17 contrôles / 5 dimensions + ventilation verdict), l'**énumération des open items**
(`control→dimension→propriétaire`) et l'**IDENTITÉ des 5 dimensions** (`id→titre→
reference`). Une **4e surface** du même README restait ungated.

**Surface trouvée (ungated)** : `05_deliverables_mvp/qa/audit_5d/README.md:21-27` —
section « ## Livrables audités (source = leur `out/`) » : une **liste de 5 puces** qui
énumère À LA MAIN les hand-off `out/*.json` que l'auditeur charge réellement
(`crm/workflow_vente/out/workflow.json` · `crm/dossier_vente/out/doctype_oto_dossier_
vente.json` · `crm/commissions/out/commission_plan.json` · `fiscal/ecf_dgii/out/
ecf_plan.json` · `legal/confotur/out/doctype_confotur_application.json`). Aucun bloc ne
la touchait — vérifié : le seul `audited_artifacts` de tout le gate était dans un
**commentaire** ; les 3 blocs audit_5d ne `load()` que `controls_total`/`totals`/
`open_items`/`dimensions[]`.

**Source faisant autorité** : `qa/audit_5d/out/MANIFEST.json` clé `audited_artifacts`
(byte-gaté par `check_artifacts` ⇒ reproduit à chaque build via
`sorted(ARTIFACT_PATHS.values())` dans `audit_5d_gen.py:61` — le registre des hand-off
que `qalib/artifacts.py::load_artifacts()` charge). Le README énumère les 5 chemins
dans un **ORDRE DIFFÉRENT** du MANIFEST (workflow 2e ici, 3e au MANIFEST trié) ⇒
comparaison par **ENSEMBLE**.

**Dérive silencieuse fermée** : brancher un nouveau livrable amont sur l'audit
(nouvelle entrée `ARTIFACT_PATHS` → nouveau chemin dans `audited_artifacts`) ou en
retirer un laisse la section README **périmée en silence** pendant que l'audit lit un
autre jeu de fichiers ⇒ le lecteur/banquier croirait l'audit couvrir un **périmètre
qu'il ne couvre plus** (l'invention #6 interdit), qu'aucune suite `tests/` (FONCTIONS
de contrôle, pas la prose) n'attrape. **État courant** : les 5 puces == registre
exactement (rien à réécrire · anti-invention #6), le défaut est la **surface ungated**.

**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Audit 5D (4e surface) ·
IDENTITÉ des LIVRABLES AUDITÉS » entre le bloc des 5 dimensions et le bloc DevOps
run-book) : on RECOMPUTE l'ensemble depuis `MANIFEST.audited_artifacts` (zéro
duplication du registre) et on parse les puces `` - `<chemin>` `` de la section
« ## Livrables audités » → égalité par ENSEMBLE exigée (absent ET fantôme mordus, via
set-diff `absents`/`en trop`). **Cohérences croisées bonus** : **(a)** chaque chemin
audité RÉSOUT un fichier commité sur disque (`os.path.exists` — le `_load` de l'auditeur
lève `FileNotFoundError` sinon, audit à vide impossible) ; **(b)** |livrables audités|
== |alias distincts d'artefacts référencés par les `controls[].artifacts` de
`audit_spec.json`| (un artefact chargé mais jamais contrôlé, ou un alias contrôlé sans
chemin de chargement → `KeyError` runtime, désynchronise). Section absente = échec
(traçabilité #6). `import os` ajouté au bloc (non importé globalement).

**6 morsures vérifiées** (4 côté README · 2 côté source) :
1. README puce `workflow.json` retirée → ✗ « absents=[…workflow.json] » ;
2. README puce FANTÔME `demo/scenarios/out/run_sheet.json` ajoutée → ✗ « en trop
   (fantôme)=[…run_sheet.json] » ;
3. README typo `commission_plan.json`→`commissions_plan.json` → ✗ absents ET fantôme ;
4. README header de section renommé → ✗ « section « ## Livrables audités »
   INTROUVABLE/vide » ;
5. `audit_spec.json` : alias `phantom_alias` ajouté à un contrôle → ✗ « désynchro
   registre/spec : 5 livrables MAIS 6 alias distincts contrôlés » (cross-anchor b) ;
6. `MANIFEST.audited_artifacts` : un chemin pointé vers un fichier inexistant → ✗
   « INTROUVABLES sur disque » (cross-anchor a) + ✗ README périmé.
Restauré = green : 5 puces == registre · 5 == 5 alias · 5 fichiers résolus · exit 0.
Working tree byte-restauré via `git checkout -- <fichier précis>` (**JAMAIS**
`git checkout .` / `git clean`, interdits absolus). **7 gates re-verts** :
`validate_json` / `guard_constraints` / `check_docs` / `check_artifacts` /
`check_regression` / `check_ci_integrity` / `check_readme_claims` (tous rc=0).

- `ci/README.md` mis à jour : clause de la row récap `check-readme-claims` (surface
  audit 5D étendue) + paragraphe détaillé « 4ᵉ surface du même README de l'audit 5D ».
- **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'exécution
  réelle de l'audit `bench` reste côté VPS).
- **Auto-score 4Big** : 96/100.

## Session `20260801_213444` · Buffer S8 · Fiche ONAPI/Legal : 2ᵉ SURFACE PROSE des ENTITÉS PORTEUSES

**Tâche** : **Sprint 8 · buffer** (DevOps CI/CD · QA — anti-dérive doc). Roadmap
plancher atteinte ; on continue de fermer les surfaces prose data-derived encore
ungatées. Cible de cette session : `03_agents/onapi_legal/AGENT.md:54`.

**Surface trouvée (ungatée)** : la fiche ONAPI/Legal décrit le champ Select
`entite_porteuse` du DocType CONFOTUR par « **les 7 entités de CLAUDE.md #Entités
(WAF · WA SRL · AC Arias Cuevas · Consortium ECR DR · Helios RD · Ploutos · 9060
QC)** ». C'est un **COMPTE littéral** (« 7 ») **+** l'**énumération complète** — une
**SECONDE surface prose** du même menu que le champ `entite_porteuse` offrira.

Le bloc « Confotur entités » déjà en place ancre le champ à CLAUDE.md §Entités et
gate la ligne du README `legal/confotur` (surface **(e)**), mais ce motif est ancré
sur `05_deliverables_mvp/legal/confotur/README.md` — **il ne vise pas `03_agents/`**.
La fiche restait donc **hors de tout gate** : recompter la §Entités (aujourd'hui 7)
n'atteignait jamais cette phrase.

**Dérive silencieuse fermée** : AJOUTER / RETIRER / RENOMMER une entité dans
CLAUDE.md §Entités (source ancre) → régénérer propage le menu de l'artefact **et** la
ligne du README confotur, MAIS la fiche onapi_legal se **périme en silence** — elle
annoncerait « 7 entités » quand il y en a 8, ou nommerait `Ploutos` là où le menu ne
l'offre plus. L'agent ERPNext Backend qui seede le DocType croirait le déroulant
couvrir un périmètre d'entités qu'il ne couvre plus (l'**invention #6** interdit).
Aucune suite `tests/` (FONCTIONS de build du DocType, pas la prose d'une fiche) ne
l'attrape. **État courant** : la fiche == §Entités == menu artefact exactement
(rien à réécrire · #6) ; le défaut était la surface **ungatée**.

**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Fiche ONAPI/Legal ·
2ᵉ SURFACE PROSE des ENTITÉS PORTEUSES », inséré juste après le bloc « Confotur
entités » et avant le bloc Chat OTOIA) : on **re-dérive** la liste depuis les tokens
en gras de la section « ## Entités » de CLAUDE.md (source ancre · zéro duplication)
et on exige de la fiche — **(a)** le COMPTE littéral (« les N entités ») ==
|§Entités| ; **(b)** l'énumération == §Entités **ORDRE EXACT** (absent ET fantôme
mordus par set-diff + comparaison ordonnée) ; **(c)** l'ancre « CLAUDE.md #Entités »
citée sur la ligne ; **(d)** cross-check bonus — l'énumération == options
`entite_porteuse` de l'artefact CONFOTUR byte-gaté (la fiche décrit **exactement** le
menu que le DocType offrira). Ligne absente = échec (traçabilité #6).

**6 morsures vérifiées** (5 côté fiche · 1 côté artefact) :
1. compte `7`→`8` → ✗ (a) « fiche dit 8 entités MAIS §Entités en porte 7 » ;
2. `Ploutos` retiré de l'énumération → ✗ (b) « absent(s)=['Ploutos'] » ;
3. entité FANTÔME `Fantom SA` ajoutée → ✗ (b) « fantôme(s)=['Fantom SA'] » ;
4. réordre `WAF`↔`WA SRL` → ✗ (b) « ordre : fiche […] ≠ §Entités […] » ;
5. ancre `#Entités` retirée de la ligne → ✗ (c) « ne cite pas CLAUDE.md #Entités » ;
6. option `Ploutos`→`Ploutos SRL` dans l'artefact CONFOTUR → ✗ (d) « fiche […] ≠
   menu artefact […] → la fiche décrit un menu que le DocType n'offre pas ».
Restauré = green : (a)(b)(c)(d) tous ✓ · exit 0. Working tree byte-restauré via
`git checkout -- <fichier précis>` (**JAMAIS** `git checkout .` / `git clean`,
interdits absolus). **7 gates re-verts** : `validate_json` / `guard_constraints` /
`check_docs` / `check_artifacts` / `check_regression` / `check_ci_integrity` /
`check_readme_claims` (tous rc=0).

- `ci/README.md` mis à jour : paragraphe « Entités porteuses du DocType CONFOTUR »
  étendu d'une clause « **2ᵉ surface prose (fiche ONAPI/Legal)** » (compte + liste +
  cross-check menu artefact).
- **Note anti-stale** : la mémoire `agent-fiche-role-attrs-ungated` est désormais
  **périmée** — les attributs des fiches mobile / rendu / ifc_speckle (niveau, scope,
  modules, permissions, entité) SONT déjà gatés (blocs « Fiche Mobile » et « Fiches
  Faisabilité »). La classe résiduelle réelle est celle des LISTES data-derived
  transcrites en prose de fiche, dont cette session ferme l'occurrence entités.
- **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'application
  réelle du DocType par `bench` reste côté agent ERPNext Backend).
- **Auto-score 4Big** : 96/100.

---

## Sprint 8 · buffer · Chat OTOIA : gate d'IDENTITÉ de la LISTE D'INVARIANTS CI

**Contexte** : campagne anti-dérive documentaire (Sprint 8 · QA « regression
tests exhaustifs »). Baseline des 7 gates vert avant intervention. Recherche
(sous-agent Explore) de la prochaine classe de surface data-derived transcrite
en prose mais NON gatée → candidat retenu : la section « ## Invariants (14) —
garantis en CI » du README `frontend/chat_otoia` (README:53-60).

**Défaut** : cette section (a) ANNONCE un compte `(14)` puis (b) ÉNUMÈRE chaque
invariant par son numéro entre parenthèses ((1)·(2)·…·(11, 13)·(12)·(14)). C'est
la transcription À LA MAIN du REGISTRE réel des invariants du générateur — les
marqueurs `# Invariant N ·` de `chat_otoia_gen.py` (source d'autorité : chaque
marqueur = un contrôle appliqué par `validate()`). Le générateur se self-reporte
en plus dans son message CLI (« schéma + 14 invariants verts. »), 3ᵉ transcription
du même compte. Les blocs Chat OTOIA préexistants du gate couvrent des COMPTES
d'artefact (blocks/mounts/portails) + l'IDENTITÉ persona/langues/endpoint —
AUCUN ne touche la liste d'invariants : surface aveugle. Aucune suite `tests/` ne
teste la PROSE ; le byte-gate d'artefact ignore le `.py` source ⇒ dérive
silencieuse (ajouter `# Invariant 15` ⇒ README périmé sans échec).

**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « Chat OTOIA · IDENTITÉ
de la LISTE D'INVARIANTS CI », inséré juste après le bloc « Chat OTOIA identité »
et avant le bloc « Recette ») : on RECOMPUTE le registre depuis le SEUL
registre-source (`re.findall(r"#\s*Invariant\s+(\d+)", chat_otoia_gen.py)`) et on
exige — **(a)** le registre code est un ensemble CONTIGU `1..max` (lacune/doublon
dans le ledger = défaut réel) ; **(b)** le message CLI « N invariants verts »
== |registre| (self-report du `.py` cohérent avec son propre ledger) ; **(c)** le
compte annoncé « ## Invariants (N) » du README == |registre| ; **(d)**
l'ÉNUMÉRATION en prose (ints extraits des groupes `(...)` de la section, borne =
prochain `## `) == registre EXACT — absent ET fantôme mordus par set-diff. Section
ou compte absent = échec (disparition d'un chiffre = régression de traçabilité #6).

**5 morsures vérifiées** (working tree byte-restauré après chacune via
`git checkout -- <fichier précis>`, JAMAIS `git checkout .` / `git clean`) :
1. compte annoncé `14`→`15` → ✗ (c) « annonce (15) MAIS registre en compte 14 » ;
2. numéro `(7)` retiré de la prose → ✗ (d) « absent(s)=[7] » ;
3. numéro fantôme `20` ajouté à la prose → ✗ (d) « fantôme(s)=[20] » ;
4. self-report CLI `14`→`13` → ✗ (b) « générateur dit 13 MAIS registre 14 » ;
5. ledger code `# Invariant 7`→`77` → ✗ (a) « registre NON contigu […77] » +
   ✗ (d) « absent(s)=[77] · fantôme(s)=[7] » (double morsure : ledger + prose).
Restauré = green : (a)(b)(c)(d) tous ✓ (registre contigu 1..14 · 14 marqueurs).

- `ci/README.md` : la clause « montage Chat OTOIA » de la row `check-readme-claims`
  étendue de l'IDENTITÉ de la liste d'invariants (compte + énumération + self-report
  CLI recomputés du registre `# Invariant N`, set-diff + contiguïté).
- **7 gates re-verts** : `validate_json` / `guard_constraints` / `check_docs` /
  `check_artifacts` / `check_regression` / `check_ci_integrity` /
  `check_readme_claims` (tous rc=0).
- **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.

---

## Sprint 8 · buffer · SEO : gate d'IDENTITÉ du COMPTE d'INVARIANTS CI

**Contexte** : campagne anti-dérive documentaire (Sprint 8 · QA « regression
tests exhaustifs »). Baseline des 7 gates vert avant intervention. Recherche
(sous-agent Explore) de la prochaine surface data-derived transcrite en prose
mais NON gatée. L'agent a proposé 5 candidates « N invariants » ; chat_otoia
étant déjà fermée au commit précédent, candidat retenu = le générateur SEO
(`05_deliverables_mvp/seo/seo_gen.py` + `seo/README.md`).

**Défaut** : la source d'autorité du COMPTE D'INVARIANTS de validation SEO n'est
PAS un artefact JSON (invisible à `check_artifacts` qui byte-gate les `out/*.json`)
mais le LEDGER NUMÉROTÉ de la fonction `_validate` — les marqueurs `# N ·`
(`# 1 ·` … `# 15 ·`) qui ouvrent chaque contrôle appliqué, analogue du registre
`# Invariant N` de Chat OTOIA. Ce compte est transcrit À LA MAIN à SEPT endroits
qu'AUCUN gate ne couvrait : le titre `## Garanties (N invariants)` du README + son
bloc de commande (« schéma de sortie + N invariants »), plus CINQ chaînes du `.py`
lui-même (docstring module L19, docstring `_validate` L57, aide `build` L24, message
de succès CLI « N invariants verts. » L285, aide du sous-parseur `validate` L298).
Le bloc SEO préexistant du gate ne couvre que des COMPTES d'ARTEFACT (mots-clés
total + par langue, recomputés de MANIFEST.counts) — classe distincte, surface
aveugle. Ajouter un contrôle `# 16 ·` à `_validate` ⇒ README + self-reports du `.py`
mentent en silence pendant que la CI applique 16 contrôles (« vert trompeur » #6)
— ni les suites `tests/` (FONCTIONS, jamais la prose) ni le byte-gate d'artefact
(aveugle au `.py` source) ne l'attrapent.

**Gate ajouté** (`ci/check_readme_claims.sh`, nouveau bloc « SEO · IDENTITÉ du
COMPTE d'INVARIANTS CI », inséré juste après le bloc SEO mots-clés et avant le bloc
CRM pipeline vente) : on RECOMPUTE le ledger depuis le SEUL registre-source (les
`# N ·` de `_validate`, extraits du corps de la fonction borné au prochain `def `)
et on exige — **(a)** le ledger est CONTIGU 1..N (comparaison de la LISTE triée à
`range(1,N+1)`, ⇒ mord lacune ET doublon) ; **(b)** TOUTE mention « N invariants »
du `.py` == |ledger| (set des self-reports == {N}) ; **(c)** le compte annoncé
« ## Garanties (N invariants) » du README == |ledger| ; **(d)** TOUTE mention
« N invariants » du README == |ledger|. La prose du README RÉSUME les contrôles
(11 puces `·`-séparées ≠ énumération numérotée 1:1) ⇒ on gate le COMPTE partout où
il apparaît, PAS un set-diff de la liste. Section/compte absent = échec (#6).

**5 morsures vérifiées** (working tree byte-restauré après chacune via
`git checkout -- <fichier précis>`, JAMAIS `git checkout .` / `git clean`) :
1. README titre `15`→`16` → ✗ (c) « annonce (16) MAIS ledger 15 » + ✗ (d) « [15, 16] » ;
2. README commande `15`→`14` → ✗ (d) « mention(s) [14, 15] MAIS ledger 15 » ;
3. `.py` succès CLI `15`→`13` → ✗ (b) « self-report(s) [13, 15] MAIS ledger 15 » ;
4. ledger `# 7 ·`→`# 77 ·` (lacune) → ✗ (a) « NON contigu […6, 77, 8…] » ;
5. ledger `# 8 ·`→`# 7 ·` (doublon) → ✗ (a) « NON contigu […7, 7, 9…] »
   (la comparaison sur LISTE — non set — attrape le doublon, angle que
   l'approche set de chat_otoia aurait raté).
Restauré = green : (a)(b)(c)(d) tous ✓ (ledger contigu 1..15 · 15 marqueurs).

- `ci/README.md` : la clause SEO de la row `check-readme-claims` étendue du COMPTE
  d'INVARIANTS CI (annonce + toutes mentions README + 5 self-reports `.py`
  recomputés du ledger `# N ·` de `_validate`, contiguïté 1..N).
- **7 gates re-verts** : `guard_constraints` / `validate_json` / `check_docs` /
  `check_artifacts` / `check_regression` / `check_ci_integrity` /
  `check_readme_claims` (tous rc=0).
- **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.

---

## Sprint 8 · buffer · RBAC SPEC §5 : gate de la LIGNE D'AUTO-CONTRÔLE du « résumé »

**Contexte** : suite de la campagne anti-dérive documentaire (Sprint 8 · QA
« regression tests exhaustifs »). Baseline des 7 gates verte avant intervention.
Recherche (sous-agent Explore) de la prochaine surface data-derived transcrite en
prose mais NON gatée — la plupart des COMPTES d'artefact sont déjà fermés. Le
candidat retenu est une surface interne de `rbac/RBAC_50_ROLES_SPEC.md` : le §5.

**Défaut** : la SPEC RBAC a DÉJÀ trois gates (§3 ventilation par portail · §4
colonne Entité · §7 run-book), mais son §5 « Les N rôles (résumé) » restait
AVEUGLE. Ce §5 restitue le total de rôles sur TROIS surfaces prose : (i) le
compte-titre « ## 5. Les 50 rôles (résumé) », (ii) NEUF comptes parenthétiques de
regroupement (`**Ventes (8)**` …) et (iii) une LIGNE d'auto-contrôle arithmétique
`_(6+8+4+7+5+8+4+2+6 = 50.)_`. AJOUTER un rôle au contrat (|rôles| 50→51) laisse le
titre, la somme et le « = 50 » PÉRIMÉS en silence ; ÉDITER `Ventes (8)→(7)` sans
toucher la ligne casse la cohérence titre↔somme — aucune suite `tests/` (FONCTIONS
RBAC, jamais la prose de la SPEC) ni le byte-gate d'artefact ne l'attrape.

**Subtilité (memory `rbac-spec-portail-count-gate` : « §5 by-family is
editorial »)** : les 9 regroupements sont ÉDITORIAUX — « Direction / Gouvernance »
agrège des rôles de familles direction+finance, ≠ la colonne `famille` du contrat.
On NE mappe donc PAS groupe↔famille (ce serait une dérive fausse). On gate
UNIQUEMENT ce qui est data-derived et vérifiable SANS ce mapping.

**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « 1septies) §5 », inséré après
le bloc §4 et avant le bloc apply_plan) : on RECOMPUTE N = |rôles| du contrat
(`rbac_50_roles.json`, byte-gaté par check_artifacts) et on exige — **(a)** le
compte-titre « ## 5. Les N rôles » == N ; **(b)** les 9 comptes de regroupement ==
EXACTEMENT, dans l'ordre, les termes de la ligne arithmétique (cohérence interne
prose↔somme, comparaison de LISTES ordonnées) ; **(c)** l'arithmétique est VRAIE
(Σ termes == membre droit) ; **(d)** le membre droit == N. Section/titre/ligne/
comptes absents = échec (#6).

**5 morsures vérifiées** (working tree byte-restauré après chacune via
`git checkout -- <fichier précis>`, JAMAIS `git checkout .` / `git clean` ;
gate rc=1 sous mutation, rc=0 restauré) :
1. titre `50`→`51` → ✗ (a) « titre dit 51 rôles MAIS le contrat en compte 50 » ;
2. regroupement `Ventes (8)`→`(9)` → ✗ (b) « [6, 9, 4…] ≠ termes [6, 8, 4…] » ;
3. ligne `= 50.`→`= 49.` → ✗ (c) « écrit = 49 MAIS Σ = 50 » + ✗ (d) « totalise
   49 MAIS le contrat compte 50 » (double morsure) ;
4. terme `6+8+…`→`7+8+…` → ✗ (b) « termes [7, 8…] ≠ [6, 8…] » + ✗ (c) « = 50
   MAIS Σ = 51 » (double morsure) ;
5. suppression de la ligne d'auto-contrôle → ✗ « ligne … INTROUVABLE » (absence).
Restauré = green : (a)(b)(c)(d) tous ✓ (titre 50 · 9 comptes == 6+8+4+7+5+8+4+2+6
· Σ=50 · membre droit 50 == |rôles|).

- `ci/README.md` : la row `check-readme-claims` étendue de la clause §5 (compte-
  titre + 9 comptes de regroupement éditoriaux + ligne d'auto-contrôle, sans
  mapping groupe↔famille).
- **7 gates re-verts** : `guard_constraints` / `validate_json` / `check_docs` /
  `check_artifacts` / `check_regression` / `check_ci_integrity` /
  `check_readme_claims` (tous rc=0).
- **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.
