Files
khadhroony-solana-project/docs/plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md

43 KiB

Plan 0.1.3 — Configuration foundation

1. Statut et objectif

Ce plan a été établi par 0.1.3-pre.001, puis corrigé par 0.1.3-pre.001-fix.001 avant tout développement fonctionnel de Config.

La base auditée reste la release stable v0.1.2.

La release 0.1.3 introduira ksp-config-lib comme propriétaire unique KSP de la configuration applicative. À terme, les autres crates et applications KSP ne doivent pas :

  • lire directement les documents JSON de configuration ;
  • valider elles-mêmes ces documents contre leurs schémas ;
  • résoudre elles-mêmes les profils ou les compositions ;
  • lire directement les variables applicatives KSP_* / KSPB_* par std::env::* ;
  • charger directement le fichier .env ;
  • interpréter elles-mêmes les expressions ${KSP_...} ;
  • écrire directement les documents Config ou le .env géré par KSP.

Elles passent par les contrats publics de ksp-config-lib.

ksp-config-lib doit fournir le moteur commun nécessaire à :

  • la lecture des documents spécialisés JSON ;
  • la validation JSON Schema ;
  • les paramètres globaux hors profils ;
  • default_profile ;
  • les profils ;
  • les compositions propres aux exécutables/applications ;
  • la lecture des variables provenant du processus et d'un .env ;
  • la résolution des placeholders ${NAME} et ${NAME:-fallback} dans les valeurs de configuration ;
  • la classification Public / Internal / Secret ;
  • la conservation de la provenance et d'une représentation sûre/redacted des valeurs résolues ;
  • les diagnostics de variables manquantes ;
  • la manipulation et la persistence autorisées des documents JSON ;
  • la création/modification/suppression des entrées du .env ;
  • la construction des contrats runtime nécessaires aux consommateurs, en commençant par ksp_logging_lib::LoggingSettings.

0.1.3-pre.001-fix.001 reste une tranche documentaire de conception. Il ne crée pas encore ksp-config-lib, ne crée aucun fichier runtime Config et n'ajoute aucune dépendance fonctionnelle.

2. Audit de la base stable 0.1.2

Le workspace stable contient actuellement :

crates/ksp-core-lib
crates/ksp-logging-lib

Le manifest racine utilise :

workspace.package.version = "0.1.2"
edition = "2024"

Aucune crate Config n'existe encore et aucun répertoire runtime config/ n'est présent dans la base stable fournie.

2.1 Core disponible

ksp-core-lib fournit déjà :

ksp_core_lib::Error
ksp_core_lib::ErrorCode
ksp_core_lib::ErrorContext
ksp_core_lib::Result<T>

Les codes propres à Config restent possédés par ksp-config-lib, sous le domaine Config. Core ne reçoit aucune connaissance métier Config.

2.2 Logging disponible

ksp-logging-lib possède déjà :

LoggingSettings
LogFilterLevel
SpanEvents
ConsoleSettings
ConsoleOutput
FileSettings
FileRotation
TargetFilter
LoggingGuard
initialize(...)
reinitialize(...)

Config ne redéfinit pas ces contrats. Il lit et résout sa configuration Logging, puis construit explicitement un ksp_logging_lib::LoggingSettings.

La direction de dépendance reste :

ksp-config-lib  -> ksp-core-lib
ksp-config-lib  -> ksp-logging-lib
ksp-logging-lib -X-> ksp-config-lib
ksp-core-lib    -X-> ksp-config-lib

2.3 Règles déjà fixées

Les règles actuelles imposent notamment :

  • Rust 2024 ;
  • unsafe_code = "forbid" ;
  • pas de unwrap, expect, panic ou ? dans le code production ;
  • façade publique au crate-root, modules privés, pas de mod.rs ;
  • tests unitaires hors src selon la convention du dépôt ;
  • dépendances externes communes sous [workspace.dependencies] puis .workspace = true ;
  • aucune dépendance ajoutée avant son usage réel ;
  • ksp-logging-lib reste le seul propriétaire KSP direct de tracing, tracing-subscriber et tracing-appender.

3. Réaudit de la référence historique bot3

La référence historique bot3 utilisait déjà :

  • plusieurs documents spécialisés ;
  • un .env à la racine ;
  • des placeholders comme ${KS_LOGS_DIRECTORY:-logs} ;
  • des URLs composées contenant ${KS_SECRET_HELIUS_API_KEY} ;
  • des compositions applicatives ;
  • une classification de sensibilité dérivée des placeholders.

Cette référence reste utile, mais KSP ne copie pas ses structures ni ses APIs aveuglément.

3.1 Principes conservés et renforcés

KSP conserve :

  • documents spécialisés indépendants ;
  • valeurs globales hors profils lorsqu'elles ne varient pas ;
  • default_profile autonome ;
  • composition propre à un exécutable/application ;
  • sélection d'un profil spécialisé depuis la composition ;
  • .env séparé des documents JSON ;
  • priorité de l'environnement réel du processus sur .env ;
  • fallback déclaré au point d'usage via ${NAME:-fallback} ;
  • classification nominale des variables ;
  • propagation de la sensibilité la plus forte dans une valeur composée ;
  • distinction entre valeur réelle et représentation diagnostic sûre ;
  • DTO Tauri possédés par l'application et non par ksp-config-lib.

3.2 Corrections par rapport au plan pre.001 initial

Le plan initial de pre.001 avait retenu à tort :

  • config/environment.env au lieu d'un vrai .env ;
  • une grammaire d'environnement KSP spéciale ;
  • l'interdiction de l'interpolation ${...} ;
  • des bindings d'environnement prédéclarés clé Config -> variable ;
  • une lecture limitée aux variables enregistrées par ces bindings.

Ces décisions sont annulées.

La règle corrigée est :

Une référence ${KSP_...} ou ${KSPB_...} présente dans une valeur JSON est elle-même une déclaration explicite d'usage de cette variable par le document.

Config peut également exposer une API explicite de lecture d'une variable par nom avec fallback optionnel pour les rares usages qui ne proviennent pas d'un document JSON, mais les consumers ne lisent jamais directement std::env.

4. Décision de périmètre : 0.1.3 reste une seule release

Le périmètre reste compatible avec une seule release si la première implémentation concrète reste bornée.

La release construit le moteur générique nécessaire à :

  1. documents JSON spécialisés ;
  2. schémas ;
  3. paramètres globaux ;
  4. profils et default_profile ;
  5. compositions ;
  6. .env + environnement du processus ;
  7. interpolation/fallback ;
  8. provenance/sensibilité/redaction ;
  9. mutation/persistence ;
  10. adaptation Logging.

Mais le seul document spécialisé runtime créé et exercé concrètement en 0.1.3 est :

config/std.logging.json

Aucun std.store.json, std.wallet.json, std.onchain-transport.json ou autre fichier futur n'est créé prématurément.

La séquence reste :

0.1.3  ksp-config-lib
0.1.4  ksp-app-config-desk

5. Matrice de responsabilités

Responsabilité Propriétaire Consommateurs Interdit
Lire un document Config JSON ksp-config-lib crates/apps via API Config lecture directe par consumer
Valider JSON Schema ksp-config-lib orchestration/management validation divergente dans chaque crate
Résoudre globals/profils/compositions ksp-config-lib orchestration résolution locale dans un binaire
Lire KSP_* / KSPB_* du processus ksp-config-lib crates/apps via API Config std::env::var* applicatif hors Config
Lire .env ksp-config-lib crates/apps via API Config loader dotenv direct hors Config
Résoudre ${...} et fallback ksp-config-lib tous les consumers interpolation locale dans les consumers
Classer sensibilité et construire une valeur sûre ksp-config-lib runtime/logging/diagnostics redaction ad hoc dans chaque crate
Modifier/sauvegarder JSON Config ksp-config-lib management explicite écriture directe par application
Créer/modifier/supprimer une entrée .env ksp-config-lib management explicite édition directe par application
Modifier l'environnement externe du shell/systemd/parent propriétaire externe de ce processus Config le lit seulement prétendre qu'un child process peut administrer son parent
Posséder LoggingSettings ksp-logging-lib Config le construit copie du type dans Config
Posséder LoggingGuard orchestration/application lifecycle Logging singleton Config global
Initialiser/recharger Logging orchestration Config fournit settings/changement Logging lisant Config
DTO/bindings Tauri application Tauri frontend TS-RS automatique dans Config

6. Arborescence et nomenclature

6.1 Documents runtime

Les vrais documents JSON runtime appartiennent sous :

config/

Nomenclature retenue pour les documents spécialisés/unitaires :

config/std.<domain>.json

Premier fichier concret :

config/std.logging.json

Exemples futurs, uniquement lorsqu'ils deviennent nécessaires :

config/std.store.json
config/std.wallet.json
config/std.onchain-transport.json

6.2 Compositions

Les compositions propres à un exécutable/application utilisent :

config/composite.<consumer>.json

Exemple futur :

config/composite.ksp-app-wallet-desk.json

Aucun composite runtime fictif n'est créé avant l'existence d'un consumer concret. Le moteur et le schéma génériques peuvent néanmoins être testés dans 0.1.3 par fixtures/examples.

6.3 Schémas

Les schémas appartiennent sous :

config/schemas/

Première surface candidate :

config/schemas/std.logging.schema.json
config/schemas/composite.schema.json

La nomenclature d'un futur document suit la même famille :

std.store.json
std.store.schema.json

6.4 Exemples

Les exemples appartiennent sous :

config/examples/

Exemples candidats :

config/examples/std.logging.example.json
config/examples/composite.example.json

Les exemples ne contiennent aucun vrai secret.

6.5 .env

Le fichier d'environnement local par défaut est le fichier conventionnel :

.env

à la racine runtime/workspace fournie à Config.

Il reste ignoré par Git. Un éventuel .env.example peut être versionné plus tard lorsqu'une première variable concrète doit être documentée.

La racine runtime et les chemins Config doivent être fournis explicitement à la session/locator Config ; ksp-config-lib ne doit pas rendre son comportement dépendant d'un current working directory implicite.

7. Contrat commun des documents JSON

Chaque document JSON KSP possède une version de format :

{
  "format_version": 1
}

Politique :

  • format_version obligatoire ;
  • version inconnue refusée ;
  • JSON syntaxiquement invalide refusé ;
  • validation par le schéma correspondant avant utilisation runtime ;
  • invariants sémantiques supplémentaires après validation schema ;
  • document source modifiable représenté par un type sérialisable ;
  • configuration effective contenant potentiellement des secrets non sérialisable aveuglément ;
  • persistence JSON avec format stable/lisible et newline final ;
  • aucun secret ajouté implicitement dans un message d'erreur ou un Debug.

Les placeholders d'environnement sont autorisés uniquement dans les valeurs string du JSON. Ils ne sont pas interprétés dans les noms de propriétés.

Le schéma source doit donc autoriser explicitement une valeur littérale ou une expression Config lorsqu'un champ est substituable.

8. Premier document spécialisé : std.logging.json

Logging est le seul composant runtime existant qui justifie aujourd'hui un document Config réel.

Structure conceptuelle candidate :

{
  "format_version": 1,
  "logs_directory": "${KSP_LOGS_DIRECTORY:-logs}",
  "default_profile": "default",
  "profiles": [
    {
      "name": "default",
      "default_filter": "info",
      "span_events": "new_and_close",
      "console": {
        "output": "stdout"
      },
      "file": {
        "file_name_prefix": "ksp",
        "rotation": "daily"
      },
      "target_filters": []
    }
  ]
}

Décisions :

  • logs_directory est global, hors profils ;
  • son fallback est exprimé dans le document au point d'usage ;
  • default_profile est global et référence un profil existant ;
  • les noms de profils sont uniques ;
  • file ne duplique pas le répertoire global ;
  • console/file peuvent être désactivés selon le schéma final ;
  • target_filters conserve l'ordre source ;
  • Config valide le document puis construit LoggingSettings ;
  • LoggingSettings::validate() reste l'ultime validation du contrat Logging.

std.logging.json sert de premier test réel du moteur générique, mais son modèle ne doit pas enfermer Config dans Logging.

9. Paramètres globaux et profils

Un document spécialisé distingue :

paramètres globaux
+ default_profile
+ profiles[]

Une valeur qui ne varie pas avec le profil reste globale.

Le résultat effectif d'un document spécialisé est conceptuellement :

globals
+ selected profile
+ environment substitutions contained in those values

Le profil ne recopie pas artificiellement les globals.

Sans composition :

profil explicitement demandé par API
    sinon document.default_profile

Un default_profile absent peut être autorisé uniquement pour un type de document dont le contrat le prévoit explicitement. Pour std.logging.json, il est requis.

10. Contrat de composition générique

Une composition assemble plusieurs documents spécialisés sans dupliquer leur contenu.

Structure conceptuelle :

{
  "format_version": 1,
  "default_profile": "default",
  "documents": {
    "logging": {
      "source": "std.logging.json"
    }
  },
  "profiles": [
    {
      "name": "default",
      "documents": {
        "logging": {
          "profile": "default"
        }
      }
    }
  ]
}

Principes :

  • la composition référence des documents spécialisés ;
  • chaque document conserve ses globals ;
  • une composition peut choisir le profil d'un document ;
  • sans choix explicite, le default_profile du document est utilisé ;
  • le composite ne recopie pas les paramètres spécialisés ;
  • un profil composite assemble donc les paramètres globaux du document et le profil sélectionné de ce document ;
  • un document référencé est toujours lu et résolu par Config ;
  • profil/document inexistant = erreur ;
  • aucun JSON patch générique n'est introduit en 0.1.3 sans besoin concret.

Cette structure répond au besoin « un composite pioche dans les documents et leurs profils/paramètres » sans dupliquer les valeurs.

Les références cross-document à une propriété individuelle ne sont pas ouvertes tant qu'un cas concret ne les exige pas.

11. Modèle d'environnement

11.1 Namespaces

Les variables applicatives admises sont :

KSP_SECRET_*
KSP_PUBLIC_*
KSP_*

KSPB_SECRET_*
KSPB_PUBLIC_*
KSPB_*

Classification :

KSP_SECRET_* / KSPB_SECRET_* -> Secret
KSP_PUBLIC_* / KSPB_PUBLIC_* -> Public
autre KSP_* / KSPB_*         -> Internal

Ordre de sensibilité :

Secret > Internal > Public

Un nom hors namespace KSP/KSPB n'est pas une variable applicative gérée par le moteur Config générique, sauf éventuel bootstrap technique explicitement documenté plus tard.

11.2 Sources et priorité

Pour une variable NAME, la résolution est :

1. environnement réel du processus
2. .env
3. fallback déclaré au point d'usage
4. missing

Donc :

export KSP_PUBLIC_FOO=console

est prioritaire sur :

KSP_PUBLIC_FOO=dotenv

et les deux sont prioritaires sur :

${KSP_PUBLIC_FOO:-fallback}

11.3 Sémantique du fallback

Syntaxes initiales :

${KSP_VAR}
${KSP_VAR:-fallback}

Dans KSP, :- signifie dans ce contrat :

utiliser le fallback seulement si la variable n'existe ni dans l'environnement du processus ni dans .env.

