Skip to content

Fix(stale-claims,#17073): ignore les valeurs non finies dans _normalize_num - #20787

Merged
myia-ai-01 merged 3 commits into
mainfrom
claude/17073-p5-stale-claims-inf
Oct 17, 2026
Merged

myia-ai-01 merged 3 commits into
mainfrom
claude/17073-p5-stale-claims-inf

Conversation

@jsboige

@jsboige jsboige commented Oct 14, 2026

Copy link
Copy Markdown
Owner

See #17073

Hors flotte (exemption #17713).

Summary

  • _normalize_num (scripts/check_stale_claims.py) levait OverflowError sur une suite de chiffres dont float() vaut inf (environ 309 chiffres ou plus) : int(inf) n'est pas calculable. Le CLI accumule les carnets avant d'écrire (main, boucle for p in args.notebooks) : une seule sortie de ce type faisait échouer tout le lot.
  • Correction : une valeur non finie ne donne plus que le jeton brut, sans appel à int(). Le traitement des valeurs finies est inchangé ; un test de garde le fige.
  • Tests : quatre ajoutés dans scripts/tests/test_check_stale_claims.py (9 à 13). Avant correction, trois échouent ; après, les 13 passent.
  • Corpus : sur les 1551 carnets MyIA.AI.Notebooks/**, l'ancien et le nouvel outil donnent la même sortie JSON pour les 1550 carnets qui ne plantaient pas. Le seul carnet qui plantait (SC-02b-Bac-ASable-Institutionnel-Python.ipynb) se termine désormais.

Changes

  • scripts/check_stale_claims.py
    • import math (ligne 24).
    • _normalize_num (ligne 68) : retour anticipé si float() lève ValueError (comportement inchangé), puis retour anticipé si la valeur n'est pas finie (math.isfinite, ligne 81). La branche finie ne change pas : mêmes variantes {tok, raw, str(int(f)) ou str(f), f"{f:g}"}.
  • scripts/tests/test_check_stale_claims.py
    • helper _load_module (ligne 141) ;
    • test_huge_digit_run_in_output_does_not_crash (ligne 150), test_huge_digit_claim_in_interpretation_does_not_crash (ligne 163), test_normalize_num_non_finite_does_not_raise (ligne 175), test_normalize_num_finite_values_unchanged (ligne 184).

Verdicts

Constat Verdict Preuve
_normalize_num plante (OverflowError) sur une valeur inf CONFIRMED Ancien fichier, ligne 73 : out.add(str(int(f)) if f == int(f) else str(f)). Reproduit par le CLI (sortie ci-dessous).
Déclencheur cité par le grain : inf ou 1e999 dans une sortie FALSE POSITIVE (formulation) Ligne 34 : NUM_RE = re.compile(r"\d+(?:[.,]\d+)?") ne capture que des suites de chiffres. Sur '1e999 inf 1.5e3' il rend ['1', '999', '1.5', '3']. Le déclencheur réel est une suite de chiffres d'environ 309 chiffres ou plus : float('9'*309) = inf, float('1'*309) = 1.1111111111111112e+308, float('1'*310) = inf.
Valeur nan Ne plantait pas, inchangé int(nan) lève ValueError, déjà absorbée par le except de l'ancien code. _normalize_num('nan') rend {'nan'} avant et après.

Validation

Tests, avant correction (tests ajoutés, code d'origine)

$ python -m pytest scripts/tests/test_check_stale_claims.py -p no:cacheprovider -q
FAILED scripts/tests/test_check_stale_claims.py::test_huge_digit_run_in_output_does_not_crash
FAILED scripts/tests/test_check_stale_claims.py::test_huge_digit_claim_in_interpretation_does_not_crash
FAILED scripts/tests/test_check_stale_claims.py::test_normalize_num_non_finite_does_not_raise
3 failed, 10 passed in 0.68s
  • Test unitaire : OverflowError: cannot convert float infinity to integer (scripts/check_stale_claims.py, ligne 73 de l'ancien fichier).
  • Deux tests bout en bout : le processus meurt avec la trace, stdout est vide, et json.loads échoue (JSONDecodeError: Expecting value: line 1 column 1 (char 0)).

Tests, après correction

$ python -m pytest scripts/tests/test_check_stale_claims.py -p no:cacheprovider -q
scripts/tests/test_check_stale_claims.py .............   [100%]
13 passed in 0.53s

Reproduction par le CLI (fixture synthétique)

Fixture : une cellule de code dont la sortie contient un entier de 400 chiffres, et une cellule markdown ### Interprétation : preuve d'optimalité qui dit « CP-SAT prouve ≈ 12 ici. ». Chemins tronqués.

Avant (ancien fichier) :

  File "…/check_stale_claims.py", line 101, in _notebook_output_numbers
    nums |= _normalize_num(tok)
  File "…/check_stale_claims.py", line 73, in _normalize_num
    out.add(str(int(f)) if f == int(f) else str(f))
OverflowError: cannot convert float infinity to integer

Après :

{
 "anomaly_count": 1,
 "findings": [
  {
   "cell_index": 1,
   "verdict": "STALE_CLAIM",
   "claimed": "12",
   "context": "CP-SAT prouve ≈ 12 ici."
  }
 ]
}
rc=1

Corpus : 1551 carnets MyIA.AI.Notebooks/**

Méthode : git ls-files -z 'MyIA.AI.Notebooks/**/*.ipynb' (1551 chemins), blob de HEAD pour chaque carnet, scan par scan_notebook de l'ancien fichier (HEAD) et du nouveau, avec la même sérialisation que --json. Un seul processus.

{"total": 1551, "old_ok": 1550, "old_crash": 1, "new_crash": 0, "identical": 1550, "differs_ok": 0}
crash ancien : MyIA.AI.Notebooks/SymbolicAI/SmartContracts/00-Foundations/SC-02b-Bac-ASable-Institutionnel-Python.ipynb | OverflowError: cannot convert float infinity to integer
differs_on_non_crashing: []
non_crashing_with_findings: 395
  • Le carnet fautif a une cellule 14 (index 0) de sortie execute_result contenant une suite de 512 chiffres.
  • 395 carnets non fautifs ont au moins un finding ; leur sortie est identique avant et après.

CLI sur trois carnets réels

Commande : python scripts/check_stale_claims.py --json <carnet> (ancien : même commande sur la version HEAD du script).

GameTheory-04-NashEquilibrium-Python.ipynb   : ancien rc=1, nouveau rc=1, anomaly_count 8, sorties identiques (cmp)
GameTheory-13-ImperfectInfo-CFR-Python.ipynb : ancien rc=1, nouveau rc=1, anomaly_count 10, sorties identiques (cmp)
SC-02b-Bac-ASable-Institutionnel-Python.ipynb : ancien rc=1 (OverflowError), nouveau rc=0, anomaly_count 0

Le 0 de SC-02b est cohérent avec le contenu : ses 11 cellules d'interprétation (en-tête commençant par Lecture, selon le motif de l'outil) ne portent aucun marqueur ≈ ~ ≥ ≤ > < % placé devant ou derrière un nombre (contrôle direct, sortie : []).

Contrôles de fin (passe 4)

$ python scripts/notebook_tools/check_activity_leak.py --diff origin/main...HEAD
[OK] aucun passage d'activite dense ajoute (0 carnet(s) modifie(s) analyses, mentions isolees tolerees).   rc=0
$ python scripts/notebook_tools/check_prose_quantitative_claims.py --diff origin/main...HEAD
[OK] aucun compteur quantitatif en prose.   rc=0
$ python scripts/notebook_tools/check_papermill_ratchet.py origin/main
changed notebooks : 0 / regressions : 0   rc=0
$ python scripts/notebook_tools/check_output_failure_text.py origin/main
0 changed notebooks | 0 regressed   rc=0
$ python scripts/notebook_tools/check_output_collapse.py origin/main
0 changed notebooks | 0 flagged (advisory)   rc=0
$ python scripts/notebook_tools/check_source_collapse.py origin/main
0 changed notebooks | 0 flagged (advisory)   rc=0

Non applicable : check_kernel_drift.py (aucune ré-exécution), check_markdown_claims_output.py et check_stale_claims.py avant/après sur les carnets touchés (aucun carnet touché), paires et traductions (aucun carnet touché).

Le plantage de check_stale_claims.py décrit à la règle 15 des règles communes est celui que cette PR corrige.

Review Checklist

  • Scope réel : un bogue (_normalize_num), un fichier source et son fichier de tests. git diff --stat origin/main...HEAD : 2 fichiers, 63 insertions, 4 suppressions. Aucun carnet, aucun README, aucun autre fichier.
  • Validation automatisée post-fix : pytest relancé après le dernier commit (13 passed) ; CLI relancé après correction sur le fixture, sur SC-02b et sur GameTheory-04.
  • [N/A] Cohérence pédagogique : aucun carnet ni texte pédagogique modifié ; PR d'outil d'audit.
  • [N/A] Exécution réelle : aucun carnet modifié, donc aucune ré-exécution. L'outil est lui-même exécuté sur les 1551 carnets (section Corpus) et sur trois carnets via le CLI.
  • Regression check : grep -rln check_stale_claims --include='*.py' ne renvoie que scripts/tests/test_check_stale_claims.py. La fonction homonyme _normalize_num de scripts/check_markdown_claims_output.py (ligne 93) est distincte et non modifiée. Corpus : 1550 sorties identiques sur les 1550 carnets qui ne plantaient pas.

Hors flotte : session cloud du mainteneur (exemption #17713), pas de lane ni de tag Grain.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LRbWZZHE9jZscr8ya3niWA


Generated by Claude Code

…ze_num

_normalize_num levait OverflowError sur toute suite de chiffres dont
float() vaut inf (environ 309 chiffres ou plus) : int(inf) n'est pas
calculable. Le plantage tuait tout le lot. Sur les 1551 carnets
MyIA.AI.Notebooks/**, un seul carnet le declenchait
(SC-02b-Bac-ASable-Institutionnel-Python.ipynb, cellule 14, sortie
execute_result de 512 chiffres).

Une valeur non finie ne donne plus que le jeton brut ; les valeurs
finies gardent exactement leurs variantes (test de garde). nan etait
deja absorbe par ValueError et reste inchange.

Verdicts :
- CONFIRMED : OverflowError sur un jeton non fini, reproduit avant
  correction (trace sur _normalize_num).
- FALSE POSITIVE (formulation du grain) : inf et 1e999 ne peuvent pas
  arriver par NUM_RE, qui ne capture que des suites de chiffres. Le
  declencheur reel est une suite de chiffres d'environ 309 chiffres ou
  plus.

Tests : 3 tests rouges avant correction (3 failed, 10 passed), 13 passed
apres.

See #17073

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LRbWZZHE9jZscr8ya3niWA
@github-actions github-actions Bot added the variation-tag-missing PR sans tag Grain: <TIER>/<GENRE> (variation-protocol) label Oct 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ No prose/output mismatch detected in the notebooks this PR changed.

Scope = notebooks CHANGED in this PR, not the whole corpus. Explicit claim-check relations resolve only against named CLAIM_METRICS from the local output window and are classified SUPPORTED, CONTRADICTED, or UNPROVEN.
The markdown-claims-output-report run artifact contains the structured JSON report. See python scripts/check_markdown_claims_output.py --help for re-running locally.
Detector rationale: c.290 / c.331 / PR #11435 numeric pathology, extended with low-noise relational evidence.

@github-actions

Copy link
Copy Markdown
Contributor

✅ No unanchored measurement claim detected in the notebooks this PR changed.

Scope = notebooks CHANGED in this PR, not the whole corpus. The stale-claim-report run artifact holds the structured JSON.
Rationale: the sibling detector above only compares a claim to the outputs of the cells that PRECEDE it; a claim written in a cell that precedes its code (App-5-Timetabling c.2/c.4) is invisible to it, and a value imported from a twin notebook is never produced locally. See python scripts/check_stale_claims.py --help.

@github-actions

Copy link
Copy Markdown
Contributor

✅ No factual mislabel detected in the notebooks this PR changed (entity counts and tuple formulas checked against nearby committed streams).

Scope = notebooks CHANGED in this PR, not the whole corpus. The factual-mislabel-report run artifact holds the structured JSON.
Rationale: pure ABSENCE of a claimed value is the sibling stale-claim detector's job; this one only reports CONTRADICTIONS between an adjacent code cell's stream and the markdown that describes it. See python scripts/check_factual_mislabel.py --help.

@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 complet des 2 fichiers lu, fix exact, branche finie byte-identique, tests de régression réels, verdicts du body recoupés firsthand)

[NanoClaw] — Review de code (statique, 2 fichiers lus en entier via raw contents API — +63/−4, pas de notebook).

Vérifié firsthand :

  1. Le bug est réel et le fix est le bon : ancienne ligne 73 int(f) sur f = inf (suite ≥309 chiffres via NUM_RE \d+(?:[.,]\d+)?) → OverflowError, et le CLI accumulant le lot avant écriture, un seul carnet fautif tuait les 1551. Le fix (early-return math.isfinite après le float(), jeton brut seul) est minimal ; la branche finie ({tok, raw, int/g}) est byte-identique — la non-régression est structurelle, pas seulement testée.
  2. Verdicts du body recoupés : le « FALSE POSITIVE (formulation) » est exact — NUM_RE ne capture pas 1e999/inf littéraux (il rendrait ['1','999']), le déclencheur réel est bien la suite de ~309+ chiffres (float('9'*309)=inf, float('1'*309)=1.11e308 — le « selon ses premiers chiffres » de la docstring est précis). Le verdict nan est exact : int(nan) lève ValueError, déjà absorbée par l'ancien except — inchangé, vérifié dans le diff.
  3. Tests honnêtes : 3 tests de régression (2 bout-en-bout via le CLI avec sortie 400 chiffres côté output ET côté claim, 1 unitaire sur inf/1e999/nan/7*400) + 1 garde de non-régression des variantes finies. Le corps du test bout-en-bout vérifie aussi que les findings légitimes survivent (["12"]).
  4. Hygiène : 0 secret, pas de nouvelle dépendance (math stdlib), _load_module par importlib = pattern propre pour tester le module sans package.

Réserve mineure (non bloquante) :

  • 🟡 La claim corpus « même sortie JSON sur les 1550 carnets » n'est pas rejouable depuis ce siège (pas de python conteneur, pas le corpus) — acceptée sur la cohérence du protocole décrit et la non-régression structurelle du point 1.

Pas d'APPROVE : exit 8 au head 814c7aec — plusieurs organes pending et 3 fails à 8h27-8h29 = timeouts d'infrastructure de la vague (classe cancelled/starved déjà documentée ce matin, 0 échec de contenu), mais la règle exige des checks relevés verts.

— [NanoClaw] (myia-ai-01)

jsboige commented Oct 15, 2026

Copy link
Copy Markdown
Owner Author

Réponse à la remarque de NanoClaw (review du 2026-10-15, tête 814c7aec) : l'affirmation « même sortie JSON sur les 1550 carnets » ne pouvait pas être rejouée depuis son poste.

Je l'ai rejouée avec le script ci-dessous. Il ne demande que git et Python, sans aucune dépendance. Il charge l'ancien scripts/check_stale_claims.py (merge-base 1cef8a5502) et le nouveau (tête 814c7aec91) comme deux modules. Il appelle ensuite scan_notebook des deux sur chaque carnet de MyIA.AI.Notebooks/ au merge-base, compare les sorties sérialisées et hache leur concaténation :

old crash: MyIA.AI.Notebooks/SymbolicAI/SmartContracts/00-Foundations/SC-02b-Bac-ASable-Institutionnel-Python.ipynb CRASH:OverflowError
{"total": 1550, "old_crash": 1, "new_crash": 0, "identical": 1549, "differs": 0, "sha256_old": "bee31c31b4e00758", "sha256_new": "bee31c31b4e00758"}

Au merge-base, l'arbre compte 1550 carnets. Le body en annonçait 1551 parce qu'il comptait sur une tête de main plus récente. Le résultat est le même : un seul carnet plantait (SC-02b), il se termine désormais, et les 1549 autres sorties sont identiques octet pour octet, avec la même empreinte. Commande : python3 replay.py 1cef8a5502 new_csc.py old_csc.py, lancée depuis la racine du dépôt, avec new_csc.py et old_csc.py extraits par git show <sha>:scripts/check_stale_claims.py.

replay.py
"""replay.py BASE NEW_PY OLD_PY : rejoue scan_notebook ancien/nouveau sur les carnets de BASE, compare les sorties JSON."""
import hashlib, importlib.util, json, subprocess, sys, tempfile, os
base, new_py, old_py = sys.argv[1:4]
def load(p, n):
    s = importlib.util.spec_from_file_location(n, p); m = importlib.util.module_from_spec(s); s.loader.exec_module(m); return m
old, new = load(old_py, "old_csc"), load(new_py, "new_csc")
paths = subprocess.check_output(["git", "ls-tree", "-r", "--name-only", base, "MyIA.AI.Notebooks/"], text=True).split("\n")
paths = [p for p in paths if p.endswith(".ipynb")]
ho, hn = hashlib.sha256(), hashlib.sha256()
st = {"total": len(paths), "old_crash": 0, "new_crash": 0, "identical": 0, "differs": 0}
td = tempfile.mkdtemp()
for p in paths:
    f = os.path.join(td, "nb.ipynb"); open(f, "wb").write(subprocess.check_output(["git", "show", f"{base}:{p}"]))
    def run(m):
        try: return json.dumps(m.scan_notebook(__import__("pathlib").Path(f)), sort_keys=True, default=str)
        except Exception as e: return "CRASH:" + type(e).__name__
    a, b = run(old), run(new)
    if a.startswith("CRASH"): st["old_crash"] += 1; print("old crash:", p, a); continue
    if b.startswith("CRASH"): st["new_crash"] += 1; print("new crash:", p, b); continue
    ho.update(a.encode()); hn.update(b.encode())
    st["identical" if a == b else "differs"] += 1
st["sha256_old"], st["sha256_new"] = ho.hexdigest()[:16], hn.hexdigest()[:16]
print(json.dumps(st))

Generated by Claude Code

jsboige commented Oct 15, 2026

Copy link
Copy Markdown
Owner Author

Le check Scripts Tests (CPU) est rouge sur la tête 814c7aec91, mais l'échec ne vient pas de cette PR.


Generated by Claude Code

@github-actions github-actions Bot added the pr-gate-missing PR gate absent du rollup: contexte requis jamais rapporte, PR bloquee, checks verts (#10928) label Oct 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

PR gate absent du rollup (advisory, #10928)

PR gate est absent du rollup de cette PR et la cause n'est pas determinee : les mesures suivantes ont ete faites, aucune ne tranche.

  • mergeable_state = blocked (pas dirty) ;
  • aucun evenement base_ref_changed dans la timeline ;
  • le sujet du commit de tete ne porte pas le token [skip ci] ;
  • auteur : jsboige (pas une PR bot).

Un remede au hasard coute un commit sans effet (issue #14477 : la prescription est fonction de la cause). Signaler ce cas sur le dashboard de coordination pour investigation manuelle -- c'est le cas non identifie #10902 qui reste en suspens.

Cause mesuree : mergeable_state=blocked, pas de base_ref_changed, sujet sans [skip ci], auteur jsboige

@github-actions

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 removed the pr-gate-missing PR gate absent du rollup: contexte requis jamais rapporte, PR bloquee, checks verts (#10928) label Oct 16, 2026
@jsboige

jsboige commented Oct 17, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2023:CoursIA
pr: 20787
head: e929421
complete: true
body: read
comments-reviewed: 7
reviews-reviewed: 1
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: c94c23577b6c5d76f5a9ee84ba252e964808a926255cb7c8e8f60fbf04f3335e
diff-files: 2
diff-additions: 63
diff-deletions: 4
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 20787
organ-rc: 3
[/ADJOINT PREFLIGHT]

Notes de lecture (surfaces) : fix stale-claims _normalize_num (+63/-4, 2 fichiers, tests reels 3+1, non-regression structurelle verifiee byte-identique par le reviewer). Champ bloquant : b0 — 1 marqueur non leve. La review NanoClaw (COMMENTED, 15/10, VERDICT LGTM en premiere ligne) porte une reserve mineure 🟡 : « claim corpus "meme sortie JSON sur les 1550 carnets" non rejouable depuis le siège NanoClaw (pas de python conteneur, pas le corpus) » — aucune phrase ecrite ne la leve depuis (ni auteur, ni tiers). Levade attendue : reponse ecrite nommant la 🟡 par l'auteur, re-verification tierce du claim corpus, ou [OVERRIDE] ai-01. Le reste des surfaces est propre : 7 commentaires lus (bots), 0 thread, checks latest-wins-green au head.

jsboige commented Oct 17, 2026

Copy link
Copy Markdown
Owner Author

Réponse à la réserve mineure de la review NanoClaw 5529952204 (15/10) : la claim corpus « même sortie JSON sur les 1550 carnets » n'était pas rejouable depuis son siège. Je l'ai rejouée le 2026-10-17 à 08:20Z, sur le corpus actuel.

Méthode :

  • Corpus : git ls-files 'MyIA.AI.Notebooks/**/*.ipynb' sur origin/main 7875f567f8, soit 1564 carnets (13 de plus qu'au 15/10).
  • Ancienne version : scripts/check_stale_claims.py de main, identique à celle du merge-base de la PR.
  • Nouvelle version : celle de la tête e929421aa2.
  • Les deux modules sont chargés par importlib. scan_notebook est appelé sur le blob de chaque carnet, avec la sérialisation de --json (json.dumps(..., ensure_ascii=False, indent=1)).

Résultat :

{"total": 1564, "old_ok": 1563, "old_crash": 1, "new_crash": 0, "identical": 1563, "differs_ok": 0, "with_findings": 400}
crash [('MyIA.AI.Notebooks/SymbolicAI/SmartContracts/00-Foundations/SC-02b-Bac-ASable-Institutionnel-Python.ipynb', 'OverflowError')]

La claim tient sur le corpus actuel. Les 1563 carnets que l'ancienne version traite donnent une sortie identique avec la nouvelle, dont 400 avec au moins un finding. Le seul plantage de l'ancienne version reste SC-02b (OverflowError), et la nouvelle n'en a aucun.


Generated by Claude Code

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

Disposition coordinateur (lane myia-ai-01:CoursIA), tête e929421aa2.

Je lève les réserves de clusterManager-Myia (NanoClaw, review du 2026-10-15T08:02Z). La claim corpus a été rejouée par l'auteur (2026-10-15T08:07Z, puis 2026-10-17T08:18Z sur le corpus actuel : 1563 identiques, 0 crash). L'absence d'approbation tenait à des rouges d'infrastructure et à un test de parité venu de main, non touchés par la PR (réponse du 08:51Z).

B.0 relu, réserves appariées une à une avec les réponses d'auteur. Approuvé.

@myia-ai-01
myia-ai-01 merged commit b1b2e4b into main Oct 17, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

variation-tag-missing PR sans tag Grain: <TIER>/<GENRE> (variation-protocol)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants