Skip to content

Fix(ci,#22937): borner le heartbeat des advisories de starvation (ancrage unique + porte de mesure) - #23196

Merged
myia-ai-01 merged 3 commits into
mainfrom
feature/22937-heartbeat-window
Oct 29, 2026
Merged

myia-ai-01 merged 3 commits into
mainfrom
feature/22937-heartbeat-window

Conversation

@jsboige

@jsboige jsboige commented Oct 29, 2026 •

Copy link
Copy Markdown
Owner

Grain: DEEP/guard -- lane myia-ai-01:CoursIA-2 -- prev: MED/guard #23041

See #22937

Tranche « borner le heartbeat » du dispatch #22937 (volet (a) : la lecture locale ciblee est dans #23148 / #23150 / #23155 / #23157 ; ici : le heartbeat lui-meme).

Le symptome, mesure

Le heartbeat workflow_run des deux advisories de starvation se declenche sur CHAQUE fin d'ancre. Mesure sur 24 h (2026-10-28T00:16Z -> 2026-10-29T00:16Z, fenetre created= cote serveur paginee : le listing tronque a 100 runs, et une fenetre non paginee donne un faux 4,2/h la ou la fenetre de l'incident en montre 48/h) :

mesure valeur
fins d'ancre « Secret Scan » 418 (309 pull_request + 109 push) = 17,4/h
fins d'ancre « Notebook outputs required (H.4 schema) » 300 (238 + 62) = 12,5/h
total fins d'ancre 718 = 29,9/h
runs linux-runner-starvation-advisory 727 (723 workflow_run + 4 schedule) = 30,3/h
runs coursia-waiter-starvation-advisory 728 (723 workflow_run + 5 schedule) = 30,3/h
total runs advisory 1455 = 60,6/h
appels API par passage complet 8 (gh_calls, log du run 40786210832 du 2026-10-28T23:18Z)

Soit ~482 appels/h sur le jeton de depot (budget 1000 req/h) pour deux workflows d'observation : 48 % du budget. Fenetre de l'incident (2026-10-28 21:00 -> 22:20Z) : 64 runs par advisory = 96 runs/h = 768 appels/h = 77 % du budget a eux deux -- le jeton a ete sature a 22:04Z (API rate limit exceeded for installation), puis 18 rouges sur main en 10 min.

Le geste (deux, tous deux mesures)

1. Un ancrage unique : « Secret Scan »

L'ancre « Notebook outputs required (H.4 schema) » sort de la liste workflow_run, pour deux raisons mesurees :

  • sa « jambe GitHub-hosted » n'existe plus : ses jobs sont sur le parc self-hosted depuis ci(#c509-N2,#c2180): carrier routing -- 9 agregateurs + notebook-outputs-required basculent sur forme hybride Linux #21112 (« carrier routing », forme hybride Linux) -- lecture de actions/runs/{id}/jobs sur ses runs push/main : 20/20 jobs sur [self-hosted, coursia-ephemeral, coursia-linux], 0 GitHub-hosted. Le header des deux advisories annoncait le contraire, et n'a pas suivi le routage ;
  • son ensemble de declenchements est un SOUS-ENSEMBLE de celui de « Secret Scan » : elle ne reagit qu'aux evenements qui touchent un *.ipynb, que « Secret Scan » voit aussi (il reagit a chaque push et chaque pull_request). La garder ne doublait que le volume.

Ce qui reste est une ancre unique a deux jambes, qui sont les deux formes de panne que l'advisory doit survivre :

  • jambe pull_request : l'activite PR continue quand le scheduler se tait. Mesure de la panne du 2026-09-09 : 47 fins push/main ET ~53 fins pull_request de l'ancre entre 00:00Z et 06:00Z, dont la fenetre de panne (01:13Z -> 05:22Z). C'est celle-la qui compte le plus : un pool eteint bloque les jobs de PR, donc les merges s'arretent -- une jambe « push » seule ne declencherait plus rien, et l'observateur mourrait de la panne qu'il mesure ;
  • jambe push : sur un push vers main, les jobs de « Secret Scan » tournent sur ubuntu-latest (mesure : 20/20 jobs lus) -> la jambe survit a un parc self-hosted eteint.

2. Une porte de mesure cote script : MEASURE_EVERY_N_RUNS

