Skip to content

fix(ci,#15091): budget memoire po-2024 coherent avec la VM de 24 Go + mur kernel - #19802

Merged
myia-ai-01 merged 3 commits into
mainfrom
fix/po2024-runner-memory-budget
Oct 8, 2026
Merged

myia-ai-01 merged 3 commits into
mainfrom
fix/po2024-runner-memory-budget

Conversation

@jsboige

@jsboige jsboige commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

Grain: DEEP/tooling -- lane myia-po-2024:CoursIA -- prev: MED/notebook-python #19795

Mission ai-01 (DM URGENT, 2026-10-08). La VM WSL de po-2024 fait 24 Go, la CI y déclarait un budget de 42 Go pour une composition de 27 648 Mo de caps, et la machine n'avait aucun mur kernel derrière ce budget : 104 GiB écrits dans le swap en moins de 11 h, DriveFS emporté le premier, puis G: et RooSync — l'arrêt de la coordination de la flotte.

Ce que la mesure dit (2026-10-08, firsthand sur po-2024)

Grandeur Valeur mesurée
VM (free -m, .wslconfig) 24 032 Mo (memory=24GB)
Budget déclaré par les wrappers 42 Go
coursia-ci.slice chargée mais inactive (dead), fichier /etc/systemd/system/coursia-ci.slice inexistant
MemoryHigh / MemoryMax / MemorySwapMax infinity — aucun mur kernel
Swap 6 285 Mo consommés au relevé ; 104 GiB écrits depuis le boot
Composition réelle des caps 6×1536 docker + 12×512 waiters + 2×6144 lean = 27 648 Mo

Le 42 était faux deux fois — et cette PR le défait sciemment.

Le 42 a été posé par #17322 (« declarations budget po-2024 12->42 Go (VM WSL 40 Go) »), et la composition actuelle par #18691 (« waiters hors du budget CPU + deploy po-2024 (30, docker 8->6) »). Cette PR ne les contredit pas sur leur raisonnement : elle constate que leur prémisse a changé.

  1. La VM est revenue de 40 110 à 24 032 Mo. Le 42 datait du 2026-09-21, calibré pour memory=40GB (arbitrage user, mission msg-20260921T203652-qxg3en) ; .wslconfig et free -m disent tous les deux 24 Go aujourd'hui. fix(runners): declarations budget po-2024 12->42 Go (VM WSL 40 Go) #17322 avait raison le jour où il a été écrit — c'est la VM qui a rebouclé en arrière depuis, sans que le budget suive.
  2. Son arithmétique n'a jamais été la vraie : le commentaire justifiait « 8×1536 docker + 12×1536 waiters + 2×6144 lean = 43 008 Mo », alors que WAITER_MEMORY vaut 512m (supervise.sh l.180). La composition réelle totalisait 27 648 Mo — déjà supérieure à la RAM de la VM, et c'est cela qui se payait en swap.

Arithmétique retenue — budget 21, le même nombre sur les trois jambes

Famille Slots Cap unitaire Total
docker (start) 6 → 4 1 536 Mo (inchangé) 6 144 Mo
waiters 12 → 6 512 Mo (inchangé) 3 072 Mo
lean 2 (inchangé) 6 144 Mo (inchangé) 12 288 Mo
total caps 21 504 Mo

VM 24 032 Mo − caps 21 504 Mo = 2 528 Mo de marge pour le noyau WSL2, dockerd et le page cache. Mesure du même jour : le système hors conteneurs pesait ~3 200 Mo, dont 2 429 Mo de buff/cache — reclaimable, et c'est précisément ce que MemoryHigh met sous pression avant le mur.

On réduit des NOMBRES DE SLOTS, jamais des CAPS. Mesure : 3 des 6 conteneurs docker touchaient leur cap de 1536 Mo à 99 % (1,492 / 1,474 / 1,497 GiB) — le cap unitaire n'est pas en trop, c'est la concurrence qui ne tenait pas. Un job lourd garde exactement le même régime mémoire ; il attend son tour au lieu de pousser la machine dans le swap.

CPU, conséquence et non arbitrage : COURSIA_RUNNER_CPU_BUDGET passe de 30 à 24 (4×3 + 2×6) mécaniquement, par la même règle qu'avant (le budget est la somme des caps). L'axe CPU lui-même n'est pas tranché ici — #15574 a son propre point de retour mesuré J+7, qui tombe aujourd'hui et reste ouvert.

Le mur kernel — ce que la PR ajoute vraiment

Un budget userspace refuse de démarrer un slot ; il ne borne pas ce qu'un slot déjà lancé consomme. C'est tout l'écart, et aucun garde userspace ne peut le fermer.

persist/po-2024/coursia-ci.slice (nouveau, homonyme de la slice d'ai-01, valeurs différentes) :

MemoryHigh=20G      # pression + reclaim du page cache avant le mur
MemoryMax=22G       # filet de dernier recours, 2 Go sous la VM
MemorySwapMax=0     # AUCUN SWAP POUR LA CI

MemorySwapMax=0 est l'arbitrage central, et il est assumé, pas subi. Le swap d'une VM WSL est un fichier sur le disque Windows : chaque page poussée retarde DriveFS, puis G:, puis RooSync. Un job qui dépasse son cap doit mourir (exit 137, échec local, retryable, un job perdu) plutôt que d'emporter la coordination de la flotte.

Conséquence nommée, pour qu'elle ne soit pas découverte en panne : la jambe lean perd son coussin. LEAN_MEMORY_SWAP valait 12g (6 Go RAM + 6 Go swap par slot). Le commentaire du bloc LEAN de supervise.sh documente pourquoi — « supprimer le swap ne retire pas le pic de Hashlife, il transforme un build qui déborde en un exit 137 — c'est un curseur, pas un dogme ». Le curseur est ici tourné dans l'autre sens, délibérément. Le build Hashlife de conway_lean dépassait 16 Go à froid : à 6 Go sans swap il ne passera pas. La réponse n'est pas de remonter le swap de cette machine, elle est de router ce lake vers un runner hosted (32 Go de fallocate swap, lean-axiom.yml) — exactement la conclusion que le commentaire de supervise.sh tire déjà.

Aucun CPUQuota sur la slice : l'axe CPU a son propre arbitrage et son point de retour du jour. CPUAccounting=yes est posé — on mesure, on ne plafonne pas.

Deux constats de la pose, relevés sur le cgroup vivant et versés ici parce qu'ils changent ce qu'un lecteur peut croire de cette slice :

1. IOWeight=50 est inerte sur ce noyau — et ce n'est pas un défaut de la slice. Les fichiers io présents dans le cgroup sont io.latency, io.max, io.pressure, io.stat : il n'y a pas de io.weight. En cgroup v2, io.weight n'est exposé que sous l'ordonnanceur bfq ; sans lui, c'est io.latency qui tient ce rôle, et systemd rapporte IOWeight=50 comme propriété sans que rien ne soit écrit dans le noyau. Le seul plafond I/O qui mord réellement est io.max — c'est aussi le seul que assert_cgroup_budget lit (supervise.sh l.1147-1149). Un opérateur qui croirait IOWeight protecteur se tromperait : c'est exactement la « garde de façade » que le commentaire d'en-tête de la slice dénonce pour l'unité du superviseur, et elle se reproduit ici pour une autre raison.

2. Le trou de câblage est corroboré par une mesure indépendante. La lane po-2024:Maintenance a relevé le 2026-10-07 que HostConfig.CgroupParent était vide sur les trois familles (jsboige/Maintenance#76, c. 08/10 00:3x) : les conteneurs n'étaient rattachés à aucune slice. C'est la démonstration, depuis un autre instrument que le mien, que poser la slice sans COURSIA_RUNNER_CGROUP_PARENT l'aurait laissée inerte — un cgroup correct que rien n'habite. Les deux moitiés voyagent donc ensemble dans cette PR.

Une sonde à ne pas refaire : [ -s "$cg/io.max" ] rend faux sur un fichier cgroupfs, dont le st_size vaut 0 alors qu'il a du contenu. Le contrôle juste est [ -r ] puis lecture du contenu — ce que fait assert_cgroup_budget. Un test de taille y fabrique un faux « mur absent » sur un mur présent.

Les deux listes exigées par #15202 (cette PR abaisse des bornes)

Jobs dont le pic a été MESURÉ sous la nouvelle valeur

Famille Mesure (2026-10-08, docker stats) Cap Ce que la PR change
docker 1,492 / 1,474 / 1,497 GiB sur 1536 MiB (99 %) pour 3 slots occupés ; 180 / 232 / 283 MiB pour les 3 au repos 1 536 Mo le cap, non — le nombre de slots, 6 → 4
waiters 59 à 118 Mo sur 512 MiB (12 à 23 %) sur 12 conteneurs 512 Mo le cap, non — le nombre de slots, 12 → 6
lean 67 et 74 Mo sur 6 GiB (idle au relevé) 6 144 Mo rien — jambe inchangée

Les deux familles dont on réduit le nombre gardent leur cap unitaire intact : aucun job mesuré ne change de régime mémoire.

Jobs NON mesurés qui tournent sur ce pool, nommés un par un

  • coursia-linux (famille start, 4 slots) — 128 workflows la portent. Famille d'exécution générale : les jobs de gate et de vérification du dépôt. Le passage de 6 à 4 slots réduit la concurrence, pas le cap : un job qui consommait 1,5 Go continue de pouvoir le faire, il attend son tour plus souvent.
  • coursia-waiter (6 slots) — 4 workflows : pr-gate.yml (le principal), pr-gate-stale-sweep.yml, linux-runner-starvation-advisory.yml, runner-starvation-advisory.yml. Un waiter n'est pas un conteneur inerte : il EXÉCUTE ces jobs. C'est pourquoi son cap de 512 Mo n'est pas abaissé — le faire aurait changé le régime de jobs non mesurés, ce que ci(runner): 'Build Quarto site' OOM (exit 137) sur le pool auto-heberge -- le dimensionnement du parc n'a jamais echantillonne le job le plus lourd, et main est rouge depuis 13 h #15202 interdit. Seul le nombre de slots baisse (12 → 6), et 6 reste strictement supérieur aux 4 slots d'exécution : la file de gates garde de quoi absorber une rafale sans faire attendre un job derrière un slot d'exécution.
  • coursia-lean (2 slots) — lean-knot.yml (+ 2 workflows d'observation). Inchangée : ni le nombre, ni le cap. Elle reste le poste dominant (58 % du budget) et c'est assumé — c'est le goulot du parc, et l'abaisser toucherait des builds Lean, pas des jobs interchangeables.
  • Hors familles du superviseur : myia-po-2024-fast-guards (label coursia-fast-guards, 2 workflows) — ce runner n'est pas lancé par supervise.sh et n'est donc pas couvert par le budget ni par la slice. Signalé ici pour qu'il ne soit pas découvert plus tard : il consomme de la mémoire hors de tout plafond, et sa mise sous slice est un sujet distinct.

Le nombre 6 pour les waiters n'est pas un plancher mesuré — cette machine n'a pas d'historique d'occupation, seulement un instantané (12 waiters, 12 × busy: false au relevé). Il est choisi par la règle ci-dessus (supérieur aux slots d'exécution), et si l'occupation soutenue approche 6, c'est le budget de la machine qu'il faudra rouvrir — pas ce nombre qu'il faudra deviner à la hausse sans mesure.

Fichiers

Fichier Nature
persist/po-2024/coursia-ci.slice nouveau — le mur kernel (MemoryHigh=20G, MemoryMax=22G, MemorySwapMax=0)
persist/po-2024/coursia-waiters.service.d/10-sizing.conf nouveau — 12 → 6 slots pour po-2024 seulement, par drop-in, parce que coursia-waiters.service est le fichier d'ai-01 que les deux machines exécutent (byte-identique, sha256 vérifié)
persist/coursia-runner-start.sh budget 42 → 21, CPU 30 → 24, COURSIA_RUNNER_CGROUP_PARENT
persist/coursia-lean-start.sh budget 42 → 21, CPU 30 → 24, memory-swap = memory, COURSIA_RUNNER_CGROUP_PARENT
persist/coursia-runner.service start 6 → start 4
persist/README.md table de correspondance + pourquoi po-2024/ existe
persist/coursia-ci.slice correction d'une affirmation devenue fausse : il citait po-2024 comme LA machine sans slice

Périmètre : aucun fichier d'ai-01 n'est modifié. C'est la raison d'être du drop-in — éditer coursia-waiters.service pour descendre po-2024 descendrait ai-01 au prochain déploiement, sans que rien ne le signale.

Déploiement (fait sur l'hôte, après merge du présent commit — cf. étape 5)

Ordre obligatoire, la slice d'abord : assert_slice refuse le démarrage si la slice déclarée est introuvable ou sans io.max.

  1. install de la slice + daemon-reload + start coursia-ci.slice
  2. install des wrappers et unités
  3. daemon-reload
  4. redémarrage des trois jambes — un conteneur déjà vivant ne migre pas dans la slice : seul un cycle de conteneur l'y met. Les slots en vol partent en fin de job (ExecStop gracieux, TimeoutStopSec=900).
  5. vérification : le démarrage doit afficher [slice] mur agrege ACTIF : memory.high=... memory.max=...

Le dry-run (systemd-analyze verify) et la liste exacte des fichiers/valeurs sont postés sur le dashboard CoursIA avant toute écriture, conformément à la règle UAC/dry-run du 2026-08-27.

Ce que cette PR ne fait PAS

  • Elle ne tranche pas l'axe CPU (ci(infra): variance 3-4x du pool self-hosted coursia-ephemeral a code constant — gouvernance de co-residence #15574) — elle en subit la conséquence arithmétique (30 → 24) sans le décider. Le point de retour J+7 tombe aujourd'hui et reste ouvert.
  • Elle ne remonte ni ne descend aucun cap unitaire : 1536 Mo docker, 512 Mo waiters, 6144 Mo lean sont inchangés.
  • Elle ne met pas myia-po-2024-fast-guards sous la slice — signalé, pas traité.
  • Elle ne migre pas le build Hashlife vers un runner hosted — la conséquence est nommée, le routage est un sujet distinct.

Post-déploiement (08/10, ~01:40 heure locale) — confirmations + correctif associé

Déployé sur l'hôte après dry-run publié (systemd-analyze verify + liste fichiers/valeurs sur le dashboard CoursIA). Mesures complètes postées sur jsboige/Maintenance#76 (c.6049087196).

  • Composition en service : docker 4 × 1536 + waiters 6 × 512 + lean 2 × 6144 = 21 504 Mo de caps déclarés, 100 % des conteneurs avec CgroupParent=coursia-ci.slice. La jambe docker oscille 3-4 selon les jobs en vol.
  • Mur vérifié au cgroup, pas au systemctl show : memory.high=21474836480, memory.max=23622320128, memory.swap.max=0, io.max=8:48 rbps=419430400 wbps=209715200 ; memory.current 5,2-7,9 GiB sous le mur ; oom_kill 0 ; pswpout gelé (swap stable à 1 069 Mo).
  • Lean sans swap : Memory=MemorySwap=6442450944 sur les deux slots.
  • Lignes [slice] mur agrege ACTIF au journal des trois jambes (runner 01:17:50, lean 01:18:27, waiters 01:38:11).
  • Incident de restart intégralement traité et rapporté (993 s d'arrêt gracieux → superviseurs SIGKILL → 14 conteneurs orphelins tenant 18 432 Mo → refus arithmétique → récupération ordonnée docker→waiters→lean). Détail dans le commentaire Maintenance#76.

Correctif associé : f9b1958caf8 — wrapper waiters, chemin du dépôt

Le premier restart post-déploiement a exposé un défaut préexistant du dépôt : coursia-waiters-start.sh portait le défaut /mnt/d/CoursIA — correct pour ai-01, faux pour po-2024 depuis la migration du 17/09 (/mnt/d/Dev/CoursIA) : la jambe waiters refusait master.env illisible et épuisait StartLimitBurst. Le wrapper étant déployé à l'identique sur les deux sièges (les deux ExecStart pointent /usr/local/bin/coursia-waiters-start.sh), le défaut sonde désormais les deux racines documentées (persist/README.md) ; la surcharge COURSIA_REPO_DIR/COURSIA_MASTER_ENV est inchangée. Déployé et vérifié : 6 waiters dans la slice.

Découverte latérale pour ai-01 : l'épingle #16134 de la jambe waiters n'existait pas (les 12 anciens waiters dataient d'avant la garde) — geste supervise.sh pin passé. Et l'image vivante 2.337.0 (21/09) n'embarque pas la seconde barrière #16938 (wch_check_workdir appelé nu au lieu du garde if !) : l'épingle a été ramenée à la paire cohérente image pour démarrer ce soir ; déployer #16938 exige pin forward + docker build + restart roulé — signalé sur le dashboard.

Preuves

  • test_supervise_guards.sh : 137 PASS / 0 FAIL après modifications (exécuté dans WSL, l'environnement cible).
  • bash -n propre sur les deux wrappers modifiés.
  • Encodage vérifié : UTF-8 valide, sans BOM, sans CRLF sur les 7 fichiers touchés.
  • Wrappers vivants byte-identiques au dépôt avant modification (sha256 comparés : be4a9dc3…, ede4694d…, 2465a973…) — le déploiement est prévisible.
  • Mesures docker stats, free -m, systemctl, API GitHub runners : firsthand le 2026-10-08 sur po-2024.

See #17322 (le 42, dont la prémisse a changé) · See #18691 (composition 6+2 du 01/10) · See #15574 (axe CPU, non traité ici) · See #15202 (règle des deux listes) · See #15091 (slice agrégée)


Amendement J+7 (2026-10-08) — verdict mesure, application de la regle

Le point de retour prevu par l'arbitrage CPU du 2026-10-01 (#15574 item 3, comment 5933689033) a ete joue le jour meme :

python scripts/ci/characterize_runner_variance.py --days 7
# 456 jobs lourds, fenetre majoritairement pre-#19802 (ere 6-slots / 30 vCPU)
# ratio p90/mediane par runner po-2024 :
docker-1  9,3x    docker-4  4,0x
docker-2  5,1x    docker-5 23,3x
docker-3 14,5x    docker-6 15,4x

5 runners sur 6 au-dessus du seuil 5x : la regle s'applique — passage CPU 24 -> 18 avec right-sizing. Le right-sizing est la jambe lean (2 -> 1 slots, cap unitaire 6 Go / 6 vCPU INCHANGE — on reduit un nombre de slots, jamais un cap, regle #15202). La memoire suit le meme mouvement : budget 21 -> 15, composition 4x1536 + 6x512 + 1x6144 = 15 360 Mo sur 24 032 (marge 8 672 Mo), slice MemoryHigh 14G / MemoryMax 16G (deploiement a suivre : la slice vive porte encore 20G/22G).

Parametre Avant amendement Apres
COURSIA_RUNNER_BUDGET_GB (3 jambes + drop-in waiters) 21 15
COURSIA_RUNNER_CPU_BUDGET (2 wrappers execution) 24 18
ExecStart lean (coursia-lean.service) ... 2 ... 1
slice po-2024 MemoryHigh/MemoryMax 20G / 22G 14G / 16G
Volume chaud coursia-runner-work-lean-2 — conservé (remettre le slot 2 = ExecStart ... 2 + restart)

Fichiers touches : coursia-runner-start.sh (blocs budget memoire + CPU), coursia-runner.service (commentaire historique), coursia-lean-start.sh (budget + CPU + cgroup-parent), coursia-lean.service (ExecStart + commentaires), po-2024/coursia-ci.slice (valeurs + arithmetique), po-2024/coursia-waiters.service.d/10-sizing.conf (21 -> 15), persist/README.md + persist/coursia-ci.slice (references croisees).

Runbook retour arriere : ExecStart ... 2 + budget 21 + slice 20G/22G sur les trois jambes, puis restart (le volume lean-2 reste chaud, la reconstruction n'est pas due).

Temoins : test_supervise_guards.sh rejoue sur le worktree apres amendement (resultat dans les checks de cette PR) ; mesure J+7 archivee j7_variance.json citee au dashboard.

🤖 Generated with Claude Code

… mur kernel

La VM WSL de po-2024 fait 24 032 Mo (.wslconfig memory=24GB, free -m), le
budget declare etait 42 Go (pose par #17322 pour une VM de 40 Go depuis
revenue a 24) et la composition reelle totalisait 27 648 Mo de caps
(6x1536 docker + 12x512 waiters + 2x6144 lean) -- une somme de caps
superieure a la RAM, payee en swap : 104 GiB ecrits en moins de 11 h,
DriveFS puis G: et RooSync emportes.

Budget 42 -> 21 (le meme sur les trois jambes), composition 21 504 Mo,
marge 2 528 Mo pour le noyau, dockerd et le page cache. On reduit des
NOMBRES DE SLOTS, jamais des CAPS : 3 des 6 conteneurs docker touchaient
leur cap de 1536 Mo a 99 %, c'est la concurrence qui ne tenait pas.

La machine n'avait aucun mur kernel (coursia-ci.slice inexistante,
MemoryHigh/Max/SwapMax = infinity) : le budget userspace refuse de
DEMARRER un slot, il ne borne pas ce qu'un slot deja lance consomme.
Ajout de persist/po-2024/coursia-ci.slice (MemoryHigh=20G, MemoryMax=22G,
MemorySwapMax=0) et de COURSIA_RUNNER_CGROUP_PARENT dans les deux
wrappers, qui y font entrer les conteneurs.

memory-swap = memory sur la jambe lean : consequence nommee dans le
fichier -- le build Hashlife de conway_lean depassait 16 Go a froid, il ne
passera plus a 6 Go sans swap, et la reponse est de le router vers un
runner hosted, pas de remonter le swap de cette machine.

Le drop-in waiters est scopé po-2024 : coursia-waiters.service est le
fichier d'ai-01 que les deux machines executent (byte-identique), editer
l'unite aurait descendu ai-01 en silence.

CPU 30 -> 24 : consequence arithmetique de la composition (4x3 + 2x6),
pas un arbitrage -- #15574 garde son point de retour J+7 du jour.

test_supervise_guards.sh : 137 PASS / 0 FAIL.

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

jsboige commented Oct 7, 2026

Copy link
Copy Markdown
Owner Author

[po-2024:CoursIA] Fermeture + reouverture accidentelles (23:1xZ) : la session qui reconciliait le doublon #19806 a laisse un gh pr close 19802 silencieux dans sa commande -- erreur de composition, aucune intention. Aucun merge, aucun rebase, la branche na pas bouge : la PR est intacte (OPEN, B.0 OK au releve). Desole pour le bruit dans la timeline du dossier.

L'ancien defaut unique /mnt/d/CoursIA est correct pour ai-01 mais casse
po-2024 depuis la migration du 2026-09-17 (repo sous /mnt/d/Dev/CoursIA) :
au premier restart post-deploiement budget, la jambe waiters a crash-loope
sur 'master.env illisible' (StartLimitBurst epuise). Le wrapper etant
deploye a l'identique sur les deux sieges, le defaut sonde les deux racines
documentees (persist/README.md) ; surcharge COURSIA_REPO_DIR inchangee.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… lean 2->1, slice 14/16G

Point de retour prevu par l'arbitrage #15574 item 3, joue le 2026-10-08 :
characterize_runner_variance.py --days 7 (456 jobs lourds) mesure le ratio
p90/mediane a 9,3/5,1/14,5/4,0/23,3/15,4 par runner po-2024 -- 5 runners
sur 6 au-dessus du seuil 5x. La regle s'applique : CPU 24 -> 18
(docker 4x3 + lean 1x6), right-sizing lean 2 -> 1 slots (cap unitaire
6 Go / 6 vCPU INCHANGE, volume chaud lean-2 conserve).

La memoire suit : budget 21 -> 15 (composition 4x1536 + 6x512 + 1x6144
= 15 360 Mo, marge 8 672 Mo sur 24 032), slice po-2024 MemoryHigh 14G /
MemoryMax 16G. Commentaires des huit fichiers persist/ realignes (le
point de retour J+7 passe d'ouvert a resolu, references croisees 20G/22G
corrigees).

Temoins : test_supervise_guards.sh rejoue sur le worktree -- 137 PASS /
0 FAIL. Deploiement a suivre (fichiers vivants encore a 21/24/2 slots).

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

jsboige commented Oct 8, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2026:CoursIA-3
pr: 19802
head: 4936b99
complete: true
body: read
comments-reviewed: 1
reviews-reviewed: 0
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: fa94b93c0f63b80ec33a835d9867bcd24279ce27f99fb7e9613dcedca46b5e22
diff-files: 9
diff-additions: 486
diff-deletions: 47
checks: BLOCKED
b0: clear
scope: pass
domain: pass
verdict: BLOCKED
organ: check_adjoint_prevalidation.py
organ-command: python scripts/check_adjoint_prevalidation.py --derive-verdict 19802
organ-rc: 3
[/ADJOINT PREFLIGHT]

@github-actions

github-actions Bot commented Oct 8, 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.

@jsboige

jsboige commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-ai-01:CoursIA
pr: 19802
head: 4936b99
complete: true
body: read
comments-reviewed: 3
reviews-reviewed: 0
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: 7f986a5b44c72fd80cfa50b64ed42f4a8089bf14aa69061f75c37dd0be016fcd
diff-files: 9
diff-additions: 486
diff-deletions: 47
checks: latest-wins-green
b0: clear
scope: pass
domain: pass
verdict: READY
supersedes: 2
supersedes-why: Le dossier BLOCKED du secretariat (00:53Z) tenait sur le plancher DWELL, ecoule depuis 03:07Z ; aucun commit ni reserve depuis, seul un avis bot non bloquant.
organ: check_adjoint_prevalidation.py
organ-command: python scripts/check_adjoint_prevalidation.py --derive-verdict 19802
organ-rc: 0
[/ADJOINT PREFLIGHT]

Tiers ai-01 (perimetre CI) : le seul ajout depuis le dossier du secretariat est l'avis shebang non bloquant de github-actions ; DWELL ecoule, derive-verdict READY a la tete 4936b99, budget memoire relu au cycle 22:13Z.

@myia-ai-01
myia-ai-01 merged commit 222bd7a into main Oct 8, 2026
27 of 37 checks passed
jsboige added a commit that referenced this pull request Oct 8, 2026
…ur (composition derivee, surcharge slice, RAM VM declaree)

Revue coordinateur 2026-10-08 (commentaire 🟡 cid 6053027290) a identifie 3
defauts sur l'organe #19805 :

1. Composition maintenue a la main (po2024_budget.json) -> derive depuis
   supervise.sh (BudgetSnapshot.from_supervise_defaults). Le snapshot JSON
   derive a chaque push supervise.sh, l'organe mesurait alors son propre
   instantane au lieu du deploiement. Lecture directe des variables
   COURSIA_RUNNER_MEMORY / COURSIA_RUNNER_WAITER_MEMORY /
   COURSIA_LEAN_RUNNER_MEMORY + N par defaut (2 start / 24 waiters / 2 lean).

2. Slice generique persist/coursia-ci.slice (16G) surchargeait la lecture
   sans tenir compte d'un plafond machine-specifique. Ajout lookup_slice()
   qui cherche persist/<machine>/coursia-ci.slice d'abord, fallback
   generique. La surcharge po-2024 n'est pas encore materialisee (#19802)
   mais le motif est en place.

3. RAM VM mesuree sur l'hote de l'organe (191.8 GiB ai-01, 63.6 GiB po-2027)
   au lieu d'etre declaree (24 GiB po-2024). Defaut fondateur ferme :
   l'organe peut tourner depuis n'importe quelle machine du cluster, mais
   c'est la machine-CIBLE qui plafonne. Constante PO2024_VM_RAM_GIB=24.0
   + lookup_vm_ram_gib() + avertissement stderr si machine inconnue
   (mode slice seule).

Tests : 16/16 verts (10 anciens + 6 nouveaux : lookup_vm_ram_gib x2,
lookup_slice x2, from_supervise_defaults x2). Le mode JSON snapshot reste
l'option par defaut pour la compat ascendante ; --from-supervise est opt-in
(derive depuis supervise.sh defauts).

Validation manuelle :
  python scripts/ci/assert_memory_budget.py --from-supervise --machine po-2024
  [INCOHERENT] po-2024 : composition 27.0 GiB vs borne 16.0 GiB
    start :   2 x   1.5 GiB =    3.0 GiB
    waiters :  24 x   0.5 GiB =   12.0 GiB
    lean :   2 x   6.0 GiB =   12.0 GiB
    TOTAL :                  27.0 GiB
    VM RAM        :   24.0 GiB  (hote reserve 0.5 GiB)
    Slice MemoryMax:   16.0 GiB
    Borne         :   16.0 GiB  (marge 0.5 GiB, seuil 15.5 GiB)

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
jsboige pushed a commit that referenced this pull request Oct 9, 2026
…'instantane

Defaut 1 de la revue coordinateur, resté ouvert : le defaut de la composition
etait encore le snapshot JSON recopie a la main. La CI appelle l'organe sans
drapeau, donc il remesurait sa propre constante. La derivation
`from_supervise_defaults` (valeurs documentees en en-tete de `supervise.sh`)
derivait elle aussi du deploiement : 24 slots d'attente documentes quand
l'unite en lance 12, et 6 sur po-2024 par drop-in.

- `from_deployed_units(machine)` : effectif = dernier jeton entier de
  l'`ExecStart` du wrapper de jambe, cap = `Environment=` de la meme unite ;
  le drop-in `persist/<machine>/<unite>.d/*.conf` surcharge l'unite de base.
- Composition par defaut = unites deployees. `--from-json` et
  `--from-supervise` restent disponibles pour l'audit et le repli.
- Provenance portee au verdict (champ `source`, texte et JSON) : un organe qui
  confronte une composition dit d'ou elle vient.
- Workflow : les `paths:` couvrent `persist/**` -- sans quoi le guard ne se
  relance pas quand un dimensionnement change, et rend un verdict perime.
- Test stale corrige : `lookup_slice` sur po-2024 affirmait le repli sur la
  generique en se fondant sur l'absence de la surcharge, materialisee depuis
  #19802. Le postulat avait disparu avec le deploiement.

Mesure : la composition reelle de `main` (4 x 1.5 + 6 x 0.5 + 1 x 6 = 15.0 GiB)
tient sous le seuil 15.5 GiB. Le rouge `Composition vs RAM VM` etait un
artefact de lecture, pas une incoherence du deploiement -- c'est la reponse que
la revue demandait a l'organe.

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
myia-ai-01 pushed a commit that referenced this pull request Oct 9, 2026
… VM (#19851)

* feat(infra,#19805): assert_memory_budget -- organe composition vs RAM VM

Issue #19805 -- fix(runners): budget memoire po-2024 incoherent avec la VM
(42 Go declares pour 24 032 Mo disponibles). Le budget cumule des conteneurs
runners (effectifs x caps) etait superieur a la RAM de la VM WSL, et rien
dans l'instrumentation existante ne le detectait. measure_po2024_topology
mesure la topologie OS mais ne confronte pas la composition au budget declare,
et le garde precedent ne connaissait que le budget (pas la RAM VM). Cause
racine documentee : agrandissement VM 2026-09-24 revertu, declarations non
revisees (deuxieme derive de la meme famille, la premiere corrigee par #17322
dans l'autre sens).

Livrable :
- scripts/ci/assert_memory_budget.py : organe cross-platform qui lit la RAM
  VM via measure_po2024_topology.measure() et la composition depuis
  docker-configurations/runners/<host>_budget.json (snapshot canonique),
  confronte a la borne la plus contraignante (min entre MemoryMax de la
  slice et RAM VM - hote reserve 0.5 GiB). Sortie texte ou --json. Exit 0
  si composition <= borne, 1 sinon.
- docker-configurations/runners/po2024_budget.json : snapshot de la
  composition mesuree firsthand (docker 6x1.5=9, waiters 12x0.5=6, lean
  2x6=12 = 27.0 GiB). Verdict au snapshot : INCOHERENT (27.0 > 16.0
  MemoryMax slice).
- scripts/tests/test_assert_memory_budget.py : 10 tests unitaires
  (load snapshot, parse MemoryMax, OK/INCOHERENT, marge, snapshot bien
  forme). 10/10 verts en local.
- .github/workflows/assert-memory-budget.yml : CI bloquante sur PR/push
  touchant le snapshot, l'organe, la topologie, la slice ou supervise.sh.
  Schedule lundi 06:37 UTC pour detecter la derive de l'instrument quand
  aucune PR ne touche le snapshot. Run sur ubuntu-latest (jamais
  self-hosted : l'observateur ne doit pas consommer ce qu'il observe).

Anti-patterns evites :
- Re-implémenter measure_po2024_topology : refus. L'organe importe la
  fonction measure() deja existante (cf. #13564 organ-first).
- Mesure VM dependante du daemon docker : refus. measure() lit la RAM
  via WMI / procfs / sysctl sans docker.
- Hand-edit sortie : refus (secrets-hygiene regle 6).
- Rapport commité : refus (cadrage coordinateur 07/10 + CLAUDE.md §A).

Scope CPU strict -- pas d'execution CI sur la machine cible (po-2024) :
le workflow tourne sur ubuntu-latest pour verifier l'instrument, et un
operateur execute l'organe sur po-2024 elle-meme (le snapshot detecte
l'incoherence via la slice 16 GiB, qui est partagee par toutes les machines
CI). Le gate est : composition <= borne tranche au plus serré.

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>

* fix(infra,#19851): assert_memory_budget -- 3 defauts revue coordinateur (composition derivee, surcharge slice, RAM VM declaree)

Revue coordinateur 2026-10-08 (commentaire 🟡 cid 6053027290) a identifie 3
defauts sur l'organe #19805 :

1. Composition maintenue a la main (po2024_budget.json) -> derive depuis
   supervise.sh (BudgetSnapshot.from_supervise_defaults). Le snapshot JSON
   derive a chaque push supervise.sh, l'organe mesurait alors son propre
   instantane au lieu du deploiement. Lecture directe des variables
   COURSIA_RUNNER_MEMORY / COURSIA_RUNNER_WAITER_MEMORY /
   COURSIA_LEAN_RUNNER_MEMORY + N par defaut (2 start / 24 waiters / 2 lean).

2. Slice generique persist/coursia-ci.slice (16G) surchargeait la lecture
   sans tenir compte d'un plafond machine-specifique. Ajout lookup_slice()
   qui cherche persist/<machine>/coursia-ci.slice d'abord, fallback
   generique. La surcharge po-2024 n'est pas encore materialisee (#19802)
   mais le motif est en place.

3. RAM VM mesuree sur l'hote de l'organe (191.8 GiB ai-01, 63.6 GiB po-2027)
   au lieu d'etre declaree (24 GiB po-2024). Defaut fondateur ferme :
   l'organe peut tourner depuis n'importe quelle machine du cluster, mais
   c'est la machine-CIBLE qui plafonne. Constante PO2024_VM_RAM_GIB=24.0
   + lookup_vm_ram_gib() + avertissement stderr si machine inconnue
   (mode slice seule).

Tests : 16/16 verts (10 anciens + 6 nouveaux : lookup_vm_ram_gib x2,
lookup_slice x2, from_supervise_defaults x2). Le mode JSON snapshot reste
l'option par defaut pour la compat ascendante ; --from-supervise est opt-in
(derive depuis supervise.sh defauts).

Validation manuelle :
  python scripts/ci/assert_memory_budget.py --from-supervise --machine po-2024
  [INCOHERENT] po-2024 : composition 27.0 GiB vs borne 16.0 GiB
    start :   2 x   1.5 GiB =    3.0 GiB
    waiters :  24 x   0.5 GiB =   12.0 GiB
    lean :   2 x   6.0 GiB =   12.0 GiB
    TOTAL :                  27.0 GiB
    VM RAM        :   24.0 GiB  (hote reserve 0.5 GiB)
    Slice MemoryMax:   16.0 GiB
    Borne         :   16.0 GiB  (marge 0.5 GiB, seuil 15.5 GiB)

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>

* fix(infra,#19851): composition derivee des unites deployees, pas de l'instantane

Defaut 1 de la revue coordinateur, resté ouvert : le defaut de la composition
etait encore le snapshot JSON recopie a la main. La CI appelle l'organe sans
drapeau, donc il remesurait sa propre constante. La derivation
`from_supervise_defaults` (valeurs documentees en en-tete de `supervise.sh`)
derivait elle aussi du deploiement : 24 slots d'attente documentes quand
l'unite en lance 12, et 6 sur po-2024 par drop-in.

- `from_deployed_units(machine)` : effectif = dernier jeton entier de
  l'`ExecStart` du wrapper de jambe, cap = `Environment=` de la meme unite ;
  le drop-in `persist/<machine>/<unite>.d/*.conf` surcharge l'unite de base.
- Composition par defaut = unites deployees. `--from-json` et
  `--from-supervise` restent disponibles pour l'audit et le repli.
- Provenance portee au verdict (champ `source`, texte et JSON) : un organe qui
  confronte une composition dit d'ou elle vient.
- Workflow : les `paths:` couvrent `persist/**` -- sans quoi le guard ne se
  relance pas quand un dimensionnement change, et rend un verdict perime.
- Test stale corrige : `lookup_slice` sur po-2024 affirmait le repli sur la
  generique en se fondant sur l'absence de la surcharge, materialisee depuis
  #19802. Le postulat avait disparu avec le deploiement.

Mesure : la composition reelle de `main` (4 x 1.5 + 6 x 0.5 + 1 x 6 = 15.0 GiB)
tient sous le seuil 15.5 GiB. Le rouge `Composition vs RAM VM` etait un
artefact de lecture, pas une incoherence du deploiement -- c'est la reponse que
la revue demandait a l'organe.

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>

* fix(ci,#19851): installer pytest dans le smoke test du workflow

Le runner ubuntu-latest n'expose pas pytest dans le Python 3.13 installe par
setup-python : l'etape `Run pytest (smoke)` echouait sur `No module named
pytest` (rouge fondeur du 2026-10-09), pas sur le code de l'organe. Ajout
d'une etape d'installation explicite avant le smoke test.

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
myia-ai-01 pushed a commit that referenced this pull request Oct 10, 2026
…-in slice-guard.conf) (#20096)

* fix(ci): garde slice CI po-2024 — activation boot + fail-closed (slice-guard.conf)

Signalement Sheriff msg-20261009T103550-mypwn3 (jsboige/Maintenance#76) : apres le
reboot du 08/10 21:47, les 11 conteneurs CI sont nes hors coursia-ci.slice. Cause
racine mesuree : rien n'active la slice au boot, et assert_ci_slice (supervise.sh)
lit memory.max dans le cgroup fs — slice inactive = pas de cgroup = « illisible »,
placement saute en silence (COURSIA_REQUIRE_CI_SLICE=0 par defaut).

Deux copies de reference du drop-in deploye ce cycle sur po-2024 (byte-identiques
au deploiement) :

- [Unit] Wants/After=coursia-ci.slice : la slice demarre avec l'unite. Une slice
  vide demarree reste active/active avec limites lisibles (valide firsthand).
- [Service] Environment="COURSIA_REQUIRE_CI_SLICE=1" : assert_ci_slice passe en
  fail-closed (branche die prevue par le code) — slice absente = unite FAILED
  visible, jamais un pool non borne silencieux.

Le deploiement porte aussi la couche J+7 (#19802, revue passee, jamais jouee) :
budget 15 / CPU 18 / lean x1 / slice 14G-16G. Dry-run poste sur le dashboard
AVANT les gestes root ; sauvegardes /root/ci-cgroup-bak-20261009T1400Z/.

See #19802 (deploiement de l'amendement J+7 ; ne le clot pas — le suivi variance
reste ouvert)

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

* fix(ci): jambe waiters sous la slice -- drop-in specifique (dispatch ai-01 c1600)

Le wrapper coursia-waiters-start.sh appartient a ai-01 (byte-identique sur les
deux sieges) et n'exporte ni slice ni garde : supervise.sh les lit dans son
environnement. Le drop-in waiters injecte donc les DEUX variables
(COURSIA_RUNNER_CGROUP_PARENT + COURSIA_REQUIRE_CI_SLICE=1) en plus du
Wants/After, perimetre po-2024 uniquement.

Deploye et verifie le 2026-10-09 : 6/6 waiters CgroupParent=coursia-ci.slice,
slice a 11 scopes docker (5 CI + 6 waiters), memory.current ~6,7 Go, budget
15 Go aligne sur la 3e jambe (drift 21->15 du 10-sizing.conf deploye au
passage -- le repo portait deja 15), waiters reconnectes et a l'ecoute apres
purge des 6 stale hors slice (idle verifie Runner.Worker=0 x6).

See jsboige/Maintenance#76 (suite) ; claim dispatch ai01-c1600-po2024c-waiters-slice

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
jsboige added a commit that referenced this pull request Oct 12, 2026
…-> 22.0)

Le test test_lookup_slice_machine_override_wins_on_real_po2024 asserte la
valeur MemoryMax de la slice po-2024 materialisee sur main -- son docstring
le dit lui-meme : il teste l'etat du depot, pas l'organe. #19802 (J+7) y
avait pose 16.0 ; l'arbitrage c2153 remonte le mur a 22G avec le budget 21
(#20174), la valeur attendue suit. Docstring completee avec la genealogie
16.0 (#19802) -> 22.0 (c2153).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
myia-ai-01 pushed a commit that referenced this pull request Oct 13, 2026
…0425)

Famine justifee : la composition persist re-etiquetee #20174 est entree
apres le J+7 de #19802 sans suivi du budget. Hier (2026-10-11 vers
03:10:57 CEST), le pool waiters a commence a refuser ses slots : 18432 Mo
CI en vol + 3072 Mo demandes (6 x 512m) = 21504 Mo > 15360 Mo budget
(COURSIA_RUNNER_BUDGET_GB=15). Le service a atteint "Start request
repeated too quickly" et Result: exit-code 1 ; aucun waiter container
n'a demarre pendant l'incident.

Mission coordinateur c2153, option b : retablir le budget a 21 Go, le
meme nombre que celui de l'ere pre-J+7 (avant amendement 15 Go du
2026-10-08). Doctrine "meme nombre sur les 3 jambes" preservee :
runner-start.sh, lean-start.sh, et le drop-in waiters portent 21.

Slice 20G/22G/0 swap inchangee, CPU 18 inchange -- seul le budget
memoire evolue. La valeur documentee reste 21 dans l'en-tete du
drop-in waiters (le commentaire note 15 comme "avant").

Deployment : dry-run sed montre les 3 substitutions, apply, verification
post-deploy. systemctl daemon-reload && systemctl restart
coursia-waiters.service. Witness : 6 waiters Up, slice memory.high=14G
memory.max=16G, hote sain (pagewrites=0 pagesout=0).

See #19802, See #20174

Co-authored-by: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
myia-ai-01 pushed a commit that referenced this pull request Oct 13, 2026
…, volet 2) (#20431)

* Fix(ci,#19802): budget CI po-2024 15 -> 21 Go (3 jambes alignees)

Famine justifee : la composition persist re-etiquetee #20174 est entree
apres le J+7 de #19802 sans suivi du budget. Hier (2026-10-11 vers
03:10:57 CEST), le pool waiters a commence a refuser ses slots : 18432 Mo
CI en vol + 3072 Mo demandes (6 x 512m) = 21504 Mo > 15360 Mo budget
(COURSIA_RUNNER_BUDGET_GB=15). Le service a atteint "Start request
repeated too quickly" et Result: exit-code 1 ; aucun waiter container
n'a demarre pendant l'incident.

Mission coordinateur c2153, option b : retablir le budget a 21 Go, le
meme nombre que celui de l'ere pre-J+7 (avant amendement 15 Go du
2026-10-08). Doctrine "meme nombre sur les 3 jambes" preservee :
runner-start.sh, lean-start.sh, et le drop-in waiters portent 21.

Slice 20G/22G/0 swap inchangee, CPU 18 inchange -- seul le budget
memoire evolue. La valeur documentee reste 21 dans l'en-tete du
drop-in waiters (le commentaire note 15 comme "avant").

Deployment : dry-run sed montre les 3 substitutions, apply, verification
post-deploy. systemctl daemon-reload && systemctl restart
coursia-waiters.service. Witness : 6 waiters Up, slice memory.high=14G
memory.max=16G, hote sain (pagewrites=0 pagesout=0).

See #19802, See #20174

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>

* Fix(ci,#20174): slice po-2024 14G/16G -> 20G/22G + arithmetique budget 21 Go

Arbitrage c2153 (option b), volet absent de #20425 : le mur kernel suit la
composition vive 8x1536 + 6x512 + 1x6144 = 21 504 Mo de caps -- un MemoryMax
16G sous cette composition tue au kernel des jobs que le budget d'admission
21 laisse partir. L'arithmetique #20174/#19802 ecrite dans les commentaires
des trois jambes, exigee par la meme consigne.

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

* Fix(tests,#20174): test slice po-2024 suit le mur 22G de c2153 (16.0 -> 22.0)

Le test test_lookup_slice_machine_override_wins_on_real_po2024 asserte la
valeur MemoryMax de la slice po-2024 materialisee sur main -- son docstring
le dit lui-meme : il teste l'etat du depot, pas l'organe. #19802 (J+7) y
avait pose 16.0 ; l'arbitrage c2153 remonte le mur a 22G avec le budget 21
(#20174), la valeur attendue suit. Docstring completee avec la genealogie
16.0 (#19802) -> 22.0 (c2153).

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

---------

Co-authored-by: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
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.

2 participants