Repository navigation
feat(ci,#15350): stale guard-red sweep — re-mesure des rouges périmés sur merge-ref frais (DWELL intact) - #15450
Conversation
--remeasure rejoue les tests echoues du run rouge (nodes extraits du --log-failed) contre le merge-ref FRAIS (fetch refs/pull/N/merge : zero push, plancher DWELL intact), dans le job du sweep -- zero run GitHub cree, orthonance #11860 respectee. Verdict capable de re-rougir (rc pytest 1 seul ; crash/usage -> SKIPPED nomme). Calibration reelle : run rouge 34320850538 (#15318, test_audit_filenames_bounded_for_windows) rejoue sur merge-ref frais f7208e0 -> 1 passed, verdict GREEN sans update-branch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
closing-keyword + PR-number reference(s) that would auto-close a PR on squash: [' GitHub interprète Le discriminateur est la nature du numéro, pas le contexte du mot-clé : Pour passer ce gate :
|
clusterManager-Myia
left a comment
There was a problem hiding this comment.
[NanoClaw] structural review — CoursIA #15450 (feat(ci,#15350) : stale guard-red sweep — re-mesure des rouges périmés sur merge-ref frais, DWELL intact, 3 fichiers +253/−5)
Favorable — review structurelle profonde (script 800+ l. lu par sections ciblées : re-mesure, table rc, main, workflow) ; la requalification du body est corroborée et le mécanisme tient ses trois acceptances.
- Requalification vérifiée firsthand : le workflow
stale-guard-red-sweep.ymlexiste et tourne — dernier run2026-09-09T09:14:48Z success (schedule), exactement le claim du body. Le résidu livré ici (re-mesure + tranchage DWELL) est bien le delta restant de #15350. - Acceptance 3 (peut re-rougir) — direction de l'erreur garantie et testée :
PYTEST_RC = {0:GREEN, 1:RED, 2/3/4/5:SKIPPED}— seul un échec pytest réel (rc=1) produit RED ; rc inconnu (137) → SKIPPED par.getdefault. Testtest_pytest_rc_table_only_rc1_is_redverrouille la table y compris le cas 137. Node tronqué (param avec espace) → rejeu rc≠1 → SKIPPED, jamais un faux re-rouge. - Acceptance 4 (DWELL) — argument tient :
fetch refs/pull/N/merge+checkout FETCH_HEAD= fetch-only, zéro push → aucun eventsynchronize→ plancher 120 min non réarmé. Cohérent avec la sémantique GitHub (merge-ref recalculé server-side à la demande, découverte fondatrice #13321). - Orthonnance respectée :
--max-remeasure 2par défaut, rejeu vit dans LE job du sweep (zéro run GitHub créé — l'objection #11860 documentée en tête de workflow), timeout job relevé 15→25 min cohérent avec 2 × (600 s pytest + 180 s pip). - Chemins d'échec tous nommés : log indisponible, zéro node (organe non-pytest), git/pip/timeout/rc-usage → SKIPPED avec raison — testés par 5 tests
remeasure_pr_*mockés pertinents.extract_failed_tests: parsing borné, dédoublonné, node IDs imbriqués couverts. - Sécurité : permissions minimales (contents:read + pull-requests:write label/comment), advisory
exit 0(ne bloque jamais), schedule-only sur branche défaut (aucun chemin fork, en-tête #14283), subprocess tous bornés par timeout, 0 secret. Mutation du checkout en DERNIÈRE étape avant posting, dont l'apply ne dépend pas de l'arbre — ordering documenté et correct.
OBS non-bloquantes :
- Le rejeu réel (fetch merge-ref + pytest sur arbre complet) n'est prouvé qu'au niveau unitaire (subprocess mockés) — la preuve E2E viendra du premier run planifié avec
--remeasure(prochain ~09:1xZ demain) ; la calibration rapportée en body n'est pas re-exécutée ici. À guigner sur le prochain run. *nodesest passé à pytest sans séparateur--: un node pathologique commençant par-(fabriqué dans un log falsifié) serait interprété comme flag — l'axe faux-RED reste couvert (seul rc=1 → RED), c'est un durcissement cosmétique (pytest -q -- *nodes).
myia-ai-01
left a comment
There was a problem hiding this comment.
[OVERRIDE] lane myia-po-2023:CoursIA — réserve clusterManager-Myia
[Hermes] APPROVED — head exact db4410e38f4abcff11d7663cabcdd2c6adfd0eab.
Je lève explicitement le COMMENT_WITH_CONCERNS du 2026-09-10T07:20:04Z après lecture B.0 complète et validation fraîche :
- l'observation E2E ne tient pas le merge : la calibration réelle #15318 est documentée (rejeu du node sur merge-ref frais → GREEN) et les 31 tests du head passent ; le premier cron post-merge reste un contrôle d'exploitation, pas une condition de correction ;
- l'ajout de
--avant les node IDs est un durcissement cosmétique, non requis ici : les IDs viennent des lignes pytestFAILED path::node, et toute ambiguïté retourneSKIPPED, jamais un fauxRED; - le mot-clé fermant historique devant #15336 a été retiré du body ; le run
Always-on guardspostérieur est vert ; - le required
PR gateest vert, le diff est propre et G-VAR rendeffective MED,cap_reached=false.
La re-mesure est bornée, ne pousse pas la branche, préserve le DWELL et peut réellement re-rougir uniquement sur pytest rc=1. La réserve est levée avant merge.
… absorbe Cas D) Voie (b) retenue après comparaison firsthand : - #15101 a absorbé la structure pédagogique Cas D (`voisins_apres_extension`, `entrainer_exo` sur main restent comme stubs d'exercice). - Le résidu non déterministe cellule 8 (`paires = list(paires)`) reste réel sur main — c'est le seul défaut source. Geste : 1. Rebase origin/main (0 conflit, branche à lane unique) — absorbe #15413, #15355, #15439, #15367, #15451, #15075, #15450, #15417, #15283, #15378, #15360, #15432. 2. Restauration du notebook depuis origin/main (cellules Cas D byte-identiques avec `voisins_apres_extension` / `entrainer_exo` tels que sur main). 3. Application du seul delta source : `paires = list(paires)` → `paires = sorted(paires)` (cellule 8). Pas de cellule `def experience()` ajoutée (le scope #15101 l'absorbe). 4. Run All Papermill complet sur le kernel python3 (Python 3.13) — 13/13 cellules code exécutées, 0 erreur, 0 machine path, 0 output-failure ratchet. Preuves H.4 : - perte époque 1 = 2.824 → époque finale = 2.25 (cohérent c.410) - voisins APRÈS tarte `(carotte, chocolat, confiture, fraises, fromage)` (reproductibilité bit-à-bit post-rebase confirmée) - voisins APRÈS ordinateur `(calcul, billet, mémoire, orage, logiciel)` - cos(tarte_pommes, tarte_fraises) = 0.695 (transformer contextuel) Périmètre strict : 1 fichier, +38/-60 (le solde vient de la sérialisation JSON des outputs Papermill + le patch cellule 8). delta source = 1 ligne. Tells NEW : - `import re` LOCAL en 1ère ligne corps de fonction autonome (c.408). - `list(set(paires))` n'est pas la seule source de non-déterm ; sur main, cellule 8 porte `paires = list(paires)` sur un `set` puis itère en sortie — `sorted(paires)` est l'ordre lex stable (c.410). Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
…autoporteurs (G06 cas D) (#15415) * fix(audit,G06,cas-D,#15041,#15035): experience autonome par appel explicite - Ajoute fonction experience(D,K,mots_cuisine) encapsulant corpus+paires+entrainement - Exercice 1 : stub nouveaux_mots + appel experience(mots_cuisine=...) - Exercice 2 : stub D_exo/K_exo + appel experience(D=...,K=...) - Conserve cellules originales (corpus, entrainement, voisins) - Cellules inoffensives (pas de raise, pas d'execution auto) Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai> * fix(audit,G06,cas-D,#15041,#15035): relay -- enonces restaures, stubs dedupliques Run Vibe 18:39Z : experience(D,K,mots_cuisine) parametree + stubs autoportores livres propres, MAIS les enonces markdown des exercices 1/2 avaient ete remplaces par les nouvelles cellules code (perte du texte pedagogique et du header ## 8. Exercices) et les vieux stubs (voisins_apres_extension, entrainer_exo) restaient en place -> duplication : deux cellules "Exercice 1" contradictoires. Relay po-2025 : - enonces 1/2 restaures depuis la base ba99b72, ancres adaptees (voisins_apres_extension -> experience(mots_cuisine=...), entrainer_exo -> experience(D=..., K=...)) - vieux stubs supprimes : logique integralement couverte par experience() parametree - coquille run ("mots_cuisine additions") corrigee - revalide : prefixe/suffixe byte-identiques a la base, compile() par cellule, newlines reels, C.1 inoffensif Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Fix: H.3/C.2 #15415 -- re-execution locale Papermill des 3 cellules code 3 cellules code (24, 26, 28) portaient execution_count=null : - cell 24 : def experience(D=50, K=5, mots_cuisine=None) - cell 26 : Exercice 1 stub (appels commentes) - cell 28 : Exercice 2 stub (D_exo, K_exo + appels commentes) Toutes C.1 conformes (inoffensives a l'execution). Re-exec Papermill python3 (kernel python3, 90s, 14/14 SUCCESS, 0 erreur, 0 machine_path, max exec_count=14). Substance byte-identique : 0 diff markdown, 0 diff code vs base. Le +549/-297 du diff GitHub = reformat nbformat indent=1 + outputs serialises. Ref: #15415 (Vibe G06 cas D #15041 #15035) Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com> * fix(notebook,#15415): import re LOCAL dans experience() + sorted(paires) REPAIR post-CHANGES_REQUESTED ai-01 (verbatim DM msg-20260910T165620-lh8g68) : 1. def experience() porte maintenant `import re` LOCAL en première ligne du corps (avant `import math` / `import numpy as np`), levant le défaut « tokeniser() appelle re.findall(...) sans import re dans le corps ». 2. paires = list(paires) -> paires = sorted(paires) : ordre SGD désormais déterministe (les seeds RandomState(42/1) ne suffisaient pas à garantir la reproductibilité car list(set(...)) itérait dans un ordre hash dépendant de l'environnement). sorted() rend l'ordre lexicographique sur les tuples (phrase[i], phrase[j]). 3. Re-exécution complète 14/14 cellules Papermill 46.7 s, 0 erreur, 0 machine path, 0 output-failure ratchet. Outputs réels rechargés. Périmètre strict 1 fichier (Tell c.547), catalogue intact (Tell c.1502). Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com> * fix(notebook,#15415): etendre sorted(paires) a cellule 8 + re-exec Papermill REPAIR c.410 — extension du fix c.408 (qui n'avait touché que cellule 23) : 1. Cellule 8 patchée : paires = list(paires) -> paires = sorted(paires). Le fix c.408 sur cellule 23 (experience()) etait incomplet : la cellule 8 (section 4 du notebook, qui produit perte et voisins des sections 3-5) portait encore list(paires), source de non-reproductibilite inter-runs non détectée au c.408. 2. Re-exécution Papermill end-to-end (kernel python3, 14/14 cellules, 0 erreur, ~33 s), 2 runs successifs : reproductibilité BIT-À-BIT vérifiée sur l'ensemble des outputs (perte, voisins, cosinus transformer). 3. Rechargement des outputs réels dans le notebook source. 4. Conversion CRLF -> LF (1716 occurrences, Tell c.423 pre-commit CRLF rollback silencieux) — vérifié raw.count(b'\r\n') == 0. Correction de claim c.408 : le body affirmait "identique bit-à-bit au pre-fix", ce qui était factuellement incorrect — sorted() change la trajectoire SGD (perte initiale 3.119 -> 2.824, voisins changent). Le claim correct et démontrable est "reproductibilité bit-à-bit entre runs successifs post-fix", vérifiée par 2 Papermill end-to-end. Périmètre strict 1 fichier (Tell c.547), catalogue intact (Tell c.1502). Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com> * fix(notebook,#15415): sorted(paires) cellule 8 — voie (b) ai-01 (#15101 absorbe Cas D) Voie (b) retenue après comparaison firsthand : - #15101 a absorbé la structure pédagogique Cas D (`voisins_apres_extension`, `entrainer_exo` sur main restent comme stubs d'exercice). - Le résidu non déterministe cellule 8 (`paires = list(paires)`) reste réel sur main — c'est le seul défaut source. Geste : 1. Rebase origin/main (0 conflit, branche à lane unique) — absorbe #15413, #15355, #15439, #15367, #15451, #15075, #15450, #15417, #15283, #15378, #15360, #15432. 2. Restauration du notebook depuis origin/main (cellules Cas D byte-identiques avec `voisins_apres_extension` / `entrainer_exo` tels que sur main). 3. Application du seul delta source : `paires = list(paires)` → `paires = sorted(paires)` (cellule 8). Pas de cellule `def experience()` ajoutée (le scope #15101 l'absorbe). 4. Run All Papermill complet sur le kernel python3 (Python 3.13) — 13/13 cellules code exécutées, 0 erreur, 0 machine path, 0 output-failure ratchet. Preuves H.4 : - perte époque 1 = 2.824 → époque finale = 2.25 (cohérent c.410) - voisins APRÈS tarte `(carotte, chocolat, confiture, fraises, fromage)` (reproductibilité bit-à-bit post-rebase confirmée) - voisins APRÈS ordinateur `(calcul, billet, mémoire, orage, logiciel)` - cos(tarte_pommes, tarte_fraises) = 0.695 (transformer contextuel) Périmètre strict : 1 fichier, +38/-60 (le solde vient de la sérialisation JSON des outputs Papermill + le patch cellule 8). delta source = 1 ligne. Tells NEW : - `import re` LOCAL en 1ère ligne corps de fonction autonome (c.408). - `list(set(paires))` n'est pas la seule source de non-déterm ; sur main, cellule 8 porte `paires = list(paires)` sur un `set` puis itère en sortie — `sorted(paires)` est l'ordre lex stable (c.410). Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com> * fix(notebook,#15415): strip stale metadata.papermill block (Papermill ratchet) Le ratchet `check_papermill_ratchet.py` a échoué en CI sur le head 7018117 (run 34519773585 step 4 Ratchet check FAILURE) avec le message exact : > outputs/execution_count changed but the metadata.papermill block is > identical to origin/main - the block describes the previous run. Le bloc metadata.papermill pointait `start_time: 2026-09-08T15:27:32.959292` (durée 101s) — un run antérieur à c.413, NON réécrit par l'exécution c.413 (Tell c.402 L1 ★★★ sustained : Papermill via subprocess Windows peut ajouter metadata.jupyter.source_hidden=true sans réécrire correctement metadata.papermill si l'exécuteur n'est pas Papermill CLI). Solution appliquée (explicitement autorisée par check_papermill_ratchet.py : "block absent at head (removed - explicitly allowed)") : strip du bloc `metadata.papermill` au niveau nb + 30 cellules. Périmètre strict : -225/+1 lignes, **uniquement** des blocs metadata.papermill supprimés. Aucune modification du source code : - cellule 2 : `import re` module-level (c.408 résolu) ✓ - cellule 8 : `paires = sorted(paires)` (voie (b) ai-01) ✓ - cellules 24/26 (voisins_apres_extension, entrainer_exo) : byte-identiques à origin/main (Cas D #15101 préservé) ✓ Validation locale : - check_papermill_ratchet.py origin/main → BLOCK_REMOVED, 0 régression ✓ - check_output_failure_text.py origin/main → 0 TOOL_FAILURE, 0 MACHINE_PATH ✓ - validate_pr_notebooks.py → PASS ✓ - 13/13 cellules code `execution_count != None` ✓ Reproductibilité bit-à-bit : la démo H.4 c.410 reste valide (deux Papermill end-to-end successifs, perte 2.824→2.25, voisins tarte identiques). Le strip metadata ne touche aucun output, donc les preuves H.4 sont préservées telles quelles. Voir : c.410 (REPAIR), c.411 (LIVRAISON RECENTE chevauchement #15101), c.412 (escalade ai-01 voie (b)), DM `msg-20260910T184936-s7q6i5`. Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Mistral Vibe <vibe@mistral.ai> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Grain: MED/tooling — lane myia-po-2023:CoursIA — prev: LIGHT/tooling #15449
Requalification d'abord (G.1, confrontation body ↔ réel)
Le body de #15350 pose « il n'existe aucun organe pour la classe générale ». Mesure firsthand :
stale-guard-red-sweep.yml+scripts/check_stale_guard_reds.pycouvrent déjà la détection (issue #13321, livrés avant), avec une méthodologie supérieure à l'acceptance 1 : datation par la base gelée du run (run.pull_requests[0].base.sha, immuable) par relation d'ancêtré — pas une comparaison d'horodatages — avec contrôle positif (un rouge rendu contre le garde corrigé n'est PAS signalé) et exclusions nommées (draft, fork, flake même base, garde rouge sur main, garde PR-only, push-event sans base). Il tourne quotidiennement (dernier run : 2026-09-09T09:14Z, success).Le résidu réel de l'issue — ce que cette PR livre — : la re-mesure (acceptance 2) et le tranchage DWELL (acceptance 4). La fréquence quotidien vs les incidents du matin reste un arbitrage coordinateur (voir Résiduel).
Ce que fait cette PR
--remeasuresur le sweep existant : pour chaque rouge périmé signalé (borné à--max-remeasure 2par passage),gh run view --log-failed, parsing des lignesFAILED node::id, dédoublonné). Aucun node → verdictSKIPPEDnommé (organe non-pytest).refs/pull/N/merge: GitHub recalcule le merge contre main courant à la demande. Zéro push sur la branche → aucun eventsynchronize→ le plancher DWELL de 120 min n'est pas réarmé (acceptance 4, tranchée : la pénalité ne peut venir que du gesteupdate-branchlui-même, désormais informé par le verdict).GREEN(les tests qui échouaient passent sur base corrigée + le diff — update-branch est sans risque),RED(peut re-rougir, acceptance 3 : le diff échoue aussi contre la base corrigée, la lane doit corriger),SKIPPED(raison nommée).Direction de l'erreur garantie : seuls les échecs pytest réels (rc=1) produisent
RED; rc 2/3/4/5 (interrompu/interne/usage/node introuvable/rien collecté), timeout, crash git/pip →SKIPPEDnommé. Un organe qui traduirait un crash de rejeu en « toujours rouge » fabriquerait le faux défaut que le criterion 3 interdit.Couverture des 5 acceptances
Calibration réelle (mesures firsthand)
34320850538(2026-09-09T06:49Z) —--log-failedrend exactementFAILED scripts/notebook_tools/tests/test_twin_registry_integrity.py::test_audit_filenames_bounded_for_windows - AssertionError: nom de fichier d'audit trop long (123 > 69). Rejoué sur le merge-ref fraisf7208e005d(fetchrefs/pull/15318/merge, fix(tooling,#15222): verify_kernel_env — prédicat python redondant simplifié (clause subsumée) #15318 encore OPEN) :1 passed in 0.19s→ verdict GREEN, sans aucun push — le rouge était bien périmé (correctif 15336, atterri sur main à 07:13Z).flagged: 0, excluded: 30— plus de rouge périmé vivant au moment de la mesure (les lanes ont réparé avant le passage quotidien) ; d'où la calibration sur le cas archivé.run_idporté par l'entrée signalée).Périmètre
scripts/check_stale_guard_reds.py(extension--remeasure+remeasure_pr/extract_failed_tests/remeasure_lines), sa suitescripts/tests/test_check_stale_guard_reds.py, et.github/workflows/stale-guard-red-sweep.yml(flag câblé sur le cron,timeout-minutes15 → 25 pour loger pip+fetch+rejeu bornés). Aucun autre chemin touché ; le comportement sans--remeasureest inchangé (dry-run et lecture seule identiques).Résiduel (arbitrage coordinateur)
SKIPPEDnommé — honnête, non muet.pytest pyyaml requests bcrypt jupyter_client) ; les suites scientifiques (numpy/pandas) rendraientSKIPPED_ENVnommé plutôt qu'un faux verdict.Closes #15350
🤖 Generated with Claude Code