diff --git a/05_activity_log/2026-08-12.md b/05_activity_log/2026-08-12.md index 3194c9d..10f6111 100644 --- a/05_activity_log/2026-08-12.md +++ b/05_activity_log/2026-08-12.md @@ -1,5 +1,29 @@ # 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`). diff --git a/05_deliverables_mvp/crm/financement_bancaire/finlib/gate.py b/05_deliverables_mvp/crm/financement_bancaire/finlib/gate.py index defc245..017522a 100644 --- a/05_deliverables_mvp/crm/financement_bancaire/finlib/gate.py +++ b/05_deliverables_mvp/crm/financement_bancaire/finlib/gate.py @@ -126,6 +126,15 @@ def _cond_documents(dossier: dict, cfg: GateConfig) -> dict: if statut in cfg.statuts_recevables: validated += 1 total = len(required_ids) + # Contrairement à _cond_apport (ratio USD *continu*, cf. fix 093114) et à + # l'agrégat (moyenne de 4, cf. fix 110124), AUCUN plafond « non ok → 99.9 » + # n'est requis ici : le dénominateur est la liste de documents du contrat + # (≤ 13 is_required), un domaine BORNÉ. round1 ne peut remonter à + # [99.95 %, 100 %[ par le bas qu'à partir de total ≥ 2000 (car + # (total-1)/total ≥ 0.9995) — inatteignable pour une liste de documents. + # Donc ok=False ⟹ percent < 100 tient déjà : l'invariant d'affichage + # percent == 100.0 ⟺ ok est préservé sans garde. (même logique que les 3 + # sites `:g`-domaine-borné · ne pas ajouter de plafond redondant · #5) pct = _round1((validated / total * 100.0) if total > 0 else 100.0) return { "ok": validated == total, @@ -144,6 +153,10 @@ def _cond_autorisations(dossier: dict, cfg: GateConfig) -> dict: if (by_id.get(aid) or {}).get("signature_date"): signed += 1 total = len(required_ids) + # Domaine BORNÉ (4 autorisations au contrat), a fortiori inférieur au seuil + # total ≥ 2000 requis pour que round1 remonte à [99.95 %, 100 %[ par le bas : + # pas de plafond « non ok → 99.9 » nécessaire ici (cf. note _cond_documents). + # L'invariant percent == 100.0 ⟺ ok tient sans garde. (ne pas ajouter · #5) pct = _round1((signed / total * 100.0) if total > 0 else 100.0) return {"ok": signed == total, "total": total, "signed": signed, "percent": pct}