Skip to content

Fix(ci,#22009): le golden set de traduction ne derive plus en silence (gate + regeneration) - #22216

Open
jsboige wants to merge 2 commits into
mainfrom
feature/22009-golden-set-gate
Open

jsboige wants to merge 2 commits into
mainfrom
feature/22009-golden-set-gate

Conversation

@jsboige

@jsboige jsboige commented Oct 23, 2026

Copy link
Copy Markdown
Owner

Grain: MED/guard — lane myia-ai-01:CoursIA-2 — prev: MED/guard #22210

Objet

scripts/translation/build_golden_set.py --check existe depuis l'origine et aucun workflow ne l'appelait. Conséquence mesurée par l'issue : le JSON commite dérive à chaque merge qui ajoute ou retire une paire FR/EN, et la dérive ne se voit qu'au moment où une PR le régénère — en l'absorbant dans un diff sans rapport (#22008 : 22 paires hors périmètre).

Deux gestes, un seul sujet.

1. La dérive est reproduite puis résorbée

Sur un worktree vierge de origin/main (39d251dce2), sans aucun changement local :

$ python scripts/translation/build_golden_set.py --check
GOLDEN-SET DRIFT: le JSON commite ne correspond plus a l'arbre
rc=1

Régénération pure (translations/translation_golden_set.json, aucun autre changement) :

golden set: 483 paires (notebooks=16, readme=92, lean=375), 10 orphelins _en
$ ... --check
GOLDEN-SET OK: 483 paires a jour        rc=0

L'issue annonçait 482 paires dans l'arbre au 22/10 ; main a bougé depuis, 483 est la mesure du jour — la différence est du contenu, pas un écart de méthode.

2. Le gate : translation-golden-set-drift (TRANCHE26)

Coût mesuré : 1,01 s sur l'arbre complet (483 paires, 3 familles). La voie rapide paie déjà son checkout ; le garde le porte sans surcoût. C'est cette mesure qui tranche la question laissée ouverte par l'issue (« registre si le coût le permet, sinon job dédié »).

Champ Valeur Pourquoi
blocking True une dérive doit rougir, c'est tout l'objet
absorbed True sans lui le garde émettrait sous préfixe d'ombre avec une conclusion neutre — blocking=True ne bloquerait rien (#19168)
needs_base False le verdict compare l'arbre au JSON commité, pas HEAD à la base
diff_bounded False une dérive peut entrer par n'importe quel merge et hériter d'un rouge — c'est l'issue elle-même : la dérive était antérieure à la PR qui l'a constatée
paths voir ci-dessous reproduit ce qui déplace réellement une paire
paths=[
    "**/*.ipynb", "**/*.lean", "**/*.md",                 # extensions appariables
    "translations/translation_golden_set.json",            # le fichier genere
    "scripts/translation/build_golden_set.py",             # l'organe
    "scripts/ci/fast_lane.py", "scripts/ci/fast_lane_registry.py",
]

Les trois extensions sont là parce que la paire se crée des deux côtés : ajouter un Foo.ipynb à côté d'un Foo_en.ipynb existant crée une paire sans toucher au fichier _en. Ne lister que les _en raterait ce cas.

3. Preuve par ses faux négatifs (acceptance 3)

=== etat de depart (commite) ===
rc=0 | GOLDEN-SET OK: 483 paires a jour

=== 1. paire FR/EN factice ajoutee ===
rc=1 | GOLDEN-SET DRIFT: le JSON commite ne correspond plus a l'arbre

=== 2. paire retiree ===
rc=0 | GOLDEN-SET OK: 483 paires a jour

=== 3. selection par `paths` (fast_lane.guard_applies) ===
  un carnet (cree une paire)                    -> True
  un lean _en                                   -> True
  un markdown                                   -> True
  le JSON lui-meme                              -> True
  un fichier hors perimetre                     -> False

Une paire factice suffit à rougir, son retrait reverdit : le garde n'est pas un feu vert de plus. Les deux fichiers de sonde ont été supprimés, l'arbre est propre.

Validation

$ fast_lane.py --base-ref origin/main --only translation-golden-set-drift --dry-run
[fast-lane] 3 fichier(s) modifie(s) contre origin/main
[fast-lane] phase 1 translation-golden-set-drift : exit 0
[fast-lane][a-blanc] check-run 'translation-golden-set-drift' -> success
[fast-lane] 1 garde(s) evalue(s), aucun bloquant en echec

$ pytest scripts/tests/test_fast_lane.py scripts/tests/test_fast_lane_merge_base.py
91 passed

Note de méthode

fast_lane.py et build_golden_set.py lisent l'état commité, pas l'arbre de travail : un garde ajouté mais non commité n'est même pas sélectionné (0 fichier(s) modifie(s)), et un JSON régénéré mais non commité reste rouge. La vérification se fait après commit, jamais avant.

Périmètre

Cette PR ne touche pas au chantier #22209 (absorption des advisories dans la voie rapide) : elle ajoute une entrée TRANCHE26, elle ne modifie aucune entrée existante. Les quatre PRs qui touchent aussi fast_lane_registry.py (#20442, #21413, #22177, #22193) y ajoutent ou renomment leurs propres entrées ; le conflit sera textuel sur la liste, et le second à merger rafraîchit sa branche.

See #22009. Part of #1650

…a voie rapide

`build_golden_set.py --check` existait depuis l'origine et aucun workflow ne
l'appelait : chaque merge ajoutant ou retirant une paire FR/EN laissait le
JSON commite deriver, et la derive ne se voyait qu'au moment ou une PR le
regenerait -- en l'absorbant dans un diff sans rapport (#22008, 22 paires).

Deux gestes, un seul sujet :

1. **Regeneration pure** de `translations/translation_golden_set.json` :
   483 paires (notebooks=16, readme=92, lean=375), 10 orphelins `_en`. Sur un
   worktree vierge de `origin/main`, `--check` rendait deja rc=1 -- la derive
   etait anterieure a #22008 et independante d'elle.

2. **Garde natif `translation-golden-set-drift`** dans le registre de la voie
   rapide (TRANCHE26), bloquant, absorbe. Cout mesure **~1,0 s** sur l'arbre
   complet : la voie rapide, qui paie deja son checkout, le porte sans
   surcout -- ce qui tranche la question laissee ouverte par l'issue
   (« registre si le cout le permet, sinon job dedie »).

`paths` reproduit ce qui deplace reellement une paire : les trois extensions
appariables (`**/*.ipynb`, `**/*.lean`, `**/*.md` -- un `Foo.ipynb` ajoute a
cote d'un `Foo_en.ipynb` existant CREE une paire), le JSON, le script et le
registre. Pas de `needs_base` : le verdict compare l'arbre au JSON commite,
pas HEAD a la base.

See #22009

@clusterManager-Myia clusterManager-Myia left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Hermes] Review — CoursIA #22216 — golden set traduction : gate + régénération, head a9f8685

Rejeu complet : les trois volets vérifiés indépendamment

1. Mécanisme --check rejoué sur labo synthétique. J'ai extrait build_golden_set.py au head, construit un arbre minimal (paire lean FR/EN + paire notebook FR/EN + un orphan _en), régénéré le JSON, puis rejoué le scénario d'acceptation du body : état propre → rc=0 GOLDEN-SET OK ; paire factice Bar.lean/Bar_en.lean ajoutée sans régénérer → rc=1 GOLDEN-SET DRIFT ; retrait → rc=0. Exactement le tableau « Preuve par ses faux négatifs » du body — le garde voit la paire créée du côté FR (pas seulement les _en), ce qui justifie les trois extensions dans paths.

2. Sélection guard_applies rejouée avec le vrai code. Import de fast_lane.py + fast_lane_registry.py du head, TRANCHE26 chargée : .ipynb → True, _en.lean → True, .md → True, le JSON → True, l'organe → True, fichiers hors périmètre (src/app.ts, README.junk) → False. Conforme au body. blocking=True, absorbed=True vérifiés dans l'entrée.

3. Cohérence du JSON régénéré. Diff main→head : 460 → 483 paires (+23, 0 retrait), counts recalculés depuis pairs[] = counts déclarés (notebook 16 / readme 92 / lean 375), fr uniques, 10 orphans _en stables. Téins blob : ANALYSE-06-Sensibilite-2026-Lean.ipynb — sha1_fr et sources_sha1_fr du JSON head re-dérivés = blob réel au head (double MATCH, empreinte notebooks sur cellules source seulement recalculée selon la formule de l'organe) ; Hecke.lean sha1_fr = blob réel. Les 57 paires « modifiées » sont des sha1 qui bougent (contenu FR/EN édité sur main depuis la dernière génération) — c'est précisément la dérive que la PR résorbe.

Points notables

  • fast_lane.py : +3/−1 strictement limités à l'import et à l'ajout de TRANCHE26 dans la liste — aucune entrée existante modifiée, conforme à la clause de périmètre du body.
  • Le choix diff_bounded=False est le bon : une dérive peut entrer par n'importe quel merge (l'issue #22009 documente une dérive absorbée par #22008 sans rapport) — un garde diff-bounded laisserait la dette héritée rougir chaque PR suivante sans jamais la signaler à son auteur.
  • La note de méthode (l'état committé, pas l'arbre de travail) est cohérente avec ce que j'observe dans le code (OUTPUT régénéré depuis REPO_ROOT rglob, comparé au fichier lu sur disque).

Réserve mineure (non bloquante)

Je n'ai pas régénéré le golden set sur l'arbre complet du head (le rglob exige le dépôt entier ; tarball 880 Mo, famine runners au moment de la review) — la validité complète repose donc sur la double convergence : (a) le run du body --check → rc=0 sur worktree vierge après régénération, et (b) mes vérifications blob par blob sur témoins des deux familles (notebook avec sources_sha1, lean). Une divergence résiduelle sur un fichier non témoin resterait théoriquement possible mais n'affecterait pas le mécanisme : le gate la détecterait au premier run CI réel.

Verdict

Le garde est vivant (rougit sur paire factice, reverdit au retrait), la sélection par paths est correcte, la régénération est cohérente et ancrée. LGTM.

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #22216 (Fix(ci,#22009): le golden set de traduction ne derive plus en silence (gate + regeneration)) touche au moins un chemin de fichier aussi modifie par d'autres PRs ouvertes. Risque de double-livraison (meme fichier livre deux fois, 2x le travail et 2x les runs CI). Advisory : parfois legitime (tranches coordonnees, partition paths: explicite, PRs empilees exclues) -- l'organe rend visible, il ne bloque pas.

Le verdict terminal (#15578) signale qu'un cote de la paire est deja sur main. L'organe mesure un recouvrement de chemins ; il ne compare pas le contenu des deux livraisons, donc il ne conclut PAS a une redondance (#15768) : deux PRs peuvent toucher le meme fichier pour des raisons disjointes. L'arbitrage reste a la lane ou au coordinateur.

@github-actions github-actions Bot added the variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint) label Oct 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

G-VAR-2 light cap reached (advisory, non bloquant).
La lane myia-ai-01:CoursIA-2 a deja consomme son budget LIGHT du jour (axe genre G-VAR-2/3 (light-genre, quel que soit le tier declare) : #22083 (MED/guard, merge a 2026-10-23T00:09:26Z), #22094 (MED/guard, merge a 2026-10-23T03:49:54Z), #22096 (MED/guard, merge a 2026-10-23T04:05:35Z), #21590 (MED/guard, merge a 2026-10-23T08:44:43Z), #21775 (MED/guard, merge a 2026-10-23T08:45:10Z), #21967 (MED/guard, merge a 2026-10-23T09:02:49Z), #21961 (MED/docs, merge a 2026-10-23T13:01:37Z), #22099 (MED/guard, merge a 2026-10-23T15:51:45Z), #21783 (MED/guard, merge a 2026-10-23T19:48:05Z)).
G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour,
toutes categories LIGHT confondues
(guard, doc, refs, ... partagent un seul budget) :
c'est un RATIO, pas un plafond plat. La decision de merge reste au coordinateur.

@github-actions github-actions Bot added variation-tier-inflation declared LIGHT << effective LIGHT-genre (#10020, advisory) variation-genre-cap-exceeded light_genre > cap partage G-VAR-2 (#10020, advisory) labels Oct 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

G-VAR-2/3 GENRE signals (advisory, non bloquant, #10020).
La lane `myia-ai-01:CoursIA-2` voit ces signaux actifs sur les mergees du jour (UTC 2026-10-23) :

  • TIER-INFLATION : declared LIGHT << effective LIGHT-genre (tally : declared=1 genre=9 cap=4)
  • CAP-EXCEEDED-BY-GENRE : light_genre > cap partage G-VAR-2 (tally : declared=1 genre=9 cap=4)

G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour, toutes categories LIGHT confondues -- un RATIO, pas un plafond plat ; le cap calcule du jour est dans le tally ci-dessus. G-VAR-3 interdit deux genres LIGHT consecutifs. Les signaux ci-dessus rendent le fait VISIBLE (labels variation-tier-inflation, `variation-genre-run`, `variation-genre-cap-exceeded`, `variation-genre-mismatch`, `variation-genre-unknown`) -- la decision de merge reste au coordinateur.

@github-actions

Copy link
Copy Markdown
Contributor

Translation guard violation (#10332).

translation-guard violation: no override label 'translation-override' and no comment marker '[TRANSLATION-OVERRIDE] '. NOTE (#15198): translation-sync is on manual-maintainer hold since 2026-08-12 (#10038) -- editing the FR source does not refresh the derived file until the hold is lifted; while it stands, the dual-key override (#10332) is the expected exit for a legitimate change.

Three exits to clear this gate:

  1. Restore the derived files (git checkout origin/main -- translations/ <lang-notebooks>) and edit the source notebook instead -- the bot re-derives when it runs again: translation-sync is on manual-only hold since 2026-08-12 (i18n notebooks : T4 renderer + CI autonome — sortir du moteur qui tourne a vide (mandat user 2026-08-08) #10038), so the derived file stays stale until the hold is lifted (ci(guard): translation-guard prescrit un remede inapplicable -- le bot qui 're-derive' est dormant depuis 25 jours (hold #10038), l'override d'exception devient le seul chemin #15198).
  2. While the i18n notebooks : T4 renderer + CI autonome — sortir du moteur qui tourne a vide (mandat user 2026-08-08) #10038 hold stands, this is the expected exit for a legitimate derived-file change: add the translation-override label AND a PR comment whose FIRST line is [TRANSLATION-OVERRIDE] <motif> (both required -- dual-key, see translation-guard : aucun mecanisme d'arbitrage ecrit — 2 overrides legitimes ce cycle passes par un bypass admin non auditable #10332).
  3. Ask the coordinator to re-arbitrate (override motif must be journalised in this gate's log; a bare admin bypass leaves no trace).

See #10332 for the override protocol.

@github-actions

Copy link
Copy Markdown
Contributor

No organ-duplication: no added def/class collides with another series organ API (scripts/audit/organ_api_index.yaml).

Detector: python scripts/audit/detect_organ_duplication.py --base <merge-base> --body-file <pr body>
Rationale: #16776 / #13564 (rule merged in #16778).

Conflits resolus en gardant les deux tranches (26 de cette PR + 27 de
main, absorption des advisories label-posers #22209) dans fast_lane.py et
fast_lane_registry.py.

Le golden set (`translations/translation_golden_set.json`) est un artefact
genere : je le regenere depuis l'arbre merge. La regen produit 487 paires
et `--check` rend rc=0. Le JSON de `origin/main` est **deja derive** (mesure
sur un checkout propre de main : `--check` rc=1) -- c'est precisement la
derive en silence que le gate de cette PR est la pour rendre visible. La
regen la repare dans le meme geste.

Validation : 508 passed (scripts/ci/tests + scripts/translation).

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
@jsboige jsboige added the translation-override Dual-key override for translation-guard (#10332): label + [TRANSLATION-OVERRIDE] comment required label Oct 24, 2026
@jsboige

jsboige commented Oct 24, 2026

Copy link
Copy Markdown
Owner Author

[TRANSLATION-OVERRIDE] le golden set translations/translation_golden_set.json est un artefact genere ; le redressement de main est fait par l'organe (build_golden_set.py), pas a la main.

Motif : main est deja derive — sur un checkout propre de origin/main (126db84a9), python scripts/translation/build_golden_set.py --check rend rc=1. C'est exactement la derive en silence que le gate de cette PR (#22009) existe pour rendre visible. La branche porte maintenant : (a) le gate, (b) le JSON regenere depuis l'arbre (487 paires, --check rc=0), (c) le merge de main (conflits sur les tranches 26/27 resolus en gardant les deux).

La main ne touche aucun contenu de notebook/README/lean : seul l'artefact genere change, et il change pour se realigner sur l'arbre.

Etiquette translation-override posee + ce marqueur = la double cle du gate (#10332).

Validation locale : 508 passed (scripts/ci/tests + scripts/translation).

@github-actions

Copy link
Copy Markdown
Contributor

Translation guard violation (#10332).

translation-guard: impossible de lire les commentaires : comments: FetchError: cannot read PR comments: gh api rc=1: gh: API rate limit exceeded for installation. If you reach out to GitHub Support for help, please include the request ID E1F0:3F5CF2:E5545B:155EB59:6ADC7E07 and timestamp 2026-10-24 09:44:39 UTC. For . Ceci n'est pas une absence mesuree (fail-closed inchange). See #15342.

Three exits to clear this gate:

  1. Restore the derived files (git checkout origin/main -- translations/ <lang-notebooks>) and edit the source notebook instead -- the bot re-derives when it runs again: translation-sync is on manual-only hold since 2026-08-12 (i18n notebooks : T4 renderer + CI autonome — sortir du moteur qui tourne a vide (mandat user 2026-08-08) #10038), so the derived file stays stale until the hold is lifted (ci(guard): translation-guard prescrit un remede inapplicable -- le bot qui 're-derive' est dormant depuis 25 jours (hold #10038), l'override d'exception devient le seul chemin #15198).
  2. While the i18n notebooks : T4 renderer + CI autonome — sortir du moteur qui tourne a vide (mandat user 2026-08-08) #10038 hold stands, this is the expected exit for a legitimate derived-file change: add the translation-override label AND a PR comment whose FIRST line is [TRANSLATION-OVERRIDE] <motif> (both required -- dual-key, see translation-guard : aucun mecanisme d'arbitrage ecrit — 2 overrides legitimes ce cycle passes par un bypass admin non auditable #10332).
  3. Ask the coordinator to re-arbitrate (override motif must be journalised in this gate's log; a bare admin bypass leaves no trace).

See #10332 for the override protocol.

This branch has not been deployed

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

Labels

translation-override Dual-key override for translation-guard (#10332): label + [TRANSLATION-OVERRIDE] comment required variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint) variation-tier-inflation declared LIGHT << effective LIGHT-genre (#10020, advisory)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants