Aller au contenu principal

ENG-001.5 — Recalibrage de la roadmap Municipal Management

0. Métadonnées de mission

EpicEPIC-001 — Restructuration de DMV Core
Rattaché àENG-001.5 — Extraction de Municipal Management ; ENG-001.5A ; ENG-001.5B ; ENG-001.5C
Dépôtdmv-docs (documentaire uniquement)
TypeRecalibrage de roadmap — aucune implémentation
Code applicatifAucun changement
StatutProposition — 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 :

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

FichierLignesRôle restantConcerne Municipal Management ?
Controllers/MairieServiceController.php74CRUD mairie_services (public + protégé mairie.access)Oui
Controllers/MairieCommuneController.php178showCommune/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.php67Publications scope mairie, délègue à PublicationWriteServiceNon (§2.5)
Services/MairieReadService.php114Lecture services, commune, élus, collectes, infos, publicationsOui (hors commune/publications)
Services/MairieWriteService.php281Écriture services, commune, publications, élus, collectes, infosOui (hors commune/publications)
Models/MairieService.php44Modèle Eloquent mairie_servicesOui
DTOs/MairieServiceDTO.php55DTO servicesOui
Requests/CreateServiceRequest.php22Validation création serviceOui
Requests/ReorderServicesRequest.php18Validation réordonnancementOui
Jobs/ExpireAlertes.php28Délègue déjà à MunicipalManagementWriter::expireAlertes() (ENG-001.5C)Déjà résolu — reste physiquement dans Mairie\Jobs (voir 13.D)
Middleware/EnsureMairieAccess.php117Vérifie kind='mairie' + ownership, résout l'acteur depuis la routeOui, transverse (protège aussi les routes services)
Middleware/EnsureCommuneManager.php140Vérifie rôle municipal_manager + accès communeOui, transverse (protège commune/élus/collectes/infos/services-all)
Providers/MairieServiceProvider.phpEnregistre routes + bindings du moduleSe réduit au fil des migrations
Routes/api.php24 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

FichierLignesRôle restantConcerne Municipal Management ?
Models/CommuneElu.php44Modèle Eloquent commune_elus, lecture publique (TerritoryService::getCommuneElus())Oui (cible de migration)
Models/CommuneCollecte.php42Idem, commune_collectesOui
Models/CommuneInfo.php44Idem, commune_infos, fusion avec des champs Actor pour section='mairie' (TerritoryService::getCommuneInfos())Oui
Services/TerritoryService.php248Lecture publique élus/collectes/infos (getCommuneElus, getCommuneCollectes, getCommuneInfos) — lecture seule, jamais d'écritureConsommateur en lecture, pas à migrer lui-même
Services/CommuneMairieDataRefreshService.php371refreshElus() (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

FichierLignesRôle restantConcerne Municipal Management ?
Controllers/AdminCommuneInfoController.php381CRUD 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.php156communes (dont mairie_actor_id), détection/confirmation mairie, déclenchement du rafraîchissement SIRENENon — Territory (hors périmètre Municipal Management)
Services/AdminCommuneService.php129Écrit communesNon — Territory
Controllers/AdminCommuneDefibrillateurController.php39Dé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 par MairieCommuneController::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-001 exclut 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 que AdminCommuneInfoController::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 type MairieService est défini dans dmv-workspace/types/index.ts:106, mais aucun appel vers /mairie/communes/{communeId}/services ou /mairie/acteurs/{acteurId}/communes/{communeId}/services n'a été trouvé dans le code source d'aucun des trois frontends. La page workspace acteur/[slug]/services/page.tsx existe mais consomme un type différent (ActeurService, capacité Actor générique, sans lien avec mairie_services — cf. ENG-001.5 §6.1).
  • Publications mairie : acteur/[slug]/publications/page.tsx (dmv-workspace) est une redirection pure vers tableau-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

RessourceTable(s)Chemins d'écriture actuelsFichiers concernés (estimé)ComplexitéRisqueConsommateurs connusDépendance frontendDépendance AdminDépendance Territory
Services municipauxmairie_services1 (Mairie uniquement)~9 (contrôleur, 2 Requests, service×2 portions, modèle, DTO Mairie + DTO/contrat Municipal Management déjà stubés)FaibleFaibleAucun consommateur frontend confirmé (§2.5) ; lecture publique existante non vérifiée en usage réelIncertaine — voir 13.FAucune (Admin n'écrit jamais mairie_services)Aucune
Collectescommune_collectes2 (Mairie, Admin)~7 (portion MairieCommuneController/MairieReadService/MairieWriteService, portion AdminCommuneInfoController, modèle CommuneCollecte, DTO/contrat déjà stubés)ModéréeModé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 TerritoryOui — dmv-workspace (CRUD complet)Oui — CRUD complet à unifierLecture seule (TerritoryService::getCommuneCollectes()), aucune écriture
Éluscommune_elus3 (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-publicOui — dmv-workspaceOui — CRUD complet à unifierOui — écriture à faire déléguer (lève la limitation temporaire ENG-001.4 §11.E)
Infos pratiquescommune_infos3 (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 Mairiedmv-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-workspaceOui — CRUD complet à unifierOui — écriture à faire déléguer
commune_info_sectionscommune_info_sections1 (Admin uniquement)1 (portion AdminCommuneInfoController, ~75 lignes) — aucun modèle EloquentFaible en soi, mais couplée à Infos pratiquesFaible isolément ; bloque Infos pratiques tant que 13.C n'est pas tranchéAdmin uniquementAucune détectéeOui — seul consommateur actuelAucune

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 de FormRequest, contrairement à CreateServiceRequest/ReorderServicesRequest qui 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.

  1. 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).
  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), AdminCommuneInfoController touché 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.

  1. Migrer mairie_services normalement (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.
  2. 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).
  3. 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)

RisqueProbabilitéImpactMitigation
Municipal-I (élus) ou Municipal-J (infos) casse la réconciliation SIRENE par une divergence non détectéeMoyenne — 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éellesConstruire 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 taxonomieBloquer explicitement Municipal-J sur 13.C (§4, §7)
Effort investi dans Municipal-G (services) sur une fonctionnalité inutilisée en productionInconnue — 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/JMoyenne — le chemin Mairie n'a aujourd'hui aucune validation explicite (§3.2)Moyen — comportement de validation qui change silencieusement au moment de l'unificationTrancher 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 partielleFaible si testé explicitementMoyen — casserait la vue agrégée du backofficeInclure 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 --check et npm run build passent (§10).
  • Aucun code applicatif n'a été modifié. Aucun commit n'a été créé.

Références