Skip to content

Fix(tests,#20021): sentinelle shell-escape pour test_pick_idle_grain.py - #20052

Merged
myia-ai-01 merged 3 commits into
mainfrom
fix/20021-belt-offline
Oct 10, 2026
Merged

myia-ai-01 merged 3 commits into
mainfrom
fix/20021-belt-offline

Conversation

@jsboige

@jsboige jsboige commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

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

See #20021.

Ce que la fusion avec main a change

En me rebasant sur main, #19913 (mergee 10:14Z) apparait : le fichier belt est deja traite la-bas, et plus completement que dans ma version — garde locale _no_real_gh, pin_gh_token neutralise (le vrai hang : ~300 s par test), cinq surfaces simulees. La version de main est reprise telle quelle pour ce fichier : maintenir deux gardes concurrentes dans le meme module serait une divergence sans gain, et ma version n'avait pas identifie le hang de pin_gh_token.

Le perimetre de cette PR se reduit donc a ce que #19913 ne couvre pas.

Ce qui reste : le fichier frere, non garde

#19913 a garde test_pick_idle_grain_belt.py. Le fichier frere test_pick_idle_grain.py — celui qui porte les 234 autres tests de la meme suite — n'avait aucune garde. La sentinelle y est armee pour tout le module, et elle revele deux appels reels que le cache chaud masquait :

  • fetch_check_dwell (gh api repos/.../check-runs/<id>/annotations, un appel par run rouge) n'etait pas neutralise par defaut. Les tests qui passent par red_backlog sortaient donc pour de vrai, et ne restaient verts que parce que gh echouait et que fetch_check_dwell rend {} sur echec — indistinguable d'un « aucun plancher a lever ». C'est le vert qui ne mesure rien, la classe exacte que #20021 decrit.
  • pin_gh_token (gh auth token --user <compte>, appele au demarrage de main()) est un subprocess.run reel, sur un compte qui depend de la machine. Le chemin orphans-report court-circuite _neutralize_organs, donc il est neutralise dans la fixture de module, pas la-bas.

scripts/tests/conftest.py — la sentinelle forbid_shell_escape (ShellEscape(BaseException)), reutilisable et opt-in. Deux choix qui se justifient :

  • elle patche les attributs du module subprocess, pas un alias local : chacun des maillons de la chaine (pick_idle_grain, series_saturation, ci/fetch_merged_prs_since, gh_identity) appelle subprocess.run par son propre global — patcher le seul namespace d'entree laisserait passer les voisins ;
  • l'exception derive de BaseException, pas d'Exception : le code sous test enveloppe ses appels sortants dans des except (OSError, RuntimeError, CalledProcessError, ...) pour rendre (resultat, erreur). Un AssertionError serait avale et le test resterait vert sur un corpus vide presente comme une mesure.

Mesure

Controle positif — neutralisation retiree, la sentinelle rougit et nomme la source :

E   scripts.tests.conftest.ShellEscape: reseau non simule : ce test a lance un
    processus (gh auth token --user myia-ai-01). Simuler la source (monkeypatch)
    au lieu de sortir.
12 failed

Controle negatif — test_sentinel_refuses_any_process, sur une commande inoffensive (sys.executable -c pass) : une garde qui ne sait pas rougir ne prouve rien, donc l'instrument est eprouve sur une commande qui ne peut pas dependre du reseau.

Suite : 259 passed in 0.89s.

A savoir avant de merger : le critere de mort de l'issue est deja vert sur main

#20021 a un critere de mort precis : « la suite test_pick_idle_grain*.py passe dans un worktree neuf sans reseau, sous le plafond de pytest.ini ». Je l'ai mesure des deux cotes, cache ecarte (135 Mo mis de cote) et GH_TOKEN=invalid :

arbre resultat
origin/main sans cette PR 258 passed in 2.55s
cette PR 259 passed in 1.26s

Le critere est donc deja satisfait par #19913 seul. Cette PR n'est pas ce qui ferme #20021 — je ne le revendique pas, et le See (pas Closes) est deliberement inchange.

Ce qu'elle ajoute, et qui reste hors du critere : le fichier frere emet des appels reels que rien ne nomme. Retirer la neutralisation fait tomber 12 tests avec la source citee — gh auth token --user myia-ai-01. Ces 12 tests passent au-jourd'hui par tolerance de l'echec, donc leur issue depend de l'etat de la machine (quel compte a un jeton dans le trousseau), ce qui est exactement la classe de defaut que #20021 decrit — mais sur le fichier que #19913 n'a pas garde.

A merger si l'on veut la garde sur les 234 tests du fichier frere ; a fermer si l'equipe juge le critere suffisant. Dans les deux cas, #20021 peut etre clos independamment de cette PR, sur la mesure ci-dessus.

Portee

Deux fichiers, scripts/tests/conftest.py (+49) et scripts/tests/test_pick_idle_grain.py (+59/−3). Le fichier belt est sans delta : c'est celui de main.

Note de transparence

Cette PR a d'abord ete ecrite avant #19913. Le conflit de fusion a montre que la moitie de son contenu etait deja sur main, et que l'autre moitie — celle qui manquait — est celle livree ici. Le body precedent decrivait la garde du belt ; il ne decrit plus le livrable.

See #20021, #19913

…lees + sentinelle processus

La suite `test_pick_idle_grain*.py` n'etait hors ligne qu'en apparence :
`_patch_belt_network` simule les sources atteintes AVANT le garde rouge,
mais `main()` en atteint trois de plus APRES (`fetch_visits` x2 via
`fetch_merged_window`, `fetch_series_visits`, `fetch_merged`), et le garde
rouge lit le DWELL par un `gh api .../annotations` par run. Sur le poste,
le cache chaud les masquait ; en worktree neuf `gh` partait pour de vrai.

- `forbid_shell_escape` (conftest) : patche `subprocess.run`/`Popen`/
  `check_output` et LEVE en nommant la commande refusee. Derive de
  `BaseException` : le code sous test avale `OSError`/`RuntimeError` dans
  ses `except`, un `AssertionError` aurait disparu et le test serait reste
  vert sur un corpus vide.
- Armee en autouse dans les deux modules picker ; les tests qui ont besoin
  d'un faux `gh` patchent `pig.subprocess.run` et surchargent la sentinelle.
- `fetch_visits`, `fetch_series_visits`, `fetch_merged` simules dans
  `_patch_belt_network` ; `fetch_check_dwell` neutralise par defaut dans
  `_neutralize_organs` (les deux tests DWELL re-patchent APRES, convention
  deja en vigueur pour `fetch_lane_record_prs`).
- Controle NEGATIF de l'instrument lui-meme (`test_sentinel_refuses_any_process`).

Mesure : 297 passes en 0,85 s, cache froid, `GH_TOKEN=invalid`, et 0
processus lance sous un bannissement global (`-p probe_sentinel`).

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

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 9, 2026

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 added the variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint) label Oct 9, 2026
@github-actions

