temps de lecture : 10 minutes

repositories/ — la zone fonctionnelle qui héberge le code de mon workspace — contient 18 projets. Huit sont open source sous Apache 2.0. Un seul est closed source — le SaaS Edster. Pendant des mois, ces neuf projets ont cohabité dans le même dossier, séparés uniquement par…​ rien. Leur statut public/privé était une information dans ma tête, pas dans le système de fichiers.

Et puis j’ai voulu brancher un RAG. Et là, tout s’est effondré.

1. L’Accident qui Attendait de se Produire

En avril 2026, j’ai commencé à implémenter le RAG pgvector pour slider-gradle. Le principe : indexer tous les dépôts de repositories/, produire des embeddings, et les injecter dans le contexte du LLM pour qu’il ait une "perception" du dossier repositories/.

Le pipeline était simple :

val repos = fileTree(rootDir) {
    include("**/*.adoc", "**/*.kts", "**/*.kt", "**/*.json", "**/*.yml")
}
val chunks = repos.map { chunk(it) }
val embeddings = chunks.map { embed(it) }
pgvector.insert(embeddings)

Simple. Efficace. Et dangereux.

Parce que ce fileTree ne fait pas la différence entre plantuml-gradle/ (Apache 2.0, public) et edster/ (closed source, private). Il avale tout. Et si un jour je publie ces embeddings — sur un dashboard, dans une réponse LLM, dans un dataset d’entraînement — le code propriétaire d’Edster fuit.

Le problème n’est pas qu’un LLM lise du code closed source. Le problème, c’est que ce code finisse dans un embedding vectoriel public — irréversible, non supprimable, non auditable.

1.1. La Vraie Question

Ce n’est pas "comment empêcher le LLM de fuiter du code ?". C’est "comment rendre la fuite structurellement impossible ?"

La réponse n’est pas un prompt. La réponse, c’est de couper le dossier en deux.

2. La Solution : OSS/ et CSS/

Avant :

__repositories__/
├── plantuml-gradle/       ← public
├── bakery-gradle/         ← public
├── magic_stick/           ← public
├── edster/                ← PRIVÉ
├── slider-gradle/         ← public
└── ...                    ← mélange invisible

Après :

__repositories__/
├── OSS/                   ← tout est Apache 2.0
│   ├── plantuml-gradle/
│   ├── bakery-gradle/
│   ├── magic_stick/
│   ├── slider-gradle/
│   └── ...
└── CSS/                   ← tout est closed source / private
    └── edster/

Le fileTree devient :

val ossRepos = fileTree(File(rootDir, "OSS")) {
    include("**/*.adoc", "**/*.kts", "**/*.kt", "**/*.json", "**/*.yml")
}
val cssRepos = fileTree(File(rootDir, "CSS")) {
    include("**/*.adoc", "**/*.kts", "**/*.kt", "**/*.json", "**/*.yml")
}

// Embeddings publics — OSS seulement
pgvectorPublic.insert(ossRepos.map { chunk(it) }.map { embed(it) })

// Dataset fine-tuning privé — CSS seulement
fineTuningDataset.insert(cssRepos.map { chunk(it) })  // jamais publié

Le RAG public ne voit jamais CSS/. Le code closed source alimente un dataset privé de fine-tuning — un modèle interne qui ne sort jamais de chez moi. La séparation est mécanique, pas déclarative.

3. Pourquoi "OSS" et "CSS" et Pas "Public" et "Private"

Le choix des acronymes OSS (Open Source Software) et CSS (Closed Source Software) est délibéré :

  • "Public"/"Private" décrit la visibilité (ce que GitHub voit)

  • "OSS"/"CSS" décrit la nature du code (ce que le LLM doit savoir)

La visibilité GitHub est une métadonnée du repo distant. La nature du code est une propriété du contenu. Le LLM n’a pas accès à l’API GitHub — mais il a accès au système de fichiers. CSS/ lui dit "attention, ce code n’est pas sous licence libre" sans avoir besoin de lire un LICENSE ou de parser un package.json.

Le nom du dossier est la métadonnée la plus robuste que vous puissiez donner à un LLM. Elle ne peut pas être mal parsée, ignorée, ou mal interprétée. Le LLM voit CSS/edster/ dans le chemin du fichier — il sait.

3.1. L’Arbre Complet — Quatre Zones, Pas Trois

La granularisation OSS/CSS complète l’ontologie à trois zones. La zone repositories/ passe de 1 à 2 sous-zones fonctionnelles :

L’organisation de __repositories__/ en deux sous-zones OSS et CSS

| Zone | RGPD | Indexation RAG public | Dataset fine-tuning | | Racine | Niveau 0 — Intime | ✗ | ✗ | | configuration/ | Niveau 1 — Restreint | ✗ | ✗ | | office/ | Niveau 2 — Collaboratif | ✓ Filtré | ✓ Filtré | | repositories/CSS/ | Niveau 3 — Ouvert conditionnel | ✗ | ✓ Privé | | repositories/OSS/ | Niveau 4 — Public natif | ✓ Libre | ✓ Public |

4. Le Cas Particulier : cheroliv.com

Pendant que j’y étais, j’ai corrigé une autre incohérence. Mon site cheroliv.com vivait dans repositories/ comme un projet logiciel à part entière — avec son propre build Gradle, sa propre CI, sa propre gouvernance .agents/.

Mais les articles de blog ne sont pas du code. Ce sont des données éditoriales de Cercle 2 — au même titre que les cadrages de office/pilotage/ ou les formations de office/formations/.

AVANT                            APRÈS
__repositories__/                office/
  cheroliv.com/                    sites/
    site/jbake/content/blog/         cheroliv.com/
      2026/0117_....adoc               2026/0117_....adoc

Ce qui change :

  • Les articles sont dans office/ → confidentialité de Cercle 2

  • La tâche publishSite devient une capability de workspace-engine via bakery-gradle — le plugin reçoit un FileTree, il ne sait pas que les articles viennent de office/

  • Une seule CI, une seule gouvernance — zéro duplication

  • Le classifier Vision/Opinion (Règle 2bis) s’applique automatiquement : les articles dans office/ sont filtrés avant publication

4.1. bakery-gradle S’En Fiche

Et c’est ça qui est beau. Le plugin bakery-gradle n’a pas été modifié. Il reçoit un répertoire de contenu AsciiDoc, il génère du HTML, il push sur GitHub Pages. Que ce répertoire s’appelle site/jbake/content/ ou office/sites/cheroliv/ — le plugin s’en fout.

// workspace-engine/build.gradle.kts
task("publishBlog") {
    doLast {
        val articles = fileTree("../../office/sites/cheroliv/2026/")
        bakery.generate(articles)  // ← bakery ne sait pas d'où ça vient
    }
}

Le contrat d’interface est une tâche Gradle. Pas une API REST. Pas un webhook. Une tâche — typée, testable, exécutable en local et dans la CI.

5. Impact sur la Gouvernance Agent

La granularisation OSS/CSS modifie la gouvernance agent sur trois points :

  1. RAG public : le périmètre d’indexation passe de repositories/ à repositories/OSS/ + office/Vision/**. Le code closed source est exclu de l’index par configuration de tâche, pas par prompt.

  2. Knowledge Graph : graphify-gradle produit deux graphes :

    • office/graph.json (public) — relations entre artéfacts OSS + office/Vision

    • configuration/graph_private.json (gitignored, privé) — relations entre artéfacts CSS, jamais publié

  3. Dataset fine-tuning : codebase-gradle a désormais deux sources :

    • OSS/ → datasets publics (modèles communautaires, benchmarks)

    • CSS/ → datasets privés (modèle interne, fine-tuning propriétaire)

6. Ce Qu’on Gagne (Et Ce Qu’on Perd)

Avant (dossier plat) Après (OSS/CSS + office/sites)

ls repositories/ → mélange public/privé

ls repositories/OSS/ → tout est publiable

RAG indexe tout sans discrimination

RAG indexe OSS/ + office/Vision exclusivement

Impossible de savoir si un projet est open source

Le chemin OSS/ ou CSS/ le dit

cheroliv.com a sa propre CI, sa propre gouvernance

La CI et la gouvernance sont mutualisées dans workspace-engine

Les articles de blog sont dans un dépôt "code"

Les articles de blog sont dans office/sites/ — data, pas code

Risque de fuite de code closed source dans des embeddings publics

Structurellement impossible — CSS/ hors scope RAG

Le seul coût : un git mv global sur les 17 dépôts OSS + un refactor du build.gradle.kts de workspace-engine. C’est une migration à froid — pas de données en vol, pas d’utilisateurs impactés.

7. Conclusion : le Signal Physique Bat le Signal Logiciel

Ce que j’ai fait cet après-midi, c’est remplacer une convention invisible par une séparation visible dans la zone repositories/. Avant, vous deviez savoir que edster/ était closed source. Maintenant, vous le voyez — le dossier s’appelle CSS/.

C’est le même principe que les permissions Unix, les VLAN réseau, ou les compartiments coupe-feu dans une base de données. La sécurité ne doit pas être une information que vous devez retenir. Elle doit être une propriété physique du système.

La granularisation OSS/CSS est la traduction de ce principe dans le domaine de la gouvernance LLM. Le LLM n’a pas besoin de savoir qu’Edster est confidentiel. Il a besoin d’être incapable de l’indexer.

Et c’est exactement ce que CSS/ garantit.