v0.2.1-pre.007

This commit is contained in:
2026-08-17 22:43:07 +02:00
parent e0a7ac0bf8
commit 79b67f8eae
20 changed files with 1379 additions and 42 deletions

View File

@@ -1,5 +1,5 @@
# file: crates/ksp-config-lib/Cargo.toml
# version: 5
# version: 6
[package]
name = "ksp-config-lib"
@@ -15,5 +15,8 @@ serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
jsonschema.workspace = true
[dev-dependencies]
tokio = { workspace = true, features = ["macros", "rt"] }
[lints]
workspace = true

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-config-lib/README.md -->
<!-- version: 4 -->
<!-- version: 5 -->
# ksp-config-lib
@@ -70,7 +70,7 @@ La dépendance inverse est interdite : `ksp-core-lib`, `ksp-logging-lib` et `ksp
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`.
Tauri et les DTO TS-RS restent hors de cette crate. La future `ksp-app-config-desk` doit rester une frontière applicative mince au-dessus des APIs Config.
Tauri et les DTO TS-RS restent hors de cette crate. `ksp-app-config-desk` reste une frontière applicative mince au-dessus des APIs Config et découvre les documents standards via le registre Config sans déplacer leur logique métier dans l'application.
## Secrets

View File

@@ -0,0 +1,32 @@
// file: crates/ksp-config-lib/tests/transport_devnet_smoke.rs
// version: 1
//! Opt-in live Devnet smoke for the Config -> Transport foundation path.
#[tokio::test(flavor = "current_thread")]
#[ignore = "opt-in live Solana Devnet smoke; performs external network requests"]
async fn committed_devnet_transport_profile_reaches_all_foundation_canaries() {
let workspace = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
let bootstrap = ksp_config_lib::ConfigBootstrapOptions::from_paths(workspace.join("config"), workspace.join("config/schemas"))
.expect("committed Config roots must be valid");
let registry = ksp_config_lib::ConfigFileRegistry::defaults().expect("default Config registry must be valid");
let engine = ksp_config_lib::ConfigDocumentEngine::new(bootstrap, registry);
let environment = ksp_config_lib::ConfigEnvironment::load().expect("Config must capture the opt-in smoke environment");
let resolved = engine
.load_resolved_transport_config(std::option::Option::Some("devnet_public"), &environment)
.expect("committed devnet_public Transport profile must resolve");
assert_eq!(resolved.profile_id(), "devnet_public");
let pool = ksp_onchain_transport_lib::HttpTransportPool::new(resolved.into_settings()).expect("resolved Transport settings must construct the HTTP pool");
let role = ksp_onchain_transport_lib::HttpRoleName::new("default");
let health = pool.get_health(&role).await.expect("Devnet getHealth smoke must succeed");
assert_eq!(health, ksp_onchain_transport_lib::SolanaNodeHealth::Healthy);
let genesis_hash = pool.get_genesis_hash(&role).await.expect("Devnet getGenesisHash smoke must succeed");
assert!(!genesis_hash.as_str().is_empty());
let version = pool.get_version(&role).await.expect("Devnet getVersion smoke must succeed");
assert!(!version.solana_core().is_empty());
let balance = pool
.get_balance(&role, &ksp_core_lib::PRGIDPK_SOLANA_SYSTEM, std::option::Option::None)
.await
.expect("Devnet getBalance smoke must succeed");
assert!(balance.context().slot() > 0);
}

View File

