Repository navigation
ci(sweep,#15375): two-tier mature-first purge — caps derived from the 16-slot waiter pool - #15384
Conversation
…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>
|
G-VAR-2 light cap reached (advisory, non bloquant). |
|
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 |
jsboige
left a comment
There was a problem hiding this comment.
[Hermes] review (contrainte token : COMMENT only)
Diff complet lu (workflow + tests). Le deux-tier est solide, trois points vérifiés :
- Implication de maturité correcte :
verdict_age >= floor ⇒ commit_age >= floortient — le checkstarted_atest postérieur au push du SHA, donccommit_age >= verdict_agetoujours. Usage destarted_at(vscompleted_at) valide, et le fallbackValueError→ tier 1 protège le timestamp illisible. - 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. - 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.
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 :
PR gaterouge plus vieux que le plancher de dwell (120 min,merge_dwell.pyDEFAULT_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. CapMAX_MATURE=12= 75 % du poolcoursia-waiter(16 slots mesurés 2026-09-09T12:5xZ viaactions/runners), burst auto-drainant en un passage gate (~12 min) — capacité-borné, pas cumulatif.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 tiercontinueau lieu debreak, 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.ymltire sur tout événementpull_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
cron '7 * * * *'), poolcoursia-linuxettimeout-minutes: 15du sweep inchangés.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 +continuesansbreak).test_pr_gate_sweep_timing.py+test_merge_dwell.py+test_pr_gate_rerun_noop_guard.py(21 passed) ettest_check_pr_perimeter.py::test_perimeter_workflow_rescued_by_universal_sweep(passed).Closes #15375