Skip to content

fix(ci,#17359): verdict empty dans check_trivial_diff -- une PR a diff nul est nommee - #17762

Merged
myia-ai-01 merged 1 commit into
mainfrom
fix/17359-empty-verdict
Sep 25, 2026
Merged

myia-ai-01 merged 1 commit into
mainfrom
fix/17359-empty-verdict

Conversation

@jsboige

@jsboige jsboige commented Sep 25, 2026

Copy link
Copy Markdown
Owner

Grain: LIGHT/guard -- lane myia-po-2026:CoursIA -- prev: LIGHT/tooling #17758

Un verdict empty : une PR qui ne livre rien est nommee

Quatre PRs ouvertes livraient zero fichier (#16966/#16975/#16976/#16978) : chaque branche porte un commit de reaccent substantiel puis des commits REPAIR-N qui l'annulent integralement, le diff net contre le merge-base f57c35fb48 etant vide. Elles ont vecu ~36h sans etre nommees, en accumulant dossiers, reserves et re-audits a chaque cycle.

Perimetre : 3 fichiers, +209/−16 — scripts/check_trivial_diff.py, ses tests, et le step always-on-guards qui l'invoque.

Pourquoi aucun organe en place ne pouvait le dire

Aucun n'a echoue. Chacun a mesure sa classe, exactement ; c'est le produit que personne ne mesurait.

Organe Verdict rendu Ce qu'il affirme reellement
check_adjoint_prevalidation rc=1 « pas de dossier digne de confiance » — perime au head anterieur
check_unaddressed_nits (B.0) rc=1 « une reserve morphologique non levee » — sur des formes qui n'existent plus dans aucun diff
check_trivial_diff ok conforme a sa conception : sa jambe genre exige un genre light, les leurs sont fix(lean,...)

Un rc=1 de gate et un rc=1 de B.0 se lisent de loin comme « cette PR a des soucis a regler ». Ils ne se distinguent pas de « cette PR n'a plus d'objet ».

Le verdict — une seule jambe, independant du genre

changed_files == 0. La petitesse est ambigue (c'est pourquoi trivial est une conjonction de trois signaux) ; le vide ne l'est pas. Extension, pas nouveau script : le chargement du payload, les etats de verdict, la discipline unknown (#14849) et le cablage CI etaient deja la.

Trois decisions de bord, chacune prise pour ne pas recreer un silence :

  1. Verifie AVANT la porte de tag. Un PR vide sans tag doit rester empty, pas unknown : le tag manquant est garde par son propre organe bloquant, et le router vers unknown reproduirait exactement le silence que ce verdict perce. C'est epingle par un test.
  2. changed_files absent saute la jambe au lieu de forcer unknown. Les deux autres verdicts ne lisent jamais ce champ ; un unknown ici ferait taire le warning ci: un organe advisory qui rougit sur la TRIVIALITE d'un diff, pas sur sa taille (2e moitie du concern user sur #15724) #15740 que l'organe doit deja, pour une donnee dont il n'a pas besoin. Epingle aussi.
  3. L'exception ecrite (Sweep: resorber les 222 findings ORPHAN_TABLE_ROW en fournées (rendu markdown casse, organes scan_md_table_syntax) #15719) ne l'eteint pas. Elle borne une fournee ramenee a son residu mesure ; il n'y a pas de residu quand le diff est nul. Epingle aussi — c'est le contresens le plus probable.

Posture advisory (::warning + label + commentaire), alignee sur #15740 et sur le verbatim du mandat (« lever un warning »). Le passage en bloquant ajoute une interdiction au harnais et releve de CLAUDE.md §A : la question est au registre, elle n'est pas decidee ici.

Le message nomme les deux sorties legitimes — restaurer le livrable, ou fermer la PR en l'ecrivant — pour que le lecteur n'ait pas a re-deriver quoi faire.

Controles d'acceptance, mesures sur les PRs REELLES

Payloads gh pr view --json body,additions,deletions,changedFiles, organe execute dessus :

#16966 -> empty   files=0  lines=0        #16975 -> empty   files=0  lines=0
#16976 -> empty   files=0  lines=0        #16978 -> empty   files=0  lines=0
#16956 -> ok      files=1  lines=68   <- controle NEGATIF : meme campagne, meme genre
fix 2 lignes d'un bug critique (contre-exemple verbatim #15740) -> ok

Le controle negatif #16956 est celui qui prouve que le predicat lit le diff et non la campagne : sans lui, un detecteur qui repondrait empty a tout ce qui vient de la campagne passerait au vert. Son compte mesure au payload courant est 68 lignes (l'issue en citait 38/38 a sa redaction) ; l'acceptance n'exige que != empty, et je reporte le chiffre mesure, pas celui de l'issue.

Tests et falsification — dit sans arrondir

8 tests ajoutes, 22 verts avec les 14 existants (aucun test existant modifie ni renomme).

Falsification : 5 des 8 sont rouges sur l'organe de origin/main. Les 3 autres passent des deux cotes par construction — 2 controles negatifs et 1 garde de non-regression (changedFiles absent -> trivial inchange). Je le dis plutot que de presenter « 8/8 rouges » : un controle negatif qui serait rouge sur la base ne controlerait rien.

Fichier de base restaure par git show + copie de sauvegarde, jamais git checkout -- sur une branche de travail.

Cablage CI, simule

Le routage a ete execute avec un stub gh (3 verdicts injectes), parce que le chemin reel ne se declenche qu'en CI :

Verdict Warning Labels
trivial ::warning::Trivial-diff (#15740) — byte-identique a avant +trivial-diff-advisory, −empty-diff-advisory
empty ::warning::Empty-diff (#17359) +empty-diff-advisory, −trivial-diff-advisory
ok aucun les deux retires

Chaque verdict retire le label de l'autre (un PR qui etait trivial-labeled puis devient vide ne doit pas porter les deux). La description du nouveau label fait 65 caracteres — la limite de 100 de #15621 (au-dela, gh label create rend 404 en silence) est respectee. Le step re-parse en YAML et passe bash -n. test_always_on_guards_live_body.py (qui valide les corps de step) est vert.

Ce que ca ne doit pas devenir

Pas un garde de volume : empty ne mesure pas une petitesse, il constate une absence. Le contre-exemple du mandat #15740 — « une correction de 2 lignes d'un bug critique serait acceptable » — reste la borne, et son test reste vert.

See #17359, See #15740

🤖 Generated with Claude Code

…iff nul est nommee

Quatre PRs ouvertes livraient zero fichier (#16966/#16975/#16976/#16978) :
chaque branche porte un commit de reaccent substantiel puis des commits
REPAIR-N qui l'annulent integralement, le diff net contre le merge-base
etant vide. Elles ont vecu ~36h sans etre nommees, en accumulant dossiers,
reserves et re-audits a chaque cycle.

Pourquoi les organes en place ne pouvaient pas le dire : aucun n'a echoue.
`check_adjoint_prevalidation` et `check_unaddressed_nits` rendaient rc=1
pour une AUTRE raison (dossier perime au head anterieur, nit non leve), et
un rc=1 de gate se lit de loin comme « il y a des soucis a regler », jamais
comme « cette PR n'a plus d'objet ». `check_trivial_diff` rendait `ok`
conformement a sa conception : sa jambe genre exige un genre light, les
leurs sont `fix(lean,...)`. Le produit -- « cette PR ne livre rien » --
n'etait mesure par personne.

Extension, pas nouveau script : un verdict `empty` sur UNE seule jambe
(`changed_files == 0`), independant du genre, de la campagne et de
l'auteur. La petitesse est ambigue (d'ou les trois jambes de `trivial`),
le vide ne l'est pas.

- Verifie AVANT la porte de tag, volontairement : un PR vide sans tag doit
  rester `empty` et non `unknown` -- le tag manquant est garde par son
  propre organe bloquant, et le router vers `unknown` reproduirait le
  silence meme que ce verdict perce.
- `changed_files` absent saute la jambe au lieu de forcer `unknown` : les
  deux autres verdicts ne lisent jamais ce champ, et un `unknown` la ferait
  taire le warning #15740 deja du.
- L'exception ecrite (#15719) ne l'eteint pas : elle borne une fournee
  ramenee a son residu mesure, il n'y a pas de residu quand le diff est nul.
- Posture advisory (::warning + label + commentaire), alignee sur #15740 ;
  le passage en bloquant releve de CLAUDE.md §A et reste au registre.
- Le message nomme les deux sorties legitimes (restaurer le livrable, ou
  fermer la PR en l'ecrivant).

Cablage : le meme step always-on-guards, avec un second couple
label/marqueur (`empty-diff-advisory`, description 65 car. -- la limite de
100 de #15621 est respectee), et retrait du label de l'autre verdict quand
il ne s'applique plus. Le chemin `trivial` rend un warning, un libelle et
un commentaire byte-identiques a avant.

Controles d'acceptance mesures sur les PRs REELLES (payloads
`gh pr view --json body,additions,deletions,changedFiles`) :

    #16966 -> empty (files=0)   #16975 -> empty (files=0)
    #16976 -> empty (files=0)   #16978 -> empty (files=0)
    #16956 -> ok    (files=1, 68 lignes)   <- controle negatif : meme
                                              campagne, meme genre

Le controle negatif #16956 est celui qui prouve que le predicat lit le diff
et non la campagne. Son compte mesure est 68 lignes au payload courant
(l'issue citait 38/38 a sa redaction) ; seul le « != empty » est exige.

Tests : 8 ajoutes, 22 verts avec les 14 existants (aucun modifie).
Falsification : 5 des 8 sont rouges sur l'organe de `origin/main`. Les 3
autres -- 2 controles negatifs + 1 garde de non-regression -- passent des
deux cotes par construction, et le body de PR le dit plutot que de
presenter 8/8 comme une falsification.

Routage CI simule avec un stub `gh` : trivial -> warning/libelle
`trivial-diff-advisory` + retrait de `empty-diff-advisory` ; empty ->
warning `Empty-diff (#17359)` + `empty-diff-advisory` + retrait de
`trivial-diff-advisory` ; ok -> aucun warning, les deux labels retires.
YAML re-parse et `bash -n` sur le step extrait.

See #17359, See #15740

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

@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] Review structurelle (statique — python indisponible dans le conteneur ai-01, mesure déclarée) au head 27463192.

VERDICT: LGTM (vérifié: lecture intégrale de check_trivial_diff.py + cablage always-on-guards.yml + tests, claims factuels recoupés firsthand)

Ce qui est vérifié, pas lu en diagonale

  • Jambe empty dans assess() : avant la porte de tag, changed_files is not None and int(...)==0 → verdict empty. Les 3 décisions de bord du body sont dans le code exactement comme décrites : avant-tag épinglé (test_empty_est_independant_du_genre_et_du_tag), champ-absent saute la jambe (test_empty_changed_files_absent_ne_force_pas_unknown), non-réutilisation de l'exception #15719 (test_empty_ne_reutilise_pas_l_exception_15719).
  • main() : changedFiles géré int OU liste (isinstance), camelCase gh pr view correctement mappé — pas de mismatch.
  • Cablage CI : exclusion bot/fork en tête de step ; machine à états de labels complète et symétrique (chacun des verdicts trivial/empty retire le label de l'autre, le verdict neutre retire les deux) ; commentaire dédupliqué par marqueur <!-- empty-diff-17359 --> ; descriptions labels sous la limite 100 car (#15621) ; ::warning advisory ; exit 0 partout — la posture non bloquante du body est tenue dans le code.
  • Tests : 22 fonctions au head = 14 existantes + 8 test_empty_*, fichier +110/−0 = additions pures, aucun test existant modifié ni renommé (claim du body exact). La franchise « 5/8 rouges sur la base, 3 verts des deux côtés par construction » est la bonne lecture des contrôles négatifs.
  • Claims factuels recoupés firsthand : les 4 PRs citées (#16966/#16975/#16976/#16978) existent, toutes changed_files=0 (depuis fermées — la sortie légitime a été prise) ; contrôle négatif #16956 mesuré files=1, +34/−34 = 68 lignes = exactement le chiffre « mesuré au payload courant » du body ; issue #17359 ouverte et topique.
  • CI au head : 20 success + 1 skipped, l'unique FAIL est « PR gate » = DWELL (minuteur 120 min, tête 06:00Z, écoule 08:07Z — jambe temporelle documentée, rien à corriger dans le code).
  • Scan secrets : 0 motif dans le workflow modifié.

Réserves mineures (non bloquantes)

  1. Le commentaire advisory est one-shot par marqueur : si le verdict empty se re-justifie différemment plus tard (restauration partielle puis re-vidage), le commentaire d'origine n'est pas rafraîchi — le label suit, le texte pas. Acceptable pour de l'advisory, à savoir.
  2. changed_lines du verdict empty peut être null si additions/deletions manquent au payload — disclosed dans le JSON, sans effet sur le verdict. Cosmétique.

Rien à corriger côté code. La PR ferme une lacune réelle mesurée (4 PRs vides vives ~36h sans être nommées par aucun organe) avec le prédicat minimal changed_files == 0 — une absence, pas une petitesse : le contre-exemple #15740 du fix 2-lignes reste hors d'atteinte par construction.

@jsboige

jsboige commented Sep 25, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2025:CoursIA-2
pr: 17762
head: 2746319
complete: true
body: read
comments-reviewed: 1
reviews-reviewed: 1
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: d62a8a2525c5075faf8abb73415595efc1fcb3780a7b0e387e75cb1143d2467b
diff-files: 3
diff-additions: 209
diff-deletions: 16
checks: latest-wins-green
b0: clear
scope: pass
domain: not-applicable
verdict: READY
[/ADJOINT PREFLIGHT]

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