v0.2.11-pre.010
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-config-lib/README.md -->
|
||||
<!-- version: 9 -->
|
||||
<!-- version: 10 -->
|
||||
|
||||
# ksp-config-lib
|
||||
|
||||
@@ -23,7 +23,8 @@ La crate centralise les documents JSON, leurs schemas, les profils et compositio
|
||||
- la classification `Public`, `Internal`, `Secret` ;
|
||||
- les représentations réelle et sûre/redacted ainsi que la provenance des valeurs résolues ;
|
||||
- l'adapter du document Logging effectif vers `ksp_logging_lib::LoggingSettings` ;
|
||||
- l'adapter du document Transport V1/V2 vers `HttpTransportSettings` et, en V2, `WsTransportSettings`, y compris redaction/provenance des URLs `KSP_SECRET_*` ;
|
||||
- l'adapter du document Transport V1/V2/V3 vers `HttpTransportSettings`, `WsTransportSettings` et, en V3, `YellowstoneGrpcTransportSettings`, y compris redaction/provenance des URLs `KSP_SECRET_*` ;
|
||||
- l'adapter de `cfg.std.offchain_transport` vers `ksp_offchain_transport_lib::MarketPriceService`, avec contrôle de provenance des credentials/public fields et sans rendre les limites provider configurables ;
|
||||
- la surface de management pour inspecter et réparer les sources Config enregistrées, modifier `std.logging.json`, consulter les rapports d'environnement, révéler explicitement une valeur réelle et modifier `.env` ;
|
||||
- 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`.
|
||||
@@ -35,10 +36,12 @@ Le registre par défaut connaît :
|
||||
```text
|
||||
cfg.composite.ksp-app-wallet-desk -> config/composite.ksp-app-wallet-desk.json
|
||||
cfg.std.logging -> config/std.logging.json
|
||||
cfg.std.offchain_transport -> config/std.offchain_transport.json
|
||||
cfg.std.transport -> config/std.transport.json
|
||||
cfg.std.wallet -> config/std.wallet.json
|
||||
schema.composite -> config/schemas/composite.schema.json
|
||||
schema.std.logging -> config/schemas/std.logging.schema.json
|
||||
schema.std.offchain_transport -> config/schemas/std.offchain_transport.schema.json
|
||||
schema.std.transport -> config/schemas/std.transport.schema.json
|
||||
schema.std.wallet -> config/schemas/std.wallet.schema.json
|
||||
```
|
||||
@@ -65,11 +68,11 @@ Les autres crates et applications KSP ne doivent pas :
|
||||
- parser ou écrire directement `.env` ;
|
||||
- ouvrir directement les documents Config connus par leur filename physique ;
|
||||
- réimplémenter la sélection de profils, les compositions ou les placeholders ;
|
||||
- reconstruire elles-mêmes la configuration Logging, Transport ou Wallet depuis le JSON.
|
||||
- reconstruire elles-mêmes la configuration Logging, On-chain Transport, Off-chain Transport ou Wallet depuis le JSON.
|
||||
|
||||
`ksp-config-lib` dépend de `ksp-core-lib` pour `Error`/`Result`, de `ksp-logging-lib` pour les événements Config utiles et le contrat `LoggingSettings`, et de `ksp-onchain-transport-lib` pour construire le contrat runtime Transport dans la direction Config -> Transport. Le document Wallet reste un contrat de chemins/profils Config et n’introduit aucune dépendance Config -> `ksp-wallet-lib`.
|
||||
`ksp-config-lib` dépend de `ksp-core-lib` pour `Error`/`Result`, de `ksp-logging-lib` pour les événements Config utiles et le contrat `LoggingSettings`, de `ksp-onchain-transport-lib` pour construire le contrat runtime On-chain Transport et de `ksp-offchain-transport-lib` pour construire le service market-price dans la direction Config -> Transport. Le document Wallet reste un contrat de chemins/profils Config et n’introduit aucune dépendance Config -> `ksp-wallet-lib`.
|
||||
|
||||
La dépendance inverse est interdite : `ksp-core-lib`, `ksp-logging-lib` et `ksp-onchain-transport-lib` ne dépendent pas de Config.
|
||||
La dépendance inverse est interdite : `ksp-core-lib`, `ksp-logging-lib`, `ksp-onchain-transport-lib` et `ksp-offchain-transport-lib` ne dépendent pas de Config.
|
||||
|
||||
Config ne possède pas le `LoggingGuard`. L'application ou le service qui orchestre le runtime construit la configuration effective puis possède le lifecycle `ksp_logging_lib::initialize/reinitialize`.
|
||||
|
||||
@@ -81,7 +84,7 @@ Un secret reste accessible au runtime ou au management lorsqu'un consumer autori
|
||||
|
||||
Les méthodes `reveal_*` constituent un opt-in explicite au réel. L'authentification/autorisation de l'utilisateur humain appartient à l'application appelante et les valeurs retournées par ces méthodes ne doivent jamais être journalisées.
|
||||
|
||||
Le document Logging refuse les valeurs de sensibilité `Secret` dans sa configuration effective. Le document Transport accepte les valeurs secrètes pour les URLs HTTP/WebSocket et, en V3, pour `grpc_endpoints[].secret_metadata[]` : la valeur réelle est transmise au runtime légitime, tandis que la projection sûre et les `Debug` restent redacted. Les metadata gRPC publiques et secrètes sont séparées et leur provenance Config est contrôlée avant mapping. `std.wallet` refuse également toute sensibilité `Secret` pour `wallets_directory`/`wallets_subdirectory`; les passwords Wallet restent un autre flux Config et ne sont jamais stockés dans ce JSON.
|
||||
Le document Logging refuse les valeurs de sensibilité `Secret` dans sa configuration effective. Le document Transport accepte les valeurs secrètes pour les URLs HTTP/WebSocket et, en V3, pour `grpc_endpoints[].secret_metadata[]` : la valeur réelle est transmise au runtime légitime, tandis que la projection sûre et les `Debug` restent redacted. Les metadata gRPC publiques et secrètes sont séparées et leur provenance Config est contrôlée avant mapping. `std.offchain_transport` exige une provenance `Secret` pour les API keys effectives et une provenance `Public` pour la paire DexScreener lorsqu'elle vient de l'environnement ; il ne permet ni URL provider arbitraire ni override de rate limit. `std.wallet` refuse également toute sensibilité `Secret` pour `wallets_directory`/`wallets_subdirectory`; les passwords Wallet restent un autre flux Config et ne sont jamais stockés dans ce JSON.
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -90,6 +93,7 @@ Le document Logging refuse les valeurs de sensibilité `Secret` dans sa configur
|
||||
- [`../../docs/plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md`](../../docs/plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md) — plan historique détaillé de la fondation Config ;
|
||||
- [`../../config/std.logging.json`](../../config/std.logging.json) — document standard Logging ;
|
||||
- [`../../config/std.transport.json`](../../config/std.transport.json) — document standard Transport V3 HTTP + WebSocket + Yellowstone gRPC, avec lecture backward des V1/V2 ;
|
||||
- [`../../config/std.offchain_transport.json`](../../config/std.offchain_transport.json) — document standard Off-chain Transport V1, actuellement limité au domaine `market_price` SOL/USD ;
|
||||
- [`../../config/std.wallet.json`](../../config/std.wallet.json) — racine Wallet globale et sous-répertoire optionnel par profil ;
|
||||
- [`../../config/composite.ksp-app-wallet-desk.json`](../../config/composite.ksp-app-wallet-desk.json) — composition Logging/Transport/Wallet de Wallet Desk ;
|
||||
- [`../../.env.example`](../../.env.example) — inventaire versionné des variables d'environnement runtime.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-config-lib/USAGE.md -->
|
||||
<!-- version: 12 -->
|
||||
<!-- version: 13 -->
|
||||
|
||||
# Utilisation de ksp-config-lib
|
||||
|
||||
@@ -182,6 +182,38 @@ let _ = (root, profile_subdirectory, effective);
|
||||
|
||||
Les chemins Wallet refusent toute valeur `KSP_SECRET_*`. Les futurs `KSP_SECRET_WALLET_PASS_*` constituent un flux de secrets distinct et ne sont pas des champs de `std.wallet.json`.
|
||||
|
||||
### 4.3 Construire le service Off-chain Transport depuis Config
|
||||
|
||||
Le document `cfg.std.offchain_transport` possède actuellement le domaine `market_price`. Config résout les placeholders, vérifie la provenance des API keys et de la paire DexScreener, puis construit directement `MarketPriceService` :
|
||||
|
||||
```rust
|
||||
let offchain = match engine.load_resolved_offchain_transport_config(
|
||||
std::option::Option::None,
|
||||
&environment,
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
|
||||
for entry in offchain.service().registry().entries() {
|
||||
println!(
|
||||
"{} {:?}",
|
||||
entry.descriptor().display_name(),
|
||||
entry.state().availability(),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Le profil versionné `public_keyless` ne requiert aucun secret. Le profil `all_free` utilise les credentials `KSP_SECRET_*` et la paire `KSP_PUBLIC_DEXSCREENER_SOL_USD_PAIR_ADDRESS` inventoriés dans `.env.example`. Une API key littérale ou issue d'une provenance non secrète est refusée par l'adapter effectif ; une paire DexScreener issue d'une provenance Secret est également refusée.
|
||||
|
||||
Config ne permet pas de fournir `base_url`, `endpoint_url`, `rate_limit` ou `requests_per` aux branches provider. Les origines et cadences sûres restent possédées par `ksp-offchain-transport-lib`. La direction de dépendance reste donc :
|
||||
|
||||
```text
|
||||
ksp-config-lib -> ksp-offchain-transport-lib
|
||||
```
|
||||
|
||||
Off-chain Transport ne lit ni `.env`, ni `KSP_*`, ni les documents Config. Une application telle que la future `ksp-app-solprices-desk` peut recevoir le service déjà composé puis utiliser uniquement `registry()`, `refresh`, `refresh_many` et `refresh_all`.
|
||||
|
||||
## 5. Profils et composites
|
||||
|
||||
Pour un document standard profilé :
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/src/offchain_transport.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Adapter from Config-owned Off-chain Transport documents to the provider-agnostic market-price runtime service.
|
||||
|
||||
@@ -322,6 +322,13 @@ fn map_coingecko(
|
||||
ksp_offchain_transport_lib::MarketPriceCoinGeckoAccessMode::Keyless => {
|
||||
ksp_offchain_transport_lib::MarketPriceCoinGeckoSettings::keyless(source.enabled)
|
||||
},
|
||||
_ => {
|
||||
return std::result::Result::Err(
|
||||
effective_error(profile, "Off-chain Transport CoinGecko access mode is not supported by this Config adapter")
|
||||
.with_context("provider", "coingecko")
|
||||
.with_context("field", "access_mode"),
|
||||
);
|
||||
},
|
||||
};
|
||||
return match settings {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
@@ -346,6 +353,13 @@ fn map_coinmarketcap(
|
||||
ksp_offchain_transport_lib::MarketPriceCoinMarketCapAccessMode::Keyless => {
|
||||
ksp_offchain_transport_lib::MarketPriceCoinMarketCapSettings::keyless(source.enabled)
|
||||
},
|
||||
_ => {
|
||||
return std::result::Result::Err(
|
||||
effective_error(profile, "Off-chain Transport CoinMarketCap access mode is not supported by this Config adapter")
|
||||
.with_context("provider", "coinmarketcap")
|
||||
.with_context("field", "access_mode"),
|
||||
);
|
||||
},
|
||||
};
|
||||
return match settings {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
@@ -386,6 +400,13 @@ fn map_jupiter(
|
||||
ksp_offchain_transport_lib::MarketPriceJupiterSettings::free(source.enabled, source.api_key)
|
||||
},
|
||||
ksp_offchain_transport_lib::MarketPriceJupiterAccessMode::Keyless => ksp_offchain_transport_lib::MarketPriceJupiterSettings::keyless(source.enabled),
|
||||
_ => {
|
||||
return std::result::Result::Err(
|
||||
effective_error(profile, "Off-chain Transport Jupiter access mode is not supported by this Config adapter")
|
||||
.with_context("provider", "jupiter")
|
||||
.with_context("field", "access_mode"),
|
||||
);
|
||||
},
|
||||
};
|
||||
return match settings {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/unit_tests/offchain_transport.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
const TEST_PAIR: &str = "Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE";
|
||||
|
||||
@@ -155,6 +155,51 @@ fn dexscreener_pair_environment_must_use_public_namespace_and_disabled_pair_may_
|
||||
assert!(disabled.is_ok(), "disabled DexScreener runtime settings should accept an absent pair after pre.009 capability reconciliation: {disabled:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_url_and_rate_limit_overrides_are_rejected_by_schema_before_runtime_mapping() {
|
||||
for (provider, field, value) in [
|
||||
("coingecko", "base_url", serde_json::Value::String("https://example.invalid".to_owned())),
|
||||
("coinmarketcap", "rate_limit", serde_json::json!({"requests": 999999, "window_seconds": 1})),
|
||||
] {
|
||||
let fixture = tempfile::tempdir();
|
||||
assert!(fixture.is_ok(), "temporary Config root should be creatable: {fixture:?}");
|
||||
let fixture = match fixture {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let source = committed_document_value();
|
||||
let mut source = match source {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let profiles = source.get_mut("profiles").and_then(serde_json::Value::as_array_mut);
|
||||
assert!(profiles.is_some(), "fixture should expose profiles");
|
||||
if let std::option::Option::Some(profiles) = profiles {
|
||||
let public_keyless = profiles
|
||||
.iter_mut()
|
||||
.find(|profile| return profile.get("profile_id").and_then(serde_json::Value::as_str) == std::option::Option::Some("public_keyless"));
|
||||
assert!(public_keyless.is_some(), "fixture should contain public_keyless profile");
|
||||
if let std::option::Option::Some(public_keyless) = public_keyless {
|
||||
public_keyless["market_price"][provider][field] = value;
|
||||
}
|
||||
}
|
||||
let engine = fixture_engine_with_document(fixture.path(), &source);
|
||||
assert!(engine.is_ok(), "override fixture engine should be constructible: {engine:?}");
|
||||
let engine = match engine {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||
let resolved = engine.load_resolved_offchain_transport_config(std::option::Option::Some("public_keyless"), &environment);
|
||||
assert!(resolved.is_err(), "provider URL/rate-limit override must be rejected before runtime mapping: provider={provider} field={field}");
|
||||
if let std::result::Result::Err(error) = resolved {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_SCHEMA_VALIDATION_FAILED);
|
||||
let debug = format!("{error:?}");
|
||||
assert!(!debug.contains("https://example.invalid"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_provider_availability(
|
||||
registry: &ksp_offchain_transport_lib::MarketPriceProviderRegistry,
|
||||
provider_id: &str,
|
||||
|
||||
Reference in New Issue
Block a user