Files
oto-enterprise-os-dtp/05_activity_log/2026-08-05.md
T
Claude Code DTP Worker caa3b90556 [DTP-Worker 20260805_064201] FIX RÉEL : fenêtre de sprint roadmap-globale de faisabilite/generator corrigée S2→S3
Défaut (1re vraie édition de prod après plusieurs sessions de sondes « 0 édition ») :
quality_spec.json:66 attribuait au générateur de faisabilité 4 volets la fenêtre roadmap
« S2 » avec un source falsifiable-faux (« roadmap Sprint 2 · Faisabilité… »), alors que la
roadmap n'a AUCUNE faisabilité au Sprint 2 (Console/CRM/RBAC) et place « génération 4 volets »
au Sprint 3 (L43, livrable L46 « P07 faisabilité complète auto-générée »). Cause racine :
confusion de 2 cadres de sprint — le label agent-interne « S2 » (AGENT.md §130-133 : phases
S1 template/S2 générateur/S3 versioning, toutes ancrées roadmap Sprint 3) recopié dans le
champ roadmap-global (seul quality_spec le porte, toutes ses entrées sourcent « roadmap
Sprint N »). Effet : le livrable-phare S3 était prouvé SANS son propre générateur, ce dernier
mal-classé comme preuve du S2 (dashboards/CRM/RBAC) sans rapport.

Correctif coordonné 2-spec + régénération byte-gatée :
- quality_spec.json:66 : sprint S2→S3 + source recitée « roadmap Sprint 3 » (style aligné sur
  le jumeau bancable, déjà S3).
- acceptance_spec.json : generator retiré de evidence_modules S2, ajouté à S3
  (sinon invariant 5 « partition exacte » casse : want lu dans quality_spec ≠ have déclaré).
- régénération audit_4big PUIS acceptance (lit la fenêtre depuis quality_spec).

Vérif : partition_ok=True · bijective=True · S2=6 (publiciste+5 rbac) · S3={bancable,generator}
· run_ci 33 PASS · score 4Big générateur inchangé 100/100 (le champ sprint ne sert qu'au tri+méta,
pas d'oracle de scoring). Portée honnête : 5 fichiers (2 specs + 3 artefacts régénérés) + log.
0 doc de module éditée (les ~10 « S2 » ailleurs = cadre agent-interne, corrects, #5) · daily_reports
= historique non réécrit · 0 gate ajouté (déjà gaté par inv5) · 0 chiffre inventé (#6) · 0 VPS (#8).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 06:56:32 +00:00

685 lines
56 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Activity Log · 2026-08-05 · Claude Code DTP Worker
## 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é -->