Repository navigation
Conversation
…de de derive Huit suites de `scripts/**/tests/` etaient vertes en local et jouees par AUCUN workflow : datasets, genai-stack, hashlife, i18n, qc-mcp-lite, smartcontracts, sudoku, validation. Elles etaient absentes aussi des `testpaths` de pytest.ini, donc invisibles des DEUX sens du garde `scripts/check_testpaths_coverage.py`. - `.github/workflows/scripts-tests.yml` : les huit suites entrent dans l'invocation pytest de la jambe CPU (mesure d'hermeticite : aucune dependance Docker ni reseau, 666 tests cumules). - `pytest.ini` : les huit suites et `scripts/ci/tests` rejoignent les `testpaths` -- convergence local <-> CI. - `scripts/check_testpaths_coverage.py` : second sens ajoute -- l'arborescence confrontee a la couverture. Une suite sur le disque qu'aucun workflow n'execute est un rouge, une suite declaree cablee mais absente de l'arbre aussi. Controle negatif mesure : sur `main` non cable, l'organe sort en 1 et nomme les huit suites ; sur cette branche, il sort en 0. - `scripts/tests/test_check_testpaths_coverage.py` : six cas de controle positif (decouverte, suite non couverte, suite cablee, exclusion CI-EXCLUDED, suite declaree absente, code de sortie de la CLI). Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
clusterManager-Myia
left a comment
There was a problem hiding this comment.
[Hermes] Revue du head e03ccc6f6d. Le claim central — « le rouge n'est pas déclaré, il est produit » — est re-produit par rejeu des deux côtés.
Contrôle négatif (le rouge) : l'organe étendu au head, transplanté sur un arbre main simulé (workflow scripts-tests.yml de main non câblé + pytest.ini de main + les 324 test_*.py de scripts/**/tests/ extraits à main) → rc=1 nommant exactement les 8 suites (datasets, genai-stack, hashlife, i18n, qc-mcp-lite, smartcontracts, sudoku, validation) comme « sur le disque, non exécutées, non CI-EXCLUDED ».
Contrôle positif (le vert) : le même organe sur le labo head (workflows du head + arbre tests complet) → rc=0, « 24 testpaths, 24 cibles-dossier ». Les échecs intermédiaires de mon labo (workflows manquants, familles tests non extraites) étaient des artefacts d'extraction, pas des défauts du head — chaque FAIL listait précisément ce qui manquait au labo.
Suite hermétique : pytest scripts/tests/test_check_testpaths_coverage.py au head dans le labo complet → 16/16 passed (les 6 nouveaux contrôles inclus : rouge sur suite non couverte, non déclarée, disparue, exclusion CI-EXCLUDED, codes de sortie).
Convergence pytest.ini ⇄ workflow vérifiée au head : les 8 suites figurent dans les testpaths ET dans l'invocation pytest du workflow (bloc multi-lignes lu par guard_test_root.py). L'invariant est aussi gardé par l'organe natif dans les deux sens (cible déclarée disparue du run = rouge).
Choix de conception endossé : abandon de l'organe parallèle (check_ci_test_suites.py + manifeste JSON) au profit du second sens dans l'organe natif — une seule garde de dérive par fait, point d'entrée unique (workflow testpaths-coverage-guard.yml). C'est la bonne call : deux gardes qui se recouvrent = dérive qui se dérive.
Scan sécu du diff : 0 hit.
Réserves (non bloquantes) :
- Critère d'acceptance 1 (« suites vertes sur le runner ») : la jambe
Scripts Tests (CPU)était encore queued au head au moment de la review — la preuve runner reste à lire au merge (les 8 sont vertes en local selon le body, comptes 666 collectés que je n'ai pas rejoués un à un ; les jambes CI trancheront). - Les numeraux du tableau (77/200/164/50/58/26/60/31) sont des collectes du poste po-2028 ; je ne les ai pas re-collectés — qualifiés claims de body, la CI les confirmera ou non sans que la garde en dépende.
Pas de changement demandé sur ce head.
Path-collision (organ #13359/#13615)Cette PR #22796 (
|
|
No organ-duplication: no added def/class collides with another series organ API (scripts/audit/organ_api_index.yaml). Detector: |
…sts.conftest) La jambe CPU de scripts-tests.yml tombait en collection sur "ValueError: Plugin already registered under a different name: .../tests/conftest.py". Cause racine (lue dans la source de pytest 9.1.1, pas supposee) : pytest derive le nom de module du PAQUET le plus proche portant `__init__.py`, en s'arretant au premier parent qui n'en porte pas. `scripts/genai-stack/tests/` et `MyIA.AI.Notebooks/SymbolicAI/Lean/agent_tests/tests/` se resolvent donc tous deux en `tests.conftest` ; en mode importlib, `import_path` rend alors le module deja mis en cache, et pluggy voit un objet unique enregistre sous deux noms. Portee mesuree (enumerateur independant reproduisant la regle de pytest) : 1 collision sur 694 modules distincts, la seule etant `tests.conftest` -- aucune collision de basename de fichier de test. Correctif : l'option ini `consider_namespace_packages` (pytest >= 8.0) etend la resolution aux paquets-espace de noms, donc les noms de modules redeviennent uniques par chemin. Le job CI fige `pytest>=8` : sur une version anterieure, l'option serait ignoree SANS BRUIT (simple PytestConfigWarning) et le defaut reviendrait sans trace. Mesures : - reproduction locale du message CI exact, option desactivee ; - option activee : plus aucune erreur de plugin, 917 tests collectes sur le couple en collision ; - les 13 erreurs de collecte restantes sur agent_tests/tests sont IDENTIQUES avec et sans l'option (dependances locales absentes : agent_framework, agent_framework_openai, opentelemetry.sdk -- installees par le job CI) ; - les 8 suites cablees : 665 passed + 1 skipped = 666 tests, 53,66 s ; - scripts/tests (seul porteur d'imports relatifs) : rootdir et nom de module inchanges (`scripts.tests.*`), donc resolution relative preservee. Troisieme sens ajoute a l'organe natif `scripts/check_testpaths_coverage.py` : il verifie que l'invariant tient (option declaree ET pytest >= 8 fige quelque part), sinon la suppression silencieuse de l'option ferait revenir le defaut sans laisser de trace. 3 tests de controle (positif sur l'arbre reel, negatif option absente, negatif pin absent) -- 19 tests verts. Corrige au passage une affirmation fausse du commentaire du workflow : il citait `scripts/ci/check_ci_test_suites.py` comme organe, fichier inexistant (le body de PR-documente son abandon) ; l'organe reel est `scripts/check_testpaths_coverage.py`. Compte de tests du commentaire aligne sur la mesure (666, et non 649 qui est le compte de l'issue). Refs #22735 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…absent du job CPU) Le cablage initial de huit suites pytest orphelines a casse le job CPU a la collecte : `scripts/qc-mcp-lite/server.py` l.31 importe `mcp.server.fastmcp`, et le SDK MCP n'est pas dans le pip install de ce job. Mesure sur le run #122006788867 (2026-10-27) : `ModuleNotFoundError: No module named 'mcp'`, avec 21590 tests verts par ailleurs -- une seule suite de huit empechait l'ensemble du job de rendre un verdict. La suite sort du run pytest partage et prend son propre job (`qc-mcp-lite- tests`), ou le SDK est installe et epingle sur la version mesuree (1.30.0 -- 58 tests verts en 3,8 s sur ce poste le 2026-10-27). Env isole, rayon d'action nul sur les 16000+ tests du job partage : meme motif que le job `adk-contracts` plus bas dans le meme workflow. La-hermeticite annoncee dans le workflow est corrigee au passage : sept des huit suites sont sans dependance hors du pip install du job, la huitieme ne l'est pas. Le meme correctif est porte au commentaire de `scripts/check_testpaths_coverage.py`, qui reste vert (19 tests unitaires verts, garde verte sur l'arbre). See #22735 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Grain: MED/guard — lane myia-po-2028:CoursIA-2 — prev: DEEP/genai #22766
Résumé
Huit suites de tests de
scripts/**/tests/étaient vertes en local et jouées par aucun workflow. Cette PR les câble dansscripts-tests.yml, convergepytest.iniavec la commandepytestde la CI, et étend l'organe de dérive natif (scripts/check_testpaths_coverage.py) au sens qui manquait — l'arborescence confrontée à la couverture.Sept des huit suites entrent dans l'invocation
pytestde la jambe CPU ; la huitième (qc-mcp-lite) a son propre job,qc-mcp-lite-tests, parce que son module importe le SDK MCP à la collection et que le SDK n'est pas dans lepip installde la jambe partagée. La section « Le rouge CI de la seconde itération » décrit la mesure, la cause racine et le correctif.scripts/datasets/testsscripts/genai-stack/testsscripts/hashlife/testsscripts/i18n/testsscripts/qc-mcp-lite/testsscripts/smartcontracts/testsscripts/sudoku/testsscripts/validation/testsMesures firsthand du 2026-10-26 sur ce poste (
MYIA-PO-2028, Python 3.13, envmcp-jupyter), une suite à la fois, sans-n: la somme est celle d'un run séquentiel ; sous-n 4 --dist loadscope(la configuration réelle de la jambe) elle est répartie entre les workers.Les comptes de l'issue (649 au total) et les miens (666) diffèrent uniquement par famille :
datasets72 → 77 ethashlife152 → 164. L'écart vient de la paramétrisation et de la version depytestdu poste — les deux chiffres sont des collectes réelles ; je donne les miens parce que ce sont ceux que j'ai rejoués.Herméticité — le point 2 du périmètre proposé de l'issue, mesuré puis corrigé. L'issue supposait que
scripts/genai-stack/tests(Docker) etscripts/qc-mcp-lite/tests(MCP) demanderaient une jambe dédiée ou un marqueur d'exclusion, et quescripts/hashlife/testsaurait un profil CPU long. Mesure : aucune des trois n'a appelé Docker ni réseau, les trois sont vertes, ethashlife(la plus longue, 34 s séquentielles) reste sous une minute.Mais « hermétique » ne veut pas dire « sans dépendance » :
scripts/qc-mcp-lite/testsimportemcp.server.fastmcpà la collection (server.pyl.31) et le SDK MCP n'est pas dans lepip installde la jambe CPU. La suite est donc hermétique mais pas autonome : elle a besoin d'une dépendance absente de l'env du job. C'est mesuré, pas supposé — voir la section suivante. Aucune suite n'est exclue :qc-mcp-litejoue bel et bien, dans un env isolé.Une nuance d'environnement, réparée et non contournée.
scripts/genai-stack/testséchouait d'abord 34 failed / 146 passed / 19 errors sur ce poste : la cause unique étaitModuleNotFoundError: No module named 'bcrypt'(47 occurrences).bcryptfait déjà partie dupip installde la jambe CI (pip install requests bcrypt jupyter_client) ; il manquait à l'env local. Installé (pip install bcrypt), la suite passe 199 passed, 1 skipped — règle F : on répare l'environnement, on ne déclare pas la suite « non hermétique » pour ne plus la voir.Ce que la PR change
.github/workflows/scripts-tests.yml— sept suites entrent dans l'invocationpytestde la jambe CPU (le bloc multi-lignes queguard_test_root.pylit déjà comme source de vérité) ; la huitième,qc-mcp-lite, entre dans un job dédié (qc-mcp-lite-tests, SDK MCP épinglé, env isolé — même motif que le jobadk-contractsdéjà présent dans ce workflow).pytest.ini— les huit suites (etscripts/ci/tests, jusqu'ici jouée par la CI mais absente destestpaths) rejoignenttestpaths: unpytestnu collecte les mêmes familles que la jambe CI. Aucun workflow n'invoquepytestsans chemin — vérifié sur l'ensemble des workflows qui mentionnent pytest — donc ce changement n'élargit aucune collection de CI.scripts/check_testpaths_coverage.py— l'organe natif de dérive testpaths ⇄ CI, étendu (détail ci-dessous).scripts/tests/test_check_testpaths_coverage.py— six cas de contrôle positif.docs/reference/scripts-reference.md— une ligne dans le catalogue des scripts.pytest.ini+ lepip installdu job — l'option iniconsider_namespace_packageset le pinpytest>=8(section suivante).scripts/check_testpaths_coverage.pyet son fichier de tests — un troisième sens : l'invariant de résolution des noms de modules pytest.Le rouge CI de cette PR : cause racine et correctif
La jambe CPU est tombée en collection sur le push de la branche :
Cause racine — lue dans la source de pytest 9.1.1, pas supposée. pytest dérive le nom de module du paquet le plus proche portant
__init__.py, en remontant tant que le répertoire en porte un et que son nom est un identifiant Python.scripts/genai-stack/tests/(son parentgenai-stackn'en porte pas) etagent_tests/tests/(son parentagent_testsnon plus) se résolvent donc tous les deux entests.conftest. En modeimportlib,import_pathrend alors le module déjà mis en cache danssys.modules: pluggy voit un objet unique enregistré sous deux noms, et refuse.Portée mesurée (énumérateur indépendant reproduisant la règle de
resolve_package_path) : 1 collision sur 694 modules distincts, la seule étanttests.conftest— aucune collision de basename de fichier de test. Le défaut ne vient donc pas des suites câblées par cette PR : il existait déjà, et seule l'absence de CI ne le rendait pas visible.Correctif. L'option ini
consider_namespace_packages(pytest >= 8.0) étend la résolution aux paquets-espace de noms : les noms de modules redeviennent uniques par chemin. Elle est déclarée danspytest.ini— donc valable pour toute invocation, y compris unpytestnu à la racine — et le job figepytest>=8: sur une version antérieure, l'option serait ignorée sans bruit (simplePytestConfigWarning) et le défaut reviendrait sans laisser de trace.Mesures du correctif (local, sur cette branche) :
ValueError: Plugin already registered under a different nameagent_tests/testsagent_framework,agent_framework_openai,opentelemetry.sdk, toutes installées par le job CI)scripts/tests— seul porteur d'imports relatifsscripts.tests.*) : la résolution relative est préservéeLe dernier point est la contre-épreuve de la contre-mesure :
scripts/testsest le seul répertoire câblé à contenir des imports relatifs (from ._bash_resolution import resolve_bash), et sonpkg_root— la racine du dépôt — ne change pas avec l'option : les modules de cette suite s'appellent toujoursscripts.tests.*.Au passage, une affirmation fausse du commentaire du workflow est corrigée. Il citait
scripts/ci/check_ci_test_suites.pycomme organe — un fichier inexistant (le body de PR documente l'abandon de cet organe parallèle). Le commentaire cite maintenant l'organe réel,scripts/check_testpaths_coverage.py, et son gardetestpaths-coverage-guard.yml; le compte de tests y est aligné sur la mesure (666, et non 649, qui est le compte de l'issue).Le rouge CI de la seconde itération : la collecte de la jambe CPU
Le premier correctif (collision de noms de modules) a rendu le job CPU vert, mais le câblage des huit suites dans la même invocation a fait tomber la jambe à la collecte, sur le run #122006788867 (2026-10-27) :
Cause racine — lue dans la source, pas supposée.
scripts/qc-mcp-lite/server.pyl.31 importemcp.server.fastmcpau niveau du module ;tests/test_server.pyl.17 importeserver; la dépendance est donc requise à la collecte, pas seulement à l'exécution. Le SDKmcpn'est pas dans lepip installde la jambe CPU (pip install requests bcrypt jupyter_clientet al.). Les 21 590 tests verts par ailleurs le montrent : une seule suite de huit empêchait le job entier de rendre un verdict.Correctif —.job dédié, env isolé, SDK épinglé.
qc-mcp-litesort de l'invocation partagée et prend son propre job dans le même workflow :Trois raisons de ne pas l'avoir laissée dans le job partagé : (a) le SDK MCP tire une chaîne de dépendances (
pydantic,httpx,starlette,uvicorn…) que lepip installpartagé n'a pas, et l'y installer risquerait des conflits de pin avec les 16 000+ tests qui y tournent — env isolé = rayon d'action nul ; (b) le job CPU est sur les runners self-hostedcoursia-linux, plafonnés en mémoire et en durée, et une installation supplémentaire s'y paierait en temps de job ; (c) c'est le motif déjà accepté dans ce workflow par le jobadk-contracts(« installing that chain into the shared env would risk pin conflicts with the 1600+ existing tests. Isolated env = zero blast radius »).Le SDK est épinglé sur
1.30.0, la version mesurée (58 tests verts en 3,8 s sur ce poste le 2026-10-27) : le pin est une garantie de non-régression, pas une mise à jour.pytest>=8est épinglé pour la même raison que dans la jambe CPU — l'option iniconsider_namespace_packages.Le commentaire du workflow et le commentaire de l'organe sont corrigés au passage. Le workflow affirmait « aucune dépendance hors du
pip installde ce job » pour les huit suites : c'est vrai pour sept, faux pour la huitième.scripts/check_testpaths_coverage.pyportait la même affirmation. Les deux sont mis à jour, et la garde reste verte (WORKFLOW_COVERAGEcontinue de porter la cible : elle est couverte par le job dédié du même fichier, ce que le garde vérifie).L'organe : le natif étendu, pas un organe parallèle
Le geste de fond est d'étendre l'organe qui existe déjà.
scripts/check_testpaths_coverage.pyconfronte déjà lestestpathsdepytest.iniaux cibles pytest réellement invoquées par les workflows ; il avait un angle mort précis : son sens part des testpaths déclarés. Une suite présente sur le disque mais absente des deux listes lui était donc invisible — c'est exactement le cas des huit suites.J'avais d'abord écrit un organe séparé (
scripts/ci/check_ci_test_suites.py+ manifeste JSON). Il a été abandonné : deux gardes de dérive qui se recouvrent sur le même fait, c'est une dérive qui se dérive. Le second sens est maintenant dans l'organe natif, qui reste le point d'entrée unique (.github/workflows/testpaths-coverage-guard.yml, enregistré dansfast_lane_registry.py).Ce que le second sens ajoute :
discover_suites(): les répertoiresscripts/**/tests/contenant au moins untest_*.py, découverts sur l'arbre — jamais lus dans une liste déclarée.uncovered_suites(): une suite sur disque qu'aucun workflow n'exécute et qui ne porte pas de marqueur# CI-EXCLUDED:est un rouge.missing_declared_suites(): une suite déclarée câblée mais disparue de l'arbre est un rouge (le pendant exact, côté arbre, de « cible déclarée disparue du run réel »).WORKFLOW_COVERAGEreçoit les neuf suites câblées, avec leur motif de mesure — la garde vérifie toujours que chaque cible déclarée apparaît littéralement dans le run (un chemin cité en commentaire ne couvre rien).Le troisième sens : l'invariant de résolution des noms de modules. L'organe vérifie que
pytest.inidéclareconsider_namespace_packageset qu'un workflow figepytest >= 8. Sans lui, la suppression de l'option — ou un retour à pytest < 8 — ferait revenir le défaut de la section précédente sans laisser de trace : le symptôme serait un crash de collecte en CI, pas un rouge de garde. Le contrôle est exercé sur l'arbre réel (pytest.ini+ workflows), jamais sur une liste déclarée : c'est la suppression effective qu'il faut voir. Trois tests : un contrôle positif sur l'arbre réel, un contrôle négatif (option absente), un contrôle négatif (pin absent).Contrôle négatif, mesuré : l'organe étendu, transplanté sur
mainnon câblé (arbre extrait pargit archive origin/main), sort en 1 et nomme les huit suites ; sur cette branche, il sort en 0 avec « 17 suites sur le disque, toutes exécutées par la CI ». Le rouge n'est pas déclaré, il est produit.Critères d'acceptation de #22735
qc-mcp-lite(SDK MCP absent) — défaut corrigé par le job dédié ; le log de la CI sur cette PR est la preuve sur le runner.pytest.iniet la commandepytestdu workflow convergentscripts/sudoku/testsentre dans l'invocationpytest: la collecte est au niveau du répertoire, donctest_core_corpus.py— le test d'invariant ajouté par la PR #22719 — sera collecté et rejoué automatiquement dès son merge. Tant que #22719 n'est pas mergée, le log montre les 60 tests sudoku déjà présents surmain; c'est la seule réserve de ce critère, et elle tient au merge de #22719, pas à ce câblage.scripts/tests/test_check_testpaths_coverage.py: 19 tests (10 existants + 9 nouveaux), dont un contrôle négatif (arbre déclaré → vert), un contrôle de rouge sur suite non couverte, sur suite non déclarée, sur suite disparue, sur exclusion CI-EXCLUDED, sur option ini absente, sur pinpytest>=8absent, et le code de sortie de la CLI. Le fichier est dansscripts/tests, donc collecté par la jambe CI elle-même.Vérification post-fix
python scripts/check_testpaths_coverage.py --verbose→ OK, 24 testpaths couverts, 17 suites sur le disque toutes exécutées par la CI (rc=0) — et le troisième sens est vert (option déclarée, pinpytest>=8présent). Rejoué après le déplacement deqc-mcp-litevers son job dédié : toujours rc=0, la cible reste couverte par le même fichier de workflow.python -m pytest scripts/qc-mcp-lite/tests -q(SDKmcp1.30.0 installé, la version épinglée dans le job) → 58 passed en 3,8 s.mainnon câblé → rc=1, les huit suites nommées (contrôle négatif).python -m pytest scripts/tests/test_check_testpaths_coverage.py→ 19 passed.python -m pytest scripts/tests/test_check_testpaths_coverage.py scripts/tests/test_fast_lane.py→ 106 passed.genai-stack/tests+agent_tests/tests→ 917 tests collectés, 0 erreur de plugin (contreValueError: Plugin already registered…sans l'option) ; les 13 erreurs de collecte résiduelles sont identiques avant/après (dépendances locales absentes, installées par le job CI).scripts/**/tests/(comme la jambe CI les invoque) → 20 235 tests collectés, 0 erreur.pytest --collect-onlyà la racine : mêmes 3 erreurs qu'avec lestestpathsdemain(comparaison faite sur deux exécutions) — collision de paquethelperspréexistante (GenAI/Vibe-Coding/Claude-Code/notebooks/helpersmasqueGenAI/shared/helpers), aucune erreur nouvelle introduite par l'élargissement.Portée
See #22735— pasCloses, et la raison est mécanique : #22735 est l'issue de suivi qui porte la voie 3 (issue de suivi ouverte et nommée) de la réserve Hermes sur la PR #22719. La condition 2 decollect_followup_liftsexige que cette issue soit OUVERTE ; la fermer avant le merge de #22719 ferait retomber la levée de #22719 et re-rougirait cette PR. L'issue sera close quand #22719 sera mergée.🤖 Generated with Claude Code