Une variable explicitement définie avec une chaîne vide est considérée comme définie. KSP ne promet donc pas de reproduire toutes les subtilités d'expansion POSIX malgré une syntaxe volontairement familière.

Le fallback appartient à l'usage dans le document, pas au .env.

11.4 Interpolation dans une chaîne composée

Exemple :

{
  "url": "https://mainnet.helius-rpc.com/?api-key=${KSP_SECRET_HELIUS_API_KEY}"
}

Le resolver doit pouvoir produire :

real:
https://mainnet.helius-rpc.com/?api-key=abcdef123456

safe:
https://mainnet.helius-rpc.com/?api-key=********

Plusieurs placeholders peuvent apparaître dans une même chaîne.

La sensibilité de la chaîne résolue est la sensibilité la plus forte de tous les placeholders utilisés.

Si le placeholder est KSP_SECRET_*, le fragment substitué est masqué dans la représentation sûre même lorsque la valeur vient du fallback.

11.5 .env n'est pas le lieu des fallbacks Config

Le .env sert de source locale de valeurs :

KSP_SECRET_HELIUS_API_KEY=...
KSP_PUBLIC_FOO=bar

La première version KSP ne dépend pas d'une expansion récursive de variables à l'intérieur du .env. Les fallbacks et compositions sont résolus dans les documents Config via ${...}.

Le parser .env retenu doit au minimum gérer proprement :

  • lignes vides ;
  • commentaires ;
  • NAME=VALUE ;
  • valeurs usuelles non quotées ;
  • valeurs quotées nécessaires aux espaces/caractères spéciaux ;
  • noms KSP/KSPB ;
  • erreurs de syntaxe diagnostics sans fuite de secret.

La grammaire exacte et la stratégie de round-trip sont fixées au delta qui implémente le parser, mais aucun loader ne doit écraser les variables déjà présentes dans le processus.

12. Référence d'environnement = déclaration d'usage

Le plan ne maintient plus de table statique du type :

logging.logs_directory -> KSP_LOGS_DIRECTORY

La déclaration :

"logs_directory": "${KSP_LOGS_DIRECTORY:-logs}"

est suffisante pour dire à Config :

  • quelle variable est utilisée ;
  • où elle est utilisée ;
  • quel fallback est prévu ;
  • quelle sensibilité s'applique d'après son nom ;
  • quelle provenance doit être reportée après résolution.

Un document peut donc introduire plus tard :

${KSP_SECRET_POSTGRES_MAINNET_URL}
${KSP_SECRET_HELIUS_API_KEY}
${KSP_WALLETS_DIRECTORY:-wallets}

sans ajouter une allowlist centrale spécifique à chaque domaine.

Config doit néanmoins valider que le nom référencé appartient bien aux namespaces autorisés.

13. API directe de variables d'environnement

Certains besoins futurs peuvent demander une variable sans qu'elle provienne d'un champ JSON.

Cette lecture reste possédée par Config et peut être modélisée conceptuellement par une requête :

name
fallback: Option<String>

avec exactement la même priorité :

process > .env > fallback

Ainsi aucune autre crate n'a de raison d'appeler std::env::var(...) pour une variable applicative KSP/KSPB.

Cette API directe ne devient pas un getter arbitraire permettant d'énumérer tous les secrets. Elle résout un nom explicitement demandé et applique les mêmes règles de sensibilité/diagnostic.

14. Provenance et résultat résolu

Une valeur effective ne doit pas être réduite trop tôt à un String nu lorsque sa provenance ou sa sensibilité sont nécessaires.

Conceptuellement, Config doit pouvoir représenter :

ResolvedValue<T>
    value          valeur réelle destinée au runtime
    safe_value     représentation sûre destinée aux logs/diagnostics
    sensitivity    Public | Internal | Secret
    provenance     source(s) ayant participé à la résolution

Pour une chaîne composée, la provenance peut contenir plusieurs segments/références.

Sources candidates :

DocumentLiteral
EnvironmentProcess { name }
EnvironmentDotEnv { name }
EnvironmentFallback { name }

Un type contenant une valeur secrète ne doit pas dériver un Debug qui révèle value. Son Debug/Display par défaut doit utiliser la représentation sûre ou ne pas être implémenté.

L'accès à la valeur réelle doit être explicite dans l'API.

15. Secret, public, internal et redaction

15.1 Public

Une valeur dérivée uniquement de KSP_PUBLIC_*/KSPB_PUBLIC_* peut être exposée dans une projection publique lorsque le contrat applicatif le prévoit.

15.2 Internal

Une valeur KSP_*/KSPB_* non public/secret est utilisable par le runtime. Son exposition générique externe n'est pas automatique ; elle peut être montrée dans un diagnostic debug explicitement prévu.

15.3 Secret

Une valeur provenant d'un KSP_SECRET_*/KSPB_SECRET_* :

  • doit être accessible en clair au runtime légitime qui en a besoin ;
  • doit être accessible en clair à une surface de management explicitement privilégiée lorsqu'elle doit l'afficher/modifier ;
  • ne doit jamais apparaître en clair dans les logs Config ;
  • ne doit jamais apparaître en clair dans un message d'erreur ordinaire ;
  • ne doit pas être exposée par un Debug générique ;
  • doit être remplacée par une représentation telle que ******** dans une chaîne destinée au logging/diagnostic.

Une URL contenant un secret reste donc utilisable réellement par un futur transport tout en offrant une chaîne sûre pour le logging.

15.4 Limite de la garantie

ksp-config-lib fournit les types et représentations empêchant les fuites accidentelles dans le chemin normal. Il ne peut pas empêcher un consumer qui demande explicitement la valeur réelle puis choisit volontairement de la logger.

Les règles KSP doivent donc imposer aux consumers d'utiliser la représentation sûre dans les logs.

ksp-logging-lib ne scanne toujours pas les messages à la recherche de secrets.

16. Variables manquantes et warning obligatoire

Cas :

{
  "url": "${KSP_PUBLIC_RPC_URL}"
}

Si KSP_PUBLIC_RPC_URL :

  • n'existe pas dans le processus ;
  • n'existe pas dans .env ;
  • n'a pas de fallback ;

alors Config doit au minimum :

  1. produire un diagnostic structuré missing environment variable without fallback ;
  2. émettre un warn sous le target ksp-config-lib lorsque Logging est disponible ;
  3. inclure le nom de variable, le document et le chemin JSON ;
  4. ne jamais inclure une valeur secrète ;
  5. considérer la résolution effective comme incomplète.

Pour une configuration runtime qui exige une valeur effective complète, cette situation devient une erreur de résolution et empêche la construction du contrat runtime.

La lecture/édition du document source reste toutefois possible dans une application de management afin que l'utilisateur puisse corriger la configuration.

Exemple de diagnostic sûr :

target = ksp-config-lib
level = warn
variable = KSP_SECRET_HELIUS_API_KEY
document = config/std.onchain-transport.json
path = $.profiles[0].url
reason = environment variable is missing and no fallback is declared

Le nom de la variable n'est pas secret ; sa valeur éventuelle l'est.

17. Validation en plusieurs phases

17.1 Document source

Pipeline :

path
-> read UTF-8
-> JSON syntax
-> format_version
-> JSON Schema source
-> typed source document
-> invariants sémantiques source

Le schéma valide le fichier tel qu'il est écrit, placeholders compris.

17.2 Sélection profil/composition

Puis :

select composition profile if any
-> select each specialized document profile
-> combine document globals + selected profile

17.3 Résolution environnement

Puis, pour chaque chaîne substituable :

parse placeholders
-> process lookup
-> .env lookup
-> fallback
-> missing diagnostic
-> build real + safe value + provenance + sensitivity

17.4 Validation effective

Après résolution :

  • aucun placeholder requis ne doit rester non résolu pour un runtime complet ;
  • les contraintes métier effectives sont vérifiées ;
  • la conversion vers le contrat du composant est exécutée ;
  • le composant propriétaire valide son propre contrat public lorsque disponible.

Pour Logging :

std.logging.json
-> schema/source validation
-> selected profile
-> env resolution
-> ResolvedLoggingConfig
-> LoggingSettings
-> LoggingSettings::validate()

Cette séparation évite de confondre « JSON source valide » et « configuration runtime utilisable ».

18. Lecture et mutation du .env

18.1 Lecture

Config charge le .env sans remplacer les valeurs déjà présentes dans l'environnement du processus.

Il construit sa propre vue :

ProcessEnvironment
DotEnv
Fallback

et choisit la source effective selon la priorité définie.

18.2 Mutation persistante

La surface management de Config doit pouvoir :

create env entry
update env entry
remove env entry
read env entry

sur le .env sélectionné.

Une modification du .env ne prétend pas modifier :

  • le shell parent ;
  • une unité systemd ;
  • Docker/Kubernetes ;
  • l'environnement d'un autre processus.

Si une variable est aussi définie dans le processus, la modification du .env réussit mais la valeur effective reste celle du processus.

Le résultat de mutation doit donc signaler :

source_changed
effective_changed
shadowed_by_process_environment
reload_required

18.3 Rust 2024

En Rust 2024, la mutation globale du process par std::env::set_var/remove_var impose des contraintes unsafe; KSP interdit unsafe.

La première surface KSP traite donc :

  • l'environnement du processus comme source read-only ;
  • .env comme source persistante read/write possédée par Config.

Cette distinction correspond aussi à la réalité opérationnelle : un processus enfant ne peut pas réécrire l'environnement de son shell parent ou d'un service manager déjà lancé.

19. Mutation et persistence des documents JSON

19.1 Surface initiale

Dans 0.1.3, le document réellement modifiable est :

config/std.logging.json

Les fixtures de composition peuvent exercer le moteur générique sans créer un composite runtime fictif.

19.2 API

Config ne fournit pas une primitive publique « écrire n'importe quel JSON à n'importe quel chemin ».

La mutation porte sur un document Config connu :

load typed source
-> modify allowed fields
-> validate candidate
-> serialize
-> persist atomically

Une API management peut fournir des helpers plus ergonomiques, mais elle passe toujours par la validation Config.

19.3 Atomicité

Garantie requise :

ancien fichier complet
OU
nouveau fichier complet
jamais un fichier destination partiellement écrit

Même garantie pour .env.

Un échec de validation ou d'écriture avant commit conserve l'ancien fichier utilisable.

La stratégie exacte d'écriture atomique et la dépendance éventuelle sont auditées au delta qui implémente cette surface.

19.4 Chemins

Les documents sous config/ sont bornés par un ConfigRoot explicite.

Les compositions ne doivent pas pouvoir sortir de cette frontière par un chemin absolu ou .. non autorisé.

Le .env est une source distincte, localisée par un chemin de session Config explicite avec défaut workspace/runtime .env.

20. Surface runtime, diagnostic et management

Trois intentions doivent être distinguées :

ConfigRuntime
ConfigDiagnostics
ConfigManagement

Les noms Rust exacts pourront évoluer, mais la séparation est normative.

20.1 Runtime

Un consumer demande la configuration dont il a besoin.

Il peut obtenir une valeur réelle nécessaire au fonctionnement et sa représentation sûre lorsqu'elle peut être loggée.

Il ne reçoit pas automatiquement une map de tous les secrets ou toute la configuration du projet.

20.2 Diagnostics

La surface diagnostic ne révèle pas les secrets.

Elle expose notamment :

  • configured/missing ;
  • provenance ;
  • source shadowed ;
  • safe value ;
  • document/path ;
  • erreurs de validation.

20.3 Management privilégié

Une application telle que ksp-app-config-desk doit pouvoir, via Config :

  • lire un document source ;
  • lire ses valeurs effectives ;
  • modifier et sauvegarder le document ;
  • inspecter les entrées .env ;
  • créer/modifier/supprimer une entrée .env ;
  • révéler explicitement une valeur Secret pour affichage/édition légitime ;
  • voir qu'une valeur .env est shadowed par le process ;
  • relancer la résolution après modification.

Cette surface privilégiée ne rend pas les secrets autorisés dans les logs. L'exception concerne la visualisation/édition volontaire dans l'UI, pas le logging.

L'authentification/autorisation de l'utilisateur final appartient à l'application ; Config fournit une frontière d'API évitant l'exposition accidentelle.

21. Frontière Tauri future

ksp-config-lib ne dépend pas de Tauri ni de TS-RS.

En 0.1.4, ksp-app-config-desk possédera :

  • commandes Tauri ;
  • DTO publics ;
  • DTO diagnostics ;
  • DTO management privilégiés ;
  • logique d'autorisation UI si nécessaire.

L'application n'utilise ni std::env, ni parser dotenv, ni lecture JSON directe pour contourner Config.

22. Relation Config -> Logging

22.1 Conversion

config/std.logging.json
-> résolution globale/profil/env
-> ResolvedLoggingConfig
-> ksp_logging_lib::LoggingSettings

22.2 Lifecycle

Config n'appelle pas automatiquement initialize au chargement.

L'orchestration fait :

Config resolve
-> LoggingSettings
-> ksp_logging_lib::initialize(...)
-> conserve LoggingGuard

Puis après modification :

Config resolve
-> nouveaux LoggingSettings
-> orchestration décide
-> ksp_logging_lib::reinitialize(&mut guard, ...)

Config peut produire un ChangeReport, mais ne possède jamais LoggingGuard.

22.3 Propriété du guard

  • dans 0.1.3, le harness d'intégration possède localement Config + LoggingGuard ;
  • dans 0.1.4, l'état backend de ksp-app-config-desk les possède ;
  • aucun singleton global Config n'est introduit.

Target Config :

ksp-config-lib

Les warnings de variables manquantes ou autres diagnostics utiles sont réémis sous ce target.

23. Erreurs Config candidates

Les codes exacts sont ajoutés seulement lorsqu'ils deviennent nécessaires.

Familles candidates :

config.document_not_found
config.document_read_failed
config.invalid_utf8
config.invalid_json
config.unsupported_format_version
config.schema_validation_failed
config.invalid_document
config.profile_not_found
config.invalid_composition
config.path_outside_root
config.invalid_environment_name
config.dotenv_read_failed
config.dotenv_parse_failed
config.dotenv_write_failed
config.environment_variable_missing
config.environment_value_invalid
config.placeholder_invalid
config.placeholder_unresolved
config.secret_access_denied
config.mutation_not_allowed
config.persistence_failed
config.effective_validation_failed

Une erreur liée à KSP_SECRET_* peut contenir le nom de variable et son origine, mais jamais sa valeur réelle.

24. Dépendances externes candidates

Aucune dépendance n'est ajoutée dans pre.001-fix.001.

Les besoins prévisibles sont :

  • serde : source typed sérialisable/désérialisable ;
  • serde_json : JSON ;
  • moteur JSON Schema ;
  • éventuellement une primitive d'écriture atomique ;
  • éventuellement une bibliothèque dotenv uniquement si elle permet la lecture sans mutation globale du processus et si sa sémantique est compatible avec le contrat KSP.

Les versions courantes et les features sont vérifiées uniquement au delta qui introduit réellement la dépendance, puis déclarées sous [workspace.dependencies] avec la contrainte de génération retenue par les règles KSP.

24.1 Parser dotenv

Aucune dépendance dotenv n'est décidée dans le plan.

