Repository navigation
feat(lean,#15666): sous-commandes operateur dry-run et doctor dans lean_exec - #20011
Conversation
…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>
|
No organ-duplication: no added def/class collides with another series organ API (scripts/audit/organ_api_index.yaml). Detector: |
Path-collision (organ #13359/#13615)Cette PR #20011 (
Le verdict terminal (#15578) signale qu'un cote de la paire est deja sur |
… (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>
|
[ADJOINT PREFLIGHT] |
…t recuperation apres crash (case 8, complet) (#20016) * feat(lean,#15666): sous-commandes operateur `dry-run` et `doctor` dans 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> * feat(lean,#15666): sous-commande `stop` — arret d'urgence des runs possedes (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> * feat(lean,#15666): sous-commande `recover` — recuperation apres crash (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> * fix(lean,#15666): stop --yes ne suicide plus sous POSIX et ne ment plus 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> --------- Co-authored-by: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
Grain: MED/tooling — lane myia-po-2027:CoursIA-2 — prev: DEEP/lean #20000
Case 8 de l'EPIC #15666, premier volet : l'interface operateur de diagnostic.
lean_exec.pyn'exposait querun,status,backends— pas dedoctor, pas dedry-run(grepdry.?run|doctor: 0 occurrence dans le fichier).Summary
dry-run: « que ferait un run ? » — backend qui serait choisi, ressourcesmesurees, parallelisme qui serait accorde, verdict d'admission.
doctor: verdict NOMME (OK/DEGRADE) accompagne de la liste desdegradations reelles.
resolve_backend(dry=True): la decision est rendue sans etre consommee(aucun epinglage ecrit).
Changes
scripts/lean/lean_exec.py—dry_run(),doctor(),resolve_backend(dry=...),en-tete de module, deux sous-parsers CLI.
scripts/lean/tests/test_lean_exec.py— 8 tests.Deux proprietes qui portent le livrable
1. Lecture seule. L'invariant n'est pas « la sortie est jolie » mais « rien n'a
bouge sur le disque » : pas d'epinglage de backend, pas de lease d'arbre, pas
d'entree de file, pas de verrou d'admission. Seul ecrit : la creation idempotente
du state dir, que
measure_resourcesexige pour lire l'espace disque.Consequence de conception :
resolve_backendecrit l'epinglage premier-ecrivain.Un
dry-runqui l'appellerait tel quel laisserait le run reel avec un choix qu'iln'a pas fait. D'ou un flag
dryet un point d'ecriture unique (_record).2. Le verdict reproduit la chaine d'admission DANS SON ORDRE — population,
telemetrie, budget, cap, cap enregistre, backend (cf
run_command._attempt). Unordre different rendrait un verdict plausible et faux : le premier refus annonce
doit etre celui que le run reel produirait.
Ecart mesure au passage
grantedse calcule surcfg["jobs"]— le parallelisme-Kjobs=Naccorde al'enfant — tandis que
budgetest le nombre de places prises dans le capmachine-wide. Deux grandeurs distinctes, que
run_commandcalcule separement.Le premier jet de
dry-runles confondait (il passaitbudgetacompute_granted) ; c'esttest_dry_run_budget_agrees_with_status_admissionqui l'a attrape, en confrontantla prediction a la mesure de
status.doctorne balaie pas, contrairement astatus: un diagnostic qui supprime lesfichiers 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 — un diagnostic qui crie au loup sur une
installation neuve ne sert a rien.
Ce que cette PR ne fait pas
Le case 8 a deux volets. Celui-ci livre le diagnostic. Restent ouverts, et
documentes dans l'en-tete du module : l'arret d'urgence des runs possedes par
l'organe (inspecter puis arreter, sans tuer aveuglement un travail etranger) et la
procedure de recuperation apres crash du superviseur.
statusetdoctorexposent deja les runs vivants et les leases sur lesquels ces deux volets
s'appuieront.
Review Checklist
dryqu'elles exigent ; aucunautre changement dans le fichier.
python -m pytest scripts/lean/tests/test_lean_exec.py -q→54 passed, 1 skipped (le skip est pre-existant : motif
native_pop,ci(tests): test_lean_exec.py rougit Scripts Tests selon le runner (5 echecs, exit 125) depuis le 05/10 #19382).
sont executees pour de vrai dans les tests, pas seulement importees.
resolve_backendn'a qu'un autre appelant(
run_command, appel par mot-cle, signature preservee :dryest ajoute avecun defaut
False).grepdes definitions :dry_run,doctoret_recordapparaissent une fois chacun. Aucun autre fichier du depot n'appelle
lean_exec.Anti-regression
sorry(aucun.leantouche).@pytest.skip/assert Trueajoute pour contourner un echec. Le seulpytest.skipnouveau est un garde de mesure : il ne se declenche que si lapopulation native bouge entre deux mesures de la meme machine — ce qui rend la
comparaison non concluante — et il le dit.
dry=Falsereste le defaut, ettest_resolve_backend_dry_decision_identical_without_writingverifie que lemode reel ecrit toujours l'epinglage.
Test plan
Les 8 tests : 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/doctorOK /doctorDEGRADE sansbackend disponible / differentiel
doctorne balaie pas vsstatusbalaie.Le dernier porte son propre controle : il verifie que
statusbalaie bien lefichier perime, sinon le test serait vacant — il passerait aussi si le balayage
etait casse des deux cotes.
See #15666
🤖 Generated with Claude Code