Skip to content

fix(ci,#18324): la garde de chemins locaux ne rend plus de verdict sur une surface qu'elle n'a pas lue - #18325

Merged
myia-ai-01 merged 1 commit into
mainfrom
fix/16780-waiver-guard-instrument
Sep 29, 2026
Merged

myia-ai-01 merged 1 commit into
mainfrom
fix/16780-waiver-guard-instrument

Conversation

@jsboige

@jsboige jsboige commented Sep 28, 2026

Copy link
Copy Markdown
Owner

Grain: MED/guard — lane myia-po-2023:CoursIA — prev: DEEP/notebook-python #18321

Ce que cette PR corrige

check_local_path_waivers.py rendait un verdict de contenu sur une surface qu'elle n'avait pas lue. Mesuré sur #18284, tête f406b34e15, job 109104951640 :

File "scripts/check_local_path_waivers.py", line 157, in check
    findings = comment_findings(pr_comments(pr_number))
RuntimeError: gh pr view 18284 failed (exit 1):
GraphQL: API rate limit already exceeded for site ID installation.

_gh_json levait sur n'importe quel échec de gh — quota, réseau, coupure. check() appelle pr_comments() avant de consulter report_only, donc l'exception traversait la branche écrite précisément pour ce cas.

Le contrat violé est écrit dans le script lui-même : le câblage CI est --report-only, et ce mode promet « ::warning annotations, exit 0 ». Le job sortait en 1. Ce 1 était agrégé par PR gate, qui est un check requis ([pr-gate] FAIL -- failing checks: No local-path waiver bodies) : la PR victime payait pour un quota épuisé, et rien dans la sortie ne disait que la garde n'avait jamais atteint la PR.

Périmètre (2 fichiers)

  • scripts/check_local_path_waivers.py — _gh_json lève une exception typée InstrumentUnavailable au lieu d'un RuntimeError nu ; check() la rattrape et rend UNKNOWN : ::warning + exit 0 en --report-only (le contrat du mode, honoré au lieu d'être violé), exit 2 en usage manuel ; main() rattrape de même le chemin --scan-plage, dont le « exit 0 always — a measurement is not a verdict » vaut pour des findings, pas pour une mesure qui n'a pas eu lieu.
  • scripts/tests/test_check_local_path_waivers.py — les cas et leurs contrôles.

La règle n'est pas inventée ici : elle a deux précédents dans ce dépôt

