diff --git a/.gitignore b/.gitignore index 1c6dfd9882..a5968a78a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1095,5 +1095,12 @@ MyIA.AI.Notebooks/GenAI/Texte/tools/llamasharp-bakeoff/cuda-detect-probe/ # SL-14 aifeynman run artifacts (results/, train/test splits, NN models -- regenerable, local only) MyIA.AI.Notebooks/SymbolicAI/SymbolicLearning/_sl14_runs/ +# PR #17422 — probes directory artifacts (papermill _out, _deps DLL mirror). +# `_out` n'est pas couvert par `*_output.ipynb` au-dessus (suffixe distinct). +# `_deps/` héberge la copie locale de QuikGraph.dll pour la mesure c.790 du +# workaround relatif (#r "./_deps/QuikGraph.dll") — ne pas la committer. +scripts/notebook_tools/probes/*_out.ipynb +scripts/notebook_tools/probes/_deps/ + # Livecoding video pipeline (#15604) : artefacts capture/PoC -- media regenerable, local only out/ diff --git a/docs/reference/dotnet-restore-rfc-17361.md b/docs/reference/dotnet-restore-rfc-17361.md new file mode 100644 index 0000000000..85f3380dd4 --- /dev/null +++ b/docs/reference/dotnet-restore-rfc-17361.md @@ -0,0 +1,77 @@ +# Reproduction et workaround : bug `#r nuget:` dans dotnet-interactive 1.0.617701 (po-2027) + +**Issue** : #17361 — `env: restore #r nuget/#r file cassé dans dotnet-interactive sur po-2027 (les 2 builds) — re-exec .NET bloquée` + +**Statut** : bug **réel mais non déterministe** — reproduit 3× le 2026-09-22 (c.760), non reproduit sur 2 re-tentatives le 2026-09-23 (c.803 cache chaud, c.807 cache froid — section *Mesures de reproduction*). Workaround identifié, fix de root cause **out-of-scope** (bug interne `Microsoft.DotNet.Interactive.PackageManagement`). + +## Reproduction (3 probes livrés, 5 tentées) + +**Tentées** (mesure first-hand c.760, po-2027) : + +| # | Probe | Résultat | +|---|---|---| +| A | cellule unique avec `#r "nuget: IKVM, 8.15.0"` seul | ✅ restore OK | +| B | cellule 2 (post-A) avec `#r "nuget: QuikGraph, 2.5.0"` | ❌ `PackageRestoreResult..ctor ArgumentException: Must provide errors when succeeded is false` | +| C | cellule unique avec 2 `#r` consécutifs (`IKVM` puis `QuikGraph`) | ❌ même erreur | +| D | cellule unique avec `#r "file.dll"` (path local résolu via `Environment.SpecialFolder.UserProfile`) | ✅ restore OK | +| E | cellule post-D avec `#r "nuget: CsvHelper, 33.0.1"` | ❌ `ArgumentException` — `file.dll` ne réinitialise **pas** le `PackageRestoreContext` | + +**Livrées dans `scripts/notebook_tools/probes/dotnet-restore-bug-17361.ipynb`** : A, D, E (3 cellules code, exécution Papermill c.760). + +### Mesures de reproduction (historique) + +| Cycle | Date | Conditions | Résultat | +|---|---|---|---| +| c.760 | 2026-09-22 (soir) | 3 exécutions (têtes `261d8aa709`, `03260ceae2`, run Papermill 01:42Z `exception: true`) | ❌ `ArgumentException` ×3 | +| c.803 | 2026-09-23 15:10Z | cache NuGet chaud (`csvhelper/33.0.1` présent) | ✅ restore OK | +| c.807 | 2026-09-23 17:44Z | cache NuGet **froid** (`33.0.1` purgé avant le run, re-téléchargé pendant) | ✅ restore OK | + +L'hypothèse « cache chaud explique la non-reproduction » est **réfutée** par c.807 : même à cache froid, le 2ᵉ restore NuGet de la session (probe E, séquence identique A → D → E) a réussi. La précondition exacte du bug reste **inconnue**. + +**Conclusion mesurée (c.760, à lire avec l'historique ci-dessus)** : le `PackageRestoreContext` interne était dans un état où `succeeded=false` était passé sans `errors` au **second restore NuGet dans une session kernel** — 3 fois sur 3 le 2026-09-22, puis plus jamais sur les re-tentatives du 2026-09-23. Pas une question de version ni de multi-cellules — la séquence « **2ᵉ restore NuGet au total** » est le déclencheur observé quand il se produit. B et C confirment (c.760) ; A isole l'état initial ; D montre le by-pass `file.dll` ; **E réfute l'hypothèse initiale** selon laquelle `file.dll` réinitialiserait le contexte (et disqualifie la workaround « mix file.dll + nuget intercalés »). + +## Workaround applicable + +**Précharger** les packages NuGet en assemblies locales (résolution + copie par helper Python), puis référencer par `#r "file.dll"` dans les notebooks : + +```python +# scripts/ci/dotnet_preload_packages.py — TODO: à implémenter +# - Lit une liste de packages (ex: ikvm, quikgraph, csvhelper) +# - Pour chaque : nuget restore -> copie .dll dans .dotnet_packages/// +# - Émet un manifest .NET-packages.json avec paths résolus +``` + +```csharp +// Dans un notebook .NET, au lieu de : +// #r "nuget: QuikGraph, 2.5.0" ← a planté au 2ᵉ restore dans la session (c.760 ; non déterministe) + +// Référencer une assembly locale — le `#r` est résolu au parse-time et exige un +// LITTÉRAL (ni variable ni interpolation) : la forme mesurée c.790/c.803 est le +// chemin RELATIF depuis le dossier du notebook : +// #r "./_deps/QuikGraph.dll" +// (DLL copiée au préalable depuis le cache NuGet vers _deps/, gitignore) +``` + +La mesure discriminante c.760 (probe E) **réfute** l'hypothèse initiale « `file.dll` réinitialise le `PackageRestoreContext` » : un `#r "nuget:"` après un `#r "file.dll"` a levé `ArgumentException` à chacune des 3 exécutions de c.760. **Mais** la re-production a échoué sur les 2 re-tentatives du 2026-09-23 (c.803 cache chaud, c.807 cache froid) : le bug est **non déterministe**. La recommandation reste néanmoins **univoque et défensive** : **préchargement complet seul** (tous les packages NuGet en `.dll` locaux, via `./_deps/` relatif en attendant le helper `dotnet_preload_packages.py`), pas de mix `file.dll` + `nuget` dans la même session kernel — quand l'exception se produit, elle tue la cellule sans contournement runtime. + +## Cause racine (out-of-scope) + +Bug interne dans `Microsoft.DotNet.Interactive.PackageManagement.PackageRestoreResult..ctor` : lève `ArgumentException` si `succeeded=false` est passé avec `errors=null` ou vide. C'est un état que le code ne devrait jamais produire — probablement une race condition dans `RestoreAsync()`. Hypothèse **renforcée** par c.807 : la même séquence de commandes échouait le 2026-09-22 et réussit le 2026-09-23 (y compris à cache froid) — le facteur variable est le **chemin/timing interne du restore**, pas la séquence des `#r` ; une race sur un restore concurrent (latence réseau, résolution, écriture cache) reste l'explication la plus cohérente. + +**À escalader upstream** : https://github.com/dotnet/interactive/issues (chercher `PackageRestoreResult..ctor ArgumentException`). + +## Fix de root cause côté po-2027 + +**NON applicable localement** : +- Le pin `1.0.617701` (état cluster) ne corrige que le mono-restore, pas le multi +- Le `1.0.712001` (état-trouvé #17361) a un bug encore plus large (mono KO aussi) +- Pas d'option CLI `dotnet interactive jupyter` qui contourne + +**Option à explorer** : mise à jour vers `Microsoft.DotNet.Interactive` >= 1.0.720000 si le bug y est fixé (à vérifier upstream). + +## Livrables possibles (par ordre de coût) + +1. **MAINTENU** : ce RFC documente le bug et la workaround pour les pairs. +2. **COURT TERME** : `scripts/ci/dotnet_preload_packages.py` (helper ~50 lignes) + convention `.net-csharp` notebooks. +3. **MOYEN TERME** : audit complet de tous les notebooks `.net-csharp` pour convertir les `#r "nuget:"` en `#r "file.dll"`. +4. **LONG TERME** : fix upstream + bump version cluster. diff --git a/scripts/notebook_tools/extract_probes_source.py b/scripts/notebook_tools/extract_probes_source.py new file mode 100644 index 0000000000..21d598add8 --- /dev/null +++ b/scripts/notebook_tools/extract_probes_source.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +"""Extract a clean source-only notebook from an executed probe notebook. + +Usage: + python scripts/notebook_tools/extract_probes_source.py + +Strips: +- All code-cell `outputs: []` and sets `execution_count = None`. +- Papermill metadata keys (`papermill`, `tags`, `jupyter`, `dotnet_interactive`) + on every cell, and from notebook-level `metadata`. +- Papermill error-banner markdown cells (cells containing + 'An Exception was encountered' or 'papermill-error-cell'). + +Kept (NOT stripped — these are the notebook's own identity, not papermill artifacts): +- `metadata.kernelspec` — kernel identity (name/display_name/language). Without it, + Jupyter cannot reopen the notebook on the right kernel, and papermill cannot replay + it without `-k .net-csharp`. Adjoint finding c.44 / 2026-09-23. +- `metadata.language_info` — language hints for syntax highlighting / introspection. + +Used in PR #17422 (RFC dotnet-restore-bug-17361) to keep a stable +pre-execution reference for future probes, without committing a notebook +that would be auto-flagged by pre-commit H.3. + +Tells respectes: +- c.1494 strict (encoding=utf-8) +- c.builder-newline (trailing newline) +- c.gh-posting-hygiene (no -f body=@, --body-file only) +- c.1148 strict (>100 chars body, non-PAYLOAD-TRAP) +""" +import argparse +import hashlib +import json +import os +import sys + + +def sha256_short(path: str, n: int = 16) -> str: + return hashlib.sha256(open(path, "rb").read()).hexdigest()[:n] + + +def main() -> int: + if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8") + + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("input", help="executed probe notebook (.ipynb)") + ap.add_argument("output", help="source-only notebook (.source.ipynb)") + args = ap.parse_args() + + if not os.path.isfile(args.input): + print(f"[err] {args.input} not found", file=sys.stderr) + return 2 + + nb = json.load(open(args.input, encoding="utf-8")) + + new_cells = [] + for c in nb.get("cells", []): + src = "".join(c.get("source", [])) + if c["cell_type"] == "code": + c["execution_count"] = None + c["outputs"] = [] + meta = c.get("metadata", {}) or {} + for k in ("papermill", "tags", "jupyter", "dotnet_interactive"): + meta.pop(k, None) + if c["cell_type"] == "markdown": + if "An Exception was encountered" in src or "papermill-error-cell" in src: + continue + new_cells.append(c) + + nb["cells"] = new_cells + nb_meta = nb.get("metadata", {}) or {} + # Only strip papermill-level artifacts. Keep `kernelspec` (kernel identity) + # and `language_info` (language hints) — see adjoint finding c.44. + for k in ("papermill",): + nb_meta.pop(k, None) + + os.makedirs(os.path.dirname(os.path.abspath(args.output)), exist_ok=True) + with open(args.output, "w", encoding="utf-8") as f: + json.dump(nb, f, ensure_ascii=False, indent=1) + + # Tell c.builder-newline: trailing newline + with open(args.output, "ab") as f: + f.write(b"\n") + + src_sha = sha256_short(args.output) + in_sha = sha256_short(args.input) + print(f"[ok] {args.input} -> {args.output}") + print(f" input size: {os.path.getsize(args.input):>6} o, sha: {in_sha}") + print(f" output size: {os.path.getsize(args.output):>6} o, sha: {src_sha}") + print(f" distinct sha: {src_sha != in_sha}") + print(f" cells kept: {len(new_cells)}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/notebook_tools/probes/dotnet-restore-bug-17361.ipynb b/scripts/notebook_tools/probes/dotnet-restore-bug-17361.ipynb new file mode 100644 index 0000000000..2ac08bdfe8 --- /dev/null +++ b/scripts/notebook_tools/probes/dotnet-restore-bug-17361.ipynb @@ -0,0 +1,420 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "intro", + "metadata": { + "papermill": { + "duration": 0.003226, + "end_time": "2026-09-23T17:49:57.720069+00:00", + "exception": false, + "start_time": "2026-09-23T17:49:57.716843+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "# Probe : bug `#r nuget:` dans dotnet-interactive sur po-2027 (#17361)\n", + "\n", + "**Lane** : `myia-po-2027:CoursIA-2`, c.760, 2026-09-22\n", + "**Issue** : #17361\n", + "**Statut** : bug **réel mais non déterministe** — reproduit 3x le 2026-09-22 (c.760), non reproduit sur 2 re-tentatives le 2026-09-23 (c.803 cache chaud, c.807 cache froid — voir la cellule probe E et la RFC section Mesures). Workaround **partiel** identifié (`file.dll` marche), fix root cause **out-of-scope**.\n", + "\n", + "Voir [`docs/reference/dotnet-restore-rfc-17361.md`](../../../docs/reference/dotnet-restore-rfc-17361.md) pour le diagnostic complet et l'historique des mesures.\n" + ] + }, + { + "cell_type": "markdown", + "id": "6afb6a20", + "metadata": { + "papermill": { + "duration": 0.001465, + "end_time": "2026-09-23T17:49:57.724085+00:00", + "exception": false, + "start_time": "2026-09-23T17:49:57.722620+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## Portabilite (mesures first-hand c.760 + c.790 + c.807)\n", + "\n", + "Le probe D utilise depuis c.803 le chemin **relatif** `#r \"./_deps/QuikGraph.dll\"` (workaround c.790 appliqué) : plus aucun chemin absolu po-2027-spécifique dans le notebook. Le répertoire `_deps/` est gitignore ; sur une nouvelle machine, copiez-y la DLL depuis le cache NuGet — la cellule prelude ci-dessous imprime le chemin source attendu.\n", + "\n", + "**Limitation parse-time** : la directive `#r \"...\"` est resolue au parse-time de la cellule (avant execution runtime), donc elle exige une chaine litterale. Ni variable ni interpolation — c'est pourquoi le workaround passe par un chemin relatif, resolu depuis le dossier du notebook (kernel lance avec `probes/` en cwd).\n", + "\n", + "### Workaround relatif (mesure first-hand c.790, appliqué c.803)\n", + "\n", + "Copier la DLL vers `./_deps/QuikGraph.dll` puis utiliser `#r \"./_deps/QuikGraph.dll\"` (path relatif). Mesure c.790 : `OK_relative: QuikGraph.AdjacencyGraph` charge sans erreur, identique au resultat du path absolu. Caveat : la copie locale doit etre rafraichie si la version de QuikGraph change.\n", + "\n", + "### Sur une autre machine\n", + "\n", + "1. Ayez QuikGraph 2.5.0 dans le cache NuGet (ex. `dotnet add package QuikGraph --version 2.5.0` dans un csproj satellite) — la cellule prelude affiche le chemin.\n", + "2. Copiez la DLL vers `scripts/notebook_tools/probes/_deps/QuikGraph.dll`.\n", + "3. Le probe E (`#r \"nuget: CsvHelper\"`) peut echouer OU reussir : le bug est **non déterministe** (voir probe E) — c'est precisement l'objet de la mesure.\n", + "\n", + "Voir `docs/reference/dotnet-restore-rfc-17361.md` section Workaround pour le prechargement complet (proposition `dotnet_preload_packages.py`, non implémenté).\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "495b29c1", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-23T17:49:57.733542Z", + "iopub.status.busy": "2026-09-23T17:49:57.729513Z", + "iopub.status.idle": "2026-09-23T17:49:58.787592Z", + "shell.execute_reply": "2026-09-23T17:49:58.785955Z" + }, + "papermill": { + "duration": 1.062554, + "end_time": "2026-09-23T17:49:58.787760+00:00", + "exception": false, + "start_time": "2026-09-23T17:49:57.725206+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "\r\n", + "
\r\n", + " \r\n", + " \r\n", + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Existe ? True\r\n" + ] + } + ], + "source": [ + "// PRELUDE - resolution du path QuikGraph.dll via le profil utilisateur\n", + "// Affiche la valeur a substituer dans la cellule probe D sur une autre machine.\n", + "var profile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);\n", + "var expected = System.IO.Path.Combine(profile, \".nuget/packages/quikgraph/2.5.0/lib/netstandard2.0/QuikGraph.dll\");\n", + "Console.WriteLine($\"QuikGraph.dll attendu a : {expected}\");\n", + "Console.WriteLine($\"Existe ? {System.IO.File.Exists(expected)}\");\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "probe_a", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-23T17:49:58.792706Z", + "iopub.status.busy": "2026-09-23T17:49:58.792423Z", + "iopub.status.idle": "2026-09-23T17:49:58.991528Z", + "shell.execute_reply": "2026-09-23T17:49:58.991128Z" + }, + "papermill": { + "duration": 0.201972, + "end_time": "2026-09-23T17:49:58.991728+00:00", + "exception": false, + "start_time": "2026-09-23T17:49:58.789756+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
Installed Packages
  • IKVM, 8.15.0
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OK_A: IKVM 8.15.0 restore (1er restore nuget).\r\n" + ] + } + ], + "source": [ + "// PROBE A — 1er #r nuget dans la session — ATTENDU: OK\n", + "#r \"nuget: IKVM, 8.15.0\"\n", + "Console.WriteLine(\"OK_A: IKVM 8.15.0 restore (1er restore nuget).\");" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "probe_d", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-23T17:49:58.999817Z", + "iopub.status.busy": "2026-09-23T17:49:58.999384Z", + "iopub.status.idle": "2026-09-23T17:49:59.047473Z", + "shell.execute_reply": "2026-09-23T17:49:59.046897Z" + }, + "papermill": { + "duration": 0.052367, + "end_time": "2026-09-23T17:49:59.047659+00:00", + "exception": false, + "start_time": "2026-09-23T17:49:58.995292+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OK_D: file.dll local restore works, type: QuikGraph.AdjacencyGraph`2[[System.Int32, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[QuikGraph.Edge`1[[System.Int32, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], QuikGraph, Version=2.5.0.0, Culture=neutral, PublicKeyToken=46bd58b0789759cb]]\r\n" + ] + } + ], + "source": [ + "// PROBE D - #r file.dll local - ATTENDU: OK\n", + "// By-pass du PackageRestoreContext pour les restores fichier.\n", + "// PORTABILITE : voir cellule prelude pour le path dynamique de votre machine.\n", + "//\n", + "// Workaround relatif c.790 (mesure first-hand OK) : copier la DLL vers\n", + "// `./_deps/QuikGraph.dll`, puis utiliser `#r \"./_deps/QuikGraph.dll\"`\n", + "// (chemin relatif, accepte par `#r` au parse-time).\n", + "//\n", + "// Workaround relatif APPLIQUE (c.803) : la DLL est copiee dans ./_deps/ (gitignore),\n", + "// le chemin relatif est resolu au parse-time depuis le dossier du notebook.\n", + "#r \"./_deps/QuikGraph.dll\"\n", + "Console.WriteLine($\"OK_D: file.dll local restore works, type: {typeof(QuikGraph.AdjacencyGraph>).FullName}\");\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "probe_e", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-23T17:49:59.057604Z", + "iopub.status.busy": "2026-09-23T17:49:59.057092Z", + "iopub.status.idle": "2026-09-23T17:49:59.175422Z", + "shell.execute_reply": "2026-09-23T17:49:59.174937Z" + }, + "papermill": { + "duration": 0.124582, + "end_time": "2026-09-23T17:49:59.175635+00:00", + "exception": false, + "start_time": "2026-09-23T17:49:59.051053+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
Installed Packages
  • CsvHelper, 33.0.1
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "E_MEASURED_OK: nuget restore SUCCEEDED this session (no ArgumentException) - c.803/c.807; see RFC Mesures.\r\n" + ] + } + ], + "source": [ + "// PROBE E — nuget APRÈS file.dll — mesure discriminante (c.760, re-mesurée c.807)\n", + "// c.760 (2026-09-22) : KO — System.ArgumentException: Must provide errors when\n", + "// succeeded is false. (PackageRestoreContext.RestoreAsync). Sorties conservées\n", + "// aux têtes 261d8aa709 et 03260ceae2 de cette branche ; le run Papermill du\n", + "// 23/09 01:42Z portait exception: true pour la même cause.\n", + "// c.803 (23/09 15:10Z) : OK — restore silencieux, CsvHelper 33.0.1 déjà en cache NuGet local.\n", + "// c.807 (23/09 17:44Z) : OK à CACHE FROID — 33.0.1 purgé avant le run, re-téléchargé\n", + "// pendant, restore réussi quand même : l'hypothèse « cache chaud » est réfutée.\n", + "// Verdict : bug NON DÉTERMINISTE — 3 occurrences c.760, 0 sur les re-tentatives\n", + "// c.803/c.807 ; précondition exacte inconnue (race candidate, RFC §Cause racine).\n", + "// Ce WriteLine ne s'affiche QUE si le restore a réussi ; si la cellule meurt sur\n", + "// l'ArgumentException, c'est l'output error qui porte la mesure.\n", + "#r \"nuget: CsvHelper, 33.0.1\"\n", + "Console.WriteLine(\"E_MEASURED_OK: nuget restore SUCCEEDED this session (no ArgumentException) - c.803/c.807; see RFC Mesures.\");\n" + ] + }, + { + "cell_type": "markdown", + "id": "conclusion", + "metadata": { + "papermill": { + "duration": 0.006464, + "end_time": "2026-09-23T17:49:59.188104+00:00", + "exception": false, + "start_time": "2026-09-23T17:49:59.181640+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## Conclusion (mesures c.760, re-mesures c.803/c.807)\n", + "\n", + "| Probe | Résultat | Diagnostic |\n", + "|---|---|---|\n", + "| A (1er `#r nuget`) | OK | `PackageRestoreContext` à l'état initial |\n", + "| D (`#r \"file.dll\"`) | OK | by-pass du PackageRestoreContext |\n", + "| E (`#r \"nuget:\"` après D) | KO c.760 / OK c.803, c.807 | `ArgumentException` **non déterministe** — voir probe E et RFC §Mesures |\n", + "\n", + "**Lecture honnête** : le bug (2ᵉ restore NuGet d'une session qui lève `Must provide errors when succeeded is false`) est **réel** — il s'est produit 3 fois le 2026-09-22 (c.760, y compris le run Papermill archivé dans l'historique de cette branche) — mais il n'a **pas** été reproduit sur les 2 re-tentatives du 2026-09-23, dont une à cache NuGet froid. La précondition exacte est inconnue ; la race soupçonnée dans `RestoreAsync()` (RFC §Cause racine) reste l'explication la plus cohérente.\n", + "\n", + "**Workaround défensif** (recommandé tant que le bug n'est pas corrigé upstream) : un notebook .NET qui doit charger plus d'un package reste plus sûr en référençant des assemblies locales (`#r \"file.dll\"` — voir la piste relative `_deps/` ci-dessus) : quand l'`ArgumentException` se produit, elle tue la cellule sans contournement runtime possible.\n", + "\n", + "**Fix root cause** : bug interne `Microsoft.DotNet.Interactive.PackageManagement.PackageRestoreResult..ctor` — out-of-scope local.\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".NET (C#)", + "language": "C#", + "name": ".net-csharp" + }, + "language_info": { + "file_extension": ".cs", + "mimetype": "text/x-csharp", + "name": "C#", + "pygments_lexer": "csharp", + "version": "13.0" + }, + "papermill": { + "default_parameters": {}, + "duration": 3.808623, + "end_time": "2026-09-23T17:49:59.436613+00:00", + "environment_variables": {}, + "exception": null, + "input_path": "dotnet-restore-bug-17361.ipynb", + "output_path": "dotnet-restore-bug-17361.ipynb", + "parameters": {}, + "start_time": "2026-09-23T17:49:55.627990+00:00", + "version": "2.7.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file