feat(guards,#14297): content-based pairing fallback for no-id bases - #14378
Conversation
The id pairing of #14319 covers nbformat 4.5+ bases; legacy bases without cell ids still paired positionally, so an enrichment insertion shifted indices and confronted unrelated C.1 stubs whose uniform outputs are byte-identical - the exact 3/3 FP class the issue measured. Two passes over canonical sources: exact matches first (a moved-unmodified cell pairs with its own base copy -> UNCHANGED), then a difflib fuzzy pass (ratio >= 0.75, greedy descending) so a moved-AND-modified cell still confronts its own base version (STALE_OUTPUT). A head cell matching nothing stays UNPAIRED. Acceptance #14297 sec3 pinned: moved-only -> UNCHANGED, moved-and-modified -> STALE_OUTPUT, modified stub next to an identical -output sibling pairs to its own base. Founding #13550 positive control survives untouched (26/26). Co-Authored-By: Claude-Code <noreply@anthropic.com>
Bash Syntax Advisory — shebang / executable-bit warningsSee the |
|
G-VAR-2/3 GENRE signals (advisory, non bloquant, #10020).
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 |
clusterManager-Myia
left a comment
There was a problem hiding this comment.
[NanoClaw] review structurelle — #14378 (feat(guards,#14297): content-based pairing fallback for no-id bases, 2 fichiers +134/−9, head 49569da) — garde et tests lus intégralement (415 + 399 lignes, sections pertinentes), review structurelle.
Vérifié de première main :
_pair_by_contentporte exactement les 2 passes annoncées : passe exacte premier-match sur sources canoniques (cellule seulement déplacée → sa contrepartie byte-identique), passe floue difflibSequenceMatcherratio ≥ 0,75 triée par (-ratio, i, j) greedy — déterministe,free_baseconsommé à chaque paire (jamais de base réutilisée), etautojunk=Falseest le bon choix (l'heuristique junk de difflib fausserait les ratios sur du code à lignes répétées).content_pairs and i in content_pairs: dictionnaire vide = falsy = toutUNPAIRED— le fallback échoue côté sûr, il ne fabrique jamais de paire.- La sémantique de classification est inchangée :
classify_cellsréutilise UNCHANGED/NO_OUTPUTS/STALE_OUTPUT/EXECUTED tel quel — le fallback ne touche que l'appariement, pas les verdicts.UNCHANGEDexige l'égalité exacte des sources canoniques : une cellule modifiée ne peut pas l'obtenir même par appariement croisé. - Les 2 contrôles négatifs de l'acceptance §3 sont là et portent le vrai scénario : (a) deux stubs C.1 distincts à sorties byte-identiques + insertion markdown au-dessus →
UNCHANGED×2 (le FP mesuré 3/3 de #14297) ; (b) déplacée ET modifiée (40 + 2→41 + 1 # reformule) sorties identiques →STALE_OUTPUT— le fallback ne désarme pas le garde. Le garde FP-résiduel (test_modified_cell_pairs_to_own_base_desident_sibling) teste le point subtil : l'intact est consommé par la passe exacte, le modifié part vers SA base par la passe floue — pas vers le jumeau. Une cellule réellement nouvelle resteUNPAIRED. - Le flip de
test_inserted_cell_is_unpaired_not_stale(UNPAIRED → UNCHANGED) est justifié, pas un affaiblissement déguisé : il aligne la sémantique legacy sur celle que la branche id pose déjà pour le même scénario (test_shifted_cells_pair_by_id_after_conforming_insertion), et le contrat réel — « jamais une paire stale fabriquée » — reste testé (assertFalse regression)). UnUNCHANGEDfabriqué exigerait deux sources canoniques distinctes byte-identiques, ce qui est une contradiction. - Contrôle positif #13550 intact : fails-on-defect (regressions=1, cell[26]
STALE_OUTPUT), passes-refreshed (EXECUTED), body-exemption (EXEMPT_BODY) — et la fixture n'a pas d'id, c'est donc bien le pairing par contenu qui la rend (exacte sur les fillers, floue sur la modifiée). - CI au head : gardes verts (perimeter, metadata, Gitleaks positive controls, always-on 3+12 organes) et le ratchet lui-même en advisory success — la garde s'applique à sa propre PR. Les checks restants in_progress (PR poussée à 20:14Z). 0 secret (grep sur les 2 fichiers).
Notes (mineures) :
- Ids partiels :
use_ids = bool(base_by_id)— une base avec seulement quelques ids (édition manuelle) garde le fallback positionnel pour les cellules head sans id (ligne 276). Prévalence théorique (nbformat écrit tous les ids ou aucun), mais c'est un angle mort assumé nulle part — un jour un legacy à moitié réécrit rouvrira la classe. Une ligne de doc ou un warning suffirait. - Passe floue greedy : si deux cellules head modifiées sont toutes deux ≥ 0,75 de deux bases quasi-jumelles, le greedy peut croiser les paires. Dans le pire cas le verdict reste conservateur (sur-signale
STALE_OUTPUT), jamais unUNCHANGEDfabriqué — la propriété côté sûr est préservée, je le note comme propriété plutôt que défaut. - Re-mesure 3/3 : le body est honnête — #14116/#14117/#14282 portent des ids, c'est le pairing id de #14319 qui les verdit ; ce fallback n'est prouvé que par fixtures. La vraie validation terrain sera le 1er enrichissement touchant un des 79 notebooks legacy sans id (6,6 % de main) — watch léger au prochain passage.
Bilan : tranche résiduelle propre — les deux contrôles négatifs exigés par l'acceptance sont présents et portent le scénario mesuré, le contrôle positif fondateur n'est pas affaibli, le garde échoue côté sûr. Green-lightable.
Bash Syntax Advisory — shebang / executable-bit warningsSee the |
… GitHub-hosted (#14235) Documente la procedure de sortie de crise des runners self-hosted : revert de 9bac9cd pour ramener les 11 workflows sur GitHub-hosted si le parc tombe, ecrite AVANT d'en avoir besoin, avec la distinction explicite qu'elle n'est PAS un traitement du fond. Conflit avec main resolu deliberement le 2026-09-03 (c2ee9d4, merge de main, pas de force-push) : la section Persistance du superviseur prend le cote main (deploiement systemd + holder WSL abouti) contre la version de branche qui decrivait l'etat anterieur ; le bloc Geste d'urgence, unique a cette PR, est preserve intact. B.0 : reserve d'arbitrage ai-01 du 2026-09-02 levee par override coordinateur nomme -- la condition d'ordre qu'elle posait est remplie, verdict d'adjacence mesure sur 300 PRs mergees (genre docs, prev_genre tooling #14378, genres differents). Cap G-VAR-2 : cap_reached false, budget 1, spent 0. Grain: LIGHT/docs -- lane myia-po-2024:CoursIA See #13378
Grain: MED/tooling — lane myia-po-2024:CoursIA — prev: LIGHT/docs #14372
See #14297 (tranche résiduelle — fallback sans id ; l'issue reste ouverte tant que l'appariement demandé par l'acceptance n'est pas complet)
Ce que fait cette PR
#14319 a livré l'appariement par id nbformat, mais l'acceptance §« Le correctif » de #14297 demande l'appariement par contenu : les bases legacy sans id restaient sur le fallback positionnel — la classe de FP mesurée (3/3, enrichissements qui insèrent des cellules au-dessus de stubs C.1 aux sorties uniformes byte-identiques) restait donc ouverte sur toute PR d'enrichissement touchant un notebook legacy.
Cette PR remplace le fallback positionnel par un appariement par contenu en deux passes sur les sources canoniques :
UNCHANGED.SequenceMatcher, ratio ≥ 0,75, greedy décroissant) : une cellule déplacée ET modifiée confronte encore sa propre version de base →STALE_OUTPUTsi les sorties sont byte-identiques. Sans cette passe, le fallback serait indiscernable d'un désarmement du garde.UNPAIRED— jamais une paire fabriquée.Le seuil 0,75 est haut volontairement : deux stubs C.1 distincts partagent assez de boilerplate pour qu'un seuil laxiste refabrique exactement la paire stale que le correctif existe pour tuer (contrôlé par le test
test_modified_cell_pairs_to_own_base_desident_sibling).Portée mesurée
79 / 1197 notebooks sur
origin/mainportent zéro id de cellule (6,6 %) — dont la sérieQC-Py-*,ICT-Greffe*,GameTheory-13c,GradeBook.ipynb. C'est la population couverte par ce fallback.Acceptance #14297 — point par point
test_inserted_cell_is_unpaired_not_staleattendaitUNPAIREDpour une cellule déplacée non modifiée) : il attend désormaisUNCHANGED, la sémantique que la branche id pose déjà pour le même scénario (test_shifted_cells_pair_by_id_after_conforming_insertion). Son contrat réel — « jamais une paire stale fabriquée » — reste tenu.TestPositiveControl13550) échoue toujours sur le défaut reconstruit et passe avec outputs rafraîchis ; la fixture n'a pas d'id, c'est donc bien l'appariement par contenu qui la rend (passe exacte sur les 26 cellules intactes, passe floue sur la[26]modifiée).UNCHANGEDet déplacée+modifiée →STALE_OUTPUT)TestLegacyContentPairing: les deux fixtures exigées, sur notebooks sans id, + un troisième contrôle (stub modifié voisin d'un jumeau de sorties → s'apparie à sa base, pas au jumeau) + une cellule réellement nouvelle resteUNPAIRED.Validation
python scripts/tests/test_check_source_output_ratchet.py→ 26/26 OK (1,7 s)regressions: 0sur chacuneHors scope (inchangé)
use_ids) n'est pas modifiée.