L'arbre complet
Voici l'architecture complète du projet, organisée par niveau d'abstraction décroissant. Chaque niveau dépend du précédent ; aucun ne dépend du suivant. La pyramide est stricte.
M3 — FrenchExDev.Net.Dsl [EXISTANT, racine framework]
5 primitives MetaConcept / Property / Reference / Constraint / Inherits
│
▼
M2-meta — Law.Dsl.MetaModel [méta pour décrire des cadres juridiques]
[LegalDslDefinition], [LegalConcept], [LegalConstraint], [LegalBridge]
│
▼
M2 racines (interfaces, no SG, no instances) :
├── FrenchExDev.Net.Common.Dsl [racine technique partagée]
│ IAsset, ISpace, ISubdivisionOf<T>, IPeriod, IProcedure, IStatus, StatusKind
│
├── FrenchExDev.Net.Law.Dsl [racine juridique]
│ ILegalContainer, ILegalSubdivision, IMeasure, IDemocraticProcedure, MeasureKind
│
├── FrenchExDev.Net.Commons.Dsl [racine biens communs (politique)]
│ IAssetStatus, ITypedStatus, IBoundary
│
└── FrenchExDev.Net.Citizen.Dsl [racine cas citoyens]
ICase, ICircumstance, IQuestion, IRemedy
M2 méta-générateurs :
├── FrenchExDev.Net.Common.Dsl.SourceGenerator (CommonDslGenerator)
│ prend [Asset]/[Space]/[Period]/[Procedure] → émet partials qui implémentent les interfaces
│
└── FrenchExDev.Net.Law.Dsl.SourceGenerator (LawDslGenerator)
prend [LegalDslDefinition] → émet un cadre Law.${Space}${Time}.${Author}.Dsl complet
M2 instances de cadres (générées) :
├── Common.France2026.Etalab.Dsl, Common.France1995.Erard.Dsl, Common.EU2026.Etalab.Dsl, ...
├── Law.France2026.Etalab.Dsl, Law.France1995.Erard.Dsl, Law.EU2026.Etalab.Dsl,
│ Law.RomeAntique.JulienPaul.IUVS.Dsl, Law.CommonLaw.UK2026.GovUK.Dsl, ...
├── Commons.France2026.LaQuadrature.Dsl, Commons.France2026.Etalab.Dsl, ...
└── Citizen.France2026.Etalab.Dsl, Citizen.UK2026.GovUK.Dsl, ...
M2 compilateurs croisés (bridges) :
├── Law.Commons.Bridge.{Core, Roslyn, SourceGenerator, Analyzers, Cli}
│ le compilateur croisé Law × Commons, factorisé par interfaces racines
│
├── Law.Citizen.Bridge.{Core, Roslyn, SourceGenerator, Analyzers, Cli}
│ le compilateur croisé Law × Citizen (régime 6 — les gens écrivent leur cas)
│
├── Catala.Bridge (Ship 6-bis) [pont avec Catala pour calculs monétaires]
└── Law.International.Bridge (Ship 9) [orchestration multi-juridictionnelle]
M2 quality gate :
└── Law.QualityGate.{Lib, Cli, Html} [transposition juridique du QualityGate existant]
M0 — cas concrets :
├── MyCases.MathildeMartin/ [cas citoyen Mathilde]
├── Law.France1995.Sample/ [smoke test cas Dumas — Ship 1]
└── Simulation.France2030.DrinkingWater/ [scénario what-if]M3 — FrenchExDev.Net.Dsl [EXISTANT, racine framework]
5 primitives MetaConcept / Property / Reference / Constraint / Inherits
│
▼
M2-meta — Law.Dsl.MetaModel [méta pour décrire des cadres juridiques]
[LegalDslDefinition], [LegalConcept], [LegalConstraint], [LegalBridge]
│
▼
M2 racines (interfaces, no SG, no instances) :
├── FrenchExDev.Net.Common.Dsl [racine technique partagée]
│ IAsset, ISpace, ISubdivisionOf<T>, IPeriod, IProcedure, IStatus, StatusKind
│
├── FrenchExDev.Net.Law.Dsl [racine juridique]
│ ILegalContainer, ILegalSubdivision, IMeasure, IDemocraticProcedure, MeasureKind
│
├── FrenchExDev.Net.Commons.Dsl [racine biens communs (politique)]
│ IAssetStatus, ITypedStatus, IBoundary
│
└── FrenchExDev.Net.Citizen.Dsl [racine cas citoyens]
ICase, ICircumstance, IQuestion, IRemedy
M2 méta-générateurs :
├── FrenchExDev.Net.Common.Dsl.SourceGenerator (CommonDslGenerator)
│ prend [Asset]/[Space]/[Period]/[Procedure] → émet partials qui implémentent les interfaces
│
└── FrenchExDev.Net.Law.Dsl.SourceGenerator (LawDslGenerator)
prend [LegalDslDefinition] → émet un cadre Law.${Space}${Time}.${Author}.Dsl complet
M2 instances de cadres (générées) :
├── Common.France2026.Etalab.Dsl, Common.France1995.Erard.Dsl, Common.EU2026.Etalab.Dsl, ...
├── Law.France2026.Etalab.Dsl, Law.France1995.Erard.Dsl, Law.EU2026.Etalab.Dsl,
│ Law.RomeAntique.JulienPaul.IUVS.Dsl, Law.CommonLaw.UK2026.GovUK.Dsl, ...
├── Commons.France2026.LaQuadrature.Dsl, Commons.France2026.Etalab.Dsl, ...
└── Citizen.France2026.Etalab.Dsl, Citizen.UK2026.GovUK.Dsl, ...
M2 compilateurs croisés (bridges) :
├── Law.Commons.Bridge.{Core, Roslyn, SourceGenerator, Analyzers, Cli}
│ le compilateur croisé Law × Commons, factorisé par interfaces racines
│
├── Law.Citizen.Bridge.{Core, Roslyn, SourceGenerator, Analyzers, Cli}
│ le compilateur croisé Law × Citizen (régime 6 — les gens écrivent leur cas)
│
├── Catala.Bridge (Ship 6-bis) [pont avec Catala pour calculs monétaires]
└── Law.International.Bridge (Ship 9) [orchestration multi-juridictionnelle]
M2 quality gate :
└── Law.QualityGate.{Lib, Cli, Html} [transposition juridique du QualityGate existant]
M0 — cas concrets :
├── MyCases.MathildeMartin/ [cas citoyen Mathilde]
├── Law.France1995.Sample/ [smoke test cas Dumas — Ship 1]
└── Simulation.France2030.DrinkingWater/ [scénario what-if]Lecture des niveaux
M3 est le niveau le plus abstrait : le métamodèle de niveau 3, qui définit ce que c'est qu'un concept, ce que c'est qu'une propriété, ce que c'est qu'une contrainte. Il existe déjà dans le monorepo sous le nom FrenchExDev.Net.Dsl et il fournit cinq primitives : MetaConcept, MetaProperty, MetaReference, MetaConstraint, MetaInherits. C'est le niveau où vivent les règles de formation des règles. Aucun cadre juridique concret n'est à ce niveau ; on y trouve seulement les outils pour décrire des cadres.
M2-meta est un niveau intermédiaire spécifique au projet métacratique : une méta pour décrire des cadres juridiques. Le seul package à ce niveau est Law.Dsl.MetaModel, qui fournit les attributs [LegalDslDefinition], [LegalConcept], [LegalConstraint], [LegalBridge] permettant de décrire formellement à quoi ressemble un cadre juridique. Quand un auteur veut produire un nouveau cadre Law.${Space}${Time}.${Author}, il commence par écrire une LegalDslDefinition au niveau M2-meta, et LawDslGenerator (qui est lui-même au niveau M2 méta-générateurs) consomme cette définition pour produire le cadre.
M2 racines sont les quatre familles d'interfaces partagées qui définissent le vocabulaire commun à toutes les instances de cadres. Elles ne contiennent aucun cadre concret — elles définissent seulement les types-pivots (IAsset, ILegalContainer, IBoundary, ICase...) que toutes les instances viendront implémenter avec leurs spécifications propres. Ces racines sont stables : elles évoluent rarement, et chaque évolution est une décision majeure qui affecte tous les cadres en aval.
M2 méta-générateurs sont les deux source generators qui produisent les instances : CommonDslGenerator produit les instances de Common à partir de classes décorées, et LawDslGenerator produit les instances de Law (et indirectement de Commons et Citizen) à partir de définitions M2-meta. Ce sont des IIncrementalGenerator Roslyn standards, ciblant netstandard2.0 selon les conventions du monorepo.
M2 instances de cadres sont les cadres concrets — les artefacts publiables en NuGet, indexés par leur signature META(Ex × Ty) complète. Chaque instance est spécifique à son (Space, Time, Author), et plusieurs instances concurrentes peuvent coexister pour le même (Space, Time) avec différents Author. C'est le niveau où vit la pluralité politique. Les instances sont les seuls artefacts qui circulent réellement entre auteurs et utilisateurs ; tout le reste est de l'infrastructure invisible pour la plupart des usagers.
M2 bridges sont les compilateurs croisés qui vérifient la cohérence entre deux familles d'instances. Law.Commons.Bridge vérifie qu'un cadre Law.${Space}${Time}.${Author} est cohérent avec un cadre Commons.${Space}${Time}.${Author} qu'il référence. Law.Citizen.Bridge fait la même chose entre Law et Citizen. Chaque bridge est organisé en cinq sous-projets selon le pattern Hexagonal (voir ci-dessous). C'est au niveau bridges que vit la cohérence cross-DSL — c'est-à-dire la vérification typée que deux cadres choisis par un utilisateur sont mutuellement compatibles selon leurs spécifications.
M2 quality gate est la transposition au droit du QualityGate existant dans le monorepo — un outil de mesure de couverture, de complexité et de qualité. Il sera traité en détail en Partie 11.
M0 est le niveau le plus concret : les cas effectifs. MyCases.MathildeMartin est le cas d'une citoyenne nommée Mathilde Martin qui type sa propre situation. Law.France1995.Sample est le smoke test du Ship 1 qui démontre le compile error politique sur la décision Dumas. Simulation.France2030.DrinkingWater est un scénario what-if qui simule la reclassification de l'eau potable comme bien commun et mesure son effet sur le corpus juridique français existant. Ces cas M0 sont les utilisateurs du système — ils référencent les instances de cadres en NuGet et en consomment les analyzers et les diagnostics.
Pattern Hexagonal pour les bridges
Chaque bridge ${A}.${B}.Bridge sépare strictement cinq sous-projets, selon le pattern Hexagonal (Cockburn 2005, Ports and Adapters) appliqué à un compilateur :
${A}.${B}.Bridge.Core: le domaine pur, sans Roslyn, sans I/O. ContientIdentifiers.cs(les FQN typés sous forme de records),CrossModel.cs(les records sérialisables qui modélisent les jointures cross-DSL),FrontierCrossingDetector.cs(la fonction pureCrossModel → IReadOnlyList<CrossingViolation>), les Guards par invariant cross-DSL, le moteur de simulationScenarioRunner, leConsensusScorerpour les comparaisons multi-sources, et les types de verdict (CrossingViolation,ValidatedCrossing).${A}.${B}.Bridge.Roslyn: les ports d'entrée Roslyn purs. ContientCrossModelExtractor.cs(transforme uneCompilationRoslyn enCrossModeldu Core),LocationIndex.cs(table desLocationRoslyn indexée par les FQN du modèle, utilisée pour rapporter les diagnostics au bon endroit du source), etRoslynAnalyzerExtensions.cs(les helpersImplementsInterface,HasAttribute,GetLocationOrNone,GetNamedArg...). Aucune logique métier ici — uniquement de l'extraction et du formatage.${A}.${B}.Bridge.SourceGenerator: l'adapterIIncrementalGeneratorqui s'exécute pendant ledotnet build. Il appelle le pipeline d'extraction duBridge.Roslyn, passe leCrossModelauBridge.Core(FrontierCrossingDetector.Analyze), et utilise leLocationIndexpour émettre les diagnostics Roslyn aux bons endroits. Il émet aussi un fichier de cross-model matérialisé (${A}.${B}.Cross.g.cs) qui sert d'audit trail consommable par le Quality Gate.${A}.${B}.Bridge.Analyzers: l'adapterDiagnosticAnalyzerqui s'exécute en temps réel dans l'IDE (VS Code, Visual Studio, Rider, Lex Studio). Il appelle exactement les mêmes méthodes duBridge.Coreque le SG, mais avec desLocationnatives Roslyn (depuis lesSyntaxNode) pour pouvoir souligner en rouge dans l'éditeur sans attendre le build. Il héberge aussi lesCodeFixProviderqui transforment chaque diagnostic en suggestion actionnable (Quick Fix).${A}.${B}.Bridge.Cli: adapter ligne de commande optionnel pour les opérations qui ne passent pas par l'IDE —validate,simulate,consensus,time-diff. UtiliseSystem.CommandLine. Ne contient aucune logique métier — c'est juste un front-end qui parse les arguments, charge les assemblies, appelle leBridge.Core, et formate la sortie.
Le Bridge.Core est référencé par tous les autres sous-projets. Aucune duplication de logique entre SG, Analyzer et Cli — ils appellent les mêmes méthodes du Core. C'est la garantie technique qu'un changement dans la sémantique du bridge se propage automatiquement à toutes ses surfaces d'exposition. C'est aussi ce qui rend le Core purement testable : pas besoin de Roslyn ni de MSBuild pour faire tourner ses tests unitaires, on lui passe un CrossModel synthétique en mémoire et on vérifie ses sorties.
Pourquoi cette séparation est importante
La séparation Core / Roslyn / SG / Analyzer / Cli n'est pas du fétichisme architectural. Elle résout trois problèmes très concrets.
Premièrement, elle élimine la duplication entre build-time et IDE-time. Sans cette séparation, on serait tenté d'écrire deux fois la logique du détecteur — une fois dans le SG, une fois dans l'analyzer — parce que les deux contextes Roslyn ont des contrats légèrement différents (SourceProductionContext vs SymbolAnalysisContext). La duplication mènerait inévitablement à des divergences subtiles entre les deux moments d'écoute, et donc à des bugs où l'analyzer dirait « tout va bien » dans l'IDE et le build dirait « compile error » après — ou l'inverse, ce qui est pire encore. Avec la séparation, la logique vit dans le Core pur et les deux adapters l'appellent à l'identique. Pas de divergence possible.
Deuxièmement, elle rend le Core purement testable. Le Core est de la C# pure sans dépendance à Roslyn, à MSBuild, à I/O ou à tout système extérieur. Ses tests unitaires sont rapides (moins de cent millisecondes par test, une suite entière en moins d'une seconde), reproductibles, et ne requièrent aucun environnement particulier. C'est ce qui permet d'avoir une couverture très élevée sur la logique métier — typiquement plus de 95% — sans payer le coût d'une infrastructure de test lourde.
Troisièmement, elle rend les contributions externes praticables. Un nouveau contributeur peut écrire un test unitaire pour ajouter un nouveau cas dans le FrontierCrossingDetector sans rien savoir de Roslyn. Il manipule des CrossModel synthétiques, il vérifie des CrossingViolation attendues, et son test passe ou échoue. Quand il a finit, le SG et l'analyzer héritent automatiquement de sa contribution, sans qu'il ait à toucher du code Roslyn. La barrière d'entrée pour contribuer à la logique métier juridique est strictement zéro en termes de connaissance de l'écosystème compilateur.
Les analyzers comme visage interactif (préview)
Les analyzers méritent un développement substantiel parce qu'ils sont le visage interactif du système — ce qui le rend vivant pour ses utilisateurs en temps réel dans l'IDE. La Partie 9 — Law.Commons.Bridge leur consacre une section entière, qui explique en détail :
- Les trois familles d'analyzers : structurels par cadre (générés par
LawDslGeneratorpour chaque cadre, vérifient sa cohérence interne), cohérence inter-cadres (dans les bridges, vérifient en temps réel la cohérence entre deux cadres jointes), et symboliques racines (invariants génériques sur les symboles). - Pourquoi le SG et les analyzers : l'analyzer enseigne, le SG atteste.
- Les CodeFixers comme l'autre moitié de l'analyzer : transforment le feu rouge passif en assistant actif.
- Pourquoi cette architecture compte démocratiquement : chaque cadre généré par
LawDslGeneratorapporte ses analyzers avec lui, donc la barrière d'entrée pour offrir une expérience IDE complète à un cadre concurrent est zéro.
Pour ne pas dupliquer ici ce qui sera développé en Partie 9, retenons seulement qu'à chaque niveau de l'architecture où un analyzer apparaît (racines, instances de cadres, bridges), il est conçu pour être mince (~80 LOC), pur adapter Roslyn (pas de logique métier), et appelant systématiquement le Core du même niveau. La verticalité est stricte. C'est ce qui rend l'architecture prévisible à mesure qu'elle grandit.
Pour aller plus loin
- Partie 4 — META(Ex × Ty) (précédente)
- Partie 6 — Common.Dsl + CommonDslGenerator (suivante)
- Hub de la série
- Série Ops DSL Ecosystem qui utilise un découpage en couches similaire