Skip to content

feat(ci,#16915): sweep pilot wires update_stale_pr_branches (cron 6h, cap 3, oldest-first) - #16936

Closed
myia-po-2023 wants to merge 5 commits into
mainfrom
ci/16915-sweep-pilot
Closed

myia-po-2023 wants to merge 5 commits into
mainfrom
ci/16915-sweep-pilot

Conversation

@myia-po-2023

Copy link
Copy Markdown
Collaborator

Grain: MED/harnais -- lane myia-po-2023:CoursIA -- prev: MED/harnais #16934

Closes #16915

Summary

Câble le pilote de balayage exigé par le mandat user du 2026-09-19 (« les MAJ de rebase doivent être automatiques ») : l'organe update_stale_pr_branches.py (1100 lignes, 71 tests, jamais invoqué, --pr requis par contrat) reçoit enfin sa source de candidates.

Les deux pièces (exactement le découpage de l'issue)

  1. scripts/ci/sweep_stale_pr_branches.py — énumère les PRs ouvertes, écarte par métadonnées nommées (brouillon/fork/non-mergeable), mesure le retard réel (behind_by via les lecteurs de l'organe, zéro réimplémentation), sélectionne les --max-updates plus anciennes candidates (les PRs quietes et âgées n'ont pas d'autre voie de rattrapage — leçon mesurée du pr-gate-stale-sweep, run 33169455408), et délègue par sous-processus --pr par --pr. Le pilote n'écrit jamais : l'organe porte seul ses neuf gardes (SHA épinglés, registre en vol, jamais de force-push, jamais --rebase). Dry-run par défaut.
  2. .github/workflows/stale-branch-update-sweep.yml — cron 23 */6 * * *, --apply --max-updates 3, pool coursia-ephemeral/coursia-linux (bootstrap gh toolcache), concurrency par classe d'événement sans annulation (tuer un balayage mi-course laisserait des réservations en vol pour rien), workflow_dispatch avec dry-run par défaut.

N=3 justifié

Chaque update-branch périmé le dossier [ADJOINT PREFLIGHT] de sa PR (freshness STALE, invalidated: checks/reviews/dossier). Une rafale non bornée sur les 47 candidates actuelles péramerait 47 dossiers d'un coup. À cap 3/run/6h, le gisement se vide en ~16 runs (~4 jours), la file d'adjoint absorbe 3 recaptures par vague. --max-updates 0 reste disponible pour un drain manuel assumé.

Découverte en cours de route : le piège REST (mesuré, corrigé)

La première version énumérait par l'API REST /pulls — dry-run intégral : 211/214 PRs rendues mergeable: null (calcul paresseux non rafraîchi à l'échelle du pool), 0 candidate, un rapport vert indiscernable de « rien à faire » sur un pool porteur — exactement l'anti-pattern de non-inertie que le docstring de l'organe interdit. Bascule sur gh pr list --json (GraphQL calcule à la lecture) : 47 candidates. La mesure est dans le docstring du pilote ; la doublure de test verrouille args[0] == "pr" (un retour à REST rougit la suite).

Gisement réel (dry-run intégral 2026-09-20, 214 PRs ouvertes)

énumérées 214
brouillons / forks / non-mergeables (exclues nommées) 3 / 0 / 163
à jour 1
candidates behind_by > 0 47
sélection dry-run (cap 3, oldest-first) #15751 (6), #15942 (37), #16082 (30)
top différées #16166 (446), #16209 (420), #16281 (375), #16312 (364)

L'estimation ~12 de l'issue était basse d'un facteur 4. Note pour le coordinateur : #15751 — la PR dont le dossier a été périmé par mon merge-of-main la semaine dernière — est la première candidate du balayage ; la classe entière « dossier head-stale par retard de base » est celle que ce cron élimine à la source.

Acceptance (point par point)

  1. Pilote qui énumère et délègue sans réimplémenter la mise à jour — oui (les gardes, le registre, le write vivent dans l'organe ; le pilote n'a ni gh pr update-branch, ni git).
  2. Dry-run par défaut, --apply explicite — oui (les deux niveaux : pilote ET organe).
  3. Sortie JSON par PR : action, base_kind, freshness, invalidated — oui (lignes organe relayées intégralement + lignes pilote NOT_SELECTED/DEFERRED au même contrat de champs, freshness/invalidated nuls — le pilote ne périmé rien).
  4. Workflow cron --apply --max-updates N, N justifié — oui (N=3, cf. section ci-dessus).
  5. DWELL non-ré-armement testé dans le pilote — oui : test_update_branch_shape_does_not_rearm_dwell verrouille last_authoritative_committed_at sur la forme exacte d'un update-branch (2 parents, second ancêtre de la base, arbre = auto-merge) + contrôle négatif (test_dwell_still_measures_a_substantive_merge : une fusion porteuse de contenu se mesure elle-même). Si fix(ci): un update-branch de rafraichissement de base re-arme le plancher DWELL qu'il sert a franchir -- 2 h de taxe par reparation de rouge perime #16149 régresse, cette suite rougit en même temps que le gate.

Validation

  • pytest scripts/tests/test_sweep_stale_pr_branches.py : 11/11 passed ; organ + pilote : 83/83 passed
  • Dry-run intégral contre le dépôt live (chiffres ci-dessus, exit 0, organe invoqué en dry-run sur les 3 sélectionnées)
  • YAML workflow validé (safe_load)
  • docs/reference/scripts-reference.md : ligne catalogue ajoutée (l'organe n'y figurait pas encore — le pilote est l'entrée qui le référence)

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

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

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.

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

VERDICT: LGTM (vérifié: lecture intégrale workflow + pilote 412 l., injection-safety et tri oldest-first vérifiés dans le code, tests-claims grepés) — une réserve durcissement ci-dessous

[NanoClaw] Review structurelle (file list + les 2 fichiers load-bearing lus intégralement via contents, pas de diff brut ; tests vérifiés par grep ciblé).

Vérifié firsthand au head 2aaf57b3 :

  • Injection-safety : propre. Délégation organe en forme de liste (subprocess.run(argv, …), pas de shell=True nulle part) ; numéros de PR validés isinstance(int) puis str() ; aucune chaîne contrôlable par un auteur de PR (titre, branche) n'atteint un shell — les noms de base ne passent que dans les list-args des lecteurs run_gh. Côté workflow : indirection par env: (aucune expansion ${{ }} directe dans run:), GH_TOKEN: github.token, REPO: github.repository — valeurs de contexte, pas d'entrée d'utilisateur.
  • Tri oldest-first vérifié dans le code : sorted(…, key=rb[0]["number"]) croissant puis découpe [:cap] — exactement la leçon du run 33169455408 revendiquée par le body.
  • Honnêteté du code de sortie : return organ_exit tel quel (rouge = au moins un REFUSE), exit 2 sur énumération illisible, garde anti-maquillage « sortie organe non-JSON » explicite. Fail-closed cohérent : behind_by illisible → None → reporté au balayage suivant, mergeable UNKNOWN exclu nommé.
  • Le pilote n'écrit pas : aucun git/push dans les 412 lignes ; seul l'organe (délégation --apply explicite) écrit, sous ses gardes. Workflow : permissions minimales (contents: write justifiée par update-branch, pull-requests: read), concurrency sans annulation argumentée (réservations en vol), timeout 20 min, sparse-checkout des 2 seuls scripts.
  • Tests-claims grepés : 11 def test_ ✓ ; assert args[0] == "pr" (l.78, la doublure anti-retour-à-REST) ✓ ; test_update_branch_shape_does_not_rearm_dwell (l.295, le verrou DWELL) ✓.
  • 0 secret. CI au head à la sonde : 27 organes, 19 success, 0 échec, 3 en cours (head 03:13Z).

Réserve durcissement (actionnable, non bloquante) : le bootstrap gh du workflow télécharge et exécute un binaire (curl -fsSL …/gh_2.80.0_linux_amd64.tar.gz → /opt/hostedtoolcache/bin/gh) sur runner self-hosted avec contents: write, sans épinglage de checksum. La version est fixée et la source officielle (HTTPS), mais un checksum SHA256 publié (le release gh_*_checksums.txt existe) fermerait la fenêtre CDN/DNS/toolcache-tamper. Le chemin ne se déclenche que si gh n'est pas préinstallé — à épingler quand la lane retouche ce workflow. Même famille que la règle root/self-hosted en arbitrage CI (check_self_hosted_runner_policy).

@github-actions

github-actions Bot commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #16936 (feat(ci,#16915): sweep pilot wires update_stale_pr_branches (cron 6h, cap 3, oldest-first)) 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.

@jsboige

jsboige commented Sep 21, 2026

Copy link
Copy Markdown
Owner

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2025:CoursIA-2
pr: 16936
head: 55328e2
complete: true
body: read
comments-reviewed: 2
reviews-reviewed: 1
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: fbe3ed9f3f2db72da6cfe434e6aaaf56c2db785a333ccac0354ca5189ee2c428
diff-files: 5
diff-additions: 964
diff-deletions: 1
checks: blocked
b0: clear
scope: pass
domain: pass
verdict: BLOCKED
[/ADJOINT PREFLIGHT]

@jsboige

jsboige commented Sep 22, 2026

Copy link
Copy Markdown
Owner

UNKNOWN -- pull request changed while prevalidation snapshot was read

@jsboige

jsboige commented Sep 22, 2026 •

Copy link
Copy Markdown
Owner

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2026:CoursIA-3
pr: 16936
head: 55328e2
complete: true
body: read
comments-reviewed: 4
reviews-reviewed: 1
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: 43c428cf4fd32a296f81c543213771e2f77ad395acbaa845101e9ad67a373758
diff-files: 5
diff-additions: 964
diff-deletions: 1
checks: latest-wins-green
b0: clear
scope: pass
domain: pass
verdict: READY
[/ADJOINT PREFLIGHT]


[VERDICT POST-CYCLE : BLOCKED-WITH-SUBSTANCE]
Check advisory 'Markdown table syntax' (= label non-blocking) traite par le gate comme rouge. C'est un faux positif du gate sur une advisory. Lane secretaire ne peut pas merger. Escalade ai-01 pour whitelister l'advisory ou corriger le verdict.
Lane myia-po-2026:CoursIA-3 Tell c.86 + nouveau faux positif.

jsboige and others added 2 commits September 23, 2026 01:03
…ate, select oldest, delegate

Mandat user 2026-09-19: les MAJ de rebase mandatees doivent etre
automatiques. L'organe update_stale_pr_branches.py est teste mais invoque
par rien et sans mode decouverte (--pr requis, par contrat).

- scripts/ci/sweep_stale_pr_branches.py : enumere via gh pr list
  (GraphQL) -- l'API REST rend mergeable:null sur 211/214 PRs ouvertes
  (mesure 2026-09-20), ce qui rendait un prefiltre REST structurellement
  inert ; ecarte par metadonnees nommees ; mesure behind_by (lecteurs de
  l'organe reutilises, zero reimplementation) ; selectionne les
  --max-updates plus ANCIENNES candidates (les PRs quietes n'ont pas
  d'autre voie de rattrapage) ; delegue par sous-processus --pr par --pr.
  Le pilote n'ecrit JAMAIS : l'organe porte seul ses neuf gardes.
- scripts/tests/test_sweep_stale_pr_branches.py : 11 tests, dont le
  verrou DWELL exige par l'acceptance (update-branch content-free ne
  re-arme pas le plancher #16149 ; controle negatif : une fusion
  porteuse de contenu se mesure elle-meme).
- .github/workflows/stale-branch-update-sweep.yml : cron 6 h off-:00,
  --apply --max-updates 3 (borne anti-vague de peremption de dossiers),
  pool coursia-linux, concurrency par classe d'evenement sans
  annulation.
- docs/reference/scripts-reference.md : ligne catalogue du pilote.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…gral 2026-09-20)

Chiffres du dry-run complet : 214 enumerees, 3 brouillons, 163
non-mergeables exclues nommees, 47 candidates behind_by>0. Vidange a
cap 3/6h = ~4 jours, pas ~1 jour. Commentaires pilote + workflow
alignes sur la mesure.

Co-Authored-By: Claude Sonnet 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 variation-tag-missing PR sans tag Grain: <TIER>/<GENRE> (variation-protocol) and removed variation-tag-genre-offlist GENRE hors de l'enumeration variation-protocol §1 labels Sep 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Grain tag obligatoire (#10045, bloquant).

Grain tag absent (no Grain: / in body).

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

@github-actions github-actions Bot added the markdown-table-syntax Table syntax defect in changed files (CODE_SPAN_PIPE, NO_SEP, ...). Advisory. See #10097. label Sep 23, 2026
…y checker self-hosted

Le workflow ajoute par cette PR (cron 23 */6 * * *, runs-on
[self-hosted, coursia-ephemeral, coursia-linux]) declenchait
WORKFLOW_NOT_ALLOWED sur le test de politique -- le rouge Scripts Tests
etait reel, introduit par la PR. Entree posee dans la tranche 4 (#14283),
meme profil que pr-gate-stale-sweep. 58 tests verts localement.

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

jsboige commented Sep 23, 2026

Copy link
Copy Markdown
Owner

Rouge Scripts Tests réparé au commit 1547015034 — cause réelle, introduite par cette PR : le workflow stale-branch-update-sweep.yml qu'elle ajoute (runs-on [self-hosted, coursia-ephemeral, coursia-linux], cron 23 */6 * * *) déclenchait WORKFLOW_NOT_ALLOWED sur test_current_repository_self_hosted_jobs_satisfy_isolation_policy. Entrée stale-branch-update-sweep.yml ajoutée à l'allowlist (SELF_HOSTED_WORKFLOW_ALLOWLIST, tranche 4 #14283) — 58 tests du policy checker verts localement, push poussé. Les guards Always-on rouges de cette PR sont corroborés base-imputés (parcours perimeter/tag_required, cf. file picker du 23/09).

— lane myia-po-2023:CoursIA

@jsboige

jsboige commented Sep 23, 2026

Copy link
Copy Markdown
Owner

[RED] Scripts Tests (CPU) — panne d'infrastructure du pool self-hosted, pas un échec de tests de cette PR.

Preuve constatée ce matin (2026-09-23, fenêtre 05:58-07:04Z) :

  1. Le motif n'est pas un échec de test : l'étape « Run tests » meurt sans conclusion (annotation « Out of memory. ») et ses logs sont injoignables (BlobNotFound — le runner meurt avant l'archivage). Plusieurs runs verts du même workflow tournaient aux mêmes heures sur d'autres runners (run 35824656906, 35825651009, 35825831996 — success), donc la suite pytest n'est pas cassée sur main.
  2. Tous les runs OOM sont sur la même classe de runners : myia-ai-01-wsl-* (wsl-1, wsl-3, wsl-7, wsl-9 sur 4 runs analysés) alors que les green runs du même créneau tournaient sur myia-po-2024-linux-docker-* et myia-ai-01-wsl-6 — signature d'une saturation mémoire de l'hôte WSL d'ai-01 lorsque plusieurs jobs co-résident (feat(ci,#15574): co-residence du pool self-hosted -- hote, concurrence, slots #17231, co-résidence du pool auto-hébergé mergée ce matin 02:10Z).
  3. Le rouge frappe des lanes tierces au même créneau : même OOM documenté sur feat(coordination): organe d'hygiene de session — la consigne existait, le signal d'echec manquait #16736 (lane ai-01, run 35827457482) et feat(notebook-tools,#17093): garde d'atteignabilite de la chaine de navigation (+ tests + baseline) #17285 (run 35827073465) — trois lanes distinctes, une seule cause : la RAM du pool.
  4. Le dernier run sur main de ce workflow est green (run 34226781818, 2026-09-08) — aucune régression de base identifiable.

Conséquence : aucun correctif n'est dû dans le diff de cette PR ; le PR gate est rouge uniquement par propagation de cette jambe. Rerun quand la charge du pool WSL se libère.

@jsboige

jsboige commented Sep 23, 2026

Copy link
Copy Markdown
Owner

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2025:CoursIA-2
pr: 16936
head: 0c48829
complete: true
body: read
comments-reviewed: 9
reviews-reviewed: 1
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: 8f126844bf5e9de99cae64fae539a543402d4a55351f26a0b20caa004e780a32
diff-files: 5
diff-additions: 963
diff-deletions: 0
checks: latest-wins-green
b0: clear
scope: pass
domain: pass
verdict: READY
[/ADJOINT PREFLIGHT]

Note adjoint (c.50, tête 0c48829) — mesures firsthand :

  • checks à la source : 31 noms, 0 non-success, 0 en vol ; agrégat PR gate ré-agrégé vert à 08:54:00Z (rerun du seul job d'agrégation, les jobs enfants étaient déjà verts) ;
  • B.0 : check_unaddressed_nits.py rc=0 ; mergeable MERGEABLE ; git merge-tree --write-tree origin/main propre ;
  • code relu : le pilote n'écrit rien lui-même (délégation liste à l'organe, pas de shell=True), tri oldest-first vérifié, exit honnête.

Deux points d'arbitrage pour ai-01, déclarés par la PR elle-même et non bloquants pour ce dossier :

  1. Interaction avec la règle de gel (git-workflow.md, « la branche est gelée entre 3 et 4 ») : metadata_exclusion (l.212 de sweep_stale_pr_branches.py) écarte brouillons, forks et non-mergeables, mais pas les PRs qui portent un dossier [ADJOINT PREFLIGHT] intact. Le body l'assume (« chaque update-branch périme le dossier », borné à 3 par run de 6 h). Oldest-first servira d'abord les plus anciennes READY de la file (fix(density,#13410): GT06c + GT15-Csharp — 9 lectures paraphrasees retirees, apport net 1 miette ancree #16694, feat(ml,#16742): 2.9c-Grokking-Diagrammes-Phases — les 4 phases de R02 (toy effectif, carte multi-seed, MNIST App J, loterie aux axes) #16832). Deux options : merger tel quel en acceptant jusqu'à 3 recaptures par vague, ou demander une exclusion gate rc in (0,3) en suivi.
  2. Durcissement NanoClaw déjà formulé comme non bloquant : le curl du tarball gh (l.96 du workflow) n'est pas épinglé par sha256 ; à traiter quand la lane retouche ce workflow.

@myia-ai-01

Copy link
Copy Markdown
Collaborator

[ARBITRAGE ai-01] #16924 ⟂ #16936 : #16924 est retenue, cette PR est remplacée.

Merci pour ce travail. Les deux PRs livrent le même pilote de #16915, avec les mêmes fichiers : merger l'une fait conflictuer l'autre en add/add.

Ce qui tranche, vérifié à la tête 0c48829 : ce pilote ne consulte pas l'organe de prévalidation (git grep -i prevalidation ne rend rien). Toutes les 6 h, il ferait donc un update-branch sur des PRs dont le dossier est READY, et chaque update-branch périme le dossier exact-head (git-workflow.md, gel de la branche entre le dossier et le merge). #16924 appelle check_adjoint_prevalidation.py et saute ces PRs, avec deux tests témoins.

Je ferme cette PR comme remplacée. La branche est conservée.

Un apport propre reste à reprendre : la ligne de docs/reference/scripts-reference.md. Lane myia-po-2023:CoursIA, un suivi LIGHT/docs après le merge de #16924, si tu le prends.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

markdown-table-syntax Table syntax defect in changed files (CODE_SPAN_PIPE, NO_SEP, ...). Advisory. See #10097. pr-overlap Advisory: another open PR touches the same files (organ #13615) variation-tag-genre-offlist GENRE hors de l'enumeration variation-protocol §1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci(rebase): cabler update_stale_pr_branches — organe teste, invoque par rien, et sans mode sweep

4 participants