5af86e3518
Durcissement d'uniformité (pas bug déclenchable aujourd'hui) qui COMPLÈTE le fix
correctness de la session 070054 (aeb30b8). Ce fix-là a percent-encodé
hero['fichier'] dans src=f"/static/projets/{code}/{quote(fichier)}", mais dans la
MÊME url('…') CSS il restait un 2ᵉ segment dynamique — `code` — interpolé brut puis
html-échappé via _esc(src). C'est exactement l'anti-pattern identifié par 070054
(html.escape = mauvais encodeur pour un segment d'URL), appliqué de façon incomplète.
Fix : 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. Zéro dérive : codes réels = P01..P09 (§Projets), quote("p01")=="p01"
→ byte-repro inchangé. Cas frère exact de 060044/criteria.py (« durcir le SEUL membre
non-durci d'une famille ») : ici le SEUL segment d'URL non-encodé de la même url().
Test à dents (test_hero_media_code_segment_percent_encoded) : code théorique "P'X" →
prouvé FAIL sans quote(code), PASS avec. Suite publiciste 25→26, matrice 630/613→631/614.
Défaut d'abord : classe encodage-contextuel balayée sur tous les modules HTML/CSS/URL ;
2 candidats Explore réfutés non-défauts (chat mount_html = persona forcée Amélie par
invariant 7 + portail_key enum ; code segment = théorique mais durci par uniformité).
Cascade régénérée dans l'ordre (quality_report en DERNIER) · 3 fiches compteurs réalignées.
run_ci 33/0/0 · NFC-clean · zéro code moteur V18 (bloqué D-06 #6) · zéro gate (#5) · zéro invention (#6).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
155 lines
34 KiB
Markdown
155 lines
34 KiB
Markdown
# Activity Log · 2026-08-12 · Claude Code DTP Worker
|
||
|
||
## 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'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.
|