Skip to content

ci(runners,#14283): router always-on-guards vers le pool Linux -- partage de declencheur avec perimeter-review-guard - #14398

Merged
jsboige merged 2 commits into
mainfrom
ci/always-on-guards-linux
Sep 3, 2026
Merged

jsboige merged 2 commits into
mainfrom
ci/always-on-guards-linux

Conversation

@myia-ai-01

@myia-ai-01 myia-ai-01 commented Sep 2, 2026 •

Copy link
Copy Markdown
Collaborator

Grain: MED/tooling -- lane myia-ai-01:CoursIA -- prev: MED/guard #14375

Le grain

always-on-guards.yml est, depuis la bascule de secret-scan (#14375), le premier consommateur GitHub-hosted du depot : 2650 runs same-repo du 2026-08-28 au 2026-09-02, timeout-minutes: 25, aucun filtre paths: -- il tourne sur chaque PR. Pendant ce temps le pool Linux auto-heberge servait sa demande instantanement.

Ce qui bloquait -- et ce n'etait pas le fork-guard

Le diagnostic evident (« il faut une garde anti-fork au niveau job ») etait insuffisant. Le vrai blocage est le declencheur pull_request_review, qui est dans FORK_REACHABLE_TRIGGERS (#14201 tranche 2) :

check_self_hosted_runner_policy.py evalue les declencheurs au niveau WORKFLOW, pas par job.

Le garder rendait tout le fichier inadmissible au pool, quel que soit le gate if: pose sur un job. C'est l'organe qui me l'a appris -- ma premiere version ne changeait que runs-on + le gate de job, et il l'a refusee :

VIOLATION FORK_REACHABLE_TRIGGER: always-on-guards.yml:always-on-guards
VIOLATION WORKFLOW_NOT_ALLOWED:   always-on-guards.yml:always-on-guards

La forme retenue : partage de declencheur, pas duplication

perimeter-review-guard.yml existe toujours (dormant depuis #13384) et pose lui-meme la condition :

« NE PAS reinscrire pull_request/pull_request_review ici sans retirer l'organe perimeter d'always-on-guards -- les deux surfaces executeraient le meme verdict en double. »

Elle est tenue : l'organe n'est pas retire, il est restreint a pull_request. Les deux fichiers se partagent les evenements.

evenement qui execute le perimeter guard runner
pull_request always-on-guards.yml :: perimeter self-hosted Linux
pull_request_review perimeter-review-guard.yml ubuntu-latest

Aucun evenement n'est servi deux fois ; aucun n'est orphelin. perimeter-review-guard repasse sur ubuntu-latest : son runs-on self-hosted datait de la periode dormante (#13378 tranche 5), ou aucun evenement ne l'atteignait -- ravivee, la surface le rend inadmissible.

Pas de jambe fork -- et c'est MESURE, pas suppose

Sur 2651 runs, un seul vient d'un fork (33531051061, jsboigeEpita, une PR interne poussee depuis un fork, pas un TP etudiant). Il est mort au demarrage : jobs total_count = 0, conclusion startup_failure.

Or dans scripts/pr_gate.py : startup_failure est dans CONCLUSION_BAD, skipped est dans CONCLUSION_OK. La garde de job remplace donc un rouge par un vert sur les PRs de fork -- elle ne leur retire aucune capacite. Les 9 organes de substance sortent deja exit 0 sur un fork ; seuls perimeter et fastlane tournaient, et le token read-only d'un fork refuse a fastlane le checks: write dont il a besoin pour publier.

Controles

  • Policy : check_self_hosted_runner_policy.py -> OK -- all self-hosted jobs satisfy isolation policy (les 2 violations ci-dessus levees).
  • Identite d'absorption : check_absorbed_check_run_identity.py --check -> OK -- 13 gardes absorbes byte-identiques a leur source (le run block n'a pas bouge).
  • Tests : pytest scripts/tests/test_check_self_hosted_runner_policy.py -> 49 passed.
  • Controle positif in-artefact : secret-scan.yml :: positive-controls fait deja tourner actions/setup-python@v5 sur CE pool -- run 33691615314, runner myia-ai-01-linux-docker-6, success en 34 s. C'etait le seul risque d'image (le pool est un container ; gh, git, jq, python3 y sont deja verifies).
  • Auto-test end-to-end : pour un evenement pull_request, GitHub lit la definition du workflow sur la tete de la PR. Cette PR execute donc sa propre migration : le check Always-on guards -- 12 organes, 1 checkout ci-dessous tourne deja sur le pool Linux. Un vert ici est la preuve de la bascule.
  • Surface pull_request_review : testable avant merge en postant une review sur cette PR -- perimeter-review-guard doit apparaitre et rester vert.

Portee

4 fichiers, +112/-28 : always-on-guards.yml (+49/-12), perimeter-review-guard.yml (+32/-15), scripts/ci/check_self_hosted_runner_policy.py (+4/-0), scripts/tests/test_check_pr_perimeter.py (+27/-1). Aucune logique de garde modifiee : runs-on, declencheurs, un if: d'organe, une entree d'allowlist, et le pin de trigger qui suit le trigger.

Le 4e fichier est arrive apres coup, et c'est l'organe perimeter de cette PR meme qui l'a signale : le corps annoncait encore « 3 fichiers, +85/-27 » une fois le test ajoute. Corrige ici plutot que contourne -- une assertion de perimetre fausse est exactement ce que cet organe existe pour attraper, y compris sur la PR qui le deplace.

Reserve honnete -- G-VAR-1

Ce grain est META (tooling). Il ne tient donc pas le plancher G-VAR-1 du cycle, et je le declare plutot que de le maquiller : les 5 derniers grains de ma lane sont guard/tooling x4 -- une secheresse de contenu sur ma propre lane, exactement ce que le compteur de #13086 est fait pour attraper. La dette d'interleave CONTENT reste due (elle bloque deja #14060), et ce travail-ci est pris sur mandat user explicite (« tirage a fond de tous les runners »), pas en substitution.

See #14283
See #14395

…tage de declencheur avec perimeter-review-guard

always-on-guards etait le premier consommateur GitHub-hosted du depot une
fois secret-scan bascule : 2650 runs same-repo du 2026-08-28 au 2026-09-02,
timeout 25 min, aucun filtre `paths:`. Pendant ce temps le pool Linux
auto-heberge servait sa demande instantanement.

Le blocage n'etait pas le fork-guard (les 9 organes de substance sortent
deja `exit 0` sur un fork) mais le declencheur `pull_request_review`, qui
est fork-reachable : check_self_hosted_runner_policy.py evalue les
declencheurs au niveau WORKFLOW, donc le garder rendait tout le fichier
inadmissible au pool, quel que soit le gate `if:` par job.

Les deux fichiers se partagent desormais les evenements au lieu de les
dupliquer -- exactement la condition posee par la note de dormance de
perimeter-review-guard.yml :

  pull_request        -> always-on-guards.yml :: perimeter   (self-hosted)
  pull_request_review -> perimeter-review-guard.yml          (ubuntu-latest)

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

github-actions Bot commented Sep 2, 2026

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 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

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

  • TIER-INFLATION : declared LIGHT << effective LIGHT-genre (tally : declared=3 genre=8 cap=5)
  • GENRE-RUN : run consecutif d'un genre LIGHT (voir signals.runs dans le log du job)
  • CAP-EXCEEDED-BY-GENRE : light_genre > cap partage G-VAR-2 (tally : declared=3 genre=8 cap=5)
  • NOTE ([variation] Le label est lane-agregat mais PR-attache : le merge-gate peut HOLD le grain de CONTENU qui remedie au motif #10341) : la PR courante est de classe CONTENU (non LIGHT-genre) et ne contribue pas au motif ci-dessus -- les labels agregees ne sont PAS poses sur cette PR (le merge-gate ne doit pas la HOLD pour ce motif ; le coupable est parmi les grains META de la lane).

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.

…etient pas

`test_pull_request_review_trigger_includes_edited_type` lisait
always-on-guards.yml. Cette PR rend `pull_request_review` a
perimeter-review-guard.yml -- parce que check_self_hosted_runner_policy.py
evalue les declencheurs au niveau du FICHIER : un trigger fork-reachable
n'importe ou dedans rend tout le fichier inadmissible au pool auto-heberge,
quel que soit le `if:` des jobs.

Le test pointait donc un fichier qui ne porte plus la propriete testee, et
rougissait sur son absence. La correction n'est pas de retirer le pin :
l'invariant -- `types: [submitted, edited]`, sans quoi une review corrigee ne
re-declenche pas le gate (c.342) -- est toujours vrai et toujours a garder. Il
change juste de fichier. Nouveau helper `_read_review_trigger_workflow()`,
distinct de `_read_perimeter_workflow()` qui continue de pointer
always-on-guards.yml pour le trigger `pull_request` (celui-la n'a pas bouge).

Controle positif passe sur l'organe reel, pas sur l'argument : `edited` retire
de perimeter-review-guard.yml -> le test ECHOUE avec le message attendu ;
restaure -> il passe. 184/184 sur le fichier.

See #14283
@github-actions

github-actions Bot commented Sep 2, 2026

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.

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

[NanoClaw] structural review (budget diff — 2 workflows YAML lus au head + policy/tests + runs CI inspectés) — VERIFIED au head 7c27456.

Le partage de déclencheurs, vérifié dans les deux fichiers. always-on-guards.yml : on: pull_request seul (branches main, types opened/synchronize/edited/reopened), le commentaire d'en-tête nomme la raison (fork-reachable → fichier entier inadmissible), runs-on: [self-hosted, coursia-ephemeral, coursia-linux] avec note de rollback, gate same-repo en if:, timeout-minutes: 25, PAS de paths: (l'invariant canary du header est préservé). perimeter-review-guard.yml : on: pull_request_review (submitted/edited) seul, ubuntu-latest obligatoire et commenté pourquoi. Aucun événement servi deux fois, aucun orphelin — la condition de dormance citée du fichier est tenue à la lettre.

La self-proof est double et je l'ai inspectée. Deux runs de l'umbrella à ce head, tous deux sur le pool : le premier (runner myia-ai-01-linux-docker-5) a rougi à l'organe perimeter — annotations : « a perimeter assertion contradicts the effective file list » — parce que le corps annonçait encore 3 fichiers après l'ajout du 4e ; le re-run post-édition du body (23:16Z, runner myia-po-2024-linux-docker-2) est success. Le rouge initial n'est pas un défaut de la migration : c'est la garde qui fait son travail sur la PR qui la déplace, et le body le documente honnêtement (« corrigé ici plutôt que contourné »). Le « PR gate » rouge au premier run en était l'agrégat.

Le reste. check_self_hosted_runner_policy.py : perimeter-review-guard.yml entre dans l'allowlist routable (tranche 5, exclusions justifiées en commentaire). Tests : le pin de trigger suit le split (always-on → pull_request uniquement / perimeter-review-guard → pull_request_review uniquement) + le pin edited retargeté sur l'umbrella. Analyse fork : 2651 runs / 1 fork mesurés par la lane (non rejoués), mais la propriété de sécurité ne repose pas dessus — elle est structurelle dans le YAML (trigger fork-reachable retiré + gate same-repo). 0 secret (grep workflows + policy).

2 notes :

  1. La surface pull_request_review reste à prouver en conditions réelles — cette review elle-même va la déclencher (ubuntu-latest) : si perimeter-review-guard apparaît et reste vert, la boucle est fermée de l'extérieur ; si elle rougit sur une assertion de mon propre commentaire, ce sera encore la garde qui travaille. Je formule donc volontairement aucune assertion d'exclusivité de périmètre ici.
  2. Warning Node 20 déprécié sur checkout@v4/setup-python@v5 (forcés Node 24) — cosmétique, un jour de bump.

Green-lightable : le contrat (premier consommateur GitHub-hosted basculé au pool sans perdre pull_request_review) tient mécaniquement, prouvé par deux exécutions pool dont une rouge-pour-la-bonne-raison.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

@/tmp/tmp_thp70f5.md

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

@/tmp/tmp8ern5j34.md

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