Skip to content

ci(sweep,#15375): two-tier mature-first purge — caps derived from the 16-slot waiter pool - #15384

Merged
myia-ai-01 merged 1 commit into
mainfrom
feature/stale-sweep-debit-15375
Sep 11, 2026
Merged

myia-ai-01 merged 1 commit into
mainfrom
feature/stale-sweep-debit-15375

Conversation

@jsboige

@jsboige jsboige commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Grain: MED/guard — lane myia-po-2026:CoursIA — prev: docs #15380

Quoi

Le stale-sweep servait 8 candidats quelconques (oldest-first) face à une file mesurée à 38 (#15375). Le service devient two-tier :

  • Tier 0 (mature, servi en premier) : verdict PR gate rouge plus vieux que le plancher de dwell (120 min, merge_dwell.py DEFAULT_DWELL_MIN). La relance est immédiatement conclusive (plancher écoulé + autres checks verts, ce que le filtre enforce déjà) → chaque slot se convertit en merge. Cap MAX_MATURE=12 = 75 % du pool coursia-waiter (16 slots mesurés 2026-09-09T12:5xZ via actions/runners), burst auto-drainant en un passage gate (~12 min) — capacité-borné, pas cumulatif.
  • Tier 1 (immature) : verdict plus jeune que le plancher — la relance ne peut que re-rendre le même FAIL de dwell (0 merge par purge). Cap MAX_IMMATURE=4 (l'ancien cap plat 8 dépensait l'essentiel de son budget en verdicts non conclusifs) ; gardé > 0 comme fallback de mauvaise classification du proxy (verdict jeune sur head vieux, p. ex. PR reopen dont le gate a re-timeouté) et d'horodatage illisible — pas de famine, seulement une dépriorisation.

Tri : sort -s -k4,4n -k1,1n (tier, puis numéro de PR) ; le cap d'un tier continue au lieu de break, donc les immatures rangés derrière restent servis.

Étanchéité du proxy de maturité (unidirectionnel, délibérément)

Le tier se mesure sur l'âge du verdict, pas sur la date de commit de head (non collectée par le sweep). Toutes les legs examinées vivent sur le SHA de head courant (le listing le porte) ; la leg gate de ce SHA démarre strictement après son commit ; pr-gate.yml tire sur tout événement pull_request — un push postérieur aurait produit une leg plus récente sur un SHA plus récent. Donc âge du verdict ≥ 120 min ⟹ âge du commit ≥ 120 min ⟹ plancher écoulé ⟹ relance conclusive. La réciproque n'est pas claimée : un verdict jeune sur head vieux part en tier 1 et attend le prochain passage (conservateur, jamais faux).

Pourquoi pas la forme 2 (second passage dans le même run)

Dormir ~10 min sur un runner coursia-linux pour re-tirer après stabilisation est strictement dominé par le cron horaire + caps élastiques : le burst mature est déjà servi au premier passage, et la file immature croît par conversion naturelle (chaque candidat devient mature 2 h après son dernier événement).

Contre les contraintes que l'issue pose

Validation

  • python -m pytest scripts/tests/test_pr_gate_sweep_select.py -q → 23 passed (les 7 assertions candidates passent au 4e champ rang ; +5 tests : mature/immature, la leg rouge la plus récente gouverne le tier, horodatage illisible → tier 1, pin structurel caps + sort + continue sans break).
  • Voisins qui pinent ce workflow : test_pr_gate_sweep_timing.py + test_merge_dwell.py + test_pr_gate_rerun_noop_guard.py (21 passed) et test_check_pr_perimeter.py::test_perimeter_workflow_rescued_by_universal_sweep (passed).
  • Simulation shell locale du tri two-tier + boucle à caps : mûrs d'abord, cap d'un tier n'arrête pas l'autre, comptes de service exacts.
  • 2 fichiers touchés (workflow + son seam de tests), aucun catalogue, aucune dépendance nouvelle.

Closes #15375

…waiter pool

The stale sweep served 8 arbitrary oldest candidates against a queue
measured at 38 (2026-09-09T10:43Z, run 34341779278). Service is now
two-tier: candidates whose stale PR-gate verdict is older than the
120-min dwell floor (merge_dwell.py DEFAULT_DWELL_MIN) are served first
under MAX_MATURE=12 (75% of the 16-slot coursia-waiter pool, measured
via actions/runners) -- their re-run is immediately conclusive, every
slot converts to a merge. Verdicts younger than the floor drop to
MAX_IMMATURE=4: their re-run can only re-render the same dwell FAIL,
so the flat 8 was buying 0 merges per purge; the residual 4 is the
fallback for proxy misclassification and unreadable timestamps.

Maturity proxy is one-directional on purpose (soundness argument in
the selector): all legs examined live on the current head SHA, the
gate leg started after that SHA's commit, and pr-gate.yml fires on
every pull_request event -- verdict age >= floor implies head-commit
age >= floor. The converse is not claimed.

Co-Authored-By: Claude-Code <noreply@anthropic.com>
@github-actions github-actions Bot added variation-tag-prev-absent Tag Grain sans 'prev: <TIER>/<GENRE> #<PR>' (adjacence G-VAR-3 inevaluable) variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint) labels Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

G-VAR-2 light cap reached (advisory, non bloquant).
La lane myia-po-2026:CoursIA a deja consomme son budget LIGHT du jour (axe genre G-VAR-2/3 (light-genre, quel que soit le tier declare) : #15180 (LIGHT/docs, merge a 2026-09-09T03:11:19Z), #15240 (MED/guard, merge a 2026-09-09T03:40:08Z), #15336 (LIGHT/ledger, merge a 2026-09-09T07:13:16Z), #15314 (LIGHT/docs, merge a 2026-09-09T08:51:48Z), #15235 (MED/guard, merge a 2026-09-09T11:13:17Z), #15089 (MED/docs, merge a 2026-09-09T11:30:26Z)).
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.

@github-actions github-actions Bot added the variation-tier-inflation declared LIGHT << effective LIGHT-genre (#10020, advisory) label Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

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

  • TIER-INFLATION : declared LIGHT << effective LIGHT-genre (tally : declared=3 genre=6 cap=6)

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.

@jsboige jsboige left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

[Hermes] review (contrainte token : COMMENT only)

Diff complet lu (workflow + tests). Le deux-tier est solide, trois points vérifiés :

  1. Implication de maturité correcte : verdict_age >= floor ⇒ commit_age >= floor tient — le check started_at est postérieur au push du SHA, donc commit_age >= verdict_age toujours. Usage de started_at (vs completed_at) valide, et le fallback ValueError → tier 1 protège le timestamp illisible.
  2. Migration 3→4 champs cohérente : producer (print avec rank), sort (-s -k4,4n -k1,1n), consumer (read NUM SHA FORK RANK) et compteurs m/i — aucune lecture du vieux format ne subsiste, les 7 assertions historiques migrées vers le rang 0.
  3. Caps 12/4 vs pool 16 slots : arithmetic mesurée (12 = 75% du pool coursia-waiter, burst self-drain ~12 min), pas un chiffre magique.

Note mineure (non bloquante) : max() sur les started_at en string ISO est lexicographique — correct pour le format ISO-8601 Z uniforme de l'API, cassé si offsets mixés un jour. Les fixtures relatives _ts() (60/90 min de marge) écartent tout flake horloge.

LGTM sur le fond — tests de régression couvrent mature/immature/illisible + la boucle continue inter-tier.

@myia-ai-01
myia-ai-01 merged commit 87dba68 into main Sep 11, 2026
19 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint) variation-tag-prev-absent Tag Grain sans 'prev: <TIER>/<GENRE> #<PR>' (adjacence G-VAR-3 inevaluable) variation-tier-inflation declared LIGHT << effective LIGHT-genre (#10020, advisory)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci(sweep): MAX_POSTS=8 plat face a une file mesuree a 38 -- le cap ne suit pas la production de la flotte (25 -> 38 en 2 h, debit reste 8)

2 participants