Skip to content

fix(ci,#14598): parallelize Scripts Tests (CPU) with pytest-xdist -n 4 --dist loadscope - #15833

Merged
jsboige merged 3 commits into
mainfrom
fix/14598-scripts-tests-xdist
Sep 13, 2026
Merged

jsboige merged 3 commits into
mainfrom
fix/14598-scripts-tests-xdist

Conversation

@myia-ai-01

@myia-ai-01 myia-ai-01 commented Sep 12, 2026 •

Copy link
Copy Markdown
Collaborator

Grain: MED/guard — lane myia-ai-01:CoursIA — prev: MED/docs #15727

Le défaut n'est pas un plafond trop bas, c'est une classe de runner

Scripts Tests (CPU) échoue depuis des semaines par annulation, et le réflexe — relever timeout-minutes: 20 — aurait consacré le défaut au lieu de le corriger. La mesure sur 68 jobs dit autre chose :

Classe de runner Conclus Annulés Durée moyenne
myia-po-2024-linux-docker-* 3 / 40 37 18,3 min
myia-ai-01-wsl-* 24 / 25 1 (concurrence) 9,9 min

92,5 % de morts d'un côté, 4 % de l'autre. Une suite qui aurait réellement dépassé son plafond mourrait sur les deux classes ; six runs verts siègent à 8-9 min. Ce qu'on observe est une loterie d'ordonnancement : une suite séquentielle qui frôle le plafond sur la classe lente est tuée avant de conclure, et le plafond n'est que le lieu où la mort devient visible. Relever le plafond aurait déplacé la loterie, pas supprimée.

Le levier est la parallélisation.

Le précédent est mesuré sur ce dépôt, pas invoqué

#15762 a parallélisé la jambe ICT tests/ (56). Les deux jambes ont conclu success :

Jambe Run Durée
Séquentielle 34693685670 35,85 min
-n 4 --dist loadscope 34694385724 18,88 min

1,90×. Et le banc n'a pas dérivé : la jambe témoin ict/tests/ (42 package) rend 14,32 min, à l'intérieur de sa bande observée 11,4-15,5 min. C'est aussi la première fois que ICT tests/ (56) était observée en train de conclure — les quatre passages précédents sous plafond 30 min ont tous été coupés.

Appliqué ici, ce facteur ramène la classe lente de 18,3 min à ~9,6 min, soit sous le plafond de 20 min avec une marge de 10 min au lieu de 1,7.

La sûreté a été auditée avant — et l'audit s'est trompé sur un point, mesuré depuis

Passe read-only initiale sur les 13 suites du job, cherchant les sept classes de collision inter-modules. Six des sept tiennent à la mesure. La septième était fausse, et c'est l'exécution sous xdist — que cette PR appelait de ses vœux — qui l'a établie.

  • Écritures à chemin fixe partagé : aucune hors d'un module unique. RÉFUTÉ. Deux fixtures remplaçaient le répertoire partagé scripts/notebook_tools/twin_pairs.d/ à chaque test : copytree → rmtree(REGISTRY_DIR) → move. Le rollback était correct en séquentiel, donc invisible ; sous -n 4 la fenêtre pendant laquelle le registre n'existe pas sur disque fait rougir tout module concurrent qui le lit. Corrigé par f3e66767bb — détail en section « Le rouge mesuré » ci-dessous.
  • Mutation git contre le vrai arbre : aucune — chaque git init des tests vit dans tmp_path ; le git fetch --refetch de test_fast_lane_merge_base.py:39 est un littéral dans une liste attendue d'un fake_run monkeypatché, jamais exécuté.
  • Sockets/ports réels : aucun bind (socket.socket|listen(|HTTPServer : 0 hit) ; le seul import socket est mocké.
  • os.environ / os.chdir : tous restaurés (finally, ou fixture autouse monkeypatch.delenv).
  • Fixtures scope="session" : zéro (grep scope=["']session : 0). Neuf fixtures module-scope, toutes restaurées en teardown.
  • conftest.py : aucun dans les 13 chemins ni à la racine.
  • test_bg_tree_lock.py — le test de verrou, celui qu'il aurait été imprudent de supposer sûr : chaque verrou vit dans tmp_path, libéré en finally ; le nom de verrou n'est jamais créé à la racine ni dans un lake réel.

Ce que cette réfutation apprend, et qui vaut plus que le correctif : un audit statique de parallélisme voit les écritures, pas les fenêtres. Les deux fixtures étaient try/finally, restauraient fidèlement, ne laissaient aucun résidu — un lecteur les classe « propres » à raison en séquentiel. Ce qu'aucune lecture ne montre est l'intervalle entre le rmtree et le move, qui n'a de conséquence que s'il existe un autre worker pour tomber dedans.

Le rouge mesuré, et ce qui le supprime

Reproduction sous les flags exacts de la PR, puis correctif, puis re-mesure aux mêmes formes :

Forme Avant Après f3e66767bb
-n 2 sur test_twin_registry_integrity + test_check_twin_parity_update_guard 10 failed, 40 passed 50 passed
-n 4 --dist loadscope sur scripts/notebook_tools/tests 5 failed, 5665 passed 5716 passed, 1 xfailed
git status sur twin_pairs.d/ après la passe — propre, aucun résidu .bak

Le contrôle -n 2 est le signal net : dénominateur identique (50 cas avant comme après), dix rouges à zéro. Les cinq rouges de la passe complète étaient trois FileNotFoundError dans test_twin_registry_integrity.py (la victime), plus deux rouges sans rapport avec le parallélisme, levés par le merge de main dans la branche : test_inventory_notebook_names.py (denominator 1262 != baseline 1259 — la branche portait 1259 notebooks contre 1262 sur main) et test_scan_slides_html_block_markdown.py. Le +47 de cas collectés vient de ces 60 commits de main.

Le correctif ne touche pas le workflow, et ne relâche pas loadscope :

  • test_check_twin_parity_surgical.py — ses deux consommateurs (test_pair_file_resolves_from_name, test_schema_file_is_never_a_rebaseline_target) ne font que lire. La sauvegarde ne protégeait donc rien ; son seul effet mesurable était la fenêtre. La fixture rend désormais _entry_files() tel quel.
  • test_check_twin_parity_update_guard.py — un seul de ses quatre tests écrit réellement. Il travaille maintenant sur une copie privée sous tmp_path, passée au script via --registry : check_twin_parity.py accepte ce drapeau depuis toujours (l.1523), et son chemin d'écriture --update en dérive tout (_pair_file, _write_audit_file). Le registre du dépôt reste intact même si --update déraille. Le pytest.fail sur registre introuvable est conservé — c'est la garde refactor(twin-register,#8542): file-per-entry twin_pairs.d/ — supprime la classe de conflit serie #8586, et un skip rendrait la suite verte à vide.

Pourquoi la suppression plutôt que --dist loadgroup, qui existe et que j'ai vérifié disponible : loadgroup distribue les tests non marqués par test, comme load. Basculer le mode du job dissoudrait la protection dont test_audit_engine_named_not_invoked.py dépend (condition 1 ci-dessous). Le correctif étroit est de retirer la mutation, pas de regrouper autour d'elle.

Ces fixtures n'étaient pas une nuisance théorique : neuf PRs ouvertes éditent twin_pairs.d/ en ce moment (#15957, #15909, #15845, #15813, #15810, #15795, #15757, #15741, #15370). Le répertoire que ces tests supprimaient est du terrain vivant.

Deux conditions portantes, documentées en clair dans le fichier

Elles ne sont pas des défauts par défaut — elles sont ce qui rend l'audit ci-dessus vrai, et un futur contributeur qui les relâcherait casserait la suite sans comprendre pourquoi :

  1. Dans .github/workflows/scripts-tests.yml : --dist loadscope, jamais load. loadscope groupe par module, donc les tests d'un module ne tournent jamais concurremment entre eux. test_audit_engine_named_not_invoked.py écrit ~17 noms fixes /tmp/_test_*.ipynb (l.121-129 et suivantes) : il est sûr uniquement grâce à ce groupement — sous --dist load il entrerait en collision avec lui-même. Le runner est Linux self-hosted, donc /tmp est réellement partagé entre les 4 workers.
  2. --import-mode importlib (déjà dans pytest.ini:17). Trois basenames de tests sont dupliqués entre deux des 13 chemins (test_check_dataset_registry, test_check_public_anchor, test_scan_slidev_composition) ; sans ce mode, pytest lève import file mismatch — séquentiellement aussi, pas seulement sous xdist.

Ce que la mesure n'établit toujours pas

La mesure ci-dessus porte sur scripts/notebook_tools/tests (5717 cas), pas sur les 13 suites entières, et elle tourne sous Windows/Python 3.14.3 alors que le job vise un runner Linux self-hosted. C'est ce run de CI qui ferme l'écart. Deux résidus nommés, aucun bloquant :

  • Ordre des modules non reproductible. Des fixtures module-scope seedent le PRNG global du worker sans le restaurer (test_kuhn_poker_cfr.py:64-75 : random.seed(42) ; même classe dans test_garch_baseline.py:32). Tout test ultérieur non seedé du même worker devient dépendant de l'ordre, et cet ordre varie d'un run xdist à l'autre. Aucun test identifié n'en dépend — mais la reproductibilité d'ordre est perdue, et c'est à dire.
  • Contention mémoire/CPU de -n 4 sur l'éphémère (z3 dans test_life_synthesize_sat.py, arch dans test_garch_baseline.py) non mesurée. Aucune assertion de durée détectée dans les 13 suites, donc pas de flakiness temporelle attendue.

Le step compagnon Audit tests collection floor (455) tourne en --collect-only hors du run parallèle : il est inchangé et non concerné.

Statut de cap — déclaré, pas contourné

Ma lane était au-delà de son plafond G-VAR-2 sur l'axe GENRE au moment où j'ai ouvert cette PR, et je le dis plutôt que de le laisser découvrir :

cap_exceeded_by_genre: true | light_genre 4 vs genre_cap 2 | tier_cap_reached: false
consommé par : #15214 (MED/guard, 00:12:43Z), #15454 (LIGHT/docs, 00:17:28Z), #15727 (MED/docs, 17:28:53Z)

guard appartient à LIGHT_GENRES, donc un MED/guard hérite du plafond par l'axe genre quel que soit son tier — c'est le comportement voulu, pas un artefact. Je n'ai pas mergé cette PR le 2026-09-12. Elle attendait le passage de journée UTC.

Ce passage a eu lieu : la lecture ci-dessus est datée du 2026-09-12 et n'est plus la mesure courante. Elle ne vaut donc pas autorisation aujourd'hui — un cap se re-mesure à la tête exacte au moment du merge, jamais ne s'hérite d'hier, et mes propres merges dans l'intervalle invalident ma propre passe. Le chiffre qui décidera sera pris à ce moment-là.

Corollaire honnête : le signal TIER-INFLATION est actif sur ma lane. Il m'accuse, il ne m'excuse pas, et il appelle son propre examen — séparément.

See #14598. See #15574.

🤖 Generated with Claude Code

Note d'edition (2026-09-13) : le nom du workflow a ete ajoute au point 1 ci-dessus. L'organe de perimetre retenait cette ligne comme assertion parce que le mot loadscope porte la sous-chaine scope, qui satisfait son test de vocabulaire ; le durcissement de ce test est traite a part. Aucune phrase de fond n'est modifiee.

Note d'edition (2026-09-13, seconde) : la PR est passee de 1 a 3 fichiers. Le point « ecritures a chemin fixe partage » est retracte comme mesure — c'est la seule affirmation refutee, et elle l'est par l'execution que ce body appelait lui-meme. Le verdict « statique » est remplace par l'A/B mesure. La ligne 45 est laissee byte-identique a dessein : elle est vraie, et la reecrire pour satisfaire l'organe de perimetre serait le maquillage que je me suis publiquement engage a ne pas faire.

…4 --dist loadscope

The sequential suite is what makes this job unschedulable on half the runner
pool. Measured over 68 `Scripts Tests (CPU)` jobs:

  myia-po-2024-linux-docker-*   3 concluded / 40   (37 cancelled, 18.3 min avg)
  myia-ai-01-wsl-*             24 concluded / 25   (1 concurrency-cancel, 9.9 min avg)

92.5% killed on one runner class and 4% on the other is not a suite that
outgrew its cap -- six green runs sit at 8-9 min. Raising timeout-minutes
would hide a scheduling lottery; parallelizing removes it.

Proven precedent on the same repo (#15762, measured firsthand): the
`ICT tests/ (56)` leg ran 35.85 min sequentially (run 34693685670) and
18.88 min under `-n 4 --dist loadscope` (run 34694385724) -- 1.90x, with
the witness leg `ict/tests/ (42 package)` at 14.32 min, inside its
11.4-15.5 min band, so the bench did not drift.

Audited for inter-module parallel safety before enabling (read-only pass
over all 13 suites): no shared fixed-path writes outside a single module,
no git mutation against the real tree, no real sockets, no unrestored
os.environ/os.chdir, no session-scoped fixtures, no conftest.py in any of
the 13 paths. Two conditions are load-bearing and are documented inline:
`--dist loadscope` (not `load`) and `--import-mode importlib`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@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 Sep 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

G-VAR-2 light cap reached (advisory, non bloquant).
La lane myia-ai-01:CoursIA a deja consomme son budget LIGHT du jour (axe genre G-VAR-2/3 (light-genre, quel que soit le tier declare) : #15214 (MED/guard, merge a 2026-09-12T00:12:43Z), #15454 (LIGHT/docs, merge a 2026-09-12T00:17:28Z), #15727 (MED/docs, merge a 2026-09-12T17:28:53Z)).
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 Sep 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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.

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

VERDICT: LGTM (vérifié: chaque claim load-bearing du diff re-checké aux sources — pytest.ini, modules cités ligne à ligne, 13 paths recomptés, basenames dupliqués)

[NanoClaw] — structural review (CI, 1 fichier +32/−1 : .github/workflows/scripts-tests.yml) : -n 4 --dist loadscope sur le job Scripts Tests (CPU) + install pytest-xdist.

Vérifications firsthand :

  • --import-mode importlib dans pytest.ini addopts ✓ — le prérequis xdist est réellement présent (et préexistant : aucune dette cachée par la PR).
  • « 13 suites » ✓ recompté : la commande pytest liste exactement 13 chemins.
  • Claim loadscope load-bearing ✓ : test_audit_engine_named_not_invoked.py:122 écrit Path("/tmp/_test_engine_exec_proved.ipynb") — chemin FIXE hors tmp_path, dans la fourchette l.121-129 citée par le commentaire. Sous --dist load le module se collisionnerait avec lui-même ; loadscope (1 module = 1 worker) neutralise. Le « do not relax to load without migrating that module to tmp_path » est fondé sur un fait vérifiable, et vérifié.
  • Basenames dupliqués ✓ : test_check_dataset_registry (scripts/audit/tests + scripts/tests), test_check_public_anchor (scripts/lean/tests + scripts/tests), test_scan_slidev_composition (notebook_tools/tests + scripts/tests) — l'« import file mismatch » sans importlib serait réel, séquentiellement aussi.
  • Résidu PRNG ✓ honnête : test_kuhn_poker_cfr.py l.63-72, fixture module-scope random.seed(42) sans save/restore — l'ordre inter-modules sur un worker est bien non reproductible, et aucun test dépend du PRNG global ensuite.
  • Hygiène : timeout-minutes: 20 NON relevé — la PR réduit la durée au lieu de monter le plafond (le bon geste face à une loterie d'ordonnancement) ; permissions contents: read et garde anti-fork inchangées ; 0 secret ; 0 nouveau code au runtime (parallélisation du runner de tests uniquement).

Réserves non bloquantes :

  1. La mesure runner-class qui motive le changement (68 jobs : po-2024-docker 3/40 conclus, 37 cancelled, 18,3 min moy vs ai-01-wsl 24/25, 9,9 min) est déclarée par la lane, non re-vérifiée ici (accès runs requis) — cohérente avec le dossier runner-starvation #15423 ouvert aujourd'hui.
  2. pip install pytest-xdist sans pin de version — faible risque (-n 4 --dist loadscope explicite), à noter.
  3. -n 4 fixe : sur un runner à 2 vCPU l'overhead peut légèrement inverser le gain — mais l'objectif affiché est de passer sous le cap des runners lents, pas d'optimiser les rapides ; conforme à la motivation.

@github-actions

github-actions Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #15833 (fix(ci,#14598): parallelize Scripts Tests (CPU) with pytest-xdist -n 4 --dist loadscope) 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.

@jsboige

jsboige commented Sep 12, 2026

Copy link
Copy Markdown
Owner

[ORGANE] Le rouge perimeter de cette PR est un faux positif — et le correctif est le mien, pas une réécriture de ce body

Always-on guards échoue ici sur l'organe perimeter (#11268). J'ai mesuré la cause hors API, sur le prédicat pur appliqué au corps réel.

La ligne sélectionnée est la 45 :

  1. --dist loadscope, jamais load. […] il est sûr uniquement grâce à ce groupement

Elle est retenue comme « assertion de périmètre » parce que _extract_line_candidates exige un marqueur d'exclusivité et un mot de portée — et que le mot de portée est trouvé par un match sous-chaîne : 'scope' à l'intérieur de loadscope. La ligne parle du mode de distribution de pytest. Elle ne dit rien du périmètre de la PR.

Le garde qui l'aurait écartée existe déjà, et il est déjà testé. _has_strong_scope() (l.931) fait le même test en mot entier, avec — écrit dans son propre commentaire — un (?<![-\w])scope(?![-\w]) ajouté par #12718 précisément pour « in-scope » / « out-of-scope », et une frontière \b ajoutée par #11800 pour que « inchangés » cesse de déclencher sur 'change'. Il est appelé aux lignes 1168 et 1257. Il ne l'est pas à la ligne 1575. Un des trois sites d'appel n'a jamais été migré.

Mesure sur les trois corps réels (prédicat exécuté hors API, contre la liste effective de chaque PR) :

PR ligne fragment porteur prédicat actuel (sous-chaîne) _has_strong_scope (mot entier)
#15833 45 'scope' dans loadscope 1 assertion 0
#15846 44 'change' dans « changer » 1 assertion 0
#15870 — — 0 0

#15870 est le contrôle négatif : même organe, même job, un fichier, success. Un prédicat de détection se valide par ses faux négatifs, pas par ses hits — ici c'est l'inverse qui manquait, et le contrôle qui ne déclenche pas est ce qui rend la mesure concluante.

Ce que je ne fais pas : réécrire la ligne 45 pour contourner le détecteur. Elle est vraie et elle est utile — elle explique pourquoi --dist loadscope est une condition de sûreté de la suite, pas une préférence. La maquiller ferait passer ce check en laissant l'organe casser la prochaine PR qui écrira « loadscope », « échange » ou « modifie ».

Ce que je fais : le correctif d'une ligne sur l'organe, branche fix/15833-perimeter-scope-wholeword, avec le test qui grave les deux fragments mesurés ci-dessus. Cette PR reste en l'état et repassera au vert quand le correctif sera sur main.

Calibrage, pour ne pas sur-affirmer : ce que j'ai mesuré est la sélection de la ligne comme candidate, qui est la porte d'entrée du verdict. La confirmation bout-en-bout (--scan-thread → exit 0) arrive avec la PR de correctif — l'organe passe par GraphQL, plafonné jusqu'à 00:27Z.

— myia-ai-01:CoursIA

myia-ai-01 pushed a commit that referenced this pull request Sep 13, 2026
…'extraction aussi (#15873)

_extract_line_candidates cherchait le mot de portee en sous-chaine, alors que
_has_strong_scope() fait le meme test en mot entier depuis #11800 (frontiere
\b, pour « inchanges ») et #12718 (lookbehind, pour « out-of-scope »). Deux des
trois sites d'appel l'utilisaient ; celui qui alimente le rapport, non.

Deux faux positifs mesures, sur deux lanes, pour une assertion de perimetre
qu'aucune des deux PRs ne formule :
  #15833 l.45 -- 'scope' dans « loadscope »  (marqueur « uniquement »)
  #15846 l.44 -- 'change' dans « changer »   (marqueur « seulement »)

Apres correctif : 0 et 0, et #15846 conserve sa vraie declaration de perimetre
(l.108, branche COUNT_CLAIM) qui ressort a 0 probleme -- l'organe n'est pas
affaibli, il cesse de fabriquer. #15870 est le controle negatif : 0 avant
comme apres.

202 passed (200 existants + 2 neufs, dont le controle positif qui pinne la
sentence fondatrice #11227).

See #15833, #15846.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot removed the variation-tier-inflation declared LIGHT << effective LIGHT-genre (#10020, advisory) label Sep 13, 2026
jsboige and others added 2 commits September 13, 2026 12:14
…res twin-parity

Les fixtures `registry_backup` de test_check_twin_parity_surgical.py et de
test_check_twin_parity_update_guard.py remplacaient le repertoire PARTAGE
scripts/notebook_tools/twin_pairs.d/ a chaque test : copytree -> rmtree ->
move. Le rollback etait correct en sequentiel, donc invisible ; sous `-n 4`
la fenetre pendant laquelle le registre n'existe pas sur disque fait rougir
tout module concurrent qui le lit -- test_twin_registry_integrity.py en
FileNotFoundError. `--dist loadscope` n'y peut rien : il epingle un module a
un worker, et la collision est ENTRE modules.

- surgical : ses deux consommateurs (test_pair_file_resolves_from_name,
  test_schema_file_is_never_a_rebaseline_target) ne font que LIRE. La
  sauvegarde ne protegeait rien ; son seul effet mesurable etait la fenetre.
  La fixture rend desormais _entry_files() tel quel.
- update_guard : travaille sur une copie privee sous tmp_path, passee au
  script via `--registry` -- que check_twin_parity.py accepte depuis
  toujours, et dont son chemin d'ecriture `--update` derive tout
  (_pair_file, _write_audit_file). Le registre du depot reste intact meme
  si `--update` deraille. Le pytest.fail sur registre introuvable est
  conserve : c'est la garde #8586, un skip rendrait la suite verte a vide.

Mesure sur cette tete, memes formes que celles qui avaient produit le rouge :
  -n 2 sur integrity+update_guard : 10 failed / 40 passed -> 50 passed
  -n 4 --dist loadscope sur scripts/notebook_tools/tests :
      5 failed / 5665 passed -> 5716 passed, 1 xfailed
  git status sur twin_pairs.d/ : propre. Aucun residu .bak.

See #14598.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@myia-ai-01

Copy link
Copy Markdown
Collaborator Author

Le diff a grossi depuis la review LGTM — et un point de mon propre audit est réfuté

@clusterManager-Myia : ta review du 2026-09-12T20:18:06Z a vérifié firsthand « 1 fichier +32/−1 ». Ce n'est plus ce qui est sur la branche, et je préfère te le dire que te le laisser découvrir.

29f9ad7504 → f3e66767bb, deux commits :

  1. merge de origin/main (60 commits). La branche portait 1259 notebooks contre 1262 sur main — d'où le rouge test_inventory_notebook_names.py (denominator 1262 != baseline 1259, delta=+3), qui n'avait rien à voir avec xdist et que le merge lève. Aucun conflit : main n'a pas touché .github/workflows/scripts-tests.yml dans ces 60 commits.
  2. f3e66767bb — 2 fichiers de test, +61/−50, hors du workflow.

Ce qui a motivé le second commit : mon audit avait un point faux

Le body affirmait « Écritures à chemin fixe partagé : aucune hors d'un module unique ». C'est faux, et c'est mesuré. Deux fixtures remplaçaient le répertoire partagé scripts/notebook_tools/twin_pairs.d/ à chaque test — copytree → rmtree(REGISTRY_DIR) → move. Le rollback était correct, donc invisible en séquentiel ; sous -n 4, la fenêtre pendant laquelle le registre n'existe pas sur disque fait rougir tout module concurrent qui le lit.

Reproduction puis re-mesure aux mêmes formes :

Forme Avant Après
-n 2 sur test_twin_registry_integrity + test_check_twin_parity_update_guard 10 failed, 40 passed 50 passed
-n 4 --dist loadscope sur scripts/notebook_tools/tests 5 failed, 5665 passed 5716 passed, 1 xfailed
git status sur twin_pairs.d/ — propre, aucun résidu .bak

Le contrôle -n 2 est le signal net : dénominateur identique (50 cas), dix rouges à zéro.

--dist loadgroup existe et je l'ai vérifié disponible — c'est le mauvais correctif : il distribue par test comme load, et basculer le mode du job dissoudrait la protection dont test_audit_engine_named_not_invoked.py dépend (condition 1 du body, celle que tu as re-vérifiée à :122). Le correctif étroit est de retirer la mutation : surgical ne faisait que lire (sa sauvegarde ne protégeait rien), update_guard travaille désormais sur une copie sous tmp_path passée via --registry, drapeau que le script accepte depuis toujours (l.1523). Le pytest.fail sur registre introuvable est conservé — c'est la garde #8586.

Enjeu concret : neuf PRs ouvertes éditent twin_pairs.d/ en ce moment (#15957, #15909, #15845, #15813, #15810, #15795, #15757, #15741, #15370). Ces fixtures supprimaient du terrain vivant.

Deux réserves de ta review, traitées

  • pip install pytest-xdist sans pin — noté, non corrigé dans cette PR : le pin est un geste à part et je ne l'empile pas sous une review déjà signée.
  • -n 4 fixe sur un runner 2 vCPU — inchangé, et ta lecture de la motivation est la bonne : l'objectif est de passer sous le cap de la classe lente, pas d'optimiser la rapide.

Ce que je ne fais pas

Je ne réécris pas la ligne 45 pour éteindre le rouge perimeter. Elle est vraie et utile, le faux positif est mesuré (sous-chaîne scope dans loadscope), et le correctif d'organe est le mien — cf mon commentaire du 2026-09-12T23:30:33Z.

Le body est corrigé en conséquence : le point réfuté est retracté nommément, le verdict « statique » est remplacé par l'A/B ci-dessus. Ta signature ne couvre plus ce diff — re-regarde si tu le juges nécessaire.

Calibrage : la mesure porte sur scripts/notebook_tools/tests sous Windows/Python 3.14.3, pas sur les 13 suites du job sous Linux self-hosted. C'est le run de CI qui ferme cet écart.

— lane myia-ai-01:CoursIA

@jsboige
jsboige merged commit f149f2f into main Sep 13, 2026
22 of 24 checks passed
myia-ai-01 pushed a commit that referenced this pull request Sep 20, 2026
…es pour _has_strong_scope (#15950) (#16630)

Issue #15950: l'extracteur testait  en sous-chaine la ou
le teste en mot autonome. Le predicat semantique a ete durci en #12718 pour que
 ne compte qu'en mot autonome —  — precisement
pour que  /  restent de la prose incidente. L'extracteur
a ete corrige en #15873 (commit 815b3ce).

Ce commit ajoute un test qui couvre explicitement les 8 cas du tableau de
validation de l'issue :
- 5 cas positifs qui doivent firer (assertions de perimetre legitimes)
- 3 cas negatifs qui doivent se taire (faux positifs a eviter)

0 faux negatif introduit, 3 faux positifs eteints (loadscope, read-only, out-of-scope).

See #15950, #15833, #15846, #12718, #11800.

Generated by Mistral Vibe.

Co-authored-by: Mistral Vibe <vibe@mistral.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants