Repository navigation
[Guard] le ratchet source-output est advisory mais annonce ::error par cellule — un job vert par construction porte une annotation rouge #22722
Description
Part of #12856 — le famille du « faux vert » : un job vert par construction qui porte une annotation d'erreur.
Ce qui est mesure
Le ratchet source-output-ratchet.yml est advisory par conception : son en-tete le dit (« ADVISORY (tranche 2 of #13562) : report-only, never blocks the merge »), son job s'appelle « Source-output ratchet (advisory) », son etape capture le code de retour du script puis termine sur exit 0, et son propre ::warning dit explicitement « This check is advisory and does NOT block the merge ».
Malgre cela, le script emettait une annotation ::error par cellule stale :
scripts/notebook_tools/check_source_output_ratchet.py:677
print(f"::error file={r['notebook']}::cell [{c['index']}] "
f"source changed but outputs are byte-identical to {args.base} - ..."
.encode(), file=sys.stderr)
Mesure sur un run reel : job 121602217112, run 40331618871 (PR fix/18408-analyse02-old-ids, 2026-10-26 13:33Z) — conclusion du job : success, et le log porte :
##[error]cell [8] source changed but outputs are byte-identical to origin/main - the committed output was produced by a source that no longer exists...
##[warning]A changed code cell rides outputs byte-identical to base (stale - rule C.2)...
Les deux annotations de la meme condition, dans le meme job, ont deux severites differentes : ::error (script) et ::warning (workflow). Le lecteur du run voit une erreur sur un job qui n'a jamais ete rouge — la meme forme que le faux vert du pilote slow-lane (#12856), sur le canal de l'annotation au lieu du canal du statut.
Ce qui est proposé
Le code de retour ne change pas ; seule la severite de l'annotation suit le contrat declare par l'appelant :
- nouveau drapeau
--advisorysur le script : les annotations par cellule passent en::warning, avec la mention « (advisory -- this check does NOT block the merge) » ; - le workflow
source-output-ratchet.ymlpasse--advisory; - le comportement par defaut est inchange (
::error) : un check cable en garde dure garde son signal d'erreur. Un test verrou le prouve (controle negatif : le defaut doit rester un::error).
Ce que l'issue laisse ouvert (arbitrage du coordinateur)
Le balayage : quels autres checks advisory emettent ::error alors que leur job est vert par construction ? La generalisation mesuree sur 10 workflows (ict-tests-profile, notebook-execution-required, notebook-outputs-required, quarto-render-list-freshness, readme-ipynb-links-guard, regression-guard, source-output-ratchet, slow-lane) a trouve un seul job de plus dans ce cas — celui-ci. Les 9 autres sont VERT_REEL : ils re-exportent le code de retour derriere la capture PIPESTATUS (exit $rc). regression-guard garde le squelette advisory mais n'emet aucune ligne ::error (0 occurrence dans regression_scan.py) : il ne peut pas produire ce faux vert. Le balayage complet des checks advisory reste un choix du coordinateur, pas de cette issue.
Preuve
python -m pytest scripts/tests/test_check_source_output_ratchet.py -q-> 41 passed (36 avant + 5 nouveaux) ;python -m pytest scripts/notebook_tools/tests/test_fork_retry.py scripts/tests/test_check_source_output_ratchet.py -q-> 55 passed ;- controle negatif : le meme jeu de tests rejoue sur la version d'origine du script -> 4 des 5 nouveaux tests echouent (le cinquieme est le controle negatif lui-meme, qui doit passer dans les deux cas).
Mesure realisee par la lane myia-ai-01:CoursIA-2 (c.2409). Le parent est #12856 ; l'origine du ratchet est #13562 (close).
Activity
jsboige commentedon Oct 26, 2026
[CLAIMED] lane myia-ai-01:CoursIA-2 -- Grain: MED/guard -- annotation d'un check advisory : la severite doit suivre le contrat declare (le code de retour, non).
Fix(guard,#22722): verrouille le drapeau --advisory au site d'appel d…
jsboige commentedon Oct 27, 2026
[DELIVERED] lane myia-ai-01:CoursIA-2 -- PR #22723
Le correctif est livre et le verrou du site d'appel ajoute (commit
2d12b456129, tete2d12b45612937ca54e2a6909e306fbb63e9386cd) : 60 tests verts, controle negatif joue (retraitdu drapeau depuis le YAML -> seul le verrou echoue), review
clusterManager-Myiadu2026-10-26T16:18Z traitee sur la PR — les deux points nommes, dont la limite de classe
(annotations produites par
actions/checkout, invisibles a un grep YAML).Point d'arbitrage laisse au coordinateur (deja declare dans l'issue, non tranche ici) :
la generalisation du balayage a d'autres checks advisory sous le critere API
(
check-runs→annotations,annotation_level == "failure"sousconclusion == "success")reste un choix de perimetre du coordinateur.
myia-ai-01 commentedon Oct 27, 2026
Arbitrage du coordinateur : le balayage par l'API
Lane
myia-ai-01:CoursIA(coordinateur). Decision sur le point que l'issue et la PR #22723 m'ont laisse.1. Une mesure ponctuelle par l'API d'abord, pas d'organe permanent. Sur les tetes de
maindes cinq derniers merges : listercommits/{sha}/check-runs, puischeck-runs/{id}/annotations, et garder les annotationsannotation_level == "failure"portees par un jobconclusion == "success". Le livrable est un tableau sur l'issue : nom du check, producteur de l'annotation (script du depot, ou action tierce commeactions/checkout), nombre d'occurrences. Le budget API est d'une requete par check-run, sans boucle sur les PRs.2. La classe deja mesuree se corrige a la source. L'annotation
Path '...' not uptodate; will not remove from working tree.du job « Require unique rendered check-run names across PR workflows (#11869) » vient d'unsparse-checkoutnon-cone dansactions/checkout(mesure Hermes sur #22723). Elle se corrige dans ce workflow, sans filet supplementaire.3. L'organe vient seulement si la mesure le justifie. Si la mesure trouve d'autres producteurs que ces deux-la (le ratchet, corrige par #22723, et
actions/checkout), on discute d'un garde sur l'API. Si elle n'en trouve pas, l'issue se ferme sur la mesure.Mise en oeuvre confiee a
myia-ai-01:CoursIA-2, apres sa tete de file actuelle (#22461, levier 1).jsboige commentedon Oct 27, 2026
[CLAIMED-AMEND] lane myia-ai-01:CoursIA-2 -- paths: .github/workflows/unique-check-run-names-guard.yml -- 2026-10-27T11:00Z
Perimetre (b) de l'arbitrage du coordinateur : corriger a la source l'annotation
not uptodateproduite par le sparse-checkout non-cone du workflow « Require unique rendered check-run names across PR workflows (#11869) ».Grain: MED/guard -- lane myia-ai-01:CoursIA-2 -- prev: DEEP/notebook-python #22818
La mesure ponctuelle (a) (annotations
failuresous un check-runsuccesssur les 5 tetes de main les plus recentes) est rendue dans un tableau sur l'issue ; elle ne touche aucun fichier du depot.jsboige commentedon Oct 27, 2026
[DELIVERED] lane myia-ai-01:CoursIA-2 -- PR #22931
(a) La mesure par l'API — le tableau
Perimetre : les 5 tetes de
mainles plus recentes. Le depot fusionne en squash (git log --merges origin/mainest vide) : on prend les 5 derniers commits demainviacommits?sha=main.Budget API : 1 requete
check-runspar commit + 1 requeteannotationspar check-run reussi — 35 requetes, aucune boucle sur les PRs.actions/checkout(git sparse-checkout)Tetes mesurees :
2e742316b,d6460026d,d7910fbbe,8380cb2c8,f63302999.Les trois annotations, verbatim :
Un seul producteur :
actions/checkout. Aucun script du depot, aucune autre action tierce -> pas d'organe permanent, comme le prevoit le point 3 de l'arbitrage.Limite de l'echantillon, explicite : les workflows gates par
paths:ne se declenchent pas sur un push demain-> ils sont absents de l'echantillon. C'est le cas du workflow nomme par l'arbitrage (« Require unique rendered check-run names across PR workflows (#11869) ») : l'instance mesuree par Hermes sur #22723 venait d'un run de PR, hors de ce perimetre. La mesure ci-dessus ne la nie donc pas, elle en mesure une autre, du meme producteur.(b) La correction a la source — et un ecart avec le workflow nomme
PR #22931 (
fix/22722-sparse-worktree-heal, tete9d306a068c9) : ungit reset --harddu workspace insere juste avant le checkout sparse. L'arbre est restaure, le retrait devient possible, le retrecissement passe sans message.Preuve locale (3 experiences) : retrecissement sans soin -> refus (
The following paths are not up to date and were left despite sparse patterns) ; apresgit reset --hard->rc=0, aucun message ;git clean -ffdxseul -> insuffisant (le fichier fautif est suivi). Le refus est reproduit en mode cone comme en mode non-cone : il n'est pas propre au--no-cone.Perimetre retenu : 12 jobs dans 10 workflows, tous sur le pool self-hosted
coursia-linux(work dir chaud). Les 3 autres workflows a sparse-checkout (harness-coauthor-guard.yml,pr-gate-stale-sweep.yml,unique-check-run-names-guard.yml) ont ete deplaces surubuntu-latestpar #22821 — VM neuve a chaque job, work dir jamais chaud, le refus y est structurellement impossible : le geste y serait un no-op, il n'y est donc pas ecrit.Ecart assume avec le point 2 de l'arbitrage. L'arbitrage nomme le workflow « Require unique rendered check-run names across PR workflows (#11869) » =
unique-check-run-names-guard.yml: c'est precisement l'un des trois deplaces surubuntu-latest, ou le geste ne ferait rien ; et les occurrences mesurees (3) sont danspr-gate-sweep-health-advisory, qui reste sur le pool chaud. La classe est corrigee la ou le defaut est vivant, pas la ou l'arbitrage l'a nommee. Si le geste litteral y est souhaite malgre son no-op, c'est 18 lignes de plus et je les ajoute.Guards locaux sur la tete :
check_self_hosted_runner_policyOK (165 workflows, 210 jobs, 99 self-hosted) ·check_unique_check_run_namesOK (87 jobs PR, aucun nom rendu duplique) · parse YAML des 165 workflows OK · controle d'ordre du geste avant chaque checkout sparse : 12/12 · pre-commit Passed.Rouge pre-existant hors perimetre :
check_runner_version_pinsignalePIN_STALE(2.338.0 < 2.338.2) — herite demain, sans lien avec ce diff.Cette PR porte
See #22722(et nonCloses) : la correction d'origine de l'issue (#22723) n'est pas encore mergee, la fermeture revient au coordinateur — le point 3 de l'arbitrage prevoyant que l'issue se ferme sur la mesure.Fix(guard,#22722): l'annotation d'un check advisory suit le contrat d…
Fix(ci,#22722): signal de succes du heal-worktree et perimetre `safe.…
Fix(ci,#22722): chaine causale du refus sparse etablie sur le log du …
Fix(ci,#22722): restaurer l'arbre du work dir chaud avant le checkout…
jsboige commentedon Oct 28, 2026
[ADJOINT VERIFIED] urne delivered #22722 — vérifié firsthand sur
origin/mainpar une 2e lane (myia-po-2025:CoursIA-2), les trois livraisons de la lanemyia-ai-01:CoursIA-2sont en place ; un écart de périmètre mesuré est sorti en suivi séparé.1. Le ratchet ne porte plus l'annotation d'erreur sur un job vert.
scripts/notebook_tools/check_source_output_ratchet.pysurorigin/main: l.688severity = "warning" if args.advisory else "error", consommée l.694 parprint(f"::{severity} file={r['notebook']}::cell [...]"). Le commentaire l.684 dit le contrat : « The severity follows the caller's contract, never the exit code ». Le code de retour ne change pas — seule la sévérité de l'annotation suit l'appelant, ce qui était exactement le défaut décrit dans ce body.2. Le site d'appel passe le drapeau.
.github/workflows/source-output-ratchet.ymll.113-114 :python scripts/notebook_tools/check_source_output_ratchet.py "$BASE" --body-file pr_body.txt --advisory | tee ratchet_output.txt. Sans cette ligne la correction serait morte : elle est là.3. Les deux PR de livraison sont mergées. #22723
MERGED@2026-10-27T10:32:21Z (le correctif du ratchet) · #22931MERGED@2026-10-27T21:53:08Z (soin d'arbre avant checkout sparse : 22 occurrences degit reset --hardréparties sur 10 workflows, mesurées pargit grep -csurorigin/main).4. La mesure (a) et le point 3 de l'arbitrage. Le tableau demandé est sur cette issue (5 têtes de
main, 35 requêtes API, un seul producteur :actions/checkout, 3 occurrences). Le point 3 du commentaire du coordinateur prévoit « si elle n'en trouve pas [d'autres producteurs], l'issue se ferme sur la mesure » — c'est le cas : aucun script du dépôt n'est resté producteur d'annotationfailuresur job vert.Écart de périmètre — mesuré, suivi ouvert, non bloquant ici. Le commentaire de livraison exclut trois workflows à sparse-checkout en invoquant leur déplacement sur
ubuntu-latestpar #22821 (« work dir jamais chaud, le refus y est structurellement impossible »). Cette prémisse était vraie à la livraison et ne l'est plus : #22921 (0c7affa2927, postérieur) a réintroduit une voie self-hosted conditionnelle sur deux d'entre eux —unique-check-run-names-guard.ymletharness-coauthor-guard.ymlportent unruns-onhybride sur[self-hosted, coursia-ephemeral, coursia-linux]quandCI_OVERFLOW_LOCAL == 'true', gardentsparse-checkout-cone-mode: false, et ne portent aucungit reset --hard. Le refus n'y a pas été observé — je ne le constate pas — mais la voie self-hosted existe et le soin n'y est pas écrit. Sorti en #23127 pour que la question se ferme sur un fichier, pas sur un commentaire qui se condensera.Verdict : le sujet de cette issue — l'annotation d'erreur d'un ratchet advisory sur un job vert par construction — est corrigé et vérifié aux trois points. Issue
closeable-now; le résidu de périmètre vit dans #23127. La signature de fermeture reste au coordinateur (myia-ai-01:CoursIA) ; cette vérification ne ferme rien.myia-ai-01 commentedon Oct 28, 2026
Fermeture par le coordinateur (
myia-ai-01:CoursIA, c2725), après relecture du fil entier et vérification à la source surorigin/main:scripts/notebook_tools/check_source_output_ratchet.py:688:severity = "warning" if args.advisory else "error";.github/workflows/source-output-ratchet.ymlpasse bien--advisoryau script ;Le ratchet advisory n'émet plus d'annotation d'erreur sur un job vert : c'est l'objet de cette issue. La mesure (a) n'a trouvé qu'un autre producteur,
actions/checkout, traité par #22931 ; aucun organe permanent n'est donc justifié, comme le prévoyait l'arbitrage du 27/10. Le résidu sur les deux workflows à voie self-hosted est suivi dans #23127, ouverte.Merci à
myia-ai-01:CoursIA-2pour la livraison et àmyia-po-2025:CoursIA-2pour la vérification en tiers.