ENG-001.5A — Fondation de Municipal Management — Rapport d'implémentation
| Mission | Municipal-A — Fondation de Municipal Management |
| Rattaché à | ENG-001.5 — Extraction de Municipal Management §12 |
| Dépôt | dmv_api |
| Branche | feature/eng-001-5a-municipal-management-foundation (basée sur main, aucune dépendance à PR #14 ENG-001.4) |
| Statut | Implémenté |
| Comportement fonctionnel | Inchangé (vérifié — 456 tests / 1702 assertions, 0 échec, 449/1690 préexistants + 7 nouveaux) |
0. Écart procédural préalable
Le fichier dmv-docs/docs/19-engineering/specs/ENG-001-5A-municipal-management-foundation.md,
cité comme lecture obligatoire, n'existe pas dans le dépôt (recherche exhaustive, y compris
variantes de casse). Traité comme en ENG-001.3 : le corps de la mission redéfinit intégralement
le périmètre, et son contenu est cohérent avec
ENG-001-5-municipal-management.md §12 (« PR Municipal-A —
Création du module (risque quasi nul) »), déjà validée. L'implémentation a donc pu procéder sans
blocage sur ce point, à l'exception d'une divergence de nommage précisée en §1.
1. Divergence de nommage des contrats, résolue par les documents déjà validés
Constat. La mission propose, à titre d'exemple explicitement modifiable (« à adapter si la
spec diffère ») : MunicipalAlertReader, MunicipalAlertWriter, MunicipalServiceReader,
MunicipalServiceWriter — quatre contrats fragmentés par ressource, avec « Municipal » comme
préfixe raccourci.
Ceci contredit deux éléments déjà validés et référencés par cette même mission :
ENG-001-5-municipal-management.md§10 (« Contrats futurs ») cite explicitementMunicipalManagementReader/MunicipalManagementWriter— un seul pair par Bounded Context, repris deRFC-001.ENG-001-2-contrats-inter-contextes.md(règle explicite pour Municipal Management) : « Nommage aligné sur celui déjà acté dansADR-014,28-dmv-core-bounded-context-map.mdet30-dmv-core-migration-roadmap.md— ne jamais raccourcir en « Municipal » dans le code ou la documentation. »- Convention déjà appliquée sans exception à chaque Bounded Context existant :
ActorReader/ActorWriter,PublicationReader/PublicationWriter,TerritoryReader,SubscriptionManager— un seul Reader et au plus un seul Writer par contexte, jamais un contrat par ressource individuelle.
Traitement. La mission elle-même invite explicitement à adapter les noms « si la spec
diffère » — ici, la « spec » (au sens large : les documents déjà validés référencés par la
mission) diffère de façon documentée et cohérente sur trois sources indépendantes, pas par
supposition. Retenu : MunicipalManagementReader et MunicipalManagementWriter, un seul
pair, méthodes multiples par ressource (getAlertes, getServices, getElus, getCollectes,
getInfos côté lecture ; méthodes CRUD équivalentes côté écriture). Non traité comme une décision
d'architecture nouvelle : c'est l'application d'une décision déjà prise, pas l'invention d'une
nouvelle.
2. Fichiers créés
Module MunicipalManagement
| Fichier | Rôle |
|---|---|
app/Modules/MunicipalManagement/Contracts/MunicipalManagementReader.php | Contrat de lecture — 5 méthodes (getAlertes, getServices, getElus, getCollectes, getInfos), chacune string $communeId → Collection du DTO correspondant |
app/Modules/MunicipalManagement/Contracts/MunicipalManagementWriter.php | Contrat d'écriture — 14 méthodes CRUD (3 alertes, 2 services, 3 élus, 3 collectes, 3 infos) |
app/Modules/MunicipalManagement/DTOs/MunicipalAlertDTO.php | Champs alignés sur MairieAlerteDTO existant |
app/Modules/MunicipalManagement/DTOs/MunicipalServiceDTO.php | Champs alignés sur MairieServiceDTO existant |
app/Modules/MunicipalManagement/DTOs/MunicipalEluDTO.php | Champs alignés sur le modèle Territory\Models\CommuneElu |
app/Modules/MunicipalManagement/DTOs/MunicipalCollecteDTO.php | Champs alignés sur le modèle Territory\Models\CommuneCollecte |
app/Modules/MunicipalManagement/DTOs/MunicipalInfoDTO.php | Champs alignés sur le modèle Territory\Models\CommuneInfo — section inclus comme chaîne brute, sans référence vers commune_info_sections (ownership non tranché, ENG-001.4 §11.C / ENG-001.5 §13.C) |
app/Modules/MunicipalManagement/Services/MunicipalManagementReadService.php | Implémente MunicipalManagementReader — chaque méthode lève LogicException |
app/Modules/MunicipalManagement/Services/MunicipalManagementWriteService.php | Implémente MunicipalManagementWriter — chaque méthode lève LogicException |
app/Modules/MunicipalManagement/Providers/MunicipalManagementServiceProvider.php | Bindings singleton + alias des deux contrats ; boot() ne charge aucune route |
tests/Feature/MunicipalManagement/MunicipalManagementFoundationTest.php | 7 tests d'infrastructure (voir §6) |
Aucun fichier créé sous Controllers/, Models/, ou Routes/ : ces répertoires ne sont pas
créés du tout (git ne suit pas les répertoires vides, et la mission autorise explicitement
l'absence de contrôleurs et interdit modèles/routes à ce stade).
Documentation
| Fichier | Contenu |
|---|---|
dmv-docs/docs/19-engineering/specs/ENG-001-5A-implementation-report.md | Ce document |
3. Fichiers modifiés
| Fichier | Changement |
|---|---|
api/bootstrap/providers.php | Ajout de MunicipalManagementServiceProvider::class à la liste des providers actifs, après RewardsModuleServiceProvider |
Aucun autre fichier applicatif touché — confirmé par git status (voir §8).
4. Arborescence créée
app/Modules/MunicipalManagement/
├── Contracts/
│ ├── MunicipalManagementReader.php
│ └── MunicipalManagementWriter.php
├── DTOs/
│ ├── MunicipalAlertDTO.php
│ ├── MunicipalCollecteDTO.php
│ ├── MunicipalEluDTO.php
│ ├── MunicipalInfoDTO.php
│ └── MunicipalServiceDTO.php
├── Providers/
│ └── MunicipalManagementServiceProvider.php
└── Services/
├── MunicipalManagementReadService.php
└── MunicipalManagementWriteService.php
Controllers/, Models/, Routes/ intentionnellement absents (voir §2).
5. Contrats créés
Voir §1 pour la résolution du nommage. Récapitulatif des signatures :
MunicipalManagementReader : getAlertes, getServices, getElus, getCollectes,
getInfos — chacune (string $communeId): Collection.
MunicipalManagementWriter : createAlerte, updateAlerte, desactiverAlerte,
createService, updateService, createElu, updateElu, deleteElu, createCollecte,
updateCollecte, deleteCollecte, createInfo, updateInfo, deleteInfo.
Signatures dérivées des méthodes déjà existantes de MairieWriteService/MairieReadService/
TerritoryService (mêmes noms de ressources, mêmes paramètres), sans reprendre leur code.
Écart volontaire par rapport à MairieWriteService : le paramètre Profile $user (présent sur
chaque méthode d'écriture de MairieWriteService mais jamais utilisé dans leurs corps actuels,
vérifié) n'a pas été repris — l'exposer dans un contrat public violerait ADR-014 §12 (« si un
service applicatif reçoit... un modèle Eloquent... une frontière a été violée »).
6. DTO créés
5 DTO, tous immuables (readonly), tous avec toArray(), aucun avec fromModel() — volontaire :
créer un fromModel() référencerait un modèle Eloquent d'un autre module (Mairie\Models\MairieAlerte,
Territory\Models\CommuneElu, etc.), ce qui créerait dès la fondation un couplage vers des
modules dont la donnée n'a pas encore été migrée. Reporté à la PR qui réalisera effectivement
chaque migration (Municipal-C/D/E). Voir §2 pour le détail champ par champ de chaque DTO.
7. Bindings créés
Dans MunicipalManagementServiceProvider::register() :
$this->app->singleton(MunicipalManagementReadService::class);
$this->app->singleton(MunicipalManagementWriteService::class);
$this->app->alias(MunicipalManagementReadService::class, MunicipalManagementReader::class);
$this->app->alias(MunicipalManagementWriteService::class, MunicipalManagementWriter::class);
Provider enregistré dans bootstrap/providers.php. Aucun binding vers Mairie, Territory ou
Admin — le module ne dépend d'aucun autre module à ce stade.
8. Tests créés
tests/Feature/MunicipalManagement/MunicipalManagementFoundationTest.php, 7 tests, tous
d'infrastructure (aucun test métier, conformément à la mission) :
- Le module se charge sans erreur (bootstrap complet).
MunicipalManagementReaderrésolu par le conteneur → instance deMunicipalManagementReadService.MunicipalManagementWriterrésolu par le conteneur → instance deMunicipalManagementWriteService.- Les deux services sont des singletons (même instance sur deux résolutions).
- Chacune des 5 méthodes de lecture lève
LogicExceptionavec un message explicite, sans effet de bord. createAlerte()(représentatif des 14 méthodes d'écriture) lèveLogicException.- Aucune route dont l'URI contient
municipaln'est enregistrée (app('router')->getRoutes()).
9. Validations exécutées
| Commande | Résultat |
|---|---|
php -l sur les 11 fichiers créés/modifiés | OK |
php artisan about (bootstrap complet) | OK |
Résolution container (MunicipalManagementReader/Writer → services, via php artisan tinker, y compris déclenchement effectif de LogicException) | OK |
php artisan route:list --except-vendor | grep municipal | Vide — confirmé aucune route |
vendor/bin/pint --test sur le module, bootstrap/providers.php et les tests | OK (1 correction automatique appliquée : guillemets simples dans le nouveau test, revérifié après correction) |
php artisan test --filter=MunicipalManagementFoundationTest | 7 tests, 12 assertions — vert |
php artisan test (suite complète) | 456 tests, 1702 assertions, 0 échec (449/1690 préexistants sur main + 7 nouveaux) |
vendor/bin/phpstan analyse | Absent du projet (vendor/bin/phpstan inexistant), comme constaté à chaque mission précédente |
composer analyse | Non défini dans composer.json |
10. Divergences
- §0 — fichier de lecture obligatoire manquant, non bloquant, même traitement qu'ENG-001.3.
- §1 — nommage des contrats résolu en faveur de
MunicipalManagementReader/Writerplutôt que les quatre noms donnés en exemple par la mission, sur la base de trois sources déjà validées et cohérentes entre elles (spec ENG-001.5, règle explicite d'ENG-001.2, convention uniforme du reste du code). Documentée avant implémentation, pas après.
Aucune autre divergence. Aucune décision d'ownership, de migration de données, ou de contrat vers un module inexistant n'a été prise — le module reste vide de toute logique réelle, exactement comme demandé.
11. Confirmation qu'aucun comportement fonctionnel n'a changé
- Le module n'est atteint par aucun code existant : aucun fichier de
Mairie,Territory,Actor,Adminne référenceMunicipalManagement(vérifié — le nouveau module n'est importé nulle part hors de lui-même et debootstrap/providers.php). - La suite de tests complète, qui exerce l'intégralité du comportement observable actuel de
l'application, reste intégralement verte : 456/456, dont les 449 tests préexistants sur
mainstrictement inchangés. - Toute méthode du nouveau module, si jamais appelée par erreur depuis un code futur non prévu
par cette PR, échoue bruyamment (
LogicException) plutôt que de produire un comportement silencieusement incorrect.
12. Confirmation qu'aucun endpoint public n'a été modifié
- Aucune route créée :
Routes/n'existe pas dans le nouveau module,boot()du Service Provider ne charge aucun fichier de routes. - Vérifié par
php artisan route:list --except-vendor(aucune entréemunicipal) et par le test automatisé dédié (§8, test 7). - Aucune route existante modifiée, aucun contrôleur existant touché.
13. Confirmation qu'aucun commit n'a été créé
Branche feature/eng-001-5a-municipal-management-foundation créée depuis main à jour, tous les
fichiers ci-dessus présents en working tree, aucun git add/git commit exécuté. git status
ne montre que les fichiers de cette mission plus les deux fichiers pré-existants et sans rapport
(database/seeders/DatabaseSeeder.php, database/seeders/ActorNotorietyConfigSeeder.php,
inchangés depuis les missions précédentes, non créés par cette mission).