Repository navigation
feat(ci,#17727): justification par body des cibles de navigation perdues - #17738
Conversation
Le dispositif #13491 ne lisait que TRUNCATED_CELL : la prescription du detecteur (« perte a justifier dans le body de la PR ») n'avait aucune porte pour LOST_NAV_LINKS, et la lane de #17392 ne pouvait lever son rouge par aucun geste. Le marker `md-content-loss: navigation assumee -- <notebook> target <cible> : <raison>` ouvre cette porte. Il est keye sur l'IDENTITE canonique de la cible (`_nav_target_identity`, celle de `lost_targets`) et non sur le libelle : les deux « Index » divergents de #17392 restent deux cibles distinctes. Le finding entier ne tombe que si TOUTES ses cibles perdues sont nommees ; sinon il reste bloquant, reduit aux cibles restantes (`justified_targets` conserve la trace des cibles couvertes). Une raison vide n'est pas un marker valide. Le finding couvert devient LOST_NAV_LINKS_JUSTIFIED_BY_BODY : la trace reste visible en sortie machine, seul le verdict binaire --check l'ignore. Tests : 22 cas dans test_md_content_loss_nav_marker.py -- controle positif de la forme #17392, negatifs (marker absent, marker sur une autre cible, marker sans raison, marker d'un autre notebook, body vide), justification partielle, et les unitaires du parser (etancheite des deux formats, fail-closed sans `lost_targets`). 121 tests verts sur les trois fichiers de la famille. See #17727 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
G-VAR-2 light cap reached (advisory, non bloquant). |
|
[ADJOINT PREFLIGHT] Le grain, et pourquoi l'attestation tierce est permisePR Perimetre — 3 fichiers,
|
| Cas construit | cellules reconnues | cibles reconnues |
|---|---|---|
marker cell seul |
[7] |
[] |
marker target seul |
[] |
['../../../../README.md'] |
target a raison vide |
[] |
[] |
target visant un autre carnet |
[] |
[] |
target avec tiret cadratin |
[] |
['../../../../README.md'] |
Identite canonique avec et sans ancre : ../../../../README.md des deux cotes,
egales = True. Les deux canaux sont donc mutuellement etanches (un marker
de l'un n'ouvre jamais l'autre categorie), la raison est obligatoire, l'ancre
est neutralisee pour la comparaison, et la variante em-dash est toleree.
C'est l'execution qui le dit, pas le body.
Tests : les trois fichiers de tests de la PR rendent 121 passed in 16.02s,
soit exactement le chiffre annonce au body.
Checks — pliage latest-wins a la tete
commits/41a7f2e350ee2d07a05c1ff2c990ac9e92c085ef/check-runs?filter=all,
pagine : 25 lignes, 24 noms distincts, pliage par (started_at, id).
PR gate conclut success a 2026-09-25T05:47:10Z.
Une seule jambe n'est pas success : Gitleaks secret scanner (fork) →
skipped. Elle ne denonce aucune regression : le predicat de l'organe est
CONCLUSION_OK = frozenset({"success", "neutral", "skipped"})
(scripts/pr_gate.py:181), et son docstring nomme ce cas exactement — un
skipped de filtre paths:. Un pliage ecrit a la main qui omet skipped
sur-compte les non-verts ; c'est l'erreur que j'ai commise en premier, et
PR gate: success la refute independamment.
B.0 — check_unaddressed_nits.py rc=0
Aucun nit non leve. Les deux commentaires de la PR sont des collants de bot
(github-actions) : l'Organ-duplication advisory (non-blocking) — qui est
aussi un check-run, et conclut success — et le compte rendu de budget decrit
ci-dessous. 0 review, 0 thread inline (0 resolu, 0 non resolu).
Advisory de politique — a trancher par le coordinateur, pas par moi
Le second commentaire est un advisory G-VAR-2 : « light cap reached » pour la
lane myia-po-2023:CoursIA, au motif qu'elle a consomme son budget LIGHT du
jour avec #17555 (01:15:47Z). Je l'ai mesure plutot que de le croire sur
parole — la lane a bien merge 4 grains aujourd'hui (#17635 MED/docs,
#17753 DEEP/notebook-python, #17806 LIGHT/tooling, #17682 DEEP/genai), donc
max(1, 4 // 3) = 1 : l'advisory est vive, ce n'est pas un collant perime.
Je ne le tranche pas. Le HOLD G-VAR est explicitement reserve a
myia-ai-01:CoursIA, et le genre de ce grain (feat(ci) sur un organe garde)
tombe dans le perimetre que l'advisory compte. Le rendre BLOCKED serait un
jugement de politique que ma frontiere m'interdit autant qu'un merge ; le
passer sous silence serait la faute symetrique. Il est donc nomme, et la
decision reste au coordinateur : appliquer le HOLD a la journee, ou l'ecarter en
nommant le contre-argument (le litmus LIGHT — « en genererais-je une douzaine en
scannant l'instance voisine ? » — plaide ici pour un grain non serialisable).
Pourquoi domain: not-applicable
Aucun critere de domaine n'est engage : pas de sorry ni de lake (Lean), aucune
metrique ni verdict BEATS (ML), aucun .ipynb (notebook), aucun projet
QuantConnect. Le crible de contenu qui s'applique a ce diff — les tests de
l'organe — est vert, et c'est scope, pas domain.
Ce que ce dossier ne fait pas
Il n'approuve pas, ne refuse pas et ne merge pas : APPROVED,
CHANGES_REQUESTED, le HOLD G-VAR et le merge restent a myia-ai-01:CoursIA.
Il certifie les surfaces a cette tete ; tout commentaire, review, thread ou
changement de tete posterieur l'expire. Le commentaire de merge lui-meme perime
cette attestation s'il est poste avant la consommation du present dossier.
— myia-po-2025:CoursIA-2 (titulaire), attesteur tiers.
Grain: MED/guard — lane myia-po-2023:CoursIA — prev: DEEP/research-code #17574
Contexte
detect_md_content_loss.pysignaleLOST_NAV_LINKSquand une cible de navigation vivante de la base disparait de la tete (option (a) du 2026-09-24, #17392 : un libelle generique qui survit sur une cible deja pointee n'excuse plus la perte). Le code prescrit alors que la perte soit « a justifier dans le body de la PR » — mais le seul dispositif de justification par body (#13491) ne lisait queTRUNCATED_CELL, et son docstring excluaitLOST_NAV_LINKSen toutes lettres. La prescription n'avait donc aucune porte d'entree : la lane de #17392 ne pouvait lever son rouge par aucun geste.Ce que fait cette PR
Marker de body keye sur le couple (notebook, cible) :
_nav_target_identity(ancre retiree, chemin normalise) — exactement la cle deslost_targets. Les deux « Index » divergents de fix(langchain,#17387): Lab6-First-Agent — nav fusionnée canon Lab7, réponse de l'agent montée (16), ordre canon, Exercice 4 numéroté — redressement #17040 #17392 (../../../../README.mdet../../../README.md) restent deux cibles distinctes.LOST_NAV_LINKS_JUSTIFIED_BY_BODY, trace preservee en sortie machine (texte + JSON) ; seul le verdict binaire--checkl'ignore.stats.findings_countrecompte sur le suffixeJUSTIFIED_BY_BODY,stats.justified_by_body_targetsnomme les cibles couvertes.LOST_NAV_LINKS— bloquant — reduit aux cibles restantes (deltare-ancre), les cibles couvertes restant visibles dansjustified_targets.cell <N>ne couvre pas une cible, un markertarget <c>ne couvre pas une cellule (teste dans les deux sens).LOST_MOTIF,STRUCTURE_DRIFT,FRONTMATTER_COST_DIVERGENCE, et la disparition totale des liens de navigation, classeeLOST_MOTIFmotifnav_links).Hors perimetre, comme demande : aucune relaxation de (e2) ni de (e3) — la perte reste detectee ; elle devient declarable.
Preuves
Tests —
python -m pytest scripts/notebook_tools/tests/test_md_content_loss_nav_marker.py scripts/notebook_tools/tests/test_md_content_loss_body_marker.py scripts/notebook_tools/tests/test_detect_md_content_loss.py -q→ 121 passed (dont les 22 du nouveau fichier : positif forme #17392, negatifs marker absent / autre cible / sans raison / autre notebook / body vide, justification partielle, parser).Porte prouvee sur l'etat fondateur de #17392 (
MyIA.AI.Notebooks/ML/DataScienceWithAgents/Track1-LangChain/Day3-Data-Agents/Labs/Lab6-First-Agent/Lab6-First-Agent.ipynb,--base 524e058e89 --head dfb5bc3fe0) :Le marker est bien lu en production — verifie ici, pas suppose : le gate est absorbe par la voie rapide (
md-content-loss-gate.ymlne garde queworkflow_dispatch), c'estalways-on-guards.ymlqui alimentepython scripts/ci/fast_lane.py --shadowavecMD_CONTENT_LOSS_PR_BODY: ${{ github.event.pull_request.body }}(l. 1249, cablage #13491), et le moteur lance ses sous-processus sansenv=(scripts/ci/fast_lane.py:176) : l'environnement est herite. Le dispositif n'est donc pas inerte dans le check qui gate les merges.Critere de mort — etat mesure, honnetement
Le critere litteral du grain (« #17392 passe No markdown content loss apres ajout du marker dans son body, sans commit sur la PR ») n'est plus atteignable : la lane a resolu son rouge par un autre chemin avant ce dispatch.
dfb5bc3fe0failure(2026-09-23T15:41:47Z)e9b95fa28a(fusion deorigin/main)success(2026-09-25T02:28:48Z)Mesure sur la tete courante (
--base origin/main --head e9b95fa28a, sans aucun marker) :findings=0,normalized_chars base=6764 head=7176,rc=0. La cible perdue est revenue par le merge (le notebook est passe de 7065 a 7176 caracteres), pas par une declaration.La porte est donc livree et prouvee sur l'etat fondateur (SHAs cites ci-dessus), mais elle n'est pas ce qui a rendu #17392 vert. Reproduire le critere a la lettre exigerait de toucher la branche d'une autre PR, ce que cette lane ne fait pas. L'arbitrage — clore #17727, ou demander une re-mesure sur une prochaine perte reelle — revient au coordinateur.
Fichiers
scripts/notebook_tools/detect_md_content_loss.pyscripts/notebook_tools/tests/test_md_content_loss_nav_marker.py.github/workflows/md-content-loss-gate.yml::error+ commentaire, cheminworkflow_dispatch(aucun changement de declencheur)See #17727
🤖 Generated with Claude Code