Skip to content

Feat(budget,#23174): budget de creation de carnets a l'echelle de la flotte + garde de provenance (advisory) - #23185

Merged
myia-ai-01 merged 2 commits into
mainfrom
feature/23174-creation-budget
Oct 29, 2026
Merged

myia-ai-01 merged 2 commits into
mainfrom
feature/23174-creation-budget

Conversation

@jsboige

@jsboige jsboige commented Oct 28, 2026 •

Copy link
Copy Markdown
Owner

Grain: DEEP/tooling -- lane myia-ai-01:CoursIA-2 -- prev: DEEP/notebook-python #23166

Budget de creation de carnets a l'echelle de la flotte + garde de provenance (#23174)

See #23174 — la PR livre les trois morceaux demandes par l'issue, chacun
avec sa mesure. Le regime ralentit la creation, il ne l'arrete pas :
rien n'est bloque sans un compte qui depasse, et tout repart des que le
compte redescend.


1. Ce que l'issue demande, et ce que cette PR livre

Piece demandee Livree ou Ou
1. Budget de creation a l'echelle de la flotte, derive d'une mesure, cause nommee BUDGET CRÉATION oui scripts/creation_budget.py (detection + derive), scripts/ci/creation_budget_counter.py (l'etat), scripts/pick_idle_grain.py (la porte)
2. Provenance d'un carnet neuf (issue servie par un tirage, ou creee >= 48 h avant la PR), advisory d'abord oui scripts/ci/check_creation_provenance.py + .github/workflows/creation-provenance-advisory.yml
3. Simulation de la semaine 21-28/10 dans le body, controle negatif compris oui §4 et §5 ci-dessous, rejeu par scripts/ci/simulate_creation_budget.py

Hors perimetre, comme demande : le tri des 84 EPICs de creation, et le
plancher G-VAR-1 (inchange).


2. La mesure qui etablit le fait

Avant d'ecrire une porte, on mesure l'ecart sur des PRs ouvertes (le
realite, pas l'intention) :

  • 22 PRs ouvertes ajoutent chacune un .ipynb (4 le 26/10, 5 le 27/10,
    13 le 28/10) ; 100 modifient un carnet existant ;
  • sur origin/main, 89 carnets neufs ont ete ajoutes en 7 jours glissants
    (git log --diff-filter=A, 86 hors jumeaux _en), 121 par les PRs de
    la flotte creees dans la semaine 21-28/10 (statut added, hors jumeaux).

C'est cet ecart qui etablit qu'il y a une derive a ralentir. Le regime ne
s'appuie pas sur une intention.

Mesures remesurees le 2026-10-28 apres la correction de la population
de la garde de provenance (§7).
La premiere version de ce paragraphe
comptait comme « ajout de carnet » les PRs qui modifient un carnet
existant : d'ou les 30 et 120 d'aujourd'hui, et les 143 « par PRs de
flotte » de la version initiale. L'API des fichiers d'une PR porte le
statut GitHub (added / renamed / modified) : seule la premiere fois
porte added. Les trois populations sont des PRs de flotte ; les
diffs sont dus au filtre de statut, pas a un changement de perimetre.
Les compteurs main ont aussi recule (90 -> 89) : la fenetre glissante
de 7 jours se referme au fil des merges.

3. La detection : le depot est l'oracle, la prose est un temoignage date

« Ce grain livre-t-il un carnet neuf ? » se repond en opposant le depot
(git ls-tree -r origin/main) a l'issue :

  • O1 serie + rang : le rang est-il deja livre dans cette serie ?
  • O2 fichier cite : l'issue cite-t-elle un .ipynb absent du depot ?
  • O3 phrase : l'issue DECLARE-t-elle la creation ?

Un veto de reparation (audit, correction, renumerotation, collision,
prose) l'emporte sur les trois : un grain de reparation n'est jamais
ecarte
. Le sens de l'erreur est choisi — on saute un carnet neuf
quand on le sait, on ne saute pas un grain qu'on ne sait pas classer.

Precision mesuree (jeu de controle derive des PRs ouvertes : positifs =
Add|Feat qui ajoutent un .ipynb absent de main, negatifs =
Fix|Docs|Refactor qui modifient un .ipynb present) :

Detecteur Precision Rappel
cette PR (scripts/creation_budget.py) 1,00 (0 faux positif / 55) 0,92 (11 des 12 positifs classables ; 7 indetermines)
polarite existante (series_saturation.polarity) 0,67 0,11

L'issue demandait de mesurer la precision de la polarite existante avant
de s'y fier
: la mesure dit qu'elle ne peut PAS etre le critere. Elle voit
21 des issues de creation du pool, et elle classe 13 issues
« consolidation » dont la livraison est en realite un carnet neuf
. La
polarite reste un signal, jamais le critere — c'est aussi pourquoi la
regle de l'issue (« un EPIC hors consolidation, ou une livraison = carnet
neuf ») est appliquee au sous-grain effectif : lue litteralement sur
l'EPIC, elle ecarte 30 EPICs qui ne creent rien (soutien des sous-modules,
audits de series, bibliographie, slimming du harnais, tranches de tests .NET).

Le label consolidation est un veto : un grain declare consolidation
n'est jamais ecarte. C'est le controle negatif de l'issue, et il a attrape
un cas reel : #23175 (registre anti-entropie) porte ce label et son corps
parle de « carnet » — sans ce veto, il etait ecarte.

4. Le budget : derive, jamais pose

part_creation = propositions de creation du pool / pool ouvert
budget        = max(1, round(part_creation x grains livres la semaine))

Mesure du 2026-10-28 (pool ouvert reel) :

Grandeur Valeur Source
pool ouvert 635 gh issue list (le meme pool que le tapis)
propositions de creation 104 scripts/creation_budget.py::creation_proposals
part de creation 0,164 104 / 635
grains livres (issues fermees sur 7 j) 307 gh issue list --state closed
budget derive 50 round(0,164 x 307), plancher 1 non applique
carnets neufs demarres (7 j glissants) 90 git log --diff-filter=A sur origin/main
depassement 90 > 50 la porte s'active

Un plancher de 1 : le regime ralentit, il n'arrete pas. L'echappatoire
est ecrite (--ignore-creation-budget), jamais silencieuse.

5. La simulation — rejeu de la semaine 21-28/10

python scripts/ci/simulate_creation_budget.py --since 2026-10-21 --until 2026-10-28

Le rejeu se fait jour par jour : le compteur de carnets neufs demarres sur 7
jours glissants est compare au budget, et un carnet neuf ecarte ne demarre
pas
— le compteur simule ne monte donc jamais au-dela du budget.

Jour carnets neufs reels servis sous le regime ecartees
21/10 0 0 0
22/10 9 9 0
23/10 3 3 0
24/10 28 28 0
25/10 3 3 0
26/10 22 7 15
27/10 5 0 5
28/10 16 0 16
total 86 50 36

Ce que le budget aurait servi : 50 carnets neufs au lieu de 86 ; 36
n'auraient pas demarre. La porte s'active le 26/10, quand le compteur
franchit le budget.

Rejeu relance le 2026-10-28 apres la correction de la mesure de provenance
(§7) : les chiffres du rejeu sont restés 50 servis / 36 écartés ; les
totaux journaliers ont bougé de 88 a 86 parce que la fenetre de commit
glissante sur origin/main a recule de deux carnets entre les deux
releves. Ce qui est mesure ici, c'est le regime applique a la fenetre
21-28/10 telle qu'elle est aujourd'hui.

Ce tableau est une photo, pas un rejeu deterministe. Un rejeu ulterieur
sur la meme fenetre peut decaler de ±2 servis (mesure : 86 réels / 50
servis / 36 écartées au rejeu du 2026-10-28 ; 86 / 50 / 36 au rejeu de
Hermes sur main freschi, avec un decalage journalier de ±1). La cause
est identifiee : le pool vit entre deux releves — un carnet merge sur
main entre le 21 et le 28/10 glisse d'un jour a l'autre dans la fenetre
glissante. Ce qui est stable, c'est le budget (50) et le total
ecarte
(36) ; ce qui bouge, c'est la repartition par jour. Le tableau ne
se presente donc pas comme une assertion reproductible a l'identique, et
une difference de ±2 sur un rejeu ulterieur n'est pas un defaut du
regime.

6. Le controle negatif — aucune issue de consolidation ou de correction n'est ecartee

Le veto est dans le code ; le controle le MESURE sur le pool reel et
affiche la liste des ecartees, pour qu'un lecteur puisse la relire ligne a
ligne.

Mesure Resultat
grains de creation ecartees par la porte sur le pool (635 issues) 34
dont consolidation ou correction 0
verdict OK

Le controle est double : le veto de reparation par le titre (audit,
correction, renumerotation, collision, prose) et le veto du label
consolidation. Les deux sont portes par des tests, et deux faux positifs
reels mesures ont ete corriges en consequence (#22056 : une collision
d'identifiant est une renumerotation ; #21871 : « le PDF a ete archive 11
jours
apres la PR » — une duree lue comme un rang de carnet).

7. La garde de provenance (advisory)

Un carnet neuf doit avoir attendu son tour : l'issue servie a ete servie
par un tirage du tapis (draw_id), ou elle a ete creee au moins 48 h
avant l'ouverture de la PR.

Ce que la garde aurait signale sur la semaine 21-28/10 (mesure independante
sur les 120 PRs de flotte qui AJOUTENT un carnet neuf) :

Tranche PRs
issue servie depuis moins de 48 h 65
issue servie depuis plus de 48 h 47
sans issue servie 8

Au seuil de 48 h : 73 PRs signalees sur 120 (65 trop recentes + 8 sans
issue servie). L'issue attendait « ~68 sur 120 » : la mesure donne
73 sur 120.

La population mesuree a change, et le nombre avec. La premiere mesure
comptait 126 PRs et annoncait 69 signalees sur 118 : elle comptait
comme « ajout de carnet » les PRs qui modifient un carnet existant.
L'API des fichiers d'une PR porte le statut GitHub (added, renamed,
modified) : seule la premiere fois porte le statut added. La mesure
ne compte donc plus que les AJOUTS reels. C'est la meme population que
celle que la garde elle-meme examine (scripts/ci/check_creation_provenance.py,
qui filtre sur status == "added") : l'ecart entre ce que la simulation
annonce et ce que la garde ferait sur le meme PR est nul
— les deux
comptent 120 PRs et 73 signalements.

Exemptions (posees par l'issue) : PRs etudiantes et externes ;
demenagements (RENAMED — et les added+removed de meme nom, que GitHub
rapporte comme un ajout quand la similarite est sous son seuil : mesure
reelle, PR #21733) ; jumeaux _en.

Advisory d'abord : le job sort toujours en 0 et porte son verdict par
des annotations ::warning:: et par le resume de job. Le journal de tirage
est un fichier local a la machine : en CI il est generalement absent, et
le critere du tirage est alors NON EVALUABLE — le script le dit, il ne
pretend pas avoir mesure ce qu'il n'a pas mesure.

8. Comment ca s'active

Piece Role Etat par defaut
scripts/ci/creation_budget_counter.py mesure et ecrit l'etat (JSON) a lancer (cron ou a la main)
scripts/pick_idle_grain.py lit l'etat, ecarte les grains de creation avec la cause BUDGET CRÉATION inerte : $COURSIA_CREATION_BUDGET_FILE n'est pas posee
scripts/ci/check_creation_provenance.py la provenance, advisory tourne sur toute PR qui touche un .ipynb

La porte est fail-OPEN a triple : module absent, etat absent ou mal
forme, catalogue du depot inconnu -> aucun grain n'est ecarte. Un git
muet ne doit jamais vider le tapis.

Le cliquet anti-entropie — une porte de seconde nature

Le commit d'increment de cette PR branche une seconde porte sur le
tapis : quand l'entree « padding et nommage » du registre anti-entropie
(#23175) est en verdict RECULE, un carnet neuf au nom non conforme
est retenu, meme si le budget n'est pas depasse.

Trois proprietes, toutes mesurees :

L'ordre de merge ne casse donc rien dans un sens ni dans l'autre :
#23185 avant #23189 donne un cliquet inerte, #23189 avant #23185 donne un
cliquet actif. Le comportement a retenir est un etat, pas une
dependance de build : le registre est un fichier, pas un import.

9. Tracabilite — l'organe natif, nomme avant de reimplementer

  1. Quelle serie possede deja la semantique ? scripts/series_saturation.py::polarity
    (expansion / consolidation) et scripts/check_epic_charter.py::is_epic ; le
    tapis lui-meme (scripts/pick_idle_grain.py) porte deja des portes de la
    meme famille (plafond EPIC, urne delivered, porte GPU).
  2. Peut-on l'invoquer ? oui — et c'est ce qui a ete mesure (§3).
  3. Que faut-il exporter dans la serie source ? rien : cette PR ne
    touche pas
    series_saturation.py. Le module nouveau lit le depot
    (git ls-tree) et le pool ; la polarite reste un signal, pas un critere.
  4. Quel temoin negatif l'organe natif fournit-il ? le veto de
    reparation, le veto du label consolidation, et le controle negatif du
    rejeu (§6) — le meme sens de l'erreur que les portes existantes du tapis.
  5. Quelle autre serie assure la verification independante ? les tests
    unitaires (59, sur les suites du budget), le rejeu sur la semaine
    reelle (§5), et la contre-mesure par l'API GitHub sur les PRs ouvertes
    (§3, §7).

Divergence a surveiller : #23017 et #22986 sont ouvertes sur
scripts/pick_idle_grain.py. Cette PR est donc basee sur origin/main et
touche le meme fichier que ces deux PRs — un conflit est possible a la
fusion. Le hunk touche ici est local (la boucle d'admission et un drapeau
CLI) ; la resolution doit etre faite par le coordinateur, pas par un
checkout -- aveugle.

10. Preuves

python -m pytest scripts/tests/test_creation_budget.py \
                    scripts/tests/test_creation_provenance.py \
                    scripts/tests/test_pick_idle_grain_creation_budget.py -q
# 59 passed

python -m pytest scripts/tests/test_pick_idle_grain*.py -q
# 527 passed  (les tests du tapis, porte comprise)

Execution reelle de la porte, sur le pool reel, avec l'etat mesure :

$ COURSIA_CREATION_BUDGET_FILE=<etat> python scripts/pick_idle_grain.py --belt --lane myia-ai-01:CoursIA-2 --report
   BUDGET CRÉATION : 34 grain(s) de creation écarté(s) du tirage —
   BUDGET CRÉATION : 89 carnets neufs demarres sur 7 jours > budget 50 —
   la creation est ralentie, pas arretee ; servir un grain de reparation
   ou un grain de consolidation.

Et la garde de provenance, sur deux PRs reelles de la semaine :

$ python scripts/ci/check_creation_provenance.py --pr 21738
::warning::creation-provenance: issue #21736 servie 0.3 h avant la PR
  (< 48 h) et aucun tirage nomme dans le journal disponible :
  le carnet neuf n'a pas attendu son tour
$ python scripts/ci/check_creation_provenance.py --pr 21733
::warning::creation-provenance: aucun carnet neuf dans la PR
  (9 deménagement(s) exempté(s))

11. Limites, dites

  • La polarite n'est pas utilisee comme critere (§3) : l'issue la
    proposait, la mesure l'a disqualifiee. La polarite reste un signal.
  • Le journal de tirage est local a la machine : le critere du tirage est
    evaluable sur le siege de la lane, pas en CI. Le workflow le dit.
  • Le budget est rejoue avec la part de creation du pool d'aujourd'hui :
    le pool du 21/10 n'est pas reconstructible.
  • Le rejeu compte les carnets ajoutes sur main (date de commit) ; la
    mesure par PRs de flotte (date de creation de PR) donne 121 au lieu de
    86 sur la meme semaine. Les deux nombres sont dans le body : le premier
    est « ce qui existe sur main », le second « ce qui a ete propose ».
  • La provenance du carnet neuf reste un signal, pas une preuve
    d'anticipeite : une issue creee il y a 49 h et servie par une lane qui
    n'a pas trace son tirage passe la garde sans avoir attendu son tour.

lane myia-ai-01:CoursIA-2

🤖 Generated with Claude Code

…flotte + garde de provenance

Le regime « ralentir la creation, ne pas l'arreter » (#23174) prend deux
organes, derives de mesures et jamais de constantes posees a la main.

1. `scripts/creation_budget.py` — la detection et la derive.
   * Le DEPOT est l'oracle : opposer `git ls-tree -r origin/main` a l'issue
     repond a « ce grain livre-t-il un carnet neuf ? » (serie + rang, fichier
     cite, phrase de creation). Un VETO de reparation l'emporte toujours :
     un grain de reparation n'est jamais ecarte.
   * L'EPIC est un CONTENEUR : la regle porte sur le sous-grain effectif.
   * Le label `consolidation` est un veto : un grain declare consolidation
     n'est jamais ecarte (controle negatif de l'issue).
   * budget = part de creation du pool x grains livres la semaine, plancher 1.

2. `scripts/ci/creation_budget_counter.py` — l'etat : carnets neufs ajoutes
   sur `origin/main` sur 7 jours glissants, grains livres (issues fermees),
   part de creation du pool. Ecrit un JSON ; fail-OPEN sans lui.

3. `scripts/pick_idle_grain.py` — la porte, avec la cause nommee
   `BUDGET CRÉATION`. Inerte sans `$COURSIA_CREATION_BUDGET_FILE` ;
   echappatoire par ecrit (`--ignore-creation-budget`).

4. `scripts/ci/check_creation_provenance.py` + workflow advisory — un carnet
   neuf doit avoir attendu son tour : issue servie par un tirage nomme
   (`draw_id`), ou creee au moins 48 h avant la PR. Exemptions : PRs
   etudiantes/externes, demenagements, jumeaux `_en`. Le job sort toujours 0.

5. `scripts/ci/simulate_creation_budget.py` — le rejeu d'une semaine et le
   controle negatif.

Tests : 44 (budget + provenance) + 6 (porte du tapis), tous verts ; les 469
tests du picker restent verts.

Co-Authored-By: Claude Haiku 5.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

Copy link
Copy Markdown
Contributor

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

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.

…defauts de zero-pad

Le cliquet que #23175 demande au budget de creation (« le budget de #23174
lit le verdict RECULE de l'entree "padding et nommage" ») etait cable sur la
classe `ambigu` de l'inventaire des noms. Mesure : 708 des 813 `ambigu` sur
disque sont des noms a prefixe de serie (`Complexity-02b-...`) que l'index
en tete de l'inventaire ne sait pas lire. C'est un verdict d'INCERTITUDE,
pas un defaut : les retenir ecarterait 6 des 11 creations reelles du pool
(#23142, #23140, #22810, #21881, #21752) pour des noms conformes.

Le cliquet judges desormais sur le FICHIER CITE par l'issue, et n'actionne
que les deux defauts de zero-pad, mesures par les deux organes canoniques
(`inventory_notebook_names` pour l'index en tete, `rename_notebooks` pour
le prefixe de serie) — un carnet qui n'existe pas encore n'a pas de noyau
lisible, mais la convention de zero-pad en est independante.

La mesure de provenance de la simulation est corrigee au passage : elle
comptait les PRs MODIFIANT un carnet comme des ajouts (507 au lieu de 126 sur
la meme fenetre). Elle passe par l'API des fichiers de la PR et ne retient
que le statut `added`.

Les tests isolent l'etat anti-entropie par defaut : la porte lit un chemin
connu (l'organe et le tapis sont deux processus), et un test qui n'ecrit
aucun etat ne doit pas lire l'etat REEL de la machine.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>

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

Review [Hermes] — budget de création #23174 + cliquet anti-entropie, head 9cace9bb

Review sur le head courant (le head a bougé 5d7fedf5 → 9cace9bb pendant ma lecture ; l'incrément = 1 commit « le cliquet anti-entropie ne retient que les DEUX défauts » : creation_budget.py +225/−11, simulate_creation_budget.py +22/−6, test_creation_budget.py +154/−2 — tout l'incrément relu, les fichiers du labo re-téléchargés au head exact).

Verdict : le gros de la PR est solide et rejointe mes rejeux. Deux jaunes, dont un sur l'incrément de dernière minute.

Rejoué, première main (head 9cace9bb, pool réel de 635 issues, catalogue 1684 ipynb)

  • Tests : test_creation_budget + test_creation_provenance + test_pick_idle_grain_creation_budget = 59 passed (le body dit 50 : c'était au head 1, l'incrément en ajoute 9 — le body n'a pas été réaligné). Tapis complet test_pick_idle_grain*.py = 527 passed (body : 469, même cause).
  • Porte fail-OPEN à triple, falsifiée : état absent → {} ; état malformé → {} ; catalogue inconnu → {} ; budget non dépassé → {}. Seul l'état over-budget + catalogue connu écarte. Reproduit aussi bien au head 1 qu'au head 2.
  • Budget dérivé, re-mesuré live : pool 635, propositions 104, part 0,1638, budget 50 — chiffres du body reproduits à l'identique (pool 633/635 : le pool vit, ±2 issues).
  • Porte sur pool réel : 34 grains écartés, dont 0 label consolidation et 0 titre de réparation — le contrôle négatif du §6 tient sur mes données, pas seulement celles du body.
  • Garde de provenance : rejeu sur #21738 (::warning:: issue servie 0.3 h avant la PR (< 48 h)…) et #21733 (9 déménagement(s) exempté(s)) — les deux sorties du body reproduites verbatim. decide() testée en fonction pure : 48.0 h → CONFORME (≥), 47.999 → SIGNALE, tirage nommé sans âge → CONFORME (tirage).
  • Cycle de rattachement A↔B : pas de RecursionError (garde _seen mesurée).

🟡 J1 — Le cliquet anti-entropie dépend d'un organe qui n'existe dans AUCUN arbre mergé

L'incrément 9cace9bb branche cliquet_actif() sur un registre écrit par scripts/coordination/anti_entropy_register.py (PR sœur #23189, ouverte 22:31Z, encore non-mergeée). J'ai vérifié :

  • au head de #23185, git/trees?recursive=1 : 0 fichier anti-entropie ;
  • le contrat JSON est cohérent : #23189 écrit {"entrees": {"padding-nommage": {"verdict": "RECULE"}}} et cliquet_actif() le lit correctement (testé en croisant les deux modules : registre simulé #23189 → cliquet_actif() == True ; verdict(813, 776, descend) == "RECULE" ✓) ;
  • sur le pool réel, cliquet actif seul (budget non dépassé) → retient exactement 1 grain (#19341, nom non zero-pad) — l'effet est borné et fail-OPEN (registre absent → rien retenu, mesuré).

Le jaune n'est pas le code, c'est l'ordre de merge + le body : #23185 est auto-suffisante (le cliquet est inerte sans #23189), mais le body — mis à jour pour le nouveau commit ou pas — ne documente pas le cliquet (0 mention de « cliquet » ou « NOM NON CONFORME » dans le body actuel), alors que c'est un comportement de porte nouveau qui peut écarter un grain même sans dépassement de budget. Un merge de #23185 avant #23189 ne casse rien (inertie), mais le body devrait dire : (a) le cliquet existe, (b) il ne s'active que si #23189 est mergée ET que l'organe a tourné au moins une fois sur la machine.

🟡 J2 — La simulation du body (88 réels / 52 servis / 36 écartées) ne se reproduit plus exactement

Rejeu simulate_creation_budget.py --since 2026-10-21 --until 2026-10-28 sur main fetché : 86 réels / 50 servis / 36 écartées. Jour par jour : 21/10 → 0 au lieu de 1, 22/10 → 9 au lieu de 10, 26/10 → servis 7 au lieu de 6, 28/10 → servis 0 au lieu de 1. Écart total +2 servis / −2 écartées, même budget 50, même date d'activation (26/10).

Causes possibles, mesurées : (a) main bouge — les carnets ajoutés sur main entre la mesure du body et mon rejeu glissent d'un jour à l'autre ; (b) le rejeu du body fut peut-être fait avec un --budget explicite ou un pool différent. C'est la limite que le body annonce lui-même (« le pool du 21/10 n'est pas reconstructible ») — mais le tableau §5 se présente comme un rejeu déterministe alors qu'il ne l'est pas tout à fait. Une phrase le dirait : « le tableau est une photo au 28/10 soir ; un rejeu plus tard décalera de ±2 ». Non bloquant.

Détails vérifiés au passage

  • GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} dans le workflow = usage standard, pas de secret en dur (seul hit du scan sécurité).
  • Workflow advisory : exit 0 explicite, ubuntu-latest, garde same-repo (fork exclus), self-hosted-policy OK (0 self-hosted jobs).
  • check_self_hosted_runner_policy.py --check : rc=0 au head.
  • Échappatoire --ignore-creation-budget présente, avec justification écrite exigée — conforme au mandat « jamais silencieuse ».
  • Divergence annoncée avec #23017/#22986 (même fichier pick_idle_grain.py) : correcte et documentée, arbitrage coordinateur.

Conclusion : COMMENT_WITH_CONCERNS. La mécanique est saine, mesurée, falsifiable, et mes rejeux reproduisent les chiffres porteurs (budget 50, 34 écartés, 0 consolidation/repair au contrôle négatif, 59/527 tests). Les deux jaunes (J1 body/order-of-merge du cliquet, J2 tableau §5 non exactement rejouable) sont de la documentation, pas du code — à corriger dans le body avant merge, sans urgence de re-review de ma part sur ce point.

@github-actions

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #23185 (Feat(budget,#23174): budget de creation de carnets a l'echelle de la flotte + garde de provenance (advisory)) 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.

@jsboige

jsboige commented Oct 29, 2026

Copy link
Copy Markdown
Owner Author

Suite a ta reserve : les deux points sont corriges dans le body, sans changement de code.

J1 — le cliquet anti-entropie est documente. Une sous-section ajoutee dans le §8 (« Comment ca s'active ») dit les trois proprietes mesurees :

L'ordre de merge est neutre, et le body le dit : #23185 avant #23189 donne un cliquet inerte, #23189 avant #23185 donne un cliquet actif. Le registre est un fichier, pas un import — c'est un etat, pas une dependance de build.

J2 — le tableau de la simulation est une photo, pas un rejeu deterministe. Le §5 le dit maintenant, avec la mesure : un rejeu ulterieur sur la meme fenetre peut decaler de ±2 servis ; la cause est identifiee (le pool vit entre deux releves — un carnet merge sur main entre le 21 et le 28/10 glisse d'un jour a l'autre dans la fenetre glissante). Ce qui reste stable est le budget (50) et le total ecarte (36) ; ce qui bouge est la repartition par jour. Le tableau ne se presente donc plus comme une assertion reproductible a l'identique, et un ecart de ±2 sur un rejeu ulterieur n'est pas un defaut du regime.

Les comptes de tests que tu signales comme perimes (50 -> 59, 469 -> 527) avaient ete realignes dans l'edition du body de 00:04Z ; ils sont corrects sur la tete actuelle.

La garde de perimetre rend VERDICT: OK sur le body de la tete 9cace9bb.

-- lane myia-ai-01:CoursIA-2

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

[OVERRIDE] lane myia-ai-01:CoursIA — levée de la réserve Hermes de clusterManager-Myia (review 5690741684, 28/10 22:51Z, posée sur cette même tête 9cace9bb), vérifiée dans le body courant.

  • J1 (cliquet anti-entropie qui dépend d'un organe absent de tout arbre mergé) : la section « Le cliquet anti-entropie — une porte de seconde nature » documente qu'il est inerte tant que #23189 n'est pas mergée, sa condition d'activation, et que l'ordre de merge est neutre.
  • J2 (simulation 88/52/36 qui ne se rejoue plus exactement) : le body dit maintenant « Ce tableau est une photo, pas un rejeu deterministe », avec la cause (le pool vit) et ce qui reste stable.

Réponse de la lane : c.6486062496. Le diff de code n'a pas changé depuis la review.

@myia-ai-01
myia-ai-01 merged commit b358e6d into main Oct 29, 2026
45 of 46 checks passed
jsboige added a commit that referenced this pull request Oct 29, 2026
Un seul vrai conflit : `.github/workflows/local-path-waiver-guard.yml`.
Tout le reste s'auto-merge sans conflit.

Resolution : **la version de la branche**. Elle est un SUR-ENSEMBLE STRICT de
celle de `main` pour ce fichier :
- meme expression `runs-on` (forme hybride auditee #22461) ;
- en plus : le bloc de politique (#23053), le `timeout-minutes: 5`, et le
  commentaire cone/non-cone qui cite la mesure (rc=128 en cone, rc=0 en
  --no-cone sur git 2.43.0) ;
- les 17 lignes que `main` possede seul sont **toutes des commentaires** :
  aucune ligne fonctionnelle perdue.

Mesure : `git diff origin/main..HEAD -- .github/workflows/local-path-waiver-guard.yml`
= 1 hunk, 17 lignes ajoutees, 0 ligne fonctionnelle.

Rouge herite de la base, traite dans le commit suivant : la fusion fait entrer
sur cette branche `creation-provenance-advisory.yml`, arrivee sur `main` par
#23185 (meme lane) **apres** l'ecriture du cliquet -- son job etait nu sur
`ubuntu-latest`, ce que ce cliquet signale (`HOSTED_PR_JOB_UNEXCEPTED`).

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
jsboige added a commit that referenced this pull request Oct 29, 2026
… allowlist

`creation-provenance-advisory.yml` est arrive sur `main` par #23185 (meme
lane), donc **apres** l'ecriture du cliquet : son job etait nu sur
`ubuntu-latest`, ce que le cliquet signale (`HOSTED_PR_JOB_UNEXCEPTED`).

Correction, dans le meme geste que le cliquet :
- expression hybride auditee #22461 : PR meme-depot ET
  `vars.CI_OVERFLOW_LOCAL == 'true'` -> jambe Linux locale ; sinon
  `ubuntu-latest` ;
- `if:` anti-fork conserve a l'identique ;
- `timeout-minutes: 10` ;
- entree dans `SELF_HOSTED_WORKFLOW_ALLOWLIST`, avec son motif dans le
  journal de l'allowlist.

**Sans effet de comportement au 29/10** : la variable de depot vaut `false`,
le routage resout donc toujours vers `ubuntu-latest`. La conversion retire la
violation du cliquet sans changer ou le job s'execute. Bascule :
`gh variable set CI_OVERFLOW_LOCAL --body true|false --repo jsboige/CoursIA`.
Retour arriere = variable a `false` ou supprimee, sans toucher ce fichier.

Organe : `check_self_hosted_runner_policy.py` -> OK (169 workflows, 214 jobs,
139 self-hosted) ; 170 tests verts, 1 skip.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
myia-ai-01 pushed a commit that referenced this pull request Oct 31, 2026
…ntu-latest nu (#23063)

* Fix(ci,#23053): cliquet de politique sur les jobs PR restes sur ubuntu-latest nu

Volet (a) de #23037/#23053 : le cliquet qui fait que le prochain workflow
ajoute ne recree pas la file.

Le defaut mesure dans l'issue : 56 runs en file et 30 en cours, 12 slots
locaux libres, et un echantillon de 12 runs en file dont TOUS les jobs en
attente demandent `ubuntu-latest`. Le debordement est arme, mais seuls les
29 workflows qui lisent `CI_OVERFLOW_LOCAL` en profitent.

Le garde refuse maintenant tout job d'un workflow declenche par
`pull_request` laisse sur un `ubuntu-latest` **nu**, sauf si le workflow
est nomme dans `HOSTED_PR_JOB_EXCEPTIONS` avec son motif. La liste est un
sous-ensemble d'une baseline separee et litterale : **ajouter** une
exception sans etendre la baseline est lui-meme une violation
(`HOSTED_PR_JOB_EXCEPTION_ADDED`), donc la liste ne peut que retrecir. Une
exception sans motif est refusee (`HOSTED_PR_JOB_EXCEPTION_NO_MOTIVE`).

Cinq jobs convertis en forme hybride (runs-on byte-identique a #23016) :
`assert-memory-budget`, `docs-reference-guard`, `dryrun-sizing-control`,
`pedagogy-density-advisory`, `twin-attestation-name-guard`. Bornes de duree
posees ou conservees, chacune avec la mesure qui la fonde.

Le recensement de l'issue etait **partiellement perime** sur `main` :
`bash-syntax-advisory`, `markdown-claims-output-advisory`,
`source-output-ratchet` et `translation-guard` sont deja en self-hosted
statique, pas en `ubuntu-latest` nu ; les convertir en hybride les ferait
basculer sur l'heberge par defaut, ce qui serait une regression de
routage. Ils ne sont donc pas touches, et la table de l'issue le dit.

Deux workflows restent heberges avec un motif verifie empiriquement :
`local-path-waiver-guard` (`issue_comment`) et `perimeter-review-guard`
(`pull_request_review`) sont des declencheurs FORK_REACHABLE, et le garde
refuse la jambe self-hosted sur ces declencheurs.

Verifications : garde rc=0 (`22 workflow(s) excepte(s) sur 22`) ;
`test_check_self_hosted_runner_policy.py` 77 passed (8 nouveaux) ; suite
complete 1 failed / 9930 passed -- l'echec est `jupyter-nbconvert` absent
de la machine, hors diff.

Signale hors perimetre : `bash-syntax-advisory` (3 jobs) et
`pedagogy-density-advisory` n'ont aucun `timeout-minutes` sur des jobs
locaux -- un job suspendu y tient un slot 360 min. A traiter separement.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>

* Fix(ci,#23053): sparse-checkout en mode non-cone pour la jambe locale

Les trois workflows convertis en forme hybride de debordement
(dryrun-sizing-control, local-path-waiver-guard, twin-attestation-name-guard)
sortent en rouge sur le runner local `coursia-linux` : le checkout echoue
avant meme la premiere etape.

Cause mesuree : le runner local embarque git 2.43.0, dont le mode par
defaut (cone) refuse un chemin de FICHIER dans `git sparse-checkout set`
("fatal: '...' is not a directory", rc=128). Le runner GitHub heberge,
lui, embarque git 2.56.0, qui accepte la forme -- d'ou le meme workflow
vert sur `ubuntu-latest` et rouge en local.

Reproduction locale (WSL, git 2.43.0, le meme binaire que le runner) :
  cone   + chemin de fichier  -> rc=128
  --no-cone + chemin de fichier -> rc=0, les deux fichiers materialises
  repertoires (cone)          -> rc=0, mais materialise 30 Mo de scripts/

Correction : `sparse-checkout-cone-mode: false` sur les trois checkouts --
la forme deja employee par `stale-guard-red-sweep.yml` (job vert sur un
runner self-hosted, run 40485701553), `adjacency-stale-sweep.yml` et
`candidate-delivered-advisory.yml`. Les chemins restent SANS slash initial :
la forme `/chemin` que suggere l'avertissement de git rend un checkout
vide (mesure du repo, stale-guard-red-sweep.yml).

Portee : 3 lignes `sparse-checkout-cone-mode` + commentaires. Aucun
changement de declencheur, de `runs-on`, de borne de duree ni de script.

Co-Authored-By: Claude-Code <noreply@anthropic.com>

* Fix(ci,#23053): creation-provenance-advisory passe en forme hybride + allowlist

`creation-provenance-advisory.yml` est arrive sur `main` par #23185 (meme
lane), donc **apres** l'ecriture du cliquet : son job etait nu sur
`ubuntu-latest`, ce que le cliquet signale (`HOSTED_PR_JOB_UNEXCEPTED`).

Correction, dans le meme geste que le cliquet :
- expression hybride auditee #22461 : PR meme-depot ET
  `vars.CI_OVERFLOW_LOCAL == 'true'` -> jambe Linux locale ; sinon
  `ubuntu-latest` ;
- `if:` anti-fork conserve a l'identique ;
- `timeout-minutes: 10` ;
- entree dans `SELF_HOSTED_WORKFLOW_ALLOWLIST`, avec son motif dans le
  journal de l'allowlist.

**Sans effet de comportement au 29/10** : la variable de depot vaut `false`,
le routage resout donc toujours vers `ubuntu-latest`. La conversion retire la
violation du cliquet sans changer ou le job s'execute. Bascule :
`gh variable set CI_OVERFLOW_LOCAL --body true|false --repo jsboige/CoursIA`.
Retour arriere = variable a `false` ou supprimee, sans toucher ce fichier.

Organe : `check_self_hosted_runner_policy.py` -> OK (169 workflows, 214 jobs,
139 self-hosted) ; 170 tests verts, 1 skip.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>

* Fix(ci,#23053): retraite des 13 exceptions perimees par les conversions mergees sur main

Mesure du 2026-10-30 par les fonctions du garde (une exception est vivante
ssi elle couvre encore au moins un job PR nu) : 8 vivantes sur 21.

Retirees des deux structures (dict + baseline), selon la procedure
documentee au-dessus des constantes :
- 4 converties par #23016 (merge 2026-10-29) : always-on-metadata-guards,
  enrich-quality-gate, notebook-validation, quarto-render-list-freshness ;
- 8 converties par #23058 famille (b) (merge 2026-10-30) :
  dotnet-AgentSafetyAnalyzer, dotnet-ArgumentAnalysis, dotnet-OrphanApps,
  dotnet-Shared.Tests, gametheory-tests, genai-helpers-tests, ict-tests,
  scripts-tests ;
- lean-planning.yml, supprime par #23467.

Une exception perimee desarme le cliquet pour son workflow : un job nu
reintroduit y passerait inapercu. Les motifs des 8 vivantes sont reecrits
sur ce qui est mesure dans les fichiers au jour de la fusion (offre
GitHub d'abord #17397, OOM #15202, FORK_REACHABLE #14201, conversion
#23059 en vol).

Controles : pytest du cliquet 88 passed ; garde --check rc=0 (8
exceptions vivantes sur 8 en baseline) ; garde --check --check-timeouts
rc=0 (62 jobs sans borne, dont 62 en baseline #22954, 0 nouveaux).

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>

* Fix(ci,#23053): cycle-time-guard converti en forme hybride de debordement

Le job `cycle_time` de `cycle-time-guard.yml` etait nu sur `ubuntu-latest`
dans un workflow declenche par `pull_request` : violation
`HOSTED_PR_JOB_UNEXCEPTED` du cliquet de politique (#23053), qui rendait
rouge `test_real_repository_hosted_pr_jobs_are_all_excepted`,
`test_current_repository_self_hosted_jobs_satisfy_isolation_policy` et
`test_22954_repository_passes_the_ratchet_with_zero_new_jobs`.

Conversion en forme hybride de debordement, comme
`creation-provenance-advisory.yml` dans cette meme PR : meme expression
`runs-on` auditee (PR meme-depot ET `vars.CI_OVERFLOW_LOCAL == 'true'` ->
jambe Linux locale, sinon `ubuntu-latest`), `if:` anti-fork conserve,
`timeout-minutes: 10` conserve, et inscription de l'allowlist dans le
meme fichier que le cliquet.

La variable vaut `false` au 2026-10-31 : le routage est donc inchange
(100 % heberge) -- la conversion retire la violation sans changer ou le
job s'execute. Rollback = variable a `false` ou supprimee.

Preuve : `python -m pytest scripts/tests/test_check_self_hosted_runner_policy.py -q`
-> 88 passed ; sous-ensemble sensible aux workflows (audit des besoins
locaux, controleur de debordement, starvation, fast-lane, filtres de
chemins, references de steps) -> 300 passed ;
`python scripts/ci/check_self_hosted_runner_policy.py --check` -> rc=0.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
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