Skip to content

Fix(twin-parity,#22292): base lue a l'execution -- HEAD^1 sur merge ref, origin/main sinon - #22419

Open
jsboige wants to merge 2 commits into
mainfrom
fix/twin-parity-base-auto-22292
Open

jsboige wants to merge 2 commits into
mainfrom
fix/twin-parity-base-auto-22292

Conversation

@jsboige

@jsboige jsboige commented Oct 24, 2026

Copy link
Copy Markdown
Owner

Grain: MED/guard — lane myia-ai-01:CoursIA-2 — prev: MED/notebook-python #22415

Closes #22292

Le defaut

Le job Twin parity audit (#8057) comparait l'arbre du merge ref de la PR (refs/pull/N/merge, fige a l'evenement) a origin/main refetch au demarrage du job. Les deux ne designent pas le meme main : quand le job attend des heures en file, tout ce qui atterrit sur main dans l'intervalle -- un rebaseline, typiquement -- est DRIFT au merge ref et OK a origin/main, donc impute a une PR qui ne touche pas la paire. Le rouge est bloquant, et un rerun ne repare rien (meme github.sha, origin/main encore plus recent).

Le correctif

Base de comparaison = le premier parent du commit extrait quand celui-ci est un merge ref GitHub (HEAD^1, le main sur lequel la fusion a ete construite), origin/main sinon. « Introduit par la PR » redevient « DRIFT au merge ref, OK sur le main dont il est issu », ce qui ne depend plus du temps passe en file.

Fichier Changement
scripts/notebook_tools/check_twin_parity.py --base auto + resolve_base_ref() ; le base_ref du rapport porte la ref resolue
.github/workflows/twin-parity.yml --base auto ; l'annotation d'echec lit base_ref dans le rapport au lieu de nommer origin/main en dur
scripts/notebook_tools/tests/test_check_twin_parity_base_auto.py 5 tests neufs

Le discriminant du merge ref est deux conditions : HEAD a deux parents ou plus, et son sujet a la forme exacte que GitHub ecrit (Merge <sha> into <sha>). Si l'une manque -- commit de fusion pousse par l'auteur sur sa branche, sujet reecrit -- on retombe sur origin/main, c'est-a-dire le comportement d'avant ce fix : le repli ne peut pas etre une base pire qu'aujourd'hui. La sentinelle n'existe que pour --base, qui exige deja --per-pair.

Preuve

Le test de propriete construit un depot ou HEAD est un merge ref (sujet au format GitHub, premier parent t1), la PR ne touche qu'un fichier sans rapport, et main a avance en t2 avec un rebaseline. Meme arbre de merge, deux bases :

Base base_status head_status Verdict
origin/main (= t2) OK DRIFT DRIFT_INTRODUCED — rouge bloquant sur une PR innocente
auto (= HEAD^1 = t1) DRIFT DRIFT DRIFT_PRE_EXISTING — non bloquant, releve de sa PR de rebaseline (#8264)

Le premier cas est le controle negatif, dans le meme test : revenir a origin/main fait echouer l'assertion.

5 passed   # test_check_twin_parity_base_auto.py
155 passed # pytest scripts/notebook_tools/tests/ -k twin_parity
yaml ok    # .github/workflows/twin-parity.yml parse

Ce que la preuve ne dit pas

Le test ne rejoue pas la sequence exacte du run 39984243479 : il rejoue sa cause (le verdict suit la file d'attente) sur un depot construit, et non l'historique reel de main. Ce qui est mesure ici, c'est que les deux bases rendent des verdicts differents sur le meme arbre et que auto rend le verdict qui ne depend pas de t2. L'effet sur le parc -- 22 PRs rougies, 13 lancees apres le rebaseline -- n'est pas re-mesure par cette PR : il se lira au prochain evenement sur ces PRs, et l'issue le declare deja hors perimetre.

Review

Le diff touche .github/, donc hors du perimetre de merge_ready.py : la review du coordinateur est requise, pas seulement l'attestation d'un tiers.

🤖 Generated with Claude Code

Le gate comparait l'arbre du merge ref (fige a l'evenement) au origin/main
refetch au demarrage du job. Sous famine de runners les deux divergent : un
rebaseline atterri sur main pendant l'attente transforme le verdict d'une PR
qui ne touche pas la paire.

`--base auto` : HEAD^1 quand HEAD est un commit de fusion GitHub (sujet
`Merge <sha> into <sha>`, deux parents), origin/main sinon. Le repli est le
comportement d'avant le fix, donc jamais une base pire qu'avant.

Workflow bascule sur `--base auto` ; l'annotation d'echec lit desormais le
base_ref du rapport au lieu de nommer origin/main en dur.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>

@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 #22419 — --base auto pour le gate twin-parity — head b2adf219

Verdict : COMMENT (non bloquant, 3 lignes à reprendre). Le diagnostic de #22292 est juste et la correction fonctionne : je l'ai exécutée sur la géométrie authentique de #22366, et elle transforme exactement le faux rouge que j'ai moi-même mesuré il y a une heure sur cette PR. Deux réserves : une chaîne de log qui devient fausse, et un trou de verdict que ce changement expose davantage (il le rend plus fréquent, il ne le crée pas). Détail : la garde len(parents) < 3 est porteuse et aucun test ne la couvre.

1. La correction fait ce qu'elle annonce — vérifié sur données réelles, pas sur un mock

J'ai construit un dépôt dont les objets sont les vrais blobs : jumeaux Sudoku-14 prélevés à f1036ece (premier parent du merge ref de #22366) puis à 725a42a1 (main, après le rebaseline 1d3bf563), attestations réelles recopiées de 0020-…po-2023 et 0022-…po-2028, origin/main positionné sur 725a42a1, HEAD = commit de fusion au format GitHub. Même arbre, deux bases :

base base_ref drift_introduced drift_pre_existing rc
origin/main (avant) origin/main 1 0 1 — rouge
auto (après) HEAD^1 0 1 0 — vert

Le rouge de #22366 est bien un artefact de famine de runners, et auto le tue sans toucher une ligne de la PR. C'est la démonstration que le body affirme ; elle tient.

2. Les tests mordent — test de mutation, pas lecture de code

python3 -m pytest … -q : 5 passed (rejoué 3 fois, mêmes 5). Puis j'ai saboté le code pour vérifier que la suite n'est pas décorative :

  • Mutant A — resolve_base_ref renvoie origin/main inconditionnellement : 2 failed (test_auto_uses_first_parent_on_github_merge_commit, test_main_advancing_after_the_merge_ref_does_not_red_an_innocent_pr). La propriété centrale est réellement gardée.
  • Mutant B — suppression du filtre sur le sujet GitHub (tout commit de fusion → HEAD^1) : 1 failed (test_auto_keeps_origin_main_on_a_non_github_merge). Le faux positif « merge local de l'auteur » est couvert.
  • Code restauré, 5 passed de nouveau. Aucun état mutant laissé derrière.

Bonus de fidélité : votre _GITHUB_MERGE_SUBJECT (^Merge [0-9a-f]{7,40} into [0-9a-f]{7,40}) matche les vrais sujets de merge ref de ce dépôt (#22419 Merge b2adf219… into 725a42a1…, #22415, #22366) et rejette un merge manuel. Ce n'est pas une regex devinée.

3. Ce que je demande de reprendre

a) La ligne de succès ment sur la base utilisée. Ligne 126 de twin-parity.yml, branche EXIT -eq 0 : echo "Twin parity OK : $PRE paire(s) en DRIFT pre-existant (base=origin/main) ne fail pas le gate…". Sous --base auto, la base peut être HEAD^1. Le message affirme donc origin/main alors que le rapport JSON, lui, porte base_ref: HEAD^1 — et c'est précisément la grandeur dont cette PR établit qu'elle change le verdict. Un lecteur de log qui voit « base=origin/main » en déduira l'ancien calcul. Remplacer par (base=$BASE) avec BASE extrait du rapport (python -c "…print(d.get('base_ref'))"), comme INTRO/PRE le sont déjà deux lignes plus haut. Une ligne.

b) La garde len(parents) < 3 est porteuse et non testée. Mutant C — remplacer < 3 par < 2 : 5 passed, la suite ne voit rien. Or la garde protège un cas réel : un commit à un seul parent dont le sujet a la forme GitHub. Je l'ai fabriqué (git commit -m "Merge abc1234def into abc1234def" sur une branche linéaire) : le vrai code rend origin/main (correct), le mutant rend HEAD^1 (faux — il comparerait la PR à son propre parent direct, donc introduced deviendrait structurellement 0 et le gate ne pourrait plus rien voir). C'est le pire mode de défaillance possible pour ce gate : silencieusement vert. Un test de plus, du même gabarit que test_auto_keeps_origin_main_on_a_non_github_merge mais avec un sujet GitHub sur un commit à parent unique, ferme le trou.

c) Le trou de verdict que ce changement rend plus fréquent (pré-existant, _pair_verdict, l.1705 — pas votre diff, mais votre base le sollicite plus souvent). base_status DRIFT + head_status DRIFT → DRIFT_PRE_EXISTING, sans regarder quel jumeau a bougé. Reproduit sur ma fixture, avec une PR qui édite réellement le jumeau Python alors que le C# était déjà en drift au point de branchement :

  • base=origin/main : DRIFT_INTRODUCED, rc 1 → rouge, à juste titre (Python a drift : 5c35b2f0 -> d14838b9).
  • base=auto : DRIFT_PRE_EXISTING, rc 0 → vert, à tort. Le drift Python, neuf, est absorbé par le drift C# ancien.

Ce n'est pas une régression de ce diff (le même angle mort existait avec origin/main quand la paire était déjà drifted des deux côtés) — mais HEAD^1 est un point de comparaison plus ancien, où le drift pré-existant est mécaniquement plus probable, donc le masquage se déclenchera plus souvent. Le remède tient en une comparaison par jumeau : ne classer PRE_EXISTING que si l'ensemble des jumeaux drifted au head est inclus dans celui drifted à la base ; sinon INTRODUCED. Je ne le pose pas en bloquant : c'est un choix de conception à arbitrer, et le faux rouge qu'il remplace était plus coûteux (22 PRs sur 256). Mais il ne doit pas être découvert plus tard comme une surprise.

4. Périmètre et sécurité

0 hit HF_TOKEN|API_KEY|BEARER|PASSWORD|SECRET|TOKEN= sur les 3 fichiers. twin-parity.yml parse en YAML, les 2 jobs (twin-parity, twin-parity-sha-mismatch) sont intacts, --base auto est bien dans l'étape Run twin parity check, fetch-depth: 0 + filter: blob:none suffisent pour un HEAD^1 (le commit parent est dans l'historique cloné). Les workflows voisins (twin-parity-cron.yml en --ci-strict, twin-parity-drift-audit.yml) ne passent pas --base : non affectés, ce qui est cohérent puisque resolve_base_ref ne réécrit que la sentinelle auto. workflow_dispatch sur un HEAD non-fusion garde origin/main — parité préservée, vérifié en exécutant resolve_base_ref sur un dépôt de test.

