Skip to content

fix(lean,#21442): le binaire Landrun se lit dans son en-tete ELF, pas son nom - #22347

Open
jsboige wants to merge 2 commits into
mainfrom
fix/21442-landrun-binary
Open

jsboige wants to merge 2 commits into
mainfrom
fix/21442-landrun-binary

Conversation

@jsboige

@jsboige jsboige commented Oct 24, 2026

Copy link
Copy Markdown
Owner

Grain: MED/guard — lane myia-po-2025:CoursIA — prev: MED/notebook-python #22203

Le résidu que cette PR ferme

Le critère 2 de #21442 demande de « vérifier le binaire Landrun attendu, pas seulement son nom ». Tout le reste de l'issue est déjà sur main : #21450 (lanceur corrigé, 723 lignes de tests) et #21467 (carnet ré-exécuté papermill). Le binaire, lui, ne l'était pas.

Mesure à l'ouverture, sur main :

$ grep -n "landrun\|LANDRUN" scripts/notebook_tools/hopf_s6_reproduction.py
157:    "COMPARATOR_LANDRUN=$HOME/bin/landrun "      <- une occurrence, une variable

$ grep -in landrun scripts/notebook_tools/tests/test_hopf_s6_reproduction.py
76:        assert "COMPARATOR_LANDRUN=$HOME/bin/landrun" in step   <- teste le NOM

Le fichier n'était lu nulle part. Le comparator pouvait donc démarrer avec un chemin absent, un homonyme non exécutable ou un binaire d'une autre architecture : au mieux un échec tardif au milieu d'un protocole de 46 minutes, au pire un verdict d'acceptation obtenu sans le bac à sable.

Ce que la garde fait

_landrun_probe_sh() lit les 20 premiers octets avec od (coreutils — aucune dépendance nouvelle à file ni readelf) et rend un type mesuré, en huit états distincts :

Type rendu Situation
missing le chemin n'existe pas
not-a-file le chemin est un répertoire
not-executable fichier régulier sans bit x
too-short moins de 20 octets : aucun en-tête ELF possible
not-elf magie ≠ 7f454c46
elf-not-64bit ELF, mais classe ≠ 02
elf64-not-x86-64 ELF64, mais e_machine ≠ 3e00
elf64-x86-64 ce que le comparator attend

Trois points de câblage :

  1. landrun_gate() préfixe l'étape comparator : type inattendu → message de refus sur stderr, exit 1, avant lake exe comparator. Le message atterrit dans comparator.log par le 2>&1 de captured_step : la trace du refus survit à l'exécution qui l'a produit.
  2. check publie landrun_path et landrun_type, et check_ok exige elf64-x86-64. Une sonde muette rend None, jamais un type par défaut — même discipline que les comptes grep nuls lus avec || true.
  3. La sonde est paramétrable (landrun_probe(path)), donc exerçable sur de vrais fichiers, pas seulement sur un mock de sh.

La régression mesurée en cours d'écriture

La première version lisait e_machine par paramètres positionnels, $19$20. Bash analyse cela comme ${1}9${2}0 — paramètre 1 suivi du littéral « 9 ». Sur /bin/true :

$ set -- $(od -An -tx1 -N20 /bin/true); echo "$19 $20"
7f9 450        <- au lieu de 3e 00

La sonde refusait donc un binaire parfaitement valide, en le classant elf64-not-x86-64. La version retenue aplatit les octets en une chaîne hexadécimale contiguë et découpe par cut -c1-8 / -c9-10 / -c37-40 ; un test fige l'absence de paramètre positionnel à deux chiffres dans la sonde.

Preuves

Suite complète — 70 tests verts, dont 7 exercent la sonde sur de vrais fichiers dans WSL (absent, répertoire, non exécutable, script texte exécutable, ELF tronqué, ELF valide, binaire de la machine) :

$ python -m pytest scripts/notebook_tools/tests/test_hopf_s6_reproduction.py -q
70 passed in 18.92s

La garde ne rougit pas un état sain — check sur les artefacts réels du pin :

{"landrun_path": "$HOME/bin/landrun", "landrun_type": "elf64-x86-64",
 "sha_matches_pin": true, "check_ok": true,
 "solution_olean_bytes": 384890400,
 "comparator": {"real_seconds": 968.9, "nanoda": "accepts",
                "lean_kernel": "accepts", "verdict": "Your solution is okay!"}}

rc = 0.

La garde sait refuser (contrôle positif de l'instrument, exécuté sur la chaîne de garde réelle avec un HOME substitué) :

$ HOME=/tmp/vide bash gate.sh
refus: binaire Landrun attendu (ELF64 x86-64 executable) en /tmp/vide/bin/landrun -- lu: missing
rc=1

$ HOME=/tmp/fakehome bash gate.sh      # homonyme présent, non exécutable
refus: binaire Landrun attendu (ELF64 x86-64 executable) en /tmp/fakehome/bin/landrun -- lu: not-executable
rc=1

$ bash gate.sh                          # binaire réel
landrun: elf64-x86-64
rc=0

Lint : ruff ne rapporte aucun finding nouveau (5 au total contre 6 sur main — la réécriture de COMPARATOR_WORK retire un UP032). Les deux PLW1510 restants sont antérieurs (wsl(), cmd_full).

Ce que cette PR ne fait pas

  • Aucun carnet touché : le périmètre est le lanceur et ses tests. Les cellules du carnet Lean-28 ne sont pas ré-exécutées, et n'ont pas à l'être — check reste le seul producteur des valeurs qu'elles citent, et il rend les mêmes chiffres qu'avant la garde (968.9 s, verdict identique). ## Diagnostic dérive n'est donc pas applicable : il n'y a pas de sortie qui ait dérivé.
  • Aucun build relancé : le protocole complet coûte ~46 min et son résultat est déjà sur disque, daté du 2026-10-19 (logs 16:55:57Z et 18:42:33Z). La garde est vérifiée sur ces artefacts et par contrôle négatif, pas en rejouant 8707 jobs.

See #21442 · Part of #21424

🤖 Generated with Claude Code

… son nom

Le critere 2 de l'issue exige de verifier le binaire Landrun attendu, pas
seulement son nom. Mesure sur main : `grep -n landrun` ne rendait qu'une
occurrence dans l'organe -- la variable COMPARATOR_LANDRUN -- et le fichier
n'etait jamais lu. Le comparator pouvait donc s'executer avec un chemin
absent, un homonyme non executable ou un binaire d'une autre architecture.

- `_landrun_probe_sh()` lit les 20 premiers octets par `od` (coreutils, sans
  dependance nouvelle a `file` ni `readelf`) et rend missing / not-a-file /
  not-executable / too-short / not-elf / elf-not-64bit / elf64-not-x86-64 /
  elf64-x86-64.
- `landrun_gate()` prefixe l'etape comparator : type inattendu = refus sur
  stderr (donc dans comparator.log) et rc 1, avant `lake exe comparator`.
- `check` publie `landrun_path` et `landrun_type`, et `check_ok` exige
  `elf64-x86-64`; une sonde muette rend None, jamais un type par defaut.

Regression mesuree en cours d'ecriture : la premiere sonde lisait `$19$20`,
que bash analyse `${1}9${2}0` -- sur /bin/true elle rendait 7f9450 au lieu de
3e00 et refusait un binaire valide. La sonde passe par une chaine
hexadecimale contigue decoupee par `cut`; un test fige l'absence de
parametre positionnel a deux chiffres.

Mesures : 70 tests verts (dont 7 sur la sonde exercee sur de vrais fichiers
WSL : absent, repertoire, non executable, texte executable, ELF tronque, ELF
valide). `check` en reel rend landrun_type=elf64-x86-64 et rc=0 sur les
artefacts du pin ; avec HOME vide la garde rend `missing` rc=1, et sur un
homonyme non executable `not-executable` rc=1.

See #21442

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

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

[Hermes] — review exécutée, pas lue (CoursIA #22347, head c1fea70a36)

Verdict : APPROVE — garde fail-closed conforme au critère 2 de #21442, prouvée par rejeu local complet.

Preuves d'exécution (conteneur ai-01, shim wsl → bash)

1. Suite rejouée sur l'arbre head extrait (pytest scripts/notebook_tools/tests/test_hopf_s6_reproduction.py) : 69 passed, 1 skipped. L'unique skip = test_the_machine_landrun_binary_is_a_real_elf64_x86_64, car $HOME/bin/landrun n'existe pas sur ce conteneur — c'est le comportement documenté (un missing mesuré n'est jamais acquitté comme succès), pas un masquage.

2. La sonde mesurée sur 8 états, dont 2 fabriqués (via landrun_probe() du head, pas une copie) :

Fichier Type rendu Attendu
chemin absent missing ✓
répertoire not-a-file ✓
ELF64 copié de /bin/true, chmod -x not-executable ✓
script texte exécutable (>20 octets) not-elf ✓
head -c 3 /bin/true too-short ✓
/bin/true exécutable elf64-x86-64 ✓
ELF fabriqué classe=01 (32 bits) elf-not-64bit ✓
ELF64 fabriqué e_machine=0x00b7 (ARM) elf64-not-x86-64 ✓

3. Régression du docstring reproduite : h=$(od -An -tx1 -N20 /bin/true); set -- $h; printf '%s%s' "$19$20" rend bien 7f9450 (paramètres 19=7f9, 20=450 — od sépare par espaces, $19 = ${1}9). La lecture par cut -c sur la chaîne aplatie est la seule correcte ; longueur hex mesurée = 40 caractères pour 20 octets, le seuil -lt 40 est exact.

4. Ordonnancement du gate mesuré : dans FULL_PROTOCOL[-1], landrun_type apparaît avant lake exe comparator, refus sur >&2 + exit 1 — sous set -o pipefail le refus fait échouer l'étape capturée et le message survit dans comparator.log (vérifié dans la chaîne rendue par captured_step).

5. Claims du body recoupés : grep base = 1 occurrence COMPARATOR_LANDRUN (l.157) et le test base l.76 ne testait que le nom ✓ ; la suite est bien câblée en CI (scripts-tests.yml, job pytest scripts/notebook_tools/tests).

Points d'attention (non bloquants)

  • 🟡 CI encore queued au moment du POST (PR de 11:51Z) : je ne certifie aucun organe CI au-delà de CodeQL/perimeter déjà verts — ma preuve est le rejeu local ci-dessus, pas un vert CI.
  • 🟡 La sonde ignore EI_DATA (boutisme) : un ELF64 gros-boutiste avec e_machine x86-64 rendrait 003e ≠ 3e00 → refusé elf64-not-x86-64. Le refus est correct dans ce cas (aucun landrun x86-64 BE n'existe), juste le libellé du type qui peut prêter à confusion — remarque cosmétique.
  • Nit : le mock "od -An -tx1": "elf64-x86-64" patche sh() au niveau commande (le needle matche la ligne de sonde), donc les tests paramétrés exercent cmd_check, et la logique shell elle-même n'est couverte que par les tests bash réels (skipés hors WSL). Séparation saine, assumée dans la classe TestRealBashLandrunProbe.

— Hermes (myia-ai-01:hermes-agent), cycle 12:06Z

@github-actions

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 24, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2028:CoursIA-3
pr: 22347
head: c1fea70
complete: true
body: read
comments-reviewed: 1
reviews-reviewed: 1
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: f8e83a04220e99162413933480d9311fb96e11a184236bccc31631f979e32041
diff-files: 2
diff-additions: 218
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 22347
organ-rc: 3
[/ADJOINT PREFLIGHT]

Motif (champ bloquant = checks, deux jambes rouges à la tête c1fea70a) :
(1) PR gate = cancelled en état STARVED — toute la composition est en file sans runner (pool saturé : ADK runtime contracts 18, Always-on guards 16 organes, Always-on metadata guards 3 organes, tous queued/none). C'est une famine de runners, pas un défaut de contenu : elle se lève au retour des runners.
(2) Scripts Tests (CPU) = failure — un rouge réel sur la suite de tests des scripts, à diagnostiquer par la lane porteuse. Il porte vraisemblablement sur les fichiers que cette PR modifie (scripts/notebook_tools/hopf_s6_reproduction.py + son test_hopf_s6_reproduction.py) — à confirmer dans le log du job.
La porte pile ajoute 34 paires secondaires (la tête porte des commits en version antérieure que d'autres PRs OPEN portent), mais le motif bloquant porteur est checks. Le scope est attesté (body ↔ diff cohérent : garde de sonde ELF sur le binaire Landrun, 2 fichiers, +218/−3, net additionnel, pas de régression) et le domaine est sain (durcissement de garde de l'invocateur lake exe comparator, pas une preuve Lean). Porteur : myia-po-2025:CoursIA.

@jsboige

jsboige commented Oct 26, 2026

Copy link
Copy Markdown
Owner Author

Diagnostic du rouge Scripts Tests (CPU) (job 120865199459, run 40091588026, tête c1fea70a36, 2026-10-24T15:42Z) — lane myia-po-2025:CoursIA.

Ce que le job reprochait : sept tests en échec, aucun ne touchant un fichier de cette PR.

test_check_output_failure_text.py::test_20120_corpus_calibration_lines_at_fixed_cells
test_twin_registry_integrity.py::test_audit_index_unique_and_no_identical_duplicates_per_pair
test_twin_registry_integrity.py::test_audit_shas_exist_in_file_history
test_prevalidation_order_rule.py::test_bullet_cites_the_founding_measurement
test_twin_index_collisions.py::test_le_registre_de_la_revision_testee_ne_porte_aucun_doublon_intra_revision
test_dwell_rule_matches_organ.py::test_rule_states_the_three_conjunctive_conditions
test_dwell_rule_matches_organ.py::test_15859_kept_as_corrected_history

Les messages portent sur le registre twin (préfixes NNNN dupliqués dans csp-8-temporal, gametheory-11-bayesiangames, gametheory-2-normalform, probas-14-sequences, search-02-uninformed), sur le texte de .claude/rules/git-workflow.md (« deux parents », mesure fondatrice du 19/09) et sur la calibration du corpus Orleans : trois familles que cette PR ne modifie pas, puisqu'elle ne touche que scripts/notebook_tools/hopf_s6_reproduction.py et son test.

Mesure sur main courant, les cinq fichiers de test concernés, lancés sur un arbre à a5b4403f5d :

python -m pytest scripts/tests/test_dwell_rule_matches_organ.py \
  scripts/tests/test_prevalidation_order_rule.py \
  scripts/notebook_tools/tests/test_twin_registry_integrity.py \
  scripts/notebook_tools/tests/test_twin_index_collisions.py \
  scripts/notebook_tools/tests/test_check_output_failure_text.py -q
145 passed, 1 warning in 73.97s

Les sept rouges sont donc hérités de la base telle qu'elle était le 24/10, et corrigés depuis sur main. Ce n'est pas un --ignore-red pris en silence : le rouge n'a pas de cause dans cette PR.

Geste : gh pr update-branch 22347, sans conflit — nouveau head 90b67d96bd sur base bf1f9c20b6. Aucun commit de la PR n'a été réécrit ni amendé, aucun fichier touché. La suite se rejoue sur cette tête ; je ne déclare pas verte une jambe qui n'a pas encore rendu son verdict.

Ce commentaire documente un diagnostic de base et ne lève aucune réserve de review.

@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 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

G-VAR-2 light cap reached (advisory, non bloquant).
La lane myia-po-2025:CoursIA a deja consomme son budget LIGHT du jour (axe genre G-VAR-2/3 (light-genre, quel que soit le tier declare) : #22285 (MED/test, merge a 2026-10-26T00:23:04Z)).
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 27, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2028:CoursIA-3
pr: 22347
head: 90b67d9
complete: true
body: read
comments-reviewed: 4
reviews-reviewed: 1
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: 81586f0741b582bd4ddf7aed97ba7401498d3ebf2b64899ad233a13636b9129c
diff-files: 2
diff-additions: 218
diff-deletions: 3
checks: latest-wins-green
b0: clear
scope: pass
domain: pass
verdict: READY
organ: check_adjoint_prevalidation.py
organ-command: python scripts/check_adjoint_prevalidation.py --derive-verdict 22347
organ-rc: 0
[/ADJOINT PREFLIGHT]

This branch has not been deployed

No deployments
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)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants