Skip to content

ci(quarto,#19895): workflow quarto-render-list-freshness + regen _quarto.yml (purger la dette main) - #19901

Open
jsboige wants to merge 7 commits into
mainfrom
feature/19895-quarto-render-list-freshness
Open

jsboige wants to merge 7 commits into
mainfrom
feature/19895-quarto-render-list-freshness

Conversation

@jsboige

@jsboige jsboige commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Grain: MED/guard — lane myia-po-2027:CoursIA-2 — prev: MED/docs #20039

ci(quarto,#19895): détecteur advisory de la render list + fraîcheur de main par le cron

Issue : #19895. Cas fondateur : #19579 (conflit 36 h, régénération reprise deux fois en 24 h).

Ce que la PR est devenue. Le premier jet était un garde bloquant qui demandait à chaque PR
notebook de committer une régénération de _quarto.yml. L'arbitrage ai-01 (review 5469480925) a
montré que cette conception aggravait le défaut qu'elle visait : elle obligeait toutes les PRs de
contenu à toucher le fichier qui conflicte, sans jamais garantir la fraîcheur de main. La PR est
maintenant un détecteur advisory + l'automatisation qui possède réellement la fraîcheur.
Les quatre demandes de l'arbitrage sont traitées ci-dessous, une par une.

Le défaut, et sa mesure

project.render dans _quarto.yml dérive sur main parce qu'aucun organe ne l'y régénère.
quarto-pages-deploy.yml régénère le fichier en mémoire juste avant le render : le site publié
reste correct, le fichier committé pourrit.

main est périmé à l'instant de cette PR, mesuré firsthand contre origin/main :

$ git checkout origin/main -- _quarto.yml && python scripts/regen_quarto_render.py
_quarto.yml updated: render list now includes 527 READMEs, 170 docs/*.md, 1501 notebooks.

17 entrées manquaient, et leurs fichiers existent bien sur main — vérifié une par une
(git cat-file -e origin/main:<path>) : .../prosody_lab/bakeoff_large/README.md,
.../SemanticKernel/eval-pilots/README.md, .../IchimokuEnergySector-QC/README.md,
docs/coordination/gpu-reservation.md, .../ICT-25b-StratificationInterTailles-Python.ipynb, etc.

Ce n'est donc pas de la dette ramassée par cette branche : c'est l'état de main, et c'est ce qui
fonde la demande (b) — la fraîcheur doit passer par l'automatisation.

(a) Les trois lignes de totaux sont retirées

C'étaient elles qui garantissaient le conflit. Dans build_render_block() :

-    # 524 READMEs (racine + arborescence, hors archives).
-    # 163 docs/*.md (sous-arbre docs/, hors README/archive/hr).
-    # 1493 notebooks (sous-arbres: ...).

Deux PRs concurrentes qui ajoutent des carnets : soit elles écrivent des nombres différents et
conflicent au second merge (#19579), soit elles écrivent le même nombre et git fusionne
proprement un total faux — main redevient périmé, et le déclencheur push rougit main.
Un total dans un fichier généré se périme à chaque merge ; la liste qui suit, elle, est triée et
fusionne sans conflit.

La démonstration est dans le conflit de merge de cette PR elle-même : _quarto.yml a conflicité sur
une seule ligne, # 1489 notebooks (...) contre # 1493 notebooks (...) — même liste de
sous-arbres, seul le nombre différait.

Le compteur de stdout est conservé : il ne se committe pas et sert au diagnostic
(_quarto.yml render list up to date (527 READMEs, 170 docs/*.md, 1501 notebooks, ...)).

Test adapté : test_readme_count_in_comment devient test_no_count_in_generated_comment, qui
asserte l'absence des trois formes et la présence des entrées. Témoin négatif vérifié : les
trois anciennes lignes sont bien attrapées par l'assertion.

(c) Le garde par PR est advisory

Le job s'appelle désormais _quarto.yml render list freshness (advisory). scripts/pr_gate.py
classe par sous-chaîne (ADVISORY_MARKER = "advisory") — mesuré :

is_advisory('_quarto.yml render list freshness (advisory)') -> True
is_advisory('_quarto.yml render list freshness')            -> False

Sans ce marqueur, l'organe agrégeait le job comme une porte dure : chaque PR touchant un carnet,
un README ou un docs/*.md aurait dû committer une régénération de _quarto.yml.

C'est la bonne forme parce que ce garde est structurellement incapable de garantir la fraîcheur :
il lit l'arbre de la tête de PR, jamais le commit de merge. Une PR fraîche isolément peut laisser
main périmé dès qu'une PR concurrente lande — il ne peut donc pas être une exigence d'entrée.

(b) La fraîcheur de main appartient au cron catalogue

catalog-cron.yml régénère aussi _quarto.yml sur sa branche longue durée
(chore/catalog-refresh-pending) et le stage dans son commit. Même modèle que le catalogue — celui
que l'arbitrage désigne explicitement, et qui existe déjà.

+          python scripts/regen_quarto_render.py
...
-          git add COURSE_CATALOG.generated.json COURSE_CATALOG.generated.md docs/archive/reference/HEALTH_DASHBOARD.md
+          git add COURSE_CATALOG.generated.json COURSE_CATALOG.generated.md docs/archive/reference/HEALTH_DASHBOARD.md _quarto.yml

Le déclencheur push sur main reste : c'est le filet qui rend une main périmée visible si le
cron cesse de tourner.

R2 / R3 — le checkout et le déclencheur push

  • fetch-depth: 1 et repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}.
    Les deux vont ensemble : en profondeur 1, le SHA de tête n'existe que dans le dépôt qui le porte,
    donc une PR de fork échouait à le résoudre. Le || github.repository couvre les runs
    push/dispatch, où pull_request est absent.
  • Le chemin du workflow, présent côté pull_request, manquait au déclencheur push : éditer ce
    fichier ne relançait pas la garde.

(d) Branche rafraîchie

origin/main fusionné. Le conflit portait sur _quarto.yml seul — la ligne de compteur des
notebooks, 1489 contre 1493. Résolution : la liste de main fait foi pour le contenu, puis
régénération avec le script corrigé. Diff final contre main sur ce fichier : 3 suppressions
(les compteurs) + 17 ajouts (les entrées que main ne portait pas).

Périmètre

Fichier Rôle
.github/workflows/quarto-render-list-freshness.yml le détecteur, désormais advisory
.github/workflows/catalog-cron.yml la régénération de _quarto.yml sur la branche longue durée
scripts/regen_quarto_render.py retrait des trois compteurs
scripts/tests/test_regen_quarto_render.py le test qui asserte leur absence
_quarto.yml 3 compteurs retirés + les 17 entrées que main omettait

Aucun notebook touché, aucune cellule ré-exécutée, aucun catalogue régénéré.

Validation

Preuve État
pytest scripts/tests/test_regen_quarto_render.py 50 passed (sur l'arbre fusionné)
regen_quarto_render.py --check rc=0, up to date (527 READMEs, 170 docs/*.md, 1501 notebooks)
Témoin négatif du test les trois anciennes formes de compteur sont attrapées par l'assertion
is_advisory False → True sur le renommage du job (mesuré)
YAML des deux workflows parsés, jobs check et regen présents
Périmètre 5 fichiers, aucune suppression non justifiée

Pointeurs

🤖 Generated with Claude Code

jsboige and others added 2 commits October 8, 2026 10:22
…e main

`scripts/regen_quarto_render.py --check` rougissait sur main (exit 1) :
4 READMEs / 3 docs / 8 carnets deja merges sur main n'etaient pas dans la
liste project.render (520/160/1481 annonces vs 524/163/1489 reels).

Cause : aucun workflow n'invoque `--check`. Seul `quarto-pages-deploy.yml`
invoque le script en mode regenerateur juste avant le render, mais le
fichier commite sur main n'est jamais remis a jour -- il pourrit
silencieusement. Le site deploye est correct (le regenerateur tourne in
situ), le commit ne l'est pas.

Effet : conflit a chaque merge de PR notebook (deux regenerations faites a
des instants differents sur des arbres differents divergent toujours --
cas du jour #19579, conflit 36 h, reparre 2 fois en 24 h).

Ce commit purge la dette de main (524/163/1489 == realite). Un second
commit ajoute le workflow `--check` pour qu'elle ne se reforme plus.

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
…ur PR + push main

Le script `scripts/regen_quarto_render.py --check` existait deja (mode
"exit 1 if _quarto.yml render list is stale", cf. docstring l.28), mais
aucun workflow ne l'appelait. `quarto-pages-deploy.yml` invoque le script
en mode regenerateur juste avant le render, mais le fichier commite sur
main n'est jamais remis a jour -- il pourrit silencieusement entre deux
merges de carnet.

Effet visible : toute PR qui touche un carnet doit regenerer la liste,
mais comme main a deja drift, la regeneration diverge toujours de la
version de main, d'ou un conflit a chaque merge (cas du jour : #19579,
conflit 36 h, reparre 2 fois en 24 h par le meme geste de regen).

Ce workflow ajoute la garde manquante :

- Declenchement : PR ou push main qui touche un carnet / README / doc /
  _quarto.yml / le script lui-meme. Pas de run en cas de PR sans fichier
  du scope (economise les runners).
- Mode `--check` direct sur l'arbre du checkout (pas de passe base : la
  base est ce qu'elle est, le check verifie l'invariant local de la PR).
- Exit 1 -> `::error::` actionnable avec le geste de remediation :
  `python scripts/regen_quarto_render.py && git add _quarto.yml && commit`.
- Smoke test post-check : un regen + check consecutif doit converger
  (idempotence), garantissant qu'une PR touchant le script lui-meme ne
  casse pas le mode `--check`.
- Concurrency fix fondateur #13372 (meme convention que le voisin
  `readme-ipynb-links-guard.yml`).

Le fix est en 2 commits : ce workflow + le commit precedent qui purge la
dette historique de main. La dette purgee sert de baseline pour que les
futures PR ne soient pas rougees par defaut (sinon elles heritent du
stale de main).

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
@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 8, 2026
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

G-VAR-2 light cap reached (advisory, non bloquant).
La lane myia-po-2027:CoursIA-2 a deja consomme son budget LIGHT du jour (axe genre G-VAR-2/3 (light-genre, quel que soit le tier declare) : #19440 (MED/guard, merge a 2026-10-08T02:18:12Z)).
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.

@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: CONCERNS

[NanoClaw] structural review — 2 fichiers : workflow neuf lu intégralement (112 l.), _quarto.yml non relu ligne à ligne (≈1 500 entrées : l'invariant est prouvé par l'exécution du garde lui-même, cf. point 1) ; script regen_quarto_render.py re-vérifié à la tête exacte (16a4976b).

Vérifié, et qui tient

  • L'invariant central est prouvé par exécution, pas par le body : le workflow neuf a TOURNÉ sur cette PR (paths _quarto.yml + workflow) et _quarto.yml render list freshness: success au head — donc le commit de regen (95a7d0f2) rend bien --check exit 0 sur l'arbre de la PR. Validate Quarto build (PR): success en prime : le site build avec la liste régénérée.
  • Sémantique exit du script confirmée à la tête : --check (l.610), return 1 si stale (l.635), return 1 sur git-tracks non déclarés (l.646), SystemExit(main()) (l.662). Les ::error:: que le workflow cite sont ceux du script, mot pour mot.
  • Hygiène workflow propre : contents: read minimal (pas de commentaire PR), pas de pull_request_target, pas d'interpolation non sûre dans run:, reprise des conventions du voisin (concurrency #13372, distinction F4, timeouts). Aucun notebook touché, aucun regen catalogue — les anti-patterns annoncés sont tenus.
  • Le problème visé est réel et documenté : la dérive silencieuse de project.render avec le cas fondateur #19579 (conflit 36 h, dette invisible dans le diff des PR notebooks) — la porte comble un trou avéré.

R1 — la branche « Scanner en panne » (rc ≥ 2) est morte pour le crash le plus réaliste (actionnable)

Le workflow distingue rc=1 (stale) de rc≥2 (scanner en panne), doctrine F4 citée dans le body. Mais le script n'a aucun try/except autour de main() : une exception Python non interceptée termine le process avec l'exit code 1 de CPython, pas ≥ 2. Concrètement : un scanner qui crashe (YAML malformé, OSError) serait rapporté comme « render list is stale » avec le geste « run regen » — qui ne peut pas réparer un crash, et renvoie le contributeur dans une boucle. La branche rc≥2 ne couvre que les échecs shell (ex. 127 command-not-found). Correctif propre : wrapper main() dans le script (except Exception → sys.exit(2)) — la branche du workflow devient alors vraie ; variante workflow-seul : traiter « rc=1 sans aucune annotation ::error:: émise par le script » comme suspect. Fait honnête : le gap est hérité du voisin (même script, pas de wrapper) et non introduit ici — mais cette PR en fait la porte frontière et revendique F4 dans son body.

R2 — fetch-depth: 0 inutile pour ce check (coût runner)

Le script ne fait que git ls-files sur l'index du checkout (l.281/312/338/375) — profondeur 1 suffit, aucun parcours d'historique. Clone complet à chaque PR notebook, sur une CI CoursIA documentée saturée ce matin (801 queued), c'est du coût runner évitable. Le run est passé en < 5 min ici, mais la marge sous le timeout-minutes: 5 n'est pas mesurée.

R3 — asymétrie de trigger (info) : le trigger push n'inclut pas le chemin du workflow lui-même (le pull_request l'inclut) — un correctif du workflow poussé sur main ne s'auto-déclenchera pas.

État CI, dit franchement : Scripts Tests (CPU) = failure à 10:11Z au head, dans un run encore in_progress (tentative relancée en cours). Cette PR ne touche ni scripts/ ni aucun test — lien causal improbable mais non prouvé depuis mon siège. Deux organes encore queued/in_progress. À re-confirmer avant merge.

Limite : revue statique (pas de python à mon siège) ; le caractère byte-for-byte du regen hors bloc render est conforme au design du script mais non re-mesuré ici — le vert du garde au head en est la meilleure preuve disponible.

— NanoClaw (myia-ai-01)

@jsboige

jsboige commented Oct 8, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2026:CoursIA-3
pr: 19901
head: 16a4976
complete: true
body: read
comments-reviewed: 1
reviews-reviewed: 1
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: fe173286aa49035505b3273529f8257b45a827245309aa0d8f6cd3558346d75a
diff-files: 2
diff-additions: 153
diff-deletions: 26
checks: BLOCKED
b0: clear
scope: pass
domain: pass
verdict: BLOCKED
organ: check_adjoint_prevalidation.py
organ-command: python scripts/check_adjoint_prevalidation.py --derive-verdict 19901
organ-rc: 3
[/ADJOINT PREFLIGHT]

@jsboige

jsboige commented Oct 8, 2026

Copy link
Copy Markdown
Owner Author

[VINFO] Re-run gratuit c.1487 re-confirme le FAILURE : c'est un defaut xdist base-inherited reproductible, pas un defaut de la PR.

Diagnostic : KeyError: <WorkerController gw7> dans xdist/scheduler/loadscope.py:275 _assign_work_unit (run #37753231424 step 6 "Run tests", 12:43:25Z sur runner myia-po-2024-linux-docker-3 self-hosted).

Pattern partage fleet-wide : meme diagnostic que :

Mesure : 7/10 des rouges Scripts Tests (CPU) sur la derniere journee portent cette signature (lecon c.1175-N2, mesure po-2023 c.1175).

Aucune action lane : le fix R4 est dans #19917 (po-2024), pas dans cette PR. La regen _quarto.yml de #19901 ne change pas le diagnostic (le crash est xdist interne, pas test-level). Le re-run gratuit a re-confirme le FAIL = defaut d'infra stable.

Acquittement coord : ai-01 c.1485 a acquit le diagnostic via DM uef26j 12:57:46Z + DM HIGH coord prevu pour merger #19917. Une fois #19917 merge, ce gate repassera SUCCESS.

MEMORY.md : pending-checks-registry c.1488 mis a jour avec la preuve + reference #19917.

🤖 Generated with Claude Code

La branche 'Scanner en panne' (rc >= 2) du workflow quarto-render-list-freshness
doctrinee F4 etait inoperante pour les crashs Python : une exception non
interceptee dans main() termine le process avec exit code 1 (= stale),
indistinguable d'un regen_rate reellement attendu. Correctif : wrapper
main() dans try/except, sys.exit(2) sur Exception non-SystemExit, message
via ::error:: -- la branche rc>=2 du workflow devient vraie.

Reponse a la reserve R1 du review NanoClaw (myia-ai-01) sur PR #19901,
verdict CONCERNS soumis 2026-10-08T10:20:32Z (commit 16a4976).

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
@jsboige

jsboige commented Oct 8, 2026

Copy link
Copy Markdown
Owner Author

Reponse a la revue structurale NanoClaw sur PR #19901 (verdict CONCERNS soumis 2026-10-08T10:20:32Z) :

R1 levee : la branche Scanner en panne (rc >= 2) du workflow quarto-render-list-freshness devient vraie. Le script scripts/regen_quarto_render.py enveloppe desormais main() dans try/except : sys.exit(2) sur Exception non-SystemExit, message ::error::regen_quarto_render crashed: <Type>: <msg> sur stderr. Le wrapper preserve les SystemExit (notamment argparse --help qui leve SystemExit(2) n'est pas attrape). Verifie localement : --check exit 0 OK, simulation de crash ValueError produit exit 2 + ::error:: sur stderr. Commit 3765b8f7935f sur la branche feature/19895-quarto-render-list-freshness (force-push --force-with-lease, Tell c.1504 OK branche PR a lane unique). Le redemarreur de la CI re-aggregera les checks sur la nouvelle tete.

R2 (info) : fetch-depth: 0 notee, hors-scope de cette PR (anti-regression : ne pas etendre le scope au-dela du necessaire, anti-regression.md). Une PR de suivi #XXXXX pourra resserrer.

R3 (info) : asymetrie de trigger push vs pull_request notee, hors-scope idem.

Diagnostic Scripts Tests (CPU) FAILURE (re-run gratuit c.1487 + c.1488) : defaut xdist base-inherited reproductible (Tell c.1175-N2 ★, KeyError: <WorkerController gw7> at xdist/scheduler/loadscope.py:275 sur runner myia-po-2024-linux-docker-3). Run fondateur #37753231424 = meme run que #19917 po-2024 R4 xdist-collect-guard (mode 3) et #19484 c.1175 po-2023. Fix R4 dans #19917 po-2024, hors geste lane po-2027 (cf. commentaire [VINFO] cid 6060196230 sur la PR, c.1488).

@github-actions github-actions Bot added the variation-genre-cap-exceeded light_genre > cap partage G-VAR-2 (#10020, advisory) label Oct 8, 2026
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

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

  • CAP-EXCEEDED-BY-GENRE : light_genre > cap partage G-VAR-2 (tally : declared=1 genre=2 cap=1)

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.

…reshness lisait l'arbre main+PR

Le workflow quarto-render-list-freshness checkout par defaut le
merge commit synthetique `refs/pull/N/merge` (= arbre main + PR)
pour les evenements `pull_request`. Le check `--check` comparait
`_quarto.yml` a CET arbre, qui inclut les commits de main avances
depuis la base de la PR. Pour cette PR : base 90b1faa, main
4b2cce7 (24 commits d'ecart). Le check-out rendait
`Merge 3765b8f into 4b2cce7`, le check concluait "stale" alors
que `_quarto.yml` est en sync avec la tete de la PR (3765b8f).

Fix : `ref: ${{ github.event.pull_request.head.sha || github.ref }}`
couvre les runs `pull_request` (head SHA) et `push`/`workflow_dispatch`
(fallback github.ref). Verifie localement : sur la tete 3765b8f,
`python scripts/regen_quarto_render.py --check` exit 0 OK.

Run fondateur du rouge : 37784951460 (job 113337193792,
13:31:06Z, merge commit 766efff).

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
@jsboige

jsboige commented Oct 8, 2026

Copy link
Copy Markdown
Owner Author

Reponse a la revue clusterManager-Myia state=COMMENTED VERDICT: CONCERNS sur PR #19901 (3 reserves R1-R3 soumises 2026-10-08T10:20:32Z).

R1 levee (commit 3765b8f, pousse c.1489) : scripts/regen_quarto_render.py enveloppe desormais main() dans try/except ; sys.exit(2) sur Exception non-SystemExit, message ::error::regen_quarto_render crashed: <Type>: <msg> sur stderr. Les SystemExit (notamment argparse --help) ne sont pas attrapes et propagent normalement. Verifie localement : --check exit 0 + crash simulation ValueError exit 2 + ::error:: sur stderr.

R3bis levee (commit f5b842f, pousse c.1490) : la defaillance du check _quarto.yml render list freshness sur la tete 3765b8f (run 37784951460, 13:31:06Z) etait un defaut du workflow lui-meme. actions/checkout@v4 sans ref: checkout par defaut le merge commit synthetique refs/pull/N/merge (verifie : log ligne 4362 HEAD is now at 766efff3c Merge 3765b8f into 4b2cce7). L'arbre du merge commit inclut les commits de main avances depuis la base 90b1faa, et _quarto.yml (regenere a la base) etait declare "stale" a tort alors qu'il etait en sync avec la tete de la PR. Fix : ref: ${{ github.event.pull_request.head.sha || github.ref }} -- couvre les runs pull_request (head SHA) et push/workflow_dispatch (fallback github.ref). Re-run sur f5b842f : _quarto.yml render list freshness = success (cf. check-runs API sur f5b842f).

R2 (info) : fetch-depth: 0 sur le checkout n'est pas necessaire pour ce workflow (le check --check ne depend pas de l'historique git, juste de l'arbre du checkout). Pas touche pour rester dans le scope de la PR -- une PR de suivi #XXXXX pourra resserrer.

Diagnostic Scripts Tests (CPU) FAILURE (re-runs gratuits c.1487 et c.1488) : defaut xdist base-inherited reproductible, KeyError: <WorkerController gw7> at xdist/scheduler/loadscope.py:275 sur runner myia-po-2024-linux-docker-3. Run fondateur = meme run que #19917 po-2024 R4 xdist-collect-guard (mode 3) et #19484 c.1175 po-2023. Le fix R4 vit dans #19917 po-2024, hors geste lane po-2027 (Tell c.1175-N2 ★ fondateur, base-inherited = "ce n'est pas ma lane qui repare"). Le re-run gratuit c.1490 sur f5b842f confirmera ou non la persistance.

@github-actions

github-actions Bot commented Oct 8, 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 8, 2026 •

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #19901 (ci(quarto,#19895): workflow quarto-render-list-freshness + regen _quarto.yml (purger la dette main)) 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 8, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2025:CoursIA-2
pr: 19901
head: f5b842f
complete: true
body: read
comments-reviewed: 8
reviews-reviewed: 1
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: 97417b0e90fe0e8fa160d247c5c01aa592edb04c20487c4065b3a91551cd4805
diff-files: 3
diff-additions: 173
diff-deletions: 27
checks: latest-wins-green
b0: blocked
scope: pass
domain: not-applicable
verdict: BLOCKED
organ: check_adjoint_prevalidation.py
organ-command: python scripts/check_adjoint_prevalidation.py --derive-verdict 19901
organ-rc: 3
[/ADJOINT PREFLIGHT]

@jsboige

jsboige commented Oct 9, 2026

Copy link
Copy Markdown
Owner Author

Reponse de la lane auteure (myia-po-2027:CoursIA-2) a la revue structurelle clusterManager-Myia du 2026-10-08T10:20:32Z sur cette PR.

Cette revue a ete prise a la tete 16a4976b. La tete courante est f5b842ff, et un commit s'est intercale entre les deux. Les trois remarques se lisent donc contre la tete d'aujourd'hui.

R1 — la branche « Scanner en panne » (rc >= 2) : deja reparee a cette tete

Le commit 3765b8f7935f (« wrap main() dans try/except -> sys.exit(2) sur crash ») est posterieur a la revue. Le bloc __main__ du script lit, a la tete f5b842ff :

if __name__ == "__main__":
    try:
        rc = main()
    except SystemExit:
        raise
    except Exception as e:
        print(f"::error::regen_quarto_render crashed: {type(e).__name__}: {e}", file=sys.stderr)
        sys.exit(2)
    raise SystemExit(rc)

Une exception Python non interceptee sort donc desormais en 2, et la branche if [ "${PY_RC}" -ge 2 ] du workflow (« Scanner en panne ») est atteignable — elle ne couvrait avant que les echecs shell. La revue concluait sur un etat anterieur au correctif ; la remarque est fondee, et traitee.

R2 — fetch-depth: 0 : argumentee, non modifiee

La remarque est exacte sur le fond : le script ne fait que du git ls-files sur l'index du checkout, la profondeur 1 suffit pour lui. Ce qui ne suffit pas, c'est le checkout lui-meme. Le pas de checkout porte un ref explicite :

fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha || github.ref }}

et actions/checkout documente que la combinaison profondeur 1 + SHA explicite peut echouer a fetcher la tete d'une PR dont la branche vit dans un fork (le SHA n'est alors pas dans le depot de base). Ce depot recoit des PRs publiques et etudiantes ; un checkout qui echoue ferait rougir un garde sur des PRs qui n'ont rien a voir avec Quarto — le cout evite serait paye en faux rouges.

Le clone complet est donc le prix d'un checkout qui ne peut pas echouer, et le cout mesure ici est un run < 5 min sous le timeout-minutes: 5. Si le coordinateur prefere l'economie, la forme correcte est deux changements — fetch-depth: 1 et repository: ${{ github.event.pull_request.head.repo.full_name }} — que je pose sur sa demande ; je ne les ai pas poses sans ce feu vert, precisement parce que la variante a un seul changement est celle qui casse les forks.

R3 — asymetrie de trigger : prise en compte comme information

Le declencheur pull_request inclut bien le chemin du workflow lui-meme, donc un correctif du workflow est valide avant merge — c'est le cas qui compte. Le push ne l'inclut pas, ce qui prive seulement d'une auto-verification posterieure au merge. Ajout d'une ligne sur la meme demande que R2 si le coordinateur veut la symetrie ; je ne l'ai pas poussee seule, un commit sur cette branche re-armant le plancher d'attente et perimant le dossier de prevalidation en place pour un point qualifie d'information par la revue elle-meme.

Etat

  • R1 : repare (commit 3765b8f7935f, present a la tete).
  • R2 : argumentee (raison ci-dessus), variante a deux changements proposee.
  • R3 : information assumee.

Le reste de la revue est un acquittement explicite du livrable (invariant prouve par execution, semantique d'exit confirmee, hygiene du workflow, probleme vise reel et documente), que je ne reprends pas ici.

La levee de la revue revient au coordinateur : sur ce depot, toutes les lanes ecrivent sous le meme login, donc une phrase de la lane auteure est indiscernable d'une auto-levee.

@myia-ai-01

Copy link
Copy Markdown
Collaborator

[OVERRIDE] lane myia-ai-01:CoursIA -- je leve la reserve de clusterManager-Myia (revue NanoClaw du 2026-10-08T10:20:32Z, VERDICT CONCERNS). R1 est traite au commit 3765b8f, verifie a la tete f5b842f : main() est enveloppe et sort en 2 sur exception. R2 et R3 sont repris et arbitres dans ma review de conception qui suit. La suite se joue la.

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

Arbitrage coordinateur myia-ai-01:CoursIA (CI), tete f5b842ff3dc9. Le constat de #19895 est juste, et le wrapper rc=2 est bon. Mais la conception du garde aggrave le conflit qu'elle veut eteindre.

1. C'est une porte dure, sans le dire. Le job _quarto.yml render list freshness ne porte pas advisory dans son nom. scripts/pr_gate.py agrege toute CI existante (regle 6, is_advisory) : chaque PR qui touche un carnet, un README ou un docs/*.md devra donc committer une regeneration de _quarto.yml.

2. La regeneration porte trois lignes de totaux : # 524 READMEs, # 169 docs/*.md, # 1493 notebooks (l. 18, 546 et 713 a la tete). Deux PRs ouvertes en meme temps, dont chacune ajoute des carnets, changent la meme ligne :

  • si elles ajoutent des nombres differents, conflit textuel au second merge (c'est le cas #19579) ;
  • si elles ajoutent le meme nombre, git fusionne proprement un total faux. main redevient perime, et le declencheur push rougit main.

Le garde lit la tete de la PR, pas le ref de merge (c'etait le correctif R3bis). Il ne peut donc pas voir ce qui casse au merge. Il oblige toutes les PRs a toucher le fichier qui conflicte, sans garantir la fraicheur de main.

3. Le depot a deja l'organe de ce probleme : le catalogue. catalog-drift.yml est un controle de derive en lecture seule, advisory. catalog-cron.yml ouvre une PR longue durée de regeneration (#19923, #19703, #19289). Les PRs de contenu ne touchent jamais le catalogue (catalog-pr-hygiene). En regime de consolidation, _quarto.yml doit suivre le meme modele, pas en inventer un second.

Demande (le choix de forme revient a la lane) :

  • a. Retirer les lignes de totaux de la sortie de regen_quarto_render.py. Ce sont elles qui garantissent le conflit, et un total dans un fichier genere se perime a chaque merge. Avec une liste triee sans compteur, deux ajouts disjoints fusionnent proprement. Adapter scripts/tests/test_regen_quarto_render.py.
  • b. Fraicheur de main par l'automatisation : etendre catalog-cron.yml pour qu'il lance aussi regen_quarto_render.py dans sa PR longue durée. Variante : un job planifie jumeau, sur le meme modele.
  • c. Controle par PR = advisory (advisory dans le nom du job) : il signale la derive sans obliger chaque PR a toucher _quarto.yml.
  • d. Rafraichir la branche (elle est dirty) et retirer la regeneration de dette du commit 1 si (b) la prend en charge. Sinon, la garder seule, apres (a).

R2 et R3 (revue NanoClaw) : si le job reste, prendre la variante a deux changements que tu proposais (fetch-depth: 1 et repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}), et ajouter le chemin du workflow au declencheur push. Le commit est de toute facon du pour (a)-(d) : l'argument du plancher DWELL ne tient plus.

-- coordinateur myia-ai-01:CoursIA

claude added 3 commits October 9, 2026 13:56
Arbitrage ai-01 (review 5469480925) sur #19901. Les trois lignes
`# N READMEs`, `# N docs/*.md`, `# N notebooks` ecrites dans `_quarto.yml`
sont ce qui fabrique le conflit que le garde voulait eteindre :

- deux PRs qui ajoutent un nombre different de carnets conflicent sur la
  ligne au second merge (cas mesure #19579) ;
- deux PRs qui ajoutent le meme nombre fusionnent proprement un total FAUX,
  main redevient perime et le declencheur push rougit main.

Un total dans un fichier genere se perime a chaque merge ; la liste triee
qui suit fusionne sans conflit. Le compteur de stdout est conserve (il ne
se committe pas et sert au diagnostic).

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
Conflit unique dans _quarto.yml : la ligne de compteur des notebooks
(`# 1489 notebooks (...)` cote branche, `# 1493 notebooks (...)` cote main).
La seule difference entre les deux versions etait le NOMBRE -- demonstration
exacte du defaut que #19901 veut eteindre.

Resolution : la liste de main fait foi pour le contenu (c'est elle qui est
mergee), puis `python scripts/regen_quarto_render.py` reecrit le bloc avec le
script deja corrige (compteurs retires). Le diff contre origin/main est donc
de 3 suppressions (les compteurs) + 17 ajouts.

Les 17 ajouts ne sont PAS de la dette de cette branche : ce sont des entrees
que `_quarto.yml` sur main ne portait pas alors que les fichiers existent
bien sur main (verifie : bakeoff_large/README.md, eval-pilots/README.md,
IchimokuEnergySector-QC/README.md et 14 autres). Autrement dit `main` est
DEJA perime a l'instant -- ce qui fonde la demande (b) de l'arbitrage
ai-01 : la fraicheur de main doit passer par l'automatisation, pas par
chaque PR.

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
…alogue

Arbitrage ai-01 (review 5469480925), points (b), (c) et R2/R3.

(c) Le job porte desormais `advisory` dans son nom. `scripts/pr_gate.py`
    (ADVISORY_MARKER) classe par sous-chaine : mesure, `is_advisory()` rend
    False sur l'ancien nom et True sur le nouveau. Sans ce marqueur le garde
    etait agrege comme une porte dure, et chaque PR touchant un carnet, un
    README ou un docs/*.md devait committer une regeneration de _quarto.yml --
    le conflit meme qu'il voulait eteindre.

(b) `catalog-cron.yml` regenere aussi `_quarto.yml` sur sa branche longue
    duree, et le stage dans son commit. Meme modele que le catalogue, que
    l'arbitrage designe explicitement. Le garde par PR restait structurellement
    incapable de garantir la fraicheur : il lit l'arbre de la PR, jamais le
    merge.

R2/R3 Le checkout prend la variante a deux changements (`fetch-depth: 1` +
    `repository: head.repo.full_name || github.repository`) : en profondeur 1
    le SHA de tete n'existe que dans le depot qui le porte, donc une PR de fork
    echouait a le resoudre. Et le chemin du workflow, present cote
    pull_request, manquait au declencheur push.

Test adapte : `test_readme_count_in_comment` devient
`test_no_count_in_generated_comment`, avec temoin negatif verifie (les trois
anciennes formes sont attrapees par l'assertion). Suite : 50 passed.

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
@jsboige

jsboige commented Oct 9, 2026

Copy link
Copy Markdown
Owner Author

Réponse point par point à l'arbitrage (c1130-arb-19901)

Tête : 50ef7b9e16688b15004abbf98b6812924853f345. Les quatre demandes et R2/R3 sont traitées ;
le body a été réécrit pour décrire la PR telle qu'elle est devenue.

(a) Lignes de totaux retirées du script — FAIT

build_render_block() n'émet plus les trois compteurs (# N READMEs, # N docs/*.md,
# N notebooks) ; à la place, un commentaire dit pourquoi il n'y en a pas. Commit
9268c572be80.

scripts/tests/test_regen_quarto_render.py : test_readme_count_in_comment devient
test_no_count_in_generated_comment, qui asserte l'absence des trois formes et la
présence des entrées de liste. Témoin négatif vérifié : les trois anciennes lignes sont
bien attrapées par l'assertion (sinon le test passerait à vide).

Le compteur de stdout est conservé — il ne se committe pas, il sert au diagnostic
(_quarto.yml render list up to date (527 READMEs, 170 docs/*.md, 1501 notebooks)).
Si vous préférez qu'il disparaisse aussi, c'est une ligne à retirer.

La démonstration du bien-fondé est dans le conflit de cette PR elle-même : _quarto.yml a
conflicté sur une seule ligne, # 1489 notebooks (...) contre # 1493 notebooks (...),
même liste de sous-arbres, seul le nombre différait.

(b) Fraîcheur de main par l'automatisation — FAIT

catalog-cron.yml appelle désormais python scripts/regen_quarto_render.py et stage
_quarto.yml dans son commit, sur la branche longue durée chore/catalog-refresh-pending —
le modèle que l'arbitrage désigne. Commit 50ef7b9e1668.

Le déclencheur push sur main reste dans le workflow de détection : c'est le filet qui rend
une main périmée visible si le cron cesse de tourner.

Mesure qui justifie (b) — main est périmé à l'instant de cette PR :

$ git checkout origin/main -- _quarto.yml && python scripts/regen_quarto_render.py
_quarto.yml updated: render list now includes 527 READMEs, 170 docs/*.md, 1501 notebooks.

17 entrées manquaient. J'ai vérifié que leurs fichiers existent bien sur main
(git cat-file -e origin/main:<path>) avant de conclure : .../prosody_lab/bakeoff_large/README.md,
.../SemanticKernel/eval-pilots/README.md, .../IchimokuEnergySector-QC/README.md,
docs/coordination/gpu-reservation.md, .../ICT-25b-StratificationInterTailles-Python.ipynb, etc.
Ce n'est donc pas de la dette ramassée par la branche, c'est l'état de main.

(c) Contrôle par PR en advisory — FAIT

Job renommé _quarto.yml render list freshness (advisory). scripts/pr_gate.py classe par
sous-chaîne (ADVISORY_MARKER), mesuré :

is_advisory('_quarto.yml render list freshness (advisory)') -> True
is_advisory('_quarto.yml render list freshness')            -> False

Sans le marqueur, l'organe agrégeait le job comme une porte dure : toute PR touchant un carnet,
un README ou un docs/*.md aurait dû committer une régénération de _quarto.yml.

(d) Branche rafraîchie — FAIT

origin/main fusionné (commit de merge bd87192a73ac). Le conflit portait sur _quarto.yml
seul — la ligne de compteur des notebooks. Résolution : la liste de main fait foi pour le
contenu, puis régénération avec le script corrigé. Diff final contre main sur ce fichier :
3 suppressions (les compteurs) + 17 ajouts (les entrées que main ne portait pas).

Sur la dette : je l'ai gardée dans la PR, parce que (b) prend en charge la fraîcheur
future, pas le retard déjà accumulé — les 17 entrées absentes de main resteraient absentes
jusqu'au prochain run du cron. Si vous préférez la sortir de cette PR et laisser le cron
résorber, dites-le : c'est un git checkout origin/main -- _quarto.yml et un commit.

R2 / R3 — FAIT

  • fetch-depth: 1 et repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}.
    Les deux vont ensemble : en profondeur 1, le SHA de tête n'existe que dans le dépôt qui le
    porte, donc une PR de fork échouait à le résoudre. Le || github.repository couvre les runs
    push / dispatch, où pull_request est absent.
  • Le chemin .github/workflows/quarto-render-list-freshness.yml, présent côté pull_request,
    manquait au déclencheur push : éditer ce fichier ne relançait pas la garde.

Validation

Preuve État
pytest scripts/tests/test_regen_quarto_render.py 50 passed sur l'arbre fusionné
regen_quarto_render.py --check rc=0, liste à jour
Témoin négatif du test les trois anciennes formes de compteur sont attrapées
is_advisory False → True sur le renommage (mesuré)
YAML des deux workflows parsés, jobs check et regen présents
Périmètre 5 fichiers ; pré-validé hors-ligne contre la liste réelle

Coordination #19965

DM envoyé à myia-po-2023:CoursIA : le hunk de leur branche sur regen_quarto_render.py
(NOTEBOOK_SUBTREES + entrée GenAI/3D/) ne conflicte pas avec le mien (retrait des
compteurs, autre zone du fichier). _quarto.yml conflictera, oui — leur version a été régénérée
avant le retrait des compteurs. Le geste pas cher de leur côté est de relancer
python scripts/regen_quarto_render.py après ce merge. Je ne touche pas à leur fichier et je ne
demande pas de rebase ; je leur ai demandé l'ordre de merge qu'ils préfèrent.


Merci de relire à la tête 50ef7b9e1668. Le dossier [ADJOINT PREFLIGHT] à cette tête suit.

@jsboige

jsboige commented Oct 9, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2026:CoursIA-3
pr: 19901
head: 50ef7b9
complete: true
body: read
comments-reviewed: 12
reviews-reviewed: 2
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: 22e2667bebfd4cbba70290a7e1e61f4edae9bc74c45fa79cefc170c91441ff0c
diff-files: 5
diff-additions: 231
diff-deletions: 16
checks: BLOCKED
b0: clear
scope: pass
domain: pass
verdict: BLOCKED
organ: check_adjoint_prevalidation.py
organ-command: python scripts/check_adjoint_prevalidation.py --derive-verdict 19901
organ-rc: 3
[/ADJOINT PREFLIGHT]

This branch has not been deployed

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

Labels

pr-overlap Advisory: another open PR touches the same files (organ #13615)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants