ENG-001.5 — Extraction du Bounded Context Municipal Management
1. Métadonnées de mission
| Epic | EPIC-001 — Restructuration de DMV Core |
| Mission | ENG-001.5 |
| Type | Spécification (documentaire uniquement) |
| Priorité | P0 |
| Dépendances | ENG-001.4 — Territory Bounded Context (implémenté, PR dmv_api#14 ouverte) |
| Statut | Spécification produite — aucune implémentation |
| Code applicatif | Aucun 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 decommune_elus/commune_collectes/commune_infos/communes.description/communes.image_urlviaMairieWriteService(SQL brut, sans passer par les modèles Eloquent Territory). - Territory : depuis ENG-001.4, n'écrit plus dans
acteurs, mais continue d'écrirecommune_elus/commune_infosvia 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_infosetcommune_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) :
ActeurModulesControllermaintient une API complète et indépendante surmairie_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èle | Fichier | Table | Module actuel |
|---|---|---|---|
MairieAlerte | Mairie/Models/MairieAlerte.php | mairie_alertes | Mairie |
MairieService | Mairie/Models/MairieService.php | mairie_services | Mairie |
CommuneElu | Territory/Models/CommuneElu.php | commune_elus | Territory (emplacement physique), cible Municipal Management |
CommuneCollecte | Territory/Models/CommuneCollecte.php | commune_collectes | Territory (emplacement physique), cible Municipal Management |
CommuneInfo | Territory/Models/CommuneInfo.php | commune_infos | Territory (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
| Service | Fichier | Responsabilité | Owner cible |
|---|---|---|---|
MairieWriteService | Mairie/Services/MairieWriteService.php | Écriture alertes, services, commune (description/image_url), élus, collectes, infos ; délègue les publications à PublicationWriteService | Scindé — 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é |
MairieReadService | Mairie/Services/MairieReadService.php | Lecture alertes, services, élus, collectes, infos, publications | Scindé de la même manière |
CommuneMairieDataRefreshService | Territory/Services/CommuneMairieDataRefreshService.php | Orchestre 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éé |
AdminCommuneService | Admin/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.php | CRUD 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.php | CRUD 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ôleur | Routes | Middleware | Table(s) |
|---|---|---|---|
MairieAlerteController | GET /mairie/communes/{communeId}/alertes (public) ; POST/PATCH/DELETE /mairie/...alertes... | identify.app (lecture) ; identify.app+auth:sanctum+mairie.access (écriture) | mairie_alertes |
MairieServiceController | GET /mairie/communes/{communeId}/services (public) ; POST/PATCH /mairie/...services... | idem | mairie_services |
MairieCommuneController | GET/PATCH /mairie/communes/{communeId} ; CRUD élus/collectes/infos ; GET .../services/all | identify.app+auth:sanctum+commune.manager | communes (partiel), commune_elus, commune_collectes, commune_infos |
MairiePublicationController | GET/POST /mairie/acteurs/{acteurId}/publications ; PATCH .../moderate | mairie.access | publications (délégué à Publication, hors périmètre) |
AdminCommuneController | POST/PATCH /admin/communes/... ; GET/PUT/DELETE .../mairie ; GET .../mairie-detect ; POST .../mairie-confirm, .../refresh-mairie-data | Auth Admin | communes (dont mairie_actor_id) — hors périmètre Municipal Management |
AdminCommuneInfoController | CRUD complet /admin/communes/{id}/infos|elus|collectes + /admin/commune-sections | Auth Admin | commune_infos, commune_elus, commune_collectes, commune_info_sections |
ActeurModulesController | GET /acteurs/{acteurId}/alertes (public) ; POST/PATCH .../alertes... | identify.app (lecture) ; identify.app+auth:sanctum + ActeurPolicy::update (écriture) — pas mairie.access | mairie_alertes (dupliqué) |
6.4 Jobs
| Job | Fichier | Fréquence | Action |
|---|---|---|---|
ExpireAlertes | Mairie/Jobs/ExpireAlertes.php | Horaire (ShouldQueue) | MairieWriteService::expireAlertes() — désactive les alertes expirées. Propre, délègue correctement, aucune écriture croisée. |
6.5 Commandes Artisan
| Commande | Fichier | Rôle |
|---|---|---|
communes:refresh-mairie-sirene | Console/Commands/RefreshMairieSireneCommand.php | Déclenche CommuneMairieDataRefreshService::refresh() — écrit encore dans commune_elus/commune_infos (temporaire) |
communes:resolve-mairies | Console/Commands/ResolveMairiesCommand.php | Dé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
| Middleware | Fichier | Enregistrement | Rôle |
|---|---|---|---|
EnsureMairieAccess | Mairie/Middleware/EnsureMairieAccess.php | mairie.access (bootstrap/app.php:46) | Vérifie kind='mairie' + ownership acteur, résout l'acteur depuis divers paramètres de route |
EnsureCommuneManager | Mairie/Middleware/EnsureCommuneManager.php | commune.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
| Contrat | Contexte | Pertinence pour Municipal Management |
|---|---|---|
TerritoryReader::getCommune() | Territory | Lecture 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ée | Owner actuel | Owner cible | Lecture | Écriture | Dépendances | Migration future |
|---|---|---|---|---|---|---|
mairie_alertes | Mairie + Actor (dupliqué) | Municipal Management | Mairie (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_services | Mairie | Municipal Management | Mairie (MairieReadService) | Mairie (MairieWriteService) | Actor (référence implicite via commune_id) | Déplacement direct, pas de duplication connue |
commune_elus | Fragmenté (Mairie, Admin, Territory) | Municipal Management | Territory (TerritoryService, public), Mairie (MairieReadService), Admin | Mairie, Admin, Territory (SIRENE, temporaire) | Territory (référence commune_id) | Le chantier le plus lourd — 3 chemins d'écriture à unifier (§13.F) |
commune_collectes | Fragmenté (Mairie, Admin) | Municipal Management | Territory (public), Mairie, Admin | Mairie, Admin | Territory (référence commune_id) | 2 chemins à unifier — Territory n'y écrit jamais |
commune_infos | Fragmenté (Mairie, Admin, Territory) | Municipal Management | Territory (public, fusionne des champs Actor pour section='mairie'), Mairie, Admin | Mairie, Admin, Territory (SIRENE, temporaire) | Territory (référence commune_id), Actor (lecture de contact, côté Territory) | 3 chemins à unifier |
commune_info_sections | Admin exclusivement | À arbitrer (ENG-001.4 §11.C, non résolu) | Admin uniquement | Admin uniquement | commune_infos.section (référence logique, pas de FK) | Dépend de l'arbitrage 11.C avant tout déplacement |
communes.mairie_actor_id | Territory (conforme, ENG-001.4) | Territory (inchangé) | Territory, Mairie (middleware), Admin | Territory, Admin | Actor (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}/alertes→indexAlertes(), lecture directe viaMairieAlerte::query().POST /api/v1/acteurs/{acteurId}/alertes→storeAlerte(), écrit directement viaMairieAlerte::create().PATCH /api/v1/acteurs/{acteurId}/alertes/{alerteId}→updateAlerte(), écrit directement.PATCH /api/v1/acteurs/{acteurId}/alertes/{alerteId}/desactiver→desactiverAlerte(), é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
| Violation | Fichier(s) | Statut |
|---|---|---|
Mairie écrit commune_elus/commune_collectes/commune_infos en SQL brut, sans passer par les modèles Eloquent Territory | MairieWriteService.php:233-351 | Confirmé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_sections | AdminCommuneInfoController.php | Confirmée, découverte par ENG-001.4, toujours non corrigée |
Territory écrit encore commune_elus/commune_infos via le rafraîchissement SIRENE | CommuneMairieDataRefreshService.php (refreshElus(), refreshInfos()/upsertInfo()) | Confirmée, explicitement temporaire (ENG-001.4 §11.E, dans l'attente de Municipal Management) |
Mairie écrit communes.description/image_url | MairieWriteService.php:216-229 | Hors 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
Mairieau-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 champscommunes.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.
- 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}/alertespour 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. - 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. - 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_alertestant 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-backofficeetapia é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.
- 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. - 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.
- 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.
- 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.
- 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_alertesrestera nommée ainsi même possédée par Municipal Management). - 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
| Risque | Probabilité | Impact | Mitigation |
|---|---|---|---|
| La duplication Actor/Mairie (§8.1, §13.A) n'est pas résolue avant la migration physique des alertes | Moyenne si non arbitrée à temps | Élevé — Municipal Management hériterait d'une propriété non exclusive dès sa création | Bloquer 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ée | Moyenne — 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éelles | Auditer 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érification | Vé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 mairie | Suivre 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) :
- Municipal-A : le module se charge (bootstrap complet), aucune route, aucun test existant ne change de comportement.
- 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 à
MairieAlertedepuis Actor (test architectural, sur le même principe queTerritoryActorBoundaryTestd'ENG-001.4). - Municipal-C :
ExpireAlertestoujours fonctionnel après déplacement ; les endpointsMairieAlerteController/MairieServiceControllerrenvoient des réponses identiques avant/après ; aucune écriture directe résiduelle depuis Mairie surmairie_alertes/mairie_services. - 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 directecommune_elus/commune_infosen dehors de Municipal Management. - Municipal-E : les deux anciens chemins (Mairie, Admin) sur
commune_collectesproduisent un résultat identique via le chemin unique. - 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.
- 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 --testsur 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 --checketnpm 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.
| Point | Nature | Bloque |
|---|---|---|
13.A — duplication Actor/Mairie sur mairie_alertes | Ownership effectif, potentiel changement d'API | PR Municipal-C |
| 13.B — découpage exact des sous-PR | Séquencement de la migration | L'ordre d'exécution du chantier |
13.C — ownership de commune_info_sections (rappel ENG-001.4) | Ownership d'une donnée non anticipée | PR Municipal-D (sous-partie) |
| 13.D — moment de déplacement des middleware d'accès | Séquencement, Platform Service Authorization | PR Municipal-C (sous réserve) |
| 13.E — renommage éventuel des tables | Migration de données potentielle | Aucune 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
- ENG-001-1 — Rapport d'audit DMV Core §4, §6, §8, §10 (PR-003, PR-004)
- ENG-001-4 — Territory Bounded Context
- ENG-001-4 — Rapport d'implémentation
- Audit des consommateurs de l'API des alertes mairie
- EPIC-001 — Restructuration de DMV Core
- ADR-014 — DMV Core : Bounded Contexts et Monolithe Modulaire
- Cartographie des Bounded Contexts réels de DMV Core
- Roadmap de migration DMV Core
- RFC-001 — Extraction du Bounded Context Municipal Management