Skip to main content
Welcome. This site supports keyboard navigation and screen readers. Press ? at any time for keyboard shortcuts. Press [ to focus the sidebar, ] to focus the content. High-contrast themes are available via the toolbar.
serard@dev00:~/cv

Trace d'exécution — le compile error politique du cas Dumas 1995

Tout ce qui précède dans cette série — la convention de nommage, la filiation intellectuelle, la signature META(Ex × Ty), l'architecture en couches, les DSL racines, le bridge — converge ici. Cette partie est la démonstration end-to-end. Elle prend un cas historique réel, la décision n°95-65 du Conseil constitutionnel du 11 janvier 1995 (dite « cas Dumas »), et montre étape par étape comment le système produit un compile error politique dessus. Huit étapes, de A à H. Aucune n'est magique. Chacune utilise des mécanismes Roslyn documentés, existants, standards. Ce qui est nouveau, c'est le domaine auquel on les applique.

Le cas Dumas est un cas où le Conseil constitutionnel a rogné les comptes de campagne de Jacques Chirac et d'Édouard Balladur de 7,8 et 5,9 millions de francs respectivement — une mesure de suppression portant sur le financement public des campagnes. La question que le compilateur pose, dans le cadre Law.France1995.Erard, est simple : cette mesure de suppression a-t-elle été accompagnée de la procédure démocratique requise par la frontière Public → Contested définie dans le cadre Commons.France1995.Erard ? Si non, le compilateur émet LOI003. Si oui, le build passe.

Étape A — Le csproj charge le bridge SG comme analyzer

Le point d'entrée est un fichier projet standard. Law.France1995.Sample.csproj déclare trois familles de dépendances : les racines d'interfaces (les DSL communs), les instances M2 concrètes (les cadres spécifiques à France1995.Erard), et les analyzers/source generators chargés dans Roslyn via OutputItemType="Analyzer".

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <LangVersion>latest</LangVersion>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <!-- Racines d'interfaces -->
    <ProjectReference Include="..\Common.Dsl\src\FrenchExDev.Net.Common.Dsl\FrenchExDev.Net.Common.Dsl.csproj" />
    <ProjectReference Include="..\Law.Dsl\src\FrenchExDev.Net.Law.Dsl\FrenchExDev.Net.Law.Dsl.csproj" />
    <ProjectReference Include="..\Commons.Dsl\src\FrenchExDev.Net.Commons.Dsl\FrenchExDev.Net.Commons.Dsl.csproj" />

    <!-- Instances M2 concrètes — le cadre France 1995 vu par Erard -->
    <ProjectReference Include="..\Common.France1995.Erard.Dsl\Common.France1995.Erard.Dsl.csproj" />
    <ProjectReference Include="..\Law.France1995.Erard.Dsl\Law.France1995.Erard.Dsl.csproj" />
    <ProjectReference Include="..\Commons.France1995.Erard.Dsl\Commons.France1995.Erard.Dsl.csproj" />

    <!-- Analyzers + SG par famille — OutputItemType="Analyzer" les charge dans Roslyn -->
    <ProjectReference Include="..\Law.Dsl\src\FrenchExDev.Net.Law.Dsl.Analyzers\FrenchExDev.Net.Law.Dsl.Analyzers.csproj"
                      OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
    <ProjectReference Include="..\Commons.Dsl\src\FrenchExDev.Net.Commons.Dsl.Analyzers\FrenchExDev.Net.Commons.Dsl.Analyzers.csproj"
                      OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
    <ProjectReference Include="..\Common.Dsl\src\FrenchExDev.Net.Common.Dsl.Analyzers\FrenchExDev.Net.Common.Dsl.Analyzers.csproj"
                      OutputItemType="Analyzer" ReferenceOutputAssembly="false" />

    <!-- LE BRIDGE : cette ligne transforme le projet en "loi cross-vérifiée" -->
    <ProjectReference Include="..\Law.Commons.Bridge\src\Law.Commons.Bridge.SourceGenerator\Law.Commons.Bridge.SourceGenerator.csproj"
                      OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
    <ProjectReference Include="..\Law.Commons.Bridge\src\Law.Commons.Bridge.Analyzers\Law.Commons.Bridge.Analyzers.csproj"
                      OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
  </ItemGroup>
</Project>

La ligne OutputItemType="Analyzer" est le mécanisme MSBuild standard qui indique que ces projets ne sont pas des dépendances runtime mais des composants Roslyn à charger dans le pipeline du compilateur. C'est exactement le même mécanisme que n'importe quel analyzer NuGet — StyleCop, SonarAnalyzer, Meziantou. La seule différence est que le contenu analysé ici est du droit, pas du style de code. Le mécanisme, lui, est identique.

Étape B — Le code source de DecisionDumas sans procédure

Voici le fichier que l'auteur du cadre a écrit pour décrire la décision Dumas. Les identifiants sont en anglais ; les commentaires en français expliquent l'intention.

using Law.France1995.Erard.Attributes;       // pour [Article], [Alinea], [Measure], [DemocraticProcedure]
using FrenchExDev.Net.Law.Dsl;                // pour MeasureKind
using Common.France1995.Erard.Assets;
using Common.France1995.Erard.Spaces;
using Common.France1995.Erard.Periods;

namespace Law.France1995.Sample.Decisions;

// Décision n°95-65 du Conseil constitutionnel — 11 janvier 1995
// Le Conseil a rogné les comptes de campagne de Chirac et Balladur
[Article("DC-95-65",
         Space        = typeof(FR),
         Period       = typeof(Y1995),
         Codification = "Conseil constitutionnel — décision n°95-65 du 11 janvier 1995")]
public sealed class DecisionDumas
{
    // Alinéa 1 : la mesure effective — suppression partielle du financement public
    [Alinea(1, Text = "Le Conseil rogne les comptes de campagne de Chirac et Balladur " +
                       "de 7,8 et 5,9 millions de francs respectivement")]
    [Measure(MeasureKind.Suppression, typeof(PublicCampaignFunding))]
    // ⬆️ ICI : aucune [DemocraticProcedure] sur cette méthode.
    //          C'est l'absence qui sera détectée comme LOI003.
    public void Section1() { }
}

Et la description du statut de l'actif dans le cadre Commons :

using Commons.France1995.Erard.Attributes;
using FrenchExDev.Net.Commons.Dsl;
using Common.France1995.Erard.Assets;
using Common.France1995.Erard.Spaces;
using Common.France1995.Erard.Periods;
using Common.France1995.Erard.Procedures;

namespace Law.France1995.Sample.Statuses;

// Le financement public des campagnes est un bien public entre 1988 et 1995
// et son statut est contesté en 1995 suite à la décision Dumas
[Asset(typeof(PublicCampaignFunding))]
[Status(StatusKind.Public,    typeof(FR), typeof(P1988_1995))]
[Status(StatusKind.Contested, typeof(FR), typeof(Y1995))]
public sealed class PublicCampaignFundingInFrance { }

// Frontière : pour passer de Public à Contested, il faut un débat parlementaire en 2 lectures
[Boundary(StatusKind.Public, StatusKind.Contested, typeof(ParliamentaryDebateTwoReadings))]
public sealed class DemocraticLifeBoundaries { }

L'absence est le point névralgique. Il n'y a pas de [DemocraticProcedure(typeof(ParliamentaryDebateTwoReadings))] sur la méthode Section1(). C'est cette absence — pas une erreur de syntaxe, pas une valeur fausse, mais un manque structurel — que le détecteur va transformer en diagnostic LOI003.

Étape C — dotnet build et Roslyn charge le bridge SG

Quand on lance dotnet build Law.France1995.Sample.csproj, MSBuild appelle csc.exe, qui est Roslyn. Le compilateur lit tous les fichiers .cs du projet, lit la liste des composants référencés avec OutputItemType="Analyzer", charge Law.Commons.Bridge.SourceGenerator.dll, instancie chaque [Generator] et chaque [DiagnosticAnalyzer] marqué dans cette assembly, et lance le pipeline d'analyse suivi de la génération de code. Rien de tout cela n'est propre au projet — c'est le fonctionnement normal de Roslyn avec des source generators et des analyzers. La seule chose propre au projet est le contenu des generators et analyzers chargés.

Étape D — Le bridge SG construit le CrossModel

Dans LawCommonsBridgeGenerator.Initialize, les pipelines ForAttributeWithMetadataName se déclenchent. CrossModelExtractor.FromCompilation() est appelé. Pour chaque classe décorée avec un attribut implémentant ILegalContainer, l'extracteur construit un modèle sémantique typé :

