diff --git a/.pydevproject b/.pydevproject
new file mode 100644
index 0000000..2b04565
--- /dev/null
+++ b/.pydevproject
@@ -0,0 +1,5 @@
+
+
+ Default
+ python interpreter
+
diff --git a/Cargo.toml b/Cargo.toml
index 57a72f1..508b6e6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,12 +1,12 @@
# file: Cargo.toml
-# version: 160
+# version: 161
[workspace]
resolver = "3"
members = ["crates/ksp-app-config-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.5-pre.10.fix.1"
+version = "0.2.5-pre.10.fix.2"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
diff --git a/crates/ksp-app-config-desk/src/bootstrap.rs b/crates/ksp-app-config-desk/src/bootstrap.rs
index d41a2f3..c2c758d 100644
--- a/crates/ksp-app-config-desk/src/bootstrap.rs
+++ b/crates/ksp-app-config-desk/src/bootstrap.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/src/bootstrap.rs
-// version: 5
+// version: 6
//! Config and Logging bootstrap for the desktop application.
@@ -49,7 +49,7 @@ pub(crate) fn config_management(arguments: &[std::ffi::OsString]) -> ksp_core_li
pub(crate) fn initialize_logging(
management: &ksp_config_lib::ConfigManagement,
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,
-) -> ksp_core_lib::Result {
+) -> ksp_core_lib::Result {
let plan = resolve_logging_startup(management);
return match plan {
LoggingStartupPlan::Managed { active_profile_id, settings } => {
@@ -110,7 +110,7 @@ fn fallback_startup_plan(initial_error: ksp_core_lib::Error) -> LoggingStartupPl
fn initialize_fallback_logging(
initial_error: ksp_core_lib::Error,
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,
-) -> ksp_core_lib::Result {
+) -> ksp_core_lib::Result {
let diagnostic = crate::CommandErrorDto::from_error(&initial_error);
return initialize_planned_fallback_logging(initial_error, diagnostic, fallback_logging_settings(), runtime_identity);
}
@@ -120,7 +120,7 @@ fn initialize_planned_fallback_logging(
diagnostic: crate::CommandErrorDto,
settings: ksp_logging_lib::LoggingSettings,
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,
-) -> ksp_core_lib::Result {
+) -> ksp_core_lib::Result {
let guard = ksp_logging_lib::initialize_with_identity(&settings, runtime_identity);
let guard = match guard {
std::result::Result::Ok(value) => value,
diff --git a/crates/ksp-app-config-desk/src/documents.rs b/crates/ksp-app-config-desk/src/documents.rs
index dfdccd6..33e0f77 100644
--- a/crates/ksp-app-config-desk/src/documents.rs
+++ b/crates/ksp-app-config-desk/src/documents.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/src/documents.rs
-// version: 2
+// version: 3
//! Generic Config document inventory, diagnostics and validated repair services.
@@ -148,7 +148,7 @@ pub(crate) fn save_document_source(
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(ConfigDocumentErrorDto::from_error(&error)),
};
- let document = detail(state, file_id.as_str());
+ let document = crate::document_detail(state, file_id.as_str());
let document = match document {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
diff --git a/crates/ksp-app-config-desk/src/environment.rs b/crates/ksp-app-config-desk/src/environment.rs
index ab9b3fd..193624f 100644
--- a/crates/ksp-app-config-desk/src/environment.rs
+++ b/crates/ksp-app-config-desk/src/environment.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/src/environment.rs
-// version: 3
+// version: 4
//! Safe Config environment reports and `.env` management projections for Config Desk.
diff --git a/crates/ksp-app-config-desk/src/profiles.rs b/crates/ksp-app-config-desk/src/profiles.rs
index 198a053..95221d4 100644
--- a/crates/ksp-app-config-desk/src/profiles.rs
+++ b/crates/ksp-app-config-desk/src/profiles.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/src/profiles.rs
-// version: 3
+// version: 4
//! Safe Config profile inspection and provenance projections for Config Desk.
diff --git a/crates/ksp-app-config-desk/src/tw_main.rs b/crates/ksp-app-config-desk/src/tw_main.rs
index ec1d0e5..a752357 100644
--- a/crates/ksp-app-config-desk/src/tw_main.rs
+++ b/crates/ksp-app-config-desk/src/tw_main.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/src/tw_main.rs
-// version: 3
+// version: 4
//! Tauri-window helpers for the Config Desk main window.
@@ -22,7 +22,7 @@ pub(crate) fn require_main_window(manager: &impl Manager) -> ksp_cor
/// Shows main window.
pub(crate) fn show_main_window(app: &tauri::AppHandle) -> ksp_core_lib::Result<()> {
- let window = require_window(app);
+ let window = crate::require_main_window(app);
let window = match window {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
diff --git a/crates/ksp-app-config-desk/src/tw_splash.rs b/crates/ksp-app-config-desk/src/tw_splash.rs
index e20849b..ef90805 100644
--- a/crates/ksp-app-config-desk/src/tw_splash.rs
+++ b/crates/ksp-app-config-desk/src/tw_splash.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/src/tw_splash.rs
-// version: 4
+// version: 5
//! Tauri-window lifecycle for the Config Desk splash window.
@@ -8,6 +8,7 @@ use tauri::Manager; // rust-rules: trait-import
/// Crate-internal `WINDOW_LABEL_SPLASH` constant.
pub(crate) const WINDOW_LABEL_SPLASH: &str = "splash";
+
const SPLASH_EVENT_NAME: &str = "ksp-splash-order";
/// Resolves the required splash window or returns a typed error.
diff --git a/crates/ksp-app-config-desk/unit_tests/environment.rs b/crates/ksp-app-config-desk/unit_tests/environment.rs
index a13b067..f76e85b 100644
--- a/crates/ksp-app-config-desk/unit_tests/environment.rs
+++ b/crates/ksp-app-config-desk/unit_tests/environment.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/unit_tests/environment.rs
-// version: 3
+// version: 4
#[test]
fn namespace_projection_distinguishes_ksp_and_kspb_sensitivity_families() {
@@ -53,7 +53,7 @@ fn committed_environment_report_is_safe_and_deterministic_when_present() {
let management = committed_management();
assert!(management.is_ok(), "committed management should construct: {management:?}");
if let std::result::Result::Ok(management) = management {
- let report = crate::environment_report_from_management(&management);
+ let report = super::report_from_management(&management);
assert!(report.is_ok(), "environment report should project safely: {report:?}");
if let std::result::Result::Ok(report) = report {
let mut previous = std::option::Option::<&str>::None;
diff --git a/crates/ksp-app-config-desk/unit_tests/logging_editor.rs b/crates/ksp-app-config-desk/unit_tests/logging_editor.rs
index b4a9ae4..0007dd8 100644
--- a/crates/ksp-app-config-desk/unit_tests/logging_editor.rs
+++ b/crates/ksp-app-config-desk/unit_tests/logging_editor.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/unit_tests/logging_editor.rs
-// version: 6
+// version: 7
#[test]
fn committed_logging_document_maps_complete_read_only_editor_contract() {
@@ -8,7 +8,7 @@ fn committed_logging_document_maps_complete_read_only_editor_contract() {
if let std::result::Result::Ok(management) = management {
let source = management.load_logging_document();
assert!(source.is_ok(), "typed Logging source should load: {source:?}");
- let document = crate::logging_document_from_management(&management);
+ let document = super::document_from_management(&management);
assert!(document.is_ok(), "Logging editor document should load: {document:?}");
if let (std::result::Result::Ok(source), std::result::Result::Ok(document)) = (source, document) {
assert_eq!(document.file_id, ksp_config_lib::FILE_ID_STD_LOGGING);
@@ -63,7 +63,7 @@ fn editor_candidate_round_trips_through_typed_config_contract() {
let management = committed_management();
assert!(management.is_ok(), "committed management should construct: {management:?}");
if let std::result::Result::Ok(management) = management {
- let document = crate::logging_document_from_management(&management);
+ let document = super::document_from_management(&management);
assert!(document.is_ok(), "Logging editor document should load: {document:?}");
if let std::result::Result::Ok(document) = document {
let candidate = crate::LoggingDocumentCandidateDto {
diff --git a/crates/ksp-app-config-desk/unit_tests/profiles.rs b/crates/ksp-app-config-desk/unit_tests/profiles.rs
index 06067e3..5f09000 100644
--- a/crates/ksp-app-config-desk/unit_tests/profiles.rs
+++ b/crates/ksp-app-config-desk/unit_tests/profiles.rs
@@ -1,12 +1,12 @@
// file: crates/ksp-app-config-desk/unit_tests/profiles.rs
-// version: 4
+// version: 5
#[test]
fn profile_inventory_exposes_registered_standard_profile_documents() {
let management = fixture_management();
assert!(management.is_ok(), "fixture management should construct: {management:?}");
if let std::result::Result::Ok(management) = management {
- let inventory = crate::profile_inventory_from_management(&management);
+ let inventory = super::inventory_from_management(&management);
assert!(inventory.is_ok(), "profile inventory should resolve: {inventory:?}");
if let std::result::Result::Ok(inventory) = inventory {
assert!(inventory.iter().any(|document| -> bool {
@@ -32,7 +32,7 @@ fn default_profile_detail_uses_safe_effective_value_and_dotenv_provenance() {
if let std::result::Result::Ok(management) = management {
let source = management.load_logging_document();
assert!(source.is_ok(), "typed Logging source should load: {source:?}");
- let detail = crate::profile_detail_from_management(&management, ksp_config_lib::FILE_ID_STD_LOGGING, std::option::Option::None);
+ let detail = super::detail_from_management(&management, ksp_config_lib::FILE_ID_STD_LOGGING, std::option::Option::None);
assert!(detail.is_ok(), "default profile should inspect safely: {detail:?}");
if let (std::result::Result::Ok(source), std::result::Result::Ok(detail)) = (source, detail) {
assert_eq!(detail.selected_profile, source.default_profile());
@@ -60,8 +60,7 @@ fn explicit_profile_inspection_reports_explicit_selection_source() {
assert!(source.is_ok(), "typed Logging source should load: {source:?}");
if let std::result::Result::Ok(source) = source {
let profile_id = source.default_profile().to_owned();
- let detail =
- crate::profile_detail_from_management(&management, ksp_config_lib::FILE_ID_STD_LOGGING, std::option::Option::Some(profile_id.as_str()));
+ let detail = super::detail_from_management(&management, ksp_config_lib::FILE_ID_STD_LOGGING, std::option::Option::Some(profile_id.as_str()));
assert!(detail.is_ok(), "explicit profile should inspect: {detail:?}");
if let std::result::Result::Ok(detail) = detail {
assert_eq!(detail.selected_profile, profile_id);
diff --git a/crates/ksp-config-lib/src/environment.rs b/crates/ksp-config-lib/src/environment.rs
index 288c38f..bd1003a 100644
--- a/crates/ksp-config-lib/src/environment.rs
+++ b/crates/ksp-config-lib/src/environment.rs
@@ -1,10 +1,11 @@
// file: crates/ksp-config-lib/src/environment.rs
-// version: 7
+// version: 8
/// Versioned environment contract template expected at the repository/runtime root.
pub const DEFAULT_DOTENV_EXAMPLE_PATH: &str = ".env.example";
/// Default local environment file read by Config from the process launch directory.
pub const DEFAULT_DOTENV_PATH: &str = ".env";
+
const LOGGING_DOMAIN: &str = "config.environment";
/// Source that supplied one resolved Config environment variable.
diff --git a/crates/ksp-core-lib/src/program_ids.rs b/crates/ksp-core-lib/src/program_ids.rs
index d4d0879..69c0683 100644
--- a/crates/ksp-core-lib/src/program_ids.rs
+++ b/crates/ksp-core-lib/src/program_ids.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-core-lib/src/program_ids.rs
-// version: 3
+// version: 4
const DOMAIN_SOLANA: &str = "solana";
const FAMILY_CONSENSUS: &str = "consensus";
diff --git a/crates/ksp-logging-lib/src/runtime.rs b/crates/ksp-logging-lib/src/runtime.rs
index 80ba3dc..8c56a63 100644
--- a/crates/ksp-logging-lib/src/runtime.rs
+++ b/crates/ksp-logging-lib/src/runtime.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-logging-lib/src/runtime.rs
-// version: 15
+// version: 16
use tracing_subscriber::Layer; // rust-rules: trait-import
use tracing_subscriber::layer::SubscriberExt; // rust-rules: trait-import
@@ -473,6 +473,7 @@ const fn map_filter_level(level: crate::LogFilterLevel) -> tracing_subscriber::f
crate::LogFilterLevel::Trace => tracing_subscriber::filter::LevelFilter::TRACE,
};
}
+
const fn map_file_rotation(rotation: crate::FileRotation) -> tracing_appender::rolling::Rotation {
return match rotation {
crate::FileRotation::Never => tracing_appender::rolling::Rotation::NEVER,
diff --git a/crates/ksp-onchain-transport-lib/src/rpc_cluster.rs b/crates/ksp-onchain-transport-lib/src/rpc_cluster.rs
index 4c41103..5da3783 100644
--- a/crates/ksp-onchain-transport-lib/src/rpc_cluster.rs
+++ b/crates/ksp-onchain-transport-lib/src/rpc_cluster.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/rpc_cluster.rs
-// version: 6
+// version: 7
const MAX_GET_SLOT_LEADERS: u64 = 5_000;
@@ -29,71 +29,85 @@ impl SolanaClusterNode {
pub const fn pubkey(&self) -> &ksp_core_lib::Pubkey {
return &self.pubkey;
}
+
/// Returns the optional feature-set identifier.
#[must_use]
pub const fn feature_set(&self) -> std::option::Option {
return self.feature_set;
}
+
/// Returns the optional gossip endpoint.
#[must_use]
pub fn gossip(&self) -> std::option::Option<&str> {
return self.gossip.as_deref();
}
+
/// Returns the optional PubSub endpoint.
#[must_use]
pub fn pubsub(&self) -> std::option::Option<&str> {
return self.pubsub.as_deref();
}
+
/// Returns the optional JSON-RPC endpoint.
#[must_use]
pub fn rpc(&self) -> std::option::Option<&str> {
return self.rpc.as_deref();
}
+
/// Returns the optional repair endpoint.
#[must_use]
pub fn serve_repair(&self) -> std::option::Option<&str> {
return self.serve_repair.as_deref();
}
+
/// Returns the optional shred version.
#[must_use]
pub const fn shred_version(&self) -> std::option::Option {
return self.shred_version;
}
+
/// Returns the optional TPU endpoint.
#[must_use]
pub fn tpu(&self) -> std::option::Option<&str> {
return self.tpu.as_deref();
}
+
/// Returns the optional TPU forwards endpoint.
#[must_use]
pub fn tpu_forwards(&self) -> std::option::Option<&str> {
return self.tpu_forwards.as_deref();
}
+
/// Returns the optional TPU forwards QUIC endpoint.
#[must_use]
pub fn tpu_forwards_quic(&self) -> std::option::Option<&str> {
return self.tpu_forwards_quic.as_deref();
}
+
/// Returns the optional TPU QUIC endpoint.
#[must_use]
pub fn tpu_quic(&self) -> std::option::Option<&str> {
return self.tpu_quic.as_deref();
}
+
/// Returns the optional TPU vote endpoint.
#[must_use]
pub fn tpu_vote(&self) -> std::option::Option<&str> {
return self.tpu_vote.as_deref();
}
+
/// Returns the optional TVU endpoint.
#[must_use]
pub fn tvu(&self) -> std::option::Option<&str> {
return self.tvu.as_deref();
}
+
/// Returns the optional software-version string.
#[must_use]
pub fn version(&self) -> std::option::Option<&str> {
return self.version.as_deref();
}
+
/// Returns the optional Agave client identifier extension.
#[must_use]
pub fn client_id(&self) -> std::option::Option<&str> {
@@ -149,26 +163,31 @@ impl SolanaEpochInfo {
pub const fn absolute_slot(&self) -> u64 {
return self.absolute_slot;
}
+
/// Returns the block height.
#[must_use]
pub const fn block_height(&self) -> u64 {
return self.block_height;
}
+
/// Returns the epoch number.
#[must_use]
pub const fn epoch(&self) -> u64 {
return self.epoch;
}
+
/// Returns the slot index within the epoch.
#[must_use]
pub const fn slot_index(&self) -> u64 {
return self.slot_index;
}
+
/// Returns the number of slots in the epoch.
#[must_use]
pub const fn slots_in_epoch(&self) -> u64 {
return self.slots_in_epoch;
}
+
/// Returns the nullable transaction count.
#[must_use]
pub const fn transaction_count(&self) -> std::option::Option {
@@ -208,21 +227,25 @@ impl SolanaEpochSchedule {
pub const fn first_normal_epoch(&self) -> u64 {
return self.first_normal_epoch;
}
+
/// Returns the first normal slot.
#[must_use]
pub const fn first_normal_slot(&self) -> u64 {
return self.first_normal_slot;
}
+
/// Returns the leader-schedule slot offset.
#[must_use]
pub const fn leader_schedule_slot_offset(&self) -> u64 {
return self.leader_schedule_slot_offset;
}
+
/// Returns the number of slots per epoch.
#[must_use]
pub const fn slots_per_epoch(&self) -> u64 {
return self.slots_per_epoch;
}
+
/// Returns whether epoch warmup is enabled.
#[must_use]
pub const fn warmup(&self) -> bool {
@@ -258,6 +281,7 @@ impl SolanaSnapshotSlotInfo {
pub const fn full(&self) -> u64 {
return self.full;
}
+
/// Returns the optional highest incremental snapshot slot.
#[must_use]
pub const fn incremental(&self) -> std::option::Option {
@@ -287,20 +311,24 @@ impl SolanaLeaderScheduleConfig {
pub const fn new(identity: std::option::Option, commitment: std::option::Option) -> Self {
return Self { identity, commitment };
}
+
/// Returns the optional validator identity filter.
#[must_use]
pub const fn identity(&self) -> std::option::Option<&ksp_core_lib::Pubkey> {
return self.identity.as_ref();
}
+
/// Returns the optional commitment level.
#[must_use]
pub const fn commitment(&self) -> std::option::Option {
return self.commitment;
}
+
/// Returns whether empty.
pub(crate) const fn is_empty(&self) -> bool {
return self.identity.is_none() && self.commitment.is_none();
}
+
/// Executes the crate-internal to json value operation for `SolanaLeaderScheduleConfig`.
pub(crate) fn to_json_value(&self) -> serde_json::Value {
let mut object = serde_json::Map::new();
@@ -402,30 +430,36 @@ impl SolanaVoteAccountsConfig {
) -> Self {
return Self { commitment, vote_pubkey, keep_unstaked_delinquents, delinquent_slot_distance };
}
+
/// Returns the optional commitment.
#[must_use]
pub const fn commitment(&self) -> std::option::Option {
return self.commitment;
}
+
/// Returns the optional vote-account public key filter.
#[must_use]
pub const fn vote_pubkey(&self) -> std::option::Option<&ksp_core_lib::Pubkey> {
return self.vote_pubkey.as_ref();
}
+
/// Returns whether unstaked delinquent validators should be kept.
#[must_use]
pub const fn keep_unstaked_delinquents(&self) -> std::option::Option {
return self.keep_unstaked_delinquents;
}
+
/// Returns the optional delinquent slot distance.
#[must_use]
pub const fn delinquent_slot_distance(&self) -> std::option::Option {
return self.delinquent_slot_distance;
}
+
/// Returns whether empty.
pub(crate) const fn is_empty(&self) -> bool {
return self.commitment.is_none() && self.vote_pubkey.is_none() && self.keep_unstaked_delinquents.is_none() && self.delinquent_slot_distance.is_none();
}
+
/// Serializes this config to the Solana JSON-RPC wire object.
#[must_use]
pub(crate) fn to_json_value(&self) -> serde_json::Value {
@@ -460,11 +494,13 @@ impl SolanaEpochCredits {
pub const fn epoch(&self) -> u64 {
return self.epoch;
}
+
/// Returns cumulative credits at the end of the epoch.
#[must_use]
pub const fn credits(&self) -> u64 {
return self.credits;
}
+
/// Returns cumulative credits before the epoch.
#[must_use]
pub const fn previous_credits(&self) -> u64 {
@@ -492,41 +528,49 @@ impl SolanaVoteAccountInfo {
pub const fn vote_pubkey(&self) -> &ksp_core_lib::Pubkey {
return &self.vote_pubkey;
}
+
/// Returns the validator identity public key.
#[must_use]
pub const fn node_pubkey(&self) -> &ksp_core_lib::Pubkey {
return &self.node_pubkey;
}
+
/// Returns the activated stake in lamports.
#[must_use]
pub const fn activated_stake(&self) -> u64 {
return self.activated_stake;
}
+
/// Returns the legacy/effective percentage commission field.
#[must_use]
pub const fn commission(&self) -> u8 {
return self.commission;
}
+
/// Returns the optional raw inflation-rewards commission in basis points.
#[must_use]
pub const fn inflation_rewards_commission_bps(&self) -> std::option::Option {
return self.inflation_rewards_commission_bps;
}
+
/// Returns whether the vote account is staked for the current epoch.
#[must_use]
pub const fn epoch_vote_account(&self) -> bool {
return self.epoch_vote_account;
}
+
/// Returns the bounded RPC epoch-credit history.
#[must_use]
pub fn epoch_credits(&self) -> &[crate::SolanaEpochCredits] {
return self.epoch_credits.as_slice();
}
+
/// Returns the latest voted slot or zero when no vote exists.
#[must_use]
pub const fn last_vote(&self) -> u64 {
return self.last_vote;
}
+
/// Returns the current root slot or zero when no root exists.
#[must_use]
pub const fn root_slot(&self) -> u64 {
@@ -581,6 +625,7 @@ impl SolanaVoteAccountStatus {
pub fn current(&self) -> &[crate::SolanaVoteAccountInfo] {
return self.current.as_slice();
}
+
/// Returns delinquent vote accounts.
#[must_use]
pub fn delinquent(&self) -> &[crate::SolanaVoteAccountInfo] {
diff --git a/crates/ksp-onchain-transport-lib/src/rpc_method.rs b/crates/ksp-onchain-transport-lib/src/rpc_method.rs
index 535ce1e..5a79657 100644
--- a/crates/ksp-onchain-transport-lib/src/rpc_method.rs
+++ b/crates/ksp-onchain-transport-lib/src/rpc_method.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/rpc_method.rs
-// version: 4
+// version: 5
const CURRENT_HTTP_RPC_METHODS: [crate::HttpRpcMethodDescriptor; 52] = [
crate::HttpRpcMethodDescriptor::new(
@@ -1080,6 +1080,7 @@ impl HttpRpcMethodDescriptor {
pub const fn current_http_rpc_methods() -> &'static [crate::HttpRpcMethodDescriptor] {
return &CURRENT_HTTP_RPC_METHODS;
}
+
/// Returns the 14 historically documented deprecated HTTP RPC descriptors retained for compliance history.
#[must_use]
pub const fn historical_http_rpc_methods() -> &'static [crate::HttpRpcMethodDescriptor] {
diff --git a/crates/ksp-wallet-lib/src/constants.rs b/crates/ksp-wallet-lib/src/constants.rs
index f6d372e..993bea6 100644
--- a/crates/ksp-wallet-lib/src/constants.rs
+++ b/crates/ksp-wallet-lib/src/constants.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-wallet-lib/src/constants.rs
-// version: 6
+// version: 7
//! Wallet-owned constants.
@@ -85,5 +85,6 @@ pub const KSPWALLET_V1_STATE_TRANSCRIPT_DOMAIN: &[u8] = b"KSPWALLET-V1-STATE";
pub const KSPWALLET_V1_VIEW_SLOT_AAD_DOMAIN: &[u8] = b"KSPWALLET-V1-AAD-VIEW-SLOT";
/// Byte length of an XChaCha20-Poly1305 nonce.
pub const KSPWALLET_V1_XCHACHA_NONCE_BYTES: usize = 24;
+
/// Owning tracing target for events emitted by the Wallet crate.
pub(crate) const TRACING_TARGET: &str = "ksp-wallet-lib";
diff --git a/deltas/0.2.5/pre.010-fix.002.md b/deltas/0.2.5/pre.010-fix.002.md
new file mode 100644
index 0000000..fad0353
--- /dev/null
+++ b/deltas/0.2.5/pre.010-fix.002.md
@@ -0,0 +1,192 @@
+
+
+
+# Delta `0.2.5-pre.010-fix.002` — correction normalisation Rust et garde-fous structurels
+
+## 1. Base et version
+
+Base :
+
+```text
+0.2.5-pre.010-fix.001
+workspace.package.version = 0.2.5-pre.10.fix.1
+```
+
+Version technique cible :
+
+```text
+workspace.package.version = 0.2.5-pre.10.fix.2
+```
+
+Identifiant de livraison :
+
+```text
+0.2.5-pre.010-fix.002
+```
+
+Commit attendu après validation :
+
+```text
+v0.2.5-pre.010-fix.002
+```
+
+Aucun tag stable n'est créé par ce fix et `rel.001` reste bloqué jusqu'à validation complète.
+
+## 2. Cause du fix
+
+La validation opérateur de `pre.010-fix.001` a montré que la normalisation automatisée avait introduit plusieurs erreurs de chemin dans `ksp-app-config-desk` :
+
+```text
+helpers privés de module référencés comme faux crate-root items
+fonctions pub(crate) renommées encore appelées sous leur ancien nom local
+réexport crate-root LoggingStartup déclaré mais non consommé explicitement
+```
+
+La revue manuelle a également détecté dans `ksp-core-lib/src/program_ids.rs` une accolade fermante restée à l'ancien emplacement après déplacement de `entries()`. Le code restait syntaxiquement valide parce que `PROGRAM_ID_ENTRIES` devenait un item local situé après un `return`, mais la structure du fichier était incorrecte.
+
+Enfin, le gate du fix précédent ne contrôlait pas assez précisément l'espacement entre items : plusieurs `const fn` et méthodes restaient accolées sans ligne vide.
+
+## 3. Corrections Config Desk
+
+Les tests séparés respectent désormais explicitement la règle de visibilité :
+
+```text
+item strictement privé du module parent -> super::Item
+item pub/pub(crate) partagé -> crate::Item via crate-root
+```
+
+Corrections principales :
+
+```text
+unit_tests/environment.rs
+ crate::environment_report_from_management
+ -> super::report_from_management
+
+unit_tests/logging_editor.rs
+ crate::logging_document_from_management
+ -> super::document_from_management
+
+unit_tests/profiles.rs
+ crate::profile_inventory_from_management
+ -> super::inventory_from_management
+ crate::profile_detail_from_management
+ -> super::detail_from_management
+
+src/documents.rs
+ detail(...)
+ -> crate::document_detail(...)
+
+src/tw_main.rs
+ require_window(...)
+ -> crate::require_main_window(...)
+```
+
+`LoggingStartup` reste `pub(crate)` parce qu'il traverse la frontière `bootstrap -> app_state`. Les signatures crate-wide utilisent désormais explicitement `crate::LoggingStartup`, ce qui rend le réexport crate-root effectif et supprime le faux `unused import`.
+
+## 4. Correction `program_ids.rs`
+
+L'accolade fermante de :
+
+```rust
+pub const fn entries() -> &'static [crate::ProgramIdEntry]
+```
+
+est replacée immédiatement après :
+
+```rust
+return PROGRAM_ID_ENTRIES;
+```
+
+`PROGRAM_ID_ENTRIES` redevient ainsi un item de module et non un `const` local déclaré après un `return`.
+
+## 5. Espacement Rust normalisé
+
+Le workspace applique maintenant les règles explicites suivantes :
+
+```text
+fonction/méthode/const fn distincte -> exactement 1 ligne vide entre items
+struct/enum/union/trait/impl voisins -> exactement 1 ligne vide
+const de même visibilité dans un même bloc -> 0 ligne vide
+type/static homogènes de même visibilité -> 0 ligne vide
+changement de visibilité/catégorie -> exactement 1 ligne vide
+aucune ligne vide dans le corps d'une fonction/méthode/struct/enum
+```
+
+Le renforcement a détecté puis corrigé 53 séparations manquantes héritées de `fix.001`, notamment dans :
+
+```text
+ksp-app-config-desk
+ksp-config-lib
+ksp-logging-lib
+ksp-onchain-transport-lib
+ksp-wallet-lib
+```
+
+La majorité concernait les méthodes de `rpc_cluster.rs`.
+
+## 6. Audits renforcés
+
+### `audit_rust_general_rules.py`
+
+Ajouts :
+
+```text
+RUST-FMT-110
+ détecte un item déclaré après un return inconditionnel au niveau principal
+ d'une fonction/méthode ; signal d'une accolade ou restructuration incorrecte
+
+RUST-FMT-111
+ impose exactement une ligne vide entre items fonctionnels/type-level voisins
+
+RUST-FMT-112
+ interdit une ligne vide dans un bloc homogène const/static/type de même visibilité
+
+RUST-FMT-113
+ impose exactement une ligne vide entre les blocs pub use et pub(crate) use
+```
+
+### `audit_rust_export_completeness.py`
+
+Ajout d'un contrôle de résolution des chemins simples `crate::Item`. Le nouvel audit vérifie qu'un symbole référencé directement depuis le crate-root est réellement déclaré ou réexporté à cette racine.
+
+Ce contrôle aurait détecté les faux noms `crate::environment_report_from_management`, `crate::logging_document_from_management`, etc. avant la compilation.
+
+Les macros `#[macro_export]`, modules crate-root et bridges `pub extern crate` sont intégrés à l'inventaire afin de ne pas produire de faux positifs.
+
+## 7. Règles normatives
+
+`docs/rules/RULES_RUST.md` précise désormais sans ambiguïté :
+
+- séparation exacte d'une ligne entre fonctions, méthodes et `const fn` ;
+- séparation exacte entre types/blocs `impl` voisins ;
+- absence de ligne vide entre constantes de même visibilité ;
+- séparation d'une ligne entre niveaux de visibilité distincts ;
+- règle analogue pour blocs homogènes `type`/`static` ;
+- interdiction d'un item placé après un `return` inconditionnel au niveau principal d'une fonction.
+
+## 8. Validation exécutée dans l'environnement de préparation
+
+Les contrôles Python ont réellement été exécutés sur l'arbre cible :
+
+```text
+General Rust rule audit: clean
+Rust export completeness audit: 0 candidate(s)
+KSP workspace Rust rule audit: clean
+```
+
+Les scripts modifiés ont aussi été vérifiés par `python3 -m py_compile` pendant la préparation.
+
+Aucun toolchain Cargo/Rust n'est disponible dans l'environnement de préparation ; aucune compilation Rust locale n'est donc revendiquée.
+
+## 9. Validation opérateur requise
+
+```bash
+cargo fmt --all
+python3 scripts/audit_rust_workspace_rules.py
+cargo check --workspace
+cargo clippy --workspace --all-targets
+cargo test -p ksp-wallet-lib
+cargo test --workspace
+```
+
+Les arbres de dépendances Dalek/Solana ne nécessitent pas d'être répétés pour ce fix : aucune dépendance n'est modifiée par `fix.002`.
diff --git a/docs/rules/RULES_RUST.md b/docs/rules/RULES_RUST.md
index 44d225f..f3f556c 100644
--- a/docs/rules/RULES_RUST.md
+++ b/docs/rules/RULES_RUST.md
@@ -1,5 +1,5 @@
-
+
# Règles Rust générales
@@ -54,13 +54,15 @@ Les règles `RUST-*` s'appliquent à tous les fichiers Rust de KSP : crates, sou
- **RUST-FMT-001** — `rustfmt.toml` à la racine est la configuration canonique du formatage Rust.
- **RUST-FMT-002** — `cargo fmt --all` est exécuté après chaque modification Rust avant l'audit structurel et les validations Cargo.
- **RUST-FMT-003** — Aucune ligne vide n'est conservée à l'intérieur du corps d'une fonction ou méthode, ni à l'intérieur d'une définition `struct` ou `enum`.
-- **RUST-FMT-004** — Les lignes vides séparent les fonctions/méthodes, types, blocs `impl` et grandes sections logiques ; elles ne fragmentent pas un bloc homogène de déclarations.
+- **RUST-FMT-004** — Deux fonctions ou méthodes distinctes, y compris deux `const fn`, sont séparées par exactement une ligne vide. La même séparation exacte s'applique entre `struct`, `enum`, `union`, `trait`, bloc `impl` et fonction/méthode lorsqu'ils constituent des items voisins d'un même scope.
- **RUST-FMT-005** — Les blocs homogènes sont ordonnés alphabétiquement par nom lorsque l'ordre n'a pas de signification sémantique.
-- **RUST-FMT-006** — Les constantes de module forment des blocs sans ligne vide interne, ordonnés par visibilité `pub`, puis `pub(crate)`, puis privée, et alphabétiquement dans chaque niveau.
+- **RUST-FMT-006** — Les constantes de module ou associées sont ordonnées par visibilité `pub`, puis `pub(crate)`, puis privée, et alphabétiquement dans chaque niveau. Deux constantes de même visibilité appartenant au même bloc ne sont séparées par aucune ligne vide ; exactement une ligne vide sépare deux niveaux de visibilité distincts.
- **RUST-FMT-007** — Dans une façade, le bloc `pub use` précède le bloc `pub(crate) use`; une seule ligne vide sépare les deux blocs, aucune ligne vide n'existe à l'intérieur d'un bloc, et les symboles sont ordonnés alphabétiquement.
- **RUST-FMT-008** — Les déclarations `mod` d'un même bloc sont ordonnées alphabétiquement ; les modules de tests conditionnels restent après les modules de production.
- **RUST-FMT-009** — Lorsqu'un `struct` et ses blocs `impl` forment une unité locale sans contrainte de séparation, l'implémentation suit la structure. Les éléments visibles précèdent les helpers privés lorsqu'aucun ordre métier ou protocolaire n'impose l'inverse.
- **RUST-FMT-010** — L'ordre alphabétique n'écrase jamais un ordre contractuel ou sémantique : wire fields, comptes Solana, étapes de protocole, priorités, transitions d'état, tableaux de dispatch et séquences explicitement normatives conservent leur ordre défini.
+- **RUST-FMT-011** — Les blocs homogènes de `type` ou `static` de même visibilité suivent la même règle d'espacement compact que les constantes ; un changement de catégorie ou de visibilité recrée une séparation d'exactement une ligne vide.
+- **RUST-FMT-012** — Une déclaration d'item placée après un `return` inconditionnel au niveau principal d'une fonction/méthode est interdite ; elle est traitée comme un signal probable d'accolade fermante déplacée ou de bloc mal restructuré.
## Contrôle de flux et erreurs
diff --git a/scripts/audit_rust_export_completeness.py b/scripts/audit_rust_export_completeness.py
old mode 100755
new mode 100644
index 7835b44..f0b1bff
--- a/scripts/audit_rust_export_completeness.py
+++ b/scripts/audit_rust_export_completeness.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
# file: scripts/audit_rust_export_completeness.py
-# version: 1
+# version: 3
"""Audit crate-root export completeness and canonical same-crate paths."""
@@ -124,6 +124,33 @@ def external_usage(crate: pathlib.Path, declaration: Declaration, test_parents:
return False
+
+
+def crate_root_symbols(crate: pathlib.Path) -> set[str]:
+ """Return names that can resolve directly after `crate::`."""
+
+ crate_root = crate / "src/lib.rs"
+ if not crate_root.is_file():
+ crate_root = crate / "src/main.rs"
+ symbols: set[str] = set()
+ if crate_root.is_file():
+ text = crate_root.read_text(encoding="utf-8")
+ for match in re.finditer(r"^\s*mod\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:;|\{)", text, re.MULTILINE):
+ symbols.add(match.group(1))
+ for match in re.finditer(r"^\s*(?:(?:pub|pub\(crate\))\s+)?(?:(?:async|const|unsafe)\s+)*(?:const|static|type|struct|enum|trait|union|fn)\s+([A-Za-z_][A-Za-z0-9_]*)", text, re.MULTILINE):
+ symbols.add(match.group(1))
+ for match in re.finditer(r"^\s*pub(?:\(crate\))?\s+use\s+[^;]*::([A-Za-z_][A-Za-z0-9_]*)\s*;", text, re.MULTILINE):
+ symbols.add(match.group(1))
+ for match in re.finditer(r"^\s*pub\s+extern\s+crate\s+([A-Za-z_][A-Za-z0-9_]*)\s*;", text, re.MULTILINE):
+ symbols.add(match.group(1))
+ for path in sorted((crate / "src").rglob("*.rs")):
+ text = path.read_text(encoding="utf-8")
+ pattern = re.compile(r"#\[macro_export\]\s*\n\s*macro_rules!\s+([A-Za-z_][A-Za-z0-9_]*)")
+ for match in pattern.finditer(text):
+ symbols.add(match.group(1))
+ return symbols
+
+
def audit_crate(workspace: pathlib.Path, crate: pathlib.Path) -> list[Candidate]:
"""Audit one crate for export completeness and canonical paths."""
@@ -156,6 +183,21 @@ def audit_crate(workspace: pathlib.Path, crate: pathlib.Path) -> list[Candidate]
alias = exports.get((match.group(1), match.group(2)))
if alias is not None:
candidates.append(Candidate("RUST-IMPORT-201", relative, idx, f"long internal path `crate::{match.group(1)}::{match.group(2)}` must use `crate::{alias}`"))
+ # Simple `crate::Item` references must resolve at the crate root.
+ root_symbols = crate_root_symbols(crate)
+ simple_root = re.compile(r"\bcrate::([A-Za-z_][A-Za-z0-9_]*)\b(?!::)")
+ for path in sorted(crate.rglob("*.rs")):
+ if "target" in path.parts:
+ continue
+ relative = path.relative_to(workspace).as_posix()
+ text = path.read_text(encoding="utf-8")
+ masked = mask_rust_source(text)
+ for idx, line in enumerate(masked.splitlines(), 1):
+ for match in simple_root.finditer(line):
+ symbol = match.group(1)
+ 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.
declarations_by_source: dict[pathlib.Path, dict[str, Declaration]] = {}
for declaration in declarations.values():
diff --git a/scripts/audit_rust_general_rules.py b/scripts/audit_rust_general_rules.py
old mode 100755
new mode 100644
index a685ddb..1343b94
--- a/scripts/audit_rust_general_rules.py
+++ b/scripts/audit_rust_general_rules.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
# file: scripts/audit_rust_general_rules.py
-# version: 1
+# version: 4
"""Audit mechanically verifiable Rust normalization rules used by KSP."""
@@ -222,6 +222,139 @@ def declaration_start(stripped: str, kind: str) -> bool:
return re.match(rf"^{prefixes}{kind}\b", stripped) is not None
+
+
+@dataclasses.dataclass(frozen=True)
+class RustItem:
+ """One mechanically detected Rust item used for spacing checks."""
+
+ kind: str
+ visibility: str
+ start: int
+ declaration: int
+ end: int
+ depth: int
+ braced: bool
+
+
+def item_visibility(stripped: str) -> str:
+ """Return normalized visibility for one item declaration."""
+
+ if stripped.startswith("pub(crate) "):
+ return "pub(crate)"
+ if stripped.startswith("pub "):
+ return "pub"
+ return "private"
+
+
+def item_kind(stripped: str) -> str | None:
+ """Return the normalized declaration kind for spacing checks."""
+
+ prefix = r"(?:(?:pub|pub\(crate\))\s+)?"
+ qualifiers = r"(?:(?:async|const|unsafe)\s+)*"
+ if re.match(rf"^{prefix}{qualifiers}fn\b", stripped):
+ return "fn"
+ for kind in ("struct", "enum", "union", "trait", "impl", "const", "static", "type"):
+ if re.match(rf"^{prefix}{qualifiers}{kind}\b", stripped):
+ return kind
+ return None
+
+
+def item_leading_line(lines: list[str], declaration_index: int) -> int:
+ """Return the first rustdoc/attribute line attached to an item."""
+
+ cursor = declaration_index - 2
+ while cursor >= 0:
+ stripped = lines[cursor].strip()
+ if stripped.startswith("///") or stripped.startswith("#["):
+ cursor -= 1
+ continue
+ break
+ return cursor + 2
+
+
+def item_end_line(masked_lines: list[str], depths: list[int], declaration_index: int, kind: str) -> tuple[int, bool]:
+ """Return the end line and whether the item owns a braced body."""
+
+ start_depth = depths[declaration_index - 1]
+ if kind in {"const", "static", "type"}:
+ for line_index in range(declaration_index - 1, len(masked_lines)):
+ if ";" in masked_lines[line_index]:
+ return line_index + 1, False
+ return declaration_index, False
+ body_started = False
+ for line_index in range(declaration_index - 1, len(masked_lines)):
+ masked_line = masked_lines[line_index]
+ if not body_started and ";" in masked_line and "{" not in masked_line:
+ return line_index + 1, False
+ if "{" in masked_line:
+ body_started = True
+ if body_started:
+ end_depth = depths[line_index] + masked_line.count("{") - masked_line.count("}")
+ if end_depth == start_depth:
+ return line_index + 1, True
+ return declaration_index, body_started
+
+
+def rust_items(lines: list[str], masked_lines: list[str], depths: list[int]) -> list[RustItem]:
+ """Return mechanically detected Rust items with source spans."""
+
+ result: list[RustItem] = []
+ for idx, line in enumerate(lines, 1):
+ stripped = line.strip()
+ kind = item_kind(stripped)
+ if kind is None:
+ continue
+ end, braced = item_end_line(masked_lines, depths, idx, kind)
+ result.append(RustItem(kind, item_visibility(stripped), item_leading_line(lines, idx), idx, end, depths[idx - 1], braced))
+ return result
+
+
+def item_parent(item: RustItem, items: list[RustItem]) -> RustItem | None:
+ """Return the smallest braced item that contains another item."""
+
+ parents = [candidate for candidate in items if candidate.braced and candidate.declaration < item.declaration <= candidate.end and candidate.depth < item.depth]
+ if not parents:
+ return None
+ return max(parents, key=lambda candidate: candidate.depth)
+
+
+def audit_item_spacing_and_nesting(relative: str, lines: list[str], masked_lines: list[str], depths: list[int]) -> list[Violation]:
+ """Enforce exact item separation and reject declarations nested in functions."""
+
+ violations: list[Violation] = []
+ items = rust_items(lines, masked_lines, depths)
+ for item in items:
+ parent = item_parent(item, items)
+ if parent is not None and parent.kind == "fn":
+ body_depth = parent.depth + 1
+ preceding = lines[parent.declaration:item.declaration - 1]
+ preceding_depths = depths[parent.declaration:item.declaration - 1]
+ returned = any(depth == body_depth and re.match(r"^\s*return\b.*;\s*$", line) is not None for line, depth in zip(preceding, preceding_depths, strict=True))
+ if returned:
+ violations.append(Violation("RUST-FMT-110", relative, item.declaration, f"item `{item.kind}` follows an unconditional function-level return; probable misplaced closing brace"))
+ children: dict[tuple[int, int] | None, list[RustItem]] = {}
+ for item in items:
+ parent = item_parent(item, items)
+ key = None if parent is None else (parent.declaration, parent.end)
+ children.setdefault(key, []).append(item)
+ for siblings in children.values():
+ siblings.sort(key=lambda item: item.declaration)
+ for previous, current in zip(siblings, siblings[1:]):
+ # Do not infer spacing across unparsed syntax such as macro invocations.
+ between = lines[previous.end:current.start - 1]
+ if any(candidate.strip() for candidate in between):
+ continue
+ blank_count = sum(1 for candidate in between if not candidate.strip())
+ same_homogeneous_block = previous.kind == current.kind and previous.kind in {"const", "static", "type"} and previous.visibility == current.visibility
+ expected = 0 if same_homogeneous_block else 1
+ if blank_count != expected:
+ rule = "RUST-FMT-111" if expected == 1 else "RUST-FMT-112"
+ expectation = "exactly one blank line between Rust items" if expected == 1 else "no blank line inside one homogeneous declaration block"
+ violations.append(Violation(rule, relative, current.start, f"{expectation}; found {blank_count}"))
+ return violations
+
+
def audit_blank_lines_in_bodies(relative: str, lines: list[str], masked_lines: list[str], depths: list[int]) -> list[Violation]:
"""Reject blank lines inside functions/methods and struct/enum bodies."""
@@ -277,9 +410,6 @@ def audit_top_level_const_blocks(relative: str, lines: list[str], depths: list[i
name = match.group(2)
if previous is not None:
previous_line, previous_rank, previous_name = previous
- between = lines[previous_line:idx - 1]
- if any(not candidate.strip() for candidate in between):
- violations.append(Violation("RUST-FMT-102", relative, idx, "blank line inside one top-level const block"))
if rank < previous_rank:
violations.append(Violation("RUST-FMT-103", relative, idx, "const visibility order must be pub, pub(crate), then private"))
if rank == previous_rank and natural_key(name) < natural_key(previous_name):
@@ -404,6 +534,15 @@ def audit_file(root: pathlib.Path, path: pathlib.Path) -> list[Violation]:
source_match = re.match(r"^pub(?:\(crate\))?\s+use\s+(?:self::)?([A-Za-z_][A-Za-z0-9_]*)::", stripped)
if source_match is not None and source_match.group(1) in local_modules and not re.match(r"^pub(?:\(crate\))?\s+use\s+self::", stripped):
violations.append(Violation("RUST-IMPORT-106", relative, idx, "crate-root internal re-export must start with self::"))
+ public_exports = [item for item in exports if item[1] == "pub"]
+ crate_exports = [item for item in exports if item[1] == "pub(crate)"]
+ if public_exports and crate_exports:
+ public_end = public_exports[-1][0]
+ crate_start = crate_exports[0][0]
+ transition = lines[public_end:crate_start - 1]
+ blank_count = sum(1 for candidate in transition if not candidate.strip())
+ if blank_count != 1:
+ violations.append(Violation("RUST-FMT-113", relative, crate_start, f"pub use and pub(crate) use blocks require exactly one blank line; found {blank_count}"))
crate_seen = False
previous_by_visibility: dict[str, tuple[int, str]] = {}
for idx, visibility, symbol in exports:
@@ -422,6 +561,7 @@ def audit_file(root: pathlib.Path, path: pathlib.Path) -> list[Violation]:
violations.append(Violation("RUST-FMT-107", relative, idx, f"{visibility} use block is not alphabetically ordered by exported symbol"))
previous_by_visibility[visibility] = (idx, symbol)
violations.extend(audit_blank_lines_in_bodies(relative, lines, masked_lines, depths))
+ violations.extend(audit_item_spacing_and_nesting(relative, lines, masked_lines, depths))
violations.extend(audit_module_order(relative, lines, depths))
violations.extend(audit_top_level_const_blocks(relative, lines, depths))
return violations