diff --git a/STATUS.md b/STATUS.md index 8fc8415..01cb79f 100644 --- a/STATUS.md +++ b/STATUS.md @@ -308,6 +308,20 @@ 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 G — les 8 écarts sont clos le 2026-08-11. Reste le dry run.** `dotnet build` vert, `dotnet test` **143/143**. + +**Trois des huit n'existaient pas.** Le plan les décrivait en « perte de données » ; l'export Mongo dit qu'il n'y a rien à perdre : +- **(a)** `BuildSection` couvre exactement les types présents : les 327 sections de l'export ne contiennent que les valeurs 0 à 10. `SectionEvent` et `SectionParcours` sont nés avec Postgres v3. +- **(f)** `IsQRCode`/`IsSearchText`/`IsSearchNumber` n'existent pas dans Mongo — `false` est le bon défaut. Contrôle inverse fait : les cinq réglages qui *existent* (`IsDate`, `IsHour`, `IsSectionImageBackground`, `RoundedValue`, `ScreenPercentageSectionsMainPage`) sont bien mappés sur `AppConfigurationLink`. +- **(g)** tombé avec le rename du lot B. + +**Les cinq vrais, corrigés** : +- **(b)** mesuré : **5 sections quiz portent 41 questions**, toutes jetées. Migrées avec leurs réponses, en `MultipleChoice` (le type de validation n'existait pas dans l'ancien modèle). Les agendas n'ont aucun événement dans Mongo et les parcours n'y existent pas : leurs collections vides sont correctes. +- **(c)** tout est généré ou dérivé, cf. v1-plan. +- **(d)** Mongo n'a **pas de champ Role** : le `ContentEditor` en dur dégradait les 10 utilisateurs en silence. Passé à `InstanceAdmin`. ⚠️ Aucun `SuperAdmin` n'est créé par la migration. +- **(e)** passe par `ResourceStorage` (L5) ; `StoragePath` n'était pas écrit du tout. L'échec du `HEAD` remonte désormais dans le rapport. +- **(h)** transaction unique, et **500 au lieu de 200 OK** sur échec fatal. + **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à. diff --git a/kanban.html b/kanban.html index a3eb6fd..109fba5 100644 --- a/kanban.html +++ b/kanban.html @@ -459,8 +459,8 @@
Le plus dangereux, parce qu'il ne produit aucune erreur : SubscriptionPlanId null → quotas à 0, PublicApiKey null → les apps visiteur ne s'authentifient plus, WebSlug null → visitapp-web injoignable, IsMobile/IsTablet/IsWeb/IsAssistant à false. La migration se déclare réussie et le lieu est inaccessible.
BuildSection a été écrit avant SectionEvent et SectionParcours : les deux tombent dans le default, une ligne dans le rapport d'erreurs et la section n'est pas migrée. Ce sont précisément les types du scénario carnaval.
QuizQuestions = new(), EventAgendas = new(), aucun GuidedPath/GuidedStep : un quiz arrive sans ses questions, un agenda sans ses événements. Et Role est forcé à ContentEditor — plus aucun administrateur après la bascule.
Neuf étapes, un SaveChangesAsync chacune, et un catch qui renvoie 200 OK avec l'erreur dans le corps. Un échec au milieu laisse la base à moitié remplie ; la reprise ne tient qu'aux tests d'idempotence — qui existent et sont corrects. Idem : StoragePath et les colonnes IA ne sont pas renseignés, et SizeBytes sort d'un HEAD dont l'échec est avalé.
dryRun=true n'écrit rien. Rejouer sur l'export Mongo déjà présent dans manager-service/migration-data/ et comparer entité par entité aux comptages Mongo. C'est ce qui révèle les quatre cartes ci-dessus sur des données réelles plutôt que par lecture de code.
Vingt-huit chantiers clos entre le 5 et le 11 août 2026.
+Trente chantiers clos entre le 5 et le 11 août 2026.
SectionEvent et SectionParcours sont nés avec Postgres v3, le default est un filet et non un trou, et le « scénario carnaval » qu'on croyait menacé est du contenu à créer, pas à migrer. (f) les trois booléens forcés à false : ces colonnes n'existent pas dans Mongo, false est le bon défaut — et le contrôle inverse a été fait, les cinq réglages qui existent vraiment dans l'export (IsDate, IsHour, IsSectionImageBackground, RoundedValue, ScreenPercentageSectionsMainPage) sont bien mappés sur AppConfigurationLink. (g) était tombé avec le rename du lot B. Trois cartes de la colonne « Bascule prod » se ferment sans une ligne de code.
+ QuizQuestions = new() jetait les questions : mesuré sur l'export, 5 sections quiz en portent 41, réponses comprises. Migrées, en MultipleChoice puisque le type de validation n'existait pas dans l'ancien modèle. Les agendas (aucun événement dans Mongo) et les parcours (inexistants avant) gardent leurs collections vides à raison. (d) était pire que décrit : Mongo n'a pas de champ Role, donc les 10 utilisateurs y avaient un accès complet, et le ContentEditor en dur les dégradait tous en silence — plus personne n'aurait pu gérer les utilisateurs de son instance. Passé à InstanceAdmin ; aucun SuperAdmin n'est créé par la migration, à poser à la main. (c) tout est généré ou dérivé, la source ne contenant que 4 champs. (e) StoragePath n'était pas écrit du tout : passe par le calculateur commun, et l'échec du HEAD, jusqu'ici avalé, remonte dans le rapport. (h) transaction unique, et surtout 500 au lieu de 200 OK sur échec fatal — le piège était qu'un script de bascule testant le code HTTP concluait au succès. dotnet test 143/143.
+ 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.
diff --git a/v1-plan.md b/v1-plan.md
index 877b800..5526fab 100644
--- a/v1-plan.md
+++ b/v1-plan.md
@@ -188,14 +188,14 @@ Les 8 écarts a→h du §1quinquies. Ne démarre qu'après les lots B et C1 (**L
| Écart | Quoi | Attention |
|---|---|---|
-| c | `Instance` : 4 champs migrés sur ~35 — **requalifié le 2026-08-11 : ce n'est pas un oubli de mapping.** `OldInstance` et l'export réel ne contiennent que `_id`, `Name`, `DateCreation`, `PinCode`. Les 31 autres colonnes **n'existent pas dans la source** : elles sont nées avec Postgres v3. Il n'y a donc rien à mapper, il faut **générer, dériver ou décider**. `PublicApiKey` → générer · `WebSlug` → dériver du `Name` (`SlugHelper` existe) · `IsMobile`/`IsTablet`/`IsWeb` → dériver des `ApplicationInstance` réellement présentes · `SubscriptionPlanId` → décidé, voir ci-dessous | **Le plus dangereux : aucune erreur levée.** `PublicApiKey` null = les apps visiteur ne s'authentifient plus ; `WebSlug` null = visitapp-web injoignable ; `SubscriptionPlanId` null = quotas à 0, donc `AiTokensPerMonth = 0`, donc **rien ne s'indexe** et l'IA tourne sans compteur. Réutiliser `ApplyPlanQuotas` |
+| ~~c~~ ✅ **2026-08-11** | `Instance` : 4 champs migrés sur ~35 — **requalifié : ce n'était pas un oubli de mapping.** `OldInstance` et l'export réel ne contiennent que `_id`, `Name`, `DateCreation`, `PinCode`. Les 31 autres colonnes **n'existent pas dans la source** : elles sont nées avec Postgres v3. Il n'y a donc rien à mapper, il faut **générer, dériver ou décider**. `PublicApiKey` → générer · `WebSlug` → dériver du `Name` (`SlugHelper` existe) · `IsMobile`/`IsTablet`/`IsWeb` → dériver · `SubscriptionPlanId` → décidé.