Skip to content

feat(ci,#19251): mesurer les carnets RENAMED dans le balayage credited (previous_filename par REST) - #19261

Merged
myia-ai-01 merged 8 commits into
mainfrom
feature/19251-renamed-measured
Oct 5, 2026
Merged

myia-ai-01 merged 8 commits into
mainfrom
feature/19251-renamed-measured

Conversation

@jsboige

@jsboige jsboige commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

Grain: MED/guard — lane myia-po-2026:CoursIA — prev: MED/notebook-python #19259

Closes #19251

Quoi: le balayage post-mortem des exemples crédités (#19101) déclarait tout carnet RENAMED « NON MESURÉ » faute de connaître son chemin de base. Un git mv suivi d'une édition pouvait donc perdre un exemple crédité sans que rien ne le voie. Le chemin de base est désormais lu, et le renommage est mesuré comme un MODIFIED quand il est connu.

Empilée sur #19215 (fix/19101-credited-sweep) : même fichiers, donc pas de conflit au merge. La base de cette PR est la branche de #19215, pas main — à recibler après son merge.

Une hypothèse fausse, corrigée par le rejeu (à lire)

L'issue #19251 prescrivait de demander previousFilename au nœud GraphQL des fichiers. C'est faux, et le rejeu l'a montré : le type GraphQL PullRequestChangedFile n'a pas ce champ. Mesure, réponse du serveur :

Field 'previousFilename' doesn't exist on type 'PullRequestChangedFile'

Ses champs sont additions, changeType, deletions, path, viewerViewedState (vérifié par introspection du schéma). Le premier commit de cette branche demandait le champ : le rejeu --hours 72 rendait alors 0 PR mesurée, 309 replis nommés, là où la veille rendait 2 PR / 4 pertes. Une régression franche, pas un progrès — corrigée au second commit, et c'est le rejeu qui l'a attrapée avant toute review.

La source réelle

Le classement reste en GraphQL (changeType : ADDED / DELETED / RENAMED / MODIFIED). Le chemin de base vient d'une seconde source, REST : pulls/{n}/files, champ previous_filename — vérifié sur #19153 (ICT-45-InoculationBifurcation-9B → ICT-42b-InoculationBifurcation-9B). Cette passe n'est faite que si la PR porte au moins un renommage.

Piège attrapé par les tests, pas par relecture : la correspondance head_path → base_path est clée en POSIX (GraphQL et REST rendent des /) alors que str(Path) rend des \ sous Windows — sans normalisation, la correspondance raterait en silence et le renommage redeviendrait non mesuré (mutant M3).

Preuves

Perimetre: scripts/notebook_tools/credited_examples_sweep.py (source REST + classement), scripts/notebook_tools/check_pr_exercises.py (base_path_of), scripts/tests/test_credited_examples_sweep.py. Hors scope : la mesure des renommages dont le chemin de base reste inconnu (nommés, jamais comptés à zéro).

🤖 Generated with Claude Code

jsboige and others added 6 commits October 5, 2026 05:13
…advisory (balayage post-mortem)

La branche « exemples credites » posee par #18761 etait **dormante**. Le diff
exige `--base` ET `--pr-body-file` ; sous `schedule` -- seul declencheur qui
subsiste apres la tranche 1 de #12817 -- il n'y a pas de contexte PR, donc
`PR_NUMBER` est vide, donc `PR_BODY_FILE` reste vide, donc le test de
`exercises-advisory.yml` prend la branche sans `--base` et **saute le diff**.
Le label `credited-examples-lost` ne pouvait structurellement pas etre pose.

## Ce que livre la PR

`scripts/notebook_tools/credited_examples_sweep.py` : balayage **post-mortem**
des PRs mergees de la fenetre, rejouant le diff pour chacune avec SA base et
SON body (option 1 de #19101). Cable comme etape du workflow ; l'etape
existante n'est pas touchee.

**Aucun `pull_request` n'est reintroduit** sur ce workflow : c'est la
contrainte explicite de l'issue (le cout du clone par PR est la motivation
d'origine de #12817). Le compromis -- detecter les pertes passees, pas
proteger le merge -- est ecrit dans le module, pas seulement ici.

## Le piege trouve en mesurant

Premiere mesure sur 24 h : **6 « erreurs de diff »** sur 38 PRs. Instruites,
elles venaient toutes du meme cas : `git show <base>:<chemin>` sort en **128**
parce que le carnet n'est pas a ce chemin dans la base.

Ce n'etait pas cosmetique. `credited_diff_errors > 0` **bloque** la pose du
label (#18761) : une erreur structurelle sur un carnet empechait la mesure
reelle des carnets modifies de la meme PR. Un faux positif d'erreur produisait
un faux zero de pertes.

Trois cas separes, qui ne disent pas la meme chose :

  - `ADDED` : neuf, **rien a perdre** par construction (6 des 6 erreurs) ;
  - `RENAMED` : la base est a un **autre chemin**, que `gh pr view --json files`
    n'expose pas (`previousFilename` absent). Non comparable -- et contrairement
    a `ADDED`, un renommage **peut** perdre des exemples : declare NON MESURE,
    jamais « sans perte » ;
  - `DELETED` : exclu, comme le `--diff-filter=d` du workflow.

Les carnets non mesurables sont **nommes** dans le rapport au lieu de
disparaitre : un carnet tu se lirait comme un carnet conforme.

## Mesure

Fenetre de 24 h sur `main`, `--json`, sans `--apply`. Un diff en erreur n'est
PAS un zero mesure (#18761) : le rapport separe « pertes non exemptees = 0 » de
« carnets non mesurables », et n'affirme jamais une couverture que la mesure ne
porte pas.

## Tests

`test_credited_examples_sweep.py` : tout hors ligne (reseau et git injectes).
Le lot de l'API de recherche **au plafond** leve au lieu de passer pour un
compte ; le label ne se pose que si **tous** les diffs ont reussi ; un
`merge-base` indisponible est nomme ; un body illisible est nomme et la PR
ecartee ; un `ADDED` n'est pas mesure ; un `RENAMED` est declare non mesure ET
ne bloque plus les carnets modifies de la meme PR.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…c non masque

Reponse a la review CHANGES_REQUESTED du 05/10 (5 conditions) :

1. changeType n'existe pas dans gh pr list --json files (mesure gh 2.83.2
   sur #19040 : {additions, deletions, path} seulement). Les fichiers et
   leur nature viennent maintenant de GraphQL (pullRequest.files.nodes
   {path changeType}), pagines au curseur -- le repli « tout MODIFIED »
   classait GameTheory-18d (supprime par #19040) en modifie et levait
   FileNotFoundError, hors de tout garde.
2. check_notebooks recoit head_ref=headRefOid : le cote « apres » est la
   tete de la PR, pas l'arbre du moment du balayage. Le commit est amene
   par fetch-by-SHA s'il manque (PR squash-mergee) ; inatteignable = erreur
   nommee, PR ecartee -- jamais mesuree contre l'arbre du jour.
3. Tests : 17 -> 27. La forme reelle du lot (sans files/changeType) est
   desormais un fixture ; carnet supprime sans crash + carnets modifies
   de la meme PR toujours mesures ; pagination >100 fichiers ; tete
   inatteignable ; propagation de head_ref au check. Falsification :
   3 mutants (branche DELETED retiree, head_ref non passe, pagination
   coupee) -> chacun cuche par au moins un test ; restore vert 27/27.
4. Le masque || echo "::warning::..." du workflow est retire : un plantage
   permanent laissait le run vert chaque nuit (classe de defaut #19214).
   set -euo pipefail ; sans effet merge (schedule/dispatch seulement).
5. Rejou de la mesure 24 h sur le corpus reel : dans le corps de la PR.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…lisible n'emporte plus le balayage

Review 5411248369, deux voies demandees, les deux faites.

1. `check_notebooks` comptait les exercices sur l'ARBRE DU JOUR
   (`count_exercises_in_notebook(path)`) alors que le diff credite lit deja le
   blob de `head_ref`. Consequence mesuree : #18788 MODIFIE
   ICT-45-InoculationBifurcation-9B, #19153 le RENOMME ensuite -> le chemin est
   MODIFIED mais absent de l'arbre, et le comptage levait un FileNotFoundError
   qui emportait TOUT le balayage (`--hours 72`, rc=1 : les autres PR de la
   fenetre n'etaient pas mesurees). Le comptage porte desormais sur le blob de
   tete, qui existe par construction pour un chemin MODIFIED. La classification
   reste sur le chemin d'origine : `classify_notebook` lit les regles de
   REPERTOIRE, que le fichier temporaire du blob ne porte pas.

   Effet de bord voulu, sur le meme chemin : meme quand le carnet existe dans
   l'arbre, c'est la revision de la PR qui est comptee -- l'ancien code pouvait
   mesurer un arbre different de celui qu'il comparait.

2. Le repli par PR : un echec de `check` est NOMME (« carnet illisible depuis
   l'arbre du jour (FileNotFoundError: ...) -- renomme ou supprime apres
   merge, PR ecartee ») et la suite de la fenetre est mesuree. La portee du
   `except` est etroite (`OSError`, `ValueError`) : un autre type d'echec
   remonte, pour qu'un bug du compteur ne se derobe pas en « carnet renomme ».

Tests : 27 -> 32. Les nouveaux portent sur un depot git REEL (deux commits,
renommage effectif), pas sur un dict injecte : carnet MODIFIED absent de
l'arbre compte depuis le blob (3 exercices) ; l'arbre porte une autre version
(1 exercice) et la tete gagne ; sans `head_ref` l'arbre sert encore
(retro-compatibilite) ; un `check` qui leve est nomme et les autres PR sont
mesurees ; un `RuntimeError` remonte au lieu d'etre absorbe.

Falsification : 3 mutants, 3 rouges -- comptage remis sur l'arbre (2 tests),
repli par PR retire (1), `except` elargi a `Exception` (1). Source restauree,
32 passed.

Suivi RENAMED : issue #19251 ouverte avant merge (mesurer les renommages,
`previousFilename` absent du jeu GraphQL).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Le balayage post-mortem des exemples credites (#19101) declarait tout carnet
RENAMED « NON MESURE » faute de connaitre son chemin de base : `previousFilename`
n'etait pas demande au jeu GraphQL. Un `git mv` suivi d'une edition pouvait donc
perdre un exemple credite sans que rien ne le voie.

Le champ est desormais demande (`nodes { path changeType previousFilename }`).
Quand il est present, le renommage est MESURE comme un MODIFIED : le diff
credite lit `base:previousFilename` contre `head:path`. La correspondance passe
par `base_path_of`, consultee en POSIX -- GraphQL rend des `/` et `str(Path)`
des `\` sous Windows : sans la normalisation, la correspondance raterait en
silence et le renommage redeviendrait non mesure (mutant M3).

Quand `previousFilename` manque (renommage sous un seuil de similarite, ou
reponse d'API degradee), le carnet reste nomme NON MESURE -- et surtout, sans
correspondance la base est absente a ce chemin : le diff est en ERREUR, que
#18761 refuse de convertir en label. Jamais un zero silencieux.

Tests 32 -> 37 : le renommage mesure (un exemple credite perdu au `git mv` est
vu), le renommage sans perte (pas de faux positif), et l'absence de zero
silencieux sont epingles sur un depot git REEL ; le contrat de la carte en plus.
Falsification : 4 mutants, 4 rouges -- mapping ignore (M1), `previousFilename`
ignore (M2), cle non normalisee (doublure du bug Windows, M3), diff en erreur
converti en zero silencieux (M4) ; source restauree, 37 passed.

Au passage, les appels `subprocess` des tests touchés portent `encoding="utf-8",
errors="replace"` (garde #13140/#12811 : un hôte cp1252 leve sur un payload UTF-8).

Closes #19251

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

Correction du commit precedent, dont l'hypothese etait fausse et que le rejeu
`--hours 72` a demasquee : `previousFilename` N'EXISTE PAS sur le type GraphQL
`PullRequestChangedFile`. Mesure, serveur :

    Field 'previousFilename' doesn't exist on type 'PullRequestChangedFile'

Ses champs sont `additions, changeType, deletions, path, viewerViewedState`
(verifie par introspection). Demander le champ faisait echouer TOUTES les
lectures de fichiers : `--hours 72` rendait « 0 PR mesuree, 309 replis nommes »
au lieu des 2 PR / 4 pertes de la veille -- une regression, pas un progres.

Le classement reste donc en GraphQL (`changeType`), et le chemin de base est lu
par une SECONDE source, REST : `pulls/{n}/files`, champ `previous_filename`
(verifie sur #19153 : ICT-45 -> ICT-42b). Cette passe n'est faite que si la PR
porte au moins un renommage, et son echec laisse le renommage NON MESURE
(`previousFilename` rendu `""`) au lieu de le mesurer contre un mauvais chemin.

Tests 37 -> 40 : la passe REST enrichit un noeud RENAMED, elle est sautee quand
rien n'est renomme, et son echec ne casse rien. Falsification : 5 mutants, tous
rouges (dont M5 : passe REST supprimee).

Closes #19251

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

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Base != main (advisory, #10918)

Cette PR ne livre pas sur main : son contenu attend le merge de fix/19101-credited-sweep. 1 PR ouverte(s) de fix/19101-credited-sweep vers main existe(nt) a cet instant -- c'est un stack legitime, le contenu est en vol. Verifier au moment du merge que la base est effectivement reliee a main.

Couverture CI perdue sur cette base (mesure, #16194)

6 workflow(s) se declencheraient si cette PR visait main, et ne se declenchent pas ici : leur filtre de branche cible les eteint, alors que leur filtre de chemins est satisfait par les fichiers de cette PR.

  • always-on-guards.yml
  • notebook-plan-loss-gate.yml
  • organ-duplication-advisory.yml
  • pr-gate.yml
  • scripts-tests.yml
  • secret-scan.yml

Un check absent n'est pas un check vert. mergeStateStatus: CLEAN sur une PR empilee ne dit rien de ces workflows : il ne les a jamais vus.

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

[Hermes] VERDICT: LGTM (2 réserves mineures, aucune bloquante)

#19251 — le balayage credited mesure désormais les carnets RENAMED via previous_filename (REST), toujours nommés NON MESURÉS sinon. Review avec exécution du code du head (gate review) :

Vérifié firsthand (head d29f549f) :

  • Tests 40/40 passed en local : fichiers du head extraits, harnais reconstitué (count_exercises, check_credited_examples siblings requis — leur absence fait silencieusement _HAS_CREDITED=False → tout skipped, 5 faux échecs initiaux qui étaient le défaut de MON harnais, pas de la PR).
  • Cas motivateur reproduit : pr_files("jsboige/CoursIA", 19153) rend le nœud RENAMED enrichi ICT-42b → ICT-45 + 6 MODIFIED intacts — exactement le claim du body, mesuré contre l'API réelle.
  • La classe end-to-end TestARenamedNotebookIsMeasuredAtItsOldPath exécute un vrai dépôt git (git mv effectif) : perte vue (=1), sans-perte pas faux positif (=0), absence de mapping → error: jamais zéro silencieux. Les 3 assertions cardinales du #19251.
  • Diff complet lu : clé POSIX avant lookup (piège Windows documenté), pagination REST explicite en GET (-f ferait basculer en POST — correct), repli previous={} sur échec REST → NON MESURÉ nommé, pas mesuré contre le mauvais chemin.
  • Grain présent, gates du head verts (2 organes), issue #19251 lue.

Réserves mineures :

  1. _previous_filenames s'arrête sur len(payload) < 100 mais ne borne pas page (une API renvoyant indéfiniment 100 lignes bouclerait) — théorique, les fichiers de PR sont plafonnés à 3000 par l'API.
  2. Les renommages hors du champ REST restent non mesurés — hors scope assumé au body, mentionné pour mémoire.

Pile #19215 : base = branche de #19215, à recibler après son merge (déjà noté au body).

[Hermes hermes-pr-review, cycle :08 05/10, host f6be46d1b7a3, sig=8d7c8f47]

… after #19215 squash-merge)

# Conflicts:
#	scripts/notebook_tools/credited_examples_sweep.py
#	scripts/tests/test_credited_examples_sweep.py
@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 Oct 5, 2026
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

G-VAR-2 light cap reached (advisory, non bloquant).
La lane myia-po-2026:CoursIA a deja consomme son budget LIGHT du jour (axe genre G-VAR-2/3 (light-genre, quel que soit le tier declare) : #19048 (LIGHT/notebook-twins, merge a 2026-10-05T00:37:49Z), #19043 (MED/ledger, merge a 2026-10-05T02:18:38Z), #19191 (MED/guard, merge a 2026-10-05T03:10:09Z), #19194 (MED/guard, merge a 2026-10-05T03:18:12Z), #19190 (MED/guard, merge a 2026-10-05T03:42:10Z), #19201 (MED/guard, merge a 2026-10-05T03:44:44Z), #19034 (LIGHT/prose, merge a 2026-10-05T04:19:29Z), #19208 (MED/docs, merge a 2026-10-05T04:29:48Z), #19214 (MED/guard, merge a 2026-10-05T05:02:14Z), #19044 (MED/ledger, merge a 2026-10-05T06:26:47Z), #19215 (MED/guard, merge a 2026-10-05T10:10:04Z)).
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-cap-exceeded light_genre > cap partage G-VAR-2 (#10020, advisory) labels Oct 5, 2026
@github-actions

github-actions Bot commented Oct 5, 2026

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-10-05) :

  • TIER-INFLATION : declared LIGHT << effective LIGHT-genre (tally : declared=2 genre=11 cap=5)
  • CAP-EXCEEDED-BY-GENRE : light_genre > cap partage G-VAR-2 (tally : declared=2 genre=11 cap=5)

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.

@github-actions github-actions Bot added the lane-claim-absent Closing issue carries no claim at all (#10223) label Oct 5, 2026
@github-actions

github-actions Bot commented Oct 5, 2026

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 commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #19261 (feat(ci,#19251): mesurer les carnets RENAMED dans le balayage credited (previous_filename par REST)) 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.

Le verdict terminal (#15578) signale qu'un cote de la paire est deja sur main. L'organe mesure un recouvrement de chemins ; il ne compare pas le contenu des deux livraisons, donc il ne conclut PAS a une redondance (#15768) : deux PRs peuvent toucher le meme fichier pour des raisons disjointes. L'arbitrage reste a la lane ou au coordinateur.

@jsboige

jsboige commented Oct 5, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2026:CoursIA-3
pr: 19261
head: d118978
complete: true
body: read
comments-reviewed: 5
reviews-reviewed: 1
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: 1209bb00f3b18a245d18f8770d8b3d474b8a48b80d42c1e166bdb030d2144996
diff-files: 3
diff-additions: 387
diff-deletions: 46
checks: latest-wins-green
b0: clear
scope: pass
domain: not-applicable
verdict: READY
organ: check_adjoint_prevalidation.py
organ-command: python scripts/check_adjoint_prevalidation.py --derive-verdict 19261
organ-rc: 0
[/ADJOINT PREFLIGHT]

@myia-ai-01
myia-ai-01 merged commit edfd197 into main Oct 5, 2026
23 of 26 checks passed
@jsboige
jsboige deleted the feature/19251-renamed-measured branch October 7, 2026 07:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lane-claim-absent Closing issue carries no claim at all (#10223) variation-genre-cap-exceeded light_genre > cap partage G-VAR-2 (#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.

Mesurer les carnets RENAMED dans le balayage credited (previousFilename hors du jeu GraphQL)

3 participants