diff --git a/Cargo.toml b/Cargo.toml index cb2502e..c2414a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ # file: Cargo.toml -# version: 219 +# version: 220 [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.8-pre.2.fix.1" +version = "0.2.8-pre.3" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" diff --git a/crates/ksp-onchain-transport-lib/src/ws_accounts.rs b/crates/ksp-onchain-transport-lib/src/ws_accounts.rs index 9dc0027..5416839 100644 --- a/crates/ksp-onchain-transport-lib/src/ws_accounts.rs +++ b/crates/ksp-onchain-transport-lib/src/ws_accounts.rs @@ -1,5 +1,5 @@ // file: crates/ksp-onchain-transport-lib/src/ws_accounts.rs -// version: 2 +// version: 3 const MAX_PROGRAM_SUBSCRIBE_FILTERS: usize = 4; const MAX_PROGRAM_SUBSCRIBE_RAW_MEMCMP_BYTES: usize = 128; @@ -277,6 +277,46 @@ fn validate_program_subscribe_filters(filters: &[crate::SolanaProgramAccountFilt return std::result::Result::Ok(()); } +impl crate::SolanaStandardWsSession { + /// Subscribes to changes for one Solana account through standard `accountSubscribe`. + pub async fn account_subscribe( + &self, + account: &ksp_core_lib::Pubkey, + config: std::option::Option<&crate::SolanaAccountSubscribeConfig>, + ) -> ksp_core_lib::Result>> { + return self.physical_session().account_subscribe(account, config).await; + } + + /// Subscribes to account changes owned by one Solana program through standard `programSubscribe`. + pub async fn program_subscribe( + &self, + program_id: &ksp_core_lib::Pubkey, + config: std::option::Option<&crate::SolanaProgramSubscribeConfig>, + ) -> ksp_core_lib::Result> { + return self.physical_session().program_subscribe(program_id, config).await; + } +} + +impl crate::HeliusLaserStreamWsSession { + /// Subscribes to account changes through the standard `accountSubscribe` wire supported by Helius LaserStream WebSocket. + pub async fn account_subscribe( + &self, + account: &ksp_core_lib::Pubkey, + config: std::option::Option<&crate::SolanaAccountSubscribeConfig>, + ) -> ksp_core_lib::Result>> { + return self.physical_session().account_subscribe(account, config).await; + } + + /// Subscribes to program-owned account changes through the standard `programSubscribe` wire supported by Helius LaserStream WebSocket. + pub async fn program_subscribe( + &self, + program_id: &ksp_core_lib::Pubkey, + config: std::option::Option<&crate::SolanaProgramSubscribeConfig>, + ) -> ksp_core_lib::Result> { + return self.physical_session().program_subscribe(program_id, config).await; + } +} + #[cfg(test)] #[path = "../unit_tests/ws_accounts.rs"] mod tests; diff --git a/crates/ksp-onchain-transport-lib/src/ws_blocks.rs b/crates/ksp-onchain-transport-lib/src/ws_blocks.rs index 62b73f9..06aaae0 100644 --- a/crates/ksp-onchain-transport-lib/src/ws_blocks.rs +++ b/crates/ksp-onchain-transport-lib/src/ws_blocks.rs @@ -1,5 +1,5 @@ // file: crates/ksp-onchain-transport-lib/src/ws_blocks.rs -// version: 1 +// version: 2 /// Filter accepted by unstable Solana `blockSubscribe`. #[derive(Clone, Debug, Eq, PartialEq)] @@ -210,6 +210,17 @@ struct WireRpcResponse { value: WireBlockNotification, } +impl crate::SolanaStandardWsSession { + /// Subscribes to unstable standard Solana block notifications through `blockSubscribe`. + pub async fn block_subscribe( + &self, + filter: &crate::SolanaBlockSubscribeFilter, + config: std::option::Option<&crate::SolanaBlockSubscribeConfig>, + ) -> ksp_core_lib::Result>> { + return self.physical_session().block_subscribe(filter, config).await; + } +} + #[cfg(test)] #[path = "../unit_tests/ws_blocks.rs"] mod tests; diff --git a/crates/ksp-onchain-transport-lib/src/ws_cluster.rs b/crates/ksp-onchain-transport-lib/src/ws_cluster.rs index 63fb994..f27376b 100644 --- a/crates/ksp-onchain-transport-lib/src/ws_cluster.rs +++ b/crates/ksp-onchain-transport-lib/src/ws_cluster.rs @@ -1,5 +1,5 @@ // file: crates/ksp-onchain-transport-lib/src/ws_cluster.rs -// version: 3 +// version: 4 /// Slot relationship reported by the standard Solana `slotNotification` WebSocket method. #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -401,6 +401,40 @@ fn decode_vote_notification(method: &str, value: serde_json::Value) -> ksp_core_ }); } +impl crate::SolanaStandardWsSession { + /// Subscribes to standard Solana slot-processing notifications through `slotSubscribe`. + pub async fn slot_subscribe(&self) -> ksp_core_lib::Result> { + return self.physical_session().slot_subscribe().await; + } + + /// Subscribes to standard Solana root-slot notifications through `rootSubscribe`. + pub async fn root_subscribe(&self) -> ksp_core_lib::Result> { + return self.physical_session().root_subscribe().await; + } + + /// Subscribes to unstable standard Solana slot-lifecycle notifications through `slotsUpdatesSubscribe`. + pub async fn slots_updates_subscribe(&self) -> ksp_core_lib::Result> { + return self.physical_session().slots_updates_subscribe().await; + } + + /// Subscribes to unstable pre-consensus gossip vote notifications through `voteSubscribe`. + pub async fn vote_subscribe(&self) -> ksp_core_lib::Result> { + return self.physical_session().vote_subscribe().await; + } +} + +impl crate::HeliusLaserStreamWsSession { + /// Subscribes to slot-processing notifications through the standard `slotSubscribe` wire supported by Helius LaserStream WebSocket. + pub async fn slot_subscribe(&self) -> ksp_core_lib::Result> { + return self.physical_session().slot_subscribe().await; + } + + /// Subscribes to root-slot notifications through the standard `rootSubscribe` wire supported by Helius LaserStream WebSocket. + pub async fn root_subscribe(&self) -> ksp_core_lib::Result> { + return self.physical_session().root_subscribe().await; + } +} + #[cfg(test)] #[path = "../unit_tests/ws_cluster.rs"] mod tests; diff --git a/crates/ksp-onchain-transport-lib/src/ws_protocol_session.rs b/crates/ksp-onchain-transport-lib/src/ws_protocol_session.rs index e4b3b53..84df7de 100644 --- a/crates/ksp-onchain-transport-lib/src/ws_protocol_session.rs +++ b/crates/ksp-onchain-transport-lib/src/ws_protocol_session.rs @@ -1,10 +1,11 @@ // file: crates/ksp-onchain-transport-lib/src/ws_protocol_session.rs -// version: 1 +// version: 2 /// Typed facade for one standard Solana WebSocket physical session. /// /// The facade delegates to the same [`crate::WsSession`] actor used by the compatibility API. It owns no socket, registry, reconnect loop or queue of its -/// own and therefore does not duplicate the physical WebSocket runtime. +/// own and therefore does not duplicate the physical WebSocket runtime. Subscription wrappers are implemented beside their wire owners in the +/// `ws_accounts`, `ws_blocks`, `ws_cluster` and `ws_transactions` modules. #[derive(Clone)] pub struct SolanaStandardWsSession { inner: crate::WsSession, @@ -43,69 +44,9 @@ impl SolanaStandardWsSession { return self.inner.close().await; } - /// Subscribes to changes for one Solana account through standard `accountSubscribe`. - pub async fn account_subscribe( - &self, - account: &ksp_core_lib::Pubkey, - config: std::option::Option<&crate::SolanaAccountSubscribeConfig>, - ) -> ksp_core_lib::Result>> { - return self.inner.account_subscribe(account, config).await; - } - - /// Subscribes to unstable standard Solana block notifications through `blockSubscribe`. - pub async fn block_subscribe( - &self, - filter: &crate::SolanaBlockSubscribeFilter, - config: std::option::Option<&crate::SolanaBlockSubscribeConfig>, - ) -> ksp_core_lib::Result>> { - return self.inner.block_subscribe(filter, config).await; - } - - /// Subscribes to Solana transaction logs through standard `logsSubscribe`. - pub async fn logs_subscribe( - &self, - filter: &crate::SolanaLogsSubscribeFilter, - config: std::option::Option<&crate::SolanaCommitmentConfig>, - ) -> ksp_core_lib::Result>> { - return self.inner.logs_subscribe(filter, config).await; - } - - /// Subscribes to account changes owned by one Solana program through standard `programSubscribe`. - pub async fn program_subscribe( - &self, - program_id: &ksp_core_lib::Pubkey, - config: std::option::Option<&crate::SolanaProgramSubscribeConfig>, - ) -> ksp_core_lib::Result> { - return self.inner.program_subscribe(program_id, config).await; - } - - /// Subscribes to standard Solana root-slot notifications through `rootSubscribe`. - pub async fn root_subscribe(&self) -> ksp_core_lib::Result> { - return self.inner.root_subscribe().await; - } - - /// Subscribes to one Solana transaction signature through standard `signatureSubscribe`. - pub async fn signature_subscribe( - &self, - signature: &str, - config: std::option::Option<&crate::SolanaSignatureSubscribeConfig>, - ) -> ksp_core_lib::Result>> { - return self.inner.signature_subscribe(signature, config).await; - } - - /// Subscribes to standard Solana slot-processing notifications through `slotSubscribe`. - pub async fn slot_subscribe(&self) -> ksp_core_lib::Result> { - return self.inner.slot_subscribe().await; - } - - /// Subscribes to unstable standard Solana slot-lifecycle notifications through `slotsUpdatesSubscribe`. - pub async fn slots_updates_subscribe(&self) -> ksp_core_lib::Result> { - return self.inner.slots_updates_subscribe().await; - } - - /// Subscribes to unstable pre-consensus gossip vote notifications through `voteSubscribe`. - pub async fn vote_subscribe(&self) -> ksp_core_lib::Result> { - return self.inner.vote_subscribe().await; + /// Returns the crate-private shared physical session used by domain-specific facade wrappers. + pub(crate) fn physical_session(&self) -> &crate::WsSession { + return &self.inner; } } @@ -117,17 +58,29 @@ impl std::fmt::Debug for SolanaStandardWsSession { /// Typed facade for one Helius LaserStream WebSocket physical session. /// -/// `0.2.8-pre.002` intentionally exposes only physical-session lifecycle through this facade. The six Helius-supported standard subscription wrappers are -/// added in the next tranche after exact delegation and API-absence canaries are established. No public inner handle is exposed, so callers cannot bypass -/// the provider-specific surface by recovering a generic [`crate::WsSession`]. +/// The facade exposes only the six standard Solana subscription families that Helius documents as supported. Provider-specific transaction subscription +/// support is intentionally deferred to a later tranche. No public inner handle is exposed, so callers cannot bypass the provider-specific surface by +/// recovering a generic [`crate::WsSession`]. /// /// ```compile_fail -/// async fn unsupported(session: &ksp_onchain_transport_lib::HeliusLaserStreamWsSession) { +/// async fn unsupported_block(session: &ksp_onchain_transport_lib::HeliusLaserStreamWsSession) { /// let _ = session.block_subscribe().await; /// } /// ``` /// /// ```compile_fail +/// async fn unsupported_slots_updates(session: &ksp_onchain_transport_lib::HeliusLaserStreamWsSession) { +/// let _ = session.slots_updates_subscribe().await; +/// } +/// ``` +/// +/// ```compile_fail +/// async fn unsupported_vote(session: &ksp_onchain_transport_lib::HeliusLaserStreamWsSession) { +/// let _ = session.vote_subscribe().await; +/// } +/// ``` +/// +/// ```compile_fail /// fn no_escape_hatch(session: ksp_onchain_transport_lib::HeliusLaserStreamWsSession) { /// let _ = session.into_inner(); /// } @@ -169,6 +122,11 @@ impl HeliusLaserStreamWsSession { pub async fn close(&self) -> ksp_core_lib::Result<()> { return self.inner.close().await; } + + /// Returns the crate-private shared physical session used by domain-specific facade wrappers. + pub(crate) fn physical_session(&self) -> &crate::WsSession { + return &self.inner; + } } impl std::fmt::Debug for HeliusLaserStreamWsSession { @@ -177,6 +135,10 @@ impl std::fmt::Debug for HeliusLaserStreamWsSession { } } +#[cfg(test)] +#[path = "../unit_tests/ws_helius_standard.rs"] +mod helius_standard_tests; + #[cfg(test)] #[path = "../unit_tests/ws_protocol_session.rs"] mod tests; diff --git a/crates/ksp-onchain-transport-lib/src/ws_transactions.rs b/crates/ksp-onchain-transport-lib/src/ws_transactions.rs index d7d4fb3..1d7e415 100644 --- a/crates/ksp-onchain-transport-lib/src/ws_transactions.rs +++ b/crates/ksp-onchain-transport-lib/src/ws_transactions.rs @@ -1,5 +1,5 @@ // file: crates/ksp-onchain-transport-lib/src/ws_transactions.rs -// version: 3 +// version: 4 /// Optional configuration accepted by standard Solana `signatureSubscribe`. #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] @@ -246,6 +246,46 @@ fn decode_logs_notification(method: &str, value: serde_json::Value) -> ksp_core_ return std::result::Result::Ok(crate::SolanaRpcResponse::new(context, notification)); } +impl crate::SolanaStandardWsSession { + /// Subscribes to one Solana transaction signature through standard `signatureSubscribe`. + pub async fn signature_subscribe( + &self, + signature: &str, + config: std::option::Option<&crate::SolanaSignatureSubscribeConfig>, + ) -> ksp_core_lib::Result>> { + return self.physical_session().signature_subscribe(signature, config).await; + } + + /// Subscribes to Solana transaction logs through standard `logsSubscribe`. + pub async fn logs_subscribe( + &self, + filter: &crate::SolanaLogsSubscribeFilter, + config: std::option::Option<&crate::SolanaCommitmentConfig>, + ) -> ksp_core_lib::Result>> { + return self.physical_session().logs_subscribe(filter, config).await; + } +} + +impl crate::HeliusLaserStreamWsSession { + /// Subscribes to one transaction signature through the standard `signatureSubscribe` wire supported by Helius LaserStream WebSocket. + pub async fn signature_subscribe( + &self, + signature: &str, + config: std::option::Option<&crate::SolanaSignatureSubscribeConfig>, + ) -> ksp_core_lib::Result>> { + return self.physical_session().signature_subscribe(signature, config).await; + } + + /// Subscribes to transaction logs through the standard `logsSubscribe` wire supported by Helius LaserStream WebSocket. + pub async fn logs_subscribe( + &self, + filter: &crate::SolanaLogsSubscribeFilter, + config: std::option::Option<&crate::SolanaCommitmentConfig>, + ) -> ksp_core_lib::Result>> { + return self.physical_session().logs_subscribe(filter, config).await; + } +} + #[cfg(test)] #[path = "../unit_tests/ws_transactions.rs"] mod tests; diff --git a/crates/ksp-onchain-transport-lib/tests/public_api.rs b/crates/ksp-onchain-transport-lib/tests/public_api.rs index 5b1128a..337e629 100644 --- a/crates/ksp-onchain-transport-lib/tests/public_api.rs +++ b/crates/ksp-onchain-transport-lib/tests/public_api.rs @@ -1,5 +1,5 @@ // file: crates/ksp-onchain-transport-lib/tests/public_api.rs -// version: 34 +// version: 35 //! Integration tests for the public `ksp-onchain-transport-lib` consumer contract. @@ -698,3 +698,18 @@ fn public_v0_2_8_pre_002_protocol_facades_are_available_without_replacing_the_st let _helius_close = ksp_onchain_transport_lib::HeliusLaserStreamWsSession::close; let _helius_snapshot = ksp_onchain_transport_lib::HeliusLaserStreamWsSession::snapshot; } + +#[test] +fn public_v0_2_8_pre_003_helius_standard_surface_reuses_shared_typed_contracts() { + let _account = ksp_onchain_transport_lib::HeliusLaserStreamWsSession::account_subscribe; + let _program = ksp_onchain_transport_lib::HeliusLaserStreamWsSession::program_subscribe; + let _logs = ksp_onchain_transport_lib::HeliusLaserStreamWsSession::logs_subscribe; + let _signature = ksp_onchain_transport_lib::HeliusLaserStreamWsSession::signature_subscribe; + let _slot = ksp_onchain_transport_lib::HeliusLaserStreamWsSession::slot_subscribe; + let _root = ksp_onchain_transport_lib::HeliusLaserStreamWsSession::root_subscribe; + let _shared_account_config = std::any::type_name::(); + let _shared_program_config = std::any::type_name::(); + let _shared_logs_filter = std::any::type_name::(); + let _shared_signature_config = std::any::type_name::(); + let _shared_slot_notification = std::any::type_name::(); +} diff --git a/crates/ksp-onchain-transport-lib/tests/release_completeness.rs b/crates/ksp-onchain-transport-lib/tests/release_completeness.rs index 6911e3e..d5f0499 100644 --- a/crates/ksp-onchain-transport-lib/tests/release_completeness.rs +++ b/crates/ksp-onchain-transport-lib/tests/release_completeness.rs @@ -1,5 +1,5 @@ // file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs -// version: 25 +// version: 26 //! Release-level completeness canaries for staged HTTP and WebSocket Transport coverage. @@ -795,3 +795,19 @@ fn release_v0_2_8_pre_002_protocol_facades_preserve_the_standard_partition() { std::option::Option::Some("HeliusLaserStreamWsSession") ); } + +#[test] +fn release_v0_2_8_pre_003_helius_surface_is_exactly_six_standard_families_before_transaction_extension() { + let _account = ksp_onchain_transport_lib::HeliusLaserStreamWsSession::account_subscribe; + let _program = ksp_onchain_transport_lib::HeliusLaserStreamWsSession::program_subscribe; + let _logs = ksp_onchain_transport_lib::HeliusLaserStreamWsSession::logs_subscribe; + let _signature = ksp_onchain_transport_lib::HeliusLaserStreamWsSession::signature_subscribe; + let _slot = ksp_onchain_transport_lib::HeliusLaserStreamWsSession::slot_subscribe; + let _root = ksp_onchain_transport_lib::HeliusLaserStreamWsSession::root_subscribe; + let source = include_str!("../src/ws_protocol_session.rs"); + assert!(source.contains("unsupported_block")); + assert!(source.contains("unsupported_slots_updates")); + assert!(source.contains("unsupported_vote")); + assert!(!source.contains("transaction_subscribe")); + assert_eq!(ksp_onchain_transport_lib::WsProtocolKind::HeliusLaserStream.as_str(), "helius_laserstream"); +} diff --git a/crates/ksp-onchain-transport-lib/unit_tests/ws_helius_standard.rs b/crates/ksp-onchain-transport-lib/unit_tests/ws_helius_standard.rs new file mode 100644 index 0000000..520095d --- /dev/null +++ b/crates/ksp-onchain-transport-lib/unit_tests/ws_helius_standard.rs @@ -0,0 +1,139 @@ +// file: crates/ksp-onchain-transport-lib/unit_tests/ws_helius_standard.rs +// version: 1 + +use futures_util::SinkExt; // rust-rules: trait-import +use futures_util::StreamExt; // rust-rules: trait-import + +fn helius_endpoint(url: &str) -> crate::WsEndpointSettings { + return crate::WsEndpointSettings::new( + "local_helius_standard_fixture", + true, + crate::WsProviderName::new("helius"), + crate::WsClusterName::new("local"), + crate::WsProtocolKind::HeliusLaserStream, + crate::WsEndpointUrl::parse(url).expect("local Helius WebSocket URL must parse"), + crate::WsSessionSettings::default(), + ); +} + +async fn bind_local_listener() -> (tokio::net::TcpListener, std::string::String) { + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.expect("local listener must bind"); + let address = listener.local_addr().expect("local listener must expose address"); + return (listener, format!("ws://{address}")); +} + +async fn read_request(websocket: &mut tokio_tungstenite::WebSocketStream) -> serde_json::Value { + let message = websocket.next().await.expect("request message must exist").expect("request message must decode"); + let text = message.to_text().expect("request must be text"); + return serde_json::from_str(text).expect("request must contain JSON"); +} + +async fn send_result(websocket: &mut tokio_tungstenite::WebSocketStream, request: &serde_json::Value, result: serde_json::Value) { + let id = request.get("id").and_then(serde_json::Value::as_u64).expect("request id must be numeric"); + let response = serde_json::json!({"jsonrpc":"2.0","id":id,"result":result}); + websocket.send(tokio_tungstenite::tungstenite::Message::Text(response.to_string().into())).await.expect("local response must send"); +} + +async fn expect_pair( + websocket: &mut tokio_tungstenite::WebSocketStream, + subscribe_method: &str, + expected_params: serde_json::Value, + unsubscribe_method: &str, + remote_id: u64, +) { + let subscribe = read_request(websocket).await; + assert_eq!(subscribe["method"], serde_json::Value::String(subscribe_method.to_owned())); + assert_eq!(subscribe["params"], expected_params); + send_result(websocket, &subscribe, serde_json::json!(remote_id)).await; + let unsubscribe = read_request(websocket).await; + assert_eq!(unsubscribe["method"], serde_json::Value::String(unsubscribe_method.to_owned())); + assert_eq!(unsubscribe["params"], serde_json::json!([remote_id])); + send_result(websocket, &unsubscribe, serde_json::json!(true)).await; +} + +async fn wait_for_close_frame(websocket: &mut tokio_tungstenite::WebSocketStream) { + loop { + let message = websocket.next().await; + match message { + std::option::Option::Some(std::result::Result::Ok(tokio_tungstenite::tungstenite::Message::Close(_))) => return, + std::option::Option::Some(std::result::Result::Ok(_)) => {}, + std::option::Option::Some(std::result::Result::Err(_)) | std::option::Option::None => return, + } + } +} + +#[tokio::test(flavor = "current_thread")] +async fn helius_facade_reuses_exact_standard_wire_for_all_six_supported_families() { + let (listener, url) = bind_local_listener().await; + let server = tokio::spawn(async move { + let (stream, _) = listener.accept().await.expect("local server must accept client"); + let mut websocket = tokio_tungstenite::accept_async(stream).await.expect("local WebSocket handshake must succeed"); + expect_pair( + &mut websocket, + "accountSubscribe", + serde_json::json!(["11111111111111111111111111111111", {"encoding":"base64","commitment":"confirmed"}]), + "accountUnsubscribe", + 101, + ) + .await; + expect_pair( + &mut websocket, + "programSubscribe", + serde_json::json!(["11111111111111111111111111111111", {"encoding":"jsonParsed","filters":[{"dataSize":80}],"withContext":true}]), + "programUnsubscribe", + 102, + ) + .await; + expect_pair(&mut websocket, "logsSubscribe", serde_json::json!(["all", {"commitment":"finalized"}]), "logsUnsubscribe", 103).await; + expect_pair( + &mut websocket, + "signatureSubscribe", + serde_json::json!(["fixture-signature", {"commitment":"confirmed","enableReceivedNotification":true}]), + "signatureUnsubscribe", + 104, + ) + .await; + expect_pair(&mut websocket, "slotSubscribe", serde_json::json!([]), "slotUnsubscribe", 105).await; + expect_pair(&mut websocket, "rootSubscribe", serde_json::json!([]), "rootUnsubscribe", 106).await; + wait_for_close_frame(&mut websocket).await; + }); + let session = crate::HeliusLaserStreamWsSession::connect(helius_endpoint(url.as_str())).await.expect("Helius facade must connect"); + let pubkey = "11111111111111111111111111111111".parse::().expect("fixture pubkey must parse"); + let account_config = crate::SolanaAccountSubscribeConfig::new( + std::option::Option::Some(crate::SolanaAccountEncoding::Base64), + std::option::Option::None, + std::option::Option::Some(crate::SolanaCommitment::Confirmed), + ); + let mut account = session.account_subscribe(&pubkey, std::option::Option::Some(&account_config)).await.expect("Helius accountSubscribe must register"); + assert!(account.unsubscribe().await.expect("Helius accountUnsubscribe must complete")); + let program_config = crate::SolanaProgramSubscribeConfig::new( + crate::SolanaAccountSubscribeConfig::new( + std::option::Option::Some(crate::SolanaAccountEncoding::JsonParsed), + std::option::Option::None, + std::option::Option::None, + ), + std::vec![crate::SolanaProgramAccountFilter::DataSize(80)], + std::option::Option::Some(true), + ); + let mut program = session.program_subscribe(&pubkey, std::option::Option::Some(&program_config)).await.expect("Helius programSubscribe must register"); + assert!(program.unsubscribe().await.expect("Helius programUnsubscribe must complete")); + let logs_config = crate::SolanaCommitmentConfig::new(std::option::Option::Some(crate::SolanaCommitment::Finalized)); + let mut logs = session + .logs_subscribe(&crate::SolanaLogsSubscribeFilter::All, std::option::Option::Some(&logs_config)) + .await + .expect("Helius logsSubscribe must register"); + assert!(logs.unsubscribe().await.expect("Helius logsUnsubscribe must complete")); + let signature_config = + crate::SolanaSignatureSubscribeConfig::new(std::option::Option::Some(crate::SolanaCommitment::Confirmed), std::option::Option::Some(true)); + let mut signature = session + .signature_subscribe("fixture-signature", std::option::Option::Some(&signature_config)) + .await + .expect("Helius signatureSubscribe must register"); + assert!(signature.unsubscribe().await.expect("Helius signatureUnsubscribe must complete")); + let mut slot = session.slot_subscribe().await.expect("Helius slotSubscribe must register"); + assert!(slot.unsubscribe().await.expect("Helius slotUnsubscribe must complete")); + let mut root = session.root_subscribe().await.expect("Helius rootSubscribe must register"); + assert!(root.unsubscribe().await.expect("Helius rootUnsubscribe must complete")); + session.close().await.expect("Helius facade close must complete"); + server.await.expect("local Helius peer task must complete"); +} diff --git a/crates/ksp-onchain-transport-lib/unit_tests/ws_protocol_session.rs b/crates/ksp-onchain-transport-lib/unit_tests/ws_protocol_session.rs index c2f159c..1b4ce39 100644 --- a/crates/ksp-onchain-transport-lib/unit_tests/ws_protocol_session.rs +++ b/crates/ksp-onchain-transport-lib/unit_tests/ws_protocol_session.rs @@ -1,5 +1,5 @@ // file: crates/ksp-onchain-transport-lib/unit_tests/ws_protocol_session.rs -// version: 2 +// version: 3 use futures_util::StreamExt; // rust-rules: trait-import @@ -92,4 +92,13 @@ fn protocol_facades_define_no_second_actor_socket_or_public_inner_escape_hatch() assert!(!source.contains("WsSessionCommand")); assert!(!source.contains("pub fn inner(")); assert!(!source.contains("pub fn into_inner(")); + assert!(!source.contains("pub async fn account_subscribe")); + assert!(!source.contains("pub async fn block_subscribe")); + assert!(!source.contains("pub async fn logs_subscribe")); + assert!(!source.contains("pub async fn program_subscribe")); + assert!(!source.contains("pub async fn root_subscribe")); + assert!(!source.contains("pub async fn signature_subscribe")); + assert!(!source.contains("pub async fn slot_subscribe")); + assert!(!source.contains("pub async fn slots_updates_subscribe")); + assert!(!source.contains("pub async fn vote_subscribe")); } diff --git a/deltas/0.2.8/pre.003.md b/deltas/0.2.8/pre.003.md new file mode 100644 index 0000000..2901a73 --- /dev/null +++ b/deltas/0.2.8/pre.003.md @@ -0,0 +1,259 @@ + + + +# Delta `0.2.8-pre.003` — Surface standard Helius WebSocket + +## 1. Objet + +Cette tranche expose sur `HeliusLaserStreamWsSession` uniquement les six familles WebSocket standard que l'audit Helius courant classe comme supportées, sans ajouter de wire provider parallèle et sans ouvrir encore Config Helius, `transactionSubscribe` ou le heartbeat. + +Le checkpoint opérateur de `pre.002-fix.001` est intégralement vert ; aucune dette de gate n'est reportée dans cette tranche. + +Version workspace : + +```text +0.2.8-pre.3 +``` + +Livraison / commit attendu : + +```text +0.2.8-pre.003 +v0.2.8-pre.003 +``` + +Aucun tag prerelease. + +## 2. Surface Helius ajoutée + +`HeliusLaserStreamWsSession` expose désormais : + +```text +account_subscribe +program_subscribe +logs_subscribe +signature_subscribe +slot_subscribe +root_subscribe +``` + +Chaque wrapper délègue au `WsSession` physique partagé et réutilise exactement les DTOs, encoders, decoders, `WsSubscriptionKind` et méthodes d'unsubscribe standard acquis en `0.2.7`. + +Aucun type suivant n'est créé : + +```text +HeliusAccountSubscribeConfig +HeliusAccountNotification +HeliusProgramSubscribeConfig +HeliusProgramNotification +HeliusLogsSubscribeFilter +HeliusLogsNotification +HeliusSignatureSubscribeConfig +HeliusSignatureNotification +HeliusSlotNotification +HeliusRootNotification +``` + +L'absence de ces copies est volontaire : aucun écart de wire Helius courant ne les justifie. + +## 3. Surface Helius explicitement absente + +La façade Helius continue de ne pas exposer : + +```text +block_subscribe +slots_updates_subscribe +vote_subscribe +transaction_subscribe +``` + +Les trois familles standard non supportées sont verrouillées par des rustdocs `compile_fail` : + +```text +blockSubscribe +slotsUpdatesSubscribe +voteSubscribe +``` + +`transactionSubscribe` reste réservé à la tranche provider-specific dédiée du forecast. + +## 4. Organisation du code + +`ws_protocol_session.rs` reste limité au lifecycle des façades et à l'accès crate-private au moteur partagé. Les wrappers sont rangés auprès du propriétaire de leur wire : + +```text +ws_accounts.rs + SolanaStandardWsSession : account / program + HeliusLaserStreamWsSession : account / program + +ws_transactions.rs + SolanaStandardWsSession : logs / signature + HeliusLaserStreamWsSession : logs / signature + +ws_cluster.rs + SolanaStandardWsSession : root / slot / slotsUpdates / vote + HeliusLaserStreamWsSession : root / slot + +ws_blocks.rs + SolanaStandardWsSession : block uniquement + +ws_protocol_session.rs + connect / id / snapshot / state / close + physical_session() crate-private +``` + +Cette répartition évite de transformer le module de façade en fichier fourre-tout tout en conservant un seul actor/socket/registry. + +Aucun accès public `inner()` / `into_inner()` / `physical_session()` n'est ajouté. + +## 5. Fixture Helius standard + +Une fixture locale dédiée couvre les six familles contre un peer WebSocket local Helius-typed. + +Elle vérifie pour chaque paire : + +```text +nom subscribe exact +params exacts +remote subscription id +nom unsubscribe exact +params [remote_id] +result true +``` + +Couverture : + +```text +accountSubscribe / accountUnsubscribe +programSubscribe / programUnsubscribe +logsSubscribe / logsUnsubscribe +signatureSubscribe / signatureUnsubscribe +slotSubscribe / slotUnsubscribe +rootSubscribe / rootUnsubscribe +``` + +Les tests standard historiques restent propriétaires du décodage exact des notifications ; la nouvelle fixture prouve que la façade Helius traverse le même wire plutôt que de créer une seconde pile de décodage. + +## 6. Canaries publiques et release completeness + +Ajouts : + +```text +public API : les six wrappers Helius sont accessibles depuis le crate-root +public API : les types de paramètres/résultats restent les types Solana partagés +release completeness : surface Helius courante = six familles standard, avant extension transaction +source canary : ws_protocol_session.rs ne redevient pas propriétaire des wrappers métier +``` + +Les neuf familles de `SolanaStandardWsSession` et les 18 opérations standard acquises restent inchangées. + +## 7. Hors périmètre préservé + +Cette tranche ne modifie pas : + +```text +ksp-config-lib +config/std.transport.json +config/schemas/std.transport.schema.json +.env.example +WsSubscriptionKind +transactionSubscribe / transactionUnsubscribe +notification transaction Helius +heartbeat/idle timer +HTTP 52/14 +Store / Program / Wallet +``` + +Aucune dépendance Rust n'est ajoutée. + +## 8. Preuve opérateur héritée avant ouverture + +Le checkpoint fourni après `pre.002-fix.001` est : + +```text +cargo fmt --all OK +python3 scripts/audit_rust_workspace_rules.py clean +cargo check --workspace OK +cargo clippy --workspace --all-targets OK +cargo test -p ksp-onchain-transport-lib OK +cargo test --workspace OK +``` + +`pre.002` + `pre.002-fix.001` sont donc considérés `DONE` avant ce delta. + +## 9. Fichiers de la livraison + +Nouveau : + +```text +crates/ksp-onchain-transport-lib/unit_tests/ws_helius_standard.rs +deltas/0.2.8/pre.003.md +``` + +Modifiés : + +```text +Cargo.toml +crates/ksp-onchain-transport-lib/src/ws_protocol_session.rs +crates/ksp-onchain-transport-lib/src/ws_accounts.rs +crates/ksp-onchain-transport-lib/src/ws_blocks.rs +crates/ksp-onchain-transport-lib/src/ws_cluster.rs +crates/ksp-onchain-transport-lib/src/ws_transactions.rs +crates/ksp-onchain-transport-lib/unit_tests/ws_protocol_session.rs +crates/ksp-onchain-transport-lib/tests/public_api.rs +crates/ksp-onchain-transport-lib/tests/release_completeness.rs +docs/plans/015-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET_PLAN.md +docs/validation/011-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET.md +``` + +## 10. Validation exécutée dans le sandbox de préparation + +Exécuté après modification : + +```text +python3 scripts/audit_rust_workspace_rules.py +General Rust rule audit: clean +Rust export completeness audit: 0 candidate(s) +KSP workspace Rust rule audit: clean +``` + +Le sandbox ne fournit pas Cargo/rustfmt ; les nouvelles fixtures et doctests ne sont donc pas déclarés compilés avant le checkpoint opérateur. + +## 11. Gate opérateur + +Après application : + +```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 +``` + +Attendu : + +```text +nouvelle fixture Helius six familles verte +4 doctests compile_fail de HeliusLaserStreamWsSession verts +public API pre.003 verte +release completeness pre.003 verte +aucun warning/clippy +standard WebSocket 9/9 non régressé +workspace complet vert +``` + +## 12. Suite + +Si le checkpoint est vert, `pre.004` ouvre uniquement : + +```text +Config V2 kind = helius_laserstream +schema / fixtures +mapping Config -> Transport +secret Helius dans URL résolue par Config +redaction api-key +``` + +`transactionSubscribe` reste hors `pre.004` conformément au forecast courant. diff --git a/docs/plans/015-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET_PLAN.md b/docs/plans/015-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET_PLAN.md index 92171ca..cf9d04e 100644 --- a/docs/plans/015-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET_PLAN.md +++ b/docs/plans/015-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET_PLAN.md @@ -1,9 +1,9 @@ - + # Plan `0.2.8` — Helius LaserStream WebSocket -> **Statut : `0.2.8-pre.002` est appliqué et testé, mais son gate Clippy requiert `pre.002-fix.001`.** Les tests Transport et workspace sont verts ; le fix corrige cinq `clippy::implicit_return` dans la nouvelle fixture protocolaire, passe Cargo à `0.2.8-pre.2.fix.1` et réorganise les documents actifs pour garder une séparation nette entre plan, validation et delta. Le moteur physique `WsSession` reste unique et partagé. +> **Statut : `0.2.8-pre.002` + `pre.002-fix.001` sont validés ; `0.2.8-pre.003` est préparé.** La façade `HeliusLaserStreamWsSession` expose désormais uniquement les six familles standard documentées comme supportées par Helius (`account`, `logs`, `program`, `root`, `signature`, `slot`) en réutilisant le wire standard existant. `block`, `slotsUpdates` et `vote` restent absents de la façade Helius. Le checkpoint Cargo opérateur de `pre.003` reste à exécuter. ## 1. Objet, base et état courant @@ -24,15 +24,16 @@ prompts/013-V0_2_8_START_PROMPT.md présent pre.001 gate audit/sizing Helius pre.001-fix.001 séparation des façades + fermeture gate Cargo pre.001-fix.002 forecast compact + namespace LaserStream WS/gRPC -pre.002 socle protocolaire appliqué ; tests verts, Clippy en échec ciblé -pre.002-fix.001 correction Clippy + audit/normalisation structure documentaire +pre.002 socle protocolaire validé +pre.002-fix.001 correction Clippy + normalisation structure documentaire validées +pre.003 six familles standard Helius préparées ; checkpoint opérateur pending -workspace.package.version courant = 0.2.8-pre.2.fix.1 -commit attendu = v0.2.8-pre.002-fix.001 +workspace.package.version courant = 0.2.8-pre.3 +commit attendu = v0.2.8-pre.003 aucun tag prerelease ``` -Le log opérateur de `pre.002` confirme : `cargo fmt`, audit Rust et `cargo check --workspace` verts ; `cargo test -p ksp-onchain-transport-lib` et `cargo test --workspace` verts ; `cargo clippy --workspace --all-targets` bloque uniquement sur cinq `clippy::implicit_return` dans `unit_tests/ws_protocol_session.rs`. Les résultats détaillés restent dans la matrice de validation et le delta du fix, pas dans le plan. +Le checkpoint opérateur de `pre.002-fix.001` est intégralement vert : `cargo fmt`, audit Rust, `cargo check --workspace`, `cargo clippy --workspace --all-targets`, tests Transport et `cargo test --workspace`. `pre.003` peut donc ouvrir la surface standard Helius sans reporter de dette de gate. ## 2. Forecast souple courant @@ -43,10 +44,10 @@ pre.001 DONE — audit Helius actuel + matrice + architecture + threat model + fix.001 DONE — séparation des façades standard/Helius + moteur unique + gate Cargo fermé fix.002 DONE — forecast normalisé + namespace LaserStream WebSocket/gRPC clarifié -pre.002 FIX REQUIRED — socle protocolaire : WsProtocolKind::HeliusLaserStream + façades standard/Helius +pre.002 DONE — socle protocolaire : WsProtocolKind::HeliusLaserStream + façades standard/Helius + connexion physique partagée + guards, sans duplication de l'actor - fix.001 PREPARED — correction Clippy `implicit_return` + audit/normalisation structure docs -pre.003 surface Helius standard supportée : account/logs/program/root/signature/slot + fix.001 DONE — correction Clippy `implicit_return` + audit/normalisation structure docs +pre.003 PREPARED — surface Helius standard supportée : account/logs/program/root/signature/slot + absence typée de block/slotsUpdates/vote sur Helius + non-régression standard 9/9 pre.004 Config V2 helius_laserstream + schema/fixtures + mapping Config -> Transport + stratégie de secret Helius et redaction URL @@ -454,7 +455,7 @@ Les 18 opérations standard restent donc présentes et testées : 9 subscribe + ### 7.2 Surface cible `HeliusLaserStreamWsSession` -À partir de `pre.003`, la façade Helius doit exposer uniquement les familles de subscription suivantes : +À terme dans `0.2.8`, la façade Helius doit exposer uniquement les familles de subscription suivantes : ```text account @@ -466,7 +467,9 @@ slot transaction ``` -Même après `pre.003`, elle **n'expose pas** : +`pre.003` matérialise exactement les six familles standard ; `transaction` reste réservé aux tranches provider-specific ultérieures du forecast. + +La façade Helius **n'expose jamais dans le scope courant** : ```text block @@ -591,7 +594,34 @@ account block logs program root signature slot slotsUpdates vote La façade Helius ne recevra les six délégations communes qu'en `pre.003`, afin de garder la tranche actuelle limitée au socle protocolaire et aux guards. -### 7.7 Protocol kind et namespace LaserStream +### 7.7 Réalisation de `pre.003` + +`pre.003` ajoute uniquement les six wrappers standard que la matrice Helius autorise : + +```text +accountSubscribe / accountUnsubscribe +programSubscribe / programUnsubscribe +logsSubscribe / logsUnsubscribe +signatureSubscribe / signatureUnsubscribe +slotSubscribe / slotUnsubscribe +rootSubscribe / rootUnsubscribe +``` + +Aucun encoder, decoder ou DTO Helius parallèle n'est créé : chaque méthode de `HeliusLaserStreamWsSession` délègue au même wrapper `WsSession` standard déjà validé en `0.2.7`. Pour éviter que le module de façade devienne un fichier fourre-tout, les implémentations sont rangées auprès de leur propriétaire wire : + +```text +ws_accounts.rs -> account / program, façades standard + Helius +ws_transactions.rs -> logs / signature, façades standard + Helius +ws_cluster.rs -> root / slot, plus unstable standard uniquement +ws_blocks.rs -> block, façade standard uniquement +ws_protocol_session.rs -> lifecycle des façades + accès crate-private au moteur partagé +``` + +`block_subscribe`, `slots_updates_subscribe` et `vote_subscribe` restent absents du type Helius et sont verrouillés par doctests `compile_fail`. L'accès interne au `WsSession` physique est `pub(crate)` seulement ; aucun `inner()`/`into_inner()` public n'est introduit. Une fixture locale Helius dédiée vérifie les six méthodes subscribe/unsubscribe et leurs paramètres exacts ; les tests standard existants restent propriétaires du décodage des notifications. + +`transactionSubscribe`, Config Helius et heartbeat restent hors scope de `pre.003`. + +### 7.8 Protocol kind et namespace LaserStream Retenir pour **WebSocket uniquement** : @@ -609,7 +639,7 @@ Le futur **Helius LaserStream gRPC** reste un backend/provider distinct, hors `0 Cette règle permet de conserver le code court `helius_laserstream` dans son conteneur WS sans créer d'ambiguïté future entre les deux produits portant le nom LaserStream. -### 7.8 Connexion et propriété du moteur +### 7.9 Connexion et propriété du moteur Cible de `pre.002` : @@ -629,7 +659,7 @@ pas de second snapshot model pas de public escape hatch Helius -> raw standard WsSession ``` -### 7.9 Capability validation defense-in-depth +### 7.10 Capability validation defense-in-depth La séparation de façade est la première barrière. Une matrice interne reste utile : @@ -645,7 +675,7 @@ HeliusLaserStream: Cette matrice sert aux guards de constructor/descriptor, aux canaries et à la protection interne ; elle ne remplace plus la séparation de l'API publique. -### 7.10 Heartbeat/idle ownership +### 7.11 Heartbeat/idle ownership Helius annonce un timeout d'inactivité de 10 minutes et recommande des pings périodiques. @@ -663,7 +693,7 @@ write failure = chemin disconnect/reconnect existant budget reconnect = fini, inchangé ``` -### 7.11 Config V2 +### 7.12 Config V2 Évolution : @@ -675,7 +705,7 @@ profiles[].ws_endpoints[].kind: Les paramètres de `transactionSubscribe` restent runtime et n'appartiennent pas au profil endpoint Config. -### 7.12 Credentials +### 7.13 Credentials L'api-key reste uniquement dans l'URL résolue : @@ -854,35 +884,37 @@ validation finale fermée prompt 0.2.9 prêt ``` -## 13. Checkpoint `pre.002` / `pre.002-fix.001` +## 13. Checkpoint `pre.003` -Le socle `pre.002` est matériellement en place : - -```text -WsProtocolKind::HeliusLaserStream -> helius_laserstream -SolanaStandardWsSession -HeliusLaserStreamWsSession -WsSession::connect historique standard-only -connect_for_protocol / connect_physical partagés -aucun second actor/socket/registry -aucun Config Helius / transactionSubscribe / heartbeat anticipé -``` - -Preuves opérateur déjà reçues pour `pre.002` : +Le checkpoint précédent est fermé par preuve opérateur : ```text +pre.002 + pre.002-fix.001 cargo fmt --all OK python3 scripts/audit_rust_workspace_rules.py clean cargo check --workspace OK -cargo clippy --workspace --all-targets FAIL ciblé : 5 implicit_return dans ws_protocol_session.rs +cargo clippy --workspace --all-targets OK cargo test -p ksp-onchain-transport-lib OK - 313 unit tests + 37 public API + 25 release completeness + 2 doctests compile_fail cargo test --workspace OK ``` -`pre.002-fix.001` corrige uniquement ce défaut Clippy dans la fixture, sans modifier le runtime protocolaire. Le fix réorganise aussi les deux documents actifs : le **plan** conserve décisions, audit, forecast et stratégie ; la **validation** conserve matrices, critères et preuves exécutées ; les détails de livraison restent dans les **deltas**. +`pre.003` prépare maintenant : -Après application du fix, le gate opérateur à rejouer est : +```text +HeliusLaserStreamWsSession::account_subscribe +HeliusLaserStreamWsSession::program_subscribe +HeliusLaserStreamWsSession::logs_subscribe +HeliusLaserStreamWsSession::signature_subscribe +HeliusLaserStreamWsSession::slot_subscribe +HeliusLaserStreamWsSession::root_subscribe + +block / slotsUpdates / vote -> toujours absents de la façade Helius +transactionSubscribe -> toujours absent +moteur/actor -> inchangé et partagé +new dependency -> aucune +``` + +La validation locale disponible au moment de préparer l'overlay est l'audit Rust workspace, qui reste `clean`. Le checkpoint opérateur requis avant de déclarer `pre.003` `DONE` est : ```bash cargo fmt --all @@ -893,4 +925,4 @@ cargo test -p ksp-onchain-transport-lib cargo test --workspace ``` -Si ce checkpoint est vert, `pre.002` + `fix.001` peuvent être considérés `DONE` et `pre.003` ouvre les six familles standard supportées sur `HeliusLaserStreamWsSession`. +Si ce checkpoint est vert, `pre.004` ouvre uniquement Config V2 `helius_laserstream`, schema/fixtures, mapping Config -> Transport et stratégie de secret/redaction. diff --git a/docs/validation/011-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET.md b/docs/validation/011-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET.md index f89cfe8..ae7a8b5 100644 --- a/docs/validation/011-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET.md +++ b/docs/validation/011-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET.md @@ -1,9 +1,9 @@ - + # Validation `0.2.8` — Helius LaserStream WebSocket -> **Statut : `pre.002` testé avec succès sauf Clippy ; `pre.002-fix.001` préparé.** La matrice conserve les critères et preuves exécutées ; le forecast détaillé reste exclusivement dans le plan actif afin que ce fichier ne devienne pas un second plan. +> **Statut : `pre.002` + `pre.002-fix.001` validés ; `pre.003` préparé.** La façade Helius contient les six wrappers standard supportés sans DTO/wire provider parallèle. Les nouvelles fixtures et canaries doivent encore passer le checkpoint Cargo opérateur avant que `pre.003` soit marqué `DONE`. ## 1. Références @@ -16,6 +16,7 @@ fix architecture deltas/0.2.8/pre.001-fix.001.md fix forecast/nomenclature deltas/0.2.8/pre.001-fix.002.md pre.002 deltas/0.2.8/pre.002.md pre.002 Clippy/docs fix deltas/0.2.8/pre.002-fix.001.md +pre.003 deltas/0.2.8/pre.003.md validation standard WS docs/validation/010-V0_2_7_ONCHAIN_WEBSOCKET.md HTTP compliance docs/validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.md KSP-TRANSPORT-007 docs/validation/005-V0_2_3_KSP_TRANSPORT_007_RETRO_AUDIT.md @@ -86,7 +87,7 @@ Verdict : doublons transitifs, aucune nouvelle dépendance Helius, aucune anomal [x] doublons ciblés analysés comme transitifs/non bloquants ``` -Verdict : **gate `0.2.8-pre.001` positif ; `pre.002` testé par l’opérateur avec un unique échec Clippy ciblé, corrigé par `pre.002-fix.001`.** +Verdict : **gate `0.2.8-pre.001` positif ; `pre.002` + `pre.002-fix.001` sont entièrement verts et fermés. `pre.003` est ouvert.** ## 3. Matrice provider normative @@ -138,8 +139,8 @@ Verdict : **gate `0.2.8-pre.001` positif ; `pre.002` testé par l’opérateur a | LaserStream gRPC | backend/type/Config distincts, hors `0.2.8` | absence de réutilisation WS | décidé | | escape hatch Helius | aucun `inner()`/`into_inner()` public | compile-fail/source canary | pre.002 implémenté | | generic Helius `WsSession::connect` | ne doit pas permettre de contourner la façade | invalid protocol pre-I/O canary | pre.002 implémenté | -| Helius unsupported | absent de la façade | compile-fail/API absence canary | décidé | -| standard Helius commun | délégation vers le même wire/actor | exact fixture | décidé | +| Helius unsupported | absent de la façade | compile-fail/API absence canary | pre.003 implémenté, checkpoint pending | +| standard Helius commun | délégation vers le même wire/actor | exact fixture | pre.003 implémenté, checkpoint pending | | DTO duplication | seulement si wire/sémantique divergent | public/source audit | décidé | | Config direction | Config -> Transport uniquement | ownership tests | décidé | | heartbeat | Helius-only, actor commun | deterministic timers | décidé | @@ -201,7 +202,7 @@ aucun payload brut dans logs/snapshots [x] SolanaStandard facade n'expose pas transactionSubscribe [x] Helius facade n'expose aucun escape hatch vers le handle générique [x] protocol mismatch rejeté avant I/O — fixture opérateur verte -[ ] 6 familles standard Helius utilisent le wire standard exact — `pre.003` +[ ] 6 familles standard Helius utilisent le wire standard exact — implémenté en `pre.003`, fixture opérateur pending [ ] transaction subscribe/ack exact [ ] transaction unsubscribe/result exact [ ] transaction notification dispatch exact @@ -254,46 +255,38 @@ Statut initial : **non exécuté**. Contrat cible : opt-in, Config résout l'api-key, Transport ne lit pas l'environnement. Aucun secret n'est versionné ou affiché. Un échec lié au plan/credential provider doit rester distinguable d'une panne du moteur. -## 10. Preuve `pre.002` et fix Clippy +## 10. Preuves `pre.002` / `fix.001` et préparation `pre.003` -Surface introduite : - -```text -WsProtocolKind::HeliusLaserStream -> "helius_laserstream" -SolanaStandardWsSession -HeliusLaserStreamWsSession -WsSession::connect_for_protocol -> crate-internal -WsSession::connect_physical -> unique physical spawn path -``` - -Canaries ajoutées/prévues dans la livraison : - -```text -[x] protocol descriptors distincts -[x] façade standard publique + 9 délégations vers les wrappers 0.2.7 -[x] façade Helius publique + lifecycle partagé -[x] generic WsSession::connect rejette Helius avant I/O -[x] chaque façade rejette le mauvais protocol kind avant I/O -[x] les deux façades ouvrent/ferment une connexion contre un peer local via le même actor — test opérateur vert -[x] Debug Helius ne projette pas une query api-key canary — test opérateur vert -[x] source de façade sans second spawn/socket/command actor -[x] compile-fail Helius blockSubscribe — doctest opérateur vert -[x] compile-fail Helius into_inner — doctest opérateur vert -[ ] six wrappers standard Helius — `pre.003` -``` - -Validation opérateur reçue pour `pre.002` : +Checkpoint `pre.002-fix.001` reçu : ```text cargo fmt --all OK python3 scripts/audit_rust_workspace_rules.py clean cargo check --workspace OK -cargo clippy --workspace --all-targets FAIL : 5 `implicit_return` dans unit_tests/ws_protocol_session.rs +cargo clippy --workspace --all-targets OK cargo test -p ksp-onchain-transport-lib OK cargo test --workspace OK ``` -`pre.002-fix.001` ajoute les retours explicites exigés par la politique Clippy KSP et ne change aucun contrat runtime. Le fix doit être revalidé par le même checkpoint avant passage à `pre.003`. +Surface `pre.003` préparée : + +```text +[ ] Helius accountSubscribe/accountUnsubscribe — wrapper présent, fixture pending +[ ] Helius programSubscribe/programUnsubscribe — wrapper présent, fixture pending +[ ] Helius logsSubscribe/logsUnsubscribe — wrapper présent, fixture pending +[ ] Helius signatureSubscribe/signatureUnsubscribe — wrapper présent, fixture pending +[ ] Helius slotSubscribe/slotUnsubscribe — wrapper présent, fixture pending +[ ] Helius rootSubscribe/rootUnsubscribe — wrapper présent, fixture pending +[ ] compile-fail Helius blockSubscribe — doctest ajouté, exécution pending +[ ] compile-fail Helius slotsUpdatesSubscribe — doctest ajouté, exécution pending +[ ] compile-fail Helius voteSubscribe — doctest ajouté, exécution pending +[ ] public API canary des six wrappers — ajouté, exécution pending +[ ] release completeness canary — ajouté, exécution pending +``` + +La fixture Helius locale vérifie les noms de méthodes, paramètres, remote IDs et méthodes d'unsubscribe pour les six familles. Elle réutilise volontairement les DTOs Solana standard ; les tests historiques `ws_accounts`, `ws_transactions` et `ws_cluster` restent la preuve du décodage exact des notifications et de leurs variantes. + +Aucun `transactionSubscribe`, changement Config, heartbeat ou nouvelle dépendance n'est introduit dans cette tranche. ## 11. Audit structurel des documents actifs @@ -320,3 +313,17 @@ Constats `pre.002-fix.001` : ``` Aucun split en fichiers supplémentaires n'est retenu : les plans historiques `0.2.5`–`0.2.7` sont de taille comparable ou supérieure, et le contenu du plan `015` reste entièrement centré sur une seule release. Le problème identifié était **l'ordre interne et la duplication de responsabilités**, pas la nécessité d'un nouveau type de document. + + +## 12. Gate opérateur `pre.003` + +```text +[ ] cargo fmt --all +[ ] python3 scripts/audit_rust_workspace_rules.py = clean +[ ] cargo check --workspace +[ ] cargo clippy --workspace --all-targets +[ ] cargo test -p ksp-onchain-transport-lib +[ ] cargo test --workspace +``` + +Verdict courant : **`pre.003` préparé, non déclaré `DONE` avant ce checkpoint.**