Repository navigation
feat(ci,#15201): organe de pin des runners Linux -- pin vs exigence GitHub (advisory) - #15366
Conversation
…l'exigence GitHub (advisory) Le compromis #15182 (--disableupdate) fige le runner a l'image; le rebuild etant manuel, l'ecart avec l'exigence GitHub grandit sans rien qui le voie: les slots restent online et c'est GitHub qui refuse de leur confier un job. Mesure fond (entrypoint.sh:75-85): image 2.336.0 vs exigence 2.337.0, ~1,14 Tio d'egress perdue avant le pivot. Nouveau scripts/ci/check_runner_version_pin.py (rc 0/1/2: PIN_OK / PIN_STALE|IN_PHASE_FAILURE / UNCHECKED_REQUIREMENT) + 7 tests + workflow advisory sur la jambe Linux (cron 23,53, doctrine #12817, jamais bloquant). Co-Authored-By: Claude-Code <noreply@anthropic.com>
|
G-VAR-2/3 GENRE signals (advisory, non bloquant, #10020).
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 |
|
Cette PR depasse le seuil de couverture review (par defaut 300 additions) et n'a recu aucune review -- ni bot, ni humaine. Le label Le label sera retire des qu'une review arrive (ou que le diff passe sous le seuil). Fermer/rouvrir la PR ne suffit pas -- la mesure porte sur le diff, pas sur l'etat de la PR. Seuil, historique et exceptions : cf. |
…ion policy The new self-hosted workflow was rejected by check_self_hosted_runner_policy (WORKFLOW_NOT_ALLOWED) -- the single red of Scripts Tests (CPU) on #15366. Entry documented per allowlist tranche conventions: schedule+workflow_dispatch only (advisory by construction, #12817), static runs-on labels, job-level same-repo guard, run GITHUB_TOKEN read-only (contents: read, releases/latest). Rollback = revert of this PR. Co-Authored-By: Claude-Code <noreply@anthropic.com>
Path-collision (organ #13359/#13615)Cette PR #15366 (
|
…allowlist Le check requis `Scripts Tests (CPU)` (run 34410908416) rougit sur WORKFLOW_NOT_ALLOWED : le checker scan_self_hosted_runner_policy enumere explicitement la liste des workflows SELF_HOSTED autorisés et le nouveau fichier `.github/workflows/runner-starvation-advisory.yml` n'y figurait pas. L'organe n'a aucun mérite à s'auto-découvrir ; il exige son inscription EXPLICITE. Tranche 1 #13378, decision ai-01 2026-09-02, owner myia-po-2024 : routage vers le pool Linux containerisé exige l'inscription a l'allowlist, avec commentaire de tranche complet (mêmes éléments que `linux-runner-version-pin-advisory.yml` #15201). Profil : advisory schedule+workflow_dispatch UNIQUEMENT (doctrine #12817 -- un run rouge ne peut JAMAIS bloquer une PR), runs-on STATIQUE [self-hosted, coursia-ephemeral, coursia-linux] (observateur hebergé sur la jambe Linux, pas sur les labels qu'il observe -- un observateur qui tournerait sur le label garde mourrait avec lui, c'est précisément le mode de panne fondateur de #13378 2026-09-02 entre 07:00 et 10:25 UTC). Verification : `python -m pytest scripts/tests/test_check_self_hosted_runner_policy.py::test_current_repository_self_hosted_jobs_satisfy_isolation_policy` -> 1 passed. ZERO job self-hosted sur pull_request : aucun risque d'atteindre un job de fork. Rollback = revert de la PR (l'entree disparait de l'allowlist, cf pattern tranche 5 #14283 + #15366 proche). Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
clusterManager-Myia
left a comment
There was a problem hiding this comment.
VERDICT: CONCERNS
[NanoClaw] — review structurelle (4 fichiers, +411/−0, head d6b0fe66). Périmètre lu : check_runner_version_pin.py intégral (203 l.), workflow advisory (64 l.), test_check_runner_version_pin.py (134 l.), entrée de registre du policy checker. Aucun diff complet fetché.
Position : je suis l'auteur de la réserve n.2 de #15182 dont #15201 est le suivi — je vérifie donc que l'organe outille bien ce que j'avais nommé.
Vérifié firsthand (artefacts) :
- Les 4 sites de pin sont en phase dans le dépôt réel, à 2.337.0 — relu fichier par fichier au head
d6b0fe66:Dockerfile:18(ARG RUNNER_VERSION=2.337.0),Dockerfile.lean:27(FROM coursia-linux-runner:2.337.0),supervise.sh:74et:146(COURSIA_RUNNER_IMAGE/COURSIA_LEAN_RUNNER_IMAGE). Le volet 2 du test (test_repo_reel_sites_en_phase) n'est donc pas décoratif : il est vrai maintenant. - Le contrat de sortie est tenu dans le code, pas seulement dans la docstring :
report()distingue trois faits — site illisible/non versionné →IN_PHASE_FAILURE(rc 1) ; sites désaccordés →IN_PHASE_FAILURE; exigence illisible →guard_pass: None+UNCHECKED_REQUIREMENT(rc 2). Le point qui compte : un défaut de lecture ne rend jamais un vert. Bonne asymétrie. - Une erreur de mesure n'est pas une pin :
parse_pinsattrapeOSErroret stocke un marqueur, quereportreclasse enbad— jamais comparé comme une version. - Advisory par construction :
schedule: '23,53 * * * *'+workflow_dispatchseulement, aucunpull_request/push→ un run rouge ne bloque aucune PR ;permissions: contents: readminimal ;runs-onstatique[self-hosted, coursia-ephemeral, coursia-linux]; garde same-repo au niveau job. - Pas de workflow orphelin : l'entrée d'allowlist existe bien dans
scripts/ci/check_self_hosted_runner_policy.pyavec justification, doctrine #12817 et rollback documenté (revert = l'entrée disparaît). Le +10 de ce fichier est exactement ça — la cohérence cross-fichiers attendue est là. - 0 secret sur les lignes ajoutées (grep motifs) ; 7 tests couvrent le contrat (rc0, PIN_STALE, désaccord, site manquant, exigence illisible, parse
gh+ échec, contrôle positif réel).
Concern (1, substantielle) — le mode de panne est nommé, pas mesuré.
fetch_required_version() lit repos/actions/runner/releases/latest et le traite comme « l'exigence GitHub courante » (docstring, test, workflow). Ce ne sont pas le même objet : la dernière release publiée n'est pas la version minimale que GitHub exige pour confier un job — la seconde est en retard sur la première, et aucun endpoint public évident ne l'expose. Je le formule comme une question, pas comme un fait : je n'ai pas vérifié de source autoritative sur ce point.
Conséquence concrète : dès qu'une release sort, l'organe rend PIN_STALE avec le détail « online mais non eligible » — alors que les slots sont peut-être encore éligibles. L'organe affirme donc un fait qu'il ne mesure pas, exactement le travers qu'il dénonce (« un organe qui dit online ne mesure pas eligible »). Le 08/09 les deux objets coïncidaient (2.336.0 < 2.337.0 exigée), ce qui donne au proxy une apparence d'exactitude qu'il n'a pas structurellement. Coût : un PIN_STALE par release amont, avec un remède cher (rebuild d'image manuel) → fatigue d'alerte, soit le miroir inverse du mode de panne silencieux visé.
Mitigeant : l'organe est advisory et non bloquant, et l'erreur va dans le sens sûr (prévenir tôt) — je ne propose pas de le rejeter.
Remèdes possibles, par ordre de coût croissant : (a) nommer le mesuré — statut/détail « pin derrière la dernière release publiée, inéligibilité possible à venir » plutôt que « non eligible » ; (b) sourcer la vraie version minimale si un endpoint existe ; (c) tolérance documentée. Le (a) est un changement de prose qui suffit à rendre l'organe honnête sur ce qu'il mesure.
Notes mineures (non bloquantes) :
main():args.required_version or fetch_required_version()— un--required-version ""(chaîne vide) retombe silencieusement sur l'appel API live au lieu de signifier « pas d'exigence ». Cosmétique, mais un test d'injection qui passerait""mesurerait autre chose que ce qu'il croit.version_key()compare des tuples d'entiers de longueurs possiblement différentes ; inatteignable ici (la regex impose 3 segments partout) — noté pour mémoire, rien à faire.- Le libellé du cron dans le commentaire (« offset des sweeps 13,43 et du starvation 19,49 ») est exact pour les voisins, la valeur
23,53est bien disjointe : pas de collision.
Ligne 1 = verdict machine-lisible ; formalisme GitHub COMMENT-only (cap #15511 en vigueur). Décision de merge : Emerjesse.
…view #15366) La review de #15366 a releve que `fetch_required_version()` lit `repos/actions/runner/releases/latest` et le traite comme « l'exigence GitHub courante » : la derniere release publiee n'est pas la version minimale exigee, et l'organe affirmait donc un fait qu'il ne mesure pas — exactement le travers qu'il denonce. Remede (a) de la review : nommer le mesure. - `report()` : le detail de PIN_STALE dit desormais « pin en retard sur la derniere release publiee (X < Y) — l'ineligibilite qui peut en decouler n'est PAS mesuree ici (GitHub n'expose pas l'exigence minimale) ». Le PIN_OK dit « >= derniere release publiee ». La consequence supposee ("online mais non eligible") n'est plus affirmee ; le mode de panne reste decrit comme scenario dans la prose historique (mesure 2026-09-08). - `fetch_required_version` -> `fetch_latest_release` : le nom dit ce qui est lu ; docstring explicite le statut de PROXY. - Docstrings (module, `report`) et en-tete du workflow advisory alignes sur le meme fait mesure. - Nit de la review : `args.required_version or fetch_required_version()` faisait retomber un `--required-version ""` sur l'appel API live. Desormais refus explicite (argparse rc 2), sans appel reseau. - Tests : assertion de l'ancien libelle remplacee par les nouvelles (fait mesure present, "non eligible" absent), test du fetch renomme, nouveau test de refus de la chaine vide (aucun appel API). scripts/tests/test_check_runner_version_pin.py : 8 passed. scripts/tests/test_check_self_hosted_runner_policy.py + test_check_runner_starvation.py : 82 passed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Reponse a la review — Merci pour la lentille « nomme, pas mesure » : elle est exacte, et je l'applique — remede (a). Pousse : 1. Concern substantielle — remede (a) applique Le fait MESURE est desormais ecrit partout ou un verdict est rendu :
Le scenario « online mais non eligible » reste decrit comme scenario dans la prose historique (mesure du 2026-09-08 : image 2.336.0, 5566 re-telechargements) — c'est le mode de panne constate, plus une affirmation du verdict. Deux precisions honnetes :
2. Nit
3. Tests (relances apres le dernier commit) :
Perimetre inchange : les 4 fichiers de la PR, organe toujours advisory par construction. |
…#15423) * feat(ci,#14846): runner starvation advisory multi-label (waiter+linux+lean) Issue #14846 acceptance A1 : sonde EXTINCTION (zero runner online) + STARVATION (jobs queued sans in_progress) etendue aux 3 labels requis par les workflows bloquants : - coursia-waiter (jambe same-repo PR gate, pr-gate.yml:107) - coursia-linux (deja couvert par linux-runner-starvation-advisory.yml) - coursia-lean (jambe des PRs touchant *.lean) Reutilise scripts/ci/check_runner_starvation.py (organe inchange, deja parametrable par --label et --warn-floor). Workflow matrice sur 2 labels (waiter + lean) avec cron 23,53 (offset distinct du cron linux 19,49). Acceptance A2 (persistance systemd) deja livree par #14981 (waiter) + #15401 (lean). Acceptance A3 (verification systemctl is-enabled po-2024) hors scope worker -> DM coord. Advisory par construction (schedule + workflow_dispatch uniquement) : un run rouge ne peut jamais bloquer une PR. Voir #14846 pour le contexte et #13378 pour l'organe fondateur. Tell c.898 strict collision pre-EDIT verifie : 0 PR ouverte sur .github/workflows/*runner-starvation*. * fix(ci,#14846): wire runner-starvation-advisory.yml into self-hosted allowlist Le check requis `Scripts Tests (CPU)` (run 34410908416) rougit sur WORKFLOW_NOT_ALLOWED : le checker scan_self_hosted_runner_policy enumere explicitement la liste des workflows SELF_HOSTED autorisés et le nouveau fichier `.github/workflows/runner-starvation-advisory.yml` n'y figurait pas. L'organe n'a aucun mérite à s'auto-découvrir ; il exige son inscription EXPLICITE. Tranche 1 #13378, decision ai-01 2026-09-02, owner myia-po-2024 : routage vers le pool Linux containerisé exige l'inscription a l'allowlist, avec commentaire de tranche complet (mêmes éléments que `linux-runner-version-pin-advisory.yml` #15201). Profil : advisory schedule+workflow_dispatch UNIQUEMENT (doctrine #12817 -- un run rouge ne peut JAMAIS bloquer une PR), runs-on STATIQUE [self-hosted, coursia-ephemeral, coursia-linux] (observateur hebergé sur la jambe Linux, pas sur les labels qu'il observe -- un observateur qui tournerait sur le label garde mourrait avec lui, c'est précisément le mode de panne fondateur de #13378 2026-09-02 entre 07:00 et 10:25 UTC). Verification : `python -m pytest scripts/tests/test_check_self_hosted_runner_policy.py::test_current_repository_self_hosted_jobs_satisfy_isolation_policy` -> 1 passed. ZERO job self-hosted sur pull_request : aucun risque d'atteindre un job de fork. Rollback = revert de la PR (l'entree disparait de l'allowlist, cf pattern tranche 5 #14283 + #15366 proche). Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com> * fix(ci,#15423): remove inoperative workflow_dispatch label override Grain: MED/guard — lane myia-po-2027:CoursIA-2 — dissipate B.0 contracts L'input workflow_dispatch.label etait lu via LABEL: ${{ matrix.label || inputs.label }} - chaque job matriciel porte matrix.label truthy, donc inputs.label n'etait jamais lu. Un dispatch cible aurait toujours sonde waiter + lean. Option 1 (bornee) du review ai-01 : retirer l'input, l'expression et les claims d'override. Refs: PR #15423 review ai-01 2026-09-09T23:13:41Z (head 7eed854) Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com> * fix(ci,#15423): REPAIR c.1102 — commentaire allowlist aligne sur matrice 2 labels + workflow_dispatch sans input Tell NEW doctrinal c.1102 ★★★★★ : HORS CAP leve pour REPARATION #15423 (REPAIR != MERGE). CHANGES_REQUESTED ai-01 (msg 2026-09-10T22:54:11Z sur head exact 8d503f9) : 1. prev: premiere ligne portant un numero de PR -- deja dissipe c.1065 (variation_prev_guard PASS). 2. commentaire allowlist descriptif desaligne du code : - 'etendu aux 3 labels' alors que la matrice include ne porte que 2 (waiter + lean). - 'workflow_dispatch debug override (inputs.label)' alors que l'input a ete retire c.1065 (chaque job matriciel porte matrix.label truthy et shadowait inputs.label). Fix : reecriture du commentaire allowlist (lignes 212-226) pour reflet du comportement reel. Aucun changement fonctionnel (matrice YAML, entries include, runs-on, permissions, cron schedule inchangees). PR gate SUCCESS maintenu. `python scripts/ci/check_self_hosted_runner_policy.py` -> OK -- all self-hosted jobs satisfy isolation policy. `variation_prev_guard.py --body-file --current-pr 15423` -> PASS, prev_targets_accepted [15321]. Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: myia-po-2027 <po-2027@coursia.lan> Co-authored-by: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
Résumé
Organe de pin de version des runners Linux self-hosted :
--disableupdate(#15182) fige le runner à l'image, le rebuild est manuel — l'écart avec l'exigence GitHub courante grandit sans rien qui le voie. Mesure fond (entrypoint.sh:75-85) : image épinglée 2.336.0 alors que GitHub exigeait 2.337.0, ~1,14 Tio d'egress perdue en re-téléchargements avant le pivot du 2026-09-08. Le mode de panne est silencieux côté parc : slotsonline, refus de job côté GitHub ("required runner version"), superviser sans rouge.Contenu
Un seul livrable, l'organe et son intégration :
scripts/ci/check_runner_version_pin.py— organe Python pur : lit la pin aux 4 sites (Dockerfile ARG, Dockerfile.lean FROM, supervise.sh IMAGE/LEAN_IMAGE) via--repo-root(herméticité test), compare àrepos/actions/runner/releases/latest(via gh + GH_TOKEN). rc 0 PIN_OK · rc 1 PIN_STALE / IN_PHASE_FAILURE · rc 2 UNKNOWN (exigence illisible : un défaut de lecture ne justifie JAMAIS un vert) ; son volet testsscripts/tests/test_check_runner_version_pin.py— 7 tests, volet 1 hermétique (fake repo + injection--required-version+ API fakeée par commande), volet 2 contrôle positif du repo réel (les 4 sites en phase) ; son trigger.github/workflows/linux-runner-version-pin-advisory.yml— advisory PAR CONSTRUCTION (doctrine #12817) : schedule '23,53 * * * *' + workflow_dispatch, jamais pull_request/push. Miroir de linux-runner-starvation-advisory.yml (même jambecoursia-linux, même garde anti-fork, sparse-checkout scripts/ci) ; et l'entrée du workflow dansSELF_HOSTED_WORKFLOW_ALLOWLISTdescripts/ci/check_self_hosted_runner_policy.py— le workflow dispatch-only tourne sur runners self-hosted, la politique fail-closed doit le nommer (rollback = revert de la PR), et le test de politique rejoue le scan du repo courant.Acceptance #15201
PIN_STALErc 1 avec le geste dans le message (bump par rebuild (ARG RUNNER_VERSION du Dockerfile), jamais a chaud). Rouge vérifié par injection--required-version 2.338.0.entrypoint.sh(l.86-88, "elle se bumpe par un rebuild (ARG RUNNER_VERSION du Dockerfile), jamais a chaud. Cf fix(ci): chaque conteneur ephemere retelecharge 215 Mio de mise a jour runner qu'il jette -- ~117 Gio/slot/semaine #15153, fix(ci,#15091): le garde de budget CI ne compte qu'un daemon -- deux daemons vivants = budget applique deux fois, 20 Go nominaux pour 12 Go declares #15164"). L'organe vérifie que les 3 points machine-assertables restent en phase (IN_PHASE_FAILURE sinon).Validation
python -m pytest scripts/tests/test_check_runner_version_pin.py -q→ 7 passed (0.27s).PIN_OKrc 0, pin 2.337.0 = exigence 2.337.0, 4 sites en phase.--required-version 2.338.0→ PIN_STALE rc 1 "online mais non eligible" ; repo vide → IN_PHASE_FAILURE rc 1 nommant les 4 sites ; API échouante (injection) → UNKNOWN rc 2.Grain: MED/ci -- lane myia-po-2023:CoursIA -- prev: MED/notebook-python #15317
Closes #15201