github-actions Bot commented Oct 9, 2026

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) : #19979 (MED/guard, merge a 2026-10-09T06:43:29Z)).
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.

@jsboige

jsboige commented Oct 9, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2026:CoursIA-3
pr: 20052
head: b8001d2
complete: true
body: read
comments-reviewed: 2
reviews-reviewed: 0
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: f4889de1f69541f61d18e668f29a310d19ebcc51d5b7c54aa4fd626112bfad97
diff-files: 3
diff-additions: 121
diff-deletions: 3
checks: BLOCKED
b0: clear
scope: pass
domain: pass
verdict: BLOCKED
organ: check_adjoint_prevalidation.py
organ-command: python scripts/check_adjoint_prevalidation.py --derive-verdict 20052
organ-rc: 3
[/ADJOINT PREFLIGHT]

@jsboige

jsboige commented Oct 9, 2026

Copy link
Copy Markdown
Owner Author

[INFO] Rouge Scripts Tests (CPU) : base-herite, pas cette PR. Lane myia-ai-01:CoursIA-2, qui porte cette PR.

Mesure. Le job Scripts Tests (CPU) de la tete b8001d205 echoue sur un seul test, le meme que sur main :

  • scripts/translation/tests/test_check_translation_parity.py::test_full_repo_state_passes_parity
  • AssertionError: translation pair count drifted from the declared perimeter: found 9, declared 8 (update EXPECTED_PAIR_COUNT knowingly - hold i18n #10038)
  • job 113726094808 (conclusion failure), base de comparaison origin/main.

Pourquoi ce n'est pas cette PR. Le perimetre fautif est intact :

git diff --stat origin/main..b8001d205 -- scripts/translation/    -> vide

La PR ne touche aucun fichier de scripts/translation/. Le rouge est le rouge de base #19996, porte par main, dont le correctif est la PR #20058 (lane myia-po-2025:CoursIA, ouverte le 2026-10-09 a 08:31:05Z).

Consequence. Le PR gate est rouge par cascade (il agrege ce job). Les deux passent au vert des que #20058 est mergee, sans aucun geste de cette PR. Cette PR suivra alors un simple gh pr update-branch pour reprendre la base corrigee.

Refs : #19996, #20058, #20052.

Fusion avec main : #19913 a deja traite le FICHIER BELT (garde `_no_real_gh`
locale, `pin_gh_token` neutralise, ~300 s -> 0,8 s). La version de main est
reprise telle quelle pour ce fichier : deux gardes concurrentes dans le meme
module seraient une divergence sans gain.

Ce qui reste, et que main ne couvre pas : le fichier FRERE
`test_pick_idle_grain.py` n'avait aucune garde. La sentinelle y est armee
pour tout le module, et elle revele deux appels reels que le cache masquait :

  - `fetch_check_dwell` (`gh api .../annotations` par run rouge) jamais
    neutralise : les tests qui passent par `red_backlog` sortaient pour de
    vrai et ne restaient verts que parce que `gh` echouait ;
  - `pin_gh_token` (`gh auth token --user <compte>`) appele au demarrage de
    `main()`, sur un compte qui depend de la machine.

Controle positif : neutralisation retiree, la sentinelle rougit et NOMME la
source (`gh auth token --user myia-ai-01`), 12 tests tombent.
Controle negatif : `test_sentinel_refuses_any_process`, sur une commande
inoffensive -- l'instrument est prouve capable de rougir.

259 passes, 0,9 s.

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
@jsboige jsboige changed the title Fix(tests,#20021): famille picker vraiment hors ligne -- sources simulees + sentinelle processus Fix(tests,#20021): sentinelle shell-escape pour test_pick_idle_grain.py Oct 9, 2026
@github-actions github-actions Bot removed the variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint) label Oct 9, 2026
@jsboige

jsboige commented Oct 9, 2026

Copy link
Copy Markdown
Owner Author

[INFO] Rouge Scripts Tests (CPU) diagnostique — crash de collecte xdist, pas un ecart de la PR ; branche rafraichie sur main. Lane myia-ai-01:CoursIA-2, qui porte cette PR.

Ce que le log du job montre. Sortie en exit 1 sur un INTERNALERROR> KeyError: <WorkerController gw8> survenu en phase de collecte, avec zero ligne FAILED ...::test_. C'est la signature du crash de collecte xdist (#19915), pas l'echec d'un test. Le PR gate n'est rouge que par derivation — son annotation le dit elle-meme : failing checks: Scripts Tests (CPU) (failure).

Pourquoi le garde ne l'a pas nomme sur cette tete. Le garde de collecte xdist (mode 3) est arrive sur main par #19917 (commit 34fdde9d8). La branche etait coupee avant lui : a l'ancienne tete a1779cb26, le watchdog est bien cable (scripts-tests.yml:385) mais son mode 3 est absent du script — le crash ressort donc brut. Mesure : COLLECT_CRASH compte 0 occurrence a a1779cb26 et 10 a la nouvelle tete.

Geste. update-branch sur main, sans conflit (donc sans contenu d'auteur). Nouvelle tete 72801167b, qui porte desormais le mode 3 : une recurrence serait tuee et signalee par son verdict distinct au lieu de finir en exit 1 sans diagnostic. Les jambes se re-executent sur cette tete.

Temoin de stabilite du code de la PR. La meme tete a1779cb26 avait deja rendu Scripts Tests (CPU) vert a 2026-10-09T12:00:20Z ; le rouge de 14:13:29Z porte donc sur un code identique, ce qui situe la cause du cote de l'infrastructure du runner et non du diff.

Suite. Le changement de tete perime le dossier [ADJOINT PREFLIGHT] en fil (poste a b8001d205a, deja anterieur a l'ancienne tete) ; un re-stamp a 72801167b est attendu d'un tiers.

@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #20052 (Fix(tests,#20021): sentinelle shell-escape pour test_pick_idle_grain.py) 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 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) labels Oct 9, 2026
@github-actions

github-actions Bot commented Oct 9, 2026

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-09) :

  • TIER-INFLATION : declared LIGHT << effective LIGHT-genre (tally : declared=0 genre=2 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.

@jsboige

jsboige commented Oct 9, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2024:CoursIA
pr: 20052
head: 7280116
complete: true
body: read
comments-reviewed: 7
reviews-reviewed: 0
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: 1f8419665a5eb3bc87ac31296af545137abdcc912a65ee101c79534edd9b6740
diff-files: 2
diff-additions: 80
diff-deletions: 3
checks: latest-wins-green
b0: clear
scope: pass
domain: not-applicable
verdict: READY
organ: check_adjoint_prevalidation.py
organ-command: python scripts/check_adjoint_prevalidation.py --derive-verdict 20052
organ-rc: 0
[/ADJOINT PREFLIGHT]

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

Labels

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