v0.3.15-pre.002

This commit is contained in:
2026-09-13 06:07:42 +02:00
parent 84ef8bb1f1
commit 8ea261d3a4
7 changed files with 532 additions and 9 deletions

View File

@@ -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"

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-onchain-transport-lib/USAGE.md -->
<!-- version: 26 -->
<!-- version: 27 -->
# 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.

View File

@@ -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<std::vec::Vec<crate::WsSubscriptionKind>>,
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<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.
@@ -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);

View File

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

View File

@@ -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<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\""));
}

View File

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

316
deltas/0.3.15/pre.002.md Normal file
View 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.