Skip to content

feat(guards,#15867): les deux organes de liens couvrent les decks slides/ - #15871

Merged
myia-ai-01 merged 1 commit into
mainfrom
fix/15867-link-scanners-slides
Sep 13, 2026
Merged

myia-ai-01 merged 1 commit into
mainfrom
fix/15867-link-scanners-slides

Conversation

@jsboige

@jsboige jsboige commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Grain: MED/tooling — lane myia-po-2023:CoursIA — prev: LIGHT/docs #15792

Summary

slides/ n'était dans le scope d'aucun des deux organes de liens. La tranche de contenu #15023 (PR #15865) a écrit 17 liens relatifs morts sur 03-logique ; les deux organes rendaient « 0 lien cassé ». Même mode de défaillance que le précédent documenté dans check_docs_links.py lui-même (#7422 : index.md pourrissait parce que rien ne le regardait).

Ce qui change

Fichier Δ
scripts/check_docs_links.py +88 — scope deck slides/**/slides.md (DECK_DIR), DeckScopeEmpty, --expect-broken N
scripts/notebook_tools/check_link_label_agreement.py +30 — DECK_GLOB, garde anti-scan-silencieux
scripts/ci/fast_lane_registry.py +14 −2 — le garde check-links déclenche sur slides/** (et .claude/agents/**, .claude/skills/**)
scripts/tests/test_check_docs_links.py +217 — 12 tests (scope, pièges, contrôle positif, parité cablage)
scripts/notebook_tools/tests/test_check_link_label_agreement.py +166 (neuf) — 9 tests

Le cablage, pas seulement le scope

Ajouter slides/ à l'organe ne suffit pas : docs-link-check.yml est workflow_dispatch-only depuis #12567 — sur PR, l'organe est rendu par la voie rapide, qui décide depuis paths. Sans slides/** dans cette liste, une PR deck-only comme #15865 ne lance jamais le garde. Un test de parité épingle désormais chaque scope scanné (SCAN_SCOPES + decks) contre les paths du garde.

Ce test a trouvé un second trou du même type : .claude/agents/** et .claude/skills/** sont scannés par l'organe depuis #7422 sans jamais déclencher le garde. Couverts ici (même classe de défaut, même sujet).

Les deux pièges de l'issue, couverts

  1. Jamais de scan silencieux — slides/ présent mais sans aucun deck ⇒ DeckScopeEmpty (exit 2, motif nommé), dans les DEUX organes. Un « 0 constat » depuis une portée vide est impossible.
  2. .. normalisé avant le test d'existence — un lien de deck sort de slides/ vers MyIA.AI.Notebooks/ : la résolution est deck-relative. Rectification au passage : check_docs_links.py résolvait déjà depuis le répertoire de la source (check_link, l.257), pas depuis la racine — la difficulté 1 de l'issue était un non-problème pour cet organe. Mesuré, pas supposé.

Contrôle positif (--expect-broken N, modèle scan_slidev_composition.py)

Matrix mesurée sur 05-theorie-des-jeux avec un lien cassé volontairement (restauré par cp backup) :

Commande rc
bare scan 1 — slides/05-theorie-des-jeux/slides.md:116 -> .../GameTheory-99-Absent.ipynb nommé
--expect-broken 1 1 — contrôle satisfait, vrai constat
--expect-broken 2 2 — POSITIVE CONTROL FAILED explicite
--check --base origin/main 1 — la régression n'est PAS excusée par la base

L'advisory « contrôle non armé » est confiné au bare scan : --check garde son contrat d'une ligne unique sur vert (docs-link-check.yml).

Acceptance

  1. ✅ Deux organes scannent slides/**/slides.md. Mesure : 702→720 fichiers, 6845→6953 liens (+108, le compte exact de l'issue), 0 cassé avant/après. Label organ : 2076→2093 fichiers (+17 decks), 0 désaccord sur tout le dépôt.
  2. ✅ Contrôle positif reproductible : la matrix ci-dessus + tests TestPositiveControl (le seuil, pas l'arbre, décide du verdict).
  3. ✅ Les 108 liens restent tous valides — 0 faux positif (mesuré, et épingle par test sur le vrai dépôt : test_real_deck_links_are_all_valid).
  4. ⚠️ Démontré par reconstruction, pas par replay : l'état cassé des 17 liens n'est pas dans l'historique git — les deux commits de docs(slides,#15023): ancrer l'arc de 03-logique sur son corpus (24 ancres, 3 slides neuves) #15865 sont déjà à 60 liens / 0 cassé (mesuré par _link_exists_in_tree sur les deux SHA ; la réparation est antérieure au premier push). Le lien cassé volontairement ci-dessus est le même acte de preuve, reproductible depuis la ligne de commande.

Deux prémisses de l'issue, corrigées par mesure

  • « check_docs_links.py résout depuis la racine » : non — résolution source-relative depuis l.257 (source_path.parent), idem _link_exists_in_tree. Le correctif 1 de l'issue était déjà en place.
  • « le prédicat de labels doit être assoupli sous peine de faux positifs massifs » : non — le prédicat existant compare un identifiant de famille extrait des DEUX côtés ; le basename sans extension et la glose hors crochets n'y entrent pas. Aucun changement de prédicat ; l'égalité naive label == basename ferait 108/108 faux positifs (mesuré, épingle par test test_naive_basename_equality_would_false_positive).

Scope deck-only, et pourquoi

slides/**/*.md = 119 fichiers, 734 cassés / 1367 (analysis/, extracted/, *.marp.md), mesuré sur origin/main — l'élargir rougirait toute PR. Le scope reste slides/**/slides.md, épingle par test.

Divergence documentée : check_docs_links.py garde l'archive (slides/S4-trading-algorithmique/_archive/slides.md, doctrine _archive de #9814) ; check_link_label_agreement.py exclut _archive pour tous ses scopes (notebooks compris) — épinglée par test, l'archive ne porte aucun lien .ipynb.

$ python -m pytest scripts/tests/test_check_docs_links.py scripts/notebook_tools/tests/test_check_link_label_agreement.py scripts/tests/test_fast_lane.py -q
161 passed

Closes #15867.

🤖 Generated with Claude Code

…des/**/slides.md

`slides/` n'etait dans le scope d'AUCUN des deux organes de liens : la tranche
#15023 (PR #15865) a ecrit 17 liens relatifs morts sur 03-logique et les deux
organes rendaient « 0 casse ». Meme mode de defaillance que #7422 (index.md
pourrissait parce que rien ne le regardait).

- check_docs_links.py : scope deck `slides/**/slides.md` (DECK_DIR), resolu
  DEPUIS le repertoire du deck (le `..` sort de slides/ vers
  MyIA.AI.Notebooks/). Le scope reste deck-only, mesure : slides/**/*.md =
  119 fichiers, 734 casses/1367 (analysis/, extracted/, *.marp.md) -- l'elargir
  rougirait toute PR.
- DeckScopeEmpty : slides/ present sans aucun deck => exit 2 explicite, jamais
  un « 0 casse » depuis une portee vide (piege 1 de l'issue).
- --expect-broken N : controle positif en bande (modele
  scan_slidev_composition.py). Arme et non atteint => exit 2. Matrix mesuree
  sur un lien casse volontairement : bare=1, --expect-broken 1=1 (controle
  satisfait), --expect-broken 2=2, --check --base origin/main=1 (regression
  non excusee). Advisory « controle non arme » confinee au bare scan : --check
  garde son contrat d'une ligne unique.
- check_link_label_agreement.py : DECK_GLOB + meme garde anti-scan-silencieux.
  Le predicat existant n'a PAS ete assoupli : il compare un identifiant de
  famille extrait des DEUX cotes, donc le basename sans extension et la glose
  hors crochets ne le concernent pas (l'egalite naive label==basename
  ferait 108/108 faux positifs, mesure).
- fast_lane_registry.py : le garde check-links declenche maintenant sur
  slides/** -- sans quoi une PR deck-only (#15865) ne lance jamais l'organe
  (entree de registre != cablage). La parite test trouve AUSSI que
  .claude/agents/** et .claude/skills/** etaient scannes depuis #7422 sans
  jamais declencher le garde : couverts.

Mesures sur origin/main : 702->720 fichiers, 6845->6953 liens (+108, le compte
exact de l'issue), 0 casse avant/apres ; label organ 2076->2093 fichiers
(+17 decks, _archive exclu -- divergence documentee et epinglee), 0
desaccord sur tout le depot.

Acceptance 4 : l'etat des 17 liens casses n'est PAS dans l'historique git (les
deux commits de #15865 sont deja a 60 liens / 0 casse -- mesure). Demontree
par reconstruction : lien casse volontairement sur un deck => detecte et
nomme (slides/05-theorie-des-jeux/slides.md:116), restaure par cp backup.

Closes #15867.

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

Copy link
Copy Markdown
Contributor

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

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.

@clusterManager-Myia clusterManager-Myia left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT: LGTM (vérifié: diff local base↔head des 5 fichiers + câblage lu au code au head + comptage indépendant 18 decks via git tree + check-links success au head)

[NanoClaw] — Review structurelle (5 fichiers, +492/−4) : file list + diff local base↔head (c2957441→d5cf08d6) filtré aux seules lignes changées, puis lecture ciblée des fichiers chargeurs au head — pas de full-diff API.

Objet (ancre vérifiée OPEN, créée 22:53Z) : #15867 — les DEUX organes de liens ignoraient slides/ : 108 liens non couverts, 17 liens morts écrits par la tranche #15023 passés sous le radar. Mode de défaillance déjà documenté dans l'organe lui-même (#7422 : index.md pourrissait, rien ne le regardait).

Ce que j'ai vérifié moi-même (pas recopié du body) :

  1. Le câblage, pas seulement le scope — c'est le cœur de la PR et il est réel : la voie rapide (fast_lane_registry.py) décide sur paths de ce que la gate tire, et slides/** y est ajouté, ÉPINGLÉ par un test de parité contre les scopes réellement scannés. Ce même test expose un second trou du même type (.claude/agents/**, .claude/skills/** scannés depuis #7422, jamais câblés) — corrigé ici. Un organe qui scanne sans gate qui déclenche est un vert de complaisance ; cette PR ferme la classe, pas l'instance.
  2. Les deux pièges de l'issue, au code : garde anti-portée-vide dans les DEUX organes (DeckScopeEmpty exit 2 dans check_docs_links.py:186-213 ; stderr + rc=2 dans check_link_label_agreement.py:174-183) — un « 0 constat » depuis un scope vide est devenu impossible. slides/ absent ⇒ [] silencieux (cas des fixtures), présent-vide ⇒ refus explicite : l'asymétrie est la bonne.
  3. Contrôle positif --expect-broken N (l.587-610) : in-band, rc=1 = finding réel, rc=2 = contrôle non tenu, advisory confiné au bare scan pour préserver le contrat une-ligne-sur-vert de --check. Le modèle (scan_slidev_composition.py) est cité, la matrix est mesurée.
  4. Comptage indépendant : arbre git récursif au head → 18 fichiers slides.md, dont 03-logique (le deck de l'incident) et S4-trading-algorithmique/_archive/slides.md (la divergence _archive documentée existe bien) — recoupe le « +18 côté liens / +17 côté label » du body.
  5. +364 lignes de tests épinglant l'acceptance SUR LE VRAI DÉPÔT (test_decks_are_scanned_on_the_repo, test_real_deck_links_are_all_valid, test_deck_scope_is_not_the_whole_slides_tree avec la justification mesurée 119 fichiers / 734 liens cassés si le scope s'élargissait).

Réserves (non bloquantes) :

  • La démonstration du défaut historique est par reconstruction, et le corps le dit (⚠ 4) : les 17 liens cassés ne sont dans AUCUN SHA de l'histoire (#15865 est déjà à 0 cassé au premier push). La couverture neuve reste justifiée indépendamment (108 liens scannés par personne), mais le dossier repose sur la reconstruction, pas sur un replay — à savoir si l'issue sert d'ancre d'audit plus tard.
  • slides/** dans les paths de la gate déclenche aussi sur analysis//extracted/ (artefacts hors scope de l'organe) : bruit de gate sans vert faux. Acceptable en l'état.
  • CI incomplète à l'instant de la mesure (Scripts Tests CPU, PR gate, Analyze csharp in_progress) ; check-links et les guards concernés sont déjà success.

0 gh pr diff / 0 /files avec patch (diff local uniquement), 0 secret (grep motifs usuels base↔head).

— [NanoClaw]

@github-actions

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #15871 (feat(guards,#15867): les deux organes de liens couvrent les decks slides/) 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.

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.

slides: les deux verificateurs de liens ne scannent pas slides/ (108 liens non couverts, 17 liens morts ecrits en #15023 passes sous le radar)

3 participants