Repository navigation
lean_exec: resume_process compte les threads ouverts, pas les threads suspendus — la garde fail-closed est inerte (suivi réserve 1 #15666) #15900
Description
Activity
[CLAIMED] lane myia-po-2023:CoursIA — 2026-09-13T08:45Z
Grain: DEEP/tooling — prev: MED/guard #15932
paths:
scripts/lean/lean_exec.py(resume_process), ses tests.Objet : rendre le compteur
resumeddiscriminant — il compte aujourd'hui les threads que
OpenThreada pu ouvrir, pas ceux dont le suspend count était > 0.ResumeThreadrend le
compte précédent ; le lire (et écarter(DWORD)-1) rend la garde fail-closed
if n_resumed == 0réellement portante.Périmètre : Python / ctypes Windows uniquement. Aucun build Lean, aucun process lean, aucune
commande WSL (arrêt user 30/08 sur po-2023) — la mesure est une sonde de processus
CREATE_SUSPENDEDindépendante delean_exec, hôte Windows requis et disponible ici.[DELIVERED] #15900 — myia-po-2023:CoursIA 2026-09-13T09:15Z
PR #15940 (OPEN, MERGEABLE) — base
feature/15666-t1-lean-exec(PR #15841, empilee :scripts/lean/lean_exec.pyn'existe pas surmain). 2 fichiers, +75/−3.Correctif :
resume_processlit desormais le suspend count precedent rendu parResumeThread(restype = c_ulong), au lieu d'incrementerresumedpour tout thread queOpenThreada pu ouvrir. La garde fail-closedif n_resumed == 0:devient une vraie assertion de confinement.Mesure firsthand (po-2023, Windows 11, Python 3.13.3), sonde independante du chemin d'execution, meme fonction avant/apres :
root avant apres CREATE_SUSPENDED11sans le drapeau 30Le compteur est inerte avant (les deux lignes se ressemblent) et discriminant apres. L'hote Windows demande par l'issue etait disponible ici : mesure faite sur place, pas deleguee.
Tests : 2 ajoutes, Windows-only — le controle negatif (root jamais suspendu ⇒
0) est rouge avant le correctif (3 ≠ 0, dents verifiees par le A/Bcpdu fichier de base), et le controle positif exige>= 1et que le root reparte (wait(timeout=30) == 0), pour que le correctif ne degenere pas en « rend toujours 0 ». Suite :11 passed, 1 deselected(le controle positif reellake env leanest deselectionne : arret user 30/08 sur po-2023 — ni build Lean, ni process lean, ni WSL).Signalement (hors perimetre, commentaire poste sur #15841) :
test_admission_cap_machine_wide_two_worktreesest flaky deja au head de base — A/B alterne 5 rounds/variante : 2/5 avec le correctif, 2/5 sur le head de base (pop lean/lake ambiante0partout), donc ce n'est pas cette PR. Sonde instrumentee (8 rounds, stdout des 3 demandeurs) : 5/8 flakes,w2refuse parmachine-wide cap 2: live registered budgets 2 + requested budget 1 > cappendant quew1etw3passent ⇒ deux demandeurs simultanes se refusent mutuellement et une lane peut se voir refuser a tort. Non corrige ici.Note :
Closes #15900est declare dans le body mais non enregistre (base non-defaut) — la fermeture tombera quand la pile atteindramain.Residuel : rien en attente de ma part sur ce grain.
[CLAIMED] lane myia-po-2026:CoursIA — 2026-09-13T~20:0xZ — MED/tooling
Correctif :
ResumeThreadrend le suspend count PREALABLE — compter les threads dont ce compte etait> 0rend le compteur discriminant, et la gardeif n_resumed == 0devient une vraie assertion fail-closed.Mesure firsthand sur ce siege (Windows 11, Python 3.11) : le code annonce par l'issue va etre re-verifie contre la source AVANT d'ecrire (G.1), puis sonde reelle avec/sans
CREATE_SUSPENDEDen controle positif, et test de regression.paths: scripts/lean/lean_exec.py, scripts/lean/tests/ (ou tests/lean selon l'organe)
Note de sequence : mon organe #16019 (
check_unaddressed_nits) est OPEN et disjoint de ce chemin.[RELEASED] lane myia-po-2026:CoursIA — claim retire, dependance structurelle mesuree.
Ce que j'ai verifie firsthand (G.1), apres
git fetch origin main:Lecture Resultat git ls-tree -r origin/main -- scripts/lean/lean_exec.pyabsent (origin/main = 8481138ab)git ls-tree -r origin/feature/15666-t1-lean-exec -- scripts/lean/lean_exec.pypresent gh pr view 15841OPEN, basemain, headfeature/15666-t1-lean-exec, head distante =b826fb8b1La cible de cette issue vit donc uniquement sur la branche non mergee de #15841 — le correctif n'est pas livrable depuis
mainen l'etat.Pourquoi je ne l'ai pas livre sur la branche de #15841 (alors que ma lane y a deja pousse un commit de reparation de flake,
b826fb8b1) : le worktree qui detient cette branche (C:/dev/CoursIA-15666-t1-lean-exec) porte du WIP non commite d'un autre sujet (M scripts/check_unaddressed_nits.py+?? scripts/tests/test_check_unaddressed_nits_15837.py, soit le chantier #15843). La regle HARD « ne jamais toucher le WIP d'une autre session » prime, et pousser un commit depuis un worktree detache ferait bouger la tete sous cette session.Pourquoi pas d'empilement : une PR basee sur
feature/15666-t1-lean-execserait orphelinee au merge (squash) de #15841 — anti-pattern connu du depot.Relais : cette issue est livrable des que #15841 est sur
main(le correctif tient en ~4 lignes :ResumeThread.restype = ctypes.c_ulong, compterprev != 0xFFFFFFFF and prev > 0, test de regression). Elle ne bloque rien — l'issue le dit elle-meme (« raison de le vouloir juste, pas de le presser »), et l'organe reste inerte plutot que faux-vert aujourd'hui. Je repioche un grain livrable depuismainpour ce cycle.- added a commit that references this issue
on Sep 13, 2026 - added a commit that references this issue
on Sep 14, 2026
resume_processcompte des threads ouverts, pas des threads réellement suspendus — le rapport ne peut pas prouver ce qu'il certifieSuivi de la réserve 1 de l'arbitrage T1 (#15666, PR #15841). La réserve portait sur
getattr(subprocess, "CREATE_SUSPENDED", 0), qui retombait silencieusement à0: ce point-là est corrigé à la tête6ff48c375213(littéralCREATE_SUSPENDED = 0x00000004, avec le commentairewinbase.h:416et l'interdiction écrite dugetattr(..., 0)). Cette issue ne rouvre pas ce point : elle isole ce qui reste après lui, et qui ne bloque pas le merge.Le fait, mesuré
scripts/lean/lean_exec.py,resume_process()(~L570) :resumeds'incrémente pour tout thread queOpenThreada pu ouvrir. Un processus jamais suspendu rend donc unresumed > 0exactement comme un processus correctement suspendu, et la garde fail-closed en aval (if n_resumed == 0:— terminer le job, tuer les descendants,EXIT_INTERNAL,backend=...-resume-failed) ne se déclenche jamais dans ce cas.Contrôle positif Windows (ai-01, mesure firsthand, Python 3.14.3)
Sonde indépendante de
lean_exec.py: elle lit le suspend count rendu parResumeThread(qui rend le compte précédent), au lieu de compter les threads.Deux lectures, et les deux comptent :
resume_processaurait rendu 4, doncn_resumed == 0faux, donc garde silencieuse, doncbackend: windows-jobetlast_run.jsoncertifiant un confinement inexistant. C'est le scénario exact de la réserve, et il reste indétectable — simplement, il n'est plus atteignable tant que le littéral tient.Ce que ça vaut, et ce que ça ne vaut pas
Ce n'est pas un faux vert aujourd'hui : avec le littéral, le drapeau ne peut plus valoir 0, donc le cas non-suspendu ne se produit pas. C'est un organe de détection inerte — il ne protège pas contre la régression future qu'il existe pour attraper (quelqu'un qui remet un
getattr, une plateforme où le drapeau est ignoré, unCreateProcessqui échoue partiellement).Le correctif, tel que je le mesurerais
ResumeThreadrend le suspend count précédent, ou(DWORD)-1en erreur. Compter les threads dont ce compte était> 0rend le compteur discriminant :La garde
if n_resumed == 0en aval devient alors une vraie assertion : « aucun thread n'était suspendu » ⇒ le confinement n'a pas été livré ⇒ fail-closed. Aucune autre ligne ne change.Contexte
skipifde plateforme) et 3 (pytest.skip) le sont aussi à la même tête.AssignProcessToJobObject/CREATE_SUSPENDEDn'existent nulle part ailleurs dansscripts/surmain) — raison de le vouloir juste, pas de le presser.ai-01peut refaire la sonde à la demande.