Constat
scripts/translation/extract_cells_to_csv.py pose un invariant de construction sur la ligne pivot d'un CSV de traduction :
row["src_hash"] = cell_hash(text)
row[f"hash_{src_lang}"] = row["src_hash"] # commentaire : « hash_{src_lang} == src_hash par construction »
Pour la langue pivot (fr), hash_fr doit donc toujours egaler src_hash — et les deux egaler cell_hash(source notebook).
Aucun check ne le valide. scripts/translation/check_translation_sync.py boucle sur TARGET_LANGS = ["en","es","ar","fa","zh","ru","pt"] — le pivot fr n'y est pas, avec le commentaire explicite « sa coherence est deja verifiee par SRC_DRIFT ci-dessus -> on le skippe ». Or SRC_DRIFT compare csv src_hash a la source notebook : il ne dit rien de hash_fr. Le champ hash_fr d'une ligne pivot n'est donc lu par personne.
Mesure (2026-09-24, lane myia-po-2026:CoursIA)
Sur la PR #17649, un resync manuel de la ligne cell-70d7edfe (translations/partner-course-quant-trading/partner-course.csv) avait ecrit src_hash=b57efbe05e2c9891 avec hash_fr=cc6406788d3ad741 — et cc6406788d3ad741 est le hash du text_en de la meme ligne. Les deux colonnes de hash etaient decalees d'un cran ; hash_en ne correspondait plus non plus a text_en.
Recalcul avec la recette du depot (cell_hash = sha256(normalize(texte))[:16], normalize = rstrip par ligne + strip des \n aux bornes) :
| Champ |
Declare |
Recalcule |
Verdict |
src_hash |
b57efbe05e2c9891 |
cell_hash(source notebook) = b57efbe05e2c9891 |
coherent |
hash_fr |
cc6406788d3ad741 |
cell_hash(text_fr) = b57efbe05e2c9891 |
incoherent (= hash du text_en) |
hash_en |
01206fb6375c62cf |
cell_hash(text_en) = cc6406788d3ad741 |
incoherent |
Tout le CI de traduction etait vert sur cette ligne : Translation drift (read-only) success et Translation hot-drift (base vs PR, advisory) success. Le seul champ faux est precisement celui qu'aucun check ne lit.
Sur les 3 autres lignes du meme cell_id (le CSV en porte 4 pour cell-70d7edfe), l'invariant etait respecte — le defaut est donc bien localise et non un artefact de lecture.
Ce que ce trou permet
Le garde translation-guard (#10332) impose un dual-key translation-override + [TRANSLATION-OVERRIDE] <motif> pour tout resync manuel, presente comme la « sortie auditable ». Mais l'auditabilité porte sur la declaration, pas sur la validite : un resync incoherent peut etre declare legitime et passer. Le commentaire d'override du precedent #17491 verifie a la main les hashes des trois cotes (hash declare / hash du champ / hash de la source) — c'est une discipline humaine, non outillee.
Proposition
Ajouter au checker une verification de l'invariant pivot, avec le meme traitement que les autres anomalies (JSON + exit code, non-bloquant en --check) :
- pour chaque ligne, si
hash_{src_lang} est non vide, exiger hash_{src_lang} == src_hash ; sinon verdict PIVOT_HASH_MISMATCH avec les deux valeurs ;
- couvrir aussi le cas ou
hash_{src_lang} et src_hash sont non vides mais differents de cell_hash(text_{src_lang}) ;
- test unitaire avec une fixture portant les deux formes (conforme + decalee d'un cran, comme ci-dessus).
Note de portee : ceci ne remplace pas la verification du contenu traduit (hash_en vs cell_hash(text_en)), qui se heurte au fait que les notebooks cibles sont absents du depot — c'est le sens du verdict MISSING_LANG existant. L'invariant pivot, lui, est entierement verifiable hors ligne.
Contexte lie
See #4957
Constat
scripts/translation/extract_cells_to_csv.pypose un invariant de construction sur la ligne pivot d'un CSV de traduction :Pour la langue pivot (
fr),hash_frdoit donc toujours egalersrc_hash— et les deux egalercell_hash(source notebook).Aucun check ne le valide.
scripts/translation/check_translation_sync.pyboucle surTARGET_LANGS = ["en","es","ar","fa","zh","ru","pt"]— le pivotfrn'y est pas, avec le commentaire explicite « sa coherence est deja verifiee par SRC_DRIFT ci-dessus -> on le skippe ». OrSRC_DRIFTcomparecsv src_hasha la source notebook : il ne dit rien dehash_fr. Le champhash_frd'une ligne pivot n'est donc lu par personne.Mesure (2026-09-24, lane myia-po-2026:CoursIA)
Sur la PR #17649, un resync manuel de la ligne
cell-70d7edfe(translations/partner-course-quant-trading/partner-course.csv) avait ecritsrc_hash=b57efbe05e2c9891avechash_fr=cc6406788d3ad741— etcc6406788d3ad741est le hash dutext_ende la meme ligne. Les deux colonnes de hash etaient decalees d'un cran ;hash_enne correspondait plus non plus atext_en.Recalcul avec la recette du depot (
cell_hash=sha256(normalize(texte))[:16],normalize= rstrip par ligne + strip des\naux bornes) :src_hashb57efbe05e2c9891cell_hash(source notebook)=b57efbe05e2c9891hash_frcc6406788d3ad741cell_hash(text_fr)=b57efbe05e2c9891text_en)hash_en01206fb6375c62cfcell_hash(text_en)=cc6406788d3ad741Tout le CI de traduction etait vert sur cette ligne :
Translation drift (read-only)success etTranslation hot-drift (base vs PR, advisory)success. Le seul champ faux est precisement celui qu'aucun check ne lit.Sur les 3 autres lignes du meme
cell_id(le CSV en porte 4 pourcell-70d7edfe), l'invariant etait respecte — le defaut est donc bien localise et non un artefact de lecture.Ce que ce trou permet
Le garde
translation-guard(#10332) impose un dual-keytranslation-override+[TRANSLATION-OVERRIDE] <motif>pour tout resync manuel, presente comme la « sortie auditable ». Mais l'auditabilité porte sur la declaration, pas sur la validite : un resync incoherent peut etre declare legitime et passer. Le commentaire d'override du precedent #17491 verifie a la main les hashes des trois cotes (hash declare / hash du champ / hash de la source) — c'est une discipline humaine, non outillee.Proposition
Ajouter au checker une verification de l'invariant pivot, avec le meme traitement que les autres anomalies (JSON + exit code, non-bloquant en
--check) :hash_{src_lang}est non vide, exigerhash_{src_lang} == src_hash; sinon verdictPIVOT_HASH_MISMATCHavec les deux valeurs ;hash_{src_lang}etsrc_hashsont non vides mais differents decell_hash(text_{src_lang});Note de portee : ceci ne remplace pas la verification du contenu traduit (
hash_envscell_hash(text_en)), qui se heurte au fait que les notebooks cibles sont absents du depot — c'est le sens du verdictMISSING_LANGexistant. L'invariant pivot, lui, est entierement verifiable hors ligne.Contexte lie
translation-sync.ymln'a plus tourne depuis le 2026-08-14 (dernier runpush/main, success) : la voie automatique est de fait morte, ce qui multiplie les resyncs manuels et donc l'exposition a ce trou.See #4957