diff --git a/Cargo.toml b/Cargo.toml index 98772d4..282ed7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ # file: Cargo.toml -# version: 594 +# version: 595 [workspace] 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"] [workspace.package] -version = "0.3.15-pre.1" +version = "0.3.15-pre.2" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" diff --git a/crates/ksp-onchain-transport-lib/USAGE.md b/crates/ksp-onchain-transport-lib/USAGE.md index 3ade543..e51f46f 100644 --- a/crates/ksp-onchain-transport-lib/USAGE.md +++ b/crates/ksp-onchain-transport-lib/USAGE.md @@ -1,5 +1,5 @@ - + # Utilisation de `ksp-onchain-transport-lib` @@ -82,7 +82,14 @@ let endpoint = ksp_onchain_transport_lib::WsEndpointSettings::new( ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard, ws_url, 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 { Ok(value) => value, Err(error) => return Err(error), @@ -90,6 +97,8 @@ let session = match ksp_onchain_transport_lib::WsSession::connect(endpoint).awai 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. 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. diff --git a/crates/ksp-onchain-transport-lib/src/ws_settings.rs b/crates/ksp-onchain-transport-lib/src/ws_settings.rs index dcbaa42..2c029a4 100644 --- a/crates/ksp-onchain-transport-lib/src/ws_settings.rs +++ b/crates/ksp-onchain-transport-lib/src/ws_settings.rs @@ -1,5 +1,5 @@ // 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_COMMAND_QUEUE_CAPACITY: usize = 128; @@ -401,6 +401,7 @@ pub struct WsEndpointSettings { provider: crate::WsProviderName, cluster: crate::WsClusterName, protocol: crate::WsProtocolKind, + subscription_capabilities: std::option::Option>, url: crate::WsEndpointUrl, session: crate::WsSessionSettings, } @@ -417,7 +418,42 @@ impl WsEndpointSettings { url: crate::WsEndpointUrl, session: crate::WsSessionSettings, ) -> 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) -> 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. @@ -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()) { 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() { 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(()); } +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<()> { if value.trim().is_empty() { return ws_invalid_settings("WebSocket transport descriptor must not be empty", field); diff --git a/crates/ksp-onchain-transport-lib/tests/public_api.rs b/crates/ksp-onchain-transport-lib/tests/public_api.rs index 41c91b3..3fee06a 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: 54 +// version: 55 //! 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:")); 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)); +} diff --git a/crates/ksp-onchain-transport-lib/tests/release_completeness.rs b/crates/ksp-onchain-transport-lib/tests/release_completeness.rs index 4cdefc4..044c5e1 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: 46 +// version: 47 //! 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")); 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>")); + 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\"")); +} diff --git a/crates/ksp-onchain-transport-lib/unit_tests/ws_settings.rs b/crates/ksp-onchain-transport-lib/unit_tests/ws_settings.rs index 505b1c4..56daa6a 100644 --- a/crates/ksp-onchain-transport-lib/unit_tests/ws_settings.rs +++ b/crates/ksp-onchain-transport-lib/unit_tests/ws_settings.rs @@ -1,5 +1,5 @@ // 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 { 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("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()); +} diff --git a/deltas/0.3.15/pre.002.md b/deltas/0.3.15/pre.002.md new file mode 100644 index 0000000..645507f --- /dev/null +++ b/deltas/0.3.15/pre.002.md @@ -0,0 +1,316 @@ + + + +# 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> +``` + +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) +``` + +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.