diff --git a/Cargo.toml b/Cargo.toml index 0de636b..1834e5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ # file: Cargo.toml -# version: 58 +# version: 59 [workspace] resolver = "3" members = ["crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib"] [workspace.package] -version = "0.1.3-pre.13.fix.1" +version = "0.1.3-pre.14" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" diff --git a/crates/ksp-config-lib/tests/ownership.rs b/crates/ksp-config-lib/tests/ownership.rs new file mode 100644 index 0000000..11aef0b --- /dev/null +++ b/crates/ksp-config-lib/tests/ownership.rs @@ -0,0 +1,304 @@ +// file: crates/ksp-config-lib/tests/ownership.rs +// version: 1 + +//! Workspace ownership audits for KSP application configuration boundaries. + +fn workspace_root() -> std::path::PathBuf { + let manifest_directory = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let root = manifest_directory.parent().and_then(std::path::Path::parent); + return match root { + std::option::Option::Some(value) => value.to_path_buf(), + std::option::Option::None => manifest_directory, + }; +} + +fn collect_rust_files(directory: &std::path::Path, files: &mut std::vec::Vec) { + let entries = std::fs::read_dir(directory); + let entries = match entries { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + for entry in entries { + let entry = match entry { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => continue, + }; + let path = entry.path(); + if path.is_dir() { + collect_rust_files(path.as_path(), files); + continue; + } + if path.extension().and_then(std::ffi::OsStr::to_str) == std::option::Option::Some("rs") { + files.push(path); + } + } +} + +fn collect_json_files(directory: &std::path::Path, files: &mut std::vec::Vec) { + let entries = std::fs::read_dir(directory); + let entries = match entries { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + for entry in entries { + let entry = match entry { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => continue, + }; + let path = entry.path(); + if path.is_dir() { + collect_json_files(path.as_path(), files); + continue; + } + if path.extension().and_then(std::ffi::OsStr::to_str) == std::option::Option::Some("json") { + files.push(path); + } + } +} + +fn non_comment_source(source: &str) -> String { + let mut filtered = String::new(); + for line in source.lines() { + let trimmed = line.trim_start(); + if trimmed.starts_with("//") { + continue; + } + filtered.push_str(line); + filtered.push('\n'); + } + return filtered; +} + +fn collect_environment_names(source: &str, names: &mut std::collections::BTreeSet) { + let bytes = source.as_bytes(); + let mut index: usize = 0; + while index < bytes.len() { + let prefix_length = if bytes[index..].starts_with(b"KSPB_") { + 5 + } else if bytes[index..].starts_with(b"KSP_") { + 4 + } else { + index += 1; + continue; + }; + let mut end = index + prefix_length; + while end < bytes.len() { + let byte = bytes[end]; + if byte.is_ascii_uppercase() || byte.is_ascii_digit() || byte == b'_' { + end += 1; + } else { + break; + } + } + let candidate = std::str::from_utf8(&bytes[index..end]); + if let std::result::Result::Ok(candidate) = candidate + && candidate.len() > prefix_length + && !candidate.ends_with('_') + { + names.insert(candidate.to_owned()); + } + index = end; + } +} + +fn dotenv_example_assignments(source: &str) -> std::collections::BTreeMap { + let mut assignments = std::collections::BTreeMap::::new(); + for (line_index, line) in source.lines().enumerate() { + let mut candidate = line.trim(); + if let std::option::Option::Some(commented) = candidate.strip_prefix('#') { + candidate = commented.trim_start(); + } + let separator = candidate.find('='); + let separator = match separator { + std::option::Option::Some(value) => value, + std::option::Option::None => continue, + }; + let name = candidate[..separator].trim(); + if (name.starts_with("KSP_") || name.starts_with("KSPB_")) && !name.ends_with('_') { + assignments.insert(name.to_owned(), line_index); + } + } + return assignments; +} + +fn has_preceding_explanatory_comment(source: &str, assignment_line_index: usize) -> bool { + let lines: std::vec::Vec<&str> = source.lines().collect(); + if assignment_line_index == 0 || assignment_line_index > lines.len() { + return false; + } + let mut index = assignment_line_index; + while index > 0 { + index -= 1; + let trimmed = lines[index].trim(); + if trimmed.is_empty() { + continue; + } + if !trimmed.starts_with('#') { + return false; + } + let comment = trimmed.trim_start_matches('#').trim_start(); + if comment.starts_with("file:") || comment.starts_with("version:") { + return false; + } + if comment.contains('=') && (comment.starts_with("KSP_") || comment.starts_with("KSPB_")) { + return false; + } + return true; + } + return false; +} + +#[test] +fn foundational_dependency_direction_does_not_point_back_to_config() { + let root = workspace_root(); + for crate_name in ["ksp-core-lib", "ksp-logging-lib"] { + let manifest_path = root.join("crates").join(crate_name).join("Cargo.toml"); + let manifest = std::fs::read_to_string(manifest_path.as_path()); + assert!(manifest.is_ok(), "unable to read {}", manifest_path.display()); + if let std::result::Result::Ok(manifest) = manifest { + assert!(!manifest.contains("ksp-config-lib"), "{} must not depend on ksp-config-lib", manifest_path.display()); + } + } +} + +#[test] +fn workspace_crates_do_not_read_ksp_environment_directly() { + let root = workspace_root(); + let crates_directory = root.join("crates"); + let entries = std::fs::read_dir(crates_directory.as_path()); + assert!(entries.is_ok(), "unable to inspect workspace crates at {}", crates_directory.display()); + let entries = match entries { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + for entry in entries { + let entry = match entry { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => continue, + }; + if !entry.path().is_dir() || entry.file_name() == std::ffi::OsStr::new("ksp-config-lib") { + continue; + } + let mut rust_files = std::vec::Vec::new(); + collect_rust_files(entry.path().as_path(), &mut rust_files); + for rust_file in rust_files { + let source = std::fs::read_to_string(rust_file.as_path()); + assert!(source.is_ok(), "unable to read {}", rust_file.display()); + let source = match source { + std::result::Result::Ok(value) => non_comment_source(value.as_str()), + std::result::Result::Err(_) => continue, + }; + let forbidden = [ + "std::env::var(\"KSP_", + "std::env::var(\"KSPB_", + "std::env::var_os(\"KSP_", + "std::env::var_os(\"KSPB_", + "env::var(\"KSP_", + "env::var(\"KSPB_", + "env::var_os(\"KSP_", + "env::var_os(\"KSPB_", + "std::env::vars()", + "std::env::vars_os()", + ]; + for token in forbidden { + assert!(!source.contains(token), "{} bypasses ksp-config-lib for KSP/KSPB environment access via {token}", rust_file.display()); + } + } + } +} + +#[test] +fn workspace_crates_do_not_hardcode_config_managed_physical_files() { + let root = workspace_root(); + let crates_directory = root.join("crates"); + let entries = std::fs::read_dir(crates_directory.as_path()); + assert!(entries.is_ok(), "unable to inspect workspace crates at {}", crates_directory.display()); + let entries = match entries { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + for entry in entries { + let entry = match entry { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => continue, + }; + if !entry.path().is_dir() || entry.file_name() == std::ffi::OsStr::new("ksp-config-lib") { + continue; + } + let mut rust_files = std::vec::Vec::new(); + collect_rust_files(entry.path().as_path(), &mut rust_files); + for rust_file in rust_files { + let source = std::fs::read_to_string(rust_file.as_path()); + assert!(source.is_ok(), "unable to read {}", rust_file.display()); + let source = match source { + std::result::Result::Ok(value) => non_comment_source(value.as_str()), + std::result::Result::Err(_) => continue, + }; + for token in ["\".env\"", "\"std.logging.json\"", "\"std.logging.schema.json\"", "\"composite.schema.json\""] { + assert!(!source.contains(token), "{} hardcodes Config-managed physical resource {token}; use ksp-config-lib contracts", rust_file.display()); + } + } + } +} + +#[test] +fn dotenv_example_covers_runtime_environment_names_with_comments() { + let root = workspace_root(); + let mut runtime_names = std::collections::BTreeSet::::new(); + let config_directory = root.join("config"); + let mut json_files = std::vec::Vec::new(); + collect_json_files(config_directory.as_path(), &mut json_files); + for json_file in json_files { + let source = std::fs::read_to_string(json_file.as_path()); + assert!(source.is_ok(), "unable to read {}", json_file.display()); + if let std::result::Result::Ok(source) = source { + collect_environment_names(source.as_str(), &mut runtime_names); + } + } + let crates_directory = root.join("crates"); + let crate_entries = std::fs::read_dir(crates_directory.as_path()); + assert!(crate_entries.is_ok(), "unable to inspect workspace crates at {}", crates_directory.display()); + let crate_entries = match crate_entries { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + for entry in crate_entries { + let entry = match entry { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => continue, + }; + let source_directory = entry.path().join("src"); + if !source_directory.is_dir() { + continue; + } + let mut rust_files = std::vec::Vec::new(); + collect_rust_files(source_directory.as_path(), &mut rust_files); + for rust_file in rust_files { + let source = std::fs::read_to_string(rust_file.as_path()); + assert!(source.is_ok(), "unable to read {}", rust_file.display()); + if let std::result::Result::Ok(source) = source { + let source = non_comment_source(source.as_str()); + collect_environment_names(source.as_str(), &mut runtime_names); + } + } + } + let example_path = root.join(ksp_config_lib::DEFAULT_DOTENV_EXAMPLE_PATH); + let example = std::fs::read_to_string(example_path.as_path()); + assert!(example.is_ok(), "unable to read canonical environment inventory {}", example_path.display()); + let example = match example { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let assignments = dotenv_example_assignments(example.as_str()); + assert!(!runtime_names.is_empty(), "runtime environment audit should discover at least one concrete KSP/KSPB variable"); + for variable_name in runtime_names { + let assignment_line = assignments.get(variable_name.as_str()); + assert!(assignment_line.is_some(), ".env.example is missing runtime variable {variable_name}"); + if let std::option::Option::Some(line_index) = assignment_line { + assert!( + has_preceding_explanatory_comment(example.as_str(), *line_index), + ".env.example variable {variable_name} must be preceded by an explanatory comment", + ); + } + } +} diff --git a/deltas/0.1.3/pre.014.md b/deltas/0.1.3/pre.014.md new file mode 100644 index 0000000..5b189c6 --- /dev/null +++ b/deltas/0.1.3/pre.014.md @@ -0,0 +1,245 @@ + + + +# Delta 0.1.3-pre.014 + +## Base requise + +Livraison précédente validée : + +```text +0.1.3-pre.013-fix.001 +``` + +Version technique de cette base : + +```text +workspace.package.version = "0.1.3-pre.13.fix.1" +Cargo.toml header version = 58 +``` + +Validations utilisateur exécutées le 2026-08-16 : + +```text +cargo fmt --all OK +cargo check --workspace OK +cargo clippy --workspace --all-targets OK +cargo test --workspace OK +cargo tree -p ksp-config-lib OK +cargo tree -p ksp-config-lib -d OK, doublon transitif syn 2/3 déjà connu +cargo tree -p ksp-config-lib -e features OK +cargo tree -p ksp-logging-lib OK +``` + +`cargo test --workspace` confirme notamment 80 tests unitaires + 11 tests publics pour `ksp-config-lib`, 34 tests unitaires Logging et toutes les intégrations existantes vertes. + +## Objet de pre.014 + +Transformer les principales frontières d'ownership Config en audits exécutables avant la clôture : + +```text +workspace source/manifests/config + -> ownership audit + -> fail cargo test on boundary regression + +config JSON + production Rust + -> concrete KSP/KSPB environment names + -> .env.example inventory coverage +``` + +Cette tranche ne crée aucun nouveau runtime Config et ne modifie aucune API métier. + +## Audit de direction des dépendances fondatrices + +Le nouveau test d'intégration vérifie explicitement que : + +```text +ksp-core-lib -X-> ksp-config-lib +ksp-logging-lib -X-> ksp-config-lib +``` + +La direction autorisée reste : + +```text +ksp-config-lib -> ksp-core-lib +ksp-config-lib -> ksp-logging-lib +``` + +L'audit est volontairement limité aux deux fondations qui ne doivent jamais dépendre de Config. Les futures applications/crates pourront, elles, dépendre normalement de `ksp-config-lib`. + +## Audit des lectures d'environnement KSP/KSPB + +Pour toutes les crates autres que `ksp-config-lib`, l'audit recherche les bypass directs simples de la frontière Config : + +```text +std::env::var("KSP_...") +std::env::var("KSPB_...") +std::env::var_os("KSP_...") +std::env::var_os("KSPB_...") +env::var(...) +env::var_os(...) +std::env::vars() +std::env::vars_os() +``` + +L'audit ne prétend pas interdire toute utilisation possible de `std::env` pour des informations système non applicatives. Il verrouille spécifiquement la frontière des variables KSP/KSPB et l'énumération globale qui permettrait de la contourner. + +## Audit des noms physiques gérés + +Les crates hors Config ne doivent pas coder en dur les ressources physiques déjà possédées par Config : + +```text +.env +std.logging.json +std.logging.schema.json +composite.schema.json +``` + +Un consumer doit utiliser les contrats publics, constantes et `file_id` Config plutôt que recréer localement la connaissance du layout physique. + +L'audit ignore les lignes de commentaire Rust afin qu'une documentation locale ne soit pas confondue avec un accès runtime. + +## Inventaire `.env.example` + +Le test collecte automatiquement les noms KSP/KSPB concrets trouvés dans : + +```text +config/**/*.json +crates/*/src/**/*.rs +``` + +Les préfixes génériques tels que `KSP_SECRET_` ou `KSPB_PUBLIC_` ne sont pas considérés comme des variables concrètes. + +Chaque variable runtime concrète détectée doit apparaître dans `.env.example` sous forme d'assignment active ou commentée et cette assignment doit être précédée d'un commentaire explicatif distinct des headers de fichier/version. + +Dans l'état actuel, l'inventaire concret reste : + +```text +KSP_LOGS_DIRECTORY +``` + +Aucune nouvelle variable runtime n'est introduite par `pre.014`. + +## Réaudit de robustesse existante + +Les tests déjà acquis avant `pre.014` couvrent notamment : + +- JSON malformed, schema invalid et document schema-invalide ; +- invariants sémantiques Logging ; +- profils dupliqués/default_profile invalide/profil explicite absent ; +- composite references et component ids invalides ; +- filenames/mappings `file_id` invalides ; +- `.env` syntaxiquement invalide, doublons, namespaces externes et valeurs vides ; +- placeholders malformés, variables absentes et fallbacks ; +- priorité process > `.env` > fallback ; +- sensibilité `Public/Internal/Secret`, real/safe/provenance et canaries de non-divulgation ; +- validation effective `logs_directory` et confinement des `files[].path` après interpolation ; +- rejet d'un secret dans `std.logging` ; +- management desired/effective/shadow, reveal explicite et persistence atomique ; +- candidate JSON invalide sans modification du destination ; +- permissions `.env` privées à la création Unix. + +Aucune lacune nécessitant une nouvelle API publique n'a été identifiée dans cette tranche. Les responsabilités restent séparées entre : + +```text +ConfigBootstrapOptions / ConfigFileRegistry +ConfigDocumentEngine +ConfigEnvironment +ConfigManagement +ResolvedLoggingConfig adapter +ksp-logging-lib runtime ownership +``` + +## Règles durables + +Ajout de : + +```text +KSP-CONFIG-017 +KSP-CONFIG-018 +``` + +pour figer : + +- les audits exécutables d'ownership Config au niveau workspace ; +- la vérification automatique de la couverture `.env.example`. + +## Dépendances + +Aucune nouvelle dépendance Cargo. + +Le seul doublon transitif attendu dans le graphe Config reste `syn 2` / `syn 3` via `jsonschema`. Aucun contournement de cette dépendance n'est introduit ici. + +## Tests ajoutés + +Nouveau fichier : + +```text +crates/ksp-config-lib/tests/ownership.rs +``` + +Quatre tests d'intégration workspace : + +```text +foundational_dependency_direction_does_not_point_back_to_config +workspace_crates_do_not_read_ksp_environment_directly +workspace_crates_do_not_hardcode_config_managed_physical_files +dotenv_example_covers_runtime_environment_names_with_comments +``` + +Les 80 tests unitaires et 11 tests de `tests/public_api.rs` restent inchangés ; `cargo test --workspace` exécute désormais en plus ce nouveau binaire d'intégration à 4 tests. + +## Version technique + +```text +workspace.package.version = "0.1.3-pre.14" +Cargo.toml header version = 59 +``` + +La version change car la tranche ajoute du code Rust de test participant au build/test du workspace. + +## Fichiers ajoutés + +```text +crates/ksp-config-lib/tests/ownership.rs +deltas/0.1.3/pre.014.md +``` + +## Fichiers modifiés + +```text +Cargo.toml +docs/rules/RULES_KSP.md +docs/plans/000-README.md +docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md +docs/plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md +``` + +## Contrôles statiques réalisés avant livraison + +Dans l'environnement de préparation : + +- reconstruction de la base par application ordonnée de tous les deltas jusqu'à `pre.013-fix.001` ; +- inventaire workspace : aucune lecture directe `std::env::var*` KSP/KSPB hors Config détectée ; +- aucune référence physique gérée interdite détectée hors Config ; +- `KSP_LOGS_DIRECTORY` est la seule variable runtime concrète détectée et figure dans `.env.example` avec commentaire ; +- aucun `.env` runtime local n'est inclus dans le delta ; +- aucune nouvelle dépendance Cargo ; +- réapplication du delta sur la base reconstruite vérifiée séparément. + +Aucune commande Cargo n'est déclarée réussie dans l'environnement de préparation lorsqu'elle n'y est pas disponible. + +## Validation demandée + +```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-logging-lib -d +``` + +Après validation, `0.1.3-pre.015` pourra ouvrir la clôture : validations finales, documentation durable, changelog, nettoyage/archivage et prompt `0.1.4 — ksp-app-config-desk`. diff --git a/docs/plans/000-README.md b/docs/plans/000-README.md index ea7e35b..de0db27 100644 --- a/docs/plans/000-README.md +++ b/docs/plans/000-README.md @@ -1,5 +1,5 @@ - + # Plans KSP @@ -13,7 +13,7 @@ Un plan décrit le périmètre, les décisions déjà acquises, les questions ou - [`002-FUNCTIONAL_RELEASE_SEQUENCE.md`](002-FUNCTIONAL_RELEASE_SEQUENCE.md) — séquence active de référence des premières releases fonctionnelles ; - [`003-V0_1_1_CORE_FOUNDATION_PLAN.md`](003-V0_1_1_CORE_FOUNDATION_PLAN.md) — plan historique clôturé de la release stable `0.1.1`, établi par `0.1.1-pre.001` puis consolidé jusqu'à `0.1.1-rel.001`. - [`004-V0_1_2_LOGGING_FOUNDATION_PLAN.md`](004-V0_1_2_LOGGING_FOUNDATION_PLAN.md) — plan historique clôturé de la release stable `0.1.2`, établi par `0.1.2-pre.001` puis consolidé jusqu'à `0.1.2-rel.001`. -- [`005-V0_1_3_CONFIG_FOUNDATION_PLAN.md`](005-V0_1_3_CONFIG_FOUNDATION_PLAN.md) — plan actif de `0.1.3 — Configuration foundation`, établi par `0.1.3-pre.001`, corrigé par `pre.001-fix.001`, complété par `pre.001-fix.002` pour le registre `file_id`/bootstrap/non-régression Logging, regranularisé par `pre.001-fix.003`, puis rescindé pendant `pre.005`; `pre.006` a fermé le routing structuré `domain`, `pre.007` le moteur JSON/JSON Schema et le premier `std.logging.json`, `pre.008` la résolution globals/profils/`default_profile`, puis `pre.009` les compositions génériques par `file_id`; `pre.010` a livré `.env`, process env, `.env.example` et le resolver `${...}`; `pre.011` ajoute sensibilité, valeur réelle/sûre, redaction et provenance; `pre.012` a livré l'adapter Config -> Logging et la validation effective des chemins Logging; `pre.013` livre management/persistence JSON/.env; `pre.014` poursuivra avec ownership audits/robustesse. +- [`005-V0_1_3_CONFIG_FOUNDATION_PLAN.md`](005-V0_1_3_CONFIG_FOUNDATION_PLAN.md) — plan actif de `0.1.3 — Configuration foundation`, établi par `0.1.3-pre.001`, corrigé par `pre.001-fix.001`, complété par `pre.001-fix.002` pour le registre `file_id`/bootstrap/non-régression Logging, regranularisé par `pre.001-fix.003`, puis rescindé pendant `pre.005`; `pre.006` a fermé le routing structuré `domain`, `pre.007` le moteur JSON/JSON Schema et le premier `std.logging.json`, `pre.008` la résolution globals/profils/`default_profile`, puis `pre.009` les compositions génériques par `file_id`; `pre.010` a livré `.env`, process env, `.env.example` et le resolver `${...}`; `pre.011` ajoute sensibilité, valeur réelle/sûre, redaction et provenance; `pre.012` a livré l'adapter Config -> Logging et la validation effective des chemins Logging; `pre.013` a livré management/persistence JSON/.env; `pre.014` ajoute les ownership audits exécutables et la vérification automatique de `.env.example`; `pre.015` assurera la clôture. Le `pre.001` de chaque release fonctionnelle peut introduire son propre plan détaillé lorsque la release s'ouvre. diff --git a/docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md b/docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md index c6e4ba7..8bceeb7 100644 --- a/docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md +++ b/docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md @@ -1,5 +1,5 @@ - + # Séquence des releases fonctionnelles KSP @@ -219,7 +219,7 @@ pre.015 clôture Cette prévision n'est pas un plafond : chaque prerelease doit rester une petite tranche, avec scission explicite si l'objectif dépasse environ 15–20 minutes de travail effectif. -`pre.006` a fermé le routing Logging structuré `domain`; `pre.007` a livré le moteur JSON/JSON Schema et `std.logging.json`; `pre.008` a ajouté la résolution générique globals/profils/`default_profile`; `pre.009` a ajouté les compositions génériques par `file_id`, avec `schema.composite` mais sans composite runtime fictif; `pre.010` a ajouté le snapshot process + `.env`, `.env.example` et le resolver `${...}`; `pre.011` a ajouté sensibilité, valeurs réelle/sûre, redaction et provenance enrichie; `pre.012` a livré l'adapter Config -> Logging, la validation effective de `logs_directory`/`files[].path` et le contrat de non-usage des secrets par Logging. `pre.013` livre management + persistence JSON/.env avec source typée Logging, reports desired/effective/shadow, reveal explicite et écriture atomique. Après validation utilisateur, `pre.014` ouvrira ownership audits + robustesse. +`pre.006` a fermé le routing Logging structuré `domain`; `pre.007` a livré le moteur JSON/JSON Schema et `std.logging.json`; `pre.008` a ajouté la résolution générique globals/profils/`default_profile`; `pre.009` a ajouté les compositions génériques par `file_id`, avec `schema.composite` mais sans composite runtime fictif; `pre.010` a ajouté le snapshot process + `.env`, `.env.example` et le resolver `${...}`; `pre.011` a ajouté sensibilité, valeurs réelle/sûre, redaction et provenance enrichie; `pre.012` a livré l'adapter Config -> Logging, la validation effective de `logs_directory`/`files[].path` et le contrat de non-usage des secrets par Logging. `pre.013` a livré management + persistence JSON/.env avec source typée Logging, reports desired/effective/shadow, reveal explicite et écriture atomique. `pre.014` ajoute les audits exécutables d'ownership Config, verrouille la direction Core/Logging ->/ Config, interdit les lectures directes KSP/KSPB et les noms physiques gérés hors Config, puis vérifie automatiquement la couverture de `.env.example`. Après validation, `pre.015` ouvrira la clôture. ## `0.1.4` — Config desktop par défaut diff --git a/docs/plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md b/docs/plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md index e324624..b5df248 100644 --- a/docs/plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md +++ b/docs/plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md @@ -1,11 +1,11 @@ - + # Plan `0.1.3` — Configuration foundation ## 1. Statut et objectif -Ce plan a été établi par `0.1.3-pre.001`, corrigé par `0.1.3-pre.001-fix.001/.002/.003`, puis exécuté par petites tranches. `pre.002` a livré le bootstrap Config, `pre.003` le registre `file_id`, `pre.004` les contrats publics multi-output de Logging, `pre.005` le runtime multi-sink sur niveau/target/formats, `pre.006` le routing structuré `domain`, `pre.007` le moteur JSON/JSON Schema, `pre.008` la résolution des globals/profils/`default_profile`, `pre.009` les compositions génériques par `file_id`, `pre.010` le snapshot process + `.env` et le resolver `${...}`, `pre.011` la sensibilité et les représentations real/safe/provenance, puis `pre.012` l'adapter Config -> Logging. `pre.013` livre maintenant management + persistence JSON/.env. La prochaine tranche est `pre.014` pour les ownership audits et la robustesse. +Ce plan a été établi par `0.1.3-pre.001`, corrigé par `0.1.3-pre.001-fix.001/.002/.003`, puis exécuté par petites tranches. `pre.002` a livré le bootstrap Config, `pre.003` le registre `file_id`, `pre.004` les contrats publics multi-output de Logging, `pre.005` le runtime multi-sink sur niveau/target/formats, `pre.006` le routing structuré `domain`, `pre.007` le moteur JSON/JSON Schema, `pre.008` la résolution des globals/profils/`default_profile`, `pre.009` les compositions génériques par `file_id`, `pre.010` le snapshot process + `.env` et le resolver `${...}`, `pre.011` la sensibilité et les représentations real/safe/provenance, puis `pre.012` l'adapter Config -> Logging. `pre.013` a livré management + persistence JSON/.env et son `fix.001` a corrigé la syntaxe du warning de cleanup. `pre.014` livre maintenant les ownership audits exécutables et la robustesse de frontière. La prochaine tranche est `pre.015` pour la clôture. La base auditée reste la release stable `v0.1.2`. @@ -1864,12 +1864,19 @@ La validation utilisateur de `pre.012` est acquise le 2026-08-16 : `fmt/check/cl ### `0.1.3-pre.014` — ownership audits + robustesse -- audits interdisant les accès Config/env directs ailleurs ; -- invalid documents/env/placeholders/file mappings ; -- graphe dépendances/features ; -- corrections de surface publique ; -- robustesse des diagnostics sans fuite ; -- vérification de la granularité réelle des responsabilités publiques. +Livré : + +- nouveau test d'intégration `tests/ownership.rs` exécuté par `cargo test --workspace` ; +- audit de direction garantissant que `ksp-core-lib` et `ksp-logging-lib` ne dépendent pas de `ksp-config-lib` ; +- audit des crates hors Config interdisant les lectures directes littérales `KSP_*`/`KSPB_*` via `std::env::var`/`var_os` ainsi que l'énumération directe `vars`/`vars_os` ; +- audit interdisant hors Config les noms physiques gérés `.env`, `std.logging.json`, `std.logging.schema.json` et `composite.schema.json`, afin que les consumers utilisent les contrats/constantes/file_id Config ; +- audit automatique de toutes les variables KSP/KSPB concrètes présentes dans les JSON de `config/` et le code Rust production ; +- comparaison de cet inventaire avec `.env.example`, en exigeant une assignment versionnée (active ou commentée) et un commentaire explicatif précédent ; +- réaudit manuel de la matrice existante : documents/schema invalides, `.env` invalide/dupliqué, placeholders invalides/manquants, file mappings invalides, profils/composites invalides, chemins Logging effectifs, secrets/redaction et persistence atomique sont déjà couverts par les 80 tests unitaires acquis en `pre.013-fix.001` ; +- aucune correction supplémentaire de surface publique n'est nécessaire dans cette tranche ; la granularité `ConfigEnvironment` / `ConfigDocumentEngine` / `ConfigManagement` / adapter Logging reste cohérente ; +- aucune nouvelle dépendance ni variable d'environnement. + +La validation utilisateur de `pre.013-fix.001` est acquise le 2026-08-16 : `fmt/check/clippy/test` passent, `ksp-config-lib` compte 80 tests unitaires + 11 tests publics, `ksp-logging-lib` 34 tests unitaires + toutes ses intégrations, `ksp-logging-lib -d` est vide et le seul doublon Config reste `syn 2`/`syn 3` transitif via `jsonschema`. ### `0.1.3-pre.015` — clôture diff --git a/docs/rules/RULES_KSP.md b/docs/rules/RULES_KSP.md index ce086bd..ac1d5c5 100644 --- a/docs/rules/RULES_KSP.md +++ b/docs/rules/RULES_KSP.md @@ -1,5 +1,5 @@ - + # Règles spécifiques à KSP @@ -45,6 +45,8 @@ - **KSP-CONFIG-014** — La persistence Config n'expose pas de primitive publique d'écriture vers un chemin arbitraire. Un document connu est muté via son contrat source typé, validé complètement puis remplacé atomiquement dans le path résolu par son `file_id`; un échec avant commit conserve l'ancien fichier. - **KSP-CONFIG-015** — L'environnement du processus reste read-only. La surface management peut créer/modifier/supprimer uniquement des entrées KSP/KSPB du `.env`; chaque mutation rapporte séparément changement de source persistée, changement effectif courant, shadowing par le process et besoin de reload. - **KSP-CONFIG-016** — Les rapports management ordinaires n'exposent que des valeurs sûres/redacted. L'accès en clair à une valeur d'environnement passe par un appel `reveal_*` explicite; l'authentification/autorisation de l'utilisateur final appartient à l'application et cette révélation n'autorise jamais le secret dans les logs/`Debug`/diagnostics génériques. +- **KSP-CONFIG-017** — Les frontières d'ownership Config sont vérifiées par des audits exécutables du workspace : Core/Logging ne dépendent pas de Config, les crates hors `ksp-config-lib` ne lisent pas directement les variables KSP/KSPB via `std::env::var*`/énumération de l'environnement et ne codent pas en dur les noms physiques des fichiers gérés lorsqu'un contrat Config existe. +- **KSP-CONFIG-018** — L'inventaire `.env.example` est vérifié automatiquement contre les variables KSP/KSPB concrètes utilisées par les JSON sous `config/` et le code Rust production. Toute clé runtime détectée doit posséder une entrée d'inventaire précédée d'un commentaire explicatif. ## Programmes et exécution