From 1513e69a796f9b517039707615641da1808fe7ce Mon Sep 17 00:00:00 2001 From: Claude Code DTP Worker Date: Wed, 12 Aug 2026 05:40:22 +0000 Subject: [PATCH] =?UTF-8?q?[DTP-Worker=2020260812=5F053034]=20v=C3=A9rif?= =?UTF-8?q?=20i18n=20=C2=B7=20asym=C3=A9trie=20cardinalit=C3=A9=20SEO=20fr?= =?UTF-8?q?=3D87=C2=B7en=3D87=C2=B7es=3D84=20PROUV=C3=89E=20NON-D=C3=89FAU?= =?UTF-8?q?T=20=C2=B7=20note=20by-design=20au=20dedup=20(anti=20re-flag,?= =?UTF-8?q?=20sans=20gate)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Angle frais jamais balayé (cohérence tri-lingue FR/EN/ES du module seo/). Un Explore a remonté « es manque 3 mots-clés projet P01 » ; refusé au mot (#6, verify-non-defects) et prouvé au byte-level un NON-DÉFAUT : l'artefact étant byte-repro-gaté, l'écart reflète la LOGIQUE du générateur. Preuve (repro du générateur sans dedup) : génération symétrique par langue → raw 115/langue identique. L'écart vient du seul dedup (lang,term). country[es] = « Republica Dominicana » == la localisation générique de 5 projets (P01/02/03/06/09) → le mot-clé global {type} Republica Dominicana absorbe le mot-clé projet identique de P01 (dedup correct, terme présent 1× en scope global). En fr/en country != localisation → pas de collision, P01 garde ses 7 termes. D'où +3 collisions en es → 87 vs 84. Rien n'est perdu ; invariants (≥ min_keywords_per_lang + unicité (term,lang)) tiennent ; es=84 déjà documenté et gaté README.md:15 (check_readme_claims). Sortie CORRECTE. Livrable = note explicative au point de dedup keywords.py:106 (asymétrie subtile non-évidente qu'un futur balayage refera l'enquête). Aucune logique/fixture/claim -gaté touché (gates byte visent out/*.json, pas la source). Style aligné ASCII + em-dash existant, NFC-clean. run_ci = 33 PASS/0/0 (inchangé). 0 code moteur V18 (#6), 0 logique modifiée, 0 artefact/fixture touché (#6), 0 gate ajouté (#5), 0 VPS (#8), 0 API externe. Co-Authored-By: Claude Opus 4.8 (1M context) --- 05_activity_log/2026-08-12.md | 18 ++++++++++++++++++ 05_deliverables_mvp/seo/seolib/keywords.py | 15 +++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/05_activity_log/2026-08-12.md b/05_activity_log/2026-08-12.md index ad8f51a..c70fb86 100644 --- a/05_activity_log/2026-08-12.md +++ b/05_activity_log/2026-08-12.md @@ -56,3 +56,21 @@ **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. diff --git a/05_deliverables_mvp/seo/seolib/keywords.py b/05_deliverables_mvp/seo/seolib/keywords.py index ef5c61a..7445cd4 100644 --- a/05_deliverables_mvp/seo/seolib/keywords.py +++ b/05_deliverables_mvp/seo/seolib/keywords.py @@ -103,6 +103,21 @@ def generate(spec: dict, master: dict) -> list[dict]: # --- Dedup (term, lang) en conservant la premiere occurrence, puis tri # deterministe. + # + # NOTE — asymetrie de cardinalite PAR LANGUE (by-design, pas un bug). + # Le nombre de mots-cles UNIQUES peut differer d'une langue a l'autre + # (ex. fr=87, en=87, es=84) sans qu'aucun mot-cle ne soit "manquant". + # Cause : un projet dont la `localisation` coincide, dans une langue, + # avec le `country[lang]` produit un mot-cle projet `{type} {loc}` + # BYTE-IDENTIQUE au mot-cle global `{type} {country}` (emis avant) ; + # la 2e occurrence est donc dedupliquee — le terme reste present une + # fois (scope `global`). En espagnol `country[es]` == "Republica + # Dominicana" == la localisation generique de 5 projets, d'ou 3 + # collisions global<->projet de plus qu'en fr/en (ou "Republique + # Dominicaine"/"Dominican Republic" != la localisation). Les invariants + # ne contraignent que `>= min_keywords_per_lang` + unicite (term, lang), + # jamais l'egalite inter-langues : ne pas "corriger" en forcant une + # symetrie (ce serait emettre un doublon strict). seen: set[tuple[str, str]] = set() deduped: list[dict] = [] for k in acc: