Aller au contenu principal

ENG-001.5 — Extraction du Bounded Context Municipal Management

1. Métadonnées de mission

EpicEPIC-001 — Restructuration de DMV Core
MissionENG-001.5
TypeSpécification (documentaire uniquement)
PrioritéP0
DépendancesENG-001.4 — Territory Bounded Context (implémenté, PR dmv_api#14 ouverte)
StatutSpécification produite — aucune implémentation
Code applicatifAucun changement (mission documentaire)

Cette spécification prépare l'extraction du Bounded Context Municipal Management, acté par RFC-001 mais jamais implémenté. Elle ne crée aucun module, aucun contrat, aucune migration.


2. Objectif

Définir précisément, à partir du code réel :

  • ce qui appartient réellement à Municipal Management ;
  • ce qui reste dans Territory (déjà mis en conformité par ENG-001.4) ;
  • ce qui reste dans Actor ;
  • ce qui devra être migré plus tard, et selon quel découpage de PR.

3. Contexte

RFC-001 a acté la création d'un Bounded Context Municipal Management, propriétaire exclusif des élus, collectes, informations pratiques, services municipaux et alertes municipales, référençant Territory et Actor en lecture seule. ENG-001-1-dmv-core-audit-report.md (§4, §6, §8, §10 — PR-004) et 28-dmv-core-bounded-context-map.md (§3, §5, §6) documentent la fragmentation actuelle entre Mairie, Territory et Admin. ENG-001.4 (ENG-001-4-territory-bounded-context.md, implémentation en PR) a déjà corrigé les écritures croisées Territory → Actor et documenté, sans les corriger, les écritures municipales restantes — cette spécification en repart directement.

ENG-001.2 (contrats inter-contextes) a explicitement reporté les contrats MunicipalManagementReader/MunicipalManagementWriter (Option B) au motif que Municipal Management n'existait pas encore comme module — cette mission prépare cette création, sans la réaliser.


4. Décisions déjà validées

Reprises telles quelles, non remises en question par cette spécification :

  • Une mairie est un Actor.
  • Territory possède le territoire (déjà mis en conformité, ENG-001.4).
  • Municipal Management possède le métier municipal (RFC-001).
  • Actor possède l'organisation.
  • Aucun contrat ne doit être créé vers un contexte inexistant.

Clarification découlant de ces décisions. 28-dmv-core-bounded-context-map.md §5 avait envisagé, à titre d'interprétation (confiance qualifiée « faible » par le document lui-même), que les alertes et services municipaux « devraient être exposées comme des extensions du contrat d'Actor ». RFC-001, document de décision postérieur, tranche explicitement en sens contraire : alertes et services municipaux appartiennent à Municipal Management, pas à Actor. Cette spécification suit RFC-001, cohérent avec la décision déjà validée ci-dessus — ce point n'est donc pas rouvert comme décision à arbitrer (voir en revanche §13.A, qui porte sur la duplication actuelle, pas sur l'ownership cible).


5. État actuel

Le métier municipal est aujourd'hui fragmenté entre trois modules, avec une découverte supplémentaire par rapport aux audits antérieurs :

  • Mairie : propriétaire de fait de mairie_alertes, mairie_services, et co-écrivain de commune_elus/commune_collectes/commune_infos/communes.description/communes.image_url via MairieWriteService (SQL brut, sans passer par les modèles Eloquent Territory).
  • Territory : depuis ENG-001.4, n'écrit plus dans acteurs, mais continue d'écrire commune_elus/commune_infos via le rafraîchissement SIRENE automatisé (CommuneMairieDataRefreshService) — décision explicitement actée comme temporaire par ENG-001.4 §11.E, dans l'attente de l'existence de Municipal Management.
  • Admin : chemin d'écriture complet et autonome sur commune_elus, commune_collectes, commune_infos et commune_info_sections (AdminCommuneInfoController), découvert lors d'ENG-001.4, non corrigé (hors périmètre de cette mission-là).
  • Actor (découverte de cette mission, non documentée par les audits antérieurs au-delà d'une seule ligne) : ActeurModulesController maintient une API complète et indépendante sur mairie_alertes — lecture, création, modification, désactivation — entièrement distincte de celle de Mairie. Voir §8.1 pour le détail.

Aucun module MunicipalManagement n'existe. Aucun modèle, aucun contrat, aucune route ne portent ce nom dans le code actuel.


6. Cartographie

6.1 Modèles

ModèleFichierTableModule actuel
MairieAlerteMairie/Models/MairieAlerte.phpmairie_alertesMairie
MairieServiceMairie/Models/MairieService.phpmairie_servicesMairie
CommuneEluTerritory/Models/CommuneElu.phpcommune_elusTerritory (emplacement physique), cible Municipal Management
CommuneCollecteTerritory/Models/CommuneCollecte.phpcommune_collectesTerritory (emplacement physique), cible Municipal Management
CommuneInfoTerritory/Models/CommuneInfo.phpcommune_infosTerritory (emplacement physique), cible Municipal Management
(aucun modèle Eloquent)commune_info_sectionsÉcrite/lue exclusivement en DB::table() brut depuis Admin

Aucun modèle ActeurInfo/ActeurService (tables acteur_infos/acteur_services, module Actor) n'est concerné par cette spécification : ce sont des capacités génériques Actor, ouvertes à tout type d'acteur, sans lien avec le métier municipal — à ne pas confondre avec MairieService/mairie_alertes, qui portent un nom proche mais une donnée différente.

6.2 Services

ServiceFichierResponsabilitéOwner cible
MairieWriteServiceMairie/Services/MairieWriteService.phpÉcriture alertes, services, commune (description/image_url), élus, collectes, infos ; délègue les publications à PublicationWriteServiceScindé — alertes/services/élus/collectes/infos → Municipal Management ; commune → hors périmètre (ENG-001.4 §11.B) ; publications → déjà correctement délégué, inchangé
MairieReadServiceMairie/Services/MairieReadService.phpLecture alertes, services, élus, collectes, infos, publicationsScindé de la même manière
CommuneMairieDataRefreshServiceTerritory/Services/CommuneMairieDataRefreshService.phpOrchestre le rafraîchissement SIRENE ; écrit encore commune_elus/commune_infos (temporaire, ENG-001.4 §11.E)Territory conserve l'orchestration ; les écritures municipales devront être déléguées à Municipal Management une fois créé
AdminCommuneServiceAdmin/Services/AdminCommuneService.phpÉcrit communes (dont mairie_actor_id)Hors périmètre (Territory), inchangé
AdminCommuneInfoController (pas de service dédié — logique dans le contrôleur)Admin/Controllers/AdminCommuneInfoController.phpCRUD complet élus/collectes/infos/sectionsÀ rediriger vers les contrats Municipal Management (Admin devient adaptateur, PR-008 du plan ENG-001.1 — hors périmètre direct de cette mission, mais dépendant de son résultat)
ActeurModulesController (logique inline, pas de service dédié)Actor/Controllers/ActeurModulesController.phpCRUD complet alertes (dupliqué avec Mairie) + infos/services génériques Actor (hors sujet)Voir §8.1 et §13.A

6.3 Contrôleurs et routes

ContrôleurRoutesMiddlewareTable(s)
MairieAlerteControllerGET /mairie/communes/{communeId}/alertes (public) ; POST/PATCH/DELETE /mairie/...alertes...identify.app (lecture) ; identify.app+auth:sanctum+mairie.access (écriture)mairie_alertes
MairieServiceControllerGET /mairie/communes/{communeId}/services (public) ; POST/PATCH /mairie/...services...idemmairie_services
MairieCommuneControllerGET/PATCH /mairie/communes/{communeId} ; CRUD élus/collectes/infos ; GET .../services/allidentify.app+auth:sanctum+commune.managercommunes (partiel), commune_elus, commune_collectes, commune_infos
MairiePublicationControllerGET/POST /mairie/acteurs/{acteurId}/publications ; PATCH .../moderatemairie.accesspublications (délégué à Publication, hors périmètre)
AdminCommuneControllerPOST/PATCH /admin/communes/... ; GET/PUT/DELETE .../mairie ; GET .../mairie-detect ; POST .../mairie-confirm, .../refresh-mairie-dataAuth Admincommunes (dont mairie_actor_id) — hors périmètre Municipal Management
AdminCommuneInfoControllerCRUD complet /admin/communes/{id}/infos|elus|collectes + /admin/commune-sectionsAuth Admincommune_infos, commune_elus, commune_collectes, commune_info_sections
ActeurModulesControllerGET /acteurs/{acteurId}/alertes (public) ; POST/PATCH .../alertes...identify.app (lecture) ; identify.app+auth:sanctum + ActeurPolicy::update (écriture) — pas mairie.accessmairie_alertes (dupliqué)

6.4 Jobs

JobFichierFréquenceAction
ExpireAlertesMairie/Jobs/ExpireAlertes.phpHoraire (ShouldQueue)MairieWriteService::expireAlertes() — désactive les alertes expirées. Propre, délègue correctement, aucune écriture croisée.

6.5 Commandes Artisan

CommandeFichierRôle
communes:refresh-mairie-sireneConsole/Commands/RefreshMairieSireneCommand.phpDéclenche CommuneMairieDataRefreshService::refresh() — écrit encore dans commune_elus/commune_infos (temporaire)
communes:resolve-mairiesConsole/Commands/ResolveMairiesCommand.phpDéclenche CommuneMairieResolverService::resolve() — écrit uniquement communes.mairie_actor_id (Territory, conforme)

Aucune commande ni job propre à Municipal Management n'existe. Aucune commande Import ne touche les tables municipales (vérifié par recherche exhaustive : grep sur mairie_alertes, mairie_services, commune_elus, commune_collectes, commune_infos dans api/app/Modules/Import — aucun résultat).

6.6 Middleware

MiddlewareFichierEnregistrementRôle
EnsureMairieAccessMairie/Middleware/EnsureMairieAccess.phpmairie.access (bootstrap/app.php:46)Vérifie kind='mairie' + ownership acteur, résout l'acteur depuis divers paramètres de route
EnsureCommuneManagerMairie/Middleware/EnsureCommuneManager.phpcommune.manager (bootstrap/app.php:47)Vérifie le rôle municipal_manager + accès à la commune (lit communes.mairie_actor_id directement)

Les deux réutilisent ActorAccessService (Actor) pour les vérifications de permission fines — cohérent avec la Phase 2 de 30-dmv-core-migration-roadmap.md (Platform Service Authorization, hors périmètre de cette mission).

6.7 Contrats existants consultables

ContratContextePertinence pour Municipal Management
TerritoryReader::getCommune()TerritoryLecture territoriale de référence, déjà disponible
ActorReader::findById(), findBySiret(), findBySiren()Actor (ENG-001.4)Lecture d'acteur par identifiant technique, réutilisable telle quelle si Municipal Management doit résoudre un acteur mairie par id
ActorWriter::applySireneRefresh()Actor (ENG-001.4)Pattern de référence (DTO minimal, méthode dédiée sans effet de bord) à reproduire pour toute future écriture croisée

Aucun contrat MunicipalManagementReader/MunicipalManagementWriter n'existe (reporté par ENG-001.2, Option B, toujours valable).


7. Ownership des données

DonnéeOwner actuelOwner cibleLectureÉcritureDépendancesMigration future
mairie_alertesMairie + Actor (dupliqué)Municipal ManagementMairie (MairieReadService), Actor (ActeurModulesController::indexAlertes)Mairie (MairieWriteService), Actor (ActeurModulesController, écriture directe Eloquent)Actor (référence acteur_id, commune_id)Résoudre la duplication (§13.A) avant tout déplacement physique
mairie_servicesMairieMunicipal ManagementMairie (MairieReadService)Mairie (MairieWriteService)Actor (référence implicite via commune_id)Déplacement direct, pas de duplication connue
commune_elusFragmenté (Mairie, Admin, Territory)Municipal ManagementTerritory (TerritoryService, public), Mairie (MairieReadService), AdminMairie, Admin, Territory (SIRENE, temporaire)Territory (référence commune_id)Le chantier le plus lourd — 3 chemins d'écriture à unifier (§13.F)
commune_collectesFragmenté (Mairie, Admin)Municipal ManagementTerritory (public), Mairie, AdminMairie, AdminTerritory (référence commune_id)2 chemins à unifier — Territory n'y écrit jamais
commune_infosFragmenté (Mairie, Admin, Territory)Municipal ManagementTerritory (public, fusionne des champs Actor pour section='mairie'), Mairie, AdminMairie, Admin, Territory (SIRENE, temporaire)Territory (référence commune_id), Actor (lecture de contact, côté Territory)3 chemins à unifier
commune_info_sectionsAdmin exclusivementÀ arbitrer (ENG-001.4 §11.C, non résolu)Admin uniquementAdmin uniquementcommune_infos.section (référence logique, pas de FK)Dépend de l'arbitrage 11.C avant tout déplacement
communes.mairie_actor_idTerritory (conforme, ENG-001.4)Territory (inchangé)Territory, Mairie (middleware), AdminTerritory, AdminActor (référence)Hors périmètre — Municipal Management le consommera en lecture seule une fois créé

8. Violations

8.1 Violation majeure découverte par cette mission : duplication Actor / Mairie sur mairie_alertes

Constat. ActeurModulesController (module Actor) expose une API REST complète et indépendante sur mairie_alertes :

  • GET /api/v1/acteurs/{acteurId}/alertesindexAlertes(), lecture directe via MairieAlerte::query().
  • POST /api/v1/acteurs/{acteurId}/alertesstoreAlerte(), écrit directement via MairieAlerte::create().
  • PATCH /api/v1/acteurs/{acteurId}/alertes/{alerteId}updateAlerte(), écrit directement.
  • PATCH /api/v1/acteurs/{acteurId}/alertes/{alerteId}/desactiverdesactiverAlerte(), écrit directement.

Cette API est entièrement distincte de celle de MairieAlerteController (/api/v1/mairie/acteurs/{acteurId}/alertes) : routes différentes, autorisation différente (ActeurPolicy::update générique plutôt que mairie.access), et surtout aucun passage par MairieWriteService/MairieReadService — accès direct au modèle Eloquent Mairie\Models\MairieAlerte depuis le module Actor. Seul le filtre findMairieActeurOrFail() (kind='mairie') restreint son usage aux acteurs mairie.

C'est la violation « Actor écrit dans MairieAlerte » déjà signalée par ENG-001-1-dmv-core-audit-report.md §4/§8 (PR-003), mais celui-ci ne citait qu'une ligne d'écriture (ActeurModulesController.php:236 dans sa numérotation) — l'analyse du code réel montre qu'il s'agit en réalité d'une API parallèle complète (4 endpoints, lecture et écriture), pas d'un correctif ponctuel. Gravité revue à la hausse par rapport à l'audit initial.

Pourquoi c'est bloquant pour cette mission. Municipal Management ne peut devenir propriétaire exclusif de mairie_alertes tant que deux modules différents croient légitimement pouvoir y écrire, avec deux mécanismes d'autorisation différents. Ce point doit être tranché avant toute migration physique — voir §13.A.

8.2 Violations déjà connues, confirmées inchangées

ViolationFichier(s)Statut
Mairie écrit commune_elus/commune_collectes/commune_infos en SQL brut, sans passer par les modèles Eloquent TerritoryMairieWriteService.php:233-351Confirmée, inchangée depuis ENG-001.4
Admin possède un chemin d'écriture complet et autonome sur commune_elus/commune_collectes/commune_infos/commune_info_sectionsAdminCommuneInfoController.phpConfirmée, découverte par ENG-001.4, toujours non corrigée
Territory écrit encore commune_elus/commune_infos via le rafraîchissement SIRENECommuneMairieDataRefreshService.php (refreshElus(), refreshInfos()/upsertInfo())Confirmée, explicitement temporaire (ENG-001.4 §11.E, dans l'attente de Municipal Management)
Mairie écrit communes.description/image_urlMairieWriteService.php:216-229Hors périmètre (ownership non tranché, ENG-001.4 §11.B)

Aucune nouvelle violation d'écriture croisée n'a été trouvée au-delà de celles déjà listées et de la duplication Actor/Mairie (§8.1).


9. Futur Bounded Context Municipal Management

Repris de RFC-001, précisé par cette analyse.

Responsabilités

  • Élus (commune_elus).
  • Collectes (commune_collectes).
  • Informations pratiques (commune_infos), y compris leur taxonomie (commune_info_sections, sous réserve de l'arbitrage §13.C).
  • Services municipaux (mairie_services).
  • Alertes municipales (mairie_alertes).

Références (lecture seule, jamais de modification)

  • territory_id (Territory — commune).
  • actor_id (Actor — acteur mairie).

Responsabilités explicitement exclues

  • Ne décide pas de la diffusion publique, des notifications, ni de l'indexation (RFC-001).
  • Ne modifie jamais le territoire ni l'identité de la mairie.
  • Ne possède pas communes.mairie_actor_id (reste Territory).
  • Ne possède pas les permissions génériques (Authorization, hors périmètre).
  • Ne possède pas acteur_infos/acteur_services (Actor, générique, sans rapport).

10. Contrats futurs

Noms cibles repris de RFC-001, non créés par cette mission (Option B, comme pour ENG-001.2) :

  • MunicipalManagementReader — lecture des élus, collectes, infos, services, alertes d'une commune ou d'un acteur mairie. Signatures indicatives, non validées : getElus(communeId), getCollectes(communeId), getInfos(communeId), getServices(communeId), getAlertes(communeId).
  • MunicipalManagementWriter — écriture des mêmes ressources, exclusivement consommée par les contrôleurs Mairie et, à terme, par l'adaptateur Admin.

Événements cibles cités par RFC-001 (MunicipalAlertPublished, MunicipalAlertUpdated, MunicipalOfficialUpdated, MunicipalCollectionUpdated, MunicipalInformationUpdated) : hors périmètre de toute PR issue de cette spécification tant que la Phase 1.3 (événements en mémoire, 30-dmv-core-migration-roadmap.md) n'est pas généralisée — à ne pas anticiper.

Aucun binding, aucune interface, aucun fichier de contrat ne doit être créé avant la Phase A du plan de migration (§12).


11. Hors périmètre

  • Création effective du module Municipal Management (préparée, pas réalisée).
  • Toute migration SQL, y compris de renommage de table ou de colonne.
  • Refonte de Mairie au-delà de ce que ce document planifie.
  • Correction des écritures Admin (documentées, pas corrigées).
  • Tranchage de l'ownership de commune_info_sections (§13.C), de la relation commune ↔ acteur mairie (hérité d'ENG-001.4 §11.A), ou des champs communes.description/image_url/site_web/ email_contact/telephone (hérité d'ENG-001.4 §11.B).
  • Introduction d'événements métier durables (Transactional Outbox).
  • Refonte du rafraîchissement SIRENE (ENG-001.4 §11.E — dépend de l'existence de Municipal Management, donc nécessairement postérieure).
  • Toute nouvelle API publique ou changement de comportement observable.

12. Plan de migration

Découpage en PR indépendantes, incrémentales, chacune testable et réversible séparément — conforme au principe « Migrations incrémentales » d'EPIC-001.

PR Municipal-A — Création du module (risque quasi nul)

Objectif. Créer la structure du module Municipal Management (dossiers, ServiceProvider vide enregistré, aucune route, aucun modèle). Aucun comportement modifié. Risques. Aucun — code mort tant que rien n'y est branché. Dépendances. Aucune. Critères d'acceptation. Le module existe, se charge, ne change aucune réponse API existante ; suite de tests intégralement verte sans modification.

PR Municipal-B — Résolution de la duplication Actor/Mairie sur les alertes

Objectif. Trancher et implémenter l'arbitrage §13.A : soit ActeurModulesController délègue ses quatre endpoints alertes à MairieWriteService/MairieReadService (ou à leurs équivalents Municipal Management une fois PR Municipal-D réalisée), soit ces endpoints sont retirés d'Actor. Ne déplace aucune donnée. Risques. Rupture d'un client existant si un consommateur (Workspace, mobile) utilise spécifiquement l'API /acteurs/{id}/alertes plutôt que /mairie/... — à vérifier côté frontend avant implémentation. Dépendances. Arbitrage §13.A rendu. Critères d'acceptation. Un seul mécanisme d'écriture effectif sur mairie_alertes ; aucune régression sur les deux surfaces API existantes (redirection transparente ou dépréciation documentée, selon l'arbitrage).

PR Municipal-C — Déplacement d'Alertes et Services municipaux

Objectif. Déplacer MairieAlerte, MairieService, les portions correspondantes de MairieWriteService/MairieReadService, et ExpireAlertes, vers le module Municipal Management. Tables inchangées (mairie_alertes, mairie_services). Introduction des premiers contrats MunicipalManagementReader/Writer a minima pour ces deux ressources. Risques. Faible — capacités déjà autonomes (doc 28 §5), mouvement à faible risque une fois Municipal-B réalisée. Dépendances. PR Municipal-A, PR Municipal-B. Critères d'acceptation. Mairie n'écrit plus mairie_alertes/mairie_services directement ; ExpireAlertes fonctionne à l'identique ; comportement API inchangé.

PR Municipal-D — Unification des écritures Élus / Infos pratiques

Objectif. Unifier les trois chemins d'écriture concurrents sur commune_elus et commune_infos (Mairie, Admin, Territory-SIRENE) en un seul, porté par Municipal Management. Déplacer CommuneElu/CommuneInfo (modèles) vers le module. Adapter CommuneMairieDataRefreshService pour déléguer au contrat Municipal Management au lieu d'écrire directement (levée de la limitation temporaire d'ENG-001.4 §11.E). Risques. Le plus élevé du plan — migration de propriété de données réelles, trois consommateurs actuels (Mairie, Admin, Territory), risque de divergence de comportement si les trois chemins ne validaient pas les données de façon strictement identique (à auditer avant migration). Dépendances. PR Municipal-A ; arbitrage §13.C si commune_info_sections doit être inclus au même mouvement. Critères d'acceptation. Un seul chemin d'écriture effectif ; aucune donnée perdue ; le rafraîchissement SIRENE continue de fonctionner à l'identique ; tests de non-régression sur les trois anciens chemins.

PR Municipal-E — Unification des écritures Collectes

Objectif. Unifier les deux chemins d'écriture (Mairie, Admin) sur commune_collectes. Risques. Modéré — deux consommateurs seulement, Territory n'y touche jamais (plus simple que Municipal-D). Dépendances. PR Municipal-A. Critères d'acceptation. Un seul chemin d'écriture effectif ; comportement inchangé.

PR Municipal-F — Bascule d'Admin vers les contrats Municipal Management

Objectif. Remplacer les écritures directes d'AdminCommuneInfoController par des appels aux contrats MunicipalManagementWriter. Recoupe le futur PR-008 (« Admin → adaptateur ») du plan de ENG-001-1-dmv-core-audit-report.md §10 — à coordonner, pas à dupliquer. Risques. Modéré — surface Admin large (135 routes au total sur le module, dont une partie seulement concernée ici). Dépendances. PR Municipal-C, PR Municipal-D, PR Municipal-E. Critères d'acceptation. Admin ne modifie plus directement commune_elus/commune_collectes/ commune_infos/mairie_alertes/mairie_services ; comportement backoffice inchangé.

Chaque PR est indépendamment revertible (aucune ne casse l'application si elle est seule mergée et les suivantes reportées), conformément au principe « Migrations incrémentales » d'EPIC-001 et au principe de rollback de RFC-001 (« chaque étape est indépendante »).


13. Décisions ouvertes

Conformément à IA-GOUVERNANCE.md, chaque point est présenté avec constat, options, avantages, inconvénients, recommandation et décision attendue. Aucun n'est tranché par cette spécification.

Décision architecturale requise — 13.A : résolution de la duplication Actor/Mairie sur mairie_alertes

Constat. Voir §8.1. Deux API complètes, deux mécanismes d'autorisation, un seul modèle de données. C'est le blocage le plus concret à la prise d'ownership exclusive de Municipal Management sur mairie_alertes.

Options.

  1. Rediriger ActeurModulesController (4 endpoints alertes) pour qu'il délègue à MairieWriteService/MairieReadService (puis à Municipal Management une fois créé), au lieu d'accéder directement au modèle. Avantages : conserve la surface API /acteurs/{id}/alertes pour d'éventuels clients existants ; un seul mécanisme d'écriture réel derrière deux façades. Inconvénients : maintient deux surfaces API pour la même ressource indéfiniment, avec le risque qu'elles divergent à nouveau plus tard.
  2. Retirer les 4 endpoints d'Actor, ne conserver que l'API Mairie. Avantages : une seule surface API, plus simple, conforme à l'ownership cible sans ambiguïté durable. Inconvénients : rupture potentielle de compatibilité si un client (Workspace, mobile) consomme spécifiquement /acteurs/{id}/alertes — à vérifier côté frontend avant de choisir cette option ; c'est un changement d'API, explicitement hors périmètre d'une PR « sans changement fonctionnel » à moins d'une dépréciation documentée.
  3. Ne rien faire pour l'instant, documenter la duplication comme dette connue et reporter la décision à la PR Municipal-C. Avantages : ne bloque pas les PR Municipal-A à Municipal-C sur les autres ressources. Inconvénients : Municipal Management ne pourrait jamais revendiquer une propriété réellement exclusive de mairie_alertes tant que ce point reste ouvert.

Recommandation. Option 1 si un client consomme l'API Actor (à vérifier factuellement avant tout arbitrage — cette spécification ne l'a pas vérifié côté frontend, hors périmètre du code inspecté) ; sinon Option 2. Dans tous les cas, ne pas laisser cette duplication franchir la PR Municipal-C.

Vérification frontend réalisée. Un audit dédié couvrant dmv-workspace, dmv-public, dmv-backoffice et api a été produit : ENG-001-5-alert-api-consumer-audit.md. Il constate que l'API Actor est aujourd'hui la seule effectivement consommée (lecture et écriture, trois écrans actifs) et que l'API Mairie n'a aucun consommateur détecté dans ce périmètre — ce qui alimente l'arbitrage ci-dessus sans le trancher. La décision reste à rendre par l'architecte.

Décision attendue. Choix d'option, après vérification de l'usage réel de l'API Actor côté clients.


Décision architecturale requise — 13.B : découpage exact des sous-PR

Constat. Le découpage proposé en §12 (six PR) est une recommandation d'ingénierie, pas une décision actée — la mission demande explicitement un découpage « logique », sans en imposer un.

Options.

  1. Découpage proposé en §12 (par ressource : alertes+services d'abord, élus/infos ensuite, collectes séparément, Admin en dernier). Avantages : suit la gravité et la complexité croissante déjà analysée ; aligné avec RFC-001 §« Migration » (élus/collectes/infos/services ensemble, alertes séparément). Inconvénients : six PR à séquencer et coordonner.
  2. Découpage plus grossier : une PR pour toutes les ressources « simples et déjà autonomes » (alertes, services), une PR pour tout le reste (élus, collectes, infos, Admin) en une fois. Avantages : moins de PR à revoir. Inconvénients : la seconde PR redevient une « grosse PR » que la mission demande explicitement d'éviter, et mélange le chantier le plus risqué (élus/infos, 3 chemins concurrents) avec le plus simple (collectes, 2 chemins).

Recommandation. Option 1 (§12), déjà détaillée avec critères d'acceptation par sous-PR.

Décision attendue. Validation du découpage en six PR, ou ajustement explicite du séquençage.


Décision architecturale requise — 13.C : ownership de commune_info_sections (rappel ENG-001.4 §11.C)

Constat. Toujours non tranché depuis sa découverte en ENG-001.4. Cette mission confirme qu'aucun autre module que Admin ne la lit ni ne l'écrit, et qu'elle catalogue exclusivement commune_infos.section — donc potentiellement du ressort de Municipal Management, sans certitude produit.

Options. Identiques à celles déjà posées par ENG-001-4-territory-bounded-context.md §11.C — non reproduites ici pour éviter la duplication. Voir ce document pour le détail.

Recommandation. Trancher ce point avant ou pendant PR Municipal-D (qui déplace commune_infos, dont commune_info_sections est la taxonomie) plutôt que de le laisser en suspens indéfiniment.

Décision attendue. Confirmation de l'owner cible avant PR Municipal-D, ou décision explicite de la traiter dans une mission séparée après.


Décision architecturale requise — 13.D : faut-il unifier EnsureMairieAccess/EnsureCommuneManager en même temps ?

Constat. Ces deux middleware vivent dans Mairie/Middleware et sont enregistrés globalement (bootstrap/app.php). Ils resteront fonctionnellement nécessaires après extraction de Municipal Management (contrôle d'accès aux routes municipales), mais leur propriétaire de module devient ambigu si Mairie se réduit progressivement (RFC-001 : « le module historique Mairie disparaît progressivement »).

Options.

  1. Déplacer les deux middleware vers Municipal Management au moment de PR Municipal-C. Avantages : cohérence immédiate, pas de dépendance résiduelle vers un module en voie de disparition. Inconvénients : couple ce déplacement à une PR qui n'en avait pas besoin fonctionnellement.
  2. Les laisser dans Mairie jusqu'à l'extraction du Platform Service Authorization (Phase 2 de 30-dmv-core-migration-roadmap.md, indépendante et déjà planifiée). Avantages : un seul mouvement au lieu de deux (Mairie → Municipal Management → Authorization). Inconvénients : Mairie continue d'exister comme coquille technique plus longtemps que son contenu métier réel.

Recommandation. Option 2 — cohérent avec la recommandation déjà faite par ENG-001-4-territory-bounded-context.md §11.A (ne pas déplacer une responsabilité deux fois).

Décision attendue. Confirmation, ou préférence pour un déplacement anticipé.


Décision architecturale requise — 13.E : les tables changent-elles de nom ?

Constat. mairie_alertes, mairie_services, commune_elus, commune_collectes, commune_infos portent des noms hérités de leur historique (mairie_*) ou de leur emplacement physique actuel (commune_*, dans Territory). Aucune contrainte de cette mission n'impose de renommage, et « aucune migration SQL » est explicitement hors périmètre de toute PR issue de ce document sans validation séparée.

Options.

  1. Conserver les noms de table actuels, l'ownership devenant un concept de module/code, pas de schéma. Avantages : zéro migration SQL, zéro risque sur les données de production, cohérent avec la contrainte « aucune migration SQL » de cette mission et de la précédente. Inconvénients : les noms de table ne refléteront jamais l'ownership final (ex. mairie_alertes restera nommée ainsi même possédée par Municipal Management).
  2. Renommer progressivement (municipal_alertes, etc.) dans une mission dédiée, après stabilisation de l'ownership applicatif. Avantages : cohérence de nommage à terme. Inconvénients : migration de données réelle, hors périmètre explicite, risque opérationnel pour un bénéfice purement cosmétique.

Recommandation. Option 1 pour toutes les PR de cette spécification. Un éventuel renommage, si souhaité, doit faire l'objet d'une mission strictement dédiée, plus tard, jamais mêlée à un déplacement d'ownership.

Décision attendue. Confirmation qu'aucun renommage de table n'est demandé dans le cadre d'ENG-001.5.


14. Risques

RisqueProbabilitéImpactMitigation
La duplication Actor/Mairie (§8.1, §13.A) n'est pas résolue avant la migration physique des alertesMoyenne si non arbitrée à tempsÉlevé — Municipal Management hériterait d'une propriété non exclusive dès sa créationBloquer PR Municipal-C tant que PR Municipal-B n'est pas mergée (dépendance explicite, §12)
PR Municipal-D (élus/infos) casse un des trois chemins d'écriture existants par une divergence de validation non détectéeMoyenne — la mission n'a pas audité en détail les règles de validation de chacun des trois cheminsÉlevé — perte ou corruption de données municipales réellesAuditer les trois chemins avant implémentation (validation, valeurs par défaut, contraintes) ; tests de non-régression explicites par chemin d'origine
commune_info_sections migré sans arbitrage préalable (13.C ignoré)Faible si le plan est respectéFaible à ce stade (peu de volume)Bloquer PR Municipal-D sur cette sous-partie tant que 13.C n'est pas tranché
Un client frontend dépend de l'API Actor alertes (/acteurs/{id}/alertes) sans que cela soit visible depuis le seul code backend inspectéInconnue — non vérifiée par cette mission (hors périmètre : uniquement api/)Moyen à élevé si Option 2 de 13.A était choisie sans vérificationVérifier l'usage frontend avant de choisir entre les options de 13.A
Le middleware commune.manager/mairie.access casse pendant un déplacement de module mal séquencéFaible si 13.D est respecté (pas de déplacement anticipé)Moyen — casserait l'accès au dashboard mairieSuivre la recommandation 13.D (ne pas déplacer avant Authorization Platform Service)

15. Tests attendus

Pour la future implémentation, par sous-PR (voir §12 pour le détail des critères d'acceptation) :

  1. Municipal-A : le module se charge (bootstrap complet), aucune route, aucun test existant ne change de comportement.
  2. Municipal-B : les deux surfaces API alertes (Actor et Mairie) produisent un résultat strictement identique après résolution ; aucun accès direct résiduel à MairieAlerte depuis Actor (test architectural, sur le même principe que TerritoryActorBoundaryTest d'ENG-001.4).
  3. Municipal-C : ExpireAlertes toujours fonctionnel après déplacement ; les endpoints MairieAlerteController/MairieServiceController renvoient des réponses identiques avant/après ; aucune écriture directe résiduelle depuis Mairie sur mairie_alertes/ mairie_services.
  4. Municipal-D : les trois anciens chemins d'écriture (Mairie, Admin, Territory-SIRENE) produisent un résultat identique via le nouveau chemin unique ; le rafraîchissement SIRENE automatisé fonctionne à l'identique (élus ajoutés/mis à jour/désactivés — mêmes compteurs qu'avant, comme déjà testé par CommuneMairieDataRefreshTest) ; garde-fou architectural interdisant toute nouvelle écriture directe commune_elus/commune_infos en dehors de Municipal Management.
  5. Municipal-E : les deux anciens chemins (Mairie, Admin) sur commune_collectes produisent un résultat identique via le chemin unique.
  6. Municipal-F : Admin ne modifie plus directement les tables municipales ; parcours backoffice existants (création/modification élu, collecte, info, alerte, service depuis l'admin) inchangés.
  7. Transverse, à chaque PR : résolution des contrats concernés par le conteneur Laravel ; suite de tests complète verte (état de référence : 469 tests / 1726 assertions au 2026-08-01, fin d'ENG-001.4) ; vendor/bin/pint --test sur les fichiers touchés.

Aucun cas d'usage non présent dans le code actuel n'est ajouté.


16. Critères d'acceptation

Pour l'ensemble du chantier ENG-001.5 (au-delà des critères par sous-PR du §12) :

  • Municipal Management existe comme module autonome, propriétaire exclusif d'élus, collectes, infos pratiques, services municipaux, alertes municipales.
  • Aucune écriture directe résiduelle sur ces cinq ressources depuis Mairie, Territory, Admin ou Actor.
  • Aucun comportement utilisateur visible ne change à l'issue de l'ensemble des sous-PR.
  • Aucun endpoint public ne change (sauf dépréciation explicitement documentée si Option 2 de 13.A est retenue).
  • Aucune migration de données non validée.
  • Les données municipales existantes ne sont ni perdues ni altérées.
  • Le rafraîchissement SIRENE continue de fonctionner, avec Territory qui ne fait plus qu'orchestrer (fetch/transform/snapshot) sans écrire lui-même de donnée municipale.
  • Les tests existants restent verts à chaque étape.
  • Les décisions non tranchées par cette spécification (§13) restent explicitement reportées, pas contournées.

17. Définition de Done

Pour cette spécification (documentaire) :

  • Le code réel de Mairie, Territory, Admin, Actor a été lu et cartographié avec citations vérifiées, y compris une violation majeure non anticipée par les audits antérieurs (§8.1).
  • L'ownership de toutes les données municipales listées par la mission, plus commune_info_sections (déjà connue d'ENG-001.4), est classé.
  • Le futur Bounded Context est décrit (responsabilités, références, exclusions) sans être implémenté.
  • Un plan de migration en six PR indépendantes est proposé, chacune avec objectif, risques, dépendances et critères d'acceptation.
  • Cinq décisions architecturales sont explicitement soumises, non tranchées.
  • git diff --check et npm run build (liens stricts) passent.
  • Aucun code applicatif n'a été modifié. Aucun commit n'a été créé.

Pour le chantier d'implémentation (hors périmètre de cette mission, rappel) : voir §16.


18. Gouvernance des divergences

Conformément à IA-GOUVERNANCE.md, cette spécification ne tranche aucune des cinq décisions identifiées en §13. Chacune suit le format « Constat / Options / Avantages / Inconvénients / Recommandation / Décision attendue » exigé par la gouvernance.

PointNatureBloque
13.A — duplication Actor/Mairie sur mairie_alertesOwnership effectif, potentiel changement d'APIPR Municipal-C
13.B — découpage exact des sous-PRSéquencement de la migrationL'ordre d'exécution du chantier
13.C — ownership de commune_info_sections (rappel ENG-001.4)Ownership d'une donnée non anticipéePR Municipal-D (sous-partie)
13.D — moment de déplacement des middleware d'accèsSéquencement, Platform Service AuthorizationPR Municipal-C (sous réserve)
13.E — renommage éventuel des tablesMigration de données potentielleAucune PR de ce plan (implicitement non retenu sauf arbitrage contraire)

Aucune de ces décisions n'a été prise seule, conformément au principe directeur d'IA-GOUVERNANCE.md : « une implémentation incomplète mais correctement interrompue vaut toujours mieux qu'une implémentation terminée ayant pris une mauvaise décision d'architecture ».


Références