v0.1.4-pre.002

This commit is contained in:
2026-08-16 10:01:04 +02:00
parent 839664377f
commit 4a68a54d85
8 changed files with 308 additions and 11 deletions

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-config-lib/README.md -->
<!-- version: 1 -->
<!-- version: 2 -->
# ksp-config-lib
@@ -12,7 +12,7 @@ La crate centralise les documents JSON, leurs schemas, les profils et compositio
`ksp-config-lib` possède :
- le bootstrap non récursif `config/` / `config/schemas/` et les overrides `--cfgpath` / `--schemapath` ;
- le registre logique `file_id -> filename` et les overrides `--filemap=<file_id>=<filename>` ;
- le registre logique `file_id -> filename`, son inventaire public read-only ordonné par `file_id` et les overrides `--filemap=<file_id>=<filename>` ;
- la lecture JSON et la validation JSON Schema Draft 2020-12 ;
- les invariants sémantiques KSP des documents connus ;
- les globals, `default_profile`, profils nommés et leur provenance ;
@@ -27,7 +27,7 @@ La crate centralise les documents JSON, leurs schemas, les profils et compositio
- les écritures atomiques JSON/`.env` et la protection des permissions `.env` ;
- les audits workspace empêchant les bypass d'ownership Config et les oublis dans `.env.example`.
## Ressources gérées dans `0.1.3`
## Ressources gérées
Le registre par défaut connaît :
@@ -37,6 +37,8 @@ schema.std.logging -> config/schemas/std.logging.schema.json
schema.composite -> config/schemas/composite.schema.json
```
`ConfigFileRegistry::descriptors()` expose ces descripteurs en lecture seule et dans un ordre déterministe par `file_id`. Une application de management peut ainsi découvrir les fichiers connus sans maintenir une liste parallèle ni dépendre de leurs filenames physiques.
`config/examples/composite.example.json` démontre le format composite sans créer de composite runtime fictif.
Le fichier local d'environnement est :

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-config-lib/TODO.md -->
<!-- version: 1 -->
<!-- version: 2 -->
# TODO ksp-config-lib
@@ -9,7 +9,13 @@ Aucun TODO fonctionnel bloquant n'est ouvert pour la fondation Config `0.1.3`.
Les responsabilités prévues pour cette release sont implémentées et couvertes par les tests : bootstrap, registre `file_id`, JSON/JSON Schema, profils, composites, environnement process/`.env`, placeholders, sensibilité/provenance, adapter Logging, management/persistence et audits d'ownership.
## Reporté explicitement à `0.1.4`
## Extensions bornées révélées par `0.1.4`
`0.1.4-pre.002` ajoute l'inventaire public read-only de `ConfigFileRegistry`, nécessaire au shell Documents de `ksp-app-config-desk`. Le registre reste propriétaire des descripteurs ; l'application n'entretient pas de liste parallèle de `file_id`.
La seconde lacune révélée pendant `pre.001` reste à traiter dans la tranche suivante : une API Config bornée permettant de soumettre le source corrigé d'un `file_id` connu, le valider puis le persister atomiquement sans accès filesystem direct de l'application.
## Validation desktop de `0.1.4`
La validation applicative desktop appartient à `ksp-app-config-desk` :

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-config-lib/USAGE.md -->
<!-- version: 2 -->
<!-- version: 3 -->
# Utilisation de ksp-config-lib
@@ -33,6 +33,22 @@ Les arguments compris par Config sont :
`cfgpath` et `schemapath` ne sont jamais lus depuis JSON, `.env` ou une variable KSP : cette règle évite un bootstrap récursif.
### 1.1 Inventorier les fichiers enregistrés
Le registre expose une vue read-only déterministe des descripteurs connus :
```rust
for descriptor in registry.descriptors() {
let file_id = descriptor.file_id().as_str();
let kind = descriptor.kind();
let filename = descriptor.filename();
let schema_file_id = descriptor.schema_file_id();
let _ = (file_id, kind, filename, schema_file_id);
}
```
L'ordre est celui des `file_id`. La vue reflète les éventuels overrides `--filemap` déjà appliqués tout en conservant le kind et l'association de schema. Elle permet notamment à une application de management de construire sa liste de documents/schemas sans dupliquer le registre dans sa propre couche.
## 2. Charger et valider un document connu
Les consumers utilisent un `file_id` logique :

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/src/registry.rs
// version: 3
// version: 4
/// Bootstrap argument used to replace a known Config filename mapping.
pub const ARG_FILE_MAP: &str = "--filemap";
@@ -165,6 +165,13 @@ impl ConfigFileRegistry {
return std::result::Result::Ok(registry);
}
/// Iterates over all registered descriptors in deterministic logical `file_id` order.
///
/// The returned view is read-only and reflects filename overrides already applied to this registry.
pub fn descriptors(&self) -> impl std::iter::Iterator<Item = &ConfigFileDescriptor> + '_ {
return self.descriptors.values();
}
/// Returns the descriptor associated with a known logical file identifier.
pub fn descriptor(&self, file_id: &ConfigFileId) -> ksp_core_lib::Result<&ConfigFileDescriptor> {
return match self.descriptors.get(file_id) {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/tests/public_api.rs
// version: 10
// version: 11
//! Integration tests for the public `ksp-config-lib` bootstrap, registry, JSON/profile/composite, environment-resolution, sensitivity, Logging-adapter and
//! management contracts.
@@ -74,6 +74,25 @@ fn logical_file_registry_is_available_from_crate_root() {
}
}
#[test]
fn registry_descriptor_inventory_is_available_from_crate_root() {
let registry = ksp_config_lib::ConfigFileRegistry::defaults();
assert!(registry.is_ok(), "public registry should remain constructible: {registry:?}");
if let std::result::Result::Ok(registry) = registry {
let descriptors: std::vec::Vec<&ksp_config_lib::ConfigFileDescriptor> = registry.descriptors().collect();
assert_eq!(descriptors.len(), 3);
assert_eq!(descriptors[0].file_id().as_str(), ksp_config_lib::FILE_ID_STD_LOGGING);
assert_eq!(descriptors[0].kind(), ksp_config_lib::ConfigFileKind::Config);
assert_eq!(descriptors[1].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_COMPOSITE);
assert_eq!(descriptors[2].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_LOGGING);
let schema_file_id = descriptors[0].schema_file_id();
assert!(schema_file_id.is_some(), "public descriptor inventory should preserve schema association");
if let std::option::Option::Some(schema_file_id) = schema_file_id {
assert_eq!(schema_file_id.as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_LOGGING);
}
}
}
#[test]
fn validated_json_document_engine_is_available_from_crate_root() {
let workspace = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");

View File

@@ -1,5 +1,58 @@
// file: crates/ksp-config-lib/unit_tests/registry.rs
// version: 3
// version: 4
#[test]
fn descriptors_expose_complete_registry_in_deterministic_file_id_order() {
let registry = super::ConfigFileRegistry::defaults();
assert!(registry.is_ok(), "default registry should be valid: {registry:?}");
if let std::result::Result::Ok(registry) = registry {
let descriptors: std::vec::Vec<&super::ConfigFileDescriptor> = registry.descriptors().collect();
assert_eq!(descriptors.len(), 3);
assert_eq!(descriptors[0].file_id().as_str(), super::FILE_ID_STD_LOGGING);
assert_eq!(descriptors[0].kind(), super::ConfigFileKind::Config);
assert_eq!(descriptors[0].filename(), std::path::Path::new(super::DEFAULT_STD_LOGGING_FILENAME));
let schema_file_id = descriptors[0].schema_file_id();
assert!(schema_file_id.is_some(), "logging descriptor should expose its validation schema");
if let std::option::Option::Some(schema_file_id) = schema_file_id {
assert_eq!(schema_file_id.as_str(), super::FILE_ID_SCHEMA_STD_LOGGING);
}
assert_eq!(descriptors[1].file_id().as_str(), super::FILE_ID_SCHEMA_COMPOSITE);
assert_eq!(descriptors[1].kind(), super::ConfigFileKind::Schema);
assert_eq!(descriptors[2].file_id().as_str(), super::FILE_ID_SCHEMA_STD_LOGGING);
assert_eq!(descriptors[2].kind(), super::ConfigFileKind::Schema);
}
}
#[test]
fn descriptors_reflect_filename_overrides_without_changing_logical_metadata() {
let registry = super::ConfigFileRegistry::defaults();
let file_id = super::ConfigFileId::new(super::FILE_ID_STD_LOGGING);
assert!(registry.is_ok(), "default registry should be valid: {registry:?}");
assert!(file_id.is_ok(), "logging file_id should be valid: {file_id:?}");
if let (std::result::Result::Ok(registry), std::result::Result::Ok(file_id)) = (registry, file_id) {
let overridden = registry.with_filename_override(&file_id, "profiles/desktop.logging.json");
assert!(overridden.is_ok(), "filename override should remain valid: {overridden:?}");
if let std::result::Result::Ok(overridden) = overridden {
let mut found: std::option::Option<&super::ConfigFileDescriptor> = std::option::Option::None;
for descriptor in overridden.descriptors() {
if descriptor.file_id() == &file_id {
found = std::option::Option::Some(descriptor);
}
}
assert!(found.is_some(), "public descriptor inventory should retain the overridden logging descriptor");
if let std::option::Option::Some(descriptor) = found {
assert_eq!(descriptor.file_id(), &file_id);
assert_eq!(descriptor.kind(), super::ConfigFileKind::Config);
assert_eq!(descriptor.filename(), std::path::Path::new("profiles/desktop.logging.json"));
let schema_file_id = descriptor.schema_file_id();
assert!(schema_file_id.is_some(), "filename override should preserve schema association");
if let std::option::Option::Some(schema_file_id) = schema_file_id {
assert_eq!(schema_file_id.as_str(), super::FILE_ID_SCHEMA_STD_LOGGING);
}
}
}
}
}
#[test]
fn defaults_register_logging_document_and_schema_with_distinct_roots() {