Skip to content

fix(nb-tools,#16110): source-collapse ratchet — second mechanism, structural fold - #16112

Merged
myia-ai-01 merged 1 commit into
mainfrom
fix/16110-source-structure-collapse
Sep 14, 2026
Merged

myia-ai-01 merged 1 commit into
mainfrom
fix/16110-source-structure-collapse

Conversation

@jsboige

@jsboige jsboige commented Sep 14, 2026 •

Copy link
Copy Markdown
Owner

Grain: MED/ci -- lane myia-po-2023:CoursIA

Sujet

#16110 : une cellule code dont la source s'effondre en un commentaire passe tous les organes au vert. Le ratchet de source existant (check_source_collapse.py, #15901) mesure le volume ; ce défaut-là est structurel — il lui est invisible par construction. Cette PR ajoute le second mécanisme à l'organe existant (même famille, même check-run, aucune nouvelle voie rapide à câbler).

Périmètre effectif : 4 fichiers — scripts/notebook_tools/check_source_collapse.py (critères structurels + --self-test + impression), scripts/notebook_tools/tests/test_check_source_collapse.py (13 tests neufs, 30 au total), .claude/rules/pr-review-discipline.md (point 8 : ce qu'exige un finding STRUCTURE), scripts/ci/fast_lane_registry.py (l'en-tête TRANCHE11 dit désormais les deux mécanismes).

Le défaut, mesuré firsthand (pas repris du body de l'issue)

Même cellule, Lean-18-Sendov-Complex-Analysis.ipynb cellule 40cb37d5, origin/main contre la tête de #16097 (1209b5357) :

base head
items de source 44 12
caractères 1132 1728
len(ast.parse(source).body) 10 0
execution_count 7 None
sorties -- 1 stream, 312 car.

Le head est long : le même geste qui a retiré les \n a ajouté une note de récupération de 639 caractères. Deux instruments restent donc verts, et aucun n'est en défaut :

Ce qui est implémenté

Le discriminant est le nombre d'instructions (magics IPython retirés, même machine à états que le garde bloquant — réutilisée, pas réécrite) :

  • emptied — cellule appariée dont la source passait de > 0 instruction à 0. Basé sur le contenu : peu importe comment les \n ont disparu.
  • orphan-output — la cellule porte des outputs non vides et 0 instruction : un résultat qu'aucune instruction ne peut avoir produit. Sans base par construction (ni baseline ni diff, seulement la cellule) — le 3ᵉ critère de l'issue.

Les exemptions existantes (contenu déplacé, purge de diagnostic) arbitrent les signaux comparatifs : elles énoncent « rien ne s'est effondré ici », ce qui est une affirmation sur la relation base→head. Elles ne blanchissent pas orphan-output, dont l'affirmation est intra-cellule et reste vraie quoi qu'il arrive au code (le contenu déplacé ailleurs laisse quand même une sortie orpheline au-dessus d'une cellule vidée). Sur le cas fondateur, les deux fractions d'exemption mesurent 0.00 : elles n'auraient rien supprimé.

Ce qui n'est PAS implémenté, et pourquoi (critère 1 de l'issue : réfuté par mesure)

