Files
oto-enterprise-os-dtp/05_activity_log/2026-07-31.md
T

1053 lines
66 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Activity Log · 2026-07-31 · Claude Code DTP
## 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 `| <portail> | \`<ws>\` | C | L | R |`) **et** la cellule
de prose de la fiche (`\`<ws>\` 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 '<N> suites|<N> 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 …/<base>` / `. …/<base>`, `#` 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/<script>`), lui-même dans `gate.needs` — un gate statique **non
câblé** (script mort) ou **décâblé** casse le check.
- **Preuve de morsure (5 modes, tous exit 1)** :
- MISSING : `check-artifacts` retiré de `needs` ⇒ `✗ ABSENT de gate.needs` + ``
INV-B sur `check_artifacts.sh` ;
- DANGLING : `seo-tests`→`seo-testz` dans `needs` ⇒ `✗ référence 'seo-testz' non
définie` + `✗ seo-tests ABSENT` ;
- MANUAL-IN-GATE : `e2e-baseline` ajouté à `needs` ⇒ `✗ job manuel … gate en
attente` ;
- SCRIPT-MORT : `ci/check_phantom.sh` commité sans job ⇒ `✗ lancé par AUCUN job` ;
- DÉCÂBLÉ : couvert par le mode MISSING (job hors `needs`).
État nominal restauré ⇒ **exit 0** (`gate.needs = 28 jobs = 30 définis 1 (gate)
1 manuel [e2e-baseline]`).
**Non-régression** : le nouveau job est **statique** (aucun `working-directory`) →
comme les 5 autres, il **n'entre pas** dans la matrice de régression ni le registre
`audit_4big` (dérivés des seuls jobs de module). Totaux **534/21 inchangés**.
Suites harnais **OK** : `qa/regression` (21 suites · 534), `qa/audit_4big` (21/21 ·
min 100), `qa/acceptance` (bijectif=True). `ci.yml` YAML **valide**.
**Anti-invention (#6)** : rien inventé — le gate **relit** `ci.yml` et recalcule les
ensembles à chaque run (aucun compteur figé).
**Vérifs** : **6 gates** statiques verts (`guard_constraints`/`validate_json`/
`check_docs`/`check_artifacts`/`check_regression`/`check_ci_integrity`) ; doc
`ci/README.md` mise à jour (tableau des jobs, section 2 détaillée, exécution locale,
checklist « 6 gates »).
**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_002924` · Buffer S8 · Intégrité de la matrice de régression (bug CI réel + invariant disque→CI)
**Tâche** : **Sprint 8 · buffer L75 (« Regression tests exhaustifs » · DevOps
CI/CD)** — roadmap fonctionnellement close (21/21 modules gated à 100/100, 534
tests verts, 5 gates statiques). Recherche d'une **incohérence de reproductibilité
réelle** plutôt qu'un correctif doc cosmétique. **Deux** défauts réels trouvés et
corrigés, tous deux dans `qa/regression`.
### Défaut #1 (PRIORITAIRE · bug CI réel) — le gate `check-regression` était cassé en checkout propre
- **Symptôme** : `ci/check_regression.sh` (l.52-53) **exige** que
`qa/regression/out/regression_run.json` **existe dans le dépôt**, puis compare un
`run` frais à ce fichier commité (baseline byte-identique).
- **Contradiction** : ce même fichier était **`.gitignore`é** (« non déterministe
(temps machine) → jamais commitée ») → **jamais présent dans un checkout propre**.
Le job Gitea `check-regression` ne fait que `checkout` + `bash check_regression.sh`,
**aucune étape ne génère** le fichier. **Conséquence** : le gate **échouait sur
CHAQUE run CI propre** (`artefact absent du dépôt`). Il ne « passait » qu'en local,
par la présence d'un `regression_run.json` **non suivi** laissé par un `run` manuel.
Le commentaire de `ci.yml` disait déjà « le regression_run.json **commité** » —
intention claire du commit précédent, jamais concrétisée (fichier resté ignoré).
- **Vérif de faisabilité (#1 analyser)** : l'artefact `run` est **byte-déterministe** —
clés `{ran,passed,failures,...}` + `path` **relatif**, **aucun** horodatage/hôte/
durée. Deux `run` successifs **byte-identiques** (vérifié). Le committer est donc
**licite** et complète l'intention du gate.
- **Fix** : `run` est désormais **commité** (baseline du gate) →
- `.gitignore` : retrait de `out/regression_run.json` + note expliquant qu'il est
commité *parce que* déterministe ;
- `README.md` : section `run` réécrite (byte-déterministe · commité · baseline
`check_regression` · distinction d'avec `check_artifacts` qui ne rejoue que
`build`) ;
- `out/regression_run.json` : **ajouté au suivi** (21 suites · 534 tests · PASS).
- **Preuve de morsure** : fichier retiré (simule checkout propre) ⇒ gate **exit 1**
(`artefact absent`) — l'état exact en CI ; restauré ⇒ **exit 0** (`run == commité`).
### Défaut #2 (invariant manquant) — dérive silencieuse disque→CI non gardée
- **Gap** : `discovery.coverage_report` prouvait le sens **CI→disque** (tout module
gated a un `tests/`) mais **pas l'inverse** : un module gagnant un `tests/` sur
disque **sans job CI** n'entrait jamais dans la matrice, ses tests **ne tournaient
jamais**, et la couverture ne le voyait pas (elle n'itère que les suites dérivées du
CI) — la matrice restait « PASS » en **sous-comptant** en silence. Même classe de
dérive que « demo 18→21 », direction opposée.
- **Fix** : nouvel invariant **INV4** —
- `discovery.disk_test_modules()` : marche disque pure listant tout module portant
un `tests/` (POSIX, trié, déterministe) ;
- `coverage_report` : champ `orphan_tests_dirs = disk ci_paths` ; `ok` exige
désormais `not orphan_tests_dirs` ;
- `regression.schema.json` : `orphan_tests_dirs` ajouté (required · strict) ;
- `regression_gen.py` : message INV4 surface les orphelins ;
- `tests/test_regression.py` : +2 tests (sous-ensemble disque⊆CI sur le dépôt réel ;
preuve de morsure monkeypatch d'un orphelin ⇒ `ok=False`).
- **Preuve de morsure end-to-end** : `mkdir zz_orphan/tests` + `build` ⇒ **build
refusé** `INV4 … orphan_tests_dirs=['zz_orphan']` ; répertoire retiré ⇒ build OK.
**Anti-invention (#6)** : rien inventé — les deux gates **régénèrent** la vérité (run
frais / couverture recomputée) au lieu de figer un compteur.
**Régénération des consommateurs (mémoire projet)** : l'édition des tests + du README
de `qa/regression` a fait dériver `qa/audit_4big/out/quality_report.json` (evidence
« README.md 3710→4236 octets » et « 24→26 méthodes test_* ») — **régénéré**, verdict
**PASS · 21/21 · min 100**. Totaux matrice **534/21** **inchangés** : le harnais
`qa/regression` est **self-exclu** (INV3) → +2 méthodes test_* n'affectent pas la
matrice.
**Vérifs** : **5 gates** locaux verts (`guard_constraints`/`validate_json`/`check_docs`/
`check_artifacts`/`check_regression`) ; `ci.yml` **YAML valide** ; tests harnais
**26/26 OK** (régression) + **34/34 OK** (audit_4big) ; matrice **534/534 · 21/21 ·
PASS** ; artefacts `out/` reproductibles à l'identique.
**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_043004` · Buffer S8 · Chiffre périmé dans une fiche agent (vert-trompeur ungated) + extension du gate
**Tâche** : **Sprint 8 · buffer L75 (« Regression tests exhaustifs »)** — roadmap
fonctionnellement close (22/22 modules gated · 558 tests · 7 gates statiques verts).
Recherche d'un **défaut réel** (chiffre inventé/périmé · CLAUDE.md #6) plutôt qu'un
correctif cosmétique.
### Défaut trouvé — `03_agents/erpnext_backend/AGENT.md` : « repo : **560 tests** au total »
- **Symptôme** : la ligne « source unique » situait les tests backend dans le repo
par un compte agrégé **saisi à la main** (« repo : 560 tests au total »).
- **Contradiction** : `560` ne correspond **à AUCUN** compte courant —
- matrice de régression faisant autorité (`qa/regression/out/regression_run.json`) :
**558 tests · 22 suites · PASS** ;
- repo-wide (méthodes `test_*` sur disque, 23 fichiers) : **584**.
`560` = ancien `534` (matrice d'alors) `+ 26` (harnais `qa/regression`, self-exclu
INV3) — **périmé en silence** au fil de 534 → 551 → 558. Même classe de « vert
trompeur » que la fiche QA (21/534) corrigée la session précédente, mais dans une
fiche agent qu'**aucun gate ne couvrait**.
- **Vérif ciblée (#1 analyser)** : recomputé **toutes** les autres bornes chiffrées
des 13 `AGENT.md` — `crm 81=(25+31+25)`, `RBAC 60=(10+16+11+11+12)`, `e-CF 39`,
`portails 19`, `chat 31`, `confotur 44`, `seo 36` : **toutes exactes**. Le `560`
était le **seul** nombre dérivé.
### Fix (2 volets · corriger + garder)
1. **`03_agents/erpnext_backend/AGENT.md`** : `560` → **« matrice de régression du
repo : 558 tests · 22 suites · verdict PASS, source
`qa/regression/out/regression_run.json` — jamais compté à la main · #6 »** (le
nombre pointe désormais vers l'artefact gaté, pas une saisie).
2. **`ci/check_readme_claims.sh`** : nouveau bloc qui **recalcule** tests+suites+
verdict depuis `regression_run` (même source que la fiche QA) et exige l'égalité
avec ce que la fiche backend **écrit** ; un claim absent échoue aussi. Header du
gate mis à jour (liste des docs couverts).
### Preuves
- **Passe** : `Fiche Backend · … 558/22/PASS == regression_run` (✓).
- **Morsure** : `560` réinjecté ⇒ gate **rouge** (`dit 560/22/PASS MAIS regression_run
dit 558/22/PASS → régénérer la fiche`) ; restauré ⇒ vert.
- **Régression nulle** : édition d'un `AGENT.md` (doc) → **aucune dérive** d'artefact
`out/` (`check_artifacts` vert : `quality_report.json` inchangé).
**Vérifs** : **7 gates** locaux verts (`guard_constraints` · `validate_json` ·
`check_docs` · `check_artifacts` · `check_regression` · `check_ci_integrity` ·
`check_readme_claims`) ; matrice **558/22 · PASS**.
**Anti-invention (#6)** : rien inventé — le gate **régénère** la vérité (matrice
re-agrégée) au lieu de figer un compteur ; le chiffre de la fiche **découle** de
l'artefact.
**Hors périmètre worker (VPS · #8)** : néant (docs + gate bash/stdlib en-repo).
**Auto-score 4Big** : 96/100.
---
## Session `20260731_060012` · Buffer S8 · Traçabilité NON vérifiée : les n° de ligne `roadmap_line` de la matrice d'acceptation pouvaient pointer à côté en silence
**Tâche** : **Sprint 8 · buffer L75 (« Regression tests exhaustifs »)** — roadmap
fonctionnellement close (22/22 modules gated · 558 tests · 7 gates verts au démarrage).
Recherche d'un **défaut réel** (traçabilité `CLAUDE.md` #6) plutôt qu'un cosmétique.
### Défaut trouvé — `qa/acceptance` : `roadmap_line` déclaré, jamais ancré
- **Symptôme** : chaque promesse de `acceptance_spec.json` (8 livrables S1..S8 + 7
métriques M1..M7) porte un `roadmap_line` (33, 39, … 87) censé pointer son bullet
dans `04_roadmap/ROADMAP_8_WEEKS_OR_LESS.md`. Le docstring promet des « CITATIONS
verbatim de la roadmap » (#6), mais le **seul** invariant (INV10) ne vérifiait que
« `roadmap_line` est un entier positif ».
- **Contradiction** : si la roadmap est éditée (une ligne insérée/retirée, un sprint
ré-ordonné), **tous** les `roadmap_line` se décalent en silence — ils pointeraient un
bullet qui n'existe plus, tout en restant « vert ». Même classe de « vert trompeur »
que les compteurs périmés (534→551→558) ou l'`orphan_tests_dirs`, appliquée cette
fois à une **référence de ligne** (traçabilité déclarée mais non prouvée).
- **Vérif (#1 analyser)** : recomputé la structure réelle → 8 bullets `**DELIVERABLE**`
(S1@33 … S8@76) + 7 bullets métrique `- ✅` (81..87). Les `roadmap_line` du spec sont
**exacts aujourd'hui** — la faille est l'absence de garde, pas une valeur fausse.
### Fix (2 volets · vérifier + garder)
1. **`acclib/deps.py`** — nouveau `parse_roadmap_anchors(roadmap_ref)` qui **DÉRIVE**
la structure de la roadmap (jamais une liste de n° saisie · #6) : `{SX → ligne du
DELIVERABLE}` (SX **lu** dans le titre `## SPRINT n · SX (…`) + lignes ordonnées des
bullets métrique. `OSError` honnête si la roadmap est absente (référence fantôme = red).
2. **`acceptance_gen.py` · INV11 (ancrage roadmap)** — exige que (a) le nombre de bullets
DELIVERABLE == nb de livrables du spec et nb de bullets métrique == nb de métriques
(le **« 8 + 7 » devient dérivé du fichier**, plus un compte figé), (b) chaque livrable
SX pointe le DELIVERABLE de SX, (c) chaque métrique (dans l'ordre) pointe le k-ième
bullet `- ✅`. Docstring #6 mis à jour (« n° de ligne VÉRIFIÉ, pas déclaré »).
3. **Tests** — fixture roadmap **synthétique** (temp file, mêmes motifs en miniature :
S2@L3, S3@L5, M1@L7) pour contrôler l'écart injecté ; classe `TestRoadmapAnchor`
(+6 méthodes) : parse réel (8+7), DELIVERABLE désancré, métrique décalée, mauvais
propriétaire de sprint, compte en trop, roadmap absente. **37 méthodes** (31→37).
### Preuves
- **Passe** : build/validate réel **vert** ; `parse_roadmap_anchors` réel = {S1:33 …
S8:76} + métriques [81..87] ; les 15 `roadmap_line` du spec **matchent**.
- **Morsure (spec RÉEL)** : `S1.roadmap_line=999` ⇒ `roadmap: S1: … ne pointe pas le
DELIVERABLE de S1 (ligne 33)` ; `M3=200` ⇒ `… ≠ 3e bullet métrique (ligne 83)`.
**Régénération des consommateurs (mémoire projet)** : +6 méthodes test dans
`qa/acceptance` → matrice **558→564** (22 suites inchangées). Régénérés :
`qa/regression/out/{regression_run,regression_plan,MANIFEST}.json` (564/564 · PASS),
`qa/audit_4big/out/quality_report.json` (evidence acceptance « 31→37 méthodes », score
**100/100 inchangé**), fiches **QA** et **Backend** (`558→564`, gate `check_readme_claims`
vert), README acceptance (« 10→11 invariants »). L'artefact `acceptance_matrix.json` est
**byte-identique** (INV11 ne change que la validation, pas la sortie).
**Vérifs** : **7 gates** locaux verts (`guard_constraints` · `validate_json` ·
`check_docs` · `check_artifacts` · `check_regression` · `check_ci_integrity` ·
`check_readme_claims`) ; matrice **564/22 · PASS** ; 22/22 modules **100/100**.
**Anti-invention (#6)** : rien inventé — INV11 **dérive** la vérité de la roadmap et
compare ; le « 8 + 7 » cesse d'être un compte à la main.
**Hors périmètre worker (VPS · #8)** : néant (stdlib pur en-repo).
**Auto-score 4Big** : 96/100.
---
## Session `20260731_063014` · Buffer S8 · Comptes de tests PAR SUITE périmés dans la fiche QA — dérive silencieuse (aucun gate ne couvrait le grain fin)
**Tâche** : **Sprint 8 · buffer L75 (« Regression tests exhaustifs »)** — roadmap
fonctionnellement close (22/22 modules gated · 564 tests · 7 gates verts au démarrage).
Chasse d'un **défaut réel** de la classe « vert trompeur » (#6) plutôt qu'un cosmétique.
### Défaut trouvé — fiche QA : colonne « Tests » par suite figée, DÉJÀ fausse
- **Symptôme** : le tableau des livrables de `03_agents/qa/AGENT.md` (col « Tests »)
affichait un compte par suite saisi à la main. Trois lignes étaient **déjà
périmées** au démarrage : `audit_4big` **35** (réel 34), `regression` **25**
(réel 26), `acceptance` **31** (réel 37 — la session précédente avait fait 31→37
sans propager à cette table).
- **Contradiction** : le gate amont `check_readme_claims` ne couvrait que la **somme
agrégée** (« 564 tests »). Une **compensation** entre deux suites (+1 / 1)
laisserait la somme juste et **les deux lignes fausses** — même classe de « vert
trompeur » que les compteurs agrégés périmés (534→551→558→564) ou INV11, mais au
**grain le plus fin** (par suite), et non gardée.
- **Vérif systématique (#1 analyser)** : recomputé la carte faisant autorité des 22
suites gated + le self_module → diffusée contre **toutes** les tables de fiches
(`03_agents/*/AGENT.md`). Seule la fiche **QA** dérivait (3 lignes) ; CRM (3),
faisabilité (2), publiciste (1) étaient justes. READMEs de module : justes aussi.
### Fix (2 volets · corriger + garder)
1. **`03_agents/qa/AGENT.md`** — 3 nombres corrigés vers le réel : 35→34, 25→26,
31→37.
2. **`ci/check_readme_claims.sh`** — nouveau bloc générique : scanne **toutes** les
fiches, extrait chaque ligne de table pointant `05_deliverables_mvp/<path>/README.md`
+ sa cellule « Tests », et **recompute** l'attendu depuis la source faisant
autorité — `regression_plan.json` (`suites[path].test_methods`) pour les 22 suites
gated, et **`reglib.discovery.count_tests`** (même fonction que le plan · zéro
duplication · #5) pour le `self_module` `qa/regression` exclu de la matrice par SoD.
Table « Tests » absente ⇒ red (disparition = régression de traçabilité).
### Preuves
- **Passe** : gate vert, **10 lignes par-suite** vérifiées (crm×3 · faisabilite×2 ·
publiciste×1 · qa×4), toutes == source.
- **Morsure** : `acceptance` reforcé à 31 ⇒ `✗ … Tests 31 MAIS source dit 37
(plan.suites/count_tests) → régénérer la fiche` + verdict ❌ ; restauration ⇒ PASS.
**Régénération des consommateurs** : néant — aucun compte de test n'a changé (fix
= docs + gate). Artefacts `out/` **byte-identiques** (`check_artifacts` vert) ;
matrice **564/22 PASS** inchangée.
**Vérifs** : **7 gates** locaux verts (`guard_constraints` · `validate_json` ·
`check_docs` · `check_artifacts` · `check_regression` · `check_ci_integrity` ·
`check_readme_claims`) ; matrice **564/22 · PASS** ; 22/22 modules **100/100**.
**Anti-invention (#6)** : rien inventé — le gate **recompute** chaque cellule depuis
plan.suites / count_tests ; les nombres de la fiche **découlent** de la source.
**Hors périmètre worker (VPS · #8)** : néant (docs + gate bash/stdlib en-repo).
**Auto-score 4Big** : 96/100.
---
## Session `20260731_083034` · Buffer S8 · Comptes RBAC des 3 volets recopiés à la main — surface non gatée
**Tâche** : **Sprint 8 · buffer L75 (« Regression tests exhaustifs »)** — roadmap
fonctionnellement close (22/22 modules gated · 564 tests · 7 gates verts au
démarrage). Chasse d'une surface **« vert trompeur » (#6)** encore non gatée.
### Défaut trouvé — 3 READMEs RBAC transcrivent à la main des comptes de MANIFEST byte-gatés
- **Symptôme** : les trois générateurs RBAC émettent chacun un `out/MANIFEST.json`
recalculé du contrat `rbac_50_roles.json` à chaque build (byte-gaté par
`check_artifacts` ⇒ autorité). Trois READMEs de module recopiaient **à la main**
des comptes qui en dérivent, sans **AUCUN** gate :
1. `rbac/roleprofile_gen/README.md` — table « Nb rôles » **par profil** (6 lignes
: Ventes 12 · Construction 10 · Direction 9 · Compta 8 · Achat 5 · Plateforme
6) + total bijectif **50**.
2. `rbac/apply_plan/README.md` — ligne agrégée « Génération réelle : **6 étapes ·
50 rôles / 116 DocPerm / 28 UP templates / 6 Role Profile · 4 DocType custom +
5 Company + 4 rôles equipe** » : **8 comptes** de TOUS les volets en une phrase.
3. `rbac/userperm_gen/README.md` — ventilation « **28 entite · 16 groupe · 2 own ·
4 equipe** » (par mécanisme d'enforcement).
- **Contradiction latente** : ajouter un rôle/DocType au contrat ferait dériver
**les trois** docs en silence pendant que les MANIFEST (byte-gatés) disent autre
chose — même classe que les cellules « Tests », la prose agrégée CRM/RBAC et les
triplets par workspace, mais sur le **domaine RBAC** (contrainte NON-NÉGOCIABLE
#5 « 50 rôles »), au grain le plus fin (par profil) **et** au niveau consolidé.
- **État au démarrage** : les 10 nombres étaient **justes** (aucune dérive déjà
survenue) — le défaut est l'**absence de garde**, pas une valeur périmée.
### Fix (1 volet · garder — aucun chiffre à corriger)
- **`ci/check_readme_claims.sh`** — nouveau bloc RBAC recomputant chaque nombre
depuis le `out/MANIFEST.json` du volet (zéro duplication des compteurs des
générateurs · #6) et exigeant l'égalité avec ce que la doc ÉCRIT :
- roleprofile : chaque `nb_roles` par profil (ancré sur nom+portail) + total
`counts.roles_couverts` ;
- apply_plan : les 8 comptes (`len(apply_plan.json)` · `counts.{roles,
custom_docperm,user_permission_templates,role_profiles}` · `len(confirmations_
vps.{custom_doctypes,companies,roles_scope_equipe})`) en un n-uplet ordonné ;
- userperm : `by_mechanism.{user_permission_company,none_consolidated,
docperm_if_owner,vps_confirm_team}`.
Claim absent ⇒ red (disparition = régression de traçabilité). Header + ci/README
documentés.
### Preuves
- **Passe** : gate vert, **10 nouvelles lignes RBAC** vérifiées (6 profils + total
+ agrégat apply_plan + ventilation userperm), toutes == MANIFEST.
- **Morsures (3, une par surface)** : Ventes 12→13 ⇒ `✗ … dit 13 MAIS MANIFEST
dit 12` ; apply_plan 116→117 DocPerm ⇒ `✗ … 6/50/117/… MAIS … 6/50/116/…` ;
userperm 28→29 entite ⇒ `✗ … 29/16/2/4 MAIS … 28/16/2/4`. Exit 1 dans les trois
cas ; restauration ⇒ PASS (exit 0).
**Régénération des consommateurs** : néant — aucun compte n'a changé (fix = gate
seul). Artefacts `out/` **byte-identiques** (`check_artifacts` vert) ; matrice
**564/22 PASS** inchangée.
**Vérifs** : **7 gates** locaux verts (`guard_constraints` · `validate_json` ·
`check_docs` · `check_artifacts` · `check_regression` · `check_ci_integrity` ·
`check_readme_claims`) ; matrice **564/22 · PASS** ; 22/22 modules **100/100**.
**Anti-invention (#6)** : rien inventé — le gate **recompute** chaque nombre depuis
les MANIFEST byte-gatés ; les comptes des READMEs **découlent** du contrat RBAC.
**Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo).
**Auto-score 4Big** : 96/100.