Bug latent réel (rendu-correctness, entrée FR/ES plausible) : _hero_media
(generator.py:92) injectait le nom de fichier via html.escape dans un
background-image:url('…'). Un rendu réel « Vue d'ensemble.png » devenait
Vue d'ensemble.png → le navigateur HTML-décode l'attribut style AVANT le
CSS → l'apostrophe referme la chaîne url('…') → hero non affiché, silencieux.
html.escape est le mauvais encodeur pour un contexte CSS.
Fix : urllib.parse.quote sur le segment de fichier ('→%27, espace→%20,
accents→%XX) — valide dans URL + attribut HTML + url() CSS ; _esc conservé pour
l'attribut HTML. Zéro dérive : quote laisse les noms ASCII simples inchangés
(hero_aerien.jpg identique) → fixture + byte-repro intacts.
Test de régression à dents (test_hero_media_url_percent_encode) : prouvé FAIL
sans quote, PASS avec. Cascade régénérée (artifact-reproducibility-gate) :
suite publiciste 24→25, matrice 629/612→630/613/17 · quality_report régénéré
en DERNIER · 3 fiches AGENT.md (compteurs dérivés) réalignées.
Défaut d'abord : 12 fichiers cœur / 7 modules ré-audités CLEAN (epsilon 1e-9
gate apport = tolérance flottante correcte). run_ci 33/0/0 · NFC-clean ·
zéro code moteur V18 (bloqué D-06 #6) · zéro gate ajouté (#5) · zéro invention (#6).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
29 KiB
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-filesle confirme) — mais commité une seule fois, dans la session autof954cc1du 2026-08-02 (scratch-dir de comparaison « fresh rebuild » commité par accident). - Byte-identique à
out/(diffjson.tool= IDENTICAL content). - Produit par rien : le générateur
workspaces_gen.pyécrit uniquement dansout/(_DEFAULT_OUT = os.path.join(_HERE, "out")·-odéfaut./out) — jamaisout_fresh. - Référencé nulle part :
git grep out_freshsur 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 danspubliciste/tests/test_publiciste.py(+15 lignes) → régression 628→629 / 611→612 +regression_plan/run/MANIFEST+quality_reportre-générés du même coup. Le rapport 08-11 ne le couvre pas → chiffre628/611désormais stale.031b5e7= PNGDASHBOARD_LUXURY_MODELE_REFERENCEajouté 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 orphelinout_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é » vsmobile_spec.jsonexpo_sdk_major: 54— a été réfuté par la mémoiremobile-runtime-actual-vs-rebuild-targetet 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). Le54du 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 lalocalisationgénérique de 5 projets (P01/P02/P03/P06/P09 dansfixtures/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 scopeglobal: 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 commissionsmontant = base × taux) : logique correcte —rate()normalise nombre/« 3.5 % »/« 3,5 % » en fraction,taux_pctde 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⌉avec52 %re-dérivé du canonique #9 (jamais codé en dur, cf. fix448c310), agrégats catalogueΣ(qté × prix)gardés surall(... 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_encfpublie sa formule, retournee_ncf: Nonequand un opérande manque (jamais 0/valeur fabriquée · #6),_secuenciazero-padde[0-9]{1,10}→ 10 chiffres sans jamais inventer de séquence.is_valid_encf/parse_encf/compose_encfcohé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 restentnull · a_confirmerpar design #6),_a_confirmer_countdéterministe, tri stable partout → bundle byte-repro. Non touché. (is_filledpartagé re-vérifié = choix conservateur correct, cf. session040034.)
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étriefr=87·en=87·es=84prouvée NON-DÉFAUT + note by-design au dedup.37c68ac(060044) — hardeningcriteria.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.