L'écosystème METACRACY
Cartographie complète des projets — Territory.Dsl, la cascade des subdivisions, et le cluster mermaid de tout ce qui peut exister
Compagnon de : Comment les DSLs communiquent, Round 7 — Implémentation Auteur : Stéphane Erard — 10 avril 2026
Sommaire
- Le cinquième DSL racine — Territory.Dsl
- La cascade Ex × Ty — du national au quartier
- Pourquoi un DSL pour le territoire
- Les types du shared kernel territorial
- Le générateur de schémas territoriaux — work item planifié
- Cluster mermaid — tous les projets
- Lecture du cluster, anneau par anneau
1. Le cinquième DSL racine — Territory.Dsl
Les pages précédentes ont introduit quatre DSLs racines : Common.Dsl, Law.Dsl, Commons.Dsl, Citizen.Dsl. Il en manque un — celui qui paramètre la dimension Espace dans META(Ex × Ty) :
Territory.Dsl— le DSL des subdivisions administratives. Il permet de découper le terrain selon n'importe quelle granularité : nation, région, département, métropole, communauté de communes, commune, arrondissement, quartier. Chaque niveau a ses compétences, son budget, ses élus, et — surtout — sa propre instance de chaque autre DSL.
Sans Territory.Dsl, la formule META(Ex × Ty) est ambiguë : quel espace ? La France entière ? L'Île-de-France ? La métropole de Lyon ? Le 3ᵉ arrondissement ? La métacratie d'un département n'est pas celle d'un quartier — l'impérium n'est pas distribué de la même façon, les acteurs ne sont pas les mêmes, les compétences sont différentes.
Territory.Dsl est ce qui rend explicite la subsidiarité dans le système. Chaque niveau territorial est un cadre métacratique à part entière, et les niveaux se composent par hiérarchie.
2. La cascade Ex × Ty — du national au quartier
En France, la cascade administrative typique est :
| Niveau | Exemple | Code typé | Compétences principales |
|---|---|---|---|
| National | France | TerritoryFqn(Country, "FR") |
Lois, défense, justice, monnaie, diplomatie |
| Régional | Auvergne-Rhône-Alpes | TerritoryFqn(Region, "84") |
Lycées, transport régional, développement économique |
| Départemental | Rhône | TerritoryFqn(Departement, "69") |
Collèges, action sociale, routes départementales |
| Métropolitain | Métropole de Lyon | TerritoryFqn(Metropole, "69M") |
Urbanisme, transports urbains, déchets, eau |
| Intercommunal | Communauté de communes | TerritoryFqn(EPCI, "...") |
Compétences mutualisées variables |
| Communal | Lyon | TerritoryFqn(Commune, "69123") |
État civil, école primaire, urbanisme local |
| Infra-communal | Lyon 3ᵉ | TerritoryFqn(Arrondissement, "69383") |
Conseil d'arrondissement, services de proximité |
Chaque niveau hérite des cadres supérieurs (la commune ne peut pas violer la loi nationale) et ajoute ses propres règlements. C'est la hiérarchie des normes lue depuis l'angle territorial. Le compilateur métacratique vérifie cette hiérarchie au compile-time : un règlement municipal qui contredit une loi nationale produit une erreur TERRITORY_HIERARCHY_001.
La dimension Temps (Ty) se compose avec la dimension Espace (Ex) :
Territory.Lyon.2026.Dsl # Lyon en 2026
Territory.Lyon.1995.Dsl # Lyon en 1995 — comparable au compile-time
Territory.Lyon.2030.Dsl # Lyon prospectif — pour les simulationsTerritory.Lyon.2026.Dsl # Lyon en 2026
Territory.Lyon.1995.Dsl # Lyon en 1995 — comparable au compile-time
Territory.Lyon.2030.Dsl # Lyon prospectif — pour les simulationsLe même cadre territorial peut être instancié à plusieurs époques pour faire des comparaisons — c'est le théorème d'expressivité de Appareil et compilateur, Pont 2.
3. Pourquoi un DSL pour le territoire
L'argument est le même que pour les autres DSLs : on ne peut vérifier que ce que l'on rend formellement explicite.
Aujourd'hui, la subdivision territoriale française vit dans :
- Le code COG (Code Officiel Géographique) de l'INSEE — fichiers CSV mis à jour annuellement
- Le code SIRENE — pour les EPCI et les structures intercommunales
- Le Code général des collectivités territoriales (CGCT) — la loi qui définit les compétences
- Des dizaines de bases SIG (Système d'Information Géographique) éparpillées
- Les délibérations municipales et préfectorales — texte libre, non typé
Aucun de ces systèmes ne vérifie la cohérence d'ensemble. Aucun ne peut répondre, au compile-time : « cette compétence est-elle valablement exercée par cette intercommunalité ? », « ce règlement municipal entre-t-il en contradiction avec une loi régionale ? », « ce citoyen relève-t-il de quelle métropole ? ». Ces questions sont laissées à des juristes humains, qui les traitent au cas par cas, avec leurs biais, leurs délais, et leurs ambiguïtés.
Territory.Dsl formalise tout cela. Le code COG de l'INSEE devient un Territory.France.Insee.2026.Dsl typé. Les compétences du CGCT deviennent des [TerritoryCompetence] annotés. Les délibérations deviennent des [TerritoryRegulation] reliées à un article de Law.Dsl. Le compilateur croisé vérifie tout.
4. Les types du shared kernel territorial
Territory.Dsl ajoute au shared kernel Metacratie.Common les types suivants :
// Metacratie.Common/SharedKernel/Territory.cs
namespace Metacratie.Common.SharedKernel;
/// <summary>
/// FQN typé d'un territoire — pas de string.
/// Le niveau est un ITerritoryLevel, pas un enum universel :
/// voir la section 4.5 sur les schémas territoriaux.
/// </summary>
public sealed record TerritoryFqn(ITerritoryLevel Level, string LocalCode)
{
public override string ToString() => $"{Level.Name}:{LocalCode}";
}
/// <summary>
/// Interface d'un niveau territorial.
///
/// ⚠️ Pas d'enum ici, parce que les niveaux territoriaux dépendent
/// du couple (Country, Period). L'Allemagne 2026 a Bund/Land/Kreis/Gemeinde,
/// la France 1789 avait Royaume/Province/Généralité/Élection/Paroisse,
/// la France 2026 a Country/Region/Département/Métropole/EPCI/Commune.
///
/// Chaque schéma est produit par le TerritorySchemeGenerator
/// sous forme d'un classlib NuGet dédié — voir section 4.5.
/// </summary>
public interface ITerritoryLevel
{
/// <summary>Rang hiérarchique dans le schéma (0 = plus haut).</summary>
int Rank { get; }
/// <summary>Nom du niveau dans le schéma (ex. "Département", "Land", "Paroisse").</summary>
string Name { get; }
/// <summary>Libellé complet, localisé si nécessaire.</summary>
string Label { get; }
/// <summary>Niveau parent dans le schéma, ou null pour le sommet.</summary>
ITerritoryLevel? ParentLevel { get; }
/// <summary>Le schéma auquel appartient ce niveau (pays + période).</summary>
TerritorySchemeId Scheme { get; }
}
/// <summary>
/// Identifiant d'un schéma territorial — un couple (Country, Period).
/// Records, pas strings : France.2026, France.1789, Germany.2026, UK.2026, etc.
/// </summary>
public sealed record TerritorySchemeId(
JurisdictionId Country,
DateOnly ValidFrom,
DateOnly? ValidTo);
/// <summary>
/// FQN typé d'une compétence territoriale — pas de magic string.
/// Ex : CompetenceFqn("Education", "Lycées"), CompetenceFqn("Voirie", "Routes départementales").
/// </summary>
public sealed record CompetenceFqn(string Domain, string Name)
{
public override string ToString() => $"{Domain}/{Name}";
}
/// <summary>Compétence territoriale typée — quel niveau peut faire quoi.</summary>
public sealed record TerritoryCompetence(
ITerritoryLevel HolderLevel,
CompetenceFqn Competence,
ArticleFqn LegalBasis);
/// <summary>Lien de subsidiarité — qui contient qui.</summary>
public sealed record TerritoryContainment(
TerritoryFqn Parent,
TerritoryFqn Child);// Metacratie.Common/SharedKernel/Territory.cs
namespace Metacratie.Common.SharedKernel;
/// <summary>
/// FQN typé d'un territoire — pas de string.
/// Le niveau est un ITerritoryLevel, pas un enum universel :
/// voir la section 4.5 sur les schémas territoriaux.
/// </summary>
public sealed record TerritoryFqn(ITerritoryLevel Level, string LocalCode)
{
public override string ToString() => $"{Level.Name}:{LocalCode}";
}
/// <summary>
/// Interface d'un niveau territorial.
///
/// ⚠️ Pas d'enum ici, parce que les niveaux territoriaux dépendent
/// du couple (Country, Period). L'Allemagne 2026 a Bund/Land/Kreis/Gemeinde,
/// la France 1789 avait Royaume/Province/Généralité/Élection/Paroisse,
/// la France 2026 a Country/Region/Département/Métropole/EPCI/Commune.
///
/// Chaque schéma est produit par le TerritorySchemeGenerator
/// sous forme d'un classlib NuGet dédié — voir section 4.5.
/// </summary>
public interface ITerritoryLevel
{
/// <summary>Rang hiérarchique dans le schéma (0 = plus haut).</summary>
int Rank { get; }
/// <summary>Nom du niveau dans le schéma (ex. "Département", "Land", "Paroisse").</summary>
string Name { get; }
/// <summary>Libellé complet, localisé si nécessaire.</summary>
string Label { get; }
/// <summary>Niveau parent dans le schéma, ou null pour le sommet.</summary>
ITerritoryLevel? ParentLevel { get; }
/// <summary>Le schéma auquel appartient ce niveau (pays + période).</summary>
TerritorySchemeId Scheme { get; }
}
/// <summary>
/// Identifiant d'un schéma territorial — un couple (Country, Period).
/// Records, pas strings : France.2026, France.1789, Germany.2026, UK.2026, etc.
/// </summary>
public sealed record TerritorySchemeId(
JurisdictionId Country,
DateOnly ValidFrom,
DateOnly? ValidTo);
/// <summary>
/// FQN typé d'une compétence territoriale — pas de magic string.
/// Ex : CompetenceFqn("Education", "Lycées"), CompetenceFqn("Voirie", "Routes départementales").
/// </summary>
public sealed record CompetenceFqn(string Domain, string Name)
{
public override string ToString() => $"{Domain}/{Name}";
}
/// <summary>Compétence territoriale typée — quel niveau peut faire quoi.</summary>
public sealed record TerritoryCompetence(
ITerritoryLevel HolderLevel,
CompetenceFqn Competence,
ArticleFqn LegalBasis);
/// <summary>Lien de subsidiarité — qui contient qui.</summary>
public sealed record TerritoryContainment(
TerritoryFqn Parent,
TerritoryFqn Child);Toujours le même principe : si une chose est un concept du domaine, elle est un record ou une interface. Pas de string libre, pas d'enum universel qui cacherait une dépendance à (Country, Period). Metacratie.Common reste à zéro dépendance externe (netstandard2.0), mais voit son shared kernel s'enrichir.
5. Le générateur de schémas territoriaux — work item planifié
⭐ Statut : À FAIRE. Le composant décrit dans cette section n'existe pas encore dans le monorepo. Il figure dans le cluster mermaid ci-dessous avec un style distinct (pointillé rouge) pour marquer les work items planifiés.
L'argument méta
Un enum TerritoryLevel codé en dur dans Metacratie.Common — comme je l'avais écrit dans une première version de cette page — est une erreur de conception qui viole le principe même de META(Ex × Ty). Les niveaux territoriaux sont eux-mêmes paramétrés par le couple (Country, Period) :
| Couple | Niveaux |
|---|---|
| France 2026 | Country → Region → Département → Métropole → EPCI → Commune → Arrondissement → Quartier |
| France 1789 | Royaume → Province → Généralité → Élection → Paroisse |
| Allemagne 2026 | Bund → Land → Regierungsbezirk → Kreis → Gemeinde |
| Royaume-Uni 2026 | Country (UK) → Nation → Region → County → District → Parish |
| États-Unis 2026 | Federal → State → County → City → Neighborhood |
| URSS 1980 | Union → SSR → Oblast → Rayon → Selsoviet |
Aucun de ces schémas n'est réductible à un autre. Un enum universel ne peut pas tous les contenir sans devenir incohérent. Le schéma est une donnée paramétrée, au même titre que le contenu des articles de loi.
Ce que fait le générateur
TerritorySchemeGenerator est un générateur de classlib NuGet. Son entrée est une définition déclarative (attributs sur une classe marqueur). Sa sortie est un classlib complet, autonome, publiable.
Standards respectés par tout classlib généré :
netstandard2.0— portable, consommable par tous<Nullable>enable</Nullable><TreatWarningsAsErrors>true</TreatWarningsAsErrors>- XML doc sur toutes les classes publiques
- Zéro dépendance externe, sauf
Metacratie.Common - Un seul fichier
.cspar schéma : une classe simple, immuable, thread-safe
Exemple d'input déclaratif
// source/territory-schemes/FranceSchemeV2026.cs
using Metacratie.Common.SharedKernel;
using Metacratie.Territory.Scheme.Abstractions;
[TerritoryScheme(
Country = "FR",
ValidFrom = "2003-03-28", // révision constitutionnelle sur la décentralisation
ValidTo = null)]
[TerritoryLevel("Country", Rank = 0, Label = "République française")]
[TerritoryLevel("Region", Rank = 1, Label = "Région", Parent = "Country")]
[TerritoryLevel("Departement", Rank = 2, Label = "Département", Parent = "Region")]
[TerritoryLevel("Metropole", Rank = 3, Label = "Métropole", Parent = "Departement")]
[TerritoryLevel("EPCI", Rank = 4, Label = "EPCI", Parent = "Departement")]
[TerritoryLevel("Commune", Rank = 5, Label = "Commune", Parent = "EPCI")]
[TerritoryLevel("Arrondissement", Rank = 6, Label = "Arrondissement", Parent = "Commune")]
[TerritoryLevel("Quartier", Rank = 7, Label = "Quartier", Parent = "Arrondissement")]
public sealed partial class FranceSchemeV2026;// source/territory-schemes/FranceSchemeV2026.cs
using Metacratie.Common.SharedKernel;
using Metacratie.Territory.Scheme.Abstractions;
[TerritoryScheme(
Country = "FR",
ValidFrom = "2003-03-28", // révision constitutionnelle sur la décentralisation
ValidTo = null)]
[TerritoryLevel("Country", Rank = 0, Label = "République française")]
[TerritoryLevel("Region", Rank = 1, Label = "Région", Parent = "Country")]
[TerritoryLevel("Departement", Rank = 2, Label = "Département", Parent = "Region")]
[TerritoryLevel("Metropole", Rank = 3, Label = "Métropole", Parent = "Departement")]
[TerritoryLevel("EPCI", Rank = 4, Label = "EPCI", Parent = "Departement")]
[TerritoryLevel("Commune", Rank = 5, Label = "Commune", Parent = "EPCI")]
[TerritoryLevel("Arrondissement", Rank = 6, Label = "Arrondissement", Parent = "Commune")]
[TerritoryLevel("Quartier", Rank = 7, Label = "Quartier", Parent = "Arrondissement")]
public sealed partial class FranceSchemeV2026;Ce que le générateur produit
Metacratie.Territory.Scheme.France.2026.Dsl/
├── Metacratie.Territory.Scheme.France.2026.Dsl.csproj ← généré
└── FranceSchemeV2026.g.cs ← généréMetacratie.Territory.Scheme.France.2026.Dsl/
├── Metacratie.Territory.Scheme.France.2026.Dsl.csproj ← généré
└── FranceSchemeV2026.g.cs ← généré<!-- Metacratie.Territory.Scheme.France.2026.Dsl.csproj — généré -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageId>Metacratie.Territory.Scheme.France.2026.Dsl</PackageId>
<Version>1.0.0</Version>
<Description>Schéma territorial France 2026 — niveaux générés depuis FranceSchemeV2026.</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Metacratie.Common" Version="1.0.0" />
</ItemGroup>
</Project><!-- Metacratie.Territory.Scheme.France.2026.Dsl.csproj — généré -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageId>Metacratie.Territory.Scheme.France.2026.Dsl</PackageId>
<Version>1.0.0</Version>
<Description>Schéma territorial France 2026 — niveaux générés depuis FranceSchemeV2026.</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Metacratie.Common" Version="1.0.0" />
</ItemGroup>
</Project>// FranceSchemeV2026.g.cs — généré
// <auto-generated by TerritorySchemeGenerator />
using Metacratie.Common.SharedKernel;
namespace Metacratie.Territory.Scheme.France.V2026;
/// <summary>Schéma territorial France 2026 — instances typées des niveaux.</summary>
public sealed partial class FranceSchemeV2026
{
public static readonly TerritorySchemeId Id = new(
Country: JurisdictionId.France,
ValidFrom: new DateOnly(2003, 3, 28),
ValidTo: null);
public static readonly FranceLevel Country = new("Country", 0, "République française", null);
public static readonly FranceLevel Region = new("Region", 1, "Région", Country);
public static readonly FranceLevel Departement = new("Departement", 2, "Département", Region);
public static readonly FranceLevel Metropole = new("Metropole", 3, "Métropole", Departement);
public static readonly FranceLevel EPCI = new("EPCI", 4, "EPCI", Departement);
public static readonly FranceLevel Commune = new("Commune", 5, "Commune", EPCI);
public static readonly FranceLevel Arrondissement = new("Arrondissement", 6, "Arrondissement", Commune);
public static readonly FranceLevel Quartier = new("Quartier", 7, "Quartier", Arrondissement);
public static readonly IReadOnlyList<FranceLevel> All =
[Country, Region, Departement, Metropole, EPCI, Commune, Arrondissement, Quartier];
}
/// <summary>Un niveau territorial du schéma France 2026. Record immuable.</summary>
public sealed record FranceLevel(
int Rank,
string Name,
string Label,
ITerritoryLevel? ParentLevel) : ITerritoryLevel
{
public TerritorySchemeId Scheme => FranceSchemeV2026.Id;
public FranceLevel(string name, int rank, string label, FranceLevel? parent)
: this(rank, name, label, parent) { }
}// FranceSchemeV2026.g.cs — généré
// <auto-generated by TerritorySchemeGenerator />
using Metacratie.Common.SharedKernel;
namespace Metacratie.Territory.Scheme.France.V2026;
/// <summary>Schéma territorial France 2026 — instances typées des niveaux.</summary>
public sealed partial class FranceSchemeV2026
{
public static readonly TerritorySchemeId Id = new(
Country: JurisdictionId.France,
ValidFrom: new DateOnly(2003, 3, 28),
ValidTo: null);
public static readonly FranceLevel Country = new("Country", 0, "République française", null);
public static readonly FranceLevel Region = new("Region", 1, "Région", Country);
public static readonly FranceLevel Departement = new("Departement", 2, "Département", Region);
public static readonly FranceLevel Metropole = new("Metropole", 3, "Métropole", Departement);
public static readonly FranceLevel EPCI = new("EPCI", 4, "EPCI", Departement);
public static readonly FranceLevel Commune = new("Commune", 5, "Commune", EPCI);
public static readonly FranceLevel Arrondissement = new("Arrondissement", 6, "Arrondissement", Commune);
public static readonly FranceLevel Quartier = new("Quartier", 7, "Quartier", Arrondissement);
public static readonly IReadOnlyList<FranceLevel> All =
[Country, Region, Departement, Metropole, EPCI, Commune, Arrondissement, Quartier];
}
/// <summary>Un niveau territorial du schéma France 2026. Record immuable.</summary>
public sealed record FranceLevel(
int Rank,
string Name,
string Label,
ITerritoryLevel? ParentLevel) : ITerritoryLevel
{
public TerritorySchemeId Scheme => FranceSchemeV2026.Id;
public FranceLevel(string name, int rank, string label, FranceLevel? parent)
: this(rank, name, label, parent) { }
}Les autres schémas générés de la même façon
Les inputs déclaratifs correspondants :
// FranceSchemeV1789 — Ancien Régime
[TerritoryScheme(Country = "FR", ValidFrom = "1000-01-01", ValidTo = "1789-08-04")]
[TerritoryLevel("Royaume", Rank = 0, Label = "Royaume de France")]
[TerritoryLevel("Province", Rank = 1, Label = "Province", Parent = "Royaume")]
[TerritoryLevel("Generalite", Rank = 2, Label = "Généralité", Parent = "Province")]
[TerritoryLevel("Election", Rank = 3, Label = "Élection", Parent = "Generalite")]
[TerritoryLevel("Paroisse", Rank = 4, Label = "Paroisse", Parent = "Election")]
public sealed partial class FranceSchemeV1789;
// GermanySchemeV2026
[TerritoryScheme(Country = "DE", ValidFrom = "1949-05-23", ValidTo = null)]
[TerritoryLevel("Bund", Rank = 0, Label = "Bundesrepublik")]
[TerritoryLevel("Land", Rank = 1, Label = "Bundesland", Parent = "Bund")]
[TerritoryLevel("Regierungsbezirk", Rank = 2, Label = "Regierungsbezirk", Parent = "Land")]
[TerritoryLevel("Kreis", Rank = 3, Label = "Landkreis", Parent = "Regierungsbezirk")]
[TerritoryLevel("Gemeinde", Rank = 4, Label = "Gemeinde", Parent = "Kreis")]
public sealed partial class GermanySchemeV2026;
// UKSchemeV2026
[TerritoryScheme(Country = "GB", ValidFrom = "1998-11-19", ValidTo = null)]
[TerritoryLevel("United Kingdom", Rank = 0, Label = "United Kingdom of GB and NI")]
[TerritoryLevel("Nation", Rank = 1, Label = "Constituent Nation", Parent = "United Kingdom")]
[TerritoryLevel("Region", Rank = 2, Label = "Region", Parent = "Nation")]
[TerritoryLevel("County", Rank = 3, Label = "County", Parent = "Region")]
[TerritoryLevel("District", Rank = 4, Label = "District", Parent = "County")]
[TerritoryLevel("Parish", Rank = 5, Label = "Parish", Parent = "District")]
public sealed partial class UKSchemeV2026;// FranceSchemeV1789 — Ancien Régime
[TerritoryScheme(Country = "FR", ValidFrom = "1000-01-01", ValidTo = "1789-08-04")]
[TerritoryLevel("Royaume", Rank = 0, Label = "Royaume de France")]
[TerritoryLevel("Province", Rank = 1, Label = "Province", Parent = "Royaume")]
[TerritoryLevel("Generalite", Rank = 2, Label = "Généralité", Parent = "Province")]
[TerritoryLevel("Election", Rank = 3, Label = "Élection", Parent = "Generalite")]
[TerritoryLevel("Paroisse", Rank = 4, Label = "Paroisse", Parent = "Election")]
public sealed partial class FranceSchemeV1789;
// GermanySchemeV2026
[TerritoryScheme(Country = "DE", ValidFrom = "1949-05-23", ValidTo = null)]
[TerritoryLevel("Bund", Rank = 0, Label = "Bundesrepublik")]
[TerritoryLevel("Land", Rank = 1, Label = "Bundesland", Parent = "Bund")]
[TerritoryLevel("Regierungsbezirk", Rank = 2, Label = "Regierungsbezirk", Parent = "Land")]
[TerritoryLevel("Kreis", Rank = 3, Label = "Landkreis", Parent = "Regierungsbezirk")]
[TerritoryLevel("Gemeinde", Rank = 4, Label = "Gemeinde", Parent = "Kreis")]
public sealed partial class GermanySchemeV2026;
// UKSchemeV2026
[TerritoryScheme(Country = "GB", ValidFrom = "1998-11-19", ValidTo = null)]
[TerritoryLevel("United Kingdom", Rank = 0, Label = "United Kingdom of GB and NI")]
[TerritoryLevel("Nation", Rank = 1, Label = "Constituent Nation", Parent = "United Kingdom")]
[TerritoryLevel("Region", Rank = 2, Label = "Region", Parent = "Nation")]
[TerritoryLevel("County", Rank = 3, Label = "County", Parent = "Region")]
[TerritoryLevel("District", Rank = 4, Label = "District", Parent = "County")]
[TerritoryLevel("Parish", Rank = 5, Label = "Parish", Parent = "District")]
public sealed partial class UKSchemeV2026;Chacun de ces inputs produit un classlib autonome. Les trois classlibs peuvent coexister dans la même solution, se comparer au compile-time, et alimenter TerritoryDslGenerator pour produire les instances concrètes (la liste des Länder allemands, la liste des comtés anglais, la liste des communes françaises).
Pourquoi c'est le bon niveau d'abstraction
Le TerritorySchemeGenerator est un méta-méta-générateur : il produit les schémas que le TerritoryDslGenerator consommera ensuite pour produire les instances. C'est exactement la cascade M3 → M2 → M1 appliquée à la dimension territoriale :
- M3 —
ITerritoryLevel+TerritorySchemeId(dansMetacratie.Common) - M2 —
FranceSchemeV2026,GermanySchemeV2026, etc. (classlib par schéma, générés parTerritorySchemeGenerator) - M1 —
Territory.Region.AURA.2026.Dsl, etc. (instances concrètes, générées parTerritoryDslGeneratorà partir des schémas M2)
Le schéma est la donnée — un enum hard-codé traitait le schéma comme une constante du langage, ce qu'il n'est pas.
6. Cluster mermaid — tous les projets
Voici la cartographie complète. Chaque rectangle est un package NuGet (ou un cluster de packages) qui peut exister dans l'écosystème METACRACY. Les flèches pleines sont les dépendances de compilation ; les flèches pointillées sont les flux d'événements à l'exécution.
Deux styles distincts dans le cluster :
- 🟡 Éléments en jaune (⭐) — composants du chantier
Territory.Dslet de ses bridges, posés dans cette page mais pas encore implémentés dans le monorepo. Ce sont des extensions du cadre existant. - 🔴 Éléments en rouge pointillé (🚧) — work items planifiés identifiés dans cette page. Le
TerritorySchemeGeneratoret les classlib de schémas qu'il produit sont des projets à faire, dans un sens conceptuel (pas au sens du mot « projet C# » — certains sont d'ailleurs des projets C#, mais ce qui les distingue ici, c'est qu'ils n'existent pas encore).
7. Lecture du cluster, anneau par anneau
Le cluster contient une dizaine de groupes. Voici comment les lire :
🔧 Le shared kernel
Au cœur, Metacratie.Common contient les contrats (ILegalEvent, ILegalQuery<T>, ILegalResponse), les attributs de déclaration, et tous les types partagés du domaine. Zéro dépendance, netstandard2.0. Tout le reste en dépend.
🚌 Le bus d'événements
ILegalEventBus est le port hexagonal. Plusieurs adaptateurs peuvent l'implémenter : InMemory pour les tests et la démo, RabbitMQ pour la production, Kafka pour les volumes massifs. Les DSLs n'en connaissent que l'interface.
🌱 Les DSLs racines
Cinq racines, chacune avec un domaine de responsabilité unique : Common.Dsl (vocabulaire technique partagé), Law.Dsl (vocabulaire juridique), Commons.Dsl (biens communs), Citizen.Dsl (réception citoyenne), Territory.Dsl (subdivisions Ex). Aucune racine n'a d'instance — ce sont des interfaces abstraites.
⚙️ Les méta-générateurs
Un générateur Roslyn par racine. Il lit les attributs de la racine et produit les instances concrètes — y compris leurs packages d'événements et les bridges nécessaires (cf. Round 7, section 10). Nouveau dans cette page : TerritorySchemeGenerator (🚧 TODO) est un méta-méta-générateur — il ne produit pas des instances, mais les schémas que TerritoryDslGenerator consommera ensuite.
🚧 Les schémas territoriaux — M2 au-dessus de M1
Le cluster schemes contient tous les classlib générés par TerritorySchemeGenerator à partir d'inputs déclaratifs. Un classlib par couple (Country, Period) pertinent : France 2026, France 1789, Germany 2026, UK 2026, etc. Chaque classlib est autonome (netstandard2.0, zéro dépendance hors Metacratie.Common), publiable comme package NuGet indépendant, et consommable par TerritoryDslGenerator pour produire les instances concrètes (la liste des communes françaises, la liste des Länder allemands…).
C'est la cascade M3 → M2-schemes → M2-roots → M1-instances :
- M3 :
ITerritoryLevel,TerritorySchemeId(dansMetacratie.Common) - M2-schemes :
FranceSchemeV2026,GermanySchemeV2026, etc. (générés parTerritorySchemeGenerator) - M2-roots :
Territory.Dsl(racine abstraite) - M1-instances :
Territory.France.Insee.2026.Dslet toute la cascade (générés parTerritoryDslGeneratoren consommant le schéma M2 approprié)
Statut : TODO. Le générateur et les schémas figurent dans cette page comme work items planifiés.
⭐ Territory.Dsl — la cascade
Le cinquième cluster racine. Ses instances forment un arbre : France contient les régions, qui contiennent les départements, qui contiennent les métropoles et communes, qui contiennent les arrondissements et quartiers. Chaque nœud de l'arbre est une instance compilable, indexée par INSEE, à un instant Ty donné.
⚖️ 🌊 👥 Les instances par famille
Pour chaque DSL racine non-territorial, une instance par niveau territorial pertinent. Lyon a sa propre Law.Commune.Lyon.2026.Dsl (les arrêtés municipaux), distincte de Law.Metropole.Lyon.2026.Dsl (le règlement métropolitain), elle-même distincte de Law.France2026.Etalab.Dsl (la loi nationale). Les flèches verticales sont la hiérarchie des normes lue comme dépendance de package.
📨 Les packages d'événements
Un package d'événements par instance. Law.France2026.Etalab.Dsl.Events ne contient que les événements émis par la France 2026, pas par Lyon. La granularité est délibérément fine — on ne dépend que des événements qu'on consomme réellement.
🌉 Les bridges
Sept bridges identifiés. Trois existent déjà (Law.Commons, Law.Citizen, Catala). Quatre sont nouveaux avec Territory.Dsl :
Law.Territory.Bridge— vérifie qu'une compétence revendiquée par un niveau correspond bien au CGCTTerritory.Commons.Bridge— vérifie que les biens communs locaux sont bien rattachés à un territoireTerritory.Citizen.Bridge— répond à la query « ce citoyen relève de quelle commune / métropole / région ? »Law.Law.Bridge— vérifie la hiérarchie verticale (un règlement municipal ne peut pas violer une loi régionale ni nationale)
📨 Les bridges events
Les queries/responses typées qui transitent entre DSLs (cf. Round 7, section 9). Chaque bridge a son propre package .Events, partagé entre les DSLs qu'il relie.
🛠️ L'outillage
Transversal : Lex Studio (l'IDE pour juristes), QualityGate (couverture), Requirements.Dsl (traçabilité), Compliance Reports (audit), Territory.Atlas (visualisation cartographique des cadres territoriaux), CLI (ligne de commande). Tous consomment les packages d'événements et les bridges.
☁️ Le runtime
En production : RabbitMQ comme broker, PostgreSQL comme event store, Grafana pour le monitoring, le pattern Outbox pour la livraison transactionnelle (les événements ne sont publiés que si la transaction de base de données réussit).
Récapitulatif chiffré. Pour la France 2026, l'écosystème complet contiendrait approximativement :
| Type | Nombre approximatif | Détail |
|---|---|---|
| Racines | 5 | Common, Law, Commons, Citizen, Territory |
| Méta-générateurs | 5 | Un par racine |
| Méta-méta-générateurs 🚧 | 1 | TerritorySchemeGenerator (TODO) — produit les schémas territoriaux |
| Schémas territoriaux 🚧 | ~10–20 | Un classlib par couple (Country, Period) pertinent : G20 contemporain + schémas historiques comparables (France 1789, URSS 1980, etc.) |
| Instances Territory | ~36 000 | Pour la France 2026 : 1 nation + 13 régions + 101 départements + 21 métropoles + ~1 250 EPCI + ~35 000 communes + arrondissements |
| Instances Law | ~1 500 | National + 13 régions + métropoles ayant un pouvoir réglementaire + grandes communes |
| Instances Commons | ~50 | Surtout au niveau métropolitain et national |
| Instances Citizen | ~50 | Surtout par grandes agglomérations |
| Packages d'événements | ~35 000 | Un par instance |
| Bridges | 7 | Voir liste ci-dessus |
| Bridges events | 7 | Un par bridge |
| Outillage | ~10 | Lex Studio + outils périphériques |
C'est un écosystème massif mais structuré. Chaque package a un domaine de responsabilité unique. Chaque dépendance est typée et vérifiée au compile-time. Aucun nœud du graphe n'est inutile : si un package n'est consommé par personne, le compilateur le signale comme orphelin.
C'est l'application littérale du principe : le code est la loi, et la loi est typée.