ENG-001.5 — Recalibrage de la roadmap Municipal Management
0. Métadonnées de mission
| Epic | EPIC-001 — Restructuration de DMV Core |
| Rattaché à | ENG-001.5 — Extraction de Municipal Management ; ENG-001.5A ; ENG-001.5B ; ENG-001.5C |
| Dépôt | dmv-docs (documentaire uniquement) |
| Type | Recalibrage de roadmap — aucune implémentation |
| Code applicatif | Aucun changement |
| Statut | Proposition — aucune décision tranchée |
Cette mission ne modifie aucun code. Elle réévalue, à partir de l'état réel du code après
ENG-001.5A/5B/5C, le découpage des prochaines PR de migration de Municipal Management, propose un
nouveau séquencement, et récapitule les décisions d'architecture qui restent à trancher par
l'architecte. Conformément à IA-GOVERNANCE.md, aucune de ces décisions n'est prise ici.
1. Rappel — ce qui est déjà fait
Les alertes municipales sont entièrement migrées et unifiées :
| PR | Résultat |
|---|---|
| Municipal-A (ENG-001.5A) | Module MunicipalManagement créé (contrats, DTO, services, provider), aucune logique réelle, aucune route |
| Municipal-B (ENG-001.5B) | ActeurModulesController (Actor) délègue ses 4 endpoints alertes à MunicipalManagementReader/Writer, qui implémentent réellement la lecture/écriture de mairie_alertes |
| Municipal-C (ENG-001.5C) | Duplication supprimée : MairieAlerteController, ses Requests, MairieAlerteDTO, le modèle Eloquent MairieAlerte, et les 4 routes /mairie/...alertes... sont supprimés. expireAlertes() relocalisée dans MunicipalManagementWriter. Une seule implémentation métier des alertes existe, garde-fou automatisé à l'appui |
Résultat : mairie_alertes a un unique propriétaire applicatif (MunicipalManagementReadService/
WriteService), un unique point d'entrée public (routes Actor /acteurs/{acteurId}/alertes...).
C'est allé au-delà du plan initial (§12 d'ENG-001.5, PR Municipal-C ne prévoyait que le
déplacement, pas la suppression de la façade Mairie) — rendu possible par l'audit consommateurs
qui a confirmé zéro client sur l'API Mairie (13.A, voir §7).
Ce qui n'est pas fait : services municipaux, élus, collectes, infos pratiques,
commune_info_sections, et la bascule d'Admin. C'est l'objet de ce document.
2. Cartographie restante
2.1 Module Mairie
| Fichier | Lignes | Rôle restant | Concerne Municipal Management ? |
|---|---|---|---|
Controllers/MairieServiceController.php | 74 | CRUD mairie_services (public + protégé mairie.access) | Oui |
Controllers/MairieCommuneController.php | 178 | showCommune/updateCommune (commune) + CRUD élus/collectes/infos + lecture complète services (protégé commune.manager) | Oui pour élus/collectes/infos/services ; non pour commune (§2.4) |
Controllers/MairiePublicationController.php | 67 | Publications scope mairie, délègue à PublicationWriteService | Non (§2.5) |
Services/MairieReadService.php | 114 | Lecture services, commune, élus, collectes, infos, publications | Oui (hors commune/publications) |
Services/MairieWriteService.php | 281 | Écriture services, commune, publications, élus, collectes, infos | Oui (hors commune/publications) |
Models/MairieService.php | 44 | Modèle Eloquent mairie_services | Oui |
DTOs/MairieServiceDTO.php | 55 | DTO services | Oui |
Requests/CreateServiceRequest.php | 22 | Validation création service | Oui |
Requests/ReorderServicesRequest.php | 18 | Validation réordonnancement | Oui |
Jobs/ExpireAlertes.php | 28 | Délègue déjà à MunicipalManagementWriter::expireAlertes() (ENG-001.5C) | Déjà résolu — reste physiquement dans Mairie\Jobs (voir 13.D) |
Middleware/EnsureMairieAccess.php | 117 | Vérifie kind='mairie' + ownership, résout l'acteur depuis la route | Oui, transverse (protège aussi les routes services) |
Middleware/EnsureCommuneManager.php | 140 | Vérifie rôle municipal_manager + accès commune | Oui, transverse (protège commune/élus/collectes/infos/services-all) |
Providers/MairieServiceProvider.php | — | Enregistre routes + bindings du module | Se réduit au fil des migrations |
Routes/api.php | — | 24 routes restantes (28 avant 5C moins 4 alertes déjà retirées) | Se réduit au fil des migrations |
Aucun modèle Eloquent n'existe pour commune_info_sections — écrite/lue exclusivement en
DB::table() brut depuis Admin (confirmé, inchangé depuis ENG-001.5).
2.2 Module Territory
| Fichier | Lignes | Rôle restant | Concerne Municipal Management ? |
|---|---|---|---|
Models/CommuneElu.php | 44 | Modèle Eloquent commune_elus, lecture publique (TerritoryService::getCommuneElus()) | Oui (cible de migration) |
Models/CommuneCollecte.php | 42 | Idem, commune_collectes | Oui |
Models/CommuneInfo.php | 44 | Idem, commune_infos, fusion avec des champs Actor pour section='mairie' (TerritoryService::getCommuneInfos()) | Oui |
Services/TerritoryService.php | 248 | Lecture publique élus/collectes/infos (getCommuneElus, getCommuneCollectes, getCommuneInfos) — lecture seule, jamais d'écriture | Consommateur en lecture, pas à migrer lui-même |
Services/CommuneMairieDataRefreshService.php | 371 | refreshElus() (réconciliation nom normalisé : update/désactive/insère) et refreshInfos()/upsertInfo() (section identite), écritures SQL brutes déclenchées par le rafraîchissement SIRENE automatisé | Oui — 3ᵉ chemin d'écriture concurrent sur élus et infos, temporaire depuis ENG-001.4 §11.E |
Confirmé par lecture du code réel : Territory n'écrit jamais commune_collectes (seul
refreshElus()/refreshInfos() existent dans CommuneMairieDataRefreshService, aucune méthode
équivalente pour les collectes) — cohérent avec ENG-001.5 §7, reconfirmé ici.
2.3 Module Admin
| Fichier | Lignes | Rôle restant | Concerne Municipal Management ? |
|---|---|---|---|
Controllers/AdminCommuneInfoController.php | 381 | CRUD complet élus/collectes/infos/commune_info_sections, plus fullCommune() (vue agrégée, y compris défibrillateurs — hors périmètre) | Oui pour élus/collectes/infos/sections |
Controllers/AdminCommuneController.php | 156 | communes (dont mairie_actor_id), détection/confirmation mairie, déclenchement du rafraîchissement SIRENE | Non — Territory (hors périmètre Municipal Management) |
Services/AdminCommuneService.php | 129 | Écrit communes | Non — Territory |
Controllers/AdminCommuneDefibrillateurController.php | 39 | Défibrillateurs (CommuneDefibrillateurService, module Territory) | Non — fonctionnalité Territory sans rapport avec RFC-001 (découverte de cette mission, absente de tous les documents antérieurs) |
AdminCommuneInfoController::fullCommune() (lignes 100-150) agrège des données de trois origines
différentes (infos/élus/collectes municipaux, commune Territory, défibrillateurs Territory) dans
une seule réponse JSON — point d'attention pour toute migration future (voir §6).
2.4 Hors périmètre Municipal Management (rappels, non rouverts)
communes.description/image_url(édités parMairieCommuneController::updateCommune(),AdminCommuneService) — ownership non tranché, question produit, cf. ENG-001.4 §11.B. Ne fait pas partie de Municipal Management par construction (RFC-001: Municipal Management « ne modifie jamais le territoire ni l'identité de la mairie »). Aucune PR de ce document ne le touche.- Publications mairie (
MairiePublicationController) — déjà correctement délégué àPublicationWriteService, Bounded Context distinct.RFC-001exclut explicitement la diffusion publique des responsabilités de Municipal Management. Aucune migration nécessaire ni prévue. communes.mairie_actor_id, résolution commune ↔ acteur mairie — Territory, cf. ENG-001.4 §11.A. Municipal Management le consommera en lecture seule une fois créé, ne le possédera jamais.- Défibrillateurs — Territory, sans lien avec
RFC-001. Mentionné uniquement parce queAdminCommuneInfoController::fullCommune()les agrège dans la même réponse (§2.3).
2.5 Constat additionnel non anticipé par ENG-001.5 : usage frontend incertain de deux ressources
Constat. Recherche exhaustive dans dmv-workspace, dmv-public, dmv-backoffice (fichiers
source uniquement, hors node_modules/.next/dist/build) :
mairie_services: le typeMairieServiceest défini dansdmv-workspace/types/index.ts:106, mais aucun appel vers/mairie/communes/{communeId}/servicesou/mairie/acteurs/{acteurId}/communes/{communeId}/servicesn'a été trouvé dans le code source d'aucun des trois frontends. La page workspaceacteur/[slug]/services/page.tsxexiste mais consomme un type différent (ActeurService, capacité Actor générique, sans lien avecmairie_services— cf. ENG-001.5 §6.1).- Publications mairie :
acteur/[slug]/publications/page.tsx(dmv-workspace) est une redirection pure verstableau-de-bord, sans appel à/mairie/acteurs/{acteurId}/publications.
À l'inverse, élus/collectes/infos/commune sont activement consommés par dmv-workspace
(services/mairie/elus.ts, collectes.ts, infos.ts, commune.ts, pages dédiées
acteur/[slug]/elus, /collectes, /commune, /infos).
Pourquoi c'est pertinent pour la roadmap. Migrer mairie_services a un coût (voir §3) même
s'il est faible ; si la fonctionnalité n'est plus utilisée en pratique (UI retirée, jamais
branchée, ou remplacée par autre chose), ce coût serait dépensé pour une fonctionnalité inerte.
Cette mission n'a inspecté que le code source des trois frontends listés — pas l'usage réel en
production, pas l'application mobile, pas d'éventuel client non recensé. Elle ne peut donc pas
conclure à un abandon produit.
Ce point est traité comme décision ouverte 13.F, §7.
3. Regroupement métier par ressource restante
| Ressource | Table(s) | Chemins d'écriture actuels | Fichiers concernés (estimé) | Complexité | Risque | Consommateurs connus | Dépendance frontend | Dépendance Admin | Dépendance Territory |
|---|---|---|---|---|---|---|---|---|---|
| Services municipaux | mairie_services | 1 (Mairie uniquement) | ~9 (contrôleur, 2 Requests, service×2 portions, modèle, DTO Mairie + DTO/contrat Municipal Management déjà stubés) | Faible | Faible | Aucun consommateur frontend confirmé (§2.5) ; lecture publique existante non vérifiée en usage réel | Incertaine — voir 13.F | Aucune (Admin n'écrit jamais mairie_services) | Aucune |
| Collectes | commune_collectes | 2 (Mairie, Admin) | ~7 (portion MairieCommuneController/MairieReadService/MairieWriteService, portion AdminCommuneInfoController, modèle CommuneCollecte, DTO/contrat déjà stubés) | Modérée | Modéré — 2 chemins à unifier, zéro test actuel sur le chemin d'écriture Mairie (§3.1) | dmv-workspace (services/mairie/collectes.ts), backoffice Admin (non vérifié dans le détail, hors périmètre code inspecté), lecture publique dmv-public via Territory | Oui — dmv-workspace (CRUD complet) | Oui — CRUD complet à unifier | Lecture seule (TerritoryService::getCommuneCollectes()), aucune écriture |
| Élus | commune_elus | 3 (Mairie, Admin, Territory-SIRENE) | ~9 (portion MairieCommuneController/services, portion AdminCommuneInfoController, portion CommuneMairieDataRefreshService::refreshElus(), modèle CommuneElu, DTO/contrat déjà stubés) | Élevée | Élevé — 3 chemins, dont un avec logique de réconciliation non triviale (correspondance par nom normalisé, désactivation, insertion — §2.2) à préserver à l'identique ; zéro test sur le chemin d'écriture Mairie, seule la lecture (GET) est testée (§3.1) | dmv-workspace (CRUD complet), Admin (CRUD complet), rafraîchissement SIRENE automatisé (cron horaire), lecture publique dmv-public | Oui — dmv-workspace | Oui — CRUD complet à unifier | Oui — écriture à faire déléguer (lève la limitation temporaire ENG-001.4 §11.E) |
| Infos pratiques | commune_infos | 3 (Mairie, Admin, Territory-SIRENE) | ~9, structure identique à Élus, plus dépendance à commune_info_sections (taxonomie) | Élevée | Élevé — mêmes facteurs qu'Élus, plus bloqué par la décision 13.C (ownership de commune_info_sections) non tranchée depuis ENG-001.4 ; zéro test sur le chemin d'écriture Mairie | dmv-workspace (CRUD complet), Admin (CRUD complet), rafraîchissement SIRENE automatisé (section identite), lecture publique dmv-public (fusion avec des champs Actor, cf. TerritoryService::getCommuneInfos()) | Oui — dmv-workspace | Oui — CRUD complet à unifier | Oui — écriture à faire déléguer |
commune_info_sections | commune_info_sections | 1 (Admin uniquement) | 1 (portion AdminCommuneInfoController, ~75 lignes) — aucun modèle Eloquent | Faible en soi, mais couplée à Infos pratiques | Faible isolément ; bloque Infos pratiques tant que 13.C n'est pas tranché | Admin uniquement | Aucune détectée | Oui — seul consommateur actuel | Aucune |
3.1 Constat de couverture de tests — non anticipé par ENG-001.5
Constat. tests/Feature/Actor/ActorAccessPhaseOneTest.php contient 5 tests couvrant
MairieCommuneController, mais uniquement la couche middleware/autorisation (commune.manager,
activation de module) sur des requêtes GET (élus, infos, services/all). Aucun test n'exerce le
chemin d'écriture Mairie (POST/PATCH/DELETE élus, collectes, infos) — ni succès, ni
persistance en base, ni règles de validation (qui n'existent d'ailleurs pas explicitement côté
Mairie, voir §3.2). AdminCommuneInfoTest.php (13 tests) et CommuneMairieDataRefreshTest.php
(12 tests) couvrent correctement leurs chemins respectifs.
Pourquoi c'est pertinent. Toute future PR d'unification (élus, collectes, infos) devra construire la couverture du chemin d'écriture Mairie à partir de zéro, pas seulement l'adapter — contrairement à ce qu'un plan fondé sur la seule lecture de la spécification initiale pourrait laisser supposer. Cela alourdit chacune des trois PR concernées par rapport à l'estimation initiale d'ENG-001.5 §15, qui ne mentionnait pas cette lacune.
3.2 Constat de divergence de validation — précision par rapport à ENG-001.5 §14
Le risque « divergence de validation entre les trois chemins » était déjà identifié (ENG-001.5 §14) mais non détaillé. Lecture du code réel :
- Mairie (
MairieWriteService::createElu/createCollecte/createInfo) : aucune validation explicite. Le contrôleur transmet$request->all()tel quel ; le service applique des valeurs par défaut (?? '',?? 0, etc.) mais ne rejette aucune entrée invalide (pas deFormRequest, contrairement àCreateServiceRequest/ReorderServicesRequestqui existent pour les services). - Admin (
AdminCommuneInfoController) : validation Laravel explicite ($request->validate([...])), avec règles de type et de longueur (max:255,in:..., etc.). - Territory-SIRENE (
CommuneMairieDataRefreshService) : aucune validation au sens formulaire (données internes issues de l'API SIRENE, pas d'entrée utilisateur), mais logique de réconciliation propre (normalisation de nom, désactivation/réactivation).
Une unification devra décider quelles règles de validation s'appliquent après unification — un sujet produit/technique à trancher pendant l'implémentation de chaque PR concernée, pas seulement une question de refactoring mécanique. Signalé ici pour que le futur exécutant ne soit pas surpris par l'absence de validation côté Mairie.
4. Proposition de découpage recalibré
Le plan initial (ENG-001.5 §12, PR Municipal-D/E/F) regroupait Élus+Infos dans une seule PR, puis reportait la bascule d'Admin à une PR finale séparée (Municipal-F) traitant toutes les ressources d'un coup. Cette section propose un recalibrage, justifié en détail en §5. Nommage poursuivant la séquence déjà utilisée (Municipal-A/B/C consommées).
PR Municipal-G — Services municipaux
Objectif. Migrer mairie_services vers Municipal Management, seul chemin d'écriture existant
(Mairie). Implémenter MunicipalManagementReader::getServices() et
MunicipalManagementWriter::createService()/updateService() (contrats déjà stubés depuis
Municipal-A). Retirer MairieServiceController, MairieService (modèle), MairieServiceDTO,
les deux Requests, les portions correspondantes de MairieReadService/WriteService.
Périmètre. mairie_services uniquement. Aucun changement de route, méthode HTTP ou format de
réponse (façade conservée, comme pour les alertes).
Dépendances. Aucune — indépendante des trois autres PR de ce plan. Peut démarrer immédiatement.
Risques. Faibles — un seul chemin d'écriture, pas de logique de réconciliation, structure déjà éprouvée par Municipal-C (alertes). Seul aléa : 13.F (§7) — si l'architecte confirme que la fonctionnalité est inutilisée, cette PR pourrait être reportée ou requalifiée en dépréciation plutôt qu'en migration.
Critères d'acceptation. Mairie n'écrit plus mairie_services directement ; comportement API
public et protégé strictement inchangé ; garde-fou « une seule implémentation métier des
services » (même principe que le garde-fou alertes d'ENG-001.5C).
PR Municipal-H — Collectes
Objectif. Unifier les deux chemins d'écriture (Mairie, Admin) sur commune_collectes en un
seul, porté par Municipal Management. Migrer le modèle CommuneCollecte vers le module. Adapter
MairieCommuneController et AdminCommuneInfoController (portion collectes) pour déléguer au
contrat au lieu d'écrire directement.
Périmètre. commune_collectes uniquement. Construire la couverture de tests du chemin Mairie
avant de le remplacer (§3.1 — aucune existante aujourd'hui). Aucun changement de comportement
observable ni pour dmv-workspace, ni pour Admin.
Dépendances. Aucune — indépendante de Municipal-G, Municipal-I, Municipal-J. Territory n'écrit
jamais cette ressource (§2.2), donc aucune dépendance vers CommuneMairieDataRefreshService.
Risques. Modérés — deux chemins à faire converger, divergence de validation à trancher (§3.2), absence de couverture de tests préexistante côté Mairie à combler d'abord.
Critères d'acceptation. Un seul chemin d'écriture effectif (vérifié par garde-fou architectural) ; comportement Mairie et Admin inchangé ; tests créés pour le chemin Mairie (inexistants avant cette PR) en plus des tests de non-régression Admin.
PR Municipal-I — Élus
Objectif. Unifier les trois chemins d'écriture (Mairie, Admin, Territory-SIRENE) sur
commune_elus. Migrer le modèle CommuneElu. Adapter CommuneMairieDataRefreshService::refreshElus()
pour déléguer sa logique de réconciliation (correspondance par nom normalisé, désactivation,
insertion) au contrat Municipal Management, en préservant le comportement exact — c'est la partie
la plus délicate de cette PR.
Périmètre. commune_elus uniquement — volontairement séparé d'Infos pratiques malgré leur
origine commune dans CommuneMairieDataRefreshService::refresh() (justifié en §5.2).
Dépendances. Aucune dépendance vers Municipal-G/H/J. Doit auditer les trois chemins de validation/valeurs par défaut avant implémentation (déjà amorcé en §3.2, à approfondir).
Risques. Élevés — trois consommateurs réels, logique de réconciliation SIRENE non triviale à reproduire fidèlement, absence de couverture de tests préexistante côté chemin d'écriture Mairie. Le risque le plus concret du plan recalibré, comme il l'était déjà dans le plan initial (Municipal-D, ENG-001.5 §14).
Critères d'acceptation. Un seul chemin d'écriture effectif ; le rafraîchissement SIRENE
continue de produire les mêmes compteurs (added/updated/disabled) qu'avant, vérifié par
CommuneMairieDataRefreshTest.php sans régression ; tests créés pour le chemin Mairie ; garde-fou
architectural.
PR Municipal-J — Infos pratiques et commune_info_sections
Objectif. Unifier les trois chemins d'écriture sur commune_infos, migrer le modèle
CommuneInfo, adapter CommuneMairieDataRefreshService::refreshInfos()/upsertInfo(). Inclure
commune_info_sections dans le même mouvement (taxonomie de commune_infos.section, un seul
consommateur — Admin — donc pas de complexité supplémentaire de convergence, juste un déplacement).
Périmètre. commune_infos + commune_info_sections. Bloquée tant que la décision 13.C
(ownership de commune_info_sections) n'est pas rendue (voir §7) — c'est un blocage de départ,
pas un risque d'exécution.
Dépendances. Décision 13.C rendue. Aucune dépendance vers Municipal-G/H/I (peut être implémentée en parallèle une fois 13.C tranché, y compris avant Municipal-I si utile).
Risques. Élevés, mêmes facteurs qu'Élus (trois chemins, réconciliation SIRENE bien que plus
simple ici — upsertInfo() n'a pas de logique de désactivation), plus le risque spécifique que
fullCommune() (Admin, §2.3) agrège commune_infos avec des données d'autres origines (élus,
collectes, défibrillateurs) — vérifier que cette vue agrégée continue de fonctionner à l'identique
après migration, bien qu'aucune de ses autres sources ne soit modifiée par cette PR.
Critères d'acceptation. Un seul chemin d'écriture effectif sur les deux tables ; rafraîchissement
SIRENE inchangé ; AdminCommuneInfoController::fullCommune() retourne un résultat identique ;
tests créés pour le chemin Mairie ; garde-fou architectural.
PR Municipal-K (optionnelle, à valider) — Clôture du module Mairie
Objectif. Une fois Municipal-G/H/I/J mergées, Mairie ne porte plus que : showCommune/
updateCommune (hors périmètre Municipal Management, §2.4), MairiePublicationController (hors
périmètre, §2.4), et les deux middleware d'accès. Cette PR évaluerait s'il reste pertinent de
garder un module Mairie pour si peu de responsabilités, ou s'il doit être renommé/absorbé.
Périmètre. Purement organisationnel — pas de nouvelle migration de donnée.
Dépendances. Municipal-G, H, I, J toutes mergées. Décision 13.D (déplacement des middleware) et confirmation que la question 11.B (commune description/image_url) reste hors périmètre.
Risques. Faibles si les quatre PR précédentes sont solides ; principalement un risque de « PR de confort » sans valeur fonctionnelle immédiate.
Critères d'acceptation. À définir si cette PR est retenue — non détaillés ici, cette PR est une option, pas une recommandation ferme (voir §7, nouvelle question implicite : faut-il la planifier maintenant ou la laisser émerger naturellement).
5. Vérification — regroupements et séparations
5.1 Pourquoi garder Services et Collectes comme PR indépendantes (Municipal-G, Municipal-H)
Aucune dépendance de code entre elles (tables, modèles, services distincts), aucun consommateur commun au-delà de la façade Mairie générique. Les regrouper n'apporterait aucune économie de risque ni de fichiers partagés — seulement une PR plus large sans bénéfice. Les garder séparées respecte le principe « cohérente fonctionnellement, indépendante autant que possible » de la mission.
5.2 Pourquoi séparer Élus et Infos pratiques (contrairement au plan initial Municipal-D)
Constat du plan initial. ENG-001.5 §12 les regroupait dans une seule PR Municipal-D, au motif
qu'elles partagent trois chemins d'écriture concurrents et une origine commune
(CommuneMairieDataRefreshService::refresh() appelle refreshElus() et refreshInfos() dans la
même exécution).
Analyse. Cette origine commune est une coïncidence d'orchestration, pas un couplage de
données : commune_elus et commune_infos sont deux tables indépendantes, sans clé étrangère
entre elles, avec des règles métier différentes (réconciliation par correspondance de nom pour les
élus ; upsert par section/label pour les infos) et des consommateurs qui les traitent déjà
séparément côté Mairie (indexElus/indexInfos, endpoints distincts) et côté Admin (contrôleurs
de méthodes distinctes dans le même fichier, mais indépendantes). Faire déléguer
refreshElus() au contrat Municipal Management sans toucher refreshInfos() (ou l'inverse) ne
casse rien : chaque méthode reste indépendamment fonctionnelle pendant la période où l'une des
deux tables est migrée et l'autre pas encore.
Bénéfice de la séparation. Deux PR plus petites, chacune plus facile à revoir et à tester
isolément, plutôt qu'une seule PR combinant deux jeux de règles de réconciliation différents. Coût
: une méthode supplémentaire de coordination dans CommuneMairieDataRefreshService (un appel
délègue déjà, l'autre écrit encore en direct, temporairement) — jugé négligeable au regard du gain
de risque. commune_info_sections reste avec Infos pratiques (Municipal-J), pas avec Élus,
puisqu'elle catalogue exclusivement commune_infos.section.
Recommandation. Séparer, comme détaillé en §4. Ceci recalibre explicitement 13.B (§7).
5.3 Pourquoi intégrer la bascule d'Admin dans chaque PR plutôt qu'une PR Municipal-F séparée
Constat du plan initial. ENG-001.5 §12 proposait de déplacer d'abord les modèles/services
(Municipal-C/D/E), puis de basculer Admin vers les contrats dans une PR finale unique
(Municipal-F), au motif de coordination avec le futur PR-008 du plan d'audit général
(ENG-001-1-dmv-core-audit-report.md §10).
Analyse. Séparer le déplacement de la bascule Admin signifierait qu'après Municipal-H (par
exemple), Municipal Management devient propriétaire du code applicatif de commune_collectes,
mais qu'Admin continue d'écrire directement dans la table en parallèle jusqu'à Municipal-F — soit
une réintroduction temporaire et délibérée de la duplication multi-chemins que Municipal-C
vient de démontrer qu'il fallait éliminer, pas répéter. Le précédent Municipal-C (ENG-001.5C) a
justement montré la valeur de clôturer complètement l'ownership d'une ressource en une seule PR
(Actor et Mairie corrigés ensemble) plutôt que de laisser une façade non convergée subsister.
Bénéfice du regroupement par ressource. Chaque PR (Municipal-G/H/I/J) ferme complètement l'ownership de sa ressource — Mairie et Admin et, le cas échéant, Territory — en une seule fois. Aucune période intermédiaire où deux chemins subsistent après le merge d'une PR.
Coût. AdminCommuneInfoController (381 lignes, un seul fichier) est touché par trois PR
différentes (Municipal-H, I, J) au lieu d'une seule — acceptable car les portions concernées
(collectes, élus, infos+sections) sont déjà des sections indépendantes du même fichier, sans
recouvrement.
Recommandation. Recalibrer le plan pour supprimer la notion de « PR Municipal-F » séparée et intégrer la bascule Admin à chaque PR ressource. C'est le changement le plus structurant de ce recalibrage par rapport au plan initial.
5.4 Territory reste hors périmètre de migration directe
Territory ne fait qu'orchestrer (CommuneMairieDataRefreshService) et lire (TerritoryService,
public). Aucune PR de ce plan ne déplace de code depuis Territory autre que l'adaptation des deux
méthodes d'écriture (refreshElus(), refreshInfos()) pour déléguer au contrat — conforme à la
levée progressive de la limitation temporaire actée par ENG-001.4 §11.E.
6. Point d'attention transverse : AdminCommuneInfoController::fullCommune()
Cette méthode (§2.3) agrège dans une seule réponse JSON des données dont la migration est répartie
sur trois PR différentes (élus → Municipal-I, collectes → Municipal-H, infos → Municipal-J) plus
deux sources hors périmètre (commune Territory, défibrillateurs Territory). Aucune de ces PR ne
change son comportement observable si elle est bien exécutée (lecture DB::table() remplacée par
lecture via le contrat, même structure de données retournée), mais c'est un point de vérification
transverse à inclure explicitement dans les tests de non-régression de chacune des trois PR
concernées, pas seulement à la fin.
7. Gouvernance — décisions ouvertes
Conformément à IA-GOVERNANCE.md, aucun des points suivants n'est tranché par ce document.
13.A — RÉSOLU
Résolu par ENG-001.5B puis ENG-001.5C. mairie_alertes a un unique propriétaire applicatif et un
unique point d'entrée public. Rappelé ici pour mémoire, non rouvert.
13.B — Découpage exact des sous-PR restantes — recalibré par ce document
Constat. Le découpage initial (ENG-001.5 §12, PR Municipal-D/E/F) reste une recommandation d'ingénierie non tranchée. Ce document en propose un autre (§4), fondé sur l'expérience acquise avec Municipal-A/B/C et sur une lecture plus fine du code réel restant (§2, §3).
Options.
- Plan initial (Municipal-D « Élus+Infos », Municipal-E « Collectes », Municipal-F « Admin » séparée). Avantages : déjà documenté, déjà annoncé. Inconvénients : réintroduit temporairement la duplication Mairie/Admin à chaque étape intermédiaire (§5.3) ; regroupe deux ressources aux règles de réconciliation différentes (§5.2).
- Plan recalibré de ce document (Municipal-G « Services », Municipal-H « Collectes »,
Municipal-I « Élus », Municipal-J « Infos+Sections », chacune incluant la bascule Admin
correspondante ; Municipal-K optionnelle). Avantages : chaque PR ferme complètement
l'ownership de sa ressource, cohérent avec le précédent Municipal-C ; granularité plus fine,
plus facile à revoir et à revert isolément. Inconvénients : une PR de plus (5 au lieu de 3
pour couvrir le même périmètre),
AdminCommuneInfoControllertouché par trois PR distinctes au lieu d'une.
Recommandation. Option 2 (ce document), pour les raisons détaillées en §5.
Décision attendue. Validation du plan recalibré (§4), ou préférence explicite pour le plan initial malgré les inconvénients identifiés en §5.3/§5.2.
13.C — Ownership de commune_info_sections — toujours ouvert, désormais strictement bloquant
Constat. Rappelé d'ENG-001.4 §11.C et ENG-001.5 §13.C, toujours non tranché. Ce document confirme qu'aucun changement n'a eu lieu depuis : Admin reste l'unique lecteur/écrivain, aucun autre module n'y touche.
Options. Identiques à celles déjà posées (voir ENG-001.4 §11.C et ENG-001.5 §13.C, non reproduites ici).
Recommandation. Inchangée — rattacher à Municipal Management au même titre que commune_infos
(option 1 des documents précédents), mais ce n'est qu'une observation, pas une décision.
Décision attendue. Ce document rend ce point bloquant de départ pour Municipal-J (§4), pas seulement une sous-partie à trancher pendant son exécution comme le formulait ENG-001.5 §13.C. Sans arbitrage, Municipal-J ne peut pas démarrer.
13.D — Moment de déplacement des middleware d'accès — toujours ouvert
Constat. Inchangé depuis ENG-001.5 §13.D. EnsureMairieAccess et EnsureCommuneManager
continuent de protéger des routes actives (services, élus, collectes, infos, commune) et
protégeront encore les routes migrées par Municipal-G/H/I/J (les routes elles-mêmes ne bougent
pas, seule l'implémentation change).
Recommandation. Inchangée (option 2 : laisser dans Mairie jusqu'à l'extraction du Platform Service Authorization). Ce recalibrage ne change rien à cet arbitrage — confirmé pertinent même avec le nouveau découpage, puisque aucune des quatre PR proposées ne nécessite de toucher ces middleware.
Décision attendue. Inchangée par rapport à ENG-001.5.
13.E — Renommage éventuel des tables — toujours ouvert
Constat. Inchangé. Aucune des quatre PR proposées (§4) ne renomme de table.
Recommandation. Inchangée (option 1 : conserver les noms actuels).
Décision attendue. Inchangée par rapport à ENG-001.5.
13.F — Nouvelle décision : confirmer l'usage produit réel de mairie_services et des publications mairie avant migration
Constat. §2.5. Recherche exhaustive dans le code source de dmv-workspace, dmv-public,
dmv-backoffice : aucun appel actif trouvé vers les routes mairie_services, ni vers les routes
publications mairie (page de redirection vide côté workspace). Cette mission n'a pas accès à des
données d'usage en production, ni à l'application mobile, ni à d'éventuels clients non recensés
dans ces trois dépôts.
Options.
- Migrer
mairie_servicesnormalement (PR Municipal-G, §4), sans attendre de confirmation produit. Avantages : ne bloque rien, cohérence architecturale immédiate si la fonctionnalité est bien vivante ailleurs (mobile, usage non détecté). Inconvénients : investit un effort de migration (même faible) sur une fonctionnalité peut-être inerte. - Suspendre Municipal-G et vérifier l'usage produit réel (analytics, logs d'accès, ou confirmation directe de l'architecte/product owner) avant de migrer. Avantages : évite un effort inutile ; ouvre la possibilité de déprécier plutôt que migrer. Inconvénients : bloque une PR par ailleurs simple et à faible risque, pour une vérification qui dépasse le périmètre de ce document (accès aux données de production).
- Migrer quand même, mais documenter la fonctionnalité comme candidate à dépréciation dans le même mouvement, à trancher séparément après coup. Avantages : ne bloque rien, capture l'observation sans lui donner plus de poids qu'elle n'en a. Inconvénients : aucun réel.
Recommandation. Option 3 — le coût de Municipal-G est faible (§3), autant le réaliser normalement tout en signalant l'observation ; la question de dépréciation, elle, est produit et ne doit pas être tranchée ici. Les publications mairie n'ont en revanche aucune PR de migration prévue dans ce plan (§2.4, hors périmètre Municipal Management par nature), donc aucune décision d'urgence les concernant — l'observation est portée pour mémoire uniquement.
Décision attendue. Confirmation de l'option retenue pour Municipal-G, ou instruction de
vérifier l'usage réel avant tout travail sur mairie_services.
8. Risques (recalibrés)
| Risque | Probabilité | Impact | Mitigation |
|---|---|---|---|
| Municipal-I (élus) ou Municipal-J (infos) casse la réconciliation SIRENE par une divergence non détectée | Moyenne — logique de réconciliation non triviale, aucun test actuel sur le chemin Mairie (§3.1) | Élevé — perte ou incohérence de données municipales réelles | Construire la couverture de tests Mairie manquante avant l'implémentation, pas pendant ; tests de non-régression explicites par chemin d'origine (Mairie, Admin, SIRENE) |
| Municipal-J démarre avant que 13.C soit tranché | Faible si ce document est suivi | Élevé si ignoré — migration de commune_infos sans savoir où classer sa taxonomie | Bloquer explicitement Municipal-J sur 13.C (§4, §7) |
| Effort investi dans Municipal-G (services) sur une fonctionnalité inutilisée en production | Inconnue — non vérifiable depuis le seul code source (§2.5, §7 13.F) | Faible (l'effort lui-même est faible) | Signaler l'observation à l'architecte (13.F) sans bloquer la PR |
| Divergence de règles de validation non résolue avant l'implémentation de Municipal-H/I/J | Moyenne — le chemin Mairie n'a aujourd'hui aucune validation explicite (§3.2) | Moyen — comportement de validation qui change silencieusement au moment de l'unification | Trancher explicitement, PR par PR, quelles règles s'appliquent après unification ; documenter le choix dans le rapport d'implémentation de chaque PR |
AdminCommuneInfoController::fullCommune() régresse après une migration partielle | Faible si testé explicitement | Moyen — casserait la vue agrégée du backoffice | Inclure fullCommune() dans les tests de non-régression de Municipal-H, I et J (§6) |
9. Définition de Done (pour cette mission de recalibrage)
- Cartographie du code réel restant (Mairie, Territory, Admin) produite avec citations vérifiées, y compris deux constats non anticipés par ENG-001.5 (usage frontend incertain §2.5/§7 13.F, absence de couverture de tests sur le chemin Mairie §3.1).
- Regroupement métier par ressource restante, avec dépendances, complexité, risques, consommateurs connus, dépendances frontend/Admin/Territory (§3).
- Nouveau découpage de PR proposé (§4), avec justification explicite des regroupements et séparations retenus (§5), sans reprendre automatiquement le plan initial.
- Cinq décisions architecturales listées (§7) : une déjà résolue (13.A, rappel), une recalibrée par ce document (13.B), deux reprises sans changement (13.D, 13.E), une devenue strictement bloquante (13.C), une nouvelle (13.F). Aucune tranchée par ce document.
git diff --checketnpm run buildpassent (§10).- Aucun code applicatif n'a été modifié. Aucun commit n'a été créé.
Références
- ENG-001.5 — Extraction de Municipal Management
- ENG-001.5A — Rapport d'implémentation
- ENG-001.5B — Rapport d'implémentation
- ENG-001.5C — Rapport d'implémentation
- ENG-001.4 — Territory Bounded Context §11 (11.A, 11.B, 11.C, 11.E)
- EPIC-001 — Restructuration de DMV Core
- ADR-014 — DMV Core : Bounded Contexts et Monolithe Modulaire
- RFC-001 — Extraction du Bounded Context Municipal Management