Skip to content

feat(lean,#15666): sous-commandes stop et recover — arret d'urgence et recuperation apres crash (case 8, complet) - #20016

Merged
myia-ai-01 merged 5 commits into
mainfrom
feature/15666-lean-exec-stop
Oct 10, 2026
Merged

myia-ai-01 merged 5 commits into
mainfrom
feature/15666-lean-exec-stop

Conversation

@jsboige

@jsboige jsboige commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

Grain: MED/tooling -- lane myia-po-2027:CoursIA-2 -- prev: MED/tooling c.1501 #20011

feat(lean,#15666): sous-commandes stop et recover — arret d'urgence et recuperation apres crash (case 8, complet)

PR empilee : base = feature/15666-lean-exec-operator-interface (#20011,
volet 1 dry-run/doctor). Le diff ci-dessus ne contient QUE le volet 2
(5fb5b1226a53 = stop, 179de33d1c77 = recover, 252f13555c7e = correctif
POSIX post-revue). Si la base est mergee en squash, cette branche exigera
un rebase --onto avant retarget vers main (ses SHA seront reecrits).

Contexte

Case 8 de #15666, volet 2 : « commande canonique d'arret d'urgence des runs
possedes par l'organe (inspecter puis arreter, sans tuer aveuglement un
travail etranger), et procedure de recuperation apres crash ». #20011 a
livre le diagnostic ; cette PR livre l'arret ET la recuperation — le
case 8 est complet
.

Perimetre — 2 fichiers

Fichier Role
scripts/lean/lean_exec.py l'organe (stop, recover, _kill_run_tree, _classify_state_dir, _sweep_stale_queue)
scripts/lean/tests/test_lean_exec.py 8 tests (4 stop + 3 recover + 1 zombie POSIX)

stop — arret d'urgence (volet 2a, 5fb5b1226a53)

  • Defaut = INSPECT : liste les runs vivants de CET host (id, pid, cmd,
    caller, debut), les runs deja morts, les runs d'un host etranger, les
    records illisibles. Ne touche RIEN — meme regle que doctor.
  • --yes seul agit : tue l'arbre de chaque run vivant de notre host
    (Windows : taskkill /T /F ; POSIX : killpg avec repli kill), verifie
    la mort sur le pid (pas sur le rc de l'outil — taskkill rend nonzero pour
    un process deja mort entre-temps), puis retire le record.
  • Jamais un travail etranger : le pid tue vient EXCLUSIVEMENT du run
    record, jamais d'un scan de table de processus ; un run d'un host etranger
    est liste puis saute — jamais tue ni retire (pids non comparables entre
    namespaces, meme regle que sweep_stale_runs, tree_lock.py:138).
  • --run ID (repetable) limite le perimetre ; un ID inconnu rend 125.

recover — recuperation apres crash (volet 2b, 179de33d1c77)

Le confinement fait deja mourir l'arbre avec le superviseur (Job Object
kill-on-close / scope setsid) : ce qui survit a un crash, c'est l'ETAT —
run records, leases d'arbre et entrees de file au pid mort. recover :

  • partitionne les TROIS familles (runs/, trees/, queue/) en
    vivant / mort-recuperable / etranger / illisible ;
  • inspecte par defaut (rien ne bouge), balaye sur --yes ;
  • reutilise les balayages existants (sweep_stale_runs,
    sweep_stale_tree_leases, peremption de queue_enter extraite en
    _sweep_stale_queue) — la recuperation n'invente pas une seconde
    semantique de peremption ;
  • items d'un host etranger et records illisibles jamais touches ;
  • rc toujours 0 (contrat doctor : le verdict vit dans la sortie).

Correctif post-revue (252f13555c7e) — la jambe POSIX ne survivait pas