Le choix doit respecter :

  • process env non écrasé ;
  • accès aux couples clé/valeur pour construire la vue Config ;
  • diagnostics contrôlables ;
  • possibilité de gérer la persistence .env ;
  • absence de comportement implicite contraire à la résolution KSP.

Si aucune bibliothèque ne convient proprement, un parser borné peut être possédé par Config. Le but n'est pas d'émuler un shell complet.

24.2 Placeholder resolver

La syntaxe ${NAME} / ${NAME:-fallback} dans les documents JSON est une responsabilité KSP, même si un parser dotenv tiers est utilisé pour .env.

Elle ne doit pas être déléguée à une expansion opaque qui ferait perdre provenance, sensibilité et représentation redacted par segment.

25. Surface Rust conceptuelle

Structure interne candidate :

src/lib.rs
src/error.rs
src/document.rs
src/schema.rs
src/profile.rs
src/composition.rs
src/environment.rs
src/placeholder.rs
src/sensitivity.rs
src/resolved.rs
src/logging.rs
src/diagnostics.rs
src/management.rs
src/persistence.rs

Les modules restent privés ; l'API utile est réexportée au crate-root.

Types conceptuels à faire émerger au besoin :

ConfigRoot
ConfigSession
ConfigDocumentKind
ConfigSource
CompositionDocument
CompositionProfile
EnvironmentSensitivity
EnvironmentSource
EnvironmentRequest
EnvironmentReference
ResolvedValue<T>
ConfigValueOrigin
ConfigDiagnostic
ConfigChangeReport
ConfigManagementAccess
LoggingConfigDocument
LoggingProfileConfig
ResolvedLoggingConfig

Cette liste n'impose pas de tout créer dans pre.002.

26. Audits et tests à introduire

26.1 Ownership

Empêcher les accès applicatifs directs hors Config :

  • std::env::var, var_os, vars pour KSP_*/KSPB_* ;
  • loaders dotenv directs ;
  • lecture/écriture directe des documents config/std.*.json et config/composite.*.json ;
  • dépendance directe tracing* depuis Config ;
  • dépendance inverse Logging -> Config ;
  • Tauri/TS-RS dans Config sans décision explicite.

L'audit doit être ciblé afin de ne pas interdire des usages système de std::env sans rapport avec la configuration applicative.

26.2 Documents/schemas

Tester :

  • JSON invalide ;
  • schema invalide ;
  • format version absent/inconnu ;
  • globals hors profils ;
  • default_profile ;
  • profils dupliqués ;
  • profil absent ;
  • composition valide/invalide ;
  • chemin hors ConfigRoot.

26.3 Environnement

Tester :

process > .env > fallback

ainsi que :

  • variable process seule ;
  • variable .env seule ;
  • process shadowing .env ;
  • fallback seul ;
  • chaîne vide définie ne déclenche pas fallback ;
  • variable manquante sans fallback -> diagnostic + warning + résolution runtime incomplète ;
  • namespace invalide ;
  • plusieurs placeholders dans une chaîne ;
  • placeholder malformé ;
  • .env invalide ;
  • commentaires/quotes du .env selon grammaire retenue.

Les tests de process env doivent éviter les races inter-tests, par isolation de processus ou autre stratégie sûre sans unsafe KSP.

26.4 Secrets

Canaries obligatoires :

SECRET-CANARY

Tester qu'elles n'apparaissent jamais dans :

  • Debug ;
  • warning Config ;
  • Error/ErrorContext ;
  • diagnostics génériques ;
  • safe value ;
  • logs de tests d'intégration.

Tester aussi qu'une valeur réelle reste accessible par le contrat runtime/management explicitement autorisé.

Exemple composé :

https://provider.invalid/?api-key=${KSP_SECRET_TEST_KEY}&network=${KSP_PUBLIC_NETWORK:-mainnet}

avec résultat sûr attendu du type :

https://provider.invalid/?api-key=********&network=mainnet

26.5 Persistence

Tester :

  • candidate JSON invalide n'altère pas le fichier ;
  • candidate .env invalide n'altère pas le fichier ;
  • create/update/remove .env ;
  • mutation shadowed par process rapportée ;
  • succès relisible ;
  • failure avant commit conserve ancien contenu ;
  • chemins gérés respectés.

26.6 Logging adapter

Tester :

  • logs_directory global avec fallback ;
  • sélection de profil ;
  • conversion de chaque enum ;
  • console/file ;
  • target filters ;
  • LoggingSettings::validate() ;
  • changement Config sans reconfiguration implicite ;
  • LoggingGuard reste orchestration-owned.

27. Découpage souple des prereleases

0.1.3-pre.001 + pre.001-fix.001 — audit, brainstorming et plan corrigé

  • audit v0.1.2 ;
  • audit historique ciblé ;
  • choix du propriétaire unique Config ;
  • correction vers .env conventionnel ;
  • interpolation ${...} + fallback ;
  • modèle real/safe/provenance/sensitivity ;
  • aucune crate/dépendance fonctionnelle Config.

0.1.3-pre.002 — fondation de crate + contrats source

  • créer ksp-config-lib ;
  • dépendances minimales réellement utilisées ;
  • erreurs initiales ;
  • ConfigRoot/session/locator ;
  • contrat commun document/version ;
  • premiers types std.logging source ;
  • tests initiaux.

