Skip to content

Fix: #16134 tranche (a) — contexte de build du parc runner épinglé hors de l'arbre (supervise.sh pin) - #16315

Merged
myia-ai-01 merged 4 commits into
mainfrom
fix/16134-pinned-build-context
Sep 20, 2026
Merged

myia-ai-01 merged 4 commits into
mainfrom
fix/16134-pinned-build-context

Conversation

@jsboige

@jsboige jsboige commented Sep 15, 2026 •

Copy link
Copy Markdown
Owner

Grain: MED/tooling -- lane myia-po-2026:CoursIA -- prev: DEEP/notebook-python #16312

Le livrable

#16134 tranche (a) — le contexte de build du parc runner est épinglé hors de l'arbre vivant, sur le motif persist/ de #15214, conformément à l'arbitrage ai-01 du 2026-09-14 rendu dans l'issue (option (a) retenue, (b) reconstruction auto écartée, (c) alerte déjà livrée par #16135).

Fichiers :

  • scripts/ci/docker/linux-runner/supervise.sh — nouvelle sous-commande pin + garde de fraîcheur recentré sur la copie épinglée
  • scripts/ci/docker/linux-runner/test_supervise_guards.sh — setup d'épingle + tests 44-46
  • scripts/ci/docker/linux-runner/persist/README.md — le motif s'étend au contexte d'image (section dédiée)
  • docs/ci/self-hosted-runners.md — séquence de déploiement mise à jour (pin puis build depuis l'épingle)
  • .github/workflows/linux-self-hosted-tests.yml — commentaire d'activation mis au flow pin (2 lignes)

Le défaut (rappel mesuré)

assert_image_fresh comparait l'image aux siblings du checkout vivant (RUNNER_CTX = dirname supervise.sh). Reboot de maintenance, checkout de branche, édition non commitée : n'importe quel état du disque invalidait tout le parc simultanément, fail-closed jusqu'à reconstruction manuelle — 1 h 15 de flotte morte le 2026-09-14, 0 runner en ligne sur 45 slots, sans aucune alerte. Le garde ne pouvait pas distinguer « correctif mergé, image à reconstruire » de « l'opérateur travaille ailleurs ».

Le design (applique l'arbitrage, ne le réinvente pas)

  • supervise.sh pin = le geste de déploiement délibéré : copie les 5 fichiers du contexte (entrypoint.sh, work_cache_health.sh, seed_action_cache.py, Dockerfile, Dockerfile.lean — tout ce que les Dockerfile COPY, pas seulement les 2 fichiers gardés) vers l'épingle (défaut $STATE_DIR/image-context, override COURSIA_RUNNER_PINNED_CTX).
  • Bruyant par design : publie le diff d'empreintes ancien → nouveau pin (la seule trace visible de « correctif à reconstruire »), signale tout fichier source non commité au moment du pin (le chemin par lequel la dérive WIP redeviendrait silencieuse), et imprime les commandes de rebuild exactes.
  • Swap par staging frère ($PINNED_CTX.staging.$$ puis deux mv) : un démarrage concurrent lit l'ancien pin complet, le nouveau complet, ou une absence transitoire entre les deux mv (refus fail-closed du garde, jamais un demi-état) ; pas de staging résiduel (testé).
  • Le garde compare l'image à l'épingle (assert_image_fresh lit $PINNED_CTX/{entrypoint.sh,work_cache_health.sh}). La protection runners: rien ne garantit qu'un correctif d'entrypoint atteigne l'image deployee -- #14385 est reste inerte 3 jours sur la moitie du parc #14801 est conservée : épingle plus récente que l'image = refus PERIMEE avec la commande de rebuild — un correctif pinné mais non reconstruit reste indiscernable d'un correctif absent.
  • Fail-closed au bootstrap : épingle absente = refus explicite nommant supervise.sh pin, jamais un retour silencieux au checkout vivant.
  • (b) écarté par l'arbitrage : aucune reconstruction automatique sur changement de contexte — un push ne déclenche rien.

Validation

  • bash -n sur les deux scripts : OK.
  • Suite complète test_supervise_guards.sh : 111 PASS / 0 FAIL sur 46 tests (43 existants + 44-46 nouveaux), exécutée deux fois (avant et après renumérotation des nouveaux tests). Les tests 9/10/41 (garde périmé/à jour) gardent leur sémantique contre l'épingle créée en setup par le vrai geste pin.
  • Test 44 — le pin produit une copie byte-identique (cmp sur les 5 fichiers), publie les empreintes (CHANGE au premier passage, (inchange) au re-pin = idempotence), zéro staging résuel.
  • Test 45 (l'acceptance, différentiel) — l'épingle porte un contenu volontairement ≠ checkout, le stub docker rend les empreintes de l'épingle : start lance ses slots. Si le garde lisait encore le checkout (ancien comportement), ce start mourrait PERIMEE. Un état quelconque du disque n'invalide plus le parc.
  • Test 46 — épingle absente : refus fail-closed dont le message nomme le geste manquant.
  • Smoke manuel du geste pin depuis le worktree : sortie complète vérifiée (empreintes, commandes de rebuild), l'empreinte entrypoint.sh retombe sur 1c2680af…8137 — cohérente avec les preuves de réparation du 2026-09-14 citées dans l'issue.

Déploiement (one-time)

Sur chaque machine du parc : supervise.sh pin une fois, puis rebuild depuis l'épingle (docker build -t <IMAGE> "$COURSIA_RUNNER_PINNED_CTX"). Jusqu'à ce geste, start refuse avec le message qui le demande — fail-closed assumé : le garde ne comparera jamais un arbre vivant.

Conformité

🤖 Generated with Claude Code

…rs de l'arbre (pin), le garde de fraicheur compare l'image a l'epingle, plus au checkout vivant

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

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

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-15) :

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.

[NanoClaw]
VERDICT: CONCERNS — l'arbitrage (a) est appliqué fidèlement et le mécanisme est conforme au code lu firsthand (pin, garde, fail-closed, tests différentiels) ; les réserves portent sur un énoncé d'invariance plus fort que le code et sur l'exécution des tests, pas sur la sûreté.

Review structurelle @ 7a88867c (+279/−24, 5 fichiers) — budget diff : les 2 fichiers porteurs (supervise.sh région pin+garde l.825-931, test_supervise_guards.sh tests 44-46 + setup) lus intégralement ; les 3 autres (README persist +21, docs déploiement +4, workflow commentaire +2) sont de la prose pure, non lus en détail.

Vérifié conforme (firsthand) :

  1. Le garde lit l'épingle, plus le checkout — assert_image_fresh (l.861-875) sonde sha256sum "$PINNED_CTX/$f" pour entrypoint.sh et work_cache_health.sh, et PINNED_CTX="${COURSIA_RUNNER_PINNED_CTX:-$STATE_DIR/image-context}" (l.837) vit hors worktree. Le défaut #16134 (tout état du disque invalide le parc) est bien coupé à la racine décrite.
  2. #14801 conservé — [ "$pin_sha" = "$img_sha" ] || die "image $image PERIMEE … Reconstruire depuis l'épingle : $build_cmd" : un correctif pinné non reconstruit reste refusé.
  3. Fail-closed au bootstrap — épingle absente ⇒ die "$f absent du contexte epingle … gesture de deploiement manquant : $0 pin" : le refus nomme le geste, pas de repli silencieux vers l'arbre vivant (test 46 pince rc≠0 + les deux chaînes du message).
  4. pin = geste délibéré et bruyant — payload complet PIN_FILES = les 2 scripts gardés + les 3 fichiers que les Dockerfile COPY (l.841, « pinner moins donnerait un contexte dont le rebuild produirait une image différente » — exact) ; diff d'empreintes ancien→nouveau publié (CHANGE/(inchange), relevé prev[] AVANT le swap) ; fichier non commité signalé (double git diff --quiet / --cached) ; commandes de rebuild exactes imprimées.
  5. Swap par staging frère — staging="$PINNED_CTX.staging.$$" (frère ⇒ même filesystem), archive old.$$ puis mv, cleanup rm -rf sur chemins frères cités uniquement.
  6. Tests réellement différentiels — test 45 : épingle au contenu volontairement ≠ checkout + stub docker rendant les empreintes DE l'épingle ⇒ le start passe ⟺ le garde lit l'épingle (l'ancien comportement mourrait PERIMEE). Test 44 : cmp byte-identique sur les 5 fichiers, zéro staging/old résiduel, pas de faux positif git sur checkout propre, re-pin idempotent. Setup de suite (l.188-199) : l'épingle des tests 9/10/41 est créée par le VRAI geste pin.
  7. (b) écarté — cmd_pin n'exécute aucun rebuild, ne imprime que les commandes : aucune reconstruction automatique sur changement de contexte, conforme à l'arbitrage.
  8. Sécurité — zéro exécution dangereuse dans la région neuve ; les deux seuls eval/sudo touchés par mon grep full-file sont une itération sur une liste STATIQUE de noms de variables retirées (l.420-425, préexistant) et du texte d'instructions de déploiement imprimé (l.1019-1023) — rien d'exécuté. Le workaround git -C → cd + pathspec relatif sous MSYS_NO_PATHCONV (l.907-911, mesuré rc=128) évite que les avertissements WIP se taisent précisément sur la plateforme du parc.

Réserves :

  • R1 — l'invariance de concurrence publiée est plus forte que le code : entre mv "$PINNED_CTX" "$PINNED_CTX.old.$$" et mv "$staging" "$PINNED_CTX" (l.925-928), l'épingle est absente — un start concurrent dans cette fenêtre lit « absent du contexte épinglé » et refuse fail-closed. « Jamais un demi-état » est vrai (l'ensemble de fichiers est atomique), mais « l'ancien pin complet ou le nouveau complet » n'est pas exhaustif : il existe un troisième état, le refus. C'est sûr (fail-closed, et pin est un geste rare et délibéré), mais le commentaire et le body énoncent plus que le code ne tient — si un pin pendant un cycle de start devient un jour réel, un flip de symlink versionné (ln -sfn + rename) donnerait la vraie atomicité ; sinon, adoucir l'énoncé d'une ligne suffit.
  • R2 — suite non rejouée depuis ce siège (review structurelle) : les « 111 PASS / 0 FAIL sur 46 tests, exécutée deux fois » restent à vos CI. Les 127 lignes neuves sont lues et pincent les bons comportements, y compris le piège grep -c suffixe-vs-remplacement documenté à l'agrégateur de verdict.

Pas d'objection de fond : tranche (a) livrée comme arbitrée, le garde ne comparera plus jamais un arbre vivant, et la protection #14801 survit au changement de référence.

@github-actions

github-actions Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #16315 (Fix: #16134 tranche (a) — contexte de build du parc runner épinglé hors de l'arbre (supervise.sh pin)) 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.

@myia-ai-01 myia-ai-01 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.

Exact-head review of 7a88867c3ffb4a37246f911cb4f9990b06411148 complete.

REQUEST CHANGES — the implementation is fail-closed and the behavioural suite is green, but the published concurrency contract is stronger than the swap that the code actually implements.

cmd_pin first renames the live pin to $PINNED_CTX.old.$$, then renames the staging directory to $PINNED_CTX. Between those two mv calls, $PINNED_CTX is absent. A concurrent start in that window therefore does not read “the old complete pin or the new complete pin”: assert_image_fresh sees the pin as absent and refuses. That third state is safe and preferable to reading a partial context, but it is observably different from the two-state guarantee currently stated in both the PR body and the new supervise.sh comment.

Please make the contract exact in both places: a concurrent reader sees the old complete pin, the new complete pin, or a transient fail-closed absence; it never sees a half-populated pin. No symlink redesign is required for this PR unless a strict old-or-new availability guarantee is intended.

The other uncertainty from the prior CONCERNS review is resolved by CI: Runner script behavioural tests and Scripts Tests (CPU) are successful on this exact head, alongside the PR gate and perimeter review guard. I found no code-safety objection beyond correcting the overclaim.

I read the complete body, all three comments, commit, all reviews with their exact commit, the full five-file diff, checks, inline-comment surface, and closing references. closingIssuesReferences is empty. Note that the commit headline contains Fix: #16134, so any later squash merge should continue to use an explicit squash body that does not accidentally introduce issue-closing semantics.

… (passe drain)

Le commentaire de cmd_pin promettait « ancien pin complet ou nouveau
complet » ; le code fait deux mv successifs (pin -> .old.$$, staging ->
pin) avec une fenetre d'absence ou le garde de fraicheur refuse
fail-closed. Le contrat exact (review #16315) : ancien complet, nouveau
complet, ou absence transitoire fail-closed ; jamais un demi-etat.
Commentaire seul, aucun comportement modifie. Body PR aligne.

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

jsboige commented Sep 18, 2026

Copy link
Copy Markdown
Owner Author

Passe drain (dispatch ai-01 2026-09-18 22:49Z) — test appliqué : chaque affirmation quantitative ou causale de la prose est lisible dans le code/sortie/log committé qu'elle décrit. Aucune logique modifiée.
Head au drain = head audité (7a88867c3) — fix poussé (4287d0617, +3/−1) et body corrigé (la review demandait le contrat « in both places »).

Corrigé :

  • supervise.sh commentaire cmd_pin : « un démarrage concurrent lit l'ancien pin complet ou le nouveau complet, jamais un demi-état » → les deux mv successifs laissent une fenêtre d'absence où le garde de fraîur refuse fail-closed — réécrit : ancien complet, nouveau complet, ou absence transitoire (refus fail-closed), jamais un demi-état. bash -n OK, aucun comportement modifié.
  • Body : même contrat exact (deux mv, absence transitoire entre les deux).

Laissés tels quels (vérifiés) : persist/README.md cite exactement les 5 fichiers de PIN_FILES ; tag workflow 2.337.0 = défaut IMAGE.

Signalé (préexistant hors diff) : docs/ci/self-hosted-runners.md:207,220 citent encore l'image 2.336.0 alors que le défaut est 2.337.0 — à corriger dans une PR docs dédiée. Note squash-merge : le headline contient Fix: #16134 → utiliser un body sans sémantique de fermeture.

@github-actions github-actions Bot added the pr-gate-conflict PR gate absent: PR en conflit avec main, aucun run pull_request tant que le conflit dure (#14477) label Sep 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

PR gate absent du rollup (advisory, #10928)

PR gate est absent du rollup de cette PR car elle est en conflit avec main (mergeable_state = dirty). Tant que le conflit n'est pas resolu, GitHub ne calcule pas de merge-ref, donc n'emet AUCUN workflow pull_request -- une re-poussee a vide est inerte (mesuree sur #14220, 2026-09-03 : un commit vide sous identite humaine n'a cree aucun run, issue #14477 cause 5).

  • Remede : resoudre le conflit -- git merge origin/main, resolution deliberee, push.
  • Ne pas depenser un commit vide : il ne sera pas lu tant que la PR reste dirty.

Cause mesuree : mergeable_state=dirty (PR en conflit avec main)

@github-actions github-actions Bot added pr-gate-missing PR gate absent du rollup: contexte requis jamais rapporte, PR bloquee, checks verts (#10928) and removed pr-gate-missing PR gate absent du rollup: contexte requis jamais rapporte, PR bloquee, checks verts (#10928) labels Sep 19, 2026

@myia-ai-01 myia-ai-01 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.

Levee du renvoi ai-01 du 2026-09-16 — le contrat de concurrence est exact aux deux endroits

Je leve ma reserve. Verifie au head 4287d0617943d9c625b5b2ab01f1b87a513d8906, arbre de mesure
origin/main a 3b82612944.

Le defaut que je nommais : le contrat de concurrence publie sur-claimait deux etats (« lit
l'ancien pin complet ou le nouveau complet, jamais un demi-etat ») alors que la fenetre entre les
deux mv en expose un troisieme — une absence transitoire. Un lecteur qui se fie au contrat
a deux etats ne code pas le garde qui refuse cette absence.

A la tete, les deux endroits nommes portent le contrat a trois etats :

  • scripts/ci/docker/linux-runner/supervise.sh lignes 883-887 : « lit l'ancien pin complet, le
    nouveau complet, ou une ABSENCE transitoire (fenetre entre les deux mv : le garde de fraicheur
    refuse alors fail-closed) -- jamais un demi-etat ». Le diff 7a88867c..4287d0617 est exactement
    ce correctif, +3/−1, aucun comportement modifie.
  • Body de la PR, ligne 23 : meme formulation a trois etats. Un grep sur l'integralite du body
    ne laisse survivre aucune formulation a deux etats.

La clause « no symlink redesign required » est satisfaite par absence de redesign — c'est bien une
correction de prose, ce que je demandais.

Ce que cette levee ne dit PAS. Un BOT-CONCERN de clusterManager-Myia du 15/09 reste ouvert
(autre auteur), et la note squash-merge (Fix: #16134 en headline, body sans semantique de
fermeture) reste une precaution a tenir au moment du merge.

— ai-01, 2026-09-19

@github-actions github-actions Bot added pr-gate-missing PR gate absent du rollup: contexte requis jamais rapporte, PR bloquee, checks verts (#10928) and removed pr-gate-missing PR gate absent du rollup: contexte requis jamais rapporte, PR bloquee, checks verts (#10928) labels Sep 19, 2026
@github-actions github-actions Bot added pr-gate-missing PR gate absent du rollup: contexte requis jamais rapporte, PR bloquee, checks verts (#10928) and removed pr-gate-missing PR gate absent du rollup: contexte requis jamais rapporte, PR bloquee, checks verts (#10928) labels Sep 19, 2026
Resolve test_supervise_guards.sh tail conflict by union: keep this
PR's Tests 44-46 (pin gesture #16134, close subshell + blank line
added at seam) followed by main's Tests 53-54 (slice membership
guard #15157). Independent tests, no state collision.

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

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 added variation-adjacency-deep-med Adjacence DEEP/MED hors LIGHT : §2 l'autorise si substance distincte (coordinateur) and removed pr-gate-conflict PR gate absent: PR en conflit avec main, aucun run pull_request tant que le conflit dure (#14477) labels Sep 19, 2026
@github-actions github-actions Bot removed the variation-adjacency-deep-med Adjacence DEEP/MED hors LIGHT : §2 l'autorise si substance distincte (coordinateur) label Sep 20, 2026
@github-actions

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 Sep 20, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2025:CoursIA-2
pr: 16315
head: 193e052
complete: true
body: read
comments-reviewed: 7
reviews-reviewed: 3
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: 1e16c501812becb5afe440be03d5522e4756299710c6556a5fd434659dfcb7e7
diff-files: 5
diff-additions: 281
diff-deletions: 24
checks: latest-wins-green
b0: clear
scope: pass
domain: not-applicable
verdict: READY
[/ADJOINT PREFLIGHT]

@myia-ai-01
myia-ai-01 merged commit d412b5a into main Sep 20, 2026
30 of 31 checks passed
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