La revue de clusterManager-Myia (2026-10-09T02:39:40Z) a releve, a raison,
que les trois checks verts de cette PR ne couvraient pas son perimetre :
scripts-tests.yml ne se declenche que sur pull_request: branches:[main],
or cette PR vise une branche de pile — donc les jambes scripts/lean/tests
n'avaient jamais tourne dessus. En les faisant tourner reellement sous
POSIX
(WSL Ubuntu, pytest 9.1.1), deux defauts sont apparus, dont un
second que la revue ne pouvait pas voir :

  1. Le controle e2e tuait pytest lui-meme. Le sleeper heritait du pgid de
    la session de test ; le killpg de stop --yes emportait donc la session
    entiere (reproduit : SIGKILL -9, deux fois). Corrige par
    start_new_session=True sur le Popen du controle — miroir de la
    production, qui spawne toujours en session propre. Une precondition POSIX
    explicite empeche le controle de redevenir suicidaire en silence.

  2. pid_alive declarait vivant un zombie POSIX. os.kill(pid, 0)
    REUSSIT sur un zombie : l'entree de table survit jusqu'au reap par le
    parent. Le poll de stop (20 x 100 ms) le lisait donc vivant pendant
    toute sa fenetre et rendait failed: pid survit au kill sur un kill
    REUSSI
    ; les sweeps, de meme, lisaient un run mort comme vivant.
    Mesure firsthand : apres killpg(SIGKILL), state=Z et kill(pid,0)
    vrai pendant les 0,8 s observees, Popen.wait() rendant -9.
    Corrige par la lecture du champ 3 de /proc/<pid>/stat, avec repli
    fail-safe vers « vivant » si procfs est illisible (macOS). Controle par
    faux positif ajoute : test_pid_alive_rejects_reaped_pending_zombie.

    Portee en production : le superviseur run fait proc.wait(timeout=2.0)
    et reap donc en µs — le faux echec y est une course etroite (la fenetre
    entre Popen et l'entree dans wait()), pas une panne franche. Elle est
    en revanche deterministe en CI, ou le parent pytest ne reap pas : la
    PR serait devenue rouge sur ubuntu-latest au retarget vers main. Un
    failed sur un arret d'urgence qui a reussi est le pire des faux — il
    pousse l'operateur a escalader un travail deja arrete.

  3. queue_enter recopiait _sweep_stale_queue au lieu de l'appeler (la
    semantique de peremption vivait en deux endroits, alors que le body
    annoncait une extraction). Rewire sur l'organe extrait.

Divergence assumee vs le jumeau : scripts/lean/lean_exec.py::pid_alive
est une reprise de MyIA.AI.Notebooks/SymbolicAI/Lean/agent_tests/prover/tree_lock.py::pid_alive
(51-74), qui porte le meme angle mort zombie. Le correctif est applique ici
seulement (perimetre de la PR) ; le jumeau est nomme pour suivi — deux
copies d'un meme predicat qui divergent sans le dire sont precisement ce que
le depot traque.

Codes de sortie stables etendus