Le critère 1 (« le nombre d'items de source sans \n final augmente ») mesure la granularité de sérialisation, pas une corruption. Témoin sur main : GenAI/Texte/21_LoRA_FineTuning.ipynb cellule 69b296cb est sérialisée caractère par caractère (['#', ' ', 'P', …], 820 items) → 802 items non terminés, et pourtant ast.parse rend 10 instructions et sa sortie est réelle : la cellule est saine.

Histogramme des items non terminés sur les 11 970 cellules de code des 953 notebooks Python de main : {0: 11970, 1: 1, 802: 1}.

Un détecteur dont le déclenchement dépend de l'outil qui a sérialisé la cellule signalerait des re-sérialisations légitimes : le signal est retiré plutôt que livré avec un seuil. Les cas de repli qu'il visait sont déjà couverts deux fois : un repli dont la première ligne est du code perd sa syntaxe (garde BLOQUANT ci-dessus) ; un repli dont la première ligne est un commentaire est pris ici par emptied.

Deux pièges mesurés avant d'écrire le prédicat

  • Magic = producteur. Le premier balayage, sans garde, signalait 6 cellules — toutes # commentaire + !python … / %pip install …. Leur sortie a un producteur (le magic) ; ast n'en voit aucun parce que le stripper a retiré la ligne. Le prédicat exige donc aucun magic (même règle de ligne logique que le stripper). Avec ce garde, le balayage de main rend ZÉRO cellule : le critère n'a aucune instance préexistante dont il faudrait être conservateur.
  • Le passé. Un finding qu'une PR seule peut créer ne solde pas le passé — et main n'en porte aucun. orphan-output n'est donc délibérément pas restreint aux cellules éditées : là où l'affirmation est intra-cellule, la baseline n'ajoute rien.

Calibration (faux positifs)

Preuves

SOTA / portée

Aucun notebook touché, aucun outil dégradé : une PR d'organe CI, advisory par design (blocking=False inchangé dans la voie rapide). Le jugement reste au reviewer ; l'organe rend la perte visible.

Closes #16110

🤖 Generated with Claude Code

…uctural fold

The existing organ (#15901) measures source VOLUME. A cell whose source folds
entirely into a comment is invisible to it by construction: on the founding
case (PR #16097, Lean-18 cell 40cb37d5) the head GROWS 1132 -> 1728 chars
because the same write that stripped the newlines appended a 639-char recovery
note, so the magnitude gate stops before any floor. The BLOCKING
notebook-cell-source-parses guard is blind too -- a fully commented cell parses
clean -- which is why it stayed green on a cell whose code had disappeared.

Adds two structural signals to the SAME check-run, no new fast-lane wiring:

  * `emptied`       matched cell whose statement count went from > 0 to 0.
  * `orphan-output` non-empty outputs with 0 statements and no IPython magic
                    (base-free by construction -- the issue's 3rd criterion).

The existing exemptions speak about the base->head RELATION, so they arbitrate
the comparative signal only; `orphan-output` is intra-cell and stays true
whatever happens to the code elsewhere. Both exemption fractions measure 0.00
on the founding case, so nothing was suppressed.

`_scan_line` / `_strip_ipython_magics` / `_is_python_kernel` are imported from
check_cell_source_parses rather than reimplemented, so the organs cannot
disagree about the same cell.

Criterion 1 of the issue (unterminated-item count) is REFUTED by measurement,
not implemented: per-character serialization already exists on main
(GenAI/Texte/21_LoRA_FineTuning.ipynb cell 69b296cb -> 802 unterminated items,
10 statements, real output, healthy). Histogram over the 11970 code cells of
main's 953 Python notebooks: {0: 11970, 1: 1, 802: 1}. The signal is withdrawn
rather than shipped with a threshold; the folds it targeted are covered twice
without it (code-first fold -> the blocking syntax guard; comment-first fold ->
`emptied`).

Calibration: 0 structural findings on main's full Python corpus; 0 structural
findings on the 18 notebooks changed by 12 merged PRs (#16080, #16071, #16069,
#16067, #16041, #16039, #16027, #16024, #16021, #16020, #16013, #16012). A
first sweep without the no-magic guard flagged 6 cells, all `# comment` +
`!python`/`%pip` -- the magic IS the producer, so the guard is measured
necessity, not caution.

Evidence: 30 tests green in the file, 89 across the sibling organ files;
`--self-test` OK with BOTH founding-case replays firing (volume #15901 and
structure #16110); live end-to-end run at #16097's head reports the exact
measured finding with RC=1.

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

Copy link
Copy Markdown
Contributor

Grain tag obligatoire (#10045, bloquant).

Grain tag absent (no Grain: / in body).

Pour passer ce gate, le body doit porter en tete une ligne de la forme :

Grain: <DEEP|MED|LIGHT>/<genre> -- lane <machine:workspace> -- prev: <TIER>/<GENRE> #<PR>

Le <genre> doit figurer dans l'enumeration §1 de variation-protocol.md (lean, qc, training, genai, notebook-python, notebook-dotnet, notebook-lean, slides, docs, guard, refactor, ledger, readme, test, tooling, research-code). Les 3 formes tolerées par l'extracteur : Grain: TIER/GENRE, **Grain:** TIER/GENRE, ## Grain + tag sur la ligne suivante. La lane doit suivre le format <machine>:<workspace> (cf. lane-claim-protocol.md).

@github-actions github-actions Bot added variation-tag-genre-offlist GENRE hors de l'enumeration variation-protocol §1 variation-tag-prev-absent Tag Grain sans 'prev: <TIER>/<GENRE> #<PR>' (adjacence G-VAR-3 inevaluable) and removed variation-tag-missing PR sans tag Grain: <TIER>/<GENRE> (variation-protocol) labels Sep 14, 2026
@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-14) :

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

[Hermes] — #16112 second mécanisme source-collapse structurel, review sur head 9e7b53fc, exécution firsthand.

Vérifications faites :

  1. Self-test exécuté firsthand (fichiers fetchés au head SHA) : SELF-TEST OK: witnesses fired, benign churn silent, exemptions hold, both founding cases fire (volume #15901, structure #16110) — exit 0.
  2. Suite pytest : 30/30 passés (13 neufs + 17 existants).
  3. Le discriminant structurel est réel : _python_statements (l.295-314) compte len(ast.parse(stripped).body) — une source réduite à un commentaire fait 0 statement là où le ratchet volume la voit « longue » (1728 car. > 1132 base). C'est bien la classe #16110 invisible au mécanisme 1 par construction.
  4. Périmètre fidèle au body : 4 fichiers annoncés = 4 fichiers dans le diff ; l'organe étendu reste le même check-run (famille check_source_collapse), pas de nouvelle voie rapide câblée.
  5. Sécurité : grep credentials sur le diff = 0 match.

Le tableau base/head de l'issue (44→12 items, 10→0 statements, execution_count 7→None, +639 car. de note de récupération) est cohérent avec le mécanisme introduit — un cas qui volume-passe et structure-fail.

(contrainte #15511 : COMMENT-only sur CoursIA)

@github-actions

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #16112 (fix(nb-tools,#16110): source-collapse ratchet — second mechanism, structural fold) 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.

@myia-ai-01
myia-ai-01 merged commit 8169cfe into main Sep 14, 2026
35 of 38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

variation-tag-genre-offlist GENRE hors de l'enumeration variation-protocol §1 variation-tag-prev-absent Tag Grain sans 'prev: <TIER>/<GENRE> #<PR>' (adjacence G-VAR-3 inevaluable)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Notebook: une cellule code dont la source s'effondre en un commentaire passe tous les organes au vert (source detruite, sortie conservee)

3 participants