- Finding : ligne 3 du README « Sprint 5 · roadmap L52 (workflow vente end-to-end · volet financement) » = citation roadmap-GLOBALE (cite L52 + reprend le texte du deliverable S4), PAS un cadre agent-interne → tranchée fausse par 4 autorités concordantes : · fichier roadmap : ## SPRINT 4 = L48, deliverable L52 ; ## SPRINT 5 = L54 « ONAPI + Compliance + Mobile » (rien à voir) → « Sprint 5 · roadmap L52 » auto-contradictoire · autorité byte-gatée acceptance_spec.json (partition INV5) : crm/financement_bancaire ∈ S4 · roadmap_line 52 · 3 modules frères du bucket S4 tous « Sprint 4 » (workflow_vente/dossier_vente/commissions · L51-52) → financement seul outlier · fiche 03_agents/crm/AGENT.md liste déjà « 4 (roadmap L52) » → le module contredisait sa propre fiche - Provenance surfacée non éditée : DIRECTIVE Michel :140 cadre « [Sprint 5 · Financement] » (son snapshot d'entrée) transcrit à tort dans un slot roadmap-global ; directive laissée INTACTE (input immuable, comme daily_reports) - Balayage exhaustif : exactement 2 surfaces manuscrites (README:3 + gen.py:4) ; aucun spec/artefact/out/gate ne porte de champ sprint → build ne régénère aucun artefact (check_artifacts vert) - 0 gate ajouté (#5 · label sprint = piège à 2 cadres légitimes, gate aveugle = faux positifs sur frames agent-internes) · 0 édition d'autorité (acceptance disait déjà S4) · 0 chiffre inventé (#6) · 0 commande VPS (#8) · CI 33 PASS Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
67 KiB
Activity Log · 2026-08-05 · Claude Code DTP Worker
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) classecrm/financement_bancairesous S4 · roadmap_line 52 (evidence_modulesde 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.mdliste 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_artifactsreste vert,git statusne 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).
6 BITES — mutations de LOGIQUE de générateur, sortie re-assertée en mémoire :
- portails ·
wslib/builder.py:114roles_couverts = sum(m["nb_roles"] …)+ 1→test_roles_restrictionrecompte le total vsmanifest.counts.roles_couverts. - audit-4big ·
q4lib/builder.py:30"fail": len(below)+ 1→test_real_build_passes_gateexigetotals.fail == 0. - demo-scenario ·
scenlib/builder.py:44duree = sum(b["duree_min"] …)+ 1→test_duree_est_somme_des_beatsrecomptesc["duree_min"]vs somme des beats. - regression ·
reglib/builder.py:44"test_methods": sum(s["test_methods"] …)- 1→test_totals_match_suitesrecompute la somme vstotals.test_methods. - deploy-runbook ·
deploylib/builder.py:59gated_set - mapped_set→ |→test_coverage_bijectiveexigebijective+missing==[]. - acceptance ·
acclib/builder.py:136"exact": want == have→ !=→test_partition_exact_all_sprintsexige chaquep["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.
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).
6 BITES (mutations de LOGIQUE réelles) :
rbac/fixtures_gen—fixturelib/builder.py:43if_owner = scope == SCOPE_IF_OWNER== → !=→ inverse tous lesif_owner,test_if_owner_reflete_scope_own(attend1 if scope=="own" else 0) mord.rbac/userperm_gen—permlib/builder.py:43by_mechanism[mechanism] += 1→ += 2→ total 50→100,test_manifeste_fidele(assertEqual(sum(by_mechanism.values()), 50)) mord.rbac/apply_plan—applylib/aggregator.py:174chaîneroles == cible == up_entries == rp_covered, 2ᵉ== → !=→couverture_bijectivebascule False,test_consistency_bijective(assertTrue) mord.mobile/app_config—mobilelib/builder.py:105roles = 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.
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).
6 BITES (mutations de LOGIQUE réelles) :
faisabilite/generator—scorer._score_completude20 * filled → 20 / filled(sed adressé L57 car la formule récurre L81) → le score4bigtombe < 95,test_complete_brief_scores_publiablemord (exit 1).faisabilite/bancable—finance.derivedpoint d'équilibremath.ceil(pe_brut) → math.floor→ 21→20,test_derived_arithmetic_traceablemord.crm/workflow_vente—buildertrans_rows.sort(...)+reverse=True→ casse le déterminisme trié,test_transitions_sorted_stablemord.crm/dossier_vente—is_submittableseuil>= "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_usdsuppression 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 desdoc_statusré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ègleis_submittable.publicistemin(prices) → maxest inerte car le retour de_prix_depuisn'alimente QUE la gateis 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.
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) |
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).
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.
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).
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.
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) · siblingsnull: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ÉCISIONaudit-IA) ·finlib/gate.py:142-145(_cond_validation_wagteste encorewag_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.pyabsents). Toutes exactes. - D-05 (mobile) :
mobile_spec.json(a_confirmer) ·out/app_config.json(bundleIdentifier/packagenull) ·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 degate.needsest passé 32→33 il y a 2 jours (b7ffb64) ; le README refuse délibérément de le figer («gate.needsdé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: historique21→22→24(commentaires decheck_readme_claims.sh) ; ce chiffre est déjà gaté (recomputé depuisquality_report.jsontotals 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.needspassent · tous les modules 4Big au seuil100/100, verdictPASS» + 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.
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:24attribue biencontrats(Promesa · Fideicomiso · HOA) àlegal/confotur— RÉEL. - D-03 :
CLAUDE.md:28listechat.py;/opt/oto/otoia/capabilities/chat.pyabsent — RÉEL. - D-04 :
AGENTS_EXISTING_ASSETS.md:127« projets_editor.py API en place » ; aucunprojets_editor.pysous/opt/oto/config— RÉEL (claim runtime VPS non réfutable, #8). Registre jugé fidèle → promotion légitime.
Éditions (2, README seul).
## Navigation— nouvelle ligne juste aprèsGAP_ANALYSIS_SPRINT1.md, pointant le registre (description : punch-list décisions produit ouvertes qui appartiennent à Michel · sourcée, ne tranche pas).## É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.
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:24attribuecontrats(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),contratsest 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) supersedewag_validated_byparaudit.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.pyabsent / D-04projets_editor.pyabsent : REAL, filesystem confirmé (ni.pyni.pycsous/opt/oto), déjà surfacés session20260803_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/packagequarantinésnull · 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.
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).
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 unif:de job (^ if:). Le contextesecretsn'est pas exposé dansjobs.<id>.if(table d'availability GitHub Actions) → suract/Gitea il s'évaluerait vide → SKIP silencieux. C'est la régression234104verrouillée. - MOB-3 · activation différée (#6/#8) — chaque job de build (
run: eas build) est gaté surneeds.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 —
preflightdéclarehas_token/has_repo, chaqueneeds.preflight.outputs.<X>référencé est déclaré (0 dangling) et chaque output déclaré est alimenté par unecho …>>$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.
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 · verdictPASS.regression_run.json.totals: 24 suites · 624 ran · 624 passed · 0/0/0 · verdictPASS.acceptance_matrix.json: 15/15status=in_repo· verdictTrue·ci_job×36 (nichématrix[].modules[]).- Structure CI :
run_ci.sh --list= 33 jobs = 8 statiques + 25 suites ·ls ci/*.sh(horslib.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.
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 annoncerun_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— docstring50_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) etconfig/projets_editor.py(§13, « API GET/POST déjà en place » — siblingprojets_config.jsonprésent, lui). Vérification anti-invention #6 AVANT de flaguer :grep -rninterne → les deux MISS sont DÉJÀ surfacés à Michel par la session133718du 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.
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) :
-
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.mdomis). 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.pydocumente bien ses sorties dans le bloc Sous-commandes (build → écrit commission_plan.json + MANIFEST.json), simple placement différent definancement(7 sorties → liste séparée). Uniformiser serait de la pure churn (#5) — écarté. -
Seule action honnête + non-redondante trouvée : currency du canal stakeholder. Le
daily_reports/2026-08-05.md(session004112, 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, commits034144→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.
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 :
- 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. - Citations cross-module en prose — un README/rapport nomme le fichier d'un AUTRE module
par son nom court. Vérifié :
crm/commissions/README.mdcitebanclib/finance.pyà l'intérieur d'un vrai lien Markdown dont la cible existe et EST validée parcheck_docs(le backtick n'est que le libellé) ;banclib/wflib/cflibrésolvent tous dans le dossier de leur module propre. - 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.mdsont les liens cassés d'exemple prouvant quecheck_docsmord, et/tmp/suite_teeth_*.shsont les harnais de mutation-test éphémères (/tmpread-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.
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 ».
- 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.mdH1 « Sprint 2 »,TEMPLATE_…:216,GAP_ANALYSIS_SPRINT1.md:74-75,publiciste/README.md:26,daily_reports/*). Cohérent dans son cadre — laissé intact. - Cadre roadmap-global (autorité de la recette) : le champ
sprintdequality_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,sourcerecitée « roadmap Sprint 3 · Faisabilité générateur 4 volets → data_room/PXX » (aligné sur le style de son jumeaubancable, ligne 71, déjà S3).acceptance_spec.json:faisabilite/generatorretiré deevidence_modulesS2, ajouté à S3 (["faisabilite/bancable", "faisabilite/generator"]) — sinon l'invariant 5 « partition exacte » casse (wantlu dansquality_spec≠havedéclaré). Édition des DEUX specs obligatoire et suffisante.- Régénération :
audit_4big(report) puisacceptance(lit la fenêtre depuisquality_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.
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édentfinancement_bancaire). La ligne pointe…/demo/scenarios/README.mdet finit| 39 |→ captée par lerow_redecheck_readme_claims(label\w+/=scenarios/, chemin résoludemo/scenarios∈plan.suites) → cellule Tests recomputée depuisregression_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 quebancablealimente le scénarioS-P07-BANQUIER.
Auto-gates confirmés (3 axes indépendants, la ligne CRM franchit chacun).
Fiche 03_agents/crm/AGENT.md · demo/scenarios — Tests 39 == source (39)(recomputeregression_plan.json).Entrée CLI · demo_scenario_gen.py — verbes build|validate == subparsers.Traçabilité roadmap · demo/scenarios — citation l.68 == roadmap. Job CIdemo-scenario-tests== ci.yml.check_docs: cross-refs../crm/AGENT.mdré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.