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

1567 lines
125 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Activity Log · 2026-08-05 · Claude Code DTP Worker
## Session 164319 · FIX régression RED : `quality_report.json` périmé après édition de doc → **VERT**
**Constat au démarrage.** `./run_ci.sh` = **32 PASS · 1 FAIL**`check-artifacts` RED sur
`qa/audit_4big/quality_report.json` (« DÉRIVE : le fichier commité diffère du build frais »).
Le tree HEAD `9d30939` (« Auto exec · session 161319 ») était commité **RED**. Cause racine :
cette session a grossi `05_deliverables_mvp/qa/regression/README.md` (+11 lignes, 4396→4948 octets)
mais **n'a pas rejoué** `audit_4big build`. Or l'audit 4Big **score le contenu des docs** et
consigne la taille de chaque README dans son champ `evidence` → drift mécanique, exactement la
classe [[audit4big-rebuild-after-doc-edits]] (« rebuild quality_report LAST, après toute édition de
prose »).
**Fix.** `python3 audit_4big_gen.py build -o out` → seul champ modifié :
`evidence: "README.md (4396 octets)"``"(4948 octets)"`. **Verdict inchangé PASS · 24/24 modules
≥ 95 (min 100)**, `coverage.ok=true` : la régression était purement l'echo d'une taille de fichier,
pas une perte de qualité. `MANIFEST.json` non touché (cohérent → `check_artifacts` revenu VERT).
**Recompute indépendant (aucun chiffre saisi · #6).** Diff commité↔build frais = **1 ligne** (le
byte-count). `run_ci.sh` post-fix = **33 PASS · 0 FAIL · 0 SKIP** (8 statiques + 25 suites).
**Portée / anti-churn.** **0 fichier de production édité** (seul l'artefact régénéré), **0 gate
ajouté** (#5 — la dérive était **déjà détectée** par `check_artifacts`, c'est la discipline
« rebuild LAST » qui a manqué, pas une gate), **0 chiffre saisi** (#6 — 4948 dérivé de la taille
réelle du README par le générateur), **aucune commande VPS** (#8). Classe connue en mémoire ;
attestation que le gate `check_artifacts` **mord sur le tree commité** (RED opposable, pas seulement
pré-commit) — jumelle de l'incident `144304`.
**Vérif.** `git diff --stat` = 1 fichier / 1 ligne · `./run_ci.sh`**33 PASS · 0 FAIL · 0 SKIP**.
---
## Session 154314 · SWEEP neuf : indépendance LOCALE / TIMEZONE des artefacts `build` → **CLEAN**
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**, arbre
propre, roadmap `ROADMAP_8_WEEKS_OR_LESS.md` intégralement livrée/gatée in-repo (restes
S3/S5/S6 = refactor BIM / builds EAS / dépôts ONAPI / déploiement prod = **hors périmètre worker**,
#8). Un audit fan-out (Explore) docstring-vs-code + prose-vs-numérique sur les livrables → **CLEAN**,
0 drift présent. Le canal `daily_reports` est déjà courant (la ligne hash-seed `151312` a été ajoutée
par `a39c05a` lui-même). Choix d'un axe de **déterminisme absent de la mémoire et orthogonal** aux
deux précédents.
**Constat — la fragilité ciblée.** Le sweep hash-seed (`151312`) a couvert `PYTHONHASHSEED` mais
**PAS** l'environnement **locale/timezone** du runner. `check_artifacts`/`check_regression` prouvent
la reproductibilité byte-à-byte, mais **une seule fois**, sous **le** `LANG`/`LC_ALL`/`TZ` que le
runner Gitea leur donne. Si un générateur formatait un nombre via `locale` (décimale-virgule sous
`de_DE`/`fr_FR`), triait des clés via `locale.strcoll`, ou embarquait un timestamp `localtime`, sa
sortie varierait d'un runner à l'autre : **VERT sur le poste dev, ROUGE sur un runner sous locale/TZ
différente** — faux-vert *forward-looking* orthogonal au hash-seed (ordre de hash) et au `-W error`
(API dépréciée). Même famille « le runner n'est pas ton poste », cause différente.
**Méthode (lecture seule).** Réplique exacte de la boucle `check_artifacts` — pour chaque module
avec `out/`, le `.py` portant `add_parser("build")` est rejoué `build -o $tmp` puis `diff` vs commité
— mais sous **5 combos** `LANG`/`LC_ALL`/`TZ`/`LC_NUMERIC` : `C/UTC`, `en_US/America-New_York`,
`fr_FR/Europe-Paris`, `de_DE/Asia-Kolkata`, `POSIX/Pacific-Kiritimati`. **21 générateurs `build`
× 5 combos = 105 rejeux**. Tree resté propre (`git status --short` vide avant/après).
**Résultat — CLEAN + preuve honnête à deux volets.** **0 dérive / 0 échec sur les 105 rejeux.**
Le volet **TZ est authentiquement exercé** (5 fuseaux, `zoneinfo` présent). Le volet **locale a une
limite empirique** — seules `C`/`C.utf8`/`POSIX` sont installées ici, donc `fr_FR`/`de_DE`/`en_US`
retombent en C locale par glibc : le cas décimale-virgule n'est pas *empiriquement* prouvé. Il l'est
en revanche **par audit du code**, qui est la preuve définitive : (a) **0 générateur** n'appelle
`locale.setlocale`/`import locale` → la sérialisation `json`/`float` reste `'.'` (locale-indépendante
par design Python, la source de risque décimale-virgule **n'existe pas dans le code**) ; (b) les
**2 seuls** lecteurs d'horloge murale du corpus — `publiciste/lib/parser.py:428` et
`faisabilite/generator/faisabilite_gen.py:51` — n'ont **aucun `out/` commité** (leur timestamp
`generated_at` n'est jamais un artefact gaté) **et** forcent tous deux `datetime.now(timezone.utc)`
(doublement TZ-safe) ; (c) `regression_run.json` ne porte **aucun champ horloge**. Conclusion : le
runner CI peut tourner sous **n'importe quelle locale/TZ**, `check_artifacts`/`check_regression`
restent VERTS.
**Portée / anti-churn.** **0 fichier de production édité**, **0 artefact reconstruit** (aucun
`out/*.json` touché → pas de rebuild `quality_report`, cf. [[audit4big-rebuild-after-doc-edits]]),
**0 gate ajouté** (#5 — attester ≠ multiplier les gates ; propriété re-jouable via la commande, pas
une classe de dérive), **0 chiffre saisi à la main** (#6 — 21/105 dérivés de la boucle de découverte
`add_parser("build")`), **0 commande VPS** (#8 — 100 % local & lecture seule). Éditions : ce journal
+ 1 ligne dans la table « Veille d'intégrité » du canal stakeholder + 1 mémoire neuve
(*locale-tz-determinism-sweep*, sœur de [[hash-seed-determinism-sweep]] et
[[forward-compat-warnings-sweep]]).
**Vérif.** `./run_ci.sh` après → **33 PASS · 0 FAIL · 0 SKIP** (aucun fichier gaté touché).
---
## Session 151312 · SWEEP neuf : déterminisme sous hash-seed aléatoire des gates de reproductibilité → **CLEAN**
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**, arbre
propre, roadmap `ROADMAP_8_WEEKS_OR_LESS.md` intégralement livrée/gatée in-repo (restes
S3/S5/S6 = refactor BIM / builds EAS / dépôts ONAPI / déploiement prod = **hors périmètre worker**,
#8). Sept sweeps déjà consignés ce jour (docstring-vs-code, forward-compat warnings, roadmap↔repo,
ré-attestation canal…). Choix d'un axe d'intégrité **absent de la mémoire et orthogonal** à tous
les précédents.
**Constat — la fragilité ciblée.** Les gates `check_artifacts` et `check_regression` prouvent que
chaque `out/*.json` versionné se régénère **byte-identique** depuis son générateur. MAIS ils ne
tournent **qu'une fois**, sous **le** `PYTHONHASHSEED` que le runner Gitea leur donne — et depuis
Python 3.3 ce seed est **randomisé par défaut**. Si **un seul** générateur sérialise un `set`/`dict`
en **ordre d'itération** (au lieu de trier), sa sortie varierait d'un seed à l'autre : le gate
serait alors **VERT aujourd'hui, ROUGE au hasard demain** sur le runner — un faux-vert
*forward-looking* qu'aucun rejeu à seed fixe n'attrape. C'est le pendant « déterminisme » du sweep
`-W error` (forward-compat interpréteur) : même famille, cause différente (ordre de hash vs API
dépréciée).
**Méthode (lecture seule · 0 édition de prod).** Rejeu de **tous** les générateurs `build` des
**21** répertoires `out/` vers un `mktemp -d`, sous **5 seeds** (`1 7 42 1000` + `random`), chaque
fichier produit `diff`-é contre son jumeau commité. Puis `regression_run.json` (artefact d'**exécution**,
produit par `run` — hors périmètre `check_artifacts` mais **dans** celui de `check_regression`) rejoué
séparément sous `1 7 42 999 random random`. Harnais auto-vérifié : **57 fichiers effectivement
comparés**, **1 seul skip** = `qa/regression/regression_run.json` (artefact `run`, exactement
l'exclusion documentée de `check_artifacts`) — donc zéro faux-vert par skip silencieux.
**Résultat — CLEAN.** **57 artefacts `build` byte-identiques sous les 5 seeds** + `regression_run.json`
**byte-identique sous 6 rejeux dont 2 `random`**. **Aucun** générateur ne dépend de l'ordre d'itération
d'un `set`/`dict` pour sa sérialisation → les deux gates de reproductibilité sont **robustes au
hash-seed**, ils ne peuvent pas flaper ROUGE sur le runner à cause de la randomisation. Le corpus
est prêt à un runner à seed non contrôlé sans régression aléatoire.
**Portée / anti-churn.** **0 fichier de production édité** (`git status --short` vide avant/après le
sweep), **0 artefact reconstruit** (les rebuilds vont en `/tmp`, jamais dans `out/` → pas de rebuild
`quality_report`, cf. [[audit4big-rebuild-after-doc-edits]]), **0 gate ajouté** (#5 — c'est une
**propriété re-jouable** via la commande, pas une *classe de dérive* : attester ≠ multiplier les
gates, cf. le sweep forward-compat), **0 chiffre saisi à la main** (#6 — 21/57 dérivés de
`find … -name out` et du décompte de comparaisons), **aucune commande VPS** (#8 — 100 % local &
lecture seule). Éditions : ce journal + une ligne dans la table *veille d'intégrité* du daily_report
+ une mémoire neuve. `./run_ci.sh` après → **33 PASS · 0 FAIL · 0 SKIP**.
---
## Session 114227 · AUDIT read-only roadmap↔repo (cross-refs numériques + liens) → **CLEAN** · clôture d'un **faux-positif « 7 dashboards vs 6 »**
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**, arbre propre,
roadmap `ROADMAP_8_WEEKS_OR_LESS.md` intégralement livrée/gatée in-repo, daily_report `2026-08-05.md`
déjà écrit ce jour (session 004112). Aucune tâche fonctionnelle in-repo → audit ciblé d'une classe
peu balayée : **cohérence des chiffres/liens de la roadmap vs l'état réel du dépôt** (table
« Gains d'accélération » + « Métriques succès MVP »), via un agent Explore + vérification manuelle.
**Un seul candidat remonté — vérifié NON-défaut (faux-positif d'arithmétique).**
La roadmap annonce « **7 dashboards uniformes** » (L39, DELIVERABLE Sprint 2 · et L83, métrique) alors
que la ligne L36 énumère « clone `/waf-home` sur **5** entités » (`/hrd-vente`, `/ac-construction`,
`/ecr-rh`, `/ploutos`, `/admin-9060`). L'agent a lu `1 base + 5 clones = 6` et signalé une
contradiction. **C'est faux** : le 7ᵉ dashboard est **`wag-home`**, page-home **déjà existante**
distincte de `waf-home`, attestée par `GAP_ANALYSIS_SPRINT1.md:91`
(« pages Frappe `www/` (**wag-home, waf-home**, pole/, projets/) »). Donc
**`wag-home` + `waf-home` + 5 clones entités = 7**. Le « 7 » est **sourcé et cohérent** entre la
roadmap (L39/L83) et la GAP (L93 « cloner `/waf-home` × 5 … → **7** dashboards uniformes ») ; aucune
occurrence littérale « 6 dashboards/entités » n'existe → **pas de contradiction interne**. WAG (parent
d'Helios RD, `CLAUDE.md` §Entités) a légitimement son propre dashboard. Ces dashboards sont par
ailleurs des **pages front VPS hors-repo** (aucun générateur/artefact `dashboard` in-repo — la
seule livraison frontend gatée est `portails/` = 5 Workspaces rôle, S4, classe distincte) : **aucune
source in-repo n'autorise à recompter**. Éditer `7→6` **inventerait** une correction fausse (#6) et
serait un churn (#5). **Décision : LEAVE / ne pas re-signaler** — famille connue du « 6-vs-7 sections »
financement déjà tranchée (`spec/liste réelle = autorité`, [[docstring-vs-code-drift]]).
**Reste de l'audit — CLEAN.** RBAC **50 rôles** recompté = 50 (`rbac/rbac_50_roles.json`) ; **5 portails**
= 5 Workspaces (`frontend/portails/README.md`) ; **Expo 51** actuel vs cible 54 conforme
([[mobile-runtime-actual-vs-rebuild-target]]) ; **0 lien markdown cassé** dans `03_agents/*/AGENT.md`
et `05_deliverables_mvp/*/README.md` (seuls non-résolus = chemins VPS `/opt/oto/…` + code-spans, par
design) ; aucune auto-contradiction numérique ailleurs.
**Portée honnête / anti-churn.** **0 fichier de production édité** (#5), **0 gate ajouté** (le « 7 »
est aspirationnel/hors-repo, non gatable — #8), **0 chiffre inventé** (#6), **0 commande VPS** (#8),
**aucun `git clean`**. Seules éditions : ce log + une mémoire de skip (`[[roadmap-7-dashboards-not-drift]]`).
`./run_ci.sh`**33 PASS · 0 FAIL · 0 SKIP** (inchangé). <!-- ci-allow : mention de l'interdit, jamais exécuté -->
---
## Session 111224 · SWEEP read-only 3 classes de dérive doc → **CLEAN** · + consignation d'un **NON-défaut vérifié** (le flag compat `-o` sur `validate`) pour clore une **récurrence de faux-positif** de l'audit docstring-vs-code
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**, arbre propre,
roadmap `ROADMAP_8_WEEKS_OR_LESS.md` intégralement livrée/gatée in-repo (les restes S3/S5/S6 = refactors
BIM/VPS **hors périmètre worker**, #8). Aucune tâche fonctionnelle in-repo → chasse à la dérive réelle
non-gatée, sur **trois classes** en parallèle (deux via Explore + vérif manuelle) :
1. **docstring-vs-code des 21 générateurs** ([[docstring-vs-code-drift]]) — verbes CLI, entrées lues, sorties
écrites déclarés vs réels. **CLEAN** (déjà attesté ce matin par 104219/84df56a ; re-balayé).
2. **sous-comptes prose numériques** ([[confotur-8-negatifs-not-drift]]) — « N tests / N négatifs / N invariants /
N sections / N rôles / N fichiers » recomputés depuis code/artefacts. **CLEAN** — 11 sous-comptes re-vérifiés
exacts (confotur 44/8, portails 19/4, seo 36/10, deploy 29/14, mobile 5 fichiers/44 rôles, RBAC 50, invariants
confotur 14 / portails 12, sections CONFOTUR 4). Aucune dérive (le seo 8→10 était déjà fixé fbe298f).
3. **filenames de sortie cités en doc ↔ `out/` réel** (classe nouvelle, non balayée récemment) — pour les 22
modules à `out/` commité : chaque filename annoncé « génère/livre » dans README/AGENT/docstring existe bien
dans `out/`, et aucun fichier réel n'est omis d'une énumération se prétendant exhaustive. **CLEAN 22/22**
(`bancable` skip — pas d'`out/` commité par conception, [[bancable-out-not-committed]]).
**Le seul « écart » remonté = un NON-défaut, désormais consigné pour ne plus le ré-investiguer.**
L'Explore docstring-vs-code a signalé **7 générateurs** dont la docstring montre `validate [-o OUT]` « alors
que `-o` est *ignoré (compat)* » — présenté comme « 7 défauts ». **Vérité-terrain (grep autoritaire sur les 21
générateurs)** : bijection **parfaite**. Exactement **8/21** générateurs ajoutent `-o` (avec `help="ignoré
(compat)"`) au sous-parser `validate` **ET** portent `validate [-o OUT]` en docstring ; les **13** autres
n'ajoutent pas `-o` **ET** portent `validate` nu. Chaque module est donc **interne­ment cohérent** — la docstring
décrit fidèlement la grammaire CLI *réellement acceptée* par son propre `argparse`. **Zéro dérive docstring-vs-code.**
| validate accepte `-o` ? | docstring | générateurs |
|---|---|---|
| **+o** (compat, ignoré) | `validate [-o OUT]` | deploy_runbook · chat_otoia · portails · app_config · acceptance · rbac/fixtures · rbac/roleprofile · rbac/userperm (**8**) |
| **noO** | `validate` | commissions · dossier_vente · financement · workflow_vente · demo/scenarios · bancable · faisabilite · ecf_dgii · confotur · pie · audit_4big · audit_5d · regression (**13**) |
**Décision : LEAVE (anti-churn #5), pas de fix, pas de gate.** Ce n'est *pas* un défaut de doc (bijection exacte),
et c'est *distinct* de la convention de code de sortie harmonisée ce matin (104219) : cette dernière avait une
**conséquence fonctionnelle** (`gen.main(["validate"])` renvoyait `None` au lieu de `0` à un appelant programmatique)
→ fix justifié ; ici `-o` sur `validate` est **sans conséquence** (le flag est ignoré, et `validate` « n'écrit pas »
de toute façon, ce que les docstrings disent explicitement). Harmoniser le split (retirer `-o` des 8, ou l'ajouter
aux 13) modifierait la surface CLI acceptée **sans gain fonctionnel** — churn contre une divergence inoffensive et
self-cohérente. **Ce n'est pas non plus un item du registre** `OPEN_DECISIONS_REGISTER.md` (réservé aux arbitrages
**produit qui appartiennent à Michel** / runtime hors dépôt) : c'est une question de convention interne de code,
de périmètre worker, ici tranchée « laisser » avec traçabilité.
**Valeur durable de la session.** Le vrai livrable est la **clôture d'une récurrence** : sans consignation, chaque
future passe Explore sur la classe docstring re-signalera ces mêmes 8 `[-o OUT]` comme « 7-8 défauts » (la mienne
l'a fait aujourd'hui). Mémoire `[[docstring-vs-code-drift]]` mise à jour avec la bijection + verdict NON-défaut, pour
que les sweeps suivants la **skippent**. Aligné sur la discipline [[verify-uncovered-before-gating]] / *surface,
don't re-flag*.
**Portée honnête / anti-churn.** **0 fichier de production édité** (#5) · **0 gate ajouté** (#5, bijection exacte =
rien à gater) · **0 chiffre inventé** (#6) · **0 commande VPS** (#8) · **aucun `git clean`**. <!-- ci-allow : mention de l'interdit, jamais exécuté --> Touché : ce log + la
mémoire. `./run_ci.sh`**33 PASS · 0 FAIL · 0 SKIP** (arbre inchangé, aucune régression possible).
---
## Session 104219 · FIX de COHÉRENCE 4Big — `qa/acceptance/acceptance_gen.py` était le **SEUL outlier sur 23 générateurs** à rompre la convention universelle de code de sortie : `main()` typé `-> None` + appel nu `main()` (au lieu de `-> int` + `raise SystemExit(main())` chez les 22 autres) → aligné (`cmd_build`/`cmd_validate`/`main` → `int`, `return 0`, `raise SystemExit(main())`) · comportement inchangé · artefacts byte-identiques · 0 chiffre inventé · 0 gate ajouté
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**, arbre propre,
roadmap `ROADMAP_8_WEEKS_OR_LESS.md` intégralement livrée/gatée. Aucune tâche fonctionnelle in-repo
restante → chasse à la dérive réelle non-gatée. **Audit end-to-end de la classe *docstring-vs-code drift***
(récurrente/non-close · [[docstring-vs-code-drift]]) : sweep des docstrings de sortie des 23 générateurs +
des modules `*lib/` helper (que l'Explore scopé `*_gen.py` rate). **Tous vérifiés exacts** : `financement`
7 fichiers out ✓, `seo` 4 hand-off ✓, `confotur` 14 invariants (ledger `# N ·` numéroté 1..14) ✓,
`userperm` ✓ ; le parser `publiciste/lib/parser.py` mappe fidèlement ses 6 sources→§ (§1.1 localisation,
§3.2 typologies, §3.3 services) — aucun phantom résiduel (cf. l'ancien §5.3 déjà corrigé). Root README
`24/24 · 15 promesses (8+7)` re-dérivé des artefacts (`quality_report.coverage`, `acceptance/MANIFEST`) =
exacts. **Classe doc-drift saturée aujourd'hui.**
**Le seul écart concret trouvé (cohérence de code, pas dérive doc).** Un Explore sur la classe a signalé
`acceptance_gen.py` comme atypique. Vérité-terrain (`grep` `def main` + bloc `__main__` des 23) :
**22/23 générateurs** exposent `def main(...) -> int:` retournant `0`, `cmd_* -> int`, et
`raise SystemExit(main())` dans `__main__` — la CLI **propage un vrai code de sortie**. `acceptance_gen`
seul : `main(argv=None) -> None` + `cmd_build/cmd_validate -> None` + appel nu `main()`. Le chemin d'échec
restait correct (`_validate_or_die``sys.exit(1)` interne, L280), **donc pas un bug fonctionnel** — mais
un appelant programmatique (`rc = gen.main(["validate"])`, la **raison d'être** du paramètre `argv` que
tous les frères exposent pour la testabilité) recevait `None` au lieu de `0`. **Défaut de cohérence 4Big
réel** (1 outlier / 23), pas une invention.
**Fix minimal, comportement préservé.** 4 annotations `-> None``-> int`, deux `return 0` en fin de
`cmd_*`, `return args.func(args)`, `main()``raise SystemExit(main())` (7+/5 sur 1 fichier). Le chemin
d'échec `sys.exit(1)` est **inchangé** (succès=0, échec=1 comme avant). Les tests n'assertent que
« succès ne lève pas » (`test_validate_exits_zero` : `gen.main(["validate"])` sans vérifier le retour) →
inchangés au vert.
**Vérifications.** `python3 -m unittest` = **Ran 37 tests · OK** · `main(["validate"])` **renvoie 0**
(était `None`), `validate` rc=**0** · rebuild `build -o /tmp``acceptance_matrix.json` + `MANIFEST.json`
**byte-identiques** à `out/` (aucune dérive d'artefact · [[artifact-reproducibility-gate]], pas de rebuild
de consommateur nécessaire) · `./run_ci.sh` final : **33 PASS · 0 FAIL · 0 SKIP**.
**Bilan.** 1 fichier touché · **0 changement de comportement** (exit 0/1 identiques) · **0 artefact modifié**
· **0 gate ajouté** (#5 · la convention `-> int`/`SystemExit(main())` reste une norme de style non gatée,
défaut isolé unique — comme les typos d'origine, ne justifie pas un 34e check) · **0 chiffre inventé** (#6)
· **0 commande VPS** (#8) · **aucun `git clean`** <!-- ci-allow : mention de l'interdit, jamais exécuté -->.
## Session 094211 · FIX RÉEL — le README module **SEO** annonçait « 36 tests (dont **8** injections négatives) » alors que la suite en compte **10** `test_negative_*` (faux depuis le commit d'origine `0d3b242`, jamais gaté) → corrigé **8→10** ; toute la classe « sous-compte négatif en prose » (seo/devops/confotur) auditée · 0 gate ajouté · 0 chiffre inventé
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**, arbre propre,
roadmap `ROADMAP_8_WEEKS_OR_LESS.md` intégralement livrée/gatée (Sprint 8 · QA). Aucune tâche
fonctionnelle in-repo restante → chasse à la **dérive documentaire réelle non-gatée** (le mode de valeur
honnête de cette phase). Recherche fan-out (Explore, read-only) + vérification manuelle : **une** dérive
présent-tense confirmée, isolée.
**Finding (calcul de la vérité-terrain, pas un soupçon · [[prose-facts-vs-numeric-drift]]).**
`05_deliverables_mvp/seo/README.md:67` écrit `# 36 tests (dont 8 injections négatives)`. Le **total 36**
est exact ET **gaté** (`ci/check_readme_claims.sh:941` recompute `# (\d+) tests` vs le nombre réel de
`def test_`). Le **sous-compte « 8 »** n'est capté par **aucun** groupe de capture (le pattern ne prend que
le total) → **non gaté**. Vérité-terrain : `grep -c "def test_negative_" tests/test_seo.py` = **10** (10
méthodes réparties dans les classes `Keywords`/`SchemaOrg`/`Hreflang`/`Manifest`, pas de classe dédiée).
`git show 0d3b242:…/test_seo.py | grep -c def test_negative_` = **10** dès le commit d'origine → le « 8 »
est **faux depuis la création** (2026-07-30), jamais un état vrai antérieur (donc pas *historical-repro*).
**Écarter le piège confotur ([[confotur-8-negatifs-not-drift]]).** Les deux modules-frères qui affichent
un sous-compte négatif en prose sont **exacts** car adossés à une **classe nommée** (structurellement
countable) : confotur « 8 négatifs » = classe `TestNegative` (**8** méthodes) ✓ · devops « 14 injections
négatives » = classe `TestNegativeInjections` (**14** méthodes) ✓. **SEO est le seul faux** : ses négatifs
ne forment **pas** de classe (préfixe `test_negative_` dispersé), et aucune lecture ne donne 8 → **10** est
le compte honnête. Fix minimal : `8 → 10`.
**Surfaces (balayage twin-fix · [[prose-facts-vs-numeric-drift]]).** Le « 8 » apparaît 3× : (a)
`seo/README.md:67` = **présent-tense, doc canonique vivante → FIX** ; (b)
`daily_reports/2026-07-30-session18.md:48` et (c) `05_activity_log/2026-07-30.md:920` = **snapshots datés
du 2026-07-30 → KEEP** (convention : rapports/logs horodatés immuables, ne pas réécrire l'histoire,
[[two-logging-channels]]).
**Effet de bord attendu et rebuild ([[audit4big-rebuild-after-doc-edits.md]]).** `audit_4big` **score le
contenu README** ; l'édition a fait diverger l'artefact byte-gaté `qa/audit_4big/out/quality_report.json`
(`check-artifacts` RED). Rebuild `audit_4big_gen.py build` **en dernier** → seule ligne changée :
`"evidence": "README.md (3900 → 3901 octets)"` (« 8 »→« 10 » = +1 octet) · verdict **PASS · 24/24 ≥95
(min 100)** inchangé.
**Décision de NON-gatage (#5 · [[verify-uncovered-before-gating]]).** Erreur d'**origine isolée** (une
faute de frappe unique), **pas** une classe de dérive récurrente : les 2 frères sont exacts et le total est
déjà gaté. Gater le sous-compte seul exigerait, par cohérence, de gater aussi confotur/devops (classe
countable, changement rare) — 3 checks pour un événement à très basse fréquence. Conforme au précédent
[[confotur-8-negatifs-not-drift]] (« ne pas fixer/gater » ces sous-comptes). **Classe désormais auditée
end-to-end** (consigné pour un futur passage : les 3 sous-comptes négatifs sont vérifiés exacts au
2026-08-05).
**Bilan.** 2 fichiers touchés (`seo/README.md` prose + `quality_report.json` régénéré) · **0 édition
d'autorité** (le test file, source de vérité, n'est pas modifié) · **0 gate ajouté** (#5) · **0 chiffre
inventé** (#6 · 10 dérivé de `grep -c`) · **0 commande VPS** (#8) · **aucun `git clean`** <!-- ci-allow : mention de l'interdit, jamais exécuté -->
· `./run_ci.sh` final : **33 PASS · 0 FAIL · 0 SKIP**.
## Session 091204 · FIX de COUVERTURE doc — la fiche **Mobile** omettait son propre **livrable in-repo gaté** (`mobile/app_config`) : elle n'ancrait le rôle que sur RBAC + acceptance, jamais sur le générateur de config app (22 tests · job CI · CLI) → sous-section dédiée + ligne de table **auto-gatée** (3 gates) + 3 énumérations « ancrage in-repo » réconciliées · 0 édition d'autorité · 0 gate ajouté · 0 chiffre inventé
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**, arbre
propre, roadmap intégralement livrée/gatée (Sprint 8 · launch-readiness). Un Explore read-only sur la
classe *docstring-vs-code drift* (récurrente, cf. [[docstring-vs-code-drift]]) n'a **rien remonté de
concret** (le « 14 invariants » de `chat_otoia` est correct : les invariants 8 et 9 sont assertés à la
fois globalement ET par-mount → 14 numéros distincts, pas une dérive). Classe saturée. Pivot vers la
**complétude des fiches** — la classe qui a produit le FIX réel de la session 071201 (CRM omettait son
4e module `financement_bancaire`) : *completeness ≠ accuracy* ([[fiche-accuracy-audit-closed]]).
**Le finding — diff `05_deliverables_mvp/*/` vs les 13 fiches.** Un `grep -rl <module> 03_agents/*/AGENT.md`
sur les 22 modules livrés a remonté **un seul orphelin** : `mobile/app_config` → référencé par **AUCUNE**
fiche (les 21 autres sont couverts ≥1×). Or `05_deliverables_mvp/mobile/app_config/` est un module
générateur **réel, commité et gaté** : 5 fichiers `out/*.json` (`app_config.json`/`eas_build.json`/
`role_navigation.json`/`store_listing.json`/`MANIFEST.json`), suite `mobile-app-config-tests` **dans
`gate.needs`**, CLI `build|validate`, 22 méthodes de test (`regression_plan.suites[mobile/app_config].
test_methods`). C'est le **livrable in-repo propre de l'agent Mobile** (docstring : « Livrable Mobile de
la roadmap Sprint 5 »). La fiche `03_agents/mobile/AGENT.md` ne le mentionnait **nulle part** — elle
énumérait ses ancrages in-repo comme *uniquement* le **rôle RBAC** + la **row QA acceptance S5**
(« ancré dans **deux** artefacts in-repo vérifiables »), en omettant son générateur le plus direct.
Ce n'était pas une inexactitude (les 2 ancres RBAC/QA sont justes) mais une **incomplétude** : le
livrable in-repo le plus concret de l'agent était absent de sa propre fiche.
**Pas de piège des deux cadres de sprint (contrairement à 071201/081202).** `mobile/app_config` est
Sprint **5** dans les DEUX cadres : `acceptance_spec` le classe en `evidence_modules` de **S5**
(`roadmap_line 58`) et la roadmap `## SPRINT 5` porte la ligne **Mobile L56** « Rebuild Expo 54 +
submit App Store #32 + Play Store ». J'ai cité **L56** (ligne Mobile, celle qu'ancre la docstring
`l.56-57`). Aucun conflit à trancher ([[agent-internal-vs-roadmap-sprint-frames]]).
**La correction (mirroir exact du pattern CRM 071201).** (a) Nouvelle sous-section « Générateur de
config app **in-repo** · `mobile/app_config` » + **1 ligne de table** identique en structure à celle du
CRM (`Module | Sprint | Rôle | Entrée CLI | Job CI | Tests`). (b) **3 réconciliations minimales** des
énumérations « ancrage in-repo » qui, sans elles, contrediraient la nouvelle section : le « livrable
in-repo est [refonte VPS] et [rôle RBAC] » (ajout du générateur), « ancré dans **deux** artefacts »
(→ « des artefacts … Outre son générateur … **deux cross-références** RBAC/QA »), et la note d'honnêteté
« l'ancrage réel est RBAC + QA S5 » (ajout du générateur). Toutes sourcées, aucune n'invente.
**Auto-gatage confirmé — le finding devient opposable au merge (3 gates indépendants de
`check_readme_claims.sh`, résolution par cible de lien `mobile/app_config ∈ plan.suites`).** `run_ci`
final montre les 3 dents mordre sur la nouvelle ligne (`AGENT.md:65`) : (a) cellule Tests `22 == source
(22)`, (b) job CI `mobile-app-config-tests == ci.yml`, (c) verbes CLI `build|validate == subparsers`.
Une future dérive du compte/job/CLI **mordra** désormais — comme pour la ligne CRM financement.
**Discipline.** Le `build` du module n'est **pas** régénéré (je n'ai touché aucun `out/`) → `git status`
ne montre **que** `03_agents/mobile/AGENT.md`. **0 gate ajouté** (surface déjà couverte par row_re/job/CLI
· #5) · **0 édition d'autorité** (roadmap, spec, directive, module intouchés) · **0 chiffre inventé**
(#6 : 22 lu dans `regression_plan`, 5 fichiers lus dans `out/` + docstring, Sprint 5/L56 lu dans
`acceptance_spec` + roadmap) · aucune commande VPS (#8) · aucun `git clean`. <!-- ci-allow : mention de l'interdit, jamais exécuté -->
**Bilan.** 1 fiche agent complétée (sous-section + 1 ligne auto-gatée + 3 réconciliations) · **CI 33
PASS · 0 FAIL · 0 SKIP** · le dernier module livré orphelin de fiche est désormais documenté et opposable.
## Session 081202 · FIX RÉEL — le module `crm/financement_bancaire` s'auto-étiquetait « **Sprint 5** » dans une **citation roadmap-globale** (`roadmap L52`) alors que L52 ∈ **Sprint 4** : label corrigé S5→S4 sur ses 2 seules surfaces (README + docstring gen) · directive Michel INTACTE · 0 gate · 0 chiffre inventé
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**, arbre propre,
roadmap intégralement livrée/gatée (Sprint 8 · launch-readiness). Tâche retenue : le **SIGNAL surfacé non
tranché** par la session 071201 — le README du module `crm/financement_bancaire` écrivait « **Sprint 5** ·
roadmap L52 » alors que L52 est une ligne **Sprint 4**. Investigué et **résolu comme drift réel** (pas un
cadre agent-interne).
**Le finding — un vrai conflit de cadres, tranché par la structure roadmap + l'autorité byte-gatée.**
La ligne 3 du README dit « **Sprint 5 · CRM natif ERPNext · roadmap L52 (workflow vente end-to-end · volet
financement)** ». C'est une **citation roadmap-GLOBALE** (elle cite `roadmap L52` + reprend le texte du
deliverable S4 « workflow vente end-to-end »), **pas** un cadre agent-interne de build-phase
([[agent-internal-vs-roadmap-sprint-frames]]). Or :
- Le **fichier roadmap** lui-même : `## SPRINT 4` = L48, deliverable L52 « 5 portails RBAC live · workflow
vente end-to-end » ; `## SPRINT 5` = L54 « ONAPI + Compliance + Mobile » (rien à voir avec le financement).
→ « Sprint 5 · roadmap L52 (workflow vente end-to-end) » est **auto-contradictoire**.
- L'**autorité byte-gatée** `qa/acceptance/acceptance_spec.json` (partition INV5) classe
`crm/financement_bancaire` sous **S4 · roadmap_line 52** (`evidence_modules` de S4).
- Les **3 modules frères** du même bucket S4 s'étiquettent tous « **Sprint 4** » (workflow_vente : « Réalise
le deliverable roadmap Sprint 4 » · dossier_vente : « Sprint 4 » · commissions : « Sprint 4 · roadmap
ligne 51 »). `financement_bancaire` était le **seul outlier**.
- La **fiche CRM** `03_agents/crm/AGENT.md` liste déjà correctement ce module en « **4 (roadmap L52)** »
(session 071201, sourcé S4). Le module contredisait donc sa propre fiche.
**Provenance de l'erreur (surfacée, non éditée).** La **DIRECTIVE de Michel**
`DIRECTIVE_FINANCEMENT_BANCAIRE_COMPLET_20260803.md:140` cadre le travail en « `[Sprint 5 · Financement]` » —
c'est **son** cadre d'entrée (snapshot daté, autorité pour son propre contenu · [[directive-vs-implementation]]).
L'auteur du module a transcrit ce « Sprint 5 » directive **dans un slot de citation roadmap-globale**, où il
devient faux (L52 ∈ S4). La directive est **laissée INTACTE** (input immuable de Michel, jamais éditée · comme
`daily_reports`).
**La correction (2 surfaces, balayées en un passage · [[prose-facts-vs-numeric-drift]]).** Grep exhaustif :
exactement **2** surfaces manuscrites portaient le mauvais label — `README.md:3` (prose) et
`financement_bancaire_gen.py:4` (docstring) ; **aucun** spec/artefact/`out/*.json`/gate ne porte de champ
sprint (vérifié). S5→S4 sur les deux. Le `build` du module ne régénère **aucun** artefact modifié (docstring
+ README non émis dans `out/`) → `check_artifacts` reste vert, `git status` ne montre que mes 2 fichiers source.
**Pas de gate ajouté (#5).** Le label de sprint des modules est un **piège à deux cadres légitimes**
(agent-interne vs roadmap-global) : un gate aveugle qui re-dériverait chaque label depuis `acceptance_spec`
produirait des faux positifs sur les frames agent-internes légitimes. Je **corrige la prose** (drift réel,
citation roadmap-globale fausse) sans instrumenter la surface. 0 chiffre inventé (#6) · 0 commande VPS (#8).
**Bilan.** 2 surfaces source alignées S5→S4 · directive Michel INTACTE · 0 édition d'autorité (l'autorité
`acceptance_spec` disait déjà S4) · 0 gate · 0 invention · **CI 33 PASS**. SIGNAL 071201 → **clos**.
## Session 071201 · FIX de COUVERTURE doc — la fiche CRM omettait son **4e module livré** (`crm/financement_bancaire`) : ajout d'une sous-section dédiée + ligne de table **auto-gatée** (35 tests · job CI · verbes CLI) · 0 édition d'autorité
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**, arbre propre.
Roadmap `ROADMAP_8_WEEKS_OR_LESS.md` intégralement livrée/gatée (24/24 modules 4Big à 100/100, matrice
d'acceptance 15/15 `in_repo`) — **Sprint 8 · launch-readiness**. Les décisions ouvertes restantes sont
des **arbitrages produit de Michel** ([[open-decisions-register]], ne pas trancher · #5). Tâche retenue :
le **fallback explicite** de la mission (« améliorer la doc d'un AGENT.md ») appliqué à un **écart réel**,
pas cosmétique.
**Le finding.** La fiche `03_agents/crm/AGENT.md` se présente comme le recensement des « **Livrables CRM
réellement produits** » mais n'en documentait que **3** (trio pipeline `workflow_vente`/`dossier_vente`/
`commissions`). Or `05_deliverables_mvp/crm/` héberge un **4e module gaté**`financement_bancaire`
(parcours hypothécaire RD · gate check 4 conditions · 35 tests · job `crm-financement-bancaire-tests`) —
**absent de TOUTES les 13 fiches agents** (grep confirmé), y compris de sa fiche propriétaire naturelle.
Ce n'était pas une *inexactitude* (les 3 lignes existantes sont justes, cf. [[fiche-accuracy-audit-closed]])
mais une **incomplétude** : un livrable réel sans surface de doc côté agent.
**Pourquoi il était hors du trio (et le reste).** Le trio partage **une source unique**
(`workflow_vente_spec.json` + `rbac_50_roles.json`) — c'est la propriété « cross-cohérents » que décrit la
prose du trio, et que le gate `check_readme_claims.sh:725` encode en figeant « Total CRM : 81 tests » aux
**3** suites du pipeline. `financement_bancaire` **ne partage pas** cette source (piloté par
`DIRECTIVE_FINANCEMENT_BANCAIRE_COMPLET_20260803.md`), donc l'ajouter comme 4e ligne du trio aurait été
faux sémantiquement **et** cassé le gate (regex `Total CRM : **N tests**`). D'où une **sous-section
séparée**, le trio et son agrégat gaté laissés **intacts**.
**Sourcing (zéro invention · #6).** Sprint **4 · roadmap L52** lu dans l'autorité roadmap-globale
`qa/acceptance/acceptance_spec.json:71-84` (byte-gaté ; `evidence_modules` inclut `crm/financement_bancaire`
sous S4) — **pas** le « Sprint 5 » du README module, qui est le **cadre agent-interne**
([[agent-internal-vs-roadmap-sprint-frames]]). Nombre de tests **35** lu dans `regression_plan.json`
(`test_methods`, source faisant autorité). CLI `financement_bancaire_gen.py build|validate` vérifié depuis
les subparsers.
**Auto-gatage confirmé — le finding devient opposable au merge.** La nouvelle ligne de table est captée
par **trois** gates indépendants de `check_readme_claims.sh` (résolution par **cible de lien**,
`crm/financement_bancaire ∈ auth`) : (a) cellule Tests `35 == source (35)`, (b) job CI
`crm-financement-bancaire-tests == ci.yml`, (c) verbes CLI `build|validate == subparsers`. Une future
dérive du compte/job/CLI **mordra** désormais. `./run_ci.sh` final : **33 PASS**.
**SIGNAL (surfacé, non tranché · SURFACE don't rewrite).** Le README du module écrit « **Sprint 5** ·
roadmap L52 » — pairing interne étrange (L52 est une ligne **Sprint 4**) : soit le cadre agent-interne,
soit une micro-dérive du label roadmap-global vs l'autorité `acceptance_spec` (S4). **Non édité** (le
module README n'est pas ma cible ; le piège des deux cadres de sprint exige une édition spec+regen
coordonnée, cf. [[agent-internal-vs-roadmap-sprint-frames]]) — consigné ici pour un passage ultérieur.
**Bilan.** 1 fiche agent enrichie (sous-section + 1 ligne auto-gatée) · **0 édition d'autorité** · 0 gate
ajouté (surface déjà couverte par `row_re`/job/CLI · #5) · 0 chiffre inventé · CI **33 PASS**.
## Session 051154 · Attestation des DENTS des **suites de MODULE** — BATCH 4 (FINAL) : les **7 dernières** suites → **25/25** prouvées · **7 BITES · 0 TOOTHLESS** · arbre byte-pristine · 0 édition de production
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**, arbre propre,
roadmap `ROADMAP_8_WEEKS_OR_LESS.md` intégralement livrée/gatée — nous sommes au **Sprint 8 · QA :
regression tests exhaustifs**. Continuation et **clôture** de l'axe des DENTS des suites de MODULE
([[module-suite-teeth-mutation]]) : les batches 034144/041149/044153 ont prouvé **18/25**, laissant les
**7 dernières** non-couvertes (rbac-aggregate, portails, audit-4big, demo-scenario, regression,
deploy-runbook, acceptance) — **non-couvertes ≠ édentées, juste non-prouvées**. Ce batch les couvre
toutes → **25/25**. Axe distinct des 8 gates STATIQUES (8/8, 031134) : une suite qui rechargerait
`out/*.json` contre elle-même serait édentée = faux vert Sprint 8 comme un gate édenté.
**Méthode.** Reconnaissance (Explore, read-only) pour cartographier, par module, **une ligne de LOGIQUE
de générateur** reconstruite en mémoire par une assertion. **Vérification critique avant de croire
l'agent** ([[module-suite-teeth-mutation]] leçon (b)) : deux mutations proposées étaient **INERTES**,
pas mordantes — `and→or` sur `deploylib/builder.py:61` (`not [] and not []` = True → `or` = True aussi,
sortie inchangée) et idem `acclib/builder.py:138`. Remplacées par des mutations qui **font basculer** la
sortie observée : `deploy``gated_set - mapped_set` **`|`** (union : `missing` non-vide, bijective
False, asserté par `test_coverage_bijective`) ; `acceptance``"exact": want == have` **`!=`**
(chaque `p["exact"]` bascule False, asserté par `test_partition_exact_all_sprints`). Unicité de chaque
cible re-vérifiée par `grep -Fc == 1` **avant** mutation. Harnais `/tmp` read-only
(`/tmp/suite_teeth_20260805_batch4.sh`) : **baseline verte** sanity → backup `cp``str.replace` (assert
1 occurrence) → run de **la seule suite du module** (working-directory dérivé de `ci.yml`) → assert
`exit≠0` → restauration `cp` + `cmp` byte-exact ; `trap EXIT` restaure tout ; jamais `git
clean`/`checkout` ([[ci-gate-verification-method]]). <!-- ci-allow : mention de l'interdit, jamais exécuté -->
**6 BITES — mutations de LOGIQUE de générateur, sortie re-assertée en mémoire :**
- **portails** · `wslib/builder.py:114` `roles_couverts = sum(m["nb_roles"] …)` **`+ 1`** → `test_roles_restriction` recompte le total vs `manifest.counts.roles_couverts`.
- **audit-4big** · `q4lib/builder.py:30` `"fail": len(below)` **`+ 1`** → `test_real_build_passes_gate` exige `totals.fail == 0`.
- **demo-scenario** · `scenlib/builder.py:44` `duree = sum(b["duree_min"] …)` **`+ 1`** → `test_duree_est_somme_des_beats` recompte `sc["duree_min"]` vs somme des beats.
- **regression** · `reglib/builder.py:44` `"test_methods": sum(s["test_methods"] …)` **`- 1`** → `test_totals_match_suites` recompute la somme vs `totals.test_methods`.
- **deploy-runbook** · `deploylib/builder.py:59` `gated_set - mapped_set` **`→ |`** → `test_coverage_bijective` exige `bijective` + `missing==[]`.
- **acceptance** · `acclib/builder.py:136` `"exact": want == have` **`→ !=`** → `test_partition_exact_all_sprints` exige chaque `p["exact"]` + `partition_ok`.
**1 BITE — sous-classe distincte (validation-de-CONTRAT, pas générateur).** `rbac-aggregate`
(`rbac/tests/test_rbac.py`) n'a **pas** de builder/`out/` : il charge directement le **contrat**
`rbac_50_roles.json` et y asserte des invariants absolus (schéma, ==50, unicité id/nom, couverture des 5
portails, invariant #6 `set_user_permissions`). Structurellement **non-echo** (il ne compare pas un
artefact à lui-même — il contraint des constantes dures). Dent prouvée par une dérive du **contrat**
`"cible_rbac_roles": 50 → 49`, assertée par `test_exactement_50_roles`**BITE**. Honnêteté de portée :
axe « contrat » ≠ axe « logique de générateur », signalé comme tel plutôt que forcé dans le même moule.
**Verdict : 7 BITES · 0 TOOTHLESS → clôture 25/25 des suites de MODULE mordantes.** Combiné aux 8 gates
STATIQUES (8/8) et à la reproductibilité des artefacts, le Sprint 8 « regression tests exhaustifs » est
attesté sur ses **deux axes de DENTS**.
**Vérifications.** Arbre **byte-pristine** après harnais (`git status --porcelain` vide, `git diff --stat`
vide). `./run_ci.sh`**33 PASS · 0 FAIL · 0 SKIP** (aucun fichier gaté touché — restauration byte-exact).
**0 chiffre inventé (#6)** · **0 gate ajouté** (prouve les tests existants, n'en crée aucun — #5) · **0
édition de production** · **aucune commande VPS (#8)** · **aucun `git clean`.** <!-- ci-allow : mention de l'interdit, jamais exécuté -->
## Session 044153 · Attestation des DENTS des **suites de MODULE** — BATCH 3 : mutation-test *generator-logic* sur 6 nouvelles suites (→ **18/25** prouvées) · **6 BITES · 0 TOOTHLESS** · arbre byte-pristine · 0 édition de production
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**, arbre
propre, roadmap `ROADMAP_8_WEEKS_OR_LESS.md` intégralement livrée/gatée. Continuation directe de
l'axe des DENTS des suites de MODULE ([[module-suite-teeth-mutation]]) : les batches 034144 (6) et
041149 (6) ont prouvé **12/25** suites, laissant **13 non-couvertes (≠ édentées, juste non-prouvées)**.
Ce batch 3 en couvre 6 de plus → **18/25**. Axe distinct des 8 gates STATIQUES (8/8, 031134) : une
suite qui rechargerait `out/*.json` contre elle-même serait édentée = faux vert Sprint 8.
**Méthode.** Reconnaissance (Explore, read-only) pour cartographier, par module, **une ligne de LOGIQUE
de générateur** (calcul/règle, jamais un octet d'artefact) qu'une assertion unittest reconstruit **en
mémoire** depuis le contrat. Unicité de chaque cible re-vérifiée par `grep -Fc == 1` **avant** mutation.
Harnais `/tmp` read-only (`/tmp/suite_teeth_20260805_3.sh`) : backup `cp -p` → remplacement d'octets
littéral (`str.replace`, assert 1 occurrence) → run de **la seule suite du module** → assert `exit≠0`
restauration `cp` + `cmp` byte-exact ; `trap EXIT` restaure tout ; jamais `git clean`/`checkout`
([[ci-gate-verification-method]]). <!-- ci-allow : mention de l'interdit, jamais exécuté -->
**6 BITES (mutations de LOGIQUE réelles) :**
- `rbac/fixtures_gen``fixturelib/builder.py:43` `if_owner = scope == SCOPE_IF_OWNER` `== → !=`
inverse tous les `if_owner`, `test_if_owner_reflete_scope_own` (attend `1 if scope=="own" else 0`) **mord**.
- `rbac/userperm_gen``permlib/builder.py:43` `by_mechanism[mechanism] += 1` `→ += 2` → total 50→100,
`test_manifeste_fidele` (`assertEqual(sum(by_mechanism.values()), 50)`) **mord**.
- `rbac/apply_plan``applylib/aggregator.py:174` chaîne `roles == cible == up_entries == rp_covered`,
2ᵉ `== → !=``couverture_bijective` bascule False, `test_consistency_bijective` (`assertTrue`) **mord**.
- `mobile/app_config``mobilelib/builder.py:105` `roles = sorted(roles_map.get(key, []))` `+reverse=True`
ordre inversé, `test_roles_allowed_egal_surface_rbac` (`==sorted(roles_map[portail])`) **mord**.
- `qa/audit_5d``qalib/builder.py:60` `"fail": all_statuts.count(_FAIL)` `_FAIL → _PASS` → totals
fail 0→13, `test_verdict_pass_with_open_items` (`totals == {...,"fail":0,...}`) **mord**.
- `frontend/chat_otoia``chatlib/builder.py:114` `"roles_couverts": sum(m["nb_roles"] …)`
`sum(1 …)` → 44→~5, `test_44_roles_couverts` (`total(44) == roles_couverts`) **mord**.
**LEÇON de méthode réappliquée — désambiguïser TOOTHLESS-test vs mutation INERTE.** L'Explore avait
d'abord proposé `mobile mobilelib/builder.py:192 "roles_couverts": sum → len` : **édenté pour la suite
de MODULE** car `roles_couverts` (agrégat manifest) n'est **jamais** ré-asserté en mémoire par la suite
mobile (seuls `n_roles`/`roles_allowed` par-onglet le sont). Observation honnête, **pas un défaut** — on
a muté à la place une ligne **réellement contrainte** (`:105` le tri). Idem `fixtures :68 doctypes_uniques`
(non référencé par la suite) → basculé sur `:43 if_owner`. → Règle confirmée : **vérifier que la ligne
mutée est bien celle que le test reconstruit avant de croire un verdict.**
**Portée honnête.** 18/25 échantillonnées pour la diversité (inversion booléenne, incrément de compteur,
chaîne d'égalité, tri-déterminisme, count de statut, agrégat de rôles). **7 restantes non-couvertes ≠
édentées** (rbac agrégé, portails, audit-4big, demo-scenario, regression, deploy-runbook, acceptance) —
pas de plafond silencieux.
**Vérifications.** Après restauration : `git status --porcelain` **vide**, arbre **byte-exact pristine**
(6/6 fichiers `cmp` OK). `./run_ci.sh`**33 PASS · 0 FAIL · 0 SKIP** (inchangé). Harnais `/tmp` supprimé.
**0 gate ajouté (#5 — prouve les tests existants, n'en ajoute aucun) · 0 chiffre inventé (#6) · 0 édition
de production · 0 commande VPS (#8) · aucun `git clean`.** <!-- ci-allow : mention de l'interdit, jamais exécuté -->
## Session 041149 · Attestation des DENTS des **suites de MODULE** — BATCH 2 : mutation-test *generator-logic* sur 6 nouvelles suites (→ **12/25** prouvées) · **6 BITES · 0 TOOTHLESS** · arbre byte-pristine · 0 édition de production
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**, arbre
propre. Continuation honnête et directe de l'axe des DENTS des suites de MODULE ([[module-suite-teeth-mutation]]) :
la session 034144 avait prouvé **6/25** suites, laissant **19 non-couvertes (≠ édentées, juste non-prouvées)**.
Ce batch 2 en couvre 6 de plus → **12/25**. Axe distinct des DENTS des 8 gates STATIQUES (8/8, 031134) : une
suite qui rechargerait `out/*.json` contre elle-même serait édentée = faux vert Sprint 8 comme un gate édenté.
**Méthode.** Reconnaissance (Explore, read-only) pour cartographier, par module, **une ligne de LOGIQUE de
générateur** (calcul/règle, jamais un octet d'artefact) qu'une assertion unittest reconstruit **en mémoire**
depuis le contrat. Unicité de chaque cible vérifiée par `grep -Fc` **avant** mutation. Harnais `/tmp` read-only
(`/tmp/suite_teeth_20260805_2.sh`) : backup `cp` → mutation de la logique → exécution de **la seule suite du
module** → assertion `exit≠0` → restauration `cp` + `cmp` byte-exact ; `trap EXIT` restaure tout ; jamais
`git clean`/`checkout` ([[ci-gate-verification-method]]). <!-- ci-allow : mention de l'interdit, jamais exécuté -->
**6 BITES (mutations de LOGIQUE réelles) :**
- `faisabilite/generator``scorer._score_completude` `20 * filled → 20 / filled` (sed adressé L57 car la
formule récurre L81) → le score `4big` tombe < 95, `test_complete_brief_scores_publiable` **mord** (exit 1).
- `faisabilite/bancable``finance.derived` point d'équilibre `math.ceil(pe_brut) → math.floor` → 21→20,
`test_derived_arithmetic_traceable` **mord**.
- `crm/workflow_vente``builder` `trans_rows.sort(...)` `+reverse=True` → casse le déterminisme trié,
`test_transitions_sorted_stable` **mord**.
- `crm/dossier_vente``is_submittable` seuil `>= "1"` (voir ci-dessous) **mord**.
- `crm/financement_bancaire``gate.apport_requis_usd` `/ 100.0 → * 100.0` → apport ×10 000,
`test_apport_requis_*` **mord**.
- `publiciste``_fmt_usd` suppression du `.replace(",", " ")` (séparateur de milliers = espace) →
"USD 150,000" ≠ "USD 150 000" attendu (test L201) **mord**.
**LEÇON de méthode — un verdict « TOOTHLESS » doit être désambiguïsé : (a) test réellement édenté vs
(b) mutation comportementalement INERTE pour la donnée du contrat.** Deux mutations de ce batch ont d'abord
lu TOOTHLESS mais étaient (b), **PAS des défauts** :
- `dossier_vente` `>= "1" → > "1"` est **inerte** car le max des `doc_status` réels = **"2"** (les deux
branches donnent 1). Une mutation qui **fait basculer** le résultat (`>= "3"``"2">="3"` False → 0)
**mord** bien : la suite contraint la règle `is_submittable`.
- `publiciste` `min(prices) → max` est **inerte** car le retour de `_prix_depuis` n'alimente QUE la gate
`is not None` (L116) ; sa valeur min-vs-max n'est **jamais** affichée (chaque ligne de typologie rend son
propre prix, L57). Observation honnête, **pas un défaut à corriger** (#5/#6) : la sémantique du minimum
n'a aucun effet observable en aval → on mute plutôt une logique **observée** (`_fmt_usd`).
→ Règle : **confirmer que la mutation change la SORTIE pour la fixture avant de croire un TOOTHLESS.**
**Portée honnête.** 12/25 échantillonnées pour la diversité (arithmétique, seuil booléen, tri-déterminisme,
zfill/format, dédup, séparateur) ; **13 restantes non-couvertes ≠ édentées** (rbac-agrégat/fixtures/userperm/
applyplan, portails, audit-5d, chat-otoia, audit-4big, demo-scenario, regression, deploy-runbook, acceptance,
mobile-app-config) — pas de plafond silencieux.
**Vérifications.** Après restauration : `git status` **propre**, arbre **byte-exact pristine** (6/6 fichiers
`cmp` OK). `./run_ci.sh`**33 PASS · 0 FAIL · 0 SKIP** (inchangé — aucun fichier gaté modifié en net).
**0 gate ajouté (#5 — prouve les tests existants, n'en ajoute aucun) · 0 chiffre inventé (#6) · 0 édition de
production · 0 commande VPS (#8) · aucun `git clean`.** <!-- ci-allow : mention de l'interdit, jamais exécuté -->
## Session 034144 · Attestation des DENTS des **suites de test de MODULE** — mutation-test *generator-logic* (6 suites échantillonnées, chacune doit sortir NONZERO sur une dérive de LOGIQUE de son générateur, pas d'écho d'artefact) → **6 BITES · 0 TOOTHLESS** · arbre byte-pristine · 0 édition de production
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**, arbre
propre, roadmap `ROADMAP_8_WEEKS_OR_LESS.md` intégralement livrée/gatée. Vérifications
d'entrée : les 3 chiffres launch-readiness rechargent à l'identique depuis les artefacts commités
(recompute `python3` indépendant, aucune valeur saisie à la main) — `quality_report` **24 modules ·
tous == 100 · coverage.ok** · `regression_run` **suites 24 · ran 624 · passed 624 · 0/0/0** ·
`acceptance_matrix` **15/15 `in_repo` · verdict True · 36 `ci_job`** · `run_ci --list` **33 = 8 statiques + 25 suites**.
Un balayage read-only fan-out (README de module + docstrings des `*_gen.py`/`*lib` + DIRECTIVE↔module)
n'a **remonté aucune dérive prose** (surfaces contenu saturées, #5). **Axe distinct et jamais
couvert en suite** : hier (`031134`) a prouvé que les **8 gates STATIQUES** mordent (8/8) ; personne
n'a jamais prouvé que les **25 suites unittest de MODULE** mordent sur une dérive de **LOGIQUE de
générateur**. Une suite qui ne ferait que recharger `out/*.json` et l'asserter contre lui-même
serait **édentée** (faux vert Sprint 8 « regression tests exhaustifs ») exactement comme un gate
édenté. Constat encourageant préalable : les suites **reconstruisent en mémoire** depuis le contrat
(`builder.build_bundle(contract)`), donc elles *devraient* contraindre la logique — restait à le **prouver**.
**Méthode ([[verify-uncovered-before-gating]] appliquée à l'inverse — prouver que la protection MORD).**
Harnais unique hors dépôt (`/tmp`, jamais commité), par suite : `backup cp → mutation ciblée de la
LOGIQUE du générateur → run de la SEULE suite du module → assert exit≠0 → restore byte-exact cp →
cmp`. Trap `EXIT` restaurant **tout** même en cas d'échec. Chaque mutation = une vraie erreur de
calcul/structure que le générateur pourrait introduire (cible re-vérifiée unique sur l'arbre courant
AVANT via `grep -Fc`), **jamais** un octet d'artefact ni un commentaire :
| # | Suite (module) | Mutation *generator-logic* injectée | Verdict |
|---|---|---|---|
| 1 | `crm-commissions` | `finance.py` : commission `base * taux``base + taux` | **BITES** exit=1 |
| 2 | `pie-manifest` | `pielib/builder.py` : `_statut` inversé (`gated``a_construire`) → compteurs faussés | **BITES** exit=1 |
| 3 | `fiscal-ecf` | `ecflib/ncf.py` : e-NCF `zfill(10)``zfill(9)` (rompt le format 13 car. Ley 32-23) | **BITES** exit=1 |
| 4 | `rbac-roleprofile` | `profilelib/builder.py` : `nb_roles = len(role_names)``+ 1` | **BITES** exit=1 |
| 5 | `seo` | `seolib/keywords.py` : dédup neutralisée (`if key in seen and False:`) → doublons | **BITES** exit=1 |
| 6 | `legal/confotur` | `cflib/builder.py` : options `entites` jointes `"\n"``"|"` (mauvais séparateur Frappe) | **BITES** exit=1 |
**Résultat : 6 BITES · 0 TOOTHLESS.** Chaque suite échantillonnée oppose au merge une dérive réelle
de la **logique** de son générateur — aucune n'est un simple miroir d'artefact. Combiné à hier,
l'opposabilité au merge est prouvée sur **8 gates statiques + 6 suites de module**.
**Portée honnête (pas de plafond silencieux).** Échantillon de **6 des 25 suites** de module,
choisi pour la diversité des styles d'assertion (calcul monétaire · compteurs de statut · format
réglementaire · métadonnée de comptage · dédup · sérialisation d'options). Les **19 suites non
mutées ce jour** (`publiciste`, `rbac`(agrégée)/`fixtures`/`userperm`/`applyplan`, `faisabilite-gen`,
`bancable`, `workflow-vente`, `dossier-vente`, `financement-bancaire`, `portails`, `audit-5d`,
`chat-otoia`, `audit-4big`, `demo-scenario`, `regression`, `deploy-runbook`, `acceptance`, `mobile-app-config`)
restent à attester en session ultérieure — non couvertes ≠ édentées, juste non prouvées aujourd'hui.
**Discipline.** Attestation **read-only par conception** (harnais en `/tmp`, mutations restaurées
`cp` depuis backup + `cmp` byte-exact · **jamais `git clean`/`git checkout`** — cf. interdit
CLAUDE.md · [[ci-gate-verification-method]]). <!-- ci-allow : mention de l'interdit, jamais exécuté -->
**0 gate ajouté** (#5 — on ne teste pas, on *prouve les tests existants*) · **0 chiffre inventé**
(#6) · **0 édition de production** (seul ce journal change). Axe **dents des suites de module**,
distinct de l'axe **dents des gates** (`031134`) et des attestations de **contenu**.
**Vérifications.** `git status --porcelain` **vide** (arbre byte-pristine post-harnais) · `cmp`
backup↔restauré pour les 6 fichiers touchés = identiques · `./run_ci.sh` → **33 PASS · 0 FAIL · 0
SKIP** (inchangé). Aucune commande VPS (#8) · aucun `git clean`. <!-- ci-allow : mention de l'interdit, jamais exécuté -->
## Session 031134 · Attestation launch-readiness des DENTS du gate — mutation-test des **8 gates statiques** (chacun doit sortir NONZERO sur sa dérive) → **8 BITES · 0 TOOTHLESS** · arbre byte-pristine · 0 édition de production
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**,
arbre propre, roadmap `ROADMAP_8_WEEKS_OR_LESS.md` intégralement livrée/gatée (8 sprints · 7
critères ✅). Les ~15 dernières sessions ont saturé les axes d'audit *contenu* : cross-artefact,
dérive free-text (docstring↔code, inventaire↔fs), attestation des 4 chiffres stakeholder, des 18
citations `file:line` du registre. **Axe distinct et non couvert en suite depuis le 2026-08-02** :
prouver que les gates ont encore des **DENTS**. `run_ci` prouve qu'ils **PASSENT** sur arbre
propre — il ne prouve **jamais** qu'ils **ÉCHOUENT** sur une vraie dérive. Un gate devenu édenté
(toujours vert) est **pire** qu'absent : un faux vert de launch-readiness. La mémoire
[[guard-constraints-flags-usage-not-mention]] notait la dernière suite-mutation-test à **7/7
ROUGE le 2026-08-02** — or depuis : **+1 gate** (8ᵉ `check-mobile-workflow`, `001109`) et **~3
jours d'éditions**. Aucune session n'a re-prouvé la suite **entière** à 8. Valeur honnête du jour :
**Sprint 8 « regression tests exhaustifs »** — attester les dents des 8, corriger tout édentement.
**Méthode (discipline [[verify-uncovered-before-gating]] appliquée à l'inverse — prouver que la
protection MORD).** Harnais unique hors dépôt (`/tmp`, jamais commité), par gate :
`backup→mutation ciblée→run gate→assert exit≠0→restore byte-exact`, avec trap `EXIT` restaurant
**tout** même en cas d'échec. Une mutation = l'exacte classe de bug que le gate est censé
opposer au merge (aucune liste en dur — chaque cible re-vérifiée sur l'arbre courant AVANT) :
| # | Gate | Mutation injectée | Verdict |
|---|---|---|---|
| 1 | `guard_constraints` | usage chemin interdit `/var/www/html/static/…` (écriture · #4) | **BITES** exit=1 |
| 2 | `validate_json` | JSON malformé (`GARBAGE_NOT_JSON` appendé à un `out/*.json`) | **BITES** exit=1 |
| 3 | `check_docs` | lien Markdown interne cassé (`./inexistant-xyz-123.md`) | **BITES** exit=1 |
| 4 | `check_artifacts` | `out/MANIFEST.json` commité ≠ build frais (1 octet) | **BITES** exit=1 |
| 5 | `check_regression` | `regression_run.json` `passed:25→24` vs run frais | **BITES** exit=1 |
| 6 | `check_ci_integrity` | 1 job (`pie-manifest-tests`) retiré de `gate.needs` | **BITES** exit=1 |
| 7 | `check_readme_claims` | compte d'agents dérivé `13→12` dans README | **BITES** exit=1 |
| 8 | `check_mobile_workflow` | `secrets.` réintroduit dans un `if:` de job (SKIP silencieux) | **BITES** exit=1 |
**Résultat : 8 BITES · 0 TOOTHLESS.** Chaque gate statique oppose encore au merge la classe de
dérive qu'il déclare protéger — **aucun édenté** malgré 3 jours d'éditions. La mutation #8 est
précisément l'anti-pattern `234104` (contexte `secrets` indisponible en `jobs.<id>.if` → build
SKIP silencieux le jour où Michel fournit `EAS_TOKEN`) : opposabilité au merge **confirmée**.
**Discipline.** Attestation **read-only par conception** (harnais en `/tmp`, mutations restaurées
`cp` depuis backup · **jamais `git clean`/`git checkout`** — cf. interdit CLAUDE.md · [[ci-gate-verification-method]]). <!-- ci-allow : mention de l'interdit, jamais exécuté -->
**0 gate ajouté** (#5 — on ne teste pas, on *prouve les tests existants*) · **0 chiffre inventé**
(#6) · **0 édition de production** (seul ce journal change). Distinct des attestations
`234104`/`004112` (contenu) : axe **dents des gates**, jamais couvert en suite complète à 8.
**Vérifications.** `git status --porcelain` **vide** (arbre byte-pristine post-harnais) · `diff`
backup↔restauré pour les 8 fichiers touchés = identiques · `./run_ci.sh` → **33 PASS · 0 FAIL · 0
SKIP** (inchangé). Aucune commande VPS (#8) · aucun `git clean`. <!-- ci-allow : mention de l'interdit, jamais exécuté -->
## Session 024134 · Attestation launch-readiness du `OPEN_DECISIONS_REGISTER` (18 citations `file:line` + 2 numériques d'en-tête re-prouvées) → 1 FIX de dérive latente : dé-figeage des 2 compteurs mobiles gelés dans le doc · 0 gate ajouté · 0 édition d'autorité
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**, arbre
propre, roadmap intégralement livrée/gatée. Le `OPEN_DECISIONS_REGISTER.md` a été **promu
découvrable hier** (session `021124`) mais son contenu — **18 citations `file:line` vers des
sources d'autorité** (`CLAUDE.md`, directives datées, `pie_spec.json`, `gate.py`, journaux) —
n'avait **jamais été re-vérifié indépendamment** contre les numéros de ligne courants. Un
punch-list *launch-readiness* dont les ancres pointent à côté est trompeur. **Valeur honnête
distincte** : attester chaque citation, et corriger toute dérive réelle du doc lui-même (le
registre est **notre** doc worker, pas une source d'autorité — l'éditer pour l'exactitude est
un travail worker légitime, distinct de *SURFACE-don't-rewrite* qui protège les autorités).
**Attestation (read-only d'abord).** Les **18** citations re-prouvées exactes au numéro de ligne :
- D-01 (PIE) : `pie_spec.json:24` (`contrats``legal/confotur`) · siblings `null` `:22,27,28,29,30` ·
`out/MANIFEST.json:24` · `README.md:48` · `out/pie_manifest.json:165-166` · `legal/confotur/README.md:8-17`. **Toutes exactes.**
- D-02 (financement) : `DIRECTIVE_FINANCEMENT…:206-210` + `:275-279` (couche `## PRÉCISION` audit-IA) ·
`finlib/gate.py:142-145` (`_cond_validation_wag` teste encore `wag_validated_by`) · `financement_spec.json:157`. **Toutes exactes.**
- D-03/D-04 (inventaire) : `AGENTS_EXISTING_ASSETS.md:102/127/128` · `CLAUDE.md:28` · `activity_log/2026-08-03.md:1245-1250`/`:1251-1253` · filesystem (`chat.py`, `projets_editor.py` absents). **Toutes exactes.**
- D-05 (mobile) : `mobile_spec.json` (`a_confirmer`) · `out/app_config.json` (`bundleIdentifier`/`package` `null`) · `DIRECTIVE_MOBILE_STORES:15-16` (`com.otov7.app`). **Toutes exactes.**
**FIX — dérive latente réelle (en-tête, ligne 9).** Le `> **Portée**` gelait **deux compteurs
activement mobiles** : `33/33 gates` et `24 modules 4Big 100/100`.
- `33` : le compte de `gate.needs` est passé **32→33 il y a 2 jours** (`b7ffb64`) ; le README
**refuse délibérément** de le figer (« `gate.needs` dérivé de ci.yml, *sans liste en dur* »,
`README.md:28`). Le registre était le **seul** doc à le geler → à contre-courant de la discipline projet.
- `24` : historique `21→22→24` (commentaires de `check_readme_claims.sh`) ; ce chiffre est **déjà
gaté** (recomputé depuis `quality_report.json` totals pour le README + `audit_4big/README.md`).
Le registre en était une **3ᵉ copie ungated** → dérive silencieuse au prochain module ajouté.
- **Correction** (dé-figeage, pas gate #5) : reformulé en qualitatif — « tous les jobs de
`gate.needs` passent · tous les modules 4Big au seuil `100/100`, verdict `PASS` » + **pointeur**
vers les sources dérivées-et-gatées (`README ## État courant` + `quality_report.json`). Aligne le
registre sur la philosophie *no-hardcoded-count* du README ; discipline single-source, **pas** un gate redondant.
**Discipline.** Attestation confirmée AVANT tout edit (33/33 gates · 24 modules `100/100`/`PASS`
re-dérivés : `gate.needs`→33 · `quality_report.json` totals→`{modules:24, pass:24, min/max:100,
verdict:PASS}`). **0 édition d'autorité** (`CLAUDE.md`, directive, `pie_spec.json`, `gate.py`
intouchés) · **0 gate ajouté** (#5) · **0 chiffre inventé** (#6, on **retire** deux chiffres, on
n'en ajoute aucun) · **1 seule ligne** du registre modifiée. Items D-01..D-05 **inchangés** (la
provenance « consolidation » reste `014119` — ce reword ne touche aucun item).
**Vérifications.** `grep` compteurs `N/N`/`N modules` résiduels dans le registre → **aucun (clean)**.
`./run_ci.sh`**33 PASS · 0 FAIL · 0 SKIP**. Aucune commande VPS (#8) · aucun `git clean`. <!-- ci-allow : mention de l'interdit, jamais exécuté -->
## Session 021124 · `OPEN_DECISIONS_REGISTER` rendu découvrable — indexé dans la Navigation README + pointeur « État courant » (le punch-list launch-readiness n'était référencé nulle part) → 2 éditions README, 0 édition d'autorité
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**,
arbre propre, roadmap intégralement livrée/gatée. La session `014119` (juste avant) a créé
`05_deliverables_mvp/OPEN_DECISIONS_REGISTER.md` — une punch-list *launch-readiness* consolidée
« pour que Michel la trouve ». **Mais elle ne la référençait de nulle part** : `grep -rl
OPEN_DECISIONS_REGISTER --include=*.md` ne renvoyait **que** la ligne du journal `014119`.
Le README `## Navigation` indexe pourtant son sibling `GAP_ANALYSIS_SPRINT1.md` ligne 24. Un
punch-list que personne ne peut atteindre est de valeur nulle : **fermer cette boucle de
découvrabilité** est la valeur honnête distincte du jour (ni gate redondant #5, ni
re-surfaçage — le registre consolide déjà).
**Vérification AVANT promotion** (anti-invention #6 · on ne met pas en avant un doc non re-vérifié).
Re-prouvé indépendamment les claims-clés du registre au filesystem/source :
- D-01 : `pie_spec.json:24` attribue bien `contrats` (Promesa · Fideicomiso · HOA) à
`legal/confotur` — RÉEL.
- D-03 : `CLAUDE.md:28` liste `chat.py` ; `/opt/oto/otoia/capabilities/chat.py` **absent** — RÉEL.
- D-04 : `AGENTS_EXISTING_ASSETS.md:127` « projets_editor.py API en place » ; **aucun**
`projets_editor.py` sous `/opt/oto/config` — RÉEL (claim runtime VPS non réfutable, #8).
Registre jugé fidèle → promotion légitime.
**Éditions (2, README seul).**
1. `## Navigation` — nouvelle ligne juste après `GAP_ANALYSIS_SPRINT1.md`, pointant le registre
(description : punch-list décisions produit ouvertes qui appartiennent à Michel · sourcée,
ne tranche pas).
2. `## État courant (sourcé)` — bullet « Décisions produit ouvertes » : *CI verte ≠ zéro décision
en attente* → pointe le registre. **Sans compte chiffré en dur** (le nombre d'items dériverait ·
#6) : pointeur qualitatif uniquement.
**Discipline.** *SURFACE, don't rewrite* : **0 édition** de `CLAUDE.md`, d'une directive datée,
d'un `pie_spec.json`/`gate.py`, ni du registre lui-même. Aucun gate ajouté (#5). Les 2 lignes
README sont de la prose de navigation (pas de claim numérique gaté).
**Vérifications.** `ci/check_readme_claims.sh`**vert** (chiffres README inchangés, fidèles).
`ci/check_docs.sh`**vert** (nouveaux liens internes valides). `./run_ci.sh` → **33 PASS · 0
FAIL · 0 SKIP**. **0 chiffre inventé (#6) · 3 claims re-prouvés avant promotion · 0 édition
d'autorité · aucune commande VPS (#8) · aucun `git clean`.** <!-- ci-allow : mention de l'interdit, jamais exécuté -->
## Session 014119 · Registre des décisions ouvertes — consolidation sourcée des arbitrages produit surfacés au fil de l'eau (Sprint 8 launch-readiness) → 1 livrable-doc neuf · 0 édition d'autorité
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**,
arbre propre. Roadmap intégralement livrée/gatée. Les **6 dernières sessions** (`200738`
08-03 → `011114` 08-05) ont saturé l'audit read-only (inter-artefacts, dérive free-text,
attestation stakeholder, 8ᵉ gate mobile). Reproduire une 7ᵉ passe read-only aurait été à
faible valeur / redondant (#5). **Valeur honnête distincte du jour** : les items *réellement
ouverts* (arbitrages de périmètre produit + dépendances runtime hors dépôt) sont **éparpillés**
dans les journaux de session et n'existaient **nulle part de façon consolidée et découvrable**
pour Michel — un vrai trou de *launch-readiness* Sprint 8. J'ai créé
`05_deliverables_mvp/OPEN_DECISIONS_REGISTER.md` : une punch-list unique, **100 % sourcée**,
qui **regroupe** (ne tranche pas, ne réécrit aucune source d'autorité).
**Vérification indépendante de CHAQUE signal AVANT consolidation** (anti-invention #6 · deux
Explore parallèles + spot-check manuel). Aucun item repris de mémoire sans re-preuve :
- **D-01 PIE `contrats``legal/confotur`** : REAL. `pie/manifest/pie_spec.json:24` attribue
`contrats` (Promesa · Fideicomiso · HOA) à `legal/confotur` ; or ce module ne produit **que**
le DocType demande CONFOTUR (`legal/confotur/README.md:8-17`). Incohérence interne confirmée :
tous les autres downstreams sans générateur portent `"module": null` (`pie_spec.json:22,27-30`),
`contrats` est le seul attribué à un module qui ne le génère pas. Arbitrage de périmètre.
- **D-02 Financement condition #4 WAG→audit IA** : REAL. Directive `## PRÉCISION`
(`DIRECTIVE_FINANCEMENT_..._20260803.md:275-279`) supersede `wag_validated_by` par
`audit.decision=='APPROVED'` signé ; le module ship encore l'ancienne couche
(`finlib/gate.py:142-145` + `financement_spec.json:157`). Dépend d'un agent auditeur hors dépôt.
- **D-03 `chat.py` absent** / **D-04 `projets_editor.py` absent** : REAL, filesystem confirmé
(ni `.py` ni `.pyc` sous `/opt/oto`), **déjà surfacés** session `20260803_133718`
(`2026-08-03.md:1245-1253`) → registre = **pointeur** anti-doublon (#5), pas re-surfaçage.
- **D-05 bundle id mobile** : soupçon INVALIDÉ → 🟢 VÉRIFIÉ-SANS-SUITE. Le renommage
OTOV7→« OTO Enterprise OS » ne touche pas le bundle : `bundleIdentifier`/`package` quarantinés
`null · a_confirmer`, découplés du nom d'app (`mobile_spec.json`, `out/app_config.json`).
Consigné pour ne pas ré-ouvrir. Faux-positif écarté (#6).
**Discipline.** *SURFACE, don't rewrite* respecté : **0 édition** de `CLAUDE.md`, d'une directive
datée, ou d'un `pie_spec.json`/`gate.py` (les toucher serait invention de périmètre ou réécriture
d'autorité). Le registre est un doc-pointeur volontairement non-gaté (chaque ligne cite sa source
file:line, contenu 100 % dérivé — faible surface de dérive), au même titre que `daily_reports`.
**Vérifications.** `./run_ci.sh`**33 PASS · 0 FAIL · 0 SKIP** (inchangé — aucun fichier gaté
touché). **0 chiffre inventé (#6) · 1 faux-positif écarté (bundle id) · 4 signaux re-prouvés
indépendamment avant consolidation · 0 édition d'autorité · aucune commande VPS (#8) · aucun
`git clean`.** <!-- ci-allow : mention de l'interdit, jamais exécuté -->
## Session 001109 · Nouveau gate `check-mobile-workflow` — la structure de `mobile-build.yml` devient opposable au merge (surface mutation-testée UNGATED, 1er contrôle) → 1 gate ajouté + câblé · 32→33 jobs
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **32 PASS · 0 FAIL · 0
SKIP**, arbre propre. Roadmap `ROADMAP_8_WEEKS_OR_LESS.md` intégralement livrée/gatée
(8 sprints). Les 4 sessions du 08-04 (`221055`, `224101`, `231101`, `234104`) avaient
saturé les surfaces d'audit read-only (inter-artefacts, dérive non-gatée, attestation
stakeholder) puis **durci `mobile-build.yml`** (pattern preflight-output, portable
GitHub/Gitea). Cette dernière édition a laissé une **queue** : son propre log notait
« Aucun gate ne linte la structure de `mobile-build.yml` (`check-ci-integrity` ne
verrouille QUE `ci.yml`) ». Autrement dit le correctif de portabilité qu'elle venait
de poser **pouvait régresser en silence** — exactement la classe « marche dans ma
tête, casse le jour où on en a besoin » (mémoire [[mobile-build-secrets-if-gating]] :
« workflow HORS `gate.needs``run_ci` won't catch a regression »). La valeur honnête
du jour : **fermer cette queue** en rendant la structure de `mobile-build.yml`
opposable au merge. NON redondant (#5) : c'est le **premier** contrôle de ce fichier.
**Preuve d'absence de couverture AVANT d'ajouter le gate** (discipline
[[verify-uncovered-before-gating]]). Mutation : réintroduire l'exact anti-pattern
corrigé en `234104``secrets.EAS_TOKEN != ''` dans un `if:` au niveau job. Résultat :
**7/7 gates statiques restent VERTS**. Surface confirmée non gatée (ni
`check_ci_integrity` — scope `CI=ci.yml` — ni `validate_json` — scope `.json`). Fichier
byte-restauré aussitôt (`cp` depuis backup tmp · jamais `git clean`). <!-- ci-allow : mention de l'interdit, jamais exécuté -->
**Livrable : `ci/check_mobile_workflow.sh` (8ᵉ gate statique) + câblage.** Le gate lit
des **FAITS dans le fichier** (jamais une liste à la main · #6), 4 invariants :
- **MOB-1 · bien-formé** — existe · `name:`/`on:`/`jobs:` + 3 jobs (`preflight`,
`build-ios`, `build-android`) ; parse YAML strict **exigé si PyYAML dispo**, sinon
awk seul (runner sans pip → best-effort, jamais un faux vert : MOB-2/3/4 en sont
indépendants).
- **MOB-2 · portabilité** — **0** `secrets.` dans un `if:` de **job** (`^ if:`).
Le contexte `secrets` n'est pas exposé dans `jobs.<id>.if` (table d'availability
GitHub Actions) → sur `act`/Gitea il s'évaluerait vide → **SKIP silencieux**. C'est
la régression `234104` verrouillée.
- **MOB-3 · activation différée (#6/#8)** — chaque job de build (`run: eas build`) est
gaté sur `needs.preflight.outputs.has_token` → SKIP tant que le token absent, donc
le workflow ne rend jamais le CI rouge avant activation Michel.
- **MOB-4 · contrat d'outputs** — `preflight` déclare `has_token`/`has_repo`, chaque
`needs.preflight.outputs.<X>` référencé est **déclaré** (0 dangling) et chaque output
déclaré est **alimenté** par un `echo …>>$GITHUB_OUTPUT`.
**Bug corrigé pendant l'écriture (auto-vérification).** 1er jet : `preflight` était
classé « job de build » car un **commentaire** `# … eas build …` (l.108, entre deux
jobs) était attribué au job précédent par le parseur awk. Corrigé en exigeant
`run:[[:space:]]*eas build` (commande réelle, pas une mention). Après correctif : gate
**VERT** sur le fichier propre.
**Mutation-testing du gate lui-même (5 mutations · toutes ROUGES, fichier propre
re-VERT).** (1) YAML cassé (indent) · (2) `secrets.` en `if:` de job · (3) `if:` du
job `build-ios` supprimé · (4) output fantôme référencé (`has_tokenX`) · (5) output
`has_repo` non alimenté. Chaque mutation → `exit 1` ; `cp` backup → gate re-VERT.
**Câblage (garde `check-ci-integrity` INV-A/B vert).** Job `check-mobile-workflow`
ajouté à `ci.yml` (steps `bash ci/check_mobile_workflow.sh`) **et** à `gate.needs`.
`check_ci_integrity` reconnaît le nouveau `ci/*.sh` comme **câblé** (lancé par un job
du gate + il source `lib.sh`) → INV-A (gate.needs == tous jobs non-manuels) et INV-B
(tout `ci/*.sh` câblé) **restent verts**. `run_ci.sh` (dérivé de `gate.needs`, sans
liste en dur) l'inclut **automatiquement** → passage **32→33 jobs (8 gates statiques
+ 25 suites)**, zéro édition de `run_ci.sh` (#6, exactitude structurelle).
**Doc (traçabilité, pas de dérive).** `ci/README.md` : ligne §1 pour
`check-mobile-workflow`, sous-section §2 détaillée (4 invariants + les 5 mutations),
et nuance ajoutée à « Second workflow » (les **jobs** restent hors gate de merge, mais
la **structure du fichier** est désormais gatée). Convention [[ci-readme-table-detail-in-section2]]
respectée : ligne §1 courte, détail en §2. Les mentions historiques « 7 gates
re-verts » (§2, records de mutations passées) restent exactes en **passé** ; le nouveau
record dit **8ᵉ gate**.
**Vérifications.** `bash ci/check_mobile_workflow.sh` → VERT (4 invariants) ; 5
mutations → ROUGE ; `python3 yaml.safe_load` sur `ci.yml` → valide ; `check_ci_integrity`
→ VERT (câblage intègre) ; `./run_ci.sh`**33 PASS · 0 FAIL · 0 SKIP**. **1 gate
ajouté (NON redondant : 1re couverture d'un fichier mutation-testé ungated · pas un
doublon #5) · 1 job CI câblé · 0 nouveau module · 0 chiffre inventé (#6) · 0 édition
de production hors CI · aucune commande VPS (#8) · aucun `git clean`.** <!-- ci-allow : mention de l'interdit, jamais exécuté -->
---
## Session 004112 · Currency du canal stakeholder `daily_reports` au jalon 33 jobs + réattestation indépendante → CLEAN · 0 édition de production
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0
SKIP**, arbre propre. Roadmap intégralement livrée+gatée. La session `001109` (même
jour, plus tôt) a fait franchir au gate de merge le cap **32 → 33 jobs** (8ᵉ gate
statique `check-mobile-workflow`). Or le canal roadmap-facing `daily_reports/` s'arrêtait
au `2026-08-04` en affichant encore « **32 PASS** ». Les surfaces de dérive read-only
étant saturées (re-scan = redondant #5), la valeur honnête du jour est de **rendre le
canal stakeholder fidèle au point courant** — discipline [[two-logging-channels]] :
garder les deux canaux, chaque figure sourcée d'un artefact commité.
**Vérification préalable — pas de dérive présent-tense « 32 » résiduelle.** Balayage des
surfaces present-tense (`README.md`, `ci/README.md`) : **aucun** décompte de jobs figé en
dur (le nombre est **dérivé à l'exécution** par `run_ci.sh --list`, jamais retranscrit).
Les nombreux « 32 » des logs/daily_reports antérieurs sont des **snapshots datés** (vrais
à l'époque) → classe *historical-repro = KEEP* ([[prose-facts-vs-numeric-drift]]), on n'y
touche pas. Le « 7 gates statiques » de `ci/README.md` l.49 décrit l'état **avant** l'ajout
du 8ᵉ gate (narratif du mutation-test préalable) → exact en passé, KEEP.
**Livrable : `05_deliverables_mvp/daily_reports/2026-08-05.md`.** Snapshot stakeholder
consignant le jalon 33 jobs (= **8 gates statiques + 25 suites**) + réattestation. Chaque
chiffre **recomputé `python3` indépendamment** des artefacts commités (aucune valeur saisie
à la main · #6), tous concordants avec la table :
- `quality_report.json` : **24/24 modules == 100** · `coverage.ok=true` · ci==registry==24 · verdict `PASS`.
- `regression_run.json.totals` : **24 suites · 624 ran · 624 passed · 0/0/0** · verdict `PASS`.
- `acceptance_matrix.json` : **15/15 `status=in_repo`** · verdict `True` · **`ci_job` ×36** (niché `matrix[].modules[]`).
- Structure CI : `run_ci.sh --list` = **33 jobs = 8 statiques + 25 suites** · `ls ci/*.sh` (hors `lib.sh`) = **8**.
**Vérifications.** `./run_ci.sh`**33 PASS · 0 FAIL · 0 SKIP** (inchangé — livrable
prose, hors `check_artifacts`/`audit_4big`). Liens Markdown du rapport résolus depuis
`daily_reports/` : `../qa/…` (artefacts), `../../run_ci.sh`, `../../.gitea/…`, `../../ci/…`
`check_docs` VERT. **0 chiffre inventé (#6) · 0 édition de production · aucune commande
VPS (#8) · aucun `git clean`.** <!-- ci-allow : mention de l'interdit, jamais exécuté -->
---
## Session 011114 · Double sweep read-only sur les DEUX classes de dérive free-text ungated (docstring↔code des 22 générateurs · inventaire↔filesystem) → CLEAN · 0 édition de production · faux « nouveau finding » écarté (anti-invention #6)
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0
SKIP**, arbre propre. Roadmap `ROADMAP_8_WEEKS_OR_LESS.md` intégralement livrée+gatée
(8 sprints · 7 critères de succès ✅). Les deux sessions du jour ont déjà (a) ajouté le
8ᵉ gate statique `check-mobile-workflow` (`001109`, 32→33 jobs) et (b) mis le canal
stakeholder `daily_reports` au jalon 33 jobs avec réattestation des 4 chiffres (`004112`).
Les surfaces d'audit inter-artefacts / dérive gatée / attestation sont **saturées**
(re-scan = redondant #5). La valeur honnête restante : re-vérifier les **deux classes de
dérive free-text NON gatées** que la mémoire marque comme récurrentes/non-fermées, avant
d'envisager quoi que ce soit — discipline [[verify-uncovered-before-gating]] (prouver
l'état AVANT d'agir) + anti-invention #6.
**Sweep 1 — docstring↔code des 22 générateurs ([[docstring-vs-code-drift]], classe
RECURRING/not-closed).** Pour chacun des 22 `*_gen.py` de `05_deliverables_mvp/*` :
comparaison du **docstring/en-tête** (inputs annoncés · fichiers `out/` produits ·
fallbacks) au **code réel** (argparse · `open(...,"w")` / `json.dump` / `write_text` ·
branches de fallback). Résultat : **22/22 CLEAN** — chaque docstring décrit exactement les
sous-commandes, les inputs et les artefacts écrits. Spot-check manuel des deux générateurs
multi-sorties les plus risqués (au-delà de la lecture agent) :
- `demo/scenarios/demo_scenario_gen.py` — docstring l.20/l.32 annonce `run_sheet.json` +
`run_sheet.md` + `MANIFEST.json` → code écrit bien les 3 (l.197-199 JSON, l.203-204 `.md`). ✓
- `faisabilite/bancable/bancable_gen.py` — docstring `50_financier_bancable/{fr,en,es}.md`
→ écrits l.171-173. ✓
Aucune régression du type « fallback §5.3 fantôme » (publiciste) ni « output omis »
(demo run_sheet.md) qui avaient été corrigés antérieurement : la classe reste propre.
**Sweep 2 — inventaire `AGENTS_EXISTING_ASSETS.md` ↔ filesystem `/opt/oto`
([[inventory-vs-filesystem-drift]]).** Extraction de tous les chemins `/opt/oto/*` cités
(38 uniques), classification **SRC-OK / PYC-ONLY / MISS** (avec check `__pycache__`) :
- **8 PYC-ONLY** (`oto_agent_faisabilite`, `oto_orchestrateur_faisabilite`,
`oto_module_{etudes_faisabilite_real,execution_faisabilite,faisabilites_pages,faisabilites_viewer,mobile_api,plans_ifc}`)
**tous** déjà annotés « (compilé pyc) » dans le doc : convention interne **cohérente**,
0 dérive.
- **2 MISS** : `otoia/capabilities/chat.py` (absent vs CLAUDE.md §Architecture cible qui le
liste) et `config/projets_editor.py` (§13, « API GET/POST déjà en place » — sibling
`projets_config.json` présent, lui). **Vérification anti-invention #6 AVANT de flaguer :**
`grep -rn` interne → **les deux MISS sont DÉJÀ surfacés à Michel** par la session `133718`
du **2026-08-03** (log l.1245/1284/1289-1295 : claims VPS-externes / non-réfutables laissés
ouverts, chat.py ≠ le module chatbot-lead distinct). Donc **PAS un nouveau finding**
re-signaler serait redondant (#5). Le reste (containers Docker, Speckle, systemd, GSC) est
runtime, non réfutable depuis le dépôt → hors périmètre worker (#8). **SURFACE, don't
rewrite** respecté : 0 édition de l'inventaire (le fichier peut viser un runtime
container-side ; un « fix » unilatéral serait une invention).
**Conclusion honnête.** Les deux seules classes de dérive free-text ungated sont **stables
et propres** à ce jalon ; la dimension chiffres-README est densément gatée (`check_readme_claims`
= 27 claims / 16 modules + fiches). Rien à corriger, **aucun gate à ajouter** (ajouter serait
un doublon #5 ; par ailleurs un gate lisant `/opt/oto` serait non-portable — le runner Gitea
n'a pas ce filesystem, cf. [[ci-gate-verification-method]]). Session **read-only** volontaire,
dans la lignée des attestations `234104`/`004112`, mais sur un **axe distinct** (les 2 classes
free-text, pas les 4 chiffres stakeholder ni le cross-artefact).
**Vérifications.** `./run_ci.sh`**33 PASS · 0 FAIL · 0 SKIP** (inchangé — aucun fichier
gaté touché). **0 chiffre inventé (#6) · faux nouveau-finding écarté par grep interne · 0
édition de production · 0 gate ajouté (anti-redondance #5) · aucune commande VPS (#8) · aucun
`git clean`.** <!-- ci-allow : mention de l'interdit, jamais exécuté -->
## Session 054200 · Currency du canal STAKEHOLDER : consigner la clôture des DEUX axes de « dents » dans `daily_reports/2026-08-05.md` · 0 édition de production
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**,
arbre propre. Roadmap intégralement livrée+gatée côté repo (Sprint 8 buffer). Les surfaces
de dérive usuelles sont saturées, donc j'ai d'abord **cherché une vraie régression**, pas une
attestation redondante (#5) :
1. **Chasse docstring↔code drift** (classe documentée RÉCURRENTE/non-close — a déjà trouvé le
« fallback §5.3 fantôme » publiciste + l'output `run_sheet.md` omis). Balayage des **21
générateurs** (docstring/commentaires vs fichiers réellement écrits : `open`/`write_text`/
`json.dump`). **Résultat : 0 contradiction réelle.** Les « écarts » remontés (docstrings
sans liste-de-sorties en puces) étaient des **faux positifs** : `commissions_gen.py` documente
bien ses sorties dans le bloc *Sous-commandes* (`build → écrit commission_plan.json +
MANIFEST.json`), simple placement différent de `financement` (7 sorties → liste séparée).
Uniformiser serait de la **pure churn** (#5) — écarté.
2. **Seule action honnête + non-redondante trouvée : currency du canal stakeholder.** Le
`daily_reports/2026-08-05.md` (session `004112`, matin) s'arrête au jalon **33 jobs** et
**précède** toute la série d'attestation *teeth* des **suites de MODULE** (BATCH 1→4,
commits `034144``051154`, clôture **25/25**) jouée plus tard le même jour. Le canal
stakeholder ([[two-logging-channels]]) ne captait donc **aucun** des deux axes de « dents ».
**Fait.** Ajout d'une section « Mise à jour fin de journée » au rapport du jour, consignant la
**clôture des deux axes** : (A) **8/8** gates statiques (`9fe8bf1`/`031134`) · (B) **25/25**
suites de module (`6+6+6+7`, `98f3f56`/`d25da28`/`e38dd93`/`986d293`). Chaque chiffre **sourcé
sur une structure commitée** (`ls ci/*.sh` hors `lib.sh` = 8 · `gate.needs` = 25 · série de
commits) — **0 saisi à la main** (#6). Message stakeholder explicité : les tables de currency
prouvent que la CI est **verte** ; les attestations *teeth* prouvent qu'elle **mord** (un gate/
suite toujours-vert = faux vert invisible aux tables).
**Vérifications.** `./run_ci.sh`**33 PASS · 0 FAIL · 0 SKIP** (seul fichier touché =
`daily_reports/2026-08-05.md` + ce log ; aucun fichier de code gaté). **0 chiffre inventé
(#6) · faux findings docstring écartés · 0 édition de production · 0 gate ajouté (#5) · aucune
commande VPS (#8) · aucun `git clean`.** <!-- ci-allow : mention de l'interdit, jamais exécuté -->
## Session 061201 · Sweep REPO-WIDE de la classe « chemin fantôme en code-span backtick » (ungated, documentée récurrente) → 0 fantôme réel · 0 édition de production
**Choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**, arbre propre,
roadmap intégralement livrée+gatée (Sprint 8 buffer). Plutôt qu'une n-ième attestation
redondante (#5), j'ai d'abord voulu **fermer un axe encore incomplet**. Deux axes de câblage
CI vérifiés d'abord et confirmés **structurellement clos** : (a) parité local↔CI —
`run_ci.sh` **dérive** sa liste de `gate.needs` (aucune liste en dur), et
`check_ci_integrity.sh` verrouille `ci.yml` comme source de vérité ; (b) **gate orphelin**
`check_ci_integrity` INV-B itère `git ls-files 'ci/*.sh'` et exige que chaque script soit
lancé par un job du gate **ou** sourcé par un gate lancé → un `ci/*.sh` mort/décâblé casse la
CI. Rien à ajouter (#5).
**Axe réellement balayé (nouveau périmètre).** La classe **« chemin in-repo fantôme dans un
code-span backtick »** est **ungated à dessein** (les backticks sont neutralisés par
`check_docs` — cf. [[backtick-path-escapes-check-docs]] ; « ne pas gater tous les backticks »).
L'audit d'exactitude des fiches ([[fiche-accuracy-audit-closed]]) ne couvrait que les **13
`03_agents/*/AGENT.md`**. Je l'ai **étendu à TOUT le markdown suivi** (~200 fichiers :
READMEs de modules, `daily_reports/`, `04_roadmap/`, `ci/README.md`, DIRECTIVE_*, GAP_ANALYSIS,
logs) via un harnais qui extrait chaque token backtické contenant un `/` et le résout contre
**quatre ancres** : dossier du doc, racine du dépôt, `05_deliverables_mvp/`, et chaque
sous-module `05_deliverables_mvp/*/`.
**Résultat : 0 fantôme réel.** Les ~180 tokens remontés se ventilent en **trois formes
bénignes**, chacune vérifiée sur échantillon :
1. **Runtime/VPS hors-dépôt** (`/static/`, `otoia/capabilities/*`, `www/`, `seo_autonome/`,
`data_room/PXX/`) — non réfutables depuis le dépôt (#8), déjà connues.
2. **Citations cross-module en prose** — un README/rapport nomme le fichier d'un AUTRE module
par son nom court. Vérifié : `crm/commissions/README.md` cite `banclib/finance.py` **à
l'intérieur d'un vrai lien Markdown** dont la **cible existe et EST validée par
`check_docs`** (le backtick n'est que le libellé) ; `banclib`/`wflib`/`cflib` résolvent tous
dans le dossier de leur module propre.
3. **Narratif d'historique + non-chemins pédagogiques** — les `05_activity_log/*` antérieurs
citent des fichiers par nom court (prose, pas navigation), et certains chemins **n'existent
pas EXPRÈS** : `./inexistant-xyz-123.md` / `./this/does/not/exist.md` sont les liens cassés
d'exemple prouvant que `check_docs` **mord**, et `/tmp/suite_teeth_*.sh` sont les harnais de
mutation-test éphémères (`/tmp` read-only). Correct par conception.
**Conclusion honnête.** L'axe fantôme-backtick est **propre repo-wide** (extension du 13→~200
`.md`), les deux axes de câblage CI sont clos, les axes *teeth* (8/8 statiques · 25/25 suites)
et free-text/chiffres étaient déjà saturés. **Aucun défaut réel trouvé → 0 édition de
production** (churner un doc exact serait de la dérive #5) et **0 gate ajouté** (gater les
backticks est explicitement rejeté, et un gate lisant `/opt/oto` serait non-portable côté
runner Gitea — cf. [[ci-gate-verification-method]]). Seule sortie : ce log + une note mémoire
pour que la prochaine session ne rejoue pas ce sweep à froid ni ne re-flague ces formes.
**Vérifications.** `./run_ci.sh`**33 PASS · 0 FAIL · 0 SKIP** (seul fichier touché = ce
log ; aucun fichier de code gaté). **0 chiffre inventé (#6) · faux positifs backtick écartés
par résolution multi-ancre + inspection · 0 édition de production · 0 gate ajouté (#5) ·
aucune commande VPS (#8) · aucun `git clean`.** <!-- ci-allow : mention de l'interdit, jamais exécuté -->
---
## Session 20260805_064201 · FIX RÉEL — la fenêtre de sprint *roadmap-globale* de `faisabilite/generator` était fausse (S2 → S3)
**Défaut trouvé (première vraie édition de production depuis plusieurs sessions de sondes « 0 édition »).**
Le registre de l'auditeur 4Big attribuait au **générateur de faisabilité 4 volets** une fenêtre
de sprint **roadmap-globale erronée** :
- `05_deliverables_mvp/qa/audit_4big/quality_spec.json:66`
`"sprint": "S2"`, `"source": "roadmap Sprint 2 · Faisabilité générateur 4 volets …"`.
Or la roadmap **ne contient AUCUNE faisabilité au Sprint 2** (`04_roadmap/…:35-39` = Console /
CRM / RBAC). La **« génération 4 volets <1h »** est explicitement au **Sprint 3**
(`…:43`, livrable `…:46` « P07 Aqua Terra faisabilité complète auto-générée »). La citation
`source` était donc **falsifiable et fausse** (classe [[prose-facts-vs-numeric-drift]] · sous-classe
*citation mal-attribuée* + [[roadmap-anchor-gate]]).
**Cause racine — confusion de deux cadres de « sprint ».**
1. **Cadre agent-interne** (phases de construction par agent) : `03_agents/faisabilite/AGENT.md`
§130-133 définit S1 = template · **S2 = générateur 4 volets** · S3 = version tracking — le tout
**ancré à la roadmap Sprint 3** (L41-46). Ce cadre est repris tel quel dans les docs de module
(`generator/README.md` H1 « Sprint 2 », `TEMPLATE_…:216`, `GAP_ANALYSIS_SPRINT1.md:74-75`,
`publiciste/README.md:26`, `daily_reports/*`). **Cohérent dans son cadre — laissé intact.**
2. **Cadre roadmap-global** (autorité de la recette) : le champ `sprint` de `quality_spec.json`
**toutes** les autres entrées le sourcent « roadmap Sprint N ». C'est le **seul** endroit qui
doit porter le sprint roadmap-global. Il avait recopié le label agent-interne « S2 ».
**Conséquence corrigée.** Le livrable-phare **S3** « P07 faisabilité complète auto-générée »
était prouvé **sans** le module qui la génère (`faisabilite/generator`), tandis que ce dernier
était compté comme preuve du **S2** (« 7 dashboards · CRM · RBAC ») — auquel il n'a **aucun**
rapport. La preuve la plus directe du livrable-phare était mal-classée.
**Correctif (édition coordonnée 2-spec + régénération byte-gatée).**
- `quality_spec.json:66` : `"sprint"` **S2 → S3**, `source` recitée « roadmap Sprint 3 · Faisabilité
générateur 4 volets → data_room/PXX » (aligné sur le style de son jumeau `bancable`, ligne 71,
déjà S3).
- `acceptance_spec.json` : `faisabilite/generator` **retiré** de `evidence_modules` S2, **ajouté**
à S3 (`["faisabilite/bancable", "faisabilite/generator"]`) — sinon l'**invariant 5 « partition
exacte »** casse (`want` lu dans `quality_spec``have` déclaré). Édition des DEUX specs
obligatoire et suffisante.
- Régénération : `audit_4big` (report) **puis** `acceptance` (lit la fenêtre depuis `quality_spec`).
**Vérifications.** `partition_ok=True · bijective=True` · S2 = 6 modules (publiciste + 5 rbac,
exact) · S3 = {bancable, generator} (exact). `./run_ci.sh`**33 PASS · 0 FAIL · 0 SKIP**.
Score 4Big du générateur **inchangé 100/100** (le champ `sprint` ne sert qu'au tri + méta du
report, pas d'oracle de scoring vérifié dans `q4lib/scoring.py`).
**Portée honnête / anti-churn.** **5 fichiers** touchés (2 specs + 3 artefacts régénérés) + ce log.
**Zéro** doc de module éditée : les ~10 « S2 » du générateur ailleurs sont le **cadre agent-interne**
(ancré Sprint 3), corrects — les churner serait de la dérive #5. `daily_reports/*` = historique,
non réécrit. **0 gate ajouté** (surface déjà gatée par invariant 5 · [[verify-uncovered-before-gating]]),
**0 chiffre inventé** (#6, tout dérivé du roadmap), **0 commande VPS** (#8), **aucun `git clean`**. <!-- ci-allow : mention de l'interdit, jamais exécuté -->
---
## Session 20260805_074202 · FIX de COUVERTURE — le livrable transverse `demo/scenarios` manquait des DEUX fiches co-porteuses
**Classe** : complétude ≠ exactitude ([[fiche-accuracy-audit-closed]]) — même classe que
le FIX précédent (CRM omettait `financement_bancaire`). Méthode : diff
`05_deliverables_mvp/*/` (modules à générateur + tests + `out/`) vs les tables de
livrables des 13 fiches `03_agents/*/AGENT.md`.
**Défaut réel trouvé.** Le module **`05_deliverables_mvp/demo/scenarios/`** — livrable
bel et bien produit (générateur `demo_scenario_gen.py` · **39 tests** · `out/{run_sheet,MANIFEST}` ·
README · déjà gaté par `check_readme_claims` sur son propre README + job CI
`demo-scenario-tests`) — n'était recensé dans **AUCUNE** des 13 fiches agents (grep
`demo|scenario` sur `03_agents/*/AGENT.md` = 0 hit). C'est un livrable **transverse
sans agent propre** : son README le cadre « Sprint 7 (CRM + Faisabilité) » et la roadmap
L68 l'assigne explicitement à **CRM + Faisabilité**. Les DEUX co-porteurs l'omettaient.
**Correctif — 2 fiches, une seule source gatée (anti-duplication #5).**
- **Fiche CRM** (`03_agents/crm/AGENT.md`) : nouvelle sous-section « Livrable transverse ·
Sprint 7 — scénarios démo », avec table 1-ligne **auto-gatée** (mirroir du précédent
`financement_bancaire`). La ligne pointe `…/demo/scenarios/README.md` et finit `| 39 |`
→ captée par le `row_re` de `check_readme_claims` (label `\w+/` = `scenarios/`, chemin
résolu `demo/scenarios``plan.suites`) → cellule Tests **recomputée** depuis
`regression_plan.json`. Framée honnêtement : **pas un livrable CRM propre** mais un
méta-générateur qui **compose** les livrables amont, co-porté, **ne partage aucune
source** avec le trio ni le financement.
- **Fiche Faisabilité** (`03_agents/faisabilite/AGENT.md`) : **cross-référence en prose**
(pas de chiffre redupliqué · #5) vers la fiche CRM + le README du module, mentionnant
que `bancable` alimente le scénario `S-P07-BANQUIER`.
**Auto-gates confirmés (3 axes indépendants, la ligne CRM franchit chacun).**
1. `Fiche 03_agents/crm/AGENT.md · demo/scenarios — Tests 39 == source (39)` (recompute
`regression_plan.json`).
2. `Entrée CLI · demo_scenario_gen.py — verbes build|validate == subparsers`.
3. `Traçabilité roadmap · demo/scenarios — citation l.68 == roadmap`.
Job CI `demo-scenario-tests` == ci.yml. `check_docs` : cross-refs `../crm/AGENT.md`
résolus OK.
**Portée honnête / anti-churn.** **2 fichiers** touchés (2 fiches) + ce log. **0 gate
ajouté** (surface déjà couverte par le `row_re` générique des cellules Tests ·
[[verify-uncovered-before-gating]] · [[doc-numeric-claims-gate]]) — la nouvelle ligne
*hérite* du gate existant. **0 chiffre inventé** (#6 · 39 dérivé de la source unique).
Le trio CRM + « Total CRM 81 tests » + le financement laissés **INTACTS** (demo hors-trio,
source distincte). **0 production éditée** (#5), **0 commande VPS** (#8), **aucun
`git clean`**. `./run_ci.sh`**33 PASS · 0 FAIL · 0 SKIP**. <!-- ci-allow : mention de l'interdit, jamais exécuté -->
---
## Session 20260805_084202 · FIX RÉEL — « 6 sections » périmé dans les 2 docs d'implémentation du module `financement_bancaire` (le module en livre 7)
**Classe** : prose FACT count périmée, ungated ([[prose-facts-vs-numeric-drift]] ·
[[doc-numeric-claims-gate]]) — distincte des comptes gatés.
**Défaut réel trouvé.** Deux surfaces d'implémentation décrivant le module livré
`crm/financement_bancaire` affirmaient **« 6 sections »** alors que le module en livre
**7** :
- `05_deliverables_mvp/crm/financement_bancaire/README.md:9` — « la structure des 6 sections »
- `03_agents/crm/AGENT.md:47` — « cœur du gate + 6 sections + bannière »
**Preuve du bon compte = 7** (triple-sourcé, byte-gaté) :
- `out/MANIFEST.json``counts.sections = 7` (artefact byte-gaté par `check_regression`/`check_artifacts`).
- `financement_spec.json` → bloc `sections` = 7 (`apport_initial`, `info_achat`,
`choix_banque`, `formulaires`, `exigences`, `autorisations`, `envoi`).
- Le README **lui-même** se contredisait : ligne 49 énumère les **7** sections de
l'accordéon, ligne 57 « **7 sections** » (annotée « source de vérité = MANIFEST.json »).
Seule la ligne 9 (prose libre) était restée à « 6 ».
**Origine du « 6 ».** La `DIRECTIVE_FINANCEMENT_BANCAIRE_COMPLET` originale (L106)
énumère exactement **6 sections non-gating** (`Info achat / Choix banque / Formulaires /
Exigences / Autorisations / Envoi`), **puis** a été **amendée** pour ajouter la section
gating `apport_initial` en position 1 → **7** au total. La spec + le module ont suivi (7),
mais les 2 phrases de prose d'implémentation avaient gardé le « 6 » d'avant l'amendement.
**Correctif — 2 surfaces d'implémentation, 6→7. Directives laissées INTACTES.**
Les **4** occurrences « 6 sections » restantes vivent dans des **snapshots directive**
gelés (`DIRECTIVE_FINANCEMENT…` L106/L122/L131 + `DIRECTIVE_WORKFLOW_FAISABILITE_V10`
L22/L47) — **input-specs datés de Michel**, jamais réécrits ([[directive-vs-implementation]] ·
`spec = authority, directive = snapshot`). Leur « 6 » est **historiquement exact** au
cadre pré-amendement (énumération des 6 aval). Seules les 2 surfaces décrivant le
**livrable** sont corrigées.
**Aucun gate ajouté (#5).** Un gate structuré sur la ligne « Comptes courants »
recomputerait la ligne 57 (déjà correcte, adossée à MANIFEST) mais **ne mordrait pas**
la prose libre de la ligne 9 — la classe réelle du défaut. Financement reste le seul
module dont `MANIFEST.counts` n'est pas recompté dans `check_readme_claims` ; **SIGNAL
surfacé** (couverture prose ungated), pas d'ajout ce tour pour éviter un gate fragile +
hors-cible. **0 chiffre inventé** (#6 · 7 dérivé de MANIFEST/spec), **0 production éditée**
(#5), **0 commande VPS** (#8), **aucun `git clean`**. `./run_ci.sh`**33 PASS · 0 FAIL · 0 SKIP**. <!-- ci-allow : mention de l'interdit, jamais exécuté -->
---
## Session 20260805_101214 · FIX RÉEL — la fiche MOBILE présentait `oto_module_mobile_api.py` comme source alors que seul le `.pyc` existe (annotation « (compilé pyc) » perdue vs sa propre source citée)
**Classe** : `inventory-vs-filesystem-drift` — un claim d'existence d'asset dérive du filesystem VPS réel ; action sanctionnée = **annoter pyc-only selon la convention du doc lui-même** (distincte des claims chat.py/VPS-API qu'on SURFACE sans réécrire → registre D-03/D-04).
**Défaut réel trouvé.** La table « Modules OTOV7 réels refactorés » de `03_agents/mobile/AGENT.md:76`
cite **explicitement** sa source `AGENTS_EXISTING_ASSETS.md §8`, mais la 1re ligne présentait
`/opt/oto/oto_module_mobile_api.py` comme un module source **sans qualificatif**, alors que :
- **Sa source citée** l'annote `(compilé pyc)` : `AGENTS_EXISTING_ASSETS.md:59`
`- /opt/oto/oto_module_mobile_api.py (compilé pyc)`. La fiche avait **perdu** l'annotation.
- **Le filesystem confirme** : aucun `.py` à ce chemin ; seul `/opt/oto/__pycache__/oto_module_mobile_api.cpython-310.pyc`
(13 Ko, root, 2026-07-02) existe. Classé **PYC-only** (vs les 4 autres lignes de la table —
`oto_module_mobile_download.py`, `deploy_mobile_rbac.sh`, `seed_mobile_rbac.py`, `oto_mobile.js`
toutes **SRC présentes**, correctement laissées sans annotation).
- **La convention sœur** confirme le format : `AGENTS_EXISTING_ASSETS.md §9` annote de la même
façon `- /opt/oto/oto_module_plans_ifc.py (compilé pyc)`.
**Correctif — 1 ligne, 1 fichier.** Ajout de l'annotation `*(compilé pyc)*` à la seule ligne
concernée, alignant la fiche sur (a) sa source citée §8, (b) le filesystem réel, (c) la
convention §9. Les 4 autres lignes (sources réelles) laissées **INTACTES** — pas de sur-annotation.
**Portée honnête / anti-churn.** **1 fichier** touché (1 fiche) + ce log. **0 gate ajouté** (#5) —
la couverture de ce claim d'existence porte sur un chemin **VPS hors-repo** non vérifiable par un
gate dépôt (#8) ; l'annotation aligne la prose sur la source in-repo `AGENTS_EXISTING_ASSETS.md`,
elle-même correcte. **0 chiffre inventé** (#6). **0 production éditée** (#5), **0 commande VPS**
(#8), **aucun `git clean`**. `./run_ci.sh`**33 PASS · 0 FAIL · 0 SKIP**. <!-- ci-allow : mention de l'interdit, jamais exécuté -->
**Audits négatifs consignés (deux passes Explore + vérifs manuelles, RAS).** Comptes/tests :
les 15+ « N tests/sections/invariants/rôles » restent synchronisés (ex. `ran=624` recomputé =
650 méthodes 26 `qa.regression` self-test = 624 ✓ · `24 suites` matrice = 25 module-suites
gate.needs 1 self-test ✓). Docstrings-vs-code : les 25 générateurs déclarent exactement
leurs sorties/verbes CLI. Citations verbatim & « gaté par X » : échantillon roadmap L29/L38/L46/
L51/L63/L73 vérifié exact. Seul défaut concret = l'annotation pyc ci-dessus.
---
## Session 121234 · RÉ-ATTESTATION indépendante du canal stakeholder (daily_report launch-readiness) → **CLEAN** · 0 édition de production
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**, arbre propre.
Roadmap `ROADMAP_8_WEEKS_OR_LESS.md` intégralement livrée+gatée in-repo ; les restes S3/S5/S6 (refactor
BIM `aec.py`, builds EAS mobile, dépôts ONAPI, déploiement prod VPS) sont **hors périmètre worker**
(`CLAUDE.md` #8 · VPS `153.75.250.214`). <!-- ci-allow : mention de l'interdit, jamais exécuté -->
Le daily_report `2026-08-05.md` (sessions 004112+054200) désigne comme prochaine tâche « maintenir la
currency des deux canaux + veille d'intégrité sur surfaces neuves » ([[two-logging-channels]]). Choix :
**re-attester par recompute `python3` indépendant** que **chaque** chiffre publié dans la table
launch-readiness recharge à l'identique depuis les artefacts commités (pas un écho de la table) — les
classes de dérive prose/count/link usuelles étant saturées et gatées (#5, ne pas re-signaler).
**Recompute indépendant — 5 dimensions, toutes concordantes (aucune saisie manuelle · #6) :**
| Dimension | Publié (daily_report) | Recompute ce jour | = ? |
|---|---|---|---|
| Qualité 4Big | 24/24 modules à 100/100 · `coverage.ok=true` · ci==registry==24 · `PASS` | `quality_report.json` : 24 modules, **tous == 100**, `coverage.ok=true`, ci=registry=24, verdict `PASS` | ✅ |
| Régression (run) | 24 suites · 624 ran · 624 passés · 0/0/0 | `regression_run.json.totals` : suites 24 · ran 624 · passed 624 · failures/errors/skipped 0/0/0 · green 24 red 0 | ✅ |
| Recette roadmap | 15/15 `status=in_repo` · 36 `ci_job` → 0 orphelin | `acceptance_matrix.json` : `matrix` len **15** ; **36** refs `ci_job` (24 uniques) · **0 orphelin** vs les 33 vrais jobs `run_ci --list` | ✅ |
| Structure gate | 33 jobs = 8 statiques + 25 suites | `run_ci.sh --list` : **33 jobs** (footer dérivé) = 8 gates `ci/*.sh` (hors `lib.sh`) + 25 suites module | ✅ |
| Miroir local | `run_ci.sh` → 33 PASS · 0 FAIL · 0 SKIP | idem, inchangé | ✅ |
**Contrôle nouveau (léger renfort, non publié auparavant).** Vérification **bidirectionnelle** du
non-orphelinage des `ci_job` de la recette : les **36** références (24 uniques, niché dans
`matrix[].modules[]`) mappent **toutes** vers un job réel de `gate.needs` (0 orphelin dans les deux
sens). Recoupé au décompte du gate lui-même (`ls ci/*.sh` hors `lib.sh` = **8** ; `run_ci --list` = **33**).
**Aucun gate ajouté**`check_ci_integrity` couvre déjà l'intégrité gate↔artefacts (#5, [[verify-uncovered-before-gating]]).
**Portée honnête / anti-churn.** **0 fichier de production édité** (#5), **0 gate ajouté** (#5),
**0 chiffre inventé/saisi** (#6 — tout dérivé de `json.load`/`ls`/`--list`), **0 commande VPS** (#8),
**aucun `git clean`** (#1). <!-- ci-allow : mention de l'interdit, jamais exécuté --> Le daily_report
est **exact et courant** → laissé intact (l'éditer serait du churn). Seule édition : ce log. `./run_ci.sh`**33 PASS · 0 FAIL · 0 SKIP** (inchangé).
---
## Session `20260805_124241` — FIX test-precision : constante `18` périmée + commentaire faux dans `test_audit_4big.py`
**Surface NEUVE (hors classes saturées).** Chasse ciblée aux surfaces non encore auditées
(le canal usuel README/counts/docstrings/fiches est saturé, #5). Trouvaille réelle, pas un
faux-positif : `05_deliverables_mvp/qa/audit_4big/tests/test_audit_4big.py`
`test_failing_module_forces_global_fail` figeait `bad["totals"]["pass"] = 18 - fail`.
**Le défaut.** Le périmètre audité a grandi à **24 modules** (`totals.modules == 24`, tous
100/100), mais le test gardait l'entier **`18`** codé en dur. Résultat : le rapport « bad »
qu'il fabrique avait `pass=17, fail=1, modules=24` → il déclenchait **INV7** (visé) MAIS
AUSSI **INV8 `pass+fail ≠ modules`** (18≠24) *et* **INV8 `min/max score incohérents`**
(module passé sous 95 alors que `min_score` restait 100). Or son propre commentaire affirmait
« *Le rapport est cohérent en interne mais porte un FAIL → INV7 le signale* » — **affirmation
fausse** : le rapport n'était PAS cohérent en interne. Le test passait quand même car son
assertion (`any("< seuil 4Big")`) restait vraie parmi les erreurs parasites → test **relâché**,
passant pour de mauvaises raisons, avec une constante latente qui pourrit en silence.
**Preuve du diagnostic (recompute indépendant).** Rejeu de l'ancien setup `18`
`check_invariants` renvoie exactement `[INV7 publiciste …, INV8 pass+fail ≠ modules, INV8
min/max score incohérents]`. La nouvelle assertion `all("INV7" in e)` **échouerait** sur ce
setup → elle a de vraies dents (pas tautologique).
**Le fix (test uniquement, 0 production).** Totaux dérivés du **vrai** `bad["modules"]` au lieu
d'un entier figé :
- `pass = len(modules) - fail` (robuste à tout futur changement de décompte, #6 — aucun
chiffre saisi à la main)
- `min_score/max_score = min/max(scores recomputés)`
Le rapport « bad » devient **réellement cohérent en interne** → INV8/INV9 ne mordent plus,
**seul INV7** signale le module sous seuil — exactement ce que le commentaire prétend. Assertion
resserrée de `any("< seuil 4Big")` (relâchée) vers `errs non vide` + `all("INV7")` + `any("< seuil
4Big")` : prouve désormais l'**isolation** d'INV7 comme unique déclencheur.
**Portée / anti-churn.** **0 fichier de production édité** (seul un fichier de test), **0
artefact reconstruit** (aucun `out/*.json` touché → pas de rebuild `quality_report`, cf.
[[audit4big-rebuild-after-doc-edits]]), **0 gate ajouté** (#5), **0 chiffre inventé** (#6
tout dérivé de `len()`/`min`/`max`), **0 commande VPS** (#8), **aucun `git clean`** (#1). <!-- ci-allow : mention de l'interdit #1, jamais exécuté (l'escape doit être SUR la ligne fautive, scan ligne-par-ligne) -->
**Vérif.** `python3 -m unittest discover -s tests`**34 tests OK** ; `./run_ci.sh` → **33
PASS · 0 FAIL · 0 SKIP**. Distinct de la classe *toothless-test/inert-mutation*
([[module-suite-teeth-mutation]]) : ici la LOGIQUE testée est saine — c'est le HARNAIS de test
(données figées) qui avait dérivé.
---
## Session 20260805_131244 · FIX régression gate `constraints-guard` (tree committé RED malgré « 33 PASS » annoncé)
**Constat.** Au démarrage, `./run_ci.sh` sur le tree committé (HEAD 127ef6f) → **32 PASS · 1
FAIL** : job `constraints-guard` ROUGE. Le commit 127ef6f annonçait pourtant « 33 PASS CI » —
il a vérifié les gates AVANT d'avoir son propre journal committé (réflexe [[ci-gate-verification-method]]
non tenu : re-jouer les gates sur le tree AVEC le log staged).
**Cause racine — 3e RÉCURRENCE de la sous-classe `aucun git clean`** (cf. <!-- ci-allow -->
[[guard-constraints-log-prose]]), mais NOUVELLE variante. Ligne 1206 :
`… **0 commande VPS** (#8), **aucun \`git clean\`** (#1).` — le mot `git clean` sans marqueur de <!-- ci-allow -->
prohibition (« aucun » n'est pas dans la regex PROHIBITION). Le journaliste précédent AVAIT bien
posé un `<!-- ci-allow -->`… mais sur la ligne 1207 (paragraphe hard-wrappé). Or `scan_forbidden`
fait `grep -in` **ligne par ligne** : l'escape doit être SUR la ligne fautive, pas sur une voisine.
Donc l'échappatoire ne couvrait rien.
**FIX (journal only, 0 prod, 0 artefact).** Escape déplacé/fusionné INLINE sur la ligne 1206 elle-même
(`… (#1). <!-- ci-allow : … l'escape doit être SUR la ligne fautive, scan ligne-par-ligne -->`) et
suppression de la ligne-comment orpheline 1207.
**Vérif.** `bash ci/guard_constraints.sh` → OK ; `./run_ci.sh` → **33 PASS · 0 FAIL · 0 SKIP**.
`grep -inE 'git[[:space:]]+clean'` filtré PROHIBITION+ci-allow → 0 hit résiduel sur tout le tree.
**Portée / anti-churn.** **0 fichier de production**, **0 gate ajouté** (#5), **0 chiffre inventé**
(#6), **0 commande VPS** (#8). Seule édition : ce journal. Mémoire [[guard-constraints-log-prose]]
enrichie : l'escape `ci-allow`/marqueur est **PAR LIGNE** — un paragraphe wrappé qui sépare le terme
de son marqueur laisse le gate mordre.
---
## Session 20260805_134254 · SWEEP read-only *docstring-vs-code drift* sur 26 modules → **CLEAN** · 3ᵉ récurrence du faux-positif `validate [-o OUT]` refermée
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**, arbre
propre. Roadmap `ROADMAP_8_WEEKS_OR_LESS.md` intégralement livrée/gatée in-repo ; les restes
S3/S5/S6 (refactor BIM, builds EAS, dépôts ONAPI, déploiement prod VPS) sont **hors périmètre
worker** (`CLAUDE.md` #8). Le daily_report `2026-08-05.md` et le `OPEN_DECISIONS_REGISTER.md`
(D-01→D-05) sont **déjà courants** (sessions 004112 / 014119) — les ré-attester serait du churn
(#5 ; déjà fait 3× ce jour, cf. 121234). J'ai donc visé la **seule classe que la mémoire marque
explicitement `RECURRING / not-closed`** : [[docstring-vs-code-drift]] — docstrings/aides argparse
de modules de PRODUCTION qui avancent un input/fallback/output/flag que le code contredit (ou qui
omettent un output réellement écrit).
**Méthode.** Fan-out `Explore` en lecture seule sur les **26** modules générateurs (`*_gen.py` +
`lib/*.py` + textes argparse embarqués), amorcé pour SKIP le faux-positif connu. Pour chaque
module : docstring/help ↔ ce que le code lit (`json.load`/reads), écrit (`open(...,'w')`/`write_text`/
`json.dump`/noms de fichiers) et enregistre (`add_parser`/`add_argument`). Direction A (advertise
non-implémenté) **et** B (omet un output réel) toutes deux couvertes.
**Résultat — CLEAN.** Aucune dérive réelle sur les 26 modules. L'unique item remonté par l'agent
est **exactement** le faux-positif déjà refermé 2× (session 111224) : `deploy_runbook_gen.py:28`
`validate [-o OUT] → (re)génère en mémoire, valide vs deploy.schema.json + invariants…`.
Vérifié en relisant la source (L26-29 + L288) : la ligne `validate` **décrit ce que fait `validate`**
(régénère en mémoire, valide) et **ne prétend jamais** que `-o` écrit quoi que ce soit ; `[-o OUT]`
n'est qu'un flag optionnel accepté, en symétrie CLI avec `build`, et argparse l'enregistre
`help="ignoré (compat)"` (`:288`). **Bijection parfaite, self-cohérente → NON-défaut.** Seul `build`
(`:27`) revendique l'écriture, ce que le code fait. **Aucune édition** de la docstring (#5 · la
mémoire ordonne LEAVE/don't-harmonize : la doc est correcte, c'est le lecteur qui la sur-lit).
**Portée / anti-churn.** **0 fichier de production édité**, **0 artefact reconstruit** (aucun
`out/*.json` touché → pas de rebuild `quality_report`, cf. [[audit4big-rebuild-after-doc-edits]]),
**0 gate ajouté** (#5), **0 chiffre inventé** (#6), **0 commande VPS** (#8 — sweep 100 % lecture),
**aucun `git clean`**. <!-- ci-allow : mention de l'interdit #1, jamais exécuté ; escape INLINE sur la ligne fautive (scan ligne-par-ligne, cf. session 131244) --> Seule édition : ce journal.
**Vérif.** `git status --short` avant/après le sweep = vide (lecture seule) ; `./run_ci.sh` →
**33 PASS · 0 FAIL · 0 SKIP**. Mémoire [[docstring-vs-code-drift]] enrichie : classe re-balayée
CLEAN sur les 26 modules ce jour, et le faux-positif `validate [-o OUT]` a **récidivé une 3ᵉ fois**
(re-signalé par un `Explore` neuf malgré l'amorce de SKIP) → confirme qu'il faut le garder documenté
comme piège, pas le "corriger".
---
## Session 20260805_141301 · SWEEP neuf *forward-compat warnings* — 25 suites sous `-W error` → **CLEAN** (classe absente de la mémoire)
**Contexte + choix de tâche.** `./run_ci.sh` au démarrage : **33 PASS · 0 FAIL · 0 SKIP**, arbre
propre (HEAD `48b19b9`). Roadmap `ROADMAP_8_WEEKS_OR_LESS.md` intégralement livrée/gatée in-repo ;
restes S3/S5/S6 (refactor BIM, builds EAS, dépôts ONAPI, déploiement prod) **hors périmètre worker**
(`CLAUDE.md` #8). Les surfaces de dérive usuelles sont saturées et le daily_report `2026-08-05` +
`OPEN_DECISIONS_REGISTER` sont courants — les ré-attester serait du churn (#5, déjà signalé ce jour,
cf. 121234/134254). J'ai donc visé une **classe de vérification que la mémoire ne couvre PAS** :
la **compatibilité Python future** du corpus de tests.
**Hypothèse de risque.** Le runner tourne aujourd'hui sous **Python 3.10.12** ; toutes les suites
sont VERTES. Mais un `DeprecationWarning`/`SyntaxWarning`/`ResourceWarning` émis silencieusement
aujourd'hui (ex. escape-séquences d'expression régulière non-`r""`, `datetime.utcnow()`, sockets/
fichiers non fermés) reste **invisible aux tables de currency** : la CI est verte tant que le warning
n'est pas promu en erreur — ce qui arrive tout seul à un futur bump d'interpréteur. Un vrai **faux
vert forward-looking**, orthogonal aux axes *teeth* (qui prouvent que les gates mordent *aujourd'hui*).
**Méthode (lecture seule).** Rejeu des **25 suites** de `gate.needs` (bijection 25 dossiers `tests/`
↔ 25 jobs, déjà gatée) avec les warnings promus en erreurs :
`python3 -W error::DeprecationWarning -W error::SyntaxWarning -W error::PendingDeprecationWarning -W error::ResourceWarning -m unittest discover -s tests`
depuis le `working-directory` de chaque module. Le `tests/` **racine** a aussi été balayé : il rend
« Ran 0 tests » — attendu, c'est la suite **Playwright e2e** (`smoke.spec.ts`, TypeScript, job manuel
`workflow_dispatch` `e2e-baseline` hors gate de merge car exige un serveur live), pas une suite python.
**Résultat — CLEAN.** **25/25 suites OK** sous warnings-as-errors (624 tests au total, cohérent avec
`regression_run.json`). **0 warning latent** dans tout le corpus python : le corpus est prêt à un
futur durcissement d'interpréteur sans régression silencieuse. Aucune suite ne dépend d'une API
dépréciée ni n'émet de `ResourceWarning` (pas de handle de fichier/socket fuité dans les fixtures).
**Portée / anti-churn.** **0 fichier de production édité**, **0 artefact reconstruit** (aucun
`out/*.json` touché → pas de rebuild `quality_report`, cf. [[audit4big-rebuild-after-doc-edits]]),
**0 gate ajouté** (#5 — attester ≠ multiplier les gates ; ce n'est pas une *classe de dérive* mais
une propriété forward-compat, re-jouable à la demande via la commande ci-dessus après tout bump
Python ou ajout de suite), **0 chiffre saisi à la main** (#6 — 25 et 624 dérivés de `gate.needs`/du
runner), **0 commande VPS** (#8 — sweep 100 % local & lecture seule). Seule édition : ce journal +
une entrée mémoire neuve.
**Vérif.** `git status --short` avant le sweep = vide (lecture seule) ; `./run_ci.sh` après →
**33 PASS · 0 FAIL · 0 SKIP** (aucun fichier gaté touché). Nouvelle mémoire *forward-compat warnings
sweep* consignée pour re-jeu futur.
---
## Session `20260805_144304` — Currency canal stakeholder + consignation d'un incident d'intégrité
**Constat.** `run_ci.sh` → **33 PASS · 0 FAIL · 0 SKIP** ; roadmap fonctionnelle
intégralement livrée+gatée, restes hors périmètre worker (VPS/EAS/ONAPI · #8). Le canal
stakeholder `daily_reports/2026-08-05.md` s'arrêtait à la session `054200`. **Sept commits**
ont suivi dans la journée. La ré-attestation de `9b6ec60` (`121234`) avait confirmé le canal
courant MAIS n'éditait que ce journal, **pas le fichier daily_report** — qui laissait donc
croire à une journée VERTE ininterrompue alors qu'un **tree commité RED** est survenu depuis.
**Gap réel fermé.** `127ef6f` (`124241`) annonçait « 33 PASS » mais commitait un tree
**RED (32 PASS · 1 FAIL)** — le gate `guard_constraints`/`constraints-guard` mordant sur le
tree lui-même ; `1321983` (`131244`) a rattrapé la régression sous ~50 min (tree revenu VERT).
Cet **incident d'intégrité matériel pour le stakeholder** était absent du canal daily_report.
Ajouté : section fin-de-journée `144304` documentant (a) l'incident RED→VERT (preuve que la
discipline de gate est opposable au tree commité, pas seulement en pré-commit), (b) les deux
sweeps lecture-seule qui ont suivi (docstring-vs-code `48b19b9` CLEAN 26 modules · forward-compat
warnings `f311a1b` CLEAN 25/25), (c) ré-attestation du point courant HEAD `f311a1b`.
**Recompute indépendant (aucun chiffre saisi · #6).** `quality_report.json` = 24 modules ·
tous == 100 · `coverage.ok=true` · verdict PASS · `regression_run.json.totals` = suites 24 ·
ran 624 · passed 624 · 0/0/0 · `run_ci.sh` = 33 PASS = 8 statiques + 25 suites. Toutes les
tables du daily_report **concordent** ce jour.
**Portée / anti-churn.** **0 fichier de production édité**, **0 artefact reconstruit** (aucun
`out/*.json` touché → pas de rebuild `quality_report`, cf. [[audit4big-rebuild-after-doc-edits]]),
**0 gate ajouté** (#5 — l'intégrité CI est déjà gatée par `check_ci_integrity` + `guard_constraints`),
**0 chiffre saisi à la main** (#6), **aucune commande VPS** (#8 — 100 % local). Gates re-joués
sur l'arbre édité : `check_docs` ✅ (liens internes OK), `guard_constraints` ✅. Éditions :
daily_report `2026-08-05.md` (section `144304`) + ce journal. Classe [[two-logging-channels]].
**Vérif.** `./run_ci.sh` → **33 PASS · 0 FAIL · 0 SKIP** (aucun fichier gaté touché).
---
## Session `20260805_171321` · Sweep docstring-vs-code étendu aux helpers `*lib/` — CLEAN, 1 faux-positif neuf réfuté
**Constat.** `run_ci.sh` → **33 PASS · 0 FAIL · 0 SKIP**. Roadmap fonctionnelle
intégralement livrée+gatée ; restes hors périmètre worker (VPS/EAS/ONAPI · #8). Aucune
tâche fonctionnelle in-repo résiduelle. Valeur honnête du jour : étendre la veille
d'intégrité `docstring-vs-code` (classe RÉCURRENTE/non-close, cf. [[docstring-vs-code-drift]])
à la **surface qu'un balayage scopé `*_gen.py` rate** : les paquets d'aide `*lib/`.
**Sweep exécuté (lecture-seule).** Fan-out `Explore` scopé UNIQUEMENT aux helpers
`seolib` · `publiciste/lib` · `deploylib` · `dvlib` · `finlib` · `wflib` · `commlib` ·
`ecflib` · `genlib` — docstrings module + fonction, directions A (annonce non-implémentée)
ET B (omet une sortie réelle) → **0 dérive réelle**.
**Faux-positif neuf réfuté (vérifié en source + contre le spec).** Le seul candidat
remonté : `crm/financement_bancaire/finlib/gate.py:58`, docstring de `apport_requis_usd`
« Apport requis = prix × taux (**20 % résident RD, 30 % étranger**) », soupçonnée de
« coder en dur une constante que le code lit du spec ». **NON — accurate-to-contract** :
la parenthèse **illustre les valeurs COURANTES du spec**, vérifiées égales à
`financement_spec.json` (`resident_rd`=20 · `etranger`=30) ; elle reflète la ligne 8 de la
MÊME docstring module (jugée correcte) ; les lignes 15-18 posent déjà le cadre « JAMAIS
codés en dur, viennent du contrat ». Le code (l.59-64) lit bien `cfg.taux_apport_pct[...]`.
→ « docstring citant la valeur courante d'un nombre spec-driven en illustration » =
même famille que l'exclusion pédagogique/illustrative. **LAISSÉ** (anti-churn #5) — pas
d'harmonisation, pas de re-flag. Leçon consignée en mémoire : vérifier la valeur COURANTE
du spec avant de qualifier de contradiction-hardcode.
**Portée / anti-churn.** **0 fichier de production édité**, **0 artefact reconstruit**
(aucun `out/*.json` touché → pas de rebuild `quality_report`, cf.
[[audit4big-rebuild-after-doc-edits]]), **0 gate ajouté** (#5 — les docstrings sont de la
prose, faible valeur à gater ; la parenthèse est de surcroît exacte), **0 chiffre saisi à
la main** (#6 — les 20/30 sont lus du spec, non retapés), **aucune commande VPS** (#8 —
100 % local). Éditions : mémoire `docstring-vs-code-drift.md` (note du faux-positif) + ce
journal.
**Vérif.** `./run_ci.sh` → **33 PASS · 0 FAIL · 0 SKIP** (aucun fichier gaté touché).
---
## Session 20260805_174324 · GATE NEUF — prose #9 des modules bancable + commissions ancrée sur CLAUDE.md
**Contexte.** Repo vert au démarrage (33 PASS · 0 FAIL · 0 SKIP), tous modules livrés,
roadmap 8-sem sans tâche-feature ouverte. Sonde ciblée sur la classe **claude-md-constant-anchor-gate** :
les constantes canoniques #9 (3 % édition · 8.5 % marketing · 52 % point d'équilibre)
recopiées hors du module generator.
**Constat (mutation-prouvé).** `sed` #9 `8.5 %→9 %` sur `CLAUDE.md` → le gate
`check_readme_claims.sh` fait rougir **8 surfaces generator/template** (model.py CANONICAL,
README, renderer `_CANONICAL_MARKERS`, oracle test, 2 docstrings scorer, template intro/§5.2/§7)
— l'ancre est vivante. **MAIS 4 copies restaient périmées EN SILENCE**, dans DEUX modules
DISTINCTS, chacune se réclamant explicitement « (#9) » / « CLAUDE.md #9/#10 » :
- (a) `faisabilite/bancable/README.md` — « 3 % édition » et « 8.5 % marketing » (#9)
- (b) `faisabilite/bancable/banclib/finance.py` — MÊME phrase, en docstring
- (c) `faisabilite/bancable/banclib/deps.py` — « CLAUDE.md #9/#10 (3 %/8.5 %/52 % · USD+DOP · Cardnet · Letter US) » (6 marqueurs)
- (d) `crm/commissions/README.md` — « pourcentages canoniques (3 % édition · 8.5 % marketing · 52 % point d'équilibre) »
Corriger le generator ne TOUCHE pas ces modules → invention #6 latente, de la classe déjà
fermée 10× (publiciste #10 · fiscal moneda · fiches #10 · template canonique).
**Fix.** Extension du bloc d'ancrage existant dans `ci/check_readme_claims.sh` (juste après
« Template canonique »), **réutilisant `exp`** déjà recomputé des lignes #9/#10 de `CLAUDE.md`
(zéro re-parse, zéro duplication). Un claim absent échoue AUSSI (traçabilité #6). Documenté
en `ci/README.md` §2 (paragraphe « paramètres canoniques »), pas de nouvelle ligne au
tableau §1 (cf. [[ci-readme-table-detail-in-section2]]).
**Teeth (mutation-vérifié, arbre restauré à chaque fois).**
- `#9 8.5 %→9 %` → mord les **4** surfaces.
- `#9 52 %→55 %` → mord **(c)+(d) SEULEMENT** ; (a)/(b) ne citent qu'édition+marketing ⇒
correctement épargnées = **ciblage précis** (pas de faux-rouge).
- `#10 Cardnet→Azul` → mord l'énumération 6-marqueurs de (c).
- Arbre restauré → gate exit=0, les 4 surfaces re-vertes.
**Portée / anti-churn.** **0 fichier de production édité** (seuls `ci/check_readme_claims.sh`
+ `ci/README.md`), **0 artefact reconstruit** (aucun `out/*.json` ni module doc scoré par
audit_4big → pas de rebuild `quality_report`), **0 chiffre saisi à la main** (#6 — les 6
valeurs sont recomputées de `CLAUDE.md`, jamais retapées), **aucune commande VPS** (#8 —
100 % local). Classe RÉCURRENTE (chaque module distinct qui recopie une constante #9/#10
citée est une surface à ancrer) : nouvelle sous-surface (bancable + commissions) close.
**Vérif.** `./run_ci.sh` → **33 PASS · 0 FAIL · 0 SKIP**.