# Activity Log · 2026-08-12 · Claude Code DTP Worker

## 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é).

**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé). `gate.py` **compile** + **NFC-clean** (vérifié) · suite module 37/37 OK. **0 code moteur V18** (bloqué #6), **0 fix de code** (classe prouvée fermée), **0 chiffre/fixture inventé** (#6), **0 gate ajouté** (#5), **0 artefact modifié/reconstruit** (byte-identique), **0 commande VPS** (#8), **0 API externe**. Fichiers : `M crm/financement_bancaire/finlib/gate.py` (2 notes anti-récurrence) + ce journal.

## 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.

**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP**. Fichiers édités **compilent** + **NFC-clean** (vérifié). **0 code moteur V18** (bloqué #6), **1 module produit corrigé** (bug latent user-facing), **artefacts autoritaires régénérés déterministes** (échantillon byte-identique), **0 gate ajouté** (#5), **0 commande VPS** (#8), **0 API externe**, **0 invention de chiffre** (#6 — tous re-dérivés des artefacts). Fichiers : `finlib/gate.py`, `tests/test_financement_bancaire.py`, `qa/regression/out/{regression_run,regression_plan,MANIFEST}.json`, `qa/audit_4big/out/quality_report.json`, fiches `03_agents/{qa,erpnext_backend,crm}/AGENT.md`, `daily_reports/2026-08-12.md` + ce journal.

## 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).

**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé — sweep en lecture seule, 0 artefact 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 : ce journal (+ mémoire agent `html-wellformedness-sweep`, hors repo).

## 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 :
- `cb8069b` (`090114`) — notes anti-récurrence `:g`-domaine-borné (commentaires purs · **0 impact matrice**).
- `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.

**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP**. **0 code moteur V18** (bloqué #6), **1 module produit corrigé** (bug latent user-facing), **artefacts autoritaires régénérés déterministes** (échantillon byte-identique), **0 gate ajouté** (#5), **0 commande VPS** (#8), **0 API externe**, **0 invention de chiffre** (#6 — tous re-dérivés des artefacts). Fichiers : `finlib/gate.py`, `tests/test_financement_bancaire.py`, `qa/regression/out/{regression_run,regression_plan,MANIFEST}.json`, `qa/audit_4big/out/quality_report.json`, fiches `03_agents/{qa,erpnext_backend,crm}/AGENT.md` + ce journal.

## 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`).
- **Axe drift numérique README/artefacts** : déjà gaté (`check_readme_claims` ∈ run_ci = vert). 0 écart.

**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.

**Expérience (replayable).** Monkeypatch `reglib.discovery.discover_suites` → `reversed(...)`, ré-exécution `builder.run_matrix(spec)`, diff par-suite (clé = `id`) + totaux vs `out/regression_run.json` commité :
- Totaux **byte-identiques** : 629 ran / 612 passés / 17 skippés / 0 échec.
- **24/24 suites** : tuples `(ran,passed,failures,errors,skipped,ok)` **identiques**. `PER-SUITE DIFFS: NONE`.

**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.

**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé — commentaire pur, artefact `seo/out/*.json` re-build byte-identique via `check_artifacts` ∈ run_ci). **0 code moteur V18** (bloqué #6), **0 logique de production modifiée** (commentaire seul), **0 artefact autoritaire modifié**, **0 fixture touchée** (#6), **0 gate ajouté** (#5), **0 commande VPS** (#8), **0 API externe**. Fichiers : `M 05_deliverables_mvp/seo/seolib/keywords.py` (note by-design au dedup) + ce journal.

## 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**.

**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé). Fichier édité **compile** + **NFC-clean** (accents intentionnels, style aligné lignes 18-30). **0 code moteur V18** (bloqué #6), **0 artefact autoritaire modifié** (byte-identique), **0 fixture/chiffre inventé** (#6), **0 gate ajouté** (#5), **0 commande VPS** (#8), **0 API externe**. Fichiers : `M 05_deliverables_mvp/qa/audit_4big/q4lib/criteria.py` (regex `_ADD_PARSER_RE` durci + commentaire doctrine) + ce journal.

## Session `20260812_063050` — CANAL STAKEHOLDER · complétion du rapport `daily_reports/2026-08-12.md` au HEAD du jour (`ee88d36` → `37c68ac`)

**É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) = **629 exécutés · 612 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**. Tous les arbitrages produit D-01→D-08 restent en attente Michel (`OPEN_DECISIONS_REGISTER.md`).

