Skip to content

fix(translation): valider l'invariant du pivot hash_<src_lang> == src_hash (aucun check ne le couvre) #17677

Description

@jsboige

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions