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) <noreply@anthropic.com>
frontend/portails/out_fresh/ (workspace.json + MANIFEST.json) était tracké mais
commité par accident une seule fois (f954cc1, 2026-08-02, scratch « fresh rebuild ») :
byte-identique à out/, produit par RIEN (le générateur n'écrit que dans out/ via
_DEFAULT_OUT), référencé NULLE PART (git grep out_fresh = 0). Il échappait à
check_artifacts (find -type d -name out = match EXACT sur `out`, jamais `out_fresh`)
donc au balayage reverse-bijection → bombe de staleness silencieuse (rien ne le
régénère : dès que la source bouge, out/ se met à jour et out_fresh/ diverge en copie
stale non-possédée). Suppression (out/ intacte, régénère byte-identique). Occurrence
isolée → pas de gate (#5). Candidat is_filled() écarté au byte-level (non-défaut, #6).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>