# Activity Log · 2026-07-31 · Claude Code DTP ## Session `20260731_170112` · Buffer S8 · Domaine RBAC/fixtures_gen : le **catalogue des DocTypes `custom`** à créer sur le VPS (« `Faisabilité`, `Publiciste Log`, `CONFOTUR Application`, `API Access` ») ET la **séparation des pouvoirs** (« `set_user_permissions` n'est émis que pour le rôle **RBAC Admin** ») étaient transcrits À LA MAIN dans le README du module sans AUCUN gate d'IDENTITÉ — le bloc racine ne gate que le COMPTE (« 50 rôles / 116 DocPerm » via l'agrégat RBAC de l'apply_plan). **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les domaines tests, portails, RBAC (3 volets : roleprofile/apply_plan/userperm), SEO, CRM (pipeline vente + séparation des pouvoirs + commissions), mobile (quadruplet + onglets), Legal/CONFOTUR, DevOps, QA (audit 5D ×2 + 4Big ×2), Démo, Frontend/Chat OTOIA, recette et Fiscal/e-CF. Cette session cible le **module `rbac/fixtures_gen`** — jamais gaté hors son compte agrégé — dont deux faits data-derived du README pointent les artefacts générés sans être recomputés par aucun gate. **Dérive silencieuse fermée** : - `05_deliverables_mvp/rbac/fixtures_gen/README.md:56` : « listés dans `MANIFEST.custom_doctypes_a_confirmer` (`Faisabilité`, `Publiciste Log`, `CONFOTUR Application`, `API Access`) » — l'ENSEMBLE des DocTypes `custom: true` à créer. - `…/README.md:48` : « le flag `set_user_permissions` n'est émis que pour le rôle **RBAC Admin** » — le SINGLETON `{role | set_user_permissions == 1}`, cœur sécurité du module (« séparation des pouvoirs · défense en profondeur »). - Sources faisant autorité (byte-gatées par `check_artifacts`) : `rbac/fixtures_gen/out/MANIFEST.json.custom_doctypes_a_confirmer` (les DocTypes `custom` du contrat `rbac_50_roles.json`) et `out/custom_docperm.json` (le seul rôle porteur du flag = `OTO Plateforme RBAC Admin`). - Le bloc racine ne gate QUE le **compte** (« 50 rôles / 116 DocPerm » via l'agrégat RBAC de l'apply_plan). Aucun gate ne comparait ces ENSEMBLES aux artefacts : **AJOUTER** un DocType `custom` au contrat (le MANIFEST rebâtit 5 entrées), **RENOMMER/ÉCHANGER** l'un des 4, ou **PROMOUVOIR** un 2ᵉ rôle porteur de `set_user_permissions` (élévation de privilège) ferait dériver la prose en silence — « vert trompeur » qu'aucune suite `tests/` (qui teste des FONCTIONS de mapping, pas la prose) n'attrape. - **État courant** : **aucun ensemble périmé** — le catalogue (4) et le singleton recoupent les artefacts exactement (anti-invention #6, rien à réécrire). Le défaut est la **surface ungated**. **Nouveau bloc dans `ci/check_readme_claims.sh`** : recompute le catalogue depuis `MANIFEST.custom_doctypes_a_confirmer` et le singleton depuis `custom_docperm.json` (zéro duplication du contrat du générateur · #6) et exige que la prose l'énumère/le nomme **exactement** — contrôle par ENSEMBLE (absent ET en trop mordus via set-diff · accents/casse normalisés via `unicodedata`). Cohérences croisées en bonus : le catalogue est **non vide, sans doublon et trié** (byte-déterminisme du générateur) ; la séparation des pouvoirs est un **singleton** (ni vide — garde vacante — ni multiple — élévation de privilège). Un claim absent échoue AUSSI. ### Preuve (6 morsures vérifiées + restauration verte) | Injection | Résultat | |---|---| | README échange nom DocType (`Faisabilité → Faisabilite2`) | ✗ absents=`[faisabilite]` · en trop=`[faisabilite2]` · exit 1 | | README retrait `API Access` (sous-ensemble) | ✗ absents=`[api access]` · exit 1 | | README rôle nommé faux (`RBAC Admin → Ventes Conseiller`) | ✗ nomme « Ventes Conseiller » MAIS flag porté par `OTO Plateforme RBAC Admin` · exit 1 | | README énumération du catalogue supprimée | ✗ énumération du catalogue custom INTROUVABLE · exit 1 | | artefact 2ᵉ rôle promu au flag (`custom_docperm`) | ✗ set_user_permissions porté par `[…RBAC Admin, …Ventes Conseiller]` (attendu 1) · séparation cassée · exit 1 | | artefact catalogue MANIFEST non trié | ✗ non trié/dédupliqué · exit 1 | | restauré | ✓ catalogue `[API Access, CONFOTUR Application, Faisabilité, Publiciste Log]` == MANIFEST · singleton `OTO Plateforme RBAC Admin` == custom_docperm · exit 0 | - **`ci/README.md`** — surface ajoutée à la table récap du pipeline + paragraphe descriptif (classe « catalogue RBAC `rbac/fixtures_gen` »). Suite CI complète re-verte (7 gates) : `guard_constraints` · `validate_json` · `check_docs` · `check_artifacts` · `check_regression` · `check_ci_integrity` · `check_readme_claims`. **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 `20260731_163112` · Buffer S8 · Domaine CRM/Commissions : la **cross-cohérence** du barème vendeurs (« cœur du livrable ») — les états **brouillon exclus** (`lead/visite/devis/abandonné`), les **champs de base** (`montant_reservation`, `montant_contrat`) et l'**invariant 3** « on ne commissionne que des états soumis » — était énumérée À LA MAIN dans le README du module sans AUCUN gate d'IDENTITÉ ; le bloc racine ne gate que le COMPTE de tests (« 25 », via l'agrégat « Total CRM »). **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les domaines tests, portails, RBAC, SEO, CRM (pipeline vente + séparation des pouvoirs), mobile (quadruplet + onglets), Legal/CONFOTUR, DevOps, QA (audit 5D + 4Big), Démo, Frontend/Chat OTOIA, recette et Fiscal/e-CF. Cette session cible le **module `crm/commissions`** — jamais gaté hors son compte de tests — dont la section « Cross-cohérence » (le CŒUR du livrable) énumère trois faits data-derived pointant les artefacts générés sans être recomputés par aucun gate. **Dérive silencieuse fermée** : - `05_deliverables_mvp/crm/commissions/README.md:47` : « on ne commissionne pas un **brouillon** (`lead/visite/devis/abandonné`) » — l'ensemble des états brouillon. - `…/README.md:50` : « `base_field` … (`montant_reservation`, `montant_contrat`) » — l'ensemble des champs de base. - `…/README.md:46` : « correspondre à un état **soumis** (`doc_status = 1`) » — INVARIANT 3 du module. - Sources faisant autorité (byte-gatées par `check_artifacts`) : `crm/workflow_vente/out/workflow.json` (brouillons = `{update_value | doc_status==0}` = `{lead, visite, devis, abandonne}` · soumis = `{… | doc_status==1}`) et `crm/commissions/out/commission_plan.json` (champs de base = `{base_field}` des évènements · états payés = `{update_value}`). - Le bloc racine ne gate QUE le **compte de tests** (« 25 »). Aucun gate ne comparait ces ENSEMBLES aux artefacts : **PROMOUVOIR** un brouillon en soumis (`abandonne` → doc_status 1) le retire de l'ensemble brouillon pendant que la prose l'énumère encore ; **AJOUTER/RENOMMER** un champ de base ou **ÉCHANGER** un état passe aussi — « vert trompeur » qu'aucune suite `tests/` (qui teste des FONCTIONS finance/résolution, pas la prose) n'attrape. - **État courant** : **aucun ensemble périmé** — les 2 énumérations recoupent les artefacts exactement (anti-invention #6, rien à réécrire). Le défaut est la **surface ungated**. **Nouveau bloc dans `ci/check_readme_claims.sh`** : recompute chaque ensemble depuis SON artefact byte-gaté (zéro duplication du contrat du générateur · #6) et exige que la prose l'énumère **exactement** — contrôle par ENSEMBLE (absent ET en trop mordus via set-diff · accents/casse normalisés via `unicodedata`). Cohérence croisée en bonus (mord un plan **internement incohérent** avec le workflow) : tout `update_value` commissionné ∈ états soumis (doc_status=1) ET ∉ brouillons — l'**invariant 3**, non vide. Un claim absent échoue AUSSI. ### Preuve (5 morsures vérifiées + restauration verte) | Injection | Résultat | |---|---| | README brouillon `abandonné → perdu` (échange) | ✗ absents=`[abandonne]` · en trop=`[perdu]` · exit 1 | | README base_field retire `montant_contrat` (sous-ensemble) | ✗ absents=`[montant_contrat]` · exit 1 | | README énumération des brouillons supprimée | ✗ énumération des brouillons INTROUVABLE · exit 1 | | artefact plan `reservation → lead` (commissionne un brouillon) | ✗ incohérence plan↔workflow : `[lead]` PAS soumis · invariant 3 cassé · exit 1 | | artefact workflow `abandonne` doc_status `0→1` (draft set change) | ✗ prose en trop=`[abandonne]` vs brouillons workflow · exit 1 | | restauré | ✓ brouillons `[abandonne,devis,lead,visite]` == workflow (doc_status=0) · champs de base == plan · invariant 3 vert · exit 0 | - **`ci/README.md`** — surface ajoutée à la table récap du pipeline + paragraphe descriptif (classe « cross-cohérence du barème de commissions »). Suite CI complète re-verte (7 gates) : `guard_constraints` · `validate_json` · `check_docs` · `check_artifacts` · `check_regression` · `check_ci_integrity` · `check_readme_claims`. **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 `20260731_160111` · Buffer S8 · Domaine Fiscal/e-CF : le **contrat de format de l'e-NCF** (« `E + tipoeCF(2) + secuencia(10)` » = **13**) était retranscrit À LA MAIN dans **QUATRE** endroits de prose (README ×3 + fiche agent `erpnext_backend`) sans AUCUN gate d'IDENTITÉ — le bloc racine ne gate que le COMPTE de tests e-CF (« 39 »). **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les domaines tests, portails, RBAC, SEO, CRM (pipeline + séparation des pouvoirs), mobile (quadruplet + énumération onglets), Legal/CONFOTUR, DevOps, QA (audit 5D + 4Big), Démo, Frontend/Chat OTOIA et recette. Cette session cible le **domaine Fiscal/e-CF DGII** (`fiscal/ecf_dgii`) — le **contrat de format de l'e-NCF**, cœur du module (le « composeur traçable »), dont les longueurs de segments citées dans quatre proses pointaient l'artefact généré sans être recomputées par aucun gate. **Dérive silencieuse fermée** : - `05_deliverables_mvp/fiscal/ecf_dgii/README.md` × **3** : résumé l.10 (`` `E + tipoeCF(2) + secuencia(10)` ``), invariant 7 l.75 (`E+tipo(2)+seq(10)=13`, **seul à porter la longueur totale**), composition l.84 (`e_ncf = "E" + tipo(2) + secuencia(10)`). - `03_agents/erpnext_backend/AGENT.md:33` : `` `E + tipoeCF(2) + secuencia(10)` ``. - Source faisant autorité : le bloc `e_ncf` de `fiscal/ecf_dgii/out/ecf_plan.json` (byte-gaté par `check_artifacts`) — `prefix` (« E »), `longueur` (13), `regex` (`^E[0-9]{12}$`), `structure` (« E + tipoeCF (2 chiffres) + secuencia (10 chiffres) »). Format e-NCF standard DGII (Ley 32-23) porté avec `source` — pas un chiffre inventé (#6). - Le bloc racine ne gate QUE le **compte de tests** e-CF (« 39 »). Aucun gate ne comparait la LONGUEUR des segments : allonger la secuencia (rango DGII à 12 chiffres → `longueur` 15, `regex` 14 digits) OU changer le préfixe ferait dériver les QUATRE proses en silence pendant que l'`ecf_plan` byte-gaté dit autre chose — « vert trompeur » qu'aucune suite `tests/` (qui teste les FONCTIONS `compose_encf`/ `parse_encf`, pas la prose) n'attrape. - **État courant** : **aucun segment périmé** — les 4 proses recoupent l'artefact exactement (anti-invention #6, rien à réécrire). Le défaut est la **surface ungated**. **Nouveau bloc dans `ci/check_readme_claims.sh`** : recompute `(prefix, tipo_len, seq_len, total)` depuis le SEUL bloc `e_ncf` (zéro duplication du format du générateur · #6) et exige que **chaque** prose l'encode **exactement** (4 sous-checks numérotés, patron du bloc DevOps). Cohérences croisées en bonus (mordent un artefact **internement incohérent**) : `1 + tipo_len + seq_len == longueur` (1+2+10==13) ET le nombre de chiffres du `regex` == `tipo_len + seq_len` (12 == 2+10). Un claim absent échoue AUSSI (traçabilité). ### Preuve (5 morsures vérifiées + morsure d'incohérence + restauration verte) | Injection | Résultat | |---|---| | artefact `secuencia 10→12` (longueur 15 · regex 14 · les 4 proses restent 10/13) | ✗ les **4** proses mordues : dit E/2/10 MAIS e_ncf tipo=2 seq=12 total=15 · exit 1 | | README résumé `tipo 2→3` | ✗ dit E/3/10 MAIS e_ncf tipo=2 · exit 1 | | README invariant total `13→14` (aveugle au segment) | ✗ dit E/2/10/14 MAIS e_ncf total=13 · exit 1 | | fiche `erpnext_backend` `secuencia 10→9` | ✗ dit E/2/9 MAIS e_ncf seq=10 · exit 1 | | README composition supprimée (reformulée) | ✗ format e-NCF INTROUVABLE · exit 1 | | artefact `regex` désaligné (`{11}` vs tipo+seq=12) | ✗ incohérence artefact : regex 11 chiffres MAIS tipo+seq=12 · exit 1 | | restauré | ✓ `E + tipo(2) + secuencia(10) = 13` == `e_ncf` (4 proses + 2 cohérences croisées) · exit 0 | - **`ci/README.md`** — surface ajoutée à la table récap du pipeline + paragraphe descriptif (classe « contrat de format de l'e-NCF fiscal »). Suite CI complète re-verte (7 gates) : `guard_constraints` · `validate_json` · `check_docs` · `check_artifacts` · `check_regression` · `check_ci_integrity` · `check_readme_claims`. **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 `20260731_150110` · Buffer S8 · Domaine Mobile (2e surface) : la ligne récap du README app Expo **énumérait** À LA MAIN ses **5 onglets** (`(Ventes/Construction/Achat/Compta/Direction)`) sans gate d'IDENTITÉ — le bloc mobile existant ne recompute que le COMPTE (« 5 onglets ») et son patron matche la parenthèse par `\([^)]*\)` en **ignorant son contenu**, aveugle à un **échange / retrait / réordre** de nom. **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Le bloc `mobile` gaté précédemment recompute le **quadruplet** de la ligne récap (`onglets` · `rôles` couverts · `langues` · `identifiants a_confirmer`) depuis `MANIFEST.counts`. Restait, dans la **même ligne du même README**, une surface data-derived distincte : la **parenthèse qui énumère les 5 onglets** — matchée par le patron du compte mais dont le **contenu est ignoré**. **Dérive silencieuse fermée** : - `05_deliverables_mvp/mobile/app_config/README.md:66` — « **5 onglets** (Ventes/Construction/Achat/Compta/Direction) · … ». La parenthèse est une **liste ordonnée data-derived** ; le patron du compte (`\*\*(\d+) onglets\*\* \([^)]*\)`) la matche mais IGNORE son contenu. - Source faisant autorité : `mobile/app_config/out/role_navigation.json` (byte-gaté par `check_artifacts`) — un onglet par portail métier du contrat RBAC (bijection 1 onglet ⇔ 1 portail · `plateforme` exclu), chaque entrée portant `portail` (id) + `label` « OTO ». Nom court énuméré = le `label` sans le préfixe « OTO ». - Le bloc mobile ne gate QUE le **compte** (« 5 onglets »). Un **échange** (`Ventes → Marketing`), un **retrait** (`Compta` absent — l'énumération tombe à 4 tandis que « **5 onglets** » reste juste, aveugle au compte) ou un **réordre** laisse le compte à 5 pendant que l'artefact dit autre chose. « Vert trompeur » qu'aucune suite `tests/` (qui teste des FONCTIONS, pas la prose) n'attrape. - **État courant** : **aucun onglet périmé** — la liste des 5 recoupe l'artefact exactement (anti-invention #6, rien à réécrire). Le défaut est la **surface ungated**. **Nouveau bloc dans `ci/check_readme_claims.sh`** : recompute la **liste ordonnée** des noms courts depuis `role_navigation.json` (zéro duplication de la navigation du générateur · #6) et exige que la parenthèse l'énumère **exactement** : contrôle par **ENSEMBLE** (absent ET en trop mordus via set-diff), puis **ordre**. Même patron que l'énumération des confirmations DevOps, la carte de renormalisation par archétype 4Big ou la table « Verdict courant » de l'audit 5D. Cohérence croisée en bonus : `len(role_navigation) == MANIFEST.counts.onglets == MANIFEST.counts.portails` (la bijection du générateur) — mord un compteur du manifeste désynchronisé de sa navigation. ### Preuve (4 morsures vérifiées + restauration verte) | Injection | Résultat | |---|---| | échange `Ventes → Marketing` (compte reste 5 · gate-compte aveugle) | ✗ énumère `[Marketing,…]` MAIS `role_navigation` `[Ventes,…]` · absents=`[Ventes]` · en trop=`[Marketing]` · exit 1 | | retrait `Compta` (énumération=4 · « 5 onglets » reste juste) | ✗ énumère 4 MAIS `role_navigation` 5 · absents=`[Compta]` · exit 1 | | réordre `Construction/Ventes/…` (même ensemble) | ✗ même ensemble, ordre ≠ · exit 1 | | énumération supprimée (claim disparu) | ✗ énumération des onglets INTROUVABLE · exit 1 | | restauré | ✓ `Ventes/Construction/Achat/Compta/Direction` == `role_navigation` · bijection 5==onglets==portails · exit 0 | - **`ci/README.md`** — surface ajoutée à la table récap du pipeline + paragraphe descriptif (2ᵉ surface du même README mobile). Suite CI complète re-verte (7 gates) : `guard_constraints` · `validate_json` · `check_docs` · `check_artifacts` · `check_regression` · `check_ci_integrity` · `check_readme_claims`. **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ; édition **hors** `05_deliverables_mvp` ⇒ 0 dérive d'artefact). **Auto-score 4Big** : 96/100. --- ## Session `20260731_143104` · Buffer S8 · Domaine QA/Audit 5D (2e surface) : la **table « Verdict courant »** de l'audit de conformité énumérait À LA MAIN ses **4 open items** (`control → dimension → propriétaire`) sans gate d'IDENTITÉ — le bloc existant ne recompute que le COMPTE (« 4 à confirmer »), aveugle à un **échange** d'open item. **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Le bloc `audit_5d` gaté précédemment couvre « 17 contrôles / 5 dimensions » (×2 docs) **et** la ventilation du verdict « **13 PASS · 0 FAIL · 4 à confirmer** » (recomputée d'`audit_report.totals`). Restait, dans le **même README**, une surface data-derived distincte : la **table « Verdict courant »** qui énumère les 4 contrôles ouverts avec leur dimension et leur propriétaire. **Dérive silencieuse fermée** : - `05_deliverables_mvp/qa/audit_5d/README.md:51-56` énumère 4 lignes `| D1.1 · … | D1 | Direction |` … — chacune porte trois faits data-derived : l'`id` du contrôle, sa **dimension**, son **propriétaire**. - Source faisant autorité : `qa/audit_5d/out/audit_report.json.open_items[]` (byte-gaté par `check_artifacts`, chaque item = `{control, dimension, owner, detail}` recalculé en rejouant les contrôles sur les hand-off amont). - Le bloc de **ventilation** ne gate QUE le **compte** (« 4 à confirmer »). Un **échange** d'open item (ex. `D2.3 → D3.1`) laisse le compte à 4 — le compteur reste AVEUGLE — pendant que l'artefact dit autre chose ; idem une **dimension** mal étiquetée (`D1.3` rangé sous `D2`) ou un **propriétaire** réattribué. « Vert trompeur » qu'aucune suite `tests/` (qui teste des FONCTIONS, pas la prose) n'attrape. - **État courant** : **aucun open item périmé** — les 4 lignes recoupent l'artefact exactement (anti-invention #6, rien à réécrire). Le défaut est la **surface ungated**. **Nouveau bloc dans `ci/check_readme_claims.sh`** : recompute l'ensemble `{control:(dimension, propriétaire)}` depuis `audit_report.open_items` (zéro duplication du modèle de l'auditeur · #6) et exige que la table README l'énumère **exactement** : contrôle par **ENSEMBLE** (absent ET en trop mordus, pas seulement présence), puis `(dimension, propriétaire)` **par LIGNE**. Même patron que l'énumération des confirmations DevOps ou la carte de renormalisation par archétype 4Big. Cohérence croisée en bonus : l'ensemble des `control` == `MANIFEST.open_items` (le manifeste qui résume le rapport) — mord un manifeste désynchronisé de son rapport. ### Preuve (4 morsures vérifiées + restauration verte) | Injection | Résultat | |---|---| | échange `D2.3 → D3.1` (compte reste 4 · gate-compte aveugle) | ✗ énumère `[…,D3.1]` MAIS référentiel `[…,D2.3]` · exit 1 | | dimension `D1.3` étiquetée `D2` (compte + ids intacts) | ✗ open item D1.3 dit dimension=D2 MAIS artefact D1 · exit 1 | | propriétaire `D1.1` `Direction → Compta` (compte + ids + dims intacts) | ✗ D1.1 dit propriétaire=Compta MAIS artefact Direction · exit 1 | | ligne `D1.2` supprimée (sous-ensemble) | ✗ énumère `[D1.1,D1.3,D2.3]` MAIS référentiel `[D1.1,D1.2,D1.3,D2.3]` · exit 1 | | restauré | ✓ `[D1.1,D1.2,D1.3,D2.3]` (control→dimension→propriétaire) == `audit_report.open_items` · exit 0 | - **`ci/README.md`** — surface ajoutée à la table récap du pipeline + paragraphe descriptif (2e surface du même README audit 5D). Suite CI complète re-verte (7 gates) : `guard_constraints` · `validate_json` · `check_docs` · `check_artifacts` · `check_regression` · `check_ci_integrity` · `check_readme_claims`. **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ; édition **hors** `05_deliverables_mvp` ⇒ 0 dérive d'artefact). **Auto-score 4Big** : 96/100. --- ## Session `20260731_140101` · Buffer S8 · Domaine QA/Audit 4Big (2e surface) : la **carte de renormalisation PAR ARCHÉTYPE** de l'auditeur (« `data_room` (sans HANDOFF) · `parser` (sans CONTRAT … ni HANDOFF) · `contract` (sans CLI ni HANDOFF) ») était transcrite À LA MAIN dans le README du module — la session précédente a gaté le barème (critères + poids + `generator (N critères)`) mais PAS les exclusions des trois autres archétypes. **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). La session `133057` a gaté le **barème** de l'auditeur (nombre de critères + poids par critère + `generator (5 critères)`). Restait, dans le **même README**, une surface data-derived distincte : la **carte de renormalisation par archétype**, qui dit quels critères CHAQUE archétype **exclut** du dénominateur. **Dérive silencieuse fermée** : - `05_deliverables_mvp/qa/audit_4big/README.md:32-34` énumère « `generator` (5 critères) · `data_room` (**sans HANDOFF**) · `parser` (**sans CONTRAT** local ni **HANDOFF**) · `contract` (**sans CLI** ni **HANDOFF**) ». - Cette carte est **DATA-DERIVED** : pour un archétype, l'ensemble des critères **applicables** = `{check.criterion}` de ses modules dans `out/quality_report.json` (byte-gaté par `check_artifacts`) — les critères non-applicables sont **absents** des `checks`. Donc **exclus** = ordre du barème − applicables. - Le bloc barème de la session précédente ne recompute QUE `generator` (« 5 critères », zéro exclusion). Les **exclusions** de `data_room`/`parser`/`contract` n'étaient recomputées par AUCUN gate : changer la règle de renormalisation d'un archétype (ex. `data_room` exclut aussi `CLI`) ferait dériver la prose en silence pendant que `quality_report` dit autre chose — « vert trompeur » qu'aucune suite `tests/` (qui teste des FONCTIONS, pas la prose) n'attrape. - **État courant** : **aucune exclusion périmée** — les 3 cartes recoupent l'artefact exactement (anti-invention #6 : rien à réécrire). Le défaut est la **surface ungated**. **Nouveau bloc (6) dans `ci/check_readme_claims.sh`** (section barème `qa/audit_4big`) : recompute **par archétype** l'ensemble des critères exclus depuis les `checks` de `quality_report.modules` (zéro duplication du modèle de renormalisation du générateur · #6) et exige que la parenthèse du README l'énumère **exactement** (mauvais critère, sous-ensemble, sur-ensemble → tous mordus). Bonus d'uniformité : tous les modules d'un même archétype portent le **même** jeu applicable (« 1 archétype → 1 jeu de critères », base même de la carte — mord un référentiel où un module d'un archétype divergerait des autres). Un claim (énumération) absent échoue AUSSI (traçabilité). ### Preuve (3 morsures vérifiées + restauration verte) | Injection | Résultat | |---|---| | `data_room` « sans HANDOFF » → « sans CLI » (mauvais critère exclu) | ✗ exclut `[CLI]` MAIS référentiel `[HANDOFF]` · exit 1 | | `parser` exclusion `HANDOFF` retirée (sous-ensemble) | ✗ exclut `[CONTRAT]` MAIS référentiel `[CONTRAT, HANDOFF]` · exit 1 | | énumération `contract` supprimée (claim disparu) | ✗ énumération `contract` INTROUVABLE · exit 1 | | restauré | ✓ 4 archétypes uniformes · `data_room`/`parser`/`contract` == référentiel · exit 0 | - **`ci/README.md`** — surface ajoutée à la table récap du pipeline + paragraphe descriptif (classe « carte de renormalisation par archétype 4Big »). Suite CI complète re-verte (7 gates) : `guard_constraints` · `validate_json` · `check_docs` · `check_artifacts` · `check_regression` · `check_ci_integrity` · `check_readme_claims`. **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo). **Auto-score 4Big** : 96/100. --- ## Session `20260731_133057` · Buffer S8 · Domaine QA/Audit 4Big : le **barème de notation** de l'auditeur (« **5 critères 4Big déterministes** » + la table `Critère | Poids` + l'archétype « `generator` (5 critères) ») était transcrit À LA MAIN dans le README du module sans AUCUN gate — le bloc racine ne gatait que sa ligne « Résultat courant » (verdict/modules), jamais le référentiel de scoring lui-même. **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Restait une surface de « vert trompeur » non gatée dans le module qui NOTE les autres : son propre barème. **Dérive silencieuse fermée** : - `05_deliverables_mvp/qa/audit_4big/README.md` retranscrit trois surfaces data-derived du référentiel de scoring : la prose « Note chaque module sur **5 critères 4Big déterministes** », la table « Critère | Poids | Fait vérifié » (une ligne `id`→poids par critère : DOC 20 · CONTRAT 20 · TESTS 25 · CLI 15 · HANDOFF 20) et l'archétype « `generator` (5 critères) ». - La source faisant autorité est `out/quality_report.json.criteria` (byte-gaté par `check_artifacts`, recomputé du référentiel à chaque build). - Aucun gate ne comparait la VALEUR écrite à l'artefact : ajouter/retirer un critère ou changer un poids ferait dériver le README en silence — dérive qu'aucune suite `tests/` (qui teste des FONCTIONS, pas la prose) n'attrape. **Nouveau bloc dans `ci/check_readme_claims.sh`** : recompute le **nombre de critères** (prose + « generator »), et **chaque poids** depuis `criteria[].weight` (zéro duplication du barème du générateur · #6), exige l'égalité avec le README. Cohérences croisées : `Σ poids == 100` (base de renormalisation documentée) ET tout module d'archétype `generator` porte EXACTEMENT `len(criteria)` `checks` (« generator = tous les critères » — 18 modules generator × 5 checks vérifiés). Une **compensation** de poids (+5 / −5) laisserait `Σ == 100` juste et deux lignes fausses → d'où le contrôle **par critère**, pas seulement de la somme. ### Preuve (4 morsures vérifiées + restauration verte) | Injection | Résultat | |---|---| | README prose 5 critères → 6 (poids table intacts) | ✗ dit 6 MAIS criteria en compte 5 · exit 1 | | README poids `TESTS` 25 → 30 (compte intact) | ✗ dit poids 30 MAIS criteria dit 25 · exit 1 | | README archétype `generator` (5→4 critères) | ✗ dit 4 MAIS generator applique les 5 · exit 1 | | README claim prose reformulé (chiffre disparu) | ✗ « N critères 4Big » INTROUVABLE · exit 1 | | restauré | ✓ 5 critères · poids 20/20/25/15/20 · Σ=100 · 18 gen×5 == artefact · exit 0 | - **`ci/README.md`** — surface ajoutée à la table récap du pipeline + paragraphe descriptif (classe « barème auditeur 4Big »). Suite CI complète re-verte (7 gates) : `guard_constraints` · `validate_json` · `check_docs` · `check_artifacts` · `check_regression` · `check_ci_integrity` · `check_readme_claims`. **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo). **Auto-score 4Big** : 96/100. --- ## Session `20260731_130055` · Buffer S8 · Domaine QA/Recette : le couple « **8 livrables de sprint · 7 métriques succès MVP** » du module d'acceptation (`qa/acceptance`) était transcrit À LA MAIN dans DEUX docs de module (README du module + fiche agent QA) sans AUCUN gate — le bloc racine ne gate QUE le README d'entrée **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA régression). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les domaines tests, portails, RBAC (3 volets), SEO, CRM (pipeline vente), mobile, Legal (CONFOTUR), QA (audit 5D + audit 4big), DevOps (run-book), Démo (run-sheet) et Frontend/Chat OTOIA. Cette session cible le **module de recette** (`qa/acceptance`) — son couple data-derived « livrables de sprint / métriques succès MVP », cité dans deux docs de module que le bloc racine ne couvrait pas. ### Défaut trouvé — couple ungated, transcrit dans DEUX docs de module - **`05_deliverables_mvp/qa/acceptance/README.md:8-9`** : puces « **8 livrables de sprint** (L33..L76) » et « **7 métriques succès MVP** (L81..L87) ». - **`03_agents/qa/AGENT.md:27`** : cellule « mappe chaque promesse roadmap (**8 livrables + 7 métriques**) vers sa preuve » — **même** couple dérivé de la **même** matrice (source unique · #5). - L'artefact source est byte-gaté par `check_artifacts` : `qa/acceptance/out/acceptance_matrix.json` porte une ligne par promesse roadmap de `kind` `sprint_deliverable` OU `mvp_metric`. Le bloc racine de `check_readme_claims` recompute déjà ce couple pour le **SEUL README d'entrée** (« N promesses (X sprint + Y métriques) ») — mais **jamais** ces deux docs de **module**. - Le **roadmap-anchor** (INV11) ancre les `roadmap_line` de l'**ARTEFACT** au fichier roadmap ; il ne compare **pas** la prose des docs. Ajouter une promesse roadmap → l'artefact rebâtit 9/7 pendant que les deux docs restent à 8/7 en silence — « vert trompeur » qu'aucune suite `tests/` (qui teste des FONCTIONS, pas la prose) n'attrape. - **État courant** : **aucun nombre périmé** — 8/7 recoupe la matrice exactement (anti-invention #6 : rien à réécrire). Le défaut est la **surface ungated**. **Nouveau bloc dans `ci/check_readme_claims.sh`** : recompute **chaque** nombre depuis **son propre** genre de la matrice (`kind` `sprint_deliverable` / `mvp_metric`, `n_sprint`/`n_metric` déjà dérivés plus haut · zéro duplication #5) et exige l'égalité avec les DEUX docs. Recomputer les DEUX (sprint ET métriques) mord une dérive de l'un là où l'autre resterait aveugle. Cohérence croisée en bonus : `MANIFEST.counts.{sprint_deliverables,mvp_metrics}` == le compte de la matrice qu'il résume — mord un compteur du manifeste désynchronisé de sa liste. Un claim absent échoue AUSSI (traçabilité). ### Preuve (4 morsures vérifiées + restauration verte) | Injection | Résultat | |---|---| | README sprint `8→9` (métriques reste 7) | ✗ dit 9/7 MAIS matrice 8/7 · exit 1 | | README métriques `7→6` (sprint reste 8) | ✗ dit 8/6 MAIS matrice 8/7 · exit 1 | | fiche QA parenthèse `8→10` (README intact) | ✗ dit 10/7 MAIS matrice 8/7 · exit 1 | | fiche QA claim supprimé (reformulé) | ✗ claim INTROUVABLE · exit 1 | | restauré | ✓ 8/7 == matrice (2 docs) · exit 0 | - **`ci/README.md`** — surface ajoutée à la table récap du pipeline + paragraphe descriptif (classe « module de recette `qa/acceptance` »). Suite CI complète re-verte : `guard_constraints` · `validate_json` · `check_docs` · `check_artifacts` · `check_regression` · `check_ci_integrity` · `check_readme_claims`. **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo). **Auto-score 4Big** : 96/100. --- ## Session `20260731_123055` · Buffer S8 · Domaine Frontend/Chat OTOIA : le montage de l'assistant conversationnel par portail (`frontend/chat_otoia`) affichait « **5 `Custom Block` + 5 configs runtime** » (fiche agent frontend_console) et « **5 portails · 5 fixtures · 5 configs runtime** » (README du module) — trois nombres data-derived, TOUS = 5 (piège aveugle), transcrits À LA MAIN dans DEUX docs sans AUCUN gate **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA régression). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les domaines tests, portails, RBAC (3 volets), SEO, CRM (pipeline vente), mobile, Legal (CONFOTUR), QA (audit 5D), DevOps (run-book) et Démo (run-sheet). Cette session cible le **domaine Frontend/Chat OTOIA** — le **montage de l'assistant conversationnel par portail** (livrable Sprint 6 « Chat OTOIA embedded »), seul grand livrable dont les comptes data-derived cités dans deux docs pointaient l'artefact généré sans être recomputés par aucun gate. ### Défaut trouvé — triplet ungated, TOUS = 5 (piège aveugle), transcrit dans DEUX docs - **`03_agents/frontend_console/AGENT.md:29`** : cellule « **5 `Custom Block` + 5 configs runtime** » — deux nombres (custom_blocks + mounts). - **`05_deliverables_mvp/frontend/chat_otoia/README.md`** : « Portails (**5**, `plateforme` exclu) » (l.24) · « **5** fixtures `Custom Block` » (l.48) · « **5** configs runtime » (l.49) — trois transcriptions (portails + custom_blocks + mounts). - L'artefact source est byte-gaté par `check_artifacts` : `frontend/chat_otoia/out/MANIFEST.json` recompte `counts.custom_blocks`, `counts.mounts` et `counts.portails` du contrat RBAC à chaque build (bijection 1 block ⇔ 1 mount ⇔ 1 portail métier, `plateforme` exclu — invariant 2 du générateur). - Aucun gate ne comparait la VALEUR écrite à l'artefact. **Piège aveugle** caractéristique (cf. DevOps phases ⁄ confirmations) : custom_blocks, mounts ET portails valent **tous trois 5 aujourd'hui** — un gate qui n'en recompterait qu'un serait aveugle à la dérive des deux autres (ajouter un portail au contrat RBAC → chat_otoia rebâtit 6 ⁄ 6 ⁄ 6 pendant que les deux docs restent à 5). « Vert trompeur » qu'aucune suite `tests/` (qui teste des FONCTIONS, pas la prose des docs) n'attrape. - **État courant** : **aucun nombre périmé** — 5/5/5 recoupe MANIFEST exactement (anti-invention #6 : rien à réécrire). Le défaut est la **surface ungated**. **Nouveau bloc dans `ci/check_readme_claims.sh`** : recompute **chaque** nombre depuis **son propre** champ du MANIFEST (`custom_blocks` · `mounts` · `portails`, jamais figé ici · zéro duplication du compteur du générateur #5) et exige l'égalité avec les deux docs. Cohérence croisée : `custom_blocks == mounts == portails == |MANIFEST.portails|` (la bijection du générateur) — mord un compteur désynchronisé de sa propre liste. Un claim absent échoue AUSSI (traçabilité). ### Preuve (5 morsures vérifiées + restauration verte) | Injection | Résultat | |---|---| | fiche custom_block `5→6` (mounts reste 5, aveugle si 1 seul recompté) | ✗ dit 6/5 MAIS artefact 5/5 · exit 1 | | fiche configs runtime `5→4` (blocks reste 5) | ✗ dit 5/4 MAIS artefact 5/5 · exit 1 | | README portails `5→6` | ✗ dit 6 portails MAIS artefact 5 · exit 1 | | README fixtures `5→7` | ✗ dit 7 fixtures MAIS artefact 5 · exit 1 | | README claim « configs runtime » supprimé (reformulé) | ✗ claim INTROUVABLE · exit 1 | | restauré | ✓ 5/5/5 == artefact (2 docs) · exit 0 | - **`ci/README.md`** — surface ajoutée à la table récap du pipeline + paragraphe descriptif (classe « montage Chat OTOIA par portail »). Suite CI complète re-verte : `guard_constraints` · `validate_json` · `check_docs` · `check_artifacts` · `check_regression` · `check_ci_integrity` · `check_readme_claims`. **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo). **Auto-score 4Big** : 96/100. --- ## Session `20260731_120054` · Buffer S8 · Domaine Démo : le run-sheet de pitch (`demo/scenarios`) affichait « **10 modules cités** » dans le diagramme d'architecture de son README — un compte data-derived transcrit À LA MAIN sans AUCUN gate **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA régression). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les domaines tests, portails, RBAC (3 volets), SEO, CRM (pipeline vente), mobile, Legal (CONFOTUR), QA (audit 5D) et DevOps (run-book). Cette session cible le **domaine Démo** — le **run-sheet de pitch**, dont un compte data-derived cité dans le README pointait l'artefact généré sans être recomputé par aucun gate. ### Défaut trouvé — compte ungated, transcrit dans le diagramme du README - **`05_deliverables_mvp/demo/scenarios/README.md:23`** : diagramme d'architecture → « `scenario_spec.json … out/ des modules livrés (`**`10 modules cités`**`)` ». - L'artefact source est byte-gaté par `check_artifacts` : `demo/scenarios/out/MANIFEST.json` recompte `counts.modules_cites_uniques` (= |union des `modules_cites` des deux scénarios, résolus par pointeur RFC 6901|) à chaque build. Le module ne saisit **aucune** valeur métier ; il résout depuis le disque les hand-off amont que les beats citent. - Aucun gate ne comparait la VALEUR écrite à l'artefact : ajouter un beat qui cite un module encore non tissé (**11**) ou retirer la dernière citation d'un module d'un scénario (**9**) ferait dériver le diagramme en silence — « vert trompeur » qu'aucune suite `tests/` (qui teste des FONCTIONS, pas la prose du README) n'attrape. - **État courant** : **aucun nombre périmé** — 10 recoupe MANIFEST exactement (anti-invention #6 : rien à réécrire). Le défaut est la **surface ungated**. **Nouveau bloc dans `ci/check_readme_claims.sh`** : recompute le nombre depuis `counts.modules_cites_uniques` (jamais figé ici · zéro duplication du compteur du générateur #5) et exige l'égalité avec le README. Cohérence croisée en bonus : `counts.modules_cites_uniques == |modules_cites|` (liste dédupliquée du manifeste, elle-même sans doublon) — mord un compteur désynchronisé de sa propre liste. Un claim absent échoue AUSSI (traçabilité). ### Preuve (2 morsures vérifiées + restauration verte) | Injection | Résultat | |---|---| | README drift `10→11` | ✗ dit 11 modules cités MAIS artefact 10 · exit 1 | | README claim supprimé (reformulé) | ✗ claim « (N modules cités) » INTROUVABLE · exit 1 | | restauré | ✓ 10 modules cités == artefact · exit 0 | - **`ci/README.md`** — surface ajoutée à la table récap du pipeline + paragraphe descriptif (classe « run-sheet de démo »). Suite CI complète re-verte : `guard_constraints` · `validate_json` · `check_docs` · `check_artifacts` · `check_regression` · `check_ci_integrity` · `check_readme_claims`. **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo). **Auto-score 4Big** : 96/100. --- ## Session `20260731_113048` · Buffer S8 · Domaine DevOps : le run-book de déploiement VPS (`devops/deploy_runbook`) affichait « **7 phases** » (fiche agent devops, ×2) et « **7 confirmations** préalables » (README du module) — deux nombres data-derived transcrits À LA MAIN dans DEUX docs sans AUCUN gate **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA régression). Roadmap fonctionnellement close ; poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les domaines tests, portails, RBAC (3 volets), SEO, CRM (pipeline vente), mobile, Legal (CONFOTUR) et QA (audit 5D). Cette session cible le **domaine DevOps** — le **run-book de déploiement VPS**, dont deux comptes data-derived cités dans deux docs pointaient l'artefact généré sans être recomputés par aucun gate. ### Défaut trouvé — comptes ungated, transcrits dans DEUX docs - **`03_agents/devops/AGENT.md:25`** : « graphe de **7 phases ordonnées** », puis **ligne 88** « exécute les **7 phases** sur le VPS » — DEUX transcriptions du nombre de phases dans la même fiche. - **`05_deliverables_mvp/devops/deploy_runbook/README.md:94`** : « renseigner les **7 confirmations** » + énumération de ces 7 confirmations (lignes 55-58). - L'artefact source est byte-gaté par `check_artifacts` : `devops/deploy_runbook/out/MANIFEST.json` recompte `counts.phases` (= `len(graph.phase_order)`) et `counts.confirmations` (= `len(confirmations_ouvertes)`) à chaque build. - Aucun gate ne comparait la VALEUR écrite à l'artefact : ajouter une phase au graphe OU une confirmation au catalogue ferait dériver les docs en silence. **Piège aveugle** caractéristique : phases ET confirmations valent **tous deux 7** aujourd'hui — un gate qui n'en recompterait qu'un serait aveugle à la dérive de l'autre. **Nouveau bloc dans `ci/check_readme_claims.sh`** : recompute **chaque** nombre depuis **son propre** champ du MANIFEST (jamais figé ici · zéro duplication du compteur du générateur #5) et exige l'égalité avec les docs. Le **22** (« suites gated » de la fiche devops) est **volontairement écarté** : sa sémantique (suites CI) ≠ `counts.modules` du run-book (qui s'auto-exclut) → pas de transcription propre, pas de gate hasardeux. Bonus anti-dérive : l'**énumération** des confirmations du README (tokens `snake_case`) doit être l'ensemble **exact** de `confirmations_ouvertes` — mord l'**échange** d'un nom là où le seul compte reste aveugle. Cohérences croisées : `counts.phases == len(phase_order)` · `counts.confirmations == len(confirmations_ouvertes)`. ### Preuve (5 morsures vérifiées + restauration verte) | Injection | Résultat | |---|---| | fiche devops phases 7→8 (1re transcription) | ✗ dit 8 MAIS artefact 7 · exit 1 | | fiche devops 2e transcription « exécute les 7 »→6 (aveugle si 1re reste 7) | ✗ dit 6 MAIS artefact 7 · exit 1 | | README confirmations 7→8 (count) | ✗ dit 8 MAIS artefact 7 · exit 1 | | README swap d'un nom de confirmation (count reste 7, aveugle au compte) | ✗ énumération ≠ confirmations_ouvertes · exit 1 | | README claim confirmations supprimé (reformulé) | ✗ claim INTROUVABLE · exit 1 | | restauré | ✓ 7 phases + 7 confirmations + énumération == artefacts · exit 0 | - **`ci/README.md`** — surface ajoutée à la table récap du pipeline + paragraphe descriptif (classe « run-book de déploiement VPS »). Suite CI complète re-verte : `check_artifacts` · `check_readme_claims` · `check_docs` · `check_regression` · `check_ci_integrity` (gates statiques verrouillés) · `guard_constraints` · `validate_json`. **Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo). **Auto-score 4Big** : 96/100. --- ## Session `20260731_103044` · Buffer S8 · Domaine Legal : la synthèse du DocType « CONFOTUR Application » (« 18 champs · 14 de donnée · 4 sections · 3 rôles · 2 évènements de dépôt ») était transcrite À LA MAIN dans DEUX docs (README du module legal/confotur + fiche agent onapi_legal) sans AUCUN gate — ajouter un champ/section/rôle au spec ferait dériver les deux docs en silence **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA régression). Roadmap fonctionnellement close (22/22 modules · 564 tests · 7 gates verts au démarrage). Poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les comptes data-derived des domaines tests, portails, RBAC (3 volets), SEO, CRM (pipeline vente) et mobile. Cette session cible le **domaine Legal** — la **structure du DocType CONFOTUR**, dont un quintuplet data-derived cité dans deux docs pointait l'artefact généré sans être recomputé par aucun gate. ### Défaut trouvé — comptes du DocType ungated, transcrits dans DEUX docs - **`05_deliverables_mvp/legal/confotur/README.md:17`** : ligne de synthèse « **18 champs (14 de donnée) · 4 sections · 3 rôles · soumissible · 2 évènements de dépôt** ». - **`03_agents/onapi_legal/AGENT.md:28`** : cellule de table reprenant les **mêmes** 5 nombres (chacun en gras), **dérivés du même spec** (source unique · #5). - **Pourquoi non gaté** : `check_readme_claims` couvrait tests, portails, RBAC, SEO, CRM pipeline et mobile — jamais la structure du DocType CONFOTUR. `check_docs` ne valide que les liens ; `check_artifacts` prouve que `legal/confotur/out/MANIFEST.json` est **reproductible byte-for-byte** (bloc `counts` = {fields, data_fields, sections, permissions, roles, depot_events} recomptés de `confotur_spec.json` à chaque build) mais **ne compare pas** ce que les DEUX docs en **écrivent**. Ajouter un champ/section/rôle ferait dériver les deux docs **en silence** — même « vert trompeur ». - **État courant** : **aucun nombre périmé** — 18/14/4/3/2 recoupe MANIFEST exactement (anti-invention #6 : rien à réécrire). Le défaut est la **surface ungated** (faux-vert latent), pas un chiffre faux. ### Fix — extension de `check_readme_claims.sh` (recompute les 5 volets depuis MANIFEST) Nouveau bloc qui RECOMPUTE le quintuplet `(fields, data_fields, sections, roles, depot_events)` depuis `legal/confotur/out/MANIFEST.counts` (source déjà byte-gatée · zéro duplication du compteur du générateur) et exige l'égalité avec **les deux** docs : la ligne du README (sans gras) **et** la cellule de la fiche onapi_legal (chaque nombre en gras). Recomputer **les 5** volets (et pas le seul total de champs) mord une dérive de N'IMPORTE lequel là où un seul nombre resterait aveugle. Un claim **absent** échoue AUSSI. En-tête du gate + `ci/README.md` (§ table + § détail) mis à jour. ### Preuves de morsure | Injection | Résultat | |---|---| | **README** sections `4→5` | **exit 1** `dit 18/14/5/3/2 MAIS MANIFEST dit 18/14/4/3/2` | | **Fiche** champs `18→19` (README intact) | **exit 1** `dit 19/14/4/3/2 MAIS MANIFEST 18/14/4/3/2` — mord la 2e doc | | **Fiche** évènements dépôt `2→3` | **exit 1** `dit 18/14/4/3/3 MAIS MANIFEST 18/14/4/3/2` — mord le dernier volet là où le total de champs est aveugle | | **Claim disparu** (README reformulé) | **exit 1** `synthèse DocType INTROUVABLE (attendu champs=18 …)` | | Restauré | **exit 0** (2 docs verts) | ### Vérifs | Contrôle | Résultat | |---|---| | 7 gates (`guard`/`json`/`docs`/`artifacts`/`regression`/`ci_integrity`/`readme_claims`) | ✅ exit 0 | | `check_artifacts` (rebuild + égalité byte des `out/`) | ✅ (édition **hors** `05_deliverables_mvp` ⇒ 0 dérive d'artefact) | | Matrice de régression | **564/22 · PASS** inchangée (ni `tests/` ni doc-evidence touchés) | | Diff | 2 fichiers (`ci/check_readme_claims.sh` + `ci/README.md`) · 0 untracked | **Anti-invention (#6)** : rien inventé — le gate recompute les 5 volets depuis `confotur MANIFEST.counts` au lieu de figer un compteur ; les nombres des deux docs **découlent** de l'artefact. **Hors périmètre worker (VPS · #8)** : néant (gate python/stdlib en-repo). **Auto-score 4Big** : 96/100. --- ## Session `20260731_093041` · Buffer S8 · Domaine CRM (non couvert hors prose « tests ») : le pipeline vente « 9 états · 11 transitions » était transcrit À LA MAIN dans DEUX README de module (titre workflow_vente + « 9 états du pipeline » de dossier_vente, même compte du même graphe) sans AUCUN gate — ajouter un état ferait dériver les deux docs en silence **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA régression). Roadmap fonctionnellement close (22/22 modules · 564 tests · 7 gates verts au démarrage). Poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les comptes data-derived des domaines tests, portails (structure), RBAC (3 volets) et SEO (mots-clés). Cette session cible le **domaine CRM** — la **structure du pipeline vente**, dont un compte data-derived cité dans deux README de module pointait le graphe généré sans être recomputé par aucun gate. ### Défaut trouvé — comptes du pipeline ungated, transcrits dans DEUX docs - **`05_deliverables_mvp/crm/workflow_vente/README.md:23`** : titre de section « ## Le pipeline (**9** états · **11** transitions) ». - **`05_deliverables_mvp/crm/dossier_vente/README.md:30`** : « options = les **9 états** du pipeline » — **même** compte d'états, **dérivé du même graphe** (source unique · #5), recopié à la main. - **Pourquoi non gaté** : `check_readme_claims` couvrait tests, portails, RBAC et SEO — jamais la structure du pipeline CRM. `check_docs` ne valide que les liens ; `check_artifacts` prouve que `crm/workflow_vente/out/MANIFEST.json` est **reproductible byte-for-byte** (bloc `counts` = {states, transitions, …} recomptés de `workflow_vente_spec.json` à chaque build) mais **ne compare pas** ce que les DEUX docs en **écrivent**. Ajouter un état/une transition ferait dériver les deux docs **en silence** — même « vert trompeur ». - **État courant** : **aucun nombre périmé** — 9/11 recoupe MANIFEST exactement (anti-invention #6 : rien à réécrire). Le défaut est la **surface ungated** (faux-vert latent), pas un chiffre faux. ### Fix — extension de `check_readme_claims.sh` (recompute states + transitions depuis MANIFEST) Nouveau bloc qui RECOMPUTE le couple `(states, transitions)` depuis `crm/workflow_vente/out/MANIFEST.counts` (source déjà byte-gatée · zéro duplication du compteur du générateur) et exige l'égalité avec **les deux** docs : le titre du README workflow (`## Le pipeline (N états · M transitions)`) **et** le compte d'états du README dossier_vente (`les **N états** du pipeline`). Recomputer **transitions** en plus de **states** mord une dérive du 2ᵉ nombre là où le compte d'états seul (repris dans dossier_vente) resterait aveugle. Un claim **absent** échoue AUSSI. En-tête du gate + `ci/README.md` (§ table + § détail) mis à jour. ### Preuves de morsure | Injection | Résultat | |---|---| | **README workflow** états `9→10` (titre) | **exit 1** `dit 10/11 MAIS MANIFEST dit 9/11` | | **README workflow** transitions `11→12` (états inchangés) | **exit 1** `dit 9/12 MAIS MANIFEST dit 9/11` — mord le 2ᵉ nombre | | **README dossier_vente** états `9→8` (2e doc) | **exit 1** `dit états=8 MAIS MANIFEST dit états=9` — mord là où le titre seul est aveugle | | **Claim disparu** (dossier reformulé sans le motif) | **exit 1** `claim « N états du pipeline » INTROUVABLE (attendu états=9)` | | Restauré | **exit 0** (2 docs verts) | ### Vérifs | Contrôle | Résultat | |---|---| | 7 gates (`guard`/`json`/`docs`/`artifacts`/`regression`/`ci_integrity`/`readme_claims`) | ✅ exit 0 | | `check_artifacts` (rebuild + égalité byte des `out/`) | ✅ (édition **hors** `05_deliverables_mvp` ⇒ 0 dérive d'artefact ; aucun README/AGENT.md touché) | | Matrice de régression | **564/22 · PASS** inchangée (ni `tests/` ni doc-evidence touchés) | | Diff | 2 fichiers (`ci/check_readme_claims.sh` + `ci/README.md`) · 0 untracked | **Anti-invention (#6)** : rien inventé — le gate recompute states + transitions depuis `workflow_vente MANIFEST.counts` au lieu de figer un compteur ; les nombres des deux docs **découlent** de l'artefact. **Hors périmètre worker (VPS · #8)** : néant (gate python/stdlib en-repo). **Auto-score 4Big** : 96/100. --- ## Session `20260731_090037` · Buffer S8 · Domaine SEO : le total ET la ventilation par langue des mots-clés (« 258 mots-clés (fr=87 · en=87 · es=84) »), transcrits À LA MAIN dans DEUX docs (table du README module + ligne de la fiche agent), n'étaient couverts par AUCUN gate — ajouter un projet ferait dériver les deux docs en silence **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA régression). Roadmap fonctionnellement close (22/22 modules · 564 tests · 7 gates verts au démarrage). Poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les comptes **data-derived** des domaines tests, portails (structure), RBAC (3 volets). Cette session cible le **domaine SEO**, dernier grand livrable dont un compte data-derived cité dans deux docs pointait un artefact sans être recomputé par aucun gate. ### Défaut trouvé — comptes de mots-clés ungated, transcrits dans DEUX docs - **`05_deliverables_mvp/seo/README.md:15`** : table « Livrables » → « `out/seo_keywords.json` — **258** mots-clés (fr=87 · en=87 · es=84) ». - **`03_agents/seo/AGENT.md:24`** : **même** ligne, mêmes 4 nombres recopiés. - **Pourquoi non gaté** : `check_readme_claims` couvrait les comptes tests, les triplets par workspace des portails et les 3 volets RBAC — jamais le domaine SEO. `check_docs` ne valide que les liens ; `check_artifacts` prouve que `seo/out/MANIFEST.json` est **reproductible byte-for-byte** (bloc `counts` = {keywords_total, keywords_per_lang{fr,en,es}} recomptés de `projets_master.json` à chaque build) mais **ne compare pas** ce que les DEUX docs en **écrivent**. Ajouter un projet / du vocabulaire éditorial ferait dériver les deux docs **en silence** pendant que l'artefact dit autre chose — même « vert trompeur ». - **État courant** : **aucun nombre périmé** — 258=(87+87+84) recoupe MANIFEST exactement (anti-invention #6 : rien à réécrire). Le défaut est la **surface ungated** (faux-vert latent), pas un chiffre faux. ### Fix — extension de `check_readme_claims.sh` (recompute total + 3 langues depuis MANIFEST) Nouveau bloc qui RECOMPUTE le quadruplet `(total, fr, en, es)` depuis `MANIFEST.counts` (source déjà byte-gatée · zéro duplication du compteur du générateur) et exige l'égalité avec **les deux** docs. Recomputer les **trois langues** (pas seulement le total) attrape une **compensation** entre langues (fr −1 / en +1) qui laisserait le total juste et la ventilation fausse. Un claim **absent** échoue AUSSI. En-tête du gate + `ci/README.md` (§ table + § détail) mis à jour. ### Preuves de morsure | Injection | Résultat | |---|---| | **README total** `258→259` | **exit 1** `dit 259/87/87/84 MAIS MANIFEST 258/87/87/84` | | **Fiche compensation** `fr=87/en=87 → fr=86/en=88` (total inchangé) | **exit 1** `dit 258/86/88/84 MAIS MANIFEST 258/87/87/84` — mord là où le total seul est aveugle | | **Claim disparu** (README) | **exit 1** `claim mots-clés INTROUVABLE (attendu total=258 …)` | | Restauré | **exit 0** (2 docs verts) | ### Vérifs | Contrôle | Résultat | |---|---| | 7 gates (`guard`/`json`/`docs`/`artifacts`/`regression`/`ci_integrity`/`readme_claims`) | ✅ exit 0 | | `check_artifacts` (rebuild + égalité byte des `out/`) | ✅ (édition **hors** `05_deliverables_mvp` ⇒ 0 dérive d'artefact ; aucun README/AGENT.md touché) | | Matrice de régression | **564/22 · PASS** inchangée (ni `tests/` ni doc-evidence touchés) | | Diff | 2 fichiers (`ci/check_readme_claims.sh` + `ci/README.md`) · 0 untracked | **Anti-invention (#6)** : rien inventé — le gate recompute total + 3 langues depuis `MANIFEST.counts` au lieu de figer un compteur ; les nombres des deux docs **découlent** de l'artefact. **Hors périmètre worker (VPS · #8)** : néant (gate python/stdlib en-repo). **Auto-score 4Big** : 96/100. --- ## Session `20260731_080025` · Buffer S8 · Hors du domaine « tests » : les comptes PAR WORKSPACE (cartes/liens/rôles) des 5 portails frontend, transcrits À LA MAIN dans DEUX docs (table du README module + prose de la fiche agent), n'étaient couverts par AUCUN gate — ajouter un rôle/DocType au contrat RBAC ferait dériver les deux docs en silence **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA régression). Roadmap fonctionnellement close (22/22 modules · 564 tests · 7 gates verts au démarrage). Poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les comptes du domaine **tests** (sommes, cellules par-suite, agrégats en prose). Cette session sort de ce domaine : audit des comptes **data-derived** cités dans les fiches et README de module qui pointent un artefact mais n'étaient recomputés par aucun gate. ### Défaut trouvé — comptes par workspace ungated, transcrits dans DEUX docs - **`05_deliverables_mvp/frontend/portails/README.md:29-33`** : table « Portail | Workspace | **Cartes** | **Liens** | **Rôles restreints** » — 5 lignes × 3 nombres **saisis à la main**. - **`03_agents/frontend_console/AGENT.md:28`** : prose « **5 `Workspace` natifs** (`OTO Ventes` 4 cartes/11 liens/12 rôles · `OTO Construction` 4/9/10 · `OTO Achat` 3/8/5 · `OTO Compta` 4/11/8 · `OTO Direction` 4/14/9) » — **mêmes** triplets recopiés. - **Pourquoi non gaté** : `check_readme_claims` couvrait les comptes de **tests** (README racine, fiches QA/Backend, cellules par-suite, prose CRM/RBAC/e-CF) et `audit_4big`/`acceptance` — jamais les comptes **de structure** des portails. `check_docs` ne valide que les liens ; `check_artifacts` prouve que `frontend/portails/out/MANIFEST.json` est **reproductible byte-for-byte** (tableau `workspaces` = {nb_cartes, nb_liens, nb_roles} recalculés depuis le contrat RBAC à chaque build) mais **ne compare pas** ce que les DEUX docs en **écrivent**. Ajouter un rôle/DocType au contrat ferait dériver les deux docs **en silence** pendant que l'artefact dit autre chose — même « vert trompeur » que les cellules « Tests ». - **État courant** : **aucun nombre périmé** — les 15 triplets et le total « 5 » des deux docs recoupent MANIFEST exactement (anti-invention #6 : rien à réécrire). Le défaut est la **surface ungated** (faux-vert latent), pas un chiffre faux. ### Fix — extension de `check_readme_claims.sh` (recompute par workspace depuis MANIFEST) Nouveau bloc qui, pour **chaque** workspace de `MANIFEST.workspaces` (source déjà byte-gatée · zéro duplication du compteur du générateur), RECOMPUTE le triplet `(nb_cartes, nb_liens, nb_roles)` et exige l'égalité avec **les deux** docs : la ligne de table du README (ancrée `| | \`\` | C | L | R |`) **et** la cellule de prose de la fiche (`\`\` C cartes/L liens/R rôles`, forme abrégée `C/L/R` tolérée). + contrôle du **total** « N `Workspace` natifs » == len(workspaces). Un workspace **absent** d'un doc échoue AUSSI. En-tête du gate + `ci/README.md` (§ table + § détail) mis à jour. ### Preuves de morsure | Injection | Résultat | |---|---| | **README table** `OTO Compta` liens `11→12` | **exit 1** `README dit 4/12/8 MAIS MANIFEST dit 4/11/8` | | **Fiche prose** `OTO Achat` `3/8/5→3/8/6` (table inchangée) | **exit 1** `fiche dit 3/8/6 MAIS MANIFEST 3/8/5` — mord là où la table seule est aveugle | | **Cellule disparue** (`OTO Direction` retirée de la prose) | **exit 1** `claim INTROUVABLE (attendu 4/14/9)` | | **Total** `5→6 Workspace natifs` | **exit 1** `fiche dit 6 MAIS MANIFEST dit 5` | | Restauré | **exit 0** (10 lignes doc + total verts) | ### Vérifs | Contrôle | Résultat | |---|---| | 7 gates (`guard`/`json`/`docs`/`artifacts`/`regression`/`ci_integrity`/`readme_claims`) | ✅ exit 0 | | `check_artifacts` (rebuild + égalité byte des `out/`) | ✅ (édition **hors** `05_deliverables_mvp` ⇒ 0 dérive d'artefact ; aucun README/AGENT.md touché) | | Matrice de régression | **564/22 · PASS** inchangée (ni `tests/` ni doc-evidence touchés) | | Diff | 2 fichiers (`ci/check_readme_claims.sh` + `ci/README.md`) · 0 untracked | **Anti-invention (#6)** : rien inventé — le gate recompute chaque triplet depuis `MANIFEST.workspaces` au lieu de figer un compteur ; les nombres des deux docs **découlent** de l'artefact. **Hors périmètre worker (VPS · #8)** : néant (gate python/stdlib en-repo). **Auto-score 4Big** : 96/100. --- ## Session `20260731_073024` · Buffer S8 · Dernière surface de la classe « compte par suite » : les agrégats de tests rédigés EN PROSE dans deux fiches (`crm` « 81 (25+31+25) » · `erpnext_backend` « RBAC 60 (10+11+12+11+16) + e-CF 39 ») n'étaient couverts par AUCUN gate — une compensation entre suites laisserait la table gatée juste et la prose fausse **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA régression). Roadmap fonctionnellement close (22/22 modules · 564 tests · 7 gates verts au démarrage). Poursuite de la série anti-dérive (CLAUDE.md #6). La session précédente a gaté les **cellules** « Tests » par suite des tables de fiches (recompute générique depuis `plan.suites[path].test_methods`), motivée par le fait qu'une **compensation** +1/−1 entre deux suites passerait sous la seule somme agrégée. Recherche de la **même classe un cran au-dessus** : les agrégats **rédigés en prose** (hors table) que ce recompute par-cellule ne voit pas. ### Défaut trouvé — 2 agrégats en PROSE ungated (total + composants saisis à la main) - **`03_agents/crm/AGENT.md:32`** : « Total CRM : **81 tests** (25 + 31 + 25) ». - **`03_agents/erpnext_backend/AGENT.md:39`** : « Total backend **RBAC 60 tests** (10 + 11 + 12 + 11 + 16) + **e-CF 39 tests** ». - **Pourquoi non gaté** : `check_readme_claims` recompute les **cellules de table** (`row_re` exige un lien `…/README.md` + dernier champ numérique) — ces phrases en prose n'en sont pas. Le total agrégé du repo (564) est gaté ailleurs mais ne couvre pas ces **sous-totaux par domaine** ni leurs **composants**. Une compensation entre deux suites CRM (l'une +1, l'autre −1) laisserait la table (gatée) juste, la somme repo (564) juste, et la prose « (25 + 31 + 25) » **fausse en silence** — exactement le piège per-suite corrigé la session précédente, au niveau **agrégé**. - **État courant** : **aucun nombre périmé** — 81=(25+31+25), RBAC 60=(10+16+11+11+12), e-CF 39=fiscal/ecf_dgii, tous recoupés sur `plan.suites`. Le défaut est la **surface ungated** (faux-vert latent), pas un chiffre faux (anti-invention #6 : rien à réécrire). ### Fix — extension de `check_readme_claims.sh` (recompute total + multiset composants) Nouveau bloc qui, pour chaque agrégat en prose, **RECOMPUTE** depuis `auth` (`plan.suites[path].test_methods`, déjà chargé · zéro duplication) : - le **total** == somme des suites du domaine ; - le **multiset des composants** (trié, **ordre-indépendant**) == multiset des comptes par suite — c'est ce qui attrape la **compensation** que la somme seule rate ; - la **cohérence interne** (total == somme des composants écrits). + un contrôle à **suite unique** pour e-CF (pas de parenthèses). Un claim **absent** échoue AUSSI. En-tête du gate + `ci/README.md` (§ table + § détail) mis à jour. ### Preuves de morsure | Injection | Résultat | |---|---| | **Total périmé** `81→82` | **exit 1** `dit total=82 … MAIS source dit total=81 (interne_ok=False)` | | **Compensation** `(25+31+25)→(24+31+26)` (somme=81 **inchangée**) | **exit 1** `composants=[24,26,31] MAIS source [25,25,31]` — le multiset mord là où la somme est aveugle | | **Claim e-CF disparu** | **exit 1** `claim INTROUVABLE (attendu 39)` | | Restauré | **exit 0** | ### Vérifs | Contrôle | Résultat | |---|---| | 7 gates (`guard`/`json`/`docs`/`artifacts`/`regression`/`ci_integrity`/`readme_claims`) | ✅ exit 0 | | `check_artifacts` (rebuild + égalité byte des `out/`) | ✅ (édition **hors** `05_deliverables_mvp` ⇒ 0 dérive d'artefact ; aucun `AGENT.md` touché) | | Matrice de régression | **564/22 · PASS** inchangée (ni `tests/` ni doc-evidence touchés) | | Diff | 2 fichiers (`ci/check_readme_claims.sh` + `ci/README.md`) · 0 untracked | **Anti-invention (#6)** : rien inventé — le gate recompute total ET composants depuis `plan.suites` au lieu de figer un compteur ; les nombres de prose **découlent** de l'artefact. **Hors périmètre worker (VPS · #8)** : néant (gate python/stdlib en-repo). **Auto-score 4Big** : 96/100. --- ## Session `20260731_070019` · Buffer S8 · « Vert trompeur » ironique : le README de l'auditeur 4Big — le module qui COMPTE les modules — figeait son PROPRE total à la main (« 17/17 modules ») alors que son artefact `quality_report.json` en dit 22 ; ungated **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA régression). Roadmap fonctionnellement close (22/22 modules · 564 tests · 7 gates verts au démarrage). Poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les comptes agrégés/par-suite des **docs d'entrée** (README racine, fiches QA/Backend, tables « Tests »). Recherche du même faux-vert **hors de ce périmètre** : audit des comptes `N modules` / `N/N` dans les **README de module** (pas seulement l'entrée + les fiches). ### Défaut trouvé — 2 comptes vivants « 17 » périmés dans `qa/audit_4big/README.md` - **`05_deliverables_mvp/qa/audit_4big/README.md:68`** (« Résultat courant ») : « **Verdict `PASS`** — **17/17** modules à **100/100** » — compte **vivant saisi à la main**, périmé (17 → 22 au fil des ajouts de modules). **Ironie complète** : c'est le README du module qui **COMPTE les modules** ; son propre artefact faisant autorité `qa/audit_4big/out/quality_report.json` (`totals.modules`) dit **22** depuis longtemps. - **`…/README.md:38`** (règle du verdict global) : « `PASS` **ssi** couverture 100 % **et** les **17** modules ≥ 95/100 » — la **règle** (« TOUS les modules ≥ 95 ») ne dépend pas d'un compte ; le « 17 » n'était qu'une **surface de dérive** de plus. - **Pourquoi non gaté** : `check_readme_claims` recomputait `22/22` pour le **README d'entrée** (racine) mais ne couvrait **pas** ce README **de module**. `check_docs` ne valide que les liens ; l'auditeur s'**auto-exclut** (SoD) donc son propre `out/` ne reflète pas l'édition de son README → aucun signal. ### Fix (2 volets · CLAUDE.md #4 — corriger + supprimer la surface + garder) 1. **`README.md:38`** : retrait du compte figé → « **tous** les modules gated ≥ 95/100 » (la règle, count-free ⇒ **zéro surface de dérive**, précédent `050007`/`053011`). 2. **`README.md:68`** : `17/17` → **22/22** (compte courant, désormais gaté). 3. **`ci/check_readme_claims.sh`** : nouveau bloc qui **RECALCULE** verdict + `pass/modules/ min_score` depuis `quality_report.json` (`totals`, déjà chargé dans `q`/`qv` · zéro duplication) et exige l'égalité avec ce que « Résultat courant » **écrit**. Un claim absent échoue AUSSI. Header du gate mis à jour (doc couverte). ### Preuves de morsure - **Bite 1** (valeur périmée) : réinjecté `17/17` ⇒ gate **exit 1** `✗ … dit PASS/17/17/100 MAIS quality_report dit PASS/22/22/100 → régénérer le README`. - **Bite 2** (claim disparu) : reformulé sans le motif ⇒ **exit 1** `✗ … claim INTROUVABLE`. - **Restauré** ⇒ **exit 0**. ### Vérifs | Contrôle | Résultat | |---|---| | 7 gates (`guard`/`json`/`docs`/`artifacts`/`regression`/`ci_integrity`/`readme_claims`) | ✅ exit 0 | | `check_artifacts` (rebuild + égalité byte des `out/`) | ✅ (auditeur **auto-exclu** SoD ⇒ `quality_report.json` **byte-identique** ; 0 régénération) | | Matrice de régression | **564/22 · PASS** inchangée (README édité, aucun `tests/`) | | Résidu `17` vivant repo-wide | **aucun** (seul restant = daily_report `2026-07-30` point-in-time, conservé) | | Diff | 2 fichiers · +39/−3 · 0 untracked | **Anti-invention (#6)** : rien inventé — le gate **recompute** la vérité depuis `totals` au lieu de figer un compteur ; le `22/22` du README **découle** de l'artefact. **Hors périmètre worker (VPS · #8)** : néant (doc + gate bash/stdlib en-repo). **Auto-score 4Big** : 96/100. --- ## Session `20260731_053011` · Buffer S8 · Comptes vivants « 551 tests » périmés DANS la machinerie CI elle-même (`.gitea/workflows/ci.yml` + `ci/check_regression.sh` + `ci/check_readme_claims.sh`) — non couverts par aucun gate **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA régression). Roadmap fonctionnellement close (22/22 modules · **558** tests · 7 gates verts). Poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté les comptes vivants des **docs** d'entrée (README, fiches QA/Backend) puis nettoyé la **doc du CI** (`ci/README.md`, session `050007`). Recherche du même faux-vert **hors de ce périmètre** : audit `git grep '551'` repo-wide (pas seulement `*.md`) → la machinerie CI **opérationnelle** (workflow + headers de gates), qu'**aucun gate ne couvre**. ### Défauts trouvés — 3 comptes vivants « 551 » périmés (→ 558), dans le code des gates - **`.gitea/workflows/ci.yml:67`** : le commentaire du job `check-regression` décrivait l'artefact commité comme « **22 suites · 551 tests · PASS**, cité dans toute la doc » — compte **vivant figé** périmé (matrice 534 → 551 → **558**). - **`ci/check_regression.sh:8`** (l'ironie, encore) : le **header du gate de fraîcheur de la matrice** — dont le rôle même est d'empêcher le compte agrégé de se périmer — s'illustrait par « totaux « **22 suites · 551 tests · verdict PASS** » cité partout ». Le gardien anti-péremption avait lui-même péri. - **`ci/check_readme_claims.sh:152`** : l'anecdote décrivant le défaut QA disait « … alors que **le run dit 22 / 551** » — présent de l'indicatif ⇒ prétention **vivante** du compte courant, désormais fausse (558). *(Le « 21 suites · 534 tests » de la même ligne est l'anecdote historique point-in-time du défaut corrigé — conservé.)* - **Pourquoi non gaté** : `check_readme_claims` ne couvre que README + fiches QA/Backend ; ces fichiers **mélangent** narratifs historiques figés volontaires (progression « 534 → 551 → 558 ») et comptes vivants — exactement le motif qui a laissé `ci/README.md` dériver (session `050007`). ### Fix (2 solutions · CLAUDE.md #4 — retenu : supprimer la surface de dérive · précédent `050007`) Plutôt que réécrire 551→558 (qui re-dériverait au prochain ajout de suite), **retrait du nombre vivant figé** au profit d'un renvoi à l'artefact — « les totaux agrégés (suites · tests · verdict) dont la doc dérive, **jamais figés en dur ici** ». Zéro nombre à périmer = **zéro surface de dérive**, structurellement plus fort qu'un gate sur de la prose (#6). - `ci.yml:67` & `check_regression.sh:8` : compte figé → renvoi descriptif à `regression_run.json`. - `check_readme_claims.sh:152` : « le run dit 22 / 551 » → « le run agrégé faisant autorité en disait déjà **davantage** (recompté ci-dessous, jamais figé ici) » — formulation identique à celle appliquée à `ci/README.md` en session `050007`. Le **534** point-in-time reste comme valeur du défaut. - *(Alt. écartée : étendre `check_readme_claims` à `ci.yml`/`ci/*.sh` — fragile, il faudrait distinguer chaque progression historique figée d'un compte vivant ; le retrait est plus fort.)* ### Preuves | Contrôle | Résultat | |---|---| | `git grep '551'` hors logs/daily_reports/progression `534→551→558` | **aucun** compte vivant résiduel ✅ | | 7 gates (`guard`/`json`/`docs`/`artifacts`/`regression`/`ci_integrity`/`readme_claims`) | ✅ exit 0 | | `check_artifacts` (rebuild + égalité byte des `out/`) | ✅ (édition **hors** `05_deliverables_mvp` ⇒ 0 dérive d'artefact) | | `check_ci_integrity` (câblage `gate.needs` inchangé) | ✅ | | Diff | 3 fichiers · +9/−6 · 0 untracked | **Hors périmètre worker (VPS · #8)** : néant (commentaires CI en-repo, aucun touch prod). **Anti-invention (#6)** : rien inventé — retrait de nombres figés, la vérité reste recomputée par les gates depuis `regression_run.json`. **Auto-score 4Big** : 96/100. --- ## Session `20260731_050007` · Buffer S8 · Chiffre vivant périmé DANS la doc du CI (`ci/README.md`) — le gate `check_regression` était illustré par un compte de tests faux, et `check_readme_claims` sous-décrit (2 docs au lieu de 3) **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA régression). Roadmap fonctionnellement close (22/22 modules · 558 tests · **7** gates verts au démarrage). Poursuite de la série anti-dérive : la classe « chiffre inventé/périmé » (README, fiche QA, fiche Backend) est gatée sur 3 docs ; recherche d'un **même faux-vert hors de ce périmètre**. Audit repo-wide des agrégats (`git grep ' suites| modules|551|534'` sur `*.md` hors logs/archives) + relecture des 7 gates pour un **bug de logique**. ### Défauts trouvés — 3 dérives réelles, toutes dans `ci/README.md` (doc du livrable DevOps) - **`ci/README.md:80`** (le pire) : la section décrivant `check_regression` — le gate *censé empêcher les compteurs de se périmer* — s'illustrait elle-même par un **compte vivant périmé** : « 22 suites · **551 tests** · PASS ». La matrice faisant autorité (`qa/regression/out/regression_run.json`) dit **558** depuis la session `043004` (534 → 551 → 558) ; l'exemple appariait le compte de **suites courant** (22) à un compte de **tests périmé** (551). Ironie complète : la doc du gate anti-dérive avait elle-même dérivé. - **`ci/README.md:130`** : la ligne anecdote de la fiche QA citait « le run réel `22 / 551` » au **présent** — 551 ne correspond plus à rien (→ 558). - **`ci/README.md:121-131`** : la section `§check_readme_claims` affirmait garder « les **deux** docs d'entrée » (README + fiche QA) et **omettait la fiche ERPNext Backend** — 3ᵉ doc pourtant ajouté à la couverture du gate en session `043004`. Le **header du script** `check_readme_claims.sh` (source de vérité · l.23/29/32) liste bien les 3 ; seul `ci/README.md` a dérivé → **doc qui sous-décrit son propre code**. - **Pourquoi non gaté** : session `030001` a délibérément laissé `ci/README.md` **hors** de `check_readme_claims` car il porte des **chiffres historiques figés** volontaires (anecdotes « demo 18→21 », « 21 suites · 534 tests »). C'est ce mélange historique/vivant qui a laissé le compte **vivant** (551) se périmer en silence. ### Fix (2 solutions · CLAUDE.md #4 — retenu : supprimer la surface de dérive + réaligner) - **Correctif de fond (l.80)** : le compte **vivant** figé « 22 suites · 551 tests » est **supprimé** au profit d'un renvoi — « le compte agrégé (suites · tests · passés · verdict) … **recompté par `check_readme_claims`** dans les fiches d'entrée, jamais figé en dur ici ». Plus de nombre à périmer = **zéro surface de dérive** (plus robuste qu'un gate sur une prose ; #6). *(Alt. écartée : étendre `check_readme_claims` à `ci/README.md` — fragile, il faudrait distinguer chaque anecdote historique figée d'un compte vivant ; supprimer le nombre est structurellement plus fort.)* - **Anecdote historique (l.130)** : « le run réel `22 / 551` » (chiffre vivant faux) remplacé par « alors que le run agrégé faisant autorité en disait davantage » ; le **534** est **conservé** comme valeur point-in-time du défaut corrigé (comme « demo 18→21 »), désormais sans nombre concurrent périmé. - **Réalignement (l.121-134)** : « les **deux** docs d'entrée » → « les **trois** » + **ajout du bloc fiche Backend** (`N tests · M suites · verdict → regression_run.json`), miroir du header du script. La doc décrit à nouveau exactement le code. ### Preuves | Contrôle | Résultat | |---|---| | `git grep '551' *.md` (hors logs/archives) | **aucun** chiffre vivant résiduel ✅ | | `534` restant | **1 seule** ligne = anecdote historique explicite (l.131) ✅ | | « trois docs » + fiche Backend décrite dans `ci/README.md` | ✅ | | 7 gates (`guard`/`json`/`docs`/`artifacts`/`regression`/`ci_integrity`/`readme_claims`) | ✅ exit 0 | | `check_artifacts` (rebuild + égalité byte de tous les `out/`) | ✅ (édition **hors** `05_deliverables_mvp` ⇒ 0 dérive d'artefact) | | Arbre de travail | ✅ 1 fichier modifié, 0 untracked | **Anti-invention (#6)** : rien inventé — la vérité (558) vient de `regression_run.json` (gaté par `check_regression`) ; le fix **retire** le compteur figé plutôt que d'en poser un nouveau. Matrice **558/22 · PASS inchangée**. **Non-régression** : édition **doc pure hors `05_deliverables_mvp`** → `qa/audit_4big` ne la lit pas comme evidence ⇒ `quality_report.json` **inchangé** ; aucune régénération de consommateur nécessaire. **Hors périmètre worker (VPS · #8)** : néant (doc en-repo ; runner Gitea = DevOps). **Auto-score 4Big** : 96/100. ## Session `20260731_040004` · Sprint 7/8 · Livrable démo : ajout du **prompteur Markdown** (`out/run_sheet.md`) — le support texte lisible manquait au « scénario démo prêt à jouer » **Tâche** : **Sprint 7 l.68** (« CRM + Faisabilité : Scénarios démo P07 banquier · P05 client ready ») × **Sprint 8 buffer**. Après audit : les 7 gates CI étaient verts, 15 promesses `in_repo`, faisabilité 4 volets complète. Le seul livrable démo (`demo/scenarios`) ne produisait qu'un **run-sheet JSON machine** — aucun support **lisible par un présentateur**. Son propre README plafonnait l'auto-score à 96/100 en qualifiant le « prompteur » de **hors périmètre (VPS)**. Or rendre un Markdown in-repo depuis le JSON est exactement le pattern déjà utilisé par `faisabilite` (rend des `.md`) — **aucune écriture VPS**. Gap réel, borné, à forte valeur. ### Livraison - **`scenlib/render.py`** (nouveau) — `render_markdown(run_sheet) -> str` **pur et déterministe**. Ne lit **que** le run-sheet (déjà résolu, déjà anti-inventé) : aucun chiffre nouveau, aucun recalcul (anti-invention #6). `_fmt` : bool→oui/non, liste→jointe, placeholder survivant rendu entre back-ticks (jamais promu en nombre). - **`demo_scenario_gen.py build`** émet désormais `out/run_sheet.md` en plus des deux JSON. Le rendu est un prompteur par scénario : en-tête (audience/projet/durée/ objectif), puis chaque beat en table « À dire | Chiffre | Source (preuve) » — chaque ligne trace son pointeur RFC 6901 amont. - **`out/run_sheet.md`** (nouveau, commité) — 2 scénarios · 12 beats · 24 min cumulés. ### Compatibilité architecture (vérifiée AVANT d'écrire) - **HANDOFF 4Big** (`q4lib/criteria.py:91`) ne `json.load` que les fichiers `.json` — un `.md` dans `out/` est **ignoré**, aucune casse du critère. - **`ci/check_artifacts.sh`** diffe **tout** fichier produit par `build` → il exige désormais `run_sheet.md` **commité + reproductible byte-for-byte** (render déterministe ⇒ OK). Preuve de morsure implicite : fichier non suivi ⇒ gate exit 1 (constaté puis corrigé par `git add`). ### Consommateurs régénérés (dérive maîtrisée) - +7 méthodes test (`TestRenderMarkdown`) → **régression 551→558 tests** (22 suites, PASS) : `regression run` + `build` régénérés (`regression_run/plan/MANIFEST.json`). - `demo/scenarios` : README `5011→5575` octets + `32→39` test_* → **`quality_report.json` régénéré** (evidence DOC+TESTS) · verdict **PASS · 22/22 · min 100** inchangé. - `03_agents/qa/AGENT.md` : verdict agrégé `551→558` (gate `check_readme_claims` vert). - README démo : Fichiers (+`render.py`, +`run_sheet.md`), chaîne de valeur, « hors périmètre » (seul l'**habillage visuel** reste VPS), auto-score **96→97**. ### Vérifs | Contrôle | Résultat | |---|---| | 7 gates CI (`guard`/`json`/`docs`/`artifacts`/`regression`/`readme_claims`/`ci_integrity`) | ✅ exit 0 | | Tests démo | ✅ 39/39 | | Tests audit_4big | ✅ 34/34 | | Régression exhaustive | ✅ 22/22 suites · 558 tests · PASS | | `run_sheet.md` reproductible (build frais == commité) | ✅ (gate artifacts) | | Arbre de travail | ✅ 0 fichier untracked | **Hors périmètre worker (VPS · #8)** : habillage visuel deck / page démo `otov7.com` + branchement `data_room` réel — inchangé. Le prompteur texte est le rendu de référence in-repo que ce visuel habillera. **Auto-score 4Big** : 97/100. ## Session `20260731_033001` · Buffer S8 · « Vert trompeur » : la fiche QA affichait un « verdict agrégé courant » PÉRIMÉ (21/534) en se disant « jamais compté à la main » — hors du périmètre du gate README **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement close (22/22 modules 100/100, 551 tests verts, **7** gates statiques). Poursuite de la série anti-dérive : le gate `check_readme_claims` créé la session précédente ne garde que `README.md` ; recherche d'une **même classe de faux-vert hors de son périmètre**. ### Défaut trouvé — 3 chiffres périmés en SILENCE dans `03_agents/qa/AGENT.md` et `ci/README.md` - **Mécanisme** : la session `030001` a corrigé le README **et** claimé avoir corrigé « 3 commentaires CI (21 suites · 534 tests) dans `ci.yml` et `check_regression.sh` ». Mais son propre gate ne couvre **que** `README.md`. Deux autres docs d'entrée portaient encore le compte **périmé** — invisibles pour tous les gates (`check_docs` ne valide que les liens) : - **`03_agents/qa/AGENT.md:35`** (le pire) : section « Verdict agrégé courant » → *« **21 suites gated · 534 tests · 534 passés · 0 échec · 0 erreur → verdict `PASS`** »* en déclarant, ligne suivante, *« jamais compté à la main »*. C'était pourtant un compte **figé à la main** qui cite `qa/regression/out/regression_run.json` comme source — alors que le run réel dit **22 / 551**. Une fiche d'agent QA qui **affirme une matrice verte avec des chiffres faux** est le « vert trompeur » le plus grave. - **`ci/README.md:80`** : exemple « compteurs cités partout » figé à « 21 suites · 534 tests · PASS » (→ 22 · 551). - **`ci/README.md:184`** : checklist DevOps « les **6 gates** statiques » (→ **7** depuis l'ajout de `check-readme-claims`). - **Preuve au sol** : `git grep -nE '534|21 suites|6 gates'` sur `*.md` (hors logs) ramenait les 3 lignes ; `regression_run.json` recalculé = **22 suites · 551 tests · 551 passés · 0 échec · 0 erreur · PASS** (somme des 22 suites du run commité). ### Fix (2 solutions · CLAUDE.md #4 — retenu : correction + extension du gate au-delà du README) - **Correction des 3 chiffres** pour refléter les artefacts (fiche QA 21/534→22/551 ; `ci/README.md` exemple 21·534→22·551 et checklist 6→7 gates). Ligne 132 « Les 6 gates commençaient… » → « Les gates… » (le compte historique contredisait la table §1 à 7). - **Correctif de fond** : **extension de `ci/check_readme_claims.sh`** (7ᵉ gate) au-delà du README. Il **RECOMPUTE** désormais aussi le « Verdict agrégé courant » de la fiche QA depuis `regression_run.json` (compte agrégé faisant autorité, commité · déterministe) : `N suites · M tests · M passés · E échec · E erreur · verdict`, exige l'égalité avec ce qui est **écrit** dans `03_agents/qa/AGENT.md`. Un claim **absent** échoue AUSSI. *(Alt. écartée : 2ᵉ gate dédié — même classe de contrôle « valeur↔artefact » ; l'ajouter au gate existant évite un doublon de câblage CI · #5.)* Le nom de fichier reste inchangé (pas de ré-câblage `ci.yml`/INV-B) ; en-tête du script + `ci/README.md` §1 table + §2 détail mis à jour pour la portée élargie (README **et** fiche QA). ### Preuves de morsure - **Bite 1** (valeur périmée) : réintroduit « 21 suites · 534 tests » ⇒ gate **exit 1** `✗ Fiche QA … dit 21/534/534/0/0/PASS MAIS regression_run dit 22/551/551/0/0/PASS`. - **Bite 2** (claim disparu) : retire toute la ligne « Verdict agrégé » ⇒ **exit 1** `✗ Fiche QA · verdict agrégé — claim INTROUVABLE`. - **Restauré** ⇒ **exit 0** (les 6 claims README + le claim QA verts). **Anti-invention (#6)** : rien inventé — le gate **recompute** la vérité depuis `regression_run.json` (somme des suites du run commité) au lieu de figer un compteur ; les 3 chiffres corrigés sont tous recoupés sur cet artefact. **Non-régression** : **7 gates** locaux verts, dont `check_artifacts` (rebuild + égalité byte de tous les `out/`) ⇒ **aucune dérive d'artefact** (l'audit_4big ne lit ni `03_agents/` ni `ci/` comme evidence → pas de régénération `quality_report`). Matrice **551/22 · PASS inchangée**. Unique « 534 » résiduel = description **historique** volontaire du défaut corrigé (`ci/README.md:130`, comme l'anecdote « demo 18→21 »). **Hors périmètre worker (VPS · #8)** : néant (stdlib pur en-repo ; runner Gitea toujours du ressort DevOps). **Auto-score 4Big** : 96/100. ## Session `20260731_030001` · Buffer S8 · « Vert trompeur » : le README (point d'entrée) affichait des chiffres PÉRIMÉS que sa propre source contredisait — aucun gate ne les enforçait **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement close (22/22 modules 100/100, 551 tests verts, 6 gates statiques). Poursuite de la série anti-dérive : recherche d'un **faux-vert réel** (une affirmation présentée comme vérifiée mais qu'aucun gate ne vérifie), même classe que les bugs `regression_run.json` / `out/` RBAC / `cd ""` des sessions précédentes. ### Défaut trouvé — les chiffres du README dérivaient en SILENCE vs leurs artefacts cités - **Mécanisme** : `README.md` est le **point d'entrée** du mandat ; sa section « État courant (sourcé) » déclare mot pour mot *« Chaque chiffre ci-dessous est sourcé vers un artefact commité (anti-invention #6) ; ce README n'introduit aucune donnée nouvelle. »* Cette promesse n'était gardée par **aucun** gate : `check_docs.sh` ne valide que les **liens** (la cible existe), **jamais la VALEUR** des nombres. Quand un module + son job CI ont été ajoutés au fil des sprints, les chiffres du README se sont **périmés en silence** tout en restant « sourcés » vers un artefact qui, lui, dit autre chose — un README qui se **contredit avec sa propre source** est le pire « vert trompeur » (CLAUDE.md #6 : « zéro invention de chiffres »), appliqué à la doc d'entrée. - **Preuve au sol (3 dérives réelles constatées)** : - README : « **21/21** modules gated à 100/100 » — `qa/audit_4big/out/quality_report.json` `totals` dit **22/22** (min_score 100, verdict PASS) ; - README : « **21** suites gated » — `qa/regression/out/regression_plan.json` `totals.suites` dit **22** (551 tests) ; - README : « 15 promesses… **14 `in_repo`** prouvées + **1 `out_of_scope`** » — `qa/acceptance/out/acceptance_matrix.json` a **15 `in_repo`** (M5 est désormais `in_repo` avec son hors-périmètre tracé per-promesse ; 0 promesse hors `in_repo`). Les liens étaient tous verts (`check_docs` OK) : la dérive était **invisible**. ### Fix (2 solutions · CLAUDE.md #4 — retenu : correction README + gate qui l'enforce) - **Correction des 3 chiffres** du README (21→22 modules, 21→22 suites, « 14+1 » → « 15 in_repo ») pour refléter les artefacts. - **Correctif de fond** : nouveau gate **`ci/check_readme_claims.sh`** (7ᵉ gate statique) qui **RECOMPUTE** chaque chiffre depuis l'artefact cité (jamais une liste à la main · #6) et exige l'égalité avec ce qui est **écrit** dans le README : modules/verdict (audit_4big), suites (régression), promesses/statuts/verdict (acceptance), nombre d'`AGENT.md` (×2 : nav + titre). Un claim **absent** du README échoue AUSSI (la dérive de formulation qui ferait disparaître un chiffre est elle-même une régression de traçabilité). *(Alt. écartée : régénérer le README depuis un template — surdimensionné et fragile pour une section en prose ; un gate qui compare valeur↔source est la preuve la plus forte et la moins couplée à la mise en forme.)* - **Câblage CI** : job `check-readme-claims` ajouté à `ci.yml` + inséré dans `gate.needs` (30 jobs bloquants désormais). `check_ci_integrity` (INV-A/INV-B) valide le câblage : `gate.needs` == 32 jobs − 1 (gate) − 1 manuel ; `ci/check_readme_claims.sh` lancé par `check-readme-claims` ∈ gate, et **source `ci/lib.sh`** (pas de code mort, échoue en `exit 3` hors arbre git comme les 6 autres). ### Preuves de morsure - **Bite 1** (valeur périmée) : réintroduit « 21/21 modules » ⇒ gate **exit 1** `✗ README dit 21/21/100 MAIS artefact dit 22/22/100 → régénérer README`. - **Bite 2** (claim disparu) : retire `**22 suites gated**` ⇒ gate **exit 1** `✗ claim INTROUVABLE dans README (attendu suites=22)`. - **Restauré** ⇒ **exit 0** vert. Gate inerte tant que README == artefacts. **Anti-invention (#6)** : rien inventé — le gate **recompute** la vérité depuis les artefacts au lieu de figer un compteur ; les 3 chiffres corrigés sont tous **recoupés** sur `quality_report`/`regression_plan`/`acceptance_matrix`. **Nettoyage cohérence (#5 · éliminer le vieux)** : 3 commentaires CI mentionnaient encore « 21 suites · 534 tests » (désormais 22 · 551) — corrigés dans `.gitea/workflows/ci.yml` et `ci/check_regression.sh`. `ci/README.md` : table §1 + détail §2 + commande §3 du nouveau gate ajoutés. **Vérifs** : **7 gates** locaux verts (`guard_constraints`/`validate_json`/`check_docs`/ `check_artifacts`/`check_regression`/`check_ci_integrity`/**`check_readme_claims`**) ; `ci.yml` **YAML valide** ; `check_ci_integrity` : `gate.needs` = **30 jobs** (32 − gate − e2e-baseline manuel), 7 `ci/*.sh` câblés + `lib.sh` sourcée par les 7. Artefacts `out/` inchangés (aucun module `05_deliverables_mvp/` touché ⇒ pas de régénération). **Hors périmètre worker (VPS · #8)** : néant (stdlib pur en-repo ; enregistrement du runner Gitea toujours du ressort DevOps). **Auto-score 4Big** : 96/100. ## Session `20260731_015954` · Buffer S8 · « Vert trompeur » : les 6 gates `ci/*.sh` passaient VERT hors d'un arbre git en ne contrôlant RIEN **Tâche** : **Sprint 8 · buffer L75 (« Regression tests exhaustifs » · DevOps CI/CD)**. Roadmap fonctionnellement close (21/21 modules 100/100, 534 tests verts, 6 gates statiques). Poursuite de la série anti-dérive : recherche d'un **faux-vert réel** (gate qui passe sans rien vérifier), même classe que les bugs `regression_run.json` / `out/` RBAC des sessions précédentes. ### Défaut trouvé — `cd "$(git rev-parse --show-toplevel)"` échoue en SILENCE hors d'un checkout git - **Mécanisme** : les **6** gates commençaient par `cd "$(git rev-parse --show-toplevel)"`. HORS d'un arbre de travail git (tarball, `git archive | tar -x`, `git` absent du PATH, working-dir erroné), `git rev-parse` n'écrit **rien** sur stdout → la commande devient `cd ""`, un **no-op qui RETOURNE SUCCÈS** (le script reste dans le cwd au lieu d'échouer). Les gates poursuivaient sur un `git ls-files` renvoyant une liste **VIDE** → `validate_json` (« Aucun fichier .json suivi — rien à valider »), `guard_constraints` et `check_docs` sortaient **exit 0 VERT en ayant contrôlé ZÉRO fichier** (≈50 JSON non validés, contraintes CLAUDE.md non vérifiées). Un gate qui passe au vert sans rien enforcer est le **pire** « vert trompeur » — précisément ce que la série buffer traque. - **Preuve au sol (avant fix)** : copie de l'arbre **sans `.git`** → `validate_json` **exit 0** « rien à valider » ; `guard_constraints` **exit 0** « Contraintes respectées » ; `check_docs` **exit 0**. Ce piège avait déjà faussé une vérif « 6 gates verts sur git archive » d'une session antérieure (un `git archive | tar -x` n'a **pas** de `.git`, alors que le runner Gitea a un vrai `.git` via `actions/checkout@v4` — donc la méthode « archive » n'est PAS équivalente au runner pour tout check qui interroge git). ### Fix (2 solutions · CLAUDE.md #4 — retenu : helper partagé DRY + durcissement INV-B) - **Correctif de fond** : nouveau helper **`ci/lib.sh`** (sourcé, jamais exécuté seul) exposant **`cd_repo_root`** — se positionne à la racine, ou **échoue bruyamment** (`exit 3`, code distinct d'un échec de contrôle `1`) avec un message clair si `git rev-parse` ne renvoie rien. Les 6 gates remplacent leur `cd "$(git rev-parse …)"` par `source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" || exit 3 ; cd_repo_root`. *(Alt. écartée : inliner le garde de 4 lignes × 6 fichiers = doublon, contraire à CLAUDE.md #5 · préférence lib partagée.)* - **Durcissement du gate de câblage (INV-B · `check_ci_integrity.sh`)** : INV-B exigeait que **chaque** `ci/*.sh` soit lancé par un job — `ci/lib.sh` (jamais dans un `run:`) aurait été flaggé « gate mort ». INV-B accepte désormais un `ci/*.sh` câblé de **deux** façons : lancé par un job du gate, **OU sourcé** par ≥1 gate script (détecté par grep d'une ligne `source …/` / `. …/`, `#` exclu). C'est un **renforcement** réel : le gate prouve maintenant aussi que les libs partagées sont **effectivement sourcées** (pas du code mort). ### Preuves de morsure - **Faux-vert corrigé** : arbre copié **sans `.git`** → les 6 gates sortent désormais **exit 3** avec « ❌ ERREUR gate : hors d'un arbre de travail git » (avant : `validate_json`/`guard_constraints`/`check_docs` = **exit 0 vert**). Idem sur `git archive HEAD | tar -x` **après commit**. - **INV-B reconnaît la lib** : `ci/lib.sh — lib sourcée par [les 6 gates] · non exécutée en propre (OK)` → **exit 0**. - **INV-B attrape une lib morte** : `ci/_dead.sh` (sourcé par personne) ⇒ `✗ … lancé/sourcé par AUCUN job CI (gate mort)` ⇒ **exit 1**. **Non-régression** : **6 gates verts** dans l'arbre de travail (= ce que voit le runner via `actions/checkout@v4`, avec un vrai `.git`) ; matrice **534/21 · PASS inchangée** ; `qa/audit_4big` **out/ inchangé** (les édits `ci/` sont **hors** `05_deliverables_mvp` → aucune dérive de consommateur) ; `ci.yml` YAML **valide**. **Anti-invention (#6)** : rien inventé — garde purement défensif (bash/git stdlib), INV-B relit `ci.yml` + grepe les gates sur disque à chaque run. **Hors périmètre worker (VPS · #8)** : néant (stdlib en-repo ; enregistrement du runner Gitea toujours du ressort DevOps). **Auto-score 4Big** : 96/100. --- ## Session `20260731_012944` · Buffer S8 · Reproductibilité en CHECKOUT PROPRE — le hand-off out/ des 4 générateurs RBAC était `.gitignore`é → gate rouge en CI, vert en local seulement **Tâche** : **Sprint 8 · buffer L75 (« Regression tests exhaustifs » · DevOps CI/CD)**. Roadmap fonctionnellement close (21/21 modules 100/100, 534 tests verts, 6 gates statiques). Poursuite de la série anti-dérive : recherche d'un **écart green-local / red-en-CI réel** (même classe que le bug `regression_run.json` corrigé plus tôt aujourd'hui) plutôt qu'un correctif cosmétique. ### Défaut trouvé — 2 gates ROUGES en checkout propre (`check-artifacts` + `check-regression`) - **Méthode** : test au sol par `git archive HEAD | tar -x` (= exactement ce que voit le runner Gitea), et non `bash ci/*.sh` dans l'arbre de travail. - **Constat** : les 4 générateurs RBAC (`rbac/fixtures_gen`, `userperm_gen`, `roleprofile_gen`, `apply_plan`) **`.gitignore`aient leur `out/`** (« re-générable à la demande ») — alors qu'ils sont audités en archétype **`generator`** (critère **HANDOFF** = `out/MANIFEST.json` + ≥1 artefact JSON). En checkout **propre**, leur `out/` est **absent** → l'audit 4Big (`qa/audit_4big`, dont le `build` **relit le `out/` de CHAQUE module**) les note **80 < 95** ⇒ **INV7** ⇒ build **refusé**. Cascade : `check-artifacts` **rouge** (rebuild audit_4big + demo échouent) et `check-regression` **rouge**. Ça ne « passait » qu'en **local**, grâce aux `out/` **non suivis** laissés sur disque par des `build` manuels — **exactement** la classe du bug `regression_run.json` (green-local/red-CI). - **Preuve au sol** : `git archive` de `HEAD` → `check_artifacts` **exit 1** (`INV7 rbac-* note 80 < 95` · `demo/scenarios build a échoué`) et `check_regression` **exit 1**. Les 15 autres générateurs commettent bien leur `out/` → seuls les 4 RBAC étaient l'exception. ### Fix (2 solutions · CLAUDE.md #4 — retenu : aligner sur les 15 autres modules) - **Correctif de fond** : **committer** le `out/` des 4 générateurs RBAC (retrait de `out/` des 4 `.gitignore` + `git add`). Faisabilité **prouvée** : chaque `build` est **byte-déterministe** (2 builds successifs identiques ; tri stable, zéro horodatage). `apply_plan` lit ses frères **en process** (imports), pas via leur `out/` — committer le `out/` sert **uniquement** le contrat HANDOFF audité. L'audit note désormais les 4 modules **100/100** en checkout propre. *(Alt. écartée : reclasser les 4 en archétype sans HANDOFF — diverge du reste des générateurs et laisse `check_artifacts` les ignorer, moins cohérent · #5.)* - **Durcissement du gate (empêche la récidive)** : `ci/check_artifacts.sh` comparait le build frais au fichier de l'**arbre de travail** — donc un `out/` **non suivi** (ignoré / jamais `git add`) le rendait **vert en local** alors qu'il serait **absent en CI**. Ajout d'une assertion **`git ls-files --error-unmatch`** : tout fichier produit par `build` DOIT être **suivi par git** → l'écart devient une **erreur locale honnête**, plus une surprise en CI. - **Régénération des consommateurs (mémoire projet)** : l'édition des 4 README RBAC (en-tête `out/` « non commité » → « commité · byte-déterministe », +18 o chacun) a fait dériver `qa/audit_4big/out/quality_report.json` (evidence DOC = taille README) — **régénéré** (PASS · 21/21 · min 100). `demo` + `regression run` rejoués : **inchangés** (ils lisaient déjà l'état correct). **Preuve de morsure du durcissement** : `git rm --cached role.json` (fichier laissé sur disque, simulant un `out/` ignoré) ⇒ `check_artifacts` **exit 1** (`✗ … NON SUIVI par git → absent en CI propre`) ; re-`git add` ⇒ **exit 0**. Avant le durcissement, ce même état passait **vert**. **Vérifs** : **6 gates verts sur `git archive` propre** (guard/json/docs/artifacts/ regression/ci_integrity) ; suites harnais **OK** (fixtures/userperm/roleprofile/ apply_plan/audit_4big/regression/demo · 32/32) ; matrice **534/21** **inchangée** ; build de TOUS les consommateurs → **zéro dérive résiduelle**. **Anti-invention (#6)** : rien inventé — `out/` RBAC = sortie déterministe des générateurs ; `quality_report` recalculé depuis les faits du dépôt. **Hors périmètre worker (VPS · #8)** : néant (stdlib pur en-repo ; les fixtures Frappe côté VPS restent `.gitignore`ées, hors de ce correctif). **Auto-score 4Big** : 96/100. --- ## Session `20260731_005934` · Buffer S8 · Intégrité du câblage CI — le gate d'agrégat verrouille TOUS les jobs (gates statiques compris) **Tâche** : **Sprint 8 · buffer L75 (« Regression tests exhaustifs » · DevOps CI/CD)**. Roadmap fonctionnellement close (21/21 modules à 100/100, 534 tests verts, **5** gates statiques tous verts au démarrage). Poursuite de la série anti-dérive (INV4, fraîcheur matrice, reproductibilité artefacts) : recherche d'un **trou de câblage réel** plutôt qu'un correctif cosmétique. ### Défaut trouvé — les gates STATIQUES n'étaient gardés par aucun invariant d'appartenance au gate - **Constat** : le job `gate` (`needs: [...]`) est le **seul verrou de merge** — un check absent de son `needs:` ne bloque **rien**, même rouge. Les deux couvertures existantes (`audit_4big/registry.coverage_report`, `qa/regression/discovery. coverage_report`) prouvent l'appartenance au gate **uniquement** pour les jobs de **module** (ceux portant un `working-directory:` sous `05_deliverables_mvp`). Les **5 gates statiques** (`constraints-guard`/`validate-json`/`check-docs`/ `check-artifacts`/`check-regression` → scripts `ci/*.sh`) **n'ont pas** de working-directory : **rien** ne garantissait qu'ils alimentent le gate. Retirer l'un d'eux de `needs:` (ou une **typo** dans la liste, ou un `ci/*.sh` **décâblé**) rendait le gate **vert en se court-circuitant** — même classe de dérive silencieuse que INV4 (orphan_tests_dirs), côté **câblage CI**. - **Fix** : nouveau gate `ci/check_ci_integrity.sh` (bash/awk/git, stdlib pur, zéro réseau) + job CI `check-ci-integrity`, lui-même ajouté à `gate.needs`. Deux invariants, lus par FAITS dans `ci.yml` (jamais une liste à la main) : - **INV-A** : `gate.needs` == { tous les jobs définis } − { `gate` } − { jobs **manuels** `if: … workflow_dispatch …`, ex. `e2e-baseline` légitimement hors du gate push/PR }. Détecte **MISSING** (job oublié → ne bloque pas), **DANGLING** (référence fantôme : typo / job supprimé) et **MANUAL-IN-GATE** (job manuel dans `needs` → gate en attente perpétuelle sur push). - **INV-B** : chaque `ci/*.sh` du dépôt est lancé par exactement un job (`run: bash ci/