@@ -0,0 +1,135 @@
<!-- file: crates/ksp-onchain-transport-lib/README.md -->
<!-- version: 1 -->
# `ksp-onchain-transport-lib`
`ksp-onchain-transport-lib` est la bibliothèque KSP propriétaire du transport on-chain Solana. Sa première surface est le transport HTTP JSON-RPC ; les extensions WebSocket et gRPC sont introduites séparément lorsque leur release les cible.
## Responsabilités
La crate possède :
- les settings runtime HTTP publics ;
- les endpoints nommés et leurs metadata provider/cluster ;
- les rôles, capabilities/request kinds et priorités ;
- la sélection/fairness/fallback du pool ;
- les limites RPS/burst/concurrence et le cooldown ;
- les deadlines et timeouts ;
- le retry/backoff borné et la règle no-resend après dispatch ambigu ;
- les enveloppes JSON-RPC 2.0 et leur validation ;
- le registre audité des méthodes Solana HTTP ;
- l'exécution générique des méthodes standard supportées ;
- les wrappers typés explicitement livrés par KSP ;
- les snapshots runtime sûrs ;
- l'observabilité Transport via `ksp-logging-lib`.
La crate ne possède ni documents Config, ni persistence Store, ni modèles Program/métier.
## Frontières de dépendances
La direction autorisée est :
```text
ksp-config-lib
-> ksp-onchain-transport-lib
-> ksp-core-lib
-> ksp-logging-lib
-> reqwest / tokio / serde
```
La direction inverse est interdite :
```text
ksp-onchain-transport-lib -X-> ksp-config-lib
ksp-onchain-transport-lib -X-> Store
ksp-onchain-transport-lib -X-> Program
ksp-onchain-transport-lib -X-> tracing direct
```
`ksp-config-lib` peut donc charger `std.transport.json` et construire `HttpTransportSettings`, tandis que Transport reste directement utilisable par un consumer qui fournit lui-même ses settings.
## Surface HTTP standard
Le registre KSP conserve deux inventaires distincts :
```text
52 méthodes HTTP courantes
14 méthodes historiques Deprecated / runtime Removed
```
Le registre porte notamment :
- catégorie ;
- request kind ;
- statut documentaire ;
- statut runtime ;
- forme de requête stable/legacy ;
- type d'opération ;
- classe de retry ;
- remplacement historique éventuel ;
- release de couverture typée KSP.
Les quatre wrappers typés de la foundation sont :
```text
getBalance
getGenesisHash
getHealth
getVersion
```
Les autres méthodes courantes peuvent déjà passer par l'exécuteur JSON-RPC standard générique lorsqu'un consumer fournit explicitement descriptor et paramètres JSON. Cette surface raw/générique **ne vaut pas couverture typée** : les wrappers et DTOs typés restants sont introduits selon la matrice HTTP KSP.
Les 14 méthodes historiques restent découvrables pour la compliance mais sont `Removed` et ne sont pas simulées comme appelables.
## Résilience
L'admission est calculée par couple endpoint/rôle. Le pool applique :
1. rôle et capability ;
2. priorité croissante ;
3. round-robin dans le meilleur tier ;
4. RPS/burst ;
5. concurrence ;
6. cooldown rate-limit ;
7. fallback vers les pairs puis les tiers inférieurs ;
8. deadline commune à l'opération et ses retries.
Les retries ne sont autorisés que lorsque la metadata de méthode et l'état de dispatch les rendent sûrs. `WriteSubmission / NeverAfterDispatch` interdit tout resend automatique après un dispatch ambigu.
Les erreurs JSON-RPC applicatives ne sont pas transformées en retries transport génériques.
## Sécurité et diagnostics
Les URLs d'endpoint peuvent contenir des credentials. Elles ne sont donc pas exposées par les `Debug`, snapshots ou logs ordinaires.
Les `reqwest::Error` attachées comme source sont neutralisées avec `without_url()` avant exposition dans le contrat d'erreur KSP.
Le target de tracing est possédé explicitement par :
```text
src/constants.rs
TRACING_TARGET = "ksp-onchain-transport-lib"
```
La configuration Logging de référence conserve un fichier dédié Transport à niveau `info`. Un niveau `debug`/`trace` ciblé peut être réactivé temporairement via Config lors d'un développement ou diagnostic explicite.
## Tests
Les tests par défaut sont déterministes et n'exigent pas Internet : fixtures JSON et serveur HTTP local couvrent requêtes, réponses, retry, 429, timeout, redaction et routing.
Un smoke Devnet live existe côté `ksp-config-lib` afin de tester la chaîne réelle :
```text
Config -> std.transport/devnet_public -> HttpTransportPool
-> getHealth/getGenesisHash/getVersion/getBalance
```
Il est `ignored` par défaut et doit être exécuté explicitement.
## Documentation
- [`USAGE.md`](USAGE.md) — consommation directe, Config -> Transport, API typed/raw et inspection runtime ;
- [`../../docs/plans/008-V0_2_1_ONCHAIN_HTTP_PLAN.md`](../../docs/plans/008-V0_2_1_ONCHAIN_HTTP_PLAN.md) — plan et matrice HTTP ;
- [`../../docs/validation/003-V0_2_1_ONCHAIN_HTTP.md`](../../docs/validation/003-V0_2_1_ONCHAIN_HTTP.md) — matrice de clôture ;
- [`../../config/std.transport.json`](../../config/std.transport.json) — configuration standard HTTP.

