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 @@
6Bugs ouverts
6À tester
20Planifié
-
11Bascule prod
-
28Fait récemment
+
7Bascule prod
+
30Fait récemment
@@ -812,7 +812,7 @@
-

Bascule prod

11
+

Bascule prod

7
@@ -824,35 +824,7 @@ security/audit-securite-manager-service.md · v1-plan.md L1, I0
-
-
MigrationControllerSilencieux
-

Instance : 4 champs migrés sur ~35

-

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.

- MigrationController.cs:106-112 · STATUS.md §1quinquies c -
- -
-
MigrationControllerPerte de données
-

11 types de section sur 13

-

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.

- MigrationController.cs:458-613 · §1quinquies a -
- -
-
MigrationControllerPerte de données
-

Collections filles jamais remplies

-

QuizQuestions = new(), EventAgendas = new(), aucun GuidedPath/GuidedStep : un quiz arrive sans ses questions, un agenda sans ses événements. Et Role est forcé à ContentEditorplus aucun administrateur après la bascule.

- MigrationController.cs:225, :526, :591 · §1quinquies b, d -
- -
-
MigrationControllerRobustesse
-

Aucune transaction globale

-

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

- MigrationController.cs:68-86, :172-181 · §1quinquies e, h -
- -
+
étape 15Sans risque

Dry run + comparaison des comptages

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.

@@ -901,9 +873,19 @@

Fait récemment

-

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.

+
+ MigrationController — trois des huit écarts n'existaient pas + Le plan les décrivait en « perte de données ». L'export Mongo, lu plutôt que supposé, dit qu'il n'y a rien à perdre. (a) « 11 types de section sur 13 » : les 327 sections de l'export ne contiennent que les types 0 à 10 — 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. +
+ +
+ MigrationController — les cinq écarts réels, corrigés + (b) 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. +
+
Le schéma est gelé — lot B, et le lot G peut enfin commencer Le point d'articulation du plan : tant qu'un changement de modèle restait ouvert, réécrire MigrationController revenait à l'écrire deux fois. Une seule migration EF porte les trois changements. SectionMap.MapResourceIdIconResourceId, 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é.
**Livré** : `PublicApiKey` générée avec le même schéma que l'inscription self-service (`ap_` + 32 octets cryptographiques) · `WebSlug` via `SlugHelper.GenerateUniqueSlug` (accents et collisions déjà gérés) · `IsTablet`/`IsMobile` dérivés des configurations Mongo, comme le fait déjà `MigrateApplicationInstancesAsync` ; `IsWeb` reste faux, le canal web n'existait pas · plan affecté par nom, un nom inconnu retombant sur **Pro**, jamais sur un plan qui offrirait l'IA · `IsAssistant` allumé seulement si le plan donne des jetons.
⚠️ `ApplyPlanQuotas` est **dupliqué** depuis `InstanceController` plutôt qu'appelé : la migration ne doit pas casser si ce contrôleur change de forme. Les deux doivent rester alignées | **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` | | ~~a~~ | ⛔ **Fausse alerte — vérifiée dans l'export le 2026-08-11.** `BuildSection` couvre **exactement les types que Mongo contient**. L'enum va de `Map`=0 à `Weather`=10, puis `Event`=11 et `Parcours`=12 ; or `TabletDb.Sections.json` (327 sections) ne contient **que les valeurs 0 à 10**. `SectionEvent` et `SectionParcours` **n'existaient pas dans Mongo** — ce sont des types nés avec Postgres v3. Le `default` est un filet, pas un trou | Le kanban parlait de « perte de données » et du « scénario carnaval » : ce scénario est du **contenu à créer** dans Postgres, pas du contenu à migrer. Vérifié aussi : le renommage `SectionPuzzle` → `SectionGame` est **déjà traité** — la branche `Game` parse `OldPuzzleDTO` et pose `GameType = GameTypes.Puzzle`, et la valeur 8 n'a pas bougé | -| b | Collections filles jamais remplies (`QuizQuestions = new()`, `EventAgendas = new()`, aucun `GuidedPath`/`GuidedStep`) | Un quiz arrive sans ses questions | -| d | `Role = UserRole.ContentEditor` en dur | Plus aucun administrateur après la bascule | -| e | Colonnes `Resource` non renseignées, `SizeBytes` déduit d'un `HEAD` avalé | **L5** — appeler le calculateur C1 | -| f | `IsQRCode`/`IsSearchText`/`IsSearchNumber` forcés à `false` | Réglages de configuration perdus | +| ~~b~~ ✅ **2026-08-11** | Collections filles jamais remplies | **Vrai, et mesuré** : `QuizQuestions = new()` jetait les questions de **5 sections quiz, soit 41 questions**, réponses comprises. Migrées. Le type de validation n'existant pas dans l'ancien modèle (tout y était à choix multiples), elles arrivent en `MultipleChoice` plutôt qu'au défaut `Simple`. **En revanche `EventAgendas = new()` et l'absence de `GuidedPath` sont corrects** : les agendas Mongo ne portent que `mapProvider` et `resourceIds`, aucun événement, et SectionParcours est né avec Postgres | +| ~~d~~ ✅ **2026-08-11** | `Role = UserRole.ContentEditor` en dur | **Vrai, et pire que décrit** : Mongo **n'a pas de champ Role**, donc les 10 utilisateurs y avaient un accès complet. Le défaut les **dégradait tous en silence** — plus personne n'aurait pu gérer les utilisateurs de son instance. Passé à `InstanceAdmin`, ce qui préserve leurs droits d'hier et correspond au rôle que l'inscription self-service donne au premier utilisateur. ⚠️ Aucun `SuperAdmin` n'est créé par la migration : à poser à la main | +| ~~e~~ ✅ **2026-08-11** | Colonnes `Resource` non renseignées, `SizeBytes` déduit d'un `HEAD` avalé | **L5 fermé** : passe par `ResourceStorage.Apply`, le même calculateur que la création et le backfill — `StoragePath` n'était pas écrit **du tout**. Et l'échec du `HEAD`, jusqu'ici silencieux, **remonte dans le rapport** : une ressource à 0 octet que le quota compte pour rien, ça se sait | +| ~~f~~ | ⛔ **Fausse alerte — vérifiée dans l'export le 2026-08-11.** Ces trois colonnes **n'existent pas dans Mongo** : les forcer à `false` est le bon défaut, pas une perte. Contrôle inverse fait aussi — les cinq réglages qui, eux, **existent** dans l'export (`IsDate`, `IsHour`, `IsSectionImageBackground`, `RoundedValue`, `ScreenPercentageSectionsMainPage`) **sont bien mappés**, sur `AppConfigurationLink` | Rien à corriger | | g | `MapResourceId = dto?.iconResourceId` | **L6** — tombe avec le lot B | -| h | Pas de transaction globale, `catch` qui renvoie 200 OK avec `FatalError` dans le corps | Transaction + vrai code d'erreur. Les tests `AnyAsync` d'idempotence, eux, sont corrects | +| ~~h~~ ✅ **2026-08-11** | Pas de transaction globale, `catch` qui renvoie 200 OK | Transaction unique sur les neuf étapes : soit tout est là, soit rien. Et l'échec fatal renvoie désormais **500** — le piège était qu'un script de bascule testant le code HTTP concluait au succès, l'erreur n'étant que dans le corps. En `dryRun`, pas de transaction ouverte : rien n'est écrit | Puis : **dry run** (`dryRun=true`) sur l'export Mongo de `manager-service/migration-data/`, et **comparaison des comptages par entité** avec Mongo. C'est la seule preuve que les écarts a, b et c sont fermés.