# Plan `0.1.3` — Configuration foundation ## 1. Statut et objectif Ce plan est établi par `0.1.3-pre.001`, tranche obligatoire de brainstorming, audit et planification. La base auditée est la release stable `v0.1.2`. La release `0.1.3` introduira `ksp-config-lib` comme frontière KSP unique pour : - les documents de configuration spécialisés ; - les compositions propres aux exécutables/applications ; - la sélection et la résolution des profils ; - la lecture des variables d'environnement KSP/KSPB et d'un fichier d'environnement géré par Config ; - la validation syntaxique, structurelle, sémantique et effective ; - la classification public/interne/secret ; - les lectures runtime et management explicitement distinctes ; - les mutations et persistences explicitement autorisées ; - la construction de settings runtime appartenant à d'autres composants, en commençant par `ksp_logging_lib::LoggingSettings`. `0.1.3-pre.001` ne crée pas encore `ksp-config-lib`, n'ajoute aucune dépendance fonctionnelle et ne crée aucun fichier runtime Config. Il fixe d'abord les contrats à implémenter. ## 2. Audit de la base stable `0.1.2` Le workspace stable contient actuellement : ```text crates/ksp-core-lib crates/ksp-logging-lib ``` Le manifest racine utilise : ```text workspace.package.version = "0.1.2" edition = "2024" ``` Aucune crate Config n'existe encore et aucun répertoire `config/` n'est présent dans la base stable fournie. ### 2.1 Core disponible `ksp-core-lib` fournit déjà les contrats nécessaires à Config : ```text ksp_core_lib::Error ksp_core_lib::ErrorCode ksp_core_lib::ErrorContext ksp_core_lib::Result ``` Les erreurs propres à Config resteront déclarées dans `ksp-config-lib` avec le domaine `config`. Core ne recevra aucune connaissance métier Config. ### 2.2 Logging disponible `ksp-logging-lib` possède déjà : ```text LoggingSettings LogFilterLevel SpanEvents ConsoleSettings ConsoleOutput FileSettings FileRotation TargetFilter LoggingGuard initialize(...) reinitialize(...) ``` Config n'a donc aucune raison de redéfinir ces contrats. Il doit uniquement produire un `LoggingSettings` valide à partir de sa propre configuration effective. La direction reste : ```text 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 sous `unit_tests/` autant que possible et tests d'intégration sous `tests/` ; - toute dépendance externe centralisée dans `[workspace.dependencies]` ; - aucune dépendance ajoutée avant un usage réel ; - `ksp-logging-lib` reste l'unique propriétaire direct de `tracing`, `tracing-subscriber` et `tracing-appender`. ## 3. Réaudit de la référence historique bot3 L'archive `khadhroony-bot3` fournie contient une ancienne `ks-config` et les documents : ```text logging.config.json transport.config.json listeners.config.json store.config.json wallet.config.json execution.config.json kb-app-demo-desktop.default.config.json ``` Cette base est une référence historique, pas une source de vérité KSP. ### 3.1 Principes conservés Les éléments suivants restent pertinents : - documents spécialisés indépendants ; - valeurs globales hors profils ; - `default_profile` autonome dans chaque document spécialisé ; - composition propre à un exécutable ; - override de profil spécialisé depuis une composition ; - schémas sous un répertoire dédié ; - distinction source/runtime/public/diagnostic ; - classification nominale des variables d'environnement ; - propagation de la sensibilité la plus forte pour une valeur dérivée ; - absence de TS-RS dans la bibliothèque Config lorsque le contrat est purement backend ; - l'application Tauri possède ses DTO et ne sérialise pas aveuglément une configuration runtime complète. ### 3.2 Éléments explicitement non repris KSP ne reprend pas aveuglément : - le document runtime monolithique `AppConfig/ProfileConfig` historique ; - les documents transport/listeners/store/wallet/execution avant l'existence des composants correspondants ; - une section `application` opaque uniquement pour conserver un ancien runtime ; - l'interpolation générique de chaînes `${NAME}` / `${NAME:-fallback}` dans n'importe quelle valeur JSON ; - le chargement d'un fichier `.env` en modifiant globalement l'environnement du processus ; - un modèle où une valeur effective secrète devient sérialisable puis est masquée après coup. La résolution d'environnement KSP sera au contraire fondée sur des bindings explicites entre une clé Config connue et un nom de variable d'environnement connu. ## 4. Décision de périmètre : `0.1.3` reste une seule release Le périmètre peut rester dans `0.1.3` à condition de le borner strictement. La première surface fonctionnelle contiendra : 1. l'infrastructure générique document/profil/composition ; 2. le seul document spécialisé actuellement justifié : Logging ; 3. l'environnement Config KSP/KSPB avec un fichier d'environnement géré ; 4. la classification et les surfaces runtime/diagnostic/management ; 5. la mutation/persistence du document Logging et du fichier d'environnement géré ; 6. l'adapter `ResolvedLoggingConfig -> ksp_logging_lib::LoggingSettings` ; 7. les tests/audits de propriété nécessaires. Ne sont pas créés dans `0.1.3` : Transport, Wallet, Store, Execution ou une configuration applicative générale fictive. Cette limitation évite d'insérer une release supplémentaire avant `0.1.4`. La séquence reste donc : ```text 0.1.3 ksp-config-lib 0.1.4 ksp-app-config-desk ``` Si une tranche de mutation révèle une contrainte technique majeure non bornable, le plan pourra encore être corrigé par un delta ultérieur sans comprimer artificiellement la release. ## 5. Matrice de responsabilités | Responsabilité | Propriétaire | Consommateurs | Interdit | |------------------------------------------|---------------------------|------------------------------------------------|-----------------------------------------------------------------| | Lire les documents Config runtime | `ksp-config-lib` | apps/crates via API Config | lecture directe par les consumers | | Lire l'environnement applicatif KSP/KSPB | `ksp-config-lib` | apps/crates via contrats Config | `std::env::*` applicatif hors Config | | Résoudre composition/profils/overrides | `ksp-config-lib` | orchestration | résolution locale dans chaque binaire | | Valider documents et valeurs effectives | `ksp-config-lib` | orchestration/management | validation divergente par consumer | | Classer public/interne/secret | `ksp-config-lib` | runtime/management/app adapters | déduction ad hoc dans l'UI | | Modifier/sauvegarder Config | `ksp-config-lib` | management explicite | écriture directe par l'application | | Authentifier/autoriser un utilisateur UI | application/orchestration | `ksp-config-lib` reçoit une capacité explicite | prétendre que Config est un sandbox de sécurité intra-processus | | Posséder `LoggingSettings` | `ksp-logging-lib` | Config construit ces settings | copie des types Logging dans Config | | Posséder `LoggingGuard` | exécutable/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 retenues ### 6.1 Racine Config et fichiers runtime réels Les documents runtime réels appartiennent sous une racine Config explicite. Le layout workspace par défaut est : ```text config/ ``` `ksp-config-lib` ne doit pas dépendre implicitement du current working directory pour ses opérations internes : une session/locator Config possède un `ConfigRoot`. Un helper workspace pourra construire `ConfigRoot = ./config`, tandis que les tests et applications installées peuvent fournir une autre racine explicite. Règles de chemin : - les documents gérés appartiennent sous `ConfigRoot` ; - les chemins relatifs d'une composition sont résolus depuis le répertoire du composite puis normalisés ; - après résolution, une source gérée ne peut pas sortir de `ConfigRoot` par `..` ou équivalent ; - un chemin absolu n'est pas accepté dans un document composite ; - `KSP_ENV_FILE` relatif est résolu depuis `ConfigRoot` et doit rester dans cette racine ; - les APIs de test/management choisissent une autre racine plutôt que d'introduire une écriture arbitraire hors frontière Config. Pour la première surface : ```text config/logging.config.json config/environment.env ``` `environment.env` est un fichier runtime local géré par Config et destiné notamment aux valeurs non versionnées/secrètes. Il devra être ignoré par Git lorsqu'il sera créé. Son absence est autorisée tant qu'aucune valeur requise n'en dépend. Ce fichier n'est **pas** défini comme un fichier dotenv générique. Il utilise une grammaire KSP versionnée et volontairement bornée afin d'éviter toute interpolation ou exécution implicite. La première ligne sémantique est l’en-tête de format réservé : ```text # ksp-env-format: 1 ``` Le parser reconnaît cet en-tête avant le traitement des commentaires ordinaires ; il doit être présent exactement une fois et annoncer une version supportée. Grammaire initiale : - UTF-8 uniquement ; - lignes vides autorisées ; - après l’en-tête de format, commentaire uniquement lorsque le premier caractère non blanc est `#` ; - affectation `NAME=VALUE` ; - `NAME` respecte `[A-Z_][A-Z0-9_]*` puis doit appartenir à `KSP_*` ou `KSPB_*` ; - une valeur non quotée est littérale après trim externe ; - une valeur nécessitant espaces de bord, newline, guillemets ou escapes utilise une chaîne JSON entre doubles guillemets ; `serde_json` fournit alors l'encodage/décodage de la chaîne ; - aucune expansion `$VAR`/`${VAR}`, aucune substitution shell, aucun `export`, aucune commande et aucune continuation implicite ; - `#` après `=` appartient à la valeur, ce n'est pas un commentaire inline ; - les doublons de nom sont refusés au lieu d'appliquer une règle first/last-wins ; - les écritures Config produisent une forme canonique avec header de version, noms triés et valeurs encodées comme chaînes JSON. Les commentaires libres d'un fichier édité manuellement ne font pas partie du contrat de conservation lors d'une sauvegarde management : la conservation est sémantique, pas byte-for-byte. ### 6.2 Schémas ```text config/schemas/logging.config.schema.json config/schemas/composition.config.schema.json ``` Les schémas utilisent JSON Schema draft 2020-12. Ils sont auto-contenus dans la première surface : pas de résolution HTTP et pas de `$ref` externe nécessaire au runtime. ### 6.3 Exemples ```text config/examples/example.logging.config.json config/examples/example.composition.config.json config/examples/example.environment.env ``` Aucun secret réel ne figure dans les exemples. ### 6.4 Compositions runtime Une composition concrète appartient au binaire/application qui la consomme et suit : ```text config/.default.config.json ``` Aucune composition runtime concrète n'est créée dans `0.1.3`, car aucun exécutable nécessitant Config n'existe encore dans le workspace. La première composition réelle est prévue avec `ksp-app-config-desk` en `0.1.4`. Le contrat générique de composition est néanmoins implémenté et testé dans `0.1.3` via schéma, exemples et fixtures de tests. ## 7. Contrat commun des documents JSON Chaque document Config JSON KSP commence par : ```json { "format_version": 1 } ``` `format_version` est obligatoire et permet à Config de refuser explicitement une génération qu'il ne sait pas lire ou réécrire. Politique retenue : - le format initial est `1` ; - une version inconnue est refusée, jamais réécrite silencieusement ; - `additionalProperties: false` est utilisé lorsque le contrat est fermé ; - les documents JSON source peuvent dériver `Serialize`/`Deserialize` parce que la persistence fait partie de leur contrat ; - une projection runtime résolue susceptible de contenir des secrets ne dérive pas automatiquement `Serialize` ou `Debug` ; - la sauvegarde JSON produit un format canonique lisible avec newline final ; - la conservation byte-for-byte des espaces n'est pas un contrat ; la conservation sémantique de la version et des champs l'est. ## 8. Premier document spécialisé : Logging `logging.config.json` reste le seul document spécialisé créé dans la première surface car Logging est le seul composant runtime déjà développé qui nécessite une configuration. Structure conceptuelle retenue : ```json { "format_version": 1, "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 ; - `default_profile` est global, autonome, et doit référencer un profil existant ; - les noms de profils sont uniques ; - `file` ne duplique pas `logs_directory` ; Config construit le `FileSettings.directory` final à partir de la racine globale ; - `console: null` ou absence de console signifie sortie console désactivée ; - `file: null` ou absence de file signifie sortie fichier désactivée ; - `target_filters` conserve l'ordre du document ; - les targets restent des prefixes KSP acceptés par `LoggingSettings::validate()` ; - Config valide sa propre syntaxe/document puis appelle aussi la validation publique Logging après conversion. ## 9. Contrat de composition générique Le format de composition n'énumère pas tous les futurs domaines dans une structure rigide. Il référence des identifiants de documents enregistrés par Config. Structure conceptuelle : ```json { "format_version": 1, "owner_namespace": "ksp", "default_profile": "default", "documents": { "logging": "logging.config.json" }, "profiles": [ { "name": "default", "document_profiles": {} }, { "name": "diagnostic", "document_profiles": { "logging": "diagnostic" } } ] } ``` Décisions : - `owner_namespace` est obligatoire et vaut `ksp` ou `kspb` ; il détermine le selector de profil de composition autorisé sans modifier la propriété des documents spécialisés inclus ; - `default_profile` du composite sélectionne un profil de composition par défaut ; le nom `active_profile` n'est pas conservé dans le fichier source, car « actif » est un état runtime ; - `documents` associe un identifiant de document KSP connu à un chemin ; - un chemin relatif est résolu relativement au répertoire contenant la composition puis doit rester sous `ConfigRoot` ; - les chemins absolus et les traversées hors `ConfigRoot` sont refusés ; - un document référencé est lu uniquement par Config ; - `document_profiles` peut remplacer le profil d'un document spécialisé ; - l'absence d'override utilise le `default_profile` propre au document spécialisé ; - un override vers un profil inexistant est une erreur avant construction de la configuration effective ; - une clé d'override absente de `documents` est une erreur ; - un identifiant de document inconnu de la version courante de Config est refusé au lieu d'être ignoré ; - la composition n'offre pas de generic JSON patch de champs spécialisés : les valeurs restent possédées par leur document spécialisé. Dans `0.1.3`, le seul identifiant de document enregistré est : ```text logging ``` ## 10. Sélection des profils Trois niveaux sont distingués : 1. profil de composition ; 2. profil d'un document spécialisé ; 3. overrides de valeurs par environnement. ### 10.1 Sans composition Pour un document spécialisé chargé directement : ```text profil explicitement demandé par API sinon document.default_profile ``` ### 10.2 Avec composition Pour la composition : ```text profil de composition explicitement demandé par API sinon selector d'environnement possédé par le namespace du composite sinon composition.default_profile ``` Selectors réservés : ```text KSP_CONFIG_PROFILE -> composition KSP KSPB_CONFIG_PROFILE -> future composition KSPB ``` `KSPB_CONFIG_PROFILE` est fixé par le contrat mais n'a aucun consumer réel en `0.1.3`. Config n'applique jamais simultanément les deux selectors : `composition.owner_namespace` choisit lequel est admissible. Pour ce selector, la precedence est : ```text profil explicite API > variable du processus > entrée environment.env > composition.default_profile ``` Puis, pour chaque document : ```text composition.profile.document_profiles[document] sinon document.default_profile ``` Le selector d'environnement du profil de composition est un contrôle de sélection, distinct des overrides de valeurs décrits plus bas. ## 11. Résolution complète et provenance L'ordre normatif retenu est : ```text 1. locator Config / composition 2. lecture + parsing du document source 3. validation schema/source 4. résolution de la composition 5. sélection du profil de composition 6. sélection du profil de chaque document 7. fusion globals + profil spécialisé 8. application des bindings d'environnement explicites 9. validation de la valeur effective 10. conversion vers le contrat runtime propriétaire ``` Pour une clé effective, Config doit pouvoir conserver une provenance bornée : ```text DocumentGlobal DocumentProfile { profile } EnvironmentFile { variable } ProcessEnvironment { variable } ``` Cette provenance sert aux diagnostics et au management, sans inclure la valeur secrète elle-même. ## 12. Environnement KSP/KSPB ### 12.1 Namespaces Les seuls namespaces applicatifs reconnus sont : ```text KSP_SECRET_* KSP_PUBLIC_* KSP_* KSPB_SECRET_* KSPB_PUBLIC_* KSPB_* ``` Ordre de sensibilité, du plus fort au plus faible : ```text Secret > Internal > Public ``` L'ordre de reconnaissance nominale reste `*_SECRET_*`, puis `*_PUBLIC_*`, puis le namespace général `KSP_*`/`KSPB_*`, afin que le préfixe général ne masque jamais les deux classes explicites. Concrètement : - `KSP_SECRET_*` / `KSPB_SECRET_*` -> `Secret` ; - `KSP_PUBLIC_*` / `KSPB_PUBLIC_*` -> `Public` ; - autre `KSP_*` / `KSPB_*` -> `Internal` ; - un nom hors namespace n'est pas une variable applicative gérée par Config. ### 12.2 Pas de mapping automatique par transformation de nom Config n'infère pas qu'une clé JSON quelconque possède automatiquement un override à partir de son chemin. Chaque override est un binding déclaré explicitement par Config : ```text document + clé effective + variable + type + sensibilité ``` Un binding dont le namespace nominal contredit la sensibilité du champ est refusé dans les tests/audits de la crate. Cette politique évite : - les collisions de noms ; - les overrides accidentels ; - les conversions de type implicites ; - la fuite d'un champ secret sous un préfixe public ; - l'interpolation arbitraire de secrets dans des chaînes opaques. ### 12.3 Sources d'environnement et precedence Config construit une vue d'environnement sans modifier l'environnement du processus : ```text valeur du processus > valeur de config/environment.env > valeur du document/profil ``` Config ne parcourt pas globalement toutes les variables du processus. Il lit uniquement les noms exacts enregistrés dans ses descriptors/bindings ainsi que ses variables bootstrap. Une valeur process non UTF-8 pour un binding attendu est une erreur d'override sans inclure la valeur dans le diagnostic. Le fichier par défaut peut être remplacé par le bootstrap interne : ```text KSP_ENV_FILE ``` Ordre de sélection du fichier : ```text chemin explicite fourni à l'API Config > KSP_ENV_FILE du processus > config/environment.env ``` `KSP_ENV_FILE` est `Internal`, est lu par Config uniquement depuis l'environnement du processus et n'est pas lu depuis le fichier qu'il sert précisément à sélectionner. Sa valeur relative est résolue depuis `ConfigRoot`; une valeur qui sortirait de cette racine est refusée. Le fichier géré n'est pas un stockage arbitraire de noms. Une mutation publique de management ne peut créer/modifier/supprimer qu'une variable enregistrée par un descriptor Config et marquée writable. Un nom namespacé mais inconnu dans `environment.env` est refusé comme erreur de document au lieu d'être silencieusement ignoré. ### 12.4 Variables de contrôle Config Les variables de contrôle initiales sont distinctes des bindings de valeurs Logging : | Variable | Classe | Source admise | Writable par Management | Rôle | |-----------------------|----------|------------------------------|-----------------------------------------------|-------------------------------------------------------------------| | `KSP_ENV_FILE` | Internal | process uniquement | non | sélectionner le fichier d'environnement géré | | `KSP_CONFIG_PROFILE` | Internal | process ou `environment.env` | oui | sélectionner le profil d'une composition `owner_namespace = ksp` | | `KSPB_CONFIG_PROFILE` | Internal | process ou `environment.env` | oui, lorsque le support KSPB devient consommé | sélectionner le profil d'une composition `owner_namespace = kspb` | Ces noms sont des descriptors Config enregistrés, pas des exceptions lues ad hoc par les applications. ### 12.5 Rust 2024 et écriture de l'environnement En Rust 2024, `std::env::set_var` et `std::env::remove_var` sont `unsafe`. KSP interdit `unsafe`. Décision : - `ksp-config-lib` peut lire l'environnement du processus ; - `ksp-config-lib` ne modifie jamais l'environnement global du processus ; - la surface de management peut modifier atomiquement `config/environment.env` ou un fichier explicitement sélectionné ; - une variable du processus continue à dominer la valeur persistée et peut donc empêcher une mutation de devenir effective immédiatement ; - le résultat de mutation doit le signaler explicitement. ### 12.6 Bindings Logging initiaux Première allowlist candidate : | Clé effective | Variable | Classe | Type | |---------------------------------|--------------------------------|----------|----------------------| | `logging.logs_directory` | `KSP_LOGS_DIRECTORY` | internal | path/string non vide | | `logging.default_filter` | `KSP_LOGGING_DEFAULT_FILTER` | internal | enum log level | | `logging.span_events` | `KSP_LOGGING_SPAN_EVENTS` | internal | enum span events | | `logging.console` | `KSP_LOGGING_CONSOLE` | internal | `off |stdout|stderr` | | `logging.file.enabled` | `KSP_LOGGING_FILE_ENABLED` | internal | bool | | `logging.file.file_name_prefix` | `KSP_LOGGING_FILE_NAME_PREFIX` | internal | string non vide | | `logging.file.rotation` | `KSP_LOGGING_FILE_ROTATION` | internal | `never |hourly|daily` | `target_filters` n'a pas d'override d'environnement initial : une liste structurée reste dans le document Logging tant qu'un besoin concret ne justifie pas un encodage dédié. Les noms exacts de cette table sont fixés par le plan et pourront uniquement être corrigés avant implémentation par un delta explicite. ## 13. Sensibilité et surfaces d'accès ### 13.1 Trois classes ```text Public Internal Secret ``` Une valeur dérivée de plusieurs inputs prend la sensibilité la plus forte. ### 13.2 Runtime Un consumer runtime obtient uniquement les contrats nécessaires à sa responsabilité. Il ne reçoit pas automatiquement : - le document source complet ; - toutes les variables d'environnement ; - une map générique de secrets ; - une projection sérialisable de toute la configuration effective. Pour Logging, le consumer final reçoit un `ksp_logging_lib::LoggingSettings`. Un futur composant ayant besoin d'un secret recevra une surface typed Config explicitement ajoutée avec ce composant ; `0.1.3` ne crée pas un getter arbitraire `get_secret(name)` pour anticiper des besoins inconnus. ### 13.3 Diagnostic ordinaire Par défaut : - `Public` : valeur exposable si la projection demandée la prévoit ; - `Internal` : utilisable par les contrats runtime typed et par Management, mais sa valeur n'appartient pas à une projection publique générique ; un diagnostic explicitement demandé en mode debug peut l'exposer uniquement lorsque le consumer autorise cette surface ; - `Secret` : jamais la valeur dans les diagnostics, uniquement état `configured/missing` et provenance non sensible. La surface diagnostic sûre est le défaut. Le mode debug ne transforme jamais un `Secret` en valeur diagnostic et n'est pas un substitut à `ConfigManagement`. Les erreurs Config ne doivent pas incorporer une valeur secrète rejetée dans leur message ou contexte. ### 13.4 Management privilégié Une application de management doit pouvoir consulter et modifier un secret réel. La bibliothèque exposera donc une surface distincte de management, conceptuellement : ```text ConfigRuntime ConfigDiagnostics ConfigManagement ``` `ConfigManagement` peut : - lire la valeur source réelle d'un champ secret ; - lire la valeur réelle d'une entrée d'environnement gérée ; - modifier/supprimer une valeur autorisée ; - sauvegarder un document connu ; - demander une nouvelle résolution effective. Cette séparation est une barrière d'API contre les fuites accidentelles, pas un sandbox contre du code Rust hostile dans le même processus. L'authentification et l'autorisation de l'utilisateur final appartiennent à l'application/orchestration. L'application devra fournir explicitement l'autorisation/capacité attendue par la surface management au lieu d'utiliser les APIs runtime ordinaires. ### 13.5 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 : - les commandes Tauri ; - les DTO publics ; - les DTO diagnostics ; - les DTO/commandes privilégiés permettant, après autorisation applicative, d'afficher ou modifier des secrets. L'application ne lira jamais directement JSON ou environnement pour implémenter ces commandes. ## 14. Validation La validation est volontairement multi-étapes. ### 14.1 Document source ```text filesystem/path -> UTF-8 -> JSON syntax -> format_version -> JSON Schema -> typed deserialize -> invariants sémantiques du document ``` ### 14.2 Composition Vérifier notamment : - `default_profile` existe ; - noms de profils uniques ; - identifiants de documents connus ; - sources lisibles ; - override de profil vers un profil existant ; - aucune clé `document_profiles` sans source correspondante. ### 14.3 Environnement Pour chaque binding appliqué : - nom exact ; - source utilisée ; - type parsable ; - enum/bornes valides ; - sensibilité cohérente ; - aucune valeur secrète dans le diagnostic d'échec. ### 14.4 Configuration effective Après overrides : - invariants Config ; - invariants propres au document ; - conversion vers le contrat du composant ; - validation publique du composant propriétaire lorsque disponible. Pour Logging : ```text ResolvedLoggingConfig -> LoggingSettings -> LoggingSettings::validate() ``` ## 15. Erreurs Config Les codes sont possédés par `ksp-config-lib` sous le domaine : ```text config ``` Candidats fixés pour être introduits uniquement lorsqu'ils deviennent nécessaires : ```text 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.unknown_document_kind config.invalid_composition config.path_outside_root config.symlink_write_denied config.invalid_environment_name config.invalid_environment_file config.unsupported_environment_format config.duplicate_environment_variable config.unknown_environment_variable config.invalid_environment_override config.invalid_effective_value config.management_access_denied config.persistence_failed ``` Les erreurs externes utiles sont conservées avec `Error::with_source(...)` lorsque possible. Contexte sûr candidat : ```text path document_kind profile field variable source operation ``` Une valeur secrète n'est jamais ajoutée à `ErrorContext`. ## 16. Mutation et persistence ### 16.1 Mutation source, pas mutation du snapshot effectif Une mutation modifie une source possédée par Config : - document JSON spécialisé ; - fichier d'environnement géré. Elle ne modifie pas directement un objet `LoggingSettings` actif et ne prétend pas que la nouvelle valeur est immédiatement appliquée au runtime. ### 16.2 Documents modifiables en `0.1.3` Surface initiale : ```text logging.config.json composition fixtures/tests via API générique config/environment.env ``` Aucune API d'écriture arbitraire de fichier n'est exposée. La composition runtime réelle n'existant pas encore, sa persistence est testée sur fixtures/exemples ; elle sera utilisée concrètement en `0.1.4`. ### 16.3 API de mutation Pas de JSON Pointer générique public pour écrire n'importe quel champ. Les stratégies retenues sont : - remplacement/sauvegarde d'un document source typed après validation complète ; - helpers typed pour les opérations fréquentes si les tests/applications en démontrent le besoin ; - `set/remove` explicites uniquement pour un descriptor d'environnement enregistré et marqué writable ; le namespace, le type et la sensibilité sont validés avant persistence. ### 16.4 Atomicité Garantie requise : ```text ancien fichier complet OU nouveau fichier complet jamais un fichier destination partiellement écrit ``` La persistence doit utiliser un fichier temporaire dans le même répertoire et un remplacement atomique ; aucun fallback `truncate puis écrire` et aucun `delete destination puis rename` n'est accepté. Dans la première surface, une destination de mutation qui est elle-même un lien symbolique est refusée avant écriture. Config ne choisit ni de remplacer silencieusement le symlink, ni de suivre implicitement sa cible potentiellement hors `ConfigRoot`. La lecture peut être traitée séparément, mais une source writable doit satisfaire la politique de chemin avant commit. La dépendance candidate `atomic-write-file` est retenue pour cette responsabilité si son audit au moment de l'introduction confirme toujours son adéquation. Elle fournit précisément un commit de remplacement atomique sur Unix, Windows et WASI. La sauvegarde suit : ```text candidate typed -> validation complète -> sérialisation canonique -> écriture temporaire -> flush/fsync selon le backend retenu -> commit atomique -> relecture/résolution de contrôle si nécessaire ``` Les détails génériques de durabilité des ACL/xattrs/timestamps ne sont pas promis par `0.1.3` ; la garantie porte d'abord sur l'absence de contenu partiel et la conservation de l'ancien contenu en cas d'échec avant commit. `environment.env` mérite toutefois une politique spécifique parce qu'il peut contenir des secrets : - il reste non versionné ; - sur Unix, sa création/réécriture doit conserver ou imposer un mode owner-only équivalent à `0600` lorsque l'API plateforme le permet ; - une permission Unix plus large est un diagnostic de sécurité et doit être corrigée par la surface management avant d'écrire de nouveaux secrets ; - aucune promesse de chiffrement au repos n'est faite ; - la stratégie Windows/ACL est auditée au `pre.007` et documentée sans prétendre à une garantie non vérifiée. ### 16.5 Desired vs effective Une mutation retourne un résultat distinguant au minimum : ```text source_changed effective_changed shadowed_by_process_environment reload_required ``` Exemple : modifier `KSP_LOGS_DIRECTORY` dans `environment.env` alors qu'une valeur `KSP_LOGS_DIRECTORY` existe dans le processus doit réussir comme persistence source mais signaler que la valeur effective reste shadowed par le processus. ## 17. Relation Config -> Logging ### 17.1 Conversion `ksp-config-lib` possède le mapping de son document Logging vers les types publics Logging : ```text logging.config.json -> ResolvedLoggingConfig -> ksp_logging_lib::LoggingSettings ``` Aucune copie structurelle de `LoggingSettings` n'est maintenue comme contrat runtime parallèle. ### 17.2 Initialisation Config n'initialise pas automatiquement Logging au simple chargement d'un document. L'orchestration fait : ```text Config resolve -> LoggingSettings -> ksp_logging_lib::initialize(...) -> conserve LoggingGuard ``` ### 17.3 Hot reload Après mutation/relecture : ```text Config resolve -> nouveaux LoggingSettings -> comparaison/changement -> orchestration appelle ksp_logging_lib::reinitialize(&mut guard, ...) ``` `ksp-config-lib` peut fournir un `ChangeReport` indiquant que Logging est affecté, mais ne possède pas le `LoggingGuard`. ### 17.4 Propriété du guard La règle est rendue concrète par étape : - dans `0.1.3`, le harness/test d’intégration qui exercera `initialize` / `reinitialize` possède localement le `LoggingGuard` et sa session Config ; - dans la première application réelle prévue en `0.1.4`, l’état backend de `ksp-app-config-desk` possédera le `LoggingGuard` et la session Config nécessaires à son orchestration ; - `ksp-config-lib` ne stocke jamais le `LoggingGuard` et n’introduit aucun singleton global Config ; - Config peut émettre ses propres événements via `ksp-logging-lib`, sans exiger que Logging ait déjà été initialisé. Target racine Config : ```text ksp-config-lib ``` ## 18. Dépendances externes candidates Aucune dépendance n'est ajoutée dans `pre.001`. Audit de génération effectué le 2026-08-14 ; les versions exactes seront revérifiées au delta qui les introduit réellement. ### 18.1 `serde` Usage réel prévu : - `Deserialize` des documents source typed ; - `Serialize` des documents source modifiables ; - derives uniquement sur les contrats qui doivent réellement traverser cette frontière. Génération candidate observée : ```text ^1.0 ``` ### 18.2 `serde_json` Usage : parsing JSON, valeur brute pour validation schema, sérialisation canonique. Génération candidate : ```text ^1.0 ``` ### 18.3 `jsonschema` Usage : validation des schémas JSON source avant conversion effective. Politique candidate : ```text default-features = false ``` La première surface n'a besoin ni de résolution HTTP, ni de TLS, ni d'async resolver, car ses schémas sont locaux et auto-contenus. Génération actuelle observée en août 2026 : ```text ^0.49 ``` ### 18.4 Parser `environment.env` possédé par KSP Aucune dépendance dotenv n'est retenue. L'audit de `dotenvy 0.15.7` montre que son iterator ne modifie certes pas l'environnement du processus, mais son parseur effectue des substitutions de variables. Cela contredit le contrat KSP sans interpolation implicite. La grammaire KSP bornée de `environment.env` est suffisamment petite pour être parsée dans `ksp-config-lib` avec la bibliothèque standard et `serde_json` déjà nécessaire pour l'encodage sûr des valeurs quotées. Ce parser n'essaie pas d'émuler Bash ou un standard dotenv complet. ### 18.5 `atomic-write-file` Usage : persistence atomique des fichiers Config lorsque la tranche mutation est ouverte. Génération candidate observée : ```text ^0.3 ``` ### 18.6 Dépendances non retenues initialement Ne pas introduire sans besoin : ```text tokio notify figment config clap ts-rs tauri anyhow thiserror ``` Le chargement Config initial est une I/O filesystem locale, bornée et synchrone ; `0.1.3` n'impose donc pas un runtime async à ses consumers. ## 19. Surface Rust candidate de `ksp-config-lib` La structure exacte reste ajustable au développement, mais les responsabilités prévues sont : ```text src/lib.rs src/error.rs src/document.rs src/composition.rs src/environment.rs src/sensitivity.rs src/logging.rs src/diagnostics.rs src/management.rs src/persistence.rs ``` Les modules restent privés ; l'API nécessaire est réexportée au crate-root. Types conceptuels à faire émerger uniquement au besoin : ```text ConfigDocumentKind ConfigSource CompositionDocument CompositionProfile EnvironmentSensitivity EnvironmentSource EnvironmentBinding ConfigValueOrigin ConfigChangeReport ConfigManagementAccess LoggingConfigDocument LoggingProfileConfig ResolvedLoggingConfig ``` Cette liste n'est pas une obligation de créer tous les types dans `pre.002`. ## 20. Audits/tests à introduire ### 20.1 Ownership Tests/audits empêchant : - `std::env::var`, `var_os`, `vars`, `set_var`, `remove_var` dans les crates KSP hors `ksp-config-lib` pour les variables applicatives KSP/KSPB ; - lecture/écriture directe de `config/*.config.json` par une autre crate ; - dépendance directe `tracing*` depuis Config ; - dépendance inverse Logging -> Config ; - TS-RS/Tauri dans Config sans décision explicite. L'audit ne doit pas interdire les usages système légitimes de `std::env` qui ne concernent pas la configuration applicative, par exemple des métadonnées de build/test ; il doit donc être suffisamment ciblé pour éviter un grep faux-positif simpliste. ### 20.2 Documents/profils Tester : - `format_version` ; - default profile présent/absent ; - noms dupliqués ; - composition sans override ; - override valide/invalide ; - chemins relatifs ; - rejet chemin absolu/traversée hors `ConfigRoot` ; - document kind inconnu ; - invalid JSON/schema/semantic ; - global hors profil. ### 20.3 Environnement Tester : - process > environment.env > document ; - parsing bool/enum/path ; - binding inconnu non appliqué ; - namespaces KSP/KSPB ; - secret/public/internal ; - aucune valeur secrète dans `Debug`, erreur ou diagnostic générique ; - `KSP_ENV_FILE` bootstrap ; - absence de `set_var/remove_var` en production. Les tests qui nécessitent de contrôler l'environnement du processus doivent éviter les races inter-tests, notamment par processus enfant ou sérialisation explicitement bornée de la fixture de test, sans introduire `unsafe` dans le code KSP. ### 20.4 Persistence Tester : - candidate invalide n'altère pas le fichier existant ; - échec avant commit conserve l'ancien contenu ; - destination symlink refusée ; - source writable hors `ConfigRoot` refusée ; - succès produit un document relisible/validable ; - newline final ; - unsupported format refusé ; - environnement shadowed correctement rapporté. ### 20.5 Logging adapter Tester : - chaque enum Config -> enum Logging ; - console off/stdout/stderr ; - file on/off ; - `logs_directory` global ; - target filters ; - env overrides ; - `LoggingSettings::validate()` ; - changement Config ne reconfigure rien implicitement ; le guard reste orchestration-owned. ## 21. Découpage souple des prereleases ### `0.1.3-pre.001` — audit + brainstorming + plan - audit stable `0.1.2` ; - audit historique ciblé ; - décisions de ce document ; - version d'ouverture ; - aucune crate/dépendance fonctionnelle Config. ### `0.1.3-pre.002` — crate foundation + erreurs + modèles source - créer `ksp-config-lib` ; - ajouter uniquement les dépendances réellement consommées par cette tranche ; - contrats communs document/version ; - document Logging typed ; - erreurs Config nécessaires ; - tests unitaires/intégration initiaux. ### `0.1.3-pre.003` — JSON Schema + fichiers Config Logging - créer `config/`, `config/schemas/`, `config/examples/` ; - schéma Logging ; - runtime `logging.config.json` minimal sûr ; - exemple Logging ; - pipeline parse/schema/semantic ; - validation du `default_profile`. ### `0.1.3-pre.004` — composition + résolution profils - contrat generic composition ; - schéma/exemple ; - locators et chemins relatifs ; - profils de composition et overrides spécialisés ; - provenance document/global/profile. ### `0.1.3-pre.005` — environnement + sensibilité - namespaces KSP/KSPB ; - `environment.env` + exemple + parser KSP strict sans interpolation ; - process/env-file precedence ; - bindings/descriptors explicites Logging ; - surfaces diagnostic/redaction ; - audits ownership env. ### `0.1.3-pre.006` — Logging adapter + lifecycle integration - conversion vers `LoggingSettings` ; - validations croisées Config/Logging ; - change report ; - démonstration initialize/reinitialize en test sans transfert de propriété du `LoggingGuard`. ### `0.1.3-pre.007` — management + persistence atomique - surface management explicitement séparée ; - lecture de secrets autorisée par contrat management ; - mutation typed Logging ; - set/remove environment file ; - atomic write ; - desired/effective/shadowing report. ### `0.1.3-pre.008` — ownership audits + robustesse - tests de non-divulgation ; - tests invalid documents/env ; - audit des lectures directes Config/env ; - graphe dépendances/features ; - corrections de surface publique. ### `0.1.3-pre.009` — clôture - validations finales ; - documentation durable ; - cleanup/archivage requis uniquement ; - TODO fermés ou reportés explicitement ; - prompt `0.1.4 — ksp-app-config-desk` ; - préparation `rel.001` puis tag stable après validation utilisateur. Le découpage reste souple : une tranche trop large est scindée ; une tranche devenue inutile est supprimée par correction explicite du plan. ## 22. Hors scope confirmé - application desktop Config dans `0.1.3` ; - Tauri/TS-RS dans `ksp-config-lib` ; - configuration Transport/Store/Wallet/Execution avant leurs composants ; - watcher filesystem ; - reload automatique générique ; - service de configuration distribué ; - secrets manager distant ; - chiffrement maison du fichier d'environnement ; - modification de l'environnement global du processus ; - interpolation arbitraire `${...}` dans les chaînes JSON ; - getter générique de secrets par nom ; - JSON patch arbitraire public ; - sauvegarde de fichiers hors frontière Config ; - RPC/WS/provider ; - Program/decoder/execution ; - workers/jobs/pipelines ; - trading/ML. ## 23. Validations finales attendues pour `0.1.3` Au minimum : ```bash 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 cargo tree -p ksp-config-lib -e normal cargo tree -p ksp-config-lib -e dev ``` Tout script d'audit réellement présent au moment de la clôture est également exécuté. Une commande non exécutée n'est jamais déclarée réussie. ## 24. Critères de sortie du `pre.001` Le plan ferme les questions de cadrage initiales : - premier document spécialisé : `logging.config.json` uniquement ; - pas de document applicatif général artificiel ; - `logs_directory` global hors profils ; - `default_profile` autonome dans les documents et compositions ; - composition generic par identifiants de documents + overrides de profils ; - chemins relatifs de sources résolus depuis le répertoire du composite ; - environnement par bindings explicites, sans interpolation générique ; - process env > environment file > document ; - `composition.owner_namespace` fixe `ksp|kspb` et donc le selector `KSP_CONFIG_PROFILE` ou `KSPB_CONFIG_PROFILE` ; - `KSP_ENV_FILE` est un bootstrap process-only avec priorité inférieure à un chemin explicite API ; - `environment.env` utilise la grammaire KSP v1 stricte, sans interpolation, doublons ni noms non enregistrés ; - `ConfigRoot` borne les sources gérées et les mutations ; - aucune mutation du process env à cause de Rust 2024 + `unsafe` interdit ; - management de `environment.env` prévu ; - classification `Public/Internal/Secret` ; - lecture de secrets autorisée seulement via surface management/runtime typed explicite ; - DTO Tauri possédés par l'application future ; - Config construit `LoggingSettings`, orchestration possède `LoggingGuard` ; - persistence atomique obligatoire ; - `0.1.3` reste une seule release bornée ; - prereleases `pre.002` à `pre.009` dimensionnées. Les seules questions laissées volontairement au delta qui introduit une dépendance sont sa version patch exacte et l'audit final de ses features/transitives au jour de l'ajout.