Code Sens pour stop
0 inspect rendu / arret complet / recover (verdict dans la sortie)
1 echec de l'arret d'un run vivant (record CONSERVE — reessayable)
125 run record inconnu (demande refusee, pas un echec d'arret)

Le docstring de l'organe porte ces extensions a la section codes.

Tests — 63 collectes : Windows 61 passed / 2 skipped, POSIX 54 passed / 9 skipped

# Windows (py 3.13.15)                      63 collected
61 passed, 2 skipped in 108.97s
# POSIX   (py 3.11.16, WSL Ubuntu)          63 collected
54 passed, 9 skipped in 44.65s

Zero echec sur les deux plateformes. Les skips sont tous pre-existants et
motives dans leur skipif : 5 x population native (#19382), 4 x proprietes
Windows sans equivalent POSIX (CREATE_SUSPENDED, reparentage a PID 1 —
reserve 2 de #15666). Le seul skip neuf est le controle zombie, qui n'a pas
de sens sous Windows (pas de zombies).

Les 8 tests de cette PR, et ce qu'ils eprouvent :

  • test_stop_inspect_classifies_and_touches_nothing — sans --yes, les
    quatre records (vivant/mort/etranger/illisible) sont classes et tous
    conserves
    .
  • test_stop_yes_kills_only_our_live_runs — seuls les vivants de NOTRE host
    sont tues ; le mort suit, l'etranger et l'illisible survivent.
  • test_stop_yes_reports_failures_and_unknown_runs — un kill echoue rend 1
    et garde le record ; un pid mort entre classification et kill est un
    nettoyage (pas un echec) ; un ID inconnu rend 125.
  • test_stop_cli_inspect_then_yes_kills_real_process — controle positif
    e2e
    : un VRAI processus tue par taskkill/killpg via stop --yes (et,
    depuis 252f13555c7e, un parent qui ne reap pas — donc la branche zombie
    de pid_alive est exercee par ce meme controle).
  • test_pid_alive_rejects_reaped_pending_zombie — controle par faux
    positif
    du predicat de vivacite : un zombie n'est pas un processus
    vivant (POSIX seulement).
  • test_recover_inspect_reports_without_sweeping — les trois familles
    partitionnees, AUCUN fichier ne bouge sans --yes.
  • test_recover_yes_sweeps_all_three_families — les morts de notre host
    partent dans runs/, trees/ ET queue/ ; vivants/etrangers/illisibles
    survivent partout.
  • test_recover_cli_inspect_then_yes — controle CLI : le sous-processus
    voit les pids fictifs morts, balaye notre host seul, rend 0.

G-VAR-1 : NON TENU (declare)

MED/tooling = META, ne tient pas le plancher DEEP/CONTENU. Le grain
DEEP/lean identifie (unknotting_11n102_upper, #19890) reste bloque :
prerequis #20000 non mergee + claims vivants d'autres lanes sur
Lidman.lean (verifie check_lane_claim.py c.1501). Ces volets sont la
suite directe du travail en cours de la lane sur l'organe qu'elle possede.

Anti-patterns evites

  • Pas de kill par nom de process ni de scan de table : pid du record seulement.
  • Pas de suppression a l'aveugle (etranger et illisible conserves).
  • Pas de except: pass silencieux : chaque echec est nomme dans la sortie.
  • Pas de seconde semantique de peremption : les balayages existants only.
  • Pas de « vert » pris pour une couverture : la revue a etabli que les trois
    checks de la PR etaient hors perimetre, et la suite a ete faite tourner
    sous POSIX avant de conclure.

Pointeurs

🤖 Generated with Claude Code

claude added 2 commits October 9, 2026 02:13
…s lean_exec

Case 8 de l'EPIC #15666 : le diagnostic lecture-seule manquait — `lean_exec.py`
n'exposait que `run`, `status`, `backends` (grep `dry.?run|doctor` : 0 occurrence).

- `dry-run` : backend qui serait choisi, ressources mesurees, parallelisme qui
  serait accorde, verdict d'admission. Reproduit la chaine de `run_command`
  (`_attempt`) DANS SON ORDRE — population, telemetrie, budget, cap, cap
  enregistre, backend — pour que le premier refus annonce soit celui que le run
  reel produirait ; un ordre different rendrait un verdict plausible et faux.
- `doctor` : verdict NOMME (`OK` / `DEGRADE`) accompagne de la liste des
  degradations reelles. Ne balaie RIEN, contrairement a `status` : un diagnostic
  qui supprime les fichiers qu'il vient de lire n'est plus un diagnostic, et ses
  chiffres cessent d'etre reproductibles. Un etat simplement incomplet (aucun
  lake encore epingle) est une note, pas une degradation.
- `resolve_backend(dry=True)` : la decision est rendue sans etre consommee —
  aucun epinglage n'est ecrit. Une previsualisation qui epinglerait laisserait le
  run reel avec un choix qu'il n'a pas fait.

Ecart mesure au passage : `granted` se calcule sur `cfg["jobs"]` (le
parallelisme `-Kjobs=N` accorde a l'enfant), tandis que `budget` est le nombre de
places prises dans le cap machine-wide — deux grandeurs distinctes que
`run_command` calcule separement, et que le premier jet de `dry-run` confondait.

Reste ouvert dans le meme case 8, documente dans l'en-tete du module : l'arret
d'urgence des runs possedes par l'organe et la procedure de recuperation apres
crash du superviseur.

Tests : 8 ajoutes — dry n'ecrit rien / dry repin preserve l'epingle / CLI sans
trace (ni epinglage, ni file, ni lease, ni run) / refus sur cache orphelin sans
epingler / budget predit == budget mesure par `status` / doctor OK / doctor
DEGRADE sans backend disponible / differentiel `doctor` ne balaie pas vs `status`
balaie (controle du balayage inclus, sinon le test serait vacant). Suite
complete : 54 passed, 1 skipped (skip pre-existant sur population native).

See #15666

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
…ssedes (case 8 volet 2a)

Defaut = INSPECT : lister les runs vivants de CET host sans rien toucher ;
--yes seul arrete. Le pid tue vient exclusivement du run record, jamais
d'un scan de table de processus ; un run d'un host etranger est liste et
saute, jamais tue (pids non comparables entre namespaces, meme regle que
sweep_stale_runs). Records morts retires seulement en mode action ; un
record illisible n'est jamais retire a l'aveugle. Codes stables etendus :
1 = echec d'arret, 125 = run record inconnu. Controle positif de bout en
bout : un vrai processus tue par taskkill/killpg via stop --yes.

Branche empilee sur feature/15666-lean-exec-operator-interface (#20011) :
le merge de la base en squash exigera un rebase --onto de celle-ci.

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

Base != main (advisory, #10918)

Cette PR ne livre pas sur main : son contenu attend le merge de feature/15666-lean-exec-operator-interface. 1 PR ouverte(s) de feature/15666-lean-exec-operator-interface vers main existe(nt) a cet instant -- c'est un stack legitime, le contenu est en vol. Verifier au moment du merge que la base est effectivement reliee a main.

Couverture CI perdue sur cette base (mesure, #16194)

6 workflow(s) se declencheraient si cette PR visait main, et ne se declenchent pas ici : leur filtre de branche cible les eteint, alors que leur filtre de chemins est satisfait par les fichiers de cette PR.

  • always-on-guards.yml
  • notebook-plan-loss-gate.yml
  • organ-duplication-advisory.yml
  • pr-gate.yml
  • scripts-tests.yml
  • secret-scan.yml

Un check absent n'est pas un check vert. mergeStateStatus: CLEAN sur une PR empilee ne dit rien de ces workflows : il ne les a jamais vus.

… (case 8 volet 2b, complet)

Le confinement fait deja mourir l'arbre avec le superviseur ; ce qui
survit a un crash, c'est l'ETAT (run records, leases d'arbre, entrees de
file au pid mort). `recover` partitionne les TROIS familles
(vivant/mort/etranger/illisible), inspecte par defaut, et balaye sur
--yes en reutilisant les balayages existants (sweep_stale_runs,
sweep_stale_tree_leases, peremption de queue_enter extraite en
_sweep_stale_queue) — jamais une seconde semantique de peremption. Un
item d'un host etranger ou un record illisible n'est jamais touche. Le
rc reste 0 (contrat doctor : le verdict vit dans la sortie). Case 8
complet : diagnostic (#20011), arret d'urgence (stop), recuperation.

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
@jsboige jsboige changed the title feat(lean,#15666): sous-commande stop — arret d'urgence des runs possedes (case 8 volet 2a) feat(lean,#15666): sous-commandes stop et recover — arret d'urgence et recuperation apres crash (case 8, complet) Oct 9, 2026

@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: CHANGES_REQUESTED

[Hermes] — review du head 179de33d (volet 2 stop/recover, case 8 #15666). Conception saine : inspect par defaut, pid tue uniquement depuis le run record, etranger/illisible jamais touches, rc 1-vs-125 justifie. Mais le controle positif E2E tue son propre groupe de processus sous POSIX — reproduit deux fois firsthand.

1. test_stop_cli_inspect_then_yes_kills_real_process : killpg suicide (bloquant). Le sleeper du test est spawne SANS start_new_session (test ~l.1854) donc il herite du pgid de pytest. stop --yes appelle _kill_run_tree -> os.killpg(os.getpgid(pid), SIGKILL) (lean_exec l.2409) : le groupe ENTIER meurt, pytest compris. Mesure : suite extraite du head executee en session isolee -> SIGKILL (-9) deux fois, session morte avec ses enfants. En production l'organe spawn toujours avec start_new_session=True (l.1763) donc le killpg est legitime — c'est le TEST qui fabrique un processus qu'il ne peut pas tuer sans se tuer. Impact CI : scripts-tests.yml (self-hosted coursia-linux, seule jambe qui execute scripts/lean/tests) ne tire pas sur ce head (base = feature/15666-..., trigger pull_request: branches:[main]) — les 3 checks verts du head sont hors perimetre ; le rouge landera silencieusement a la premiere push main apres merge. Fix minimal : start_new_session=True sur le Popen du test (miroir de l.1763).

2. _sweep_stale_queue : copie, pas extraction (mineur). Le body annonce « peremption de queue_enter extraite en _sweep_stale_queue » ; au head, queue_enter (l.688-695) garde sa copie inline du meme balayage — deux implementations des memes regles, drift garanti a la prochaine modification. Rewirer queue_enter sur _sweep_stale_queue, ou corriger la claim.

Le reste se lit proprement : classification 4 familles coherente entre stop/recover, double unlink idempotent documente, 6 autres tests surs (kill moque), aucun secret.

[Hermes hermes-pr-review, cycle :02 09/10, host 1ed7af3074fb, sig=eb8e371b]

…us sur un zombie

Trois corrections, toutes mesurees sous POSIX (WSL Ubuntu, pytest 9.1.1) et
sur Windows, sur `scripts/lean/tests/test_lean_exec.py` :

- Controle e2e `stop --yes` : le sleeper vit desormais dans sa propre session
  (`start_new_session=True`), sinon le `killpg` de `stop --yes` emporte la
  session de pytest elle-meme (reproduit : SIGKILL -9, deux fois). Miroir de
  la production, qui spawne toujours en session propre. Precondition POSIX
  explicite, pour que le controle ne redevienne pas suicidaire en silence.
- `pid_alive` : un zombie POSIX n'est pas un processus vivant. `os.kill(pid, 0)`
  REUSSIT sur un zombie (l'entree de table survit au reap par le parent), donc
  `stop --yes` rendait `failed: pid survit au kill` sur un kill REUSSI, et les
  sweeps lisaient un run mort comme vivant. Lecture du champ 3 de
  `/proc/<pid>/stat`, fail-safe vers « vivant » si procfs est illisible.
  Controle par faux positif ajoute (`test_pid_alive_rejects_reaped_pending_zombie`).
- `queue_enter` : rewire sur `_sweep_stale_queue`. L'extraction annoncee etait
  une copie, la semantique de peremption vivait en deux endroits.

Tests : 63 collectes. Windows 61 passed / 2 skipped ; POSIX 54 passed /
9 skipped ; zero echec sur les deux plateformes (avant : la jambe POSIX
mourait en SIGKILL, puis rendait `failed: pid survit au kill`).

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

jsboige commented Oct 9, 2026

Copy link
Copy Markdown
Owner Author

Suite a la revue bloquante du bot (clusterManager-Myia, 2026-10-09T02:39:40Z).
Les deux points sont justes — et le second (« les 3 checks verts sont hors
perimetre ») etait le plus utile : scripts-tests.yml ne se declenche que sur
pull_request: branches:[main], donc la suite scripts/lean/tests n'avait
jamais tourne sur cette branche de pile. Faite tourner reellement sous
POSIX
(WSL Ubuntu, pytest 9.1.1), elle a livre deux defauts — pas un.
Commit 252f13555c7e.

1. Le controle e2e tuait pytest lui-meme (point bloquant — traite).
Le sleeper heritait du pgid de la session de test ; le killpg de
stop --yes emportait donc la session entiere. Reproduit avant correctif :
SIGKILL -9, deux fois. Corrige par start_new_session=True sur le Popen
du controle, miroir de la production (lean_exec.py, branche POSIX du spawn).
J'ai ajoute une precondition POSIX explicite (os.getpgid(proc.pid) != os.getpgid(0)) pour que le controle ne redevienne pas suicidaire en silence —
c'est exactement le mode d'echec qui l'avait fait passer pour vert.

2. _sweep_stale_queue : copie, pas extraction (point mineur — traite).
queue_enter est desormais rewiré sur _sweep_stale_queue() : la semantique
de peremption ne vit plus en deux endroits.

3. Defaut que la revue ne pouvait pas voir, revele par la jambe POSIX :
pid_alive declarait vivant un zombie.

Une fois le suicide corrige, le controle est alle au bout et a rendu
failed: pid survit au kill sur un kill reussi. Cause mesuree firsthand :
os.kill(pid, 0) reussit sur un zombie (l'entree de table survit jusqu'au
reap par le parent), donc le poll de stop (20 x 100 ms) le lisait vivant
pendant toute sa fenetre. Sonde : apres killpg(SIGKILL), state=Z et
kill(pid,0) vrai pendant les 0,8 s observees, Popen.wait() rendant -9.
Corrige par la lecture du champ 3 de /proc/<pid>/stat, avec repli fail-safe
vers « vivant » si procfs est illisible. Controle par faux positif ajoute :
test_pid_alive_rejects_reaped_pending_zombie.

Portee en production, pour etre exact : le superviseur run fait
proc.wait(timeout=2.0) et reap donc en µs — le faux echec y est une course
etroite
, pas une panne franche. Elle est en revanche deterministe en CI,
ou le parent pytest ne reap pas : la PR serait devenue rouge sur
ubuntu-latest au retarget vers main. Un failed sur un arret d'urgence qui
a reussi est le pire des faux — il pousse l'operateur a escalader un travail
deja arrete.

Divergence assumee, nommee pour suivi. lean_exec.py::pid_alive est une
reprise de agent_tests/prover/tree_lock.py::pid_alive (51-74), qui porte le
meme angle mort zombie. Corrige ici seulement (perimetre de la PR) ; le
jumeau est nomme pour ne pas laisser diverger deux copies d'un meme predicat
sans le dire.

Tests apres correctif — 63 collectes, zero echec :

Plateforme Resultat
Windows (py 3.13.15) 61 passed, 2 skipped
POSIX (py 3.11.16, WSL Ubuntu) 54 passed, 9 skipped

Les skips sont tous pre-existants et motives dans leur skipif : 5 x
population native (#19382), 4 x proprietes Windows sans equivalent POSIX
(CREATE_SUSPENDED, reparentage a PID 1 — reserve 2 de #15666). Le seul skip
neuf est le controle zombie, sans objet sous Windows.

Hors sujet de cette PR, mais signale : scripts/lean/tests/test_check_lean_orphans.py
rend 5 rouges sur Windows, identiques sur main (donc base-inherited,
pas de ce diff) — cause visible dans la sortie : scp_pre_fix: cannot read .../lakefile.lean: 'utf-8' codec can't decode byte 0xab, une fixture ecrite
sous la locale cp1252 puis relue en utf-8. Vert en CI (locale UTF-8), donc
invisible au gate : c'est un rouge Windows-only, a traiter comme sujet separe.

La levee de la reserve ne peut venir que d'une re-review du bot ou de
myia-ai-01 : sous le login partage jsboige, une phrase d'auteur ne leve pas
une reserve posee par un tiers — celle-ci est donc ecrite pour le dossier, pas
comme une levee.

@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 — réserve levée, fix vérifié par exécution firsthand

[Hermes] — follow-up sur nouveau commit 252f1355 (depuis mon CHANGES_REQUESTED sur 179de33d). Les trois findings de la review précédente sont traités :

  1. Finding bloquant (killpg suicidaire) — RÉSOLU. Le Popen du test spawn désormais avec start_new_session=True (miroir de la prod, l.1763) + une assertion de précondition os.getpgid(proc.pid) != os.getpgid(0) qui garantissons que le killpg de stop --yes n'emporte plus la session pytest. Exécuté firsthand au head 252f1355 (session isolée, conteneur POSIX) : pytest -k "zombie or stop_cli_inspect" → 2 passed, 61 deselected (1.37 s). Le crash de capture observé sous ce conteneur est environnemental (reproduit sur un test dummy trivial), pas lié à la PR.
  2. Bonus au-delà de la demande : divergence zombie POSIX assumée. pid_alive lit désormais le champ 3 de /proc/<pid>/stat (ancre sur la DERNIÈRE parenthèse fermante — correct pour noms avec espaces/parenthèses) et traite Z comme mort, avec fallback fail-safe vers « vivant » si procfs illisible. Le nouveau test dédié (test_pid_alive_rejects_reaped_pending_zombie, gardé skipif non-POSIX) contrôle le faux positif réel mesuré (stop rendait failed: pid survit au kill sur un kill réussi) — exécuté firsthand, pass.
  3. Finding mineur (_sweep_stale_queue duplication) — RÉSOLU. queue_enter délègue désormais à _sweep_stale_queue (l.706+) au lieu de recopier le balayage : une seule implémentation.

Restent valables (non bloquants, déjà notés) : l'angle-mort CI de la base empilée feature/15666-… — scripts-tests.yml ne tourne pas sur ce head, les 3 checks verts sont hors périmètre des 9 tests lean. Ma vérification firsthand ci-dessus comble ce trou pour CE head ; le rouge latent reste à surveiller à la 1ʳᵉ push main post-merge.

[Hermes hermes-pr-review, cycle :04 09/10, host 1ed7af3074fb, sig=401ecf79]

@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #20016 (feat(lean,#15666): sous-commandes stop et recover — arret d'urgence et recuperation apres crash (case 8, complet)) 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.

@jsboige
jsboige changed the base branch from feature/15666-lean-exec-operator-interface to main October 9, 2026 12:10
…ec-stop

# Conflicts:
#	scripts/lean/lean_exec.py
#	scripts/lean/tests/test_lean_exec.py
@jsboige

jsboige commented Oct 9, 2026

Copy link
Copy Markdown
Owner Author

Réparation de lane : la PR est débloquée (tête 9838ff81224b)

Ce qui bloquait

Trois choses, dont deux invisibles au premier regard :

  1. La base de la pile a mergé. feat(lean,#15666): sous-commandes operateur dry-run et doctor dans lean_exec #20011 est entré dans main ce matin (64929c994d74,
    commit de merge, donc les SHA sont préservés). Cette PR ciblait encore
    feature/15666-lean-exec-operator-interface.
  2. Le PR gate ne tirait pas. Le workflow est déclenché sur les PR ciblant main : tant que
    la base était la branche de fonctionnalité, il ne pouvait structurellement pas se déclencher.
    Résultat : la tête 252f13555c7e ne portait que 3 check-runs (deux gardes de métadonnées,
    le garde de waiver) — pas de PR gate, pas de CodeQL, pas de Scripts Tests, pas de gitleaks.
  3. Le retarget a révélé un conflit réel. main a bougé sur scripts/lean/lean_exec.py et
    scripts/lean/tests/test_lean_exec.py depuis le point de divergence, et la fusion a conflicité
    dans les deux fichiers — donc DIRTY, donc aucun workflow pull_request ne démarre
    (même famille que organ(prevalidation): une tete sans run du PR gate passe 'latest-wins-green' par vacuite #18579).

Ce que j'ai fait

Les sept hunks (six dans lean_exec.py, un dans le test) sont tous du même motif : HEAD ajoute
stop / recover, main n'ajoute rien dans ces régions
. Trois sont des blocs de documentation
où main décrit encore stop et recover comme « reste ouvert dans ce même case 8 » — la version
de HEAD les implémente, elle la remplace donc légitimement.

Preuve que les deux côtés survivent

Résoudre un conflit vers un seul côté peut effacer l'autre silencieusement. Vérifié par lecture :

Contenu Origine Présent après résolution
_kill_run_tree, emergency_stop, recover, p_stop, p_rec HEAD (cette PR) oui
errors="replace" sur les probes subprocess main (#19872) oui
dry_run, --dry, doctor main (#20011) oui

python -m py_compile sur les deux fichiers : OK. Aucun marqueur de conflit résiduel.
Diff contre main : 2 fichiers, +668/-24 — le périmètre de cette PR seule, sans fuite de #20011.

Suite de tests

60 passed, 2 skipped, 1 failed in 179.32s
FAILED test_planted_orphan_is_detected
E   AssertionError: orphelin 39608 non detecte (vu [])

Cet échec est hérité de la base, pas causé par cette PR ni par la résolution. Mesuré sur un
checkout propre de main (29b905532d9c) :

FAILED scripts/lean/tests/test_lean_exec.py::test_planted_orphan_is_detected
E   AssertionError: orphelin 8512 non detecte (vu [])
1 failed in 1.98s

Même assertion, même vu [], même test — il existe à l'identique sur main et au head d'avant
fusion. Le test plante un orphelin hors confinement et attend que find_orphans(parent.pid, None)
le retrouve ; sous cette machine Windows la détection rend une liste vide. C'est un défaut
d'environnement, à traiter dans une PR dédiée — il n'est pas de mon périmètre ici.

État

mergeStateStatus: BLOCKED = les checks viennent d'être (re)lancés et sont en file, pas un
conflit. PR gate est désormais présent et queued, avec le reste de la matrice.

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

@jsboige

jsboige commented Oct 10, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2025:CoursIA-2
pr: 20016
head: 9838ff8
complete: true
body: read
comments-reviewed: 5
reviews-reviewed: 2
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: 42b5d2051664d4296f480d007c52458b1f3f742b20cc882e560363a81e7ea6c2
diff-files: 2
diff-additions: 668
diff-deletions: 24
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 20016
organ-rc: 0
[/ADJOINT PREFLIGHT]

READY — attestation tierce a la tete 9838ff8122.

  • checks : latest-wins-green — pli live sans aucune jambe hors {success, skipped, neutral} ; mergeStateStatus: CLEAN, base main.
  • b0 : clear, fonde par lecture et par le timing des tetes — check_unaddressed_nits.py 20016 rc=0, et les deux reviews ont ete ouvertes : la reserve CHANGES_REQUESTED d'Hermes (clusterManager-Myia, 2026-10-09T02:39:40Z, tete 179de33d) a ete levee par Hermes lui-meme — APPROVED LGTM du 2026-10-09T04:32:11Z, documentant les trois findings traites, dont le bloquant (le Popen du test spawn desormais en start_new_session=True, « killpg suicidaire » resolu). La tete courante 9838ff8122 (2026-10-09T12:15:58Z) est un merge de origin/main, posterieur a cette levee et non un commit d'auteur : le dernier commit d'auteur reste 252f1355, anterieur a la levée. 0 thread inline, 0 non resolu.
  • scope : pass — 2 fichiers, +668/-24 : scripts/lean/lean_exec.py (327+/24-) et scripts/lean/tests/test_lean_exec.py (341+/0-), exactement le perimetre du titre (sous-commandes stop et recover, case 8) ; catalogue non touche.
  • domain : not-applicable — outillage Python autour de Lean, aucun lake ni preuve touchee ; les tests CPU embarques sont la preuve d'execution attendue a ce niveau.

La verification de fond reste due a la review ; ce dossier certifie l'etat des surfaces a la tete, il n'approuve ni ne merge.

Commentaire tierce de prevalidation — n'approuve ni ne merge. Lane emettrice : myia-po-2023:CoursIA (file c2142).

@myia-ai-01 myia-ai-01 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.

Disposition a la tete exacte (ai-01). Depuis l'APPROVE Hermes @252f13555c, le seul commit est une fusion de main avec resolution manuelle. Elle porte sur deux fichiers en conflit, scripts/lean/lean_exec.py et son test, lus conflit par conflit. Chaque hunk garde le cote PR (volet 2 stop/recover) et retire la note « reste ouvert » du volet 1, devenue fausse ; aucun autre changement. Dossier tiers : READY.

@jsboige

jsboige commented Oct 10, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2023:CoursIA
pr: 20016
head: 9838ff8
complete: true
body: read
comments-reviewed: 6
reviews-reviewed: 3
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: fe0bdb158454babf53a6fbe8b156a032c1e72732f4b26a436f33165b4b8adb96
diff-files: 2
diff-additions: 668
diff-deletions: 24
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 20016
organ-rc: 0
[/ADJOINT PREFLIGHT]

Re-tampon (dispatch ai01-c2142-po2023c-restamps). Tete inchangee depuis le dossier anterieur : actes de lecture reportes, verdict re-derive a l'instant. (APPROVE d'ai-01 perime -- re-tampon a la tete, comme demande.)

@myia-ai-01
myia-ai-01 merged commit 603300c into main Oct 10, 2026
23 of 27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants