Skip to content

fix(training,#14362): la jambe de sanite DM etait silencieusement recentree - #14392

Merged
myia-ai-01 merged 1 commit into
mainfrom
fix/14362-dm-raw-noncentered
Sep 2, 2026
Merged

myia-ai-01 merged 1 commit into
mainfrom
fix/14362-dm-raw-noncentered

Conversation

@jsboige

@jsboige jsboige commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Grain: MED/training — lane myia-po-2024:CoursIA-2 — prev: DEEP/qc #14369

La jambe dm_raw de run_btc_debiased_recentered était censée porter la sanité : un DM sur pertes brutes, contre HAR brute, reproduisant le keeper #11011. Elle passait par _dm_centered_mse, comme la jambe de verdict. Ce n'est pas un détail de nommage : les deux HAR ne diffèrent que d'une constante, et le recentrage par la moyenne propre annule exactement une constante — les deux jambes rendaient le même nombre.

La falsification, sans rien construire

L'artefact commité contredit lui-même ce que la jambe prétendait mesurer. Ses p-médianes « raw », comparées à ce qu'elle devait reproduire :

h dm_raw p_median (artefact) keeper #11011 publié dm_centered_p_median publié (#12734)
1 1,545e−09 0,00e+00 BEATS 1,5e−09
5 9,660e−05 2,25e−10 BEATS 9,7e−05
10 1,021e−01 INCONCLUSIVE 2,39e−09 BEATS 1,0e−01

La jambe « brute » reproduisait la jambe recentrée à la précision d'impression près — pas le keeper. À h=10 elle rendait INCONCLUSIVE là où le keeper qu'elle devait retrouver publie BEATS à p = 2,39e−09 : sept ordres de grandeur et un verdict retourné. Un contrôle qui suit la mauvaise cible n'en est pas un.

Sur les 12 combos, |dm_raw_stat − dm_centered_stat| vaut 0 exactement sur 8 lignes, et 1,78e−15 / 8,88e−16 / 2,22e−16 sur les 4 autres.

La cause, tracée

har_bias_oos    = float(np.mean(har_errors))
har_fc_debiased = har_fc - har_bias_oos        # soustraction d'un SCALAIRE

d'où har_errors_debiased = har_errors − c, et _dm_centered_mse — dont la docstring énonce « Centering annihilates the bias component » — calcule (e − c) − mean(e − c) = e − mean(e). Deux arguments différents, même fonction, même sortie. La lecture des seuls sites d'appel montre un contraste ; c'est la composition avec le helper qui l'annule.

Ce qui est livré

  • _dm_uncentered_mse — DM loss_fn="mse" sur erreurs intactes, mêmes sentinelles (SHAPE_MISMATCH, INSUFFICIENT_DATA) et même forme de retour que sa jumelle. La jambe de sanité l'appelle sur dl_errors vs har_errors bruts.
  • Le renommage dm_raw_* → dm_uncentered_vs_har_raw_*, qui nomme les deux choses (non centré, contre HAR brute). Le nom précédent était réellement ambigu — « raw » se lisait « perte brute » alors qu'il signifiait « contre HAR brut ». Vérifié au grep : zéro consommateur hors du script. dm_centered_* reste inchangé : celui-là est consommé (m4_dlinear_vol_sc_validation.ipynb, cellules 24/26/30), le renommer casserait des sorties commitées sans qu'aucune cellule ne lève.
  • Le contrôle scellé qui manquait, et sa falsification : test_old_composition_is_degenerate rejoue l'ancienne composition et prouve qu'elle s'effondre — sans quoi le test principal pourrait passer pour une raison étrangère au centrage. Plus les deux sens de la propriété : la jambe centrée est invariante par translation constante, la non centrée ne l'est pas.
  • Vérifié par mutation : en réintroduisant la régression (la jambe de sanité repasse par le helper centré, rien d'autre ne change), 3 gardes sur 3 rougissent, dont le contrôle scellé. Un garde qui ne tire pas sur la faute qu'il vise n'est pas un garde.
  • L'artefact régénéré à configuration identique au keeper publié (--horizons 1 5 10 --seeds 0 7 42 99 --epochs 50), le notebook re-exécuté (C.2), et la note de correction au REGISTRY.md — l'entrée training(#1454): re-valider les keepers BTC hors biais — M4 survit numeriquement, M15 invérifiable post-hoc #12734 décrivait cette jambe comme « RAW = sanité reproduisant le keeper » ; elle ne le faisait pas, et le registre doit le dire.

La vérification post-régénération — les deux contrôles

Artefact régénéré à configuration identique (1776,4 s, 12 combos). Deux contrôles, tous deux falsifiables.

(A) Rien d'autre n'a bougé. dm_centered_stat revient bit-identique sur 12 lignes sur 12, Δ max = 0,000e+00 — M4 est déterministe et seedé, donc c'est la forme la plus forte que ce contrôle puisse prendre. La jambe de verdict n'a pas frémi.

(B) La jambe corrigée retrouve le keeper qu'elle existe pour reproduire :

h jambe corrigée p_median keeper #11011 ancienne jambe (buggée) verdicts
1 0,000e+00 0,00e+00 1,545e−09 BEATS 4/4
5 1,435e−10 2,25e−10 9,660e−05 BEATS 4/4
10 3,083e−09 2,39e−09 1,021e−01 BEATS 4/4 (était INCONCLUSIVE)

Et à dire honnêtement : sur h=5 et h=10 l'accord est de rang de grandeur et de verdict, pas bit-à-bit (facteur < 2). Le keeper vient d'un entraînement DLinear distinct — les erreurs du modèle ne sont pas les mêmes séries. C'est l'accord attendu d'une reproduction indépendante, pas d'un rejeu, et je ne le présente pas pour plus que ça. Ce qui compte est le renversement à h=10 : INCONCLUSIVE à p = 0,102 → BEATS à p = 3,08e−09, là où le keeper publie BEATS à 2,39e−09.

(C) Les deux jambes coïncident désormais sur 0/12 lignes (contre 12/12).

Le notebook

m4_dlinear_vol_sc_validation.ipynb re-exécuté end-to-end via papermill (C.2) : sa cellule d'index 24 imprime elapsed_s, que la régénération fait passer de 1787 s à 1776 s. 14/14 cellules code avec execution_count, 0 sortie d'erreur, papermill.exception: null. Aucun chiffre de résultat ne bouge — corollaire attendu du contrôle (A), puisque le notebook ne lit que dm_centered_*.

Effet de bord bienvenu : le metadata.papermill.input_path/output_path commité pointait C:\dev\CoursIA-c1331p441-12734\... (fuite de chemin machine préexistante sur main, que scrub_papermill_paths.py --scan signalait). La ré-exécution en chemins relatifs la referme — --scan et --scan --outputs rendent 0.

Comment le défaut a survécu — et ce que je diffère

Le bloc aggregated de l'artefact ne contient que dm_centered_p_median : la jambe de sanité n'était agrégée nulle part. Elle existait en champ par ligne, que rien ne résumait et que rien ne confrontait au keeper qu'elle prétendait reproduire. Une jambe qu'on ne lit jamais ne peut pas rougir, même quand elle est correcte — le centrage silencieux n'a fait que rendre l'aveuglement total.

L'agréger (dm_uncentered_vs_har_raw_p_median, à côté de sa jumelle) rendrait la comparaison au keeper lisible là où on la cherche. Je le diffère délibérément : la régénération de 30 min était déjà lancée quand je l'ai constaté, et l'ajouter imposerait un second run complet pour un champ additif. Suivi : #14390, ouverte avant cette PR. Les valeurs par ligne étant présentes, le contrôle est falsifiable dès cette PR — la médiane se calcule, elle n'est simplement pas pré-écrite.

Ce que cela n'invalide pas

Rien du verdict. aggregate_verdicts_recentered ne lit que dm_centered_pvalue et dm_centered_verdict, corrects : M4 reste confirmed h=1/h=5, INCONCLUSIVE h=10 ; M15 reste refuted-de-biased 3/3. Le défaut portait sur un champ de diagnostic. Ce qui était perdu, c'est la capacité de le contrôler — et c'est ce que cette PR rend.

Closes #14362

…g the verdict leg

`run_btc_debiased_recentered` computes two DM legs: a verdict leg on centered
errors vs de-biased HAR, and a sanity leg meant to reproduce the #11011 keeper
on raw losses vs raw HAR. Both went through `_dm_centered_mse`.

The two HAR series differ by exactly a constant (`har_errors_debiased =
har_errors - har_bias_oos`), and centering by each series' own mean annihilates
precisely that constant: `(e - c) - mean(e - c) = e - mean(e)`. The legs
returned the same number -- bit-identical on 12 of 12 rows of the published
artefact. A control that cannot go red is not a control.

The artefact contradicted its own stated purpose: the sanity leg's p_medians
(1.545e-09 / 9.660e-05 / 1.021e-01) reproduced `dm_centered_p_median`, not the
keeper (0.00e+00 / 2.25e-10 / 2.39e-09) -- and at h=10 it reported
INCONCLUSIVE where the keeper it was meant to retrieve publishes BEATS.

Delivered:
- `_dm_uncentered_mse`: DM with loss_fn="mse" on untouched errors, same return
  shape and sentinels as its twin. The sanity leg now calls it on raw series.
- Rename `dm_raw_*` -> `dm_uncentered_vs_har_raw_*` (names both properties;
  zero consumers repo-wide, verified by grep). `dm_centered_*` left untouched
  -- that one IS consumed by committed notebook cells.
- 7 tests, including the sealed control and its falsification
  (`test_old_composition_is_degenerate` proves the old path collapses, so the
  main assertion is not incidental). Verified by mutation: reintroducing the
  regression fires 3 of 3 guards. Full suite: 1089 passed, 1 skipped.
- Artefact regenerated at identical config. `dm_centered_stat` returns
  bit-identical on 12/12 rows (max delta 0.000e+00); the corrected leg now
  matches the keeper in magnitude and verdict, recovering BEATS at h=10
  (p = 3.08e-09 vs keeper 2.39e-09). Legs now coincide on 0/12 rows.
- Notebook re-executed end-to-end (C.2: cell 24 prints `elapsed_s`), which also
  closes a pre-existing machine-path leak in its papermill metadata.
- REGISTRY.md correction note; the entry described this leg as reproducing the
  keeper, and it did not.

The verdict is unaffected: `aggregate_verdicts_recentered` reads only
`dm_centered_*`. M4 stays confirmed h=1/h=5 + INCONCLUSIVE h=10; M15 stays
refuted-de-biased 3/3. The defect cost the ability to check that, not the
result itself.

Closes #14362
See #14390

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

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

⚠️ Detector abstained (merge-base introuvable, shallow fetch or unanchored branch).

c.415 (#11873): scope = notebooks CHANGED in this PR, not the whole corpus.
See python scripts/check_markdown_claims_output.py --help for re-running locally.
Detector rationale: c.290 / c.331 / PR #11435 pathologie.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Notebook PR Validation: PASS

  • Notebooks checked: 1
  • Code cells validated: 14
  • Result: All passed

Checks: H.1 (no errors), H.3 (execution_count), C.1 (no banned patterns)
Non-Python kernels (.NET/Lean): C.1 + errors only (execution_count advisory)
QuantConnect notebooks: C.1 + errors only (require QC Cloud for execution)

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

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

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.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Golden-Set Execution (H.7 P3)

✅ 8/8 notebooks passed (certified reproducible)

Notebook Status Time
2.1-Workflow-ML.ipynb ✅ SUCCESS 11.9s
2.2-Descente-de-gradient.ipynb ✅ SUCCESS 3.7s
2.3-Regression-lineaire-logistique.ipynb ✅ SUCCESS 4.0s
2.4-Arbres-Forets-Ensembles.ipynb ✅ SUCCESS 4.1s
Search-1-StateSpace.ipynb ✅ SUCCESS 3.2s
SL-1-LogicalLearning.ipynb ✅ SUCCESS 2.9s
rl_4_multi_armed_bandits.ipynb ✅ SUCCESS 16.4s
GameTheory-04c-NashExistence-Python.ipynb ✅ SUCCESS 2.6s

Pinned lockfile: scripts/notebook_tools/golden_set.lock.txt (H.7 P3, axe A #4208)

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

[NanoClaw] structural review (budget diff — file list + 2 fichiers porteurs lus au head, vérifs mécaniques ciblées) — VERIFIED au head 5d8aa04.

Le fix, vérifié ligne à ligne. _dm_uncentered_mse passe bien les erreurs intactes à dm_verdict_fn (aucun centrage), avec les mêmes sentinelles (SHAPE_MISMATCH, INSUFFICIENT_DATA < 10) et la même forme de retour que sa jumelle — la docstring énonce la cause racine (#14362) noir sur blanc. Le site d'appel est correct : jambe sanité = _dm_uncentered_mse(dl_errors, har_errors) raw-vs-raw, avec le commentaire expliquant pourquoi elle ne doit PAS passer par le helper centré (constante annihilée par le recentrage). Renommage complet vérifié dans l'artefact régénéré : clés dm_uncentered_vs_har_raw_* présentes, 0 clé dm_raw_* résiduelle ; dm_centered_* inchangé (l'agrégation lignes 251-297 et le notebook ne lisent que lui — rien ne casse).

Les gardes tirent sur la faute qu'elles visent. Le contrôle scellé (test_legs_are_not_the_same_statistic, seuil >1e-6) est adossé à sa falsification : test_old_composition_is_degenerate rejoue l'ancienne composition et montre la collision bit-identique (abs=1e-12) — sans lui, le scellé pourrait passer pour une raison étrangère au centrage. La propriété est testée dans les deux sens (centrée invariante par translation / non-centrée ne l'est pas), le test substantif test_uncentered_leg_sees_a_pure_bias_gap construit un gap de pur biais (har = dl + 1.5, dispersion identique) : non-centrée le voit (BEATS, p<1e-6), centrée aveugle (dm_stat≈0, INCONCLUSIVE) — exactement l'histoire #11011. Guard-the-guard présent (test_biases_actually_differ_in_the_fixture, >0.1). Sentinelles paritaires testées.

L'artefact régénéré, re-vérifié à la main : 12 rows, coïncidence des deux jambes 0/12 (le bug la rendait 12/12), flip h=10 confirmé au seed 0 (non-centrée p=3,2e-9 BEATS / centrée p=0,103 INCONCLUSIVE), verdicts BEATS 4/4 aux 3 horizons. Médianes de la table REGISTRY recalculées depuis les 12 rows committés = exactes (h1 0.000e+00, h5 1.435e-10, h10 3.083e-09) — la provenance est prouvée, pas déclarée. Cross-check interne bonus : les valeurs de l'« ancienne jambe buggée » du body (1.545e-09 / 9,66e-05 / 1,021e-01) coïncident avec les dm_centered_p_median de l'artefact neuf — cohérent par construction, le bug identifiait les deux jambes.

Notebook + registre. 14/14 cellules code execution_count séquentiels, 0 sortie d'erreur, papermill.exception: null, fuite de chemin refermée (input/output_path relatifs, plus de C:\dev\...). REGISTRY.md porte la note de correction datée #14362, honnête sur la nature de l'accord (rang de grandeur et verdict, pas bit-à-bit — reproduction indépendante, pas rejeu). CI au head : gardes notebook-health 10/10 success. 0 secret (grep ghp_/hf_/AKIA/sk- sur les fichiers porteurs).

2 notes mineures (non bloquantes) :

  1. La section aggregated de l'artefact ne porte que dm_centered_p_median — un champ dm_uncentered_vs_har_raw_p_median y donnerait à la jambe de sanité la même provenance committée qu'à la jambe de verdict (aujourd'hui la table REGISTRY se re-dérive à la main des rows — je l'ai fait, ça tombe juste, mais un champ agrégé l'éviterait).
  2. test_uncentered_leg_sees_a_pure_bias_gap asserte le libellé exact "BEATS baseline" — couplage au vocabulaire de dm_test ; un renommage de verdict dans la lib casserait le test pour une raison étrangère au centrage (le reste de la suite utilise les mêmes libellés, donc cohérent — juste à savoir).

Green-lightable : chaque claim du body vérifié mécaniquement, le garde scellé est falsifié dans le bon sens, et la correction du registre assume ce que l'artefact prouve.

@myia-ai-01
myia-ai-01 merged commit 2b4578a into main Sep 2, 2026
63 checks passed
jsboige added a commit that referenced this pull request Sep 3, 2026
…ok 06 (sync-over-async, deux variantes) (#14396)

* feat(aspire,#10473): section D1 -- AGENTGUARD005 livre dans le notebook 06 (sync-over-async, deux variantes)

EPIC #10473 (The Unexpected AI Stack, axe Roslyn) : AGENTGUARD005 et
AGENTGUARD005b sont livres dans AgentGuard.Analyzers/ (PRs #13819 + #13885)
et 8 terrains SyncOverAsync* sont committes dans AgentGuard.Verifier/samples/,
mais le notebook 06 n'en parlait pas. L'Exercice 1 etait un squelette
("etendre TaskResultBlockAnalyzer a GetAwaiter().GetResult()") qui designait
precisement l'analyseur deja livre. Cette PR comble le trou pedagogique.

- Section D1 (4 cellules) : contexte des deux analyseurs, lecture cote a cote
  (borne semantique partagee + pivot distinctif de 005b), verdicts reels sur
  6 terrains (3 rouges fautifs, 3 propres relevant des 3 clauses d'exemption),
  lecture des verdicts.
- Exercice 1 reformule : prediction AVANT execution sur le terrain
  SyncOverAsyncConfigureAwaitValueTask.cs (3 cas d'exemption), puis
  confrontation, puis citation des clauses gagnees.

Mesure CLI : les verdicts sont reproduits par 'dotnet run --project
AgentGuard.Verifier --' (sortie verbatim dans le body PR).

Grain: MED/notebook-dotnet -- lane myia-po-2024:CoursIA-2 -- prev: MED/training #14392
G-VAR-1 tenu (genre CONTENU, tier MED).

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>

* chore(ci,#14396): wake PR gate (body INCIDENTAL count) -- reaffirme 1 fichier pousse, 8 terrains SyncOverAsync upstream verifies

* fix(aspire,#14396): renuméroter exec_count 1..N + convertir cellule D1 en md

Réparation de PR #14396 (c.873 c.877) : le gate
'Exec-sequence ratchet (base vs PR)' échouait avec CLEAN->DUPLICATE
sur 06-Aspire-GardeFous-Roslyn.ipynb après l'insertion de la section D1
sans ré-exécution du notebook. La séquence execution_count était
[1,2,3,2,5,..,9,1,2,10,..,18] au lieu de 1..N.

Constat firsthand :
- 4 nouvelles cellules D1 insérées entre cell 22 (ec=9) et cell 30 (ec=10)
  avec execution_count 1,2,10,11 au lieu de 10,11,12,13,14,15,16,17,18
- cellule 25 (l'illustration côte-à-côte des analyseurs) avait une
  chaîne C# Console.WriteLine avec un backtick ` dans une string
  literal non échappée — erreur CS1056, non-détectée par c.873
- le notebook n'avait pas été ré-exécuté de bout en bout après
  l'enrichissement (C.2 violation latente)

Réparations :
1. conversion cellule 25 (Console.WriteLine de strings statiques) en
   cellule markdown : le contenu était de la prose illustrative, sans
   computation réelle — le bloc de code échouait à cause du backtick
   non échappé et n'apportait aucune valeur ajoutée
2. ré-exécution complète .NET Interactive sur kernel .net-csharp
   (17 cellules code, 0 erreur), séquence execution_count désormais
   CLEAN = [1..17]

Vérifications post-fix :
- check_exec_ratchet.py origin/main : regressions: 0, CLEAN->CLEAN
- check_papermill_ratchet.py origin/main : BLOCK_REMOVED (autorisé par #11155)
- check_source_output_ratchet.py origin/main : 0 stale cells
- check_notebook_navlinks.py : 0 broken links
- 17/17 cellules code avec execution_count, 17/17 avec outputs,
  0 output error

Ref: #14396 (PR repair, même branche feature/c873-agentguard005-section)

* fix(asipre,#14396): strip probeAddresses banner from 06-Aspire-GardeFous-Roslyn cell 6 output

c.880 REPAIR: c.878 .NET re-exec re-injected the .NET Interactive
probeAddresses banner via NetworkInterface enumeration, contaminating
display_data cell output. Stop & Repair surgical strip via
scripts/notebook_tools/strip_probe_banner.py --apply -- rewrites
text/html to '' while preserving output_type=display_data structure
(no source touched, no execution_count changed). CI banner guard now
clean.

Verified: python strip_probe_banner.py --scan = 0 banner lines.

Grain: MED/notebook-dotnet (repair) -- lane myia-po-2024:CoursIA-2 -- prev: MED/tooling #14440
Co-Authored-By: Claude-Code <noreply@anthropic.com>

* fix(asipre,#14396): Exercice 1 cellule 10 -- Verifier exige args.Length >= 2 (Hermes c.883) (#14460)

Constat Hermes 2026-09-03T00:25:18Z : la cellule 10 (Exercice 1)
invoquait le Verifier avec 1 seul argument
(SyncOverAsyncConfigureAwaitValueTask.cs), mais Program.cs:13
retourne exit 2 + banner 'usage:' si args.Length < 2. L'output
commite etait donc le banner, pas les 3 cas PROPRE que le body
annoncait -- la confrontation pedagogique predire/executer/
verifier etait inoperante.

Fix : ajouter un 2e terrain a l'invocation -- la fautive
(SyncOverAsyncConfigureAwaitFautif.cs, 2 diagnostics
AGENTGUARD005b sur ConfigureAwait(false)/(true).GetAwaiter()
.GetResult()) + la valueTask (3 diagnostics PROPRE). La
confrontation redevient possible : l'etudiant confronte sa
prediction '2 rouges / 3 propres' au verdict reel et peut
relier chaque cas PROPRE a la bonne clause d'exemption
(ValueTask != Task, literal non-bool, pas de GetResult).

Substance diff : cellule 10 uniquement (substance MODIFIEE ;
toutes les autres 16 cellules code byte-identiques au HEAD de
la branche). Le 796/106 du diff brut est du reformatage JSON
nbconvert (reorganisation cles, normalisation whitespace) + 9
lignes probeAddresses banner strippees via
scripts/notebook_tools/strip_probe_banner.py --apply.

Verifications :
- check_exec_ratchet.py origin/main : CLEAN->CLEAN (seq 1..17)
- check_papermill_ratchet.py origin/main : BLOCK_REMOVED (autorise #11155)
- check_source_output_ratchet.py origin/main : 0 stale cells
- check_notebook_navlinks.py : OK 0 lien casse
- C.1 grep : 0 violation (le hit '001/002' dans markdown n'est pas 1/0)
- check_pr_perimeter.py 14396 : VERDICT OK
- Cellule 10 re-executee kernel .net-csharp : ec=4, output =
  2 diagnostics AGENTGUARD005b + VERDICT PROPRE (au lieu de
  banner usage)

Genre : MED/notebook-dotnet (REPAIR herite du genre substance,
G-VAR-1 TENU). Lane myia-po-2024:CoursIA-2.

---------

Co-authored-by: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants