v0.5.2-pre.006

This commit is contained in:
2026-08-10 23:14:17 +02:00
parent e3acb8ceb1
commit 066d969f5a
51 changed files with 1937 additions and 88 deletions

View File

@@ -1,8 +1,14 @@
<!-- file: ks-config/CHANGELOG.md -->
<!-- version: 20 -->
<!-- version: 21 -->
# CHANGELOG — ks-config
## `0.5.2-pre.006`
- ajoute `wallet_alias` aux profils du document wallet comme sélection optionnelle/nullable d'un wallet natif persistant, sans password ni matériau secret dans `ks-config` ;
- propage cette sélection non sensible vers le contrat runtime `WalletConfig` et valide l'alias avec les mêmes bornes que `ks-wallet` ;
- garde les documents `0.5.1` compatibles lorsque `wallet_alias` est omis, conserve `null` comme absence explicite de sélection et documente un exemple de sélection persistante.
## `0.5.1-pre.009`
- réconcilie schémas, exemples sous `config/exemples/`, guide de configuration et documentation de crate avant archivage du plan `0.5.1` ;

View File

@@ -1,5 +1,5 @@
<!-- file: ks-config/README.md -->
<!-- version: 12 -->
<!-- version: 13 -->
# ks-config
@@ -10,6 +10,7 @@
- charger l'environnement du workspace et résoudre les placeholders `${KS_*}` / `${KB_*}` ;
- valider les documents transport, listeners, store, wallet, execution et composition contre leurs schémas sous `config/schemas/` ;
- fournir les types partagés d'endpoints, listeners, stockage, wallet, exécution et profils runtime ;
- permettre à un profil wallet de sélectionner un alias persistant non sensible, sans devenir gestionnaire de password ou de matériau privé ;
- résoudre les defaults de chaque document ou les overrides d'une composition ;
- refuser une référence de fichier ou de profil inexistante ;
- reconstruire temporairement `AppConfig/ProfileConfig` pour les consommateurs existants ;

View File

@@ -1,5 +1,5 @@
<!-- file: ks-config/USAGE.md -->
<!-- version: 10 -->
<!-- version: 11 -->
# Utilisation de ks-config
@@ -9,6 +9,22 @@
`ks-config` ne connaît pas la liste des futurs workers ou applications.
## Sélectionner un wallet persistant par alias
Le document `wallet.config.json` accepte un champ optionnel `wallet_alias` par profil. Son omission ou `null` signifie qu'aucun wallet natif persistant n'est sélectionné ; une chaîne valide sélectionne uniquement l'identité logique. Le mot de passe reste fourni au runtime directement à `ks-wallet` et n'appartient jamais à `ks-config`.
```json
{
"name": "worker_devnet",
"directory": "workers/devnet",
"cluster": "devnet",
"wallet_alias": "worker-operator",
"temporary_wallet_enabled": false,
"temporary_wallet_alias": "worker-temporary-disabled",
"temporary_wallet_persist": false
}
```
## Charger les defaults partagés
```rust

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/lib.rs
// version: 9
// version: 10
//! Khadhroony Solana shared configuration contracts and composition helpers.
#![warn(missing_docs)]
@@ -234,5 +234,7 @@ pub(crate) use self::settings::validate_http_endpoint;
pub(crate) use self::settings::validate_listeners;
/// Keeps `validate_wallet` available to internal configuration modules.
pub(crate) use self::settings::validate_wallet;
/// Keeps wallet-alias validation shared by source and resolved configuration modules.
pub(crate) use self::settings::validate_wallet_alias;
/// Keeps `validate_ws_endpoint` available to internal configuration modules.
pub(crate) use self::settings::validate_ws_endpoint;

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/settings.rs
// version: 23
// version: 24
//! Resolved runtime configuration models retained during the `0.5.1` source-document split.
@@ -226,6 +226,8 @@ pub struct WalletConfig {
pub wallet_dir: std::string::String,
/// Selected cluster name.
pub cluster: std::string::String,
/// Optional persistent native wallet alias selected by this profile.
pub wallet_alias: std::option::Option<std::string::String>,
/// Enables the managed temporary wallet for this profile.
pub temporary_wallet_enabled: bool,
/// Alias used for the managed temporary wallet.
@@ -736,7 +738,13 @@ pub(crate) fn validate_wallet(config: &WalletConfig) -> ks_core::Result<()> {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
match validate_wallet_alias(&config.temporary_wallet_alias) {
if let std::option::Option::Some(alias) = config.wallet_alias.as_deref() {
match validate_wallet_alias(alias, "wallet.wallet_alias") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
}
match validate_wallet_alias(&config.temporary_wallet_alias, "wallet.temporary_wallet_alias") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
@@ -755,11 +763,11 @@ pub(crate) fn validate_wallet(config: &WalletConfig) -> ks_core::Result<()> {
return std::result::Result::Ok(());
}
fn validate_wallet_alias(value: &str) -> ks_core::Result<()> {
pub(crate) fn validate_wallet_alias(value: &str, field: &str) -> ks_core::Result<()> {
if value.is_empty() || value.len() > 64 {
return std::result::Result::Err(ks_core::Error::new(
"wallet_alias_length_invalid",
"temporary wallet alias length must be between 1 and 64 bytes",
format!("{field} length must be between 1 and 64 bytes"),
));
}
if !value.bytes().enumerate().all(|(index, byte)| {
@@ -767,7 +775,7 @@ fn validate_wallet_alias(value: &str) -> ks_core::Result<()> {
}) {
return std::result::Result::Err(ks_core::Error::new(
"wallet_alias_invalid",
"temporary wallet alias contains invalid characters",
format!("{field} contains invalid characters"),
));
}
return std::result::Result::Ok(());
@@ -1016,6 +1024,16 @@ mod tests {
assert!(result.is_err());
}
#[test]
fn parser_rejects_invalid_persistent_wallet_alias() {
let mut value = parse_default_value();
value["profiles"][0]["wallet"]["wallet_alias"] =
serde_json::Value::String("../wallet".to_string());
let raw_json = value_to_json(&value);
let result = super::parse_config_json(&raw_json);
assert!(result.is_err());
}
#[test]
fn parser_rejects_invalid_temporary_wallet_alias() {
let mut value = parse_default_value();

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/wallet.rs
// version: 3
// version: 4
//! Independent wallet configuration document loading and validation.
@@ -25,6 +25,8 @@ pub struct WalletProfileConfig {
pub directory: std::string::String,
/// Selected cluster name.
pub cluster: std::string::String,
/// Optional persistent native wallet alias selected by this profile.
pub wallet_alias: std::option::Option<std::string::String>,
/// Enables the managed temporary wallet for this profile.
pub temporary_wallet_enabled: bool,
/// Alias used for the managed temporary wallet.
@@ -150,6 +152,7 @@ pub fn resolved_wallet_profile(
let resolved = crate::WalletConfig {
wallet_dir: wallet_dir.display().to_string(),
cluster: profile.cluster.clone(),
wallet_alias: profile.wallet_alias.clone(),
temporary_wallet_enabled: profile.temporary_wallet_enabled,
temporary_wallet_alias: profile.temporary_wallet_alias.clone(),
temporary_wallet_persist: profile.temporary_wallet_persist,
@@ -186,6 +189,12 @@ pub fn validate_wallet_document(document: &WalletConfigDocument) -> ks_core::Res
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
if let std::option::Option::Some(alias) = profile.wallet_alias.as_deref() {
match crate::validate_wallet_alias(alias, "wallet.profile.wallet_alias") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
}
if !names.insert(profile.name.clone()) {
return std::result::Result::Err(ks_core::Error::new(
"wallet_profile_name_duplicate",
@@ -210,7 +219,55 @@ mod tests {
#[test]
fn default_and_example_wallet_configs_validate() {
assert!(super::parse_wallet_json(DEFAULT_WALLET).is_ok());
assert!(super::parse_wallet_json(EXAMPLE_WALLET).is_ok());
let default = super::parse_wallet_json(DEFAULT_WALLET);
assert!(default.is_ok());
let example = super::parse_wallet_json(EXAMPLE_WALLET);
assert!(example.is_ok());
let example = match example {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(
example.profiles[0].wallet_alias.as_deref(),
std::option::Option::Some("example-operator")
);
}
#[test]
fn wallet_profile_without_persistent_alias_remains_backward_compatible() {
let mut value = match serde_json::from_str::<serde_json::Value>(DEFAULT_WALLET) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("default wallet JSON failed: {error}"),
};
let profile = match value["profiles"][0].as_object_mut() {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("default wallet profile must be an object"),
};
assert!(profile.remove("wallet_alias").is_some());
let raw = match serde_json::to_string(&value) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("wallet JSON encode failed: {error}"),
};
let parsed = super::parse_wallet_json(raw.as_str());
assert!(parsed.is_ok());
let parsed = match parsed {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert!(parsed.profiles[0].wallet_alias.is_none());
}
#[test]
fn wallet_profile_rejects_invalid_persistent_alias() {
let mut value = match serde_json::from_str::<serde_json::Value>(EXAMPLE_WALLET) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("example wallet JSON failed: {error}"),
};
value["profiles"][0]["wallet_alias"] = serde_json::Value::String("../wallet".to_string());
let raw = match serde_json::to_string(&value) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("wallet JSON encode failed: {error}"),
};
assert!(super::parse_wallet_json(raw.as_str()).is_err());
}
}