**Chasse au défaut d'abord (2 axes, correctness fiscale jamais auditée en profondeur) → 0 défaut, modules confirmés CLEAN.**
- **`fiscal/ecf_dgii/ecflib/ncf.py`** (composition e-NCF DGII, Ley 32-23) : logique **correcte** — `ENCF_RE` = `E + tipo(2) + secuencia(10)` = 13 car. ; `compose_encf` publie sa formule, retourne `e_ncf: None` quand un opérande manque (jamais 0/valeur fabriquée · #6), `_secuencia` zero-padde `[0-9]{1,10}` → 10 chiffres sans jamais inventer de séquence. `is_valid_encf`/`parse_encf`/`compose_encf` cohérents (une valeur composée re-valide toujours). **Non touché.**
- **`fiscal/ecf_dgii/ecflib/builder.py`** (plan de config e-CF) : **correct** — aucun chiffre OTO fixé (RNC/ITBIS/TipoCambio/endpoints Compupar restent `null · a_confirmer` par design #6), `_a_confirmer_count` déterministe, tri stable partout → bundle byte-repro. **Non touché.** (`is_filled` partagé re-vérifié = choix conservateur correct, cf. session `040034`.)

**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&#x27;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.

**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP**. Fichiers édités **compilent** + **NFC-clean** (vérifié). **0 code moteur V18** (bloqué #6), **0 chiffre/fixture inventé** (#6 · tous les compteurs re-dérivés des artefacts régénérés), **0 gate ajouté** (#5), **0 commande VPS** (#8), **0 API externe**. Fichiers : `M generator.py` (fix `quote`) · `M test_publiciste.py` (test à dents) · `M` 4 artefacts régénérés (regression×3 + quality_report) · `M` 3 fiches AGENT.md (compteurs) + ce journal.

## 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).

**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP**. Fichiers édités **NFC-clean** (vérifié). **0 code moteur V18** (bloqué #6), **0 chiffre/fixture inventé** (#6 · compteurs re-dérivés), **0 gate ajouté** (#5), **0 commande VPS** (#8), **0 API externe**. Fichiers : `M generator.py` (`quote(code)`) · `M test_publiciste.py` (test à dents) · `M` 4 artefacts régénérés (regression×3 + quality_report) · `M` 3 fiches AGENT.md (compteurs) + ce journal.

## 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).

**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP**. Fichiers édités **compilent** + **NFC-clean** (vérifié). **0 code moteur V18** (bloqué #6), **0 chiffre/fixture inventé** (#6 · compteurs re-dérivés des artefacts régénérés), **0 gate ajouté** (#5), **0 commande VPS** (#8), **0 API externe**. Fichiers : `M commlib/finance.py` (`_amount_label`) · `M tests/test_commissions.py` (2 tests à dents) · `M crm/commissions/README.md` (compteurs) · `M` 4 artefacts régénérés (regression×3 + quality_report) · `M` 3 fiches AGENT.md (compteurs) + ce journal.

## 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** :
- `aeb30b8` (`070054`) — fix rendu publiciste : URL hero percent-encodée (`html.escape` = mauvais encodeur CSS ; `Vue d'ensemble.png` FR cassait le hero) → suite publiciste 24→25, matrice **629/612 → 630/613**.
- `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 #2** `bancable/banclib/report.py:38` `_pct` `f"{n:g} %"` — pourcentage (marge/taux) **borné 0-100** : mêmes deux pièges hors domaine.
- **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.

**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé). 3 fichiers édités **compilent** + **NFC-clean** (vérifié). **0 code moteur V18** (bloqué #6), **0 fix de code** (défauts inexistants), **0 chiffre/fixture inventé** (#6), **0 gate ajouté** (#5), **0 artefact modifié/reconstruit**, **0 commande VPS** (#8), **0 API externe**. Fichiers : `M crm/commissions/commlib/finance.py` · `M faisabilite/bancable/banclib/finance.py` · `M faisabilite/bancable/banclib/report.py` (notes anti-récurrence `:g`) + ce journal.