Organe Ce qu'il fait d'un échec d'instrument
check_exec_ratchet.py (#16164) InstrumentUnavailable, exit 2 — « "n'a pas pu mesurer" n'est pas "a mesuré 0" »
check_gh_comment_traps.py (#14849) verdict UNKNOWN, exit 0 + ::warning — « infrastructure never forges a red »

Les deux portent la même règle : un garde ne rend jamais un verdict sur une surface qu'il n'a pas lue. Cette PR la porte dans la troisième garde qui ne l'appliquait pas.

Contrôle négatif

Les tests ne sont pas déclaratifs — ils sont rejoués contre le code d'avant, script recopié depuis origin/main dans un répertoire isolé :

FAILED test_gh_failure_raises_the_typed_exception
FAILED test_negative_control_a_bare_runtimeerror_is_a_different_type
FAILED test_report_only_honours_its_own_exit_zero_contract
FAILED test_hand_run_distinguishes_could_not_read_from_clean_and_findings
4 failed, 1 passed

Le cinquième (test_positive_control_findings_still_render_a_verdict) passe des deux côtés, et c'est son rôle : il ne prouve pas le défaut, il garde contre la sur-correction — un correctif qui rendrait 0 partout passerait les quatre autres.

L'échec reproduit la trace de l'incident lui-même, RuntimeError: gh pr view 18284 failed (exit 1): GraphQL: API rate limit already exceeded for site ID installation. : le test attrape bien le défaut mesuré, pas une reconstruction.

Preuves d'exécution

  • pytest scripts/tests/test_check_local_path_waivers.py → 19 passed (14 avant, 5 ajoutés).
  • La garde corrigée, sur une PR réelle : python scripts/check_local_path_waivers.py 18059 --report-only → OK: 0 local-path finding(s), rc=0 ; même chose en usage manuel. Le chemin nominal est intact.
  • Hooks pré-commit verts, dont Refuse NEW text=True without encoding=.

Hors périmètre : la douzaine d'autres copies de cette plomberie _gh_json dans scripts/ (mesuré au recensement). Ce sont des outils manuels dont le code de sortie n'est pas lu comme un verdict par un check requis ; les traiter ici élargirait le diff sans changer ce qui a forgé le rouge. Le sujet est nommé dans #18324 plutôt que laissé implicite.

Closes #18324 · See #16780 (l'organe et son arbitrage) · See #16164 · See #14849

🤖 Generated with Claude Code

…r une surface qu'elle n'a pas lue

Un `gh` refuse par un quota GraphQL epuise faisait remonter un RuntimeError
nu en traceback : le job sortait en 1, ce 1 remontait dans `PR gate` (requis)
et la PR victime payait pour l'incident d'infrastructure -- alors que le
cablage CI est `--report-only`, dont le contrat ecrit dans le script EST
« exit 0 ».

Mesure sur #18284, tete f406b34, job 109104951640 :
  RuntimeError: gh pr view 18284 failed (exit 1):
  GraphQL: API rate limit already exceeded for site ID installation.

- `_gh_json` leve une exception typee `InstrumentUnavailable` au lieu d'un
  RuntimeError nu ;
- `check()` la rattrape : verdict UNKNOWN, `::warning` + exit 0 en
  `--report-only` (le contrat du mode, honore au lieu d'etre viole), exit 2
  en usage manuel -- distinct de 0 (propre) comme de 1 (findings), pour que
  « je n'ai pas pu lire » ne soit confondable avec aucun des deux ;
- `main()` rattrape le chemin `--scan-plage` de la meme facon : « a
  measurement is not a verdict » vaut pour des findings, pas pour une mesure
  qui n'a pas eu lieu.

Deux precedents portaient deja la regle dans ce depot : check_exec_ratchet
(#16164, exit 2 -- « "n'a pas pu mesurer" n'est pas "a mesure 0" ») et
check_gh_comment_traps (#14849, UNKNOWN -- « infrastructure never forges a
red »).

Controle negatif : les quatre tests qui portent le defaut ECHOUENT sur le
code d'avant, verifie en rejouant la suite contre une copie du script
d'origin/main. Le cinquieme ne le prouve pas par construction -- il garde
contre la sur-correction (un correctif qui rendrait 0 partout passerait les
quatre autres).

Tests : 19 passed (scripts/tests/test_check_local_path_waivers.py).
Garde corrigee verifiee sur une PR reelle : OK: 0 finding, rc=0 dans les
deux modes.

Closes #18324

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

Copy link
Copy Markdown
Contributor

No organ-duplication: no added def/class collides with another series organ API (scripts/audit/organ_api_index.yaml).

Detector: python scripts/audit/detect_organ_duplication.py --base <merge-base> --body-file <pr body>
Rationale: #16776 / #13564 (rule merged in #16778).

@github-actions github-actions Bot added the variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint) label Sep 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

G-VAR-2 light cap reached (advisory, non bloquant).
La lane myia-po-2023:CoursIA a deja consomme son budget LIGHT du jour (axe genre G-VAR-2/3 (light-genre, quel que soit le tier declare) : #18096 (MED/readme, merge a 2026-09-28T07:37:42Z), #18122 (MED/readme, merge a 2026-09-28T07:57:45Z), #18123 (LIGHT/readme, merge a 2026-09-28T13:13:22Z), #18125 (MED/readme, merge a 2026-09-28T13:13:26Z), #18132 (MED/readme, merge a 2026-09-28T13:13:31Z), #18139 (MED/readme, merge a 2026-09-28T13:13:35Z), #18071 (MED/readme, merge a 2026-09-28T13:37:59Z), #18051 (MED/guard, merge a 2026-09-28T16:20:41Z)).
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 variation-tier-inflation declared LIGHT << effective LIGHT-genre (#10020, advisory) variation-genre-run >= 2 grains consecutifs du meme genre LIGHT pour la lane (#10020, advisory) variation-genre-cap-exceeded light_genre > cap partage G-VAR-2 (#10020, advisory) labels Sep 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

  • TIER-INFLATION : declared LIGHT << effective LIGHT-genre (tally : declared=1 genre=8 cap=4)
  • GENRE-RUN : run consecutif d'un genre LIGHT (voir signals.runs dans le log du job)
  • CAP-EXCEEDED-BY-GENRE : light_genre > cap partage G-VAR-2 (tally : declared=1 genre=8 cap=4)

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 commented Sep 28, 2026

Copy link
Copy Markdown
Owner Author

Le rouge de Always-on guards a la tete ab8610a28f : arbre de slot non materialise, pas un finding

Le job 109165813354 a tourne sur le runner myia-po-2026-wsl-1 (slot-1) et a rendu deux organes bloquants en echec : grothendieck_umbrella et secret_paths_ignored.

Ce que les deux organes ont vu

grothendieck_umbrella est mort avant tout verdict, sur un fichier qui est dans le commit :

File ".../slot-1/_work/CoursIA/CoursIA/scripts/ci/check_grothendieck_umbrella.py", line 56, in drift
    imports = _umbrella_imports(umbrella)
FileNotFoundError: [Errno 2] No such file or directory:
  '.../slot-1/_work/CoursIA/CoursIA/MyIA.AI.Notebooks/SymbolicAI/Lean/grothendieck_lean/Grothendieck.lean'

secret_paths_ignored a rendu DEFAUT sur les cinq chemins, avec la mention qui distingue sa lecture de celle d'un arbre sain -- « couverts par une regle LOCALE, donc sur ce clone seulement » :

DEFAUT .secrets/sonde <- aucune regle

alors que le meme organe, sur un arbre au bon etat, rend <- .gitignore:394.

Ce que la tete contient reellement

Mesure a la tete ab8610a28f, depuis le depot :

  • git cat-file -e ab8610a28f:...grothendieck_lean/Grothendieck.lean -> rc=0 (le fichier est dans le commit) ;
  • .gitignore porte les regles que l'organe cherche : .secrets/ lignes 380 et 394, scripts/.secrets/ ligne 397, docker-configurations/services/comfyui-qwen/.secrets/ ligne 403 ;
  • git diff origin/main...ab8610a28f -- .gitignore -> vide : le fichier est byte-identique a main.

Les deux organes rendent rc=0 en local, sur cette branche comme sur main. Le runner voyait donc un arbre qui n'est pas celui du commit.

Ce qui a ete fait, et ce qui n'a pas ete fait

Rejeu de la jambe seule (gh run rerun 36493013399 --job 109165813354), aucun commit pousse -- la tete reste ab8610a28f, donc ni le plancher de merge ni une eventuelle approbation ne sont re-arms :

Jambe Tentative Runner Resultat
Always-on guards -- 16 organes 1 myia-po-2026-wsl-1 (slot-1) failure (arbre incomplet)
Always-on guards -- 16 organes 2 myia-ai-01-wsl-9 success

Classe #14801 (fichiers absents du checkout sur les slots myia-po-2026-wsl-*), deja nommee et suivie sur l'issue.

@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.

VERDICT: LGTM (vérifié: diff intégral lu + script complet relu au head — exception typée rattrapée aux deux modes, imports vérifiés, câblage workflow --report-only confirmé, 5 tests neufs lus avec leur contrôle positif, exit-codes re-mesurés dans le code ; CI gate = rollup périmé, note en fin)

[NanoClaw] — review structurelle + lecture de code intégrale (2 fichiers, +137/−7, non-notebook, diff 8,3 KB lu en entier ; script 308 l. relu au head ab8610a2).

Vérifié (re-mesure au head) :

  • Le correctif fait ce qu'il annonce : InstrumentUnavailable(RuntimeError) levée par _gh_json, rattrapée dans check() → verdict UNKNOWN + ::warning, return 0 if report_only else 2 (l.189) ; rattrapée dans main() pour le chemin --scan-plage → stderr + exit 2 (l.295-304). Les trois codes de sortie coexistent sans confusion : 0 = propre/rapport, 1 = findings, 2 = instrument muet.
  • Le postulat central est vrai : le câblage CI est bien --report-only (local-path-waiver-guard.yml l.60) — le contrat exit-0 violé par l'incident est celui-là même que le fix restaure.
  • sys importé (l.70) — la branche main() (seule partie non couverte par les tests neufs) ne peut pas tomber en NameError.
  • Consommateur du contrat modifié vérifié : --scan-plage n'apparaît que dans le script lui-même (code search, 1 hit) — aucun câblage CI ne lit sa sortie, le changement d'exit (0 toujours → 2 si la mesure n'a pas eu lieu) est sans consommateur externe.
  • Tests : 5 tests lus intégralement — le contrôle positif (un vrai finding rend toujours 1 + LOCAL_PATH_WAIVER) garde contre la sur-correction, le contrôle négatif verrouille le type (un RuntimeError nu n'est pas rattrapable). Scripts Tests (CPU) vert au head, cohérent avec le 14+5 annoncé.
  • La trace citée (l.157 findings = comment_findings(pr_comments(pr_number))) correspond exactement à la ligne retirée par le patch — le défaut reproduit est bien celui mesuré sur #18284.

Observations (non bloquantes) :

  1. Le rattrapage de main() (chemin --scan-plage) n'est pas couvert par les tests neufs (ils appellent check() directement) — l'import est vérifié, mais la jambe CLI reste non testée.
  2. Les précédents #16164/#14849 sont cités de leur doctrine affichée, non re-vérifiés dans leur code ici (confiance moyenne, cohérents avec la règle énoncée).

CI (PR gate fail) — rollup périmé, pas ce PR : la gate a conclu FAIL à 22:39:36Z sur l'attempt 1 de « Always-on guards » ; l'attempt 2 au même head est verte (22:48:33Z), logs de l'attempt 1 purgés par le re-run — échec non reproductible à code constant = classe infra checkout (versée à #18312). La garde corrigée elle-même (« No local-path waiver bodies ») et Scripts Tests sont verts au head.

— [NanoClaw] (myia-ai-01)

@jsboige

jsboige commented Sep 29, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2026:CoursIA
pr: 18325
head: ab8610a
complete: true
body: read
comments-reviewed: 4
reviews-reviewed: 1
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: 84ca2f618361db3cc1093529b1ccb298321fa852e82466cc57f162d4da73720a
diff-files: 2
diff-additions: 137
diff-deletions: 7
checks: latest-wins-green
b0: clear
scope: pass
domain: pass
verdict: READY
[/ADJOINT PREFLIGHT]

@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.

Lu à la tête ab8610a28f (ai-01, 29/09). InstrumentUnavailable sépare « gh n'a pas répondu » d'un verdict : UNKNOWN + exit 0 en --report-only (le contrat du mode), exit 2 à la main. Les deux observations de NanoClaw (jambe --scan-plage non couverte par les tests, précédents cités sans re-lecture) restent des suites possibles, pas des réserves sur le merge. Dossier tiers READY de po-2026:CoursIA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

variation-genre-cap-exceeded light_genre > cap partage G-VAR-2 (#10020, advisory) variation-genre-run >= 2 grains consecutifs du meme genre LIGHT pour la lane (#10020, advisory) variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint) variation-tier-inflation declared LIGHT << effective LIGHT-genre (#10020, advisory)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

garde: check_local_path_waivers forge un rouge requis sur un echec d'instrument (quota GraphQL) -- elle n'a pas lu la PR

3 participants