v0.2.8-pre.005-fix.001

This commit is contained in:
2026-08-23 15:02:41 +02:00
parent 92224e5ac6
commit 53dbb5bccd
9 changed files with 264 additions and 94 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml
# version: 223
# version: 224
[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.5"
version = "0.2.8-pre.5.fix.1"
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/src/lib.rs
// version: 31
// version: 32
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -415,16 +415,6 @@ pub(crate) use self::rpc_common::decode_wire_json;
pub(crate) use self::rpc_common::parse_wire_pubkey;
/// Validates endpoint settings.
pub(crate) use self::settings::validate_endpoint_settings;
/// Crate-internal decoder for Helius transaction-subscribe acknowledgement IDs.
pub(crate) use self::ws_helius_transactions::decode_helius_transaction_subscribe_result;
/// Crate-internal decoder for Helius transaction-unsubscribe boolean results.
pub(crate) use self::ws_helius_transactions::decode_helius_transaction_unsubscribe_result;
/// Crate-internal exact Helius transaction-subscribe method descriptor.
pub(crate) use self::ws_helius_transactions::helius_transaction_subscribe_method;
/// Crate-internal exact Helius transaction-unsubscribe method descriptor.
pub(crate) use self::ws_helius_transactions::helius_transaction_unsubscribe_method;
/// Crate-internal Helius transaction-unsubscribe parameter encoder.
pub(crate) use self::ws_helius_transactions::helius_transaction_unsubscribe_params;
/// Crate-internal command surface shared by the physical session and typed subscription handle.
pub(crate) use self::ws_session::WsSessionCommand;
/// Crate-internal notification dispatch result.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/ws_helius_transactions.rs
// version: 1
// version: 2
const MAX_HELIUS_TRANSACTION_FILTER_ACCOUNTS: usize = 50_000;
@@ -325,20 +325,6 @@ impl HeliusTransactionSubscribeRequest {
}
return std::result::Result::Ok(());
}
/// Builds the exact JSON-RPC params array after deterministic validation.
#[allow(dead_code)] // Consumed by actor-owned transaction subscription registration in pre.006.
pub(crate) fn to_params(&self) -> ksp_core_lib::Result<std::vec::Vec<serde_json::Value>> {
let validation = self.validate();
if let std::result::Result::Err(error) = validation {
return std::result::Result::Err(error);
}
let mut params = std::vec![self.filter.to_json_value()];
if let std::option::Option::Some(options) = self.options {
params.push(options.to_json_value());
}
return std::result::Result::Ok(params);
}
}
impl std::fmt::Debug for HeliusTransactionSubscribeRequest {
@@ -347,21 +333,27 @@ impl std::fmt::Debug for HeliusTransactionSubscribeRequest {
}
}
/// Returns the exact Helius transaction-subscribe JSON-RPC method name.
#[allow(dead_code)] // Consumed by actor-owned transaction subscription registration in pre.006.
pub(crate) const fn helius_transaction_subscribe_method() -> &'static str {
fn helius_transaction_subscribe_params(request: &crate::HeliusTransactionSubscribeRequest) -> ksp_core_lib::Result<std::vec::Vec<serde_json::Value>> {
let validation = request.validate();
if let std::result::Result::Err(error) = validation {
return std::result::Result::Err(error);
}
let mut params = std::vec![request.filter.to_json_value()];
if let std::option::Option::Some(options) = request.options {
params.push(options.to_json_value());
}
return std::result::Result::Ok(params);
}
const fn helius_transaction_subscribe_method() -> &'static str {
return "transactionSubscribe";
}
/// Returns the exact Helius transaction-unsubscribe JSON-RPC method name.
#[allow(dead_code)] // Consumed by actor-owned transaction subscription cleanup in pre.006.
pub(crate) const fn helius_transaction_unsubscribe_method() -> &'static str {
const fn helius_transaction_unsubscribe_method() -> &'static str {
return "transactionUnsubscribe";
}
/// Decodes a successful Helius transaction-subscribe acknowledgement without exposing the remote ID publicly.
#[allow(dead_code)] // Consumed by actor-owned transaction subscription registration in pre.006.
pub(crate) fn decode_helius_transaction_subscribe_result(value: serde_json::Value) -> ksp_core_lib::Result<u64> {
fn decode_helius_transaction_subscribe_result(value: serde_json::Value) -> ksp_core_lib::Result<u64> {
return match value.as_u64() {
std::option::Option::Some(remote_id) => std::result::Result::Ok(remote_id),
std::option::Option::None => std::result::Result::Err(
@@ -371,15 +363,11 @@ pub(crate) fn decode_helius_transaction_subscribe_result(value: serde_json::Valu
};
}
/// Builds the exact Helius transaction-unsubscribe params array for one actor-owned remote subscription ID.
#[allow(dead_code)] // Consumed by actor-owned transaction subscription cleanup in pre.006.
pub(crate) fn helius_transaction_unsubscribe_params(remote_id: u64) -> std::vec::Vec<serde_json::Value> {
fn helius_transaction_unsubscribe_params(remote_id: u64) -> std::vec::Vec<serde_json::Value> {
return std::vec![serde_json::Value::Number(remote_id.into())];
}
/// Decodes the boolean Helius transaction-unsubscribe result.
#[allow(dead_code)] // Consumed by actor-owned transaction subscription cleanup in pre.006.
pub(crate) fn decode_helius_transaction_unsubscribe_result(value: serde_json::Value) -> ksp_core_lib::Result<bool> {
fn decode_helius_transaction_unsubscribe_result(value: serde_json::Value) -> ksp_core_lib::Result<bool> {
return match value.as_bool() {
std::option::Option::Some(unsubscribed) => std::result::Result::Ok(unsubscribed),
std::option::Option::None => std::result::Result::Err(

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/ws_helius_transactions.rs
// version: 2
// version: 3
use futures_util::SinkExt; // rust-rules: trait-import
use futures_util::StreamExt; // rust-rules: trait-import
@@ -91,11 +91,11 @@ fn helius_transaction_subscribe_request_serializes_complete_documented_filter_an
std::option::Option::Some(0),
);
let request = crate::HeliusTransactionSubscribeRequest::new(filter, std::option::Option::Some(options));
let params = request.to_params().expect("complete documented Helius request must validate");
let params = super::helius_transaction_subscribe_params(&request).expect("complete documented Helius request must validate");
assert_eq!(
params,
serde_json::json!([
{
std::vec![
serde_json::json!({
"vote": false,
"failed": false,
"signature": "fixture-signature-secret-canary",
@@ -103,15 +103,15 @@ fn helius_transaction_subscribe_request_serializes_complete_documented_filter_an
"accountExclude": ["SysvarC1ock11111111111111111111111111111111"],
"accountRequired": ["Vote111111111111111111111111111111111111111"],
"tokenAccounts": "balanceChanged"
},
{
}),
serde_json::json!({
"commitment": "confirmed",
"encoding": "jsonParsed",
"transactionDetails": "accounts",
"showRewards": true,
"maxSupportedTransactionVersion": 0
}
])
})
]
);
assert_eq!(request.filter().vote(), std::option::Option::Some(false));
assert_eq!(request.filter().failed(), std::option::Option::Some(false));
@@ -131,7 +131,10 @@ fn helius_transaction_subscribe_request_serializes_complete_documented_filter_an
#[test]
fn helius_transaction_request_preserves_omitted_explicit_empty_and_explicit_none_states() {
let omitted = crate::HeliusTransactionSubscribeRequest::new(crate::HeliusTransactionSubscribeFilter::default(), std::option::Option::None);
assert_eq!(omitted.to_params().expect("fully omitted optional request must validate"), serde_json::json!([{}]));
assert_eq!(
super::helius_transaction_subscribe_params(&omitted).expect("fully omitted optional request must validate"),
std::vec![serde_json::json!({})]
);
let explicit = crate::HeliusTransactionSubscribeRequest::new(
crate::HeliusTransactionSubscribeFilter::new(
std::option::Option::None,
@@ -145,8 +148,8 @@ fn helius_transaction_request_preserves_omitted_explicit_empty_and_explicit_none
std::option::Option::Some(crate::HeliusTransactionSubscribeOptions::default()),
);
assert_eq!(
explicit.to_params().expect("explicit empty Helius request states must validate"),
serde_json::json!([{"accountInclude":[],"accountExclude":[],"accountRequired":[],"tokenAccounts":"none"}, {}])
super::helius_transaction_subscribe_params(&explicit).expect("explicit empty Helius request states must validate"),
std::vec![serde_json::json!({"accountInclude":[],"accountExclude":[],"accountRequired":[],"tokenAccounts":"none"}), serde_json::json!({})]
);
}
@@ -235,21 +238,21 @@ fn helius_transaction_details_require_max_supported_version_only_for_accounts_an
#[test]
fn helius_transaction_subscribe_and_unsubscribe_control_wire_is_exact() {
assert_eq!(crate::helius_transaction_subscribe_method(), "transactionSubscribe");
assert_eq!(crate::helius_transaction_unsubscribe_method(), "transactionUnsubscribe");
assert_eq!(super::helius_transaction_subscribe_method(), "transactionSubscribe");
assert_eq!(super::helius_transaction_unsubscribe_method(), "transactionUnsubscribe");
assert_eq!(
crate::decode_helius_transaction_subscribe_result(serde_json::json!(4_743_323_479_349_712_u64)).expect("numeric ack must decode"),
super::decode_helius_transaction_subscribe_result(serde_json::json!(4_743_323_479_349_712_u64)).expect("numeric ack must decode"),
4_743_323_479_349_712
);
assert_eq!(crate::helius_transaction_unsubscribe_params(4_743_323_479_349_712), serde_json::json!([4_743_323_479_349_712_u64]));
assert!(crate::decode_helius_transaction_unsubscribe_result(serde_json::json!(true)).expect("boolean true must decode"));
assert!(!crate::decode_helius_transaction_unsubscribe_result(serde_json::json!(false)).expect("boolean false must decode"));
assert_eq!(super::helius_transaction_unsubscribe_params(4_743_323_479_349_712), std::vec![serde_json::json!(4_743_323_479_349_712_u64)]);
assert!(super::decode_helius_transaction_unsubscribe_result(serde_json::json!(true)).expect("boolean true must decode"));
assert!(!super::decode_helius_transaction_unsubscribe_result(serde_json::json!(false)).expect("boolean false must decode"));
assert_eq!(
crate::decode_helius_transaction_subscribe_result(serde_json::json!("not-an-id")).expect_err("non-numeric subscribe ack must fail").code(),
super::decode_helius_transaction_subscribe_result(serde_json::json!("not-an-id")).expect_err("non-numeric subscribe ack must fail").code(),
crate::ERROR_CODE_INVALID_RESPONSE
);
assert_eq!(
crate::decode_helius_transaction_unsubscribe_result(serde_json::json!(1)).expect_err("non-boolean unsubscribe ack must fail").code(),
super::decode_helius_transaction_unsubscribe_result(serde_json::json!(1)).expect_err("non-boolean unsubscribe ack must fail").code(),
crate::ERROR_CODE_INVALID_RESPONSE
);
}
@@ -307,20 +310,20 @@ async fn helius_transaction_control_wire_round_trips_through_shared_physical_act
std::option::Option::Some(0),
);
let request = crate::HeliusTransactionSubscribeRequest::new(filter, std::option::Option::Some(options));
let params = request.to_params().expect("typed Helius request must validate before I/O");
let params = super::helius_transaction_subscribe_params(&request).expect("typed Helius request must validate before I/O");
let subscribe_result = session
.physical_session()
.execute_json_rpc(crate::helius_transaction_subscribe_method(), params)
.execute_json_rpc(super::helius_transaction_subscribe_method(), params)
.await
.expect("transactionSubscribe acknowledgement must arrive");
let remote_id = crate::decode_helius_transaction_subscribe_result(subscribe_result).expect("transactionSubscribe id must decode");
let remote_id = super::decode_helius_transaction_subscribe_result(subscribe_result).expect("transactionSubscribe id must decode");
assert_eq!(remote_id, 4242);
let unsubscribe_result = session
.physical_session()
.execute_json_rpc(crate::helius_transaction_unsubscribe_method(), crate::helius_transaction_unsubscribe_params(remote_id))
.execute_json_rpc(super::helius_transaction_unsubscribe_method(), super::helius_transaction_unsubscribe_params(remote_id))
.await
.expect("transactionUnsubscribe acknowledgement must arrive");
assert!(crate::decode_helius_transaction_unsubscribe_result(unsubscribe_result).expect("transactionUnsubscribe boolean must decode"));
assert!(super::decode_helius_transaction_unsubscribe_result(unsubscribe_result).expect("transactionUnsubscribe boolean must decode"));
session.close().await.expect("Helius fixture session must close");
server.await.expect("local Helius transaction server must finish");
}

View File

@@ -0,0 +1,95 @@
<!-- file: deltas/0.2.8/pre.005-fix.001.md -->
<!-- version: 1 -->
# Delta `0.2.8-pre.005-fix.001` — canaris JSON, visibilité test/private et audit des chemins
## 1. Cause
Le checkpoint opérateur de `pre.005` compile le workspace normal mais échoue dès la compilation des tests Transport. Quatre assertions comparent un `Vec<serde_json::Value>` produit par les encodeurs de params à un `serde_json::Value` construit par `serde_json::json!([...])`, ce qui produit `E0277`.
Le même checkpoint révèle cinq warnings `unused import` au crate-root : cinq helpers Helius avaient été rendus `pub(crate)` et réexportés uniquement pour être appelés par les tests. Cette visibilité est contraire aux règles KSP : une visibilité n'est pas élargie pour les tests.
## 2. Correction des canaris
Les attentes de params utilisent maintenant des `Vec<Value>` explicites :
```text
transactionSubscribe complet
omission complète
états []/none explicites
transactionUnsubscribe [remote_id]
```
Le wire attendu ne change pas.
## 3. Correction de visibilité
Les helpers suivants redeviennent strictement privés au module `ws_helius_transactions` :
```text
helius_transaction_subscribe_params
helius_transaction_subscribe_method
helius_transaction_unsubscribe_method
decode_helius_transaction_subscribe_result
helius_transaction_unsubscribe_params
decode_helius_transaction_unsubscribe_result
```
Les cinq réexports `pub(crate)` de `lib.rs` sont supprimés. Les tests séparés y accèdent via `super::...`. Aucun `#[allow(dead_code)]` n'est conservé pour masquer une visibilité prématurée.
Les types réellement publics de `pre.005` restent réexportés au crate-root et les tests continuent à les consommer via `crate::Item`.
En `pre.006`, si un helper devient réellement partagé entre modules de production, il pourra être promu en `pub(crate)`, réexporté au crate-root et consommé via `crate::Item` conformément aux règles.
## 4. Durcissement des règles et de l'audit
`RULES_RUST.md` explicite désormais :
```text
private parent dans unit_tests -> super::Item obligatoire
pub/pub(crate) -> crate::Item obligatoire, jamais super:: ni nom nu
visibilité -> jamais élargie uniquement pour tester
```
`audit_rust_export_completeness.py` ajoute des canaris mécaniques bidirectionnels pour les fichiers `unit_tests/` rattachés :
```text
RUST-IMPORT-204 private parent appelé sans super::
RUST-IMPORT-205 visible parent appelé sans crate-root
RUST-IMPORT-202 visible parent appelé via super:: (déjà présent)
```
## 5. Version
```text
workspace.package.version = 0.2.8-pre.5.fix.1
commit attendu = v0.2.8-pre.005-fix.001
tag prerelease = aucun
```
## 6. Fichiers modifiés
```text
Cargo.toml
crates/ksp-onchain-transport-lib/src/lib.rs
crates/ksp-onchain-transport-lib/src/ws_helius_transactions.rs
crates/ksp-onchain-transport-lib/unit_tests/ws_helius_transactions.rs
scripts/audit_rust_export_completeness.py
docs/rules/RULES_RUST.md
docs/plans/015-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET_PLAN.md
docs/validation/011-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET.md
deltas/0.2.8/pre.005-fix.001.md
```
## 7. Gate opérateur
```bash
cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-onchain-transport-lib
cargo test --workspace
```
Le fix reste `PREPARED` jusqu'à ce gate.

View File

@@ -1,9 +1,9 @@
<!-- file: docs/plans/015-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET_PLAN.md -->
<!-- version: 13 -->
<!-- version: 14 -->
# Plan `0.2.8` — Helius LaserStream WebSocket
> **Statut : `pre.004` et ses deux fixes sont validés par checkpoint opérateur complet. `0.2.8-pre.005` est préparé : contrat typed `transactionSubscribe`, filtres/options Helius, `tokenAccounts`, limites 50k et wire `transactionUnsubscribe`, sans exposer encore de handle transaction avant l'intégration actor de `pre.006`.**
> **Statut : `pre.004` et ses deux fixes sont validés. Le premier checkpoint de `0.2.8-pre.005` a révélé quatre erreurs de type dans les canaris JSON et une visibilité `pub(crate)` injustifiée pour des helpers utilisés uniquement par le module/tests. `0.2.8-pre.005-fix.001` est préparé pour corriger ces deux points et durcir l'audit des accès `super::PrivateItem` / `crate::VisibleItem`.**
## 1. Objet, base et état courant
@@ -30,10 +30,11 @@ pre.003 six familles standard Helius validées
pre.004 Config V2 Helius validé
pre.004-fix.001 redaction segmentaire + couverture Devnet Helius validées
pre.004-fix.002 provenance composée validée
pre.005 contrat typed transactionSubscribe/unsubscribe préparé
pre.005 contrat typed transactionSubscribe/unsubscribe ; fix requis après checkpoint
pre.005-fix.001 correction types de canaris + visibilité/tests/règles préparée
workspace.package.version courant = 0.2.8-pre.5
commit attendu = v0.2.8-pre.005
workspace.package.version courant = 0.2.8-pre.5.fix.1
commit attendu = v0.2.8-pre.005-fix.001
aucun tag prerelease
```
@@ -57,8 +58,9 @@ pre.004 DONE — Config V2 helius_laserstream + schema/fixtures + mapping Confi
+ secret/redaction Helius mainnet/devnet validés
fix.001 DONE — redaction segmentaire corrigée + représentation Devnet Helius ajoutée
fix.002 DONE — provenance composée `DocumentLiteral` + `EnvironmentProcess` corrigée
pre.005 PREPARED — transactionSubscribe request typed + filters/options/tokenAccounts + transactionUnsubscribe
pre.005 FIX REQUIRED — transactionSubscribe request typed + filters/options/tokenAccounts + transactionUnsubscribe
+ bounds 50k + maxSupportedTransactionVersion conditionnel ; live handle différé à pre.006
fix.001 PREPARED — assertions Vec<Value>/Value corrigées + helpers test-only privés + audit super/crate durci
pre.006 transactionNotification + actor integration + reconnect/resubscribe/unsubscribe races
+ late notifications + backpressure ciblée
pre.007 heartbeat Helius WebSocket/idle + timers + interaction reconnect/control frames/shutdown
@@ -995,3 +997,19 @@ new dependency aucune
```
La séparation `pre.005` / `pre.006` est normative : publier dès maintenant un `transaction_subscribe()` public sans registry de notification/reconnect produirait un handle transitoire qui perdrait les notifications. Le contrat public de requête est donc stable dès `pre.005`, tandis que l'abonnement live est ajouté atomiquement avec l'actor integration en `pre.006`.
### Checkpoint `pre.005` et correctif `pre.005-fix.001`
Le premier checkpoint opérateur de `pre.005` a donné :
```text
cargo fmt --all OK
python3 scripts/audit_rust_workspace_rules.py clean mais incomplet pour la règle test/private
cargo check --workspace OK avec 5 warnings unused pub(crate) reexports
cargo clippy --workspace --all-targets FAIL — 4 comparaisons Vec<Value> / Value
cargo test -p ksp-onchain-transport-lib FAIL — mêmes 4 erreurs de type
```
Le correctif ne change ni le contrat Helius public ni le wire. Il applique la règle de visibilité KSP : un helper utilisé seulement par son module et son sous-module de tests reste strictement privé ; le test l'appelle via `super::Item`. Les éléments `pub` et `pub(crate)` restent réexportés et consommés via `crate::Item`. Les helpers transaction wire de `pre.005` ne deviendront `pub(crate)` qu'en `pre.006` si l'actor les consomme réellement.
Le script `audit_rust_export_completeness.py` est étendu pour détecter dans les `unit_tests/` séparés les accès non qualifiés aux items privés du parent et les accès non canoniques aux items visibles.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/rules/RULES_RUST.md -->
<!-- version: 8 -->
<!-- version: 9 -->
# Règles Rust générales
@@ -35,7 +35,7 @@ Les règles `RUST-*` s'appliquent à tous les fichiers Rust de KSP : crates, sou
- **RUST-IMPORT-009** — Dans sa propre crate, un élément `pub` ou `pub(crate)` partagé est appelé via `crate::Item`, y compris depuis son module de déclaration lorsque le contrat est crate-wide.
- **RUST-IMPORT-010** — Un chemin `crate::module::Item` est interdit pour un élément partagé `pub`/`pub(crate)` qui peut être consommé via le crate-root. Le chemin du module interne n'est pas une façade.
- **RUST-IMPORT-011** — Un élément strictement privé à un module n'est pas réexporté et est appelé par son nom local dans ce module.
- **RUST-IMPORT-012** — Dans un sous-module de tests, `super::Item` est réservé à un élément strictement privé du module parent. Un élément `pub` ou `pub(crate)` continue d'être appelé via `crate::Item`.
- **RUST-IMPORT-012** — Dans un fichier `unit_tests/...` rattaché au module parent, tout élément strictement privé du parent est appelé explicitement via `super::Item`. Inversement, un élément `pub` ou `pub(crate)` n'est jamais appelé via `super::` ni par un nom nu : il continue d'être appelé via le crate-root `crate::Item`, y compris lorsque le test est rattaché à son module de déclaration.
- **RUST-IMPORT-013** — Les réexports internes commencent par `self::`. Un réexport d'une crate externe peut utiliser directement le chemin externe canonique.
- **RUST-IMPORT-014** — Un export correspond à une ligne de réexport distincte ; les accolades ne servent jamais à regrouper une façade.
- **RUST-IMPORT-015** — Une crate externe rendue publique uniquement pour l'hygiène d'une macro exportée conserve son nom canonique, porte `#[doc(hidden)]` et ne devient pas une API de consommation. `ksp-logging-lib::tracing` est ce bridge technique pour les macros Logging ; les autres crates KSP n'y accèdent jamais directement.
@@ -48,6 +48,7 @@ Les règles `RUST-*` s'appliquent à tous les fichiers Rust de KSP : crates, sou
- **RUST-API-004** — Un élément `pub(crate)` consommé hors de son module est réexporté au crate-root via `pub(crate) use` puis appelé via `crate::Item`.
- **RUST-API-005** — Les chemins internes de modules ne constituent jamais une API stable.
- **RUST-API-006** — Si deux éléments crate-wide auraient le même nom au crate-root, ils sont renommés dans leurs modules propriétaires avec des noms canoniques non ambigus ; un alias de réexport n'est pas utilisé pour masquer la collision.
- **RUST-API-007** — La visibilité d'un item n'est jamais élargie uniquement pour permettre son test. Un helper utilisé seulement par son module et ses `unit_tests/` reste privé et les tests y accèdent via `super::Item`; il ne devient `pub(crate)` que lorsqu'un autre module de production le consomme réellement, auquel cas `RUST-API-004` et `RUST-IMPORT-009` s'appliquent.
## Formatage, blocs et ordre
@@ -102,7 +103,7 @@ Les règles `RUST-*` s'appliquent à tous les fichiers Rust de KSP : crates, sou
- **RUST-AUDIT-001** — `scripts/audit_rust_workspace_rules.py` est le point d'entrée obligatoire de l'audit Rust KSP. Il exécute les audits généraux, la complétude des réexports/chemins et les frontières KSP sans fusionner leurs responsabilités.
- **RUST-AUDIT-002** — L'audit est dependency-free côté Python standard et échoue avec un code non nul dès qu'une violation mécanique est détectée.
- **RUST-AUDIT-003** — L'audit contrôle au minimum : headers/version/newline, lints de crate-root, visibilité interdite, `use`/aliases/groupes/globs/scope, rustdocs visibles, structure des blocs de réexports, ordre des imports de traits et constantes lorsque KSP le possède, lignes vides dans fonctions/structs/enums, complétude des réexports crate-root, chemins `crate::module::Item`, usage de `super::` dans les tests séparés et frontières KSP directement vérifiables. L'ordre intra-bloc des `use`/réexports reste la responsabilité canonique de `cargo fmt --all` et n'est pas réimplémenté par le script.
- **RUST-AUDIT-003** — L'audit contrôle au minimum : headers/version/newline, lints de crate-root, visibilité interdite, `use`/aliases/groupes/globs/scope, rustdocs visibles, structure des blocs de réexports, ordre des imports de traits et constantes lorsque KSP le possède, lignes vides dans fonctions/structs/enums, complétude des réexports crate-root, chemins `crate::module::Item`, accès `super::PrivateItem` et `crate::VisibleItem` dans les tests séparés, ainsi que les frontières KSP directement vérifiables. L'ordre intra-bloc des `use`/réexports reste la responsabilité canonique de `cargo fmt --all` et n'est pas réimplémenté par le script.
- **RUST-AUDIT-004** — Les règles contextuelles qui ne peuvent pas être prouvées sans interpréter la sémantique restent des critères de revue humaine ; le script ne doit pas produire de faux sentiment de complétude.
## Contrôle avant livraison

View File

@@ -1,9 +1,9 @@
<!-- file: docs/validation/011-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET.md -->
<!-- version: 13 -->
<!-- version: 14 -->
# Validation `0.2.8` — Helius LaserStream WebSocket
> **Statut : `pre.004` + `fix.001` + `fix.002` validés par checkpoint opérateur complet. `pre.005` est préparé : contrat public typed de requête Helius transaction, validations 50k/version, `tokenAccounts`, wire subscribe/unsubscribe et canari actor local ; le handle live reste volontairement différé à `pre.006`.**
> **Statut : `pre.004` + ses fixes sont validés. Le checkpoint `pre.005` échoue uniquement sur quatre comparaisons de canaris `Vec<Value>`/`Value` et révèle cinq réexports `pub(crate)` test-only inutilisés. `pre.005-fix.001` corrige les canaris, rétablit les visibilités privées et durcit l'audit `super::`/`crate::`.**
## 1. Références
@@ -21,6 +21,7 @@ pre.004 deltas/0.2.8/pre.004.md
pre.004 redaction/devnet fix deltas/0.2.8/pre.004-fix.001.md
pre.004 provenance fix deltas/0.2.8/pre.004-fix.002.md
pre.005 deltas/0.2.8/pre.005.md
pre.005 visibility/test fix deltas/0.2.8/pre.005-fix.001.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
@@ -134,22 +135,22 @@ Verdict : **gate `0.2.8-pre.001` positif ; `pre.002` + `fix.001` et `pre.003` so
### 4.2 Invariants architecture
| Critère | Décision | Preuve cible | État |
|-------------------------------------|------------------------------------------------------------------|---------------------------------|--------|
| Critère | Décision | Preuve cible | État |
|-------------------------------------|------------------------------------------------------------------|---------------------------------|--------------------|
| actor physique | un seul `WsSession` actor partagé | source/runtime canary | pre.002 implémenté |
| façade standard | `SolanaStandardWsSession` | public API canary | pre.002 implémenté |
| façade Helius | `HeliusLaserStreamWsSession` | public API canary | pre.002 implémenté |
| namespace LaserStream WS | `WsProtocolKind` + `ws_endpoints` possèdent `helius_laserstream` | API/Config/docs canary | pre.004 validé |
| LaserStream gRPC | backend/type/Config distincts, hors `0.2.8` | absence de réutilisation WS | décidé |
| namespace LaserStream WS | `WsProtocolKind` + `ws_endpoints` possèdent `helius_laserstream` | API/Config/docs canary | pre.004 validé |
| 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 | pre.003 validé |
| standard Helius commun | délégation vers le même wire/actor | exact fixture | pre.003 validé |
| DTO duplication | seulement si wire/sémantique divergent | public/source audit | décidé |
| Config direction | Config -> Transport uniquement | ownership tests | pre.004 validé |
| heartbeat | Helius-only, actor commun | deterministic timers | décidé |
| secret | query URL derrière `WsEndpointUrl` | redaction canaries | pre.004 validé |
| new Rust dependency | aucune | manifest/tree audit | décidé |
| Helius unsupported | absent de la façade | compile-fail/API absence canary | pre.003 validé |
| standard Helius commun | délégation vers le même wire/actor | exact fixture | pre.003 validé |
| DTO duplication | seulement si wire/sémantique divergent | public/source audit | décidé |
| Config direction | Config -> Transport uniquement | ownership tests | pre.004 validé |
| heartbeat | Helius-only, actor commun | deterministic timers | décidé |
| secret | query URL derrière `WsEndpointUrl` | redaction canaries | pre.004 validé |
| new Rust dependency | aucune | manifest/tree audit | décidé |
## 5. Contrat `transactionSubscribe` à valider
@@ -339,6 +340,7 @@ 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. Gates opérateur `pre.004` / `fix.001` / `fix.002`
Premier passage `pre.004` reçu le 2026-08-23 :
@@ -455,3 +457,34 @@ Gates opérateur `pre.005` à exécuter :
```
Verdict courant : **`pre.004` et ses fixes DONE ; `pre.005` PREPARED.**
## 14. Gate `pre.005` et `pre.005-fix.001`
Checkpoint opérateur initial `pre.005` :
```text
[x] cargo fmt --all
[x] audit Rust général/workspace
[x] cargo check --workspace compile
[!] cargo check — 5 warnings de réexports pub(crate) utilisés uniquement par les tests
[ ] cargo clippy --workspace --all-targets — 4 erreurs E0277 Vec<Value> == Value
[ ] cargo test -p ksp-onchain-transport-lib — mêmes 4 erreurs E0277
[ ] cargo test --workspace — non retenu comme preuve de fermeture tant que Transport ne compile pas en tests
```
Critères du fix :
```text
[ ] les quatre attentes JSON comparent Vec<Value> à Vec<Value>
[ ] les helpers wire utilisés uniquement par le module/tests sont strictement privés
[ ] aucun pub(crate) test-only ni #[allow(dead_code)] compensatoire
[ ] unit_tests utilise super::Helper pour ces items privés
[ ] unit_tests continue d'utiliser crate::Item pour tous les éléments pub/pub(crate)
[ ] l'audit détecte private parent utilisé sans super::
[ ] l'audit détecte visible parent utilisé via super:: ou nom nu
[ ] cargo check sans les cinq warnings pre.005
[ ] clippy Transport/workspace vert
[ ] tests Transport/workspace verts
```
Verdict courant : **`pre.005` FIX REQUIRED ; `pre.005-fix.001` PREPARED.**

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3
# file: scripts/audit_rust_export_completeness.py
# version: 3
# version: 4
"""Audit crate-root export completeness and canonical same-crate paths."""
@@ -32,6 +32,7 @@ class Declaration:
module: str
name: str
visibility: str
kind: str
path: pathlib.Path
line: int
@@ -62,17 +63,47 @@ def declaration_candidates(crate: pathlib.Path, path: pathlib.Path) -> list[Decl
depths = line_depths(mask_rust_source(text))
found: list[Declaration] = []
pattern = re.compile(
r"^\s*(pub(?:\(crate\))?)\s+(?:(?:async|unsafe|const)\s+)*(?:const|static|type|struct|enum|trait|union|fn)\s+([A-Za-z_][A-Za-z0-9_]*)"
r"^\s*(pub(?:\(crate\))?)\s+(?:(?:async|unsafe|const)\s+)*(const|static|type|struct|enum|trait|union|fn)\s+([A-Za-z_][A-Za-z0-9_]*)"
)
for idx, line in enumerate(lines, 1):
if depths[idx - 1] != 0:
continue
match = pattern.match(line)
if match is not None:
found.append(Declaration(module_path(crate, path), match.group(2), match.group(1), path, idx))
found.append(Declaration(module_path(crate, path), match.group(3), match.group(1), match.group(2), path, idx))
return found
def private_declaration_candidates(crate: pathlib.Path, path: pathlib.Path) -> list[Declaration]:
"""Return module-level strictly private declarations from one source module."""
text = path.read_text(encoding="utf-8")
lines = text.splitlines()
depths = line_depths(mask_rust_source(text))
found: list[Declaration] = []
pattern = re.compile(r"^\s*(?:(?:async|unsafe|const)\s+)*(const|static|type|struct|enum|trait|union|fn)\s+([A-Za-z_][A-Za-z0-9_]*)")
for idx, line in enumerate(lines, 1):
if depths[idx - 1] != 0:
continue
if line.lstrip().startswith(("pub ", "pub(crate) ")):
continue
match = pattern.match(line)
if match is not None:
found.append(Declaration(module_path(crate, path), match.group(2), "private", match.group(1), path, idx))
return found
def unqualified_test_reference_pattern(declaration: Declaration) -> re.Pattern[str]:
"""Return a conservative pattern for one unqualified parent-item reference in a separated unit test."""
name = re.escape(declaration.name)
if declaration.kind == "fn":
return re.compile(rf"(?<![\w:.]){name}\s*\(")
if declaration.kind in {"const", "static", "type", "struct", "enum", "trait", "union"}:
return re.compile(rf"(?<![\w:]){name}\b")
return re.compile(rf"(?<![\w:]){name}\b")
def root_exports(crate: pathlib.Path) -> dict[tuple[str, str], str]:
"""Return explicit crate-root re-exports keyed by source module and symbol."""
@@ -160,10 +191,12 @@ def audit_crate(workspace: pathlib.Path, crate: pathlib.Path) -> list[Candidate]
exports = root_exports(crate)
tests = unit_test_parents(crate)
declarations: dict[tuple[str, str], Declaration] = {}
private_declarations_by_source: dict[pathlib.Path, dict[str, Declaration]] = {}
candidates: list[Candidate] = []
for path in sorted((crate / "src").rglob("*.rs")):
if path == crate_root:
continue
private_declarations_by_source[path.resolve()] = {item.name: item for item in private_declaration_candidates(crate, path)}
for declaration in declaration_candidates(crate, path):
key = (declaration.module, declaration.name)
declarations[key] = declaration
@@ -198,19 +231,28 @@ def audit_crate(workspace: pathlib.Path, crate: pathlib.Path) -> list[Candidate]
if symbol not in root_symbols:
candidates.append(Candidate("RUST-IMPORT-203", relative, idx, f"`crate::{symbol}` does not resolve to a declared/re-exported crate-root symbol"))
# `super::Item` is reserved to strictly private parent items in separated unit tests.
# Separated unit tests use `super::Item` only for strictly private parent items; visible items use the crate-root façade.
declarations_by_source: dict[pathlib.Path, dict[str, Declaration]] = {}
for declaration in declarations.values():
declarations_by_source.setdefault(declaration.path.resolve(), {})[declaration.name] = declaration
super_pattern = re.compile(r"\bsuper::([A-Za-z_][A-Za-z0-9_]*)")
for test, parent in tests.items():
parent_declarations = declarations_by_source.get(parent, {})
parent_private_declarations = private_declarations_by_source.get(parent, {})
relative = test.relative_to(workspace).as_posix()
for idx, line in enumerate(test.read_text(encoding="utf-8").splitlines(), 1):
text = test.read_text(encoding="utf-8")
masked_lines = mask_rust_source(text).splitlines()
for idx, line in enumerate(masked_lines, 1):
for match in super_pattern.finditer(line):
declaration = parent_declarations.get(match.group(1))
if declaration is not None and declaration.visibility in {"pub", "pub(crate)"}:
candidates.append(Candidate("RUST-IMPORT-202", relative, idx, f"`super::{declaration.name}` targets {declaration.visibility}; use crate-root `crate::{declaration.name}`"))
for declaration in parent_private_declarations.values():
if unqualified_test_reference_pattern(declaration).search(line) is not None:
candidates.append(Candidate("RUST-IMPORT-204", relative, idx, f"strictly private parent item `{declaration.name}` must be accessed as `super::{declaration.name}` in separated unit tests"))
for declaration in parent_declarations.values():
if unqualified_test_reference_pattern(declaration).search(line) is not None:
candidates.append(Candidate("RUST-IMPORT-205", relative, idx, f"{declaration.visibility} parent item `{declaration.name}` must be accessed through crate-root `crate::{declaration.name}` in separated unit tests"))
return candidates