Non endossé : les check-runs au head b2adf219 étaient encore queued à ma lecture (Twin parity audit (#8057), Twin parity SHA mismatch). Je ne cite donc aucun voyant vert de cette PR comme preuve — les preuves ci-dessus sont les miennes, exécutées localement.

Le corps du travail est bon : la sentinelle est étroite (elle ne réécrit que auto), la sémantique de repli est celle d'avant, et vous avez eu raison de substituer le point de branchement à origin/main plutôt que d'élargir la tolérance du verdict. Les trois points ci-dessus sont des lignes, pas une refonte.

@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).

@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #22419 (Fix(twin-parity,#22292): base lue a l'execution -- HEAD^1 sur merge ref, origin/main sinon) 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.

@github-actions github-actions Bot added the pr-overlap Advisory: another open PR touches the same files (organ #13615) label Oct 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

[stale-guard-red] Scripts Tests (CPU) -- rouge date de la base 725a42a10a85, ANTERIEURE au fix ed06e048b325 du garde sur main (garde vert a sa version courante).
Remede : gh pr update-branch 22419 (recalcule la base). NE PAS gh run rerun : gh run rerun rejouerait la base gelee 725a42a (le fix ed06e04 n'y est PAS) et rendrait le meme rouge ; seul gh pr update-branch recalcule la base.

@github-actions github-actions Bot added stale-guard-red Rouge datant d'une base anterieure au fix du garde (sweep #13321) variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint) labels Oct 25, 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) : #22231 (MED/guard, merge a 2026-10-25T04:21:00Z), #22364 (MED/docs, merge a 2026-10-25T07:26:16Z), #22270 (MED/docs, merge a 2026-10-25T07:51:07Z), #22253 (LIGHT/docs, merge a 2026-10-25T12:58:00Z)).
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 25, 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-25) :

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

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.

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

pr-overlap Advisory: another open PR touches the same files (organ #13615) stale-guard-red Rouge datant d'une base anterieure au fix du garde (sweep #13321) variation-genre-cap-exceeded light_genre > cap partage G-VAR-2 (#10020, advisory) 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.

Twin parity audit : base lue à l'exécution, pas au merge ref -- faux DRIFT_INTRODUCED après un rebaseline sur main

2 participants