Axe absent de la mémoire : classe latent-KeyError i18n (un libellé référencé au rendu manquant dans une des 3 langues du dossier bancable). Balayé sur banclib/i18n.py + report.py :
- Parité : fr/en/es = jeu de clés IDENTIQUE 37/37/37 (miss+extra vides bidirectionnel) ; LANGS/LANG_FLAG/LANG_NOM concordent.
- Résolution : chaque L('…') statique + la clé dynamique lbl_{cle} (report.py:219) existe en fr ⟹ résout dans les 3 langues.
- Fumée : render_report x3 langues (chemin placeholder max) + render_all = 0 KeyError.
Verdict CLEAN + transitivement gaté : test_bancable.py rend déjà les 3 langues (render_report/render_all, briefs synthétique+normal l.~88-105) → une clé manquante casserait un test aujourd'hui. Donc PAS de gate dédié (#5 · doublon d'une protection transitive) ni de fix (aucun défaut). Distinct de seo-per-lang-cardinality-by-design (SEO, inégal par design).
0 code moteur V18 (bloqué #6) · 0 module prod touché · 0 artefact reconstruit · 0 chiffre figé (#6) · 0 gate (#5) · 0 commande VPS (#8) · 0 API externe. run_ci.sh = 33 PASS · 0 FAIL · 0 SKIP (inchangé). NFC-clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diagnostic : le journal des ~10 sessions du jour est saturé de sweeps « CLEAN · NOT a gate » marginaux + re-currency du rapport. Cause = l'index de mémoire agent (MEMORY.md) dépassait sa limite (29 Ko > 24.4 Ko) et ne se chargeait que PARTIELLEMENT (signalé par le harnais) → insights « by-design / don't re-flag / NOT a gate » invisibles → re-litige → churn. Corriger l'outil, pas surveiller le symptôme (même principe que le dé-épinglage HEAD en 150201).
Action (hors dépôt · outillage agent) : lignes d'index réécrites body-entier→hook concis (détail déjà dans les fichiers-sujets). 30117→17264 octets (charge désormais en entier · marge). Bijection 88 entrées ↔ 88 fichiers-sujets PROUVÉE par script ; 1 entrée perdue à la réécriture (link-label-target-mismatch) détectée par ce contrôle et RESTAURÉE. Ligne max 1040→252 car.
Dépôt : ce journal seul. État vérifié vert : run_ci 33/0/0 · matrice 635/618/17 PASS re-dérivée artefacts commités (#6) · 24 modules 100/100. Sans fix (V18 bloqué D-06 #6) · sans gate (#5, mémoire hors dépôt) · sans currency (rapport dé-épinglé) · sans commande VPS (#8) · sans API externe.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
La session 143156 avait diagnostiqué la boucle de churn (chaque commit worker
périme l'en-tête « HEAD attesté <hash> » ⟹ la session suivante « réconcilie »
⟹ nouveau commit ⟹ HEAD re-périmé) et converti la §Jalons en table roulante,
mais n'avait ôté le hash figé que du SOUS-TITRE de section — en le LAISSANT dans
l'en-tête du document (`HEAD attesté: 4a13557`), la ligne Session, et la
trajectoire §État courant. Fix partiel : la cause survivait dans 4 emplacements.
Ce commit neutralise les 4 : l'en-tête n'épingle plus AUCUN hash de HEAD (un
hash périme à chaque commit = cause structurelle) → « Point d'attestation » : le
HEAD se LIT (git log -1), il ne se FIGE pas ; ce qui fait foi = la matrice
635/618/17 · PASS (dérivée d'artefacts commités, stable jusqu'au prochain fix).
Hashes des §Jalons re-qualifiés jalons historiques immuables. §Contexte passé au
passé (émission initiale ee88d36). Le rapport ne peut plus périmer au prochain
commit → boucle cassée à la racine (≥6 sessions consommées).
Document éditorial hors gate (check_docs:50 continue · check_readme_claims:8239
exclu) → PAS de gate (#5), supprime la cause au lieu de la surveiller. 0 chiffre
inventé (#6, matrice re-dérivée). 0 code moteur V18 (bloqué D-06). NFC-clean.
CI 33 PASS · 0 FAIL · 0 SKIP.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Défaut latent, byte-repro-aveugle : _cond_apport arrondit percent à 1 décimale
(round) tandis que ok est un test exact. Un versement dans la bande [99.95%,100%[
(ex. 19 999/20 000 USD = 99.995%) remonte à percent:100.0 alors que ok=False,
remaining=1 USD → barre frontend « 100% ✓ » qui refuse la soumission + raison
auto-contradictoire « reste 1 USD · 100.0% ». Seul _cond_apport atteignable
(montant continu) ; documents/autorisations = ratios entiers sur listes 13/4,
bande inatteignable.
Fix chirurgical : garde `if not ok and pct>=100.0: pct=99.9` → invariant
d'affichage percent==100.0 ⟺ ok. Échantillon commité à 67.8% (hors bande) →
gate_status_example.json/out rebuild BYTE-IDENTIQUE.
Test à dents test_apport_quasi_complet_ninclut_pas_100pct (mordu sans le fix).
Cascade +1 test : régression 633→634/616→617, plan+MANIFEST+quality_report
régénérés, fiches QA/Backend/CRM re-dérivées. run_ci 33/0/0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Explore very-thorough sur crm/dossier_vente · crm/financement_bancaire ·
faisabilite/bancable · frontend/chat_otoia · pie/manifest · qa/audit_4big ·
mobile/app_config · fiscal/ecf_dgii → 0 défaut réel. Les 4 candidats remontés
sont tous NON-défauts (verify-non-defects #6) : 3× `:g` sur domaine BORNÉ
(nombre d'unités / pourcentages 0-100 → exponentiel + arrondi-6-chiffres hors
domaine, et `:g` nettoie le bruit flottant) + 1× HTML chat_otoia à entrée
canonique contrôlée. Preuve robuste : report.py `_money` réserve sciemment
`,.0f`/`,.2f` aux montants non bornés → les `:g` restants sont un choix
d'ingénierie correct, pas un oubli.
Fait nouveau : le taux commissions `:g` (déjà statué non-défaut en 080104) a
RÉCIDIVÉ de flag ce cycle, et bancable ×2 fraîchement flaggés → la classe
gaspille un cycle d'audit à chaque passage. `grep ':g}'` prod = exactement 3
sites. Passe unique : note de justification in-situ à chacun (domaine borné,
2 pièges hors domaine, montants → `,.0f`, « ne pas corriger »). Convertit le
faux-positif récurrent en « déjà revu, sûr ».
0 fix de code (défauts inexistants ; corriger `:g`→`:f` réintroduirait du bruit
flottant = régression) · 0 gate (#5) · 0 artefact reconstruit (bancable sans
out/ ; _rate_label runtime non sérialisé) · matrice inchangée 633/616/17 ·
run_ci 33/0/0 · NFC-clean · 0 code moteur V18 (bloqué D-06) · 0 commande VPS.
M crm/commissions/commlib/finance.py
M faisabilite/bancable/banclib/finance.py
M faisabilite/bancable/banclib/report.py
M 05_activity_log/2026-08-12.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bug latent réel (crm/commissions/commlib/finance.py:75) : le libellé de base de
la formule traçable « commission = base × taux » utilisait f"{base:g}", qui casse
deux fois sur des montants immobiliers réels en RD (une unité USD 300k ≈ 18M DOP) :
(1) notation exponentielle dès 1e6 (18000000 → 1.8e+07, illisible/non auditable) ;
(2) arrondi silencieux à 6 chiffres significatifs (123456.78 → 123457) = une base
FABRIQUÉE ≠ de la réelle, l'invention interdite par #6 dans le module même qui
proclame l'anti-invention.
Fix : helper _amount_label (f"{x:f}" jamais exponentiel + strip zéros) → décimal
fidèle, identique à l'ancien pour tous les montants simples (200000 reste 200000) ;
seuls les cas buggés changent. 2 tests à dents (millions non-exponentiel + décimales
préservées) — teeth prouvé : les DEUX échouent sans le fix.
Byte-repro : 0 impact d'artefact du module (compute_line est runtime, jamais appelé
par le générateur). Cascade compteur-de-suite seule : matrice 631/614 → 633/616,
regression×3 + quality_report régénérés, fiches qa/erpnext/crm + README réalignés
(commissions 25→27, Total CRM 81→83). run_ci 33/0/0. 0 code moteur V18 (bloqué D-06).
0 gate ajouté (#5). 0 commande VPS (#8).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Durcissement d'uniformité (pas bug déclenchable aujourd'hui) qui COMPLÈTE le fix
correctness de la session 070054 (aeb30b8). Ce fix-là a percent-encodé
hero['fichier'] dans src=f"/static/projets/{code}/{quote(fichier)}", mais dans la
MÊME url('…') CSS il restait un 2ᵉ segment dynamique — `code` — interpolé brut puis
html-échappé via _esc(src). C'est exactement l'anti-pattern identifié par 070054
(html.escape = mauvais encodeur pour un segment d'URL), appliqué de façon incomplète.
Fix : src = f"/static/projets/{quote(code)}/{quote(hero['fichier'])}" — quote() est
le SEUL encodeur correct pour un segment d'URL ; le CONTEXTE, pas la donnée courante,
dicte l'encodeur. Zéro dérive : codes réels = P01..P09 (§Projets), quote("p01")=="p01"
→ byte-repro inchangé. Cas frère exact de 060044/criteria.py (« durcir le SEUL membre
non-durci d'une famille ») : ici le SEUL segment d'URL non-encodé de la même url().
Test à dents (test_hero_media_code_segment_percent_encoded) : code théorique "P'X" →
prouvé FAIL sans quote(code), PASS avec. Suite publiciste 25→26, matrice 630/613→631/614.
Défaut d'abord : classe encodage-contextuel balayée sur tous les modules HTML/CSS/URL ;
2 candidats Explore réfutés non-défauts (chat mount_html = persona forcée Amélie par
invariant 7 + portail_key enum ; code segment = théorique mais durci par uniformité).
Cascade régénérée dans l'ordre (quality_report en DERNIER) · 3 fiches compteurs réalignées.
run_ci 33/0/0 · NFC-clean · zéro code moteur V18 (bloqué D-06 #6) · zéro gate (#5) · zéro invention (#6).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_ADD_PARSER_RE exigeait la parenthèse collée (add_parser\(|add_subparsers\() → `add_subparsers ()` / `add_parser ("build")` (espace avant `(`, Python valide, PEP 8 déconseille sans imposer) silencieusement loupés → sous-scorage −20 pts du critère CLI d'un module sain (symptôme fd99362). Durci en add_parser\s*\(|add_subparsers\s*\(, harmonisé aux 2 frères déjà durcis (_TEST_DEF_RE `def\s+`, _MAIN_GUARD_RE `\s*==\s*`) et à la doctrine écrite du fichier (l.18-30 « détecter la forme, pas une graphie »). 4e fix de cette classe dans ce fichier après ac595d0/fd99362/35de616.
Fix latent (aucun module n'utilise l'espace → quality_report.json byte-identique, check_artifacts vert) mais teeth-prouvé (ancien regex False sur les 2 formes espacées, nouveau True + True sur les formes normales). Cores computationnels commissions/bancable audités CLEAN par ailleurs. run_ci 33 PASS 0 FAIL 0 SKIP · 0 gate (#5) · 0 chiffre inventé (#6) · 0 VPS (#8).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Angle frais jamais balayé (cohérence tri-lingue FR/EN/ES du module seo/). Un
Explore a remonté « es manque 3 mots-clés projet P01 » ; refusé au mot (#6,
verify-non-defects) et prouvé au byte-level un NON-DÉFAUT : l'artefact étant
byte-repro-gaté, l'écart reflète la LOGIQUE du générateur.
Preuve (repro du générateur sans dedup) : génération symétrique par langue →
raw 115/langue identique. L'écart vient du seul dedup (lang,term). country[es]
= « Republica Dominicana » == la localisation générique de 5 projets
(P01/02/03/06/09) → le mot-clé global {type} Republica Dominicana absorbe le
mot-clé projet identique de P01 (dedup correct, terme présent 1× en scope
global). En fr/en country != localisation → pas de collision, P01 garde ses 7
termes. D'où +3 collisions en es → 87 vs 84. Rien n'est perdu ; invariants
(≥ min_keywords_per_lang + unicité (term,lang)) tiennent ; es=84 déjà documenté
et gaté README.md:15 (check_readme_claims). Sortie CORRECTE.
Livrable = note explicative au point de dedup keywords.py:106 (asymétrie subtile
non-évidente qu'un futur balayage refera l'enquête). Aucune logique/fixture/claim
-gaté touché (gates byte visent out/*.json, pas la source). Style aligné ASCII +
em-dash existant, NFC-clean.
run_ci = 33 PASS/0/0 (inchangé). 0 code moteur V18 (#6), 0 logique modifiée,
0 artefact/fixture touché (#6), 0 gate ajouté (#5), 0 VPS (#8), 0 API externe.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
frontend/portails/out_fresh/ (workspace.json + MANIFEST.json) était tracké mais
commité par accident une seule fois (f954cc1, 2026-08-02, scratch « fresh rebuild ») :
byte-identique à out/, produit par RIEN (le générateur n'écrit que dans out/ via
_DEFAULT_OUT), référencé NULLE PART (git grep out_fresh = 0). Il échappait à
check_artifacts (find -type d -name out = match EXACT sur `out`, jamais `out_fresh`)
donc au balayage reverse-bijection → bombe de staleness silencieuse (rien ne le
régénère : dès que la source bouge, out/ se met à jour et out_fresh/ diverge en copie
stale non-possédée). Suppression (out/ intacte, régénère byte-identique). Occurrence
isolée → pas de gate (#5). Candidat is_filled() écarté au byte-level (non-défaut, #6).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Même racine que le fix ASCII ac595d0 : une détection assume une graphie là où le langage admet plusieurs formes. Fix = regex de FORME (_MAIN_GUARD_RE : 2 styles de guillemets · ordre inversé · espacement libre) + teeth test_cli_detects_single_quoted_and_yoda_guards (3 fixtures, prouvé mordant : ancien littéral rend False sur les 3, nouvelle regex True). Robustesse pure : les 50 gardes du dépôt sont double-quotées et toujours matchées → aucun score ne bouge, quality_report.json byte-identique (audit_4big s'auto-exclut de son scoring). Cascade régression uniquement : 626/609 → 627/610 (nouveau test) + fiches qa/audit_4big 35→36 & agrégat & backend 626→627 re-dérivés (RED check_readme_claims). Sweep préalable : classe ASCII-vs-Unicode close au niveau CODE (générateurs+libs+gates shell tous ASCII-par-domaine sauf les 2 compteurs test déjà \w).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
criteria.py:18 comptait les méthodes via char-class ASCII [A-Za-z0-9_] →
loupait le test réel `test_traçabilite_source` (ç · PEP 3131, exécuté par
unittest) → évidence fausse « 22 méthodes » pour publiciste (réel 23), en
violation du cœur anti-invention #6 de l'audit lui-même. La source sœur
qa/regression/reglib/discovery.py:23 comptait déjà correctement (`test_\w+`,
\w Unicode) = 23 → les 2 outils QA divergeaient d'un. Fix aligné sur la sœur
(\w). Teeth ajoutée (test_tests_counts_non_ascii_method_names, mordante :
1 sur l'ancienne regex, 2 sur la nouvelle).
Cascade régénérée : quality_report.json (publiciste 22→23, verdict PASS 24/24
min 100 inchangé) · regression_run/plan/MANIFEST (625→626 exéc · 608→609 passés)
· fiches qa (audit_4big 34→35, agrégat 626/609) + erpnext_backend (625→626).
Blast-radius = 1 seul nom de test non-ASCII dans tout le dépôt (scan AST 24
suites). Pas de nouveau gate (#5) : check_readme_claims mord déjà la divergence.
run_ci = 33 PASS · 0 FAIL · 0 SKIP. 0 code moteur V18 (#6) · 0 VPS (#8).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Confronté tous les claims factuels de OTO_V18_MIGRATION_ARCHITECTURE_AUDIT
au code réel : 622/640 LOC, 17/22 tests, 4 schémas, absence DSCR/LTV/LTC,
legal=CONFOTUR, sorties bancable, commits sources — TOUS exacts.
Ajout Annexe A : chaque chiffre apparié à sa commande de re-dérivation
(le doc devient auto-vérifiable ; a déjà servi à corriger 446→622).
0 code moteur V18 (bloqué D-06→D-08), 0 gate, run_ci 33/0/0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claim stale non gaté dans OTO_V18_MIGRATION_ARCHITECTURE_AUDIT: §1 table
disait generator/genlib = 446 LOC alors que wc -l = 622 (113+315+176+18).
Preuve interne: §0 total « 1262 LOC lib » = 622+640, pas 446+640 (=1086) →
le doc se contredisait; la correction résout la contradiction.
bancable 640 + tests 17/22/39 re-vérifiés exacts. Occurrence isolée → FIX,
pas de gate (#5). Log ligne 26 laissée en correction-forward.
run_ci = 33 PASS · 0 FAIL · 0 SKIP. 0 code moteur V18 (bloqué D-06→D-08).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le canal roadmap daily_reports/ s'arrêtait au 2026-08-06 alors que 3 jalons
worker ont porté l'arbre à 18d8763 (fix CI · audit V18 · registre décisions).
Ce rapport porte le canal au HEAD courant + remonte explicitement à Michel que
toute la séquence moteur V18 attend ses arbitrages (D-06 approbation audit ·
D-07 formules financières · D-08 Master Intake sur-ensemble · D-01 périmètre
juridique Sec.13). Tous chiffres re-dérivés d'artefacts commités (gate 33/0/0 ·
régression 625/608/17 PASS · audit 4Big 24 modules min 100 · bijection 24↔24).
0 code moteur V18 (séquence bloquée #6/directive) · 0 gate · 0 artefact
reconstruit · 0 commande VPS (#8).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
check_docs : README.md liait DIRECTIVE_WORKFLOW_FAISABILITE_V10 (supprimé par
l'archivage V18 f316060) → repointé vers la directive active
DIRECTIVE_V18_MASTER_FEASIBILITY_ENGINE_20260810.
check_readme_claims : le scan VPS-infra ouvrait 4 docs REFERENCE root-owned
mode 600 de Michel (AUDIT_*/DIRECTIVE_COMPTE_CLIENT/GO_SIGNAL) → PermissionError
→ bad(). Tolère désormais PermissionError SPÉCIFIQUEMENT (note ⋯, continue),
tout autre OSError reste fatal. Même classe que les exclusions guard_constraints.
Garde anti-évaporation SSOT préservée sur le corpus lisible.
run_ci : 33 PASS · 0 FAIL · 0 SKIP rétabli.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Python json.load (validate_json.sh) ACCEPTE NaN/Infinity/-Infinity par défaut ;
le JS JSON.parse de la Console Helios lève SyntaxError dessus → un artefact NaN
(dérivé div-par-zéro sérialisé allow_nan=True) passerait tous les gates + byte-repro
tout en cassant le rendu navigateur = faux-vert invisible à la chaîne Python.
Détecteur stdlib json.loads(parse_constant=raise), teeth 5/5 (NaN/Infinity/-Infinity
MORDENT, float+chaîne 'NaN' épargnés) + e2e mutant sur fichier réel flagué.
CLEAN 114/114 out/*.json. run_ci 33 PASS 0 FAIL 0 SKIP inchangé, rejouable, pas de gate (#5).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Axe FRAGMENT jamais balayé : check_docs valide la cible-FICHIER d'un lien mais RETIRE le #slug (link=${link%%#*}) et ignore les ancres pures → un [label](#titre) dont le titre est renommé/typo passe au vert (fichier existe, saut d'ancre mort, silencieux). Détecteur stdlib (slug GitHub/Gitea, accents conservés, code-spans neutralisés) sur 379 .md → 1 seul lien-ancre réel (README:20 → ## Directives Michel...) résout, 0 pendante ; 2 ](#frag) en code-span écartés. Teeth-prouvé (valide True, mutant-typo False). run_ci 33 PASS 0 FAIL 0 SKIP inchangé, 0 édition prod, rejouable, pas de gate (#5).
Axe NEUF absent mémoire : tous les axes numériques testent la VALEUR (accord inter/intra, re-dérivation, arithmétique) ou la forme-date ; aucun ne teste la FORME scalaire JSON. Un compte '3.0'/'"3"' au lieu de 3 se lit pareil mais casse type:integer / == strict / colonne typée = aveugle au byte-repro ET aux oracles (tous type:number accepte int+float, un sequence_id float passe). 3 sous-checks teeth-prouvés (/tmp stdlib) : (A) cohérence type intra-fichier par chemin structurel, (B) clés-compte nb_*/…_total/passed = int obligatoire, (C) 0 NaN/Infinity. Dents : un sequence_id int-parmi-floats MORD, nb=5.0/"44" MORD, comptes réels épargnés. Résultat CLEAN : B=0 C=0 ; A flague seulement run_sheet valeur (colonne clé-valeur hétérogène by-design) ; divergences inter-fichiers = collisions de nom per-module. 2 formes délibérées NON-défauts (sequence_id float = cast wslib/frappe.py:162 fidélité Frappe Float ; is_submittable 0/1 int = convention DocType Frappe). run_ci 33 PASS 0 FAIL 0 SKIP inchangé, rejouable, pas de gate (#5)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Question neuve : quand un même concept-singleton plateforme apparaît comme scalaire dans deux out/*.json indépendants, les valeurs s'accordent-elles ? Une divergence silencieuse ship verte car aucun gate ne compare deux artefacts entre eux. Détecteur teeth-prouvé (57 fichiers ; position GLOBALE sans indice [i] = singleton vs PER-ROW = compte-de-ligne) : mutation cible_rbac_roles 50→49 dans un manifest → MORD. Résultat CLEAN : tous les singletons plateforme s'accordent (cible_rbac_roles=50, ci_modules_count/modules/suites=24, portails=5, doctypes_uniques=45, custom_docperm=116, role_profiles=6, min_score=100, pass_score=95).
TRAP documenté : « même nom = même concept » sur-signale — fields/data_fields/permissions/roles/sections/pass divergent car chacun décrit SON propre DocType/suite (per-module-local, pas un singleton). Seul nom SURCHARGÉ divergent = roles_couverts 50 (tous les role-profiles) vs 44 (rôles des 5 portails business = 50 − 6 plateforme) = by-design, les deux re-dérivés de rbac_50_roles.json.
0 gate ajouté (#5, occurrence-zéro rejouable), 0 commande VPS (#8), run_ci 33 PASS 0 FAIL 0 SKIP. Canal stakeholder daily_reports porté 3710253→HEAD.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Constat: faisabilite/generator/brief.schema.json, documenté « Contrat d'entrée »
et sibling de version/projets_master (tous deux test-enforced), n'était validé
par AUCUN test — _validate_brief() ne garde que le code projet, jamais la forme
complète du brief; un brief drifté hors contrat passait inaperçu.
Fix (teeth): test_input_briefs_validate_against_brief_schema valide les 2 fixtures
contre brief.schema.json via le validateur maison Publiciste (zéro-pip, toujours
exécuté, pas de skip sous python -S). Conformité pré-vérifiée sous validateur
maison ET oracle jsonschema. L'« incomplet » est conforme au sens schéma
(null admis) — incomplet seulement au sens sémantique (prix→placeholders #6).
Cascade régénérée (générateurs, jamais à la main #6): regression_plan/run/MANIFEST
16→17 · totaux 624→625 exécutés / 607→608 passés / 17 skippés · quality_report
evidence 16→17. Prose gatée réalignée (le gate check-readme-claims a mordu):
README module + fiches faisabilite/qa/erpnext_backend. Prose ungatée: GAP_ANALYSIS
16→17 + nouveau bloc daily_report (blocs currency antérieurs = snapshots datés,
non réécrits).
0 gate ajouté (#5) · 0 chiffre à la main (#6) · 0 commande VPS (#8).
run_ci.sh = 33 PASS · 0 FAIL · 0 SKIP.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nouvel axe absent de la mémoire, distinct de module-suite-teeth-mutation (mute la LOGIQUE) et
schema-oracle-teeth-audit (audite l'ORACLE) : cible le HARNAIS de test lui-même. 3 cas où une
méthode est définie mais unittest discover ne l'exécute jamais, sans qu'aucun gate ne le voie —
(a) def test_x dupliqué dans une classe (Python garde la dernière liaison), (b) class TestFoo
dupliqué dans un fichier (2ᵉ masque 1ʳᵉ), (c) def test_* niveau-module (discover ne le lance pas).
Trou réel : un shadow passe par absence ET ferait SUR-compter check_readme_claims (compte textuel
des def test_). Détecteur AST teeth-prouvé sur sonde synthétique (3/3 mordent). Balayage réel
CLEAN 25/25 fichiers (0 méthode-dup, 0 classe-dup, 0 test niveau-module). Sweep rejouable, NOT a
gate (occurrence-zéro, #5). run_ci 33 PASS 0 FAIL 0 SKIP · 0 prod édité · 0 artefact reconstruit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nouvel axe: la FORCE de la couche oracle elle-même (distinct des dents de logique-générateur). ~20 modules ont un test_schema_oracle qui valide le build-mémoire contre un *.schema.json commité; un schéma vide ({} / type:object sans required) ou malformé rendrait cet oracle faux-vert (accepte tout) — classe ungatée, byte-repro & suites aveugles à la force du schéma. Audit 26/26: tous valides Draft-N + non-vides (required+properties, kw 3..103). Détecteurs de l'audit prouvés: validité mord un schéma malformé, dents mord {} et {type:object}, 0 faux positif sur rbac.schema. Connexes CLEAN: role_id→erpnext_role_name = fonction sur 57 artefacts (0 conflit); oracle exécuté non-skippé sous run_ci (python3 nu, jsonschema 3.2.0); 0 TODO/stub prod. Lecture-seule, occurrence-zéro, rejouable → PAS un gate (#5). run_ci 33 PASS 0 FAIL 0 SKIP · 0 prod édité · 0 artefact reconstruit · 0 gate ajouté (#5)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Un Explore adversarial a re-flagué (2e fois) la cellule « Sortie » du chat_otoia
comme « manque MANIFEST.json » — faux positif connu (fiche-sortie-cell-omits-manifest-
by-design) : les DEUX rangs (portails+chat) listent le payload seul, MANIFEST omis par
convention, docstrings/READMEs énumèrent les 3 (byte-gatés). La récurrence prouve un
footgun non documenté (brûle des cycles d'audit, risque un fix #5/#6 cassant le gate
regex-parsé). Mitigation : blockquote « Note de lecture » après la table, rédigé pour
éviter les regex ancrées du gate (aucun compteur cartes/liens/rôles ni **N tests**).
check_readme_claims PASS · check_docs PASS · run_ci 33 PASS 0 FAIL 0 SKIP · 0 artefact
reconstruit · 0 gate ajouté (#5) · 0 chiffre inventé (#6) · 0 commande VPS (#8)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Investigation: les 6 marqueurs canoniques #9/#10 existent en ≥4 endroits. Hypothèse
« (c) renderer._CANONICAL_MARKERS + (d) l'oracle du test sont des copies non gatées
→ dériver de model.CANONICAL (source unique) ». RÉFUTÉE par ./run_ci.sh (check-readme-claims
RED): la 2e moitié du gate ast.literal_eval STATIQUEMENT ces 2 listes et les set-diffe vs
CLAUDE.md → déjà gatées. Et surtout: check_canonical() PROUVE que le rendu (bâti DEPUIS
model.CANONICAL) émet chaque valeur; le dériver de CANONICAL rend le contrôle tautologique
(vert vacux). EXCEPTION à fix-vs-gate-transitively-protected-constant (qui vise les copies
PASSIVES; un oracle d'enforcement est ACTIF, sa duplication EST le test). Revert intégral
des 2 éditions (test byte-identique). Seul survivant: +5 lignes de commentaire remontant la
raison (indépendance de l'oracle) au site de code, autrement enfouie dans le script CI.
Sortie inchangée (commentaire pur), check_artifacts byte-identique.
run_ci 33 PASS 0 FAIL 0 SKIP · 0 artefact reconstruit · 0 gate ajouté (#5) · 0 chiffre inventé (#6)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Axe NEUF (absent mémoire): liens `[`label`](cible)` où le label code-span est un
chemin de fichier EXISTANT dont le basename diffère de la cible href. check_docs
prouve que la cible résout, jamais que le label la désigne — un Cmd-clic sur
`ci/guard_constraints.sh` atterrissait sur `ci/README.md` (mauvais fichier).
Balayage git ls-files '*.md' → 3 occurrences, toutes README.md (L54 x2 + L75).
Les scripts nommés existent + sont git-trackés (gates) ⇒ FIX = pointer chaque
label vers le fichier qu'il nomme. label==cible, navigation véridique, check_docs
toujours vert. Re-balayage mismatch → 0.
Axe éliminé au passage (CLEAN, non re-flaggé): chemins-fantômes backtick — 2
survivants = faux positifs (label de lien à vraie cible · résolution sous-arbre
confotur/cflib). run_ci = 33 PASS · 0 FAIL · 0 SKIP. 0 gate (#5, origine isolée),
0 artefact rebuild, 0 chiffre inventé (#6), 0 VPS (#8).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sweep rejouable de tous les out/*.json: 0 slug de rôle non résolu vs les 50 ids canoniques;
6 tokens portail hors portails_business = non-défauts vérifiés (5 valeurs icon ERPNext du champ
mobile + le 6e portail technique legitime plateforme). Axe jamais balayé, distinct de
docstring/currency. 0 prod, 0 artefact rebuild, 0 gate (#5), 0 chiffre inventé (#6), 0 VPS (#8).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Défaut réel/reproductible (pas stale, byte-repro vert) : finlib/builder.py:89
faisait [roles_resolved[k] for k in sorted(roles_resolved)], itérant les 4 SLOTS
logiques de spec["roles"] — 2 slots (conseiller_wag ET validation_dossier) pointent
le même rôle ventes-conseiller → 4 entrées pour 3 rôles distincts. Preuve bug (pas
per-slot voulu) : la sortie drope la clé de slot (cle), donc le doublon ne porte
aucune info distinctive ; sémantique documentée README.md:54 « rôles RBAC utilisés »
+ ci/README.md:1100 « ensemble distinct » ; les 3 modules frères produisent tous un
ensemble distinct (confotur 3/3, commissions 4/4, workflow_vente 7/7) — financement
seul outlier 4/3.
Fix (aligné idiome commissions) : ensemble distinct trié par role_id via
sorted({rr["role_id"] for rr in roles_resolved.values()}). Artefact régénéré → 3/3
distinct. 0 gate ajouté (#5 — champ lu par AUCUN gate financement ni consommateur
externe ; convention distinctness déjà tenue par les frères). 0 chiffre inventé (#6).
run_ci 33 PASS · 0 FAIL · 0 SKIP (byte-repro vert avec nouvel artefact, 35 tests verts).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Poursuite de la sous-classe help-text du fix a8342e9 (session 004414). Audit exhaustif
des 22 générateurs `*_gen.py` (help d'une sous-commande énumérant ses fichiers de sortie
↔ fichiers réellement écrits) → 21/22 CLEAN, 1 vrai défaut : seo_gen.py:293.
Le help `build` disait « genere seo_keywords/schema_org/hreflang + MANIFEST » alors que
le code (l.259-262) écrit seo_keywords.json, seo_schema_org.json, seo_hreflang.json,
MANIFEST.json : le help droppait le préfixe seo_ sur 2 des 3 fichiers (schema_org,
hreflang) + l'extension .json sur tous → nommait des fichiers INEXISTANTS
(schema_org ≠ seo_schema_org.json). Même classe que a8342e9.
Preuve fix-en-une-passe : docstring module (l.6-9) ET README (l.15-18) nommaient déjà
correctement les 4 fichiers exacts ; le help était le seul outlier, aligné maintenant
sur eux + la convention des frères (commissions/audit_5d/app_config = noms complets).
Style ASCII sans-accent du fichier respecté (genere).
0 gate (#5 — help ni sérialisé ni AST-parsé ; parseur de help fragile : pas de
convention commune aux 22, classe couverte par audit périodique). Sortie build
byte-identical aux 4 out/ commités (0 rebuild). 0 prod (#8), 0 chiffre inventé (#6).
run_ci 33 PASS · 0 FAIL · 0 SKIP.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Défaut réel (classe prose-facts-vs-numeric-drift) présent dans DEUX surfaces —
daily_reports/2026-08-06.md (l.48-54, canal stakeholder) ET 05_activity_log/2026-08-06.md
(entrée 011419) : l'« axe de vérification objectif » annonçait « concordance totale sur les
14 READMEs annonceurs » mais son énumération omettait 2 modules qui annoncent pourtant un
compte de tests dans leur README, tous deux concordants avec le réel — frontend/portails 19
(19 def test_) et demo/scenarios 39 (39 def test_). Le vrai total est 16 annonceurs / 9 sans
annonce, corroboré par l'en-tête faisant autorité de ci/check_readme_claims.sh (l.96 :
« 27 claims sur 16 modules »). Le « 11 sans annonce » était le complément erroné 25−14 → 9.
Fix (sweep des 2 surfaces en une passe, cf. prose-facts-vs-numeric-drift : un twin fix balaie
TOUTES les surfaces) : daily_reports 14→16 + ajout des 2 entrées manquantes + 11→9 + ancrage
explicite au gate ; journal 14→16 dans l'entrée 011419 (fait courant) avec commentaire de
traçabilité + nouvelle entrée documentant la correction. Chiffres recomputés, non saisis
(grep READMEs annonceurs=16 · def test_ concordance 16/16 · find tests=25 → complément 9).
Écarté au préalable : candidat comment↔code check_regression.sh:17 (« ran/passed/failures »)
= FAUX POSITIF — le commentaire décrit ce que l'ARTEFACT porte (le JSON a bien failures:0),
pas la ligne d'affichage l.79 ; le contrat réel = diff -q byte-à-byte du fichier entier l.71.
0 prod édité, 0 artefact rebuild, 0 gate ajouté (#5 — la surface README-count est déjà gatée),
0 chiffre inventé (#6), 0 VPS (#8). run_ci 33 PASS · 0 FAIL · 0 SKIP avant/après.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Chasse adversariale ciblée (classe RECURRING docstring↔code, scopée aux modules non balayés
la veille) → 1 vrai défaut ungaté. Le help= de sub.add_parser("build") énumérait 4 sorties
(app_config/eas_build/role_navigation/MANIFEST) alors que cmd_build() en écrit 5 : store_listing.json
(l.243) était omis. Docstring (l.15 « les 5 fichiers ci-dessus ») + README (l.56/68) listent bien
les 5. Nouvelle sous-classe help-text argparse du motif docstring↔code (2e surface prose dans le code,
distincte des open('w') vs docstring déjà balayés). Fix = ajout store_listing.json à sa position
canonique. Aucun gate/test ne fige la chaîne (grep -rn = 0 hit externe) → pas d'oracle à réaligner,
pas de gate ajouté (#5). Non-régression : help n'affecte aucune sortie, artefacts byte-identiques,
run_ci 33 PASS·0 FAIL·0 SKIP avant/après. Reste de l'audit (audit_5d/acceptance/regression/
deploy_runbook/commissions/dossier_vente/ecf/portails/faisabilite) CLEAN. 0 artefact rebuild, 0 VPS (#8).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Chasse à un vrai défaut ungaté (agent Explore skeptique) plutôt que répéter le motif de currency (churn). Candidat remonté puis RÉFUTÉ par vérif indépendante : la cellule « Sortie » de 03_agents/frontend_console/AGENT.md omet MANIFEST.json dans les DEUX lignes (portails:28 `out/workspace.json` alors que workspaces_gen.py:219 l'écrit ; chat_otoia:29 `{custom_block,chat_mount}` alors que chat_otoia_gen.py:229 l'écrit) → convention éditoriale (payload listé, manifeste de traçabilité omis), PAS une dérive. L'énumération complète des 3 fichiers est correcte là où elle compte (docstring chat_otoia_gen.py:12/15/18 + README module). Corriger la seule ligne chat = asymétrie ; éditer les deux = churn d'une cellule parsée par ci/check_readme_claims.sh (CO_FI ~L3506/L3576). CLEAN, laissé tel quel (#5/#6, « X not drift, don't fix »).
0 fichier de production édité, 0 artefact reconstruit, 0 gate ajouté (#5), 0 chiffre saisi (#6, 33 recompté de run_ci.sh), 0 VPS (#8). Seule édition in-repo : le journal. Mémoire projet : fiche-sortie-cell-omits-manifest-by-design.md. run_ci 33 PASS·0 FAIL·0 SKIP.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le gate valide la bonne-formation JSON SEULEMENT (parse strict), mais l.27 portait un
commentaire orphelin annonçant un contrôle de conformité $schema/draft que le code
n'implemente jamais (branche then imprime juste le check). Nouvelle sous-classe
comment-vs-code : les scripts de GATE eux-memes, jamais couverts par les sweeps
docstring-vs-code (scopes aux generateurs de prod + helpers *lib/).
Isolement verifie : sweep des 8 scripts ci/*.sh + lib.sh = 0 autre derive.
Fix 2 volets : (1) suppression de la ligne fantome ; (2) en-tete PERIMETRE explicite
(bonne-formation seule ; conformite $schema = oracle jsonschema optionnel de
check_regression.sh, 17 skips par design) -> le commentaire retire devient une
affirmation positive et vraie du perimetre.
0 changement de logique executable, 0 artefact rebuild, 0 gate ajoute (#5, FIX pas
controle neuf), 0 chiffre saisi (#6), 0 VPS (#8). run_ci 33 PASS / 0 FAIL / 0 SKIP.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
La table §1 « Ce que fait le pipeline » de ci/README.md est LA doc canonique des jobs
statiques (une ligne par gate ci/*.sh). Sa complétude était le jumeau exact de la fiche QA
fermée par afa6165, mais NON gaté : check_ci_integrity verrouille le câblage (ci/*.sh ↔
gate.needs), le bloc fiche-QA ne lit que 03_agents/qa/AGENT.md — rien n'exigeait que chaque
gate figure dans la table §1. Un 9ᵉ gate y serait omis EN SILENCE. Prouvé ungate par mutation
(retrait de la ligne de table check_mobile_workflow → run_ci --static 8 PASS, aucun gate ne mord).
Fix = extension du gate existant (PAS un nouveau job · #5) : bloc Python dans check_readme_claims.sh
(avant sys.exit) réutilisant _gate_names re-dérivé de git ls-files 'ci/*.sh' (hors lib.sh · #6).
Exige chaque basename cité dans une LIGNE DE TABLE (|…) de la section §1 — délibérément PAS la
prose §1 (qui ne cite que certains gates pour l'explication mobile-build.yml : un contrôle
whole-section serait édenté sur un retrait de ligne de table). 0 nouveau ci/*.sh, 33 jobs inchangés.
Teeth 2 axes : MUT1 suppression de la vraie ligne de table (l.24) → RED nommant check_mobile_workflow.sh ;
MUT2 9ᵉ ci/*.sh factice tracké → RED sur les DEUX surfaces (fiche QA + table §1 : forward-protection)
puis git rm --cached + rm, arbre byte-restauré (JAMAIS git clean). Piège inerte écarté : sed 15d
visait l'en-tête de table (grep -n renumérotait relatif au pipe), re-ciblé sur la ligne 24 réelle.
Doc sous-section « COMPLÉTUDE de l'énumération dans la TABLE §1 » ajoutée en §2 de ci/README
(discipline ci-readme-table-detail-in-section2). Sweep préalable repo-wide 7-gates/32-jobs : 0
dérive réelle (toutes occurrences historiques datées ou pédagogiques, classées KEEP).
0 édition de production, 0 artefact rebuild, 0 chiffre saisi (#6 — 8/9 recomputés), 0 VPS (#8).
run_ci 33 PASS · 0 FAIL · 0 SKIP.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
run_ci = 33 PASS au démarrage, arbre propre, roadmap intégralement livrée/gatée.
Deux sondes préalables NÉGATIVES : (a) Explore very-thorough dérive prose/docstring
sur tout 05_deliverables_mvp = 0 dérive ungatée ; (b) méta-gate check_ci_integrity
relu (INV-A gate.needs · INV-B tout ci/*.sh câblé · INV-C PYTHONUTF8) = câblage
exhaustif, risque « gate orphelin » déjà fermé. Bascule sur la COMPLÉTUDE des fiches.
Énumération réelle des modules (tests/ + test*.py) = 25 (le MEMORY disait « 22 » :
écart réconcilié = split rbac en 4 sous-générateurs apply_plan/fixtures_gen/
roleprofile_gen/userperm_gen + rbac racine ; PAS 3 modules neufs non-fichés).
Cross-check : 25/25 cités dans ≥1 fiche · 24 comme LIGNES de table.
Faux-positif « pie/manifest manque une ligne » RÉFUTÉ à la source : documenté en
PROSE délibérée (faisabilite §40-52, « les comptes exacts font foi dans le README du
module — cet agent en est la source, pas la copie ») → une ligne de table à cellule
Tests réintroduirait un compte recopié (#6) + duperait la source (#5). demo/scenarios
= ligne source-unique délibérément côté fiche CRM (§Sprint 7), cross-ref depuis
faisabilité (#5). Complétude fiches CONFIRMÉE CLOSE, compteur mémoire réaligné 22→25.
0 fichier de production édité · 0 gate ajouté (#5) · 0 chiffre saisi à la main
(#6, tous recomptés) · aucune commande VPS (#8) · aucune plateforme git externe.
Deliverable = ce log (+ màj memory fiche-accuracy-audit-closed). run_ci 33 PASS·0·0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Classe absente de la mémoire, orthogonale aux axes teeth ET au sweep forward-compat warnings : même famille faux-vert FORWARD, cause différente (ordre de hash vs API dépréciée). check_artifacts/check_regression prouvent le rebuild byte-identique MAIS ne tournent QU'UNE fois, sous LE seed que le runner Gitea donne (randomisé par défaut depuis Python 3.3). Un seul générateur sérialisant un set/dict en ordre d'itération → gate VERT aujourd'hui, ROUGE au hasard demain sur le runner. Sweep prouve : aucun ne le fait → les deux gates de reproductibilité sont robustes au hash-seed randomisé.
Harnais auto-vérifié (57 comparés · 1 skip = regression_run.json sous la passe build-only = l'exclusion documentée de check_artifacts, pas un skip silencieux). 0 fichier de prod édité (rebuilds → /tmp), 0 artefact reconstruit, 0 gate ajouté (#5 · propriété re-jouable via commande, pas classe de dérive), 0 chiffre saisi (#6 · 21/57 dérivés de find+décompte), 0 VPS (#8 · 100% local lecture seule). run_ci 33 PASS. Éditions : activity_log + daily_report (table veille) + mémoire neuve.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le daily_report 2026-08-05 s'arrêtait à la session 054200 et laissait croire à une journée VERTE ininterrompue. Or 7 commits ont suivi, dont UN matériel pour le stakeholder : 127ef6f (124241) annonçait « 33 PASS » mais commitait un tree RED (32 PASS·1 FAIL) — guard_constraints/constraints-guard mordant sur le tree commité lui-même — rattrapé par 1321983 (131244) sous ~50 min (tree revenu VERT). La ré-attestation 9b6ec60 (121234) avait confirmé le canal courant mais n'éditait QUE le journal, pas le fichier daily_report → l'incident RED→VERT restait absent du canal stakeholder.
FIX (currency channel, 0 prod, 0 artefact rebuild) : section fin-de-journée 144304 ajoutée au daily_report documentant (a) l'incident RED→VERT = preuve que la discipline de gate est opposable au tree commité (pas seulement pré-commit local), (b) les 2 sweeps lecture-seule suivants (docstring-vs-code 48b19b9 CLEAN 26 modules · forward-compat warnings f311a1b CLEAN 25/25/624), (c) ré-attestation HEAD f311a1b.
Recompute python3 indépendant (aucun chiffre saisi · #6) : quality_report 24 modules tous==100 coverage.ok verdict PASS · regression_run totals suites 24/ran 624/passed 624/0-0-0 · run_ci 33 PASS=8 statiques+25 suites — toutes tables CONCORDENT. Gates re-joués sur l'arbre édité : check_docs OK, guard_constraints OK. 0 gate ajouté (#5 · intégrité déjà gatée par check_ci_integrity+guard_constraints), 0 VPS (#8). Editions : daily_report (section 144304) + activity_log. Classe two-logging-channels.
Balayage lecture seule de la SEULE classe que la mémoire marque `RECURRING / not-closed`
(docstring-vs-code-drift) : fan-out Explore sur les 26 modules générateurs (*_gen.py +
lib/*.py + textes argparse embarqués), directions A (advertise non-implémenté) ET B (omet
un output réel) couvertes. Résultat : 0 dérive réelle. L'unique item remonté est EXACTEMENT
le faux-positif déjà refermé 2× (deploy_runbook_gen.py:28 `validate [-o OUT]`, help="ignoré
(compat)" :288) — re-signalé une 3e fois par un Explore neuf MALGRÉ l'amorce de SKIP. Vérifié
en source (L26-29 + L288) : la ligne `validate` décrit ce que fait validate (régénère en
mémoire, valide) et ne prétend jamais que -o écrit ; seul `build` (:27) revendique l'écriture,
ce que le code fait. Bijection parfaite self-cohérente → NON-défaut, LEAVE (#5), 0 édition doc.
Portée : 0 fichier de prod édité, 0 artefact reconstruit, 0 gate ajouté (#5), 0 chiffre
inventé (#6), 0 commande VPS (#8), sweep 100% lecture. Seule édition : activity_log.
Vérif : ./run_ci.sh → 33 PASS · 0 FAIL · 0 SKIP. Mémoire docstring-vs-code-drift enrichie
(3e récurrence documentée : faux-positif essentiellement insupprimable par amorce — l'attendre
comme unique hit et confirmer-puis-écarter, ne pas le traiter comme signal).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Au démarrage ./run_ci.sh échouait sur constraints-guard : ligne 1206 de l'activity_log 2026-08-05 cite « aucun `git clean` » (#1) sans marqueur ; le `ci-allow` posé par la session précédente était sur la ligne 1207 (paragraphe hard-wrappé) alors que scan_forbidden fait grep -in LIGNE PAR LIGNE → l'escape doit être SUR la ligne fautive, une ligne voisine est inerte. 3e récurrence de la sous-classe `aucun git clean` mais 1re variante « marqueur mal placé » (les 2 précédentes = marqueur absent).
FIX (journal only, 0 prod, 0 artefact, 0 gate) : escape fusionné INLINE sur la ligne fautive + suppression du comment orphelin. Le journal-de-correction lui-même re-mord (il cite `git clean`) → escapé inline aussi. Vérif : grep -inE 'git +clean' filtré PROHIBITION+ci-allow = 0 hit résiduel ; ./run_ci.sh → 33 PASS · 0 FAIL · 0 SKIP (avec ce log inclus). Réflexe [[ci-gate-verification-method]] : re-jouer les gates sur le tree AVEC le nouveau log staged, pas seulement après l'édition de code.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Vérité-terrain : grep -c def test_negative_ = 10 (réparties Keywords/SchemaOrg/Hreflang/Manifest, PAS de classe dédiée) ; git show 0d3b242 = déjà 10 dès la création → jamais un état vrai antérieur (pas historical-repro). Piège confotur écarté : les 2 frères qui affichent un sous-compte négatif en prose sont EXACTS car adossés à une classe nommée countable (confotur « 8 négatifs » = classe TestNegative=8 ; devops « 14 injections négatives » = classe TestNegativeInjections=14) — SEO est le seul faux, ses négatifs ne forment pas de classe. Toute la classe « sous-compte négatif en prose » (seo/devops/confotur) auditée end-to-end.
Surfaces (balayage twin-fix) : seule seo/README.md:67 corrigée (présent-tense, doc vivante) ; les 2 autres occurrences du « 8 » sont dans daily_reports/2026-07-30-session18.md + 05_activity_log/2026-07-30.md = snapshots datés du 2026-07-30 → KEEP.
Effet de bord : audit_4big score le contenu README → quality_report.json régénéré EN DERNIER (seule ligne changée : evidence README.md 3900→3901 octets) · verdict PASS · 24/24 ≥95 (min 100) inchangé.
0 gate ajouté (#5 · erreur d'origine isolée, pas une dérive récurrente ; total déjà gaté ; précédent confotur = ne pas gater ces sous-comptes) · 0 édition d'autorité (le test file n'est pas modifié) · 0 chiffre inventé (#6) · 0 commande VPS (#8). run_ci.sh : 33 PASS
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dernier des 22 modules livrés orphelin de fiche (grep -rl sur 03_agents/*/AGENT.md). Mirroir exact du pattern CRM 071201 : sous-section dédiée + 1 ligne de table AUTO-GATÉE par 3 gates indépendants de check_readme_claims (résolution par cible de lien mobile/app_config ∈ plan.suites) — cellule Tests 22==source · job CI mobile-app-config-tests==ci.yml · verbes CLI build|validate==subparsers. Pas de piège des 2 cadres de sprint : S5 dans les deux (acceptance evidence_modules S5 · roadmap ## SPRINT 5 ligne Mobile L56) → cité L56. 3 énumérations « ancrage in-repo » réconciliées (sinon contradiction avec la nouvelle section), toutes sourcées. 0 out/ régénéré (git status = fiche seule) · 0 gate ajouté (#5) · 0 édition d'autorité · 0 chiffre inventé (#6, 22 lu dans regression_plan) · CI 33 PASS.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Classe prose FACT count ungated (prose-facts-vs-numeric-drift). Défaut : README:9 « la structure des 6 sections » + fiche CRM AGENT.md:47 « cœur du gate + 6 sections » décrivaient le livrable avec un compte périmé. Le bon compte = 7 est triple-sourcé et byte-gaté : out/MANIFEST.json counts.sections=7 · financement_spec.json bloc sections=7 (apport_initial,info_achat,choix_banque,formulaires,exigences,autorisations,envoi) · et le README se contredisait lui-même (L49 énumère 7, L57 « 7 sections » annotée source=MANIFEST). Origine du 6 : la DIRECTIVE originale énumérait 6 sections non-gating puis fut amendée pour ajouter apport_initial (gating, pos.1) → 7 ; spec+module ont suivi, les 2 prose L9/L47 non.
Correctif 6→7 sur les 2 SEULES surfaces d'implémentation. Les 4 « 6 sections » restantes sont des snapshots directive gelés (DIRECTIVE_FINANCEMENT L106/122/131 + DIRECTIVE_WORKFLOW_V10 L22/47) — input-specs datés de Michel, jamais réécrits (directive-vs-implementation · spec=authority) ; leur « 6 » est exact au cadre pré-amendement → INTACTS.
0 gate ajouté (#5 · un gate structuré ne mordrait pas la prose L9, hors-cible ; SIGNAL surfacé : financement seul module dont MANIFEST.counts n'est pas recompté par check_readme_claims). 0 chiffre inventé (#6). 0 production éditée. 0 commande VPS (#8). run_ci.sh 33 PASS 0 FAIL 0 SKIP.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le rapport stakeholder du jour (session 004112) s'arrêtait au jalon 33 jobs et
PRÉCÉDAIT toute la série d'attestation teeth des suites de MODULE (BATCH 1→4,
034144→051154, clôture 25/25). Le canal daily_reports ne captait donc aucun des
deux axes de « dents ». Section « fin de journée » ajoutée :
· axe A — 8/8 gates statiques mutation-prouvés (9fe8bf1/031134)
· axe B — 25/25 suites de module mutation-prouvées (6+6+6+7)
Chaque chiffre sourcé sur une structure commitée (ls ci/*.sh=8 · gate.needs=25 ·
série de commits) — 0 saisi à la main (#6). Message : les tables prouvent la CI
VERTE, les attestations teeth prouvent qu'elle MORD (gate/suite toujours-vert =
faux vert invisible).
Avant de choisir : chasse docstring↔code drift sur les 21 générateurs (classe
récurrente) → 0 contradiction réelle ; les « docstrings sans liste-sorties »
sont des faux positifs (sorties documentées dans Sous-commandes), uniformiser =
churn écartée (#5).
0 édition de production · 0 gate ajouté (#5) · aucune commande VPS (#8) ·
aucun git clean · CI 33 PASS.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Continuation de l'axe des DENTS des suites de MODULE ([[module-suite-teeth-mutation]]) : batches 034144+041149 avaient prouvé 12/25 ; ce batch 3 en couvre 6 de plus → 18/25 · 7 restantes non-couvertes ≠ édentées (pas de plafond silencieux).
6 BITES (chaque suite mord sur une dérive de LOGIQUE de son générateur, jamais un écho d'artefact) :
- rbac/fixtures_gen builder:43 if_owner ==→!= (test_if_owner_reflete_scope_own)
- rbac/userperm_gen builder:43 by_mechanism += 1→+=2 (sum==50)
- rbac/apply_plan aggregator:174 chaîne bijective 2e ==→!= (assertTrue couverture_bijective)
- mobile/app_config builder:105 roles=sorted(...)+reverse=True (==sorted(roles_map[portail]))
- qa/audit_5d builder:60 count(_FAIL)→count(_PASS) (totals fail:0→13)
- frontend/chat_otoia builder:114 sum(nb_roles)→sum(1) (test_44_roles_couverts 44→~5)
Axe distinct des DENTS des 8 gates STATIQUES (031134, 8/8) : une suite qui rechargerait out/*.json contre elle-même serait édentée = faux vert Sprint 8 · les suites reconstruisent en mémoire depuis le contrat.
LEÇON réappliquée (désambiguïser TOOTHLESS-test vs mutation INERTE) : le 1er pick mobile (:192 roles_couverts sum→len) était édenté pour la suite de MODULE (agrégat jamais ré-asserté en mémoire) ; basculé sur :105 (tri, réellement contraint). Idem fixtures :68 → :43.
Harnais /tmp read-only, remplacement d'octets littéral (assert 1 occurrence), run de la seule suite, restore byte-exact cp+cmp, trap EXIT, jamais git clean, arbre pristine · 0 gate ajouté (#5) · 0 chiffre inventé (#6) · 0 édition prod · CI 33 PASS
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Continuation honnête de l'axe DENTS-des-suites (034144 : 6/25, 19 non-couvertes≠édentées). Batch 2 couvre 6 de plus → 12/25. Chaque suite doit sortir NONZERO sur une dérive de LOGIQUE de son générateur (jamais un écho d'artefact) : scorer completude 20*filled, bancable point-équilibre math.ceil, workflow-vente tri-déterminisme .sort, dossier is_submittable seuil, financement apport /100.0, publiciste _fmt_usd séparateur espace.
LEÇON : un verdict TOOTHLESS doit être désambiguïsé — (a) test réellement édenté vs (b) mutation comportementalement INERTE pour la donnée du contrat. 2 mutations ont d'abord lu TOOTHLESS mais étaient (b), PAS des défauts : dossier >= '1'→>'1' inerte car max doc_status='2' (les 2 branches donnent 1) → une mutation qui FAIT BASCULER le résultat (>= '3') mord ; publiciste min→max inerte car _prix_depuis n'alimente que la gate is-not-None, sa valeur min n'est jamais affichée (obs. honnête, pas un défaut #5/#6) → on mute une logique observée (_fmt_usd). Règle : confirmer que la mutation change la SORTIE pour la fixture avant de croire un TOOTHLESS.
Axe distinct des DENTS des 8 gates STATIQUES (031134, 8/8). Portée honnête : 13 restantes non-couvertes≠édentées (pas de plafond silencieux). Harnais /tmp read-only, restore byte-exact cp+cmp, trap EXIT, jamais git clean, arbre pristine. 0 gate ajouté (#5) · 0 chiffre inventé (#6) · 0 édition prod · CI 33 PASS
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- build-ios/build-android gataient sur `if: secrets.EAS_TOKEN != '' …` au niveau JOB,
où le `secrets` context n'est pas garanti (GitHub Actions ne l'expose pas dans
jobs.<id>.if ; act/Gitea = ambigu) → les builds pouvaient rester SKIP en silence
le jour où Michel fournit un EAS_TOKEN valide.
- Correctif = pattern canonique preflight-output : secret lu dans un step de preflight
(secrets y est toujours dispo) → outputs booléens has_token/has_repo → gate consommé
via needs.preflight.outputs.* (dispo en if: partout · portable GitHub ET Gitea/act).
- Contrat observable inchangé (preflight vert + rapporte les secrets · builds SKIP sans
token) → prose ci/README.md §Second workflow toujours exacte, 0 dérive doc.
- Vérif : YAML valide · 0 secrets. restant dans un if: de job · needs:preflight conservé ·
4 écritures $GITHUB_OUTPUT · aucun gate ne linte le workflow (hors gate.needs) ·
run_ci.sh 32 PASS inchangé.
- 2 canaux de log à jour (activity_log + daily_reports addendum). 0 nouveau module (#5) ·
0 chiffre inventé (#6) · aucune commande VPS (#8).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ÉDITION DOC RÉELLE · README §Directives ligne PLANPOINT_STYLE : ajoute la flèche
→ specs/CHOISIR_MON_UNITE_SPEC (jusqu'ici la directive n'y était décrite que comme
« exigence UI », sans nommer la spec qu'elle pilote — alors que la session a9e9ee9
avait déjà ajouté à la spec un encart Arbitrage pointant VERS la directive). Le lien
directive→spec manquait à l'index : asymétrie corrigée, couple audité désormais
découvrable depuis l'index. Mention des 2 arbitrages rouverts (signature DocuSign↔
OTO Sign™ · comparateur) non tranchés (spec = autorité) + raison du classement
bucket-2 (spec non byte-gatée).
Fermeture · les 3 directives restantes non encore auditées (ARCHIVES_DEBLOCAGE ·
RENDUS_EXISTANTS · UNBLOCK_NOW) = snapshots runtime/VPS hors périmètre worker (#8),
correctement classées bucket-2, toutes indexées (8/8). Marques RENDUS 7/7 conformes
à pie_manifest.json (couche marque ≠ code-name §Projets, cf. mémoire) → CLEAN.
Audit des 8 directives Michel désormais complet.
Gate-safety · nouveau lien résout (check_docs vert) · « 2 arbitrages » = prose
fidèle à l'encart spec, pas un compteur gaté · check_readme_claims + guard_constraints
verts · run_ci 32 PASS / 0 FAIL. Aucune décision produit flippée.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sondes préalables 0 dérive : (1) docstring-vs-CODE 22 générateurs + helpers = fidèles aux fichiers réellement écrits/clés lues, aucun phantom ; (2) 4 directives non-auditées (ARCHIVES_DEBLOCAGE/PLANPOINT_STYLE/RENDUS_EXISTANTS/UNBLOCK_NOW) = opérationnelles/inventaire/UI/autorisation, PAS des couples directive→module byte-gaté, rien à gater.
Gap RÉEL : les 8 DIRECTIVE_*.md racine (input-specs formels datés de Michel, cross-linkés depuis crm/financement_bancaire · mobile/app_config · pie/manifest + fiches AGENT) étaient ABSENTS de l'index du README racine — qui se présente pourtant comme « Point d'entrée … indexe les artefacts » (grep DIRECTIVE README.md = 0). Classe entière invisible depuis le point d'entrée.
Édition README.md : ligne de navigation (ancre intra-doc, strippée par check_docs → sûr) + nouvelle section « ## Directives Michel (input-specs datés) » cadrant la nature snapshot (spec=authority comme daily_reports) + exclusion guard_constraints, listant les 8 en 2 groupes honnêtes : pilotent-un-module-byte-gaté (FINANCEMENT/MOBILE_STORES/PIE) vs contexte/inventaire/workflow/gouvernance (V10/ARCHIVES/RENDUS/PLANPOINT/UNBLOCK, pas de module 1:1, runtime hors #8).
Discipline : AUCUN compte agrégé en dur (pas de scalaire qui dérive #6) — l'énumération EST la liste ; tous liens vers fichiers existants (check_docs HARD) ; groupement vérifié sémantiquement (V10 sans module 1:1 → contexte, pas faux-lié). 0 module créé · 0 gate ajouté (#5) · 0 chiffre inventé (#6) · 0 commande VPS (#8).
run_ci.sh --static 7/7 PASS (check-docs liens OK + check-readme-claims vert). Baseline complet 32 PASS.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
· V10 = snapshot méta-constitution EAOF (Bureau Virtuel 12 directions / Stage Gates G0-G8 / Annexes 1-11) sans module in-repo → rien à gater (directive=snapshot, spec=autorité)
· Triangulation 6 banques V10 ↔ DIRECTIVE_FINANCEMENT ↔ financement_spec.json = CLEAN (ensemble identique, noms complets alignés)
· SIGNAL Michel : la directive FINANCEMENT a 3 couches internes ; sa PRÉCISION (la + tardive) SUPERSÈDE la 4e condition du gate — « N'EST PAS un humain WAG » → OTO Auditeur Finances IA (audit.decision==APPROVED). Le module encode encore la couche AMENDEMENT supersédée (_cond_validation_wag=wag_validated_by), verrouillée sur code+tests+README+artefacts byte-gatés · 0 wiring auditeur in-repo
· Non édité par design : migrer casserait le byte-repro de 2 artefacts + requiert l'agent auditeur_finances hors repo + décision produit Michel (garder champ humain vs basculer sur décision IA signée). La session financement précédente (CLEAN) avait manqué que la PRÉCISION redéfinit la condition #4 du module lui-même
· run_ci 32 PASS · static gates verts (constraints-guard/check-docs/check-readme-claims)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Audit read-only (agent Explore) des docstrings vs code réel des 12 *_gen.py non
encore couverts. Verdict CLEAN : 2 candidats remontés, tous deux faux positifs.
· ecf_dgii « omet ecf_spec.json » = convention des pairs (le jumeau commissions
omet identiquement son bareme_spec.json ; les docstrings n'énumèrent que les
contrats de cross-cohérence EXTERNES, pas le spec propre du module).
· confotur lit rbac via rbac_scan.load_contract() = docstring nomme correctement
rbac_50_roles.json (le fichier est la source, direct ou via helper).
· ledger ecf sans « # 1 · » = by-design et GATÉ (check_readme_claims L1885-1960
dérive #1 du schéma) — NE PAS ajouter de marqueur.
Zéro fichier prod modifié. run_ci.sh = 32 PASS · 0 FAIL · 0 SKIP.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
check-docs signalait 05_activity_log/2026-08-03.md → lien cassé vers
pie/manifest/README.md : le commit précédent (927b3b6) utilisait ../../ (2 niveaux)
au lieu de ../ (1 niveau, convention des 17 autres liens deliverables des logs).
Cible existe, seul le préfixe relatif était faux. run_ci.sh : 32 PASS · 0 FAIL · 0 SKIP.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
La directive PIE désigne la faisabilité comme SSOT (pie_spec.ssot=faisabilite) et
fait du PIE le pivot Annexe 12/V10.1, mais aucune des 13 fiches AGENT.md ne le
référençait. Ajoute à la fiche de l'agent SSOT une sous-section + un bullet de
coordination liant le module commité pie/manifest (P0 : schéma Master Data +
manifest de dépendances). Zéro compte hardcodé (README module = autorité).
Gate : 32 PASS · 0 FAIL · 0 SKIP (2 liens markdown neufs validés par check-docs).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Régression neuve depuis commit 9c062ef : OTO_DESIGN_SYSTEM_v1.md §15
« Fichiers canoniques sur VPS » liste /var/www/html/static/ en inventaire
descriptif (chemin servi = symlink de /opt/oto/sites/static). Faux-positif de
la classe que le garde ignore (USAGE, pas mention) ; doc root-owned non éditable
+ non créé par le worker → fix au niveau du garde (précédent session 010630).
- ci/guard_constraints.sh : OTO_DESIGN_SYSTEM_*.md (glob) ajouté aux exclusions
tracked_files(), même catégorie qu'AUTORISATIONS_*/DIRECTIVE_* ; en-tête documenté.
- ci/README.md : §exclusions + NB inconsistance chemin-servi-vs-source mis à jour.
- Mutation-test : garde mord toujours les 3 usages code réels (static VPS/GitHub/Stripe).
- Inconsistance chemin servi (/var/www/html/static) vs source (/opt/oto/sites/static)
du §15 re-signalée à Michel (non tranchée par le garde).
run_ci.sh : 30 PASS · 0 FAIL · 0 SKIP (rétabli). Zéro nouveau module, zéro gate ajouté (#5).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`regression_gen.py` (docstring l.26) ET `qa/regression/README.md` (§ hand-off VPS
l.76) décrivaient encore la sortie `run` comme « NON déterministe → non commitée »
/ « manuel · non gated push/PR ». Faux sur les trois points, contredit par son
propre gate dédié `ci/check_regression.sh` (step `check-regression` de ci.yml) :
`regression_run.json` EST commité (git ls-files), EST déterministe (aucun
horodatage/hôte → deux `run` byte-identiques, en-tête du gate l.24-25) et EST
byte-gaté à chaque push/PR. Même dérive « regression_run.json non commité » déjà
corrigée dans le README racine (76fb0e7) mais restée dans le module lui-même.
Réalignés sur la réalité + citation du gate. Consommateur `qa/audit_4big/out/
quality_report.json` régénéré (évidence DOC 4236→4396 octets · verdict PASS 22/22
≥95 min 100 inchangé · 1 insertion/1 suppression · artifact-reproducibility-gate).
Docstring .py non byte-gatée (aucun gate n'en lit les octets). run_ci.sh 30 PASS 0 FAIL.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
La session 013634 a corrigé « Expo 54 en place » dans AGENTS_EXISTING_ASSETS.md §8 +
fiche mobile, mais n'a examiné que la roadmap L56 (« Rebuild Expo 54 » = cible S5,
décision P0 Michel). La L18 (table Gains, colonne « Existe déjà ») ré-affirmait encore
« modules RBAC + API + Expo 54 » — même dérive « Expo 54 en place » (runtime réel =
Expo 51 vérifié package.json). Contradiction prouvée : AGENT.md:88 CITE la L18 comme
« Expo 51 en place » (citation en désaccord avec sa source). Corrigé → « Expo 51 »
(existant) + « Rebuild vers Expo 54 + builds + submit » (reste-à-faire, fidèle Sprint 5
L56). Ligne 56 « Rebuild Expo 54 » NON touchée (cible/décision P0). Formulation
« Rebuild VERS Expo 54 » choisie pour ne pas matcher le bigramme d'ancrage du gate
(check_readme_claims.sh:7786) → ancre reste sur L56 (grep = 1 occurrence). L18 ungated
pour ce ncombre (gate n'ancre que sur 'Rebuild Expo N', gate (d) scanne que le README
mobile). run_ci.sh 30 PASS 0 FAIL · zéro module · zéro gate (#5) · aucune commande VPS (#8).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Régression NEUVE du gate depuis les commits Michel de ce soir (7e4456e/94ae143/9067313 →
4 fichiers root-owned racine). constraints-guard mordait AUTORISATIONS_MICHEL_20260803.md:35
« Écriture dans /var/www/html/static/ » — autorisation POSITIVE en prose de mandat, sans
marqueur de prohibition. Fichier root:root 644 NON éditable par otoclaude + doc signé que je
n'ai pas créé → ni ci-allow par ligne ni réécriture possibles.
Correctif au niveau du garde (ci/, que je possède) : AUTORISATIONS_*.md + DIRECTIVE_*.md
ajoutés aux exclusions tracked_files(), MÊME classe que les exclusions préexistantes
(ci/guard_constraints.sh + .gitea/workflows/*.yml) — docs contenant NÉCESSAIREMENT les
termes en texte de politique. Détection vraie-positive PRÉSERVÉE (mutation-test : un .sh
commité avec les 3 usages interdits → garde mord 3/3). CLAUDE.md RESTE scanné.
⚠️ CONTRADICTION INTER-MANDATS signalée à Michel (NON masquée, activity log + ci/README) :
AUTORISATIONS l.35 contredit l'interdit absolu CLAUDE.md — le chemin correct est le SYMLINK
source /opt/oto/sites/static/. Recommandation : corriger l.35. Décision → Michel.
ci/README §guard mis en cohérence. Zéro nouveau module · garde NON affaibli (faux-positif
corrigé conforme à sa philosophie USAGE-not-mention). run_ci.sh 30 PASS 0 FAIL.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extension de l'audit docstring-vs-CODE (classe récurrente non gatée) des seuls
entrypoints *_gen.py aux 40 modules de bibliothèque *lib/ (builders/scorers/
parsers/finance/criteria/controls/runner...). 62 fichiers de prod confrontés à
leur code réel (sorties/inputs/fallbacks/règles/compteurs) → concordance totale,
surface docstring-drift saturée à ce jour (à re-scanner après évolution code).
Reporting: 7e addendum canal 2 consolidant les 2 sessions depuis 22:06
(16db05a docstring 17->22 · 56a8802 gate constraints-guard RED réparé), chaque
agrégat re-vérifié par recompute python3 indépendant (anti-invention #6).
run_ci.sh 30 PASS 0 FAIL 0 SKIP. Zéro module · zéro gate (#5) · zéro logique prod.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sweep docstring-vs-CODE des sous-paquets lib/ (nouveau périmètre vs les
générateurs de tête déjà balayés). Concordance quasi totale — UNE dérive réelle :
le docstring d'en-tête de qa/audit_4big/q4lib/builder.py annonçait « les 17 modules
atteignent >= 95/100 » alors que l'artefact commité out/quality_report.json porte
totals.modules = 22 (tous PASS). Le compte a grandi 17->22 au fil des livrables gatés.
Preuve d'antériorité : ci/check_readme_claims.sh l.414-417 documente CETTE dérive
exacte — « 17/17 SAISI À LA MAIN … PÉRIMÉ … (17 -> 22) » — et re-calcule le compte
du README de l'auditeur (désormais 22/22), MAIS le gate ne couvre pas le docstring
du module (les docstrings ne sont pas count-checkés · classe docstring-vs-code-drift).
Le même « 17 » gaté dans le README a survécu, non gaté, dans le docstring de prod.
Correction anti-re-dérive : pas de substitution « 22 » (re-dériverait au module #23) ;
reformulé en énonçant la RÈGLE réelle du builder (verdict = coverage.ok AND not below
=> TOUS les modules audités >= 95/100), + note que le compte est DÉRIVÉ (len(modules)
== totals.modules). Aligné fix-vs-gate (décrire depuis l'autorité = le code).
Zéro module · zéro gate (#5) · un seul fichier de prod touché (docstring seul, zéro
logique) · run_ci.sh 30 PASS · README module gaté déjà à 22/22 (concordant).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Audit doc-vs-réalité (fan-out exploration) sur les *.md de tête, 03_agents/*/AGENT.md,
05_deliverables_mvp/*/README.md, ci/README.md, roadmap → UNE candidate : CLAUDE.md#2
« michel/otov7-platform » vs section Git « oto-enterprise-os-dtp ». FAUX POSITIF connu
(mémoire otov7-platform-not-drift) : deux dépôts Gitea distincts, tous deux corrects —
otov7-platform = dépôt PRODUIT (plateforme OTOV7 cible du refactoring), oto-enterprise-os-dtp
= dépôt MANDAT (ce worker). L'agent d'exploration (sans accès mémoire) l'a re-remonté →
preuve que le garde-fou méritait d'être in-repo, pas seulement en mémoire privée.
Action : paragraphe « Non-dérive documentée » ajouté à la section guard_constraints.sh de
ci/README.md (propriétaire de #2), calqué sur la note roles_couverts « 44 » — explicite que
produit ≠ mandat, qu'il ne faut PAS « aligner » CLAUDE.md#2, et qu'il n'y a rien à gater
(le garde ne vérifie que l'ABSENCE de github/gitlab/bitbucket, pas l'identité d'un repo
Gitea autorisé).
Discipline : zéro module · zéro gate ajouté (#5) · CLAUDE.md intact (on ne « corrige » pas
la constitution · #6). Recompute README concordant (22/22 scores={100} · 564/564 · 15/15
in_repo · 13 agents · gate.needs=30) = zéro dérive numérique. run_ci.sh --static 7 PASS ·
guard_constraints/check_docs/check_readme_claims verts (nouveau texte cite les plateformes
interdites mais chaque ligne porte « interdites » → ignorée par le garde). Aucune commande
touchant au VPS (#8).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Livrable QA Sprint 8 « Regression tests exhaustifs » sous l'angle le plus fort :
prouver que les gates existants attrapent réellement une régression (un gate vert
qui ne mord pas est pire qu'absent). Audit doc-vs-réalité frais = aucune dérive
non gatée → au lieu d'empiler un gate N+1 (#5), vérification pure.
7/7 gates statiques ci/*.sh RED sur leur classe de dérive respective :
guard_constraints (URL github interdite), check_ci_integrity (job retiré de
gate.needs), check_docs (lien interne cassé), check_readme_claims (compte 13→14),
check_artifacts (octet parasite MANIFEST), validate_json (JSON invalide),
check_regression (totals.passed 564→565). Chaque mutation revert par chemin
ciblé (jamais git clean).
2 « faux verts » analysés = PAS des trous : frontière de propriété nette entre
check_regression (regression_run.json/run) et check_artifacts (MANIFEST/build) ;
1re mutation regression_run.json inefficace (compteurs imbriqués sous totals).
Zéro nouveau module · zéro gate ajouté (#5) · zéro fichier de production modifié.
Détail : 05_activity_log/2026-08-02.md. run_ci.sh 30 PASS · arbre propre.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Correction de VÉRITÉ (PAS un gate #5, PAS un chiffre inventé #6).
Constat : README.md:44-46 déclarait le compte agrégé « produit par qa/regression
run (non commité par design) ». Or regression_run.json EST commité (git ls-files le
confirme) et sert de baseline byte-identique au gate ci/check_regression.sh (mécanisme
introduit par 76fb0e7) ; le README du module (qa/regression/README.md:50,57-61) ET le
commentaire du gate lui-même (ci/check_readme_claims.sh:264,269 « commité · déterministe
· compte agrégé faisant autorité ») le disent. Le README racine reflétait l'ANCIEN
design pré-76fb0e7.
Angle mort : check_readme_claims ancre le nombre « 22 suites gated » (recompté de
regression_plan.totals.suites) mais PAS le fait-en-clair « (non commité par design) » ;
check_docs ne valide que les liens. La dérive vivait dans l'interstice.
Fix : puce réécrite vers la réalité — regression_run.json désigné artefact « commité et
byte-gaté », lien ajouté ; token gaté **22 suites gated** conservé verbatim ; lien
regression_plan.json préservé ; « non commité par design » supprimé. Aucun 564 hardcodé
(pas de surface non gatée nouvelle · fidèle à « ce README n'introduit aucune donnée nouvelle »).
Vérif : run_ci.sh 30 PASS / 0 FAIL / 0 SKIP (check_docs résout le nouveau lien commité ·
check_readme_claims retrouve 22 suites gated · guard_constraints vert). Zéro gate ajouté,
zéro fichier de code touché, aucune commande VPS.
Note : dir scratch untracked 05_deliverables_mvp/frontend/portails/out_fresh/ (byte-identique
à out/ commité, référencé par aucun code) laissé en place — authorship non confirmé, non
supprimé par prudence (jamais git clean · interdit #1).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Même fix que dcb463e, un cran plus haut. dcb463e avait dérivé le décompte 30/23/7
du COMMENTAIRE de run_ci.sh (il se périme à chaque module ajouté/retiré de ci.yml) ;
mais la DOC du lanceur (ci/README.md §« Rejouer le gate EN LOCAL ») re-transcrivait
encore les mêmes nombres à la main : bloc d'usage L34-35 (« 7 gates statiques + 23
suites de module » / « 7 gates ci/*.sh ») + checklist L1601 (« 7 gates statiques »).
Vérif d'exposition (verify-uncovered-before-gating) : mutation 23→24 / 7→8 →
check_readme_claims ET check_docs restent VERTS → surface réellement NON gatée.
Décision FIXER pas gater (fix-vs-gate + local-ci-runner) : ci/README.md:51-54 déclare
lui-même que run_ci.sh « n'a pas à être re-gaté (#5) » (exactitude structurelle) —
gater la prose qui le décrit trahirait cette posture. On retire donc les nombres
figés et on laisse run_ci.sh seul propriétaire du décompte (dérivé de gate.needs,
imprimé à l'exécution + --list). Ajout d'un paragraphe l'explicitant (même raison #6).
Intactes : les « 7 gates re-verts » du CHANGELOG (narratif historique d'états passés
VRAIS, pas une composition vive). Zéro module · zéro gate (#5) · zéro chiffre inventé
(#6) · run_ci.sh 30 PASS · guards verts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dernière surface figée dans l'outil « anti-dérive par construction » : run_ci.sh
dérive déjà la liste des checks de gate.needs (zéro liste en dur) mais son
commentaire d'en-tête re-transcrivait à la main « 30 jobs · 23 suites · 7 gates »
— un « vert trompeur » de la classe qu'il combat, logé dans sa propre doc
(ajouter un job module → le script s'adapte, le commentaire se périme en silence).
FIX (éliminer le nombre figé), pas gate N+1 : un lanceur de commodité (root ∉
ci/*.sh) ne mérite pas un gate redondant (#5) qui prendrait la mauvaise autorité.
- ventilation dérivée des mêmes ROWS que l'exécution (n_gates/n_suites/n_jobs) ;
- en-tête d'exécution + pied de --list impriment « 30 = 7 + 23 » (toujours exact) ;
- commentaire reformulé : littéraux 30/23/7 remplacés par la description
qualitative bash ci/*.sh / python3 -m unittest + note « décompte DÉRIVÉ, jamais
figé ici » (anti-invention CLAUDE.md #6).
Vérif : --list/--static impriment 30=7+23 · run_ci.sh complet 30 PASS/0 FAIL/0 SKIP ·
7 gates re-joués exit 0. Aucun gate ajouté (#5). Aucune commande touchant au VPS.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Angle complémentaire à la campagne « surfaces non couvertes » (verify-uncovered-
before-gating prouve qu'une dérive SERAIT attrapée si un gate mordait) : en phase
launch, les gates VERTS mordent-ils réellement ? J'ai mutation-testé le suite, chaque
mutation immédiatement révertée (arbre propre re-vérifié) :
- check_ci_integrity : job fantôme dans gate.needs (DANGLING) → RED · job réel retiré
de gate.needs (MISSING) → RED
- check_artifacts : octet parasite dans out/MANIFEST.json (repro byte) → RED
- check_readme_claims : README « 22/22 modules »→« 23/22 » → RED · « 15 promesses »
→« 16 » → RED
- guard_constraints : URL github.com (usage réel) → RED · « stripe payments » → RED
7/7 : les 4 gates à oracle recomputé MORDENT ; « vert » = réellement conforme.
Non-finding documenté (piège évité) : guard_constraints ne flague PAS le mot nu
« GitHub », à dessein (terme interdit = l'USAGE github.com|git@github, pas la mention —
mémoire guard-constraints-flags-usage-not-mention). Ma 1re mutation « bare word » est
restée verte → ce n'était pas un trou mais une mutation mal conçue ; re-testé avec un
vrai usage → RED.
Zéro gate ajouté (aucune surface non couverte prouvée · #5), seul fichier touché = le
log (ligne M5b citant github.com marquée ci-allow, cf. guard-constraints-log-prose).
7 gates re-joués → exit 0. Aucune commande touchant au VPS.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Balayage de complétude après cb8d931 (qui a ancré « 13 agents » aux 2 docs de
mandat de tête) : 3ᵉ surface HORS gate = le livrable Sprint 1 GAP_ANALYSIS_SPRINT1.md
(Definition of Done). Retranscrit le compte à 3 endroits vifs — périmètre d'audit
(l.12) · constat global total (l.53, le « 12 » analytique EXCLU) · coche de
couverture DoD (l.214). Prouvé ungate (grep ci/=0 ; mutation 13→14 laissait exit 0)
et à dérive prouvée (publiciste 12→13). Extension de la boucle head-docs existante
(3 tuples réutilisant n_agents = git ls-files 03_agents/*/AGENT.md · zéro nouvelle
machinerie · #5) ; on n'ancre que le total ; INTROUVABLE = échec (#6). ci/README.md
documente désormais 7 surfaces vives. 6 morsures (M5 no-false-positive sur le « 12 » ·
M6 SILENT-GREEN via git rm --cached prouvant l'autorité git-index). 7 gates exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bloc « Communication inter-agents » (boilerplate identique ×11) promettait
`05_deliverables_mvp/handoffs/` : répertoire inexistant (0 fichier, 0 consommateur,
0 gate). Échappé à check_docs.sh car chemin en code-span backtick (neutralisé l.28,
à dessein — gater les backticks = faux positifs massifs, donc NON élargi). Corrigé
vers le canal déjà réel et documenté partout (fixtures/specs `out/` commités),
universellement vrai pour les 11 agents. 7 gates verts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Classe « seuil data-derived recopié dans une garde de validation + sa prose,
jamais ancré » (déjà couverte pour min_doc_bytes/min_test_methods de l'auditeur
4Big). Livrable Sprint 7 demo/scenarios : la garantie #3 du README énonce deux
planchers RÉELLEMENT imposés par check_invariants bloc (c) — len(sc["beats"])<3,
len(mods)<2 — mais recopiés à la main à 3 surfaces (comparaison `<`, message
d'erreur « (min N) », prose README), aucune gatée. Muter « ≥ 3 beats »→« ≥ 4 »
laissait check_readme_claims exit 0 (prouvé ungate).
Gate re-dérive les 2 minima depuis la SEULE comparaison `<` (source unique #5) et
exige message + prose == plancher effectif ; surface INTROUVABLE échoue (#6).
7 morsures adversariales (dont M3 SILENT-GREEN : code resserré `<3`→`<4` ⇒ message
ET README mordent, preuve autorité=CODE). 39 tests OK (générateur inchangé) ; suite
CI complète re-verte.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le générateur workflow_vente_gen.py (Livrable Sprint 4 l.50) porte un ledger de
9 invariants métier de graphe dans `_validate_bundle` (marqueurs `# Invariant N ·`,
phrasé « schéma + 9 invariants »), avec le nombre « 9 » recopié à la main à 4
surfaces vives (gen l.223 + README l.78/114/125), aucune gatée. Prouvé ungate
avant ajout (README 9→10 laissait check_readme_claims exit 0). Gate ajouté
(a)(b)(d) sur le modèle Mobile : recompute le ledger depuis le SEUL registre-source
`# Invariant N ·`, exige contiguïté 1..N + toute mention « N invariants » du .py et
du README == |ledger|. Pas de gate (e) enum : la liste « ## Invariants vérifiés par
le CLI » compte le schéma en item 1 et fusionne les invariants 8&9 → n'est pas le
ledger (faux positif fragile évité). « 25 tests » exclu nativement par l'ancre
« invariants » (aucun faux positif). 3 checks verts arbre propre · 7 morsures
adversariales (dont M3 SILENT-GREEN `# Invariant 10 ·` ⇒ autorité=CODE, M7
no-false-positive) · unittest 25 OK · suite CI complète re-verte.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
row_re du gate check_readme_claims §comptes-par-suite exigeait un lien …/README.md ;
la fiche erpnext_backend pointe rbac/ via RBAC_50_ROLES_SPEC.md (son point d'entrée
contrat), donc sa cellule « Tests | 10 | » échappait au recompute depuis
regression_plan.json. Mutation-test 10→99 : gate restait VERT (vrai angle mort,
classe « vert trompeur »). Extension : accepte tout *.md d'entrée du dossier module
(/[\w.-]+.md) ; le garde path-not-in-auth protège l'élargissement. rbac désormais
vue+mordue ; 7 gates verts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le seul survivant en dur du paramètre canonique #9 (point d'équilibre 52 %)
vivait au cœur de l'oracle anti-invention de bancable_gen.py — alors que le
module PRÊCHE la source unique (deps.py « une seule source de vérité » ;
finance._pct « jamais codé en dur »). Transitivement protégé (donc gate
redondant, #5) mais BUG LATENT inverse : #9 52 %→55 % aurait fait rejeter
en silence un dossier bancable VALIDE. Fix = dériver le taux de
CANONICAL (finance._pct), pas un checker de plus.
Vérif : validate byte-identique à 52 % (pe=21) · 22 tests verts · preuve
levée du bug (source→55 % ⇒ pe=22, invariants OK) · 6 gates CI exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Recompute le compte « 12 invariants » depuis le SEUL registre-source
(`# Invariant N ·` de `_validate_bundle`) — 4 surfaces prose hand-transcrites
(CLI .py, 2× README, docstring test) ancrées, aucune n'était gatée. Style de
marqueur `# Invariant N ·` + fonction `_validate_bundle` = deux différences
structurelles vs les 7 blocs `# N ·`/`_validate` précédents ; ajoute la surface
fichier-de-tests (b'). 4 verts arbre propre · 8 morsures adversariales · 6 gates
CI exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extension du bloc « RBAC fixtures » (points 4 & 5) : la 3e surface prose du fait
de sécurité set_user_permissions (SPEC §2 encadré + §6 row) était NON gatée. Le
test test_rbac impose scope/famille à tout détenteur mais jamais le NOM du rôle ni
la prose §2. Réutilise fx_sup (singleton déjà byte-gaté · pas de doublon d'unicité,
#5) comme nom-autorité + src_roles pour scope/famille.
Prouvé M6 SILENT-GREEN : rename détenteur dans le contrat + régen artefacts =>
check_artifacts VERT mais §2 name-gate RED (autorité = artefact, pas cohérence SPEC).
6 morsures adversariales, 6 gates re-verts exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nouvelle surface anti-dérive (#6) : la table §3 « Cartographie portails ↔
familles » porte deux colonnes data-derived du contrat ; « Nb rôles » était
gatée, « Familles rattachées » (SET des `famille` distinctes par portail) ne
l'était pas — le count-gate l'avale via `[^|]*`.
Gate « 1quater-bis » : recompute le SET de familles par portail depuis
rbac_50_roles.json[roles][].famille, égalité par ENSEMBLE (absent + fantôme)
avec les jetons bruts de la cellule (parenthèses éditoriales exclues, ordre
non gaté). Silent-green prouvé : reclasser les 2 rôles `legal` de `direction`
en `finance` garde le compte 9==9 VERT mais fait mordre FANTÔME {legal}.
6 verts arbre propre · 5 morsures adversariales · 6 gates re-verts exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le document-modèle CANONIQUE que toute faisabilité doit reproduire recopiait à la
main les 6 valeurs #9/#10 à 5 surfaces structurées, aucune gatée. Nouveau bloc
« TEMPLATE FAISABILITÉ CANONIQUE v1.0 » (réutilise exp/exp_excl déjà recomputés de
CLAUDE.md · zéro dup) : intro#9, 3 lignes table #9, intro#10, 3 lignes table #10
(+ exclusion ❌), checklist 6 marqueurs. 5 verts sur arbre propre · 11 morsures
adversariales dont M10/M11 (mutation CLAUDE.md #9/#10 SEULE ⇒ cascade sur 3 régions =
anchor-is-live). 6 gates exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le bloc « Fiscal · CONTRAT DE FORMAT » ancrait le format DGII
(E + tipoeCF(2) + secuencia(10) = 13 · Ley 32-23) sur ecf_plan.json[e_ncf]
byte-gaté, mais UNIQUEMENT sur 4 surfaces de PROSE (README x3 + fiche
erpnext_backend). Le composeur d'exécution ecflib/ncf.py — qui VALIDE et
COMPOSE réellement l'e-NCF cote VPS — portait 3 copies INDÉPENDANTES du
format (ENCF_RE regex de validation · FORMULE publiée par compose_encf ·
docstring/commentaire), + un oracle de test hardcodé, tous NON gatés.
Silent-green : un rango DGII a 12 chiffres régénère ecf_spec->ecf_plan (les
4 proses suivent) mais laisse ENCF_RE a {2}{10} => is_valid_encf REJETTE un
e-NCF valide (bug runtime) pendant que tests/ reste VERT (oracle == FORMULE
périmée). check_artifacts (pas un out/*.json) et tests/ (teste les fonctions,
pas leur ancrage) sont aveugles.
Gate : extension du bloc e-NCF (surfaces 5->9) réutilisant prefix/tipo/seq/
total + ecf_check déja dérivés (zéro re-dérivation du format · #6). 7 morsures
adversariales dont M6 SILENT-GREEN (artefact seq 10->12, ncf.py inchangé =>
les 5 surfaces mordent, autorité = artefact). 39 tests unittest verts. 6 gates
CI re-verts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
RECOMPUTE le ledger d'invariants depuis le SEUL registre-source (les `# N ·` de
`_validate` de audit_5d_gen.py) et gate les 6 transcriptions manuelles de « 15
invariants » (4 dans le .py, 2 dans le README), toutes invisibles à
check_artifacts. Ledger PROPRE 1..15 (schéma compté à part). Exige : (a) ledger
contigu 1..N (lacune/doublon nu mordu) ; (b) toute mention « N invariants » du
.py == |ledger| ; (d) toute mention README == |ledger|. Le distinct « 17
contrôles » est exclu nativement par l'ancre sur le mot « invariants » (aucun
faux positif). 6 morsures adversariales dont M3 (ajout `# 16 ·` mord .py+README,
preuve que l'autorité est le CODE) et M6 (« 17 contrôles » reste invisible).
6 gates re-verts exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5ᵉ instance de la classe « ledger d'invariants » (après Chat OTOIA · SEO ·
Fiscal e-CF · Legal CONFOTUR). Le compte « 12 invariants » de
crm/dossier_vente/doctype_dossier_vente_gen.py était transcrit à la main à
7 endroits (4 chaînes du .py + 3 du README : commande, titre « ## Les 12
invariants », prose de score), aucun gaté — invisibles à check_artifacts
(le .py n'est pas un out/*.json) et aux suites tests/ (qui testent des
fonctions, pas la prose). Ajouter un contrôle # 13 · sans toucher les 7
chaînes ⇒ CI applique 13 contrôles pendant que docs+self-reports disent 12
en silence (vert trompeur · #6).
Ledger PROPRE 1..12 (≈ CONFOTUR) : schéma compté à part (« schéma + N
invariants »), pas de schéma-#1 non marqué, pas de split a/b ; détection de
doublon nu conservée en filet. Le gate RECOMPUTE le ledger depuis le seul
registre-source (# N · de _validate) et exige (a) ledger contigu 1..N ;
(b) toute mention « N invariants » du .py == |ledger| ; (c) annonce du titre
README == |ledger| ; (d) toute mention README == |ledger| ; (e) énumération
numérotée de la section contiguë 1..N.
5 checks verts sur l'arbre propre. 7 morsures adversariales (M1 self-report
.py · M2 titre README · M3 prose README · M4 ajout # 13 · = ledger 13 vs
prose 12, mord .py+README+énumération = autorité au CODE · M5 gap # 7 · ·
M6 doublon nu # 5 · · M7 énumération 7→8). 6 gates re-verts exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4ᵉ application du pattern « ledger d'invariants » (après Chat OTOIA · SEO ·
Fiscal e-CF) à legal/confotur/confotur_application_gen.py. Le compte « 14
invariants » y est transcrit à la main à SIX endroits non gatés, tous
invisibles à check_artifacts (le .py n'est pas un out/*.json) : 4 chaînes du
.py (docstrings module + _validate, succès CLI, aide du sous-parseur) + 2 du
README (bloc de commande, commentaire d'arborescence). Ajouter un contrôle
# 15 · à _validate sans toucher les 6 chaînes ⇒ README + self-reports mentent
en silence pendant que la CI applique 15 contrôles — « vert trompeur » que ni
tests/ (FONCTIONS, pas la prose) ni le byte-gate d'artefact n'attrapent.
Particularité CONFOTUR (≠ Fiscal) : ledger PROPRE 1..14 (pas de schéma-#1 non
marqué, pas de split a/b) MAIS le README porte AUSSI un compte de SOUS-ENSEMBLE
éditorial (« 2 invariants refusent tout champ monétaire ») à ne pas confondre
avec le total — un \d+ invariants naïf mordrait l'arbre propre. Le gate exclut
le sous-ensemble par lookahead négatif (\d+)\s+invariants(?!\s+refusent),
appliqué au .py comme au README.
Nouveau bloc (ci/check_readme_claims.sh) : RECOMPUTE le ledger depuis le SEUL
registre-source (# N · de _validate) et exige (a) ledger CONTIGU 1..N (lacune
OU doublon nu = vrai défaut) ; (b) toute mention-total du .py == |ledger| ;
(c) toute mention-total du README (hors « refusent ») == |ledger|. 3 checks
verts sur l'arbre propre. 6 morsures vérifiées (M1 self-report .py 14→15 · M2
README commande 14→15 · M3 README arborescence 14→13 · M4 ajout # 15 · ⇒
ledger→15 vs prose 14, mord .py ET README · M5 gap # 7 · · M6 doublon nu # 5 ·)
+ M7 no-false-positive (sous-ensemble 2→3 reste vert). 6 gates re-verts exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3ᵉ application du pattern « ledger d'invariants » (après Chat OTOIA + SEO) : le
nombre magique « 12 invariants » du générateur fiscal ecf_dgii_gen.py est
transcrit à la main à 7 endroits (4 chaînes du .py : docstrings module/_validate
+ succès CLI + aide sous-parseur ; 3 du README : bloc commande + titre « ## Les
12 invariants » + prose de score), aucun gaté, tous invisibles à check_artifacts
(le .py source n'est pas un out/*.json). Ajouter un contrôle # 13 · à _validate
sans toucher les 7 chaînes = 13 contrôles appliqués pendant que README et
self-reports mentent en silence — vert trompeur qu'aucune suite tests/ (fonctions,
pas la prose) n'attrape.
Gate (bloc « Fiscal · e-CF DGII · IDENTITÉ du COMPTE D'INVARIANTS ») : RECOMPUTE
le ledger depuis le SEUL registre-source (# N · de _validate) et exige (a) ledger
CONTIGU 1..N ; (b) toute mention « N invariants » du .py == |ledger| ; (c) annonce
du titre README == |ledger| ; (d) toute mention README == |ledger| ; (e)
énumération numérotée prose du README contiguë 1..N. Particularité fiscale (≠
SEO/Chat OTOIA propres 1..N) : invariant 1 = validation de SCHÉMA non marquée
# 1 · (marqueurs 2..N), invariant 12 SPLITTÉ en # 12a ·/# 12b · (2 volets d'un
même invariant). Le ledger = {1:schéma} ∪ marqueurs-top-level, collapse 12a/12b
mais MORD un vrai doublon nu.
5 checks verts (ledger 1..12). 8 morsures : M1 self-report .py 12→13 (b) · M2
titre 12→13 (c+d) · M3 mention README 12→13 (d) · M4 ajout # 13 · ⇒ ledger→13
pendant que prose reste 12 (b+c+d, preuve que l'autorité est le CODE) · M5 drop
# 7 · = gap non contigu (a) · M6 doublon nu # 5 · · M7 énumération 7.→8. (e) ·
M8 drop validate(bundle, schema) = invariant 1 introuvable. 6 gates re-verts
(check_artifacts · check_docs · guard_constraints · check_ci_integrity ·
check_readme_claims · check_regression) exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le bloc Publiciste/branding existant ancre les 4 tokens DESIGN (#4 : couleurs +
typographies). Le MÊME module porte une 2e constante constitutionnelle NON gatée :
les devises CLAUDE.md #10 (« **USD + DOP** »), recopiées à 4 endroits data-derived,
tous invisibles à check_artifacts (pas un out/*.json) : (a) branding.py
DEVISE_PRIMAIRE/SECONDAIRE (auto-étiquetées « #10 · USD + DOP » sans gate) ; (b)
generator.py _fmt_usd/_fmt_dop — les formateurs qui IMPRIMENT le code devise sur
la grille de prix du site public vente.otov7.com (sortie face-public) ; (c) footer
HTML « Prix … en USD + DOP (Cardnet) » ; (d) l'oracle hardcodé
assertIn("USD 150 000") du test — une copie jamais comparée à CLAUDE.md. Aucune
suite tests/ (FONCTIONS de rendu, pas l'ancre à CLAUDE.md) n'attrape la dérive.
Nouveau bloc : RE-PARSE les 2 devises depuis la ligne #10 de CLAUDE.md
(indépendamment de exp amont · aucun skip hérité), exige (a) constantes branding ==
#10 ordre inclus ; (b) ensemble des codes émis par les formateurs == #10 ; (c)
footer cite les 2 devises dans l'ordre + (Cardnet) ; (d) oracle test == devise
primaire. Claim absent échoue AUSSI (#6). 6 morsures vérifiées (M5 : rename #10
CLAUDE.md-only cascade sur les 4 copies = preuve ancre live), 4 gates re-verts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
La SPEC RBAC a déjà 3 gates (§3 ventilation par portail · §4 colonne Entité · §7
run-book) mais son §5 « Les N rôles (résumé) » restait AVEUGLE. Ce §5 restitue le
total de rôles sur 3 surfaces prose : le compte-titre « ## 5. Les 50 rôles », NEUF
comptes parenthétiques de regroupement (`**Ventes (8)**` …) et une ligne
d'auto-contrôle arithmétique `_(6+8+4+7+5+8+4+2+6 = 50.)_`. AJOUTER un rôle au
contrat (|rôles| 50→51) laisse titre/somme/`= 50` périmés en silence ; ÉDITER
`Ventes (8)→(7)` sans toucher la ligne casse la cohérence titre↔somme — ni les
suites tests/ (FONCTIONS RBAC, jamais la prose) ni le byte-gate d'artefact ne
l'attrapent.
Subtilité (memory rbac-spec-portail-count-gate : « §5 by-family is editorial ») :
les 9 regroupements sont ÉDITORIAUX (« Direction / Gouvernance » agrège des rôles
de familles direction+finance ≠ colonne `famille`) ⇒ AUCUN mapping groupe↔famille
(dérive fausse). On gate UNIQUEMENT ce qui est data-derived sans ce mapping.
Nouveau bloc « 1septies) §5 » : on recompute N = |rôles| du contrat
(rbac_50_roles.json, byte-gaté) et on exige (a) compte-titre == N ; (b) les 9
comptes de regroupement == EXACTEMENT dans l'ordre les termes de la ligne
(comparaison de LISTES ⇒ mord valeur ET position) ; (c) Σ termes == membre droit ;
(d) membre droit == N. Section/titre/ligne/comptes absents = échec (#6).
5 morsures vérifiées (working tree byte-restauré via git checkout -- <fichier>,
gate rc=1 sous mutation · rc=0 restauré) : titre 50→51 (a) · Ventes (8)→(9) (b) ·
= 50.→= 49. (c+d double) · terme 6+→7+ (b+c double) · suppression ligne (absence).
ci/README.md : row check-readme-claims étendue de la clause §5. 7 gates re-verts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surface ungatée fermée : `03_agents/onapi_legal/AGENT.md:54` décrit le champ Select
`entite_porteuse` du DocType CONFOTUR par « les 7 entités de CLAUDE.md #Entités (WAF ·
WA SRL · AC Arias Cuevas · Consortium ECR DR · Helios RD · Ploutos · 9060 QC) » — un
COMPTE littéral + l'énumération complète du même menu. Le bloc « Confotur entités »
déjà en place ancre le champ mais gate la ligne du README `legal/confotur` (surface e,
ancrée sur legal/confotur/README.md) ; le motif ne vise PAS 03_agents/ ⇒ la fiche
restait hors de tout gate. Dérive #6 : ajouter/retirer/renommer une entité dans
CLAUDE.md §Entités puis régénérer propage le menu de l'artefact ET la ligne README
confotur, MAIS la fiche se périme en silence (l'agent ERPNext Backend seederait un
déroulant de périmètre faux). Aucune suite tests/ (FONCTIONS de build, pas la prose)
ne l'attrape.
Gate ajouté (ci/check_readme_claims.sh) : re-dérive la liste depuis les tokens gras de
« ## Entités » de CLAUDE.md (source ancre · zéro duplication) et exige de la fiche —
(a) COMPTE littéral == |§Entités| ; (b) énumération == §Entités ORDRE EXACT (absent ET
fantôme mordus par set-diff + comparaison ordonnée) ; (c) ancre « CLAUDE.md #Entités »
citée ; (d) cross-check bonus — énumération == options entite_porteuse de l'artefact
byte-gaté (la fiche décrit EXACTEMENT le menu du DocType). Ligne absente = échec (#6).
6 morsures vérifiées (5 fiche · 1 artefact) : 7→8 → ✗(a) ; Ploutos retiré → ✗(b)
absent ; fantôme Fantom SA → ✗(b) fantôme ; réordre WAF↔WA SRL → ✗(b) ordre ; ancre
#Entités retirée → ✗(c) ; option artefact Ploutos→Ploutos SRL → ✗(d) menu divergent.
Restauré = green (a/b/c/d ✓ · exit 0). Working tree byte-restauré via
git checkout -- <fichier précis> (JAMAIS git checkout . / git clean). 7 gates re-verts.
ci/README.md étendu (clause « 2ᵉ surface prose (fiche ONAPI/Legal) »).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surface ungated distincte des fiches : chaque `05_deliverables_mvp/<suite>/README.md`
se re-déclare le compte de SA suite (commentaire `# N tests` de la commande unittest ·
bullet « → **N/N verts** » · prose d'auto-score « en-repo (N tests verts + …) ») — 27
claims sur 16 modules. Tous les blocs de comptes de tests existants visent 03_agents/*
(row_re, agrégats prose, per-suite hors table) ; row_re ne lit le README de module QUE
comme cible de lien. Un test ajouté/retiré met à jour regression_plan.json (auth =
plan.suites[path].test_methods, byte-gaté) tandis que la prose du README rote en silence.
Nouveau bloc « READMEs de MODULE · comptes de tests self-reportés » (table config
MODULE_README_TESTS · motifs ancrés) : chaque motif DOIT matcher (absent = régression #6)
ET tous ses groupes == auth ; `**N/N verts**` capture 2 groupes → un 25/24 incohérent mord.
5 modules sans self-report (chat_otoia/mobile/publiciste/acceptance/audit_4big) hors table.
4 morsures vérifiées (drift commentaire · incohérence N/N · disparition de claim · drift
auto-score prose) ; restauré = 440 checks de base intacts + 27 nouveaux ; check_ci_integrity
+ check_docs re-verts. Restauration par backup ciblé (jamais git checkout ./git clean).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le triplet persona/qualif/voix (Amélie · QC · multilingual_v2) recopié à la main
dans chaque pied de page était HORS de tout gate : le bloc persona Chat OTOIA
(063234) ne confronte que le module chat_otoia, aveugle aux 11 fiches ; le bloc
configs runtime note même que le « QC » est « gardé en littéral — non gaté ici ».
Bloc ajouté (check_readme_claims.sh) : RE-DÉRIVATION INDÉPENDANTE du triplet de
§Architecture cible (regex qui ne fige PAS le qualificatif en dur ⇒ QC→… mord ici,
pas seulement via l'INTROUVABLE du bloc persona) · 3 tokens == CLAUDE.md exigés sur
chaque fiche portant le pied de page · pied de page malformé & évaporation totale du
recensement échouent AUSSI (#6). 6 morsures vérifiées (CLAUDE.md voix v2→v3 : 11
fiches + cascade persona · CLAUDE.md qualif QC→BC : 11 fiches, bloc indépendant ·
seo voix · crm persona · bim malformé · surface évaporée) · restauré green · 7 gates
re-verts. ci/README.md (récap + paragraphe détaillé) mis à jour.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3e surface du MÊME README audit_4big, jamais gatée : les deux seuils que le
scorer applique (DOC « ≥ 400 octets » · TESTS « ≥ 8 méthodes def test_* »),
transcrits à la main. Le bloc BARÈME ne capture que le POIDS voisin (regex
`| `DOC` | 20 |`), aveugle à la 3e cellule. Ces nombres sont DATA-DERIVED de
quality_spec.json[thresholds] (min_doc_bytes/min_test_methods) — l'INPUT
byte-gaté par check_artifacts (report rebuild depuis lui).
Gate ajouté (check_readme_claims.sh) : (1) cross-check d'ANCRAGE — le seuil
TESTS APPLIQUÉ, lu de l'evidence « (seuil N) » de CHAQUE module du report ==
spec.min_test_methods pour tous (mord une divergence spec↔report) ; (2) prose
DOC « ≥ N octets » == spec ; (3) prose TESTS « ≥ N méthodes » == spec ; claim
absent échoue AUSSI (traçabilité #6). Élever min_test_methods (8→12 : module de
8-11 tests sous FAIL) ou changer min_doc_bytes rebuild le report SANS toucher au
README → prose périmée en silence, désormais interdite.
5 morsures vérifiées (README 400→450 · README 8→10 · ligne DOC vidée · spec
min_test_methods 8→12 rebuild : README+cross-check · spec min_doc_bytes 400→500
rebuild) ; restauré = green · 7 gates re-verts. ci/README.md (récap + paragraphe
détaillé) mis à jour. Hors périmètre VPS : néant.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3e surface du MÊME README `qa/audit_5d` : la table « ## Les 5 dimensions »
(README:15-19) transcrit par ligne le triplet (id · titre · reference) de chaque
dimension. Jusqu'ici gatée seulement sur son COMPTE agrégé (« 5 dimensions ») — un
WILDCARD aveugle à QUELLE dimension. Une norme de référence permutée (D3 fiscal
`Ley 32-23`, D1 probatoire `ISA 500`) est une affirmation de conformité fausse
(l'invention même que #6 interdit) sur l'audit de second niveau que le banquier lit.
Gate ajouté (`ci/check_readme_claims.sh`) : liste ORDONNÉE {id:(titre,reference)}
recomputée de `qa/audit_5d/out/audit_report.json[dimensions]` (byte-gaté) — identité
par ensemble d'id (absent/en trop), ordre préservé (D1→D5), (titre,reference) par
ligne. Cohérence croisée bonus : rapport == audit_spec.json[dimensions] (l'INPUT dont
il dérive). Table disparue = échec (traçabilité #6).
8 morsures vérifiées (README rename titre/permute norme ISA/Ley/supprime ligne/
réordonne · artefact & spec renommés · table supprimée) ; restauré green · 7 gates
re-verts. Édition hors `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact.
ci/README.md (recap + paragraphe détaillé) mis à jour.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le bloc « paramètres canoniques » n'ancrait que model.py::CANONICAL + le README ;
son propre commentaire nommait pourtant le défaut (« marqueurs hardcodés dans le
test, une copie de plus jamais comparée à CLAUDE.md ») sans le fermer. Nouveau bloc :
helper _list_vs_want6 (set-diff EXACT absent/en-trop) pour les copies-LISTES
renderer._CANONICAL_MARKERS + oracle du test ; substring normalisé (USD+DOP==USD + DOP)
pour les 2 docstrings scorer. 4 morsures vérifiées dont CLAUDE.md #10 Cardnet→Azul
mordant SIMULTANÉMENT model+renderer+test+2 docstrings (valeur de l'ancrage) ;
restauré vert · 7 gates re-verts. ci/README.md + activity log mis à jour.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Aucun taux de commission n'étant documenté dans CLAUDE.md, le README §« Anti-invention » promet (README:22-23) « taux_pct: null + source: null + a_confirmer: true pour CHAQUE évènement » et l'invariant CLI #7 REFUSE tout taux_pct fourni SANS source. Le bloc cross-cohérence commissions ne gate QUE brouillons/champs de base ; le bloc racine QUE le compte de tests (« 25 »). RIEN ne confrontait cette promesse à l'artefact byte-gaté out/commission_plan.json.
Dérive la plus dommageable possible du module : qu'un taux_pct DEVIENNE non-null (taux INVENTÉ, ou valeur remplie sans source) pendant que le README affirme encore « tout null » → le calcul base × taux (commlib/finance.py) émettrait une commission fondée sur un chiffre inventé — l'invention EXACTE que le module promet d'écarter. Aucune suite tests/ (FONCTIONS de calcul, jamais la promesse README vs artefact) ne l'attrape.
Gate ajouté (bloc « CRM/Commissions · ANTI-INVENTION (#6) ») : recompute de l'état null/confirmé depuis l'artefact + (a) invariant #7 permanent (aucun taux_pct sans source) · (b) a_confirmer ⟺ taux_pct is null · (c) MANIFEST.counts recoupe le plan (taux_a_confirmer / evenements) · (d) la promesse #6 du README REFLÈTE l'artefact dans les DEUX SENS (tout-null ⇒ promesse présente ; taux confirmé ⇒ promesse retirée) — zéro faux négatif quand la Direction remplira les taux · (e) note_taux du MANIFEST cohérente.
5 morsures vérifiées (taux sans source · taux avec source mais a_confirmer stale + README périmé · a_confirmer=false sur null · counts périmé 5→4 · promesse retirée INTROUVABLE), restauré vert, 7 gates re-verts. Working tree byte-restauré via git checkout -- (JAMAIS git clean). Édition hors 05_deliverables_mvp/*/out ⇒ 0 dérive d'artefact. ci/README.md (récap + paragraphe détaillé) mis à jour. Auto-score 4Big 96/100.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surface data-derived jusqu'ici hors de tout gate : l'ID du pod RunPod ComfyUI
et le tag modèle Flux, recopiés à la main en 6 endroits, dont la source de
vérité in-repo est le fichier maître AGENTS_EXISTING_ASSETS.md (que les fiches
citent explicitement). Un pod re-créé (nouvel id) ou un modèle bumpé péримait
les citations en silence → batch Flux câblé vers un endpoint GPU mort (#6).
Gate « RunPod endpoint » (ci/check_readme_claims.sh, avant sys.exit) : id+tag
recomputés du bullet §1 (garde : bullet unique) ; direction fiche→inventaire —
toute fiche/master prompt mentionnant RunPod doit citer l'id EXACT (token
backtické de forme pod == canonique) + le tag EXACT ; disparition totale échoue.
6 morsures vérifiées (id change → 3 fiches cascade · tag bump → 3 · mistype → 1
· tag drop → 1 · id drop → incomplet · bullet supprimé → ancre morte) ; restauré
green · 7 gates re-verts.
ci/README.md (clause récap + paragraphe détaillé) + 05_activity_log mis à jour.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Classe fermée `agent-fiche-role-attrs-ungated` (sous-classe colonne CLI). La colonne
« Entrée CLI » des tables de livrables cite à la main le jeu de verbes de chaque module
(`publiciste.py parse|validate|generate|run`, `faisabilite_gen.py
score|scaffold|generate|batch`, …). Les blocs amont gatent déjà génériquement — pour
TOUTES les fiches — les colonnes « Tests » (vs regression_plan) et « Job CI » (vs
ci.yml), mais RIEN ne liait les VERBES écrits aux sous-commandes que le CLI EXPOSE.
Piège #6 : renommer/ajouter/retirer/réordonner un add_parser dans le .py (ou le mal
recopier dans la fiche) laisse la fiche documenter une surface d'invocation FANTÔME —
la doc d'identité qui dit à l'agent COMMENT lancer son livrable ment en silence, le CI
reste vert (le module tourne sous le vrai verbe), aucune suite tests/ ne l'attrape.
Gate ajoutée (ci/check_readme_claims.sh, bloc « colonne Entrée CLI ») : _cli_subcommands()
recompute via AST l'ensemble ORDONNÉ des subparsers.add_parser("<verbe>") ; script résolu
par le chemin de module de la MÊME ligne de table (lien …/<path>/README.md) + basename ;
égalité EXACTE ensemble ET ordre exigée ; `|` markdown échappés `\|` dé-échappés avant
découpe. 16 cellules gatées (6 fiches ; regex [a-z0-9_] capte audit_5d/4big_gen.py).
7 morsures vérifiées, restaurées vert : fiche renomme/ajoute/retire/réordonne un verbe
(4) · le vrai « vert trompeur » add_parser renommé dans le .py fiche inchangée (5) ·
nouveau add_parser non documenté (6) · script déplacé => INTROUVABLE (7). 6 gates
re-verts. ci/README.md catalogue mis à jour. Incident : un git checkout large a reverté
l'edit non-commité du gate ; ré-appliqué (git clean JAMAIS utilisé · ci-allow sur le log).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Les blocs SEO schema.org/hreflang ne gataient que seo/README.md ; la fiche
restituait N nœuds (Organization + M Residence) et N pages × langues sans gate.
Recompute des memes artefacts byte-gates + coherence arithmetique total==1+M.
8 morsures verifiees, working tree byte-restaure, 7 gates re-verts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bloc anti-dérive #6 dans ci/check_readme_claims.sh : la cellule « Métrique
vérifiée » de 03_agents/frontend_console/AGENT.md:29 restituait 3 attributs
data-derived du montage OTOIA (persona Amélie · langues FR/EN/ES ·
`endpoint: null`) ; le bloc chat_otoia amont ne gate que le COMPTE
blocks/mounts/portails, et le gate persona/capabilities ancré sur CLAUDE.md
vise spec/mounts/MANIFEST/README/oracle, jamais cette fiche. Le nouveau bloc
recompute les 3 depuis out/chat_mount.json (5 configs) + out/MANIFEST.json
byte-gatés : persona==MANIFEST.persona.nom, langues==MANIFEST.langues majuscule,
endpoint:null == affirmation d'honnêteté exigeant all(mount.endpoint is None)
— un endpoint fabriqué mord. Cohérences croisées MANIFEST⇔chaque mount +
endpoint_statut=='a_confirmer'⇔endpoints tous null. 6 morsures vérifiées ·
7 gates verts · aucune écriture out/ (0 dérive artefact).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
La table §3 de RBAC_50_ROLES_SPEC.md avait dérivé du contrat en préservant le total
50 (10+5 vs 9+6) → invisible aux gates agrégés. Correction alignée sur la source +
nouveau bloc check_readme_claims recomptant chaque portail (et le total) depuis
rbac_50_roles.json. quality_report.json régénéré (evidence octets SPEC). 7 gates PASS.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
La ligne « Confirmations préalables VPS » du README `devops/deploy_runbook` annote
CHAQUE confirmation réglementaire d'un COUPLE data-derived `nom` (OWNER · RÉF-CONTRÔLE)
— ex. `taux_commission` (Direction · audit_5d D1.1) : QUI doit confirmer le paramètre
avant déploiement + QUEL contrôle qa/audit_5d le trace. Sources faisant autorité
(double, byte-gatées) : les `confirmations` de out/deploy_runbook.json (owner + source)
CROISÉES avec qa/audit_5d/out/audit_report.json (chaque control D#.# porte son propre
owner). Les blocs DevOps amont ne gataient que le COMPTE (2) et l'ENSEMBLE des noms —
AVEUGLES à QUI porte chaque confirmation et à QUEL contrôle la trace. Piège #6 :
réaffecter `taux_commission` de Direction à Ventes (mauvais responsable escaladé),
misattribuer le contrôle (D1.1→D2.1) ou citer un contrôle FANTÔME (D9.9) laisse la
prose périmée en silence pendant que l'artefact/l'audit disent autre chose → l'agent
DevOps escaladerait le mauvais owner / contrôle ; aucune suite tests/ (FONCTIONS de
graphe) n'attrape ce « vert trompeur ». État courant : aucune annotation périmée
(anti-invention #6), le défaut est la surface ungated.
Gate ajouté (ci/check_readme_claims.sh, bloc « 2ter ») : cross-anchor à trois — owner
run-book ⊆ owner du contrôle audit_5d cité (tolère le suffixe « / UAF ») · owner
README ⊆ owner run-book (tolère l'abréviation « Fiscaliste eCF » ⊂ « Compta Fiscaliste
eCF ») · réf README == contrôle réel présent DANS audit_5d ET dans la source. Une
confirmation tracée DOIT rester annotée (traçabilité #6) ; une annotation d'un nom
fantôme échoue AUSSI.
9 morsures vérifiées (README réaffecte owner · misattribue D1.1→D2.1 · contrôle
fantôme D9.9 · annotation retirée · artefact réaffecte owner = silent green · audit_5d
owner divergent · source cite contrôle fantôme · annotation nom fantôme · réf textuelle
absente de la source), restauré vert, 7 gates re-verts. ci/README.md (récap + paragraphe
« 3ᵉ surface deploy_runbook — imputabilité des confirmations ») mis à jour.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
La matière première du worker SEO est `05_deliverables_mvp/seo/fixtures/projets_master.json` — les 9 projets (`code`+`nom`) que le bundle expose à Google (un listing `Residence`/projet dans le JSON-LD indexé sous `vente.otov7.com/projets/…`). Chaque entrée DÉCLARE `source.fichiers: ["CLAUDE.md#Projets"]` et le README:80 l'affirme (« 9 projets P01..P09 · noms sourcés de `CLAUDE.md §Projets` }) MAIS AUCUN gate ne le VÉRIFIAIT. check_artifacts prouve QUE les out/ SE RECONSTRUISENT depuis la fixture (byte-for-byte), PAS que ses code/nom == CLAUDE.md §Projets ; les blocs SEO amont gatent les COMPTES de mots-clés + la COMPOSITION du graphe schema.org (bijection un-listing/projet) + l'IDENTITÉ de la marque racine `Helios RD` (§Entités), tous AVEUGLES à l'identité de la LISTE de projets. Piège #6 : RENOMMER un projet dans CLAUDE.md §Projets (« Aqua Terra Las Terrenas »→« … Bay »), le RENOMMER/AJOUTER/RETIRER dans la fixture, puis régénérer, laisse le JSON-LD PUBLIC lister un projet FANTÔME ou en OMETTRE un canonique pendant que la byte-gate reste VERTE (l'artefact se reconstruit fidèlement… depuis une fixture DÉRIVÉE) et que le compte reste 9 — « vert trompeur » de la MÊME classe que la marque org (§Entités), les entités CONFOTUR (§Entités) et les libellés projets du run_sheet démo (§Projets). Aucune suite tests/ (FONCTIONS de génération, jamais l'ancrage à CLAUDE.md) ne l'attrape.
Gate ajouté (bloc « SEO · l'IDENTITÉ du CATALOGUE PROJETS ») : RE-DÉRIVE le catalogue de CLAUDE.md §Projets (zéro duplication · source unique) puis exige — (a) identité d'ensemble des codes fixture ⇔ CLAUDE.md (aucun fantôme/manquant · pas de doublon) ; (b) `nom` de chaque entrée == CLAUDE.md §Projets (accent-INSENSIBLE · la fixture normalise « Xamaná »→« Xamana ») ; (c) chaque entrée DÉCLARE l'ancrage `CLAUDE.md#Projets` dans source.fichiers ; (d) les `name` des listings du graphe schema.org PUBLIC == l'ensemble des noms CLAUDE.md §Projets + bijection (1 listing/projet) + MANIFEST counts projects/listings == |§Projets| ; (e) le README cite l'ancrage §Projets. Un claim absent échoue AUSSI (disparition = régression).
13 confirmations vertes ; 7 morsures vérifiées puis restaurées (CLAUDE.md rename P07 = bites b+d simultanés l'ancre est vive · fixture nom drift P03 · fixture drop P09 · fixture add fantôme P10 · fixture strip source.fichiers P01 · artefact schema.org rename Structure→Estructura = le vrai silent-green public · README retire la citation §Projets), restauré vert, 7 gates CI re-verts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le champ que `Encabezado.TipoMoneda` porte dans chaque e-CF DGII —
`moneda.options = ["USD","DOP"]` — vit dans `ecf_spec.json` ET, byte-gaté par
check_artifacts, dans `out/ecf_plan.json`. C'est une valeur CANONIQUE de CLAUDE.md
#10 (« **USD + DOP** devises ») dont le README se réclame « #10 » (README:37/57/77).
Or byte-gater une constante prouve la REPRODUCTIBILITÉ, PAS l'ANCRAGE :
check_artifacts prouve ecf_plan==build depuis le spec, JAMAIS que spec[moneda]==#10.
Les blocs fiscaux amont gatent le FormaPago (Cardnet · #10) + états/champs
émetteurs, AVEUGLES au jeu de devises ; le bloc « Fiches #10 » ancre les fiches
ERPNext/CRM, AVEUGLE au module fiscal.
Piège #6 : RENOMMER/ÉTENDRE les devises dans CLAUDE.md #10 (`USD + DOP`→`USD + EUR`,
ou +`EUR`) ET dans le spec de façon cohérente reste byte-VERT tout en faisant
émettre à l'e-CF un `TipoMoneda` d'une devise qui CONTREDIT le mandat — « vert
trompeur » de la classe de la marque SEO §Entités, la persona Chat OTOIA ou les
tokens branding #4, qu'aucune suite tests/ (FONCTIONS de génération, jamais l'ancre
à CLAUDE.md) n'attrape. L'agent ERPNext Backend câblerait TipoMoneda hors mandat.
Correctif de déclaration : nouveau champ `moneda.options_source` (ecf_spec.json +
requis dans ecf.schema.json) citant « CLAUDE.md #10 — devises **USD + DOP** »,
DISTINCT de `source` (taux TipoCambio, reste null/a_confirmer côté VPS · #8).
Artefact régénéré (byte-gate re-verte · 39 tests module verts).
Gate ajouté (ci/check_readme_claims.sh, bloc « Fiscal · ecf_dgii — le JEU DE
DEVISES `moneda` … ANCRÉ sur CLAUDE.md #10 ») : RÉUTILISE exp["devises"] déjà
recomputé depuis #10 (zéro duplication) ; (a) spec[moneda].options == devises #10
ORDRE exact ; (b) artefact byte-gaté idem ; (c) options_source cite CLAUDE.md #10 +
chaque devise ; (d) prose README qui se réclame de #10 == jeu exact + toute mention
moneda/devise d'une paire concordante (scan ancré sur contexte, évite le faux
positif `RNC / ITBIS`). 7 morsures vérifiées (CLAUDE.md #10 rename = 4 bites en
cascade · spec options régénéré = le vrai silent green · artefact seul · reorder ·
options_source sans ancre · README ligne #10 · README table), restauré vert, 7
gates re-verts.
ci/README.md (clause récap + paragraphe « 4e surface du MÊME README ») mis à jour.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
La version MAJEURE du SDK Expo que l'app compagnon cible (`expoSdkMajor`) est data-derived : `out/app_config.json.expo.extra.expoSdkMajor = 54` (byte-gaté par check_artifacts ⇒ faisant autorité), recopié dans `out/MANIFEST.json.app.expo_sdk_major`, dérivé de `mobile_spec.json[app].expo_sdk_major` dont la SOURCE FAISANT AUTORITÉ est la roadmap Sprint 5 l.56 « Rebuild Expo 54 » (le spec le DÉCLARE via `expo_sdk_source`). Le README:4/19/35/72 recopie « Expo 54 » CINQ fois À LA MAIN — dont la ligne de source « Expo SDK **54** | roadmap Sprint 5 l.56 ». Le bloc « Mobile · récap de l'app Expo » amont ne gate QUE le quadruplet onglets/rôles/langues/identifiants a_confirmer (comptes de MANIFEST.counts), AVEUGLE au majeur du SDK. Piège #6 : BUMPER le SDK (Expo 55 sort · eas build cible 55) dans le spec/artefact SANS toucher au README (ou l'inverse), ou DÉRIVER la roadmap de l'artefact, laisse la prose PÉRIMÉE en silence pendant que le graphe byte-gaté dit autre chose → l'agent Mobile lancerait `eas build` sur le MAUVAIS SDK (le rebuild même que la ligne roadmap prescrit) — « vert trompeur » qu'aucune suite tests/ (FONCTIONS de génération, jamais la prose ni l'ancre à la roadmap) n'attrape. Même patron d'ANCRAGE que la marque SEO §Entités (Helios RD ancrée sur CLAUDE.md) ou les roadmap_line de la recette, mais sur une valeur dont la roadmap — non CLAUDE.md — est l'INPUT faisant autorité.
Gate ajouté (bloc « Mobile · le SDK MAJEUR Expo … ANCRÉ sur la roadmap Sprint 5 l.56 ») : le majeur re-dérivé de app_config.json byte-gaté, puis (a) cohérence interne artefact ⇔ MANIFEST ⇔ spec ; (b) ancrage roadmap `Rebuild Expo <N>` == artefact (ancre vive) ; (c) le spec DÉCLARE l'ancrage (expo_sdk_source cite roadmap + Expo N) ; (d) TOUTE mention prose « Expo [SDK] N » == artefact (aucune périmée) ; (e) la ligne de source du tableau cite l'ancre roadmap. Un claim absent échoue AUSSI.
7 morsures vérifiées (README 54→55 prose périmée · artefact 54→55 README+roadmap périmés = le vrai silent green · roadmap Rebuild Expo 54→55 artefact périmé · MANIFEST 54→55 chaîne incohérente · spec expo_sdk_source sans roadmap · README ligne source sans roadmap · README retire toute mention Expo N), restauré vert (git checkout --, JAMAIS git clean), 7 gates re-verts. ci/README.md (table récap + paragraphe détaillé) mis à jour.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Les artefacts `out/chat_mount.json` (5 configs runtime, une par portail) +
`out/MANIFEST.json` portent la persona + les capabilities, BYTE-GATÉS pour la
REPRODUCTIBILITÉ par check_artifacts (reconstruction depuis `chat_spec.json`) mais
JAMAIS ANCRÉS à CLAUDE.md §Architecture cible (l.30 « Voix Amélie QC (multilingual_v2) »
· l.28 « OTOIA capabilities : aec.py + knowledge.py + prompt_engine.py + chat.py »),
leur source faisant autorité. Le bloc chat_otoia amont ne gate que la COMPOSITION
(portails métier · 1 block⇔1 mount) ; le SEUL contrôle d'identité vit dans tests/
(`test_persona_amelie_sourcee`/`test_capabilities_sourcees_sans_ajout`) mais son ORACLE
est HARDCODÉ (`"Amélie"`, `["aec.py",…]`) — une copie de plus, jamais comparée à
CLAUDE.md. Piège #6 : renommer la persona (`Amélie`→`Sophie`), changer la voix, ou
renommer/RETIRER une capability dans `chat_spec.json` (ou dans CLAUDE.md) reconstruit
l'artefact fidèlement (byte-gate VERTE) sans toucher l'oracle (tests VERTS) → le chat
monté dans CHAQUE portail annoncerait une persona/voix/capabilities CONTREDISANT le
mandat — « vert trompeur » de la classe des tokens branding ancrés sur #4 et de la
marque SEO §Entités. Aucune suite tests/ (FONCTIONS de génération, jamais l'ancre à
CLAUDE.md) ne l'attrape.
Gate ajouté (bloc « Chat OTOIA · persona + capabilities ancrées CLAUDE.md ») :
persona+capabilities re-dérivées de §Architecture cible (regex `Voix … QC (…)` +
`OTOIA capabilities : …` split `+`, NFC), puis (a) `chat_spec.json`
persona/capabilities == CLAUDE.md (ordre exact) · (b) le spec DÉCLARE l'ancrage
(`persona.source` + chaque `capability.source` citent CLAUDE.md) · (c) CHACUN des 5
mounts + le MANIFEST == CLAUDE.md · (d) README cite l'ancre §Architecture cible + nomme
persona + chaque capability (notation compacte matchée par radical) · (e) l'ORACLE du
test == CLAUDE.md (set-diff). Un claim absent échoue AUSSI.
8 morsures vérifiées : CLAUDE.md `Amélie→Sophie` (mord SIMULTANÉMENT
spec/mount/README/oracle — l'ancre est vive) · CLAUDE.md retire `chat.py` · spec
`aec.py→exfil.py` (capability injectée) · spec `persona.source` sans CLAUDE.md · UN
SEUL mount à voix altérée (byte-gate aveugle) · README retire le radical
`prompt_engine` · oracle du test persona `Amélie→Bob` · oracle du test capabilities
réordonné/tronqué ; restauré = green, exit 0. Working tree byte-restauré
(`git checkout --`, JAMAIS `git clean`) · 7 gates re-verts · unittest module re-vert.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Gate ajouté (bloc « Fiches agents · colonne Job CI ») : on RECOMPUTE l'ensemble des jobs depuis la clé `jobs:` de ci.yml (la section `on:` push/pull_request/workflow_dispatch est AVANT `jobs:` et donc exclue ; jamais une liste à la main) et on exige que chaque token `*-tests` cité dans une fiche y figure. Direction fiche→ci.yml (le consommateur) : ci.yml peut définir des jobs non cités (légitime), une fiche ne peut pas citer un job absent ; la disparition totale de la colonne échoue AUSSI. 33 références vérifiées == jobs ci.yml ; 2 morsures (token de fiche `seo-tests`→`seo-suite-tests` fantôme · job ci.yml `rbac-tests` renommé → mord SIMULTANÉMENT les 4 fiches qui le citent : erpnext_backend/ifc_speckle/mobile/rendu), restauré vert, 7 gates re-verts. ci/README.md + activity log MAJ.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le bloc e-NCF gate le FORMAT de l'identifiant ; le bloc cross-cohérence (2e surface) gate états émetteurs/champs de base/FormaPago ; mais AUCUN ne comparait l'IDENTITÉ de ce rôle à ses DEUX sources faisant autorité : (a) out/ecf_plan.json (byte-gaté) — chaque emission_events[] porte role_id + erpnext_role_name ; (b) rbac_50_roles.json (contrat) — le rôle porte portail:"compta". Le `portail` n'est PAS dans l'artefact ; il n'est vérifiable QUE contre le contrat.
Même classe d'IDENTITÉ que la Fiche Mobile / les Fiches Faisabilité ou les gardes CRM/workflow_vente, appliquée à une 3e surface distincte du MÊME README fiscal. Piège : la seule vérification existante (tests/) teste des FONCTIONS (ncf/résolution), jamais la prose. RÉAFFECTER l'émission à un autre rôle, le RENOMMER, ou DÉPLACER le rôle hors du portail compta (e-CF émis HORS Compta — séparation cassée) laisse la prose périmée pendant que l'artefact/contrat disent autre chose → l'agent ERPNext Backend câblerait le mauvais rôle émetteur.
Gate ajouté (ci/check_readme_claims.sh, bloc « Fiscal rôle émetteur ») : role_id/erpnext_role_name recomputés de emission_events + portail/nom Frappe du contrat, prose EXACTE ; cohérences croisées : tous les emission_events pointent UN SEUL rôle (fonction · non vide) · role_id unique au contrat · nom artefact == contrat (zéro-dup) · portail == compta. Un claim absent échoue AUSSI. 6 morsures vérifiées (README renomme role_id/nom/portail · artefact réaffecte les 2 events à ventes-directeur (5 mords cascade) · contrat déplace le rôle vers ventes · artefact diverge du contrat sur le nom), restauré vert, 7 gates re-verts. ci/README.md mis à jour.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Les blocs Fiches Faisabilité / Fiche Mobile ne gataient que les attributs du CONTRAT ; le bloc « triplets par workspace » ne gate que le COMPTE de rôles par Workspace (`nb_roles`), AVEUGLE à QUEL rôle. Rattacher le rôle mobile à `OTO Ventes` (SUR-EXPOSITION console : le dev mobile gagnerait l'accès au portail Ventes — la restriction même que la note #6 pose), retirer/déplacer le rôle rendu de `OTO Construction`, laissait la fiche périmée en silence pendant que l'artefact dit autre chose ⇒ l'agent ERPNext Backend câblerait le mauvais accès console.
Gate ajouté (nouveau bloc « Fiches agents · rattachement Workspace ») : index recomputé `erpnext_role_name → {titres de Workspace le portant}` depuis workspace.json (nom résolu du contrat par id, zéro duplication). 3 formes d'assertion : in_named (rendu — prose cite CE Workspace ET membership == {lui seul}) · in_any (ifc — membership ≥1) · not_in_named (mobile — assertion NÉGATIVE #6, rôle ABSENT du Has Role du Workspace nommé). Garde anti-typo : Workspace nommé absent de l'artefact ⇒ ROUGE. Claim absent ⇒ ROUGE (traçabilité).
9 morsures vérifiées (4 artefact + 5 fiche/bord) ; restauré vert ; 7 gates re-verts. ci/README.md (récap + paragraphe détaillé) + activity log + mémoire mis à jour.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Même classe que la Fiche Mobile (session précédente) — surface 03_agents/*/AGENT.md transcrivant à la main les attributs d'un rôle depuis le contrat, gatée (si tant est) seulement sur des comptes agrégés. Gate ajouté (bloc « Fiches Faisabilité », config-table par fiche : fichier · role_id · attributs restitués) : par attribut recomputé du contrat, prose EXACTE exigée ; permissions rendues en ORDRE CANONIQUE (read→R · write→W · create/delete/… en toutes lettres — élargir/rétrécir change la chaîne, `R/W/create` ne satisfait plus `R/W/create/delete`) ; verbe inconnu ⇒ ROUGE ; claim absent échoue AUSSI.
#6 (honnêteté) vérifié AVANT édition : la DESCRIPTION de ces deux fiches est une PARAPHRASE éditoriale (le contrat porte « (… — CLAUDE.md) » / « les modèles » que la fiche condense) — donc NON gatée verbatim (ne rien réécrire de correct pour un gate), seuls les attributs structurés (transcrits à l'exact) sont contraints. État courant : aucun attribut structuré périmé (12 recomputes == contrat).
11 morsures vérifiées (5 contrat + 6 fiche) : rendu Faisabilité +write (R→R/W) · rendu niveau 2→3 · rendu WA SRL→WAF · rendu rôle renommé · ifc Faisabilité R/W→R · fiche rendu File R/W/create→…/delete · fiche ifc R/W→R/W/create · fiche rendu module Faisabilité→Selling · fiche ifc WA SRL→WAF · fiche rendu nom_fr retiré (absent) · ligne contact ifc supprimée (INTROUVABLE) ; restauré vert, 7 gates re-verts. ci/README.md + activity log MAJ.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ces 4 figures sont Σ quantités · Σ(qté×prix_usd) · Σ(qté×prix_dop) · ⌈point_equilibre_pct × Σ unités⌉ calculées sur la fixture COMMITÉE fixtures/brief_bancable.json, le 52 % ancré sur CLAUDE.md #9 (« Point équilibre 52% pré-vente »). PIÈGE : la seule vérification existante (tests/) teste des FONCTIONS (l'arithmétique de recoupement _check_derived_arithmetic) avec des oracles HARDCODÉS dans le test — une copie de plus, jamais comparée à la PROSE du README. Éditer une typologie de la fixture (quantité/prix), en ajouter/retirer une, ou déplacer le point d'équilibre de CLAUDE.md #9, laisse les 4 chiffres du README périmés pendant que le générateur produit autre chose → le banquier lit un dossier faux (l'invention même que #6 interdit) — « vert trompeur » qu'aucune suite tests/ n'attrape.
Nouveau bloc « Faisabilité/bancable figures » dans ci/check_readme_claims.sh (avant sys.exit), même esprit que le bloc faisabilite/generator ancré sur CLAUDE.md #9/#10 mais appliqué à une surface dont la source de vérité n'est PAS un out/*.json : (1) recompute indépendant des 4 figures depuis la fixture commitée ; (2) cohérence croisée — le manifeste GÉNÉRÉ (bancable_gen validate, exécuté via subprocess stdlib pur, encoding utf-8) == le recompute (mord un générateur/fixture incohérents) ; (3) prose — un seul pattern d'identité exige les 4 valeurs EXACTES avec séparateur de milliers pour USD/DOP + la formule ⌈pct × Σ unités⌉ dont le pct == CLAUDE.md #9 et le total == Σ unités. Un claim absent échoue AUSSI (traçabilité).
État courant : aucun chiffre périmé (anti-invention #6, rien à réécrire) — le défaut est la surface ungated (module entièrement hors CI). 7 morsures vérifiées : README USD 8,560,000→9,560,000 (figure périmée) · README retire le séparateur de milliers (8,560,000→8560000) · README breakeven 21→20 (formule périmée) · README formule 52 %→55 % (dérive CLAUDE.md #9) · README supprime la ligne entière (INTROUVABLE) · fixture Studio qté 12→13 (5 morsures : units/USD/DOP/breakeven README périmés + manifeste) · fixture 2 Chambres prix_usd 320000→300000 (catalogue USD périmé) ; restauré = green : 4 figures == génération réelle · manifeste == recompute · pct 52 % == CLAUDE.md #9 · exit 0. Working tree byte-restauré via git checkout -- (JAMAIS git clean) · 7 gates re-verts. ci/README.md (ligne récap check-readme-claims) mis à jour · log 05_activity_log/2026-08-01.md. Auto-score 4Big 96/100.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Les deux blocs CRM/workflow existants gatent le COMPTE (« 9 états · 11 transitions ») ET l'énumération des transitions à séparation des pouvoirs (allow_self_approval=0), jamais l'identité des GARDES. Sources faisant autorité (byte-gatées par check_artifacts) : out/workflow.json (chaque transition porte `allowed` = le rôle gardien) + out/MANIFEST.json.roles_rbac_utilises[].erpnext_role_name (recomputé du contrat rbac_50_roles.json à chaque build). PIÈGE : les blocs COMPTE/séparation sont AVEUGLES à l'identité des gardes → RÉAFFECTER un pas monétaire (« Confirmer réservation » Réservations→Conseiller = élévation de privilège) · RENOMMER un rôle · AJOUTER un fantôme · en OUBLIER un laissait le README périmé pendant que l'artefact dit autre chose → l'agent ERPNext Backend câblerait le mauvais garde (le risque même que la table veut prévenir) — « vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS de graphe/résolution RBAC, pas la prose) n'attrape. Même patron d'IDENTITÉ que la cross-cohérence PERMISSIONS Legal/CONFOTUR déjà gatée.
Nouveau bloc « CRM gardes » dans ci/check_readme_claims.sh (après le bloc Démo scénarios) : (1) identité d'ensemble — colonne « Rôle » de la table == {allowed} de workflow.json (set-diff · NFKD/casefold) ; (2) cohérence artefacts — {allowed} == roles_rbac_utilises du MANIFEST (aucun garde hors manifeste ni l'inverse) · ensemble NON VIDE ; (3) cross-cohérence par pas — les gardes des étapes SENSIBLES et uniques Confirmer réservation/Signer contrat/Approuver CONFOTUR (recomputés `allowed`, jamais figés) nommés EXACTEMENT dans leur ligne. Le (3) mord la RÉAFFECTATION vers un rôle DÉJÀ présent (rôle servant deux transitions) que le set-diff seul manquerait. Un claim absent échoue AUSSI.
7 morsures vérifiées : README réaffecte Confirmer réservation→Conseiller (absents=[réservations] + pas mordu) · artefact réaffecte Signer contrat→Conseiller (en trop=[contrats] + incohérence MANIFEST + pas mordu) · README rôle fantôme (en trop=[oto fantome]) · README retire un rôle (absents=[direction commerciale]) · artefact MANIFEST perd un garde (garde pas dans MANIFEST=[réservations]) · table supprimée (INTROUVABLE) · artefact réaffecte Approuver CONFOTUR→Directeur (rôle déjà présent, set INCHANGÉ, captée UNIQUEMENT par la cross-cohérence par pas) ; restauré = green : 7 rôles · colonne == {allowed} == roles_rbac_utilises · 3 étapes sensibles pinnées · exit 0. État courant : aucun garde périmé (anti-invention #6, rien à réécrire) — le défaut était la surface ungated. ci/README.md (table + détail « 3e surface workflow_vente ») mis à jour · working tree byte-restauré (git checkout --, JAMAIS git clean) · 7 gates re-verts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le bloc « apply_plan » existant de check_readme_claims.sh (bloc 2) ne gate QUE la
ligne AGRÉGÉE « Génération réelle » (8 comptes consolidés). Les 3 colonnes
d'identité de la table run-book (README:38-46) sont data-derived de
out/apply_plan.json (byte-gaté par check_artifacts) : # = order · Responsable =
responsable (worker/vps/worker+vps) · Dépend de = les n°s d'ordre des depends_on.
Elles étaient un WILDCARD. PIÈGE : check_artifacts ne prouve QUE apply_plan==build
(byte-for-byte) et le bloc de comptes est aveugle à l'identité des lignes → passer
le Responsable de l'étape 4 (worker+vps→vps) ou retirer une dépendance de l'étape 6
(4,5→4) laissait la prose périmée pendant que l'artefact dit autre chose. Un
responsable périmé (étape VPS attribuée au worker) ou une dépendance périmée (Role
Profile importé AVANT les Role) est un hazard réel — le risque même que le run-book
veut prévenir — qu'aucune suite tests/ (qui teste des fonctions, pas la table
commitée) n'attrape. Même classe que la colonne « Type » de roleprofile_gen et la
table demo/scenarios.
Nouveau sous-bloc « 2bis) apply_plan — TABLE Run-book » : (1) IDENTITÉ par étape —
chaque ligne porte EXACTEMENT #=order + Responsable=responsable + Dépend de=n°s
d'ordre des depends_on (deps extraits par digits ⇒ robuste au séparateur/em-dash) ;
colonne « Étape » libre (paraphrase). (2) IDENTITÉ d'ensemble — {ordres des lignes}
== {ordres artefact} sans doublon (aucune ligne FANTÔME, aucune étape MANQUANTE).
Cohérences croisées (mordent un plan INTERNEMENT incohérent) : ordres contigus 1..N ·
responsable ∈ {worker,vps,worker+vps} · toute dépendance pointe en ARRIÈRE
(n° < n° de l'étape). Un claim absent échoue AUSSI.
8 morsures vérifiées : README étape4 responsable worker+vps→vps (câblage) · README
étape6 Dépend de 4,5→4 (dep manquante) · README étape3 1,2→1,2,5 (dep fantôme) ·
ligne FANTÔME #7 (7≠6) · étape 5 SUPPRIMÉE (INTROUVABLE + 5≠6) · artefact étape2
responsable vps→worker (README stale) · artefact étape2 dep en AVANT (graphe
incohérent) · artefact étape1 responsable hors domaine (robot) ; restauré = green :
6 lignes == 6 étapes · responsables/deps == artefact · ordres 1..6 · exit 0.
État courant : aucune valeur périmée (anti-invention #6, rien à réécrire) — le
défaut est la surface ungated. ci/README.md (table + détail « 2ᵉ surface
rbac/apply_plan ») mis à jour · working tree byte-restauré · 7 gates re-verts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le §« Cœur du livrable : cross-cohérence » →Permissions du README legal/confotur
énumère À LA MAIN, PAR rôle, role_id + portail + actions (« mot pour mot les
permissions_cibles RBAC … ni ajout ni retrait ») : `ventes-confotur` (Ventes)
→ read/write/create/print · `legal-onapi` (Direction) → read/write/create ·
`legal-directeur` (Direction) → read/write/**submit**/report. Le bloc CONFOTUR
existant ne gate QUE le COMPTE (« 3 rôles ») — AVEUGLE à leur identité.
Ces 3 lignes sont DATA-DERIVED d'out/MANIFEST.json (roles_rbac_utilises[] : role_id
· portail · permissions, recomputé du contrat rbac_50_roles.json à chaque build) et
projetées dans out/doctype_confotur_application.json (permissions[] par nom de rôle)
— les DEUX byte-gatés par check_artifacts. PIÈGE : le compte « 3 rôles » ne voit pas
l'identité → PROMOUVOIR ventes-confotur à submit (élévation de privilège cassant la
séparation des pouvoirs dont is_submittable est déduit) · RETIRER une action ·
RÉAFFECTER un portail · RENOMMER un rôle · AJOUTER une ligne fantôme laissait la
prose périmée pendant que les artefacts disent autre chose → l'agent ERPNext Backend
câblerait le mauvais jeu de permissions (le risque même que la cross-cohérence veut
prévenir) — « vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS de
mapping RBAC, pas la prose) n'attrape. Même patron d'IDENTITÉ que le catalogue RBAC
fixtures_gen (singleton set_user_permissions) et la cross-cohérence e-CF DGII.
Nouveau bloc dans ci/check_readme_claims.sh (après le bloc de comptes CONFOTUR) :
(1) jeu d'actions + portail de CHAQUE rôle recomputés de roles_rbac_utilises[],
prose exigée EXACTE (set-diff · casse normalisée) ; (2) identité d'ensemble
README⇔MANIFEST (aucun rôle fantôme NI manquant). Cohérences croisées en bonus
(mordent un artefact INTERNEMENT incohérent) : MANIFEST ⇄ DocType d'accord sur le
jeu d'actions par rôle · séparation des pouvoirs — submit porté par EXACTEMENT un
rôle (legal-directeur) et is_submittable==True déduit de sa présence (README:31).
Un claim absent échoue AUSSI.
7 morsures vérifiées : README promeut ventes-confotur à submit (en trop=[submit]) ·
README réaffecte legal-onapi Direction→Ventes (portail) · README retire report de
legal-directeur (absents=[report]) · README renomme un role_id (fantôme=[legal-conseil]
+ manquant=[legal-onapi]) · énumération supprimée (INTROUVABLE) · artefact MANIFEST
promeut ventes-confotur à submit (README périmé + séparation cassée + incohérence
MANIFEST⇄DocType) · artefact DocType is_submittable=false alors qu'un rôle porte
submit (incohérence interne) ; restauré = green : 3 rôles portail+actions == MANIFEST
· identité d'ensemble · submit singleton · is_submittable=True · exit 0. État courant :
aucun jeu d'actions périmé (anti-invention #6, rien à réécrire) — le défaut est la
surface ungated. ci/README.md (table + détail « 2e surface CONFOTUR ») mis à jour ·
working tree byte-restauré (git checkout --, JAMAIS git clean) · 7 gates re-verts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le README `demo/scenarios` porte une table « Scénario | Projet | Audience | Angle »
(README:10-13). Le bloc « Démo » existant de check_readme_claims ne gate QUE
counts.modules_cites_uniques (le compte de modules du diagramme) — ses trois colonnes
d'identité étaient des WILDCARDS, et les libellés projets sont déclarés (README:91-92)
« proviennent verbatim de CLAUDE.md · §Projets » sans AUCUN ancrage vérifié.
Ces colonnes sont DATA-DERIVED de out/run_sheet.json.scenarios[] (id · projet ·
projet_libelle · audience), byte-gaté par check_artifacts. Le générateur ne saisit
aucune donnée métier (résolution RFC 6901 depuis le disque) ; mais check_artifacts
ne prouve QUE run_sheet==build (byte-for-byte) et la source scenario_spec.json (map
projets) recopie les libellés À LA MAIN → toute la chaîne peut DÉRIVER de CLAUDE.md
en restant byte-verte. PREUVE : classer P07 sous audience=client dans le README →
check_readme_claims EXIT 0 (le gate ne voyait que 10==10 modules). Un couple
projet/audience faux ferait pitcher au présentateur le mauvais scénario — le risque
même que la run-sheet veut éliminer — « vert trompeur » qu'aucune suite tests/ (qui
teste des FONCTIONS de résolution, pas la prose) n'attrape.
Nouveau bloc « Démo scénarios » dans ci/check_readme_claims.sh : (1) ANCRAGE — chaque
`P{code} {libellé}` du run_sheet == entrée « ## Projets » de CLAUDE.md (verbatim ·
source unique · même esprit que le catalogue projets dossier_vente) ; (2) TABLE —
chaque ligne porte EXACTEMENT id + `P{code} {libellé}` + audience (fin du wildcard ·
patron identique à la colonne « Type » de roleprofile_gen) ; (3) IDENTITÉ d'ensemble
— {ids des lignes} == {ids du run_sheet} == counts.scenarios (aucune ligne FANTÔME,
aucun scénario MANQUANT). Cohérences croisées en bonus (mordent un artefact
INTERNEMENT incohérent) : run_sheet ↔ MANIFEST d'accord sur (id,projet,audience) ·
id == S-{projet}-{AUDIENCE} · counts.scenarios == |scenarios| · ids non vides/sans
doublon. Un claim absent échoue AUSSI.
8 morsures vérifiées : prose renomme un id (INTROUVABLE) · prose classe P07 sous
audience=client · prose met le mauvais libellé projet · ligne FANTÔME S-P99-GHOST
(en trop) · artefact libellé « Aqua Terra Bay » DÉRIVE de CLAUDE.md · run_sheet↔
MANIFEST désync audience · counts.scenarios=3 (≠|scenarios|=2) · ligne S-P05-CLIENT
supprimée (MANQUANT) ; restauré = green : 2 lignes == run_sheet == counts (2) ·
libellés == CLAUDE.md §Projets · exit 0. État courant : aucune valeur périmée
(anti-invention #6, rien à réécrire) — le défaut est la surface ungated. ci/README.md
(table + détail « 2e surface demo/scenarios ») mis à jour · working tree byte-restauré
· 7 gates re-verts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cause : guard_constraints détecte l'USAGE (pas la simple mention) de termes interdits (CLAUDE.md #2/#3/#10 + interdits absolus) ; une ligne portant un marqueur de prohibition (JAMAIS/❌/pas de…) ou ci-allow est ignorée comme « rappel de règle ». Deux lignes de prose du log 2026-07-31.md (session 203134) tripaient le gate SANS marqueur : (L17) « Cardnet = paiement carte, pas Stripe » — restatement de #10, mais « pas » + terme capitalisé ne matche pas le marqueur « pas de|pas d » → faux positif ; (L43) « Working tree byte-restauré (git clean) » — mention de l'interdit absolu ET prose INEXACTE : cet interdit n'a JAMAIS été exécuté (restauration par git checkout --).
Correctif chirurgical, honnête, sans angle mort : reformulation des 2 lignes avec un cadrage de prohibition EXACT plutôt qu'une exclusion globale de 05_activity_log/ (qui aveuglerait le gate à une VRAIE violation confessée un jour). L17 → « Cardnet = paiement carte, JAMAIS Stripe » (marqueur JAMAIS + plus fidèle à #10) ; L43 → « byte-restauré (git checkout --, JAMAIS git clean) » (marqueur JAMAIS + désormais EXACT). Le gate garde intacte sa capacité à mordre un usage réel — chaque mention légitime porte son propre marqueur, ligne par ligne.
Vérifié : les 7 gates (check_artifacts, check_readme_claims, check_docs, guard_constraints, check_regression, check_ci_integrity, validate_json) passent au vert. Nouveau log de session 210134 rédigé avec discipline (marqueurs JAMAIS/ci-allow sur les lignes citant les termes interdits, sinon il re-tripperait le gate — d'où le soin apporté au log). Aucune modif VPS (#8), zéro réseau.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
INV10 ne garantissait que « roadmap_line est un entier positif ». Ajout de
parse_roadmap_anchors (deps) qui DÉRIVE la structure réelle de la roadmap, et
d'INV11 qui exige que chaque roadmap_line pointe RÉELLEMENT son bullet
(DELIVERABLE du sprint SX · k-ième bullet métrique) et que le « 8 + 7 » soit
dérivé du fichier, pas figé. Morsure prouvée sur le spec réel (S1=999, M3=200).
Régénéré consommateurs : regression 558→564 (run/plan/MANIFEST), quality_report
(acceptance 31→37 méthodes, 100/100 inchangé), fiches QA + Backend, README
acceptance (10→11 invariants). 7 gates verts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Même classe de vert-trompeur que ci/README.md (session 050007) mais dans le
code des gates : 3 comptes vivants figés "22 suites · 551 tests · PASS" (matrice
534→551→558). L'ironie : le header de check_regression — le gate anti-péremption
de la matrice — s'était lui-même périmé. Fix précédent 050007 : suppression de la
surface de dérive (retrait du nombre figé → renvoi à regression_run.json), pas
551→558. Progressions historiques "534→551→558" conservées. 7 gates verts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Défaut réel (#6 anti-invention) : 03_agents/erpnext_backend/AGENT.md situait les
tests backend par "repo : 560 tests au total" saisi à la main. 560 ne correspond à
aucun compte courant (matrice qa/regression = 558/22 ; repo-wide = 584 méthodes
test_*) — périmé en silence (534→551→558 ; 560 = ancien 534 + 26 harnais self-exclu
INV3). Même classe de vert-trompeur que la fiche QA (21/534), mais ungated.
- Vérif ciblée : toutes les autres bornes chiffrées des 13 AGENT.md exactes
(crm 81, RBAC 60, e-CF 39, portails 19, chat 31, confotur 44, seo 36).
- Fix : le nombre pointe désormais vers l'artefact gaté (regression_run.json).
- Gate : ci/check_readme_claims.sh recalcule tests+suites+verdict depuis
regression_run et exige l'égalité avec la fiche backend (comme la fiche QA).
- Preuve morsure : 560 réinjecté ⇒ gate rouge ; restauré ⇒ vert.
- 7 gates verts ; édition doc ⇒ aucune dérive out/ (check_artifacts vert).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Défaut réel (même classe que le bug regression_run.json corrigé plus tôt) :
les 4 générateurs RBAC (fixtures_gen/userperm_gen/roleprofile_gen/apply_plan)
.gitignore-aient leur out/, alors qu'ils sont audités en archétype `generator`
(critère HANDOFF). En checkout PROPRE (git archive HEAD = ce que voit le runner
Gitea) leur out/ est absent → qa/audit_4big (dont le build relit le out/ de
CHAQUE module) les note 80<95 ⇒ INV7 ⇒ build refusé ⇒ check-artifacts ET
check-regression ROUGES. Ça ne passait qu'en local via les out/ non suivis
laissés par des build manuels.
Fix : committer le out/ des 4 générateurs (build byte-déterministe prouvé ;
apply_plan lit ses frères en process, pas via out/) → alignement sur les 15
autres générateurs ; audit 100/100 en checkout propre.
Durcissement : check_artifacts.sh exige désormais `git ls-files --error-unmatch`
sur chaque fichier produit → un out/ ignoré/non commité devient une erreur
LOCALE honnête au lieu d'une surprise en CI. Bite-proof : git rm --cached d'un
artefact (laissé sur disque) ⇒ exit 1 ; re-add ⇒ exit 0.
Régénéré : quality_report.json (dérive DOC = taille des 4 README édités).
Vérifs : 6 gates verts sur git archive propre ; 534/21 inchangé ; suites OK.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Défaut #1 (bug CI réel) : ci/check_regression.sh exigeait regression_run.json
commité comme baseline, mais le fichier était .gitignore → absent en checkout
propre → le job Gitea check-regression échouait sur CHAQUE run CI (ne passait
qu'en local via un fichier non suivi). L'artefact run est byte-déterministe
(aucun horodatage/hôte/durée · path relatif) → committer est licite. Fix :
run désormais commité (baseline), .gitignore + README réécrits.
Défaut #2 (invariant manquant) : coverage_report prouvait CI→disque mais pas
l'inverse. Nouveau disk_test_modules() + orphan_tests_dirs dans INV4 : un module
gagnant un tests/ sans job CI fait chuter la couverture (fini la sous-comptée
silencieuse de la matrice). Schéma + 2 tests de morsure.
Consommateur régénéré : qa/audit_4big/quality_report.json (README 3710→4236 o,
24→26 méthodes). Matrice 534/21/PASS inchangée (harnais self-exclu, INV3).
5 gates verts · 26+34 tests OK · YAML valide.
L'artefact le plus cité du dépôt (qa/regression/out/regression_run.json ·
21 suites · 534 tests · PASS) n'avait aucun garde-fou CI : check_artifacts
ne rejoue que 'build' et exclut les artefacts d'exécution 'run'. Nouveau
5e gate statique rejoue 'regression_gen.py run' (~5s, déterministe) et exige
byte-identité + verdict PASS. Rend impossible la re-commission d'une matrice
périmée (dérive type demo 18->21) ou rouge commitée verte. parse_ci inchangé
(gate sans working-directory) → 0 dérive des counts dérivés (check_artifacts
exit 0). Preuve de morsure OK (534->999 => exit 1). 5 gates verts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nouveau gate CI bloquant : régénère chaque 05_deliverables_mvp/**/out/ depuis son
générateur `build` et exige l'égalité byte-for-byte avec le fichier commité
(découverte auto · stdlib pur · zéro pip). Attrape la dérive silencieuse d'un
artefact qui auto-résout une valeur depuis un autre module et devient périmé quand
la source grandit — dérive qu'aucune suite tests/ (fonctions, pas fichier commité)
n'attrapait.
Défaut détecté et corrigé par ce gate : demo/scenarios/out/run_sheet.json
embarquait ci_modules_count=18 (buildé Sprint 7) alors que qa/audit_4big en compte
désormais 21. Régénéré (diff 1 ligne). Consistance live re-vérifiée.
Wiring : job check-artifacts + needs du gate agrégé (.gitea/workflows/ci.yml) ·
doc (ci/README.md). Vérifs : test négatif du gate OK (dérive → EXIT 1), 4 gates
statiques EXIT 0, suites CI-parsing vertes (regression/acceptance/audit_4big/demo),
21/21 suites · 534 tests, compteurs dérivés stables.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
En-tête de specs/CHOISIR_MON_UNITE_SPEC.md se contredisait avec le bas du fichier :
Status affichait 'Attente vision précise Michel · placeholder' + 5 'Questions
ouvertes pour Michel', alors que DÉCISIONS FINALES MICHEL (2026-07-28 · L63-109)
tranchent déjà chacune de ces questions + parcours vendeur finalisé.
- Status → '✅ Vision finalisée (2026-07-28) · spec design-complete', implémentation
page /choisir-mon-unite marquée frontend VPS hors-périmètre worker (#8, comme EAS mobile).
- Vision cible : (à confirmer) → (✅ confirmée · cf. Décisions finales).
- Questions ouvertes : conservées pour traçabilité, chacune mappée à sa décision
(Speckle 3D / prix public+login / réservation 10j ERPNext / DocuSign / pas de comparateur).
Honnêteté #6 : fichier volontairement non-scoré (soft-warn attendu) — spec dont
l'implémentation est hors-repo, scorer inventerait une qualité de livrable inexistante ;
rationale mis à jour (plus 'placeholder attente vision', framing devenu faux).
Vérifs : check_docs EXIT 0 (0 lien cassé) · guard_constraints EXIT 0 · validate_json
EXIT 0 · régression inchangée 534 tests verts · docs pures, 0 code.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Seule fiche agent (parmi qa/crm/devops qui en ont une) sans aucun lien vers ses
livrables gated : 03_agents/faisabilite/AGENT.md avait 0 lien 05_deliverables_mvp
vs 3 chez ses pairs. Aucun chemin de la mission vers les artefacts commités qui la
réalisent — vrai gap de finition, non un polish cosmétique.
+ section « Livrables Faisabilité réellement produits » (table calquée sur le
pattern des agents pairs) · 3 entrées TOUTES vérifiées par lecture directe :
TEMPLATE_FAISABILITE_CANONIQUE_v1.0.md [S1 · check-docs] · generator/ [S2 ·
faisabilite_gen score/scaffold/generate/batch · faisabilite-gen-tests · 16t] ·
bancable/ [S3 · bancable_gen build/validate · bancable-tests · 22t].
Anti-invention #6 : sprints lus dans en-têtes README/CLI, sous-commandes via --help,
comptes de tests repris du regression run faisant autorité, jobs CI lus dans ci.yml.
Frontière worker/VPS #8 rappelée : générateurs = contrepartie commitée+testable des
composants runtime OTOIA hors périmètre (aucune faisabilité PXX concrète prétendue).
Vérifs : check_docs vert (0 lien cassé · 3 nouveaux liens relatifs résolvent) ·
guard_constraints vert (exit 0). Doc-only, 0 code · régression inchangée 534 verts.
Auto-score 4Big 96/100.
Harnais méta-niveau : agrège l'exécution de toutes les suites gated en une
matrice + verdict PASS/FAIL et fournit le compte agrégé faisant autorité
(N tests verts). Périmètre dérivé du CI (réutilise q4lib/registry.parse_ci ·
zéro duplication) ; anti-invention (#6) : le plan ne contient aucun compteur de
résultat, recomputé à la validation. Enregistré dans l'audit 4Big (18→19
modules · PASS 19/19). run exhaustif : 19/19 suites vertes · 474 tests passés.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Orchestrateur méta-niveau : compose les hand-offs out/ des 10 modules déjà
livrés en un run-sheet de démo jouable, via pointeurs RFC 6901 résolus à la
construction (zéro chiffre en dur · #6). Réutilise le validateur Publiciste et
la preuve de couverture CI de l'auditeur 4Big (#5) — une démo ne s'appuie que
sur des modules gated. Enregistre demo/scenarios dans l'audit 4Big (17→18,
verdict PASS 18/18 à 100). 32 tests (9 injections négatives) · régression 474
tests verts · guards CI verts · builds déterministes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Audit de méta-niveau + gate : note la qualité 4Big de 100% des livrables gated
et bloque (FAIL) si un module < 95/100 (CLAUDE.md #5). Couverture PROUVÉE par
recoupement bijectif registre ↔ working-directory du CI (moins l'auditeur · SoD
ISA 315). 5 critères déterministes (DOC/CONTRAT/TESTS/CLI/HANDOFF) renormalisés
par archétype. Anti-invention (#6) : chaque note est recalculée depuis des faits
du dépôt, jamais saisie ; un invariant recompute chaque note.
Résultat : PASS · 17/17 modules à 100/100. Régression 442 tests verts (+34).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DocType custom porteur des dossiers d'incitation touristique CONFOTUR (Ley
158-01, RD), référencé par RBAC (3 rôles) et le workflow vente mais jamais
généré. Permissions = permissions_cibles RBAC mot pour mot ; is_submittable
déduit de l'action submit ; estado/dossier_vente dérivés du workflow ; aucun
taux/loi/montant fabriqué (#6). 14 invariants, 44 tests, hand-off dans out/.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Livre le dernier volet ouvert de Sprint 4 : Frontend Console « 5 portails
(Ventes/Construction/Achat/Compta/Direction) ». Contrainte #1 (ERPNext natif) :
le portail de landing par rôle EST le DocType Workspace v15 → 5 Workspaces natifs.
Anti-invention (#6) : rôles et DocTypes dérivés du contrat rbac_50_roles.json ;
chaque lien/raccourci vise un DocType présent dans les permissions_cibles du
portail (droit prouvé) ; couverture exhaustive ; flag custom issu du contrat ;
tokens de marque repris verbatim de CLAUDE.md #4. CLI + 12 invariants + 19 tests
(4 négatifs). Régression 260 tests verts. Hand-off VPS #8 documenté.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contrat pipeline commercial CRM natif (lead → visite → devis → réservation →
contrat → CONFOTUR) → fixtures Frappe/ERPNext v15 : Workflow (9 états / 11
transitions) + Workflow State + Workflow Action Master + MANIFEST.
Rôles gardant états/transitions résolus depuis rbac_50_roles.json (réutilisation,
zéro duplication · #6) : le contrat ne cite qu'un id de rôle, jamais un nom
Frappe en dur. CLI build/validate refuse d'écrire si l'un des 9 invariants de
graphe casse (monotonie doc_status, atteignabilité, séparation des pouvoirs sur
les étapes engageant de l'argent / clôturant).
25 tests (stdlib pur + oracle jsonschema) · job CI crm-workflow-vente-tests ajouté
au gate · 146 tests de régression verts au total. Application VPS (DocType porteur
OTO Dossier Vente + bench migrate) = agent ERPNext Backend, hors périmètre worker.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Clôt le volet RBAC en-repo : recoud fixtures Role+DocPerm, plan User Permission
et Role Profile en un run-book ordonné + manifeste agrégé. Zéro chiffre
recalculé (tout vient d'un manifeste source, #6), graphe de dépendances validé
(Role avant Role Profile), cohérence inter-volets + couverture bijective 50/50.
16 tests + job CI rbac-applyplan-tests · 99 tests de régression au total.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| [`dossier_vente/`](../../05_deliverables_mvp/crm/dossier_vente/README.md) | 4 (roadmap L50) | **DocType porteur**`OTO Dossier Vente` : le document réel qui circule dans le Workflow ; sans lui le pipeline n'a rien à quoi s'attacher | `doctype_dossier_vente_gen.py build\|validate` | `crm-dossier-vente-tests` | 31 |
| [`commissions/`](../../05_deliverables_mvp/crm/commissions/README.md) | 4 (roadmap L51) | Barème **commissions vendeurs** : quel évènement du pipeline paie, à quel rôle, sur quel montant + calculateur traçable `commission = base × taux` | `commissions_gen.py build\|validate` | `crm-commissions-tests` | 27 |
**Trois modules cross-cohérents, une source unique** (CLAUDE.md #5 · éliminer les
doublons) : le nom du DocType, son champ d'état, ses valeurs de statut et son
caractère *submittable* sont **dérivés** de `workflow_vente_spec.json` (anti-dérive) ;
les rôles sont **résolus** depuis `rbac/rbac_50_roles.json` — jamais un nom Frappe
en dur. Total CRM : **83 tests** (25 + 31 + 27), tous gated dans le CI — c'est le
total du **trio pipeline** ; le module financement bancaire ci-dessous porte ses
| [`run_ci.sh`](../../run_ci.sh) | 8 (roadmap L73 · « regression exhaustifs ») | **Miroir LOCAL du gate** : rejoue en une commande, hors runner et hors réseau, l'ensemble exact des checks de `gate.needs` | `bash run_ci.sh` (outil, non un gate) | — (voir ci-dessous) |
### `run_ci.sh` — outillage local, PAS un gate (#5)
Confort développeur, pas un maillon de la chaîne de merge : le script **dérive**
sa liste de checks de `gate.needs` (`.gitea/workflows/ci.yml`, la source déjà
verrouillée par `ci/check_ci_integrity.sh`) — **zéro liste en dur**, donc aucune
dérive silencieuse possible (ajouter/retirer un job CI se répercute ici sans
édition). Il n'ajoute **aucun `ci/*.sh` ni aucune assertion** : ce n'est **pas**
un gate (CLAUDE.md #5), il ne s'inscrit pas dans `gate.needs` et, la racine
n'étant pas sous `ci/`, il échappe par construction à l'invariant qui interdit un
`ci/*.sh` non câblé au gate. Périmètre = uniquement les jobs de `gate.needs`
(exclut les jobs `workflow_dispatch` type `e2e-baseline`/`npm test` qui exigent
Node). Le décompte jobs/gates/suites est **imprimé à l'exécution** — jamais figé
dans le code, où il se périmerait (anti-invention · #6).
### Le gate CI — trois guards blocants (`ci/`)
- **`guard_constraints.sh`** : détecte l'**usage** (pas la simple mention) des
**Rôle** : Cet agent OTOIA génère et maintient à jour toutes les faisabilités de projets sur le **modèle canonique le plus récent**. Élimine les faisabilités obsolètes ou hétérogènes.
> **⚠️ Statut migration V18 (2026-08-10) · à lire AVANT le reste de cette fiche.** Michel a émis la
- **PIE (Annexe 12)** : consomme la faisabilité comme SSOT et propage toute modif aux livrables downstream via le manifest de dépendances (voir §*Faisabilité = SSOT* ci-dessus)
sur le **serveur Speckle**`bim-cloud.otov7.com` → intégration du **viewer 3D**
(iframe Speckle) dans la console, notamment le sélecteur d'unités
`/choisir-mon-unite`.
## Principe directeur : exécution VPS/Speckle, jamais dans le repo mandat (#8)
La conversion IFC→GLB et la publication tournent sur le **VPS** et le **serveur
Speckle** (`bim-cloud.otov7.com`) — **pas** dans ce dépôt de mandat, qui reste un
dépôt de **planification et de contrats**. Il n'existe donc **aucun artefact
diffable commité ici** (pas de `.ifc` / `.glb` / stream Speckle dans
`05_deliverables_mvp/`), et ce document ne prétend pas le contraire : le prétendre
serait une invention (#6, « documenter du code sans vérifier son existence
courante » est interdit). Le livrable de l'agent est la **refonte des modules VPS
existants** ci-dessous et leur **wiring** dans le viewer console, validé par
auto-vérification HTTP côté serveur.
## Points de contact **réellement commités** dans ce dépôt (vérifiés)
Contrairement à la géométrie (hors-repo), le rôle de cet agent est **ancré dans
trois artefacts in-repo vérifiables** — la preuve que sa place dans la plateforme
est contractualisée, pas inventée :
| Contact in-repo (vérifié) | Ce qu'il fixe |
|---|---|
| `05_deliverables_mvp/rbac/rbac_50_roles.json` → rôle `faisabilite-ifc-speckle` | rôle ERPNext **`OTO Faisabilité IFC Speckle`** (portail construction · WA SRL · niveau 2 · perms `File` R/W/create + `Faisabilité` R/W · description « Exporte IFC→GLB et publie sur bim-cloud.otov7.com pour le viewer console ») |
| `05_deliverables_mvp/frontend/portails/out/workspace.json` → `Has Role` | ce rôle est **rattaché à un Workspace** ERPNext (accès console au viewer) |
| `05_deliverables_mvp/qa/acceptance/acceptance_spec.json` → row `S3` (P07, roadmap L46) | le « viewer 3D console LIVE » y est **explicitement `out_of_scope` VPS** (`bim-cloud.otov7.com`, CLAUDE.md #8) — l'honnêteté du périmètre est déjà gatée QA |
> Ces trois fichiers existent et sont couverts par la CI (`rbac-tests`,
> `frontend-portails-tests`, `qa-acceptance-tests`). Le rôle et le viewer sont
> donc **spécifiés en-repo** ; leur **exécution** reste VPS (#8).
Les **builds EAS**, les **soumissions stores** et les **updates OTA** s'exécutent
sur les services **cloud EAS / Apple / Google** — **pas** dans ce dépôt de
mandat, qui reste un dépôt de **planification et de contrats**. Il n'existe donc
**aucun binaire, `.ipa`/`.aab` ni bundle OTA diffable commité ici**, et ce
document ne prétend pas le contraire : le prétendre serait une invention (#6,
« documenter du code sans vérifier son existence courante » est interdit). Le
livrable in-repo de l'agent est le **générateur de config app**`mobile/app_config`
(§ dédiée ci-dessous · la config **versionnable** de l'app, pas le binaire), la
**refonte des modules VPS existants** ci-dessous et le **rôle RBAC** qui les
gouverne, validés par auto-vérification côté serveur (HTTP + builds EAS).
## Points de contact **réellement commités** dans ce dépôt (vérifiés)
Contrairement aux binaires (hors-repo), le rôle de cet agent est **ancré dans des
artefacts in-repo vérifiables** — la preuve que sa place dans la plateforme est
contractualisée, pas inventée. Outre son **générateur de config**`mobile/app_config`
(§ dédiée ci-dessous), **deux cross-références** l'ancrent côté RBAC et QA :
| Contact in-repo (vérifié) | Ce qu'il fixe |
|---|---|
| `05_deliverables_mvp/rbac/rbac_50_roles.json` → rôle `plateforme-mobile` | rôle ERPNext **`OTO Plateforme Mobile`** (« Développeur Mobile » · famille/portail **`plateforme`** · entité **9060 QC** · niveau 2 · scope `groupe` · modules `Core`+`Website` · perm `API Access` custom R/W · description « Maintient l'app **Expo/React Native** et l'**API mobile** ; gère les **builds EAS** et les **soumissions stores** ») |
| `05_deliverables_mvp/qa/acceptance/acceptance_spec.json` → row **S5** (roadmap L58) | l'acceptation Sprint 5 « Apps mobile live 2 stores » ; les « **builds Expo 54 et submit App Store / Play Store** » y sont explicitement listés `out_of_scope` (source : roadmap Sprint 5 · Mobile L56 · CLAUDE.md #8) — l'honnêteté du périmètre est déjà gatée QA |
> Note d'honnêteté (anti-invention #6) : le rôle mobile est de famille
> **`plateforme`** (9060 QC), et non `construction`/`vente`. Il n'est donc
> **volontairement pas** rattaché au Workspace `OTO Ventes` ni à la liste
> `roles_allowed` du chat OTOIA — ces surfaces couvrent les portails
> construction/vente. Ne pas revendiquer ici un contact workspace/chat qui
> n'existe pas ; l'ancrage in-repo réel est son **générateur de config**
> `mobile/app_config` (§ ci-dessous), le **rôle RBAC** et la **row QA S5**.
>
> Ces deux fichiers existent et sont couverts par la CI (`rbac-tests`,
> `qa-acceptance-tests`, dans le `gate`). Le rôle est **spécifié en-repo** ; son
> **exécution** (EAS/stores/OTA) reste hors repo (#8).
| `check_ci_integrity.sh` | Intégrité du câblage CI : chaque `ci/*.sh` est câblé à un job **dans**`gate.needs` (aucun gate décâblé ni orphelin) — c'est le gardien des gardiens (il **s'exclut** lui-même · SoD). |
| `check_mobile_workflow.sh` | Intégrité du **second** workflow `mobile-build.yml` (EAS build iOS/Android) — **hors**`gate.needs` (il s'active quand Michel fournit `EAS_TOKEN`), donc *non* couvert par `check_ci_integrity` qui ne verrouille que `ci.yml` : bien-formé, gating d'activation `if:` job-level porté par `needs.preflight.outputs.*` (jamais le contexte `secrets.`, non exposé hors GitHub → SKIP silencieux), contrat d'outputs sans dangling (MOB-1..4). |
| `check_readme_claims.sh` | Intégrité des **chiffres** des README / fiches agent (CLAUDE.md #6) : chaque compte cité est **recalculé** depuis l'artefact `out/` ou la source (jamais une donnée inventée). |
| `check_artifacts.sh` | **Reproductibilité** : chaque `out/*.json` se régénère **byte-identique** (aucune dérive entre source et hand-off commité). |
| `check_regression.sh` | **Fraîcheur** de la matrice de régression : le run rejoué == le run commité (aucune régression silencieuse · #5). |
| `check_docs.sh` | Qualité documentaire : les liens Markdown relatifs internes pointent vers une cible **existante** (gate 4Big). |
| `guard_constraints.sh` | Enforce les **contraintes NON-NÉGOCIABLES** de CLAUDE.md : plateformes git proscrites (hors Gitea), CRM interdits, paiement non conforme, écriture serveur bannie — via une prohibition marquée ou l'échappatoire `ci-allow`. |
| `validate_json.sh` | Tous les `.json` suivis sont **bien formés** (parse strict) — première ligne de défense avant toute lecture d'artefact. |
Ces gates ne **produisent** pas de livrable métier : ils **prouvent** que les
livrables des autres agents tiennent. Un nouveau gate suit la même règle — il est
**automatiquement** intégré au périmètre dès qu'il est câblé (découverte CI), sans
liste à maintenir.
## Non-négociables (voir CLAUDE.md racine pour la liste complète)
- Gitea only (jamais GitHub)
- ERPNext natif en priorité
- Score 4Big 95+/100
- Zéro invention chiffres
- Score 4Big 95+/100 — **c'est le seuil que ces gates font respecter**
- Zéro invention de chiffres — les nombres cités par les gates sont des **citations
verbatim** de la roadmap ou des comptes **recalculés** depuis le CI/les `out/`
- Vérifier · Investiguer · Valider · Confirmer
## Livrable attendu semaines 1-8
Voir `04_roadmap/ROADMAP_8_WEEKS.md` pour deliverables par sprint.
**Rôle** : Cet agent produit les **visuels photoréalistes** d'un projet — **6 vues
minimum par projet** — par génération **Flux/ComfyUI** pilotée depuis le modèle
**IFC** (produit par l'agent BIM) et la description du **projet réel**. Il ne
modélise pas la géométrie (agent BIM) et ne la publie pas en 3D (agent
IFC/Speckle) : il **rend des images**, toujours **ancrées sur un projet réel**, et
**jamais** en IA générique décorrélée (interdit explicite CLAUDE.md · #3 des
interdits absolus).
## Scope
Batch renders depuis IFC/description projet, 6 vues par projet minimum
Modèle **IFC** + description projet (P01…P09) → **batch de rendus Flux**
(dev fp8, RunPod ComfyUI) → **6 vues/projet** minimum → livraison pour la
faisabilité (volet Architecture / Paysage-Expérience) et le site public / la
console. Refresh automatique quand le modèle ou la description évolue.
## Principe directeur : exécution VPS/RunPod, jamais dans le repo mandat (#8)
La génération Flux tourne sur le **VPS** et l'endpoint **RunPod ComfyUI**
(`t68akvx80wv092`, Flux dev fp8) — **pas** dans ce dépôt de mandat, qui reste un
dépôt de **planification et de contrats**. Il n'existe donc **aucun rendu diffable
commité ici** (pas de `.png` / `.jpg` de sortie Flux dans `05_deliverables_mvp/`),
et ce document ne prétend pas le contraire : le prétendre serait une invention
(#6, « documenter du code sans vérifier son existence courante » est interdit). Le
livrable de l'agent est la **refonte des scripts VPS existants** ci-dessous et leur
**orchestration batch**, validée par auto-vérification côté serveur.
## Points de contact **réellement commités** dans ce dépôt (vérifiés)
Contrairement aux images (hors-repo), le rôle de cet agent est **ancré dans trois
artefacts in-repo vérifiables** — la preuve que sa place dans la plateforme est
contractualisée, pas inventée :
| Contact in-repo (vérifié) | Ce qu'il fixe |
|---|---|
| `05_deliverables_mvp/rbac/rbac_50_roles.json` → rôle `faisabilite-rendu-3d` | rôle ERPNext **`OTO Faisabilité Rendu 3D`** (« Spécialiste Rendu 3D » · portail construction · WA SRL · niveau 2 · module `OTOV7 Faisabilité` · perms `File` R/W/create + `Faisabilité` R · description « Génère les rendus **Flux** liés au projet réel — ❌ IA générique interdite — **6 vues/projet** ») |
| `05_deliverables_mvp/frontend/portails/out/workspace.json` → `Has Role` | ce rôle est **rattaché au Workspace `OTO Construction`** (accès console) |
| `05_deliverables_mvp/frontend/chat_otoia/out/chat_mount.json` → `roles_allowed` (portail construction) | ce rôle fait partie des rôles autorisés à **invoquer le chat OTOIA** (déclenchement des rendus assisté) |
# Activity Log · 2026-08-11 · Claude Code DTP Worker
## Session `20260811_015744` — FIX · CI RED → VERT · 2 régressions induites par les commits root V18 (archivage V10/V11 + 4 docs REFERENCE root-owned mode 600)
**Constat au démarrage.** `./run_ci.sh` = **31 PASS · 2 FAIL · 0 SKIP** (régression vs la baseline 33/0/0 du 2026-08-06). Deux gates rouges : `check-docs` et `check-readme-claims`. Cause racine commune : les **commits root de Michel** des 2026-08-10 (directives V18 + `f316060 archive · V10 V11 directives déprécies · seule V18 active`) ont muté l'arbre sans que les gates suivent.
**Régression 1 · `check_docs` — lien pendant.** `README.md:88` liait `DIRECTIVE_WORKFLOW_FAISABILITE_V10_20260803.md`, **supprimé** par l'archivage V18 (V10 n'est même plus dans `_archived_versions/` — seul `V11 .archived` y subsiste). Lien cassé → gate rouge.
- **Fix (édition worker légitime, doc du worker).** Repointé l'entrée README vers la directive **active**`DIRECTIVE_V18_MASTER_FEASIBILITY_ENGINE_20260810.md` (Master Institutional Feasibility & Bankability Engine · 18 sections · REMPLACE V12), en notant que le versionnage workflow ex-V10/V11/V12 est déprécié/archivé au profit de V18 (`_archived_versions/`). Le fragment-slug de section `#directives-michel-input-specs-datés` (cf. mémoire `markdown-anchor-fragment-integrity`) est intact.
**Régression 2 · `check_readme_claims` — 4 docs root-owned illisibles.** Le scan « VPS infra » énumère `git ls-files *.md`, ouvre chaque fichier en Python (`open()`) et vérifie IP/conteneurs. Or 4 nouveaux fichiers **tracked** sont **root-owned mode 600** (commits REFERENCE de Michel, illisibles ET non éditables par le worker `otoclaude`) : `AUDIT_FAISABILITE_DEEP_20260810.md`, `AUDIT_P1_COMPTE_CLIENT_20260810.md`, `DIRECTIVE_COMPTE_CLIENT_COURRIELS_20260810.md`, `GO_SIGNAL_20260810_1540.md`. `open()` lève `PermissionError` → `bad()` → gate rouge.
- **Précédent appliqué (mémoire `guard-tracked-files-exclusion`).**`guard_constraints.sh` gère déjà exactement cette classe : il **exclut**`DIRECTIVE_*.md` / `AUTORISATIONS_*.md` / `OTO_DESIGN_SYSTEM_*.md` de son scan et lit via `grep 2>/dev/null` (tolère l'illisible → aucun rouge). `check_readme_claims` n'avait pas l'équivalent.
- **Fix (chirurgical, honnête).** Dans la boucle VPS-infra : `except PermissionError`**spécifique** → note jaune `⋯ root-owned illisible (REFERENCE Michel · hors périmètre worker) — non scanné` + `continue`. **Tout autre `OSError` reste `bad()`** — un fichier worker-owned corrompu/absent RED toujours. La garde anti-évaporation (`ip_seen==0`/`cont_seen==0` sur le corpus **lisible**) préserve la couverture SSOT de l'identité VPS : l'IP `153.75.250.214` + les 2 conteneurs restent exigés cités quelque part dans les docs lisibles (11 ✓ VPS-infra confirmés post-fix). Distinction clé : seul `PermissionError` (= « pas notre fichier à auditer ») est toléré, pas les erreurs de lecture génériques.
**Pourquoi ne PAS éditer les 4 docs / ne PAS les untrack.** Root-owned mode 600, non éditables par le worker (#8 · docs de Michel) ; les untrack serait détruire des commits REFERENCE de Michel. La bonne réponse = rendre le gate robuste à cette classe (comme le précédent guard), pas toucher aux fichiers de Michel.
**Contexte V18 (prochaine étape, hors ce commit).** Le GO signal `V18_GO_SIGNAL_DEVELOPMENT_20260810.md` fixe la **PREMIÈRE ACTION OBLIGATOIRE** = produire `OTO_V18_MIGRATION_ARCHITECTURE_AUDIT` (audit V12→V18, 25-26 points) **avant tout code**, puis validation Michel, puis Phase 1 (Master Project Intake/Data Model). Ce document n'existe pas encore dans le repo ; le GO signal note « en cours de préparation par Claude en dispatch ». Les docs d'audit deep de Michel (`AUDIT_FAISABILITE_DEEP`, etc.) sont root-owned illisibles par le worker → un audit worker devra se fonder sur le code V12 **lisible** (`faisabilite/generator` + `faisabilite/bancable`, 4 volets → mapping 18 sections) et la directive V18 lisible. Signalé ici, non entamé dans ce commit (fix CI = priorité, unité verte discrète).
---
## Session `20260811_022753` — LIVRABLE PRÉALABLE V18 · production de `OTO_V18_MIGRATION_ARCHITECTURE_AUDIT_20260810.md` (audit V12→V18 avant tout code)
**Tâche prioritaire identifiée.** CI vert au démarrage (`./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP**, baseline rétablie session précédente). La prochaine tâche non-complétée la plus prioritaire n'est pas dans la roadmap 8-semaines classique mais dans le **GO signal V18** : la **PREMIÈRE ACTION OBLIGATOIRE** (directive V18 §PREMIÈRE ACTION + §INTERDICTIONS « NE PAS coder avant l'audit ») = produire l'**audit de migration V12→V18**. Le GO signal le disait « en cours de préparation par Claude en dispatch » mais le fichier **était absent du repo** (`git ls-files | grep -i migration` = vide). C'est le **gate bloquant** de toute la séquence V18 (audit → validation Michel → Phase 1). Rien d'autre ne peut avancer côté moteur avant lui.
**Cartographie préalable (code lisible uniquement, anti-invention #6).** Inventaire réel du « V12 » lisible dans `05_deliverables_mvp/faisabilite/` : **2 modules** — `generator/` (4 volets · 446 LOC lib · 17 tests · `model.py CANONICAL` impose 3 %/8.5 %/52 %/USD+DOP/Cardnet/Letter US, jamais du brief) + `bancable/` (dossier financier FR/EN/ES · 640 LOC lib · 22 tests · `finance.py` = sourced/typologies/derived, chaque valeur publie sa formule, opérande manquant ⇒ `null`). Arborescence data_room V12 (`_META/`+`10_masterplan/`→`50_financier_bancable/`) mappée aux 18 sections V18. **Confirmé grep :** aucun `DSCR/LTV/LTC` ni DCF multi-période dans `finance.py` → écart moteur Financial/Bankability (4/8) identifié sans le deviner.
**Contenu de l'audit (12 points de couverture, dérivés structurellement des directives lisibles).** §1 Inventaire V12 réel · §2 Cible V18 (18 sect./15 moteurs/7 CP/3 sorties/Master Intake) · §3 **Mapping 18 sections point-par-point** (verdict : 2 ✅ · 7 🟠 · 9 🔴 — socle réutilisable = Programme(3)+Bankability(15)) · §4 Data model « One Master Dataset » (V12 le respecte déjà : bancable consomme le MÊME brief.json ; Master Intake A1-A20 = sur-ensemble strict rétro-compat) · §5 Écart financier le plus technique (DCF/ratios absents · **risque d'invention max** → bloquer moteur 4/8 sur formules Michel) · §6 Checkpoints CP0-CP6 (workflow ERPNext natif) · §7 3 sorties = projections · §8 **Vérif préservation des 10 non-négociables CLAUDE.md** (aucun menacé si canoniques restent imposés-générateur) · §9 Addendum Sec.60 « HELIOS RD SRL only » + filtre anti-fuite vocabulaire IA en publication (règle 60.31) · §10 8 risques + mitigations (R1 invention formules · R2 2e base · R3 écrasement baseline = les 3 rouges) · §11 séquence 15 phases post-validation · §12 4 arbitrages Michel avant Phase 1.
**Honnêteté de sourçage (déclarée, non cachée · §0).** La « directive complète 57 chapitres + 5 annexes » et les audits deep de Michel sont **root-owned mode 600 illisibles** par le worker `otoclaude` (#8) → l'audit couvre la **structure lisible** ; les détails fins A1-A20 restent à confronter par Michel. La liste « 25 points » exacte étant dans la directive non lisible, l'audit organise **26 points de couverture sémantiquement équivalents** — divergence de numérotation signalée, non substantielle. **Aucun contenu deviné.**
**Interdiction respectée.** Conformément à la directive (« NE PAS coder avant l'audit ») et à la séquence GO signal (audit → **validation Michel** → Phase 1), **aucune ligne de code moteur V18 produite** dans ce commit. Le livrable EST le document. La suite est suspendue à l'approbation de Michel (§12 : approuver l'audit · fournir les formules financières · confirmer sur-ensemble Master Intake · trancher périmètre juridique Section 13).
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé — le nouveau `.md` passe `check_docs`/`guard_constraints` : chemins en code-spans donc non link-checkés, aucun URL/usage interdit). Aucune commande VPS (#8), aucune API externe. Fichiers : `OTO_V18_MIGRATION_ARCHITECTURE_AUDIT_20260810.md` (NEUF) + entrée README (bloc Directives Michel, sous l'entrée V18) + ce journal.
---
## Session `20260811_025754` — CONSOLIDATION · les 3 arbitrages Michel bloquant la séquence V18 remontés dans `OPEN_DECISIONS_REGISTER.md` (D-06→D-08)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). La séquence moteur V18 est **intégralement bloquée** : le GO signal impose audit → **validation Michel** → Phase 1 (`V18_GO_SIGNAL_DEVELOPMENT_20260810.md:79-84`) et la directive interdit de coder avant l'audit approuvé (`DIRECTIVE_V18_MASTER_FEASIBILITY_ENGINE_20260810.md:90-91`). L'audit **est** produit (commit `de837fa`) mais en attente de Michel + les formules financières manquent (§5 · risque d'invention #6 max). **Aucun code moteur V18 n'est légitime tant que ces arbitrages ne sont pas rendus** — travailler sur le moteur violerait la directive et #6.
**Tâche prioritaire non-bloquée identifiée.** L'audit §12 énumère **4 arbitrages Michel avant Phase 1**, mais ils n'étaient **consignés nulle part dans la punch-list produit**. Or `OPEN_DECISIONS_REGISTER.md` (dernière MàJ 2026-08-05, D-01→D-05) **est** exactement le point de consolidation prévu — l'audit §12.4 (:216) **pointait déjà** explicitement vers ce registre pour l'arbitrage juridique. Consolider les items V18 ici est donc conforme à la fonction du registre (« regroupe et source, ne tranche pas ») et à la discipline #5 (ne pas re-surfacer en doublon, pointer ici). **Non-bloqué, sans invention, doc worker éditable.**
**Fait.**
- **D-06 · 🟠 Approbation de l'audit** = gate d'entrée de TOUTE la séquence moteur. Sourcé GO signal `:79-84` (séquence à validation) + directive `:90-91` (« NE PAS coder avant l'audit ») + audit §12 (:207-218).
- **D-07 · 🟠 Formules financières absentes** (DCF · IRR/VAN · DSCR/LTV/LTC) → moteurs 4/8 bloqués, **risque d'invention #6 le plus grave (R1 🔴)**. Sourcé audit §5 (:102-114) + §10-R1 (:182) + §12.2 (:214) ; `grep` confirme `finance.py` = snapshot sans DSCR/LTV/LTC. Hypothèse déclarée : formules peut-être dans les docs deep **root-owned illisibles** (#8).
- **D-01 enrichi** — l'arbitrage juridique Section 13 (§12.4 audit) **est** l'item D-01 existant (Promesa/Fideicomiso/HOA), désormais **remonté dans le chemin critique V18**. Cross-link ajouté, **pas de doublon** (#5).
- En-tête « Mise à jour » repointé 2026-08-11 avec note du bloc V18.
**Honnêteté de sourçage.** Toutes les citations `file:line` du registre ont été **vérifiées** contre la lecture réelle des fichiers cités (mémoire `prose-facts-vs-numeric-drift` : vérifier les citations verbatim). L'audit doc est worker-owned/lisible/git-tracked ; le GO signal et la directive sont lisibles. Aucun item n'est **tranché** — le worker les surface, Michel décide.
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé — ajouts en code-spans `file:line`, non link-checkés ; aucun décompte de gate ne parse le nombre d'items D-xx du registre — `grep` ci/ confirme : README pointe le fichier sans compter). Aucune commande VPS (#8), aucune API externe. Fichiers : `05_deliverables_mvp/OPEN_DECISIONS_REGISTER.md` + ce journal.
---
## Session `20260811_032800` — CANAL STAKEHOLDER · rapport quotidien `2026-08-11` (currency + remontée du chemin critique V18 bloqué à Michel)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). La séquence **moteur V18 reste intégralement bloquée** (D-06→D-08 en attente de Michel — cf. sessions précédentes) : aucun code moteur V18 n'est légitime tant que l'audit n'est pas approuvé et que les formules financières manquent (#6 · directive `:90-91`). Travailler sur le moteur violerait la directive.
**Tâche prioritaire non-bloquée identifiée.** Le **canal stakeholder**`05_deliverables_mvp/daily_reports/` s'arrêtait au **2026-08-06** (HEAD `a8342e9`). Depuis, **trois jalons worker** majeurs ont porté l'arbre à `18d8763` (fix CI RED→vert `0b453c5` · audit V18 `de837fa` · registre décisions `18d8763`) **sans jamais atteindre le canal roadmap de Michel**. Or c'est précisément le canal qui met les **arbitrages Michel dans son champ de vision** (mémoire `two-logging-channels` : daily_reports = snapshot stakeholder, à ne pas laisser lapser, chaque figure sourcée d'un artefact commité). Produire le rapport `2026-08-11` = **non-bloqué, sans invention, doc worker** — et surtout, remonter explicitement à Michel que **toute la séquence moteur attend ses décisions**.
**Fait.** Rapport `2026-08-11.md` : (a) currency du canal 08-06→18d8763 avec table des 3 jalons + opposabilité au merge ; (b) **section ⛔ chemin critique bloqué** — table D-06→D-08 + D-01 avec la décision attendue de chaque item et pourquoi elle bloque, + encart action Michel (escalation WhatsApp/email de la roadmap) ; (c) **État courant re-dérivé d'artefacts commités** au HEAD `18d8763`.
**Honnêteté de sourçage (tous chiffres re-calculés ce jour, aucun figé · #6).** Gate `33/0/0` = `run_ci.sh` · 8 statiques = `git ls-files 'ci/*.sh'` hors `lib.sh` · 25 suites = jobs `*-tests``gate.needs` · régression **625/608/17/0-PASS** = `regression_run.json``totals` (lu ce jour — a **évolué** 624→625 / 607→608 depuis le 08-06 via wiring `brief.schema.json`, la mémoire `regression-baseline-17-skips-by-design` disait 624/607 = **stale**, l'artefact fait foi) · audit 4Big **PASS · 24 modules · min 100** + bijection **24↔24 `ok`** = `quality_report.json` (`verdict`/`modules`/`coverage`). Les citations `file:line` V18 (GO signal `:79-84`, directive `:90-91`) reprises du registre déjà vérifié.
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé — le nouveau `.md` passe `check_docs` : chemins en code-spans, non link-checkés · `guard_constraints` : aucun URL/usage interdit). **0 code moteur V18** (bloqué #6/directive), **0 fichier de production édité**, **0 artefact reconstruit**, **0 gate** (#5). Aucune commande VPS (#8), aucune API externe. Fichiers : `05_deliverables_mvp/daily_reports/2026-08-11.md` (NEUF) + ce journal.
---
## Session `20260811_035804` — FIX exactitude · claim LOC stale dans l'audit V18 (`generator` 446→622, contradiction interne avec son propre total)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). Séquence moteur V18 toujours **intégralement bloquée** (D-06→D-08 en attente Michel · #6/directive`:90-91`) — aucun code moteur légitime. Tâche non-bloquée = audit d'exactitude d'un livrable worker récent.
**Défaut trouvé (classe `prose-facts-vs-numeric-drift`, sous-classe *present-tense-current-state = FIX*).** L'audit V18 fraîchement produit (`de837fa`, `OTO_V18_MIGRATION_ARCHITECTURE_AUDIT_20260810.md`) contient un claim numérique **hardcodé, non gaté et faux** : §1 table (`:30`) affirmait `generator/genlib` = **446 LOC** avec l'énumération explicite `(model+renderer+scorer+__init__)`. Le compte réel (`wc -l generator/genlib/*.py`) = **622** (`model 113 + renderer 315 + scorer 176 + __init__ 18`). Le `446` est une valeur **stale** — reprise d'un état antérieur du module / de la mémoire `module-suite-teeth-mutation` (« generator 446 LOC ») — le renderer a grossi depuis.
**Preuve interne décisive (le doc se contredit lui-même).** L'audit §0 (`:17`) donne un total « **1262 LOC lib** ». Or `446+640=1086 ≠ 1262`, tandis que `622+640 = 1262`**exactement**. Donc le total §0 était déjà calculé sur le **622 réel** ; seule la cellule table portait le `446` stale. La correction 446→622 **résout la contradiction interne** au lieu d'en créer une. Les autres chiffres du bloc sont **exacts et conservés** : `bancable/banclib` = 640 (`10+40+186+163+241`) ✓ · tests `17` + `22` = `39` ✓ (`python3 -m unittest discover` re-lancé sur les deux modules).
**Fix (chirurgical, 1 cellule).** `446` → `622` dans la table §1 de l'audit. **Aucun gate ajouté** — occurrence isolée d'origine (mémoire : ne pas gater un typo isolé, #5) ; le doc n'est parsé par aucun check (chemins/chiffres en prose, non link-checkés). Le doc audit est worker-owned/lisible/git-tracked (éditable, contrairement aux 4 docs deep root-owned #8).
**Ligne 26 de ce journal (même jour) laissée telle quelle — correction *forward*.** Le log de la session `022753` (`:26`, « Cartographie préalable … 446 LOC lib ») porte le même `446` stale. Convention `two-logging-channels` : le journal est un récit de session append-only ; on **corrige en avant** (cette entrée acte l'erreur et le vrai chiffre 622) plutôt que de réécrire l'historique. Le livrable authoritative (l'audit) est, lui, remis exact.
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé). **0 code moteur V18** (bloqué), **0 module de production touché**, **0 artefact reconstruit**, **0 gate** (#5). Aucune commande VPS (#8), aucune API externe. Fichiers : `OTO_V18_MIGRATION_ARCHITECTURE_AUDIT_20260810.md` (1 cellule) + ce journal.
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). Séquence moteur V18 toujours **intégralement bloquée** : GO signal impose audit → **validation Michel** → Phase 1, directive interdit de coder avant l'audit approuvé (`:90-91`), formules financières manquantes (D-06→D-08 en attente Michel). **Aucun code moteur légitime.** Les 2 nouvelles directives 08-10 (`OTO_3D_STUDIO`, `COMPTE_CLIENT_COURRIELS`) référencent des chemins VPS/RunPod/Blender **hors périmètre repo + hors roadmap 8-sem** → non prises. Tâche non-bloquée = audit d'exactitude du livrable-gate le plus critique.
**Vérification menée (le doc que Michel lit pour décider).** Confronté **tous les claims factuels vérifiables** de `OTO_V18_MIGRATION_ARCHITECTURE_AUDIT_20260810.md` au code réel :
- tests **17** (générateur, `Ran 17 tests`) + **22** (bancable, `Ran 22 tests`) = **39** ✓ *(re-exécutés — un premier comptage par grep `... ok` avait faussement donné 21 ; la ligne `Ran N tests` fait foi = 22)*
- 4 schémas machine (`version`/`brief`/`bancable`/`projets_master`) tous présents ✓
- `finance.py` = `sourced`/`typologies`/`derived`/`missing_fields` ✓ · **aucun `DSCR`/`LTV`/`LTC`/`IRR`/`VAN`/`DCF`** (grep vide) ✓ — l'écart moteur 4/8 est réel, pas deviné
**Résultat : l'audit est factuellement SAIN** — aucun défaut résiduel (le seul défaut historique, `LOC 446→622`, a été corrigé session `035804`, commit `c3f5664`). Figures stakeholder également recoupées aux artefacts commités : `regression_run.json totals` = **625/608/17/0-red** ✓ · `quality_report.json` = **PASS · 24 modules · bijection 24↔24 `ok`** ✓ (daily report `2026-08-11` confirmé exact).
**Contribution (non-bloquée, sans invention).** Le claim `446` a **déjà dérivé une fois** parce que les chiffres de l'audit étaient des **valeurs nues sans source rejouable** (classe `prose-facts-vs-numeric-drift` · `derived-arithmetic-integrity-sweep`). Ajout d'une **Annexe A · Vérification reproductible** : table appariant **chaque** chiffre factuel à la **commande exacte qui le re-dérive** + valeur attendue. Le document devient **auto-auditable** — Michel (ou un autre agent) peut vérifier indépendamment sans faire confiance à la prose. Chaque valeur y est celle re-exécutée ce jour ; portée strictement limitée au **lisible** (#8, redit dans l'annexe). Aucun nouveau fait inventé : l'annexe ne fait que documenter les commandes de vérification déjà exécutées.
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé — l'annexe passe `check_docs` : chemins/commandes en code-spans, non link-checkés · `guard_constraints` : aucun URL/usage interdit). **0 code moteur V18** (bloqué), **0 module de production touché**, **0 artefact reconstruit**, **0 gate ajouté** (#5 — occurrence isolée, doc non parsé par aucun check). Aucune commande VPS (#8), aucune API externe. Fichiers : `OTO_V18_MIGRATION_ARCHITECTURE_AUDIT_20260810.md` (Annexe A) + ce journal.
## Session `20260811_045813` — DOC AGENT.md · bannière statut V18 dans la fiche `faisabilite/` (le module le plus touché par le pivot, muet sur la migration)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). Séquence moteur V18 toujours **intégralement bloquée** en attente Michel (D-06→D-08 · directive `:90-91` « NE PAS coder avant l'audit approuvé » · #6) — **aucun code moteur légitime**. Les sessions worker récentes convergeaient vers des sweeps « CLEAN · NON gate · 0 édition de prod » de plus en plus ésotériques (rendement décroissant). Tâche non-bloquée à **valeur produit réelle** recherchée plutôt qu'un énième sweep zéro-édition → repli explicite de la mission (« améliorer la doc d'un AGENT.md existant »).
**Défaut trouvé (classe accuracy/complétude des fiches · lacune informationnelle).** La fiche [`03_agents/faisabilite/AGENT.md`](../03_agents/faisabilite/AGENT.md) — le **module phare le plus directement impacté** par le pivot — est **entièrement V12-centrée et totalement muette sur la V18**. Un lecteur (agent ou humain) de cette fiche aujourd'hui n'a **aucune indication** que, le 2026-08-10, Michel a émis `DIRECTIVE_V18_MASTER_FEASIBILITY_ENGINE` qui **remplace** le modèle « 4 volets » décrit, qu'un audit de migration existe, ni que **toute la séquence moteur est bloquée** sur ses arbitrages. Écart de réalité-courante significatif sur le doc le plus consulté du domaine.
**Fix (doc worker · sourcé · zéro invention #6).** Bannière `> ⚠️ Statut migration V18` insérée en **tête de fiche** (juste après la ligne Rôle · visibilité maximale), avec **3 liens résolus** vers les sources lisibles commitées : la directive V18, l'audit de migration `OTO_V18_MIGRATION_ARCHITECTURE_AUDIT`, et le `OPEN_DECISIONS_REGISTER` (D-06 approbation audit · D-07 formules DCF/IRR/VAN/DSCR/LTV/LTC absentes · D-08 périmètre Master Intake). Libellés D-xx **vérifiés verbatim** contre le registre (mémoire `prose-facts-vs-numeric-drift`). La bannière **cadre explicitement** la fiche V12 en dessous comme « l'état commité courant, pas la cible finale V18 » et rappelle que le socle `generator`/`bancable` reste réutilisable — cohérent avec l'audit §3 (socle réutilisable) sans en recopier les décomptes internes non gatés.
**Sûreté des gates (vérifiée avant édition).** `ci/check_readme_claims.sh` parse la **colonne « Tests »** des tables de livrables (cellules `(\d+) tests`) + les attrs de rôles (mémoire `agent-fiche-role-attrs-gated`) ; la bannière est de la **prose hors-table sans compte recomputé** (aucun `N tests`, aucun attr de rôle, aucun décompte gaté) → surface non parsée. `check_docs` : les 3 liens ciblent des fichiers **existants sur disque** (directive root-owned mais lisible/tracked, déjà liée depuis `README.md:88` ; audit + registre worker-owned) ; aucun fragment-anchor. `guard_constraints` : aucun URL/usage interdit (DCF/IRR/DSCR/LTV/LTC ≠ termes proscrits).
**Incident auto-détecté (RED induit dans ce journal, corrigé avant push).** Première rédaction du lien vers la fiche dans ce log avec le préfixe `../../`**recopié de la bannière** — or la bannière vit à `03_agents/faisabilite/` (2 niveaux) tandis que ce journal est à `05_activity_log/` (**1 niveau**) → `check_docs` a signalé « lien cassé `../../03_agents/faisabilite/AGENT.md` » (RED transitoire capté par `run_ci.sh`). Corrigé en `../03_agents/…`. Leçon : la profondeur relative d'un lien dépend du fichier **hôte**, pas de la source copiée (mémoire `acceptance-evidence-paths-deliverables-root` — même piège de base relative). Le commit fautif n'a **jamais été poussé** ; amend appliqué sur un arbre vert.
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (rétabli après correction du lien). **0 code moteur V18** (bloqué), **0 module de production touché**, **0 artefact reconstruit**, **0 gate ajouté** (#5 — occurrence isolée, doc non parsé pour cette surface). Aucune commande VPS (#8), aucune API externe. Fichiers : `03_agents/faisabilite/AGENT.md` (bannière) + ce journal.
---
## Session `20260811_052814` — DOC AGENT.md · bannière statut V18 dans la fiche `bim/` (2e module le plus impacté par le pivot — 7/18 sections « + BIM » + Clash Detection — muet dessus)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). Séquence moteur V18 toujours **intégralement bloquée** en attente Michel (D-06→D-08 · directive `:90-91` « NE PAS coder avant l'audit approuvé » · #6) — **aucun code moteur légitime**. Roadmap 8-sem : les 24 modules commités sont tous **PASS** (`quality_report.json` verdict PASS · 24↔24 bijection). Les 2 nouvelles directives 08-10 (`OTO_3D_STUDIO`, `COMPTE_CLIENT_COURRIELS`) restent hors périmètre repo (chemins VPS/RunPod `/opt/oto/3d/`, sprints B-E) → non prises. Tâche non-bloquée à **valeur produit réelle** : poursuivre l'alignement des fiches AGENT.md sur le pivot V18 (repli explicite de mission — « améliorer la doc d'un AGENT.md existant »).
**Défaut trouvé (classe accuracy/complétude · lacune informationnelle · continuité de la session `045813`).** La session précédente a doté [`faisabilite/AGENT.md`](../03_agents/faisabilite/AGENT.md) d'une bannière V18. Or la fiche [`bim/AGENT.md`](../03_agents/bim/AGENT.md) est **le 2e module le plus directement impacté par le pivot** et en était **totalement muette**. Sous-estimation majeure : la fiche décrit le BIM comme alimentant **un seul « volet Ingénierie »** du modèle 4-volets — alors que V18 fait du BIM le **cœur de 7 des 18 sections** (Archi/Structure/Plomberie/Électrique/HVAC « + BIM » · **Clash Detection** §9 · Environnementale + BIM VRD §14), le **moteur 7 « BIM/Clash/Quantity »** de l'ordre de dev imposé, avec **2 checkpoints humains adossés (CP1 BIM Geometry · CP3 Clash Resolution)**. Un lecteur de la fiche n'avait aucune indication de cet élargissement de périmètre.
**Fix (doc worker · sourcé · zéro invention #6).** Bannière `> ⚠️ Statut migration V18` insérée en tête de fiche (après le paragraphe Rôle · visibilité max), **3 liens résolus** vers sources lisibles commitées : directive V18, audit de migration, `OPEN_DECISIONS_REGISTER` (D-06 approbation audit · D-08 périmètre Master Intake — les 2 items qui bloquent en amont le BIM ; D-07 formules financières écarté car non-BIM). **Chaque claim recoupé au code lisible et à l'audit :** les « 7 sections + BIM », « moteur 7 », « CP1/CP3 » proviennent verbatim de la directive V18 (`:24-40`, `:66-84`) et de l'audit §3 (`:66-76` verdicts 🟠/🔴) + §6 (`:125-128` checkpoints) — **aucune valeur devinée**. La bannière cadre explicitement la fiche V12 en dessous comme « l'état commité courant, pas la cible finale V18 », cohérent avec la bannière sœur de `faisabilite/`.
**Sûreté des gates (vérifiée avant édition).** `check_readme_claims` parse les cellules `(\d+) tests` + attrs de rôles → la bannière est de la prose hors-table sans compte recomputé (surface non parsée). `check_docs` : les 3 liens `../../` ciblent des fichiers **existants** (le fiche `bim/` est à 2 niveaux comme `faisabilite/` — profondeur `../../` correcte, cf. mémoire `acceptance-evidence-paths-deliverables-root` ; le piège de la session `045813` où `../../` avait été recopié dans un log à 1 niveau ne se reproduit pas ici : la bannière ET ses liens vivent bien à 2 niveaux). `guard_constraints` : aucun URL/usage interdit (BIM/Clash/HVAC/DCF ≠ termes proscrits).
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé). **0 code moteur V18** (bloqué), **0 module de production touché**, **0 artefact reconstruit**, **0 gate ajouté** (#5 — occurrence isolée, doc non parsé pour cette surface). Aucune commande VPS (#8), aucune API externe. Fichiers : `03_agents/bim/AGENT.md` (bannière) + ce journal.
---
## Session `20260811_055819` — DOC module · bannière statut V18 dans le README du module `bancable` (la graine V12 des moteurs financiers 4/8 — le risque de migration R1 🔴 le plus grave — muet dessus)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). Séquence moteur V18 toujours **intégralement bloquée** en gouvernance : D-06 (approbation de l'audit de migration par Michel) est le gate d'entrée de TOUTE la séquence ; D-07 (formules DCF/IRR/VAN/DSCR/LTV/LTC absentes) et D-08 (Master Data Model sur-ensemble strict) bloquent en aval (`OPEN_DECISIONS_REGISTER.md` · directive `:90-91` « NE PAS coder avant l'audit » · #6). Confirmé aussi via `V18_GO_SIGNAL_DEVELOPMENT_20260810.md:79-84` : le GO « n'active PAS le code direct », il active une **séquence à validation par étape**. Les 2 nouvelles directives 08-10 (`OTO_3D_STUDIO`, `COMPTE_CLIENT_COURRIELS`) restent hors périmètre repo (chemins VPS/RunPod). Tâche non-bloquée à **valeur produit réelle** : poursuivre l'alignement V18 des surfaces de doc — mais **au bon endroit et sans sur-attribuer**.
**Piège d'exactitude écarté avant d'agir (#6).** Candidat initial = bannière V18 sur la fiche `crm/AGENT.md` (module `crm/financement_bancaire`). **Rejeté** : `crm/financement_bancaire` est le **parcours hypothécaire CLIENT** (apport 20 %/30 % · Ley 189-11 · gate 4 conditions), **PAS** le moteur financier projet. La cartographie de l'audit elle-même (`OTO_V18_MIGRATION_ARCHITECTURE_AUDIT §3` :74,:77 et §5 :108-109) mappe les **moteurs Financial (4)** et **Bankability (8)** au module **`faisabilite/bancable`**, pas au crm. Bannière « moteur 4/8 » sur la fiche crm = **sur-attribution inventée** → écartée. (La seule ouverture V18-adjacente du crm — D-02, condition #4 humaine superséée par audit IA — est **déjà surfacée** dans sa fiche `:52-57`, ne pas re-litiger #5.)
**Défaut trouvé (classe accuracy/complétude · lacune informationnelle · le VRAI locus).** Le module [`faisabilite/bancable/README.md`](../05_deliverables_mvp/faisabilite/bancable/README.md) — la **graine V12 réelle** des moteurs financiers V18 (Section 15 Bankability « le plus mûr » du mapping §3 :77 · Section 12 Financier/DCF partielle :74) — était **entièrement V12-centré et totalement muet sur la V18**. Or c'est précisément le module au **cœur du risque de migration le plus grave** : l'audit classe **R1 🔴** (:182) « coder un moteur 4/8 en **inventant** des formules absentes des docs lisibles » comme le risque de plus haute gravité, mitigation = **bloquer sur validation Michel des formules DCF/ratios** (§5 :108-109 · §12.2 :214 · D-07). Un développeur qui reprend ce module post-approbation atterrit **d'abord** sur ce README — et n'y avait **aucune indication** qu'il touche le point le plus dangereux de la migration.
**Fix (doc worker · sourcé · zéro invention #6).** Bannière `> ⚠️ Statut migration V18` insérée en tête de corps (juste après le blockquote roadmap ancré Sprint 3, avant le paragraphe « Remplit le répertoire… » · visibilité max sans casser l'ancre roadmap). Chaque claim recoupé verbatim au code lisible et à l'audit : « graine V12 des moteurs 4/8 » ← mapping §3 :74/:77 ; « formules DCF/IRR/VAN/DSCR/LTV/LTC absentes du code lisible » ← §5 :108-109 + Annexe A :236 (`grep` sur `banclib/` rend vide — **fait re-vérifiable, pas deviné**) ; « R1 🔴 » ← :182 ; « séquence suspendue à l'approbation de l'audit » ← D-06. **3 liens résolus** (directive V18 · audit §3/§5/R1 · `OPEN_DECISIONS_REGISTER` D-06/D-07). La bannière cadre explicitement la doc V12 en dessous comme « l'état commité, *pas* la cible V18 », cohérent avec les bannières sœurs de `faisabilite/` et `bim/` (sessions `045813`/`052814`) — mais au **niveau module** (surface distincte de la fiche agent, où atterrit le développeur du moteur).
**Sûreté des gates (vérifiée avant édition).** `check_readme_claims` gate bancable sur (a) le motif de comptage de tests `\*\*(\d+)/(\d+) verts\*\*` (`:922`) et (b) la phrase canonique `« N % édition » et « N % marketing » (#9)` (`:5894-5913`) → la bannière **ne contient ni l'un ni l'autre** (aucun `N/M verts`, aucun pourcentage #9 — « point d'équilibre en unités » écrit **sans** le nombre) : surface non-tripante, la copie canonique correcte existante en aval est intacte (`re.search` = première occurrence, non touchée). `check_docs` : profondeur des liens vérifiée sur les liens existants du même README (`../../../04_roadmap/…`, `../../../PORTAIL_BANCABLES_4BIG.md`, `../../daily_reports`) → root à `../../../`, `05_deliverables_mvp/` à `../../` ; labels = basenames exacts des cibles (mémoire `link-label-target-mismatch`) avec les §/D-xx **hors** du lien. `guard_constraints` : aucun URL/usage interdit (DCF/IRR/DSCR/LTV/LTC ≠ termes proscrits).
**Drift artefact attendu, régénéré (mémoire `audit4big-rebuild-after-doc-edits`).** `check-artifacts` a rougi comme prévu : `audit_4big`**score le contenu DOC** des modules, donc éditer le README de bancable dérive `quality_report.json`. Régénéré **en dernier** (`audit_4big_gen.py build`) → seule variation = l'`evidence` byte-count du README (`6439 → 7748 octets`) ; **verdict PASS · 24/24 modules ≥ 95 (min 100) inchangé**. Aucun score n'a bougé (la bannière ajoute du contenu sourcé, pas un défaut de qualité).
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (rétabli après régénération). **0 code moteur V18** (bloqué #6), **0 module de production touché** (doc README + son artefact d'audit dérivé uniquement), **0 gate ajouté** (#5 — occurrence isolée, la surface bannière n'est parsée par aucun check). Aucune commande VPS (#8), aucune API externe. Fichiers : `05_deliverables_mvp/faisabilite/bancable/README.md` (bannière) + `05_deliverables_mvp/qa/audit_4big/out/quality_report.json` (byte-count régénéré) + ce journal.
---
## Session `20260811_062822` — DOC module · bannière statut V18 dans le README du module `legal/confotur` (le module-origine de la Section 13 Juridique — 🟠 CONFOTUR seul · arbitrage D-01 — muet dessus)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). Séquence moteur V18 toujours **intégralement bloquée** en gouvernance : D-06 (approbation de l'audit de migration par Michel) = gate d'entrée de TOUTE la séquence ; D-07 (formules DCF/IRR/VAN/DSCR/LTV/LTC absentes) et D-08 (Master Data Model sur-ensemble strict) bloquent en aval (`OPEN_DECISIONS_REGISTER.md` · directive `:90-91` « NE PAS coder avant l'audit » · #6). Les 2 nouvelles directives 08-10 (`OTO_3D_STUDIO`, `COMPTE_CLIENT_COURRIELS`) restent hors périmètre repo. Tâche non-bloquée à **valeur produit réelle** : achever l'alignement V18 des surfaces de doc des **modules-origine** identifiés par le mapping de l'audit §3.
**Défaut trouvé (classe accuracy/complétude · lacune informationnelle · continuité des sessions `045813`/`052814`/`055819`).** Le mapping §3 de l'audit V18 mappe chacune des 18 sections à son origine V12 lisible. Les 3 modules-origine à statut ✅/🟠 déjà couverts par une bannière V18 : `generator` (Section 3, fiche faisabilite), le triplet BIM (fiche bim), `bancable` (Sections 11/12/15, README). **Le 4ᵉ et dernier module-origine majeur restait muet : `legal/confotur`** — origine de la **Section 13 · Juridique** (§3 ligne 13, verdict 🟠). `grep -rl V18` sur `05_deliverables_mvp/legal/` = **vide**. Un développeur atterrissant sur [`legal/confotur/README.md`](../05_deliverables_mvp/legal/confotur/README.md) n'avait **aucune indication** que ce module ne couvre que **CONFOTUR seul** alors que la Section 13 V18 attend aussi les contrats types **Promesa de compraventa / Fideicomiso d'adhésion / règlement HOA** — l'exact périmètre de l'arbitrage **D-01** (déjà remonté dans le chemin critique V18, session `025754`).
**Piège de sur-attribution écarté (#6).** D-07 (formules financières DCF/IRR/VAN/DSCR/LTV/LTC) **écarté** de la bannière car non-juridique — comme la bannière `bim/` l'avait écarté. Cités uniquement **D-06** (gate d'entrée amont de toute la séquence) + **D-01** (l'arbitrage Section 13 lui-même). Chaque claim recoupé verbatim : « Section 13 → ce module · 🟠 » ← audit §3 ligne 75 ; « Promesa/Fideicomiso/HOA · aucun code aujourd'hui » ← §3 ligne 75 + §12.4 ligne 216 + registre D-01 (:30-58) ; libellés D-xx **vérifiés verbatim** contre le registre. **Aucune valeur devinée.**
**Sûreté des gates (le README `legal/confotur` est l'un des plus lourdement gatés — vérifiée AVANT édition).** `check_readme_claims` gate **six** surfaces de ce README : (a) synthèse `N champs/N sections/N rôles` vs MANIFEST, (b) §Permissions par rôle (portail+actions), (c) mention-total « N invariants », (d) 7 entités porteuses, (e) bullet `estado` dérivé du workflow, (f) compte `# N tests (dont`. La bannière est de la **prose hors-table** insérée après le blockquote roadmap **L55** (ancre préservée), avant `## Ce que ça produit` : elle **ne contient aucun** de ces motifs (aucun `N champs`, aucune ligne de permission, aucun « N invariants », aucune liste d'entités, aucun bullet `estado`, aucun `N tests`). `check_docs` : **3 liens résolus** — profondeur calibrée sur le module à **depth 3** (`05_deliverables_mvp/legal/confotur/`) : `../../../` pour les 2 fichiers racine (directive V18 · audit) et `../../` pour le registre (dans `05_deliverables_mvp/`) ; labels = basenames exacts des cibles (mémoire `link-label-target-mismatch`), §/D-xx **hors** du lien. Ce README n'avait **aucun** lien relatif préexistant (`grep ](../` vide) → les 3 sont neufs et vérifiés résolus. `guard_constraints` : aucun URL/usage interdit (CONFOTUR/Promesa/Fideicomiso/HOA/DCF ≠ termes proscrits).
**Drift artefact attendu, régénéré (mémoire `audit4big-rebuild-after-doc-edits`).** `audit_4big`**score le contenu DOC** des modules → éditer ce README dérive `quality_report.json`. Régénéré **en dernier** (`audit_4big_gen.py build`) → seule variation = l'`evidence` byte-count du README (`4888 → 6436` octets) ; **verdict PASS · 24/24 modules · bijection 24↔24 `ok` inchangée**. Aucun score n'a bougé (contenu sourcé ajouté, pas un défaut).
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (rétabli après régénération). **0 code moteur V18** (bloqué #6), **0 module de production touché** (doc README + son artefact d'audit dérivé uniquement), **0 gate ajouté** (#5 — occurrence isolée, la surface bannière n'est parsée par aucun check). Aucune commande VPS (#8), aucune API externe. **Note de complétude :** avec `legal/confotur`, les **4 modules-origine ✅/🟠** du mapping §3 (Sections 3 · 11/12/15 · BIM · 13) portent désormais une bannière V18 — l'alignement des surfaces de doc sur le pivot est **couvert pour les modules réels impactés** (les 9 sections 🔴 sont *nouvelles*, sans code-origine à annoter). Fichiers : `05_deliverables_mvp/legal/confotur/README.md` (bannière) + `05_deliverables_mvp/qa/audit_4big/out/quality_report.json` (byte-count régénéré) + ce journal.
---
## Session `20260811_065822` — DOC index racine · 2 documents de gouvernance V18 tracked+lisibles mais NON liés depuis le README (`V18_GO_SIGNAL_DEVELOPMENT` + `V18_ADDENDUM_SECTION_60`)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). Séquence moteur V18 toujours **intégralement bloquée** en gouvernance (D-06 approbation de l'audit = gate d'entrée · D-07 formules DCF/IRR/VAN/DSCR/LTV/LTC absentes · D-08 Master Data Model sur-ensemble strict · directive `:90-91` « NE PAS coder avant l'audit » · #6) — **aucun code moteur légitime**. L'alignement des bannières V18 des **4 modules-origine** ✅/🟠 est **clos** (session `062822`). Les 2 nouvelles directives 08-10 (`OTO_3D_STUDIO`, `COMPTE_CLIENT_COURRIELS`) restent hors périmètre repo. Tâche non-bloquée à valeur réelle recherchée dans la **complétude de l'index racine** (même classe que la currency du canal daily_reports : le README est le **point d'entrée du mandat**).
**Défaut trouvé (classe accuracy/complétude · index racine · lacune de liage).** Le bloc « Contexte · inventaire · workflow · gouvernance » du `README.md` (`:88-89`) liait la directive V18 **active** + son **audit de migration**, mais **deux autres documents de gouvernance V18** — `git`-**tracked** et **lisibles** (mode 644 root, world-readable, ≠ les 4 docs deep mode 600 #8) — n'étaient **liés depuis nulle part** dans le README :
- `V18_GO_SIGNAL_DEVELOPMENT_20260810.md` (90 l.) = **l'autorité de la séquence** (audit → validation Michel → Phase 1 → Phases 2-15 · projet pilote **P01 Coralis** · 8 interdictions) — précisément **la source du blocage gouvernance** que tout le reste de la doc V18 invoque sans jamais pointer vers elle.
- `V18_ADDENDUM_SECTION_60_DOCUMENT_INTEGRITY_20260810.md` (115 l.) = addendum Section 60 (35 sous-sections · Document ID/QR/hash SHA-256/registres/signatures loi 126-02 RD) dont la **règle 60.31** (« seul HELIOS RD SRL visible en externe ») est **l'origine** du filtre anti-fuite vocabulaire de l'audit §9 et de l'arrière-plan de l'arbitrage juridique D-01.
Un lecteur (agent ou humain) du README — le doc le plus consulté — pouvait donc lire l'audit et le registre sans jamais atteindre le GO signal qui **définit le gate bloquant**, ni l'addendum qui **contraint la publication**.
**Fix (doc worker · sourcé · zéro invention #6).** 2 sous-bullets ajoutés sous l'entrée `DIRECTIVE_V18` (même niveau/style que le sous-bullet audit), descriptions **dérivées de la lecture réelle des deux fichiers** (séquence 15 phases · P01 Coralis · 8 interdictions ← GO signal `:38-71` ; 35 sous-sections 60.1→60.35 · règle 60.31 · loi 126-02 ← addendum `:10-73`). Labels = **basenames exacts** des cibles (mémoire `link-label-target-mismatch`). Liens à profondeur racine (bare filename, comme l'entrée directive existante).
**Sûreté des gates (vérifiée AVANT édition).** `check_readme_claims` recompute des claims **très spécifiques** bornés à `**\d+/\d+ modules gated à \d+/100**`, `**\d+ suites gated**`, `## Les \d+ agents`, etc., tous liés à un `out/*.json` — mes chiffres de prose (35 sous-sections · 8 interdictions · 15 phases) proviennent des **.md de gouvernance** (non-artefacts, non recomputés) et n'épousent **aucun** de ces motifs bold-wrappés → surface non parsée. `check_docs` : les 2 liens ciblent des fichiers **existants/tracked/lisibles**, aucun fragment-anchor → résolus (`run_ci` vert le confirme). `guard_constraints` : aucun URL/usage interdit (« HELIOS RD SRL »/« Section 60 »/« loi 126-02 » ≠ termes proscrits ; aucun `github`/`stripe`/`espocrm`/`hubspot` écrit).
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé). **0 code moteur V18** (bloqué #6), **0 module de production touché**, **0 artefact reconstruit** (README non scoré par `audit_4big`, hors `05_deliverables_mvp/`), **0 gate ajouté** (#5 — occurrence isolée, surface non parsée). Aucune commande VPS (#8), aucune API externe. **Note de complétude :** les **4 documents de gouvernance V18** tracked+lisibles (directive · audit · GO signal · addendum Sec.60) sont désormais tous liés depuis l'index racine ; le point d'entrée du mandat expose la séquence bloquée et ses contraintes de publication de bout en bout. Fichiers : `README.md` (2 sous-bullets) + ce journal.
---
## Session `20260811_072823` — FIX correctness · l'audit 4Big sous-comptait un test au nom accentué (regex ASCII vs `\w` Unicode de la source sœur) — 22 → 23, teeth ajoutée
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). Séquence moteur V18 toujours **intégralement bloquée** en gouvernance (D-06 approbation de l'audit = gate d'entrée · D-07 formules DCF/IRR/VAN/DSCR/LTV/LTC absentes · D-08 Master Data Model · directive `:90-91` « NE PAS coder avant l'audit » · #6). L'alignement des bannières V18 des 4 modules-origine + le liage index sont clos (sessions précédentes). Rendements décroissants sur les micro-tâches doc → recherche d'un **défaut de correction réel** non-bloqué et à valeur produit (hunt Explore ciblé, hors périmètre V18/#8).
**Défaut trouvé (classe #6 anti-invention · DANS l'outil d'audit qualité lui-même).** `qa/audit_4big/q4lib/criteria.py:18` comptait les méthodes de test via une char-class **ASCII pure** : `_TEST_DEF_RE = re.compile(r"^\s*def (test_[A-Za-z0-9_]+)\s*\(", …)`. Or Python 3 (PEP 3131) autorise les identifiants **Unicode** : le test réel **`test_traçabilite_source`** (le `ç`) existe à `publiciste/tests/test_publiciste.py:99`, est **valide et exécuté** par unittest (« Ran 23 tests »). La regex ASCII le **loupait** → `crit_tests` publiait l'évidence fausse **« 22 méthodes test_* »** dans `quality_report.json` (réel = **23**). Le critère TESTS reste PASS (seuil 8) et le score inchangé (100) — mais **le fait publié était faux**, en violation du cœur anti-invention #6 de l'audit (« une note est recomputée à partir de faits vérifiables », `criteria.py:1-7`).
**Preuve que c'est un bug (pas by-design) — la source SŒUR le compte déjà correctement.** `qa/regression/reglib/discovery.py:23` compte le MÊME concept avec `_TEST_METHOD_RE = re.compile(r"^\s*def\s+(test_\w+)\s*\(")` — **`\w` est Unicode-aware par défaut en Python 3** → il compte publiciste = **23** (attesté dans `regression_plan.json`, byte-gaté, autorité de la colonne « Tests » des fiches via `count_tests`). Les deux outils QA **divergeaient d'un** sur publiciste (23 vs 22) ; `criteria.py` était le mauvais. Blast-radius vérifié : **un seul** nom de test non-ASCII dans tout le dépôt (scan AST des 24 suites) → publiciste seul impacté ; le « 22 méthodes mobile » du `daily_reports/2026-08-03` est un module distinct (coïncidence), non touché.
**Fix (chirurgical · single-source · aligné sur la sœur).** `criteria.py:18` : char-class ASCII `[A-Za-z0-9_]` → `\w` (Unicode), forme identique à `reglib.discovery`. **+ teeth** : nouveau test `test_tests_counts_non_ascii_method_names` dans `qa/audit_4big/tests/test_audit_4big.py` (fixture ASCII + `test_traçabilite_source`, exige « 2 méthodes ») — **prouvé mordant** : sur l'ancienne regex ASCII il compte 1 → assertion échoue ; sur `\w` → 2 → passe. La fixture existante du suite étant en ASCII, aucune régression sur `test_tests_counts_methods_and_thresholds` (« 5 méthodes » inchangé).
**Cascade d'artefacts régénérée (mémoire `artifact-reproducibility-gate` + `audit4big-rebuild-after-doc-edits`).** (1) `quality_report.json` régénéré **en dernier** → seule variation = évidence publiciste TESTS « 22 → 23 méthodes » ; verdict **PASS · 24/24 · min 100** inchangé. (2) L'ajout du test change le décompte de la matrice : `regression_gen.py build` (plan) **puis**`run` (matrice live) → `regression_run.json`**625 → 626 exécutés · 608 → 609 passés · 17 skippés · 0 échec** (le nouveau test lui-même + audit_4big se score lui-même). (3) Surfaces de prose re-dérivées : fiche `03_agents/qa/AGENT.md` (colonne audit_4big **34 → 35** ← `count_tests` ; agrégat **625/608 → 626/609**) — la première flaggée RED par `check_readme_claims`, la seconde par le même gate ; + `03_agents/erpnext_backend/AGENT.md:40` (claim live **625 → 626**, sourcé `regression_run.json`). Le `daily_reports/2026-08-11` (snapshot signé HEAD `18d8763`) et les logs sont des instantanés datés → laissés (mémoire `two-logging-channels`).
**Pourquoi pas un nouveau gate (#5).** La divergence est déjà **structurellement gatée** : `check_readme_claims` a **immédiatement rougi** (fiche QA vs `count_tests`) dès que la matrice a bougé, et la teeth ajoutée verrouille le comportement de `criteria.py`. Ajouter un gate « criteria.py == discovery.py » serait redondant — la réconciliation croisée existante mord déjà. Occurrence isolée (1 nom non-ASCII), corrigée à la source unique.
## Session `20260811_075824` — CANAL STAKEHOLDER · actualisation du rapport quotidien `2026-08-11` (périmé dans la journée : `18d8763` → `ac595d0`, 9 commits, dont le fix de correction `audit_4big`)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). Séquence moteur V18 toujours **intégralement bloquée** en gouvernance (D-06 approbation de l'audit = gate d'entrée · D-07 formules DCF/IRR/VAN/DSCR/LTV/LTC absentes · D-08 Master Data Model sur-ensemble strict · directive `:90-91` « NE PAS coder avant l'audit » · #6) — **aucun code moteur légitime**. L'alignement des bannières V18 des 4 modules-origine, le liage index racine et le fix `audit_4big` sont clos. Les 2 nouvelles directives 08-10 (`OTO_3D_STUDIO`, `COMPTE_CLIENT_COURRIELS`) restent hors périmètre repo.
**Chasse au bug menée d'abord (Explore very-thorough sur `*lib/` + `*_gen.py`).** Recherche d'un défaut de correction de calibre équivalent au fix regex ASCII de la session précédente (char-classes ASCII vs `\w`, off-by-one, comparateurs de seuil, dedup). **Rien de solide** — le seul candidat (`publiciste/lib/parser.py:433``P0[1-9]`) est **correct** (9 projets P01-P09 par CLAUDE.md §Projets, pas un défaut de scaling). J'ai refusé de fabriquer un faux positif (#6).
**Tâche prioritaire non-bloquée retenue (classe `two-logging-channels`).** Le **canal stakeholder**`daily_reports/2026-08-11.md` était figé au HEAD `18d8763` (émis session `032800`) et citait régression **625/608** — mais **9 commits `[DTP-Worker]`** ont depuis porté l'arbre à `ac595d0`**le même jour**, dont l'**événement produit le plus notable** : le fix de correction `audit_4big` (`ac595d0`) qui a corrigé un vrai bug dans l'outil d'audit lui-même et porté la matrice à **626/609**. Le canal roadmap — celui qui met l'état dans le champ de vision de Michel — était donc **périmé dans la journée** de 9 commits et d'un chiffre live faux. Actualiser = **non-bloqué, sans invention, doc worker**, et exactement la fonction du canal (snapshot stakeholder à ne pas laisser lapser · chaque figure sourcée d'un artefact commité).
**Fait (une entrée/jour, rendue exacte — pas un doublon de rapport).** (a) En-tête : `HEAD attesté``18d8763` → **`ac595d0`** + mention « actualisé `20260811_075824` » et note des 9 commits suivants. (b) Nouvelle section **« Jalons worker suivants (`18d8763` → `ac595d0`) »** : table des 6 jalons `[DTP-Worker]` (exactitude audit V18 · 4 bannières statut V18 · liage index racine · **fix correctness `audit_4big`**), chacun avec commit + nature + opposabilité au merge, + encart « événement produit le plus notable ». (c) Table « État courant » : HEAD `18d8763` → `ac595d0`, régression **625→626 / 608→609**, lecture stakeholder **609 passés + 17 skippés** ; paragraphe des skips re-dérivé (deux évolutions 624→625 puis 625→626, cause de chacune sourcée). La section « Portée / anti-churn (session `032800`) » **laissée telle quelle** (récit exact de cette session-là ; l'actualisation est transparente via l'en-tête + la nouvelle section — convention `two-logging-channels` : ne pas réécrire l'historique, actualiser en ajoutant).
**Honnêteté de sourçage (tous chiffres re-lus ce jour d'artefacts commités · #6).** HEAD `ac595d0` = `git rev-parse` · régression **626/609/17/0** = `regression_run.json totals` (lu ce jour) · audit 4Big **PASS · 24 modules · min 100 · bijection 24↔24 `ok`** = `quality_report.json` (inchangé) · les 9 hashes de commit = `git log 18d8763..HEAD`. Aucune figure figée.
**Sûreté des gates (vérifiée AVANT édition).** `check_readme_claims`**exclut**`daily_reports/` de son scan (`:8239``not f.startswith(f"{D}/daily_reports/")`) → chiffres non parsés. `check_docs` link-checke les `daily_reports/` mais la nouvelle section n'ajoute **aucun lien relatif** (uniquement des hashes de commit et chemins en code-spans, non link-checkés). `guard_constraints` : aucun URL/usage interdit (DCF/IRR/DSCR/LTV/LTC ≠ termes proscrits).
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé). **0 code moteur V18** (bloqué #6), **0 module de production touché**, **0 artefact reconstruit** (rapport actualisé depuis les artefacts, pas l'inverse), **0 gate ajouté** (#5). Aucune commande VPS (#8), aucune API externe. Fichiers : `05_deliverables_mvp/daily_reports/2026-08-11.md` (actualisation) + ce journal.
---
## Session `20260811_082825` — HARDENING correctness · le critère CLI de l'audit 4Big loupait les gardes `__main__` simple-quotées/Yoda (détection figée sur une graphie · même classe que le fix ASCII de `ac595d0`) — teeth ajoutée
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). Séquence moteur V18 toujours **intégralement bloquée** en gouvernance (D-06 approbation de l'audit = gate d'entrée · D-07 formules DCF/IRR/VAN/DSCR/LTV/LTC absentes · D-08 Master Data Model · directive `:90-91` « NE PAS coder avant l'audit » · #6) — **aucun code moteur légitime**. Les bannières V18 des 4 modules-origine, le liage index et le fix `audit_4big` (session `072823`) sont clos. Les 2 nouvelles directives 08-10 (`OTO_3D_STUDIO`, `COMPTE_CLIENT_COURRIELS`) restent hors périmètre repo.
**Vérif d'abord : le fix ASCII (`ac595d0`) a-t-il un rayon de blast CODE complet ?** La session `072823` avait vérifié le rayon **données** (« un seul nom de test non-ASCII dans le dépôt → publiciste seul »), mais **pas** le rayon **code** (d'autres regex partagent-elles le même défaut char-class-ASCII-sur-contenu-accentué ?). Sweep mené : (a) toutes les regex Python génératrices/`*lib/` (hors `/out/`, `/tests/`) → **2 compteurs de test** seulement, **tous deux `\w`** (`criteria.py:18` + `reglib/discovery.py:23`), et `check_readme_claims`**réutilise**`discovery.count_tests` (mono-source, `:663`) ; (b) `registry.py:30``_JOB_RE``[A-Za-z0-9_-]` = **domaine ASCII** (noms de jobs YAML, jamais accentués) — non-défaut ; (c) toutes les char-class ASCII des **gates shell** (`ci/*.sh`) → **toutes sur tokens ASCII-par-domaine** (slugs de rôles/portails kebab-case · lettres e-NCF · abréviations critères en CAPS · IDs d'invariants) — **aucune** ne scanne de la prose accentée. **Classe ASCII-vs-Unicode CLOSE au niveau code** (extension du check données de `072823`). Figures de régression en prose également **à jour** (aucun `624`/`625`/`607`/`608` stale hors snapshots datés). Le candidat `crit_cli` quote-style (littéral `"__main__"` figé) : **all 50 gardes du dépôt sont double-quotées** → aucun mis-scorage courant.
**Défaut trouvé (classe #6 · robustesse de détection de l'outil d'audit lui-même · MÊME classe que le fix ASCII).** `q4lib/criteria.py:78` détectait l'entrypoint CLI par un **littéral figé** : `'__name__ == "__main__"' in src` — assume **une seule graphie**. Or `if __name__ == '__main__':` (**simple quote**), `if "__main__" == __name__:` (**« Yoda »**, opérandes inversés) et `if __name__=='__main__':` (**sans espaces**) sont tous du **Python valide** = un entrypoint réel. Le littéral les **loupe silencieusement** → le critère CLI échouerait sur un module sain (**−20 pts**, chute possible < 95), en violation du cœur anti-invention #6 de l'audit (« une note est recomputée à partir de faits vérifiables ») : le critère **sous-spécifie** ce qu'est un entrypoint valide. Exactement la même racine que le fix ASCII (`ac595d0`) : *une détection assume une forme syntaxique là où le langage en admet plusieurs.*
**Pourquoi c'est légitime et pas de la sur-défense (#5).** Distinction du « ne pas coder défensivement des cas non-occurrents » : ici ce n'est **pas un gate** ajouté pour un cas d'entrée improbable d'un module produit — c'est la **correction de la logique de détection d'un critère existant** de l'**outil d'enforcement #6** lui-même, dont le contrat est d'évaluer correctement **n'importe quel Python valide**. La robustesse aux deux styles de guillemets est **dans le contrat** de l'outil, pas de l'ornement. Précédent direct : le fix ASCII a été accepté sur des bases identiques (regex ne matchant pas des identifiants Python valides).
**Fix (chirurgical · regex de forme, pas de graphie · teeth).** `criteria.py` : nouveau `_MAIN_GUARD_RE = re.compile(r"__name__\s*==\s*['\"]__main__['\"]|['\"]__main__['\"]\s*==\s*__name__")` (deux styles de guillemets · ordre inversé · espacement libre) ; `crit_cli` passe de `'__name__ == "__main__"' in src` à `_MAIN_GUARD_RE.search(src)`. **+ teeth** : `test_cli_detects_single_quoted_and_yoda_guards` (3 fixtures : simple-quote · Yoda · sans-espaces) — **prouvé mordant** : sur l'ancien littéral les 3 rendent `False` (garde loupée) ; sur la regex les 3 rendent `True`. Aucun risque de faux positif nouveau (le motif exige la séquence complète `__name__ == "__main__"`, comme le littéral).
**Sortie inchangée (robustesse pure · #6 auto-vérifié).** Les 50 gardes du dépôt étant double-quotées et **toujours matchées** par la nouvelle regex, `crit_cli` rend un résultat **identique pour les 24 modules** → **`quality_report.json` byte-identique** (correctement NON régénéré : `audit_4big`**s'auto-exclut** de son propre scoring — les modules qa scorés sont `qa-audit-5d`/`qa-acceptance`/`qa-regression`, pas `audit_4big` ; même patron d'auto-exclusion que la régression). Le fix ne change **aucun score** — c'est une garantie de robustesse, pas une correction de note.
**Cascade d'artefacts (uniquement la matrice de régression · mémoire `artifact-reproducibility-gate`).** L'ajout du test augmente le décompte de la suite `audit_4big` : `regression_gen.py build` (plan) **puis**`run` (matrice) → `regression_run.json`**626 → 627 exécutés · 609 → 610 passés · 17 skippés · 0 échec** (le nouveau test). Surfaces de prose gatées re-dérivées (flaggées RED par `check_readme_claims`, corrigées) : fiche `03_agents/qa/AGENT.md` (colonne `audit_4big`**35 → 36** ← `count_tests` ; agrégat **626/609 → 627/610**) + `03_agents/erpnext_backend/AGENT.md:40` (claim live **626 → 627**). Le `daily_reports/2026-08-11` (snapshot signé) et les logs sont des instantanés datés → laissés (`two-logging-channels`).
**Pourquoi pas un nouveau gate (#5).** La divergence de compte est déjà **structurellement gatée** (`check_readme_claims` a immédiatement rougi la fiche QA + le compte `audit_4big`) et la teeth verrouille le comportement de `crit_cli`. Occurrence latente (0 garde simple-quotée aujourd'hui) mais correction de contrat de l'outil, pas un cas d'entrée produit → pas de gate additionnel.
## Session `20260811_085834` — HARDENING correctness · l'alignement du compteur de tests `audit_4big`↔`discovery` (fix ASCII `ac595d0`) était INCOMPLET : `def ` (un espace figé) ≠ `def\s+` de la sœur — teeth ajoutée, classe désormais close
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). Séquence moteur V18 toujours **intégralement bloquée** en gouvernance (D-06 approbation de l'audit = gate d'entrée · D-07 formules DCF/IRR/VAN/DSCR/LTV/LTC absentes · D-08 Master Data Model sur-ensemble strict · directive `:90-91` « NE PAS coder avant l'audit » · #6) — **aucun code moteur légitime**. Les bannières V18 des 4 modules-origine, le liage index et les 2 fixes `audit_4big` (regex ASCII `ac595d0` · garde `__main__``fd99362`) sont clos. Les 2 directives 08-10 (`OTO_3D_STUDIO`, `COMPTE_CLIENT_COURRIELS`) restent hors périmètre repo.
**Défaut trouvé (classe #6 · MÊME racine que les 2 fixes précédents : « détecter la forme, pas une graphie » · + il COMPLÈTE un fix laissé à moitié).** Le fix ASCII `ac595d0` (session `072823`) avait pour but explicite d'**aligner** le compteur de méthodes de test d'`audit_4big` (`q4lib/criteria.py:_TEST_DEF_RE`) sur celui de la source sœur `reglib/discovery.py:_TEST_METHOD_RE` — autorité **byte-gatée** de la colonne « Tests » des fiches (réutilisée par `check_readme_claims` via `discovery.count_tests`). Il a aligné le **char-class** (`[A-Za-z0-9_]` → `\w`, pour les noms accentués), mais a laissé **une seconde divergence intacte** sur le **séparateur `def`** :
Or `def test_x(` (deux espaces) et `def\ttest_x(` (tabulation) sont du **Python valide** (PEP 8 recommande un espace, le langage n'en impose pas). Le littéral `def ` les **loupe** → `crit_tests` sous-compterait, en **désaccord d'un ou plusieurs** avec la sœur — exactement le type de dérive d'évidence #6 que le fix ASCII prétendait clore. Les **deux compteurs du MÊME concept** doivent coïncider sur **toute graphie valide**, pas seulement les identifiants Unicode.
**Occurrence (comme la garde `__main__` de `fd99362`) : latente, 0 aujourd'hui.** `grep -rnE '^\s*def( +|\t)test_'` sur tous les `test_*.py` = **vide** (tous en un-espace PEP 8) → aucun mis-comptage courant, `quality_report.json`**byte-identique**. Mais c'est la **correction du contrat de détection de l'outil d'enforcement #6** (compter correctement n'importe quel Python valide + coïncider avec la sœur), pas une sur-défense d'un cas d'entrée produit improbable — même base d'acceptation que `fd99362`. Divergence sœur de recensement de fichiers vérifiée aussi : `criteria` recurse (`os.walk`) là où `discovery` liste à plat (`os.listdir`) — **aucun `tests/*/test_*.py` imbriqué dans le dépôt** (`find` vide) → non-occurrent, axe distinct (découverte de fichiers, pas détection de méthode), **laissé** (discovery = autorité du gate ; ne pas muter son comportement pour un cas nul).
**Fix (chirurgical · forme, pas graphie · single-source-de-forme · teeth).** `criteria.py:18` : `def ` → `def\s+`, forme **strictement identique** à `discovery._TEST_METHOD_RE` (commentaire mis à jour : les deux compteurs doivent coïncider). **+ teeth** : `test_tests_counts_non_single_space_def_separator` (fixture 3 méthodes : un-espace · deux-espaces · tab) — **prouvé mordant** : ancien regex `def ` compte **1** des 3 (loupe deux-espaces + tab) ; nouveau `def\s+` compte **3**. La fixture ASCII existante (`test_tests_counts_methods_and_thresholds` « 5 méthodes ») reste en un-espace → aucune régression.
**Sortie inchangée (robustesse pure · #6 auto-vérifié).** Aucun module du dépôt n'ayant de `def` multi-espace/tab, `crit_tests` rend une évidence **identique pour les 24 modules** → `quality_report.json`**byte-identique** (rebuild `audit_4big_gen.py build` → `git diff --stat`**vide** ; `audit_4big`**s'auto-exclut** de son propre scoring — les modules qa scorés sont `qa-audit-5d`/`qa-acceptance`/`qa-regression`, pas lui). Le fix ne change **aucun score**.
**Cascade d'artefacts (uniquement la matrice de régression · mémoire `artifact-reproducibility-gate`).** L'ajout du test augmente le décompte de la suite `audit_4big` : `regression_gen.py build` (plan) **puis**`run` (matrice) → `regression_run.json`**627 → 628 exécutés · 610 → 611 passés · 17 skippés · 0 échec**. Surfaces de prose gatées re-dérivées (flaggées RED par `check_readme_claims`, corrigées) : fiche `03_agents/qa/AGENT.md` (colonne `audit_4big`**36 → 37** ← `count_tests` ; agrégat **627/610 → 628/611**) + `03_agents/erpnext_backend/AGENT.md:40` (claim live **627 → 628**). Le `daily_reports/2026-08-11` (snapshot signé) et les logs sont des instantanés datés → laissés (`two-logging-channels`).
**Classe désormais close (les 2 axes de la coïncidence des compteurs).** Après `072823` (char-class) + cette session (séparateur), `criteria._TEST_DEF_RE` et `discovery._TEST_METHOD_RE` sont **forme-identiques** ; l'axe fichiers (recursion) est non-occurrent et documenté laissé. **Pas de nouveau gate (#5)** : la divergence de compte est déjà **structurellement gatée** (`check_readme_claims` a immédiatement rougi la fiche QA dès que la matrice a bougé) et la teeth verrouille le comportement. Occurrence latente → correction de contrat de l'outil, pas de gate additionnel.
## Session `20260811_092842` — CANAL STAKEHOLDER · actualisation daily report au HEAD courant `35de616` (l'arc de fixes `audit_4big` a fait bouger la régression deux fois de plus depuis l'épinglage `ac595d0`)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). Séquence moteur V18 toujours **intégralement bloquée** en gouvernance (D-06 approbation de l'audit = gate d'entrée · D-07 formules DCF/IRR/VAN/DSCR/LTV/LTC absentes · D-08 Master Data Model · directive `:90-91` « NE PAS coder avant l'audit » · #6) → **aucun code moteur légitime**. Les 3 fixes `audit_4big` (char-class `ac595d0` · garde `__main__``fd99362` · séparateur `def``35de616`), les 4 bannières V18 et le liage index sont clos.
**Vérif d'abord — pas de dérive de chiffres avant de choisir la tâche.** Artefact autoritaire `regression_run.json` (`totals`) = **628 ran · 611 passés · 17 skippés · 0 échec** (PASS). Les surfaces de prose **gatées** qui figent l'agrégat sont à jour : fiche `03_agents/qa/AGENT.md:35` (**628/611**) + `03_agents/erpnext_backend/AGENT.md:40` (**628 · 24 suites**) — concordent avec l'artefact, aucune dérive (`check_readme_claims` vert). Le README racine ne fige aucun compte de tests (renvoie à l'artefact byte-gaté). **Classe de dérive numérique : nulle.**
**Défaut trouvé (canal stakeholder en retard sur le HEAD · pas un bug code).** Le `daily_reports/2026-08-11.md` était **épinglé à HEAD `ac595d0`** (régression **626/609**), mais **deux commits worker de plus** ont atterri le **même jour** après sa dernière actualisation (`9e02c38`) : `fd99362` (garde `__main__` → 627/610) puis `35de616` (séparateur `def` → 628/611). Le canal stakeholder — dont le contrat (`two-logging-channels`) est de porter le snapshot roadmap au **point courant du jour** avec chaque chiffre sourcé d'un artefact commité — accusait donc **2 commits + 2 pas de régression** de retard. C'est exactement la tâche de currency que le worker maintient (précédents `8ec11de`, `9e02c38` le même jour).
- §« Jalons worker suivants » : titre `→ ac595d0` → **`→ 35de616`**, « six → **huit jalons** » ; **2 lignes ajoutées** (`fd99362` garde `__main__`−20 pts CLI évités · `35de616` séparateur `def` complète le fix ASCII), chacune avec sa cascade de régression re-dérivée.
- L'encart « événement le plus notable » recadré du seul `ac595d0` vers l'**arc des 3 fixes de même racine** (« détecter la forme, pas une graphie »), **classe close**.
- §État courant : HEAD `35de616` · matrice **626/609 → 628/611** ; prose de progression « deux → **quatre fois** depuis 08-06 » avec les 3 pas du jour explicités (625→626→627→628 / 608→609→610→611).
- §Portée : ligne **Currency `092842`** (re-épinglage + provenance des chiffres).
**Pourquoi c'est légitime et non du churn (#5).** Ce n'est pas un re-write d'un snapshot daté figé (ceux-là restent, `two-logging-channels`) : c'est la **mise au point courant du jour même** du canal stakeholder, dont le `9e02c38` précédent avait fait exactement autant (18d8763→ac595d0). Chaque chiffre injecté est **re-dérivé** de `regression_run.json` (`totals` = 628/611), aucun figé. **0 fichier de production · 0 artefact reconstruit · 0 gate ajouté · 0 code moteur V18 · 0 commande VPS · 0 API externe.**
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé · le daily report tracked passe `check_docs` + `guard_constraints`). Fichiers : `05_deliverables_mvp/daily_reports/2026-08-11.md` + ce journal.
---
## Session `20260811_095844` — COUVERTURE produit · teeth `publiciste` sur les prix à ESPACE INSÉCABLE (U+00A0) · classe d'entrée réelle FR/ES 100 % non testée jusqu'ici
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte · `regression_run.json totals` = 628/611/17). Séquence moteur V18 toujours **intégralement bloquée** en gouvernance (D-06 approbation de l'audit = gate d'entrée · D-07 formules DCF/IRR/VAN/DSCR/LTV/LTC absentes · D-08 Master Data Model · directive `:90-91` « NE PAS coder avant l'audit » · #6) — **aucun code moteur légitime**. Les 2 directives 08-10 (`OTO_3D_STUDIO`, `COMPTE_CLIENT_COURRIELS`) restent hors périmètre repo. Les micro-tâches doc + l'arc `audit_4big` sont clos → recherche d'une tâche à **valeur produit réelle**.
**Chasse au bug d'abord (2 passes indépendantes) → aucun défaut, refus de fabriquer (#6).** Explore very-thorough sur les `*_gen.py`/`*lib/`**hors**`audit_4big`/`regression` (déjà balayés) : classes visées = char-class ASCII-vs-Unicode, off-by-one, comparateurs de seuil, sets à doublons, arithmétique dérivée, collisions de clés. **Rien de solide.** Les 2 candidats remontés étaient des **non-défauts** : (a) `publiciste/lib/parser.py:77``.replace(" ", " ")` — l'Explore l'a lu « no-op » ; **hexdump** montre `c2 a0` = le 1er argument est un **NBSP** → c'est une **normalisation NBSP→espace réelle**, pas un no-op (l'Explore s'est fait piéger par des glyphes identiques à l'œil — exactement le risque byte-invisible) ; (b) `seo_gen.py` global-keyword digit-check = latent masqué par invariant. **Aucun n'est un bug** → non touchés.
**Vrai locus trouvé = un TROU DE COUVERTURE, pas un bug.** `parse_number`/`parse_price` de `publiciste` sont **conçus** pour l'**espace insécable U+00A0** comme séparateur de milliers — la graphie que Word/InDesign FR/ES insèrent dans les prix réels des `data_room` (« DOP 8 850 000 » avec NBSP). La capture repose sur la char-class **Unicode-whitespace** de `parser.py:74` (`[0-9\s.,` + un **NBSP littéral**`c2 a0` redondant avec `\s`). Or **aucun des 23 tests** n'exerçait le NBSP : `test_prix_formats:39` (« DOP 8 850 000 ») utilise des **espaces ASCII 0x20** (`hexdump` confirmé), pas des NBSP. La classe d'entrée réelle la plus caractéristique du parser était **100 % non testée** — et c'est précisément ce trou qui a permis à l'Explore de mésinterpréter la ligne 77 comme du code mort.
**Preuve end-to-end + bite ACCURATE (aucune affirmation non vérifiée · #6).** (1) `parse_price("DOP 8850000")` = **8850000.0** ✓, `parse_number("1250")` = **1250.0** ✓ (exécuté). (2) Condition de bite **déterminée empiriquement, pas devinée** : la char-class porte `\s`**et** un NBSP littéral → retirer **l'un** est inoffensif (redondance belt-and-suspenders) ; le bite ne survient que si l'on **réduit la classe à de l'ASCII pur**`[0-9.,]` (retrait de TOUTE prise en charge whitespace-Unicode) → le test échoue alors `8.0 != 8850000.0` (prouvé : cassé RED, restauré VERT). Le commentaire du test a été **corrigé** d'une 1re rédaction fausse (« retirer `\s` mord ») vers la condition réelle — honnêteté de claim (#6). Même racine que l'arc `audit_4big` : « détecter la forme, pas une graphie ».
**Hygiène byte-invisible (leçon appliquée au test lui-même).** 1re rédaction du test contenait des **NBSP littéraux** (`nbsp = " "`) — je réintroduisais l'octet invisible que je dénonce. Réécrit en **escape explicite ``** (`nb = ""`) → **0 octet invisible** dans le fichier de test (vérifié `grep -c`), intention 100 % lisible. La char-class de **production** (ligne 74), elle, est **laissée telle quelle** : son NBSP littéral est **redondant mais correct** (`\s` le couvre) ; l'éditer serait du churn cosmétique sur un non-défaut (#5, `verify-non-defects`) avec risque de dérive d'artefact pour zéro changement de comportement.
**Fix = 1 test teeth `test_prix_espace_insecable_milliers`** (classe `TestNumberParsing`) : `parse_price`/`parse_number`/`parse_int` sur NBSP (DOP/USD milliers + décimale). Distinct de l'arc récent : ce n'est **pas** un test de l'outil d'audit mais de la **couverture d'un module produit** sur une **entrée réelle** — valeur produit directe.
**Cascade d'artefacts régénérée (mémoires `artifact-reproducibility-gate` + `audit4big-rebuild-after-doc-edits`).** (1) `audit_4big_gen.py build` → `quality_report.json` : évidence publiciste TESTS **« 23 → 24 méthodes »** ; **verdict PASS · 24/24 · min 100 inchangé** (aucun score bougé — le seul delta est le compte). (2) `regression_gen.py build` puis `run` → `regression_run.json`**628 → 629 exécutés · 611 → 612 passés · 17 skippés · 0 échec** (publiciste suite ran 23→24 / passed 21→22 — les 2 « non-incréments » = ses tests oracle jsonschema neutralisés par `python -S`, cf. `regression-baseline-17-skips-by-design`). (3) Surfaces de prose gatées re-dérivées (flaggées RED par `check_readme_claims`, corrigées) : fiche `03_agents/publiciste/AGENT.md:95` (cellule Tests **23 → 24** ← `count_tests`) + agrégat `03_agents/qa/AGENT.md:35` (**628/611 → 629/612**) + `03_agents/erpnext_backend/AGENT.md:40` (claim live **628 → 629**). Le `daily_reports/2026-08-11` (snapshot signé) et les logs = instantanés datés → laissés (`two-logging-channels`).
**Pourquoi pas un nouveau gate (#5).** La divergence de compte est déjà **structurellement gatée** (`check_readme_claims` a immédiatement rougi la fiche publiciste + l'agrégat QA dès le rebuild) et la teeth verrouille le comportement NBSP. Trou de couverture isolé, comblé à la source → pas de gate additionnel.
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** rétabli après régénération. **0 code moteur V18** (bloqué #6), **0 module de production touché** (seul un test ajouté · la char-class de prod laissée intacte), **0 gate ajouté** (#5), **0 commande VPS** (#8), **0 API externe**. Fichiers : `05_deliverables_mvp/publiciste/tests/test_publiciste.py` (teeth) + `qa/audit_4big/out/quality_report.json` + `qa/regression/out/{regression_plan,regression_run,MANIFEST}.json` + `03_agents/{publiciste,qa,erpnext_backend}/AGENT.md` + ce journal.
# Activity Log · 2026-08-12 · Claude Code DTP Worker
## Session `20260812_153203` — MÉTA-MAINTENANCE · réduction de l'**index de mémoire agent** au-dessus de sa limite de taille (cause-racine du churn de session), plutôt qu'un énième sweep « CLEAN » ou une re-currency du rapport (le motif que les ~10 sessions précédentes du jour ont elles-mêmes signalé)
**Diagnostic — la vraie cause n'est pas un défaut de code, c'est un défaut d'outillage.** Le journal des ~10 dernières sessions du jour est saturé de deux motifs : (a) des *sweeps de vérification* « CLEAN, teeth-prouvé, NOT a gate » sur des axes de plus en plus marginaux, et (b) des *re-currency* du rapport stakeholder qui, en épinglant un HEAD, se re-périmaient à chaque commit (boucle enfin brisée en `150201`). Ces deux motifs sont la **conséquence** d'une saturation : la mémoire agent avait déjà consigné, pour la plupart de ces axes, « déjà balayé · by-design · ne pas re-flag » — mais l'**index de mémoire ne se chargeait que partiellement**. Le harnais l'a signalé explicitement ce jour : *« MEMORY.md is 29KB (limit: 24.4KB) — index entries are too long. Only part of it was loaded. »* Un index tronqué ⟹ des insights « NOT a gate / by-design / don't re-flag » invisibles ⟹ re-litige du même terrain ⟹ churn. **Corriger l'outil, pas surveiller le symptôme** (même principe structurel que le dé-épinglage du HEAD en `150201`).
**Action — compaction de l'index de mémoire (`memory/MEMORY.md`, hors dépôt, maintenance d'outillage).** L'index avait accumulé le **corps entier** de chaque mémoire dans sa ligne (jusqu'à **1040 caractères**/ligne ; guide = « une ligne sous ~200 »). Le détail vit déjà dans les fichiers-sujets `.md` (un par entrée) ; l'index ne doit porter qu'un **hook** de pertinence. Chaque ligne réécrite en hook concis, en **préservant le distinguo anti-churn** de chaque note (« NOT a gate », « by-design », « don't re-flag », le piège-clé). Résultat vérifié :
- Taille **30 117 → 17 264 octets** (sous la limite 24.4 Ko, avec marge → l'index se charge désormais **en entier**).
- **88 entrées d'index ↔ 88 fichiers-sujets** = **bijection prouvée** (script : chaque `.md` du dossier est référencé, chaque lien de l'index pointe un fichier existant). Une entrée initialement perdue à la réécriture (`link-label-target-mismatch`) **détectée par ce contrôle de bijection et restaurée** avant clôture — aucune mémoire perdue.
- Ligne la plus longue **1040 → 252** caractères.
**Pourquoi PAS un sweep / une currency / un fix / un gate.** (a) Aucun défaut de code : les sweeps du jour ont épuisé les axes réels ; en ré-ouvrir un serait le churn même que ce diagnostic identifie. (b) Aucune currency due : rapport dé-épinglé, registre à jour. (c) Aucun fix : V18 bloqué D-06 (#6), 0 défaut résiduel. (d) Aucun gate (#5) : l'index de mémoire est **hors dépôt** (outillage agent, non gaté par nature) ; le corriger est de la maintenance, pas une invariant de produit.
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé — aucun fichier de dépôt de production/gate touché). **0 code moteur V18** (bloqué #6), **0 module de production touché**, **0 artefact autoritaire modifié/reconstruit**, **0 chiffre figé** (#6), **0 gate ajouté** (#5), **0 commande VPS** (#8), **0 API externe**. Fichiers de **dépôt** : ce journal seul. Maintenance **hors dépôt** (mémoire agent) : `memory/MEMORY.md` compacté 30→17 Ko, bijection 88/88 restaurée.
## Session `20260812_150201` — STRUCTUREL · fin de la boucle de re-currency du rapport stakeholder — **dé-épinglage total du HEAD** dans le document (la session précédente n'avait ôté le hash figé que du *sous-titre* de la table roulante, en le **laissant** dans l'en-tête du document `HEAD attesté: 4a13557`) → la cause du churn est supprimée, pas surveillée
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json totals` (artefact commité · #6) = **635 exécutés · 618 passés · 17 skippés · 0 échec** (PASS). `quality_report.json` = **PASS · 24 modules · min 100/100 · bijection 24↔24**. Séquence moteur V18 **intégralement bloquée** en gouvernance (D-06 = gate d'entrée · « NE PAS coder avant l'audit » · #6) → **aucun code moteur légitime** ; arbitrages D-01→D-08 en attente Michel (`OPEN_DECISIONS_REGISTER.md`).
**Locus = la cause récurrente du churn de session, pas un énième sweep.** Le journal des ~8 dernières sessions du jour montre une **boucle** : chaque commit worker déplace le HEAD ⟹ l'en-tête du `daily_report` qui **atteste** « HEAD attesté `<hash>` » périme ⟹ la session suivante « réconcilie la currency » ⟹ nouveau commit ⟹ HEAD re-périmé, *ad infinitum*. La session `143156` a **diagnostiqué** ce mécanisme et converti la §« Jalons de vérification » en **table roulante** — mais n'a ôté le hash figé que du **sous-titre de section**, en **laissant** l'épinglage `**HEAD attesté** : \`4a13557\`` dans l'**en-tête du document** (lignes 9-13) + « portée au HEAD courant `4a13557` » dans la ligne `Session` + « portant l'arbre au HEAD courant `4a13557` » dans la trajectoire §État courant. Le fix était donc **partiel** : la cause survivait dans 4 emplacements, et *mon propre commit d'aujourd'hui* aurait re-périmé l'en-tête → churn perpétué.
**Correction structurelle (4 emplacements de HEAD figé neutralisés · document éditorial hors gate).**
- **En-tête `Session`/`HEAD attesté`** (l.3-13) — remplacé le bloc `**HEAD attesté** : \`4a13557\`` + la trace de hashes par un **« Point d'attestation »** explicite : *le rapport n'épingle volontairement AUCUN hash de HEAD* (un hash périme à chaque commit = cause structurelle de la boucle) ; le HEAD courant **se lit** (`git log -1`), il ne **se fige** pas ; ce qui **fait foi** est la **matrice** (`635/618/17 · PASS`, dérivée d'artefacts commités, stable jusqu'au prochain fix de correctness). Les hashes des §Jalons sont désormais qualifiés de **jalons historiques immuables**, pas d'attestation de HEAD courant.
- **§Contexte** (l.25-29) — « ont porté l'arbre au **HEAD courant**`ee88d36` » → passé historique « à l'**émission initiale**… avaient porté l'arbre à `ee88d36` » (framing origine, plus une prétention de HEAD courant).
- **Sous-titre §Jalons de vérification** — « → HEAD courant `4a13557` » → « depuis le 5ᵉ fix `1992ee6` · au fil de l'eau » (dé-épinglé).
- **Trajectoire §État courant** — « portant l'arbre au HEAD courant `4a13557` » → « trace dans la table roulante ci-dessus, non ré-épinglée à un hash ».
**Résultat.** Le rapport **ne peut plus périmer** au prochain commit worker : il n'affirme plus aucun HEAD mutable. La boucle de re-currency (≥ 6 sessions consommées) est **cassée à la racine**. Les hashes restants (Jalons tables + `35de616`/`ee88d36` de l'émission initiale) sont des **faits historiques** stables. Aucun chiffre inventé — matrice re-dérivée de l'artefact commité (#6).
**Pourquoi PAS un gate (#5).** Le rapport est un **document éditorial explicitement hors gate** (`ci/check_docs.sh:50``continue` sur `daily_reports/*` · `ci/check_readme_claims.sh:8239` exclut le préfixe). Gater de la prose stakeholder serait fragile et de faible valeur ; le **durcissement structurel** (aucun hash mutable à surveiller) **supprime la cause** au lieu de poser une sentinelle sur elle — sœur exacte de la doctrine `verify-non-defects` / « supprime la cause » invoquée (à moitié) par la session précédente.
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé — 0 dépendance de gate sur le rapport, confirmé par grep sur `ci/`+`.gitea/`). Rapport **NFC-clean** (vérifié). **0 code moteur V18** (bloqué #6), **0 module de production touché**, **0 artefact autoritaire modifié/reconstruit** (matrice stable, byte-repro implicite), **0 chiffre figé** (#6), **0 gate ajouté** (#5), **0 commande VPS** (#8), **0 API externe**. Fichiers : `M 05_deliverables_mvp/daily_reports/2026-08-12.md` (4 dé-épinglages de HEAD) + ce journal.
## Session `20260812_143156` — CURRENCY · rapport stakeholder du jour porté au HEAD courant `4a13557` (le canal s'arrêtait à `fb2d3e1` tout en attestant « HEAD courant ») + §« Jalons de vérification » convertie en **table roulante** pour briser la boucle de re-figeage du HEAD à chaque commit
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json totals` (artefact commité · #6) = **635 exécutés · 618 passés · 17 skippés · 0 échec** (PASS). `quality_report.json` = **PASS · 24 modules · min 100/100 · bijection couverture 24↔24**. Séquence moteur V18 toujours **bloquée** en gouvernance (D-06 = gate d'entrée · « NE PAS coder avant l'audit » · #6) → **aucun code moteur légitime** ; arbitrages D-01→D-08 en attente Michel (`OPEN_DECISIONS_REGISTER.md`).
**Chasse au défaut d'abord → vrai locus = currency du canal stakeholder (classe `e15129b`).** Plutôt qu'un énième sweep « CLEAN sans fix » (saturation des axes déjà couverts, reconnue par les sessions précédentes), j'ai audité la **currency** du rapport `daily_reports/2026-08-12.md`. Il **atteste** en en-tête « **HEAD attesté**`fb2d3e1` » + « **au HEAD courant** », or **deux commits worker** ont suivi le même jour sans être remontés : `e15129b` (la currency `133154` elle-même) et `4a13557` (sweep FRAIS mode d'arrondi). `git log fb2d3e1..HEAD` = ces 2 commits ; `grep -c` de chacun dans le rapport = **0** avant édition (dérive confirmée). Note : `e15129b` a **créé** la §« Jalons » mais **sans s'y inscrire** (auto-omission) — d'où la boucle de churn observée : chaque commit de currency re-fige un HEAD qui périme au commit suivant.
- **§« Jalons de vérification depuis la 4ᵉ vague »** — convertie en **table roulante** : bandeau explicite « tout commit post-5ᵉ-fix est **non opposable par construction** ; la matrice ne bougera qu'au prochain fix de correctness ; cette section liste ces jalons au fil de l'eau **sans re-figer le HEAD ailleurs** » → **brise structurellement la boucle de churn** (plus de hash épinglé dans l'intro). En-tête de section `→ HEAD courant fb2d3e1` → `4a13557`.
- **2 lignes de table ajoutées** — `e15129b` (currency, non opposable) + `4a13557` (sweep FRAIS arrondi lecture-seule, journal only, aucun mis-flag, non opposable). Compte « quatre » → « **six** » jalons.
- **§« État courant »** trajectoire — « 4 commits de vérification (`…/fb2d3e1`) » → « **six** commits de vérification / currency (`…/fb2d3e1`/`e15129b`/`4a13557`) portant l'arbre au HEAD courant `4a13557` ».
**Zéro invention (#6).** Tous les chiffres re-dérivés de l'artefact commité : `regression_run.json.totals` = **635/618/17 · PASS**. La nature de chaque jalon reprend verbatim son message de commit (`git show --stat` : `e15129b` = activity_log + daily_report ; `4a13557` = activity_log **seul**, sweep sans note in-situ). Aucune décision/verdict modifié ; D-01→D-08 inchangés.
**Pourquoi PAS un gate (#5).** Dérive de currency d'un document éditorial (le rapport = *surface, ne tranche pas*), comme `daily_reports` par nature ; un gate line-number/hash sur de la prose stakeholder serait fragile (chaque commit worker le rougirait) et de faible valeur. Le durcissement **structurel** appliqué (table roulante, plus de HEAD re-figé dans l'intro) vaut mieux qu'un gate : il **supprime la cause** du churn au lieu de la surveiller.
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé — le rapport est un document, 0 dépendance de gate). Rapport **NFC-clean** (vérifié). **0 code moteur V18** (bloqué #6), **0 module de production touché**, **0 artefact autoritaire modifié/reconstruit** (matrice stable, byte-repro implicite), **0 chiffre figé** (#6), **0 gate ajouté** (#5), **0 commande VPS** (#8), **0 API externe**. Fichiers : `M 05_deliverables_mvp/daily_reports/2026-08-12.md` (4 blocs de currency + table roulante) + ce journal.
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json totals` (lu de l'artefact commité · #6) = **635 exécutés · 618 passés · 17 skippés · 0 échec** (24 suites vertes · PASS). `quality_report.json` = **PASS · 24 modules · min 100/100 · all PASS**. Séquence moteur V18 toujours **intégralement bloquée** en gouvernance (D-06 approbation audit = gate d'entrée · « NE PAS coder avant l'audit » · #6) → **aucun code moteur légitime**. Arbitrages produit D-01→D-08 en attente Michel (`OPEN_DECISIONS_REGISTER.md`). Canal stakeholder `daily_reports/2026-08-12.md`**à jour au HEAD `e15129b`** (grep-count 5 hashes 4ᵉ-vague = 10 hits) → rien à ré-émettre.
**Chasse au défaut d'abord (angle jamais balayé en ~45 sweeps : le MODE D'ARRONDI).** Aucun des sweeps antérieurs (arithmétique dérivée, date/time, parsing money/percent, déterminisme hash/locale/TZ/ordre) n'avait attaqué la question « Python `round()` est round-half-to-**even** (banquier) — un site s'appuie-t-il implicitement sur round-half-**up** ? ». Un `.5` exact y renverrait une valeur qui diffère de l'attente naïve (`round(2.5)==2`, pas 3), byte-repro-aveugle si l'échantillon figé ne tombe pas sur la bande. Recensement des `round()` de production (hors `test`, hors `_round1`) = **8 sites** sur 4 modules : `finlib/gate.py:54,64,72,96` · `genlib/scorer.py:57,81` · `lib/parser.py:101` · `audit_4big/scoring.py:8` (docstring).
**Classement des 8 sites (tous NON-DÉFAUT, teeth-prouvé) :**
- **`scorer.py:57``round(20*filled/total)`** (axe Complétude) — SEUL site `round()`-sur-ratio dont l'immunité au verdict est **non-locale**. Teeth : `total=16` champs 🔴 → la bande `.5`**EST atteignable** (filled∈{2,6,10,14} → 2.5/7.5/12.5/17.5, banquier diffère de half-up de ±1 pt). **Mais verdict-immune** : `_statut` n'émet « complete » que si `not champs_manquants` ⟹ `filled==total` ⟹ `pts1 == round(20) == 20`**exact** ; l'arrondi banquier ne s'engage donc **jamais** sur le chemin décisionnel. Reproduit end-to-end : brief complet → completude=20/statut=complete ; masterplan vidé → completude=11, 7 champs manquants, statut=en_developpement (**jamais** complete). Le seul effet de l'arrondi = ±1 pt sur le **score partiel affiché** d'un brief **incomplet** — cosmétique, aucun `spec` ne définit half-up, découplé du verdict, épinglé byte-repro. **Non-défaut.**
- **`scorer.py:81`** (axe Anti-gap prix) — même famille, immunité par **inclusion** : une cellule manquante ⟹ `champs2 ⊆ champs_manquants` non vide ⟹ verdict ≠ complete, indépendamment de l'arrondi de `pts2` (même si `pts2` pouvait remonter à 20 avec `filled/total ≥ 0.975`, la clause `not champs_manquants` bloque). **Non-défaut.**
- **`gate.py:64,72,96`** (montants USD, `round(...,2)`) — arrondi **au cent** = montant métier canonique (un USD ne se paie pas au sous-cent). L'arrondi banquier au sous-cent est immatériel : `ok = paid+1e-9 >= required` compare des valeurs déjà en cents. **Non-défaut.**
- **`gate.py:54``_round1`** (percents user-facing) — la classe « affichage arrondi ⟺ gate booléen » est **déjà FERMÉE** ce cycle (gardes `:105`/agrégat des fixes `85cd625`/`1992ee6` + notes `113134`). **Non-défaut** (déjà couvert).
- **`parser.py:101``int(round(n))`** — parse la **quantité** de typologies (entier métier : pas de demi-unité) → bande `.5` sans signification. **Non-défaut** (domaine entier).
- **`audit_4big/scoring.py:8`** — **docstring** (formule §), pas du code ; scoring réel prouvé immune par `ea8458d` (score-au-seuil = quantité UNIQUE). **Non-défaut.**
**Teeth du détecteur (prouvé mordant).** Confirmé au runtime que `round()` EST banquier (`round(2.5)=2 · round(3.5)=4 · round(0.5)=0`) — l'axe n'est pas un homme de paille ; ET que la bande `.5` est réellement atteignable sur `scorer.py:57`. Le CLEAN n'est donc pas un faux-vert : la propriété testée existe, elle est simplement **découplée du verdict** partout.
**Pourquoi PAS un fix ni un gate ni une note in-situ (#5/#6).** Aucun défaut (8/8 non-défauts). Passer à `Decimal(ROUND_HALF_UP)` serait de la sur-ingénierie sur un effet cosmétique ±1 pt inatteignable au verdict, + casserait le déterminisme byte-repro. **Pas de note in-situ non plus** (contrairement à `rate()`/`_cond_documents`) : le trigger de ces notes était un Explore ayant *mal-classé* le locus — ici **aucun mis-flag**, et sur-annoter violerait `verify-uncovered`/#5. Comme les axes de déterminisme (`run-order`/`hash-seed`/`locale-tz`), c'est une **commande replayable**, occurrence-zéro, pas une matière à gate.
**Impact d'artefact : ZÉRO.** Sweep en lecture seule (repro dans le module, aucun fichier tracké touché hors ce journal). `regression_run.json`/`quality_report.json` inchangés (635/618/17 · 24×100).
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé). **0 code moteur V18** (bloqué #6), **0 module de production touché**, **0 artefact autoritaire modifié/reconstruit**, **0 chiffre inventé** (#6 — tous lus des artefacts commités), **0 gate ajouté** (#5), **0 commande VPS** (#8), **0 API externe**. Fichiers : ce journal (+ mémoire agent `rounding-mode-bankers-sweep`, hors repo).
## Session `20260812_130149` — VÉRIF · chasse au défaut sur axe FRAIS (date/time + parsing chaîne money/percent) → 3 candidats RÉFUTÉS comme non-défauts + 1 note anti-récurrence in-situ sur `rate()` (commentaire pur · 0 impact artefact)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json totals` (lu de l'artefact commité · #6) = **635 exécutés · 618 passés · 17 skippés · 0 échec** (PASS). `quality_report.json` = **PASS · 24 modules · min 100/100**. Séquence moteur V18 toujours **intégralement bloquée** en gouvernance (D-06 approbation audit = gate d'entrée · « NE PAS coder avant l'audit » · #6) → **aucun code moteur légitime**. Arbitrages produit D-01→D-08 en attente Michel (`OPEN_DECISIONS_REGISTER.md`, citations réalignées au HEAD par `4eb615b`). Canal stakeholder `daily_reports/2026-08-12.md`**à jour** (matrice 635, 4ᵉ vague · les commits `626c39d`→`4eb615b` sont vérif/currency à 0 impact matrice) → rien à ré-émettre.
**Chasse au défaut d'abord (axe FRAIS jamais balayé : date/time + parsing de chaînes money/percent dans les `*lib/*.py`).** Explore ciblé (brief strict anti-faux-positif : rejeter défensif-seul + domaine-borné · mémoires `verify-non-defects`/`docstring-vs-code-drift`). 3 candidats remontés, **TOUS réfutés au raisonnement domaine-borné** :
- **#2`finlib/gate.py:153`** (`signature_date` en test de véracité) — exigerait une donnée **corrompue**`signature_date="0"` (chaîne « 0 » vraie en Python). Non atteignable : une date de signature est une chaîne ISO ou `null`, jamais « 0 ». Test de véracité **correct** pour des entrées réelles. **Non-défaut.**
- **#1`commlib/finance.py:47``rate()`** — le candidat « CONFIRMÉ » de l'agent (`rate("1.234,56 %")` → `None` car `replace(",",".")` fabrique « 1.234.56 »). **Réfuté** : un taux de commission est **borné 0-100** (doctrine écrite de `_rate_label` lignes 76-77), donc jamais ≥ 1000 → **aucun séparateur de milliers** ne peut apparaître. La virgule est donc toujours **décimale** ici (« 3,5 % » → 3.5 = le cas français **voulu et géré**). Même classe que le `:g` de `_rate_label` (deux pièges inatteignables sur domaine borné). **Non-défaut** — cohérent avec le CLEAN de la session `060044` sur ce même fichier.
**Action = 1 note anti-récurrence in-situ sur `rate()` (commentaire pur · sœur des notes `090114`/`113134`).** Fait nouveau motivant l'action : un **Explore capable vient de mal-classer `#1` en « CONFIRMED DEFECT »** — preuve que le locus est **re-flaggable**. La fonction de **parsing**`rate()` ne portait **aucune** note (sa sœur `_rate_label` en porte une, mais sur le formatage `:g`, pas sur le parse à la virgule). Passe unique : ajout d'un commentaire *accurate* (vérifié) documentant (a) l'acceptation de la virgule décimale française et (b) l'inatteignabilité du séparateur de milliers (taux borné 0-100, renvoi à `_rate_label`), + interdiction de « corriger » vers `locale.atof` (#5/#6). Convertit le faux-positif futur en « déjà revu, sûr ».
**Pourquoi PAS un fix de code ni un gate (#5/#6).** Aucun défaut réel (3/3 réfutés) : passer à `locale.atof` serait de la sur-ingénierie sur un cas inatteignable + casserait le déterminisme (dépendance locale — cf. `locale-tz-determinism-sweep`). Robustesse portée par le domaine borné + les suites (∈ régression gatée). Commentaire sans marqueur `# N ·` d'invariant (pas de déclenchement du gate ledger).
**Impact d'artefact : ZÉRO (prouvé byte-level).** Commentaire pur → aucun changement de sortie. Rebuild de `commissions/out/` dans `/tmp` (avant ET après édit) → `diff -rq` = **BYTE-IDENTICAL**. `regression_run.json`/`quality_report.json`**inchangés** (635/618/17 · 24×100). Aucun compteur de fiche/README à réaligner (pas de test ajouté).
## Session `20260812_113134` — VÉRIF · fermeture de la classe « percent == 100 ⟺ ok » sur les 4 conditions du gate financement + durcissement anti-récurrence des 2 conditions à domaine borné (commentaires purs · 0 impact artefact)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json totals` (lu de l'artefact commité · #6) = **635 exécutés · 618 passés · 17 skippés · 0 échec** (PASS). `quality_report.json` = **PASS · 24 modules · min 100/100 · bijection 24↔24**. Séquence moteur V18 toujours **intégralement bloquée** en gouvernance (D-06 approbation audit = gate d'entrée · « NE PAS coder avant l'audit » · #6) → **aucun code moteur légitime**. Arbitrages produit D-01→D-08 en attente Michel (`OPEN_DECISIONS_REGISTER.md`). Canal stakeholder `daily_reports/2026-08-12.md` à jour (session `103119`, matrice 635).
**Chasse au défaut d'abord (angle : la classe « barre de progression == 100 % ⟺ soumissible » est-elle vraiment fermée après les 2 fixes `093114`+`110124` ?) → classe PROUVÉE FERMÉE, 0 nouveau défaut.** Les deux bugs réels de ce cycle (`093114` per-condition apport, `110124` agrégat `overall_percent`) partagent une classe : un percent user-facing (barre de progression) qui pouvait afficher **100.0 tout en BLOQUANT**. J'ai balayé toutes les surfaces susceptibles de porter cette classe.
**Balayage cross-module (Grep sur les artefacts commités).** Un seul module émet un champ `percent`/`overall_percent`/`progress` dans un `out/*.json` commité : `crm/financement_bancaire/out/gate_status_example.json`. Les autres scorers examinés dérivent leur verdict **directement** de la note, sans divergence possible : `audit_4big/scoring.py` — `verdict = "PASS" if score >= pass_score` où `score` EST la note affichée (pas deux quantités indépendantes) ; `bancable` — le `pe_brut` (point d'équilibre en unités) n'est pas une barre de complétion. **La classe est donc confinée au seul gate financement.**
**Audit exhaustif des 4 conditions du gate + agrégat (invariant : `percent == 100.0 ⟺ ok`).**
- **`_cond_apport`** — ratio USD **continu**, plafonné explicitement « non ok → 99.9 » par le fix `093114` ✓
- **`overall_percent`** (agrégat) — moyenne de 4, plafonnée explicitement par le fix `110124` ✓
- **`_cond_validation_wag`** — percent exact `100.0 if ok else 0.0`, aucune divergence ✓
- **`_cond_documents`** / **`_cond_autorisations`** — `pct = round1(validated/total*100)`**SANS** garde de plafond. Vérifié **empiriquement** : le dénominateur est une liste du contrat (`financement_spec.json`) — **13** documents `is_required` (max) et **4** autorisations. Or `round1((total-1)/total*100)` ne remonte à `[99.95 %, 100 %[` par le bas qu'à partir de **total ≥ 2000** (première valeur calculée par script : 2000, car `(total-1)/total ≥ 0.9995`). Domaines **bornés à 13/4 ≪ 2000** → `ok=False ⟹ percent < 100` tient **déjà** sans garde. Sœur exacte des 3 sites `:g`-domaine-borné (`090114`). Direction inverse triviale : `ok=True ⟹ validated==total ⟹ percent=100.0` exact.
**Conclusion : classe FERMÉE.** Les 2 conditions à ratio potentiellement continu (apport, agrégat) portent une garde explicite prouvée par test à dents ∈ régression gatée ; les 2 à domaine borné (documents ≤13, autorisations =4) sont sûres par construction ; wag est exact. Aucune 3ᵉ instance latente.
**Action = durcissement anti-récurrence (commentaires purs · sœur de `090114` « twin fix must sweep ALL surfaces »).** Fait nouveau motivant l'action : `_cond_documents`/`_cond_autorisations` sont les **seules** des 4 conditions à **ne porter NI garde NI note** expliquant leur sûreté — un audit futur posant « l'invariant est-il fermé pour les 4 ? » les re-flaggerait (cycle gaspillé) ou y ajouterait un plafond **redondant** (`#5`), exactement le problème que `090114` a réglé pour les `:g`. Passe unique : ajout à chacune d'une **note de justification in-situ** (domaine borné ≤13/4 ≪ seuil 2000 → invariant préservé sans garde · renvoi aux fixes `093114`/`110124` · « ne pas ajouter de plafond redondant · #5 »). Convertit le faux-positif futur en « déjà revu, sûr ».
**Pourquoi PAS un fix de code ni un gate (#5/#6).** Aucun défaut réel : ajouter un plafond aux 2 conditions bornées serait du **code mort** (inatteignable) — pas un fix. Robustesse portée par le domaine borné + les 2 tests à dents des conditions continues ; rien de nouveau à gater (#5). Commentaires sans marqueur `# N ·` d'invariant (pas de déclenchement du gate ledger).
**Impact d'artefact : ZÉRO (prouvé byte-level).** Commentaires purs → aucun changement de sortie. Rebuild de `financement_bancaire/out/` dans `/tmp` → `diff -rq out /tmp` = **BYTE-IDENTICAL** (7 fichiers). `regression_run.json`/`quality_report.json`**inchangés** (635/618/17 · 24×100). Aucun compteur de fiche/README à réaligner (pas de test ajouté).
## Session `20260812_110124` — FIX correctness · `financement_bancaire` — même bug que `093114`, un niveau au-dessus : la barre GLOBALE `overall_percent` pouvait afficher 100.0 tout en BLOQUANT (l'invariant per-condition ne s'était PAS propagé à l'agrégat)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json totals` (lu de l'artefact commité · #6) = **634 exécutés · 617 passés · 17 skippés · 0 échec** (PASS). `quality_report.json` = **PASS · 24 modules · min 100/100 · bijection 24↔24 (`ok: true`)**. Séquence moteur V18 toujours **intégralement bloquée** en gouvernance (D-06 approbation audit = gate d'entrée · directive « NE PAS coder avant l'audit » · #6) → **aucun code moteur légitime**. Arbitrages produit D-01→D-08 en attente Michel (`OPEN_DECISIONS_REGISTER.md`). Canal stakeholder `daily_reports/2026-08-12.md` à jour au HEAD `85cd625` (session `100114`).
**Chasse au défaut d'abord (angle : la robustesse de la classe « affichage percent == 100 ⟺ soumissible » est-elle vraiment fermée après `093114` ?) → 1 candidat CONFIRMÉ au byte-level, latent mais réel.** Le fix `093114` (`gate.py:105`) a rétabli l'invariant **`percent == 100.0 ⟺ ok`** *par condition* (`_cond_apport` plafonné à 99.9 quand non ok). J'ai vérifié si l'**agrégat** `overall_percent` (la **barre GLOBALE** du frontend, `gate_status:171`) héritait de l'invariant — **il ne l'héritait pas**.
**Le défaut (même classe, un cran plus haut).** `overall = _round1(Σ percents / 4)` sur les 4 conditions. Cas limite atteignable : apport plafonné à **99.9** (non ok, exactement l'état laissé par le fix `093114` p. ex. 19 999/20 000 USD) **+** les 3 autres conditions **ok à 100.0** (documents tous validés WAG, autorisations toutes signées, validation WAG faite) → moyenne `(99.9 + 300)/4 = 99.975` → `round1` = **100.0**. Résultat user-facing : `overall_percent: 100.0`**tout en**`can_submit: False` (403). La **barre de progression GLOBALE** — celle que le client regarde en premier — afficherait « 100 % » sur un dossier refusé. **Exactement** la même auto-contradiction que le fix per-condition `093114`, mais à l'agrégat que ce fix n'a pas couvert. Reproduit **end-to-end** via le vrai `gate_status` (dossier résident RD 100 000 USD, apport 19 999) : `overall_percent=100.0`, `can_submit=False`.
**Pourquoi byte-repro est aveugle (preuve que ça échappe aux gates).** `gate_status_example.json` commité est à `overall_percent: 39.1` (`can_submit: false`) — **hors bande**. `check_artifacts` (rebuild == commité) le reproduit à l'identique éternellement. Bug **latent** : correct sur l'échantillon figé, faux dès qu'un dossier réel atteint la configuration limite. Même veine que `093114` (per-condition), `073055` (`base×taux`), `080104` (commissions).
**Fix (chirurgical, symétrique au fix `093114`, byte-repro préservé).** Après le calcul de `overall`, garde `if not can_submit and overall >= 100.0: overall = 99.9`. Invariant d'affichage rétabli au niveau agrégat : **`overall_percent == 100.0 ⟺ can_submit`**. L'exemple à 39.1 % **inchangé** → `financement/out/` rebuild **BYTE-IDENTIQUE** (`diff -rq out/ /tmp` = IDENTICAL).
**Test à dents (prouvé mordant).** Nouveau `test_overall_percent_agrege_ninclut_pas_100pct` (frère de `test_apport_quasi_complet_ninclut_pas_100pct`, mais à l'agrégat) : dossier avec apport 19 999/20 000 + 3 autres conditions ok → assert `can_submit=False`, les 3 autres conditions `ok=True` (isolation du cas limite), `overall_percent < 100.0` (== 99.9). **Sans le fix** → `AssertionError: 100.0 not less than 100.0` (mordu, vérifié par mutation) ; **avec** → OK. Suite financement **36 → 37**.
**Cascade artefacts (test count +1).** Régénéré `regression_run.json` (**634→635 / 617→618**), `regression_plan.json` + `MANIFEST.json` (build), `quality_report.json` (audit_4big **en dernier** · mémoire `audit4big-rebuild-after-doc-edits` — PASS 24/24, min 100). Fiches gatées re-dérivées (`check_readme_claims`) : QA `634/617→635/618`, Backend `634→635`, CRM financement `36→37` (le « Total CRM » du trio est source séparée · financement suivi à part ligne 44 · convention `fiche-accuracy-audit-closed`). Canal stakeholder `daily_reports/2026-08-12.md` prolongé (4ᵉ vague · matrice 634→635 · mémoire `two-logging-channels`).
**Pourquoi pas un nouveau gate (#5).** L'invariant `overall_percent == 100 ⟺ can_submit` est désormais **porté par le code + prouvé par un test à dents** ∈ régression gatée — comme son frère per-condition `093114`. Un gate statique dédié serait redondant (`verify-uncovered` : la suite couvre, mutation confirmée). Origine unique, un seul site.
## Session `20260812_103119` — VÉRIF · nouvel axe « bien-formation structurelle du HTML généré » (parseur réel + entrées adverses FR/ES) → CLEAN teeth-prouvé, replayable, sans gate
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json totals` (lu depuis l'artefact commité · #6) = **634 exécutés · 617 passés · 17 skippés · 0 échec** (PASS). `quality_report.json` = verdict **PASS · 24 modules · min 100/100 · bijection couverture 24↔24 (`ok: true`)**. Séquence moteur V18 toujours **intégralement bloquée** en gouvernance (D-06 approbation audit = gate d'entrée · directive « NE PAS coder avant l'audit » · #6) → **aucun code moteur légitime**. Arbitrages produit D-01→D-08 en attente Michel (`OPEN_DECISIONS_REGISTER.md`). Canal stakeholder `daily_reports/2026-08-12.md` re-réconcilié au HEAD par la session `100114` (matrice 634) → à jour ; cette session est **vérification-seule à 0 impact matrice**, rien à ré-émettre.
**Chasse au défaut d'abord (angle FRAIS jamais balayé = bien-formation *structurelle* du HTML généré).** Les ~40 sweeps antérieurs ont couvert l'octet (`runtime-encoding`), la forme NFC, le déterminisme (hash/locale/TZ/ordre), les schémas (7 axes), l'arithmétique dérivée, etc. — mais **jamais** la question « le HTML émis par les générateurs est-il structurellement bien-formé (balises équilibrées, attributs non cassés) sous une entrée réaliste FR/ES ». Distinct de la classe déjà fixée en publiciste `070054`/`073055` (mauvais encodeur de *contexte*) : ici on vérifie la **structure de sortie** au parseur.
**Méthode (à dents, replayable).** (1) Explore ciblé sur tous les chemins générant du markup (`publiciste/lib/generator.py` — pages HTML ; `frontend/chat_otoia/chatlib/frappe.py` — `mount_html`). (2) Parsing réel de la sortie avec un détecteur `html.parser` maison suivant une **pile de balises** (gestion des éléments void), flaggant unclosed-EOF / misnest / stray-close. Exécuté sur :
- La **sortie site commitée** (`render_site` sur `fixtures/data_room`, 2 projets P01/P02) → **len 6084, 0 erreur**.
- Un **projet adverse** (free-text injecté dans tous les champs éditables : `nom="Résidence «L'Été» & <Cie>"`, `localisation` avec guillemets doubles, `positionnement` avec `<b>`/`"`/`'`, `typologies[0].nom="T2 «Prestige» & <balcon>"`, `inclus` avec `&`/`"`/`'`/`<sud>`) → **0 erreur** : `_esc` (= `html.escape(quote=True)`) neutralise correctement tout le texte et les attributs.
- Le chemin **hero CSS `url('…')`** (le locus des fix `070054`/`073055`) reconfirmé bien-formé en page complète.
**Teeth du détecteur (prouvé mordant, mémoire `module-suite-teeth-mutation`).** 3 échantillons délibérément mal-formés **doivent** être flaggés : `<div><span>x</span>` → `unclosed EOF: div` ; `<div><span>x</div></span>` → `misnest`+`stray` ; `<p>x</p></p>` → `stray </p>`. Et un bien-formé `<div><span>x</span></div>` → `NONE`. **4/4 conformes** → le détecteur mord, le CLEAN n'est pas un faux-vert.
**Résultat = CLEAN, 0 défaut.** `_esc` est appliqué à **100 %** des insertions texte/attribut de `generator.py` (vérifié lignes 60/66/82/114/117/118-124), et le contexte CSS `url()` combine `quote()` (URL) puis `_esc()` (attribut HTML) — deux-étages correct. `frappe.py mount_html` reste non-échappé mais ses 4 entrées sont **provablement des enums/constantes** (`persona="Amélie"` figé, `portail_key`∈{ventes,construction,achat,compta,direction}, `css_class`/`id_prefix` du spec contrôlé) — aucun chemin free-text ne l'atteint → non-défaut (déjà statué 2× · `verify-non-defects`, non re-flaggé).
**Pourquoi pas un fix ni un gate (#5/#6).** Aucun défaut trouvé (structure saine end-to-end, teeth-prouvé). Comme les axes de déterminisme (`hash-seed`/`locale-tz`/`run-order`) et d'encodage (`runtime-encoding`/`unicode-nfc`), c'est une **commande replayable**, occurrence-zéro, pas une matière à gate — la robustesse structurelle vit dans `_esc`/`quote` (déjà couverts par la suite publiciste ∈ régression gatée). Aucun chiffre inventé (#6 — tous lus des artefacts commités).
## Session `20260812_100114` — CANAL STAKEHOLDER · re-réconciliation `daily_reports/2026-08-12.md` au HEAD courant (`e7cc3c4` → `85cd625`) · remontée du 4ᵉ fix de correctness (matrice `633→634`)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json totals` (lu depuis l'artefact commité · #6) = **634 exécutés · 617 passés · 17 skippés · 0 échec** (PASS). `quality_report.json` = verdict **PASS · 24 modules · min 100/100 · bijection couverture 24↔24 (`ok: true`)**. Séquence moteur V18 toujours **intégralement bloquée** en gouvernance (D-06 approbation audit = gate d'entrée · directive « NE PAS coder avant l'audit » · #6) → **aucun code moteur légitime**. Arbitrages produit D-01→D-08 en attente Michel (`OPEN_DECISIONS_REGISTER.md`).
**Chasse au défaut d'abord (2 axes frais, jamais audités en profondeur) → 0 défaut, surfaces confirmées CLEAN.**
- **Axe computationnel (Explore ciblé sur les 8 surfaces les moins récemment auditées** : `rbac/apply_plan/aggregator.py` · `qa/audit_5d/{controls,artifacts}.py` · `seo/{hreflang,schemaorg}.py` · `legal/confotur/{rbac_scan,builder}.py` · `demo/scenarios/{evidence,render}.py`**).** Brief strict anti-faux-positif (rejeter défensif-seul + domaine-borné · mémoires `verify-non-defects`/`docstring-vs-code-drift`). **Aucun défaut réel** : formatage bornes correct, échappement contextuel sûr sur entrées contrôlées, gardes de division présentes, bornes d'index correctes (`evidence.py:88``-len(obj) <= idx < len(obj)`), tri déterministe, 0 défaut fabriqué.
- **Axe i18n frais (`faisabilite/bancable/banclib/i18n.py`, jamais balayé).** Vérifié la **parité des clés** FR/EN/ES du dictionnaire `LABELS` (une clé présente dans une langue mais absente d'une autre lèverait `KeyError` au rendu, byte-repro-aveugle si non exercée) → **les 3 langues ont des jeux de clés strictement identiques** (`missing: ∅ · extra: ∅` pour fr/en/es). CLEAN.
**Vrai locus = currency du canal stakeholder (mémoire `two-logging-channels` : garder les DEUX canaux à jour, chaque chiffre sourcé d'un artefact commité).** Le rapport `daily_reports/2026-08-12.md` avait été réconcilié en session `083114` au HEAD `e7cc3c4` (matrice **633/616**). Depuis, **2 commits worker** ont avancé l'arbre à `85cd625`, dont **un fix de correctness à impact matrice** que le snapshot ne reflétait pas :
- `85cd625` (`093114`) — **fix `financement_bancaire/finlib/gate.py`** : un dossier dans la bande [99.95 %, 100 %[ (p. ex. 19 999/20 000 USD) affichait `percent: 100.0` (barre frontend « 100 % ✓ ») tout en **bloquant** la soumission (403). Fix = garde `if not ok and pct >= 100.0: pct = 99.9` → invariant `percent == 100.0 ⟺ ok` rétabli · test à dents · suite financement **35→36**, matrice **633/616 → 634/617**.
**Fix = re-réconciliation (pas ré-émission) du rapport du jour.** Le rapport daté `2026-08-12.md`**est** le snapshot du jour ; le re-réconcilier au HEAD courant est le contrat, pas un doublon (#5). Porté : header `HEAD e7cc3c4 → 85cd625` + session `100114`, nouvelle section « Jalon du jour · 3ᵉ vague (`e7cc3c4` → `85cd625`) » (table du fix gate financement + opposabilité), `État courant` re-titré au HEAD `85cd625` avec **tous les chiffres re-dérivés de l'artefact commité** (matrice **634/617/17** · PASS 24× · bijection 24↔24), trajectoire de matrice étendue (633 → **+1** au `85cd625` = 634), §Portée actualisé (commit intercalaire `cb8069b` noté sans impact).
**Pourquoi pas un fix ni un gate (#5/#6).** Aucun défaut trouvé (Explore CLEAN + parité i18n + tree cohérent) ; le seul « écart » était la **staleness du canal stakeholder** (canal éditorial daté par design, comme un snapshot — pas un artefact byte-gaté). L'actualiser EST le contrat de `two-logging-channels`, pas une matière à gate.
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé — rapport = document, 0 dépendance de gate). Rapport **NFC-clean** (vérifié). **0 code moteur V18** (bloqué #6), **0 module de production touché**, **0 artefact autoritaire modifié/reconstruit**, **0 chiffre figé** (tous lus depuis l'artefact commité · #6), **0 gate ajouté** (#5), **0 commande VPS** (#8), **0 API externe**. Fichiers : `M 05_deliverables_mvp/daily_reports/2026-08-12.md` (re-réconciliation au HEAD courant) + ce journal.
## Session `20260812_093114` — FIX correctness · `financement_bancaire` gate — un dossier incomplet pouvait afficher `percent: 100.0` (barre pleine ✓) tout en BLOQUANT (bug latent, byte-repro-aveugle)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json totals` = **633 ran · 616 passés · 17 skippés · 0 échec** (PASS). `quality_report.json` = PASS · 24 modules · min 100/100 · bijection 24↔24. Séquence moteur V18 toujours **bloquée** en gouvernance (directive « NE PAS coder avant l'audit » · D-06 gate d'entrée · #6) → **aucun code moteur légitime**. Canal stakeholder `daily_reports/2026-08-12.md`**à jour** au HEAD (matrice stable — `cb8069b` commentaires purs).
**Chasse au défaut d'abord (vein correctness, comme 070054/073055/080104) → 1 candidat CONFIRMÉ au byte-level, latent mais réel.** Explore ciblé sur l'arithmétique/parsing des générateurs (arrondis · taux · bornes · division). Le candidat remonté — `crm/financement_bancaire/finlib/gate.py:97``_cond_apport` — est un **vrai défaut de cohérence d'affichage** confirmé par trace concrète, pas un cas théorique.
**Le défaut.** `pct = _round1(min(100.0, paid/required*100))` arrondit à 1 décimale, tandis que `ok = paid + 1e-9 >= required` est un test **exact**. Un versement dans la bande **[99.95 %, 100 %[** (p. ex. **19 999 / 20 000 USD**, 99.995 %) **remonte à `percent: 100.0`** par l'arrondi, alors que `ok=False` et `remaining=1.0 USD`. Conséquences user-facing :
- La **barre de progression frontend** (`GET /api/hypotheque/dossier/{id}/gate-status`, contrat DIRECTIVE) afficherait **« 100 % ✓ »** tout en refusant la soumission (403).
- La **raison de blocage** (`can_submit_dossier`, ligne 187) lirait « Apport initial incomplet : … (reste 1 USD · **100.0 %**) » — auto-contradictoire.
- Seul `_cond_apport` est **atteignable** : les deux autres conditions à `percent` continu-arrondi (`_cond_documents`/`_cond_autorisations`) sont des **ratios entiers**`validated/total` sur de **petites listes** (13 docs · 4 autorisations) — atteindre la bande [99.95 %,100 %[ avec `ok` faux exigerait `total ≥ 2000`, **impossible** ; leur `percent==100 ⟺ ok` tient structurellement. Non touchées (pas de sur-ingénierie · #5).
**Pourquoi byte-repro est aveugle (preuve que ça échappe aux gates).** Le `dossier_exemple` commité est à **67.8 %** (29 500/43 500 USD) — **hors bande**. `gate_status_example.json` échoue donc jamais le bug, et `check_artifacts` (rebuild==commité) le reproduit à l'identique éternellement. C'est un **bug latent** : correct sur l'échantillon figé, faux dès qu'un dossier réel atterrit dans la bande. Même classe que les fixes 073055 (`base×taux` cassait sur montants réels) et 080104 (commissions).
**Fix (chirurgical, byte-repro préservé).** `gate.py` : extraction de `ok`, puis garde `if not ok and pct >= 100.0: pct = 99.9` (plus grande valeur 1-décimale strictement < 100 · convention `_round1`). Invariant d'affichage rétabli : **`percent == 100.0 ⟺ ok`**. L'exemple à 67.8 % **inchangé** → `gate_status_example.json`/`out/` rebuild **BYTE-IDENTIQUE** (vérifié `diff -rq out/ /tmp` = IDENTICAL).
**Test à dents (prouvé mordant).** Nouveau `test_apport_quasi_complet_ninclut_pas_100pct` : 19 999/20 000 → `ok=False`, `remaining≈1.0`, `percent < 100.0` (== 99.9), et la raison **ne contient pas « 100 »**. **Sans le fix** → `AssertionError: 100.0 not less than 100.0` (mordu) ; **avec** → OK. Suite financement **35 → 36**.
**Cascade artefacts (test count +1).** Régénéré `regression_run.json` (**633→634 / 616→617**), `regression_plan.json` + `MANIFEST.json` (build), `quality_report.json` (audit_4big **en dernier** · mémoire `audit4big-rebuild-after-doc-edits` — PASS 24/24 ≥95, min 100). Fiches gatées re-dérivées : QA `634/617`, Backend `634`, CRM financement `35→36` (le « Total CRM 83 » du **trio** est source séparée · financement suivi à part ligne 40 · inchangé).
**Pourquoi pas un nouveau gate (#5).** L'invariant `percent==100 ⟺ ok` est désormais **porté par le code + prouvé par un test à dents dans la suite** (∈ régression gatée). Un gate statique dédié serait redondant (`verify-uncovered` : la suite couvre déjà, mutation confirmée). Origine unique, un seul site atteignable.
## Session `20260812_040034` — CLEANUP #5 · suppression d'un orphelin `out_fresh/` (doublon d'artefacts commité par accident, échappe à TOUS les gates)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json totals` = **629 ran · 612 passés · 17 skippés · 0 échec** (PASS). Séquence moteur V18 toujours **intégralement bloquée** en gouvernance (directive `:90-91` « NE PAS coder avant l'audit » · D-06 approbation audit = gate d'entrée · #6) → **aucun code moteur légitime**. Dernier commit `031b5e7` = PNG `DASHBOARD_LUXURY_MODELE_REFERENCE` ajouté par Michel (référence design, hors production repo).
**Chasse au défaut d'abord (2 axes) → 1 candidat écarté comme NON-DÉFAUT (refus de fabriquer · #6).** Explore very-thorough sur les modules produit hors `audit_4big`/`regression`/`publiciste` (déjà balayés). Le seul candidat remonté « confirmé » — `faisabilite/generator/genlib/model.py:104``is_filled()` exigeant `"{{" in text and "}}" in text` pour classer un placeholder — a été **réfuté au byte-level** : le renderer (`renderer.py:23-42`) n'émet **jamais** que des placeholders `{{...}}`**complets** (deux accolades), `brief_incomplete.json` n'utilise **aucune** accolade pour marquer l'absence (il utilise `_PLACEHOLDER_ABSENT` : « tbd »/« todo »/… + champs omis), et **aucune** occurrence mono-accolade n'existe dans tout le module. Exiger les deux accolades est donc le choix **conservateur correct** (flaguer un `}}` isolé dans de la prose réelle serait un faux positif). L'Explore s'était sur-engagé sur un cas théorique inatteignable → **non touché** (`verify-non-defects`).
**Vrai locus trouvé = un ORPHELIN ACCUMULÉ (violation #5 « JAMAIS accumuler doublons »), pas un bug.** `05_deliverables_mvp/frontend/portails/out_fresh/` (2 fichiers : `workspace.json` + `MANIFEST.json`) est :
- **Tracké** (`git ls-files` le confirme) — mais commité **une seule fois**, dans la session auto `f954cc1` du **2026-08-02** (scratch-dir de comparaison « fresh rebuild » commité par accident).
- **Byte-identique** à `out/` (diff `json.tool` = IDENTICAL content).
- **Produit par rien** : le générateur `workspaces_gen.py` écrit **uniquement** dans `out/` (`_DEFAULT_OUT = os.path.join(_HERE, "out")` · `-o` défaut `./out`) — jamais `out_fresh`.
- **Référencé nulle part** : `git grep out_fresh` sur tout l'arbre tracké (hors le dossier lui-même) = **ZÉRO** (ni code, ni doc, ni CI, ni `.gitignore`, ni test).
**Pourquoi c'est un vrai défaut et pas un artefact by-design (preuve qu'il échappe à tout gate).** `check_artifacts.sh` découvre les cibles par `find -type d -name out` — un match **exact** sur `out`, donc `out_fresh`**n'entre dans aucun gate de reproductibilité**. C'est précisément pourquoi il a survécu au balayage `reverse-bijection-artifact-sweep` (qui ne vise que les répertoires `out/`). Conséquence : c'est une **bombe à retardement de staleness** — rien ne le régénère, donc dès que la source du générateur bouge, `out/` se met à jour tandis que `out_fresh/`**diverge silencieusement** en une copie **stale, non-possédée et trompeuse** (« laquelle fait autorité ? »). Dead weight ungated. C'est l'unique scratch-dir du genre dans tout le repo (`find` pour `out_new`/`out_old`/`out_tmp`/`out_bak`/`out_fresh` → seul `out_fresh` existe · occurrence isolée).
**Fix = suppression (`git rm -r out_fresh/`).** Sûr (réversible via l'historique git), et **je ne l'ai pas créé** (règle « avant de supprimer, regarde la cible » : contenu byte-identique à la copie autoritaire, aucun consommateur). La copie autoritaire `out/` est **intacte et se régénère BYTE-IDENTIQUE** (rebuild `-o /tmp` vs `out/` = identique, vérifié après suppression).
**Pourquoi pas un nouveau gate (#5).** Occurrence **isolée** (origine unique `f954cc1`, un seul module). Ajouter un gate « aucun répertoire `out_*` non-`out` » serait de la sur-ingénierie pour un événement-zéro. La discipline existante (générateur écrit dans `out/` · `.gitignore` documente que `out/` est commité) est le contrat ; l'orphelin était une déviation ponctuelle, corrigée à la source.
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé — rien ne dépendait de `out_fresh/`). **0 code moteur V18** (bloqué #6), **0 module de production touché** (seul un doublon orphelin supprimé · char-class `model.py` laissée intacte car non-défaut), **0 artefact autoritaire modifié**, **0 gate ajouté** (#5), **0 commande VPS** (#8), **0 API externe**. Fichiers : `- 05_deliverables_mvp/frontend/portails/out_fresh/{workspace,MANIFEST}.json` (supprimés) + ce journal.
## Session `20260812_043034` — CANAL STAKEHOLDER · actualisation `daily_reports` au HEAD courant (`35de616` → `ee88d36`)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json totals` = **629 ran · 612 passés · 17 skippés · 0 échec** (PASS). `quality_report.json` = verdict **PASS · 24 modules · min 100/100 · bijection couverture 24↔24 ok**. Séquence moteur V18 toujours **bloquée** en gouvernance (audit préalable produit mais non validé · D-06 = gate d'entrée · #6) → **aucun code moteur légitime**.
**Chasse au défaut d'abord (2 axes) → tree cohérent, aucun défaut candidat.** (1) Vérifié que les chiffres cités dans le journal `20260812_040034` (629/612/17) sont **byte-cohérents** avec le `regression_run.json` commité (identiques). (2) Vérifié que `README.md`**ne fige aucun total de tests** (il pointe l'artefact byte-gaté comme « autorité », prose « 24 suites gated / 24 modules » exacte vs `quality_report.json`) → **0 drift numérique README**.
**Vrai locus = currency du canal stakeholder (mémoire `two-logging-channels` : garder les DEUX canaux à jour, chaque chiffre sourcé d'un artefact commité).** Le dernier rapport `daily_reports/2026-08-11.md` était épinglé à `35de616` (currency `20260811_092842`) et citait la régression à **628/611**. Depuis, **3 commits** ont avancé l'arbre à `ee88d36`, et la matrice a **divergé d'un incrément** :
- `39def91` (`[DTP-Worker 20260811_095844] Auto exec`, **postérieur** à la currency du rapport 08-11) a ajouté un test verrou **NBSP U+00A0** dans `publiciste/tests/test_publiciste.py` (+15 lignes) → régression **628→629 / 611→612** + `regression_plan/run/MANIFEST` + `quality_report` re-générés du même coup. Le rapport 08-11 **ne le couvre pas** → chiffre `628/611` désormais **stale**.
- `031b5e7` = PNG `DASHBOARD_LUXURY_MODELE_REFERENCE` ajouté par Michel (asset de référence design, hors production repo, root-owned).
- `ee88d36` = jalon worker de la session précédente de ce jour (suppression orphelin `out_fresh/` · déjà journalisée ci-dessus).
**Fix = émission de `daily_reports/2026-08-12.md`.** Porte le canal `35de616 → ee88d36`, **re-dérive tous les chiffres depuis les artefacts commités** (jamais figés · #6) : gate 33/0/0, régression **629/612/17 PASS**, audit 4Big **PASS 24× 100/100**, bijection **24↔24 ok**. Attribue explicitement la seule évolution de matrice (628→629) à `39def91`, note que `031b5e7`/`ee88d36`**ne touchent aucun test** (matrice stable). Reprend le §« Chemin critique bloqué » (D-06/D-07/D-01 en attente Michel) + escalation.
**Pourquoi pas un nouveau gate (#5).** Aucun défaut trouvé : le tree est cohérent, le seul « écart » était la **staleness du canal stakeholder** (canal éditorial daté par design, comme un snapshot — pas un artefact byte-gaté). L'actualiser EST le contrat de la mémoire `two-logging-channels`, pas une matière à gate.
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé — rapport = document, 0 dépendance). **0 code moteur V18** (bloqué #6), **0 module de production touché**, **0 artefact autoritaire modifié/reconstruit**, **0 gate ajouté** (#5), **0 commande VPS** (#8), **0 API externe**. Fichiers : `+ 05_deliverables_mvp/daily_reports/2026-08-12.md` (nouveau rapport stakeholder) + ce journal.
## Session `20260812_050034` — VÉRIF · nouvel axe de déterminisme « ordre d'exécution des suites » (balayage CLEAN, replayable, sans gate)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json totals` = **629 ran · 612 passés · 17 skippés · 0 échec** (PASS). `quality_report.json` = PASS · 24 modules · min 100/100 · bijection 24↔24. Moteur V18 toujours **bloqué** en gouvernance (directive « NE PAS coder avant l'audit » · D-06 gate d'entrée · #6). Canal stakeholder `daily_reports/2026-08-12.md`**déjà à jour** au HEAD `ee88d36` (session précédente `043034`) → rien à ré-émettre.
**Chasse au défaut d'abord (2 axes) → 1 candidat Explore RÉFUTÉ comme non-défaut, tree propre.**
- **Axe prose/drift** (Explore very-thorough sur `daily_reports/*.md`, `04_roadmap/*.md`, `03_agents/**/AGENT.md`, liens Markdown). Le seul candidat « confirmé » — `03_agents/mobile/AGENT.md:81` « upgrade 54 tenté puis abandonné » vs `mobile_spec.json``expo_sdk_major: 54` — a été **réfuté** par la mémoire `mobile-runtime-actual-vs-rebuild-target`**et** relecture byte : la ligne distingue explicitement le **runtime ACTUEL** (Expo 51, tentative 54 abandonnée le 2026-07-27) de la **cible de rebuild** (Expo 54 · S5). Le `54` du spec correspond exactement à « rebuild cible Expo 54 (S5) ». **Interne cohérent → non touché** (`verify-non-defects`).
**Vrai locus = un axe de déterminisme JAMAIS balayé — l'ORDRE D'EXÉCUTION des 24 suites** (4ᵉ axe, frères : `hash-seed-determinism-sweep`, `locale-tz-determinism-sweep`, `forward-compat-warnings-sweep` — **aucun** ne varie l'ordre des suites). Hypothèse de risque : un couplage inter-suites (état global partagé, scratch `/tmp` à nom fixe, cwd partagé) rendrait la matrice `regression_run.json` dépendante de l'ordre → RED intermittent sur le runner.
**Pourquoi c'est fermé par construction (teeth, pas chance).** `runner.run_suite` (`reglib/runner.py:98`) lance **chaque suite dans son propre subprocess** avec `cwd=abs_path` (le dossier de la suite) — aucun état module in-process partagé, aucun cwd partagé ; l'agrégation dans `run_matrix` est une **somme indépendante de l'ordre**. Le couplage inter-suites est donc empêché *by design* ; le run inversé est une **confirmation teeth**, pas une découverte. Mémoire : `run-order-determinism-sweep`.
**Pourquoi pas un nouveau gate (#5).** Résultat CLEAN, occurrence-zéro, l'isolation subprocess est déjà le contrat architectural. Comme les 3 autres axes de déterminisme, c'est une **commande replayable**, pas une matière à gate. Aucun chiffre inventé (#6 — tous lus depuis l'artefact commité).
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé — expérience en lecture seule sur `/tmp`, 0 artefact touché). **0 code moteur V18** (bloqué #6), **0 module de production touché**, **0 artefact autoritaire modifié**, **0 gate ajouté** (#5), **0 commande VPS** (#8), **0 API externe**. Fichiers : ce journal (+ mémoire agent `run-order-determinism-sweep`, hors repo).
## Session `20260812_053034` — VÉRIF (nouvel angle i18n) · asymétrie de cardinalité SEO per-langue `fr=87·en=87·es=84`**prouvée NON-DÉFAUT** · note explicative au point de dedup (anti re-flag, sans gate)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json totals` = **629/612/17** (PASS). `quality_report.json` = PASS · 24 modules · min 100/100 · bijection 24↔24. Moteur V18 toujours **bloqué** en gouvernance (directive « NE PAS coder avant l'audit » · D-06 gate d'entrée · #6). Canal stakeholder `daily_reports/2026-08-12.md`**déjà à jour** au HEAD (session `043034`) → rien à ré-émettre.
**Angle FRAIS (jamais balayé) = cohérence tri-lingue FR/EN/ES du module `seo/`.** Aucun des ~40 balayages antérieurs n'avait attaqué l'i18n des mots-clés. Un Explore very-thorough a remonté un candidat « défaut confirmé » : `out/seo_keywords.json` = **fr 87 · en 87 · es 84**, l'espagnol « manquant » de 3 entrées projet P01 (generic/residence/appartement). J'ai **refusé de le prendre au mot** (#6, `verify-non-defects`) et **prouvé au byte-level que c'est un NON-DÉFAUT** — l'artefact étant byte-repro-gaté, tout écart reflète la LOGIQUE du générateur, pas une faute de saisie.
**Preuve (reproduction du générateur sans dedup, tracé collision→terme-gardé).** La génération est **symétrique par langue** (`keywords.py:58``for lang in langs`) → **raw = 115/langue** identique. L'écart vient **uniquement** du dedup `(lang, term)` (`keywords.py:106-113`, « conserver la 1ʳᵉ occurrence ») :
- `country[es]` = **« Republica Dominicana »** est **exactement** la `localisation` générique de **5 projets** (P01/P02/P03/P06/P09 dans `fixtures/projets_master.json`). En espagnol, le mot-clé **global**`{type} Republica Dominicana` (émis avant les projets) est **byte-identique** au mot-clé **projet**`{type} {loc}` de P01 → la 2ᵉ occurrence (P01) est dédupliquée. Le terme **reste présent une fois**, en scope `global` : **rien n'est perdu**.
- En **fr/en**, `country` = « Republique Dominicaine » / « Dominican Republic » **≠** la localisation → pas de collision global↔projet ; P01 garde ses 7 termes et ce sont P02/03/06/09 (même loc) qui percutent P01. D'où **3 collisions global↔projet de plus en es** → 87 vs 84.
**Conclusion : sortie CORRECTE.** Chaque chaîne unique existe **exactement une fois** ; les invariants ne contraignent que `≥ min_keywords_per_lang` (50) + unicité `(term, lang)` (`seo_gen.py:88-98`, `test_seo.py:94-100`) — **jamais** l'égalité inter-langues, et à raison : forcer une symétrie ré-émettrait un **doublon strict**. Le chiffre `es=84` est d'ailleurs **déjà documenté et gaté** dans `README.md:15` (`258 (fr=87·en=87·es=84)`, re-calculé par `check_readme_claims`) → aucun drift, aucun « manquant ».
**Livrable = une NOTE explicative au point de dedup (`keywords.py:106`).** L'asymétrie est subtile et **non-évidente** : un Explore capable a failli la mal-classer en bug, et un futur balayage i18n refera l'enquête. J'ai ajouté un commentaire *accurate* (vérifié) expliquant la cause (coïncidence `country[es]` == localisation) et l'interdiction de « corriger » vers une symétrie. **Aucune** logique/fixture/claim-gaté touché (les gates byte visent `out/*.json`, pas la source `keywords.py`). Style aligné : ASCII sans accents + em-dash `—` U+2014 (convention existante lignes 5-6), NFC-clean.
**Pourquoi pas un fix ni un gate (#5/#6/`verify-non-defects`).** (1) Pas de fix logique : changer le tie-break dedup (préférer projet vs global) altèrerait l'artefact byte-gaté + le claim README pour « corriger » un comportement correct. (2) Pas de fix fixture : compléter la localisation générique des 5 projets serait **inventer** une donnée (#6) sans source. (3) Pas de gate d'égalité per-langue : ce serait une **fausse contrainte** (l'asymétrie est linguistiquement légitime) — le seuil `≥ min` par langue est déjà gaté et suffit.
## Session `20260812_060044` — HARDENING correctness · `criteria.py` — le 3ᵉ détecteur regex (le SEUL non durci) de la famille « détecter la forme, pas une graphie »
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json totals` = **629/612/17** (PASS). `quality_report.json` = PASS · 24 modules · min 100/100 · bijection 24↔24. Moteur V18 toujours **bloqué** en gouvernance (directive « NE PAS coder avant l'audit » · D-06 gate d'entrée · #6) → **aucun code moteur légitime**. Canal stakeholder `daily_reports/2026-08-12.md` déjà à jour au HEAD (session `043034`).
**Chasse au défaut d'abord (3 axes computationnels, jamais audités en profondeur) → 2 modules confirmés CLEAN, 1 vrai locus de robustesse trouvé.**
- **`crm/commissions/comlib/finance.py`** (calcul commissions `montant = base × taux`) : logique **correcte** — `rate()` normalise nombre/« 3.5 % »/« 3,5 % » en fraction, `taux_pct` de sortie = fraction cohérente avec le libellé de formule, opérande manquant → `None` (jamais 0-inventé). **Non touché.**
- **`faisabilite/bancable/banclib/finance.py`** (cœur break-even, zone de risque D-07) : **correct** — point d'équilibre `⌈52 % × total_unites⌉` avec `52 %` re-dérivé du canonique #9 (jamais codé en dur, cf. fix `448c310`), agrégats catalogue `Σ(qté × prix)` gardés sur `all(... is not None)`, formule publiée avec chaque valeur. **Non touché.**
- **Vrai locus = `qa/audit_4big/q4lib/criteria.py:25`** — voir ci-dessous.
**Le locus : le 3ᵉ détecteur regex non durci d'une famille dont les 2 autres l'ont été.** Ce fichier est la source de **TROIS** fixes de correctness confirmés de la classe « détecter la **forme**, pas une **graphie** » : `ac595d0` (regex ASCII vs `\w` Unicode → test accentué sous-compté), `fd99362` (garde `__main__` littérale double-quotée → simple-quotée/Yoda/sans-espace loupées), `35de616` (`def ` figé vs `def\s+` de la source sœur byte-gatée → `def test_x(` loupé). Les deux détecteurs `_TEST_DEF_RE` (`def\s+…\w`) et `_MAIN_GUARD_RE` (`\s*==\s*`, deux styles de guillemets, ordre Yoda) sont explicitement durcis pour tolérer tout **Python valide** (doctrine articulée lignes 18-30). **Restait `_ADD_PARSER_RE`** — le 3ᵉ frère — figé sur `add_parser\(|add_subparsers\(` : il exige la parenthèse **collée** au nom de méthode, donc `add_subparsers ()` / `add_parser ("build")` (espace avant `(` — Python valide, PEP 8 déconseille sans l'imposer) sont **silencieusement loupés** → sous-scorage `−20 pts` du critère CLI d'un module sain, exactement le symptôme de `fd99362`.
**Preuve de dents (avant fix).** Ancien regex : `p.add_subparsers ()` → **False**, `sub.add_parser ("x")` → **False** ; nouveau `add_parser\s*\(|add_subparsers\s*\(` → **True** sur ces deux formes **et** True sur les formes normales `add_parser("build")` / `add_subparsers(dest=…)` (aucune régression de couverture).
**Pourquoi c'est un durcissement légitime et non une invention (#6).** (1) **Classe déjà jugée réelle 3×** dans CE fichier ; les 2 frères sont durcis, le 3ᵉ était l'anomalie — j'harmonise vers la doctrine **écrite du fichier lui-même** (« détecter la forme, pas une graphie »), pas une convention externe. (2) **Précédent direct** : `fd99362`/`35de616` ont durci pour des variantes de Python valide **pas nécessairement présentes** dans un module courant (guillemets simples, Yoda, `def\t`). (3) **Fix latent, artefact byte-identique** : `grep -rE 'add_(parser|subparsers)[ \t]+\('` sur tous les `.py` = **AUCUN** module n'utilise l'espace aujourd'hui → aucun re-scorage, `quality_report.json` re-build **byte-identique** (vérifié par `check_artifacts` ∈ run_ci, vert).
**Pourquoi pas un nouveau gate (#5).** Ce n'est pas une classe de drift à surveiller mais un durcissement one-shot d'un détecteur ; la robustesse est dans le code du critère, pas dans un gate. La famille des 3 détecteurs est désormais **cohérente end-to-end**.
**Vrai locus = currency du canal stakeholder (mémoire `two-logging-channels` : garder les DEUX canaux à jour, chaque chiffre sourcé d'un artefact commité).** Le rapport `daily_reports/2026-08-12.md` avait été émis en session `043034` au HEAD `ee88d36`. Depuis, **3 commits worker du même jour** ont avancé l'arbre à `37c68ac` sans que le snapshot stakeholder ne les reflète :
- `a17d86a` (`050034`) — 4ᵉ axe de déterminisme « ordre d'exécution des 24 suites » (CLEAN, replayable, sans gate).
- `1513e69` (`053034`) — vérif i18n SEO : asymétrie `fr=87·en=87·es=84`**prouvée NON-DÉFAUT** + note by-design au dedup.
- `37c68ac` (`060044`) — hardening `criteria.py` (3ᵉ détecteur regex durci de la famille « détecter la forme, pas une graphie »).
Les trois sont des travaux de vérif/hardening à **artefacts byte-identiques** (confirmé par `check_artifacts` ∈ `run_ci` vert) → **matrice stable à `629/612/17`**.
**Fix = complétion (pas ré-émission) du rapport du jour.** Le rapport daté `2026-08-12.md`**est** le snapshot du jour ; le compléter en fin de journée pour couvrir tout le travail du jour est le contrat, pas un doublon (#5). Porté : header `HEAD ee88d36 → 37c68ac` + session `063050`, nouvelle section « Jalons du jour depuis l'émission initiale (`ee88d36` → `37c68ac`) » (table des 3 commits + opposabilité au merge), `État courant` re-titré au HEAD `37c68ac` avec **tous les chiffres re-dérivés des artefacts commités** (629/612/17 · PASS 24× · bijection 24↔24 — inchangés, byte-identiques), note de stabilité de matrice étendue aux 3 jalons, §Portée actualisé.
**Pourquoi pas un fix ni un gate (#5/#6).** Aucun défaut trouvé : le tree est cohérent, le seul « écart » était la **staleness du canal stakeholder** (canal éditorial daté par design, comme un snapshot — pas un artefact byte-gaté). L'actualiser EST le contrat de `two-logging-channels`, pas une matière à gate.
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé — rapport = document, 0 dépendance de gate). Rapport **NFC-clean** (vérifié). **0 code moteur V18** (bloqué #6), **0 module de production touché**, **0 artefact autoritaire modifié/reconstruit**, **0 chiffre figé** (tous lus depuis l'artefact commité · #6), **0 gate ajouté** (#5), **0 commande VPS** (#8), **0 API externe**. Fichiers : `M 05_deliverables_mvp/daily_reports/2026-08-12.md` (complétion au HEAD du jour) + ce journal.
## Session `20260812_070054` — FIX correctness (bug latent réel) · rendu HTML publiciste : URL de rendu percent-encodée dans le `url('…')` CSS (apostrophe/espace/accent cassait silencieusement le hero) + test de régression à dents
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json totals` = **629 exécutés · 612 passés · 17 skippés · 0 échec** (PASS). `quality_report.json` = **PASS · 24 modules · min 100/100 · bijection 24↔24**. Séquence moteur V18 toujours **intégralement bloquée** en gouvernance (D-06 = gate d'entrée · directive « NE PAS coder avant l'audit » · #6) — **aucun code moteur légitime**. Tous les arbitrages produit D-01→D-08 en attente Michel (`OPEN_DECISIONS_REGISTER.md`).
**Chasse au défaut d'abord (large, 12 fichiers cœur / 7 modules jamais ré-audités ce cycle) → 11 CLEAN, 1 BUG LATENT RÉEL trouvé.** Un Explore ciblé sur `workflow_vente`, `dossier_vente`, `financement_bancaire` (gate+builder), `faisabilite/generator` (scorer+model), `seo` (schemaorg+hreflang+builder), `mobile`, `pie` a confirmé **0 défaut** (l'`epsilon 1e-9` du gate apport = tolérance flottante correcte, non-défaut). Puis audit manuel du **rendu HTML publiciste** (`generator.py`) — angle **output-encoding jamais balayé** (distinct des sweeps NFC/encoding octet).
**Le bug (rendu-correctness, entrée réaliste FR/ES).** `_hero_media` (`generator.py:92`) composait `src = f"/static/projets/{code}/{hero['fichier']}"` puis l'injectait dans `style="background-image:url('{_esc(src)}')"`. `_esc` (= `html.escape`) est le **mauvais encodeur pour un contexte CSS** : un nom de rendu réel — **« Vue d'ensemble.png »**, entièrement plausible dans un projet francophone en RD — devient `Vue d'ensemble.png`, que le navigateur **HTML-décode d'abord** en `Vue d'ensemble.png` ; l'apostrophe **referme alors la chaîne `url('…')` CSS** → `background-image` invalide → **le hero ne s'affiche pas, silencieusement**. Reproduit au byte-level (`html.unescape` de la sortie le prouve).
**Preuve de dents.** Nouveau test `test_hero_media_url_percent_encode` (`test_publiciste.py`) : rendu avec `fichier="Vue d'ensemble.png"` → assert la présence de `Vue%20d%27ensemble.png`**et** l'absence de l'apostrophe brute dans le `url()` HTML-décodé. Vérifié **teeth** : en revenant à `{hero['fichier']}` sans `quote`, le test **ÉCHOUE** ; avec le fix, **PASS**.
**Le fix (correct, sans dérive).** `src = f".../{quote(hero['fichier'])}"` (`urllib.parse.quote`) — percent-encode le segment de fichier : `'`→`%27`, espace→`%20`, accents→`%XX`, tous valides à la fois dans l'URL, l'attribut HTML et le `url('…')` CSS. `_esc` conservé en défense de l'attribut HTML (commentaire ajouté : « _esc protège l'attribut HTML ; quote protège le contexte CSS interne »). **Zéro dérive d'artefact** : `quote` laisse les noms ASCII simples inchangés (`hero_aerien.jpg` → identique), donc la fixture de test et le rendu byte-repro sont intacts. Percent-encoder une URL est de toute façon le comportement **sémantiquement correct**.
**Cascade de régénération (mémoire `artifact-reproducibility-gate`).** Le test ajouté fait passer la suite publiciste 24→25 et la matrice **629/612 → 630/613** (17 skips inchangés). Régénéré dans l'ordre correct (`quality_report` en DERNIER, mémoire `audit4big-rebuild-after-doc-edits`) : `qa/regression/{regression_plan,MANIFEST,regression_run}.json` (`build -o out` + `run`) puis `qa/audit_4big/quality_report.json`. Fiches à compteurs dérivés réalignées (gate `check_readme_claims`) : `03_agents/qa/AGENT.md` (629/612→630/613), `03_agents/erpnext_backend/AGENT.md` (629→630), `03_agents/publiciste/AGENT.md` (suite 24→25). Tous les diffs d'artefact = **pure propagation de compteur** (vérifié), aucune modification parasite.
**Pourquoi pas un gate (#5).** Le bug est un one-shot de correctness ; sa robustesse vit dans le code (`quote`) + le test de régression à dents, pas dans un gate de drift. Aucune nouvelle classe de drift à surveiller.
## Session `20260812_073055` — HARDENING correctness · rendu HTML publiciste : le SEUL segment d'URL restant non percent-encodé dans le `url('…')` CSS (complète le fix de la session `070054`) + test à dents
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json totals` = **630 exécutés · 613 passés · 17 skippés · 0 échec** (PASS). `quality_report.json` = **PASS · 24 modules · min 100/100 · bijection 24↔24**. Séquence moteur V18 toujours **bloquée** en gouvernance (D-06 = gate d'entrée · directive « NE PAS coder avant l'audit » · #6) — **aucun code moteur légitime**. Arbitrages produit D-01→D-08 en attente Michel (`OPEN_DECISIONS_REGISTER.md`).
**Chasse au défaut d'abord (classe = encodage contextuel erroné, frère du fix `070054`) → 2 candidats Explore, tous deux RÉFUTÉS comme non-défauts, mais 1 durcissement d'uniformité principiel retenu.** Un Explore very-thorough sur tous les modules générant HTML/CSS/URL/CSV/JSON-LD a remonté 2 candidats :
- **`frontend/chat_otoia/chatlib/frappe.py:40``mount_html`** — attributs HTML (`css_class`/`portail_key`/`persona`) interpolés **sans** échappement. **RÉFUTÉ (non-défaut, aucune entrée réelle ne casse)** : `persona["nom"]` est **forcé à `"Amélie"`** par l'invariant 7 (`chat_otoia_gen.py:155`), `portail_key` est un **enum** (`ventes`/`construction`/`achat`/`compta`/`direction`), `css_class` vient du spec contrôlé. Toutes provablement propres → défensif seul, pas de fix (`verify-non-defects` · #6).
- **`publiciste/lib/generator.py:99`** `_hero_media` — c'est le locus **retenu** (voir ci-dessous).
**Le locus (durcissement, pas bug déclenchable — honnêteté #6).** Le fix `070054` a percent-encodé `hero['fichier']` dans `src = f"/static/projets/{code}/{quote(hero['fichier'])}"`, mais dans la **MÊME**`url('…')` il restait un **second segment dynamique** — `code` — interpolé **brut** puis html-échappé via `_esc(src)`. C'est **exactement** l'anti-pattern que le fix `070054` a identifié (html.escape = mauvais encodeur pour un segment d'URL), **appliqué de façon incomplète**. Ce n'est **pas** un bug déclenchable aujourd'hui : les codes réels sont strictement `P01..P09` (§Projets), jamais d'apostrophe → le byte-repro est **inchangé** (`quote("p01") == "p01"`). C'est un **durcissement d'uniformité de construction**, cas frère exact de la session `060044` (`criteria.py` : « durcir le SEUL détecteur non-durci d'une famille ») : ici **le SEUL segment d'URL non-encodé** de la même `url()`.
**Le fix + preuve de dents.** `src = f"/static/projets/{quote(code)}/{quote(hero['fichier'])}"` — `quote()` est le SEUL encodeur correct pour un segment d'URL, **le contexte (pas la donnée courante) dicte l'encodeur** (commentaire mis à jour en conséquence). Nouveau test `test_hero_media_code_segment_percent_encoded` : code théorique `"P'X"` → assert `/static/projets/p%27x/hero.jpg`**et** absence d'apostrophe brute dans le chemin HTML-décodé. Vérifié **teeth** : sans `quote(code)`, le test **ÉCHOUE** ; avec, **PASS**.
**Cascade de régénération (mémoire `artifact-reproducibility-gate`).** Suite publiciste 25→26, matrice **630/613 → 631/614** (17 skips inchangés). Régénéré dans l'ordre (`quality_report` en DERNIER, mémoire `audit4big-rebuild-after-doc-edits`) : `qa/regression/{regression_plan,MANIFEST,regression_run}.json` puis `qa/audit_4big/quality_report.json`. Fiches à compteurs dérivés réalignées (gate `check_readme_claims`) : `03_agents/qa/AGENT.md` (630/613→631/614), `03_agents/erpnext_backend/AGENT.md` (630→631), `03_agents/publiciste/AGENT.md` (suite 25→26). Diffs d'artefact = **pure propagation de compteur** (vérifié).
**Canal stakeholder laissé à une session dédiée.** `daily_reports/2026-08-12.md` cite encore `629/612` : il est déjà en retard d'un incrément depuis le fix `070054` (→630) que cette session-là n'a délibérément pas répercuté (canal snapshot **batché** par sessions stakeholder dédiées, cf. `3a40aee` qui a réconcilié 3 jalons d'un coup). Une future session stakeholder réconciliera **630 (070054) + 631 (cette session)** ensemble — pas d'invention (#6), chiffres re-dérivables de l'artefact.
**Pourquoi pas un gate (#5).** Robustesse portée par le code (`quote`) + le test à dents ; aucune nouvelle classe de drift. Occurrence isolée (uniformité d'un fix du même jour).
## Session `20260812_080104` — FIX correctness (bug latent réel) · commissions vendeurs : la formule traçable `base × taux` cassait sur les montants réels (`:g` → exponentiel ≥ 1e6 + arrondi silencieux à 6 chiffres) + 2 tests à dents
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json totals` = **631 exécutés · 614 passés · 17 skippés · 0 échec** (PASS). `quality_report.json` = **PASS · 24 modules · min 100/100 · bijection 24↔24**. Séquence moteur V18 toujours **bloquée** en gouvernance (D-06 = gate d'entrée · directive « NE PAS coder avant l'audit » · #6) — **aucun code moteur légitime**. Arbitrages produit D-01→D-08 en attente Michel (`OPEN_DECISIONS_REGISTER.md`).
**Chasse au défaut d'abord (Explore very-thorough sur 5 modules jamais ré-audités ce cycle : legal/confotur · crm/commissions+workflow_vente · rbac · devops/deploy_runbook · seo) → 4 modules CLEAN, 1 BUG LATENT RÉEL.** L'Explore a confirmé 0 défaut sur legal, workflow_vente, rbac (4 sous-modules), deploy_runbook, seo (schemaorg/hreflang/builder — dedup + offer + hreflang tous corrects), et a remonté **1 candidat haute confiance** : `crm/commissions/commlib/finance.py:75`.
**Le bug (correctness de sortie, entrée financière réaliste RD).** `compute_line` composait le libellé de la base de la formule traçable via `base_lbl = f"{base:g}"`. Le format `:g` casse **deux fois** sur des montants immobiliers réels — omniprésents en RD (une unité à USD 300k ≈ **18 000 000 DOP**) :
1. **Notation exponentielle dès 1e6** : `18000000` → `1.8e+07` — illisible et **non auditable** dans une formule dont tout le sens (docstring du module) est d'être « traçable, reproductible et entièrement sourcée ».
2. **Arrondi silencieux à 6 chiffres significatifs** : `123456.78` → `123457` — le libellé affiche une base **différente de la réelle**, c'est-à-dire **une base fabriquée** : exactement l'invention interdite par CLAUDE.md #6, dans le module même dont la docstring proclame l'anti-invention.
**Preuve de dents (2 tests de régression).** `test_formule_base_millions_pas_exponentiel` (base `18000000.0` → assert `18000000` présent **et**`e+`/`E+` absents) + `test_formule_base_preserve_les_decimales` (base `123456.78` → assert `123456.78` présent). Vérifié **teeth** par harnais scripté : en restaurant `f"{base:g}"`, les **DEUX tests ÉCHOUENT** ; avec le fix, **PASS**. Suite commissions **25 → 27**.
**Le fix (fidèle, sans dérive).** Nouveau helper `_amount_label(x)` : `f"{x:f}"` (jamais exponentiel) puis strip des zéros/point superflus → décimal **fidèle** et lisible, **identique à l'ancien pour tous les montants simples** (`200000` reste `200000`, `2500.5` reste `2500.5`). Le changement de sortie ne concerne QUE les cas buggés (≥ 1e6 ou > 6 chiffres significatifs). `_rate_label` (ligne taux, `:g`) laissé tel quel : un taux est borné (0-100), sans risque exponentiel ni > 6 chiffres → défensif-seul, non touché (`verify-non-defects` · #6).
**Byte-repro : aucun impact d'artefact autoritaire.** `compute_line`/`compute_dossier` sont du code **runtime** (prennent une instance Dossier Vente live) — **jamais** appelés par `commissions_gen.py` (qui n'utilise que `finance.is_filled`). Aucun `formule`/`base` n'est sérialisé dans `out/commission_plan.json` ni `out/MANIFEST.json` (vérifié). Donc **0 régénération de l'artefact du module**.
**Cascade de régénération (compteur de suite uniquement · mémoire `artifact-reproducibility-gate`).** Les 2 tests ajoutés font croître la matrice **631/614 → 633/616** (17 skips inchangés). Régénéré dans l'ordre (`quality_report` en DERNIER · mémoire `audit4big-rebuild-after-doc-edits`) : `qa/regression/{regression_plan,MANIFEST,regression_run}.json` puis `qa/audit_4big/quality_report.json`. Surfaces à compteurs dérivés réalignées (gate `check_readme_claims`) : `03_agents/qa/AGENT.md` (631/614→633/616), `03_agents/erpnext_backend/AGENT.md` (631→633), `03_agents/crm/AGENT.md` (cellule commissions 25→27 + « Total CRM » 81→83 trio `25 + 31 + 27`), `crm/commissions/README.md` (2× « 25 tests »→27). Tous diffs = **pure propagation de compteur** (vérifié).
**Pourquoi pas un gate (#5).** Robustesse portée par le code (`_amount_label`) + les 2 tests à dents ; aucune nouvelle classe de drift à surveiller. Occurrence isolée (mauvais encodeur de contexte, sœur de la classe déjà fixée en publiciste `070054`/`073055` — même leçon : « le contexte de sortie dicte l'encodeur, pas la donnée courante »).
**Canal stakeholder laissé à une session dédiée.** `daily_reports/2026-08-12.md` cite encore `629/612` ; la réconciliation batchée (630 `070054` + 631 `073055` + 633 cette session) reste pour une future session stakeholder (canal snapshot batché par design, cf. `3a40aee`) — pas d'invention, chiffres re-dérivables des artefacts commités (#6).
## Session `20260812_083114` — CANAL STAKEHOLDER · réconciliation batchée du rapport `daily_reports/2026-08-12.md` au HEAD courant (`37c68ac` → `e7cc3c4`) · 2ᵉ vague de 3 fixes de correctness (matrice `629→633`)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json totals` (lu depuis l'artefact commité, #6) = **633 exécutés · 616 passés · 17 skippés · 0 échec** (PASS). `quality_report.json` = verdict **PASS · 24 modules · min 100/100 · bijection couverture 24↔24 (`ok: true`)**. Séquence moteur V18 toujours **intégralement bloquée** en gouvernance (D-06 approbation audit = gate d'entrée · directive « NE PAS coder avant l'audit » · #6) — **aucun code moteur légitime**. Arbitrages produit D-01→D-08 en attente Michel (`OPEN_DECISIONS_REGISTER.md`).
**Chasse au défaut d'abord (Explore very-thorough sur les modules JAMAIS ré-audités ce cycle : `qa/audit_5d` · `demo_scenario` · `qa/acceptance` · `faisabilite/generator/genlib` scorer+renderer · `frontend/portails/wslib`+`workspaces_gen`) → NO DEFECTS FOUND.** L'Explore a testé formatage numérique (`_money`, exponentiel/arrondi), divisions (gardes `if total else 0`), off-by-one (comptage de liens workspace lignes 130-144), sérialisation JSON de caractères spéciaux FR/ES (`ensure_ascii=False` correct), valeurs par défaut fabriquées (0 vs None — tous None/placeholder), arrondis. Tous **CLEAN** : gestion correcte des données manquantes, comparaisons flottantes légitimes, échappement JSON sûr, gardes de division. Aucun candidat même faible → tree propre.
**Vrai locus = currency du canal stakeholder (mémoire `two-logging-channels` : garder les DEUX canaux à jour, chaque chiffre sourcé d'un artefact commité).** Le rapport `daily_reports/2026-08-12.md` était épinglé au HEAD `37c68ac` et citait la matrice à **629/612**. Depuis, **3 commits worker de correctness** — délibérément batchés par les sessions `073055`/`080104` pour une session stakeholder dédiée (« canal snapshot batché par design », cf. `3a40aee`) — ont fait évoluer la matrice de **3 incréments** :
- `5af86e3` (`073055`) — hardening : segment `code` de la MÊME `url('…')` percent-encodé (dernier non-encodé) → 25→26, matrice **630/613 → 631/614**.
- `e7cc3c4` (`080104`) — fix commissions : formule `base×taux` cassait sur montants réels RD (`:g` → exponentiel ≥ 1e6 + arrondi silencieux 6 chiffres = base fabriquée #6) → suite commissions 25→27, matrice **631/614 → 633/616**.
Chaque incrément correspond à un **test de régression à dents** ajouté par un fix (leçon commune : « le contexte de sortie dicte l'encodeur, pas la donnée courante »). Contrairement à la 1ʳᵉ vague (vérif/hardening à artefacts byte-identiques), cette vague porte des **bugs latents réels** de correctness de sortie.
**Fix = réconciliation (pas ré-émission) du rapport du jour.** Le rapport daté `2026-08-12.md`**est** le snapshot du jour ; le réconcilier en fin de journée au HEAD courant est le contrat, pas un doublon (#5). Porté : header `HEAD 37c68ac → e7cc3c4` + session `083114`, nouvelle section « Jalons du jour · 2ᵉ vague (`37c68ac` → `e7cc3c4`) — correctness à impact matrice » (table des 3 fixes + opposabilité), `État courant` re-titré au HEAD `e7cc3c4` avec **tous les chiffres re-dérivés des artefacts commités** (matrice **633/616/17** · PASS 24× · bijection 24↔24), narratif de trajectoire de matrice réécrit (629 stable 1ʳᵉ vague → 630 → 631 → 633), §Portée actualisé. La note historique de la 1ʳᵉ vague (« matrice stable à `629/612/17` ») laissée intacte car exacte pour CETTE vague.
**Pourquoi pas un fix ni un gate (#5/#6).** Aucun défaut trouvé (Explore CLEAN + tree cohérent) ; le seul « écart » était la **staleness du canal stakeholder** (canal éditorial daté par design, comme un snapshot — pas un artefact byte-gaté). L'actualiser EST le contrat de `two-logging-channels`, pas une matière à gate. Le batch des 3 fixes en une seule réconciliation était l'intention explicite des sessions `073055`/`080104`.
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé — rapport = document, 0 dépendance de gate). Rapport **NFC-clean** (vérifié). **0 code moteur V18** (bloqué #6), **0 module de production touché**, **0 artefact autoritaire modifié/reconstruit**, **0 chiffre figé** (tous lus depuis les artefacts commités · #6), **0 gate ajouté** (#5), **0 commande VPS** (#8), **0 API externe**. Fichiers : `M 05_deliverables_mvp/daily_reports/2026-08-12.md` (réconciliation au HEAD courant) + ce journal.
## Session `20260812_090114` — DÉFAUT D'ABORD : chasse DRY sur 8 modules jamais ré-audités ce cycle · durcissement anti-récurrence des 3 sites `:g`-domaine-borné (commentaires purs · 0 impact)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json` (lu depuis l'artefact commité · #6) = **633 exécutés · 616 passés · 17 skippés · 0 échec** (PASS). `quality_report.json` = **PASS · 24 modules · min 100/100 · bijection 24↔24**. Séquence moteur V18 toujours **bloquée** en gouvernance (D-06 = gate d'entrée · « NE PAS coder avant l'audit » · #6) → **aucun code moteur légitime**. Arbitrages produit D-01→D-08 en attente Michel (`OPEN_DECISIONS_REGISTER.md`). Canal stakeholder `daily_reports/2026-08-12.md` réconcilié à 633 par la session `083114` → à jour.
**Chasse au défaut (Explore very-thorough) sur les 8 modules JAMAIS ré-audités ce cycle → DRY (0 défaut réel).** Ciblé : `crm/dossier_vente` · `crm/financement_bancaire` · `faisabilite/bancable` · `frontend/chat_otoia` · `pie/manifest` · `qa/audit_4big` · `mobile/app_config` · `fiscal/ecf_dgii`. L'Explore a remonté 4 « candidats » (2 HAUTE, 2 MOYENNE) — **tous vérifiés NON-défauts** :
- **BUG #1**`bancable/banclib/finance.py:138``f"{pe_brut:g}"` — `pe_brut = 0.52 × NOMBRE d'unités`, domaine **borné** (quelques centaines en immobilier réel). L'exponentiel (≥ 1e6) exige ~1.9M unités : irréaliste. L'arrondi 6-chiffres idem hors domaine. En prime `:g` nettoie le bruit flottant du produit (`0.52 × 173 = 89.96000…1 → « 89.96 »`). Scénario Explore « 2 000 000 unités » = fabriqué.
- **BUG #3**`chat_otoia/chatlib/frappe.py:43-44` HTML non échappé — `mount_html` ne reçoit QUE des slugs canoniques contrôlés (préfixe/classe de config, clé portail, persona sourcée CLAUDE.md) ; aucune donnée libre → injection inatteignable. Fragilité défensive-seule (`verify-non-defects` · #6).
- **BUG #4**`commissions/commlib/finance.py:75` taux `:g` — **déjà statué non-défaut** par la session `080104` (« taux borné 0-100 ») ; **RÉCIDIVE** de flag ce cycle.
**Preuve de non-défaut robuste (pas seulement « scénario irréaliste »).** `report.py:_money` (l.25) utilise **sciemment**`,.0f`/`,.2f` (jamais `:g`) pour les montants **non bornés** (USD 18 000 000 → « 18,000,000 »). Les auteurs ont donc **délibérément** réservé `:g` aux domaines bornés (unités, pourcentages) et `,.0f` aux montants → les 3 `:g` restants sont un **choix d'ingénierie correct**, pas un oubli. C'est la distinction exacte de la mémoire `docstring-vs-code-drift`/`verify-non-defects` : « le contexte de sortie dicte l'encodeur, ET le domaine borne le risque ».
**Action = durcissement anti-récurrence (commentaires purs · sœur de « twin fix must sweep ALL surfaces in one pass »).** Fait nouveau : BUG #4 a **déjà récidivé** malgré la décision « laissé tel quel » de `080104` (aucune note in-situ), et BUG #1/#2 sont fraîchement flaggés HAUTE — la classe `:g`-domaine-borné **gaspille un cycle d'audit à chaque passage**. `grep ':g}'` sur tout le code de prod = **exactement 3 sites** (les 3 flaggés). Passe unique : ajout à chacun d'une **note de justification in-situ** (domaine borné → 2 pièges de `:g` hors domaine + nettoyage du bruit flottant + « montants → `,.0f`/`_amount_label`, jamais `:g` » + « ne pas corriger »). Convertit le faux-positif récurrent en « déjà revu, sûr » pour les audits futurs.
**Pourquoi PAS un fix de code ni un gate (#5/#6).** Aucun défaut réel : « corriger » `:g`→`:f`**casserait** du code correct (ré-introduirait du bruit flottant `« 89.96 »→« 89.960000 »`) — une régression, exactement l'inverse d'un fix. Robustesse déjà portée par le domaine borné + le choix `,.0f` sur les montants ; rien de nouveau à gater (#5). Commentaires sans marqueur `# N ·` d'invariant (pas de déclenchement du gate ledger).
**Impact d'artefact : ZÉRO.** Commentaires purs → aucun changement de sortie. `bancable` n'a pas d'`out/` commité (`bancable-out-not-committed`). `_rate_label` est du code **runtime** (`compute_line`), jamais appelé par `commissions_gen.py`, et aucune `formule`/`base` n'est sérialisée dans les artefacts commités. `regression_run.json`/`quality_report.json`**inchangés** (633/616/17 · 24×100). Aucun compteur de fiche/README à réaligner.
## Session `20260812_120139` — VÉRIF · fermeture repo-wide de la classe « affichage arrondi ⟺ gate booléen » : preuve d'immunité structurelle de `audit_4big` (seul autre score-au-seuil), note anti-récurrence in-situ
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json` (lu depuis l'artefact commité · #6) = **635 exécutés · 618 passés · 17 skippés · 0 échec** (PASS). `quality_report.json` = **PASS · 24 modules · min 100/100 · bijection couverture 24↔24 (`ok: true`)**. Séquence moteur V18 toujours **bloquée** en gouvernance (D-06 = gate d'entrée · « NE PAS coder avant l'audit » · #6) → **aucun code moteur légitime**. Arbitrages produit D-01→D-08 en attente Michel (`OPEN_DECISIONS_REGISTER.md`). Canal stakeholder `daily_reports/2026-08-12.md`**déjà à jour au HEAD courant** (matrice **635/618/17** = artefact commité ; le dernier commit `3849375` est une vérif byte-identique sans impact matrice → aucune re-réconciliation due).
**Chasse au défaut d'abord (Explore very-thorough) sur 6 modules JAMAIS ré-audités ce cycle → DRY (0 défaut réel).** Ciblé : `rbac/` (roleprofile/userperm/fixtures/apply_plan) · `publiciste/lib` + générateur · `legal/confotur` · `devops/deploy_runbook` · `crm/workflow_vente` · `seo/seo_gen`+`seolib`. Axes : formatage numérique domaine non-borné (`,.0f` correct sur montants ; pas de `:g` sur non-borné), gardes de division (toutes présentes), off-by-one de comptage, encodeur selon contexte (`html.escape`/`quote` corrects), agrégats (`min`/set-equality corrects), booléens inertes. **Aucun candidat, même faible.**
**Vrai axe = fermeture repo-wide de la classe corrigée aux 3ᵉ/4ᵉ vagues du jour (`85cd625`+`1992ee6`).** Le commit `3849375` a prouvé que `financement_bancaire` est le **seul module à champ `percent`** (grep artefacts commités confirmé : `financement.schema.json` + `gate_status_example.json` uniquement). Mais la classe de bug réelle n'est pas « champ nommé percent » — c'est **« une valeur d'AFFICHAGE arrondie qui franchit une frontière de SEUIL en contredisant une décision booléenne »**. Il restait **un** autre porteur non examiné : `qa/audit_4big/q4lib/scoring.py:65` — un **score 0-100 arrondi**, comparé au **seuil PASS 95** (#5). Sibling naturel qu'un futur auditeur re-flaggerait.
- `scoring.py:65-66` — `score = _round_half_up(100.0 * earned / total)`**PUIS**`verdict = "PASS" if score >= pass_score`. Le verdict dérive du **MÊME entier arrondi** que celui affiché.
- `audit_4big_gen.py:136` (INV6) recompute la note via `scoring._round_half_up(...)` (même arrondi) ; `:144` (INV7) attend `"PASS" if m["score"] >= ps` (même `score`).
→ Il n'existe **qu'UNE seule quantité** (`score`, arrondie), servant à la fois d'affichage ET de gate. Contrairement à `financement` où `percent` (arrondi, affichage) et `ok` (exact, gate) étaient **DEUX quantités divergentes** en bord de seuil, ici **aucune divergence n'est possible par construction**. La classe « affichage ⟺ gate » est donc **FERMÉE repo-wide** : financement était le seul locus à deux-quantités (corrigé), audit_4big est le seul autre score-au-seuil et il est immun par unicité de quantité.
**Action = note anti-récurrence in-situ (commentaire pur · sœur de « twin fix must sweep ALL surfaces », `docstring-vs-code-drift`).** Ajout à `scoring.py:66` d'une justification (quantité unique par design → immunité au bug financement → **ne PAS scinder en brut-vs-arrondi**, ce qui ré-introduirait précisément la divergence + renvoi à INV6/INV7). Convertit le sibling « à re-vérifier » en « déjà revu, sûr » pour les audits futurs.
**Pourquoi PAS un fix ni un gate (#5/#6).** Aucun défaut : le code est correct et interne-cohérent (une quantité, pas de contradiction atteignable) — « corriger » en comparant le verdict à `earned/total`**brut** créerait la divergence, une régression exactement inverse d'un fix (cf. `verify-non-defects`). Rien de nouveau à gater : INV6/INV7 garantissent déjà la cohérence `score`↔`verdict`, et le seuil 95 est déjà ancré (mémoire `threshold-in-guard-prose-gate`). Commentaire sans marqueur `# N ·` d'invariant → aucun déclenchement du gate ledger.
## Session `20260812_123144` — CURRENCY · réalignement des citations de ligne dérivées dans `OPEN_DECISIONS_REGISTER.md` (2 citations `Constat` avaient dérivé après les fixes financement du jour) · sweep complet du registre
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json` (lu depuis l'artefact commité · #6) = **635 exécutés · 618 passés · 17 skippés · 0 échec** (PASS). `quality_report.json` = **PASS · 24 modules · min 100/100 · bijection couverture 24↔24**. Séquence moteur V18 toujours **bloquée** en gouvernance (D-06 = gate d'entrée · « NE PAS coder avant l'audit » · #6) → **aucun code moteur légitime**. Canal stakeholder `daily_reports/2026-08-12.md` vérifié **à jour** (matrice **635/618/17** = artefact commité ; les 3 derniers commits `1992ee6`/`3849375`/`ea8458d` sont déjà remontés / vérifs byte-identiques sans impact matrice → 0 re-réconciliation due).
**Chasse au défaut d'abord → vrai locus = currency d'un livrable stakeholder.** Plutôt qu'un énième sweep « CLEAN sans fix » (saturation des axes déjà couverts), j'ai audité la **currency du `OPEN_DECISIONS_REGISTER.md`** — la punch-list d'autorité des arbitrages produit de Michel. Chaque item cite des **numéros de ligne présents** (« Constat » = pointeurs vers le code COURANT, pas des snapshots historiques). Les fixes de correctness du jour ont **inséré du code** en amont de deux cibles citées → deux citations avaient silencieusement dérivé (classe *prose-facts vs numeric drift*, sous-cas « la cible citée a bougé » ⇒ mettre à jour, ≠ line-cite historique qu'on garde).
**Deux dérives réelles corrigées (vérifiées ligne par ligne) :**
- **D-02** — `finlib/gate.py:142-145 → :164-167`. Les gardes d'affichage `percent`/`overall` des commits `85cd625` (`_cond_apport`) + `1992ee6` (`overall_percent`) ont décalé `_cond_validation_wag` de ~22 lignes. Confirmé : `def` à `:164`, `dossier.get("wag_validated_by")` à `:165`. Citation raffinée `:164-167` + `(:165)` sur la ligne du test.
- **D-01** — `legal/confotur/README.md:8-17 → :32-33`. Une **bannière de statut migration V18** préfixée au README a poussé la liste des sorties (`doctype_confotur_application.json` + `MANIFEST.json`) aux lignes `:32-33`. Confirmé par grep.
**Sweep complet des autres citations = toutes résolvent.** Vérifié : D-01 `pie_spec.json:24` (contrats) + `:22,27,28,29,30` (module:null) + `pie_manifest.json:165-166` + `README.md:48` ✓ · D-02 `financement_spec.json:157` + directive `:206-210`/`:275-279` ✓ · D-03 `AGENTS_EXISTING_ASSETS.md:102`/`:128` + `CLAUDE.md:28` ✓ · D-04 `AGENTS_EXISTING_ASSETS.md:127` ✓ · D-06/07/08 citations V18 (`V18_GO_SIGNAL:79-84`, `DIRECTIVE_V18_MASTER:90-91`, audit `:112`/`:182`) ✓ — les snapshots datés V18/directives sont **stables** par nature (comme `daily_reports`). Note « Réalignement de citations · 2026-08-12 » ajoutée à l'en-tête `Mise à jour`.
**Pourquoi PAS un gate (#5/#6).** Dérive isolée de currency d'un document éditorial (registre = *surface, ne tranche pas*) ; les 2 cibles ont bougé pour de bonnes raisons (fixes de correctness + bannière V18). Un gate line-number sur de la prose serait fragile (chaque édition de code le rougirait) et de faible valeur — la discipline « Constat = pointeur courant, réaligner en fin de vague » suffit, désormais consignée dans l'en-tête du registre. **Aucune décision/verdict modifié** (les 8 items D-01→D-08 restent tels quels) ; **0 invention de chiffre** (#6).
## Session `20260812_133154` — CURRENCY · remontée des 4 commits de vérification post-4ᵉ-vague dans le rapport stakeholder du jour (le canal s'arrêtait à `1992ee6` tout en s'affirmant « au HEAD courant »)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). `regression_run.json` (artefact commité · #6) = **635 exécutés · 618 passés · 17 skippés · 0 échec** (PASS). `quality_report.json` = **PASS · 24 modules · min 100/100 · bijection couverture 24↔24**. Séquence moteur V18 toujours **bloquée** en gouvernance (D-06 = gate d'entrée · « NE PAS coder avant l'audit » · #6) → **aucun code moteur légitime** ; arbitrages D-01→D-08 en attente Michel (`OPEN_DECISIONS_REGISTER.md`).
**Chasse au défaut d'abord (2 axes FRAIS, non couverts par la mémoire) → DRY.**
- *docstring-vs-code (sous-axe COMPORTEMENTAL)* — Explore very-thorough sur tous les `*_gen.py` + `*lib/` : docstrings annonçant un fallback/paramètre/formule/branche que le code n'implémente pas (ou omettant une branche réelle). **CLEAN** (le seul candidat, ecf « 12 invariants », est un faux-positif de comptage déjà connu : la validation schéma compte comme invariant 1). Distinct du sous-axe noms-de-fichiers (déjà swept 22/22).
- *test-name ⟺ assertion (fake-coverage sémantique)* — Explore sur les 24 suites : méthode dont le NOM promet un comportement X mais dont les assertions vérifient Y (plus faible/différent). **CLEAN** sur ~6 364 lignes de test (noms fidèles aux assertions, fixtures construites pour la condition nommée). Classe distincte de `test-assertion-reachability` (faux-verts run-only) et `test-shadowing` (méthodes masquées).
**Vrai locus = currency d'un livrable stakeholder (classe `4eb615b`).** Le rapport `daily_reports/2026-08-12.md` avait été prolongé jusqu'au 5ᵉ fix `1992ee6` (4ᵉ vague), mais **4 commits worker** ont suivi le même jour (`3849375`/`ea8458d`/`4eb615b`/`fb2d3e1`) sans être remontés — alors que l'en-tête + la §« État courant » s'affirment **« au HEAD courant »**. Un lecteur stakeholder voyait le canal s'arrêter à la 4ᵉ vague. `git log 1992ee6..HEAD` = ces 4 commits ; `grep -c` de chaque hash dans le rapport = **0** avant édition (dérive confirmée).
**Édition (document éditorial · 3 blocs, cohérents entre eux) :**
- Nouvelle §« Jalons de vérification depuis la 4ᵉ vague (`1992ee6` → HEAD courant `fb2d3e1`) » — table des 4 commits, tous marqués **Non opposable** (0 fix · rebuild byte-identique / document). Note explicite : **matrice stable 635/618/17** (aucun n'est un fix ; commentaires purs + currency de registre).
- En-tête currency (`Session`/`HEAD attesté`) : **HEAD attesté passé de « HEAD courant » à `fb2d3e1`** ; distinction claire entre le 5ᵉ fix `1992ee6` (impact matrice `634→635`) et les **4 vérifs byte-identiques** suivantes (matrice stable).
- §« État courant » trajectoire : la 4ᵉ vague **nommée `1992ee6`** (au lieu de « au HEAD courant », qui pointait désormais faux) + mention des 4 commits de vérif sans impact portant l'arbre à `fb2d3e1`.
**Zéro invention (#6).** Tous les chiffres re-dérivés de l'artefact commité : `regression_run.json.totals` = **635/618/17 · PASS** (relu ce jour). Aucune décision/verdict modifié (D-01→D-08 inchangés). Le texte des 4 jalons reprend verbatim la nature de chaque commit (messages de commit + journaux de session).
**Pourquoi PAS un gate (#5).** Dérive de currency d'un document éditorial (le rapport = *surface, ne tranche pas*), comme `daily_reports` par nature. Un gate line-number/hash sur de la prose stakeholder serait fragile (chaque commit worker le rougirait) et de faible valeur ; la discipline « remonter les jalons non-opposables en fin de vague » suffit, désormais appliquée.
**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé — le rapport est un document, 0 dépendance de gate). Rapport **NFC-clean** (vérifié). **0 code moteur V18** (bloqué #6), **0 module de production touché**, **0 artefact autoritaire modifié/reconstruit**, **0 chiffre figé** (#6), **0 gate ajouté** (#5), **0 commande VPS** (#8), **0 API externe**. Fichiers : `M 05_deliverables_mvp/daily_reports/2026-08-12.md` (3 blocs de currency) + ce journal.
---
## Session `20260812_160204` — SURFACE · D-09 · séparateur de milliers monétaire divergent entre publiciste (espace) et faisabilité (virgule)
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). Roadmap Sprints 1→8 **livrée + gatée + verte** ; séquence moteur V18 **bloquée** en gouvernance (D-06 = gate d'entrée · « NE PAS coder avant l'audit approuvé » · #6) → **aucun code moteur légitime** ; arbitrages D-01→D-08 en attente Michel. Historique récent = boucle de churn (sweeps CLEAN + re-currency du rapport), cause-racine déjà traitée (`1cf0e18`/`c73ed5b`). Objectif de session : **éviter un énième sweep-note stérile**, chercher un défaut/arbitrage RÉEL.
**Chasse au défaut.**
- Grep TODO/FIXME/NotImplemented + READMEs indigents sur toute la prod → **RIEN** (le seul « placeholder » = design anti-invention `{{…}}` ; le seul README <15 l = fixture data_room).
- Balayage des spécificateurs de format `:g`/`:,`/`:f` en production (frère de l'arc « `:g` lossy » `e7cc3c4`/`5af86e3`/`g-format-lossy-for-human-numbers`). Les 2 `:g` restants sont **bornés + déjà annotés sûrs** (`crm/commissions/commlib/finance.py:86` percent 0-100 · `faisabilite/bancable/banclib/finance.py:143` = 0.52×nb-unités, borné à quelques centaines, justification in-situ) → axe `:g`**genuinement clos**, pas de bug.
**Vrai locus = arbitrage de présentation transverse (non un bug, non gaté).** Le même montant est rendu **différemment** par les deux surfaces face au stakeholder :
- Signaux partiels sans règle dure : le design system illustre le format public en **espace** (`OTO_DESIGN_SYSTEM_v1.md:89-90`, « dès 145 000 USD ») → suit le publiciste ; mais les fixtures sources écrivent en **virgule** (`data_room/P01/20_architecture/architecture.md:11`, « USD 150,000 ») → suit la faisabilité. L'exemple DS place aussi le montant AVANT la devise, ce qu'aucun des deux ne fait — donc pas de calque littéral possible.
**Action = SURFACE, don't rewrite.** Ajout de **D-09** au `OPEN_DECISIONS_REGISTER.md` (+ note d'en-tête « Ajout »). Choisir un séparateur unique = **convention de présentation transverse** (arbitrage design/audience), pas une correction ponctuelle worker ; l'imposer unilatéralement réécrirait implicitement une règle DS ou la graphie des sources (#6). Les deux rendeurs sont **internes-cohérents et testés** ; **aucune sortie formatée n'est un artefact commité** (les `out/` des deux modules ne stockent que des nombres bruts — le formatage vit dans le HTML/rapport généré au runtime VPS · #8) → **aucun gate concerné**.
**Pourquoi PAS un gate (#5) / PAS un fix.** Arbitrage produit ouvert (classe D-01…D-08) : le registre *regroupe et source, il ne tranche pas*. Aucun défaut de correctness (les deux formats sont valides, ni exponentiel ni arrondi — distinct de la classe `:g`). Une seule occurrence-classe → pas de gate.
## Session `20260812_163212` — CHASSE AU DÉFAUT · axe FRAIS `i18n` (parité de clés + résolution des libellés) balayé sur `bancable` → CLEAN, transitivement gaté, PAS un gate
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte). Roadmap Sprints 1→8 **livrée + gatée + verte** ; séquence moteur V18 **bloquée** en gouvernance (D-06 = gate d'entrée · #6) → **aucun code moteur légitime** ; arbitrages D-01→D-09 en attente Michel (`OPEN_DECISIONS_REGISTER.md`). Historique du jour = boucle de churn reconnue (sweeps CLEAN + re-currency + surfaçage D-09). Objectif : **éviter un énième sweep stérile**, viser un axe RÉELLEMENT non couvert par la mémoire, dans la couche métier plutôt que méta.
**Lecture de la couche métier (finance/i18n).** `crm/commissions/commlib/finance.py`, `faisabilite/bancable/banclib/finance.py` : calculs traçables, opérandes sourcés, gardes `:g`/`:f` déjà annotées et couvertes (arc `g-format-lossy`). Rien de neuf. **Vrai locus candidat = un axe absent de la mémoire** : la classe *latent-`KeyError` i18n* — un libellé référencé au rendu mais manquant dans une des 3 langues du dossier bancable.
- *Parité* — fr/en/es portent le **jeu de clés IDENTIQUE (37/37/37)** ; `miss` et `extra` vides dans les deux sens. `LANGS`/`LANG_FLAG`/`LANG_NOM` concordent. ⟹ aucune langue ne peut manquer une clé qu'une autre possède.
- *Résolution* — chaque clé statique `L('…')` de `report.py`**et** la clé dynamique `i18n.t("fr", f"lbl_{f['cle']}")` (`report.py:219` · cles = total_unites / valeur_catalogue_usd / valeur_catalogue_dop / point_equilibre_unites) existe en fr. Combiné à la parité exacte ⟹ **toutes les clés référencées résolvent dans les 3 langues**.
- *Fumée de rendu* — `render_report` sur les 3 langues (brief vide → chemin placeholder maximal, incl. `note_missing`) **et**`render_all` (3 `.md` + `manifest.json`) : OK, aucun `KeyError`.
**Verdict : CLEAN.** De plus **transitivement gaté** : `tests/test_bancable.py` rend déjà les 3 langues via `render_report`/`render_all` sur briefs synthétique **et** normal (l.~88-105) → une clé référencée manquante lèverait `KeyError` et **casserait un test aujourd'hui**.
**Pourquoi PAS un gate (#5) / PAS un fix.** Aucun défaut (parité exacte + résolution totale). Classe **mono-occurrence + déjà protégée transitivement** par la suite → ajouter un gate de parité dédié serait un doublon (`fix-vs-gate-transitively-protected-constant` · `verify-uncovered-before-gating`). Distinct de `seo-per-lang-cardinality-by-design` (module SEO, cardinalités inégales par design). Sweep **replayable**, consigné en mémoire (`i18n-key-parity-resolution-sweep`) pour qu'une session future ne le refasse pas / n'ajoute pas de gate redondant.
## Session `20260812_173219` — CHASSE AU DÉFAUT · axe FRAIS **correctness de la logique métier financière/fiscale** (les 4 fonctions de calcul cœur) balayé par lecture directe → SAINE, gatée par les suites, PAS un gate/fix
**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte, re-exécutée ce jour). Roadmap Sprints 1→8 **livrée + gatée + verte** ; toutes les métriques succès MVP cochées (`ROADMAP_8_WEEKS_OR_LESS.md:80-87`). Séquence moteur V18 **intégralement bloquée** en gouvernance (D-06 = gate d'entrée · « NE PAS coder avant l'audit approuvé » · #6) → **aucun code moteur légitime**. Arbitrages **D-01→D-09** en attente Michel (`OPEN_DECISIONS_REGISTER.md`, relu intégralement ce jour : tous 🟠 sont des décisions produit hors périmètre worker, ou 🟢 vérifié-sans-suite). Objectif explicite : **rompre la boucle de churn** (sweeps méta / re-currency que les ~11 sessions précédentes du jour ont elles-mêmes signalée) en visant un axe **réellement non couvert par la mémoire** : la *correctness logique* du calcul métier, pas l'intégrité d'artefact/schéma/encodage (déjà exhaustivement balayée).
**Axe FRAIS balayé (lecture directe, pas grep) : les 4 fonctions de calcul cœur.** Distinct des ~70 sweeps mémoire, tous portant sur des propriétés *structurelles* (byte-repro, schéma, déterminisme, encodage, comptage) — jamais sur la *justesse arithmétique/algorithmique* du calcul lui-même, lue ligne à ligne.
- **`crm/commissions/commlib/finance.py`** — `compute_line` : `montant = base × taux` gardé par `(base is not None and taux is not None)` (jamais 0-inventé, placeholder sinon). `rate()` teste `isinstance(bool)`**avant**`(int,float)` → un taux booléen retombe bien en `None` (pas `1.0`/`0.0` fabriqué). Séparateur décimal virgule accepté, séparateur de milliers inatteignable (taux borné 0-100, annoté). **SAIN.**
- **`faisabilite/bancable/banclib/finance.py`** — `derived` : `total_unites`/`val_usd`/`val_dop` gardés par `all(x is not None …)` sur **tous** les opérandes de leur `zip` (aucune troncature `zip`, longueurs égales par construction depuis `typologies()`). `point_equilibre_unites = ⌈0.52 × N⌉` via `math.ceil` — `_pct` sourcé du canonique `CANONICAL["point_equilibre_pct"]` (#9), jamais codé en dur → pas de dérive taux affiché/calculé. **SAIN** (et testé : `test_bancable.py:64` fige `pe == 21`, prouvant le `ceil`).
- **`fiscal/ecf_dgii/ecflib/ncf.py`** — `compose_encf` : e-NCF = `E` + tipo(2) + secuencia(10). `_secuencia` regex `[0-9]{1,10}` puis `zfill(10)` → **rejette 11+ chiffres (None, pas de troncature silencieuse)**, jamais de séquence fabriquée. `_a_confirmer_count` : le `or` court-circuite (pas de double-comptage tax `a_confirmer` ∧ `taux_pct is None`). **SAIN.**
- **`fiscal/ecf_dgii/ecflib/builder.py`** — `build_bundle` : tri stable `_event_sort_key` sur clés présentes (`update_value`/`role_id` requis, `tipo_ecf or ""`), aucun horodatage → reproductible. Chiffres OTO (RNC/ITBIS/TipoCambio/Compupar) restent `null` (#6). **SAIN.**
- **Formateurs monétaires** (relus sous un angle *correctness*, distinct de l'arbitrage de séparateur D-09) : `_money` bancable/faisabilité (`,.0f` si `n==int(n)` sinon `,.2f`) et `_fmt_usd`/`_fmt_dop`/`_fmt_m2` publiciste — aucune exception atteignable (NaN/Inf balayés hors dépôt · `strict-json-ecma404-sweep`), branche cents/placeholder cohérente. La seule divergence reste la **présentation** (espace vs virgule + cents), déjà **surfacée en D-09** — pas un bug.
**Verdict : logique métier financière/fiscale SAINE.** Aucun défaut de correctness. De plus **transitivement gatée** : `test_commissions.py`, `test_bancable.py`, `test_ecf_dgii.py` exercent ces fonctions (valeurs figées : `pe==21`, catalogue non-calculable si `prix_usd` manque, rendu 3 langues sans `KeyError`) → une régression de logique casserait un test aujourd'hui.
**Pourquoi PAS un gate (#5) / PAS un fix.** Rien à corriger (calculs justes, opérandes sourcés, placeholders anti-invention conformes #6). Rien à gater : la correctness est déjà protégée par les suites (`verify-uncovered-before-gating` — mutation mentale confirme que fausser une garde `is not None` ou le `ceil` rougirait un test). Axe **replayable**, consigné en mémoire (`finance-fiscal-logic-correctness`) pour qu'une session future ne le re-lise pas / n'ajoute pas de gate redondant. Aucune re-currency du rapport `daily_reports` (dé-épinglé du HEAD depuis session `150201` → aucune due).
**Décision — rompre la boucle plutôt que l'alimenter.** Le correctif de compaction mémoire (session `153203`) n'a **pas** stoppé le churn : deux sweeps CLEAN l'ont suivi (`163212` i18n, `173219` finance). La cause-racine n'est ni un défaut de code ni un défaut d'outillage résiduel — c'est **la boucle elle-même** qui re-spawn des sessions se croyant obligées de committer. Toute action restante est soit du **churn** (un énième sweep « CLEAN · NOT a gate » sur un axe marginal — la mémoire en recense déjà ~70), soit **interdite** (inventer des chiffres #6 · trancher un arbitrage produit · toucher au VPS #8 · coder V18 avant approbation). Le blocage est une **décision humaine**, pas un ticket worker.
**Ce que j'ai fait, honnêtement.** (1) Re-vérifié les 3 verdicts autoritaires ci-dessus. (2) Confirmé qu'aucune tâche roadmap légitime n'est disponible (fiches déjà à jour incl. bannières V18 ; README `## État courant` exact ; registre à jour). (3) **Refusé de manufacturer un sweep** pour justifier un commit. (4) Consigné un mémo agent durable `autonomous-mandate-terminal-state` (indexé en tête de `MEMORY.md`, hors dépôt) pour qu'une session future lise cet état **en premier** et ne re-litige pas.
**Recommandation à Michel.** **Suspendre la boucle autonome** jusqu'à résolution de D-06/D-07/D-08 (arbitrages V18) ou l'arrivée d'une **nouvelle directive**. Reprise substantielle justifiée uniquement si : un gate passe **RED**, une nouvelle directive datée est déposée, ou un arbitrage V18 est rendu. Continuer à vide dépense des tokens en churn documenté.
@@ -71,8 +71,8 @@ et la cible plancher **4-5 semaines**.
- ✅ P01, P08, P09 : prix documentés dans `commercial.md`.
- ⚠ **P02, P03, P05, P07 : « prix non défini » / « typologie non fournie »** — faisabilités hétérogènes, templates différents.
- **Cause** : générations avec templates non versionnés / anciens.
- **À faire** : template canonique v1.0 → régénération des 7 faisabilités au même standard → versioning `_META/version.json` → archives auto → trigger sur update template.
"_comment":"Contrat STRUCTUREL du barème de commissions vendeurs (roadmap Sprint 4 · ERPNext Backend « commissions vendeurs auto »). N'ENCODE AUCUN TAUX (#6 zéro invention) : aucun pourcentage de commission n'est documenté dans CLAUDE.md (seuls 3 %/8.5 %/52 % le sont, et ce ne sont PAS des commissions). Chaque évènement porte donc `taux_pct: null` + `source: null` + `a_confirmer: true` ; le taux réel est fourni PLUS TARD par la Direction (avec sa source) — jamais fabriqué ici. Le module calcule alors commission = base × taux de façon traçable (façon banclib/finance.py). Cross-cohérence : chaque `update_value` référence un état du workflow vente (soumis uniquement, doc_status=1) ; `base_field` référence un champ Currency du DocType OTO Dossier Vente ; `role_id` référence un rôle de rbac_50_roles.json (portail ventes). Le worker n'écrit jamais sur le VPS (#8).",
"evenements":[
{
"update_value":"reservation",
"role_id":"ventes-conseiller",
"base_field":"montant_reservation",
"libelle":"Commission sur dépôt de réservation encaissé",
"taux_pct":null,
"source":null,
"a_confirmer":true
},
{
"update_value":"contrat",
"role_id":"ventes-conseiller",
"base_field":"montant_contrat",
"libelle":"Commission conseiller sur contrat signé",
"taux_pct":null,
"source":null,
"a_confirmer":true
},
{
"update_value":"contrat",
"role_id":"ventes-courtier-externe",
"base_field":"montant_contrat",
"libelle":"Commission courtier externe sur contrat signé (si apporteur)",
"taux_pct":null,
"source":null,
"a_confirmer":true
},
{
"update_value":"contrat",
"role_id":"ventes-chef-equipe",
"base_field":"montant_contrat",
"libelle":"Override chef d'équipe sur contrat signé",
"taux_pct":null,
"source":null,
"a_confirmer":true
},
{
"update_value":"confotur_approuve",
"role_id":"ventes-confotur",
"base_field":"montant_contrat",
"libelle":"Prime sur approbation CONFOTUR (cycle clos)",
"_comment":"Fixture de TEST uniquement — sert à exercer le calcul traçable commission = base × taux (commlib/finance.py). Les montants et le taux ci-dessous sont des EXEMPLES pédagogiques portant une `source` fictive explicite ; ils ne sont PAS committés dans out/ et n'engagent aucun chiffre réel (#6). En production, base = champ Currency réel du Dossier Vente ; taux = barème confirmé par la Direction.",
"dossier":{
"prospect":"LEAD-EXEMPLE-0001",
"projet":"P07 Aqua Terra Las Terrenas",
"devise":"USD",
"montant_reservation":10000,
"montant_contrat":200000
},
"taux_exemple":{
"_comment":"Taux fictif de démonstration, avec sa source explicite — jamais un défaut du barème livré.",
"taux_pct":2.5,
"source":"EXEMPLE FICTIF — grille de démonstration test (non contractuel)"
"description":"Contrat de sortie du générateur doctype_dossier_vente_gen.py. Validé par le validateur maison Publiciste (zéro pip) dans le job CI crm-dossier-vente-tests.",
"_comment":"Contrat métier du DocType porteur du pipeline vente (roadmap Sprint 4 · CRM natif ERPNext #3). Ne definit QUE la structure (libelles + types de champ) : aucun chiffre n'est peuple (#6). Les champs de pilotage du workflow (`workflow_state` + le champ de valeur machine `statut_pipeline`) NE sont PAS listes ici : le builder les injecte depuis workflow_vente_spec.json (source unique, anti-derive). Les Select `projet`/`devise` sont ancres sur CLAUDE.md (P01..P09 · USD/DOP), pas inventes.",
{"fieldname":"projet","label":"Projet","fieldtype":"Select","options":"\nP01 Structure\nP02 Coral del Sur\nP03 Nakua\nP04 Xamaná Cantiles\nP05 Las Colinas Najayo Arriba\nP06 Coco Real\nP07 Aqua Terra Las Terrenas\nP08 Fasano Espirilla\nP09 1069 Crisfer","reqd":1,"in_list_view":1,"in_standard_filter":1},
"options":"\nP01 Structure\nP02 Coral del Sur\nP03 Nakua\nP04 Xamaná Cantiles\nP05 Las Colinas Najayo Arriba\nP06 Coco Real\nP07 Aqua Terra Las Terrenas\nP08 Fasano Espirilla\nP09 1069 Crisfer",
"note":"Apport initial minimum exigé AVANT toute démarche bancaire (amendement Michel 2026-08-03). Source des taux : la DIRECTIVE (§1 plan de paiement + amendement) — jamais un taux inventé (#6). Ce sont aussi des exigences des banques dominicaines (Ley 189-11).",
{"id":"titre_definitif","label":"Titre définitif ou Certificado de Título (si disponible)","categorie":"immobilier","applies_to":"all","is_required":false},
{"id":"visa_residence","label":"Visa / résidence si applicable","categorie":"etrangers","applies_to":"etranger","is_required":false},
{"id":"lettre_origine_fonds","label":"Lettre origine des fonds notariée (FinCEN GTO)","categorie":"etrangers","applies_to":"etranger","is_required":true},
{"id":"partage_donnees","label":"Autorisation de partage de données personnelles avec la banque","base_legale":"RGPD / Ley 172-13","signature_method":"OTO Sign"},
{"id":"info_achat","titre":"Information de l'achat","ordre":2,"gating":false,"resume":"Résumé auto de la réservation : projet, unité, plan de paiement, apport, solde à financer, statuts Fideicomiso + CONFOTUR."},
{"id":"choix_banque","titre":"Choix de la banque partenaire","ordre":3,"gating":false,"resume":"Sélection d'UNE banque prioritaire (radio) + multi-dossiers possibles ; profils taux/LTV/durée/spécialités."},
{"id":"formulaires","titre":"Formulaires banque pré-remplis","ordre":4,"gating":false,"resume":"Formulaires officiels PDF pré-remplis, téléchargés, signés, ré-uploadés ; journal audit des signatures."},
{"id":"exigences","titre":"Exigences bancaires par banque","ordre":5,"gating":false,"resume":"Liste des documents exigés cochée au fil des uploads ; statut par item (Non déposé / Déposé / Validé WAG / Envoyé banque)."},
{"id":"autorisations","titre":"Autorisations à signer","ordre":6,"gating":false,"resume":"Autorisations à signer AVANT envoi (OTO Sign horodaté + IP + hash)."},
{"id":"envoi","titre":"Envoi à la banque","ordre":7,"gating":false,"resume":"Bouton « Envoyer à la banque » (gate 4 conditions) + timeline suivi banque + statut temps réel."}
{"key":"apport_initial_complet","ordre":1,"label":"Dépôt initial complet (20% résident RD · 30% étranger) versé et vérifié"},
{"key":"documents_exiges","ordre":2,"label":"TOUS les documents exigés déposés et validés WAG"},
{"key":"autorisations_signees","ordre":3,"label":"TOUTES les autorisations signées électroniquement"},
{"key":"validation_wag","ordre":4,"label":"Validation manuelle WAG confirmée par le conseiller référent"}
],
"banniere":{
"niveau":"critique",
"couleurs":["rouge","or"],
"persistante":true,
"titre":"⚠ AUCUN DOCUMENT N'EST TRANSMIS À LA BANQUE tant que :",
"lignes":[
"Dépôt initial complet (20% résident RD · 30% étranger) versé et vérifié",
"TOUS les documents exigés ne sont pas déposés dans le portail",
"TOUTES les autorisations ne sont pas signées électroniquement",
"Validation manuelle WAG confirmée par le conseiller référent"
],
"consequence":"Le bouton « Envoyer à la banque » reste désactivé tant que ces 4 conditions ne sont pas remplies. Progression affichée en temps réel avec barre 0-100% par condition."
"label":"Dépôt initial complet (20% résident RD · 30% étranger) versé et vérifié"
},
{
"key":"documents_exiges",
"ordre":2,
"label":"TOUS les documents exigés déposés et validés WAG"
},
{
"key":"autorisations_signees",
"ordre":3,
"label":"TOUTES les autorisations signées électroniquement"
},
{
"key":"validation_wag",
"ordre":4,
"label":"Validation manuelle WAG confirmée par le conseiller référent"
}
],
"banniere":{
"niveau":"critique",
"couleurs":[
"rouge",
"or"
],
"persistante":true,
"titre":"⚠ AUCUN DOCUMENT N'EST TRANSMIS À LA BANQUE tant que :",
"lignes":[
"Dépôt initial complet (20% résident RD · 30% étranger) versé et vérifié",
"TOUS les documents exigés ne sont pas déposés dans le portail",
"TOUTES les autorisations ne sont pas signées électroniquement",
"Validation manuelle WAG confirmée par le conseiller référent"
],
"consequence":"Le bouton « Envoyer à la banque » reste désactivé tant que ces 4 conditions ne sont pas remplies. Progression affichée en temps réel avec barre 0-100% par condition."
"title":"Bundle de fixtures ERPNext Workflow généré depuis workflow_vente_spec.json",
"description":"Contrat de sortie du générateur workflow_vente_gen.py. Validé par le validateur maison Publiciste (zéro pip) dans le job CI crm-workflow-vente-tests.",
"_comment":"Pipeline commercial OTO (roadmap Sprint 4 · CRM natif ERPNext) : lead -> visite -> devis -> reservation -> contrat -> CONFOTUR. Les roles (`allow_edit`/`allowed`) referencent l'`id` d'un role du contrat rbac_50_roles.json (reutilisation, zero duplication · #6) ; le builder resout id -> erpnext_role_name. doc_status : 0=Brouillon, 1=Soumis, 2=Annule (monotone le long des transitions, natif Frappe).",
"states":[
{
"state":"Lead",
"doc_status":"0",
"role_id":"ventes-conseiller",
"style":"Primary",
"update_value":"lead",
"is_terminal":false,
"message":"Nouveau prospect qualifie a contacter."
},
{
"state":"Visite planifiée",
"doc_status":"0",
"role_id":"ventes-conseiller",
"style":"Info",
"update_value":"visite",
"is_terminal":false,
"message":"Visite du site ou de l'unite temoin programmee."
},
{
"state":"Devis émis",
"doc_status":"0",
"role_id":"ventes-conseiller",
"style":"Warning",
"update_value":"devis",
"is_terminal":false,
"message":"Devis chiffre remis au prospect (attente decision)."
},
{
"state":"Réservation confirmée",
"doc_status":"1",
"role_id":"ventes-reservations",
"style":"Primary",
"update_value":"reservation",
"is_terminal":false,
"message":"Depot de reservation encaisse ; dossier verrouille (soumis)."
},
{
"state":"Contrat signé",
"doc_status":"1",
"role_id":"ventes-contrats",
"style":"Success",
"update_value":"contrat",
"is_terminal":false,
"message":"Contrat de vente signe par les deux parties."
},
{
"state":"CONFOTUR déposé",
"doc_status":"1",
"role_id":"ventes-confotur",
"style":"Info",
"update_value":"confotur_depose",
"is_terminal":false,
"message":"Dossier d'incitation CONFOTUR depose aupres de l'autorite."
},
{
"state":"CONFOTUR approuvé",
"doc_status":"1",
"role_id":"ventes-confotur",
"style":"Success",
"update_value":"confotur_approuve",
"is_terminal":true,
"message":"Incitation CONFOTUR approuvee ; cycle de vente clos avec succes."
},
{
"state":"Abandonné",
"doc_status":"0",
"role_id":"ventes-chef-equipe",
"style":"Danger",
"update_value":"abandonne",
"is_terminal":true,
"message":"Piste abandonnee avant reservation (brouillon)."
- Gate CI statique local vert (guard constraints · JSON · docs · YAML `ci.yml`).
- Build déterministe (régénérable bit-à-bit).
## Hors périmètre worker (VPS · #8)
Ce module **ne déploie rien** : il produit un document de recette en-repo.
L'exécution réelle (démo publique, application du run-book VPS, builds stores,
indexation, voix Amélie) revient à l'agent DevOps / la direction.
## Auto-score 4Big : 96/100
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.