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,12 +1,12 @@
# file: Cargo.toml
# version: 213
# version: 214
[workspace]
resolver = "3"
members = ["crates/ksp-app-config-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"]
[workspace.package]
version = "0.2.7-pre.12.fix.1"
version = "0.2.7-pre.13"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

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);
}

99
deltas/0.2.7/pre.013.md Normal file
View File

@@ -0,0 +1,99 @@
<!-- file: deltas/0.2.7/pre.013.md -->
<!-- version: 1 -->
# Delta `0.2.7-pre.013` — smoke WebSocket live et audit final des dépendances
## Base
Base directe validée par l'opérateur :
```text
0.2.7-pre.012-fix.001
Cargo 0.2.7-pre.12.fix.1
```
Les gates `fmt`, audit Python, `check`, `clippy`, les 309 tests unitaires Transport, 36 tests d'API publique, 24 tests de release completeness, 109 tests unitaires Config et le workspace complet sont verts.
## Signal technique
```text
livraison = 0.2.7-pre.013
workspace.package.version = 0.2.7-pre.13
commit = v0.2.7-pre.013
tag = aucun
```
## Smoke WebSocket Devnet opt-in
Un nouveau smoke Transport pur `websocket_devnet_smoke.rs` couvre le chemin live stable minimal :
```text
settings programmatiques
-> wss://api.devnet.solana.com
-> WsSession::connect
-> slotSubscribe
-> une slotNotification sous timeout 20 s
-> slotUnsubscribe via WsSubscription
-> WsSession::close
```
Le test est `#[ignore]` par défaut. Il ne lit ni Config ni environnement et n'utilise aucune famille unstable. Les fixtures WebSocket locales restent les gates déterministes du lifecycle, des DTOs et des familles `block`, `slotsUpdates` et `vote`.
Un échec lié au réseau, au rate-limit ou à l'indisponibilité Devnet ne constitue pas automatiquement une régression KSP.
## Audit de dépendances
Le canari workspace de `ksp-core-lib` verrouille désormais les noms directs de dépendances de Transport :
```text
runtime : futures-util, ksp-core-lib, ksp-logging-lib, reqwest, serde, serde_json, tokio, tokio-tungstenite
dev : tokio
```
Il complète le firewall existant contre Config, Store, Program et `tracing` direct.
L'inspection du graphe résolu doit être faite côté opérateur :
```bash
cargo tree -p ksp-onchain-transport-lib
cargo tree -p ksp-onchain-transport-lib --duplicates
cargo tree --duplicates
```
Les doublons transitifs sont à analyser, pas à supprimer mécaniquement lorsqu'ils résultent de contraintes upstream différentes.
## Documentation
`README.md` et `USAGE.md` distinguent désormais les smokes HTTP Transport, WebSocket Transport et le smoke historique Config -> Transport. Le plan et la matrice enregistrent le checkpoint `pre.013`.
`ROADMAP.md` et `CHANGELOG.md` restent inchangés pendant cette prerelease.
## Comptages attendus
```text
Transport unit tests = 309
Transport public API tests = 36
Transport release completeness = 24
Transport WebSocket live smoke = 1 ignored par défaut
Config unit tests = 109
Core workspace dependency tests = 3
```
## Validation opérateur
```bash
cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-onchain-transport-lib
cargo test --workspace
cargo tree -p ksp-onchain-transport-lib
cargo tree -p ksp-onchain-transport-lib --duplicates
cargo tree --duplicates
cargo test -p ksp-onchain-transport-lib --test websocket_devnet_smoke -- --ignored --nocapture
```
Le smoke live peut être qualifié séparément si Devnet est indisponible ; les autres gates restent déterministes.
Le sandbox de préparation ne possède pas Cargo/rustc ; les gates Cargo et le smoke live restent à exécuter côté opérateur.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/014-V0_2_7_ONCHAIN_WEBSOCKET_PLAN.md -->
<!-- version: 14 -->
<!-- version: 15 -->
# Plan `0.2.7` — WebSocket Solana standard
@@ -947,6 +947,34 @@ Les noms JSON-RPC exacts restent verrouillés par le canari interne `websocket_s
La preuve Config -> session est volontairement déterministe : le future de connexion est construit à partir d'un `WsEndpointSettings` issu du document V2 committed sans polling. Aucun réseau réel n'est donc nécessaire ; le smoke live reste dans `pre.013`.
## 21.2 Checkpoint smoke live et dépendances `pre.013`
`pre.013` ne modifie pas le runtime WebSocket. Il ajoute le smoke live Transport pur retenu par le plan et ferme la documentation d'utilisation avant la validation workspace finale.
Le smoke est volontairement stable et minimal :
```text
wss://api.devnet.solana.com
-> WsSession::connect
-> slotSubscribe
-> une slotNotification sous timeout
-> WsSubscription::unsubscribe
-> WsSession::close
```
Il est `#[ignore]` par défaut, utilise des settings programmatiques et n'accède ni à Config ni à l'environnement. Les trois familles unstable ne participent pas au smoke : leur activation dépend des capabilities du validator et un refus externe ne doit pas devenir une régression locale.
Le gate de dépendances est renforcé de deux façons :
```text
source canary -> noms exacts des dépendances runtime/dev de ksp-onchain-transport-lib
opérateur -> cargo tree Transport + cargo tree --duplicates local et workspace
```
Le canari source interdit qu'une dépendance nouvelle se glisse silencieusement dans Transport. `cargo tree --duplicates` reste un diagnostic de résolution transitive : le gate demande inspection et justification, pas la suppression mécanique de doublons imposés par upstream.
README/USAGE documentent désormais les smokes HTTP/WS séparés, les limites du smoke live et les commandes d'audit du graphe Cargo.
## 22. Forecast recalibré
L'inventaire officiel n'impose que 9 familles de subscriptions, mais le lifecycle concurrent est plus coûteux que le forecast initial. Le gate reste **positif sans split de release**, à condition de granulariser les tranches au lieu de compresser le moteur et les wrappers.
@@ -964,7 +992,7 @@ pre.009 DONE — wrappers stable lot A : account + program + logs, DTOs/options
pre.010 DONE — wrappers stable lot B : signature + slot + root, terminaison signature/KSP-TRANSPORT-007
pre.011 DONE — unstable : block + slotsUpdates + vote, warnings + fallbacks wire/KSP-TRANSPORT-007
pre.012 DONE — compliance 18/18 + canaries public API + composition Config + régressions HTTP
pre.013 smoke live opt-in + README/USAGE + cargo tree/duplicates + dependency audit final
pre.013 DONE — smoke live opt-in + README/USAGE + source dependency canary + cargo tree/duplicates opérateur
pre.014 validation workspace finale + docs/compliance + prompt 0.2.8
rel.001 publication stable stricte
```

View File

@@ -1,9 +1,9 @@
<!-- file: docs/validation/010-V0_2_7_ONCHAIN_WEBSOCKET.md -->
<!-- version: 14 -->
<!-- version: 15 -->
# Validation `0.2.7` — WebSocket Solana standard
> **Statut : matrice active, `0.2.7-pre.012`.** Les 9 familles standard et leurs 9 opérations unsubscribe sont matérialisées. `pre.012` consolide la compliance 18/18, la surface publique, la composition Config V2 -> Transport et la non-régression HTTP avant le smoke/audit de dépendances final.
> **Statut : matrice active, `0.2.7-pre.013`.** La compliance 18/18 est acquise. `pre.013` ajoute le smoke WebSocket Devnet opt-in, synchronise README/USAGE et renforce le gate de dépendances avant la validation workspace finale.
## 1. Baseline normative
@@ -206,33 +206,35 @@ Les votes observés sont gossip/pre-consensus ; aucune garantie d'entrée dans l
## 7. Threat/security compliance initiale
| Invariant | Preuve attendue | Statut |
|---------------------------------------------------|----------------------------------------------|------------------------------------------|
| URL/credentials absents de `Debug` | unit tests URL wrapper | **Done `pre.002`** |
| URL/credentials absents des erreurs | validation URL + connection errors safe | **Done through `pre.004`** |
| URL/credentials absents des logs | actor logs only safe endpoint metadata | Partial `pre.004`, capture finale future |
| snapshots sans URL/raw payload | unit shape + public contract | **Done `pre.002`** |
| frame/message finis | settings bornés + `WebSocketConfig` raccordé | **Done `pre.005`** |
| JSON borné indirectement par message | oversized + malformed fixture | **Done `pre.005`** |
| queues notifications bornées | queue typed bornée + overflow isolé | **Done `pre.008`** |
| pending RPC borné + timeout | map actor bornée + timeout request | **Done `pre.004`** |
| reconnect loop bornée | repeated disconnect fixture | Done `pre.007` |
| unsubscribe pendant reconnect ne resubscribe pas | race fixture | Done `pre.007` |
| signature terminale ne resubscribe pas | terminal fixture | Planned |
| shutdown ne bloque pas | peer hostile/no close ack fixture | **Done `pre.005`** |
| no Store/Program/Wallet/Config dep dans Transport | cargo tree + source canary | Planned |
| no direct `tracing` dans Transport | workspace audit + source audit | **Done `pre.002`** |
| Invariant | Preuve attendue | Statut |
|---------------------------------------------------|----------------------------------------------|--------------------------------------------------------|
| URL/credentials absents de `Debug` | unit tests URL wrapper | **Done `pre.002`** |
| URL/credentials absents des erreurs | validation URL + connection errors safe | **Done through `pre.004`** |
| URL/credentials absents des logs | actor logs only safe endpoint metadata | **Done through `pre.011`, source audit `pre.013`** |
| snapshots sans URL/raw payload | unit shape + public contract | **Done `pre.002`** |
| frame/message finis | settings bornés + `WebSocketConfig` raccordé | **Done `pre.005`** |
| JSON borné indirectement par message | oversized + malformed fixture | **Done `pre.005`** |
| queues notifications bornées | queue typed bornée + overflow isolé | **Done `pre.008`** |
| pending RPC borné + timeout | map actor bornée + timeout request | **Done `pre.004`** |
| reconnect loop bornée | repeated disconnect fixture | Done `pre.007` |
| unsubscribe pendant reconnect ne resubscribe pas | race fixture | Done `pre.007` |
| signature terminale ne resubscribe pas | terminal fixture | **Done `pre.010`** |
| shutdown ne bloque pas | peer hostile/no close ack fixture | **Done `pre.005`** |
| no Store/Program/Wallet/Config dep dans Transport | cargo tree + source canary | **Source gate `pre.013`, cargo tree opérateur requis** |
| no direct `tracing` dans Transport | workspace audit + source audit | **Done `pre.002`** |
## 8. Dependency compliance initiale
Candidate retenue au gate :
Candidates auditées à l'ouverture de release :
```text
tokio-tungstenite 0.30.0
futures-util 0.3.34
tokio-tungstenite 0.30.x
futures-util 0.3.x
```
Features prévues :
Le workspace conserve des contraintes caret `^0.30` et `^0.3` sans lockfile versionné. La version transitive effectivement résolue n'est donc pas figée dans cette matrice ; `pre.013` exige l'inspection du `cargo tree` opérateur correspondant au checkout validé.
Features retenues :
```text
tokio-tungstenite: default-features=false + connect + rustls-tls-webpki-roots
@@ -642,6 +644,50 @@ Transport release completeness = 24
Config unit tests = 109
```
## 9.11 Checkpoint smoke live et audit de dépendances `pre.013`
Le smoke WebSocket live retenu est un test Transport pur `#[ignore]` qui ne lit ni Config ni environnement :
```text
endpoint programmatique wss://api.devnet.solana.com
WsSession::connect
slotSubscribe stable
attente bornée d'une slotNotification
slot > 0
slotUnsubscribe via handle
close explicite de la session
```
Les familles `block`, `slotsUpdates` et `vote` ne sont pas utilisées dans ce smoke car leur disponibilité dépend de capabilities validator unstable. Les fixtures locales restent les preuves reproductibles de ces familles et du lifecycle.
Le canari workspace de dépendances verrouille désormais les noms directs de Transport :
```text
runtime : futures-util, ksp-core-lib, ksp-logging-lib, reqwest, serde, serde_json, tokio, tokio-tungstenite
dev : tokio
```
Il complète le firewall existant contre Config/Store/Program/tracing direct. L'inspection du graphe résolu reste une preuve opérateur avec :
```bash
cargo tree -p ksp-onchain-transport-lib
cargo tree -p ksp-onchain-transport-lib --duplicates
cargo tree --duplicates
```
Une duplication transitive n'est pas considérée automatiquement comme un défaut : elle doit être comprise et n'est supprimée que si KSP peut la résoudre sans downgrade, pin artificiel ou violation des responsabilités upstream.
Comptages déterministes attendus inchangés côté Transport, plus un smoke live ignoré et un canari workspace supplémentaire dans Core :
```text
Transport unit tests = 309
Transport public API tests = 36
Transport release completeness = 24
Transport WebSocket live smoke = 1 ignored par défaut
Config unit tests = 109
Core workspace dependency tests = 3
```
## 10. Validation du gate `pre.001`
Exécuté dans le sandbox :
@@ -689,9 +735,9 @@ reconnect/resubscribe/backpressure/shutdown gates verts
unstable warnings centralisés
HTTP 52+14 non régressé (**Done `pre.012`**)
Config V1 backward + V2 WS validés (**Done `pre.003`**, compilation opérateur requise)
smoke live opt-in documenté
cargo tree inspecté
smoke live opt-in documenté (**Done `pre.013`**)
cargo tree inspecté (**preuve opérateur `pre.013` requise**)
cargo test --workspace vert
README/USAGE synchronisés
README/USAGE synchronisés (**Done `pre.013`**)
prompt 0.2.8 préparé
```