diff --git a/03_agents/crm/AGENT.md b/03_agents/crm/AGENT.md index b243b2c..7edbb56 100644 --- a/03_agents/crm/AGENT.md +++ b/03_agents/crm/AGENT.md @@ -41,7 +41,7 @@ de Michel, d'où son suivi séparé (il ne partage pas la source unique du trio) | Module | Sprint | Rôle | Entrée CLI | Job CI | Tests | |---|---|---|---|---|---| -| [`financement_bancaire/`](../../05_deliverables_mvp/crm/financement_bancaire/README.md) | 4 (roadmap L52) | Parcours **hypothécaire RD** : cœur métier du **gate check 4 conditions** — aucun document n'est transmis à la banque tant que apport initial (20 % résident · 30 % étranger · Ley 189-11) + documents exigés + autorisations signées + validation référente ne sont pas réunis ; fonctions **pures** sans I/O | `financement_bancaire_gen.py build\|validate` | `crm-financement-bancaire-tests` | 35 | +| [`financement_bancaire/`](../../05_deliverables_mvp/crm/financement_bancaire/README.md) | 4 (roadmap L52) | Parcours **hypothécaire RD** : cœur métier du **gate check 4 conditions** — aucun document n'est transmis à la banque tant que apport initial (20 % résident · 30 % étranger · Ley 189-11) + documents exigés + autorisations signées + validation référente ne sont pas réunis ; fonctions **pures** sans I/O | `financement_bancaire_gen.py build\|validate` | `crm-financement-bancaire-tests` | 36 | Matérialise [`DIRECTIVE_FINANCEMENT_BANCAIRE_COMPLET_20260803.md`](../../DIRECTIVE_FINANCEMENT_BANCAIRE_COMPLET_20260803.md) (**Phase 1 · P0 · MVP** : contrat de données + cœur du gate + 7 sections + bannière diff --git a/03_agents/erpnext_backend/AGENT.md b/03_agents/erpnext_backend/AGENT.md index 892b6b4..e8e7407 100644 --- a/03_agents/erpnext_backend/AGENT.md +++ b/03_agents/erpnext_backend/AGENT.md @@ -37,7 +37,7 @@ le VPS reçoit — jamais l'inverse. vente, le barème commissions, le DocType Dossier Vente et le plan e-CF **résolvent** leurs rôles depuis ce fichier, jamais un nom Frappe en dur. Total backend **RBAC 60 tests** (10 + 11 + 12 + 11 + 16) + **e-CF 39 tests**, tous gated dans le CI (matrice -de régression du repo : **633 tests · 24 suites** · verdict `PASS`, source +de régression du repo : **634 tests · 24 suites** · verdict `PASS`, source `qa/regression/out/regression_run.json` — jamais compté à la main · #6). ## Hand-off reçus (à appliquer sur le VPS, dans l'ordre) diff --git a/03_agents/qa/AGENT.md b/03_agents/qa/AGENT.md index 27046a0..d9c2d94 100644 --- a/03_agents/qa/AGENT.md +++ b/03_agents/qa/AGENT.md @@ -32,7 +32,7 @@ périmètre — un auditeur ne s'audite jamais. **conformité** normative des hand-off. ## Verdict agrégé courant (source `qa/regression/out/regression_run.json`) -**24 suites gated · 633 tests · 616 passés · 0 échec · 0 erreur → verdict `PASS`.** +**24 suites gated · 634 tests · 617 passés · 0 échec · 0 erreur → verdict `PASS`.** Ce compte est **découvert automatiquement depuis le CI** (`q4lib.registry.parse_ci`, réutilisé sans duplication) — jamais compté à la main. Les **17 tests restants sont ignorés** (`skipped`) : ce sont les *oracles optionnels* diff --git a/05_activity_log/2026-08-12.md b/05_activity_log/2026-08-12.md index d269e9b..04a4ea4 100644 --- a/05_activity_log/2026-08-12.md +++ b/05_activity_log/2026-08-12.md @@ -1,5 +1,28 @@ # Activity Log · 2026-08-12 · Claude Code DTP Worker +## 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). diff --git a/05_deliverables_mvp/crm/financement_bancaire/finlib/gate.py b/05_deliverables_mvp/crm/financement_bancaire/finlib/gate.py index a0d54f9..784b8c8 100644 --- a/05_deliverables_mvp/crm/financement_bancaire/finlib/gate.py +++ b/05_deliverables_mvp/crm/financement_bancaire/finlib/gate.py @@ -94,9 +94,18 @@ def _cond_apport(dossier: dict, cfg: GateConfig) -> dict: required = apport_requis_usd(dossier.get("prix_usd", 0), residence, cfg) paid = apport_verse_usd(dossier) remaining = round(max(0.0, required - paid), 2) + ok = paid + 1e-9 >= required pct = _round1(min(100.0, (paid / required * 100.0) if required > 0 else 100.0)) + # Un versement dans la bande [99.95 %, 100 %[ (p. ex. 19 999/20 000 USD) + # remonterait à 100.0 par l'arrondi 1-décimale, alors que le gate BLOQUE + # (ok=False, remaining>0). La barre de progression afficherait « 100 % ✓ » + # tout en refusant la soumission, et la raison lirait « reste 1 USD · 100 %». + # Invariant d'affichage : percent == 100.0 ⟺ ok — un dossier incomplet + # plafonne à 99.9 (plus grande valeur 1-décimale strictement < 100). + if not ok and pct >= 100.0: + pct = 99.9 return { - "ok": paid + 1e-9 >= required, + "ok": ok, "required": required, "paid": paid, "remaining": remaining, diff --git a/05_deliverables_mvp/crm/financement_bancaire/tests/test_financement_bancaire.py b/05_deliverables_mvp/crm/financement_bancaire/tests/test_financement_bancaire.py index 6007dc5..e4bed7e 100644 --- a/05_deliverables_mvp/crm/financement_bancaire/tests/test_financement_bancaire.py +++ b/05_deliverables_mvp/crm/financement_bancaire/tests/test_financement_bancaire.py @@ -118,6 +118,21 @@ class GateCheckTest(unittest.TestCase): self.assertTrue(status["conditions"]["apport_initial_complet"]["ok"]) self.assertEqual(status["conditions"]["apport_initial_complet"]["percent"], 100.0) + def test_apport_quasi_complet_ninclut_pas_100pct(self): + # Bande [99.95 %, 100 %[ : l'arrondi 1-décimale de 99.995 % remonte à + # 100.0, mais le gate BLOQUE (ok=False, reste 1 USD). Un dossier + # incomplet ne doit JAMAIS afficher 100 % (barre pleine ⇔ soumissible). + d = _dossier_complet(self.cfg, self.spec) + d["paiements"] = [{"montant_verse_usd": 19999}] # 20 000 requis → 99.995 % + c = gate.gate_status(d, self.cfg)["conditions"]["apport_initial_complet"] + self.assertFalse(c["ok"]) + self.assertAlmostEqual(c["remaining"], 1.0) + self.assertLess(c["percent"], 100.0) + self.assertEqual(c["percent"], 99.9) + # La raison de blocage ne doit pas dire « 100 % ». + _, reasons = gate.can_submit_dossier(d, self.cfg) + self.assertTrue(any("Apport initial incomplet" in r and "100" not in r for r in reasons)) + def test_apport_manquant_bloque(self): d = _dossier_complet(self.cfg, self.spec) d["paiements"] = [{"montant_verse_usd": 19999}] diff --git a/05_deliverables_mvp/qa/audit_4big/out/quality_report.json b/05_deliverables_mvp/qa/audit_4big/out/quality_report.json index 516af7c..1e69392 100644 --- a/05_deliverables_mvp/qa/audit_4big/out/quality_report.json +++ b/05_deliverables_mvp/qa/audit_4big/out/quality_report.json @@ -450,7 +450,7 @@ "criterion": "TESTS", "weight": 25, "passed": true, - "evidence": "35 méthodes test_* dans 1 fichier(s) (seuil 8)" + "evidence": "36 méthodes test_* dans 1 fichier(s) (seuil 8)" }, { "criterion": "CLI", diff --git a/05_deliverables_mvp/qa/regression/out/MANIFEST.json b/05_deliverables_mvp/qa/regression/out/MANIFEST.json index 962c533..e5133de 100644 --- a/05_deliverables_mvp/qa/regression/out/MANIFEST.json +++ b/05_deliverables_mvp/qa/regression/out/MANIFEST.json @@ -5,7 +5,7 @@ "version": "1.0", "verdict": "PASS", "suites": 24, - "test_methods": 633, + "test_methods": 634, "coverage_ok": true, "artifacts": [ "regression_plan.json" diff --git a/05_deliverables_mvp/qa/regression/out/regression_plan.json b/05_deliverables_mvp/qa/regression/out/regression_plan.json index 3a7cf8f..4a5572b 100644 --- a/05_deliverables_mvp/qa/regression/out/regression_plan.json +++ b/05_deliverables_mvp/qa/regression/out/regression_plan.json @@ -57,7 +57,7 @@ "in_gate": true, "has_tests_dir": true, "test_files": 1, - "test_methods": 35 + "test_methods": 36 }, { "id": "crm.workflow_vente", @@ -295,7 +295,7 @@ "totals": { "suites": 24, "test_files": 24, - "test_methods": 633, + "test_methods": 634, "min_methods": 10, "under_threshold": 0 }, diff --git a/05_deliverables_mvp/qa/regression/out/regression_run.json b/05_deliverables_mvp/qa/regression/out/regression_run.json index 5efffcc..def88e9 100644 --- a/05_deliverables_mvp/qa/regression/out/regression_run.json +++ b/05_deliverables_mvp/qa/regression/out/regression_run.json @@ -30,9 +30,9 @@ { "id": "crm.financement_bancaire", "path": "crm/financement_bancaire", - "expected_methods": 35, - "ran": 35, - "passed": 35, + "expected_methods": 36, + "ran": 36, + "passed": 36, "failures": 0, "errors": 0, "skipped": 0, @@ -296,8 +296,8 @@ "suites": 24, "green": 24, "red": 0, - "ran": 633, - "passed": 616, + "ran": 634, + "passed": 617, "failures": 0, "errors": 0, "skipped": 17