Commons.Dsl et Citizen.Dsl — cadres politiques et cadres citoyens
La Partie 6 a posé Common.Dsl, la racine technique neutre. La Partie 7 a posé Law.Dsl, la racine juridique agnostique aux traditions. Cette partie pose les deux dernières racines : Commons.Dsl, qui politise les biens techniques en leur attribuant un statut (commun, public, privé, contesté, en transition), et Citizen.Dsl, qui donne aux destinataires du droit un vocabulaire pour décrire leur situation et recevoir un diagnostic typé.
La distinction Common / Commons a été formalisée en Partie 2 et rappelée dans le hub. Le S final est toute la différence : Common est l'adjectif technique (partagé), Commons est le substantif politique (les communs au sens d'Ostrom). Les deux racines se complètent sans jamais se confondre, et le compilateur ne s'en plaindra pas — c'est à l'humain d'imposer la discipline sémantique.
La racine FrenchExDev.Net.Commons.Dsl
Comme les autres racines, Commons.Dsl est un projet C# minimal. Trois interfaces, pas de Source Generator, pas de Roslyn.
namespace FrenchExDev.Net.Commons.Dsl;
using FrenchExDev.Net.Common.Dsl;
// Statut d'un bien dans un (Espace x Temps) donné
public interface IAssetStatus
{
Type AssetType { get; } // doit implémenter IAsset
}
// Statut typé avec jugement politique explicite
public interface ITypedStatus
{
StatusKind Kind { get; }
Type Space { get; } // doit implémenter ISpace
Type Period { get; } // doit implémenter IPeriod
}
// Frontière entre deux statuts, avec procédure requise
public interface IBoundary
{
StatusKind From { get; }
StatusKind To { get; }
Type RequiredProcedure { get; } // doit implémenter IProcedure
}namespace FrenchExDev.Net.Commons.Dsl;
using FrenchExDev.Net.Common.Dsl;
// Statut d'un bien dans un (Espace x Temps) donné
public interface IAssetStatus
{
Type AssetType { get; } // doit implémenter IAsset
}
// Statut typé avec jugement politique explicite
public interface ITypedStatus
{
StatusKind Kind { get; }
Type Space { get; } // doit implémenter ISpace
Type Period { get; } // doit implémenter IPeriod
}
// Frontière entre deux statuts, avec procédure requise
public interface IBoundary
{
StatusKind From { get; }
StatusKind To { get; }
Type RequiredProcedure { get; } // doit implémenter IProcedure
}IAssetStatus est le point d'ancrage : il lie un statut politique à un bien technique via typeof(...). Le AssetType doit implémenter IAsset (défini dans Common.Dsl), ce qui signifie que la chaîne de typage est complète : du bien technique (neutre) au statut politique (engagé), tout est type C# cadre-aware.
ITypedStatus est le jugement politique proprement dit. Il dit : dans cet espace, à cette période, ce bien a ce statut. Le StatusKind (enum défini dans Common.Dsl : Commons, Public, Private, Contested, InTransition) est la classification. Notez que Contested et InTransition existent à côté des trois statuts "stables" — c'est essentiel pour modéliser la réalité politique, où de nombreux biens sont en débat plutôt que nettement classés.
IBoundary est la pièce la plus intéressante politiquement. Elle dit : pour passer du statut X au statut Y, cette procédure démocratique est requise. C'est ce que le bridge Law.Commons.Bridge exploite pour vérifier qu'une mesure juridique (une privatisation, par exemple) respecte la procédure exigée par le cadre politique choisi. La frontière est déclarative — elle ne dit pas si le passage est souhaitable, elle dit comment il doit être légitimé.
Là où ça devient politiquement intéressant : deux cadres sur le même bien
Le cœur de Commons.Dsl n'est pas dans les interfaces elles-mêmes — il est dans le fait que plusieurs cadres concurrents peuvent référencer le même typeof(IAsset) avec des statuts différents. C'est la superposition d'un cadre politique sur un cadre technique qui rend la divergence traçable.
Prenons un exemple concret. Common.France2026.Etalab.Dsl définit, parmi ses biens techniques, l'eau potable :
namespace Common.France2026.Etalab.Assets;
[Asset("Eau potable", Category = "Ressources naturelles")]
public sealed partial class DrinkingWater { }namespace Common.France2026.Etalab.Assets;
[Asset("Eau potable", Category = "Ressources naturelles")]
public sealed partial class DrinkingWater { }Ce bien existe une seule fois dans le cadre technique. Mais au niveau politique, deux cadres concurrents peuvent le qualifier différemment.
Commons.France2026.LaQuadrature.Dsl — cadre d'une association de défense des communs numériques et physiques :
namespace Commons.France2026.LaQuadrature.Statuses;
using Common.France2026.Etalab.Assets;
using Common.France2026.Etalab.Spaces;
using Common.France2026.Etalab.Periods;
using Common.France2026.Etalab.Procedures;
// L'eau potable est un bien commun, selon LaQuadrature
[AssetStatus(typeof(DrinkingWater))]
[TypedStatus(StatusKind.Commons, typeof(FR), typeof(Y2026))]
public sealed class DrinkingWaterInFrance { }
// Toute privatisation d'un commun requiert un référendum
[Boundary(StatusKind.Commons, StatusKind.Private, typeof(NationalReferendum))]
public sealed class CommonsBoundaries { }namespace Commons.France2026.LaQuadrature.Statuses;
using Common.France2026.Etalab.Assets;
using Common.France2026.Etalab.Spaces;
using Common.France2026.Etalab.Periods;
using Common.France2026.Etalab.Procedures;
// L'eau potable est un bien commun, selon LaQuadrature
[AssetStatus(typeof(DrinkingWater))]
[TypedStatus(StatusKind.Commons, typeof(FR), typeof(Y2026))]
public sealed class DrinkingWaterInFrance { }
// Toute privatisation d'un commun requiert un référendum
[Boundary(StatusKind.Commons, StatusKind.Private, typeof(NationalReferendum))]
public sealed class CommonsBoundaries { }Commons.France2026.InstitutMontaigne.Dsl — cadre d'un think tank libéral :
namespace Commons.France2026.InstitutMontaigne.Statuses;
using Common.France2026.Etalab.Assets;
using Common.France2026.Etalab.Spaces;
using Common.France2026.Etalab.Periods;
using Common.France2026.Etalab.Procedures;
// L'eau potable est un service public délégable, selon Montaigne
[AssetStatus(typeof(DrinkingWater))]
[TypedStatus(StatusKind.Public, typeof(FR), typeof(Y2026))]
public sealed class DrinkingWaterInFrance { }
// La transition Public → Private ne requiert qu'un débat parlementaire
[Boundary(StatusKind.Public, StatusKind.Private, typeof(ParliamentaryDebateTwoReadings))]
public sealed class PublicBoundaries { }namespace Commons.France2026.InstitutMontaigne.Statuses;
using Common.France2026.Etalab.Assets;
using Common.France2026.Etalab.Spaces;
using Common.France2026.Etalab.Periods;
using Common.France2026.Etalab.Procedures;
// L'eau potable est un service public délégable, selon Montaigne
[AssetStatus(typeof(DrinkingWater))]
[TypedStatus(StatusKind.Public, typeof(FR), typeof(Y2026))]
public sealed class DrinkingWaterInFrance { }
// La transition Public → Private ne requiert qu'un débat parlementaire
[Boundary(StatusKind.Public, StatusKind.Private, typeof(ParliamentaryDebateTwoReadings))]
public sealed class PublicBoundaries { }Les deux cadres référencent exactement le même type : typeof(Common.France2026.Etalab.Assets.DrinkingWater). Il n'y a pas d'ambiguïté, pas de chaîne magique, pas de malentendu possible sur de quel bien on parle. Ce qui diverge, c'est le jugement politique : LaQuadrature dit commun avec référendum requis pour privatiser, Montaigne dit public avec débat parlementaire suffisant.
Le bridge Law.Commons.Bridge exploite cette structure. Quand un auteur de cas référence Commons.France2026.LaQuadrature dans son csproj, une mesure de privatisation de l'eau sans référendum produit un compile error. Quand il référence Commons.France2026.InstitutMontaigne, la même mesure avec un débat parlementaire compile. Changer de cadre politique revient à changer un <PackageReference> dans le csproj. L'acte politique est explicite, versionné, traçable.
C'est précisément cette superposition d'un cadre politique sur un cadre technique qui rend la divergence adressable par le ConsensusScorer (cf. Partie 9 sur le bridge). Le scorer peut prendre les deux cadres concurrents, les aligner sur les mêmes biens, et exhiber mécaniquement les points de divergence. Pas pour trancher — trancher est un acte politique qui appartient aux citoyens — mais pour rendre visible ce qui diverge et comment.
La racine FrenchExDev.Net.Citizen.Dsl
Si Law.Dsl décrit les actes de production juridique (articles, alinéas, mesures, procédures) et Commons.Dsl les jugements politiques sur les biens (statuts, frontières), Citizen.Dsl décrit les actes de réception du droit par ses destinataires. Un cas, des circonstances, des questions, des recours.
namespace FrenchExDev.Net.Citizen.Dsl;
using FrenchExDev.Net.Common.Dsl;
// Un cas citoyen : une personne dans un (Espace x Temps)
public interface ICase
{
Type Space { get; }
Type Period { get; }
}
// Une circonstance du cas (locataire, parent isolé, ...)
public interface ICircumstance
{
Type Category { get; }
}
// Une question adressée au système juridique
public interface IQuestion
{
Type Domain { get; }
}
// Un recours possible (contentieux, administratif, ...)
public interface IRemedy
{
Type ProcedureType { get; }
string? FilingDate { get; }
}namespace FrenchExDev.Net.Citizen.Dsl;
using FrenchExDev.Net.Common.Dsl;
// Un cas citoyen : une personne dans un (Espace x Temps)
public interface ICase
{
Type Space { get; }
Type Period { get; }
}
// Une circonstance du cas (locataire, parent isolé, ...)
public interface ICircumstance
{
Type Category { get; }
}
// Une question adressée au système juridique
public interface IQuestion
{
Type Domain { get; }
}
// Un recours possible (contentieux, administratif, ...)
public interface IRemedy
{
Type ProcedureType { get; }
string? FilingDate { get; }
}Quatre interfaces. ICase positionne la personne dans un espace et un temps. ICircumstance décrit les circonstances de sa situation — chaque circonstance est typée par une catégorie qui vient du cadre Common.* ou du cadre Citizen.* choisi. IQuestion est ce que la personne demande au système : est-ce que mon bailleur peut augmenter mon loyer ? Ai-je droit au RSA majoré ? IRemedy est le recours que le système recommande si un droit est activé mais non respecté.
Deux vocabulaires pour la même Mathilde
La spécificité du cadre citoyen importe autant que celle du cadre juridique ou politique. Et c'est ici que la pluralité des cadres prend une dimension très concrète, très humaine.
Un cadre Citizen.France2026.Etalab.Dsl, maintenu par l'administration, aura un vocabulaire de circonstances institutionnel : locataire de logement social, parent isolé, demandeur d'asile, bénéficiaire du RSA, allocataire CAF. Ce vocabulaire reflète les catégories administratives existantes — celles que les formulaires Cerfa connaissent, celles que les bases de données de la CAF et de Pole Emploi utilisent.
Un cadre Citizen.France2026.SecoursPopulaire.Dsl, maintenu par une association de terrain, aura un vocabulaire de circonstances vécu : personne en hébergement d'urgence, famille à la rue, travailleur sans papiers, femme en situation de violence conjugale, mineur non accompagné. Ce vocabulaire reflète les réalités que les travailleurs sociaux rencontrent quotidiennement — des réalités que les catégories administratives ne capturent pas toujours, ou capturent avec un décalage qui perd l'essentiel.
La même Mathilde Martin peut typer son cas dans l'un ou dans l'autre cadre. Avec Citizen.France2026.Etalab, elle est locataire de logement social, parent isolé, deux enfants à charge. Avec Citizen.France2026.SecoursPopulaire, elle est famille monoparentale en impayé de loyer, enfants en liste d'attente pour la cantine. Les deux descriptions sont vraies. Elles ne couvrent pas les mêmes aspects de sa situation, et le bridge Law.Citizen.Bridge (régime 6) ne lui retournera pas les mêmes diagnostics.
Voici à quoi ressemble le cas de Mathilde dans le cadre Etalab :
using Citizen.France2026.Etalab.Attributes;
using Common.France2026.Etalab.Spaces;
using Common.France2026.Etalab.Periods;
// Circonstances du vocabulaire institutionnel Etalab
using Common.France2026.Etalab.Circumstances;
using Common.France2026.Etalab.Domains;
[Case(typeof(FR), typeof(Y2026))]
public sealed class MathildeCase
{
[Circumstance(typeof(SocialHousingTenant))]
[Circumstance(typeof(SingleParent))]
[Circumstance(typeof(IncomeBelowPovertyThreshold))]
[Circumstance(typeof(DependentChildren))]
public int NumberOfChildren => 2;
[Question(typeof(HousingRent))]
public void CanMyLandlordRaiseMyRent() { }
[Question(typeof(SocialBenefits))]
public void BenefitsIfJobLoss() { }
[Question(typeof(LitigationRemedy))]
public void RentDisputeRemedy() { }
}using Citizen.France2026.Etalab.Attributes;
using Common.France2026.Etalab.Spaces;
using Common.France2026.Etalab.Periods;
// Circonstances du vocabulaire institutionnel Etalab
using Common.France2026.Etalab.Circumstances;
using Common.France2026.Etalab.Domains;
[Case(typeof(FR), typeof(Y2026))]
public sealed class MathildeCase
{
[Circumstance(typeof(SocialHousingTenant))]
[Circumstance(typeof(SingleParent))]
[Circumstance(typeof(IncomeBelowPovertyThreshold))]
[Circumstance(typeof(DependentChildren))]
public int NumberOfChildren => 2;
[Question(typeof(HousingRent))]
public void CanMyLandlordRaiseMyRent() { }
[Question(typeof(SocialBenefits))]
public void BenefitsIfJobLoss() { }
[Question(typeof(LitigationRemedy))]
public void RentDisputeRemedy() { }
}Et le même cas dans le cadre Secours Populaire, avec un vocabulaire différent :
using Citizen.France2026.SecoursPopulaire.Attributes;
using Common.France2026.Etalab.Spaces;
using Common.France2026.Etalab.Periods;
// Circonstances du vocabulaire de terrain
using Citizen.France2026.SecoursPopulaire.Circumstances;
using Citizen.France2026.SecoursPopulaire.Domains;
[Case(typeof(FR), typeof(Y2026))]
public sealed class MathildeCase
{
[Circumstance(typeof(SingleParentFamilyInRentArrears))]
[Circumstance(typeof(ChildrenOnCanteenWaitingList))]
[Circumstance(typeof(PartTimeEmployee))]
public int NumberOfChildren => 2;
[Question(typeof(EmergencyHousingAccess))]
public void AmIEligibleForEmergencyHousing() { }
[Question(typeof(FoodAid))]
public void CanteenAccessForMyChildren() { }
[Question(typeof(EmploymentRights))]
public void RightsAsPartTimeWorker() { }
}using Citizen.France2026.SecoursPopulaire.Attributes;
using Common.France2026.Etalab.Spaces;
using Common.France2026.Etalab.Periods;
// Circonstances du vocabulaire de terrain
using Citizen.France2026.SecoursPopulaire.Circumstances;
using Citizen.France2026.SecoursPopulaire.Domains;
[Case(typeof(FR), typeof(Y2026))]
public sealed class MathildeCase
{
[Circumstance(typeof(SingleParentFamilyInRentArrears))]
[Circumstance(typeof(ChildrenOnCanteenWaitingList))]
[Circumstance(typeof(PartTimeEmployee))]
public int NumberOfChildren => 2;
[Question(typeof(EmergencyHousingAccess))]
public void AmIEligibleForEmergencyHousing() { }
[Question(typeof(FoodAid))]
public void CanteenAccessForMyChildren() { }
[Question(typeof(EmploymentRights))]
public void RightsAsPartTimeWorker() { }
}Les deux fichiers décrivent la même personne. Mais le vocabulaire du Secours Populaire voit des choses que le vocabulaire Etalab ne voit pas — les impayés de loyer en tant que situation d'urgence, pas en tant que catégorie administrative. Et réciproquement, le vocabulaire Etalab ouvre des questions (le recours contentieux sur le loyer) que le vocabulaire du Secours Populaire ne pose pas, parce que son public ne passe pas par le contentieux.
C'est une autre couche de pluralité des cadres : pas seulement qui type le droit (Law), pas seulement qui juge les biens (Commons), mais qui type les destinataires du droit (Citizen). Chaque couche est plurielle, et chaque pluralité est traçable.
Ce que le bridge fait du cas citoyen
Law.Citizen.Bridge (régime 6, développé en Partie 12) lit le cas de Mathilde et le corpus juridique typé Law.France2026.Etalab. Pour chaque [Question], il cherche les articles applicables en fonction des [Circumstance] déclarées, et émet des diagnostics CIT001..CIT099 qui répondent aux questions en pointant vers les articles de loi. Pas un chatbot qui hallucine des réponses plausibles — un compilateur qui joint des types.
Si Mathilde a une question HousingRent et une circonstance SocialHousingTenant, le bridge cherche les articles du Code de la construction qui s'appliquent aux logements sociaux en matière de loyer, et émet CIT012 : Votre loyer est encadré par l'article L442-1 du Code de la construction — augmentation maximale indexée sur l'IRL. Si elle a une circonstance SingleParent et une question SocialBenefits, il émet CIT023 : Vous activez le RSA majoré via l'article L262-9 du Code de l'action sociale.
Le CodeFixer ActivateRightCodeFix va plus loin : il propose d'ajouter [Activate(typeof(MajoredRSA))] à la propriété de Mathilde et génère un fichier MySteps.g.md avec les étapes administratives concrètes. Le compilateur ne se contente pas de pointer le droit non-activé ; il propose le geste pour l'activer. C'est la différence entre une UI passive (un message d'erreur) et une UI émancipatrice (une action proposée).
Symétrie architecturale des quatre racines
Les quatre racines du système forment une symétrie claire :
| Racine | Domaine | Ce qu'elle type | Qui l'utilise |
|---|---|---|---|
Common.Dsl |
Technique | Biens, espaces, périodes, procédures (neutre) | Tous les autres cadres |
Law.Dsl |
Juridique | Containers, subdivisions, mesures, procédures démocratiques | Législateurs, juristes |
Commons.Dsl |
Politique | Statuts des biens, frontières entre statuts | Militants, associations, think tanks |
Citizen.Dsl |
Réception | Cas, circonstances, questions, recours | Citoyens, travailleurs sociaux |
Chaque racine est un projet C# minimal avec des interfaces. Chaque racine est instanciée par des cadres ${DslName}.${Space}${Time}.${Author}.Dsl générés par un méta-générateur. Chaque paire de racines est reliée par un bridge (Law.Commons.Bridge, Law.Citizen.Bridge) qui vérifie la cohérence inter-cadres au compile-time. Et chaque couche est plurielle : plusieurs cadres concurrents peuvent coexister pour le même (Space, Time), offrant des perspectives différentes sur les mêmes objets techniques.
Le bridge ne tranche jamais entre cadres concurrents. Il vérifie la cohérence interne de chaque paire choisie. Le ConsensusScorer (cf. Partie 9) exhibe les divergences entre cadres concurrents. Et le choix du cadre reste un acte politique explicite, matérialisé par un <PackageReference> dans le csproj — pas enfoui dans une configuration cachée ou un paramètre par défaut invisible.
Pour aller plus loin
- Partie 7 —
Law.Dslracine +LawDslGenerator(precedente) - Partie 9 —
Law.Commons.Bridge(suivante) - Hub de la serie
- Partie 2 — Convention de nommage pour la distinction
Common/Commons - Partie 6 —
Common.Dslpour la racine technique que tous les cadres referent