v0.3.15-pre.002
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 594
|
# version: 595
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-store-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-job-api", "crates/ksp-job-backfill-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-raw-transaction-lib", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib", "crates/ksp-worker-api", "crates/ksp-worker-raw-transaction-ingest-lib"]
|
members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-store-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-job-api", "crates/ksp-job-backfill-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-raw-transaction-lib", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib", "crates/ksp-worker-api", "crates/ksp-worker-raw-transaction-ingest-lib"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.3.15-pre.1"
|
version = "0.3.15-pre.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: crates/ksp-onchain-transport-lib/USAGE.md -->
|
<!-- file: crates/ksp-onchain-transport-lib/USAGE.md -->
|
||||||
<!-- version: 26 -->
|
<!-- version: 27 -->
|
||||||
|
|
||||||
# Utilisation de `ksp-onchain-transport-lib`
|
# Utilisation de `ksp-onchain-transport-lib`
|
||||||
|
|
||||||
@@ -82,7 +82,14 @@ let endpoint = ksp_onchain_transport_lib::WsEndpointSettings::new(
|
|||||||
ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard,
|
ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard,
|
||||||
ws_url,
|
ws_url,
|
||||||
ksp_onchain_transport_lib::WsSessionSettings::default(),
|
ksp_onchain_transport_lib::WsSessionSettings::default(),
|
||||||
);
|
)
|
||||||
|
.with_subscription_capabilities(vec![ksp_onchain_transport_lib::WsSubscriptionKind::Logs]);
|
||||||
|
if !endpoint.supports_subscription(ksp_onchain_transport_lib::WsSubscriptionKind::Logs) {
|
||||||
|
return Err(ksp_core_lib::Error::new(
|
||||||
|
ksp_onchain_transport_lib::ERROR_CODE_INVALID_SETTINGS,
|
||||||
|
"required WebSocket subscription capability is not declared",
|
||||||
|
));
|
||||||
|
}
|
||||||
let session = match ksp_onchain_transport_lib::WsSession::connect(endpoint).await {
|
let session = match ksp_onchain_transport_lib::WsSession::connect(endpoint).await {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(error) => return Err(error),
|
Err(error) => return Err(error),
|
||||||
@@ -90,6 +97,8 @@ let session = match ksp_onchain_transport_lib::WsSession::connect(endpoint).awai
|
|||||||
let snapshot = session.snapshot();
|
let snapshot = session.snapshot();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`with_subscription_capabilities(...)` porte une preuve explicite et typed des familles déclarées pour cet endpoint. Une déclaration vide, dupliquée ou incompatible avec `WsProtocolKind` est rejetée par `WsTransportSettings::validate()`. Un endpoint construit sans déclaration reste valide pour compatibilité, mais `subscription_capabilities()` retourne `None` et `supports_subscription(...)` retourne `false` : l'absence de déclaration n'est jamais une preuve implicite de support.
|
||||||
|
|
||||||
Deux appels `WsSession::connect` avec le même endpoint créent volontairement deux connexions physiques distinctes. Il n'existe encore aucun pool de sessions automatique.
|
Deux appels `WsSession::connect` avec le même endpoint créent volontairement deux connexions physiques distinctes. Il n'existe encore aucun pool de sessions automatique.
|
||||||
|
|
||||||
Le socket brut et la primitive JSON-RPC générique ne sont pas publics. Le moteur générique de subscription typed reste `pub(crate)` ; il ne constitue donc pas une escape hatch provider-specific.
|
Le socket brut et la primitive JSON-RPC générique ne sont pas publics. Le moteur générique de subscription typed reste `pub(crate)` ; il ne constitue donc pas une escape hatch provider-specific.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/src/ws_settings.rs
|
// file: crates/ksp-onchain-transport-lib/src/ws_settings.rs
|
||||||
// version: 4
|
// version: 5
|
||||||
|
|
||||||
const DEFAULT_WS_CLOSE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
|
const DEFAULT_WS_CLOSE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
|
||||||
const DEFAULT_WS_COMMAND_QUEUE_CAPACITY: usize = 128;
|
const DEFAULT_WS_COMMAND_QUEUE_CAPACITY: usize = 128;
|
||||||
@@ -401,6 +401,7 @@ pub struct WsEndpointSettings {
|
|||||||
provider: crate::WsProviderName,
|
provider: crate::WsProviderName,
|
||||||
cluster: crate::WsClusterName,
|
cluster: crate::WsClusterName,
|
||||||
protocol: crate::WsProtocolKind,
|
protocol: crate::WsProtocolKind,
|
||||||
|
subscription_capabilities: std::option::Option<std::vec::Vec<crate::WsSubscriptionKind>>,
|
||||||
url: crate::WsEndpointUrl,
|
url: crate::WsEndpointUrl,
|
||||||
session: crate::WsSessionSettings,
|
session: crate::WsSessionSettings,
|
||||||
}
|
}
|
||||||
@@ -417,7 +418,42 @@ impl WsEndpointSettings {
|
|||||||
url: crate::WsEndpointUrl,
|
url: crate::WsEndpointUrl,
|
||||||
session: crate::WsSessionSettings,
|
session: crate::WsSessionSettings,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
return Self { name: name.into(), enabled, provider, cluster, protocol, url, session };
|
return Self {
|
||||||
|
name: name.into(),
|
||||||
|
enabled,
|
||||||
|
provider,
|
||||||
|
cluster,
|
||||||
|
protocol,
|
||||||
|
subscription_capabilities: std::option::Option::None,
|
||||||
|
url,
|
||||||
|
session,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Declares the exact WebSocket subscription families supported by this endpoint.
|
||||||
|
///
|
||||||
|
/// An endpoint created without this builder remains valid for backward compatibility, but its capabilities are considered undeclared and therefore
|
||||||
|
/// never constitute positive support evidence for a subscription family. Validation rejects an explicitly declared empty set, duplicates and
|
||||||
|
/// capabilities that cannot belong to the selected protocol surface.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_subscription_capabilities(mut self, capabilities: std::vec::Vec<crate::WsSubscriptionKind>) -> Self {
|
||||||
|
self.subscription_capabilities = std::option::Option::Some(capabilities);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the explicitly declared WebSocket subscription capabilities, or `None` when the endpoint still uses the legacy undeclared form.
|
||||||
|
#[must_use]
|
||||||
|
pub fn subscription_capabilities(&self) -> std::option::Option<&[crate::WsSubscriptionKind]> {
|
||||||
|
return self.subscription_capabilities.as_deref();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether this endpoint explicitly declares support for one WebSocket subscription family.
|
||||||
|
///
|
||||||
|
/// Undeclared legacy capability state is fail-closed and therefore returns `false`.
|
||||||
|
#[must_use]
|
||||||
|
pub fn supports_subscription(&self, kind: crate::WsSubscriptionKind) -> bool {
|
||||||
|
return ws_protocol_supports_subscription(self.protocol, kind)
|
||||||
|
&& self.subscription_capabilities().is_some_and(|capabilities| return capabilities.contains(&kind));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the logical endpoint name.
|
/// Returns the logical endpoint name.
|
||||||
@@ -532,6 +568,32 @@ fn validate_ws_endpoint(endpoint: &crate::WsEndpointSettings, endpoint_index: us
|
|||||||
if let std::result::Result::Err(error) = validate_ws_descriptor(endpoint.cluster().as_str(), cluster_field.as_str()) {
|
if let std::result::Result::Err(error) = validate_ws_descriptor(endpoint.cluster().as_str(), cluster_field.as_str()) {
|
||||||
return std::result::Result::Err(error);
|
return std::result::Result::Err(error);
|
||||||
}
|
}
|
||||||
|
if let std::result::Result::Some(capabilities) = endpoint.subscription_capabilities() {
|
||||||
|
if capabilities.is_empty() {
|
||||||
|
return ws_invalid_settings(
|
||||||
|
"explicit WebSocket subscription capabilities must not be empty",
|
||||||
|
format!("ws_endpoints[{endpoint_index}].subscription_capabilities").as_str(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (capability_index, capability) in capabilities.iter().copied().enumerate() {
|
||||||
|
let field = format!("ws_endpoints[{endpoint_index}].subscription_capabilities[{capability_index}]");
|
||||||
|
if !ws_protocol_supports_subscription(endpoint.protocol(), capability) {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, "WebSocket subscription capability is incompatible with endpoint protocol")
|
||||||
|
.with_context("field", field)
|
||||||
|
.with_context("subscription_kind", capability.as_str())
|
||||||
|
.with_context("protocol", endpoint.protocol().as_str()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if capabilities[..capability_index].contains(&capability) {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, "WebSocket subscription capabilities must be unique")
|
||||||
|
.with_context("field", field)
|
||||||
|
.with_context("subscription_kind", capability.as_str()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if let std::result::Result::Err(error) = endpoint.session().validate() {
|
if let std::result::Result::Err(error) = endpoint.session().validate() {
|
||||||
return std::result::Result::Err(error);
|
return std::result::Result::Err(error);
|
||||||
}
|
}
|
||||||
@@ -547,6 +609,34 @@ fn validate_ws_endpoint(endpoint: &crate::WsEndpointSettings, endpoint_index: us
|
|||||||
return std::result::Result::Ok(());
|
return std::result::Result::Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fn ws_protocol_supports_subscription(protocol: crate::WsProtocolKind, kind: crate::WsSubscriptionKind) -> bool {
|
||||||
|
return match protocol {
|
||||||
|
crate::WsProtocolKind::SolanaStandard => matches!(
|
||||||
|
kind,
|
||||||
|
crate::WsSubscriptionKind::Account
|
||||||
|
| crate::WsSubscriptionKind::Block
|
||||||
|
| crate::WsSubscriptionKind::Logs
|
||||||
|
| crate::WsSubscriptionKind::Program
|
||||||
|
| crate::WsSubscriptionKind::Root
|
||||||
|
| crate::WsSubscriptionKind::Signature
|
||||||
|
| crate::WsSubscriptionKind::Slot
|
||||||
|
| crate::WsSubscriptionKind::SlotsUpdates
|
||||||
|
| crate::WsSubscriptionKind::Vote
|
||||||
|
),
|
||||||
|
crate::WsProtocolKind::HeliusLaserStream => matches!(
|
||||||
|
kind,
|
||||||
|
crate::WsSubscriptionKind::Account
|
||||||
|
| crate::WsSubscriptionKind::Logs
|
||||||
|
| crate::WsSubscriptionKind::Program
|
||||||
|
| crate::WsSubscriptionKind::Root
|
||||||
|
| crate::WsSubscriptionKind::Signature
|
||||||
|
| crate::WsSubscriptionKind::Slot
|
||||||
|
| crate::WsSubscriptionKind::SlotsUpdates
|
||||||
|
| crate::WsSubscriptionKind::HeliusTransaction
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_ws_descriptor(value: &str, field: &str) -> ksp_core_lib::Result<()> {
|
fn validate_ws_descriptor(value: &str, field: &str) -> ksp_core_lib::Result<()> {
|
||||||
if value.trim().is_empty() {
|
if value.trim().is_empty() {
|
||||||
return ws_invalid_settings("WebSocket transport descriptor must not be empty", field);
|
return ws_invalid_settings("WebSocket transport descriptor must not be empty", field);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs
|
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs
|
||||||
// version: 54
|
// version: 55
|
||||||
|
|
||||||
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
|
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
|
||||||
|
|
||||||
@@ -1057,3 +1057,21 @@ fn public_v0_3_13_pre_002_yellowstone_subscribe_identity_is_opaque_and_available
|
|||||||
assert!(!debug.contains("bytes:"));
|
assert!(!debug.contains("bytes:"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_v0_3_15_pre_002_websocket_endpoint_capability_contract_is_available_from_crate_root() {
|
||||||
|
let endpoint = ksp_onchain_transport_lib::WsEndpointSettings::new(
|
||||||
|
"typed_public",
|
||||||
|
true,
|
||||||
|
ksp_onchain_transport_lib::WsProviderName::new("public"),
|
||||||
|
ksp_onchain_transport_lib::WsClusterName::new("devnet"),
|
||||||
|
ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard,
|
||||||
|
ksp_onchain_transport_lib::WsEndpointUrl::parse("wss://api.devnet.solana.com").expect("public WebSocket URL fixture must parse"),
|
||||||
|
ksp_onchain_transport_lib::WsSessionSettings::default(),
|
||||||
|
)
|
||||||
|
.with_subscription_capabilities(std::vec![ksp_onchain_transport_lib::WsSubscriptionKind::Logs, ksp_onchain_transport_lib::WsSubscriptionKind::Block,]);
|
||||||
|
assert!(endpoint.supports_subscription(ksp_onchain_transport_lib::WsSubscriptionKind::Logs));
|
||||||
|
assert!(endpoint.supports_subscription(ksp_onchain_transport_lib::WsSubscriptionKind::Block));
|
||||||
|
assert!(!endpoint.supports_subscription(ksp_onchain_transport_lib::WsSubscriptionKind::HeliusTransaction));
|
||||||
|
assert_eq!(endpoint.subscription_capabilities().map(|capabilities| return capabilities.len()), std::option::Option::Some(2));
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||||
// version: 46
|
// version: 47
|
||||||
|
|
||||||
//! Release-level completeness canaries for staged HTTP and WebSocket Transport coverage.
|
//! Release-level completeness canaries for staged HTTP and WebSocket Transport coverage.
|
||||||
|
|
||||||
@@ -1399,3 +1399,16 @@ fn release_v0_3_14_pre_004_native_replay_evidence_never_conflates_attempt_delive
|
|||||||
assert!(!stream_source.contains("replay_coverage_proven_count"));
|
assert!(!stream_source.contains("replay_coverage_proven_count"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn release_v0_3_15_pre_002_websocket_capabilities_are_endpoint_declared_typed_and_protocol_bounded() {
|
||||||
|
let settings_source = include_str!("../src/ws_settings.rs");
|
||||||
|
assert!(settings_source.contains("subscription_capabilities: std::option::Option<std::vec::Vec<crate::WsSubscriptionKind>>"));
|
||||||
|
assert!(settings_source.contains("pub fn with_subscription_capabilities"));
|
||||||
|
assert!(settings_source.contains("pub fn subscription_capabilities"));
|
||||||
|
assert!(settings_source.contains("pub fn supports_subscription"));
|
||||||
|
assert!(settings_source.contains("crate::WsProtocolKind::SolanaStandard => matches!("));
|
||||||
|
assert!(settings_source.contains("crate::WsProtocolKind::HeliusLaserStream => matches!("));
|
||||||
|
assert!(!settings_source.contains("provider().as_str() == \"helius\""));
|
||||||
|
assert!(!settings_source.contains("provider().as_str() == \"publicnode\""));
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/unit_tests/ws_settings.rs
|
// file: crates/ksp-onchain-transport-lib/unit_tests/ws_settings.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
fn valid_endpoint(name: &str, url_text: &str) -> crate::WsEndpointSettings {
|
fn valid_endpoint(name: &str, url_text: &str) -> crate::WsEndpointSettings {
|
||||||
return crate::WsEndpointSettings::new(
|
return crate::WsEndpointSettings::new(
|
||||||
@@ -148,3 +148,80 @@ fn websocket_transport_settings_debug_never_exposes_endpoint_url() {
|
|||||||
assert!(!rendered.contains("provider.invalid"));
|
assert!(!rendered.contains("provider.invalid"));
|
||||||
assert!(!rendered.contains("password"));
|
assert!(!rendered.contains("password"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_15_pre_002_undeclared_websocket_capabilities_fail_closed_without_breaking_legacy_settings() {
|
||||||
|
let endpoint = valid_endpoint("legacy", "wss://legacy.invalid/ws");
|
||||||
|
assert_eq!(endpoint.subscription_capabilities(), std::option::Option::None);
|
||||||
|
assert!(!endpoint.supports_subscription(crate::WsSubscriptionKind::Logs));
|
||||||
|
assert!(!endpoint.supports_subscription(crate::WsSubscriptionKind::Block));
|
||||||
|
let settings = crate::WsTransportSettings::new(std::vec![endpoint]);
|
||||||
|
assert!(settings.validate().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_15_pre_002_standard_endpoint_declares_exact_typed_subscription_capabilities() {
|
||||||
|
let endpoint = valid_endpoint("typed", "wss://typed.invalid/ws")
|
||||||
|
.with_subscription_capabilities(std::vec![crate::WsSubscriptionKind::Logs, crate::WsSubscriptionKind::Block,]);
|
||||||
|
assert_eq!(
|
||||||
|
endpoint.subscription_capabilities(),
|
||||||
|
std::option::Option::Some([crate::WsSubscriptionKind::Logs, crate::WsSubscriptionKind::Block].as_slice())
|
||||||
|
);
|
||||||
|
assert!(endpoint.supports_subscription(crate::WsSubscriptionKind::Logs));
|
||||||
|
assert!(endpoint.supports_subscription(crate::WsSubscriptionKind::Block));
|
||||||
|
assert!(!endpoint.supports_subscription(crate::WsSubscriptionKind::Vote));
|
||||||
|
assert!(!endpoint.supports_subscription(crate::WsSubscriptionKind::HeliusTransaction));
|
||||||
|
let settings = crate::WsTransportSettings::new(std::vec![endpoint]);
|
||||||
|
assert!(settings.validate().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_15_pre_002_explicit_capability_set_rejects_empty_and_duplicate_entries() {
|
||||||
|
let empty = valid_endpoint("empty", "wss://empty.invalid/ws").with_subscription_capabilities(std::vec![]);
|
||||||
|
let empty_error = crate::WsTransportSettings::new(std::vec![empty]).validate().expect_err("explicit empty capabilities must fail");
|
||||||
|
assert_eq!(empty_error.code(), crate::ERROR_CODE_INVALID_SETTINGS);
|
||||||
|
let duplicate = valid_endpoint("duplicate_caps", "wss://duplicate.invalid/ws")
|
||||||
|
.with_subscription_capabilities(std::vec![crate::WsSubscriptionKind::Logs, crate::WsSubscriptionKind::Logs,]);
|
||||||
|
let duplicate_error = crate::WsTransportSettings::new(std::vec![duplicate]).validate().expect_err("duplicate capabilities must fail");
|
||||||
|
assert_eq!(duplicate_error.code(), crate::ERROR_CODE_INVALID_SETTINGS);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_15_pre_002_capabilities_must_belong_to_selected_protocol_surface() {
|
||||||
|
let standard =
|
||||||
|
valid_endpoint("standard_bad", "wss://standard.invalid/ws").with_subscription_capabilities(std::vec![crate::WsSubscriptionKind::HeliusTransaction]);
|
||||||
|
assert!(!standard.supports_subscription(crate::WsSubscriptionKind::HeliusTransaction));
|
||||||
|
let standard_error =
|
||||||
|
crate::WsTransportSettings::new(std::vec![standard]).validate().expect_err("standard protocol must reject Helius transaction capability");
|
||||||
|
assert_eq!(standard_error.code(), crate::ERROR_CODE_INVALID_SETTINGS);
|
||||||
|
let helius_block = crate::WsEndpointSettings::new(
|
||||||
|
"helius_block",
|
||||||
|
true,
|
||||||
|
crate::WsProviderName::new("helius"),
|
||||||
|
crate::WsClusterName::new("mainnet"),
|
||||||
|
crate::WsProtocolKind::HeliusLaserStream,
|
||||||
|
crate::WsEndpointUrl::parse("wss://helius.invalid/ws").expect("test URL must parse"),
|
||||||
|
crate::WsSessionSettings::default(),
|
||||||
|
)
|
||||||
|
.with_subscription_capabilities(std::vec![crate::WsSubscriptionKind::Block]);
|
||||||
|
assert!(!helius_block.supports_subscription(crate::WsSubscriptionKind::Block));
|
||||||
|
let helius_block_error =
|
||||||
|
crate::WsTransportSettings::new(std::vec![helius_block]).validate().expect_err("Helius protocol must reject unsupported block capability");
|
||||||
|
assert_eq!(helius_block_error.code(), crate::ERROR_CODE_INVALID_SETTINGS);
|
||||||
|
let helius_supported = crate::WsEndpointSettings::new(
|
||||||
|
"helius_supported",
|
||||||
|
true,
|
||||||
|
crate::WsProviderName::new("helius"),
|
||||||
|
crate::WsClusterName::new("mainnet"),
|
||||||
|
crate::WsProtocolKind::HeliusLaserStream,
|
||||||
|
crate::WsEndpointUrl::parse("wss://helius-supported.invalid/ws").expect("test URL must parse"),
|
||||||
|
crate::WsSessionSettings::default(),
|
||||||
|
)
|
||||||
|
.with_subscription_capabilities(std::vec![crate::WsSubscriptionKind::Logs, crate::WsSubscriptionKind::HeliusTransaction]);
|
||||||
|
assert!(helius_supported.supports_subscription(crate::WsSubscriptionKind::Logs));
|
||||||
|
assert!(helius_supported.supports_subscription(crate::WsSubscriptionKind::HeliusTransaction));
|
||||||
|
assert!(!helius_supported.supports_subscription(crate::WsSubscriptionKind::Block));
|
||||||
|
assert!(!helius_supported.supports_subscription(crate::WsSubscriptionKind::Vote));
|
||||||
|
let helius_settings = crate::WsTransportSettings::new(std::vec![helius_supported]);
|
||||||
|
assert!(helius_settings.validate().is_ok());
|
||||||
|
}
|
||||||
|
|||||||
316
deltas/0.3.15/pre.002.md
Normal file
316
deltas/0.3.15/pre.002.md
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
<!-- file: deltas/0.3.15/pre.002.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.15-pre.002` — capability WebSocket typed par endpoint
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.15-pre.001-fix.001 appliquée
|
||||||
|
workspace.package.version = 0.3.15-pre.1
|
||||||
|
deltas/0.3.15/pre.001-fix.001.md présent
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gate de la base
|
||||||
|
|
||||||
|
Le gate opérateur communiqué après `pre.001-fix.001` est propre :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all -- --check : PASS
|
||||||
|
General Rust rule audit : clean
|
||||||
|
Rust export completeness audit : 0 candidate(s)
|
||||||
|
KSP workspace Rust rule audit : clean
|
||||||
|
Markdown table audit : clean (318 table(s), 185 file(s))
|
||||||
|
cargo check --workspace : PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce gate qualifie la base directe de `pre.002` avant toute modification Transport.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Implémenter strictement la tranche `pre.002` du plan `036` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ajouter une déclaration WebSocket typed exacte par subscription au niveau Transport
|
||||||
|
réutiliser WsSubscriptionKind comme identité de capability
|
||||||
|
conserver la compatibilité des WsEndpointSettings existants
|
||||||
|
faire de l'absence de déclaration un état fail-closed pour toute preuve de capability
|
||||||
|
valider les déclarations explicites sans inférence par provider
|
||||||
|
borner les capabilities à la surface réellement portée par WsProtocolKind
|
||||||
|
exposer la preuve via la façade crate-root déjà existante
|
||||||
|
documenter l'usage version-neutral
|
||||||
|
ajouter les tests unitaires/public API/release completeness
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette tranche ne mappe encore aucun champ Config et ne modifie aucun constructeur Worker. Ces responsabilités restent respectivement `pre.003` et `pre.004`.
|
||||||
|
|
||||||
|
## Contrat `WsEndpointSettings`
|
||||||
|
|
||||||
|
`WsEndpointSettings` conserve son constructeur historique `new(...)` sans argument supplémentaire afin de ne pas forcer artificiellement Config à évoluer dans la même tranche.
|
||||||
|
|
||||||
|
Un endpoint possède désormais l'état privé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
subscription_capabilities: Option<Vec<WsSubscriptionKind>>
|
||||||
|
```
|
||||||
|
|
||||||
|
Sémantique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
None = capabilities non déclarées ; compatibilité legacy ; aucune preuve positive
|
||||||
|
Some(vec) = déclaration explicite des seules familles annoncées pour cet endpoint
|
||||||
|
```
|
||||||
|
|
||||||
|
Le builder public :
|
||||||
|
|
||||||
|
```text
|
||||||
|
with_subscription_capabilities(Vec<WsSubscriptionKind>)
|
||||||
|
```
|
||||||
|
|
||||||
|
permet d'attacher la déclaration exacte sans introduire de second enum ni de second registre de méthodes.
|
||||||
|
|
||||||
|
Les getters publics :
|
||||||
|
|
||||||
|
```text
|
||||||
|
subscription_capabilities() -> Option<&[WsSubscriptionKind]>
|
||||||
|
supports_subscription(WsSubscriptionKind) -> bool
|
||||||
|
```
|
||||||
|
|
||||||
|
restent accessibles via `WsEndpointSettings`, déjà réexporté depuis la racine de crate.
|
||||||
|
|
||||||
|
`supports_subscription(...)` est volontairement fail-closed :
|
||||||
|
|
||||||
|
```text
|
||||||
|
capability non déclarée -> false
|
||||||
|
capability incompatible avec le protocole -> false même avant validate()
|
||||||
|
capability explicitement déclarée et protocol-compatible -> true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation Transport
|
||||||
|
|
||||||
|
`WsTransportSettings::validate()` rejette maintenant une déclaration explicite :
|
||||||
|
|
||||||
|
```text
|
||||||
|
vide
|
||||||
|
dupliquée
|
||||||
|
incompatible avec le WsProtocolKind de l'endpoint
|
||||||
|
```
|
||||||
|
|
||||||
|
Les erreurs réutilisent `ERROR_CODE_INVALID_SETTINGS` et n'exposent que :
|
||||||
|
|
||||||
|
```text
|
||||||
|
field
|
||||||
|
subscription_kind stable
|
||||||
|
protocol stable
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune URL, credential, source key ou payload n'est ajouté aux contextes d'erreur.
|
||||||
|
|
||||||
|
## Partition protocolaire
|
||||||
|
|
||||||
|
La validation est protocol-owned et ne branche jamais sur `provider`.
|
||||||
|
|
||||||
|
`SolanaStandard` admet les neuf familles standard actuelles :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Account
|
||||||
|
Block
|
||||||
|
Logs
|
||||||
|
Program
|
||||||
|
Root
|
||||||
|
Signature
|
||||||
|
Slot
|
||||||
|
SlotsUpdates
|
||||||
|
Vote
|
||||||
|
```
|
||||||
|
|
||||||
|
`HeliusLaserStream` admet la surface déjà portée par sa façade typed :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Account
|
||||||
|
Logs
|
||||||
|
Program
|
||||||
|
Root
|
||||||
|
Signature
|
||||||
|
Slot
|
||||||
|
SlotsUpdates
|
||||||
|
HeliusTransaction
|
||||||
|
```
|
||||||
|
|
||||||
|
Il n'admet donc pas `Block` ni `Vote`.
|
||||||
|
|
||||||
|
Cette partition est écrite positivement. Une future variante de `WsSubscriptionKind` ne devient donc pas implicitement compatible avec un protocole existant.
|
||||||
|
|
||||||
|
## Absence d'inférence provider
|
||||||
|
|
||||||
|
Aucune règle du type :
|
||||||
|
|
||||||
|
```text
|
||||||
|
provider == helius -> capability X
|
||||||
|
provider == publicnode -> capability Y
|
||||||
|
```
|
||||||
|
|
||||||
|
n'est ajoutée.
|
||||||
|
|
||||||
|
Le provider reste un descriptor ouvert. La preuve de capability est attachée explicitement à l'endpoint et devra être alimentée par Config en `pre.003`.
|
||||||
|
|
||||||
|
## Documentation d'usage
|
||||||
|
|
||||||
|
`ksp-onchain-transport-lib/USAGE.md` montre maintenant la déclaration typed sur un endpoint standard et documente les deux propriétés importantes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
absence de déclaration != support implicite
|
||||||
|
validation explicite des listes vide/dupliquée/protocol-incompatible
|
||||||
|
```
|
||||||
|
|
||||||
|
Le guide reste version-neutral et ne sert pas de journal de gate.
|
||||||
|
|
||||||
|
## Tests ajoutés
|
||||||
|
|
||||||
|
Unitaires `ws_settings` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
legacy undeclared reste structurally valide mais fail-closed
|
||||||
|
capabilities standard exactes et typed
|
||||||
|
liste explicite vide rejetée
|
||||||
|
capability dupliquée rejetée
|
||||||
|
HeliusTransaction rejeté sous SolanaStandard
|
||||||
|
Block rejeté sous HeliusLaserStream
|
||||||
|
Logs + HeliusTransaction admis sous HeliusLaserStream
|
||||||
|
Block/Vote restent non supportés sous HeliusLaserStream
|
||||||
|
supports_subscription reste false sur capability protocol-incompatible avant validate
|
||||||
|
```
|
||||||
|
|
||||||
|
Public API :
|
||||||
|
|
||||||
|
```text
|
||||||
|
builder/getter/support check consommables depuis la crate root
|
||||||
|
aucun nouveau module public requis
|
||||||
|
```
|
||||||
|
|
||||||
|
Release completeness :
|
||||||
|
|
||||||
|
```text
|
||||||
|
état capability porté par WsEndpointSettings
|
||||||
|
WsSubscriptionKind réutilisé directement
|
||||||
|
partition protocolaire présente
|
||||||
|
absence de branche provider-name pour attribuer des capabilities
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.15/pre.002.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-onchain-transport-lib/USAGE.md
|
||||||
|
crates/ksp-onchain-transport-lib/src/ws_settings.rs
|
||||||
|
crates/ksp-onchain-transport-lib/unit_tests/ws_settings.rs
|
||||||
|
crates/ksp-onchain-transport-lib/tests/public_api.rs
|
||||||
|
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## Version Cargo
|
||||||
|
|
||||||
|
Cette tranche modifie le contrat/runtime settings Transport. Conformément à la policy de versioning :
|
||||||
|
|
||||||
|
```text
|
||||||
|
header Cargo.toml : 594 -> 595
|
||||||
|
workspace.package.version : 0.3.15-pre.1 -> 0.3.15-pre.2
|
||||||
|
```
|
||||||
|
|
||||||
|
Versions des fichiers modifiés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-onchain-transport-lib/USAGE.md : 26 -> 27
|
||||||
|
ksp-onchain-transport-lib/src/ws_settings.rs : 4 -> 5
|
||||||
|
ksp-onchain-transport-lib/unit_tests/ws_settings.rs : 2 -> 3
|
||||||
|
ksp-onchain-transport-lib/tests/public_api.rs : 54 -> 55
|
||||||
|
ksp-onchain-transport-lib/tests/release_completeness.rs : 46 -> 47
|
||||||
|
```
|
||||||
|
|
||||||
|
## Frontières préservées
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun changement Config/schema/composite
|
||||||
|
aucun changement Worker
|
||||||
|
aucun changement Store
|
||||||
|
aucune crate Desk ajoutée
|
||||||
|
aucun Start/Stop Worker applicatif
|
||||||
|
aucun I/O réseau supplémentaire
|
||||||
|
aucune nouvelle dépendance
|
||||||
|
aucune nouvelle feature
|
||||||
|
aucun provider SDK
|
||||||
|
aucune inférence capability par provider name
|
||||||
|
aucun nouveau type public nécessitant une réexportation lib.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Audits statiques d'assemblage
|
||||||
|
|
||||||
|
Après modification et ajout du delta :
|
||||||
|
|
||||||
|
```text
|
||||||
|
General Rust rule audit : clean
|
||||||
|
Rust export completeness audit : 0 candidate(s)
|
||||||
|
KSP workspace Rust rule audit : clean
|
||||||
|
Markdown table audit : clean (318 table(s), 186 file(s))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gates Cargo non exécutés dans l'environnement d'assemblage
|
||||||
|
|
||||||
|
L'environnement d'assemblage ne fournit pas Cargo/Rustfmt. Aucun faux PASS n'est revendiqué pour :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all -- --check
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||||
|
cargo test -p ksp-onchain-transport-lib --all-targets --all-features
|
||||||
|
```
|
||||||
|
|
||||||
|
Ils doivent être exécutés par l'opérateur après application du delta.
|
||||||
|
|
||||||
|
## Archive d'échange
|
||||||
|
|
||||||
|
Livraison attendue selon `VER-ARCHIVE-004` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-general-0.3.15-pre.002.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Contenu exact :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-onchain-transport-lib/USAGE.md
|
||||||
|
crates/ksp-onchain-transport-lib/src/ws_settings.rs
|
||||||
|
crates/ksp-onchain-transport-lib/unit_tests/ws_settings.rs
|
||||||
|
crates/ksp-onchain-transport-lib/tests/public_api.rs
|
||||||
|
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||||
|
deltas/0.3.15/pre.002.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun fichier stable inchangé, lockfile, cache, sortie de build ou artefact temporaire n'est inclus.
|
||||||
|
|
||||||
|
## Gate opérateur après application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
cargo fmt --all -- --check
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.15
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||||
|
cargo test -p ksp-onchain-transport-lib --all-targets --all-features
|
||||||
|
```
|
||||||
|
|
||||||
|
Si ce gate est propre, `pre.003` mappe cette capability depuis Config et prépare le composite/profils Raw Transaction Ingest Desk, sans frontend.
|
||||||
Reference in New Issue
Block a user