v0.2.8-pre.005-fix.001
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 223
|
# version: 224
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
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"]
|
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]
|
[workspace.package]
|
||||||
version = "0.2.8-pre.5"
|
version = "0.2.8-pre.5.fix.1"
|
||||||
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/src/lib.rs
|
// file: crates/ksp-onchain-transport-lib/src/lib.rs
|
||||||
// version: 31
|
// version: 32
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![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;
|
pub(crate) use self::rpc_common::parse_wire_pubkey;
|
||||||
/// Validates endpoint settings.
|
/// Validates endpoint settings.
|
||||||
pub(crate) use self::settings::validate_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.
|
/// Crate-internal command surface shared by the physical session and typed subscription handle.
|
||||||
pub(crate) use self::ws_session::WsSessionCommand;
|
pub(crate) use self::ws_session::WsSessionCommand;
|
||||||
/// Crate-internal notification dispatch result.
|
/// Crate-internal notification dispatch result.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/src/ws_helius_transactions.rs
|
// file: crates/ksp-onchain-transport-lib/src/ws_helius_transactions.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
const MAX_HELIUS_TRANSACTION_FILTER_ACCOUNTS: usize = 50_000;
|
const MAX_HELIUS_TRANSACTION_FILTER_ACCOUNTS: usize = 50_000;
|
||||||
|
|
||||||
@@ -325,20 +325,6 @@ impl HeliusTransactionSubscribeRequest {
|
|||||||
}
|
}
|
||||||
return std::result::Result::Ok(());
|
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 {
|
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.
|
fn helius_transaction_subscribe_params(request: &crate::HeliusTransactionSubscribeRequest) -> ksp_core_lib::Result<std::vec::Vec<serde_json::Value>> {
|
||||||
#[allow(dead_code)] // Consumed by actor-owned transaction subscription registration in pre.006.
|
let validation = request.validate();
|
||||||
pub(crate) const fn helius_transaction_subscribe_method() -> &'static str {
|
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";
|
return "transactionSubscribe";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the exact Helius transaction-unsubscribe JSON-RPC method name.
|
const fn helius_transaction_unsubscribe_method() -> &'static str {
|
||||||
#[allow(dead_code)] // Consumed by actor-owned transaction subscription cleanup in pre.006.
|
|
||||||
pub(crate) const fn helius_transaction_unsubscribe_method() -> &'static str {
|
|
||||||
return "transactionUnsubscribe";
|
return "transactionUnsubscribe";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decodes a successful Helius transaction-subscribe acknowledgement without exposing the remote ID publicly.
|
fn decode_helius_transaction_subscribe_result(value: serde_json::Value) -> ksp_core_lib::Result<u64> {
|
||||||
#[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> {
|
|
||||||
return match value.as_u64() {
|
return match value.as_u64() {
|
||||||
std::option::Option::Some(remote_id) => std::result::Result::Ok(remote_id),
|
std::option::Option::Some(remote_id) => std::result::Result::Ok(remote_id),
|
||||||
std::option::Option::None => std::result::Result::Err(
|
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.
|
fn helius_transaction_unsubscribe_params(remote_id: u64) -> std::vec::Vec<serde_json::Value> {
|
||||||
#[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> {
|
|
||||||
return std::vec![serde_json::Value::Number(remote_id.into())];
|
return std::vec![serde_json::Value::Number(remote_id.into())];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decodes the boolean Helius transaction-unsubscribe result.
|
fn decode_helius_transaction_unsubscribe_result(value: serde_json::Value) -> ksp_core_lib::Result<bool> {
|
||||||
#[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> {
|
|
||||||
return match value.as_bool() {
|
return match value.as_bool() {
|
||||||
std::option::Option::Some(unsubscribed) => std::result::Result::Ok(unsubscribed),
|
std::option::Option::Some(unsubscribed) => std::result::Result::Ok(unsubscribed),
|
||||||
std::option::Option::None => std::result::Result::Err(
|
std::option::Option::None => std::result::Result::Err(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/unit_tests/ws_helius_transactions.rs
|
// 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::SinkExt; // rust-rules: trait-import
|
||||||
use futures_util::StreamExt; // 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),
|
std::option::Option::Some(0),
|
||||||
);
|
);
|
||||||
let request = crate::HeliusTransactionSubscribeRequest::new(filter, std::option::Option::Some(options));
|
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!(
|
assert_eq!(
|
||||||
params,
|
params,
|
||||||
serde_json::json!([
|
std::vec![
|
||||||
{
|
serde_json::json!({
|
||||||
"vote": false,
|
"vote": false,
|
||||||
"failed": false,
|
"failed": false,
|
||||||
"signature": "fixture-signature-secret-canary",
|
"signature": "fixture-signature-secret-canary",
|
||||||
@@ -103,15 +103,15 @@ fn helius_transaction_subscribe_request_serializes_complete_documented_filter_an
|
|||||||
"accountExclude": ["SysvarC1ock11111111111111111111111111111111"],
|
"accountExclude": ["SysvarC1ock11111111111111111111111111111111"],
|
||||||
"accountRequired": ["Vote111111111111111111111111111111111111111"],
|
"accountRequired": ["Vote111111111111111111111111111111111111111"],
|
||||||
"tokenAccounts": "balanceChanged"
|
"tokenAccounts": "balanceChanged"
|
||||||
},
|
}),
|
||||||
{
|
serde_json::json!({
|
||||||
"commitment": "confirmed",
|
"commitment": "confirmed",
|
||||||
"encoding": "jsonParsed",
|
"encoding": "jsonParsed",
|
||||||
"transactionDetails": "accounts",
|
"transactionDetails": "accounts",
|
||||||
"showRewards": true,
|
"showRewards": true,
|
||||||
"maxSupportedTransactionVersion": 0
|
"maxSupportedTransactionVersion": 0
|
||||||
}
|
})
|
||||||
])
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(request.filter().vote(), std::option::Option::Some(false));
|
assert_eq!(request.filter().vote(), std::option::Option::Some(false));
|
||||||
assert_eq!(request.filter().failed(), 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]
|
#[test]
|
||||||
fn helius_transaction_request_preserves_omitted_explicit_empty_and_explicit_none_states() {
|
fn helius_transaction_request_preserves_omitted_explicit_empty_and_explicit_none_states() {
|
||||||
let omitted = crate::HeliusTransactionSubscribeRequest::new(crate::HeliusTransactionSubscribeFilter::default(), std::option::Option::None);
|
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(
|
let explicit = crate::HeliusTransactionSubscribeRequest::new(
|
||||||
crate::HeliusTransactionSubscribeFilter::new(
|
crate::HeliusTransactionSubscribeFilter::new(
|
||||||
std::option::Option::None,
|
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()),
|
std::option::Option::Some(crate::HeliusTransactionSubscribeOptions::default()),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
explicit.to_params().expect("explicit empty Helius request states must validate"),
|
super::helius_transaction_subscribe_params(&explicit).expect("explicit empty Helius request states must validate"),
|
||||||
serde_json::json!([{"accountInclude":[],"accountExclude":[],"accountRequired":[],"tokenAccounts":"none"}, {}])
|
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]
|
#[test]
|
||||||
fn helius_transaction_subscribe_and_unsubscribe_control_wire_is_exact() {
|
fn helius_transaction_subscribe_and_unsubscribe_control_wire_is_exact() {
|
||||||
assert_eq!(crate::helius_transaction_subscribe_method(), "transactionSubscribe");
|
assert_eq!(super::helius_transaction_subscribe_method(), "transactionSubscribe");
|
||||||
assert_eq!(crate::helius_transaction_unsubscribe_method(), "transactionUnsubscribe");
|
assert_eq!(super::helius_transaction_unsubscribe_method(), "transactionUnsubscribe");
|
||||||
assert_eq!(
|
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
|
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_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!(crate::decode_helius_transaction_unsubscribe_result(serde_json::json!(true)).expect("boolean true must decode"));
|
assert!(super::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!(!super::decode_helius_transaction_unsubscribe_result(serde_json::json!(false)).expect("boolean false must decode"));
|
||||||
assert_eq!(
|
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
|
crate::ERROR_CODE_INVALID_RESPONSE
|
||||||
);
|
);
|
||||||
assert_eq!(
|
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
|
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),
|
std::option::Option::Some(0),
|
||||||
);
|
);
|
||||||
let request = crate::HeliusTransactionSubscribeRequest::new(filter, std::option::Option::Some(options));
|
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
|
let subscribe_result = session
|
||||||
.physical_session()
|
.physical_session()
|
||||||
.execute_json_rpc(crate::helius_transaction_subscribe_method(), params)
|
.execute_json_rpc(super::helius_transaction_subscribe_method(), params)
|
||||||
.await
|
.await
|
||||||
.expect("transactionSubscribe acknowledgement must arrive");
|
.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);
|
assert_eq!(remote_id, 4242);
|
||||||
let unsubscribe_result = session
|
let unsubscribe_result = session
|
||||||
.physical_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
|
.await
|
||||||
.expect("transactionUnsubscribe acknowledgement must arrive");
|
.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");
|
session.close().await.expect("Helius fixture session must close");
|
||||||
server.await.expect("local Helius transaction server must finish");
|
server.await.expect("local Helius transaction server must finish");
|
||||||
}
|
}
|
||||||
|
|||||||
95
deltas/0.2.8/pre.005-fix.001.md
Normal file
95
deltas/0.2.8/pre.005-fix.001.md
Normal 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.
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<!-- file: docs/plans/015-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET_PLAN.md -->
|
<!-- file: docs/plans/015-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET_PLAN.md -->
|
||||||
<!-- version: 13 -->
|
<!-- version: 14 -->
|
||||||
|
|
||||||
# Plan `0.2.8` — Helius LaserStream WebSocket
|
# 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
|
## 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 Config V2 Helius validé
|
||||||
pre.004-fix.001 redaction segmentaire + couverture Devnet Helius validées
|
pre.004-fix.001 redaction segmentaire + couverture Devnet Helius validées
|
||||||
pre.004-fix.002 provenance composée validée
|
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
|
workspace.package.version courant = 0.2.8-pre.5.fix.1
|
||||||
commit attendu = v0.2.8-pre.005
|
commit attendu = v0.2.8-pre.005-fix.001
|
||||||
aucun tag prerelease
|
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
|
+ secret/redaction Helius mainnet/devnet validés
|
||||||
fix.001 DONE — redaction segmentaire corrigée + représentation Devnet Helius ajoutée
|
fix.001 DONE — redaction segmentaire corrigée + représentation Devnet Helius ajoutée
|
||||||
fix.002 DONE — provenance composée `DocumentLiteral` + `EnvironmentProcess` corrigé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
|
+ 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
|
pre.006 transactionNotification + actor integration + reconnect/resubscribe/unsubscribe races
|
||||||
+ late notifications + backpressure ciblée
|
+ late notifications + backpressure ciblée
|
||||||
pre.007 heartbeat Helius WebSocket/idle + timers + interaction reconnect/control frames/shutdown
|
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`.
|
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.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/rules/RULES_RUST.md -->
|
<!-- file: docs/rules/RULES_RUST.md -->
|
||||||
<!-- version: 8 -->
|
<!-- version: 9 -->
|
||||||
|
|
||||||
# Règles Rust générales
|
# 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-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-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-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-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-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.
|
- **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-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-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-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
|
## 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-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-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.
|
- **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
|
## Contrôle avant livraison
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<!-- file: docs/validation/011-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET.md -->
|
<!-- file: docs/validation/011-V0_2_8_HELIUS_LASERSTREAM_WEBSOCKET.md -->
|
||||||
<!-- version: 13 -->
|
<!-- version: 14 -->
|
||||||
|
|
||||||
# Validation `0.2.8` — Helius LaserStream WebSocket
|
# 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
|
## 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 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.004 provenance fix deltas/0.2.8/pre.004-fix.002.md
|
||||||
pre.005 deltas/0.2.8/pre.005.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
|
validation standard WS docs/validation/010-V0_2_7_ONCHAIN_WEBSOCKET.md
|
||||||
HTTP compliance docs/validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.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
|
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
|
### 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é |
|
| 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 standard | `SolanaStandardWsSession` | public API canary | pre.002 implémenté |
|
||||||
| façade Helius | `HeliusLaserStreamWsSession` | 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é |
|
| 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é |
|
| 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é |
|
| 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é |
|
| 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é |
|
| 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é |
|
| 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é |
|
| DTO duplication | seulement si wire/sémantique divergent | public/source audit | décidé |
|
||||||
| Config direction | Config -> Transport uniquement | ownership tests | pre.004 validé |
|
| Config direction | Config -> Transport uniquement | ownership tests | pre.004 validé |
|
||||||
| heartbeat | Helius-only, actor commun | deterministic timers | décidé |
|
| heartbeat | Helius-only, actor commun | deterministic timers | décidé |
|
||||||
| secret | query URL derrière `WsEndpointUrl` | redaction canaries | pre.004 validé |
|
| secret | query URL derrière `WsEndpointUrl` | redaction canaries | pre.004 validé |
|
||||||
| new Rust dependency | aucune | manifest/tree audit | décidé |
|
| new Rust dependency | aucune | manifest/tree audit | décidé |
|
||||||
|
|
||||||
## 5. Contrat `transactionSubscribe` à valider
|
## 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.
|
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`
|
## 12. Gates opérateur `pre.004` / `fix.001` / `fix.002`
|
||||||
|
|
||||||
Premier passage `pre.004` reçu le 2026-08-23 :
|
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.**
|
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.**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# file: scripts/audit_rust_export_completeness.py
|
# file: scripts/audit_rust_export_completeness.py
|
||||||
# version: 3
|
# version: 4
|
||||||
|
|
||||||
"""Audit crate-root export completeness and canonical same-crate paths."""
|
"""Audit crate-root export completeness and canonical same-crate paths."""
|
||||||
|
|
||||||
@@ -32,6 +32,7 @@ class Declaration:
|
|||||||
module: str
|
module: str
|
||||||
name: str
|
name: str
|
||||||
visibility: str
|
visibility: str
|
||||||
|
kind: str
|
||||||
path: pathlib.Path
|
path: pathlib.Path
|
||||||
line: int
|
line: int
|
||||||
|
|
||||||
@@ -62,17 +63,47 @@ def declaration_candidates(crate: pathlib.Path, path: pathlib.Path) -> list[Decl
|
|||||||
depths = line_depths(mask_rust_source(text))
|
depths = line_depths(mask_rust_source(text))
|
||||||
found: list[Declaration] = []
|
found: list[Declaration] = []
|
||||||
pattern = re.compile(
|
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):
|
for idx, line in enumerate(lines, 1):
|
||||||
if depths[idx - 1] != 0:
|
if depths[idx - 1] != 0:
|
||||||
continue
|
continue
|
||||||
match = pattern.match(line)
|
match = pattern.match(line)
|
||||||
if match is not None:
|
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
|
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]:
|
def root_exports(crate: pathlib.Path) -> dict[tuple[str, str], str]:
|
||||||
"""Return explicit crate-root re-exports keyed by source module and symbol."""
|
"""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)
|
exports = root_exports(crate)
|
||||||
tests = unit_test_parents(crate)
|
tests = unit_test_parents(crate)
|
||||||
declarations: dict[tuple[str, str], Declaration] = {}
|
declarations: dict[tuple[str, str], Declaration] = {}
|
||||||
|
private_declarations_by_source: dict[pathlib.Path, dict[str, Declaration]] = {}
|
||||||
candidates: list[Candidate] = []
|
candidates: list[Candidate] = []
|
||||||
for path in sorted((crate / "src").rglob("*.rs")):
|
for path in sorted((crate / "src").rglob("*.rs")):
|
||||||
if path == crate_root:
|
if path == crate_root:
|
||||||
continue
|
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):
|
for declaration in declaration_candidates(crate, path):
|
||||||
key = (declaration.module, declaration.name)
|
key = (declaration.module, declaration.name)
|
||||||
declarations[key] = declaration
|
declarations[key] = declaration
|
||||||
@@ -198,19 +231,28 @@ def audit_crate(workspace: pathlib.Path, crate: pathlib.Path) -> list[Candidate]
|
|||||||
if symbol not in root_symbols:
|
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"))
|
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]] = {}
|
declarations_by_source: dict[pathlib.Path, dict[str, Declaration]] = {}
|
||||||
for declaration in declarations.values():
|
for declaration in declarations.values():
|
||||||
declarations_by_source.setdefault(declaration.path.resolve(), {})[declaration.name] = declaration
|
declarations_by_source.setdefault(declaration.path.resolve(), {})[declaration.name] = declaration
|
||||||
super_pattern = re.compile(r"\bsuper::([A-Za-z_][A-Za-z0-9_]*)")
|
super_pattern = re.compile(r"\bsuper::([A-Za-z_][A-Za-z0-9_]*)")
|
||||||
for test, parent in tests.items():
|
for test, parent in tests.items():
|
||||||
parent_declarations = declarations_by_source.get(parent, {})
|
parent_declarations = declarations_by_source.get(parent, {})
|
||||||
|
parent_private_declarations = private_declarations_by_source.get(parent, {})
|
||||||
relative = test.relative_to(workspace).as_posix()
|
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):
|
for match in super_pattern.finditer(line):
|
||||||
declaration = parent_declarations.get(match.group(1))
|
declaration = parent_declarations.get(match.group(1))
|
||||||
if declaration is not None and declaration.visibility in {"pub", "pub(crate)"}:
|
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}`"))
|
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
|
return candidates
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user