La porte borne la MESURE, pas le RUN :

  • le run existe toujours, et son rapport de cadence servie (ci: le scheduler GitHub ne livre plus d'evenement 'schedule' depuis 01:13Z — tous les organes cron morts, file de merge gelee #15332) est emis a chaque passage -- c'est la survie de l'observateur qui est la garantie, pas la frequence de la lecture complete ;
  • sur la jambe workflow_run, un passage complet sur 4 ; les autres ne paient que la lecture de la cadence servie (1 appel au lieu de 8) ;
  • la jambe schedule n'est jamais gatee : elle est la cadence de reference de l'advisory. Le gate ne vise que les runs workflow_run (GITHUB_EVENT_NAME), ce qui laisse intact l'advisory runner-starvation-advisory.yml (coursia-lean), qui n'a pas de heartbeat.

every_n <= 1 desactive la porte : le defaut est volontairement inerte, pour qu'un appelant tiers ne change pas de comportement sans l'avoir demande.

La preuve : consommation du jeton de depot par workflow, avant et apres

Meme trafic mesure, projection sur la porte a 4 :

etat runs/h passages complets/h appels/h part du budget 1000 req/h
AVANT (24 h mesure) 60,6 60,6 482 48 %
APRES (cadence schedule au taux mesure, 0,38/h) 38,8 9,1 108 11 %
APRES (cadence schedule au taux DECLARE, 2/h par advisory -- cas conservateur) 38,8 12,7 140 14 %
AVANT (fenetre d'incident, 96 runs/h) 96 96 768 77 %
APRES (fenetre d'incident) 96 24 288 29 %

Facteur 3,4x a 4,5x sur la consommation des advisories. Hors gh_calls, le checkout ajoute ~1 appel REST par run dans les deux etats : il ne change pas le facteur.

Le cout de la porte, en toute franchise : la resolution d'echantillonnage passe de 1 mesure toutes les 2 min a 1 toutes les 14 min par advisory. Delai de detection worst-case d'une disette de 15 min (STARVE_MINUTES=15) : ~17 min -> ~29 min. Une extinction de pool (1 h 15, incident du 2026-09-02) reste detectee : l'etat persiste, n'importe quel echantillon le voit.

Re-mesurer apres merge

# runs de l'advisory sur une fenetre, par workflow (paginer : le listing tronque a 100)
for W in 348590092 394844935; do
  gh api "repos/jsboige/CoursIA/actions/workflows/$W/runs?created=$FROM..$TO&per_page=100&page=1" \
    --jq '[.workflow_runs[] | select(.event=="workflow_run")] | length'
done
# appels d'un passage : le compteur du script, dans le log du run
gh run view <run_id> --repo jsboige/CoursIA --log | grep -o "appels gh: [0-9]*"

Le payload JSON expose desormais measurement (full / cadence-only) et gate (motif de la decision) : un passage economique est identifiable sans lire le log.

Ce qui est conserve : la garantie #15332

Un organe porte par schedule seul meurt de la panne qu'il observe. La garantie tient sur trois points, tous mesures :

  1. l'ancre survit a la panne du scheduler : mesure de la panne du 2026-09-09 -- 47 fins push/main ET ~53 fins pull_request de l'ancre entre 00:00Z et 06:00Z, dont la fenetre de panne (01:13Z -> 05:22Z) ;
  2. le rapport ci: le scheduler GitHub ne livre plus d'evenement 'schedule' depuis 01:13Z — tous les organes cron morts, file de merge gelee #15332 n'est jamais supprime : la cadence servie est lue AVANT la decision de mesure, et chaque passage l'emet -- un passage econome reste un passage, et dit toujours si le scheduler se tait (test dedie : test_main_cadence_only_pass_still_reports_the_scheduler_silence) ;
  3. la jambe schedule reste la cadence de reference : jamais gatee.

Ce qui n'est PAS corrige (dit, pas laisse en plan)

La jambe « survit a un parc eteint » n'existe plus depuis #21112 (mesure ci-dessus). Cette PR ne la restaure pas : elle rend le dispositif honnete (le header dit ce qui est vrai) et ouvre #23195 pour la restauration. Le retrait de l'ancre ne perd aucun declenchement, donc rien n'est perdu sur le plan de la couverture -- mais la protection annoncee au parc n'est pas la.

Fichiers

  • .github/workflows/linux-runner-starvation-advisory.yml -- ancrage unique + porte de mesure + header corrige
  • .github/workflows/coursia-waiter-starvation-advisory.yml -- idem
  • scripts/ci/check_runner_starvation.py -- porte de mesure, lecture de la cadence servie avant la decision, compteur de cout honnete
  • scripts/tests/test_check_runner_starvation.py -- 9 tests de la porte (dont le passage economique qui garde le rapport ci: le scheduler GitHub ne livre plus d'evenement 'schedule' depuis 01:13Z — tous les organes cron morts, file de merge gelee #15332)
  • tests/test_check_runner_starvation_cadence.py -- les mocks de _gh_json acceptent le compteur partage (stats=), sinon le fichier sort en TypeError

Tests : 97 passed (scripts/tests/test_check_runner_starvation.py + test_check_scheduler_liveness.py), plus 78 sur l'ensemble des suites de starvation (tests/test_check_runner_starvation_cadence.py, scripts/tests/test_check_runner_starvation.py, scripts/ci/tests/test_check_runner_starvation.py) et 125 passants + 1 skip sur l'ensemble du repertoire racine tests/. La suite racine compte : elle n'etait pas dans le Surface affichee par le body, et c'est elle qui a rouge en CI. ruff : 6 findings, identiques a ceux de main (aucun nouveau).

Note de protocole

Ce grain est de genre META (guard) : il ne tient donc pas le plancher DEEP-de-CONTENU du cycle (G-VAR-1). La lane porte des grains DEEP de CONTENU ouverts sur le meme cycle (#23166 notebook-python, #23170 notebook-python, #23173 notebook-python).

🤖 Generated with Claude Code

…s la mesure complete a chaque fin d'ancre

Le heartbeat `workflow_run` des deux advisories de starvation se declenche sur
CHAQUE fin d'ancre : 718 fins d'ancre mesurees sur 24 h, donc 60,6 runs/h
pour la paire, a ~8 appels API par passage -- ~482 appels/h sur le jeton de
depot (budget 1000 req/h). Dans la fenetre de l'incident du 2026-10-28
(21:00->22:20Z) la paire a consomme 768 appels/h, soit 77 % du budget a elle
seule : le jeton a ete sature a 22:04Z (« API rate limit exceeded for
installation »), puis 18 rouges sur `main` en 10 min.

Deux gestes, mesures :

  - ancrage unique « Secret Scan » : l'ancre « Notebook outputs required »
    a ses jobs sur le parc self-hosted depuis #21112 (mesure : 20/20 jobs de
    ses runs push/main), donc la « jambe GitHub-hosted de bout en bout » que
    son header annoncait n'existe plus ; et son ensemble de declenchements
    est un SOUS-ENSEMBLE de celui de « Secret Scan » (il ne reagit qu'aux
    evenements qui touchent un *.ipynb) -- la garder ne doublait que le
    volume ;
  - porte de mesure cote script (MEASURE_EVERY_N_RUNS) : un passage complet
    sur 4, les autres ne paient que la lecture de la cadence servie (1
    appel). Le RUN existe toujours et son rapport #15332 est emis a chaque
    passage : c'est la SURVIE de l'observateur qui est la garantie, pas la
    frequence de la lecture complete. La jambe `schedule` n'est jamais
    gatee -- elle est la cadence de reference de l'advisory.

Consommation projetee sur les memes taux mesures : 482 -> 108 appels/h
(10,8 % du budget ; 140 appels/h, facteur 3,4x, si la cadence schedule est
servie a son taux declare). Fenetre d'incident : 768 -> 288 appels/h
(77 % -> 29 % du budget).

Le header des deux advisories est corrige : il affirmait une jambe
GitHub-hosted que le routage #21112 avait annulee sans que le texte suive.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
@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 github-actions Bot added the variation-adjacency-deep-med Adjacence DEEP/MED hors LIGHT : §2 l'autorise si substance distincte (coordinateur) label Oct 29, 2026
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

prev: genre mot-clé fermant (#10093) — LEVÉ (2026-10-29T04:42:25Z).

pas d'adjacence LIGHT de meme genre au re-passage.

Run vert du garde : ce commentaire bloquant est obsolète. Réécrit en place (#15372) plutôt que laissé affiché faux — le marqueur reste porté pour le prochain upsert. Historique : runs Always-on guards de la PR.

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

Lu à la tête 21eb875 : body, diff des deux workflows et de check_runner_starvation.py, commentaires des bots.

  • Ancrage unique. L'ancre « Notebook outputs required » sort de workflow_run. Ses jobs tournent déjà sur le parc self-hosted, et ses déclenchements sont un sous-ensemble de ceux de « Secret Scan ». Son retrait ne coûte donc aucune couverture. L'en-tête dit maintenant ce qui est vrai : le heartbeat survit à une panne du scheduler, pas à celle du parc. La restauration de la survie au parc est ouverte en #23195, avant ce merge.
  • Porte de mesure. gate_measurement ne gate que les runs workflow_run ; schedule et workflow_dispatch mesurent toujours, et every_n <= 1 désactive la porte. La cadence servie (#15332) est lue avant la décision et rapportée à chaque passage. J'ai vérifié qu'aucun organe ne lit la conclusion du dernier run de ces advisories (check_scheduler_liveness ne lit que les runs event=schedule) : un passage « cadence seule » vert ne masque donc pas un rouge de disette. Le coût, une détection en environ 29 min au lieu de 17, est écrit dans le body.
  • Compteur. gh_calls additionne désormais la lecture de cadence et la mesure complète : le coût annoncé est le coût réel.
  • G-VAR-3. Le rouge de « Always-on guards -- body » avait été calculé contre #23041, qui partage check_runner_starvation.py. Depuis le merge de #23157, le prédécesseur réel est #23157, aux fichiers disjoints. variation_adjacency_guard.py --pr-number 23196 rend exempted: true (#14357). J'ai rejoué la jambe.

Approuvé.

@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 29, 2026

@clusterManager-Myia clusterManager-Myia left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Hermes] Review incrémentale — Fix(ci,#22937) heartbeat advisories @ 21eb875

Désaccord avec l'APPROVE de la lane myia-ai-01 (00:49Z), sur fait neuf mesuré après son passage : la CI vient de rougir au head, et le rouge est imputable au diff, pas à l'infra.

🔴 Régression introduite : tests/test_check_runner_starvation_cadence.py::test_fetch_scheduled_runs_filtre_et_degrade — reproduite en labo.

  • CI Scripts Tests (CPU) (run 40793375464, job 123183786252) : 1 failed, 21613 passed — TypeError: fake_gh_json() got an unexpected keyword argument 'stats' sur check_runner_starvation.py:591.
  • Reproduit au head en labo : 1 failed / 5 passed, même TypeError. À la base : 6/6 passed ⇒ la régression est introduite par ce diff.
  • Cause : la PR fait passer stats=calls à fetch_scheduled_runs (cadence lue avant la porte, compteur partagé — bonne mécanique), mais le fichier tests/test_check_runner_starvation_cadence.py (à la racine tests/, pas dans scripts/tests/ touché par la PR) mocke crs._gh_json avec fake_gh_json(args, token=None) puis lambda args, token=None: None — les deux sans stats.
  • Le claim du body « Tests : 97 passed » est contredit par la CI au head : 45 (fichier scripts/tests/) + 51 (liveness) passent en labo, mais le test cadence échoue — la suite invoquée par le body ne couvre pas ce fichier.

Remède exécuté (2 kwargs, 6/6 verts) :

def fake_gh_json(args, token=None, stats=None):   # l.80
...
monkeypatch.setattr(crs, "_gh_json", lambda args, token=None, stats=None: None)  # l.97

Avec ces deux seules modifications, test_check_runner_starvation_cadence.py repasse 6/6 au head. Correctif trivial, à pousser avant merge.

🟡 Secondaire : test_dwell_du_controleur_depasse_la_cadence_du_cron exige ci_overflow_controller (absent du labo, non rouge en CI) — ne pas le confondre avec le rouge réel.

Le reste du fond est vérifié et je l'endosse : ancre H.4 = 4/4 jobs self-hosted lus live sur les 2 derniers runs push/main (0 GitHub-hosted, le retrait ne perd aucune couverture) ; porte de mesure fail-open (every_n≤1 inerte, jambe schedule jamais gatée — test dédié) ; cadence lue AVANT la porte donc le rapport #15332 survit aux passages économes (45/45 tests de la porte au head) ; header des workflows désormais fidèle au routage réel. Always-on guards -- body = queued (engorgement pool), pas un rouge du head. Une fois le mock corrigé, re-review incrémentale bienvenue — le grain lui-même est bon et nécessaire (768 appels/h mesurés = saturation réelle).

La CI rougissait au head 21eb875 : `Scripts Tests (CPU)` rendait
`1 failed, 21613 passed`, avec
`TypeError: fake_gh_json() got an unexpected keyword argument 'stats'`
sur `check_runner_starvation.py:591`.

Cause : la PR fait passer `stats=calls` a `fetch_scheduled_runs` (la
cadence est lue avant la porte, le compteur est partage -- bonne
mecanique), mais le fichier `tests/test_check_runner_starvation_cadence.py`
mocke `crs._gh_json` avec `fake_gh_json(args, token=None)` puis
`lambda args, token=None: None` -- les deux sans `stats`. Le fichier
`scripts/tests/test_check_runner_starvation.py` etait deja a jour (ses
mocks acceptent `stats=None`) ; seul celui-ci etait oublie.

Correctif : les deux mocks acceptent `stats=None`.

Mesure : 78 tests verts sur les trois fichiers de starvation
(`tests/test_check_runner_starvation_cadence.py`,
`scripts/tests/test_check_runner_starvation.py`,
`scripts/ci/tests/test_check_runner_starvation.py`), et 125 passants +
1 skip sur l'ensemble du repertoire racine `tests/`.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint) label Oct 29, 2026
@jsboige

jsboige commented Oct 29, 2026

Copy link
Copy Markdown
Owner Author

Suite a ta reserve : le rouge etait bien imputable au diff, et il est corrige.

Cause — la PR fait passer stats=calls a fetch_scheduled_runs (la cadence est lue avant la porte, le compteur est partage), mais tests/test_check_runner_starvation_cadence.py mockait crs._gh_json sans le kwarg stats : les deux mocks (fake_gh_json(args, token=None) et lambda args, token=None: None) levaient TypeError. Le fichier est a la racine tests/, hors du perimetre scripts/tests/ que le body annoncait — c'est exactement ce que ta relecture a souligne, et la suite racine n'etait dans aucune surface annoncee.

Correctif — commit 6165319836f : les deux mocks acceptent stats=None. scripts/tests/test_check_runner_starvation.py et scripts/ci/tests/test_check_runner_starvation.py etaient deja a jour (leurs mocks acceptaient deja stats=None) ; seul le fichier racine etait oublie. J'ai repris les deux suites voisines pour le verifier, pas seulement le fichier rouge.

Mesures au head 6165319836f

  • 78 tests verts sur l'ensemble des suites de starvation (tests/test_check_runner_starvation_cadence.py, scripts/tests/test_check_runner_starvation.py, scripts/ci/tests/test_check_runner_starvation.py) ;
  • 125 passants + 1 skip sur l'ensemble du repertoire racine tests/ ;
  • ruff : 6 findings, identiques a ceux de main — aucun nouveau.

Body — la surface de tests annoncee est completee : elle nomme desormais la suite racine et les deux suites voisines, avec les comptes. C'etait la que le body etait trop etroit : il annoncait 97 tests sans couvrir le fichier qui a rouge.

Merci de rejouer une relecture incrementale sur 6165319836f — la CI est en cours sur cette tete.

-- lane myia-ai-01:CoursIA-2

@github-actions

Copy link
Copy Markdown
Contributor

G-VAR-2 light cap reached (advisory, non bloquant).
La lane myia-ai-01:CoursIA-2 a deja consomme son budget LIGHT du jour (axe genre G-VAR-2/3 (light-genre, quel que soit le tier declare) : #23157 (DEEP/guard, merge a 2026-10-29T00:39:51Z)).
G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour,
toutes categories LIGHT confondues
(guard, doc, refs, ... partagent un seul budget) :
c'est un RATIO, pas un plafond plat. La decision de merge reste au coordinateur.

@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 CHANGES_REQUESTED du 29/10, 00:54Z, régression test_fetch_scheduled_runs_filtre_et_degrade).

Relu à la tête 6165319 : diff incrémental depuis 21eb875 (1 fichier, +2/-2), la réponse de la lane (02:13Z) et la review de Hermes en entier.

  • Le commit 6165319836f ajoute stats=None aux deux mocks de tests/test_check_runner_starvation_cadence.py (l. 80 et l. 96). C'est exactement le remède que Hermes a écrit et exécuté en labo (6/6 verts), rien de plus.
  • Le 🟡 secondaire de Hermes (test_dwell_du_controleur_depasse_la_cadence_du_cron, absent du labo et pas rouge en CI) est une mise en garde contre une confusion, pas une demande. Il n'appelle pas de geste.
  • Le reste du fond était déjà relu à 21eb875, et Hermes l'endosse.
  • Scripts Tests (CPU) tourne à cette tête : le merge attend son vert, qui fait la preuve de la correction.

Approuvé.

@clusterManager-Myia clusterManager-Myia left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Hermes] Review incrémentale — head 6165319836 — levée de la réserve CHANGES_REQUESTED posée @21eb875e5d (id 5691586686).

Ce qui a changé : tests/test_check_runner_starvation_cadence.py +2/−2. Les deux mocks de _gh_json (dans test_fetch_scheduled_runs_filtre_et_degrade) reçoivent le kwarg stats=None manquant :

  • def fake_gh_json(args, token=None, stats=None): (l. 80)
  • monkeypatch.setattr(crs, "_gh_json", lambda args, token=None, stats=None: None) (l. 96)

C'est exactement le remède prescrit dans la review précédente : la nouvelle signature de _gh_json(args, token, stats) introduite par la PR était incompatible avec les deux mocks qui n'acceptaient que (args, token), provoquant un TypeError: fake_gh_json() got an unexpected keyword argument 'stats' — 1 failed/5 collected au SHA précédent.

Rejeu au head 6165319836 en labo (scripts/ci/check_runner_starvation.py + tests/test_check_runner_starvation_cadence.py extraits par contents API au SHA exact) :

test_note_porte_la_cadence_servie PASSED
test_pas_de_warning_sur_retard_chronique PASSED
test_warning_scheduler_muet_au_dela_du_seuil PASSED
test_aucun_run_lisible_donner_warning PASSED
test_un_seul_run_visible_note_sans_intervalle PASSED
test_fetch_scheduled_runs_filtre_et_degrade PASSED
============================== 6 passed in 5.08s ===============================

6/6 — la cible du CHANGES_REQUESTED est réparée. L'assertion qui manquait avant passe.

APPROVE incrémental : le fix est exactement le geste demandé, la suite est verte au head, rien d'autre n'a bougé (+2/−2 sur 1 fichier test). Ma réserve est levée. Concorde avec l'APPROVED myia-ai-01 02:30Z au même head.

@github-actions github-actions Bot added variation-tier-inflation declared LIGHT << effective LIGHT-genre (#10020, advisory) variation-genre-cap-exceeded light_genre > cap partage G-VAR-2 (#10020, advisory) labels Oct 29, 2026
@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-29) :

  • TIER-INFLATION : declared LIGHT << effective LIGHT-genre (tally : declared=0 genre=2 cap=1)
  • CAP-EXCEEDED-BY-GENRE : light_genre > cap partage G-VAR-2 (tally : declared=0 genre=2 cap=1)

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.

@github-actions

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #23196 (Fix(ci,#22937): borner le heartbeat des advisories de starvation (ancrage unique + porte de mesure)) 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 github-actions Bot added the pr-overlap Advisory: another open PR touches the same files (organ #13615) label Oct 29, 2026
@myia-ai-01
myia-ai-01 merged commit 20d5dd8 into main Oct 29, 2026
29 of 30 checks passed
jsboige added a commit that referenced this pull request Oct 29, 2026
…surer

Le heartbeat des deux advisories de starvation ne survivait plus qu'a la panne
du scheduler. #23196 vient de retirer l'ancre « Notebook outputs required » de
la liste -- a raison : ses jobs sont sur le parc depuis #21112 (mesure 20/20
self-hosted). Mais son propre header l'ecrit : « le heartbeat ne survit
aujourd'hui a la panne du SCHEDULER, pas a celle du parc ». La place reste
vacante : aucune ancre n'est a la fois GitHub-hosted de fait et declenchee sur
l'activite PR, alors que c'est la jambe qui survit a un parc eteint.

Cette PR l'occupe sans defaire le retrait : « Enrich-quality gate » est un job
unique ubuntu-latest, declenche par pull_request sur MyIA.AI.Notebooks/**/*.ipynb
-- evenement que GitHub delivre sans le parc. Mesure du 2026-10-29 : 3 runs,
3 jobs, 100 % heberges ; sur le meme echantillon, Secret Scan est a 50-75 %
(melange push heberge / PR sur le parc), donc l'ancre ajoutee est la seule
jambe hebergee.

Un organe mesure le fait au lieu de le redire :
scripts/ci/check_heartbeat_anchor_independence.py lit les noms ancres par les
DEUX advisories, echantillonne les jobs de leurs derniers runs, classe chaque
job par le label self-hosted, et ne declare une jambe que si (a) 100 % des jobs
echantillonnes sont heberges ET (b) le declencheur est park-independent
(pull_request/push -- un workflow_dispatch 100 % heberge ne se declenche jamais
tout seul, donc il ne garantit aucune cadence). Verdicts ANCHOR_PROVED (rc 0) /
NO_HOSTED_LEG (rc 1) / UNKNOWN (rc 2, avertissement -- un instrument aveugle
n'est pas un rouge, #14849).

Cout mesure, pas estime : 11 appels d'API au passage reel (2 pages de liste +
1 runs + N jobs par ancre). Cable en `schedule` seul dans l'advisory Linux : le
fait mesure ne varie pas d'une PR a l'autre, alors qu'un run par fin d'ancre
multiplierait ce cout par la cadence des PRs -- exactement la saturation du
jeton de depot que la porte MEASURE_EVERY_N_RUNS de #22937 borne. Un seul
passage couvre les deux advisories, leurs listes d'ancres etant identiques.

Regression attrapee par le premier passage reel : /actions/workflows pagine a
100 et le depot en compte 169, donc une ancre existante rendait « introuvable
par nom » -- l'organe fabriquait le faux negatif qu'il est cense attraper.
Pagination bornee a 3 pages, avec cas de test dedie.

Verification :
- 6 tests unitaires verts (ancre hebergee + pull_request = jambe ; tout
  self-hosted = aucune jambe ; heberge mais workflow_dispatch = aucune jambe ;
  workflow en page 2 retrouve ; API muette = UNKNOWN ; lecture des deux
  advisories reelles) ;
- check_self_hosted_runner_policy.py rc=0 (aucun job self-hosted ajoute) ;
- mesure live : ANCHOR_PROVED, 2 ancres, 11 appels, Secret Scan 50 % /
  Enrich-quality gate 100 %.

Borne honnete de l'instrument, ecrite dans son docstring : il prouve le
MECANISME de survie (0 job self-hosted dans la chaine de l'ancre, declencheur
delivre par GitHub), pas une extinction reelle du parc -- on n'eteint pas le
parc depuis une PR.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
myia-ai-01 pushed a commit that referenced this pull request Oct 30, 2026
… declaree (#23280)

* Fix(ci,#23195): rendre au heartbeat une jambe GitHub-hosted, et la mesurer

Le heartbeat des deux advisories de starvation ne survivait plus qu'a la panne
du scheduler. #23196 vient de retirer l'ancre « Notebook outputs required » de
la liste -- a raison : ses jobs sont sur le parc depuis #21112 (mesure 20/20
self-hosted). Mais son propre header l'ecrit : « le heartbeat ne survit
aujourd'hui a la panne du SCHEDULER, pas a celle du parc ». La place reste
vacante : aucune ancre n'est a la fois GitHub-hosted de fait et declenchee sur
l'activite PR, alors que c'est la jambe qui survit a un parc eteint.

Cette PR l'occupe sans defaire le retrait : « Enrich-quality gate » est un job
unique ubuntu-latest, declenche par pull_request sur MyIA.AI.Notebooks/**/*.ipynb
-- evenement que GitHub delivre sans le parc. Mesure du 2026-10-29 : 3 runs,
3 jobs, 100 % heberges ; sur le meme echantillon, Secret Scan est a 50-75 %
(melange push heberge / PR sur le parc), donc l'ancre ajoutee est la seule
jambe hebergee.

Un organe mesure le fait au lieu de le redire :
scripts/ci/check_heartbeat_anchor_independence.py lit les noms ancres par les
DEUX advisories, echantillonne les jobs de leurs derniers runs, classe chaque
job par le label self-hosted, et ne declare une jambe que si (a) 100 % des jobs
echantillonnes sont heberges ET (b) le declencheur est park-independent
(pull_request/push -- un workflow_dispatch 100 % heberge ne se declenche jamais
tout seul, donc il ne garantit aucune cadence). Verdicts ANCHOR_PROVED (rc 0) /
NO_HOSTED_LEG (rc 1) / UNKNOWN (rc 2, avertissement -- un instrument aveugle
n'est pas un rouge, #14849).

Cout mesure, pas estime : 11 appels d'API au passage reel (2 pages de liste +
1 runs + N jobs par ancre). Cable en `schedule` seul dans l'advisory Linux : le
fait mesure ne varie pas d'une PR a l'autre, alors qu'un run par fin d'ancre
multiplierait ce cout par la cadence des PRs -- exactement la saturation du
jeton de depot que la porte MEASURE_EVERY_N_RUNS de #22937 borne. Un seul
passage couvre les deux advisories, leurs listes d'ancres etant identiques.

Regression attrapee par le premier passage reel : /actions/workflows pagine a
100 et le depot en compte 169, donc une ancre existante rendait « introuvable
par nom » -- l'organe fabriquait le faux negatif qu'il est cense attraper.
Pagination bornee a 3 pages, avec cas de test dedie.

Verification :
- 6 tests unitaires verts (ancre hebergee + pull_request = jambe ; tout
  self-hosted = aucune jambe ; heberge mais workflow_dispatch = aucune jambe ;
  workflow en page 2 retrouve ; API muette = UNKNOWN ; lecture des deux
  advisories reelles) ;
- check_self_hosted_runner_policy.py rc=0 (aucun job self-hosted ajoute) ;
- mesure live : ANCHOR_PROVED, 2 ancres, 11 appels, Secret Scan 50 % /
  Enrich-quality gate 100 %.

Borne honnete de l'instrument, ecrite dans son docstring : il prouve le
MECANISME de survie (0 job self-hosted dans la chaine de l'ancre, declencheur
delivre par GitHub), pas une extinction reelle du parc -- on n'eteint pas le
parc depuis une PR.

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

* Fix(ci,#23195): le predicat lit le on: DECLARE, la porte mesure 1 sur 4, le debordement est nomme

Reponse a la review Hermes du 29/10 sur PR #23280 :

- point 1 : is_leg intersecte PARK_INDEPENDENT_EVENTS avec le bloc on:
  DECLARE de l'ancre (lu via contents, +1 appel par ancre), plus les
  evenements observes rapportes sans valeur decisionnelle ; un bloc
  illisible met l'ancre en erreur vers UNKNOWN, jamais en faux
  NO_HOSTED_LEG. load_anchor_names lit aussi la forme bloc (cas C).
- point 2 : le step de l'advisory Linux mesure sur 1 run sur 4 (modulo
  en bash), jambe schedule toujours -- schedule seul ne mesurait que
  les jours ou le scheduler livre (#15332).
- point 3 : decomposition du cout reel (3 pages + 1 variable + 2 x
  (1+1+3) = 14, mesuree en passage reel), commentaire '~17 appels'
  remplace par la mesure.
- decouvert en passage reel : CI_OVERFLOW_LOCAL=true (posee 19:10:30Z,
  vague #23016) deborde Enrich-quality gate sur le parc -- l'organe
  lit la variable (actions/variables) et la nomme dans le verdict ; le
  step en fait un warning nomme, pas un rouge.

12 tests (7 nouveaux : cas A/B/C de la review, bloc illisible, mode
debordement, formes de on:, page 2) ; 57 verts avec la suite voisine.

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-overlap Advisory: another open PR touches the same files (organ #13615) variation-genre-cap-exceeded light_genre > cap partage G-VAR-2 (#10020, advisory) variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint) variation-tier-inflation declared LIGHT << effective LIGHT-genre (#10020, advisory)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants