# Activity Log · 2026-07-31 · Claude Code DTP ## 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/