diff --git a/03_agents/erpnext_backend/AGENT.md b/03_agents/erpnext_backend/AGENT.md index e42164e..9736a0a 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 : **628 tests · 24 suites** · verdict `PASS`, source +de régression du repo : **629 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/publiciste/AGENT.md b/03_agents/publiciste/AGENT.md index a73dd21..c3f0ebb 100644 --- a/03_agents/publiciste/AGENT.md +++ b/03_agents/publiciste/AGENT.md @@ -92,7 +92,7 @@ du générateur, gaté en CI. C'est le **seul module net-neuf** du mandat, sur l | Module | Sprint | Rôle | Entrée CLI | Job CI | Tests | |---|---|---|---|---|---| -| [`publiciste/`](../../05_deliverables_mvp/publiciste/README.md) | 2 (README `scaffold (Sprint 2)` · GAP §3.13) | **Pipeline site public** : `data_room/PXX/` → `projets_master.json` → `index.html` luxury (`lib/parser.py` extraction · `lib/validator.py` schéma · `lib/generator.py` + `lib/branding.py` rendu dark+doré `#0a0a12`/`#f0b429` · `templates/site_public.html.tmpl`) | `publiciste.py parse\|validate\|generate\|run` | `publiciste-tests` | 23 | +| [`publiciste/`](../../05_deliverables_mvp/publiciste/README.md) | 2 (README `scaffold (Sprint 2)` · GAP §3.13) | **Pipeline site public** : `data_room/PXX/` → `projets_master.json` → `index.html` luxury (`lib/parser.py` extraction · `lib/validator.py` schéma · `lib/generator.py` + `lib/branding.py` rendu dark+doré `#0a0a12`/`#f0b429` · `templates/site_public.html.tmpl`) | `publiciste.py parse\|validate\|generate\|run` | `publiciste-tests` | 24 | Le contrat d'échange `projets_master.json` n'est **pas** défini ici mais par le Faisabilité Agent ([`../../05_deliverables_mvp/faisabilite/projets_master.schema.json`](../../05_deliverables_mvp/faisabilite/projets_master.schema.json), diff --git a/03_agents/qa/AGENT.md b/03_agents/qa/AGENT.md index 4d84155..e12f0ea 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 · 628 tests · 611 passés · 0 échec · 0 erreur → verdict `PASS`.** +**24 suites gated · 629 tests · 612 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-11.md b/05_activity_log/2026-08-11.md index 83a2513..9536969 100644 --- a/05_activity_log/2026-08-11.md +++ b/05_activity_log/2026-08-11.md @@ -284,3 +284,25 @@ Or `def test_x(` (deux espaces) et `def\ttest_x(` (tabulation) sont du **Python **Pourquoi c'est légitime et non du churn (#5).** Ce n'est pas un re-write d'un snapshot daté figé (ceux-là restent, `two-logging-channels`) : c'est la **mise au point courant du jour même** du canal stakeholder, dont le `9e02c38` précédent avait fait exactement autant (18d8763→ac595d0). Chaque chiffre injecté est **re-dérivé** de `regression_run.json` (`totals` = 628/611), aucun figé. **0 fichier de production · 0 artefact reconstruit · 0 gate ajouté · 0 code moteur V18 · 0 commande VPS · 0 API externe.** **Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (inchangé · le daily report tracked passe `check_docs` + `guard_constraints`). Fichiers : `05_deliverables_mvp/daily_reports/2026-08-11.md` + ce journal. + +--- + +## Session `20260811_095844` — COUVERTURE produit · teeth `publiciste` sur les prix à ESPACE INSÉCABLE (U+00A0) · classe d'entrée réelle FR/ES 100 % non testée jusqu'ici + +**État au démarrage.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** (baseline verte · `regression_run.json totals` = 628/611/17). Séquence moteur V18 toujours **intégralement bloquée** en gouvernance (D-06 approbation de l'audit = gate d'entrée · D-07 formules DCF/IRR/VAN/DSCR/LTV/LTC absentes · D-08 Master Data Model · directive `:90-91` « NE PAS coder avant l'audit » · #6) — **aucun code moteur légitime**. Les 2 directives 08-10 (`OTO_3D_STUDIO`, `COMPTE_CLIENT_COURRIELS`) restent hors périmètre repo. Les micro-tâches doc + l'arc `audit_4big` sont clos → recherche d'une tâche à **valeur produit réelle**. + +**Chasse au bug d'abord (2 passes indépendantes) → aucun défaut, refus de fabriquer (#6).** Explore very-thorough sur les `*_gen.py`/`*lib/` **hors** `audit_4big`/`regression` (déjà balayés) : classes visées = char-class ASCII-vs-Unicode, off-by-one, comparateurs de seuil, sets à doublons, arithmétique dérivée, collisions de clés. **Rien de solide.** Les 2 candidats remontés étaient des **non-défauts** : (a) `publiciste/lib/parser.py:77` `.replace(" ", " ")` — l'Explore l'a lu « no-op » ; **hexdump** montre `c2 a0` = le 1er argument est un **NBSP** → c'est une **normalisation NBSP→espace réelle**, pas un no-op (l'Explore s'est fait piéger par des glyphes identiques à l'œil — exactement le risque byte-invisible) ; (b) `seo_gen.py` global-keyword digit-check = latent masqué par invariant. **Aucun n'est un bug** → non touchés. + +**Vrai locus trouvé = un TROU DE COUVERTURE, pas un bug.** `parse_number`/`parse_price` de `publiciste` sont **conçus** pour l'**espace insécable U+00A0** comme séparateur de milliers — la graphie que Word/InDesign FR/ES insèrent dans les prix réels des `data_room` (« DOP 8 850 000 » avec NBSP). La capture repose sur la char-class **Unicode-whitespace** de `parser.py:74` (`[0-9\s.,` + un **NBSP littéral** `c2 a0` redondant avec `\s`). Or **aucun des 23 tests** n'exerçait le NBSP : `test_prix_formats:39` (« DOP 8 850 000 ») utilise des **espaces ASCII 0x20** (`hexdump` confirmé), pas des NBSP. La classe d'entrée réelle la plus caractéristique du parser était **100 % non testée** — et c'est précisément ce trou qui a permis à l'Explore de mésinterpréter la ligne 77 comme du code mort. + +**Preuve end-to-end + bite ACCURATE (aucune affirmation non vérifiée · #6).** (1) `parse_price("DOP 8 850 000")` = **8850000.0** ✓, `parse_number("1 250")` = **1250.0** ✓ (exécuté). (2) Condition de bite **déterminée empiriquement, pas devinée** : la char-class porte `\s` **et** un NBSP littéral → retirer **l'un** est inoffensif (redondance belt-and-suspenders) ; le bite ne survient que si l'on **réduit la classe à de l'ASCII pur** `[0-9.,]` (retrait de TOUTE prise en charge whitespace-Unicode) → le test échoue alors `8.0 != 8850000.0` (prouvé : cassé RED, restauré VERT). Le commentaire du test a été **corrigé** d'une 1re rédaction fausse (« retirer `\s` mord ») vers la condition réelle — honnêteté de claim (#6). Même racine que l'arc `audit_4big` : « détecter la forme, pas une graphie ». + +**Hygiène byte-invisible (leçon appliquée au test lui-même).** 1re rédaction du test contenait des **NBSP littéraux** (`nbsp = " "`) — je réintroduisais l'octet invisible que je dénonce. Réécrit en **escape explicite ` `** (`nb = " "`) → **0 octet invisible** dans le fichier de test (vérifié `grep -c`), intention 100 % lisible. La char-class de **production** (ligne 74), elle, est **laissée telle quelle** : son NBSP littéral est **redondant mais correct** (`\s` le couvre) ; l'éditer serait du churn cosmétique sur un non-défaut (#5, `verify-non-defects`) avec risque de dérive d'artefact pour zéro changement de comportement. + +**Fix = 1 test teeth `test_prix_espace_insecable_milliers`** (classe `TestNumberParsing`) : `parse_price`/`parse_number`/`parse_int` sur NBSP (DOP/USD milliers + décimale). Distinct de l'arc récent : ce n'est **pas** un test de l'outil d'audit mais de la **couverture d'un module produit** sur une **entrée réelle** — valeur produit directe. + +**Cascade d'artefacts régénérée (mémoires `artifact-reproducibility-gate` + `audit4big-rebuild-after-doc-edits`).** (1) `audit_4big_gen.py build` → `quality_report.json` : évidence publiciste TESTS **« 23 → 24 méthodes »** ; **verdict PASS · 24/24 · min 100 inchangé** (aucun score bougé — le seul delta est le compte). (2) `regression_gen.py build` puis `run` → `regression_run.json` **628 → 629 exécutés · 611 → 612 passés · 17 skippés · 0 échec** (publiciste suite ran 23→24 / passed 21→22 — les 2 « non-incréments » = ses tests oracle jsonschema neutralisés par `python -S`, cf. `regression-baseline-17-skips-by-design`). (3) Surfaces de prose gatées re-dérivées (flaggées RED par `check_readme_claims`, corrigées) : fiche `03_agents/publiciste/AGENT.md:95` (cellule Tests **23 → 24** ← `count_tests`) + agrégat `03_agents/qa/AGENT.md:35` (**628/611 → 629/612**) + `03_agents/erpnext_backend/AGENT.md:40` (claim live **628 → 629**). Le `daily_reports/2026-08-11` (snapshot signé) et les logs = instantanés datés → laissés (`two-logging-channels`). + +**Pourquoi pas un nouveau gate (#5).** La divergence de compte est déjà **structurellement gatée** (`check_readme_claims` a immédiatement rougi la fiche publiciste + l'agrégat QA dès le rebuild) et la teeth verrouille le comportement NBSP. Trou de couverture isolé, comblé à la source → pas de gate additionnel. + +**Vérif.** `./run_ci.sh` = **33 PASS · 0 FAIL · 0 SKIP** rétabli après régénération. **0 code moteur V18** (bloqué #6), **0 module de production touché** (seul un test ajouté · la char-class de prod laissée intacte), **0 gate ajouté** (#5), **0 commande VPS** (#8), **0 API externe**. Fichiers : `05_deliverables_mvp/publiciste/tests/test_publiciste.py` (teeth) + `qa/audit_4big/out/quality_report.json` + `qa/regression/out/{regression_plan,regression_run,MANIFEST}.json` + `03_agents/{publiciste,qa,erpnext_backend}/AGENT.md` + ce journal. diff --git a/05_deliverables_mvp/publiciste/tests/test_publiciste.py b/05_deliverables_mvp/publiciste/tests/test_publiciste.py index bf62842..e40c1f5 100644 --- a/05_deliverables_mvp/publiciste/tests/test_publiciste.py +++ b/05_deliverables_mvp/publiciste/tests/test_publiciste.py @@ -39,6 +39,21 @@ class TestNumberParsing(unittest.TestCase): self.assertEqual(parser.parse_price("DOP 8 850 000"), 8850000.0) self.assertEqual(parser.parse_price("1,250,000.50"), 1250000.50) + def test_prix_espace_insecable_milliers(self): + # Les outils d'édition FR/ES (Word, InDesign) insèrent l'ESPACE INSÉCABLE + # U+00A0 comme séparateur de milliers dans les prix réels des data_room. + # `lib/parser.py` la capte via une char-class Unicode-whitespace (`\s`). + # Verrou : ce test mord si l'on réduit cette capture à de l'ASCII pur + # (retrait de TOUTE prise en charge whitespace-Unicode → seul le 1er + # chiffre survit, ex. 8.0 au lieu de 8 850 000) — même racine « détecter + # la forme, pas une graphie » que l'arc de fixes audit_4big. + # NBSP en escape explicite \u00a0 pour garder l'intention lisible (0 octet invisible). + nb = "\u00a0" + self.assertEqual(parser.parse_price(f"DOP 8{nb}850{nb}000"), 8850000.0) + self.assertEqual(parser.parse_price(f"USD 1{nb}250{nb}000,50"), 1250000.50) + self.assertEqual(parser.parse_number(f"1{nb}250"), 1250.0) + self.assertEqual(parser.parse_int(f"12{nb}unités"), 12) + def test_absents_donnent_none(self): for token in ["", "—", "-", "non défini", "n/d", "{{typo_1_prix_usd}}", "...", "TBD"]: self.assertIsNone(parser.parse_price(token), f"{token!r} devrait être None") 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 a186ad3..5f01f60 100644 --- a/05_deliverables_mvp/qa/audit_4big/out/quality_report.json +++ b/05_deliverables_mvp/qa/audit_4big/out/quality_report.json @@ -60,7 +60,7 @@ "criterion": "TESTS", "weight": 25, "passed": true, - "evidence": "23 méthodes test_* dans 1 fichier(s) (seuil 8)" + "evidence": "24 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 45e34d3..4d8df04 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": 628, + "test_methods": 629, "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 d477358..3373188 100644 --- a/05_deliverables_mvp/qa/regression/out/regression_plan.json +++ b/05_deliverables_mvp/qa/regression/out/regression_plan.json @@ -189,7 +189,7 @@ "in_gate": true, "has_tests_dir": true, "test_files": 1, - "test_methods": 23 + "test_methods": 24 }, { "id": "qa.acceptance", @@ -295,7 +295,7 @@ "totals": { "suites": 24, "test_files": 24, - "test_methods": 628, + "test_methods": 629, "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 5f836bf..e92d892 100644 --- a/05_deliverables_mvp/qa/regression/out/regression_run.json +++ b/05_deliverables_mvp/qa/regression/out/regression_run.json @@ -174,9 +174,9 @@ { "id": "publiciste", "path": "publiciste", - "expected_methods": 23, - "ran": 23, - "passed": 21, + "expected_methods": 24, + "ran": 24, + "passed": 22, "failures": 0, "errors": 0, "skipped": 2, @@ -296,8 +296,8 @@ "suites": 24, "green": 24, "red": 0, - "ran": 628, - "passed": 611, + "ran": 629, + "passed": 612, "failures": 0, "errors": 0, "skipped": 17