# 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.
