v0.2.7-pre.013

This commit is contained in:
2026-08-23 10:24:28 +02:00
parent 3b4d355537
commit 3c5786f273
8 changed files with 352 additions and 42 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-core-lib/tests/workspace_dependencies.rs
// version: 3
// version: 4
//! Workspace-level dependency policy canaries owned by the foundational KSP test surface.
@@ -68,3 +68,57 @@ fn transport_manifest_preserves_ksp_dependency_firewall() {
assert!(manifest.contains("[dev-dependencies]"));
assert!(manifest.contains("tokio = { workspace = true, features = [\"net\", \"rt\"] }"));
}
#[test]
fn transport_manifest_runtime_and_dev_dependency_names_are_exact() {
let manifest_path = workspace_root().join("crates/ksp-onchain-transport-lib/Cargo.toml");
let manifest = std::fs::read_to_string(manifest_path).expect("transport manifest must be readable during workspace integration tests");
let dependencies_tail = manifest.split("[dependencies]").nth(1);
assert!(dependencies_tail.is_some(), "transport dependencies section must exist");
let dependencies_tail = match dependencies_tail {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let dependencies = match dependencies_tail.split("[dev-dependencies]").next() {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let dependency_names = manifest_dependency_names(dependencies);
assert_eq!(
dependency_names,
std::vec!["futures-util", "ksp-core-lib", "ksp-logging-lib", "reqwest", "serde", "serde_json", "tokio", "tokio-tungstenite"]
);
let dev_dependencies_tail = manifest.split("[dev-dependencies]").nth(1);
assert!(dev_dependencies_tail.is_some(), "transport dev-dependencies section must exist");
let dev_dependencies_tail = match dev_dependencies_tail {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let dev_dependencies = match dev_dependencies_tail.split("[lints]").next() {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert_eq!(manifest_dependency_names(dev_dependencies), std::vec!["tokio"]);
}
fn manifest_dependency_names(section: &str) -> std::vec::Vec<&str> {
let mut names = std::vec::Vec::new();
for line in section.lines() {
let content = match line.split('#').next() {
std::option::Option::Some(value) => value.trim(),
std::option::Option::None => continue,
};
if content.is_empty() {
continue;
}
let name = match content.split('=').next() {
std::option::Option::Some(value) => value.trim().trim_end_matches(".workspace"),
std::option::Option::None => continue,
};
if !name.is_empty() {
names.push(name);
}
}
names.sort_unstable();
return names;
}

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-onchain-transport-lib/README.md -->
<!-- version: 17 -->
<!-- version: 18 -->
# `ksp-onchain-transport-lib`
@@ -234,22 +234,30 @@ La configuration Logging de référence conserve un fichier dédié Transport à
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.
Deux smokes Devnet opt-in sont séparés par responsabilité :
Trois smokes Devnet opt-in sont séparés par responsabilité :
```text
Transport pur : settings programmatiques -> HttpTransportPool
-> Accounts/Tokens/Cluster représentatifs
-> trois reads Transactions
-> getBlockHeight
-> getInflationRate/getStakeMinimumDelegation
Transport HTTP pur : settings programmatiques -> HttpTransportPool
-> Accounts/Tokens/Cluster représentatifs
-> trois reads Transactions
-> getBlockHeight
-> getInflationRate/getStakeMinimumDelegation
Transport WebSocket pur : settings programmatiques -> WsSession
-> slotSubscribe
-> une slotNotification sous timeout
-> slotUnsubscribe
-> close
Composition historique : Config -> std.transport/devnet_public -> HttpTransportPool
-> getHealth/getGenesisHash/getVersion/getBalance
```
Le smoke Transport utilise pour sa branche Token la forme Devnet documentée `getTokenAccountsByOwner(owner, { programId }, { commitment: finalized, encoding: jsonParsed })`. L'owner est une Pubkey ordinaire de l'exemple officiel ; aucune présence de token account n'est exigée, donc une liste vide reste valide.
Le smoke HTTP Transport utilise pour sa branche Token la forme Devnet documentée `getTokenAccountsByOwner(owner, { programId }, { commitment: finalized, encoding: jsonParsed })`. L'owner est une Pubkey ordinaire de l'exemple officiel ; aucune présence de token account n'est exigée, donc une liste vide reste valide.
Les deux sont `ignored` par défaut. Le smoke Transport appartient durablement à cette crate ; le smoke cross-crates hébergé dans Config reste transitoire jusqu'à l'existence d'une surface KSP d'intégration/orchestration appropriée.
Le smoke WebSocket Transport cible uniquement la famille stable `slotSubscribe` sur l'endpoint public Devnet `wss://api.devnet.solana.com`. Il borne connexion, attente de notification, unsubscribe et fermeture ; il ne transforme aucune famille unstable en gate live.
Les trois tests sont `ignored` par défaut. Les deux smokes Transport appartiennent durablement à cette crate ; le smoke cross-crates hébergé dans Config reste transitoire jusqu'à l'existence d'une surface KSP d'intégration/orchestration appropriée. Un rate-limit, refus externe ou incident Devnet n'est pas assimilé automatiquement à une régression locale.
## Documentation

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-onchain-transport-lib/USAGE.md -->
<!-- version: 17 -->
<!-- version: 18 -->
# Utilisation de `ksp-onchain-transport-lib`
@@ -334,7 +334,7 @@ La configuration standard route les événements `info` de Transport vers un fic
## 10. Smokes Devnet opt-in
Le smoke **Transport pur** construit ses settings programmatiquement et exerce un sous-ensemble représentatif d'Accounts/Tokens/Cluster, trois reads Transactions, puis des reads Blocks/Economics de la release stable `0.2.4` :
Le smoke **Transport HTTP pur** construit ses settings programmatiquement et exerce un sous-ensemble représentatif d'Accounts/Tokens/Cluster, trois reads Transactions, puis des reads Blocks/Economics de la release stable `0.2.4` :
```bash
cargo test -p ksp-onchain-transport-lib --test transport_devnet_smoke -- --ignored --nocapture
@@ -342,6 +342,14 @@ cargo test -p ksp-onchain-transport-lib --test transport_devnet_smoke -- --ignor
Il appelle `getAccountInfo`, `getTokenAccountsByOwner`, `getEpochInfo`, `getVoteAccounts`, puis `getLatestBlockhash`, `isBlockhashValid`, `getTransactionCount`, `getBlockHeight`, `getInflationRate` et `getStakeMinimumDelegation`. La branche Token suit la forme Devnet documentée : owner Pubkey ordinaire de l'exemple officiel, selector `programId` avec l'ID canonique du programme SPL Token, puis config explicite `commitment: finalized` + `encoding: jsonParsed`. Une réponse vide reste acceptable. La branche Transaction reste read-only : elle ne déclenche ni airdrop ni soumission de transaction et ne remplace pas les fixtures déterministes couvrant les 11 wrappers.
Le smoke **Transport WebSocket pur** utilise l'endpoint public Devnet standard avec des settings programmatiques, ouvre une session physique, crée une subscription stable `slotSubscribe`, attend une notification bornée, vérifie une valeur de slot non nulle, annule la subscription avec son handle puis ferme explicitement la session :
```bash
cargo test -p ksp-onchain-transport-lib --test websocket_devnet_smoke -- --ignored --nocapture
```
Il n'utilise ni `blockSubscribe`, ni `slotsUpdatesSubscribe`, ni `voteSubscribe` : ces familles restent unstable et leur disponibilité dépend des capabilities du validator. Le smoke live n'est donc pas un gate de disponibilité de ces extensions.
Le smoke historique de **composition Config -> Transport** reste également disponible :
```bash
@@ -350,4 +358,14 @@ cargo test -p ksp-config-lib --test transport_devnet_smoke -- --ignored --nocapt
Il valide le profil committé `devnet_public` et les quatre canaris foundation. Il reste transitoirement hébergé dans Config : les futurs smokes cross-crates ne doivent pas faire de Config leur destination générale et devront migrer vers une surface d'intégration/orchestration dédiée lorsqu'elle existera.
Les endpoints publics Solana sont rate-limités et non destinés à la production. Un échec réseau externe n'est pas assimilé automatiquement à une régression locale ; les fixtures HTTP locales restent les gates reproductibles.
Les endpoints publics Solana sont rate-limités et non destinés à la production. Un échec réseau externe n'est pas assimilé automatiquement à une régression locale ; les fixtures HTTP et WebSocket locales restent les gates reproductibles.
Pour l'audit final de dépendances de `0.2.7`, inspecter également le graphe effectif après résolution Cargo :
```bash
cargo tree -p ksp-onchain-transport-lib
cargo tree -p ksp-onchain-transport-lib --duplicates
cargo tree --duplicates
```
Le premier graphe doit conserver la frontière `Transport -> Core + Logging + crates techniques`; il ne doit introduire aucune dépendance Config, Wallet, Store, Program ou `tracing` directe. Les sorties `--duplicates` sont un diagnostic de résolution transitive : une duplication n'est pas supprimée aveuglément si elle est imposée par des dépendances upstream incompatibles.

View File

@@ -0,0 +1,57 @@
// file: crates/ksp-onchain-transport-lib/tests/websocket_devnet_smoke.rs
// version: 1
//! Opt-in live Devnet smoke for one stable standard Solana WebSocket subscription.
fn devnet_websocket_endpoint() -> ksp_core_lib::Result<ksp_onchain_transport_lib::WsEndpointSettings> {
let url_result = ksp_onchain_transport_lib::WsEndpointUrl::parse("wss://api.devnet.solana.com");
let url = match url_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let session = ksp_onchain_transport_lib::WsSessionSettings::new(
std::time::Duration::from_secs(10),
std::time::Duration::from_secs(5),
ksp_onchain_transport_lib::WsReconnectSettings::new(0, std::time::Duration::from_millis(250), std::time::Duration::from_secs(2)),
ksp_onchain_transport_lib::WsResubscribePolicy::ActiveSubscriptions,
16,
16,
8,
8,
4 * 1024 * 1024,
4 * 1024 * 1024,
256 * 1024,
);
return std::result::Result::Ok(ksp_onchain_transport_lib::WsEndpointSettings::new(
"solana_devnet_public_ws",
true,
ksp_onchain_transport_lib::WsProviderName::new("solana-public"),
ksp_onchain_transport_lib::WsClusterName::new("devnet"),
ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard,
url,
session,
));
}
#[tokio::test(flavor = "current_thread")]
#[ignore = "opt-in live Solana Devnet WebSocket smoke; performs an external network connection"]
async fn programmatic_devnet_websocket_reaches_slot_notification_then_unsubscribes_and_closes() {
let endpoint = devnet_websocket_endpoint().expect("programmatic Devnet WebSocket settings must construct an endpoint");
let session = ksp_onchain_transport_lib::WsSession::connect(endpoint).await.expect("Devnet WebSocket handshake must succeed");
assert_eq!(session.snapshot().state(), ksp_onchain_transport_lib::WsSessionState::Active);
let mut subscription = session.slot_subscribe().await.expect("Devnet slotSubscribe must succeed");
assert_eq!(subscription.kind(), ksp_onchain_transport_lib::WsSubscriptionKind::Slot);
assert_eq!(subscription.state(), ksp_onchain_transport_lib::WsSubscriptionState::Active);
let notification_wait = tokio::time::timeout(std::time::Duration::from_secs(20), subscription.recv()).await;
let notification = match notification_wait {
std::result::Result::Ok(std::option::Option::Some(std::result::Result::Ok(value))) => value,
std::result::Result::Ok(std::option::Option::Some(std::result::Result::Err(error))) => panic!("Devnet slot notification decoding failed: {error}"),
std::result::Result::Ok(std::option::Option::None) => panic!("Devnet slot subscription closed before one notification arrived"),
std::result::Result::Err(_) => panic!("Devnet slot notification did not arrive within the smoke timeout"),
};
assert!(notification.slot() > 0);
let unsubscribed = subscription.unsubscribe().await.expect("Devnet slotUnsubscribe must succeed");
assert!(unsubscribed);
session.close().await.expect("Devnet WebSocket session must close cleanly");
assert_eq!(session.snapshot().state(), ksp_onchain_transport_lib::WsSessionState::Closed);
}