LawArticleModel {
    ContainingType = ArticleFqn("global::Law.France1995.Sample.Decisions.DecisionDumas"),
    Number = "DC-95-65",
    Space = SpaceFqn("global::Common.France1995.Erard.Spaces.FR"),
    Period = PeriodFqn("global::Common.France1995.Erard.Periods.Y1995"),
    Alineas = [
        LawAlineaModel {
            Index = 1,
            Method = AlineaFqn("...DecisionDumas.Section1()"),
            Measures = [
                LawMeasureModel {
                    Kind = MeasureKindCode.Suppression,
                    TargetAsset = AssetFqn("global::Common.France1995.Erard.Assets.PublicCampaignFunding")
                }
            ],
            Procedures = [ ]    // ← VIDE — aucune [DemocraticProcedure] sur Section1
        }
    ]
}

CommonsAssetStatusModel {
    Asset = AssetFqn("global::Common.France1995.Erard.Assets.PublicCampaignFunding"),
    Statuses = [
        CommonsStatusModel { Kind = Public,    Space = "...FR", Period = "...P1988_1995" },
        CommonsStatusModel { Kind = Contested, Space = "...FR", Period = "...Y1995" }
    ]
}

CommonsBoundaryModel {
    From = StatusKindCode.Public,
    To = StatusKindCode.Contested,
    RequiredProcedure = ProcedureFqn("global::Common.France1995.Erard.Procedures.ParliamentaryDebateTwoReadings")
}

Tous les pivots sont des FQN typés — des record structs immutables, pas des strings. Le compilateur a déjà vérifié que les typeof(...) dans le code source sont des types valides au moment où il a compilé DecisionDumas.cs. Si PublicCampaignFunding n'existait pas comme type dans le cadre Common.France1995.Erard, on aurait eu un compile error C# normal — CS0246: The type or namespace name 'PublicCampaignFunding' could not be foundavant même que le bridge SG ne tourne. Le type system C# est la première ligne de défense ; le bridge SG est la seconde.

Étape E — FrontierCrossingDetector détecte la violation LOI003

Le FrontierCrossingDetector est une fonction pure. Il prend un CrossModel en entrée et retourne une liste de CrossingViolation. Pour la mesure de Section1, le raisonnement est le suivant.

Premièrement, le détecteur cherche le statut de l'actif ciblé par la mesure. Jointure sur AssetFqn : trouvé — PublicCampaignFunding est déclaré dans le cadre Commons.France1995.Erard. Deuxièmement, le détecteur vérifie que l'espace de l'article (FR) correspond à l'un des espaces déclarés dans les statuts de l'actif : oui, trouvé. Troisièmement, le détecteur détermine le statut courant de l'actif à la période de l'article (Y1995) : le statut le plus récent applicable est Public (issu de P1988_1995, qui couvre 1995). Quatrièmement, le détecteur calcule le statut cible impliqué par la mesure : ComputeTargetStatus(Public, Suppression) retourne Contested — une suppression d'un bien public le fait basculer vers un statut contesté. Cinquièmement, le détecteur cherche la frontière applicable : Boundary(Public → Contested, requires ParliamentaryDebateTwoReadings) — trouvée. Sixièmement, le détecteur cherche si la procédure requise est déclarée sur l'alinéa : alinea.Procedures.Any(p => p.Procedure == boundary.RequiredProcedure). La liste alinea.Procedures est vide. Any(...) retourne false. Violation confirmée.

// Résultat de l'analyse — la violation matérialisée comme valeur
violations.Add(new CrossingViolation(
    Code:     ViolationCode.LOI003_MissingProcedure,
    Severity: ViolationSeverity.Error,
    Article:  ArticleFqn("global::Law.France1995.Sample.Decisions.DecisionDumas"),
    Alinea:   1,
    Message:  "Measure Suppression on global::Common.France1995.Erard.Assets.PublicCampaignFunding " +
              "requires [DemocraticProcedure(typeof(global::Common.France1995.Erard.Procedures" +
              ".ParliamentaryDebateTwoReadings))] — absent"));

Ce raisonnement est déterministe, reproductible, et testable unitairement (c'est le régime 1 de la Partie 12). Il ne contient aucune heuristique, aucune probabilité, aucun réseau de neurones. C'est une jointure typée sur des FQN, suivie d'un test de présence sur une collection. La complexité est dans la modélisation (les types, les attributs, les frontières), pas dans le détecteur — et c'est voulu, parce que la modélisation est vérifiable par un juriste alors qu'un algorithme opaque ne l'est pas.

Étape F — Du modèle au Diagnostic Roslyn, en build et en temps réel

Le bridge SG transforme chaque violation en un Diagnostic Roslyn, avec sa Location précise dans le code source :

// Dans le source generator — au moment du build
foreach (var v in violations)
{
    // LocationIndex a été construit pendant l'extraction du CrossModel :
    // pour chaque [Measure] rencontré, on a stocké la Location du SyntaxNode
    var loc = locations.TryGet(v.Article, v.Alinea, v.Code) ?? Location.None;
    spc.ReportDiagnostic(BridgeDiagnostics.From(v, loc));
}

Le LocationIndex (vivant dans Bridge.Roslyn) a été construit pendant l'extraction du CrossModel : pour chaque attribut [Measure] rencontré dans l'arbre syntaxique, il a stocké la Location du SyntaxNode dans une table indexée par (ArticleFqn, AlineaIndex). Le bridge SG retrouve donc la ligne et la colonne précises du code source où l'attribut manquant devrait se trouver.

Mais le source generator n'est que la moitié de l'histoire. En parallèle — et c'est ce qui rend l'expérience démocratique vivante — le LawCommonsCoherenceAnalyzer (qui vit dans Bridge.Analyzers) fait exactement le même travail mais en temps réel dans l'IDE. Pendant que l'auteur tape [Measure(MeasureKind.Suppression, typeof(PublicCampaignFunding))], l'analyzer détecte immédiatement l'absence de [DemocraticProcedure(...)] requise par la frontière Public → Contested du cadre Commons.France1995.Erard, et souligne la ligne en rouge avant même que le fichier soit sauvegardé. Hover sur le soulignement affiche le message complet. Ctrl+. ouvre le MissingProcedureCodeFix qui propose d'ajouter la procédure manquante, avec la liste des procédures déclarées dans le cadre Commons référencé.

L'auteur ne lit pas un rapport : il dialogue avec le compilateur. Le SG viendra plus tard, au build, pour produire l'attestation cryptographique signée et le fichier .g.cs matérialisé. Mais le travail interactif — l'enseignement, la confrontation, la découverte du manque — a déjà eu lieu dans l'IDE. C'est la distinction fonctionnelle entre les deux : l'analyzer enseigne, le SG atteste (cf. Partie 9 — Le bridge).

Étape G — MSBuild échoue

$ dotnet build Law.France1995.Sample/Law.France1995.Sample.csproj

  Law.France1995.Sample -> ...

Law.France1995.Sample/Decisions/DecisionDumas.cs(13,6): error LOI003: Measure Suppression
on global::Common.France1995.Erard.Assets.PublicCampaignFunding requires
[DemocraticProcedure(typeof(global::Common.France1995.Erard.Procedures.ParliamentaryDebateTwoReadings))]
— absent
[/.../Law.France1995.Sample.csproj]

Build FAILED.

    1 Error(s)

Le compile error politique est produit. Ligne 13, colonne 6 — précisément le [Measure(...)] dans VS Code, Visual Studio, Rider, ou Lex Studio (cf. Partie 15). Hover dessus affiche le message complet. Click droit puis Quick Fix propose MissingProcedureCodeFix.AddDemocraticProcedure. Le message d'erreur contient les FQN complets des types impliqués, ce qui permet F12 (Go to Definition) sur chacun d'eux : on peut naviguer jusqu'à la définition de PublicCampaignFunding, de ParliamentaryDebateTwoReadings, de la frontière qui les relie. Le diagnostic est traçable de bout en bout.

Étape H — Le Quick Fix est appliqué, le build passe

L'auteur applique le Quick Fix, ou ajoute manuellement la procédure manquante :

// Après application du Quick Fix — la procédure démocratique est maintenant déclarée
[Alinea(1, Text = "Le Conseil rogne les comptes de campagne...")]
[Measure(MeasureKind.Suppression, typeof(PublicCampaignFunding))]
[DemocraticProcedure(typeof(ParliamentaryDebateTwoReadings), ReferenceDate = "1995-01-09")]   // ← ajouté
public void Section1() { }

dotnet build retourne 0. obj/Debug/net10.0/generated/.../LawCommonsCross.g.cs existe maintenant et contient le LawCommonsCross.ValidatedCrossings matérialisé — la preuve formelle que toutes les mesures du projet ont une procédure démocratique conforme à la frontière déclarée dans le cadre Commons référencé.

Le moment de vérité métacratique

C'est ici que la pluralité des cadres prend son sens politique complet.

La décision Dumas, codée dans le cadre Law.France1995.Erard, exigeait sa propre traçabilité démocratique pour compiler à l'intérieur de ce cadre-là. Le cadre ne condamne pas la décision — il exige sa documentation dans un type system manipulable, selon ses propres spécifications. La spécification de ce cadre-ci dit : « pour passer un bien public à un statut contesté, il faut un débat parlementaire en deux lectures ». C'est un choix de l'auteur du cadre, pas une loi de la nature.

Un autre auteur publiant Law.France1995.LaQuadrature pourrait imposer des spécifications plus strictes — par exemple exiger un référendum pour toute mesure touchant le financement démocratique, pas juste un débat parlementaire. La même DecisionDumas échouerait encore plus fort dans ce cadre-là. Un troisième auteur, Law.France1995.InstitutMontaigne, pourrait au contraire ne déclarer aucune frontière sur le financement public des campagnes, considérant que le Conseil constitutionnel est souverain en la matière et n'a besoin d'aucune procédure supplémentaire. La même DecisionDumas compilerait sans erreur dans ce troisième cadre.

Le ConsensusScorer (Partie 12 — Les six régimes, régime 5) peut alors agréger les verdicts des trois cadres et exhiber publiquement : « la décision Dumas compile dans 1 cadre sur 3 — divergence sur l'exigence de procédure démocratique pour les mesures touchant le financement public des campagnes ». Le désaccord devient typé, traçable, cadré. Personne ne peut prétendre que « tout le monde sait » ou que « c'est évident ».

Le compile error n'est pas une vérité absolue — c'est le verdict d'un cadre spécifique. Et c'est précisément ce qui rend ce verdict politiquement actionnable. Un verdict universel serait dogmatique — il prétendrait clore le débat. Un verdict spécifique est démocratique — il ouvre le débat en rendant les hypothèses explicites, les divergences mesurables, les alternatives navigables. Le même Quick Fix qui ajoute la procédure permet aussi de constater qu'il n'y a pas eu de débat parlementaire de trente jours en janvier 1995 — et donc que le Quick Fix pourrait être refusé, et que le compile error reste rouge à perpétuité dans ce cadre-là. C'est un diff Git éternel entre l'état du droit selon ce cadre et l'état où la procédure aurait été respectée pour ce cadre. La force démocratique du dispositif est exactement dans la spécificité du cadre, pas dans une prétention à l'universalité.

Ce que cette trace prouve

Cette trace prouve quatre choses. D'abord, que le mécanisme fonctionne end-to-end sur un cas historique réel — pas sur un jouet pédagogique, mais sur une décision du Conseil constitutionnel de 1995 avec un enjeu politique documenté. Ensuite, que chaque étape utilise un mécanisme Roslyn existant et documenté — OutputItemType="Analyzer", IIncrementalGenerator, ForAttributeWithMetadataName, ReportDiagnostic, Location, CodeFixProvider — sans aucune extension propriétaire du compilateur. Puis, que le retour est interactif (via les analyzers dans l'IDE) et formellement attestable (via le SG au build), et que les deux se complètent sans se remplacer. Enfin, que la pluralité des cadres est constitutive, pas accidentelle : le même cas peut compiler dans un cadre et échouer dans un autre, et c'est cette divergence qui fait de l'infrastructure un outil de débat public plutôt qu'un tribunal algorithmique.

La Partie 11 — Law.QualityGate montre ensuite comment mesurer la couverture de ces vérifications sur un corpus juridique entier. La Partie 12 — Les six régimes montre comment tester, simuler, comparer et mettre entre les mains des citoyens le même compilateur qui vient de produire ce diagnostic.


Partie précédente : Partie 9 — Le bridge | Partie suivante : Partie 11 — Law.QualityGate | Retour au sommaire

⬇ Download