0.1.3-pre.003 — JSON Schema + std.logging.json

  • config/, config/schemas/, config/examples/ ;
  • std.logging.schema.json ;
  • runtime config/std.logging.json ;
  • exemple Logging ;
  • pipeline parse/schema/semantic ;
  • default_profile et globals.

0.1.3-pre.004 — profils + composition

  • résolution de profils spécialisés ;
  • contrat composite générique ;
  • schema/example composite ;
  • sélection de profils documentaires ;
  • provenance document/global/profile ;
  • aucun composite runtime fictif obligatoire.

0.1.3-pre.005.env + resolver ${...}

  • lecture process env ;
  • lecture .env sans écrasement process ;
  • priorité process > .env > fallback ;
  • ${NAME} / ${NAME:-fallback} ;
  • missing diagnostics + warning ;
  • namespaces KSP/KSPB ;
  • tests d'isolation process env.

0.1.3-pre.006 — sensibilité + valeurs real/safe + Logging adapter

  • Public/Internal/Secret ;
  • propagation par chaîne composée ;
  • redaction par segment ;
  • contrat ResolvedValue ou équivalent ;
  • adaptation ResolvedLoggingConfig -> LoggingSettings ;
  • tests canary de non-divulgation ;
  • démonstration initialize/reinitialize avec guard orchestration-owned.

0.1.3-pre.007 — management + persistence JSON/.env

  • lecture management ;
  • reveal secret explicite ;
  • mutation typed de std.logging.json ;
  • create/update/remove .env ;
  • persistence atomique ;
  • desired/effective/shadow report.

0.1.3-pre.008 — ownership audits + robustesse

  • audits interdisant les accès Config/env directs ailleurs ;
  • invalid documents/env/placeholders ;
  • graphe dépendances/features ;
  • corrections de surface publique ;
  • robustesse des diagnostics sans fuite.

0.1.3-pre.009 — clôture

  • validations finales ;
  • documentation durable ;
  • cleanup/archivage requis ;
  • TODO fermés ou reportés ;
  • prompt 0.1.4 — ksp-app-config-desk ;
  • préparation rel.001 puis tag stable après validation utilisateur.

Le découpage reste souple et peut être corrigé par delta explicite.

28. Hors scope confirmé

  • application desktop Config dans 0.1.3 ;
  • Tauri/TS-RS dans ksp-config-lib ;
  • documents Store/Wallet/Transport/Execution avant leurs composants ;
  • watcher filesystem générique ;
  • reload automatique de tous les fichiers ;
  • service distribué de configuration ;
  • secrets manager distant ;
  • chiffrement maison de .env ;
  • modification du shell parent/systemd/Docker par Config ;
  • exposition générique de tous les secrets ;
  • redaction automatique de messages arbitraires par ksp-logging-lib ;
  • JSON patch arbitraire public ;
  • RPC/WS/provider ;
  • Program/decoder/execution ;
  • workers/jobs/pipelines ;
  • trading/ML.

29. Validations finales attendues pour 0.1.3

Au minimum :

cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test --workspace
cargo tree -p ksp-config-lib
cargo tree -p ksp-config-lib -d
cargo tree -p ksp-config-lib -e features

Exécuter aussi tout script d'audit réellement présent au moment de la clôture.

Une commande non exécutée n'est jamais déclarée réussie.

30. Critères de validation du plan avant pre.002

Le pre.001-fix.001 est validable lorsque les décisions suivantes sont acceptées :

  • ksp-config-lib est l'unique manager KSP des documents Config et variables applicatives ;
  • premier document réel : config/std.logging.json ;
  • futurs documents : config/std.<domain>.json ;
  • futurs composites : config/composite.<consumer>.json ;
  • JSON validé par schema avant usage ;
  • globals hors profils ;
  • default_profile autonome ;
  • composite assemble documents + globals + profils sans recopier les valeurs ;
  • .env conventionnel à la racine runtime/workspace ;
  • environnement du processus > .env > fallback ;
  • fallback déclaré par ${KSP_VAR:-fallback} ;
  • fallback utilisé seulement lorsque la variable est absente ; une chaîne vide compte comme définie ;
  • ${KSP_VAR} manquant sans fallback produit diagnostic + warning et empêche une résolution runtime complète ;
  • une référence ${KSP_...}/${KSPB_...} est elle-même la déclaration d'usage, sans table de bindings centrale ;
  • Config peut aussi résoudre explicitement une variable demandée par API ;
  • .env est read/write via Config ; process env est read-only ;
  • les autres crates n'appellent pas std::env::var(...) pour les variables applicatives ;
  • Secret reste disponible au runtime légitime mais dispose toujours d'une représentation sûre/redacted ;
  • une chaîne composée conserve valeur réelle + valeur sûre + provenance + sensibilité ;
  • ksp-app-config-desk pourra révéler/modifier les secrets via une surface management privilégiée ;
  • même cette application ne logge jamais les secrets ;
  • Config construit LoggingSettings et l'orchestration possède LoggingGuard ;
  • persistence JSON et .env atomique ;
  • 0.1.3 reste une release unique bornée ;
  • aucune implémentation pre.002 ne commence avant validation utilisateur de ce plan corrigé.