diff --git a/STATUS.md b/STATUS.md
index c0616e7..8fc8415 100644
--- a/STATUS.md
+++ b/STATUS.md
@@ -308,6 +308,25 @@ Les lots 1 et 2 sont terminés. **Le lot 3 et le lot médias sont tous les deux
**Chemin critique** : A (assainir) → B (geler le schéma) → G (`MigrationController`) → H (tests) → **J (RGPD)** → I (bascule). C, D, D-bis, E, F se parallélisent.
+**Lot C1 — livré le 2026-08-11.** Calculateur `StoragePath`/`SizeBytes` extrait dans `Helpers/ResourceStorage.cs`, avec 13 tests (`dotnet test` **143/143**). Il ferme le lien **L5** : C2 (backfill) et l'écart (e) du lot G appelleront le même code au lieu d'en écrire deux.
+
+- ⚠️ **L'extraction a prouvé la divergence qu'elle devait empêcher.** `ResourceController` a **deux** chemins de création : le chemin **multipart** écrivait `SizeBytes` mais laissait **`StoragePath` nul**, seul le chemin JSON écrivait les deux. Le §1quater annonçait « `StoragePath`/`SizeBytes` écrits à `Create` depuis le 10/08 » — c'était vrai d'un chemin sur deux. Une partie des lignes à rattraper par C2 vient de là.
+- **Angle mort laissé ouvert sciemment** : `Update` peut faire passer `Type` d'un type URL à un type fichier, laissant `StoragePath` nul sur une ligne qui a désormais un blob. Recalculer le chemin sur une ressource existante pointerait vers un objet qui n'a pas bougé dans le bucket — à trancher avec C3 (quota).
+
+**Lot B — livré le 2026-08-11. Le schéma est gelé, le lot G est débloqué.** Une seule migration EF (`20260811130738_LotB_FreezeSchema`) : rename `SectionMap.MapResourceId` → `IconResourceId` (**l'écart (g) du §1quinquies tombe avec**), suppression de `SectionEvent.ParcoursIds`, et `Instance.IsImageWatermark` remplaçant le `instanceId == "633ee379…"` en dur de `ResourceController`. **130/130 tests avant comme après**, build Debug **et** Release verts.
+
+- ⚠️ **Le rename imposait aussi la propriété de navigation** `MapResource` → `IconResource` : la convention EF l'appariait au FK, la laisser en place aurait fabriqué un FK fantôme. Elle n'était utilisée nulle part ailleurs.
+- ✅ **Point de risque levé par vérification** : EF a généré un `RenameColumn`, **pas** un drop+add — les icônes déjà configurées survivent. L'avertissement « may result in the loss of data » ne porte que sur le `DropColumn` de `ParcoursIds`, qui est l'intention du lot.
+- ⛔ **« Supprimer `SectionEvent.IconResourceId` » était une erreur de doc — ne pas rouvrir.** `SectionEvent` n'a pas ce champ : la ligne visée appartient à la classe **imbriquée `MapAnnotation`**, partagée par SectionEvent, SectionAgenda et SectionMap, et lue par cinq contrôleurs plus les deux `SelectMany` de `GetReferencedResourceIds`. La supprimer aurait cassé les icônes d'annotation des trois types de section et la collecte offline.
+- **Sécurité rapide du lot A, dans la même passe** : le « backdoor `#if DEBUG` » était un bloc de `AuthenticationController.Authenticate` qui **écrasait l'email et le mot de passe reçus** par `test@email.be` — toute compilation Debug authentifiait n'importe quelle saisie. Retiré. `EnableSensitiveDataLogging` passe sous `#if DEBUG` (idiome déjà utilisé dans `Startup.cs` pour CORS et Hangfire ; le Dockerfile publiant en `-c Release`, c'est un verrou réel).
+- **Reste du volet sécurité, chiffré et non fait** : **112** retours `new ObjectResult(ex.Message) { StatusCode = 500 }` exposent le message d'exception interne, répartis sur 17 contrôleurs. Les 136 autres `ex.Message` sont des 400/404 volontaires, à garder. `Startup.UseExceptionHandler(HandleError)` existe mais ne traite que `RequestException`. C'est un chantier à part, pas une retouche.
+
+**Lot E — livré le 2026-08-11.** PDF en média d'étape (les trois volets : liste de types paramétrable, `ResourceViewer.tsx`, `CachedCustomResource`), comparaisons `QuestionType` par entier brut, et **W1 tranché : le web reste sur Leaflet** (option a — ni MapBox ni Google Maps JS, donc aucun coût récurrent ajouté avant la prod).
+
+⚠️ **W1 ne pouvait pas se faire comme l'option le disait.** « Retirer le champ pour les instances web » est **impossible** : le fournisseur est porté par la **section** — `SectionMap.MapMapProvider` et `SectionAgenda.AgendaMapProvider` — pas par l'instance. Et une même `Configuration` est liée à plusieurs `ApplicationInstance` via `AppConfigurationLink`, donc servie au mobile **comme** au web : masquer le champ aurait supprimé le réglage mobile. Le champ est donc conservé, et manager-app cesse de laisser croire qu'il vaut pour le web (mention sous les deux sélecteurs). Côté `visitapp-web`, zéro ligne : Leaflet inconditionnel était déjà le comportement.
+
+⚠️ **Piège de doc corrigé au passage** : le plan disait « cas PDF dans `getElementForResource` ». Cette fonction de `mymuseum-visitapp` fait un `return CachedCustomResource(...)` **avant** son `switch` — tout son switch est du code mort. Le vrai dispatcher est `CachedCustomResource`, et il en a **deux** (ressource distante / fichier local de l'offline). Suivre la doc à la lettre aurait produit un correctif inopérant et « vérifié » par une analyse verte.
+
### Contrôle du code du 2026-08-11 (2e passe) — ce qui a bougé depuis la rédaction du plan
> Tout vérifié dans le code, pas dans les docs. Deux points du lot A tombent, un point du lot A est différé, un nouveau suspect apparaît.
@@ -478,7 +497,7 @@ Le client ne manipule plus 9 booléens répartis sur 3 niveaux, mais répond à
- [ ] **Basse** — conventions REST hétérogènes, `PasswordUtils` avec RNG non crypto, code mort (MQTT, CORS AllowAll), CORS en dur, ordre middleware
### manager-app — reste à traiter (par priorité)
-- [ ] **Critique** — Client généré désynchronisé (`manager_api_new/`) : 14 fichiers modèle orphelins → `flutter analyze` inexploitable (183/200 erreurs de bruit)
+- [x] ✅ **Critique — résolu le 2026-08-11.** Client généré désynchronisé (`manager_api_new/`) : 14 fichiers modèle orphelins → `flutter analyze` inexploitable (183/200 erreurs de bruit, 68 au 09/08). **Mesuré après nettoyage** : `flutter analyze manager_api_new` passe de **67 à 3 issues**, l'analyse globale de manager-app de 68 erreurs de bruit à **3 erreurs**, toutes dans `test/widget_test.dart` (cassé et connu, voir « Basse » ci-dessous). `flutter build web` ✅ avant comme après — attendu, ces fichiers n'étant dans aucun graphe. **`flutter analyze` redevient un feu vert utilisable**, ce qui lève le lien L8
- **Caractérisé précisément le 2026-08-11.** `lib/api.dart` déclare **139 `part`** pour **153 fichiers** dans `lib/model/`. Les 14 restants portent bien `part of openapi.api;` mais **ne sont déclarés `part` par personne** — en Dart, un fichier `part` non listé par sa bibliothèque est **hors du graphe de compilation**. D'où l'asymétrie qui a coûté du temps : l'analyzer les lit (il parcourt tout `lib/`), le compilateur ne les voit jamais. **Les supprimer ne peut pas changer un build.**
- Les 14 : `agenda_event_stat_dto`, `app_configuration_link_dto_application_instance`, `content_dto_resource`, `content_geo_point`, `day_stat_dto`, `game_stat_dto`, `level_dto`, `map_dto_map_provider`, `map_dto_map_type`, `map_dto_map_type_mapbox`, `poi_stat_dto`, `quiz_stat_dto`, `section_stat_dto`, `user`.
- **Fausse alerte levée le 2026-08-11, à ne pas rouvrir** : `ContentGeoPoint` (un des 14) *semble* référencé par du code vivant — `tablet-app/lib/Screens/Map/marker_view.dart:456` et `mymuseum-visitapp/lib/Screens/Sections/Map/marker_view.dart:367`, les deux apps dépendant de `manager_api_new` par `path:`. **Les deux lignes sont dans un bloc commenté** (`/*` ouvert respectivement en 418 et 329) : du code mort, invisible pour le compilateur *et* pour l'analyzer. Vérifié par comptage de délimiteurs et confirmé par `flutter analyze` sur les deux fichiers — zéro erreur. **Aucun des 14 n'a de référence vivante.**
diff --git a/kanban.html b/kanban.html
index 3cf5466..a3eb6fd 100644
--- a/kanban.html
+++ b/kanban.html
@@ -454,13 +454,13 @@
Le compte exact : api.dart déclare 139 part pour 153 fichiers dans lib/model/. Les 14 restants portent bien part of openapi.api; mais ne sont déclarés part par personne. En Dart, un fichier part non listé par sa bibliothèque est hors du graphe de compilation — l'analyzer le lit puisqu'il parcourt tout lib/, le compilateur ne le voit jamais. Toute l'asymétrie qui a coûté du temps est là, et avec elle la garantie : supprimer ces fichiers ne peut pas changer un résultat de build.
Les déclencheurs de génération partent avec eux, pour une autre raison : ce n'est pas du modèle mort, c'est le moyen de régénérer (annotation @Openapi + build_runner). Les supprimer ne retire aucun code exécuté — ça verrouille la règle « ce client s'édite à la main » : tant qu'ils sont là, un build_runner build lancé par réflexe écrase les éditions manuelles, dont onboarding_api.dart, le câblage d'AIApi et le mapping isGood → isCorrect.
⚠️ Ils étaient trois, pas un. Le verrou ne tenait que dans manager-app : mymuseum-visitapp/lib/api/openApiTest.dart générait depuis son propre swagger vers un manager_api_new local — un second client divergent dans un repo qui consomme celui de manager-app par path: — et tablet-app/lib/api/openApi.dart faisait de même sous un autre nom de fichier, ce qui lui avait permis d'échapper aux recherches sur « openApiTest ». Les trois sont supprimés. Contrôle de non-régression : grep -rl "@Openapi" */lib doit rester vide.
Aucun des 14 n'a de référence vivante — vérifié. content_geo_point en avait l'air (tablet-app et mymuseum-visitapp), mais les deux usages sont dans des blocs commentés : voir la carte tablet-app.
StoragePath/SizeBytes des 45 lignes✅ Prérequis levé le 10/08 : Create écrit désormais StoragePath (pictures/{instanceId}/{resourceId}) et SizeBytes, types URL exclus, et manager-app envoie la taille avant l'upload. Le backfill ne sera donc pas à refaire au prochain upload. Reste le backfill lui-même, en deux moitiés : StoragePath est un simple UPDATE SQL (chemin déterministe), seul SizeBytes exige de lister le bucket. ⚠️ Mesuré le 09/08 sur 45 lignes : 37 ont un blob, 7 sont des types URL (Wikipedia, YouTube, agenda.php) sans aucun fichier — les inclure serait faux — et 1 est de type fichier sans URL. SizeBytes vaut 0 partout : le quota de stockage ne veut rien dire pour personne.
⚠️ Le « prérequis levé le 10/08 » n'était vrai qu'à moitié — relevé le 11/08 en extrayant le calculateur (C1). ResourceController a deux chemins de création, et le chemin multipart (le téléversement) écrivait SizeBytes mais laissait StoragePath nul ; seul le chemin JSON écrivait les deux. Les deux passent maintenant par ResourceStorage.Apply, donc le prérequis est réellement levé — mais des lignes créées entre-temps sont à rattraper par ce backfill.
Appeler ResourceStorage.PathFor plutôt que réécrire le chemin : c'est tout l'objet de C1, et l'écart (e) de la migration l'appellera aussi.
Remplace un if sur un id d'instance codé en dur. Doit remonter dans la config lue par manager-app, puisque le watermark s'applique côté client.
Un client qui choisit « Google Hybrid » voit de l'OSM : LeafletMap.tsx a un TileLayer en dur.
SectionMap.MapResourceId → à renommer en IconResourceId, pas à supprimer : il est branché de bout en bout sous un nom trompeur. Le renommage est l'écart (g) de la migration — le faire ici le fait disparaître là-bas.
SectionEvent.ParcoursIds → supprimer. Vérifié le 11/08 : lu ni écrit nulle part, commentaire (// Liens vers GeoPoints) contredisant son nom, et le lien événement↔parcours existe déjà en sens inverse via GuidedPath.SectionEventId. Décisif : il est sur SectionEvent et non sur ProgrammeBlock, donc incapable du cas « parcours de tel jour » pour lequel on le gardait.
QuestionType → ne pas toucher au schéma : le trio TextLibre/Digicode/ExpectedAnswer n'existe pas dans le code, et le digicode est un comportement dérivé. Reste une propreté front. Sort du lot « geler le schéma ».
Retenu à la place de faire de SectionParcours un conteneur de sections — idée écartée le 09/08 : la moitié des types (Agenda, Météo, Event) n'a pas de sens dans un point de parcours, et le lien vers une section existante existe déjà là où il est cohérent (baseSectionMapId). Le vrai manque tient à un type de ressource : une étape accepte déjà image, vidéo et audio, PDF est le seul absent. Trois points : liste de types paramétrable côté étape, cas PDF dans getElementForResource, cas PDF dans ResourceViewer.tsx. Sans les deux derniers, le PDF s'afficherait en image cassée — le bug corrigé pour la vidéo le 09/08.
Vingt et un chantiers clos entre le 5 et le 11 août 2026.
+Vingt-huit chantiers clos entre le 5 et le 11 août 2026.
MigrationController revenait à l'écrire deux fois. Une seule migration EF porte les trois changements. SectionMap.MapResourceId → IconResourceId, ce qui fait tomber l'écart (g) de la bascule tout seul — et il fallait renommer la propriété de navigation MapResource avec, sinon la convention EF fabriquait un FK fantôme. Point vérifié plutôt que supposé : EF a produit un RenameColumn, pas un drop+add — les icônes déjà configurées survivent. SectionEvent.ParcoursIds supprimé (l'avertissement de perte de données d'EF ne porte que sur lui, c'est l'intention). Filigrane : Instance.IsImageWatermark remplace le instanceId == "633ee379…" en dur. Référence tenue : 130/130 tests avant comme après, build Debug et Release verts.
+ #if DEBUG » du rapport d'audit : dans AuthenticationController.Authenticate, un bloc écrasait l'email et le mot de passe reçus par un compte de test — toute compilation en Debug authentifiait n'importe quelle saisie en tant que test@email.be. Retiré. EnableSensitiveDataLogging (qui écrit les valeurs des paramètres dans les logs : hashes, clés API, données de visiteurs) passe sous #if DEBUG, l'idiome déjà employé dans ce même fichier pour le CORS et Hangfire — et le Dockerfile publiant en -c Release, c'est un vrai verrou, ce qu'un build Release a confirmé. Les autres #if DEBUG du repo sont légitimes.
+ api.dart déclarait 139 part pour 153 fichiers. Les 14 restants portaient part of openapi.api; sans être listés par leur bibliothèque — en Dart, hors du graphe de compilation : l'analyzer les lisait, le compilateur ne les voyait jamais. Toute l'asymétrie qui a coûté du temps était là, et avec elle la garantie que les supprimer ne pouvait pas casser un build. Mesuré : flutter analyze sur le package passe de 67 à 3 issues, et l'analyse globale de manager-app de 68 erreurs de bruit à 3 erreurs, toutes dans test/widget_test.dart (cassé et connu) — flutter analyze redevient un feu vert utilisable, ce qui débloque les lots design. flutter build web ✅ avant comme après.
+ mymuseum-visitapp/lib/api/openApiTest.dart générait depuis son propre swagger vers un manager_api_new local — un second client divergent dans un repo qui consomme celui de manager-app par path: — et tablet-app/lib/api/openApi.dart faisait de même sous un autre nom de fichier, ce qui lui avait permis d'échapper à toutes les recherches sur « openApiTest ». Plus un résidu tablet-app/manager_api_new/ réduit à un pubspec.lock, non suivi par git, daté d'avril. Les trois sont supprimés : ce ne sont pas des fichiers morts, c'était le moyen d'écraser les éditions manuelles — onboarding_api.dart, le câblage d'AIApi, le mapping isGood → isCorrect. Contrôle de non-régression : grep -rl "@Openapi" */lib doit rester vide.
+ SectionMap.MapMapProvider, SectionAgenda.AgendaMapProvider), pas par l'instance — et une même Configuration est liée à plusieurs ApplicationInstance via AppConfigurationLink, donc servie au mobile comme au web. Masquer le champ aurait supprimé le réglage mobile. Il est donc conservé, et manager-app cesse de laisser croire qu'il vaut pour le web : mention explicite sous les deux sélecteurs. Zéro ligne côté visitapp-web — Leaflet inconditionnel était déjà le comportement ; ce qui manquait n'était pas du code mais une phrase honnête dans l'interface.
+ PdfViewer existant et son proxy /api/pdf — le contournement CORS était déjà écrit, il n'y avait qu'à le brancher. Côté Flutter, la carte indiquait le mauvais endroit : showElementForResource fait un return CachedCustomResource(...) avant son switch, donc tout ce switch est du code mort depuis un moment — ajouter le cas PDF là n'aurait rien changé et aurait été « vérifié » sans rien prouver. Le vrai dispatcher est CachedCustomResource, et il a deux switch (ressource distante / fichier déjà téléchargé pour l'offline) qui tombaient tous deux sur Text("Not supported type") : les deux sont traités, le distant devant télécharger avant d'afficher puisque PDFView n'accepte qu'un chemin de fichier. Au passage : l'enum du client s'appelle ResourceType.Pdf, pas PDF. npm run build ✅, flutter build web ✅ — le volet mymuseum ne tient que sur l'analyse, son APK ne compilant pas.
+ number0/1/2, et le front finissait donc par écrire ?.value == 2 ? 'Puzzle' : … — lisible seulement si l'on connaît l'ordre de l'enum serveur par cœur. Des alias nommés d'après le serveur (simple, multipleChoice, puzzle) sont ajoutés à la main dans question_type.dart, sans toucher à values : ce sont les mêmes trois valeurs, pas de nouvelles. Les deux sites visés par le plan sont corrigés, plus les 7 usages number* du dialogue de question — il ne reste aucun QuestionType.number dans les trois apps.
+ Navigator.pop sur « Annuler » sans rien demander, pas un — Parcours, Étape et Question, chacun jetant tout ce qui était saisi sous lui. Le plus coûteux est le plus profond : une question de quiz avec ses réponses. Ce n'était pas « la croix » comme le disait cette carte, c'était le bouton Annuler, et le clic hors fenêtre était déjà neutralisé — le vrai chemin de perte restant est le retour arrière du navigateur, couvert par PopScope. Détection des modifications par instantané JSON, pris après la première frame : le dialog Question normalise ses réponses pendant sa construction, un instantané pris avant aurait déclaré « modifié » une fenêtre intacte. Réutilise showConfirmationDialog plutôt que d'ouvrir un second composant de confirmation. 3 clés i18n FR/EN/NL, flutter build web ✅.
diff --git a/v1-plan.md b/v1-plan.md
index 06ae133..0e236a7 100644
--- a/v1-plan.md
+++ b/v1-plan.md
@@ -91,22 +91,30 @@ Rien de créatif, tout est un verrou pour la suite.
| Quoi | Décision préalable | Lien |
|---|---|---|
-| Renommer `SectionMap.MapResourceId` → `IconResourceId` (branché de bout en bout : `SectionFactory:179/511`, `SectionMap.ToDTO:58`, `MigrationController:469`) | Aucune, déjà tranché au §6bis | **L6** = écart (g) |
-| **Supprimer `SectionEvent.ParcoursIds`** — champ, DTO, migration EF | ✅ **Tranché le 2026-08-11 : supprimer.** Investigation : le champ n'est **lu ni écrit nulle part** (entité + DTO + migrations, zéro contrôleur, zéro service), son commentaire dit `// Liens vers GeoPoints spécifiques` ce qui contredit son nom, et le lien événement↔parcours existe dans l'autre sens via `GuidedPath.SectionEventId`, lui bien entretenu (`SectionMapController:396/449`, `SectionController:878`). Argument décisif : il est porté par **`SectionEvent`, pas par `ProgrammeBlock`** — il ne peut donc pas exprimer « les parcours de tel jour », le cas carnaval pour lequel on le gardait | Nettoyage §1 « À faire » |
-| Supprimer `SectionEvent.IconResourceId` (seul candidat sans usage visiteur identifié) | Aucune | §6bis |
+| ~~Renommer `SectionMap.MapResourceId` → `IconResourceId`~~ ✅ **2026-08-11** | Aucune, déjà tranché au §6bis | **L6** = écart (g), **qui tombe**. ⚠️ Il fallait renommer **aussi la propriété de navigation `MapResource`** → `IconResource` : la convention EF l'appariait au FK, la laisser aurait fabriqué un FK fantôme. Elle n'était utilisée nulle part ailleurs. Sites touchés : `SectionMap.cs:25/26/45/77`, `SectionFactory:227/559`, `ResourceController:469`, `MigrationController:469` |
+| ~~**Supprimer `SectionEvent.ParcoursIds`**~~ ✅ **2026-08-11** — champ, DTO, `SectionFactory:35/199/529`, et **un montage de test** (`SectionEventControllerTests:47`) que l'investigation initiale n'avait pas couvert : elle disait « zéro contrôleur, zéro service », ce qui était exact, mais les tests n'étaient pas dans le périmètre. La ligne retirée était une simple initialisation, sans rapport avec l'assertion du test | ✅ **Tranché le 2026-08-11 : supprimer.** Investigation : le champ n'est **lu ni écrit nulle part** (entité + DTO + migrations, zéro contrôleur, zéro service), son commentaire dit `// Liens vers GeoPoints spécifiques` ce qui contredit son nom, et le lien événement↔parcours existe dans l'autre sens via `GuidedPath.SectionEventId`, lui bien entretenu (`SectionMapController:396/449`, `SectionController:878`). Argument décisif : il est porté par **`SectionEvent`, pas par `ProgrammeBlock`** — il ne peut donc pas exprimer « les parcours de tel jour », le cas carnaval pour lequel on le gardait | Nettoyage §1 « À faire » |
+| ⛔ ~~Supprimer `SectionEvent.IconResourceId`~~ **— écarté le 2026-08-11 : ce champ n'existe pas** | **Erreur de doc, à ne pas rouvrir.** `SectionEvent` n'a aucun `IconResourceId`. La ligne visée (`SectionEvent.cs:105`) appartient à la classe **imbriquée `MapAnnotation`**, déclarée dans le même fichier — et elle est **très utilisée** : `SectionEventController:59/230/330`, `SectionAgendaController:58`, `SectionMapController:349/560`, plus les deux `.SelectMany(a => ResourceId(a.IconResourceId))` de `GetReferencedResourceIds` (lignes 50 et 53). `MapAnnotation` est partagée par SectionEvent, SectionAgenda **et** SectionMap : la supprimer aurait cassé les icônes d'annotation des trois types **et** la collecte de ressources offline | §6bis |
| ~~Unifier `QuestionType`~~ → **sorti du lot B le 2026-08-11** | ✅ **Tranché : on ne touche pas au schéma.** Le trio TextLibre/Digicode/ExpectedAnswer **n'existe pas dans le code** : l'enum a trois valeurs (`Simple`, `MultipleChoice`, `Puzzle`) référencées à **un seul endroit** côté serveur, et le digicode n'est pas un type mais un comportement dérivé d'une réponse numérique. Reste une propreté front : les comparaisons par entier brut (`…?.value == 2 ? 'Puzzle' : …`) dans `showNewOrUpdateGuidedStep.dart:409` et `guided_step_challenge.dart:14` → **déplacé au lot E** | **Ne bloque plus `MigrationController`** |
| Implémenter `Section.meterZoneGPS` côté visiteur (la colonne existe, une constante à 100 m la remplace) | Aucune, tranché au §6bis | Bug M3 du kanban — même passe |
| Flag watermark sur `Instance` + exposition dans la config, retrait du `if instanceId == "633ee379…"` de `ResourceController:265` | Aucune | Point 6 du §1ter |
Sortie du lot : `dotnet build` + `dotnet test` verts, une migration EF unique regroupant les renommages, et **plus aucun changement de modèle en attente**.
+✅ **Lot B livré le 2026-08-11.** Migration unique `20260811130738_LotB_FreezeSchema`. **Référence tenue : 130/130 tests avant comme après**, build **Debug et Release** verts (Release vérifié parce que le point sécurité ci-dessous change ce que Release compile).
+
+Deux points vérifiés plutôt que supposés :
+- **EF a généré un `RenameColumn`, pas un drop+add** pour `MapResourceId` → `IconResourceId` : les icônes déjà configurées survivent à la migration. C'était le vrai risque du lot.
+- **L'avertissement « may result in the loss of data » d'EF ne porte que sur le `DropColumn` de `ParcoursIds`** — c'est l'intention du lot, pas un effet de bord.
+
+**Reste hors de ce lot** : `Section.meterZoneGPS` côté visiteur (Flutter/TypeScript, pas le schéma) — la colonne serveur est correctement mappée, c'est le bug M3 qui attend son implémentation visiteur.
+
### Lot C — Terminer le lot médias (2 à 3 jours)
L'étape A du §1quater est faite (`StoragePath`/`SizeBytes` écrits à `Create` depuis le 10/08). Reste l'étape C.
| # | Quoi | Note |
|---|---|---|
-| C1 | **Extraire le calculateur** `StoragePath` + `SizeBytes` dans un service appelable | **L5** — consommé par C2 *et* par l'écart (e) du lot G |
+| ~~C1~~ ✅ **2026-08-11** | **Calculateur extrait** dans `Helpers/ResourceStorage.cs` (statique, comme `ImageHelper`/`SlugHelper` — logique pure, pas d'I/O, donc appelable sans DI depuis `MigrationController` et le futur backfill). Trois membres : `HasBlob(type)`, `PathFor(type, instanceId, resourceId)`, `Apply(resource, sizeBytes)`. **13 tests** fixent l'invariant des types URL. `dotnet test` **143/143** | **L5** — consommé par C2 *et* par l'écart (e) du lot G.