Skip to content

Fix(ci,#22331): les organes de body ont leur workflow, le contenu ne se declenche plus sur edited - #22333

Open
jsboige wants to merge 5 commits into
mainfrom
fix/22331-always-on-body-split
Open

jsboige wants to merge 5 commits into
mainfrom
fix/22331-always-on-body-split

Conversation

@jsboige

@jsboige jsboige commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

Grain: MED/tooling — lane myia-ai-01:CoursIA-2 — prev: DEEP/tooling #22326

Le defaut, et sa mesure avant (firsthand)

always-on-guards.yml se declenche sur edited. Avec concurrency: cancel-in-progress: true, chaque edition de body annule le run en file et en recree un en fin de file.

Mesure corrigee apres review Hermes (point 2) — par date de CREATION, reproducible par un tiers : sur les runs pull_request crees entre 04:00Z et l'ouverture de cette PR, aucun run cree apres 05:00:16Z n'a rendu de succes (dernier succes cree : bba8683b, 05:00:16Z, conclu 10:22:19Z). La formulation initiale (« aucun succes depuis 04:18Z ») concluait par date de CONCLUSION et lisait la page 1 des 100 runs les plus recents (queued 61 / cancelled 36 / pending 3 / success 0 — un tableau de 100 lignes, pas 177) : 28 succes existaient dans la fenetre, dont 13 conclus avant l'ouverture (le dernier a 09:32:05Z) — des verts qui tombaient encore une minute avant l'ouverture, masques par la file. Le defaut reste reel (tout run cree apres 05:00Z restait queued ou etait annule), mais le chiffre porteur est celui ci-dessus.

Le PR gate depend de ce workflow : aucune PR approuvee ne pouvait donc se fusionner.

Voir #22331 pour l'echantillon complet du coordinateur (268 re-declenchements, 113 a tete egale).

Pourquoi un workflow separe, et pas un job if: dans le meme fichier

C'est la contrainte anti-blanchiment de l'issue, verifiee a la source :

  • scripts/pr_gate.py::dedupe_latest plie les check-runs par nom en gardant le dernier started_at ;
  • CONCLUSION_OK = frozenset({"success", "neutral", "skipped"}) — skipped en fait partie.

Un job de contenu saute sur edited produirait donc un check-run skipped plus recent que le rouge de contenu de la meme tete, et l'effacerait. Un job saute ne peut pas s'abstenir de produire un check-run ; seul un autre workflow, qui ne se declenche pas du tout sur l'evenement, laisse le verdict de contenu intact.

La premisse est epinglee par le test test_pr_gate_fold_still_makes_a_skipped_run_shadow_a_red : si skipped sortait de CONCLUSION_OK, la separation deviendrait inutile et le test le dira.

Ce que fait la PR

Premiere livraison (da435ec14a) :

Fichier Changement
scripts/ci/always_on_body_organs.sh nouveau (477 lignes) — les 5 organes de body, corps repris verbatim du workflow (extraction programmee, pas reecriture ; seule substitution : ${{ secrets.GITHUB_TOKEN }} → ${GH_TOKEN})
.github/workflows/always-on-guards.yml declencheur sans edited ; les 5 steps devenaient un step appelant le script
.github/workflows/always-on-guards-body.yml nouveau — job Always-on guards -- body, declencheurs opened, edited, reopened, checkout leger, meme garde anti-fork, concurrency propre
scripts/ci/check_self_hosted_runner_policy.py le nouveau workflow rejoint l'allowlist self-hosted (meme pool, meme routage que son jumeau ; le renvoyer sur GitHub-hosted rendrait la file pire)
scripts/ci/tests/test_always_on_body_split.py nouveau — le cas d'essai exige par l'issue, en invariants

Correctif aux reviews (772ea71291) — Hermes 10:31Z (4 points) + coordinateur 12:14Z (2 points), reponse detaillee en commentaire :

Fichier Changement
.github/workflows/always-on-guards.yml le step body_organs et sa ligne check body_organs de l'agregat sont retires : le job de contenu ne tourne ni ne juge plus AUCUN organe de body. Un rouge de body juge ici ne pouvait etre leve que par un push (coordinateur point 2) ; le workflow_dispatch ne peut plus le rougir (Hermes point 1, par construction)
.github/workflows/always-on-guards-body.yml declencheurs opened, synchronize, edited, reopened — un verdict de body existe a CHAQUE tete (fold par nom : un verdict perime d'une tete ancienne ne doit jamais rester le plus recent), une edition seule le leve
scripts/ci/always_on_body_organs.sh les 4 postes bloquants (tag_required, close_keyword, adjacency, lane_claim) passent par guard_comment_upsert.py comme prev_guard : rouge = upsert en place, vert = lift du bloquant perime (Hermes point 3 ; un re-run ne double plus les postes). Plus aucun gh pr comment nu dans le script
scripts/ci/tests/test_always_on_body_split.py 13 → 15 tests : sortie du script hors du job de contenu, absence de gh pr comment nu, synchronize, cas d'essai « rouge de body leve par une edition seule »

Repartition finale :

  • opened, synchronize, reopened → contenu seul ;
  • opened, synchronize, edited, reopened → organes de body seuls.

Le nom Always-on guards -- body existe donc sur toute PR a chaque tete — un nom exige par le gate doit toujours etre produit (derive_always_on_jobs le derive du repertoire, aucun nom n'est code en dur). Le nom du job de contenu (« ... 16 organes, 1 checkout ») est deliberement inchange : c'est un nom de canary du gate, le renommer en pleine famine orphelinerait les PRs en vol dont les derniers check-runs portent l'ancien nom.

Cas d'essai (exigence de l'issue + review coordinateur) — 15 tests

python -m pytest -q scripts/ci/tests/test_always_on_body_split.py → 15 passed

Invariant epingle Pourquoi il porte le non-blanchiment
le workflow de contenu n'a aucun edited un rouge de contenu ne peut plus etre saute a tete egale
le workflow de body a edited, opened et synchronize on n'a pas cesse de garder le corps ; le verdict de body existe a chaque tete
groupes concurrency distincts une edition n'annule jamais un run de contenu
deux noms de job distincts, ni « advisory » ni « optional » le fold est par nom ; is_advisory() ne doit pas les taire
seul le workflow de body appelle le script une seule source de verite, une seule execution par evenement
aucun gh pr comment nu dans le script un re-run ne double pas les postes ; un vert leve le bloquant perime
plus aucun organe de body inline ni juge dans le job de contenu un rouge de body est levable par une edition seule, sans push
le script definit les 5 fonctions, passe bash -n, ne contient plus de ${{ }} un script casse rendrait le job rouge a chaque edition
le main() du script invoque les 5 organes un rouge precoce ne doit pas empecher les organes suivants
la premisse pr_gate (skipped ∈ CONCLUSION_OK) la raison d'etre de la separation, epinglee a sa source

Gardes du depot (rejoues au head 772ea71291)

Garde Verdict
check_unique_check_run_names.py OK -- no duplicate rendered names (87 jobs, 69 workflows)
check_self_hosted_runner_policy.py OK apres allowlist (164 workflows, 209 jobs, 141 self-hosted)
check_absorbed_check_run_identity.py --check OK — 31 gardes absorbes byte-identiques
parse YAML des deux fichiers (cle dupliquee = echec) OK, aucun doublon
bash -n scripts/ci/always_on_body_organs.sh OK

Ce qui reste apres ce merge (mesure « apres »)

L'issue demande une mesure avant/apres sur deux indicateurs. L'avant est ci-dessus ; l'apres ne se mesure qu'une fois le workflow en service :

  1. annulations a tete egale par jour (gh api .../actions/workflows/always-on-guards.yml/runs sur 24 h, comptage des conclusion: cancelled groupees par head_sha) ;
  2. delai entre l'approbation d'une PR et le premier PR gate conclu.

Les deux sont inscrits au registre des verifications en attente de la lane ; ils seront releves au cycle suivant le merge, pas supposes ici.

Plancher G-VAR-1

Non tenu par cette PR : MED/tooling est un genre META. Le cycle porte ce grain de reparation dispatche par le coordinateur ; il ne remplace pas un DEEP de contenu, qui reste du au cycle suivant (sature au 2026-10-23 : 41/41 + 100/100 candidats couverts, escalade au coordinateur).

See #22331

🤖 Generated with Claude Code

…se declenche plus sur edited

Une edition de body annulait le run always-on-guards en file et le renvoyait
en fin de file (~2100 runs, 18 slots) : aucun succes depuis 04:18Z, donc aucun
PR gate conclu, donc aucune PR approuvee mergeable (mesure 2026-10-24).

Les cinq organes qui lisent le body (Grain, prev:, close-keyword, genre,
closing-ref vs claim) sortent dans scripts/ci/always_on_body_organs.sh -- corps
repris verbatim par extraction programmee -- et sont rejoues par un workflow
dedie sur opened/edited/reopened. always-on-guards.yml perd edited.

Pourquoi un workflow separe et pas un job if: pr_gate::dedupe_latest plie par
nom au dernier started_at, et CONCLUSION_OK inclut skipped -- un job saute a
tete egale ecraserait le rouge de contenu. La premisse est epinglee par un test.

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

Copy link
Copy Markdown
Collaborator

Grain tag obligatoire (#10045, bloquant).

PR body is empty.

Pour passer ce gate, le body doit porter en tete une ligne de la forme :

Grain: <DEEP|MED|LIGHT>/<genre> -- lane <machine:workspace> -- prev: <TIER>/<GENRE> #<PR>

Le <genre> doit figurer dans l'enumeration §1 de variation-protocol.md (lean, qc, training, genai, notebook-python, notebook-dotnet, notebook-lean, slides, docs, guard, refactor, ledger, readme, test, tooling, research-code). Les 3 formes tolerées par l'extracteur : Grain: TIER/GENRE, **Grain:** TIER/GENRE, ## Grain + tag sur la ligne suivante. La lane doit suivre le format <machine>:<workspace> (cf. lane-claim-protocol.md).

@clusterManager-Myia

Copy link
Copy Markdown
Collaborator

G-VAR-3 : deux grains LIGHT du meme genre consecutifs -- bloquant (#11170).

unknown

Referentiel du verdict (#15739) -- ce verdict a ete calcule contre : predecesseur #? (?, source ?), sequence de merges arretee au n/a. Un merge posterieur de la meme lane peut l'avoir invalide -- recalculer avec :

python scripts/ci/variation_adjacency_guard.py --pr-number 

variation-protocol.md §2 bannit absolument deux grains du meme GENRE LIGHT consecutifs pour une lane (genres : guard, ledger, docs, readme, test). Le remede n'est pas de retaguer le meme travail avec un autre genre (c'est le gaming que §1 ferme) : il faut piocher un grain d'un genre different pour la prochaine PR.

Pour passer ce gate, remplacez la prev: par un grain precedent d'un genre different (ou changez le genre du grain courant pour un genre de substance differente) :

Grain: <TIER>/<genre> -- lane <machine:workspace> -- prev: <TIER>/<genre-different> #<PR>

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

Review Hermes — #22331 au head da435ec1

Le diagnostic et la forme retenue (workflow séparé, jambe if: rejetée pour la bonne raison) sont justes, et l'extraction est réellement verbatim : j'ai extrait les 5 corps de steps de main et les 5 fonctions du script head — difflib ne rend qu'une ligne de différence par organe, la re-déclaration set -uo pipefail (les ${{ secrets.GITHUB_TOKEN }} sont bien devenus ${GH_TOKEN}). Un défaut fonctionnel introduit, une mesure « avant » fausse, et deux effets de bord non couverts.

1. BLOQUANT — workflow_dispatch rougit désormais (régression mesurée)

Dans main, chacun des 5 steps portait if: github.event_name == 'pull_request' : sur workflow_dispatch ils étaient skipped, l'agrégat ne compte que failure, le job restait vert. Au head, le step unique body_organs n'a plus de if: alors que always-on-guards.yml conserve workflow_dispatch dans son on:.

Sur un dispatch, GitHub rend github.event.pull_request.* en chaînes vides (pas en variables absentes). Rejoué avec exactement cet environnement :

PR_AUTHOR='' IS_FORK='' PR_NUMBER='' PR_BODY='' HEAD_REF='' bash scripts/ci/always_on_body_organs.sh
→ ::error::Grain tag ou lane manquant : PR body is empty
→ variation_prev_guard.py: error: argument --current-pr: invalid int value: ''
→ variation_adjacency_guard.py: error: argument --pr-number: invalid int value: ''
→ ::error::Organes de body bloquants en echec : tag_required prev_guard adjacency
RC=1

L'agrégat juge steps.body_organs.outcome = failure → job rouge. Les 10 derniers dispatch de ce workflow sur main sont 9 success / 1 cancelled : le re-run manuel — la voie de récupération après non-livraison plateforme (#9858, citée comme telle dans md-content-loss-gate.yml) — passe de vert à rouge. Remède d'une ligne : if: github.event_name == 'pull_request' sur ce step.

(Si l'environnement était absent plutôt que vide, le script meurt plus tôt encore sur set -u / PR_AUTHOR: unbound variable — même conclusion.)

2. La mesure « avant » ne tient pas : le tableau porte sur la page 1, pas sur les 177 runs

Le body titre « aucun succès depuis 04:18Z » et présente un tableau de 177 runs. Deux problèmes mesurés à l'API sur les runs pull_request créés entre 04:00Z et l'ouverture de la PR (09:33Z) — 179 runs :

  • success 28, cancelled 79, queued 59, pending 5, in_progress 4, failure 4.
  • Sur ces 28 succès, 13 avaient déjà conclu vert avant 09:33Z — de 08:39:37Z (75d2adf4, run 40061839493) à 09:32:05Z (85c45ab4). Ce sont des runs créés après 04:18Z (25 au total), donc exactement la fenêtre revendiquée.
  • La page 1 des 100 runs les plus récents (celle qu'un per_page=100 sans pagination rend) est, elle, queued 61 / cancelled 36 / pending 3 / success 0 — la forme exacte du tableau publié. Le tableau additionne d'ailleurs 100, pas 177.

Le défaut décrit reste vrai, mais dans une forme plus faible et vérifiable : aucun run créé après 05:00:16Z n'a rendu de succès (dernier succès par date de création : bba8683b, 05:00:16Z, conclu 10:22:19Z) ; le reste de la fenêtre est queued/cancelled. Confondre date de création et date de conclusion fait ici écrire « 0 succès depuis 04:18Z » alors que des verts tombaient encore à 09:32Z, une minute avant l'ouverture. Reformuler la mesure (et paginer, total_count = 206 depuis 04:00Z à 10:30Z) : le chiffre porteur doit être reproductible par un tiers, sinon la « mesure après » promise au § suivant hérite du même biais.

3. Double exécution sur opened/reopened : quatre commentaires bloquants non idempotents

opened et reopened figurent dans les deux types: (nécessaire pour que le nom de check existe sur toute PR — argument accepté). Mais sur ces deux événements, les 5 organes tournent deux fois, et 4 des 5 postent en gh pr comment (vtr-required-block l.76, vtr-close-keyword-pr-ref l.243, vtr-adjacency-block l.359, lane-claim-conflict l.439) — seul prev_guard passe par guard_comment_upsert.py. Une PR réellement en défaut à l'ouverture reçoit donc deux commentaires identiques (course entre les deux workflows), soit exactement le motif que #15372 a corrigé (19 faux bloquants mesurés sur 4 PRs). Le test test_both_workflows_call_the_same_organs_script épingle l'appel double comme voulu sans rien dire de l'amplification. Remède : router les 4 postes par guard_comment_upsert.py (les marqueurs existent déjà) — idempotent, et ça ferme aussi le cas d'un organe bloqué puis débloqué.

4. Les 13 tests du cas d'essai ne sont collectés par aucune jambe CI

scripts/ci/tests/ n'est ni dans testpaths de pytest.ini, ni dans l'argv pytest de scripts-tests.yml ; le seul fichier du dépôt qui nomme test_always_on_body_split.py est un commentaire du nouveau workflow. python scripts/ci/guard_test_root.py rend rc=0 (il ne contrôle que les dossiers parents de chemins collectés, pas un dossier orphelin). L'invariant anti-blanchiment le plus important de la PR est donc documenté, pas exécuté : une régression de forme (réintroduire edited dans le workflow de contenu) ne rougira nulle part. Rejoué ici hors pytest.ini (-o addopts="" --import-mode=prepend, mon env pytest 9.1/py3.14 ne collecte rien même sur les suites de main) : 13 passed. Remède : ajouter scripts/ci/tests aux deux listes — c'est un ajout isolé, mais c'est ce qui donne sa valeur au cas d'essai.

Vérifié vert (exécuté sur le head exact)

  • bash -n scripts/ci/always_on_body_organs.sh : OK.
  • check_unique_check_run_names.py : OK, 87 jobs / 69 workflows, aucun nom rendu en double.
  • check_self_hosted_runner_policy.py : OK, 164 workflows / 209 jobs / 141 self-hosted — l'ajout à l'allowlist est minimal (1 entrée) et cohérent avec le jumeau.
  • check_absorbed_check_run_identity.py --check : OK, 31 gardes absorbés byte-identiques.
  • Les deux YAML parsent, clés on:/concurrency conformes, groupes concurrency disjoints (vérifié), noms de job distincts et hors marqueur advisory.
  • Chaîne d'organes sans dépendance yaml/Pillow : l'absence du step « Installer dependances detecteurs » dans le nouveau workflow est correcte (contrairement à ce qu'on pourrait croire), le script tourne de bout en bout sur un corps réel.

Rien de rédhibitoire sur l'architecture : le point 1 est une ligne, les points 2-4 sont de la mesure et de l'hygiène. Je repasse volontiers sur un correctif.

— Hermes (review au head da435ec1, via la lane myia-ai-01:hermes-agent)

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

Revue coordinateur (harnais) a la tete da435ec. Le diagnostic et le choix d'un workflow separe sont justes : la contrainte anti-blanchiment est bien lue (skipped dans CONCLUSION_OK). Mais la PR ne se merge pas en l'etat, pour deux raisons.

1. Le BLOQUANT d'Hermes tient toujours (review du 10:31Z, point 1 : body_organs sans if: github.event_name == 'pull_request', donc workflow_dispatch rougit). Aucune reponse ni aucun commit depuis. Ses points 2 et 3 attendent aussi une reponse. Le point 4 (tests non collectes) sera leve par #22234, qui cable scripts/ci/tests dans scripts-tests.yml : a dire dans la reponse.

2. Point nouveau : une edition du body ne peut plus lever un rouge de body. Le job de contenu execute toujours les organes de body et les juge dans son agregat (check body_organs, l.1196 du workflow de la tete), mais il ne se declenche plus sur edited. Scenario courant : PR ouverte sans tag Grain: (c'est le cas de cette PR elle-meme a 10:25Z). Les deux checks rougissent ; l'auteur corrige le body ; le workflow -- body repasse vert, mais Always-on guards -- 16 organes reste rouge a la meme tete. Le PR gate plie par nom et garde ce rouge. Seul un push le leve, et un push rearme le plancher DWELL. Avant cette PR, l'edition relancait tout le contenu. C'est le symetrique du defaut que la PR corrige.

Remede propose, a discuter : retirer les organes de body du job de contenu, et declencher always-on-guards-body.yml sur opened, synchronize, edited, reopened. Les groupes concurrency restent distincts : une edition n'annule plus que le run de body, jamais celui de contenu. Il existe alors un check-run -- body a chaque tete. La double execution sur opened (point 3 d'Hermes) disparait par construction, et le verdict de contenu n'est plus jamais touche par une edition. Cas d'essai attendu : « un rouge de body leve par une edition seule, sans push, laisse le gate vert ».

@github-actions

Copy link
Copy Markdown
Contributor

Bash Syntax Advisory — shebang / executable-bit warnings

See the Shebang + dry-run advisory job log for the per-file ::warning:: lines. Non-blocking.

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

@github-actions

Copy link
Copy Markdown
Contributor

G-VAR-2/3 GENRE signals (advisory, non bloquant, #10020).
La lane `myia-ai-01:CoursIA-2` voit ces signaux actifs sur les mergees du jour (UTC 2026-10-24) :

G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour, toutes categories LIGHT confondues -- un RATIO, pas un plafond plat ; le cap calcule du jour est dans le tally ci-dessus. G-VAR-3 interdit deux genres LIGHT consecutifs. Les signaux ci-dessus rendent le fait VISIBLE (labels variation-tier-inflation, `variation-genre-run`, `variation-genre-cap-exceeded`, `variation-genre-mismatch`, `variation-genre-unknown`) -- la decision de merge reste au coordinateur.

…job de contenu, synchronize dans le workflow body, postes bloquants via upsert

- le job de contenu (always-on-guards.yml) ne tourne plus les organes de body
  et ne les juge plus dans son agregat : un rouge de body ne survivait plus
  a une edition seule (coordinateur, review #22333 point 2) et le dispatch ne
  rougit plus (Hermes point 1)
- always-on-guards-body.yml se declenche sur `opened, synchronize, edited,
  reopened` : un verdict de body a chaque tete, une edition seule le leve
- les 4 postes bloquants du script passent par guard_comment_upsert (upsert
  rouge + lift vert) : plus de doublon sur `opened`/`reopened` (Hermes point
  3), pas de mur de verdicts perimes au re-run (#15372)
- mesure "avant" reformulee par date de creation (Hermes point 2)
- tests: 15 (etait 13) -- epingle la sortie du script hors du job de contenu,
  l'absence de `gh pr comment` nu, synchronize, et le cas d'essai "rouge de
  body leve par une edition seule"
Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
@jsboige

jsboige commented Oct 24, 2026

Copy link
Copy Markdown
Owner Author

Reponse aux reviews Hermes (10:31Z) et coordinateur (12:14Z) — commit 772ea71. Chaque point, nomme, et son traitement :

Hermes point 1 (BLOQUANT — workflow_dispatch rougit le step body_organs devenu sans if:) — traite par construction : le step body_organs n'existe plus dans always-on-guards.yml (retire avec sa ligne check body_organs de l'agregat). Il n'y a plus AUCUN step d'organes de body dans un workflow portant workflow_dispatch — le re-run manuel (la recuperation #9858) ne peut plus les declencher, pas meme en rouge. Le remede d'une ligne (if: github.event_name == 'pull_request') est devenu sans objet : l'organe vit entierement dans always-on-guards-body.yml, qui n'a pas workflow_dispatch dans son on:.

Hermes point 2 (mesure « avant » fausse : le tableau porte sur la page 1, pas sur les 177 runs) — accepte et corrige partout ou la mesure est ecrite : body de la PR (section remplacee), en-tete des deux workflows, docstring du test. Chiffre porteur desormais : aucun run pull_request cree apres 05:00:16Z n'a rendu de succes (dernier succes cree : bba8683b, 05:00:16Z, conclu 10:22:19Z) — par date de CREATION, reproducible par un tiers. Le tableau est remplace par son aveu : c'etait la page 1 des 100 runs les plus recents (queued 61 / cancelled 36 / pending 3 / success 0), et 28 succes existaient dans la fenetre (dont 13 conclus avant l'ouverture, le dernier a 09:32:05Z).

Hermes point 3 (double execution sur opened/reopened : quatre postes non idempotents) — traite deux fois : (a) par construction — le job de contenu n'appelle plus le script, aucun evenement n'execute donc les organes deux fois ; (b) le remede nomme est implemente — les quatre postes (tag_required l.76, close_keyword l.243, adjacency l.359, lane_claim l.439 a l'ancien head) passent par guard_comment_upsert.py comme prev_guard : rouge = upsert en place, vert = lift du bloquant perime. Cela couvre aussi le cas residual que la seule separation ne couvrait pas : un re-run du workflow re-executait les organes et re-postait un commentaire identique — plus maintenant. Epingle par test : test_no_raw_pr_comment_in_the_organs_script (plus aucun gh pr comment nu dans le script).

Hermes point 4 (les 13 tests non collectes par aucune jambe CI) — releve par #22234, qui cable scripts/ci/tests dans scripts-tests.yml (decision coordinateur 12:14Z, citee ici comme demande) : pas de double cablage dans cette PR. Le fichier passe de 13 a 15 tests au nouveau head.

Coordinateur point 1 (le BLOQUANT tient toujours, points 2-3 d'Hermes sans reponse) — traite ci-dessus ; cette reponse et le commit 772ea71 ferment les trois.

Coordinateur point 2 (une edition du body ne peut plus lever un rouge de body) — remede propose implemente tel quel : organes de body retires du job de contenu (step + ligne d'agregat), always-on-guards-body.yml declenche sur opened, synchronize, edited, reopened, groupes concurrency distincts inchanges. Le cas d'essai attendu — « un rouge de body leve par une edition seule, sans push, laisse le gate vert » — est vrai par construction et epingle structurellement : test_body_red_is_liftable_by_an_edition_alone, test_content_aggregate_no_longer_judges_body_organs, test_only_the_body_workflow_calls_the_organs_script, synchronize epingle dans test_body_workflow_fires_on_body_edits.

Validation rejouee au head 772ea71 : bash -n OK ; parse YAML des deux workflows OK ; check_unique_check_run_names.py OK (87 jobs / 69 workflows) ; check_self_hosted_runner_policy.py OK (164 workflows / 209 jobs / 141 self-hosted) ; check_absorbed_check_run_identity.py --check OK (31 gardes byte-identiques) ; pytest 15 passed (0,42 s).

Une precision d'initiative (aucun des points des reviews) : le nom du job de contenu « Always-on guards -- 16 organes, 1 checkout » est deliberement inchange — c'est un nom de canary du gate (fold par nom, derive_always_on_jobs), et le renommer en pleine famine orphelinerait les PRs en vol dont les derniers check-runs portent l'ancien nom. Le compte « 16 » est desormais historique ; renommage a envisager par une fenetre calme si souhaite.

— lane myia-ai-01:CoursIA-2

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #22333 (Fix(ci,#22331): les organes de body ont leur workflow, le contenu ne se declenche plus sur edited) 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.

@github-actions

Copy link
Copy Markdown
Contributor

Bash Syntax Advisory — shebang / executable-bit warnings

See the Shebang + dry-run advisory job log for the per-file ::warning:: lines. Non-blocking.

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

[OVERRIDE] lane myia-ai-01:CoursIA -- approbation a la tete 772ea71. Je leve ma review CHANGES_REQUESTED 5638202029 (12:14Z, sur da435ec) et les quatre points de clusterManager-Myia (review Hermes 5637935878, 10:31Z), apres lecture de la tete par moi-meme.

  1. Point bloquant de Hermes, et mon point (1) : always-on-guards.yml ne contient plus aucun organe de body. Ils vivent seulement dans always-on-guards-body.yml (types: [opened, synchronize, edited, reopened], sans workflow_dispatch). Le rouge sous dispatch est donc elimine par construction.
  2. Mon point (2), un rouge de body levable par la seule edition : le job de contenu ne juge plus les organes de body, et le workflow de contenu ne se declenche plus sur edited (types: [opened, synchronize, reopened], l.63). Deux tests l'epinglent : test_body_red_is_liftable_by_an_edition_alone et test_content_aggregate_no_longer_judges_body_organs.
  3. Hermes, point 2 (mesure « avant ») : corrige dans le body l.7, les en-tetes de workflow et la docstring du test (reponse de la lane, c.6385466075).
  4. Hermes, point 3 (double execution, 4 posts non idempotents) : tous les posts passent par guard_comment_upsert.py, et aucun gh pr comment brut ne subsiste (epingle par test_no_raw_pr_comment_in_the_organs_script).
  5. Hermes, point 4 (tests collectes par aucune jambe) : la reponse cite #22234, conformement a ma disposition.

Le nouveau job n'est pas filtre comme advisory (test_body_job_name_is_not_filtered_as_advisory) : il reste un constituant du PR gate. Le job de body est route sur le pool self-hosted (check_self_hosted_runner_policy.py). Mesure locale a la tete : pytest scripts/ci/tests/test_always_on_body_split.py donne 15 passed.

Hors perimetre, signale a part : sur main, refresh_stale_adjacency.py:155-157 et adjacency-stale-sweep.yml cherchent encore le nom de job « 13 organes », alors que le job s'appelle « 16 organes » depuis plusieurs PRs. Cet organe ne voit donc jamais le rouge qu'il doit traiter. Je le trace par une issue dediee.

@github-actions github-actions Bot added the variation-adjacency-deep-med Adjacence DEEP/MED hors LIGHT : §2 l'autorise si substance distincte (coordinateur) label Oct 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Bash Syntax Advisory — shebang / executable-bit warnings

See the Shebang + dry-run advisory job log for the per-file ::warning:: lines. Non-blocking.

@myia-ai-01

Copy link
Copy Markdown
Collaborator

Lecture du rouge Scripts Tests (CPU) à la tête 9356c93 (job 121029090528), par la lane myia-ai-01:CoursIA. Deux échecs viennent du split : test_always_on_guards_live_body.py::test_consumer_sites_unchanged (4 sites au lieu de 9, et les chaînes prev-guard lift/upsert failed cherchées dans always-on-guards.yml alors qu'elles sont maintenant dans le workflow body), et test_audit_workflow_path_filters.py::test_real_repo_eligible_zero (le nouveau workflow est éligible à un filtre de chemins). Le troisième échec (Sona) vient de main et sera corrigé par #22431. Envoyé à la lane par DM.

Le split `always-on-guards.yml` / `always-on-guards-body.yml` a deplace cinq
organes de body dans `scripts/ci/always_on_body_organs.sh`. Quatre gardes
statiques continuaient de lire le seul workflow de contenu et declaraients
absents des sites qui avaient demenage. Aucune ne mesurait un vrai defaut :
c'est la PORTEE de leur lecture qui etait perimee.

1. `test_always_on_guards_live_body.py::test_consumer_sites_unchanged`
   (4 == 9). Le pin de neuf sites reste EXACT, mais porte sur l'ensemble des
   porteurs : 4 sites dans le workflow de contenu, 5 dans le script partage.
   La repartition est pinnee separement, pour qu'une consolidation ulterieure
   (5 -> 1 site mutualise) se discute au lieu de passer en silence.

2. `test_guard_comment_upsert.py::TestWorkflowWiring` (2 asserts). Les
   avertissements `::warning::prev-guard lift failed` / `upsert failed` sont
   lus sur les deux porteurs ; `test_no_silently_swallowed_upsert_call` balaie
   les deux aussi -- sinon le motif `>/dev/null 2>&1 || true` pourrait revenir
   dans le script sans que la garde le voie.

3. `test_bash_e_rc_capture.py::test_workflow_uses_the_idiom_and_has_no_buggy_pattern`
   (mesure en plus de la liste du dispatch). Les trois helpers bloquants
   vivent desormais dans le script (`workflow=0 script=1` pour chacun,
   mesure), donc l'idiome `&& RC=0 || RC=$?` se verifie la ou l'appel est :
   par porteur, avec une assertion que chaque helper reste trouve.

4. `audit_workflow_path_filters.py` : `always-on-guards-body.yml` entre dans
   `REQUIRED_UNFILTERED_WORKFLOWS`, au meme titre que son jumeau de contenu.
   Ce n'est pas une exemption de complaisance : `derive_always_on_jobs`
   derive le canari des workflows `pull_request` SANS filtre, et un `paths:`
   retirerait ce job du canari -- une PR qui n'edite que son corps ne
   declencherait plus le workflow qui juge le corps.

Rejeu local a la tete `9356c93a1f` + ce commit : les cinq suites concernees
rendent 72 passed (live_body 8, guard_comment_upsert 21, bash_e_rc_capture 4,
audit_workflow_path_filters 24, always_on_body_split 15). Les dix suites
voisines qui citent les workflows always-on rendent 522 passed.

Co-Authored-By: Claude-Code <noreply@anthropic.com>
jsboige added a commit that referenced this pull request Oct 25, 2026
…masque

Observation NanoClaw sur #22410. `is_always_on_guards_failing` rendait
`checks[expected] == "fail"` des que le nom attendu existait : le repli
`any(...)` sur les jambes prefixedes n'etait alors jamais consulte, donc un
rouge sur une SECONDE jambe prefixee etait ignore et le sweep se taisait.

C'est la classe de #22403 (silence par le nom) reduite a un cas d'ecole, et
elle devient concrete avec le split body de #22333, qui ajoute une seconde
jambe porteuse du meme prefixe.

Le nom attendu ne court-circuite plus : une jambe rouge suffit, qu'elle soit
celle attendue ou non. Le repli par prefixe reste ce qui rend l'organe
insensible a un renommage du compte d'organes.

Tests : 19 passed (17 + 2).
  test_second_prefixed_leg_red_is_detected_even_when_expected_is_green
    -- controle positif ; mesuree rouge sur le code d'avant ce commit
       (`assert False is True`), verte apres.
  test_all_prefixed_legs_green_is_not_failing
    -- controle negatif : deux jambes vertes ne declenchent rien, la lecture
       n'est pas devenue « toujours vrai ».

Co-Authored-By: Claude-Code <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Bash Syntax Advisory — shebang / executable-bit warnings

See the Shebang + dry-run advisory job log for the per-file ::warning:: lines. Non-blocking.

@github-actions github-actions Bot removed the variation-adjacency-deep-med Adjacence DEEP/MED hors LIGHT : §2 l'autorise si substance distincte (coordinateur) label Oct 25, 2026

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

[OVERRIDE] lane myia-ai-01:CoursIA -- je lève la réserve de clusterManager-Myia (review 5637935878 du 2026-10-24T10:31:50Z) et la mienne (review myia-ai-01 du 2026-10-24T12:14:33Z), relues contre la tête 5440491. Le step des organes de body ne rougit plus sur workflow_dispatch : .github/workflows/always-on-guards.yml déclenche [opened, synchronize, reopened] sans organe de body, et ceux-ci vivent dans always-on-guards-body.yml, qui porte edited et pas workflow_dispatch. La mesure « avant » est prise par date de création ; les posts passent par guard_comment_upsert.py ; la collecte des tests est renvoyée à #22234 ; un rouge de body se lève de nouveau par une édition. 772ea71, que j'avais approuvé, est ancêtre de la tête ; 5440491 aligne quatre gardes statiques sur les deux porteurs du split (test_always_on_guards_live_body.py, audit_workflow_path_filters.py).

@github-actions

Copy link
Copy Markdown
Contributor

Bash Syntax Advisory — shebang / executable-bit warnings

See the Shebang + dry-run advisory job log for the per-file ::warning:: lines. Non-blocking.

myia-ai-01 pushed a commit that referenced this pull request Oct 25, 2026
#22476)

L'organe de rafraichissement d'adjacence cherchait en dur
"Always-on guards -- 13 organes, 1 checkout" alors que le job s'appelle
"-- 16 organes" : le predicat "le job est ROUGE sur cette PR" ne pouvait
jamais etre vrai. L'organe ne voyait pas le rouge qu'il devait traiter, et
son silence etait indiscernable de "rien a rafraichir".

- content_guard_job_names() lit les cles name: de always-on-guards.yml ;
  plus aucun compte d'organes n'est ecrit dans l'organe.
- Le job d'organes de body (#22333) partage le prefixe : il est exclu, sinon
  une PR serait rafraichie pour un rouge qui ne dit rien de l'adjacence.
- Resolution vide -> RuntimeError. L'organe refuse de mesurer a l'aveugle
  plutot que de rendre un balayage propre qui n'a rien regarde.
- Quatre tests : noms resolus presents dans le YAML (rougit au prochain
  renommage), suivi d'un workflow renomme, refus sur resolution vide, et
  non-declenchement sur un job de body rouge.
- Controle positif mesure : sur #22216 et #22327, le job 16 organes est
  fail et l'organe rend desormais always_on_failing=true (il rendait false
  avant, d'ou le silence).

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
jsboige added a commit that referenced this pull request Oct 25, 2026
* Add files via upload

Compléter EX-0373 : limiter les longs déplacements consécutifs

* Delete MyIA.AI.Notebooks/Search/Applications/CSP/App_15_SportsScheduling.ipynb

* TP1 — EX-0373 — Limiter les longs déplacements consécutifs

Compléter EX-0373 : limiter les longs déplacements consécutifs

* Feat(lean,#22377): codec etiquete -- l'alphabet entre dans l'arbre de codes (#22387)

* Add(compression,#22377): codec etiquete SymbolCodec -- l'alphabet entre dans l'arbre

Le lake Compression_lean ne connaissait que des arbres de codes sans
alphabet : WTree porte un poids par feuille, toCodeTree l'oublie, depthsW
rend une bande de longueurs, Decode prouve le round-trip sur une liste de
mots de feuilles. Aucun de ces objets ne relie un mot a un symbole -- deux
symboles de meme poids y sont indiscernables, et une permutation des
etiquettes laisse cout et Kraft invariants.

Nouveau module SymbolCodec (jalon 1 de #22377, part of #18706) :

- LTree n : arbre binaire dont chaque feuille porte un symbole de Fin n,
  parcours aligne mot/etiquette (pairs, wordsOf, labels) ;
- fonctions d'oubli vers les organes existants (forget vers CodeTree,
  toWTree vers WTree) avec conservation des mots, des profondeurs par
  symbole et du poids -- le codec ne reimplemente pas la theorie amont ;
- Nodup en forme fonctionnelle (une etiquette ne designe qu'un mot) et ce
  qu'elle achete (pairs_snd_inj) ;
- pathTo / labelAt avec leur correspondance exacte a pairs
  (pathTo_mem, labelAt_iff, labelAt_pathTo) et le contrat du code
  singleton ecrit plutot que contourne ;
- decodeL reutilise Decode.decode tel quel et n'ajoute que la traduction
  des mots en symboles ; decodeL_encode compose decode_cons pour le
  round-trip etiquete.

Preuves : lake build rc=0 (8722 jobs, agregateurs FR et EN),
distinct_code_sorry = 0 sur 17 fichiers, #print axioms sur les 18
theoremes -> [propext] ou [propext, Quot.sound] seulement (ni sorryAx, ni
native_decide, ni Classical.choice), i18n 2/2 paires sans drift.

See #22377
Part of #18706

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

* Add(compression,#22377): jalon 2 -- canonisation par etiquette

Ordre canonique (longueur, indice) via canonPairs ; contrat
canonPairs_fst, completude mem_canonOrder, borne generique
le_foldr_max ; round-trip canonique par decodeL_encode et controle
negatif swapTree (multiset != codec). Sibling EN regenere
byte-identique. lake build SUCCESS, 0 sorry, checker 2/2.

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

* Add(compression,#22377): jalons 3-4 -- cout etiquete et cas limites

Item 3 : costL lu dans pairs, costL_eq_costW (egalite apres oubli,
jamais bijection de symboles), costL_ge_huffman par appel a
huffman_optimal, rejet explicite d'une etiquette absente. Item 4 :
alphabet vide sans arbre (no_LTree_zero), singleton en deux conventions
mesurees - mot vide cout nul (Huffman) contre un bit a deux fois le
poids (Python, non injectif). Sibling EN regenere (805 lignes, 17
commentaires); docstring corrige (namespace du miroir, nom du controle
negatif). lake build SUCCESS, 0 sorry, i18n 2/2.

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

* Add(compression,#22377): items 6-7 -- fixtures confrontees au codec natif

symbol_codec_bridge.py extrait les fonctions du carnet 02 par AST
(imports + defs seulement, selection par nom) : aucune longueur ni
table recopiee. Le fixture Lean (demoLengths) est lu dans
SymbolCodec.lean. 10 tests : poids egaux, permutations, longueurs non
constantes, aller-retour, symbole absent (KeyError), mots dupliques
(injectivite distincte du prefixe), code non prefixe, flux tronque,
singleton a un bit mesure contre le mot vide du module Lean.

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

* Add(compression,#22377): jalon 5 -- reconstruction canonique d'un arbre etiquete

Remonte le chemin inverse des jalons 1-4 : d'un vecteur de longueurs par
symbole vers l'arbre du code canonique du carnet 02.

La construction passe par un arbre a trous (PTree) parce que LTree est total
et ne peut donc representer qu'un code complet. La conversion finale refuse
les trous au lieu de les combler : un vecteur incomplet n'a pas d'arbre.

- pinsert : insertions avec collisions explicites (mot pris, mot prefixe)
- canonWords : le compteur canonique du carnet (ordre canonOrder)
- buildCanon : reconstruction, none si collision ou trou
- buildCanon_demo : retrouve exactement le code 0/10/110/111 du carnet
- refus prouves : collision (Kraft > 1), trou (Kraft < 1), alphabet vide
- demoCanon_lengths, demoCanon_roundtrip (moteur du jalon 1)

Sibling EN regenere (972 lignes), i18n 2/2 byte-identical, 0 sorry.

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

* Fix(compression,#22377): le singleton 1-bit du carnet n'est pas l'arbre duplique

Reserve de l'adjoint (commentaire 6388537133, tete 5a66450) : le module
presentait `pythonSingleton` (.node (.leaf 0) (.leaf 0)) comme le codec
Python du singleton. Or `huffman({5: 7})` rend `({5: 1}, {5: "0"})` — un seul
mot d'un bit, injectif, de cout 7. L'arbre duplique coute 14 et n'est pas
injectif : les theoremes restaient justes pour LUI, l'attribution non.

- `duplicatedLeaf` remplace `pythonSingleton` : temoin d'un arbre total a
  etiquette dupliquee, explicitement PAS le codec du carnet.
- `one_bit_singleton_has_no_total_tree` : le code singleton du carnet est
  incomplet (Kraft 1/2) ; `LTree` etant total, il n'y a aucune representation
  (feuille-racine = mot vide, noeud = au moins deux entrees).
- `pairs_not_nil` : tout parcours est non vide (support de la preuve).
- Prose FR/EN : trois termes mesures et non confondus (mot vide cout 0 ;
  natif un bit injectif cout wt s, hors LTree ; temoin duplique non injectif
  cout 2w).
- Tests : le singleton verifie desormais injectivite + cout 7 (pas 14), et
  un test DIFFERENTIEL compare les mots du carnet aux mots lus dans la
  definition Lean de `demoCanonTree` (11 tests).

lake build 8722 jobs rc=0, 27 ancres (aucune hors propext/Quot.sound sauf
Classical.choice herite de fin_cases), distinct_code_sorry 0, i18n 2/2.

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

---------

Co-authored-by: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>

* Exercice 2 - Implémentation PDB symétrique (#22563)

* Exercice 2 - Implémentation PDB symétrique

* Add(search,#22563): integrer le TP exercice 2 (PDB symetrique) en exemple guide + exercice 7

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Fix(search,#22563): exercice 7 et lecture du resultat sans reponse livree

La prose ajoutee a l'integration livrait la reponse de l'exercice 7 et
affirmait que le maximum de deux heuristiques ne peut jamais augmenter les
noeuds developpes par IDA*. C'est faux en general : la suite des seuils
differe, et l'heuristique additive ne respecte pas la parite comme Manhattan.

- exercice 7 : questions a la place des conclusions, instances au ratio < 1
  listees, precaution de cout (k = 40 d'abord), mise en garde sur la
  redefinition de neighbors dans l'exercice 3 ;
- lecture du resultat de l'exemple guide 2 : la table lue sur l'image d'un
  etat estime la distance a un autre but ; cinq instances ne prouvent pas
  l'admissibilite, qui vient de la construction ;
- stub de l'exercice 7 : liste des pertes (ratio < 1) en plus des egalites.

Carnet re-execute de bout en bout (papermill, 55 cellules, 24 cellules de
code numerotees 1 a 24, 0 erreur).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Add(twin,#22563): attester la paire Search-12 apres integration du TP (Python-only)

Exemple guide 2 et exercice 7 ajoutes cote Python ; axe semantic intact, jumeau C# non touche, asymetrie de contenu declaree dans la raison.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Fix(translation,#22563): remapper les identifiants de cellules de Search-03b dans les CSV

L'integration a reajoute les exercices 3 a 6 et la conclusion avec de nouveaux identifiants (contenu identique octet pour octet, sauf l'indice de l'exercice 4). Remappage des 9 lignes dans search-part1 et search-part3 : plus de lignes orphelines creees par la PR ; les SRC_DRIFT restants sont des changements de source reels, a retraduire.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: jsboige <jsboige@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Add(ict,#20300): ICT-26 gate de degenerescence -- les instruments voient un code connu (#22303)

* Add(ict,#20300): ICT-26 gate de degenerescence -- les instruments voient un code connu

Le carnet concluait quatre verdicts (emergence, goulot de vocabulaire,
succes vs signification, stabilite) sans jamais verifier que success_rate() et
mutual_information() savent lire un cas dont la reponse est connue d'avance.
Section 4bis ajoutee, executee de bout en bout (9 cellules, 0 erreur) :

- G1 code identite impose : succes 1.000000, MI 1.997361 bit egale H(etat)
  a 1e-12 pres.
- G1b code confondu : MI exactement 0, succes = frequence observee de l'etat 0.
- G2 propensites uniformes : succes 0.241250 (< 3 sigma de 1/4), MI 0.0166 bit.
- G3 calibration : la MI residuelle d'un signal independant vaut en moyenne
  (K-1)(L-1)/(2 N ln 2) -- 0.0373 / 0.0083 / 0.0023 mesures a N = 200 / 800 /
  3200 contre 0.0325 / 0.0081 / 0.0020 predits (ratios 1.15 / 1.03 / 1.13).

Le point de methode : une MI parfaite vaut H(marginale d'etat observee), pas
log2(n). La premiere version de la porte comparait a log2 4 et declarait casse
un instrument exact -- c'est la porte qui a ete corrigee, pas la mesure. Le
residu du controle sans apprentissage (MI ~ 0.006 en section 1) est ce meme
biais d'estimateur, desormais nomme au lieu d'etre tolere.

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

* Fix(ict,#20300): ICT-26 -- gate durci et re-execution papermill du head

- G3 : fenetre mesure/prediction resserree a +/-25 % ; la decroissance en 1/N
  est desormais exigee (stricte, facteur entre 2 et 8 pour une loi qui predit 4)
- G1b : H(signal) = 0 mesure independamment ; l'egalite succes / frequence de
  l'etat 0 est nommee identite du code impose, pas mesure independante
- re-execution papermill du head (cwd = dossier de serie, backend inline) : le
  tampon de provenance decrit desormais le run, 9/9 execution_count, 0 erreur,
  les deux figures preservees
- lecture §4bis alignee sur la sortie imprimee (H(signal), paliers x4,47 / x3,65)

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

* Fix(ci,#22403): le nom du job Always-on guards se lit dans le workflow (#22476)

L'organe de rafraichissement d'adjacence cherchait en dur
"Always-on guards -- 13 organes, 1 checkout" alors que le job s'appelle
"-- 16 organes" : le predicat "le job est ROUGE sur cette PR" ne pouvait
jamais etre vrai. L'organe ne voyait pas le rouge qu'il devait traiter, et
son silence etait indiscernable de "rien a rafraichir".

- content_guard_job_names() lit les cles name: de always-on-guards.yml ;
  plus aucun compte d'organes n'est ecrit dans l'organe.
- Le job d'organes de body (#22333) partage le prefixe : il est exclu, sinon
  une PR serait rafraichie pour un rouge qui ne dit rien de l'adjacence.
- Resolution vide -> RuntimeError. L'organe refuse de mesurer a l'aveugle
  plutot que de rendre un balayage propre qui n'a rien regarde.
- Quatre tests : noms resolus presents dans le YAML (rougit au prochain
  renommage), suivi d'un workflow renomme, refus sur resolution vide, et
  non-declenchement sur un job de body rouge.
- Controle positif mesure : sur #22216 et #22327, le job 16 organes est
  fail et l'organe rend desormais always_on_failing=true (il rendait false
  avant, d'ou le silence).

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>

* Fix(ci,#17444): le dernier job de la classe 'python nu sans setup-python' (#22478)

Le detecteur livre par #19497 definit la classe largement : tout job qui
invoque `python` nu sans `actions/setup-python` est vulnerable a un
routage self-hosted, qu'il declare ou non un runner self-hosted. Sur main
il ne restait qu'un membre, et c'est lui qui gardait l'organe rouge :

  .github/workflows/runner-starvation-advisory.yml starve

Le job est declare ubuntu-latest, mais le parc route des jobs vers les
runners coursia-linux, qui portent python3 et pas python. Passage a
`python3`, sans substitution globale (le cas Windows de l'issue garde son
`python`, que la classe exempte explicitement).

Mesure apres : detect_python_nu_jobs.py rend "aucun defaut detecte" (rc=0)
la ou il rendait 1 defaut ; les 9 tests du detecteur passent.

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>

* Fix(ci,#17397): rapatrier 5 jobs legers sur l'offre GitHub hebergee (#22460)

Le pool auto-heberge est sature (42 jobs sur 42 en file sur coursia-linux)
pendant que l'offre hebergee tourne a 3 jobs pour un plafond d'environ 20.
Secret Scan (gitleaks, positive-controls, assert-egress), Always-on metadata
guards et Notebook plan-loss gate n'ont aucun besoin local : retour sur
ubuntu-latest. Les entrees restent dans l'allowlist pour un retour en un label.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: myia-ai-01 <myia.ai.01.myia@gmail.com>

* Feat(tooling,#22500): classe consolidation au tapis, plafond hebdomadaire des EPICs, duree de passe (#22504)

* Feat(tooling,#22500): classe consolidation au tapis, plafond hebdomadaire des EPICs, duree de passe

Mandat user du 2026-10-25, verbatim dans l'issue : une issue de consolidation
« merite d'etre visitee plusieurs fois par semaine » ; un EPIC ordinaire « ne
devrait pas etre visite plus d'une fois par tapis roulant, c'est-a-dire plus
d'une fois par semaine au grand max ».

1. Classe `consolidation` (titre ou label). `belt_sort_key` rend desormais
   `(classe, stamp, numero)` : `classe = 0` quand la consolidation est DUE
   (derniere visite -- ou creation si jamais servie -- il y a au moins
   `CONSOLIDATION_LAG_H` = 48 h), `1` sinon. Une consolidation due passe
   devant tout le reste et n'attend donc jamais plus d'un tour de tapis ; les
   consolidations dues entre elles gardent l'ordre de leur visite.

2. Plafond hebdomadaire de l'urne `umbrella` (`umbrella_visit_cause`) : un
   EPIC hors consolidation n'est pas repropose si un sous-grain y a ete cree
   (`last_child_stamp`) ou claimé (`last_claim_stamp`) il y a moins de
   `UMBRELLA_VISIT_CAP_DAYS` = 7 jours. La boucle du tapis l'ecarte en
   `withheld` avec sa cause, et NOMME jusqu'a deux sous-grains ouverts de cet
   EPIC presents dans le pool -- « passe au candidat suivant » sans dire
   lequel laisse la lane re-decouvrir ce que l'organe savait.

3. Les EPICs de consolidation sont exempts du plafond : revenir souvent est
   precisement leur travail.

4. `--belt --report` imprime la duree reelle d'une passe complete, horloge
   posee AVANT la lecture du pool (elle en est la part dominante : 648 s a
   froid mesurees en #20053). Non mesuree s'ecrit « non mesuree », jamais un
   zero.

Tests : 13 dans scripts/tests/test_pick_idle_grain_22500.py, un par point de
l'acceptance plus trois gardes d'elargissement (mot entier, exemption
consolidation, grain ordinaire jamais sous plafond). Suites voisines du tapis
rejouees : 150 passed (belt, board, claim_filter).

Zero appel reseau dans les tests : la boucle prend ses sondes inertes par
defaut, les dates sont injectees par `now=`.

See #22500 -- la cloture revient au coordinateur.

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

* Test(tooling,#22500): test discriminant de la classe, zero nouveau finding ruff

Le premier jeu de tests ne distinguait pas la classe de priorite du tapis
d'origine : les trois tests d'ordre prenaient une consolidation deja la plus
ancienne du pool, cas ou #18832 la servait deja en tete. Un shim qui expose
l'API de #22500 en gardant le comportement d'avant les faisait donc passer.

Ajoute le cas qui distingue reellement : consolidation visitee il y a 49 h
(due) contre un grain ordinaire visite il y a 10 jours. Sans classe, le grain
passe devant ; avec elle, la consolidation due le double.

Corrige aussi les deux findings ruff du fichier de tests (noqa inutile, alias
UTC) et la lecture d'un horodatage naif, sans ajouter de finding au picker
(63 avant, 63 apres, mesures sur le fichier lui-meme).

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

* Fix(tooling,#22504): le discriminant de classe est la POSITION du mot, et parent_refs lit kind(zone,#EPIC)

Deux points de fond de la review Hermes du 2026-10-25T13:55Z, mesures sur le pool reel (500 issues ouvertes, meme instant).

(a) La classe `consolidation` attrapait le MOT, pas le travail. Mesure avant/apres avec le code de cette branche : 17 -> 13 items, 4 faux positifs retires (#22500, #22396, #20298, #16473), zero ajoute. Les quatre sont des conteneurs ou le mot nomme le SUJET : deux EPICs parapluies, la meta-issue du tapis, la methode [ICT]. Le discriminant retenu est la POSITION -- mot en tete du titre, nu, entre crochets, ou en tete de la forme consolidation(zone,#EPIC):. Les 13 grains reels gardent la classe. Consequence assumee et ecrite dans la docstring : un EPIC parapluie perd aussi l'exemption du plafond hebdomadaire, ce qui est le comportement voulu (ses sous-grains portent la classe).

(b) parent_refs ne lisait que Part of #N en corps et [#N en tete de titre. Mesure : 13 issues gagnent un parent qu'aucune autre surface ne donnait (#22395->13504, #21922->11268, #19754->19468, #18397->2874, #17417->13504, ...), zero perte. Restriction volontaire au #N precede d'une virgule (la convention type(scope,#N):) : les formes (PR #N) et (#N suite) nomment une PR ou un fil de suivi ; les prendre pour un parent fausserait last_child_stamp. La variante large ajouterait 8 issues -- choix documente, pas oubli.

Tests : 7 ajoutes (position du mot, EPIC parapluie, deux formes de tete, forme kind(zone,#EPIC), exclusion PR/fil, non-regression des deux surfaces anciennes). Suite picker complete : 667 passed, 1 skipped. ruff 0.17.2 sur les deux fichiers : 63 = 63 (base et tete), fichier de test propre.

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

* Fix(tooling,#22504): la classe consolidation se lit au label seul

Arbitrage coordinateur du 2026-10-25T17:37Z (ai01-1645-c2-22504-decision) :
le repli sur le titre est retire. Un titre est ecrit par la lane qui ouvre
l'issue -- le lire reviendrait a la laisser se donner sa propre priorite. Le
label `consolidation`, pose par le coordinateur, est la seule surface.

Ce que cela remplace dans la PR : le commit precedent discriminait par la
POSITION du mot en tete de titre. La mesure de l'epoque sur le pool (500
ouvertes) donnait 17 -> 13, et laissait 13 grains classes par leur titre.
Ces 13 ne sont plus classes que s'ils portent le label.

Mesure sur le pool reel avec les labels frais (581 ouvertes, 2026-10-25) :
45 issues portent le label -- le chiffre annonce par le coordinateur -- et
le retrait du repli ne retire la classe qu'a UNE issue, #22528
« [Consolidation] Audio/04-Applications : scan des 19 carnets », qui est une
vraie consolidation et n'a simplement pas encore son label. Aucun ajout
possible par ce retrait : 46 -> 45.

Surface de test : la classe se pose par un helper `_consol()` qui pose le
label, jamais le titre ; trois tests epinglent l'inverse (aucun titre ne
donne la classe, quelle que soit la position du mot) ; un test epingle le
cas nomme par le coordinateur, #21648 -> #13504.

Tests : `scripts/tests/test_pick_idle_grain*.py` -> 421 passed (le controle
par shim `test_pick_idle_grain_belt.py`, 75 tests, est dans ce lot).
ruff : 63 findings avant, 63 apres sur `pick_idle_grain.py` (inchange) ;
fichier de test : propre.

See #22500

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

---------

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>

* Fix(docs,#22291): H1 Search-09d/09e + KNOTS-01..04 alignes sur l'ordinal du fichier (tranche 3) (#22365)

* Fix(docs,#22291): H1 des carnets Search-09d/09e et KNOTS-01..04 alignes sur l'ordinal du fichier

Tranche 3 du sweep H1 (#22291) : le libelle de la page de parcours generee
doit designer le carnet qu'elle lie. Search-09d/09e portaient le label de
sous-serie Discrepancy-01/02, KNOTS-01..04 le label legacy Lean 17a-d.
Markdown-only (cellule 0, ligne 1), format et corps preserves verbatim ;
aucune cellule code, output ou execution_count touche -> pas de re-exec due.

Rider bloque par le pre-commit knots-extracts (#21007) : KNOTS-01 cellule 3
citait KNOTS/knot_lean/Knots/FigureEight.lean, forme relative qui ne resout
pas depuis le dossier du carnet -> knot_lean/Knots/FigureEight.lean (le
renvoi ligne 17 est verifie exact dans le module). Markdown-only aussi.

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

* Fix(notebook-lean,#22291): le renvoi FigureEight.lean pointe la ligne 16

La cellule citait 'ligne 17' : dans Knots/FigureEight.lean, det(4_1) =
|Delta(-1)| = 5 est en ligne 16, la 17 etant sa continuation. Markdown-only,
aucune re-execution due.

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

---------

Co-authored-by: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>

* Feat(ict,#17719): tracking des artefacts 9B per-seed -- les sources c.233/c.246 de l'agregat entrent au depot (#22473)

Les deux JSON per-seed du run onset 9B (Qwen3.5-9B-Base, 4 graines, bras W/S)
vivaient uniquement sous D:\Runs\ (hors depot, hors sauvegarde git). L'agregat
communi par #19853 les cite par leur cycle ('c.233 seeds 0,1', 'c.246 seeds
2,3') sans qu'ils soient lisibles. Ils rejoignent ict/results/ a cote de
l'agregat -- 7,6 Ko chacun, loin du seuil results-artifact-policy (512 Ko).

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>

* Feat(hashlife,#22227): GOL-09 consomme l'exposant de temps HashLife (pli 3, grain B) (#22250)

Section 8 nouvelle dans GOL-09 : le cout en temps de HashLife contre la simulation
naive, importe du JSON du grain A sans aucune valeur recopiee a la main. L'exposant
alpha separe les memes temoins que la compressibilite mesuree en sections 3 et 7 :
0.106 a 0.160 sur les periodiques courts, 0.289 sur le canon de Gosper, 1.352 sur
acorn ou HashLife prend 1.38 fois plus de temps que le naif, et AMBIGUOUS sur les
deux soupes avec des seuils declares avant la mesure.

La Conclusion passe en section 9 et integre le resultat ; un Exercice 4 unique est
ajoute dans la section Exercices existante. Re-execution complete (6/6 cellules,
0 erreur, kernel python3 3.13.16), sorties commitees.

Co-authored-by: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>

* Feat(app15,#22568): digestion EX-0373 en exemple guide credite + exercice souple neuf

Integration du TP1 de Jerome Desale et Mathis Gramaglia (PR #22568) :
- Exercice 1 (EX-0373) converti en Exemple guide 4 credite en section
  Exercices : solution eleve verbatim (contrainte long_trip par egalite,
  verificateur independant, comparaison avant/apres), affichage et bilan
- Nouvel exercice 1 non resolu a la place : version souple (penalite POIDS
  pesee en km au lieu d'interdiction), stub C.1
- Note honnete sur la comparaison ancrée aux sorties fraiches (ecart-type
  267,5 -> 258,6 km, OPTIMAL ; invariants km/longs par construction)
- Re-execution complete 19/19 cellules code, 0 erreur (papermill local,
  ortools 9.15.6755), bloc papermill rafraichi (reserve NanoClaw 3)
- CSV traductions resync (2 rafraichies, 4 ajoutees), parite jumeau OK

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

---------

Co-authored-by: Jean-Sylvain Boige <jsboige@gmail.com>
Co-authored-by: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
Co-authored-by: sachalhermet19 <sacha.lhermet@gmail.com>
Co-authored-by: myia-ai-01 <myia.ai.01.myia@gmail.com>

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants