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

Law.QualityGate — couverture juridique alinéa par AC

Le QualityGate est un pattern que j'utilise depuis des années dans mon infrastructure logicielle. Quatre interfaces SOLID, chacune avec une seule méthode, qui forment ensemble un pipeline de mesure de la qualité d'un artefact compilé. Le package existant mesure la qualité du code — couverture de tests, mutation score, complexité cyclomatique, cohésion. Le Law.QualityGate fait exactement la même chose, transposé au droit : couverture des alinéas par des tests, mutation score juridique des mesures, classification des frontières traversées, densité de la traçabilité démocratique. La transposition est structurelle — les interfaces ont la même forme, les fakes ont la même discipline, le rapport a la même structure. Ce qui change, c'est le vocabulaire du domaine, pas l'architecture du pipeline.

Le tableau de transposition

Pour que la transposition soit précise, voici le mapping terme à terme entre le QualityGate existant et sa version juridique.

QualityGate (existant) Law.QualityGate (nouveau)
ISolutionLoader ILawCorpusLoader
ICoverageReportParser (Cobertura) IAlineaTestCoverageParser (lit les [Verifies] du package Requirements appliqués aux alinéas)
IMutationReportParser (Stryker) ILegalMutationReportParser (mutations sémantiques de mesures/statuts/frontières)
ProjectAnalyzer → TypeMetrics → MethodMetrics LawAnalyzer → ArticleMetrics → AlineaMetrics
ComplexityAnalyzer, CohesionAnalyzer AlineaCoverageAnalyzer, MeasureClassificationAnalyzer, BoundaryTraversalDensityAnalyzer
quality-gate.yml law-gates.yml
QualityReport.json LawQualityReport.json
Scriban → run-dashboard.html Scriban → law-dashboard.html (heatmap article × alinéa × procédure)
4 interfaces SOLID (1 méthode chacune) 4 mêmes interfaces, transposées
Hand-written fakes Idem : FakeLawCorpusLoader, RoslynLawTestHelper

La symétrie est délibérée. Le pattern a été éprouvé en production sur du code C# pendant des années. La transposition juridique ne change que le vocabulaire des types qui traversent les interfaces — la mécanique du pipeline reste identique.

Les quatre interfaces SOLID

Chaque interface a une seule responsabilité, une seule méthode, et un contrat clair.

ILawCorpusLoader

// Charge un corpus juridique compilé à partir de son assembly
// Équivalent de ISolutionLoader pour le code source
public interface ILawCorpusLoader
{
    // Charge tous les articles, alinéas, mesures, frontières
    // depuis l'assembly compilée du cadre Law.${Space}${Period}.${Author}
    LawCorpusModel Load(string assemblyPath);
}

L'implémentation réelle utilise la réflexion sur l'assembly compilée pour reconstruire le LawCorpusModel — les mêmes types (ArticleFqn, AlineaFqn, AssetFqn) que ceux produits par le bridge SG à la Partie 10, mais lus depuis les métadonnées de l'assembly au lieu d'être extraits de l'arbre syntaxique Roslyn. Le fake, FakeLawCorpusLoader, retourne un modèle construit en mémoire sans jamais toucher au disque — ce qui permet de tester le pipeline entier sans compiler quoi que ce soit.

IAlineaTestCoverageParser

// Lit la couverture de tests par alinéa
// Équivalent de ICoverageReportParser (Cobertura) pour le code
public interface IAlineaTestCoverageParser
{
    // Chaque alinéa a-t-il au moins un [Verifies] pointant vers lui ?
    // Le package Requirements (existant) est réutilisé ici
    AlineaCoverageReport Parse(string coverageReportPath, LawCorpusModel corpus);
}

Le lien avec le package Requirements existant (cf. la série Feature Tracking) est direct : chaque Feature du package Requirements peut être liée à un alinéa juridique via un [Verifies("DC-95-65", Alinea = 1)]. Le parser lit ces liaisons et construit une carte de couverture alinéa par alinéa. Un alinéa non couvert par aucun test est un trou dans la vérification — exactement comme une méthode non couverte par Cobertura est un trou dans la couverture de code.

ILegalMutationReportParser

// Lit le rapport de mutations sémantiques juridiques
// Équivalent de IMutationReportParser (Stryker) pour le code
public interface ILegalMutationReportParser
{
    // Mutations testées : inverser un MeasureKind, supprimer une frontière,
    // changer un StatusKind, ajouter/retirer une procédure
    LegalMutationReport Parse(string mutationReportPath, LawCorpusModel corpus);
}

Le mutation testing juridique est l'analogue exact du mutation testing logiciel. Stryker, pour du code, introduit des mutations (inverser une condition, supprimer un return, changer un + en -) et vérifie que les tests détectent chaque mutation. Le mutateur juridique fait la même chose sur le modèle : il inverse un MeasureKind.Suppression en MeasureKind.Restoration, il supprime une frontière, il change un StatusKind.Public en StatusKind.Private, il retire une [DemocraticProcedure]. Si le test ne détecte pas la mutation, c'est que le test est trop faible — il accepte un droit muté sans broncher, ce qui est aussi grave en droit qu'en code.

ILawReportWriter

// Écrit le rapport de qualité juridique dans les formats de sortie
// Équivalent de IReportWriter pour le code
public interface ILawReportWriter
{
    void WriteJson(LawQualityReport report, string outputPath);
    void WriteHtml(LawQualityReport report, string outputPath);
    void WriteSummary(LawQualityReport report, TextWriter output);
}

L'implémentation HTML utilise Scriban pour générer un dashboard avec une heatmap visuelle — la pièce maîtresse du rapport, décrite plus bas.

La hiérarchie de métriques juridiques

Le rapport de qualité est structuré selon la hiérarchie naturelle du droit codifié français. Ce n'est pas un choix arbitraire — c'est la structure dans laquelle les juristes naviguent quotidiennement, et c'est la structure qui rend la heatmap lisible.

LawQualityReport
  └── CorpusMetrics[]                    (par instance Law.${Space}${Period}.${Author})
      └── CodificationMetrics[]          (par code : Code de l'environnement, Code du travail, ...)
          └── TitleMetrics[]
              └── ChapterMetrics[]
                  └── ArticleMetrics[]   (couverture, complexité, dépendances)
                      └── AlineaMetrics[](couverture par AC, mutation score)

Chaque niveau agrège les métriques de ses enfants. Un CodificationMetrics dont tous les ArticleMetrics enfants ont 100% de couverture d'alinéas est vert. Un ChapterMetrics dont un seul article a une mesure non couverte est orange. Un ArticleMetrics avec une frontière traversée sans procédure est rouge. La heatmap rend cette hiérarchie visible d'un coup d'oeil, exactement comme un rapport Cobertura rend visible la couverture de code par namespace, classe et méthode.

Pour le common law, la hiérarchie serait différente : Statute → Section → Subsection. C'est l'analyzer qui traduit selon l'instance Law.${Space}${Period}.${Author} consommée — le pipeline, lui, reste identique.

law-gates.yml — les six seuils

Le fichier de configuration des gates est le contrat entre l'auteur du cadre et le pipeline de qualité. Chaque gate est un seuil numérique avec une sémantique juridique précise.

gates:
  # 100% des alinéas ont au moins un test [Verifies]
  min-alinea-coverage: 1.0

  # 100% des mesures avec frontière ont [DemocraticProcedure]
  min-measure-procedure-coverage: 1.0

  # Aucun article sans Feature liée (Requirements)
  max-orphan-articles: 0

  # 95% des Features ont au moins 1 alinéa qui les implémente
  min-feature-coverage: 0.95

  # Aucune [Measure] sans Type IAsset valide
  max-untyped-measures: 0

  # Le mutation score juridique minimal
  min-mutation-score: 0.85

Les deux premiers gates sont les plus critiques. min-alinea-coverage: 1.0 signifie que chaque alinéa du corpus doit être couvert par au moins un test — aucun trou n'est accepté. min-measure-procedure-coverage: 1.0 signifie que chaque mesure traversant une frontière doit avoir sa procédure démocratique déclarée — exactement la vérification que le bridge SG fait au compile-time (cf. Partie 10), mais mesurée ici à l'échelle du corpus entier plutôt qu'article par article.

Le sixième gate, min-mutation-score: 0.85, mérite une explication. Un mutation score de 85% signifie que 85% des mutations sémantiques introduites dans le corpus (inverser un type de mesure, supprimer une frontière, changer un statut) sont détectées par les tests existants. Les 15% restants sont des mutations non détectées — des changements de droit que les tests actuels ne remarqueraient pas. C'est un indicateur de la vivacité du corpus de tests : un mutation score faible signifie que les tests sont trop tolérants, qu'ils acceptent des changements de droit sans réagir. L'objectif est de le monter progressivement vers 95% au fil des itérations.

La CLI

L'invocation est un one-liner qui prend l'assembly compilée et le fichier de configuration :

dotnet law-quality-gate test \
  --corpus Law.France1995.Sample/bin/Debug/net10.0/Law.France1995.Sample.dll \
  --config Law.France1995.Sample/law-gates.yml

La commande génère trois artefacts :

  • law-quality-report.json — le rapport structuré, lisible par un pipeline CI, avec la hiérarchie complète corpus → codification → titre → chapitre → article → alinéa et les métriques à chaque niveau
  • law-dashboard.html — le dashboard visuel avec la heatmap, ouvrable dans n'importe quel navigateur
  • summary.txt — le résumé textuel, pour les logs

Le code de retour est 0 si tous les gates passent, 1 sinon. C'est le même pattern que n'importe quel outil de CI : le pipeline de build appelle dotnet law-quality-gate test, et le build échoue si un gate est violé.

Le dashboard HTML — la heatmap article × alinéa × procédure

Le dashboard HTML est la pièce maîtresse de la sortie. Il est généré par Scriban à partir du LawQualityReport et il présente une heatmap à deux dimensions : les articles en lignes, les alinéas en colonnes, et la couleur de chaque cellule indique le niveau de couverture et de conformité procédurale.

Une cellule verte signifie que l'alinéa est couvert par au moins un test, que ses mesures ont toutes les procédures requises, et que le mutation score est au-dessus du seuil. Une cellule orange signifie qu'il y a une couverture partielle — par exemple l'alinéa est testé mais une de ses mesures a un mutation score faible. Une cellule rouge signifie qu'il y a un manque critique — un alinéa non couvert, ou une mesure sans procédure. Une cellule grise signifie que l'alinéa n'a pas de mesure et n'est donc pas dans le périmètre du bridge (c'est un alinéa purement déclaratif, sans effet juridique mesurable par le système).

La troisième dimension — la procédure — est accessible au hover : survoler une cellule affiche la liste des procédures déclarées, les frontières traversées, les tests qui couvrent l'alinéa, et le mutation score local. C'est l'analogue exact d'un rapport Cobertura interactif, mais le vocabulaire est juridique au lieu d'être technique.

Hand-written fakes et testabilité

Conformément au pattern QualityGate existant, toutes les implémentations des quatre interfaces ont des fakes écrits à la main — pas des mocks générés, pas des stubs magiques, mais des classes concrètes qui implémentent l'interface avec des données en mémoire.

// Fake pour les tests — retourne un corpus construit en mémoire
public sealed class FakeLawCorpusLoader : ILawCorpusLoader
{
    private readonly LawCorpusModel _corpus;

    public FakeLawCorpusLoader(LawCorpusModel corpus) => _corpus = corpus;

    public LawCorpusModel Load(string assemblyPath) => _corpus;
}
// Helper pour construire des CrossModel de test sans compiler de code
public static class RoslynLawTestHelper
{
    // Crée un corpus minimal avec un article, un alinéa, une mesure
    public static LawCorpusModel CreateMinimalCorpus(
        string articleNumber = "TEST-001",
        MeasureKindCode measureKind = MeasureKindCode.Suppression,
        bool withProcedure = false)
    {
        // ... construit le modèle en mémoire
    }
}

L'intérêt des fakes écrits à la main est triple. D'abord, ils sont lisibles — un juriste qui lit le code du fake comprend exactement ce que le test vérifie, sans devoir décrypter une API de mocking. Ensuite, ils sont stables — ils ne dépendent d'aucune bibliothèque tierce et ne cassent pas quand on monte de version. Enfin, ils sont exhaustifs — chaque fake est une documentation vivante de ce que l'interface attend, plus fiable qu'un commentaire XML.

Le pattern complet en action

Pour résumer le flux complet : l'auteur d'un cadre juridique écrit ses articles, alinéas, mesures et procédures en C# avec des attributs. Le bridge SG (cf. Partie 9 et Partie 10) vérifie la cohérence cross-DSL au compile-time et émet des diagnostics sur les violations individuelles. Le Law.QualityGate prend le relais après le build et mesure la couverture globale du corpus : combien d'alinéas sont testés, combien de mesures sont correctement procédurées, combien de mutations sont détectées. Le SG dit « cet alinéa-ci a un problème » ; le QualityGate dit « 87% du corpus est couvert, et voici les 13% qui ne le sont pas, classés par codification, titre, chapitre, article, alinéa ».

Les deux outils sont complémentaires. Le SG est le garde-fou du quotidien — il empêche de compiler du droit incohérent. Le QualityGate est le tableau de bord stratégique — il mesure l'avancement du typage d'un corpus juridique entier et identifie les zones qui nécessitent du travail supplémentaire. Ensemble, ils forment une infrastructure de qualité juridique dont le pattern est éprouvé (il fonctionne depuis des années pour la qualité du code) et dont la transposition au droit est structurelle, pas métaphorique.

La Partie 12 — Les six régimes montre comment les tests unitaires, les tests d'intégration, les simulations et le property-based testing viennent alimenter les métriques que le QualityGate mesure.


Partie précédente : Partie 10 — Trace d'exécution Dumas | Partie suivante : Partie 12 — Les six régimes | Retour au sommaire

⬇ Download