View File

@@ -0,0 +1,164 @@
<!-- file: crates/ksp-onchain-transport-lib/USAGE.md -->
<!-- version: 1 -->
# Utilisation de `ksp-onchain-transport-lib`
Ce guide présente les surfaces publiques destinées aux consumers. Les notes de release restent dans `CHANGELOG.md` et les deltas.
## 1. Construction directe du runtime
Transport peut être utilisé sans Config. Le consumer construit les settings publics puis le pool :
```rust
let url = match ksp_onchain_transport_lib::HttpEndpointUrl::parse("https://api.devnet.solana.com") {
Ok(value) => value,
Err(error) => return Err(error),
};
let role = ksp_onchain_transport_lib::HttpEndpointRoleSettings::new(
ksp_onchain_transport_lib::HttpRoleName::new("default"),
true,
vec![ksp_onchain_transport_lib::HttpRequestKind::wildcard()],
100,
ksp_onchain_transport_lib::HttpRoleLimits::new(None, None, None, None),
);
let endpoint = ksp_onchain_transport_lib::HttpEndpointSettings::new(
"solana_devnet_public",
true,
ksp_onchain_transport_lib::HttpProviderName::new("solana-public"),
ksp_onchain_transport_lib::HttpClusterName::new("devnet"),
url,
std::time::Duration::from_secs(5),
std::time::Duration::from_secs(15),
Some(8),
vec![role],
);
let settings = ksp_onchain_transport_lib::HttpTransportSettings::new(
vec![endpoint],
ksp_onchain_transport_lib::HttpRetrySettings::new(
2,
std::time::Duration::from_millis(100),
std::time::Duration::from_secs(2),
),
);
let pool = match ksp_onchain_transport_lib::HttpTransportPool::new(settings) {
Ok(value) => value,
Err(error) => return Err(error),
};
```
`HttpTransportSettings::validate()` peut être appelé explicitement avant la construction du pool lorsque le consumer veut séparer validation et initialisation.
## 2. Construction via `ksp-config-lib`
Lorsque le consumer utilise Config, la direction reste Config -> Transport :
```rust
let resolved = match engine.load_resolved_transport_config(Some("devnet_public"), &environment) {
Ok(value) => value,
Err(error) => return Err(error),
};
let pool = match ksp_onchain_transport_lib::HttpTransportPool::new(resolved.into_settings()) {
Ok(value) => value,
Err(error) => return Err(error),
};
```
Le document standard peut contenir une URL provenant d'un `KSP_SECRET_*`. La valeur réelle est transmise au runtime, mais les projections sûres et `Debug` restent redacted.
## 3. Appels typés
Les wrappers typés se trouvent directement sur `HttpTransportPool`.
```rust
let role = ksp_onchain_transport_lib::HttpRoleName::new("default");
let health = pool.get_health(&role).await;
let genesis_hash = pool.get_genesis_hash(&role).await;
let version = pool.get_version(&role).await;
let balance = pool
.get_balance(
&role,
&ksp_core_lib::PRGIDPK_SOLANA_SYSTEM,
Some(&ksp_onchain_transport_lib::GetBalanceConfig::new(
Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed),
None,
)),
)
.await;
```
Les types de retour associés sont :
```text
SolanaNodeHealth
SolanaGenesisHash
SolanaNodeVersion
GetBalanceResult
SolanaRpcContext
```
`GetBalanceResult::value()` renvoie les lamports et `context()` fournit le slot/API version retournés par Solana.
## 4. Exécution JSON-RPC standard générique
Une méthode courante auditée peut être appelée via son descriptor :
```rust
if let Some(descriptor) = ksp_onchain_transport_lib::find_http_rpc_method("getSlot") {
let _result = pool.execute_standard_rpc(&role, descriptor, vec![]).await;
}
```
Cette API retourne un `serde_json::Value`. Elle est utile pour les consumers techniques et pour préparer les futures surfaces typées, mais elle ne remplace pas le wrapper typé d'une méthode dans la matrice de couverture KSP.
Avant exécution, `ensure_runtime_supported()` est appliqué. Une méthode historique `Removed` retourne `ERROR_CODE_METHOD_REMOVED` au lieu d'émettre un appel réseau fictif.
## 5. Sélection et admission sans exécuter la requête
Pour inspecter le routing :
```rust
if let Some(descriptor) = ksp_onchain_transport_lib::find_http_rpc_method("getBalance") {
let _selection = pool.select_for_method(&role, descriptor);
let _permit = pool.acquire_for_method(&role, descriptor).await;
}
```
Dans le même bloc, `acquire_for_method()` réserve réellement la capacité RPS/concurrence sous deadline.
`HttpRequestPermit` détient la capacité de concurrence jusqu'à sa destruction. Aucun verrou synchrone n'est conservé pendant l'attente réseau.
## 6. Snapshots runtime
`HttpTransportPool::snapshot()` fournit une vue sûre des endpoints/rôles : disponibilité, limites, requêtes en vol, cooldown restant et compteurs runtime.
Les URLs d'endpoint n'y apparaissent jamais.
## 7. Retry et write submissions
La policy de retry est portée par la metadata des méthodes et `evaluate_transport_retry()`.
Les reads/simulations classés `RetrySafe` peuvent être réessayés dans le budget configuré lorsqu'une cause transport est explicitement retryable.
Pour une opération `WriteSubmission / NeverAfterDispatch`, un timeout ou autre résultat ambigu après dispatch arrête la resoumission automatique. Le consumer métier ne doit pas contourner cette protection avec une boucle de retry externe aveugle.
## 8. Logging
Les événements Transport utilisent le target :
```text
ksp-onchain-transport-lib
```
Ne jamais journaliser l'URL complète, un token provider, un body massif, une transaction complète ou une réponse complète.
La configuration standard route les événements `info` de Transport vers un fichier dédié. Pour une investigation temporaire, élever uniquement ce target/sink à `debug` ou `trace`, puis revenir à `info` avant clôture du développement.
## 9. Smoke Devnet opt-in
Le smoke live est volontairement hors des tests par défaut :
```bash
cargo test -p ksp-config-lib --test transport_devnet_smoke -- --ignored --nocapture
```
Il charge le profil Config `devnet_public`, construit le pool puis appelle les quatre wrappers typés. Les endpoints publics Solana étant rate-limités et non destinés à la production, un échec réseau externe n'est pas interprété comme un échec déterministe de la suite locale.

View File

@@ -0,0 +1,48 @@
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
// version: 1
//! Release-level completeness canaries for the `0.2.1` HTTP foundation contract.
#[test]
fn release_registry_partition_matches_the_audited_http_plan() {
let mut foundation = 0_usize;
let mut accounts_tokens_cluster = 0_usize;
let mut transactions = 0_usize;
let mut blocks_economics = 0_usize;
let mut historical_in_current = 0_usize;
for descriptor in ksp_onchain_transport_lib::current_http_rpc_methods() {
match descriptor.coverage_release() {
ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_1 => foundation += 1,
ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_2 => accounts_tokens_cluster += 1,
ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_3 => transactions += 1,
ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_4 => blocks_economics += 1,
ksp_onchain_transport_lib::HttpRpcCoverageRelease::Historical => historical_in_current += 1,
}
}
assert_eq!(ksp_onchain_transport_lib::current_http_rpc_methods().len(), 52);
assert_eq!(ksp_onchain_transport_lib::historical_http_rpc_methods().len(), 14);
assert_eq!(foundation, 4);
assert_eq!(accounts_tokens_cluster, 22);
assert_eq!(transactions, 11);
assert_eq!(blocks_economics, 15);
assert_eq!(historical_in_current, 0);
}
#[test]
fn release_foundation_canaries_and_historical_statuses_are_exact() {
let mut foundation_names = std::vec::Vec::<&str>::new();
for descriptor in ksp_onchain_transport_lib::current_http_rpc_methods() {
if descriptor.coverage_release() == ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_1 {
foundation_names.push(descriptor.method());
assert_eq!(descriptor.runtime_status(), ksp_onchain_transport_lib::RpcRuntimeStatus::Supported);
}
}
foundation_names.sort_unstable();
assert_eq!(foundation_names, std::vec!["getBalance", "getGenesisHash", "getHealth", "getVersion"]);
for descriptor in ksp_onchain_transport_lib::historical_http_rpc_methods() {
assert_eq!(descriptor.documentation_status(), ksp_onchain_transport_lib::RpcDocumentationStatus::Deprecated);
assert_eq!(descriptor.runtime_status(), ksp_onchain_transport_lib::RpcRuntimeStatus::Removed);
assert_eq!(descriptor.coverage_release(), ksp_onchain_transport_lib::HttpRpcCoverageRelease::Historical);
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::NotApplicable);
}
}