v0.2.5-pre.010.fix.001

This commit is contained in:
2026-08-20 11:17:12 +02:00
parent 5bf9651038
commit c0b131bf6f
97 changed files with 2277 additions and 655 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/src/app_state.rs
// version: 7
// version: 8
//! Shared backend state owned by the Tauri application.
@@ -166,7 +166,7 @@ impl AppState {
let dropped = runtime.guard.dropped_lines();
let mut files = std::vec::Vec::<crate::LoggingRuntimeFileDto>::new();
for metadata in runtime.guard.active_file_outputs() {
files.push(crate::logging_runtime::project_file(&metadata));
files.push(crate::project_logging_file(&metadata));
}
return std::result::Result::Ok(crate::LoggingRuntimeStatusDto {
active_profile: runtime.active_profile_id.clone(),

View File

@@ -1,13 +1,19 @@
// file: crates/ksp-app-config-desk/src/bootstrap.rs
// version: 4
// version: 5
//! Config and Logging bootstrap for the desktop application.
/// Crate-internal `LoggingStartup` state shared across the owning crate.
pub(crate) struct LoggingStartup {
/// Stores the guard value for this state.
pub(crate) guard: ksp_logging_lib::LoggingGuard,
/// Stores the active profile id value for this state.
pub(crate) active_profile_id: std::option::Option<String>,
/// Stores the selection source value for this state.
pub(crate) selection_source: String,
/// Stores the fallback active value for this state.
pub(crate) fallback_active: bool,
/// Stores the startup diagnostic value for this state.
pub(crate) startup_diagnostic: std::option::Option<crate::CommandErrorDto>,
}
@@ -23,6 +29,7 @@ enum LoggingStartupPlan {
},
}
/// Executes the crate-internal config management operation for the owning module.
pub(crate) fn config_management(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<ksp_config_lib::ConfigManagement> {
let bootstrap = ksp_config_lib::ConfigBootstrapOptions::from_args(arguments);
let bootstrap = match bootstrap {
@@ -38,6 +45,7 @@ pub(crate) fn config_management(arguments: &[std::ffi::OsString]) -> ksp_core_li
return std::result::Result::Ok(ksp_config_lib::ConfigManagement::new(engine));
}
/// Executes the crate-internal initialize logging operation for the owning module.
pub(crate) fn initialize_logging(
management: &ksp_config_lib::ConfigManagement,
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,

View File

@@ -1,19 +1,19 @@
// file: crates/ksp-app-config-desk/src/documents.rs
// version: 1
// version: 2
//! Generic Config document inventory, diagnostics and validated repair services.
use ts_rs::TS; // rust-rules: derive-import
use ts_rs::TS; // rust-rules: trait-import
const STATUS_VALID: &str = "valid";
const STATUS_INVALID: &str = "invalid";
const STAGE_VALID: &str = "valid";
const STAGE_READ: &str = "read";
const STAGE_EFFECTIVE: &str = "effective";
const STAGE_JSON: &str = "json";
const STAGE_OTHER: &str = "other";
const STAGE_READ: &str = "read";
const STAGE_SCHEMA: &str = "schema";
const STAGE_SEMANTIC: &str = "semantic";
const STAGE_EFFECTIVE: &str = "effective";
const STAGE_OTHER: &str = "other";
const STAGE_VALID: &str = "valid";
const STATUS_INVALID: &str = "invalid";
const STATUS_VALID: &str = "valid";
/// Safe inventory row for one Config document registered by `ksp-config-lib`.
#[derive(Clone, Debug, serde::Serialize, TS)]
@@ -78,7 +78,7 @@ impl ConfigDocumentErrorDto {
}
/// Lists all Config-kind documents from the Config registry and evaluates their current backend validation state.
pub(crate) fn inventory(state: &crate::AppState) -> std::vec::Vec<ConfigDocumentSummaryDto> {
pub(crate) fn document_inventory(state: &crate::AppState) -> std::vec::Vec<ConfigDocumentSummaryDto> {
let management = state.config_management();
let engine = management.engine();
let mut documents = std::vec::Vec::new();
@@ -98,7 +98,7 @@ pub(crate) fn inventory(state: &crate::AppState) -> std::vec::Vec<ConfigDocument
}
/// Loads one Config document detail, including raw source when Config can read it.
pub(crate) fn detail(state: &crate::AppState, file_id_text: &str) -> std::result::Result<ConfigDocumentDetailDto, ConfigDocumentErrorDto> {
pub(crate) fn document_detail(state: &crate::AppState, file_id_text: &str) -> std::result::Result<ConfigDocumentDetailDto, ConfigDocumentErrorDto> {
let file_id = parse_config_file_id(file_id_text);
let file_id = match file_id {
std::result::Result::Ok(value) => value,
@@ -133,7 +133,7 @@ pub(crate) fn detail(state: &crate::AppState, file_id_text: &str) -> std::result
}
/// Validates and atomically persists one raw Config source candidate, then reloads the document detail.
pub(crate) fn save_source(
pub(crate) fn save_document_source(
state: &crate::AppState,
file_id_text: &str,
source: &str,

View File

@@ -1,9 +1,9 @@
// file: crates/ksp-app-config-desk/src/dto_common.rs
// version: 1
// version: 2
//! Common Tauri DTOs shared by Config Desk commands.
use ts_rs::TS; // rust-rules: derive-import
use ts_rs::TS; // rust-rules: trait-import
/// Safe command error projection that never serializes arbitrary KSP error context or source values.
#[derive(Clone, Debug, serde::Serialize, TS)]

View File

@@ -1,9 +1,9 @@
// file: crates/ksp-app-config-desk/src/environment.rs
// version: 2
// version: 3
//! Safe Config environment reports and `.env` management projections for Config Desk.
use ts_rs::TS; // rust-rules: derive-import
use ts_rs::TS; // rust-rules: trait-import
/// Safe desired/effective environment view exposed to the ordinary Config Desk frontend.
#[derive(Clone, Debug, serde::Serialize, TS)]
@@ -63,17 +63,17 @@ impl EnvironmentMutationOperation {
}
/// Returns the safe environment report owned by Config.
pub(crate) fn report(state: &crate::AppState) -> ksp_core_lib::Result<std::vec::Vec<ConfigEnvironmentReportDto>> {
pub(crate) fn environment_report(state: &crate::AppState) -> ksp_core_lib::Result<std::vec::Vec<ConfigEnvironmentReportDto>> {
return report_from_management(state.config_management());
}
/// Creates or replaces one `.env` entry exclusively through ConfigManagement.
pub(crate) fn set_value(state: &crate::AppState, variable_name: &str, value: &str) -> ksp_core_lib::Result<ConfigEnvironmentChangeDto> {
pub(crate) fn set_environment_value(state: &crate::AppState, variable_name: &str, value: &str) -> ksp_core_lib::Result<ConfigEnvironmentChangeDto> {
return set_value_from_management(state.config_management(), variable_name, value);
}
/// Removes one `.env` entry exclusively through ConfigManagement.
pub(crate) fn remove_value(state: &crate::AppState, variable_name: &str) -> ksp_core_lib::Result<ConfigEnvironmentChangeDto> {
pub(crate) fn remove_environment_value(state: &crate::AppState, variable_name: &str) -> ksp_core_lib::Result<ConfigEnvironmentChangeDto> {
return remove_value_from_management(state.config_management(), variable_name);
}

View File

@@ -1,9 +1,9 @@
// file: crates/ksp-app-config-desk/src/frontend_logging.rs
// version: 1
// version: 2
//! KSP-owned bridge for technical log events emitted by Config Desk frontend scripts.
use ts_rs::TS; // rust-rules: derive-import
use ts_rs::TS; // rust-rules: trait-import
/// Log payload sent by Config Desk frontend scripts.
#[derive(Clone, Debug, serde::Deserialize, TS)]

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/src/lib.rs
// version: 15
// version: 16
//! Tauri desktop application for managing and validating KSP configuration.
@@ -28,70 +28,173 @@ mod tw_splash;
/// Runs the KSP configuration desktop application.
pub use self::tauri::run;
/// Shared Config Desk application state managed by Tauri.
pub(crate) use self::app_state::AppState;
/// Crate-internal `LoggingStartup` state shared across the owning crate.
pub(crate) use self::bootstrap::LoggingStartup;
/// Executes the crate-internal config management operation for the owning module.
pub(crate) use self::bootstrap::config_management;
/// Executes the crate-internal initialize logging operation for the owning module.
pub(crate) use self::bootstrap::initialize_logging;
/// Structured domain used while bootstrapping Config and Logging.
pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP;
/// Structured domain for Config document inventory, diagnostics and repair.
pub(crate) use self::constants::TRACING_DOMAIN_DOCUMENTS;
/// Structured domain for safe Config environment reports.
pub(crate) use self::constants::TRACING_DOMAIN_ENVIRONMENT;
/// Structured domain used by technical frontend events.
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
/// Structured domain for typed Logging editor inspection.
pub(crate) use self::constants::TRACING_DOMAIN_LOGGING_EDITOR;
/// Structured domain for active Logging runtime metadata and explicit profile application.
pub(crate) use self::constants::TRACING_DOMAIN_LOGGING_RUNTIME;
/// Structured domain used by controlled Logging test events.
pub(crate) use self::constants::TRACING_DOMAIN_LOGGING_TEST;
/// Structured domain for Config profile selection and provenance inspection.
pub(crate) use self::constants::TRACING_DOMAIN_PROFILES;
/// Structured domain for explicit privileged Secret reveal operations.
pub(crate) use self::constants::TRACING_DOMAIN_SECRETS;
/// Structured domain used by Tauri window lifecycle operations.
pub(crate) use self::constants::TRACING_DOMAIN_WINDOWS;
/// Owning target for backend events emitted by Config Desk.
pub(crate) use self::constants::TRACING_TARGET;
/// Owning target for generic frontend events emitted through the KSP bridge.
pub(crate) use self::constants::TRACING_TARGET_FRONTEND;
/// Owning target for main-window frontend events.
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_MAIN;
/// Owning target for splash-window frontend events.
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_SPLASH;
/// Dedicated target used by the controlled Logging test panel.
pub(crate) use self::constants::TRACING_TARGET_LOGGING_TEST;
/// Detailed management view for one registered Config document.
pub(crate) use self::documents::ConfigDocumentDetailDto;
/// Safe document-specific command error with backend-owned diagnostic classification.
pub(crate) use self::documents::ConfigDocumentErrorDto;
/// Result of one validated raw-source repair attempt that reached persistence successfully.
pub(crate) use self::documents::ConfigDocumentSaveResultDto;
/// Safe inventory row for one Config document registered by `ksp-config-lib`.
pub(crate) use self::documents::ConfigDocumentSummaryDto;
/// Loads one Config document detail, including raw source when Config can read it.
pub(crate) use self::documents::document_detail;
/// Lists all Config-kind documents from the Config registry and evaluates their current backend validation state.
pub(crate) use self::documents::document_inventory;
/// Validates and atomically persists one raw Config source candidate, then reloads the document detail.
pub(crate) use self::documents::save_document_source;
/// Initial application/runtime snapshot exposed to the frontend.
pub(crate) use self::dto_common::AppSnapshotDto;
/// Safe command error projection that never serializes arbitrary KSP error context or source values.
pub(crate) use self::dto_common::CommandErrorDto;
/// Safe result metadata for one `.env` mutation.
pub(crate) use self::environment::ConfigEnvironmentChangeDto;
/// Safe desired/effective environment view exposed to the ordinary Config Desk frontend.
pub(crate) use self::environment::ConfigEnvironmentReportDto;
/// Returns the safe environment report owned by Config.
pub(crate) use self::environment::environment_report;
/// Removes one `.env` entry exclusively through ConfigManagement.
pub(crate) use self::environment::remove_environment_value;
/// Creates or replaces one `.env` entry exclusively through ConfigManagement.
pub(crate) use self::environment::set_environment_value;
/// Shared Config Desk application state is internally inconsistent.
pub(crate) use self::errors::ERROR_CODE_APP_STATE_INVALID;
/// Shared Config Desk runtime state cannot be locked safely.
pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED;
/// A Documents command requested a registered file that is not a Config-kind document.
pub(crate) use self::errors::ERROR_CODE_DOCUMENT_KIND_INVALID;
/// Frontend logging requested an unsupported level.
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID;
/// Frontend logging requested a target outside the application whitelist.
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_TARGET_INVALID;
/// Config Desk could not install the managed Logging runtime or its safe fallback.
pub(crate) use self::errors::ERROR_CODE_LOGGING_BOOTSTRAP_FAILED;
/// Config Desk persisted a Logging candidate but could not restore the previous source after runtime application failed.
pub(crate) use self::errors::ERROR_CODE_LOGGING_SOURCE_ROLLBACK_FAILED;
/// Controlled Logging test request contains an unsupported or invalid selector.
pub(crate) use self::errors::ERROR_CODE_LOGGING_TEST_REQUEST_INVALID;
/// A validated Config document does not expose the standard profile contract expected by the Profiles panel.
pub(crate) use self::errors::ERROR_CODE_PROFILE_CONTRACT_MISSING;
/// Config profile data could not be projected safely for the frontend.
pub(crate) use self::errors::ERROR_CODE_PROFILE_PROJECTION_FAILED;
/// Privileged reveal was requested for a non-Secret KSP/KSPB namespace.
pub(crate) use self::errors::ERROR_CODE_SECRET_REVEAL_REQUIRES_SECRET;
/// Privileged reveal requested an unsupported source selector.
pub(crate) use self::errors::ERROR_CODE_SECRET_REVEAL_SOURCE_INVALID;
/// Splash readiness was invoked from a window other than the splash window.
pub(crate) use self::errors::ERROR_CODE_SPLASH_ORIGIN_INVALID;
/// A KSP desk splash environment duration is malformed or exceeds its safety bound.
pub(crate) use self::errors::ERROR_CODE_SPLASH_SETTING_INVALID;
/// Tauri runtime assembly or execution failed.
pub(crate) use self::errors::ERROR_CODE_TAURI_RUNTIME_FAILED;
/// A required Tauri window is missing from the configured application runtime.
pub(crate) use self::errors::ERROR_CODE_TAURI_WINDOW_MISSING;
/// A Tauri window show/focus/destroy/event operation failed.
pub(crate) use self::errors::ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED;
/// Log payload sent by Config Desk frontend scripts.
pub(crate) use self::frontend_logging::FrontendLogPayloadDto;
/// Emits one validated frontend event through the KSP Logging facade.
pub(crate) use self::frontend_logging::emit_frontend_log_event;
/// Console sink configuration exposed to the editor.
pub(crate) use self::logging_editor::LoggingConsoleDto;
/// Complete editable Logging candidate accepted from the frontend.
pub(crate) use self::logging_editor::LoggingDocumentCandidateDto;
/// Complete typed Logging document exposed to the editor.
pub(crate) use self::logging_editor::LoggingDocumentDto;
/// Result of one validated typed Logging persistence operation.
pub(crate) use self::logging_editor::LoggingDocumentSaveResultDto;
/// One persistent file sink exposed to the editor.
pub(crate) use self::logging_editor::LoggingFileDto;
/// One Logging sink selector/filter exposed to the editor.
pub(crate) use self::logging_editor::LoggingOutputFilterDto;
/// One typed Logging profile exposed to the editor.
pub(crate) use self::logging_editor::LoggingProfileDto;
/// One global Logging target override exposed to the editor.
pub(crate) use self::logging_editor::LoggingTargetFilterDto;
/// Loads the typed Logging source through ConfigManagement and projects it for the frontend.
pub(crate) use self::logging_editor::logging_document;
/// Validates, atomically persists and hot-reloads one typed Logging candidate through ConfigManagement and `ksp-logging-lib`.
pub(crate) use self::logging_editor::save_logging_document;
/// Metadata for one currently active persistent Logging file output.
pub(crate) use self::logging_runtime::LoggingRuntimeFileDto;
/// Safe observable state of the currently active KSP Logging runtime.
pub(crate) use self::logging_runtime::LoggingRuntimeStatusDto;
/// Applies one persisted Logging profile explicitly without changing `default_profile` or the source document.
pub(crate) use self::logging_runtime::apply_logging_profile;
/// Executes the crate-internal count to u64 operation for the owning module.
pub(crate) use self::logging_runtime::count_to_u64;
/// Creates the stable runtime identity for this Config Desk process launch.
pub(crate) use self::logging_runtime::launch_identity;
/// Returns safe metadata for the currently active Logging runtime.
pub(crate) use self::logging_runtime::logging_runtime_status;
/// Executes the crate-internal project logging file operation for the owning module.
pub(crate) use self::logging_runtime::project_logging_file;
/// Controlled request emitted through the backend KSP Logging facade.
pub(crate) use self::logging_test::LoggingTestRequestDto;
/// Safe result metadata for one controlled backend Logging test request.
pub(crate) use self::logging_test::LoggingTestResultDto;
/// Emits one controlled backend test request through `ksp-logging-lib` only.
pub(crate) use self::logging_test::emit_logging_test;
/// Safe detailed view of one resolved Config profile.
pub(crate) use self::profiles::ConfigProfileDetailDto;
/// One Config document that exposes the standard `default_profile` / `profiles` contract.
pub(crate) use self::profiles::ConfigProfileDocumentDto;
/// Resolves one default or explicitly selected profile into a safe inspection DTO.
pub(crate) use self::profiles::profile_detail;
/// Lists validated Config documents that expose standard profiles.
pub(crate) use self::profiles::profile_inventory;
/// Privileged reveal request kept separate from ordinary environment-report DTOs.
pub(crate) use self::secrets::SecretRevealRequestDto;
/// Privileged reveal response. This type deliberately does not derive `Clone` or `Debug`.
pub(crate) use self::secrets::SecretRevealResponseDto;
/// Reveals one real Secret value only after the dedicated Tauri command has been explicitly invoked.
pub(crate) use self::secrets::reveal_secret;
/// Command emitted by Rust to the splash frontend.
pub(crate) use self::splash::SplashOrderDto;
/// Runtime timings used by the common desk splash lifecycle.
pub(crate) use self::splash::SplashSettings;
/// Resolves the required main window or returns a typed error.
pub(crate) use self::tw_main::require_main_window;
/// Shows main window.
pub(crate) use self::tw_main::show_main_window;
/// Resolves the required splash window or returns a typed error.
pub(crate) use self::tw_splash::require_splash_window;
/// Executes the crate-internal splash frontend ready service operation for the owning module.
pub(crate) use self::tw_splash::splash_frontend_ready_service;

View File

@@ -1,9 +1,9 @@
// file: crates/ksp-app-config-desk/src/logging_editor.rs
// version: 5
// version: 6
//! Typed Logging document projection and validated persistence for the Config Desk Logging editor.
use ts_rs::TS; // rust-rules: derive-import
use ts_rs::TS; // rust-rules: trait-import
/// One Logging sink selector/filter exposed to the editor.
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, TS)]
@@ -138,12 +138,12 @@ pub(crate) struct LoggingDocumentSaveResultDto {
}
/// Loads the typed Logging source through ConfigManagement and projects it for the frontend.
pub(crate) fn document(state: &crate::AppState) -> ksp_core_lib::Result<LoggingDocumentDto> {
pub(crate) fn logging_document(state: &crate::AppState) -> ksp_core_lib::Result<LoggingDocumentDto> {
return document_from_management(state.config_management());
}
/// Validates, atomically persists and hot-reloads one typed Logging candidate through ConfigManagement and `ksp-logging-lib`.
pub(crate) fn save(state: &crate::AppState, candidate: LoggingDocumentCandidateDto) -> ksp_core_lib::Result<LoggingDocumentSaveResultDto> {
pub(crate) fn save_logging_document(state: &crate::AppState, candidate: LoggingDocumentCandidateDto) -> ksp_core_lib::Result<LoggingDocumentSaveResultDto> {
let file_id = ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_STD_LOGGING);
let file_id = match file_id {
std::result::Result::Ok(value) => value,

View File

@@ -1,9 +1,9 @@
// file: crates/ksp-app-config-desk/src/logging_runtime.rs
// version: 3
// version: 4
//! Runtime Logging metadata, launch identity and explicit profile application for Config Desk.
use ts_rs::TS; // rust-rules: derive-import
use ts_rs::TS; // rust-rules: trait-import
/// Metadata for one currently active persistent Logging file output.
#[derive(Clone, Debug, serde::Serialize, TS)]
@@ -56,12 +56,12 @@ pub(crate) fn launch_identity() -> ksp_core_lib::Result<ksp_logging_lib::Logging
}
/// Returns safe metadata for the currently active Logging runtime.
pub(crate) fn status(state: &crate::AppState) -> ksp_core_lib::Result<LoggingRuntimeStatusDto> {
pub(crate) fn logging_runtime_status(state: &crate::AppState) -> ksp_core_lib::Result<LoggingRuntimeStatusDto> {
return state.logging_runtime_status();
}
/// Applies one persisted Logging profile explicitly without changing `default_profile` or the source document.
pub(crate) fn apply_profile(state: &crate::AppState, profile_id: &str) -> ksp_core_lib::Result<LoggingRuntimeStatusDto> {
pub(crate) fn apply_logging_profile(state: &crate::AppState, profile_id: &str) -> ksp_core_lib::Result<LoggingRuntimeStatusDto> {
let environment = ksp_config_lib::ConfigEnvironment::load();
let environment = match environment {
std::result::Result::Ok(value) => value,
@@ -88,7 +88,8 @@ pub(crate) fn apply_profile(state: &crate::AppState, profile_id: &str) -> ksp_co
return state.logging_runtime_status();
}
pub(crate) fn project_file(metadata: &ksp_logging_lib::RuntimeFileMetadata) -> LoggingRuntimeFileDto {
/// Executes the crate-internal project logging file operation for the owning module.
pub(crate) fn project_logging_file(metadata: &ksp_logging_lib::RuntimeFileMetadata) -> LoggingRuntimeFileDto {
return LoggingRuntimeFileDto {
output_id: metadata.output_id().to_owned(),
directory: metadata.directory().display().to_string(),
@@ -105,6 +106,7 @@ const fn rotation_label(rotation: ksp_logging_lib::FileRotation) -> &'static str
};
}
/// Executes the crate-internal count to u64 operation for the owning module.
pub(crate) fn count_to_u64(value: usize) -> u64 {
let converted = u64::try_from(value);
return match converted {

View File

@@ -1,9 +1,9 @@
// file: crates/ksp-app-config-desk/src/logging_test.rs
// version: 1
// version: 2
//! Controlled Logging test events for validating KSP runtime routing from Config Desk.
use ts_rs::TS; // rust-rules: derive-import
use ts_rs::TS; // rust-rules: trait-import
/// Controlled request emitted through the backend KSP Logging facade.
#[derive(Clone, Debug, serde::Deserialize, TS)]
@@ -56,7 +56,7 @@ enum LoggingTestTarget {
}
/// Emits one controlled backend test request through `ksp-logging-lib` only.
pub(crate) fn emit(state: &crate::AppState, request: LoggingTestRequestDto) -> ksp_core_lib::Result<LoggingTestResultDto> {
pub(crate) fn emit_logging_test(state: &crate::AppState, request: LoggingTestRequestDto) -> ksp_core_lib::Result<LoggingTestResultDto> {
let level = parse_level(request.level.as_str());
let level = match level {
std::result::Result::Ok(value) => value,

View File

@@ -1,9 +1,9 @@
// file: crates/ksp-app-config-desk/src/profiles.rs
// version: 2
// version: 3
//! Safe Config profile inspection and provenance projections for Config Desk.
use ts_rs::TS; // rust-rules: derive-import
use ts_rs::TS; // rust-rules: trait-import
/// One Config document that exposes the standard `default_profile` / `profiles` contract.
#[derive(Clone, Debug, serde::Serialize, TS)]
@@ -83,12 +83,16 @@ struct ProfileContract {
}
/// Lists validated Config documents that expose standard profiles.
pub(crate) fn inventory(state: &crate::AppState) -> ksp_core_lib::Result<std::vec::Vec<ConfigProfileDocumentDto>> {
pub(crate) fn profile_inventory(state: &crate::AppState) -> ksp_core_lib::Result<std::vec::Vec<ConfigProfileDocumentDto>> {
return inventory_from_management(state.config_management());
}
/// Resolves one default or explicitly selected profile into a safe inspection DTO.
pub(crate) fn detail(state: &crate::AppState, file_id: &str, requested_profile: std::option::Option<&str>) -> ksp_core_lib::Result<ConfigProfileDetailDto> {
pub(crate) fn profile_detail(
state: &crate::AppState,
file_id: &str,
requested_profile: std::option::Option<&str>,
) -> ksp_core_lib::Result<ConfigProfileDetailDto> {
return detail_from_management(state.config_management(), file_id, requested_profile);
}

View File

@@ -1,9 +1,9 @@
// file: crates/ksp-app-config-desk/src/secrets.rs
// version: 1
// version: 2
//! Privileged, explicitly requested Config Secret reveal boundary for Config Desk.
use ts_rs::TS; // rust-rules: derive-import
use ts_rs::TS; // rust-rules: trait-import
/// Privileged reveal request kept separate from ordinary environment-report DTOs.
#[derive(serde::Deserialize, TS)]
@@ -56,7 +56,7 @@ impl SecretRevealSource {
}
/// Reveals one real Secret value only after the dedicated Tauri command has been explicitly invoked.
pub(crate) fn reveal(state: &crate::AppState, request: SecretRevealRequestDto) -> ksp_core_lib::Result<SecretRevealResponseDto> {
pub(crate) fn reveal_secret(state: &crate::AppState, request: SecretRevealRequestDto) -> ksp_core_lib::Result<SecretRevealResponseDto> {
return reveal_from_management(state.config_management(), request);
}

View File

@@ -1,16 +1,16 @@
// file: crates/ksp-app-config-desk/src/splash.rs
// version: 4
// version: 5
//! Common splash settings and frontend event contracts for Config Desk.
use ts_rs::TS; // rust-rules: derive-import
use ts_rs::TS; // rust-rules: trait-import
const ENV_SPLASH_MINIMUM_MS: &str = "KSP_DESK_SPLASH_MINIMUM_MS";
const ENV_SPLASH_FADE_MS: &str = "KSP_DESK_SPLASH_FADE_MS";
const DEFAULT_SPLASH_MINIMUM_MS: u64 = 1200;
const DEFAULT_SPLASH_FADE_MS: u32 = 300;
const MAX_SPLASH_MINIMUM_MS: u64 = 60_000;
const DEFAULT_SPLASH_MINIMUM_MS: u64 = 1200;
const ENV_SPLASH_FADE_MS: &str = "KSP_DESK_SPLASH_FADE_MS";
const ENV_SPLASH_MINIMUM_MS: &str = "KSP_DESK_SPLASH_MINIMUM_MS";
const MAX_SPLASH_FADE_MS: u32 = 10_000;
const MAX_SPLASH_MINIMUM_MS: u64 = 60_000;
/// Runtime timings used by the common desk splash lifecycle.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -108,6 +108,7 @@ pub(crate) struct SplashOrderDto {
}
impl SplashOrderDto {
/// Creates a new `SplashOrderDto` value.
#[must_use]
pub(crate) fn new(action: &str, message: std::option::Option<&str>, duration_ms: std::option::Option<u32>) -> Self {
return Self { action: action.to_owned(), message: message.map(std::string::ToString::to_string), duration_ms };

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/src/tauri.rs
// version: 15
// version: 16
//! Tauri runtime assembly for the KSP configuration desktop application.
@@ -60,11 +60,11 @@ fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tau
fn configure_setup(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
return builder.setup(|app| {
let splash = crate::tw_splash::require_window(app);
let splash = crate::require_splash_window(app);
if let std::result::Result::Err(error) = splash {
return std::result::Result::Err(std::boxed::Box::new(error));
}
let main = crate::tw_main::require_window(app);
let main = crate::require_main_window(app);
if let std::result::Result::Err(error) = main {
return std::result::Result::Err(std::boxed::Box::new(error));
}
@@ -83,7 +83,7 @@ fn get_app_snapshot(state: tauri::State<'_, crate::AppState>) -> std::result::Re
#[tauri::command]
fn get_config_documents(state: tauri::State<'_, crate::AppState>) -> std::vec::Vec<crate::ConfigDocumentSummaryDto> {
return crate::documents::inventory(&state);
return crate::document_inventory(&state);
}
#[tauri::command]
@@ -91,7 +91,7 @@ fn get_config_document_detail(
file_id: String,
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::ConfigDocumentDetailDto, crate::ConfigDocumentErrorDto> {
return crate::documents::detail(&state, file_id.as_str());
return crate::document_detail(&state, file_id.as_str());
}
#[tauri::command]
@@ -100,14 +100,14 @@ fn save_config_document_source(
source: String,
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::ConfigDocumentSaveResultDto, crate::ConfigDocumentErrorDto> {
return crate::documents::save_source(&state, file_id.as_str(), source.as_str());
return crate::save_document_source(&state, file_id.as_str(), source.as_str());
}
#[tauri::command]
fn get_config_profile_documents(
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<std::vec::Vec<crate::ConfigProfileDocumentDto>, crate::CommandErrorDto> {
let result = crate::profiles::inventory(&state);
let result = crate::profile_inventory(&state);
return match result {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
@@ -120,7 +120,7 @@ fn get_config_profile_detail(
profile_id: std::option::Option<String>,
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::ConfigProfileDetailDto, crate::CommandErrorDto> {
let result = crate::profiles::detail(&state, file_id.as_str(), profile_id.as_deref());
let result = crate::profile_detail(&state, file_id.as_str(), profile_id.as_deref());
return match result {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
@@ -131,7 +131,7 @@ fn get_config_profile_detail(
fn get_environment_report(
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<std::vec::Vec<crate::ConfigEnvironmentReportDto>, crate::CommandErrorDto> {
let result = crate::environment::report(&state);
let result = crate::environment_report(&state);
return match result {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
@@ -144,7 +144,7 @@ fn set_environment_value(
value: String,
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::ConfigEnvironmentChangeDto, crate::CommandErrorDto> {
let result = crate::environment::set_value(&state, variable_name.as_str(), value.as_str());
let result = crate::set_environment_value(&state, variable_name.as_str(), value.as_str());
return match result {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
@@ -156,7 +156,7 @@ fn remove_environment_value(
variable_name: String,
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::ConfigEnvironmentChangeDto, crate::CommandErrorDto> {
let result = crate::environment::remove_value(&state, variable_name.as_str());
let result = crate::remove_environment_value(&state, variable_name.as_str());
return match result {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
@@ -168,7 +168,7 @@ fn reveal_environment_value(
request: crate::SecretRevealRequestDto,
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::SecretRevealResponseDto, crate::CommandErrorDto> {
let result = crate::secrets::reveal(&state, request);
let result = crate::reveal_secret(&state, request);
return match result {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
@@ -177,7 +177,7 @@ fn reveal_environment_value(
#[tauri::command]
fn get_logging_document(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::LoggingDocumentDto, crate::CommandErrorDto> {
let result = crate::logging_editor::document(&state);
let result = crate::logging_document(&state);
return match result {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
@@ -186,7 +186,7 @@ fn get_logging_document(state: tauri::State<'_, crate::AppState>) -> std::result
#[tauri::command]
fn get_logging_runtime_status(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::LoggingRuntimeStatusDto, crate::CommandErrorDto> {
let result = crate::logging_runtime::status(&state);
let result = crate::logging_runtime_status(&state);
return match result {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
@@ -198,7 +198,7 @@ fn apply_logging_profile(
profile_id: String,
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::LoggingRuntimeStatusDto, crate::CommandErrorDto> {
let result = crate::logging_runtime::apply_profile(&state, profile_id.as_str());
let result = crate::apply_logging_profile(&state, profile_id.as_str());
return match result {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
@@ -210,7 +210,7 @@ fn save_logging_document(
candidate: crate::LoggingDocumentCandidateDto,
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::LoggingDocumentSaveResultDto, crate::CommandErrorDto> {
let result = crate::logging_editor::save(&state, candidate);
let result = crate::save_logging_document(&state, candidate);
return match result {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
@@ -222,7 +222,7 @@ fn emit_logging_test(
request: crate::LoggingTestRequestDto,
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::LoggingTestResultDto, crate::CommandErrorDto> {
let result = crate::logging_test::emit(&state, request);
let result = crate::emit_logging_test(&state, request);
return match result {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),

View File

@@ -1,13 +1,15 @@
// file: crates/ksp-app-config-desk/src/tw_main.rs
// version: 2
// version: 3
//! Tauri-window helpers for the Config Desk main window.
use tauri::Manager; // rust-rules: trait-import
/// Crate-internal `WINDOW_LABEL_MAIN` constant.
pub(crate) const WINDOW_LABEL_MAIN: &str = "main";
pub(crate) fn require_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib::Result<tauri::WebviewWindow> {
/// Resolves the required main window or returns a typed error.
pub(crate) fn require_main_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib::Result<tauri::WebviewWindow> {
let window = manager.get_webview_window(WINDOW_LABEL_MAIN);
return match window {
std::option::Option::Some(value) => std::result::Result::Ok(value),
@@ -18,6 +20,7 @@ pub(crate) fn require_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib
};
}
/// Shows main window.
pub(crate) fn show_main_window(app: &tauri::AppHandle) -> ksp_core_lib::Result<()> {
let window = require_window(app);
let window = match window {

View File

@@ -1,15 +1,17 @@
// file: crates/ksp-app-config-desk/src/tw_splash.rs
// version: 3
// version: 4
//! Tauri-window lifecycle for the Config Desk splash window.
use tauri::Emitter; // rust-rules: trait-import
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";
pub(crate) fn require_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib::Result<tauri::WebviewWindow> {
/// Resolves the required splash window or returns a typed error.
pub(crate) fn require_splash_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib::Result<tauri::WebviewWindow> {
let window = manager.get_webview_window(WINDOW_LABEL_SPLASH);
return match window {
std::option::Option::Some(value) => std::result::Result::Ok(value),
@@ -20,6 +22,7 @@ pub(crate) fn require_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib
};
}
/// Executes the crate-internal splash frontend ready service operation for the owning module.
pub(crate) async fn splash_frontend_ready_service(
app: tauri::AppHandle,
invoking_window: tauri::WebviewWindow,

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/unit_tests/environment.rs
// version: 2
// version: 3
#[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 = super::report_from_management(&management);
let report = crate::environment_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;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/unit_tests/logging_editor.rs
// version: 5
// version: 6
#[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 = super::document_from_management(&management);
let document = crate::logging_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 = super::document_from_management(&management);
let document = crate::logging_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 {

View File

@@ -1,12 +1,12 @@
// file: crates/ksp-app-config-desk/unit_tests/profiles.rs
// version: 3
// version: 4
#[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 = super::inventory_from_management(&management);
let inventory = crate::profile_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 = super::detail_from_management(&management, ksp_config_lib::FILE_ID_STD_LOGGING, std::option::Option::None);
let detail = crate::profile_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,7 +60,8 @@ 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 = super::detail_from_management(&management, ksp_config_lib::FILE_ID_STD_LOGGING, std::option::Option::Some(profile_id.as_str()));
let detail =
crate::profile_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);

View File

@@ -1,14 +1,14 @@
// file: crates/ksp-config-lib/src/bootstrap.rs
// version: 1
// version: 2
/// Default root containing KSP runtime configuration documents.
pub const DEFAULT_CFG_PATH: &str = "config";
/// Default root containing KSP JSON schemas.
pub const DEFAULT_SCHEMA_PATH: &str = "config/schemas";
/// Bootstrap argument used to replace the configuration document root.
pub const ARG_CFG_PATH: &str = "--cfgpath";
/// Bootstrap argument used to replace the schema root.
pub const ARG_SCHEMA_PATH: &str = "--schemapath";
/// Default root containing KSP runtime configuration documents.
pub const DEFAULT_CFG_PATH: &str = "config";
/// Default root containing KSP JSON schemas.
pub const DEFAULT_SCHEMA_PATH: &str = "config/schemas";
/// Non-recursive bootstrap options required before Config can resolve any managed document.
#[derive(Clone, Debug, Eq, PartialEq)]

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/src/composite.rs
// version: 1
// version: 2
/// One resolved document component selected by a composite profile.
#[derive(Clone, Debug, PartialEq)]
@@ -118,6 +118,7 @@ struct CompositeDocumentReferenceSource {
profile_id: std::option::Option<String>,
}
/// Validates composite document contract.
pub(crate) fn validate_composite_document_contract(engine: &crate::ConfigDocumentEngine, document: &crate::ConfigJsonDocument) -> ksp_core_lib::Result<()> {
if !document.file_id().as_str().starts_with("cfg.composite.") {
return std::result::Result::Ok(());
@@ -198,12 +199,8 @@ fn resolve_composite_document(
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let resolved = crate::profile::load_resolved_profile_with_source(
engine,
&file_id,
reference.profile_id.as_deref(),
crate::ConfigProfileSelectionSource::Composite,
);
let resolved =
crate::load_resolved_profile_with_source(engine, &file_id, reference.profile_id.as_deref(), crate::ConfigProfileSelectionSource::Composite);
let resolved = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -262,8 +259,7 @@ fn validate_reference(
"referenced file_id is not a Config document",
));
}
let resolved =
crate::profile::load_resolved_profile_with_source(engine, &file_id, reference.profile_id.as_deref(), crate::ConfigProfileSelectionSource::Composite);
let resolved = crate::load_resolved_profile_with_source(engine, &file_id, reference.profile_id.as_deref(), crate::ConfigProfileSelectionSource::Composite);
if let std::result::Result::Err(error) = resolved {
return std::result::Result::Err(error);
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/src/document.rs
// version: 5
// version: 6
/// A Config-managed JSON document that has passed syntax, schema and current semantic validation.
#[derive(Clone, Debug, PartialEq)]
@@ -85,6 +85,7 @@ impl ConfigDocumentEngine {
return self.validate_document(document, &schema_file_id);
}
/// Validates source candidate.
pub(crate) fn validate_source_candidate(&self, file_id: &crate::ConfigFileId, source: &str) -> ksp_core_lib::Result<ConfigJsonDocument> {
let path = self.registry.resolve_path(&self.bootstrap, file_id);
let path = match path {
@@ -99,6 +100,7 @@ impl ConfigDocumentEngine {
return self.validate_candidate(file_id, value);
}
/// Validates candidate.
pub(crate) fn validate_candidate(&self, file_id: &crate::ConfigFileId, value: serde_json::Value) -> ksp_core_lib::Result<ConfigJsonDocument> {
let descriptor = self.registry.descriptor(file_id);
let descriptor = match descriptor {
@@ -256,11 +258,11 @@ fn validate_instance(document: &ConfigJsonDocument, schema: &ConfigJsonDocument)
}
fn validate_document_semantics(engine: &ConfigDocumentEngine, document: &ConfigJsonDocument) -> ksp_core_lib::Result<()> {
let profile_validation = crate::profile::validate_document_profile_contract(document);
let profile_validation = crate::validate_document_profile_contract(document);
if let std::result::Result::Err(error) = profile_validation {
return std::result::Result::Err(error);
}
let composite_validation = crate::composite::validate_composite_document_contract(engine, document);
let composite_validation = crate::validate_composite_document_contract(engine, document);
if let std::result::Result::Err(error) = composite_validation {
return std::result::Result::Err(error);
}

View File

@@ -1,12 +1,10 @@
// file: crates/ksp-config-lib/src/environment.rs
// version: 6
/// Default local environment file read by Config from the process launch directory.
pub const DEFAULT_DOTENV_PATH: &str = ".env";
// version: 7
/// 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.
@@ -230,6 +228,7 @@ impl ConfigEnvironment {
};
}
/// Loads from dotenv path.
pub(crate) fn load_from_dotenv_path(dotenv_path: &std::path::Path) -> ksp_core_lib::Result<Self> {
let process = collect_process_environment(std::env::vars_os());
let process = match process {
@@ -244,20 +243,24 @@ impl ConfigEnvironment {
return std::result::Result::Ok(Self { process, dotenv, dotenv_path: dotenv_path.to_path_buf() });
}
/// Returns the current process values.
pub(crate) const fn process_values(&self) -> &std::collections::BTreeMap<String, String> {
return &self.process;
}
/// Returns the current dotenv values.
pub(crate) const fn dotenv_values(&self) -> &std::collections::BTreeMap<String, String> {
return &self.dotenv;
}
/// Builds `ConfigEnvironment` from maps.
#[cfg(test)]
pub(crate) fn from_maps(process: std::collections::BTreeMap<String, String>, dotenv: std::collections::BTreeMap<String, String>) -> Self {
return Self { process, dotenv, dotenv_path: std::path::PathBuf::from(DEFAULT_DOTENV_PATH) };
}
}
/// Executes the crate-internal parse dotenv content operation for the owning module.
pub(crate) fn parse_dotenv_content(path: &std::path::Path, content: &str) -> ksp_core_lib::Result<std::collections::BTreeMap<String, String>> {
let mut output = std::collections::BTreeMap::<String, String>::new();
for (line_index, raw_line) in content.lines().enumerate() {
@@ -305,6 +308,7 @@ pub(crate) fn parse_dotenv_content(path: &std::path::Path, content: &str) -> ksp
return std::result::Result::Ok(output);
}
/// Validates supported variable name.
pub(crate) fn validate_supported_variable_name(variable_name: &str) -> ksp_core_lib::Result<()> {
if !has_supported_namespace(variable_name) {
return std::result::Result::Err(invalid_variable_error(variable_name, "variable must use the KSP_ or KSPB_ namespace"));

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/src/lib.rs
// version: 13
// version: 14
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -171,6 +171,21 @@ pub use self::sensitivity::ResolvedConfigText;
/// Effective standard HTTP Transport configuration mapped to `ksp_onchain_transport_lib::HttpTransportSettings`.
pub use self::transport::ResolvedTransportConfig;
/// Validates composite document contract.
pub(crate) use self::composite::validate_composite_document_contract;
/// Owning tracing target for events emitted by the Config crate.
pub(crate) use self::constants::TRACING_TARGET;
/// Executes the crate-internal parse dotenv content operation for the owning module.
pub(crate) use self::environment::parse_dotenv_content;
/// Validates supported variable name.
pub(crate) use self::environment::validate_supported_variable_name;
/// Executes the crate-internal atomic write operation for the owning module.
pub(crate) use self::persistence::atomic_write;
/// Executes the crate-internal atomic write private operation for the owning module.
pub(crate) use self::persistence::atomic_write_private;
/// Loads resolved profile with source.
pub(crate) use self::profile::load_resolved_profile_with_source;
/// Validates document profile contract.
pub(crate) use self::profile::validate_document_profile_contract;
/// Executes the crate-internal build registry operation for the owning module.
pub(crate) use self::registry::build_registry;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/src/management.rs
// version: 5
// version: 6
/// Raw source of one registered Config document read for explicit management/correction.
#[derive(Clone, Eq, PartialEq)]
@@ -727,7 +727,7 @@ impl ConfigManagement {
/// Authentication/authorization of the human user belongs to the calling application. Calling this method is the explicit Config boundary that opts into
/// real-value access; the returned value must never be logged.
pub fn reveal_effective_environment_value(&self, variable_name: &str) -> ksp_core_lib::Result<std::option::Option<String>> {
let validation = crate::environment::validate_supported_variable_name(variable_name);
let validation = crate::validate_supported_variable_name(variable_name);
if let std::result::Result::Err(error) = validation {
return std::result::Result::Err(error);
}
@@ -744,7 +744,7 @@ impl ConfigManagement {
/// Explicitly reveals the real persisted `.env` value for management display/editing.
pub fn reveal_dotenv_value(&self, variable_name: &str) -> ksp_core_lib::Result<std::option::Option<String>> {
let validation = crate::environment::validate_supported_variable_name(variable_name);
let validation = crate::validate_supported_variable_name(variable_name);
if let std::result::Result::Err(error) = validation {
return std::result::Result::Err(error);
}
@@ -758,7 +758,7 @@ impl ConfigManagement {
/// Creates or updates one supported KSP/KSPB `.env` entry atomically without mutating the inherited process environment.
pub fn set_dotenv_value(&self, variable_name: &str, value: &str) -> ksp_core_lib::Result<ConfigEnvironmentChangeReport> {
let validation = crate::environment::validate_supported_variable_name(variable_name);
let validation = crate::validate_supported_variable_name(variable_name);
if let std::result::Result::Err(error) = validation {
return std::result::Result::Err(error);
}
@@ -781,7 +781,7 @@ impl ConfigManagement {
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if candidate != content {
let write = crate::persistence::atomic_write_private(self.dotenv_path.as_path(), candidate.as_bytes());
let write = crate::atomic_write_private(self.dotenv_path.as_path(), candidate.as_bytes());
match write {
std::result::Result::Ok(()) => {},
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -797,7 +797,7 @@ impl ConfigManagement {
/// Removes one supported KSP/KSPB `.env` entry atomically without mutating the inherited process environment.
pub fn remove_dotenv_value(&self, variable_name: &str) -> ksp_core_lib::Result<ConfigEnvironmentChangeReport> {
let validation = crate::environment::validate_supported_variable_name(variable_name);
let validation = crate::validate_supported_variable_name(variable_name);
if let std::result::Result::Err(error) = validation {
return std::result::Result::Err(error);
}
@@ -820,7 +820,7 @@ impl ConfigManagement {
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if candidate != content {
let write = crate::persistence::atomic_write_private(self.dotenv_path.as_path(), candidate.as_bytes());
let write = crate::atomic_write_private(self.dotenv_path.as_path(), candidate.as_bytes());
match write {
std::result::Result::Ok(()) => {},
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -838,6 +838,7 @@ impl ConfigManagement {
return crate::ConfigEnvironment::load_from_dotenv_path(self.dotenv_path.as_path());
}
/// Executes the crate-internal with dotenv path operation for `ConfigManagement`.
#[cfg(test)]
pub(crate) fn with_dotenv_path(engine: crate::ConfigDocumentEngine, dotenv_path: std::path::PathBuf) -> Self {
return Self { engine, dotenv_path };
@@ -1030,7 +1031,7 @@ fn persist_document_source(file_id: &crate::ConfigFileId, path: &std::path::Path
if existing.as_slice() == source {
return std::result::Result::Ok(ConfigDocumentChangeReport { source_changed: false, reload_required: false });
}
let write = crate::persistence::atomic_write(path, source);
let write = crate::atomic_write(path, source);
if let std::result::Result::Err(error) = write {
return std::result::Result::Err(error);
}

View File

@@ -1,12 +1,14 @@
// file: crates/ksp-config-lib/src/persistence.rs
// version: 3
// version: 4
static NEXT_TEMPORARY_FILE_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
/// Executes the crate-internal atomic write operation for the owning module.
pub(crate) fn atomic_write(path: &std::path::Path, content: &[u8]) -> ksp_core_lib::Result<()> {
return atomic_write_with_policy(path, content, false);
}
/// Executes the crate-internal atomic write private operation for the owning module.
pub(crate) fn atomic_write_private(path: &std::path::Path, content: &[u8]) -> ksp_core_lib::Result<()> {
return atomic_write_with_policy(path, content, true);
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/src/profile.rs
// version: 4
// version: 5
/// Origin of one top-level value in a resolved standard Config profile.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -135,6 +135,7 @@ impl crate::ConfigDocumentEngine {
}
}
/// Loads resolved profile with source.
pub(crate) fn load_resolved_profile_with_source(
engine: &crate::ConfigDocumentEngine,
file_id: &crate::ConfigFileId,
@@ -153,6 +154,7 @@ pub(crate) fn load_resolved_profile_with_source(
return resolve_document_profile(&document, requested_profile, source);
}
/// Validates document profile contract.
pub(crate) fn validate_document_profile_contract(document: &crate::ConfigJsonDocument) -> ksp_core_lib::Result<()> {
let root = match document.value().as_object() {
std::option::Option::Some(value) => value,

View File

@@ -1,18 +1,10 @@
// file: crates/ksp-config-lib/src/registry.rs
// version: 6
// version: 7
/// Bootstrap argument used to replace a known Config filename mapping.
pub const ARG_FILE_MAP: &str = "--filemap";
/// Logical file identifier for the standard Logging configuration document.
pub const FILE_ID_STD_LOGGING: &str = "cfg.std.logging";
/// Logical file identifier for the standard Logging JSON Schema document.
pub const FILE_ID_SCHEMA_STD_LOGGING: &str = "schema.std.logging";
/// Logical file identifier for the standard HTTP Transport configuration document.
pub const FILE_ID_STD_TRANSPORT: &str = "cfg.std.transport";
/// Logical file identifier for the standard HTTP Transport JSON Schema document.
pub const FILE_ID_SCHEMA_STD_TRANSPORT: &str = "schema.std.transport";
/// Logical file identifier for the generic composite JSON Schema document.
pub const FILE_ID_SCHEMA_COMPOSITE: &str = "schema.composite";
/// Default physical filename for the generic composite JSON Schema document.
pub const DEFAULT_COMPOSITE_SCHEMA_FILENAME: &str = "composite.schema.json";
/// Default physical filename for the standard Logging configuration document.
pub const DEFAULT_STD_LOGGING_FILENAME: &str = "std.logging.json";
/// Default physical filename for the standard Logging JSON Schema document.
@@ -21,8 +13,16 @@ pub const DEFAULT_STD_LOGGING_SCHEMA_FILENAME: &str = "std.logging.schema.json";
pub const DEFAULT_STD_TRANSPORT_FILENAME: &str = "std.transport.json";
/// Default physical filename for the standard HTTP Transport JSON Schema document.
pub const DEFAULT_STD_TRANSPORT_SCHEMA_FILENAME: &str = "std.transport.schema.json";
/// Default physical filename for the generic composite JSON Schema document.
pub const DEFAULT_COMPOSITE_SCHEMA_FILENAME: &str = "composite.schema.json";
/// Logical file identifier for the generic composite JSON Schema document.
pub const FILE_ID_SCHEMA_COMPOSITE: &str = "schema.composite";
/// Logical file identifier for the standard Logging JSON Schema document.
pub const FILE_ID_SCHEMA_STD_LOGGING: &str = "schema.std.logging";
/// Logical file identifier for the standard HTTP Transport JSON Schema document.
pub const FILE_ID_SCHEMA_STD_TRANSPORT: &str = "schema.std.transport";
/// Logical file identifier for the standard Logging configuration document.
pub const FILE_ID_STD_LOGGING: &str = "cfg.std.logging";
/// Logical file identifier for the standard HTTP Transport configuration document.
pub const FILE_ID_STD_TRANSPORT: &str = "cfg.std.transport";
/// Stable logical identifier for a Config-managed file.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
@@ -89,6 +89,7 @@ impl ConfigFileDescriptor {
return self.schema_file_id.as_ref();
}
/// Creates a new `ConfigFileDescriptor` value.
pub(crate) fn new(
file_id: &'static str,
kind: ConfigFileKind,
@@ -249,6 +250,7 @@ impl ConfigFileRegistry {
}
}
/// Executes the crate-internal build registry operation for the owning module.
pub(crate) fn build_registry<const N: usize>(descriptors: [ConfigFileDescriptor; N]) -> ksp_core_lib::Result<ConfigFileRegistry> {
let mut registry = ConfigFileRegistry { descriptors: std::collections::BTreeMap::new() };
for descriptor in descriptors {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/src/sensitivity.rs
// version: 1
// version: 2
/// Replacement used for secret environment fragments in safe diagnostic representations.
pub const REDACTED_CONFIG_VALUE: &str = "********";
@@ -18,7 +18,7 @@ pub enum ConfigSensitivity {
impl ConfigSensitivity {
/// Classifies one supported KSP/KSPB environment variable by its namespace.
pub fn from_variable_name(variable_name: &str) -> ksp_core_lib::Result<Self> {
let validation = crate::environment::validate_supported_variable_name(variable_name);
let validation = crate::validate_supported_variable_name(variable_name);
if let std::result::Result::Err(error) = validation {
return std::result::Result::Err(error);
}
@@ -103,6 +103,7 @@ pub struct ResolvedConfigText {
}
impl ResolvedConfigText {
/// Creates a new `ResolvedConfigText` value.
pub(crate) fn new(value: String, safe_value: String, sensitivity: ConfigSensitivity, provenance: std::vec::Vec<ConfigValueProvenance>) -> Self {
return Self { value, safe_value, sensitivity, provenance };
}
@@ -153,6 +154,7 @@ pub struct ResolvedConfigJson {
}
impl ResolvedConfigJson {
/// Creates a new `ResolvedConfigJson` value.
pub(crate) fn new(
value: serde_json::Value,
safe_value: serde_json::Value,

View File

@@ -1,8 +1,8 @@
// file: crates/ksp-config-lib/unit_tests/composite.rs
// version: 3
// version: 4
const TEST_COMPOSITE_FILE_ID: &str = "cfg.composite.test";
const TEST_COMPOSITE_FILENAME: &str = "examples/composite.example.json";
const TEST_COMPOSITE_FILE_ID: &str = "cfg.composite.test";
#[test]
fn fixture_composite_example_resolves_default_document_profile() {
@@ -197,7 +197,7 @@ fn test_registry(composite_filename: &'static str) -> ksp_core_lib::Result<crate
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return crate::registry::build_registry([logging, logging_schema, composite_schema, composite]);
return crate::build_registry([logging, logging_schema, composite_schema, composite]);
}
fn prepare_fixture(fixture: &FixtureRoots, composite: &str) -> std::io::Result<()> {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-core-lib/src/lib.rs
// version: 7
// version: 8
#![warn(missing_docs)]
#![deny(unreachable_pub)]

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-core-lib/src/program_ids.rs
// version: 2
// version: 3
const DOMAIN_SOLANA: &str = "solana";
const FAMILY_CONSENSUS: &str = "consensus";
@@ -266,6 +266,12 @@ impl<'a> ProgramIdFilter<'a> {
}
}
/// Returns the canonical KSP Program ID registry.
#[must_use]
pub const fn entries() -> &'static [crate::ProgramIdEntry] {
return PROGRAM_ID_ENTRIES;
}
const PROGRAM_ID_ENTRIES: &[crate::ProgramIdEntry] = &[
crate::ProgramIdEntry::new(
"solana.address_lookup_table",
@@ -438,12 +444,6 @@ const PROGRAM_ID_ENTRIES: &[crate::ProgramIdEntry] = &[
),
];
/// Returns the canonical KSP Program ID registry.
#[must_use]
pub const fn entries() -> &'static [crate::ProgramIdEntry] {
return PROGRAM_ID_ENTRIES;
}
/// Returns a lazy view of Program IDs matching all configured filter axes.
pub fn program_ids<'a>(filter: crate::ProgramIdFilter<'a>) -> impl std::iter::Iterator<Item = &'static crate::ProgramIdEntry> + 'a {
return PROGRAM_ID_ENTRIES.iter().filter(move |entry| {

View File

@@ -1,13 +1,15 @@
// file: crates/ksp-logging-lib/src/domain.rs
// version: 2
// version: 3
std::thread_local! {
static CURRENT_DOMAIN: std::cell::RefCell<std::option::Option<std::string::String>> = const { std::cell::RefCell::new(std::option::Option::None) };
}
/// Crate-internal `DomainContextLayer` state shared across the owning crate.
pub(crate) struct DomainContextLayer;
impl DomainContextLayer {
/// Creates a new `DomainContextLayer` value.
pub(crate) const fn new() -> Self {
return Self;
}
@@ -99,6 +101,7 @@ impl tracing::field::Visit for DomainVisitor {
}
}
/// Executes the crate-internal current domain matches operation for the owning module.
pub(crate) fn current_domain_matches(selectors: &[std::string::String]) -> bool {
if let [selector] = selectors
&& selector == "*"

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-logging-lib/src/identity.rs
// version: 1
// version: 2
//! Stable runtime identity used to separate persistent file outputs between application launches.
@@ -41,6 +41,7 @@ impl LoggingRuntimeIdentity {
return self.launch_timestamp.as_str();
}
/// Executes the crate-internal file name prefix operation for `LoggingRuntimeIdentity`.
pub(crate) fn file_name_prefix(&self, configured_prefix: &str) -> std::string::String {
return format!("{}.{}.{}", self.application_id, self.launch_timestamp, configured_prefix);
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-logging-lib/src/lib.rs
// version: 9
// version: 10
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -70,10 +70,17 @@ pub use self::span::Span;
/// Instruments an asynchronous future with a KSP span.
pub use self::span::instrument;
/// Crate-internal `DomainContextLayer` state shared across the owning crate.
pub(crate) use self::domain::DomainContextLayer;
/// Executes the crate-internal current domain matches operation for the owning module.
pub(crate) use self::domain::current_domain_matches;
/// Crate-internal `RouteMakeWriter` state shared across the owning crate.
pub(crate) use self::writer::RouteMakeWriter;
/// Crate-internal `RoutedWriter` variants used by the owning crate.
pub(crate) use self::writer::RoutedWriter;
/// Crate-internal `StripAnsiWriter` state shared across the owning crate.
pub(crate) use self::writer::StripAnsiWriter;
/// Hidden tracing crate bridge used exclusively by exported KSP logging macros.
#[doc(hidden)]
/// Internal macro bridge. KSP consumers must not use this reexport directly.
pub extern crate tracing as __private_tracing;
pub extern crate tracing;

View File

@@ -1,11 +1,11 @@
// file: crates/ksp-logging-lib/src/macros.rs
// version: 1
// version: 2
/// Emits a KSP error event with an explicit owning target.
#[macro_export]
macro_rules! error {
(target: $target:expr, $($argument:tt)+) => {{
$crate::__private_tracing::error!(target: $target, $($argument)+);
$crate::tracing::error!(target: $target, $($argument)+);
}};
}
@@ -13,7 +13,7 @@ macro_rules! error {
#[macro_export]
macro_rules! warn {
(target: $target:expr, $($argument:tt)+) => {{
$crate::__private_tracing::warn!(target: $target, $($argument)+);
$crate::tracing::warn!(target: $target, $($argument)+);
}};
}
@@ -21,7 +21,7 @@ macro_rules! warn {
#[macro_export]
macro_rules! info {
(target: $target:expr, $($argument:tt)+) => {{
$crate::__private_tracing::info!(target: $target, $($argument)+);
$crate::tracing::info!(target: $target, $($argument)+);
}};
}
@@ -29,7 +29,7 @@ macro_rules! info {
#[macro_export]
macro_rules! debug {
(target: $target:expr, $($argument:tt)+) => {{
$crate::__private_tracing::debug!(target: $target, $($argument)+);
$crate::tracing::debug!(target: $target, $($argument)+);
}};
}
@@ -37,7 +37,7 @@ macro_rules! debug {
#[macro_export]
macro_rules! trace {
(target: $target:expr, $($argument:tt)+) => {{
$crate::__private_tracing::trace!(target: $target, $($argument)+);
$crate::tracing::trace!(target: $target, $($argument)+);
}};
}
@@ -45,10 +45,10 @@ macro_rules! trace {
#[macro_export]
macro_rules! error_span {
(target: $target:expr, $name:expr) => {{
$crate::Span::__from_tracing($crate::__private_tracing::error_span!(target: $target, $name))
$crate::Span::__from_tracing($crate::tracing::error_span!(target: $target, $name))
}};
(target: $target:expr, $name:expr, $($field:tt)+) => {{
$crate::Span::__from_tracing($crate::__private_tracing::error_span!(target: $target, $name, $($field)+))
$crate::Span::__from_tracing($crate::tracing::error_span!(target: $target, $name, $($field)+))
}};
}
@@ -56,10 +56,10 @@ macro_rules! error_span {
#[macro_export]
macro_rules! warn_span {
(target: $target:expr, $name:expr) => {{
$crate::Span::__from_tracing($crate::__private_tracing::warn_span!(target: $target, $name))
$crate::Span::__from_tracing($crate::tracing::warn_span!(target: $target, $name))
}};
(target: $target:expr, $name:expr, $($field:tt)+) => {{
$crate::Span::__from_tracing($crate::__private_tracing::warn_span!(target: $target, $name, $($field)+))
$crate::Span::__from_tracing($crate::tracing::warn_span!(target: $target, $name, $($field)+))
}};
}
@@ -67,10 +67,10 @@ macro_rules! warn_span {
#[macro_export]
macro_rules! info_span {
(target: $target:expr, $name:expr) => {{
$crate::Span::__from_tracing($crate::__private_tracing::info_span!(target: $target, $name))
$crate::Span::__from_tracing($crate::tracing::info_span!(target: $target, $name))
}};
(target: $target:expr, $name:expr, $($field:tt)+) => {{
$crate::Span::__from_tracing($crate::__private_tracing::info_span!(target: $target, $name, $($field)+))
$crate::Span::__from_tracing($crate::tracing::info_span!(target: $target, $name, $($field)+))
}};
}
@@ -78,10 +78,10 @@ macro_rules! info_span {
#[macro_export]
macro_rules! debug_span {
(target: $target:expr, $name:expr) => {{
$crate::Span::__from_tracing($crate::__private_tracing::debug_span!(target: $target, $name))
$crate::Span::__from_tracing($crate::tracing::debug_span!(target: $target, $name))
}};
(target: $target:expr, $name:expr, $($field:tt)+) => {{
$crate::Span::__from_tracing($crate::__private_tracing::debug_span!(target: $target, $name, $($field)+))
$crate::Span::__from_tracing($crate::tracing::debug_span!(target: $target, $name, $($field)+))
}};
}
@@ -89,9 +89,9 @@ macro_rules! debug_span {
#[macro_export]
macro_rules! trace_span {
(target: $target:expr, $name:expr) => {{
$crate::Span::__from_tracing($crate::__private_tracing::trace_span!(target: $target, $name))
$crate::Span::__from_tracing($crate::tracing::trace_span!(target: $target, $name))
}};
(target: $target:expr, $name:expr, $($field:tt)+) => {{
$crate::Span::__from_tracing($crate::__private_tracing::trace_span!(target: $target, $name, $($field)+))
$crate::Span::__from_tracing($crate::tracing::trace_span!(target: $target, $name, $($field)+))
}};
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-logging-lib/src/runtime.rs
// version: 14
// version: 15
use tracing_subscriber::Layer; // rust-rules: trait-import
use tracing_subscriber::layer::SubscriberExt; // rust-rules: trait-import
@@ -315,7 +315,7 @@ fn prepare_runtime_with_identity(
if output_layers.is_empty() {
return std::result::Result::Ok(PreparedRuntime { layers: RuntimeLayers::new(), outputs });
}
output_layers.insert(0, crate::domain::DomainContextLayer::new().boxed());
output_layers.insert(0, crate::DomainContextLayer::new().boxed());
let takeover_layer = build_target_filter(settings).and_then(output_layers).boxed();
return std::result::Result::Ok(PreparedRuntime { layers: vec![takeover_layer], outputs });
}
@@ -413,7 +413,7 @@ fn build_format_layer(
let span_events = map_span_events(span_events);
return match format {
crate::LogFormat::Human => tracing_subscriber::fmt::layer()
.with_writer(crate::writer::RouteMakeWriter::new(writer, filter.clone()))
.with_writer(crate::RouteMakeWriter::new(writer, filter.clone()))
.with_ansi(ansi)
.with_ansi_sanitization(ansi_sanitization)
.with_target(true)
@@ -423,7 +423,7 @@ fn build_format_layer(
.boxed(),
crate::LogFormat::Compact => tracing_subscriber::fmt::layer()
.compact()
.with_writer(crate::writer::RouteMakeWriter::new(writer, filter.clone()))
.with_writer(crate::RouteMakeWriter::new(writer, filter.clone()))
.with_ansi(ansi)
.with_ansi_sanitization(ansi_sanitization)
.with_target(true)
@@ -433,7 +433,7 @@ fn build_format_layer(
.boxed(),
crate::LogFormat::Pretty => tracing_subscriber::fmt::layer()
.pretty()
.with_writer(crate::writer::RouteMakeWriter::new(writer, filter.clone()))
.with_writer(crate::RouteMakeWriter::new(writer, filter.clone()))
.with_ansi(ansi)
.with_ansi_sanitization(ansi_sanitization)
.with_target(true)
@@ -443,7 +443,7 @@ fn build_format_layer(
.boxed(),
crate::LogFormat::Json => tracing_subscriber::fmt::layer()
.json()
.with_writer(crate::writer::RouteMakeWriter::new(writer, filter.clone()))
.with_writer(crate::RouteMakeWriter::new(writer, filter.clone()))
.with_ansi(false)
.with_target(true)
.with_file(true)
@@ -473,7 +473,6 @@ 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,

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-logging-lib/src/writer.rs
// version: 4
// version: 5
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum StripAnsiState {
@@ -12,12 +12,14 @@ enum StripAnsiState {
StringEscape,
}
/// Crate-internal `StripAnsiWriter` state shared across the owning crate.
pub(crate) struct StripAnsiWriter<W> {
inner: W,
state: StripAnsiState,
}
impl<W> StripAnsiWriter<W> {
/// Creates a new `StripAnsiWriter` value.
pub(crate) const fn new(inner: W) -> Self {
return Self { inner, state: StripAnsiState::Text };
}
@@ -100,6 +102,7 @@ impl<W> StripAnsiWriter<W> {
}
}
/// Crate-internal `RouteMakeWriter` state shared across the owning crate.
#[derive(Clone)]
pub(crate) struct RouteMakeWriter<W> {
inner: W,
@@ -107,11 +110,13 @@ pub(crate) struct RouteMakeWriter<W> {
}
impl<W> RouteMakeWriter<W> {
/// Creates a new `RouteMakeWriter` value.
pub(crate) fn new(inner: W, filter: crate::OutputFilter) -> Self {
return Self { inner, filter };
}
}
/// Crate-internal `RoutedWriter` variants used by the owning crate.
pub(crate) enum RoutedWriter<W> {
Enabled(W),
Disabled,

View File

@@ -1,12 +1,12 @@
// file: crates/ksp-logging-lib/tests/overhead.rs
// version: 1
// version: 2
//! Diagnostic gross-overhead probe for the reload layer used by KSP Logging.
use tracing_subscriber::layer::SubscriberExt; // rust-rules: trait-import
const TEST_TARGET: &str = "ksp-logging-lib";
const ITERATIONS: u64 = 200_000;
const TEST_TARGET: &str = "ksp-logging-lib";
fn emit_probe_events() {
for sequence in 0..ITERATIONS {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-logging-lib/tests/ownership.rs
// version: 2
// version: 3
//! Integration audit ensuring KSP crates do not bypass the logging facade.
@@ -157,6 +157,11 @@ fn workspace_crates_do_not_bypass_ksp_logging_facade() {
std::result::Result::Err(_) => continue,
};
assert!(!source_uses_direct_path(source.as_str(), "tracing::"), "{} bypasses ksp-logging-lib via tracing", rust_file.display());
assert!(
!source_uses_direct_path(source.as_str(), "ksp_logging_lib::tracing::"),
"{} bypasses ksp-logging-lib via its hidden tracing bridge",
rust_file.display(),
);
assert!(
!source_uses_direct_path(source.as_str(), "tracing_subscriber::"),
"{} bypasses ksp-logging-lib via tracing-subscriber",

View File

@@ -1,12 +1,12 @@
// file: crates/ksp-logging-lib/tests/runtime.rs
// version: 9
// version: 10
//! Integration tests for global initialization, takeover filtering, non-blocking outputs and hot reload.
const EXTERNAL_TARGET: &str = "sqlx";
const JSON_KSP_TARGET: &str = "ksp-logging-json-test";
const LOGGING_TARGET: &str = "ksp-logging-lib";
const OTHER_KSP_TARGET: &str = "ksp-store-lib";
const JSON_KSP_TARGET: &str = "ksp-logging-json-test";
const EXTERNAL_TARGET: &str = "sqlx";
fn logging_trace_enabled() -> bool {
return tracing::enabled!(target: LOGGING_TARGET, tracing::Level::TRACE);

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/client.rs
// version: 6
// version: 7
/// Passive runtime availability reported for one logical HTTP endpoint or role.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
@@ -177,8 +177,9 @@ impl HttpEndpointClient {
return Self::new_with_notify(settings, std::sync::Arc::new(tokio::sync::Notify::new()));
}
/// Creates a new with notify value for `HttpEndpointClient`.
pub(crate) fn new_with_notify(settings: crate::HttpEndpointSettings, notify: std::sync::Arc<tokio::sync::Notify>) -> ksp_core_lib::Result<Self> {
let validation = crate::settings::validate_endpoint_settings(&settings);
let validation = crate::validate_endpoint_settings(&settings);
if let std::result::Result::Err(error) = validation {
return std::result::Result::Err(error);
}
@@ -239,6 +240,7 @@ impl HttpEndpointClient {
return self.inner.settings.request_timeout();
}
/// Executes the crate-internal post json rpc operation for `HttpEndpointClient`.
pub(crate) async fn post_json_rpc(&self, payload: &str, timeout: std::time::Duration) -> ksp_core_lib::Result<HttpEndpointHttpResponse> {
if timeout.is_zero() {
return std::result::Result::Err(
@@ -301,6 +303,7 @@ impl HttpEndpointClient {
};
}
/// Executes the crate-internal matching role operation for `HttpEndpointClient`.
pub(crate) fn matching_role<'a>(
&'a self,
role: &crate::HttpRoleName,
@@ -322,6 +325,7 @@ impl HttpEndpointClient {
return std::option::Option::None;
}
/// Executes the crate-internal matching role runtime operation for `HttpEndpointClient`.
pub(crate) fn matching_role_runtime(
&self,
role: &crate::HttpRoleName,
@@ -352,6 +356,7 @@ impl HttpEndpointClient {
return std::option::Option::None;
}
/// Returns the current availability.
pub(crate) fn availability(&self) -> crate::HttpEndpointAvailability {
if !self.enabled() {
return crate::HttpEndpointAvailability::Disabled;
@@ -393,6 +398,7 @@ impl std::fmt::Debug for HttpEndpointClient {
}
}
/// Crate-internal `HttpEndpointHttpResponse` state shared across the owning crate.
pub(crate) struct HttpEndpointHttpResponse {
status: u16,
retry_after: std::option::Option<std::time::Duration>,
@@ -400,14 +406,17 @@ pub(crate) struct HttpEndpointHttpResponse {
}
impl HttpEndpointHttpResponse {
/// Returns the current status.
pub(crate) const fn status(&self) -> u16 {
return self.status;
}
/// Returns the current retry after.
pub(crate) const fn retry_after(&self) -> std::option::Option<std::time::Duration> {
return self.retry_after;
}
/// Returns the current body.
pub(crate) fn body(&self) -> &[u8] {
return self.body.as_slice();
}

View File

@@ -1,12 +1,12 @@
// file: crates/ksp-onchain-transport-lib/src/executor.rs
// version: 2
// version: 3
const HTTP_REQUEST_TIMEOUT: u16 = 408;
const HTTP_TOO_MANY_REQUESTS: u16 = 429;
const HTTP_INTERNAL_SERVER_ERROR: u16 = 500;
const HTTP_BAD_GATEWAY: u16 = 502;
const HTTP_SERVICE_UNAVAILABLE: u16 = 503;
const HTTP_GATEWAY_TIMEOUT: u16 = 504;
const HTTP_INTERNAL_SERVER_ERROR: u16 = 500;
const HTTP_REQUEST_TIMEOUT: u16 = 408;
const HTTP_SERVICE_UNAVAILABLE: u16 = 503;
const HTTP_TOO_MANY_REQUESTS: u16 = 429;
impl crate::HttpTransportPool {
/// Executes one audited standard Solana HTTP JSON-RPC method through KSP routing, admission and bounded retry policy.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/lib.rs
// version: 20
// version: 21
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -193,10 +193,6 @@ pub use self::rpc_common::SolanaContextConfig;
pub use self::rpc_common::SolanaRpcContext;
/// Generic contextual result returned by typed Solana HTTP RPC adapters.
pub use self::rpc_common::SolanaRpcResponse;
/// Decodes one private serde wire type into the shared Transport error domain for typed RPC adapters.
pub(crate) use self::rpc_common::decode_wire_json;
/// Parses a base58 public key without echoing its wire value into diagnostics for typed RPC adapters.
pub(crate) use self::rpc_common::parse_wire_pubkey;
/// Inflation-governor values returned by `getInflationGovernor`.
pub use self::rpc_economics::SolanaInflationGovernor;
/// Current inflation-rate values returned by `getInflationRate`.
@@ -296,7 +292,17 @@ pub use self::settings::HttpRoleName;
/// Complete runtime settings consumed by the Solana HTTP transport foundation.
pub use self::settings::HttpTransportSettings;
/// Owning tracing target for events emitted by the on-chain transport crate.
pub(crate) use self::constants::TRACING_TARGET;
/// Crate-internal `HttpConcurrencyPermit` state shared across the owning crate.
pub(crate) use self::resilience::HttpConcurrencyPermit;
/// Crate-internal `HttpRoleRuntime` state shared across the owning crate.
pub(crate) use self::resilience::HttpRoleRuntime;
/// Crate-internal `RoleAdmissionAttempt` variants used by the owning crate.
pub(crate) use self::resilience::RoleAdmissionAttempt;
/// Decodes one private serde wire type into the shared Transport error domain for typed RPC adapters.
pub(crate) use self::rpc_common::decode_wire_json;
/// Parses a base58 public key without echoing its wire value into diagnostics for typed RPC adapters.
pub(crate) use self::rpc_common::parse_wire_pubkey;
/// Validates endpoint settings.
pub(crate) use self::settings::validate_endpoint_settings;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/pool.rs
// version: 6
// version: 7
/// Safe snapshot of the logical HTTP endpoint pool.
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -207,6 +207,7 @@ impl HttpTransportPool {
return &self.inner.retry;
}
/// Executes the crate-internal next request id operation for `HttpTransportPool`.
pub(crate) fn next_request_id(&self) -> u64 {
let id = self.inner.request_ids.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if id == 0 {
@@ -426,6 +427,7 @@ impl HttpTransportPool {
return std::time::Instant::now() < deadline;
}
/// Executes the crate-internal common request timeout operation for `HttpTransportPool`.
pub(crate) fn common_request_timeout(
&self,
role: &crate::HttpRoleName,

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/resilience.rs
// version: 2
// version: 3
const DEFAULT_RATE_LIMIT_COOLDOWN: std::time::Duration = std::time::Duration::from_secs(1);
const MAX_PROVIDER_RETRY_AFTER: std::time::Duration = std::time::Duration::from_secs(60);
@@ -71,6 +71,7 @@ impl HttpRetryDecision {
}
}
/// Crate-internal `HttpRoleRuntime` state shared across the owning crate.
pub(crate) struct HttpRoleRuntime {
limits: crate::HttpRoleLimits,
bucket: std::sync::Mutex<std::option::Option<HttpTokenBucketState>>,
@@ -84,6 +85,7 @@ pub(crate) struct HttpRoleRuntime {
}
impl HttpRoleRuntime {
/// Creates a new `HttpRoleRuntime` value.
pub(crate) fn new(settings: &crate::HttpEndpointRoleSettings, notify: std::sync::Arc<tokio::sync::Notify>) -> Self {
let bucket = match settings.limits().requests_per_second() {
std::option::Option::Some(requests_per_second) => {
@@ -114,6 +116,7 @@ impl HttpRoleRuntime {
};
}
/// Returns the current availability.
pub(crate) fn availability(&self, now: std::time::Instant) -> crate::HttpEndpointAvailability {
if self.cooldown_remaining_at(now).is_some() {
return crate::HttpEndpointAvailability::RateLimited;
@@ -124,14 +127,17 @@ impl HttpRoleRuntime {
return crate::HttpEndpointAvailability::Available;
}
/// Returns the current cooldown remaining.
pub(crate) fn cooldown_remaining(&self) -> std::option::Option<std::time::Duration> {
return self.cooldown_remaining_at(std::time::Instant::now());
}
/// Returns the current max concurrent requests.
pub(crate) fn max_concurrent_requests(&self) -> std::option::Option<u32> {
return self.limits.max_concurrent_requests().map(|value| return value.get());
}
/// Returns the current in flight requests.
pub(crate) fn in_flight_requests(&self) -> std::option::Option<u32> {
let semaphore = match &self.semaphore {
std::option::Option::Some(value) => value,
@@ -149,18 +155,22 @@ impl HttpRoleRuntime {
return std::option::Option::Some(maximum.saturating_sub(available_u32));
}
/// Returns the current success count.
pub(crate) fn success_count(&self) -> u64 {
return self.success_count.load(std::sync::atomic::Ordering::Relaxed);
}
/// Returns the current failure count.
pub(crate) fn failure_count(&self) -> u64 {
return self.failure_count.load(std::sync::atomic::Ordering::Relaxed);
}
/// Returns the current rate limit count.
pub(crate) fn rate_limit_count(&self) -> u64 {
return self.rate_limit_count.load(std::sync::atomic::Ordering::Relaxed);
}
/// Attempts to acquire.
pub(crate) fn try_acquire(self: &std::sync::Arc<Self>, now: std::time::Instant) -> crate::RoleAdmissionAttempt {
if let std::option::Option::Some(remaining) = self.cooldown_remaining_at(now) {
let ready_at = match now.checked_add(remaining) {
@@ -189,6 +199,7 @@ impl HttpRoleRuntime {
return crate::RoleAdmissionAttempt::Ready(HttpConcurrencyPermit { semaphore_permit, notify: std::sync::Arc::clone(&self.notify) });
}
/// Records success.
pub(crate) fn record_success(&self) {
self.success_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
self.degraded.store(false, std::sync::atomic::Ordering::Relaxed);
@@ -196,6 +207,7 @@ impl HttpRoleRuntime {
return;
}
/// Records failure.
pub(crate) fn record_failure(&self) {
self.failure_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
self.degraded.store(true, std::sync::atomic::Ordering::Relaxed);
@@ -203,6 +215,7 @@ impl HttpRoleRuntime {
return;
}
/// Records rate limited.
pub(crate) fn record_rate_limited(&self, provider_retry_after: std::option::Option<std::time::Duration>) -> std::time::Duration {
self.failure_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
self.rate_limit_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
@@ -269,6 +282,7 @@ impl HttpRoleRuntime {
}
}
/// Crate-internal `RoleAdmissionAttempt` variants used by the owning crate.
pub(crate) enum RoleAdmissionAttempt {
Ready(HttpConcurrencyPermit),
BlockedUntil(std::time::Instant),
@@ -276,6 +290,7 @@ pub(crate) enum RoleAdmissionAttempt {
Unavailable,
}
/// Crate-internal `HttpConcurrencyPermit` state shared across the owning crate.
pub(crate) struct HttpConcurrencyPermit {
semaphore_permit: std::option::Option<tokio::sync::OwnedSemaphorePermit>,
notify: std::sync::Arc<tokio::sync::Notify>,

View File

@@ -1,9 +1,9 @@
// file: crates/ksp-onchain-transport-lib/src/rpc_accounts.rs
// version: 5
// version: 6
const MAX_MEMCMP_BYTES: usize = 128;
const MAX_MULTIPLE_ACCOUNTS: usize = 100;
const MAX_PROGRAM_ACCOUNT_FILTERS: usize = 4;
const MAX_MEMCMP_BYTES: usize = 128;
/// Account-data encoding accepted by Solana HTTP account methods.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
@@ -120,6 +120,7 @@ impl SolanaAccountInfoConfig {
return self.context.min_context_slot();
}
/// Returns whether empty.
pub(crate) fn is_empty(&self) -> bool {
return self.encoding.is_none() && self.data_slice.is_none() && self.commitment().is_none() && self.min_context_slot().is_none();
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/rpc_cluster.rs
// version: 5
// version: 6
const MAX_GET_SLOT_LEADERS: u64 = 5_000;
@@ -297,9 +297,11 @@ impl SolanaLeaderScheduleConfig {
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
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();
if let std::option::Option::Some(identity) = self.identity.as_ref() {
@@ -420,6 +422,7 @@ impl SolanaVoteAccountsConfig {
pub const fn delinquent_slot_distance(&self) -> std::option::Option<u64> {
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();
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/rpc_method.rs
// version: 3
// version: 4
const CURRENT_HTTP_RPC_METHODS: [crate::HttpRpcMethodDescriptor; 52] = [
crate::HttpRpcMethodDescriptor::new(
@@ -1080,7 +1080,6 @@ 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] {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/rpc_transactions.rs
// version: 9
// version: 10
const MAX_RECENT_PRIORITIZATION_FEE_ACCOUNTS: usize = 128;
const MAX_SIGNATURES_FOR_ADDRESS_LIMIT: usize = 1_000;
@@ -1389,7 +1389,6 @@ impl crate::HttpTransportPool {
);
}
}
let mut params = std::vec![serde_json::Value::String(transaction.to_owned())];
if let std::option::Option::Some(config) = config
&& !config.is_empty()

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/settings.rs
// version: 5
// version: 6
/// Runtime HTTP endpoint URL owned by Transport.
///
@@ -446,6 +446,7 @@ impl HttpTransportSettings {
}
}
/// Validates endpoint settings.
pub(crate) fn validate_endpoint_settings(endpoint: &crate::HttpEndpointSettings) -> ksp_core_lib::Result<()> {
return validate_endpoint(endpoint, 0);
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs
// version: 23
// version: 24
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
@@ -442,7 +442,6 @@ fn public_v0_2_4_pre_007_simple_economics_wrappers_are_available_from_crate_root
let _get_inflation_rate = ksp_onchain_transport_lib::HttpTransportPool::get_inflation_rate;
let _get_stake_minimum_delegation = ksp_onchain_transport_lib::HttpTransportPool::get_stake_minimum_delegation;
let _get_supply = ksp_onchain_transport_lib::HttpTransportPool::get_supply;
let supply_config = ksp_onchain_transport_lib::SolanaSupplyConfig::new(
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Processed),
std::option::Option::Some(false),

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
// version: 21
// version: 22
//! Release-level completeness canaries for the staged HTTP wrapper sequence.
@@ -681,7 +681,6 @@ fn release_v0_2_4_pre_009_final_http_inventory_and_coverage_partition_are_exact(
"getSnapshotSlot",
"getStakeActivation",
];
let current = ksp_onchain_transport_lib::current_http_rpc_methods();
let historical = ksp_onchain_transport_lib::historical_http_rpc_methods();
let mut actual_current = std::vec::Vec::with_capacity(current.len());
@@ -691,7 +690,6 @@ fn release_v0_2_4_pre_009_final_http_inventory_and_coverage_partition_are_exact(
let mut v0_2_3 = 0_usize;
let mut v0_2_4 = 0_usize;
let mut historical_current = 0_usize;
for descriptor in current {
actual_current.push(descriptor.method());
assert_eq!(descriptor.runtime_status(), ksp_onchain_transport_lib::RpcRuntimeStatus::Supported);
@@ -719,7 +717,6 @@ fn release_v0_2_4_pre_009_final_http_inventory_and_coverage_partition_are_exact(
assert_eq!(descriptor.coverage_release(), ksp_onchain_transport_lib::HttpRpcCoverageRelease::Historical);
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::NotApplicable);
}
actual_current.sort_unstable();
actual_historical.sort_unstable();
let mut expected_current = expected_current;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/tests/transport_devnet_smoke.rs
// version: 5
// version: 6
//! Opt-in live Devnet smoke for representative pure Transport reads across the complete HTTP typed surface.
@@ -45,7 +45,6 @@ fn devnet_pool() -> ksp_core_lib::Result<ksp_onchain_transport_lib::HttpTranspor
async fn programmatic_devnet_transport_reaches_accounts_tokens_cluster_transactions_blocks_and_economics_reads() {
let pool = devnet_pool().expect("programmatic Devnet Transport settings must construct a pool");
let role = ksp_onchain_transport_lib::HttpRoleName::new("default");
let account_config = ksp_onchain_transport_lib::SolanaAccountInfoConfig::new(
std::option::Option::Some(ksp_onchain_transport_lib::SolanaAccountEncoding::Base64),
std::option::Option::None,
@@ -58,7 +57,6 @@ async fn programmatic_devnet_transport_reaches_accounts_tokens_cluster_transacti
.expect("Devnet getAccountInfo smoke must succeed");
assert!(account.context().slot() > 0);
assert!(account.value().is_some());
// Follow the current official Devnet example shape with an ordinary owner, the canonical SPL Token program selector,
// and an explicit finalized/jsonParsed config. The owner need not retain any token account; an empty list remains valid.
let token_owner = "A1TMhSGzQxMr1TboBKtgixKz1sS6REASMxPo1qsyTSJd"
@@ -79,7 +77,6 @@ async fn programmatic_devnet_transport_reaches_accounts_tokens_cluster_transacti
.await
.expect("Devnet getTokenAccountsByOwner smoke must succeed with the documented finalized/jsonParsed request shape");
assert!(token_accounts.context().slot() > 0);
let epoch = pool
.get_epoch_info(
&role,
@@ -91,10 +88,8 @@ async fn programmatic_devnet_transport_reaches_accounts_tokens_cluster_transacti
.await
.expect("Devnet getEpochInfo smoke must succeed");
assert!(epoch.absolute_slot() > 0);
let vote_accounts = pool.get_vote_accounts(&role, std::option::Option::None).await.expect("Devnet getVoteAccounts smoke must succeed");
assert!(!vote_accounts.current().is_empty() || !vote_accounts.delinquent().is_empty());
let transaction_context = ksp_onchain_transport_lib::SolanaContextConfig::new(
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Finalized),
std::option::Option::None,
@@ -105,27 +100,22 @@ async fn programmatic_devnet_transport_reaches_accounts_tokens_cluster_transacti
.expect("Devnet getLatestBlockhash smoke must succeed");
assert!(!latest_blockhash.value().blockhash().is_empty());
assert!(latest_blockhash.value().last_valid_block_height() > 0);
let blockhash_valid = pool
.is_blockhash_valid(&role, latest_blockhash.value().blockhash(), std::option::Option::Some(&transaction_context))
.await
.expect("Devnet isBlockhashValid smoke must succeed");
assert_eq!(blockhash_valid.value(), &true);
let transaction_count = pool
.get_transaction_count(&role, std::option::Option::Some(&transaction_context))
.await
.expect("Devnet getTransactionCount smoke must succeed");
assert!(transaction_count > 0);
let block_height = pool.get_block_height(&role, std::option::Option::Some(&transaction_context)).await.expect("Devnet getBlockHeight smoke must succeed");
assert!(block_height > 0);
let inflation_rate = pool.get_inflation_rate(&role).await.expect("Devnet getInflationRate smoke must succeed");
assert!(inflation_rate.total().is_finite());
assert!(inflation_rate.validator().is_finite());
assert!(inflation_rate.foundation().is_finite());
let stake_minimum_delegation = pool
.get_stake_minimum_delegation(&role, std::option::Option::Some(&transaction_context))
.await

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_accounts.rs
// version: 3
// version: 4
#[test]
fn account_config_serializes_all_common_fields() {
@@ -64,7 +64,6 @@ fn staged_largest_and_keyed_account_helpers_match_wire_shapes() {
std::option::Option::Some(true),
);
assert_eq!(config.to_json_value(), serde_json::json!({"commitment":"finalized","filter":"nonCirculating","sortResults":true}));
let keyed = crate::SolanaKeyedAccount::decode_wire(
"fixture",
serde_json::json!({
@@ -82,7 +81,6 @@ fn staged_largest_and_keyed_account_helpers_match_wire_shapes() {
.expect("keyed account must decode");
assert_eq!(keyed.pubkey().to_string(), "11111111111111111111111111111111");
assert_eq!(keyed.account().lamports(), 42);
let balance = crate::SolanaAccountBalance::decode_wire("fixture", serde_json::json!({"address":"11111111111111111111111111111111","lamports":99}))
.expect("account balance must decode");
assert_eq!(balance.address().to_string(), "11111111111111111111111111111111");
@@ -417,7 +415,6 @@ async fn typed_get_program_accounts_rejects_filter_cardinality_and_oversized_raw
let error = result.expect_err("more than four program-account filters must be rejected locally");
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
assert_eq!(error.context()[1].value(), "5");
let oversized = crate::SolanaProgramAccountsConfig::new(
crate::SolanaAccountInfoConfig::default(),
std::vec![crate::SolanaProgramAccountFilter::Memcmp(crate::SolanaMemcmpFilter::new(0, crate::SolanaMemcmpBytes::Bytes(std::vec![0_u8; 129]),))],

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_blocks.rs
// version: 5
// version: 6
#[test]
fn transaction_details_and_get_block_config_preserve_all_modern_options() {
@@ -280,7 +280,6 @@ async fn typed_get_block_time_preserves_timestamp_and_null() {
assert_eq!(timestamp, std::option::Option::Some(1_787_072_400));
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([430000123]));
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block_time.null.json"));
let pool = pool_for_url(url.as_str());
let timestamp = pool.get_block_time(&crate::HttpRoleName::new("default"), 430_000_124).await.expect("null block time fixture must succeed");
@@ -356,7 +355,6 @@ async fn typed_get_blocks_allows_reversed_and_boundary_ranges_but_rejects_oversi
assert!(blocks.is_empty());
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([430000100, 430000099]));
let (url, handle) = serve_once(include_str!("../fixtures/http/get_blocks.success.json"));
let pool = pool_for_url(url.as_str());
let blocks = pool
@@ -366,7 +364,6 @@ async fn typed_get_blocks_allows_reversed_and_boundary_ranges_but_rejects_oversi
assert_eq!(blocks, std::vec![430_000_100, 430_000_103, 430_000_109]);
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([10, 500010]));
let pool = pool_for_url("http://127.0.0.1:9");
let result = pool.get_blocks(&role, 10, std::option::Option::Some(500_011), std::option::Option::None).await;
let error = result.expect_err("range above 500000 must reject before I/O");
@@ -387,7 +384,6 @@ async fn typed_block_range_wrappers_reject_processed_commitment_before_io() {
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
assert_eq!(error.context()[1].key(), "commitment");
assert_eq!(error.context()[1].value(), "processed");
let get_blocks_with_limit = pool.get_blocks_with_limit(&role, 1, 1, std::option::Option::Some(&config)).await;
let error = get_blocks_with_limit.expect_err("getBlocksWithLimit processed commitment must reject before I/O");
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
@@ -404,7 +400,6 @@ async fn typed_get_blocks_with_limit_accepts_zero_and_maximum_and_rejects_above_
assert!(blocks.is_empty());
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([430000200, 0]));
let config = crate::SolanaContextConfig::new(std::option::Option::Some(crate::SolanaCommitment::Finalized), std::option::Option::Some(430_000_000));
let (url, handle) = serve_once(include_str!("../fixtures/http/get_blocks_with_limit.success.json"));
let pool = pool_for_url(url.as_str());
@@ -415,7 +410,6 @@ async fn typed_get_blocks_with_limit_accepts_zero_and_maximum_and_rejects_above_
assert_eq!(blocks, std::vec![430_000_200, 430_000_201, 430_000_205]);
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([430000200,500000,{"commitment":"finalized","minContextSlot":430000000}]));
let pool = pool_for_url("http://127.0.0.1:9");
let result = pool.get_blocks_with_limit(&role, 1, 500_001, std::option::Option::None).await;
let error = result.expect_err("getBlocksWithLimit above 500000 must reject before I/O");
@@ -497,7 +491,6 @@ async fn typed_get_block_production_omits_empty_config_and_supports_open_range_w
assert_eq!(response.value().range().last_slot(), 430_000_099);
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([]));
let range = crate::SolanaBlockProductionRange::new(430_000_000, std::option::Option::None);
let config = crate::SolanaBlockProductionConfig::new(
std::option::Option::Some(crate::SolanaCommitment::Processed),

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_cluster.rs
// version: 4
// version: 5
#[test]
fn cluster_node_fixture_preserves_v4_client_id_and_optional_fields() {
@@ -59,19 +59,16 @@ fn epoch_snapshot_and_leader_helpers_preserve_wire_shapes() {
)
.expect("epoch info must decode");
assert_eq!(epoch.transaction_count(), std::option::Option::None);
let schedule = crate::SolanaEpochSchedule::decode_wire(
"getEpochSchedule",
serde_json::json!({"firstNormalEpoch":1,"firstNormalSlot":32,"leaderScheduleSlotOffset":32,"slotsPerEpoch":64,"warmup":false}),
)
.expect("epoch schedule must decode");
assert_eq!(schedule.slots_per_epoch(), 64);
let snapshot = crate::SolanaSnapshotSlotInfo::decode_wire("getHighestSnapshotSlot", serde_json::json!({"full":100,"incremental":null}))
.expect("snapshot info must decode");
assert_eq!(snapshot.full(), 100);
assert_eq!(snapshot.incremental(), std::option::Option::None);
let leader = crate::SolanaLeaderSchedule::decode_wire("getLeaderSchedule", serde_json::json!({"11111111111111111111111111111111":[0,2,4]}))
.expect("leader schedule must decode");
assert_eq!(leader.entries().len(), 1);
@@ -90,7 +87,6 @@ fn vote_status_and_config_helpers_preserve_wire_shapes() {
config.to_json_value(),
serde_json::json!({"commitment":"finalized","votePubkey":"11111111111111111111111111111111","keepUnstakedDelinquents":true,"delinquentSlotDistance":128})
);
let status = crate::SolanaVoteAccountStatus::decode_wire(
"getVoteAccounts",
serde_json::json!({
@@ -363,7 +359,6 @@ async fn typed_get_slot_serializes_context_config_and_omits_empty_config() {
assert_eq!(slot, 430_000_020);
let request = configured_handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([{"commitment":"confirmed","minContextSlot":429999999}]));
let (empty_url, empty_handle) = serve_once(include_str!("../fixtures/http/get_slot.success.json"));
let empty_pool = pool_for_url(empty_url.as_str());
let empty = crate::SolanaContextConfig::default();

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_economics.rs
// version: 3
// version: 4
#[test]
fn inflation_reward_config_preserves_epoch_commitment_and_min_context_slot() {
@@ -346,7 +346,6 @@ async fn typed_get_inflation_reward_serializes_full_config_and_preserves_positio
let fourth = rewards[3].as_ref().expect("fourth reward must be present");
assert_eq!(fourth.commission(), std::option::Option::Some(3));
assert!(fourth.commission_bps().is_null());
let request = handle.join().expect("fixture server must join");
let body = request_body(request.as_str());
assert_eq!(body["method"], serde_json::json!("getInflationReward"));

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_transactions.rs
// version: 8
// version: 9
#[test]
fn transaction_encoding_strings_match_current_and_legacy_wire_labels() {
@@ -19,7 +19,6 @@ fn wire_field_preserves_omitted_null_and_value_states() {
#[serde(default)]
field: crate::SolanaWireField<u64>,
}
let omitted = serde_json::from_value::<Holder>(serde_json::json!({})).expect("omitted fixture must decode");
let null = serde_json::from_value::<Holder>(serde_json::json!({"field": null})).expect("null fixture must decode");
let value = serde_json::from_value::<Holder>(serde_json::json!({"field": 7})).expect("value fixture must decode");
@@ -402,7 +401,6 @@ fn serve_transaction_status_then_body(
let first_request = read_transaction_request(&mut first_stream);
let first_response = format!("HTTP/1.1 {status_line}\r\nContent-Length: 0\r\nConnection: close\r\n\r\n");
std::io::Write::write_all(&mut first_stream, first_response.as_bytes()).expect("fixture first response must write");
listener.set_nonblocking(true).expect("fixture listener must become nonblocking");
let deadline = std::time::Instant::now() + std::time::Duration::from_millis(500);
return loop {
@@ -612,7 +610,6 @@ async fn typed_get_recent_prioritization_fees_omits_optional_accounts_and_reject
assert_eq!(fees.len(), 2);
let request = handle.join().expect("fixture server must join");
assert_eq!(transaction_request_body(request.as_str())["params"], serde_json::json!([]));
let account = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture pubkey must parse");
let max_accounts = std::vec![account; 128];
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/get_recent_prioritization_fees.success.json"));
@@ -625,7 +622,6 @@ async fn typed_get_recent_prioritization_fees_omits_optional_accounts_and_reject
let request = handle.join().expect("fixture server must join");
let body = transaction_request_body(request.as_str());
assert_eq!(body["params"][0].as_array().expect("address parameter must be an array").len(), 128);
let invalid_pool = transaction_pool_for_url("http://127.0.0.1:9");
let accounts = std::vec![account; 129];
let result = invalid_pool.get_recent_prioritization_fees(&crate::HttpRoleName::new("default"), std::option::Option::Some(accounts.as_slice())).await;
@@ -768,7 +764,6 @@ async fn typed_get_signature_statuses_accepts_256_and_rejects_more_than_256_befo
let request = handle.join().expect("fixture server must join");
let body = transaction_request_body(request.as_str());
assert_eq!(body["params"][0].as_array().expect("signature parameter must be an array").len(), 256);
let invalid_pool = transaction_pool_for_url("http://127.0.0.1:9");
let signatures = std::vec!["signature".to_owned(); 257];
let result = invalid_pool.get_signature_statuses(&crate::HttpRoleName::new("default"), signatures.as_slice(), std::option::Option::None).await;
@@ -804,7 +799,6 @@ async fn typed_get_transaction_modern_supports_omitted_and_explicit_empty_config
assert!(result.is_none());
let request = handle.join().expect("fixture server must join");
assert_eq!(transaction_request_body(request.as_str())["params"], serde_json::json!(["fixture-signature"]));
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/get_transaction.null.json"));
let pool = transaction_pool_for_url(url.as_str());
let config = crate::SolanaGetTransactionConfig::default();
@@ -1006,7 +1000,6 @@ async fn typed_request_airdrop_exposes_runtime_config_and_canonicalizes_empty_co
{"recentBlockhash":"recent-blockhash-fixture","commitment":"finalized"}
])
);
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/request_airdrop.success.json"));
let pool = transaction_pool_for_url(url.as_str());
let empty = crate::SolanaRequestAirdropConfig::default();
@@ -1050,7 +1043,6 @@ async fn typed_send_transaction_exposes_all_current_options_and_both_binary_enco
}
])
);
let base58 = crate::SolanaSendTransactionConfig::new(
std::option::Option::None,
std::option::Option::None,
@@ -1066,7 +1058,6 @@ async fn typed_send_transaction_exposes_all_current_options_and_both_binary_enco
.expect("sendTransaction base58 fixture must succeed");
let request = handle.join().expect("fixture server must join");
assert_eq!(transaction_request_body(request.as_str())["params"], serde_json::json!(["opaque-base58-transaction", {"encoding":"base58"}]));
let empty = crate::SolanaSendTransactionConfig::default();
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/send_transaction.success.json"));
let pool = transaction_pool_for_url(url.as_str());
@@ -1086,7 +1077,6 @@ async fn typed_write_wrappers_preserve_rpc_application_errors_without_transport_
let result = pool.request_airdrop(&crate::HttpRoleName::new("default"), &recipient, 1, std::option::Option::None).await;
assert_eq!(result.expect_err("requestAirdrop RPC error must propagate").code(), crate::ERROR_CODE_RPC_APPLICATION_ERROR);
handle.join().expect("fixture server must join");
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/send_transaction.preflight_error.json"));
let pool = transaction_pool_for_urls(&[(url.as_str(), 10)], std::time::Duration::from_millis(500), 3);
let result = pool.send_transaction(&crate::HttpRoleName::new("default"), "opaque-transaction", std::option::Option::None).await;
@@ -1104,7 +1094,6 @@ async fn typed_write_wrappers_never_resend_after_http_429_dispatch() {
let (count, request) = handle.join().expect("fixture server must join");
assert_eq!(count, 1);
assert_eq!(transaction_request_body(request.as_str())["method"], serde_json::json!("requestAirdrop"));
let (url, handle) = serve_transaction_status_and_count("429 Too Many Requests");
let pool = transaction_pool_for_urls(&[(url.as_str(), 10)], std::time::Duration::from_millis(500), 3);
let result = pool.send_transaction(&crate::HttpRoleName::new("default"), "opaque-transaction", std::option::Option::None).await;
@@ -1123,7 +1112,6 @@ async fn typed_write_wrappers_never_resend_after_temporary_http_dispatch() {
assert_eq!(result.expect_err("airdrop 503 must stop after dispatch").code(), crate::ERROR_CODE_HTTP_REQUEST_FAILED);
let (count, _) = handle.join().expect("fixture server must join");
assert_eq!(count, 1);
let (url, handle) = serve_transaction_status_and_count("503 Service Unavailable");
let pool = transaction_pool_for_urls(&[(url.as_str(), 10)], std::time::Duration::from_millis(500), 3);
let result = pool.send_transaction(&crate::HttpRoleName::new("default"), "opaque-transaction", std::option::Option::None).await;
@@ -1141,7 +1129,6 @@ async fn typed_write_wrappers_never_resend_after_ambiguous_timeout_dispatch() {
assert_eq!(result.expect_err("airdrop timeout must stop after ambiguous dispatch").code(), crate::ERROR_CODE_TIMEOUT);
let (count, _) = handle.join().expect("fixture server must join");
assert_eq!(count, 1);
let (url, handle) = serve_transaction_timeout_and_count(std::time::Duration::from_millis(80));
let pool = transaction_pool_for_urls(&[(url.as_str(), 10)], std::time::Duration::from_millis(20), 3);
let result = pool.send_transaction(&crate::HttpRoleName::new("default"), "opaque-transaction", std::option::Option::None).await;
@@ -1163,7 +1150,6 @@ async fn typed_write_wrappers_can_retry_when_connection_failure_proves_not_dispa
assert_eq!(result, "airdrop-fixture-signature-111111111111111111111111111111111111111111111111");
let request = handle.join().expect("secondary fixture server must join");
assert_eq!(transaction_request_body(request.as_str())["method"], serde_json::json!("requestAirdrop"));
let closed = unused_local_url();
let (secondary, handle) = serve_transaction_once(include_str!("../fixtures/http/send_transaction.success.json"));
let pool = transaction_pool_for_urls(&[(closed.as_str(), 10), (secondary.as_str(), 10)], std::time::Duration::from_millis(500), 1);
@@ -1210,7 +1196,6 @@ async fn typed_simulate_transaction_serializes_complete_config_and_preserves_ric
let replacement = response.value().replacement_blockhash().value().expect("replacement blockhash must be present");
assert_eq!(replacement.blockhash(), "ComputeBudget111111111111111111111111111111");
assert_eq!(replacement.last_valid_block_height(), 430_123_999);
let request = handle.join().expect("fixture server must join");
let body = transaction_request_body(request.as_str());
assert_eq!(body["method"], serde_json::json!("simulateTransaction"));
@@ -1250,7 +1235,6 @@ async fn typed_simulate_transaction_supports_base58_and_omits_empty_config() {
.expect("simulateTransaction base58 fixture must succeed");
let request = handle.join().expect("fixture server must join");
assert_eq!(transaction_request_body(request.as_str())["params"], serde_json::json!(["opaque-base58-transaction", {"encoding":"base58"}]));
let empty = crate::SolanaSimulateTransactionConfig::default();
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/simulate_transaction.partial.json"));
let pool = transaction_pool_for_url(url.as_str());
@@ -1303,7 +1287,6 @@ async fn typed_simulate_transaction_rejects_deterministic_invalid_configs_before
);
let result = pool.simulate_transaction(&crate::HttpRoleName::new("default"), "opaque-transaction", std::option::Option::Some(&conflicting)).await;
assert_eq!(result.expect_err("conflicting simulation flags must fail before I/O").code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
let address = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture pubkey must parse");
let rejected_encodings = [crate::SolanaAccountEncoding::Binary, crate::SolanaAccountEncoding::Base58];
for encoding in rejected_encodings {

View File

@@ -1,90 +1,89 @@
// file: crates/ksp-wallet-lib/src/constants.rs
// version: 5
// version: 6
//! Wallet-owned constants.
/// Exact magic string required by every native `.kspwallet` document.
pub const KSPWALLET_MAGIC: &str = "KSPWALLET";
/// Native Wallet format version implemented by the V1 codec.
pub const KSPWALLET_FORMAT_VERSION_V1: u32 = 1;
/// Exact magic string required by every native `.kspwallet` document.
pub const KSPWALLET_MAGIC: &str = "KSPWALLET";
/// Maximum accepted `.kspwallet` document size before JSON parsing.
pub const KSPWALLET_MAX_FILE_BYTES: usize = 1024 * 1024;
/// Maximum accepted Solana CLI keypair JSON transfer size before parsing.
pub const KSPWALLET_TRANSFER_MAX_SOLANA_CLI_JSON_BYTES: usize = 1024;
/// Maximum accepted canonical Base58 keypair transfer size before decoding.
pub const KSPWALLET_TRANSFER_MAX_BASE58_BYTES: usize = 128;
/// Maximum protected alias size in UTF-8 bytes for metadata V1.
pub const KSPWALLET_V1_MAX_ALIAS_BYTES: usize = 256;
/// Maximum number of protected notes in metadata V1.
pub const KSPWALLET_V1_MAX_NOTES: usize = 64;
/// Maximum protected note text size in UTF-8 bytes for metadata V1.
pub const KSPWALLET_V1_MAX_NOTE_TEXT_BYTES: usize = 8 * 1024;
/// Maximum metadata plaintext size before V1 encryption.
pub const KSPWALLET_V1_MAX_METADATA_PLAINTEXT_BYTES: usize = 64 * 1024;
/// Maximum password size in exact UTF-8 input bytes.
pub const KSPWALLET_V1_MAX_PASSWORD_BYTES: usize = 1024;
/// Byte length of every V1 key-slot identifier.
pub const KSPWALLET_V1_SLOT_ID_BYTES: usize = 16;
/// Byte length of every protected metadata note identifier.
pub const KSPWALLET_V1_NOTE_ID_BYTES: usize = 16;
/// Byte length of an Ed25519 public key used as Wallet format authority.
pub const KSPWALLET_V1_ED25519_PUBLIC_KEY_BYTES: usize = 32;
/// Byte length of an Ed25519 detached state signature.
pub const KSPWALLET_V1_ED25519_SIGNATURE_BYTES: usize = 64;
/// Byte length of every Solana Ed25519 message signature returned by Wallet OWNER.
pub const KSPWALLET_SOLANA_SIGNATURE_BYTES: usize = 64;
/// Byte length of an XChaCha20-Poly1305 nonce.
pub const KSPWALLET_V1_XCHACHA_NONCE_BYTES: usize = 24;
/// Maximum accepted canonical Base58 keypair transfer size before decoding.
pub const KSPWALLET_TRANSFER_MAX_BASE58_BYTES: usize = 128;
/// Maximum accepted Solana CLI keypair JSON transfer size before parsing.
pub const KSPWALLET_TRANSFER_MAX_SOLANA_CLI_JSON_BYTES: usize = 1024;
/// Byte length of the Poly1305 authentication tag appended to each ciphertext.
pub const KSPWALLET_V1_AEAD_TAG_BYTES: usize = 16;
/// Argon2 version serialized by V1 key slots.
pub const KSPWALLET_V1_ARGON2_VERSION: u32 = 19;
/// Default Argon2id memory cost for newly created V1 key slots, calibrated on the 2026-08-19 operator benchmark.
pub const KSPWALLET_V1_DEFAULT_ARGON2_MEMORY_KIB: u32 = 65_536;
/// Default Argon2id iteration count for newly created V1 key slots.
pub const KSPWALLET_V1_DEFAULT_ARGON2_ITERATIONS: u32 = 3;
/// Default Argon2id memory cost for newly created V1 key slots, calibrated on the 2026-08-19 operator benchmark.
pub const KSPWALLET_V1_DEFAULT_ARGON2_MEMORY_KIB: u32 = 65_536;
/// Default Argon2id parallelism for newly created V1 key slots.
pub const KSPWALLET_V1_DEFAULT_ARGON2_PARALLELISM: u32 = 1;
/// Salt size generated independently for every newly created V1 key slot.
pub const KSPWALLET_V1_DEFAULT_KDF_SALT_BYTES: usize = 32;
/// Exact plaintext size of the V1 OWNER-control compartment.
pub const KSPWALLET_V1_OWNER_CONTROL_PLAINTEXT_BYTES: usize = 96;
/// Exact plaintext size of the V1 Solana secret compartment.
pub const KSPWALLET_V1_SECRET_PLAINTEXT_BYTES: usize = 64;
/// Minimum accepted Argon2 salt size in bytes.
pub const KSPWALLET_V1_MIN_KDF_SALT_BYTES: usize = 16;
/// Maximum accepted Argon2 salt size in bytes.
pub const KSPWALLET_V1_MAX_KDF_SALT_BYTES: usize = 64;
/// Structural V1 ceiling for serialized Argon2 memory cost; creation defaults are benchmarked separately.
pub const KSPWALLET_V1_MAX_ARGON2_MEMORY_KIB: u32 = 1024 * 1024;
/// Byte length of an Ed25519 public key used as Wallet format authority.
pub const KSPWALLET_V1_ED25519_PUBLIC_KEY_BYTES: usize = 32;
/// Byte length of an Ed25519 detached state signature.
pub const KSPWALLET_V1_ED25519_SIGNATURE_BYTES: usize = 64;
/// Initial protected payload version used independently by control, metadata and secret compartments.
pub const KSPWALLET_V1_INITIAL_PAYLOAD_VERSION: u32 = 1;
/// Maximum protected alias size in UTF-8 bytes for metadata V1.
pub const KSPWALLET_V1_MAX_ALIAS_BYTES: usize = 256;
/// Structural V1 ceiling for serialized Argon2 iteration cost; creation defaults are benchmarked separately.
pub const KSPWALLET_V1_MAX_ARGON2_ITERATIONS: u32 = 64;
/// Structural V1 ceiling for serialized Argon2 memory cost; creation defaults are benchmarked separately.
pub const KSPWALLET_V1_MAX_ARGON2_MEMORY_KIB: u32 = 1024 * 1024;
/// Structural V1 ceiling for serialized Argon2 parallelism; creation defaults are benchmarked separately.
pub const KSPWALLET_V1_MAX_ARGON2_PARALLELISM: u32 = 64;
/// Maximum accepted Argon2 salt size in bytes.
pub const KSPWALLET_V1_MAX_KDF_SALT_BYTES: usize = 64;
/// Maximum number of key slots understood by format V1: exactly one OWNER plus optional VIEW.
pub const KSPWALLET_V1_MAX_KEY_SLOTS: usize = 2;
/// Maximum ciphertext size of one wrapped capability payload.
pub const KSPWALLET_V1_MAX_KEY_WRAP_CIPHERTEXT_BYTES: usize = 4096;
/// Maximum OWNER-control ciphertext size accepted by envelope V1.
pub const KSPWALLET_V1_MAX_OWNER_CONTROL_CIPHERTEXT_BYTES: usize = 4096;
/// Maximum metadata ciphertext size, including the AEAD tag over the bounded 64-KiB plaintext.
pub const KSPWALLET_V1_MAX_METADATA_CIPHERTEXT_BYTES: usize = KSPWALLET_V1_MAX_METADATA_PLAINTEXT_BYTES + KSPWALLET_V1_AEAD_TAG_BYTES;
/// Maximum metadata plaintext size before V1 encryption.
pub const KSPWALLET_V1_MAX_METADATA_PLAINTEXT_BYTES: usize = 64 * 1024;
/// Maximum number of protected notes in metadata V1.
pub const KSPWALLET_V1_MAX_NOTES: usize = 64;
/// Maximum protected note text size in UTF-8 bytes for metadata V1.
pub const KSPWALLET_V1_MAX_NOTE_TEXT_BYTES: usize = 8 * 1024;
/// Maximum OWNER-control ciphertext size accepted by envelope V1.
pub const KSPWALLET_V1_MAX_OWNER_CONTROL_CIPHERTEXT_BYTES: usize = 4096;
/// Maximum password size in exact UTF-8 input bytes.
pub const KSPWALLET_V1_MAX_PASSWORD_BYTES: usize = 1024;
/// Maximum OWNER-only secret ciphertext size accepted by envelope V1.
pub const KSPWALLET_V1_MAX_SECRET_CIPHERTEXT_BYTES: usize = 4096;
/// Initial protected payload version used independently by control, metadata and secret compartments.
pub const KSPWALLET_V1_INITIAL_PAYLOAD_VERSION: u32 = 1;
/// Domain separator for the OWNER state-signature transcript.
pub const KSPWALLET_V1_STATE_TRANSCRIPT_DOMAIN: &[u8] = b"KSPWALLET-V1-STATE";
/// Domain separator for OWNER key-slot wrapping AAD.
pub const KSPWALLET_V1_OWNER_SLOT_AAD_DOMAIN: &[u8] = b"KSPWALLET-V1-AAD-OWNER-SLOT";
/// Domain separator for VIEW key-slot wrapping AAD.
pub const KSPWALLET_V1_VIEW_SLOT_AAD_DOMAIN: &[u8] = b"KSPWALLET-V1-AAD-VIEW-SLOT";
/// Domain separator for OWNER-control compartment AAD.
pub const KSPWALLET_V1_OWNER_CONTROL_AAD_DOMAIN: &[u8] = b"KSPWALLET-V1-AAD-OWNER-CONTROL";
/// Domain separator for metadata compartment AAD.
pub const KSPWALLET_V1_METADATA_AAD_DOMAIN: &[u8] = b"KSPWALLET-V1-AAD-METADATA";
/// Minimum accepted Argon2 salt size in bytes.
pub const KSPWALLET_V1_MIN_KDF_SALT_BYTES: usize = 16;
/// Byte length of every protected metadata note identifier.
pub const KSPWALLET_V1_NOTE_ID_BYTES: usize = 16;
/// Domain separator for OWNER-control compartment AAD.
pub const KSPWALLET_V1_OWNER_CONTROL_AAD_DOMAIN: &[u8] = b"KSPWALLET-V1-AAD-OWNER-CONTROL";
/// Exact plaintext size of the V1 OWNER-control compartment.
pub const KSPWALLET_V1_OWNER_CONTROL_PLAINTEXT_BYTES: usize = 96;
/// Domain separator for OWNER key-slot wrapping AAD.
pub const KSPWALLET_V1_OWNER_SLOT_AAD_DOMAIN: &[u8] = b"KSPWALLET-V1-AAD-OWNER-SLOT";
/// Domain separator for OWNER-only secret compartment AAD.
pub const KSPWALLET_V1_SECRET_AAD_DOMAIN: &[u8] = b"KSPWALLET-V1-AAD-SECRET";
/// Exact plaintext size of the V1 Solana secret compartment.
pub const KSPWALLET_V1_SECRET_PLAINTEXT_BYTES: usize = 64;
/// Byte length of every V1 key-slot identifier.
pub const KSPWALLET_V1_SLOT_ID_BYTES: usize = 16;
/// Domain separator for the OWNER state-signature transcript.
pub const KSPWALLET_V1_STATE_TRANSCRIPT_DOMAIN: &[u8] = b"KSPWALLET-V1-STATE";
/// Domain separator for VIEW key-slot wrapping AAD.
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";

View File

@@ -1,10 +1,10 @@
// file: crates/ksp-wallet-lib/src/crypto.rs
// version: 3
// version: 4
//! In-memory cryptographic primitives for native `.kspwallet` V1.
use chacha20poly1305::KeyInit; // rust-rules: derive-import
use chacha20poly1305::aead::Aead; // rust-rules: derive-import
use chacha20poly1305::KeyInit; // rust-rules: trait-import
use chacha20poly1305::aead::Aead; // rust-rules: trait-import
/// Exact V1 content-key and password-derived-key size in bytes.
pub(crate) const SECRET_KEY_BYTES: usize = 32;
@@ -165,7 +165,6 @@ fn derive_argon2id(password: &[u8], salt: &[u8], memory_kib: u32, iterations: u3
{
return std::result::Result::Err(crypto_parameter_error());
}
let params_result = argon2::Params::new(memory_kib, iterations, parallelism, std::option::Option::Some(SECRET_KEY_BYTES));
let params = match params_result {
std::result::Result::Ok(params) => params,

View File

@@ -1,37 +1,37 @@
// file: crates/ksp-wallet-lib/src/error.rs
// version: 4
// version: 5
/// Error code used when an atomic Wallet persistence operation cannot publish a valid replacement.
pub const ERROR_CODE_ATOMIC_PERSISTENCE_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "atomic_persistence_failed");
/// Error code used when an authenticated Wallet structure cannot be verified.
pub const ERROR_CODE_AUTHENTICATION_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "authentication_failed");
/// Error code used when an operation requires a capability that the caller does not own.
pub const ERROR_CODE_CAPABILITY_INSUFFICIENT: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "capability_insufficient");
/// Error code used when an internal blocking cryptographic operation cannot complete.
pub const ERROR_CODE_CRYPTO_OPERATION_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "crypto_operation_failed");
/// Error code used when serialized cryptographic parameters are invalid or unsupported.
pub const ERROR_CODE_CRYPTO_PARAMETERS_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "crypto_parameters_invalid");
/// Error code used when a no-clobber create or import destination already exists.
pub const ERROR_CODE_DESTINATION_EXISTS: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "destination_exists");
/// Error code used when a native Wallet structure is invalid.
pub const ERROR_CODE_FORMAT_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "format_invalid");
/// Error code used when a native Wallet format version is unsupported.
pub const ERROR_CODE_FORMAT_VERSION_UNSUPPORTED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "format_version_unsupported");
/// Error code used when serialized cryptographic parameters are invalid or unsupported.
pub const ERROR_CODE_CRYPTO_PARAMETERS_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "crypto_parameters_invalid");
/// Error code used when the operating-system cryptographic random source cannot provide bytes.
pub const ERROR_CODE_RANDOMNESS_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "randomness_failed");
/// Error code used when an authenticated Wallet structure cannot be verified.
pub const ERROR_CODE_AUTHENTICATION_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "authentication_failed");
/// Error code used when an internal blocking cryptographic operation cannot complete.
pub const ERROR_CODE_CRYPTO_OPERATION_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "crypto_operation_failed");
/// Error code used when a VIEW unlock attempt fails without exposing a finer cryptographic oracle.
pub const ERROR_CODE_VIEW_UNLOCK_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "view_unlock_failed");
/// Error code used when an OWNER unlock attempt fails without exposing a finer cryptographic oracle.
pub const ERROR_CODE_OWNER_UNLOCK_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "owner_unlock_failed");
/// Error code used when an operation requires a capability that the caller does not own.
pub const ERROR_CODE_CAPABILITY_INSUFFICIENT: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "capability_insufficient");
/// Error code used when Wallet filesystem I/O fails.
pub const ERROR_CODE_IO_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "io_failed");
/// Error code used when a no-clobber create or import destination already exists.
pub const ERROR_CODE_DESTINATION_EXISTS: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "destination_exists");
/// Error code used when an atomic Wallet persistence operation cannot publish a valid replacement.
pub const ERROR_CODE_ATOMIC_PERSISTENCE_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "atomic_persistence_failed");
/// Error code used when an import/export transfer format is unsupported.
pub const ERROR_CODE_TRANSFER_FORMAT_UNSUPPORTED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "transfer_format_unsupported");
/// Error code used when imported or decoded key material is invalid.
pub const ERROR_CODE_KEY_MATERIAL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "key_material_invalid");
/// Error code used when a Wallet signing operation fails.
pub const ERROR_CODE_SIGNATURE_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "signature_failed");
/// Error code used when a protected Wallet note identifier is not present.
pub const ERROR_CODE_NOTE_NOT_FOUND: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "note_not_found");
/// Error code used when an OWNER unlock attempt fails without exposing a finer cryptographic oracle.
pub const ERROR_CODE_OWNER_UNLOCK_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "owner_unlock_failed");
/// Error code used when the operating-system cryptographic random source cannot provide bytes.
pub const ERROR_CODE_RANDOMNESS_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "randomness_failed");
/// Error code used when a Wallet signing operation fails.
pub const ERROR_CODE_SIGNATURE_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "signature_failed");
/// Error code used when an administrative replacement targets a different or stale Wallet state.
pub const ERROR_CODE_STATE_CONFLICT: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "state_conflict");
/// Error code used when an import/export transfer format is unsupported.
pub const ERROR_CODE_TRANSFER_FORMAT_UNSUPPORTED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "transfer_format_unsupported");
/// Error code used when a VIEW unlock attempt fails without exposing a finer cryptographic oracle.
pub const ERROR_CODE_VIEW_UNLOCK_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet", "view_unlock_failed");

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-wallet-lib/src/lib.rs
// version: 10
// version: 11
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -245,10 +245,32 @@ pub(crate) use self::crypto::random_nonce;
pub(crate) use self::crypto::unwrap_key;
/// Wraps one 32-byte content key with XChaCha20-Poly1305 and caller-provided domain-separated AAD.
pub(crate) use self::crypto::wrap_key;
/// Crate-internal `MetadataPayloadV1` state shared across the owning crate.
pub(crate) use self::payload::MetadataPayloadV1;
/// Decodes metadata payload.
pub(crate) use self::payload::decode_metadata_payload;
/// Decodes owner control payload.
pub(crate) use self::payload::decode_owner_control_payload;
/// Encodes initial metadata payload.
pub(crate) use self::payload::encode_initial_metadata_payload;
/// Encodes owner control payload.
pub(crate) use self::payload::encode_owner_control_payload;
/// Internal no-clobber native persistence path shared by transfer adapters.
pub(crate) use self::persistence::persist_new_wallet_content_v1;
/// Persists new wallet fault before publish.
#[cfg(test)]
pub(crate) use self::persistence::persist_new_wallet_fault_before_publish;
/// Persists new wallet for test.
#[cfg(test)]
pub(crate) use self::persistence::persist_new_wallet_for_test;
/// Replaces wallet fault before publish.
#[cfg(test)]
pub(crate) use self::persistence::replace_wallet_fault_before_publish;
/// Replaces wallet file v1.
pub(crate) use self::persistence::replace_wallet_file_v1;
/// Replaces wallet for test.
#[cfg(test)]
pub(crate) use self::persistence::replace_wallet_for_test;
/// Internal deterministic compartment-AAD codec shared by Wallet crypto layers.
pub(crate) use self::transcript::compartment_aad;
/// Internal deterministic key-slot-AAD codec shared by Wallet crypto layers.
@@ -257,5 +279,11 @@ pub(crate) use self::transcript::slot_aad;
pub(crate) use self::transcript::state_transcript;
/// Internal no-clobber transfer-file writer used only by OWNER export.
pub(crate) use self::transfer::write_wallet_transfer_file_v1;
/// Crate-internal `OwnerStateV1` state shared across the owning crate.
pub(crate) use self::wallet::OwnerStateV1;
/// Crate-internal `ViewStateV1` state shared across the owning crate.
pub(crate) use self::wallet::ViewStateV1;
/// Internal imported-keypair creation path shared by transfer adapters.
pub(crate) use self::wallet::create_wallet_v1_from_keypair;
/// Verifies state signature.
pub(crate) use self::wallet::verify_state_signature;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-wallet-lib/src/metadata.rs
// version: 3
// version: 4
/// Protected metadata requested when creating a new native Wallet.
///
@@ -17,6 +17,7 @@ impl WalletCreateMetadataV1 {
return Self { alias, note_texts };
}
/// Consumes this value and returns parts.
pub(crate) fn into_parts(self) -> (std::option::Option<std::string::String>, std::vec::Vec<std::string::String>) {
return (self.alias, self.note_texts);
}
@@ -40,6 +41,7 @@ pub struct WalletNote {
}
impl WalletNote {
/// Creates a new `WalletNote` value.
pub(crate) fn new(id: std::string::String, text: std::string::String) -> Self {
return Self { id, text };
}
@@ -74,6 +76,7 @@ pub struct WalletInfo {
}
impl WalletInfo {
/// Creates a new `WalletInfo` value.
pub(crate) fn new(
capability: crate::WalletCapability,
pubkey: ksp_core_lib::Pubkey,
@@ -135,6 +138,7 @@ pub struct LockedWalletInfo {
}
impl LockedWalletInfo {
/// Creates a new `LockedWalletInfo` value.
pub(crate) const fn new(view_enabled: bool) -> Self {
return Self { format_version: crate::KSPWALLET_FORMAT_VERSION_V1, view_enabled };
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-wallet-lib/src/owner.rs
// version: 5
// version: 6
/// Authorized OWNER capability handle.
///
@@ -7,11 +7,12 @@
/// material remains encapsulated and is never exposed through a general-purpose getter.
pub struct WalletOwner {
info: crate::WalletInfo,
state: crate::wallet::OwnerStateV1,
state: crate::OwnerStateV1,
}
impl WalletOwner {
pub(crate) fn from_unlocked(info: crate::WalletInfo, state: crate::wallet::OwnerStateV1) -> Self {
/// Builds `WalletOwner` from unlocked.
pub(crate) fn from_unlocked(info: crate::WalletInfo, state: crate::OwnerStateV1) -> Self {
return Self { info, state };
}
@@ -307,7 +308,7 @@ async fn persist_staged(
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return crate::persistence::replace_wallet_file_v1(destination, expected_current.clone(), serialized).await;
return crate::replace_wallet_file_v1(destination, expected_current.clone(), serialized).await;
}
#[cfg(test)]

View File

@@ -1,11 +1,12 @@
// file: crates/ksp-wallet-lib/src/payload.rs
// version: 4
// version: 5
//! Plaintext payload codecs protected inside native `.kspwallet` V1 compartments.
use base64::Engine; // rust-rules: derive-import
use std::str::FromStr; // rust-rules: derive-import
use base64::Engine; // rust-rules: trait-import
use std::str::FromStr; // rust-rules: trait-import
/// Crate-internal `MetadataPayloadV1` state shared across the owning crate.
pub(crate) struct MetadataPayloadV1 {
pubkey: ksp_core_lib::Pubkey,
alias: std::option::Option<std::string::String>,
@@ -13,16 +14,19 @@ pub(crate) struct MetadataPayloadV1 {
}
impl crate::MetadataPayloadV1 {
/// Returns the current pubkey.
pub(crate) const fn pubkey(&self) -> &ksp_core_lib::Pubkey {
return &self.pubkey;
}
/// Builds `MetadataPayloadV1` from info.
pub(crate) fn from_info(info: &crate::WalletInfo) -> Self {
let alias = info.alias().map(std::string::String::from);
let notes = info.notes().to_vec();
return Self { pubkey: *info.pubkey(), alias, notes };
}
/// Updates alias.
pub(crate) fn set_alias(&mut self, alias: std::option::Option<std::string::String>) -> ksp_core_lib::Result<()> {
let validation_result = validate_alias(alias.as_deref());
if let std::result::Result::Err(error) = validation_result {
@@ -32,6 +36,7 @@ impl crate::MetadataPayloadV1 {
return std::result::Result::Ok(());
}
/// Adds note.
pub(crate) fn add_note(&mut self, text: std::string::String) -> ksp_core_lib::Result<std::string::String> {
if self.notes.len() >= crate::KSPWALLET_V1_MAX_NOTES {
return std::result::Result::Err(metadata_error("Wallet note count exceeds the V1 limit", "metadata.notes"));
@@ -40,7 +45,7 @@ impl crate::MetadataPayloadV1 {
if let std::result::Result::Err(error) = validation_result {
return std::result::Result::Err(error);
}
let note_id_bytes = match crate::crypto::random_bytes::<{ crate::KSPWALLET_V1_NOTE_ID_BYTES }>() {
let note_id_bytes = match crate::random_bytes::<{ crate::KSPWALLET_V1_NOTE_ID_BYTES }>() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
@@ -54,6 +59,7 @@ impl crate::MetadataPayloadV1 {
return std::result::Result::Ok(note_id);
}
/// Updates note.
pub(crate) fn update_note(&mut self, note_id: &str, text: std::string::String) -> ksp_core_lib::Result<()> {
let validation_result = validate_note_text(text.as_str());
if let std::result::Result::Err(error) = validation_result {
@@ -70,6 +76,7 @@ impl crate::MetadataPayloadV1 {
return std::result::Result::Err(note_not_found_error());
}
/// Deletes note.
pub(crate) fn delete_note(&mut self, note_id: &str) -> ksp_core_lib::Result<()> {
let mut index = 0_usize;
while index < self.notes.len() {
@@ -82,6 +89,7 @@ impl crate::MetadataPayloadV1 {
return std::result::Result::Err(note_not_found_error());
}
/// Executes the crate-internal encode operation for `MetadataPayloadV1`.
pub(crate) fn encode(&self) -> ksp_core_lib::Result<std::vec::Vec<u8>> {
let validation_result = validate_alias(self.alias.as_deref());
if let std::result::Result::Err(error) = validation_result {
@@ -110,23 +118,26 @@ impl crate::MetadataPayloadV1 {
return std::result::Result::Ok(serialized);
}
/// Consumes this value and returns info.
pub(crate) fn into_info(self, capability: crate::WalletCapability) -> crate::WalletInfo {
return crate::WalletInfo::new(capability, self.pubkey, self.alias, self.notes);
}
}
/// Crate-internal `OwnerControlMaterialV1` state shared across the owning crate.
pub(crate) struct OwnerControlMaterialV1 {
admin_signing_secret: [u8; crate::crypto::SECRET_KEY_BYTES],
admin_signing_secret: [u8; crate::SECRET_KEY_BYTES],
metadata_key: crate::SecretKeyV1,
secret_key: crate::SecretKeyV1,
}
impl OwnerControlMaterialV1 {
pub(crate) fn into_parts(mut self) -> ([u8; crate::crypto::SECRET_KEY_BYTES], crate::SecretKeyV1, crate::SecretKeyV1) {
let mut admin_signing_secret = [0_u8; crate::crypto::SECRET_KEY_BYTES];
/// Consumes this value and returns parts.
pub(crate) fn into_parts(mut self) -> ([u8; crate::SECRET_KEY_BYTES], crate::SecretKeyV1, crate::SecretKeyV1) {
let mut admin_signing_secret = [0_u8; crate::SECRET_KEY_BYTES];
std::mem::swap(&mut admin_signing_secret, &mut self.admin_signing_secret);
let metadata_key = std::mem::replace(&mut self.metadata_key, crate::SecretKeyV1::from_bytes([0_u8; crate::crypto::SECRET_KEY_BYTES]));
let secret_key = std::mem::replace(&mut self.secret_key, crate::SecretKeyV1::from_bytes([0_u8; crate::crypto::SECRET_KEY_BYTES]));
let metadata_key = std::mem::replace(&mut self.metadata_key, crate::SecretKeyV1::from_bytes([0_u8; crate::SECRET_KEY_BYTES]));
let secret_key = std::mem::replace(&mut self.secret_key, crate::SecretKeyV1::from_bytes([0_u8; crate::SECRET_KEY_BYTES]));
return (admin_signing_secret, metadata_key, secret_key);
}
}
@@ -152,6 +163,7 @@ struct RawMetadataNoteV1 {
text: std::string::String,
}
/// Encodes initial metadata payload.
pub(crate) fn encode_initial_metadata_payload(
pubkey: ksp_core_lib::Pubkey,
metadata: crate::WalletCreateMetadataV1,
@@ -164,7 +176,6 @@ pub(crate) fn encode_initial_metadata_payload(
if note_texts.len() > crate::KSPWALLET_V1_MAX_NOTES {
return std::result::Result::Err(metadata_error("Wallet note count exceeds the V1 limit", "metadata.notes"));
}
let mut notes = std::vec::Vec::with_capacity(note_texts.len());
let mut note_ids = std::collections::BTreeSet::new();
for text in note_texts {
@@ -172,7 +183,7 @@ pub(crate) fn encode_initial_metadata_payload(
if let std::result::Result::Err(error) = validation_result {
return std::result::Result::Err(error);
}
let note_id_bytes = match crate::crypto::random_bytes::<{ crate::KSPWALLET_V1_NOTE_ID_BYTES }>() {
let note_id_bytes = match crate::random_bytes::<{ crate::KSPWALLET_V1_NOTE_ID_BYTES }>() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
@@ -182,7 +193,6 @@ pub(crate) fn encode_initial_metadata_payload(
}
notes.push(crate::WalletNote::new(note_id, text));
}
let payload = crate::MetadataPayloadV1 { pubkey, alias, notes };
let serialized = match payload.encode() {
std::result::Result::Ok(value) => value,
@@ -191,6 +201,7 @@ pub(crate) fn encode_initial_metadata_payload(
return std::result::Result::Ok((serialized, payload));
}
/// Decodes metadata payload.
pub(crate) fn decode_metadata_payload(source: &[u8]) -> ksp_core_lib::Result<crate::MetadataPayloadV1> {
if source.len() > crate::KSPWALLET_V1_MAX_METADATA_PLAINTEXT_BYTES {
return std::result::Result::Err(metadata_error("Wallet metadata payload exceeds the V1 plaintext limit", "metadata"));
@@ -207,7 +218,6 @@ pub(crate) fn decode_metadata_payload(source: &[u8]) -> ksp_core_lib::Result<cra
if raw.notes.len() > crate::KSPWALLET_V1_MAX_NOTES {
return std::result::Result::Err(metadata_error("Wallet note count exceeds the V1 limit", "metadata.notes"));
}
let pubkey_result = ksp_core_lib::Pubkey::from_str(raw.pubkey.as_str());
let pubkey = match pubkey_result {
std::result::Result::Ok(value) => value,
@@ -216,7 +226,6 @@ pub(crate) fn decode_metadata_payload(source: &[u8]) -> ksp_core_lib::Result<cra
if pubkey.to_string() != raw.pubkey {
return std::result::Result::Err(metadata_error("Wallet metadata Pubkey is not canonical Base58", "metadata.pubkey"));
}
let mut notes = std::vec::Vec::with_capacity(raw.notes.len());
let mut note_ids = std::collections::BTreeSet::new();
for raw_note in raw.notes {
@@ -239,8 +248,9 @@ pub(crate) fn decode_metadata_payload(source: &[u8]) -> ksp_core_lib::Result<cra
return std::result::Result::Ok(crate::MetadataPayloadV1 { pubkey, alias: raw.alias, notes });
}
/// Encodes owner control payload.
pub(crate) fn encode_owner_control_payload(
admin_signing_secret: &[u8; crate::crypto::SECRET_KEY_BYTES],
admin_signing_secret: &[u8; crate::SECRET_KEY_BYTES],
metadata_key: &crate::SecretKeyV1,
secret_key: &crate::SecretKeyV1,
) -> [u8; crate::KSPWALLET_V1_OWNER_CONTROL_PLAINTEXT_BYTES] {
@@ -251,15 +261,16 @@ pub(crate) fn encode_owner_control_payload(
return output;
}
/// Decodes owner control payload.
pub(crate) fn decode_owner_control_payload(source: &[u8]) -> ksp_core_lib::Result<OwnerControlMaterialV1> {
if source.len() != crate::KSPWALLET_V1_OWNER_CONTROL_PLAINTEXT_BYTES {
return std::result::Result::Err(metadata_error("Wallet OWNER-control payload length is invalid", "owner_control"));
}
let mut admin_signing_secret = [0_u8; crate::crypto::SECRET_KEY_BYTES];
let mut admin_signing_secret = [0_u8; crate::SECRET_KEY_BYTES];
admin_signing_secret.copy_from_slice(&source[0..32]);
let mut metadata_key = [0_u8; crate::crypto::SECRET_KEY_BYTES];
let mut metadata_key = [0_u8; crate::SECRET_KEY_BYTES];
metadata_key.copy_from_slice(&source[32..64]);
let mut secret_key = [0_u8; crate::crypto::SECRET_KEY_BYTES];
let mut secret_key = [0_u8; crate::SECRET_KEY_BYTES];
secret_key.copy_from_slice(&source[64..96]);
return std::result::Result::Ok(OwnerControlMaterialV1 {
admin_signing_secret,

View File

@@ -1,10 +1,10 @@
// file: crates/ksp-wallet-lib/src/persistence.rs
// version: 6
// version: 7
//! Async-first native Wallet V1 filesystem persistence.
use std::io::Read; // rust-rules: derive-import
use std::io::Write; // rust-rules: derive-import
use std::io::Read; // rust-rules: trait-import
use std::io::Write; // rust-rules: trait-import
/// Creates a new native `.kspwallet` V1 at `destination` without overwriting an existing path.
///
@@ -83,10 +83,12 @@ pub async fn inspect_locked_wallet_file_v1(source: impl std::convert::AsRef<std:
return crate::inspect_locked_wallet_v1(bytes.as_slice());
}
/// Persists new wallet content v1.
pub(crate) async fn persist_new_wallet_content_v1(destination: std::path::PathBuf, content: std::vec::Vec<u8>) -> ksp_core_lib::Result<()> {
return persist_new_wallet_async(destination, content).await;
}
/// Replaces wallet file v1.
pub(crate) async fn replace_wallet_file_v1(
destination: std::path::PathBuf,
expected_current: crate::KspWalletEnvelopeV1,
@@ -131,7 +133,6 @@ fn read_wallet_file_blocking(source: &std::path::Path) -> ksp_core_lib::Result<s
if metadata.len() > crate::KSPWALLET_MAX_FILE_BYTES as u64 {
return std::result::Result::Err(oversized_document_error());
}
let capacity = std::cmp::min(metadata.len(), crate::KSPWALLET_MAX_FILE_BYTES as u64) as usize;
let mut bytes = std::vec::Vec::with_capacity(capacity);
let mut bounded = file.take((crate::KSPWALLET_MAX_FILE_BYTES + 1) as u64);
@@ -175,7 +176,7 @@ fn verify_expected_wallet_state(destination: &std::path::Path, expected_current:
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let verify_result = crate::wallet::verify_state_signature(&current);
let verify_result = crate::verify_state_signature(&current);
if let std::result::Result::Err(error) = verify_result {
return std::result::Result::Err(error);
}
@@ -207,7 +208,6 @@ where
if !existing_metadata.is_file() {
return std::result::Result::Err(atomic_error("replace_destination", "Wallet replacement destination is not a regular file"));
}
let temporary_result = tempfile::Builder::new().prefix(".kspwallet-replace-").suffix(".tmp").tempfile_in(parent);
let mut temporary = match temporary_result {
std::result::Result::Ok(value) => value,
@@ -225,7 +225,6 @@ where
if let std::result::Result::Err(error) = hook_result {
return std::result::Result::Err(error);
}
let persist_result = temporary.persist(destination);
let persisted = match persist_result {
std::result::Result::Ok(value) => value,
@@ -254,7 +253,6 @@ where
if destination.file_name().is_none() {
return std::result::Result::Err(atomic_error("destination", "Wallet destination has no file name"));
}
let temporary_result = tempfile::Builder::new().prefix(".kspwallet-write-").suffix(".tmp").tempfile_in(parent);
let mut temporary = match temporary_result {
std::result::Result::Ok(value) => value,
@@ -268,12 +266,10 @@ where
if let std::result::Result::Err(error) = sync_result {
return std::result::Result::Err(atomic_io_error("temporary_sync", error));
}
let hook_result = before_publish();
if let std::result::Result::Err(error) = hook_result {
return std::result::Result::Err(error);
}
let persist_result = temporary.persist_noclobber(destination);
let persisted = match persist_result {
std::result::Result::Ok(value) => value,
@@ -371,6 +367,7 @@ fn blocking_atomic_error(operation: &'static str, source: tokio::task::JoinError
.with_source(source);
}
/// Persists new wallet fault before publish.
#[cfg(test)]
pub(crate) fn persist_new_wallet_fault_before_publish(destination: &std::path::Path, content: &[u8]) -> ksp_core_lib::Result<()> {
return persist_new_wallet_with_hook(destination, content, || {
@@ -378,11 +375,13 @@ pub(crate) fn persist_new_wallet_fault_before_publish(destination: &std::path::P
});
}
/// Persists new wallet for test.
#[cfg(test)]
pub(crate) fn persist_new_wallet_for_test(destination: &std::path::Path, content: &[u8]) -> ksp_core_lib::Result<()> {
return persist_new_wallet_blocking(destination, content);
}
/// Replaces wallet fault before publish.
#[cfg(test)]
pub(crate) fn replace_wallet_fault_before_publish(destination: &std::path::Path, content: &[u8]) -> ksp_core_lib::Result<()> {
return replace_wallet_file_with_hook(destination, content, || {
@@ -390,6 +389,7 @@ pub(crate) fn replace_wallet_fault_before_publish(destination: &std::path::Path,
});
}
/// Replaces wallet for test.
#[cfg(test)]
pub(crate) fn replace_wallet_for_test(destination: &std::path::Path, content: &[u8]) -> ksp_core_lib::Result<()> {
return replace_wallet_file_blocking(destination, content);

View File

@@ -1,31 +1,31 @@
// file: crates/ksp-wallet-lib/src/transcript.rs
// version: 1
// version: 2
//! Deterministic `.kspwallet` V1 state-transcript and AEAD-AAD encoding.
const TAG_MAGIC: u16 = 0x0001;
const TAG_COMPARTMENT_ALGORITHM: u16 = 0x0202;
const TAG_COMPARTMENT_CIPHERTEXT: u16 = 0x0204;
const TAG_COMPARTMENT_KIND: u16 = 0x0200;
const TAG_COMPARTMENT_NONCE: u16 = 0x0203;
const TAG_COMPARTMENT_VERSION: u16 = 0x0201;
const TAG_FORMAT_VERSION: u16 = 0x0002;
const TAG_KDF_ALGORITHM: u16 = 0x0102;
const TAG_KDF_ITERATIONS: u16 = 0x0105;
const TAG_KDF_MEMORY_KIB: u16 = 0x0104;
const TAG_KDF_PARALLELISM: u16 = 0x0106;
const TAG_KDF_SALT: u16 = 0x0107;
const TAG_KDF_VERSION: u16 = 0x0103;
const TAG_MAGIC: u16 = 0x0001;
const TAG_OWNER_AUTH_PUBLIC_KEY: u16 = 0x0003;
const TAG_SLOT_ID: u16 = 0x0100;
const TAG_SLOT_ROLE: u16 = 0x0101;
const TAG_STATE_SIGNATURE_ALGORITHM: u16 = 0x0500;
const TAG_VIEW_ENABLED: u16 = 0x0010;
const TAG_VIEW_ROLE: u16 = 0x0011;
const TAG_VIEW_SLOT_ID: u16 = 0x0012;
const TAG_SLOT_ID: u16 = 0x0100;
const TAG_SLOT_ROLE: u16 = 0x0101;
const TAG_KDF_ALGORITHM: u16 = 0x0102;
const TAG_KDF_VERSION: u16 = 0x0103;
const TAG_KDF_MEMORY_KIB: u16 = 0x0104;
const TAG_KDF_ITERATIONS: u16 = 0x0105;
const TAG_KDF_PARALLELISM: u16 = 0x0106;
const TAG_KDF_SALT: u16 = 0x0107;
const TAG_WRAP_ALGORITHM: u16 = 0x0108;
const TAG_WRAP_NONCE: u16 = 0x0109;
const TAG_WRAP_CIPHERTEXT: u16 = 0x010A;
const TAG_COMPARTMENT_KIND: u16 = 0x0200;
const TAG_COMPARTMENT_VERSION: u16 = 0x0201;
const TAG_COMPARTMENT_ALGORITHM: u16 = 0x0202;
const TAG_COMPARTMENT_NONCE: u16 = 0x0203;
const TAG_COMPARTMENT_CIPHERTEXT: u16 = 0x0204;
const TAG_STATE_SIGNATURE_ALGORITHM: u16 = 0x0500;
const TAG_WRAP_NONCE: u16 = 0x0109;
/// Builds the normative OWNER state-signature transcript for one validated V1 envelope.
pub(crate) fn state_transcript(envelope: &crate::KspWalletEnvelopeV1) -> std::vec::Vec<u8> {
@@ -37,7 +37,6 @@ pub(crate) fn state_transcript(envelope: &crate::KspWalletEnvelopeV1) -> std::ve
std::option::Option::Some(slot_id) => push_bytes(&mut output, TAG_VIEW_SLOT_ID, slot_id),
std::option::Option::None => push_bytes(&mut output, TAG_VIEW_SLOT_ID, &[]),
}
push_slot(&mut output, envelope.owner_slot(), true);
push_compartment(&mut output, envelope.owner_control(), true);
push_compartment(&mut output, envelope.metadata(), true);

View File

@@ -1,13 +1,13 @@
// file: crates/ksp-wallet-lib/src/transfer.rs
// version: 2
// version: 3
//! Explicit OWNER-only Solana keypair import/export adapters.
use std::io::Read; // rust-rules: derive-import
use std::io::Write; // rust-rules: derive-import
use std::io::Read; // rust-rules: trait-import
use std::io::Write; // rust-rules: trait-import
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt; // rust-rules: derive-import
use zeroize::Zeroize; // rust-rules: derive-import
use std::os::unix::fs::PermissionsExt; // rust-rules: trait-import
use zeroize::Zeroize; // rust-rules: trait-import
/// Explicit secret-transfer formats supported by Wallet `0.2.5`.
#[non_exhaustive]
@@ -161,6 +161,7 @@ pub async fn import_wallet_transfer_file_v1(
return import_wallet_transfer_v1(destination, bytes.as_slice(), format, owner_password, view_password, metadata).await;
}
/// Executes the crate-internal write wallet transfer file v1 operation for the owning module.
pub(crate) async fn write_wallet_transfer_file_v1(
destination: std::path::PathBuf,
content: std::vec::Vec<u8>,

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-wallet-lib/src/view.rs
// version: 3
// version: 4
/// Authorized VIEW capability handle.
///
@@ -7,11 +7,12 @@
/// Solana secret, OWNER administration material or metadata-write authority.
pub struct WalletView {
info: crate::WalletInfo,
state: crate::wallet::ViewStateV1,
state: crate::ViewStateV1,
}
impl WalletView {
pub(crate) fn from_unlocked(info: crate::WalletInfo, state: crate::wallet::ViewStateV1) -> Self {
/// Builds `WalletView` from unlocked.
pub(crate) fn from_unlocked(info: crate::WalletInfo, state: crate::ViewStateV1) -> Self {
return Self { info, state };
}
@@ -68,7 +69,7 @@ impl WalletView {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let persist_result = crate::persistence::replace_wallet_file_v1(destination.as_ref().to_path_buf(), self.state.envelope().clone(), serialized).await;
let persist_result = crate::replace_wallet_file_v1(destination.as_ref().to_path_buf(), self.state.envelope().clone(), serialized).await;
if let std::result::Result::Err(error) = persist_result {
return std::result::Result::Err(error);
}

View File

@@ -1,11 +1,12 @@
// file: crates/ksp-wallet-lib/src/wallet.rs
// version: 7
// version: 8
//! In-memory native Wallet V1 create/open orchestration.
use ed25519_dalek::Signer; // rust-rules: derive-import
use zeroize::Zeroize; // rust-rules: derive-import
use ed25519_dalek::Signer; // rust-rules: trait-import
use zeroize::Zeroize; // rust-rules: trait-import
/// Crate-internal `OwnerStateV1` state shared across the owning crate.
pub(crate) struct OwnerStateV1 {
envelope: crate::KspWalletEnvelopeV1,
owner_root: std::option::Option<crate::SecretKeyV1>,
@@ -16,6 +17,7 @@ pub(crate) struct OwnerStateV1 {
}
impl OwnerStateV1 {
/// Creates a new `OwnerStateV1` value.
pub(crate) fn new(
envelope: crate::KspWalletEnvelopeV1,
owner_root: crate::SecretKeyV1,
@@ -34,15 +36,18 @@ impl OwnerStateV1 {
};
}
/// Returns the current envelope.
pub(crate) const fn envelope(&self) -> &crate::KspWalletEnvelopeV1 {
return &self.envelope;
}
/// Applies envelope.
pub(crate) fn apply_envelope(&mut self, envelope: crate::KspWalletEnvelopeV1) {
self.envelope = envelope;
return;
}
/// Applies strong view state.
pub(crate) fn apply_strong_view_state(&mut self, envelope: crate::KspWalletEnvelopeV1, metadata_key: crate::SecretKeyV1) {
self.envelope = envelope;
let previous = self.metadata_key.replace(metadata_key);
@@ -50,6 +55,7 @@ impl OwnerStateV1 {
return;
}
/// Executes the crate-internal export transfer operation for `OwnerStateV1`.
pub(crate) fn export_transfer(&self, format: crate::WalletTransferFormat) -> ksp_core_lib::Result<std::vec::Vec<u8>> {
let keypair = match self.solana_keypair.as_ref() {
std::option::Option::Some(value) => value,
@@ -74,6 +80,7 @@ impl OwnerStateV1 {
};
}
/// Executes the crate-internal sign message operation for `OwnerStateV1`.
pub(crate) fn sign_message(&self, message: &[u8]) -> ksp_core_lib::Result<[u8; crate::KSPWALLET_SOLANA_SIGNATURE_BYTES]> {
let keypair = match self.solana_keypair.as_ref() {
std::option::Option::Some(value) => value,
@@ -89,6 +96,7 @@ impl OwnerStateV1 {
return std::result::Result::Ok(signature);
}
/// Executes the crate-internal stage metadata payload operation for `OwnerStateV1`.
pub(crate) fn stage_metadata_payload(&self, payload: crate::MetadataPayloadV1) -> ksp_core_lib::Result<(crate::KspWalletEnvelopeV1, crate::WalletInfo)> {
let metadata_key = match self.metadata_key.as_ref() {
std::option::Option::Some(value) => value,
@@ -138,6 +146,7 @@ impl OwnerStateV1 {
return std::result::Result::Ok((envelope, payload.into_info(crate::WalletCapability::Owner)));
}
/// Executes the crate-internal stage owner password rotation operation for `OwnerStateV1`.
pub(crate) async fn stage_owner_password_rotation(&self, new_password: crate::OwnerPassword) -> ksp_core_lib::Result<crate::KspWalletEnvelopeV1> {
let owner_root = match self.owner_root.as_ref() {
std::option::Option::Some(value) => value,
@@ -172,6 +181,7 @@ impl OwnerStateV1 {
return verify_and_return(envelope);
}
/// Executes the crate-internal stage view password rotation operation for `OwnerStateV1`.
pub(crate) async fn stage_view_password_rotation(&self, new_password: crate::ViewPassword) -> ksp_core_lib::Result<crate::KspWalletEnvelopeV1> {
let metadata_key = match self.metadata_key.as_ref() {
std::option::Option::Some(value) => value,
@@ -201,6 +211,7 @@ impl OwnerStateV1 {
return verify_and_return(envelope);
}
/// Executes the crate-internal stage disable view operation for `OwnerStateV1`.
pub(crate) fn stage_disable_view(&self) -> ksp_core_lib::Result<(crate::KspWalletEnvelopeV1, crate::SecretKeyV1)> {
if !self.envelope.view_descriptor().enabled() {
return std::result::Result::Err(capability_error("Wallet VIEW capability is already disabled"));
@@ -208,6 +219,7 @@ impl OwnerStateV1 {
return self.stage_strong_view_change(std::option::Option::None);
}
/// Executes the crate-internal stage recreate view operation for `OwnerStateV1`.
pub(crate) async fn stage_recreate_view(
&self,
new_password: crate::ViewPassword,
@@ -260,7 +272,6 @@ impl OwnerStateV1 {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::result::Result::Err(crypto_operation_error()),
};
let current_plaintext_result = crate::decrypt_bytes(
current_metadata_key,
self.envelope.metadata().nonce(),
@@ -290,9 +301,8 @@ impl OwnerStateV1 {
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let metadata = crate::WalletEncryptedCompartmentV1::new(crate::WalletCompartmentKindV1::Metadata, metadata_nonce, metadata_ciphertext);
let mut admin_secret = admin_signing_key.to_bytes();
let mut owner_control_plaintext = crate::payload::encode_owner_control_payload(&admin_secret, &new_metadata_key, secret_key);
let mut owner_control_plaintext = crate::encode_owner_control_payload(&admin_secret, &new_metadata_key, secret_key);
admin_secret.zeroize();
let owner_control_nonce = match crate::random_nonce() {
std::result::Result::Ok(value) => value,
@@ -314,7 +324,6 @@ impl OwnerStateV1 {
};
let owner_control =
crate::WalletEncryptedCompartmentV1::new(crate::WalletCompartmentKindV1::OwnerControl, owner_control_nonce, owner_control_ciphertext);
let view_descriptor = match view_slot.as_ref() {
std::option::Option::Some(slot) => crate::WalletViewDescriptorV1::enabled_for_slot(*slot.slot_id()),
std::option::Option::None => crate::WalletViewDescriptorV1::disabled(),
@@ -352,25 +361,30 @@ impl std::ops::Drop for OwnerStateV1 {
}
}
/// Crate-internal `ViewStateV1` state shared across the owning crate.
pub(crate) struct ViewStateV1 {
envelope: crate::KspWalletEnvelopeV1,
metadata_key: std::option::Option<crate::SecretKeyV1>,
}
impl ViewStateV1 {
/// Creates a new `ViewStateV1` value.
pub(crate) fn new(envelope: crate::KspWalletEnvelopeV1, metadata_key: crate::SecretKeyV1) -> Self {
return Self { envelope, metadata_key: std::option::Option::Some(metadata_key) };
}
/// Returns the current envelope.
pub(crate) const fn envelope(&self) -> &crate::KspWalletEnvelopeV1 {
return &self.envelope;
}
/// Applies envelope.
pub(crate) fn apply_envelope(&mut self, envelope: crate::KspWalletEnvelopeV1) {
self.envelope = envelope;
return;
}
/// Executes the crate-internal stage view password rotation operation for `ViewStateV1`.
pub(crate) async fn stage_view_password_rotation(&self, new_password: crate::ViewPassword) -> ksp_core_lib::Result<crate::KspWalletEnvelopeV1> {
let metadata_key = match self.metadata_key.as_ref() {
std::option::Option::Some(value) => value,
@@ -526,7 +540,7 @@ pub async fn open_wallet_owner_v1(source: &[u8], password: crate::OwnerPassword)
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(owner_unlock_error()),
};
let control_result = crate::payload::decode_owner_control_payload(owner_control_plaintext.as_slice());
let control_result = crate::decode_owner_control_payload(owner_control_plaintext.as_slice());
owner_control_plaintext.zeroize();
let control = match control_result {
std::result::Result::Ok(value) => value,
@@ -538,7 +552,6 @@ pub async fn open_wallet_owner_v1(source: &[u8], password: crate::OwnerPassword)
if admin_signing_key.verifying_key().to_bytes() != *envelope.owner_auth_public_key() {
return std::result::Result::Err(authentication_error());
}
let metadata_plaintext_result = crate::decrypt_bytes(
&metadata_key,
envelope.metadata().nonce(),
@@ -555,7 +568,6 @@ pub async fn open_wallet_owner_v1(source: &[u8], password: crate::OwnerPassword)
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let secret_plaintext_result = crate::decrypt_bytes(
&secret_key,
envelope.secret().nonce(),
@@ -590,7 +602,6 @@ pub async fn open_wallet_owner_v1(source: &[u8], password: crate::OwnerPassword)
if &secret_pubkey != metadata_payload.pubkey() {
return std::result::Result::Err(key_material_error());
}
let info = metadata_payload.into_info(crate::WalletCapability::Owner);
let state = OwnerStateV1::new(envelope, owner_root, metadata_key, secret_key, admin_signing_key, solana_keypair);
ksp_logging_lib::debug!(
@@ -616,6 +627,7 @@ pub fn inspect_locked_wallet_v1(source: &[u8]) -> ksp_core_lib::Result<crate::Lo
return std::result::Result::Ok(crate::LockedWalletInfo::new(envelope.view_descriptor().enabled()));
}
/// Executes the crate-internal create wallet v1 from keypair operation for the owning module.
pub(crate) async fn create_wallet_v1_from_keypair(
owner_password: crate::OwnerPassword,
view_password: std::option::Option<crate::ViewPassword>,
@@ -634,14 +646,12 @@ pub(crate) async fn create_wallet_v1_from_keypair(
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut admin_secret = match crate::random_bytes::<{ crate::SECRET_KEY_BYTES }>() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let admin_signing_key = ed25519_dalek::SigningKey::from_bytes(&admin_secret);
let owner_auth_public_key = admin_signing_key.verifying_key().to_bytes();
let mut solana_keypair_bytes = solana_keypair.to_bytes();
let pubkey_result = pubkey_from_keypair_bytes(&solana_keypair_bytes);
let pubkey = match pubkey_result {
@@ -652,8 +662,7 @@ pub(crate) async fn create_wallet_v1_from_keypair(
return std::result::Result::Err(error);
},
};
let metadata_encoded_result = crate::payload::encode_initial_metadata_payload(pubkey, metadata);
let metadata_encoded_result = crate::encode_initial_metadata_payload(pubkey, metadata);
let (mut metadata_plaintext, metadata_payload) = match metadata_encoded_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
@@ -662,9 +671,8 @@ pub(crate) async fn create_wallet_v1_from_keypair(
return std::result::Result::Err(error);
},
};
let mut owner_control_plaintext = crate::payload::encode_owner_control_payload(&admin_secret, &metadata_key, &secret_key);
let mut owner_control_plaintext = crate::encode_owner_control_payload(&admin_secret, &metadata_key, &secret_key);
admin_secret.zeroize();
let owner_slot_id = match crate::random_bytes::<{ crate::KSPWALLET_V1_SLOT_ID_BYTES }>() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
@@ -684,7 +692,6 @@ pub(crate) async fn create_wallet_v1_from_keypair(
return cleanup_create_error(error, &mut owner_control_plaintext, &mut metadata_plaintext, &mut solana_keypair_bytes);
},
};
let view_material_result = prepare_view_creation(view_password);
let view_material = match view_material_result {
std::result::Result::Ok(value) => value,
@@ -692,7 +699,6 @@ pub(crate) async fn create_wallet_v1_from_keypair(
return cleanup_create_error(error, &mut owner_control_plaintext, &mut metadata_plaintext, &mut solana_keypair_bytes);
},
};
let owner_control_nonce = match crate::random_nonce() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
@@ -711,7 +717,6 @@ pub(crate) async fn create_wallet_v1_from_keypair(
return cleanup_create_error(error, &mut owner_control_plaintext, &mut metadata_plaintext, &mut solana_keypair_bytes);
},
};
let provisional = provisional_envelope(
owner_auth_public_key,
owner_slot_id,
@@ -722,7 +727,6 @@ pub(crate) async fn create_wallet_v1_from_keypair(
metadata_nonce,
secret_nonce,
);
let owner_derived_result = derive_owner_password_key_async(owner_password, owner_kdf.clone()).await;
let owner_derived = match owner_derived_result {
std::result::Result::Ok(value) => value,
@@ -737,7 +741,6 @@ pub(crate) async fn create_wallet_v1_from_keypair(
return cleanup_create_error(error, &mut owner_control_plaintext, &mut metadata_plaintext, &mut solana_keypair_bytes);
},
};
let view_slot_result = seal_view_slot(&provisional, view_material, &metadata_key).await;
let view_slot = match view_slot_result {
std::result::Result::Ok(value) => value,
@@ -745,7 +748,6 @@ pub(crate) async fn create_wallet_v1_from_keypair(
return cleanup_create_error(error, &mut owner_control_plaintext, &mut metadata_plaintext, &mut solana_keypair_bytes);
},
};
let owner_control_ciphertext_result = crate::encrypt_bytes(
&owner_root,
&owner_control_nonce,
@@ -759,7 +761,6 @@ pub(crate) async fn create_wallet_v1_from_keypair(
},
};
owner_control_plaintext.zeroize();
let metadata_ciphertext_result = crate::encrypt_bytes(
&metadata_key,
&metadata_nonce,
@@ -773,7 +774,6 @@ pub(crate) async fn create_wallet_v1_from_keypair(
},
};
metadata_plaintext.zeroize();
let secret_ciphertext_result = crate::encrypt_bytes(
&secret_key,
&secret_nonce,
@@ -787,7 +787,6 @@ pub(crate) async fn create_wallet_v1_from_keypair(
},
};
solana_keypair_bytes.zeroize();
let owner_slot =
crate::WalletKeySlotV1::new(owner_slot_id, crate::WalletKeySlotRoleV1::Owner, owner_kdf, crate::WalletKeyWrapV1::new(owner_wrap_nonce, owner_wrapped));
let view_descriptor = match view_slot.as_ref() {
@@ -822,7 +821,6 @@ pub(crate) async fn create_wallet_v1_from_keypair(
if let std::result::Result::Err(error) = verify_result {
return std::result::Result::Err(error);
}
let info = metadata_payload.into_info(crate::WalletCapability::Owner);
let state = OwnerStateV1::new(envelope, owner_root, metadata_key, secret_key, admin_signing_key, solana_keypair);
ksp_logging_lib::debug!(
@@ -835,6 +833,7 @@ pub(crate) async fn create_wallet_v1_from_keypair(
return std::result::Result::Ok(crate::WalletOwner::from_unlocked(info, state));
}
/// Verifies state signature.
pub(crate) fn verify_state_signature(envelope: &crate::KspWalletEnvelopeV1) -> ksp_core_lib::Result<()> {
let verifying_key_result = ed25519_dalek::VerifyingKey::from_bytes(envelope.owner_auth_public_key());
let verifying_key = match verifying_key_result {

View File

@@ -1,9 +1,9 @@
// file: crates/ksp-wallet-lib/src/wire.rs
// version: 6
// version: 7
//! Strict native `.kspwallet` V1 wire envelope.
use base64::Engine; // rust-rules: derive-import
use base64::Engine; // rust-rules: trait-import
/// Password KDF supported by `.kspwallet` V1 key slots.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -105,6 +105,7 @@ pub struct WalletKdfParametersV1 {
}
impl WalletKdfParametersV1 {
/// Creates a new creation value for `WalletKdfParametersV1`.
pub(crate) fn new_creation(salt: std::vec::Vec<u8>) -> Self {
return Self {
algorithm: WalletKdfAlgorithmV1::Argon2id,
@@ -176,6 +177,7 @@ pub struct WalletKeyWrapV1 {
}
impl WalletKeyWrapV1 {
/// Creates a new `WalletKeyWrapV1` value.
pub(crate) fn new(nonce: [u8; crate::KSPWALLET_V1_XCHACHA_NONCE_BYTES], ciphertext: std::vec::Vec<u8>) -> Self {
return Self { algorithm: WalletAeadAlgorithmV1::XChaCha20Poly1305, nonce, ciphertext };
}
@@ -220,6 +222,7 @@ pub struct WalletKeySlotV1 {
}
impl WalletKeySlotV1 {
/// Creates a new `WalletKeySlotV1` value.
pub(crate) fn new(slot_id: [u8; crate::KSPWALLET_V1_SLOT_ID_BYTES], role: WalletKeySlotRoleV1, kdf: WalletKdfParametersV1, wrap: WalletKeyWrapV1) -> Self {
return Self { slot_id, role, kdf, wrap };
}
@@ -269,10 +272,12 @@ pub struct WalletViewDescriptorV1 {
}
impl WalletViewDescriptorV1 {
/// Executes the crate-internal disabled operation for `WalletViewDescriptorV1`.
pub(crate) const fn disabled() -> Self {
return Self { enabled: false, slot_id: std::option::Option::None };
}
/// Executes the crate-internal enabled for slot operation for `WalletViewDescriptorV1`.
pub(crate) const fn enabled_for_slot(slot_id: [u8; crate::KSPWALLET_V1_SLOT_ID_BYTES]) -> Self {
return Self { enabled: true, slot_id: std::option::Option::Some(slot_id) };
}
@@ -301,6 +306,7 @@ pub struct WalletEncryptedCompartmentV1 {
}
impl WalletEncryptedCompartmentV1 {
/// Creates a new `WalletEncryptedCompartmentV1` value.
pub(crate) fn new(kind: WalletCompartmentKindV1, nonce: [u8; crate::KSPWALLET_V1_XCHACHA_NONCE_BYTES], ciphertext: std::vec::Vec<u8>) -> Self {
return Self {
kind,
@@ -363,6 +369,7 @@ pub struct WalletStateSignatureV1 {
}
impl WalletStateSignatureV1 {
/// Creates a new `WalletStateSignatureV1` value.
pub(crate) const fn new(signature: [u8; crate::KSPWALLET_V1_ED25519_SIGNATURE_BYTES]) -> Self {
return Self { algorithm: WalletStateSignatureAlgorithmV1::Ed25519, signature };
}
@@ -404,6 +411,7 @@ pub struct KspWalletEnvelopeV1 {
}
impl KspWalletEnvelopeV1 {
/// Creates a new internal value for `KspWalletEnvelopeV1`.
pub(crate) fn new_internal(
owner_auth_public_key: [u8; crate::KSPWALLET_V1_ED25519_PUBLIC_KEY_BYTES],
view_descriptor: WalletViewDescriptorV1,
@@ -434,7 +442,6 @@ impl KspWalletEnvelopeV1 {
if source.len() > crate::KSPWALLET_MAX_FILE_BYTES {
return std::result::Result::Err(format_error("Wallet document exceeds the V1 maximum size", "document"));
}
let probe_result = serde_json::from_slice::<RawVersionProbe>(source);
let probe = match probe_result {
std::result::Result::Ok(probe) => probe,
@@ -449,7 +456,6 @@ impl KspWalletEnvelopeV1 {
.with_context("format_version", probe.format_version.to_string()),
);
}
let raw_result = serde_json::from_slice::<RawEnvelopeV1>(source);
let raw = match raw_result {
std::result::Result::Ok(raw) => raw,
@@ -793,7 +799,6 @@ fn parse_raw_envelope(raw: RawEnvelopeV1) -> ksp_core_lib::Result<KspWalletEnvel
.with_context("format_version", raw.format_version.to_string()),
);
}
let owner_auth_public_key =
match decode_fixed::<{ crate::KSPWALLET_V1_ED25519_PUBLIC_KEY_BYTES }>(raw.owner_auth_public_key.as_str(), "owner_auth_public_key") {
std::result::Result::Ok(value) => value,
@@ -803,7 +808,6 @@ fn parse_raw_envelope(raw: RawEnvelopeV1) -> ksp_core_lib::Result<KspWalletEnvel
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if raw.key_slots.is_empty() || raw.key_slots.len() > crate::KSPWALLET_V1_MAX_KEY_SLOTS {
return std::result::Result::Err(format_error("Wallet key_slots count is invalid for V1", "key_slots"));
}
@@ -853,7 +857,6 @@ fn parse_raw_envelope(raw: RawEnvelopeV1) -> ksp_core_lib::Result<KspWalletEnvel
} else if view_slot.is_some() {
return std::result::Result::Err(format_error("Disabled VIEW descriptor forbids a VIEW key slot", "key_slots"));
}
let owner_control = match parse_compartment(
WalletCompartmentKindV1::OwnerControl,
raw.owner_control.control_version,
@@ -891,7 +894,6 @@ fn parse_raw_envelope(raw: RawEnvelopeV1) -> ksp_core_lib::Result<KspWalletEnvel
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(KspWalletEnvelopeV1 {
owner_auth_public_key,
view_descriptor,

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-wallet-lib/tests/dependency_boundary.rs
// version: 10
// version: 11
//! Wallet-specific dependency and ownership canaries.
@@ -38,6 +38,10 @@ fn wallet_manifest_preserves_dependency_firewall() -> std::io::Result<()> {
std::result::Result::Ok(manifest) => manifest,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let workspace_manifest = match std::fs::read_to_string(crate_root().join("../../Cargo.toml")) {
std::result::Result::Ok(workspace_manifest) => workspace_manifest,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
assert!(manifest.contains("ksp-core-lib"));
assert!(manifest.contains("ksp-logging-lib"));
assert!(manifest.contains("argon2 = { workspace = true, features = [\"alloc\", \"zeroize\"] }"));
@@ -51,6 +55,7 @@ fn wallet_manifest_preserves_dependency_firewall() -> std::io::Result<()> {
assert!(manifest.contains("tokio = { workspace = true, features = [\"rt\"] }"));
assert!(manifest.contains("tempfile.workspace = true"));
assert!(manifest.contains("zeroize.workspace = true"));
assert!(workspace_manifest.contains("ed25519-dalek = { version = \"^3.0\", default-features = false }"));
for forbidden in [
"ksp-config-lib",
"ksp-onchain-transport-lib",

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-wallet-lib/tests/public_api.rs
// version: 8
// version: 9
//! Public API canaries for the Wallet foundation.
@@ -102,15 +102,12 @@ fn public_pre_006_file_persistence_surface_is_available_from_crate_root() {
let create_future =
ksp_wallet_lib::create_wallet_file_v1(path, owner_password, std::option::Option::None, ksp_wallet_lib::WalletCreateMetadataV1::default());
drop(create_future);
let view_password = ksp_wallet_lib::ViewPassword::new(std::string::String::from("public-pre006-view-password"));
let view_future = ksp_wallet_lib::open_wallet_view_file_v1(path, view_password);
drop(view_future);
let owner_password = ksp_wallet_lib::OwnerPassword::new(std::string::String::from("public-pre006-owner-password"));
let owner_future = ksp_wallet_lib::open_wallet_owner_file_v1(path, owner_password);
drop(owner_future);
let inspect_future = ksp_wallet_lib::inspect_locked_wallet_file_v1(path);
drop(inspect_future);
}
@@ -137,7 +134,6 @@ fn public_pre_008_transfer_adapters_are_available_from_crate_root() {
let inspect_method: fn(&[u8], ksp_wallet_lib::WalletTransferFormat) -> ksp_core_lib::Result<ksp_wallet_lib::WalletTransferInspection> =
ksp_wallet_lib::inspect_wallet_transfer;
let _ = inspect_method;
let path = std::path::Path::new("not-polled-transfer");
let inspect_file_future = ksp_wallet_lib::inspect_wallet_transfer_file(path, formats[0]);
drop(inspect_file_future);

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-wallet-lib/unit_tests/administration.rs
// version: 1
// version: 2
const FULL_VECTOR: &[u8] = include_bytes!("../tests/fixtures/kspwallet_v1_full_vector.json");
@@ -10,7 +10,7 @@ fn runtime() -> tokio::runtime::Runtime {
fn published_vector(name: &str) -> (tempfile::TempDir, std::path::PathBuf) {
let directory = tempfile::Builder::new().prefix("ksp-wallet-admin-").tempdir().expect("Wallet administration test directory must be creatable");
let path = directory.path().join(name);
crate::persistence::persist_new_wallet_for_test(path.as_path(), FULL_VECTOR).expect("full Wallet vector must publish for administration test");
crate::persist_new_wallet_for_test(path.as_path(), FULL_VECTOR).expect("full Wallet vector must publish for administration test");
return (directory, path);
}
@@ -26,14 +26,11 @@ fn owner_signing_is_deterministic_and_owner_rotation_preserves_solana_identity()
let signature_before = owner.sign(message).expect("OWNER must sign");
let signature_repeat = owner.sign(message).expect("OWNER repeated signature must succeed");
assert_eq!(signature_before, signature_repeat);
runtime
.block_on(owner.rotate_owner_password(path.as_path(), crate::OwnerPassword::new(std::string::String::from("pre007-new-owner-password"))))
.expect("OWNER password rotation must persist");
let old = runtime.block_on(crate::open_wallet_owner_file_v1(path.as_path(), crate::OwnerPassword::new(std::string::String::from("pre005-owner-password"))));
assert_eq!(old.expect_err("old OWNER password must stop unlocking current file").code(), crate::ERROR_CODE_OWNER_UNLOCK_FAILED);
let reopened = runtime
.block_on(crate::open_wallet_owner_file_v1(path.as_path(), crate::OwnerPassword::new(std::string::String::from("pre007-new-owner-password"))))
.expect("new OWNER password must unlock current file");
@@ -51,21 +48,17 @@ fn view_self_rotation_changes_only_the_current_view_credential() {
let pubkey = *view.pubkey();
let alias = view.alias().map(std::string::String::from);
let notes = view.notes().to_vec();
runtime
.block_on(view.rotate_view_password(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre007-new-view-password"))))
.expect("VIEW self-rotation must persist");
let old = runtime.block_on(crate::open_wallet_view_file_v1(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre005-view-password"))));
assert_eq!(old.expect_err("old VIEW password must stop unlocking current file").code(), crate::ERROR_CODE_VIEW_UNLOCK_FAILED);
let reopened = runtime
.block_on(crate::open_wallet_view_file_v1(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre007-new-view-password"))))
.expect("new VIEW password must unlock current file");
assert_eq!(*reopened.pubkey(), pubkey);
assert_eq!(reopened.alias(), alias.as_deref());
assert_eq!(reopened.notes(), notes.as_slice());
let owner = runtime
.block_on(crate::open_wallet_owner_file_v1(path.as_path(), crate::OwnerPassword::new(std::string::String::from("pre005-owner-password"))))
.expect("VIEW self-rotation must not change OWNER credential");
@@ -80,11 +73,9 @@ fn owner_rotates_view_without_old_view_password() {
.block_on(crate::open_wallet_owner_file_v1(path.as_path(), crate::OwnerPassword::new(std::string::String::from("pre005-owner-password"))))
.expect("OWNER fixture must open");
let pubkey = *owner.pubkey();
runtime
.block_on(owner.rotate_view_password(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre007-owner-set-view-password"))))
.expect("OWNER must rotate VIEW without old VIEW password");
let old = runtime.block_on(crate::open_wallet_view_file_v1(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre005-view-password"))));
assert_eq!(old.expect_err("old VIEW password must stop unlocking current file").code(), crate::ERROR_CODE_VIEW_UNLOCK_FAILED);
let reopened = runtime
@@ -101,7 +92,6 @@ fn owner_metadata_administration_is_visible_to_view_but_does_not_change_pubkey()
.block_on(crate::open_wallet_owner_file_v1(path.as_path(), crate::OwnerPassword::new(std::string::String::from("pre005-owner-password"))))
.expect("OWNER fixture must open");
let pubkey = *owner.pubkey();
runtime
.block_on(owner.update_alias(path.as_path(), std::option::Option::Some(std::string::String::from("pre007-updated-alias"))))
.expect("OWNER alias update must persist");
@@ -109,14 +99,12 @@ fn owner_metadata_administration_is_visible_to_view_but_does_not_change_pubkey()
runtime
.block_on(owner.update_note(path.as_path(), note_id.as_str(), std::string::String::from("pre007-updated-note")))
.expect("OWNER note update must persist");
let view = runtime
.block_on(crate::open_wallet_view_file_v1(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre005-view-password"))))
.expect("VIEW must read OWNER-updated metadata");
assert_eq!(*view.pubkey(), pubkey);
assert_eq!(view.alias(), std::option::Option::Some("pre007-updated-alias"));
assert!(view.notes().iter().any(|note| return note.id() == note_id.as_str() && note.text() == "pre007-updated-note"));
runtime.block_on(owner.delete_note(path.as_path(), note_id.as_str())).expect("OWNER note delete must persist");
let view_after_delete = runtime
.block_on(crate::open_wallet_view_file_v1(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre005-view-password"))))
@@ -139,14 +127,12 @@ fn strong_view_disable_and_recreate_change_current_view_authority_without_changi
let before = crate::KspWalletEnvelopeV1::parse_json(before_bytes.as_slice()).expect("current Wallet must parse");
let old_slot_id = *before.view_descriptor().slot_id().expect("fixture VIEW descriptor must have slot ID");
let old_metadata_ciphertext = before.metadata().ciphertext().to_vec();
runtime.block_on(owner.disable_view(path.as_path())).expect("OWNER strong VIEW disable must persist");
let locked_disabled = runtime.block_on(crate::inspect_locked_wallet_file_v1(path.as_path())).expect("disabled Wallet must inspect");
assert!(!locked_disabled.view_enabled());
let disabled_view =
runtime.block_on(crate::open_wallet_view_file_v1(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre005-view-password"))));
assert_eq!(disabled_view.expect_err("disabled VIEW must not open").code(), crate::ERROR_CODE_CAPABILITY_INSUFFICIENT);
runtime
.block_on(owner.recreate_view(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre007-recreated-view-password"))))
.expect("OWNER strong VIEW recreate must persist");
@@ -155,7 +141,6 @@ fn strong_view_disable_and_recreate_change_current_view_authority_without_changi
let new_slot_id = *recreated.view_descriptor().slot_id().expect("recreated VIEW descriptor must have slot ID");
assert_ne!(new_slot_id, old_slot_id);
assert_ne!(recreated.metadata().ciphertext(), old_metadata_ciphertext.as_slice());
let old = runtime.block_on(crate::open_wallet_view_file_v1(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre005-view-password"))));
assert_eq!(old.expect_err("historical VIEW password must not unlock recreated current state").code(), crate::ERROR_CODE_VIEW_UNLOCK_FAILED);
let recreated_view = runtime
@@ -173,7 +158,6 @@ fn owner_note_mutation_reports_missing_identifier_without_persisting() {
let mut owner = runtime
.block_on(crate::open_wallet_owner_file_v1(path.as_path(), crate::OwnerPassword::new(std::string::String::from("pre005-owner-password"))))
.expect("OWNER fixture must open");
let result = runtime.block_on(owner.update_note(path.as_path(), "AAAAAAAAAAAAAAAAAAAAAA", std::string::String::from("must-not-persist")));
assert_eq!(result.expect_err("missing note identifier must be rejected").code(), crate::ERROR_CODE_NOTE_NOT_FOUND);
assert_eq!(std::fs::read(path.as_path()).expect("Wallet bytes must remain readable"), before);
@@ -189,13 +173,11 @@ fn stale_owner_handle_cannot_overwrite_a_newer_authenticated_wallet_state() {
let mut stale = runtime
.block_on(crate::open_wallet_owner_file_v1(path.as_path(), crate::OwnerPassword::new(std::string::String::from("pre005-owner-password"))))
.expect("second OWNER handle must open on the same state");
runtime
.block_on(first.update_alias(path.as_path(), std::option::Option::Some(std::string::String::from("first-writer"))))
.expect("first OWNER mutation must persist");
let stale_result = runtime.block_on(stale.update_alias(path.as_path(), std::option::Option::Some(std::string::String::from("stale-writer"))));
assert_eq!(stale_result.expect_err("stale OWNER handle must not overwrite newer state").code(), crate::ERROR_CODE_STATE_CONFLICT);
let current = runtime
.block_on(crate::open_wallet_view_file_v1(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre005-view-password"))))
.expect("VIEW must open the first writer's state");
@@ -213,14 +195,12 @@ fn owner_handle_cannot_replace_a_different_authenticated_wallet_state() {
let mut second = runtime
.block_on(crate::open_wallet_owner_file_v1(second_path.as_path(), crate::OwnerPassword::new(std::string::String::from("pre005-owner-password"))))
.expect("second OWNER handle must open");
runtime
.block_on(second.update_alias(second_path.as_path(), std::option::Option::Some(std::string::String::from("different-authenticated-state"))))
.expect("second Wallet must diverge before wrong-target canary");
let wrong_target =
runtime.block_on(first.update_alias(second_path.as_path(), std::option::Option::Some(std::string::String::from("must-not-overwrite-second-wallet"))));
assert_eq!(wrong_target.expect_err("OWNER handle must not replace a different authenticated Wallet state").code(), crate::ERROR_CODE_STATE_CONFLICT);
let unchanged_first = runtime
.block_on(crate::open_wallet_view_file_v1(first_path.as_path(), crate::ViewPassword::new(std::string::String::from("pre005-view-password"))))
.expect("first Wallet must remain unchanged");

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-wallet-lib/unit_tests/crypto.rs
// version: 2
// version: 3
use base64::Engine; // rust-rules: derive-import
use base64::Engine; // rust-rules: trait-import
const VECTOR: &[u8] = include_bytes!("../tests/fixtures/kspwallet_v1_crypto_vectors.json");
@@ -62,14 +62,12 @@ fn deterministic_argon2id_and_xchacha_wrap_vector_matches_external_canary() -> k
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let derived_result = super::derive_argon2id(vector.password_utf8.as_bytes(), salt.as_slice(), vector.memory_kib, vector.iterations, vector.parallelism);
let derived = match derived_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
assert_eq!(derived.as_bytes(), &expected_derived);
let content = crate::SecretKeyV1::from_bytes(content_key);
let wrapped_result = crate::wrap_key(&derived, &content, &nonce, aad.as_slice());
let wrapped = match wrapped_result {
@@ -77,7 +75,6 @@ fn deterministic_argon2id_and_xchacha_wrap_vector_matches_external_canary() -> k
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
assert_eq!(wrapped, expected_wrapped);
let unwrapped_result = crate::unwrap_key(&derived, &nonce, aad.as_slice(), wrapped.as_slice());
let unwrapped = match unwrapped_result {
std::result::Result::Ok(value) => value,

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-wallet-lib/unit_tests/payload.rs
// version: 2
// version: 3
use base64::Engine; // rust-rules: derive-import
use base64::Engine; // rust-rules: trait-import
#[test]
fn metadata_payload_rejects_duplicate_note_identifiers() -> ksp_core_lib::Result<()> {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-wallet-lib/unit_tests/persistence.rs
// version: 3
// version: 4
fn temp_directory() -> std::io::Result<tempfile::TempDir> {
return tempfile::tempdir();
@@ -11,9 +11,8 @@ fn no_clobber_create_keeps_the_first_published_document() {
let destination = directory.path().join("wallet.kspwallet");
let first = b"first-wallet";
let second = b"second-wallet";
crate::persistence::persist_new_wallet_for_test(destination.as_path(), first).expect("first no-clobber publication must succeed");
let second_result = crate::persistence::persist_new_wallet_for_test(destination.as_path(), second);
crate::persist_new_wallet_for_test(destination.as_path(), first).expect("first no-clobber publication must succeed");
let second_result = crate::persist_new_wallet_for_test(destination.as_path(), second);
let error = second_result.expect_err("second publication must not overwrite an existing wallet");
assert_eq!(error.code(), crate::ERROR_CODE_DESTINATION_EXISTS);
let persisted = std::fs::read(destination.as_path()).expect("published wallet must remain readable");
@@ -24,11 +23,10 @@ fn no_clobber_create_keeps_the_first_published_document() {
fn injected_failure_before_publish_leaves_no_destination_or_partial_wallet() {
let directory = temp_directory().expect("Wallet persistence test directory must be creatable");
let destination = directory.path().join("wallet.kspwallet");
let result = crate::persistence::persist_new_wallet_fault_before_publish(destination.as_path(), b"candidate-wallet");
let result = crate::persist_new_wallet_fault_before_publish(destination.as_path(), b"candidate-wallet");
let error = result.expect_err("fault injection must abort before publication");
assert_eq!(error.code(), crate::ERROR_CODE_ATOMIC_PERSISTENCE_FAILED);
assert!(!destination.exists());
let entries = std::fs::read_dir(directory.path()).expect("Wallet persistence test directory must remain readable");
let count = entries.count();
assert_eq!(count, 0, "temporary artifacts should be cleaned on ordinary error unwinding");
@@ -40,17 +38,15 @@ fn concurrent_no_clobber_publish_has_exactly_one_winner() {
let destination = directory.path().join("wallet.kspwallet");
let barrier = std::sync::Arc::new(std::sync::Barrier::new(8));
let mut handles = std::vec::Vec::new();
for index in 0_u8..8 {
let destination = destination.clone();
let barrier = std::sync::Arc::clone(&barrier);
handles.push(std::thread::spawn(move || {
barrier.wait();
let content = [index; 32];
return crate::persistence::persist_new_wallet_for_test(destination.as_path(), content.as_slice());
return crate::persist_new_wallet_for_test(destination.as_path(), content.as_slice());
}));
}
let mut success_count = 0_usize;
let mut exists_count = 0_usize;
let mut unexpected_code = std::option::Option::None;
@@ -74,7 +70,6 @@ fn bounded_reader_rejects_oversized_wallet_before_parser_allocation() {
let destination = directory.path().join("oversized.kspwallet");
let oversized = std::vec![b'x'; crate::KSPWALLET_MAX_FILE_BYTES + 1];
std::fs::write(destination.as_path(), oversized).expect("oversized fixture must be writable");
let runtime = tokio::runtime::Builder::new_current_thread().build().expect("Wallet persistence test runtime must build");
let result = runtime.block_on(crate::inspect_locked_wallet_file_v1(destination.as_path()));
let error = result.expect_err("oversized Wallet file must be rejected before parsing");
@@ -91,7 +86,6 @@ fn public_file_create_and_locked_inspect_round_trip_without_revealing_identity()
.block_on(crate::create_wallet_file_v1(destination.as_path(), owner_password, std::option::Option::None, crate::WalletCreateMetadataV1::default()))
.expect("native Wallet file creation must succeed");
let locked = runtime.block_on(crate::inspect_locked_wallet_file_v1(destination.as_path())).expect("persisted Wallet must inspect successfully");
assert!(!locked.view_enabled());
assert_eq!(created.capability(), crate::WalletCapability::Owner);
assert!(destination.exists());
@@ -110,8 +104,7 @@ fn persisted_full_vector_opens_view_and_owner_through_file_apis() {
let directory = temp_directory().expect("Wallet persistence test directory must be creatable");
let destination = directory.path().join("vector.kspwallet");
let vector = include_bytes!("../tests/fixtures/kspwallet_v1_full_vector.json");
crate::persistence::persist_new_wallet_for_test(destination.as_path(), vector).expect("full vector must publish through no-clobber persistence");
crate::persist_new_wallet_for_test(destination.as_path(), vector).expect("full vector must publish through no-clobber persistence");
let runtime = tokio::runtime::Builder::new_current_thread().build().expect("Wallet persistence test runtime must build");
let view_password = crate::ViewPassword::new(std::string::String::from("pre005-view-password"));
let view = runtime
@@ -121,7 +114,6 @@ fn persisted_full_vector_opens_view_and_owner_through_file_apis() {
let owner = runtime
.block_on(crate::open_wallet_owner_file_v1(destination.as_path(), owner_password))
.expect("persisted full vector must open through OWNER file API");
assert_eq!(view.capability(), crate::WalletCapability::View);
assert_eq!(owner.capability(), crate::WalletCapability::Owner);
assert_eq!(view.pubkey(), owner.pubkey());
@@ -136,8 +128,7 @@ fn replacement_fault_before_publish_preserves_the_previous_wallet_bytes() {
let original = b"ORIGINAL-WALLET-CANARY";
let replacement = b"REPLACEMENT-WALLET-CANARY";
std::fs::write(destination.as_path(), original).expect("replacement test original must be writable");
let result = crate::persistence::replace_wallet_fault_before_publish(destination.as_path(), replacement);
let result = crate::replace_wallet_fault_before_publish(destination.as_path(), replacement);
assert_eq!(result.expect_err("injected replacement fault must fail").code(), crate::ERROR_CODE_ATOMIC_PERSISTENCE_FAILED);
assert_eq!(std::fs::read(destination.as_path()).expect("original Wallet bytes must remain readable"), original);
}
@@ -146,10 +137,9 @@ fn replacement_fault_before_publish_preserves_the_previous_wallet_bytes() {
fn replacement_requires_an_existing_regular_file_and_replaces_complete_content() {
let directory = temp_directory().expect("Wallet replacement test directory must be creatable");
let destination = directory.path().join("replace.kspwallet");
let missing = crate::persistence::replace_wallet_for_test(destination.as_path(), b"replacement");
let missing = crate::replace_wallet_for_test(destination.as_path(), b"replacement");
assert_eq!(missing.expect_err("replacement must not create a missing Wallet destination").code(), crate::ERROR_CODE_IO_FAILED);
std::fs::write(destination.as_path(), b"old").expect("replacement test original must be writable");
crate::persistence::replace_wallet_for_test(destination.as_path(), b"new-complete-wallet").expect("existing Wallet replacement must succeed");
crate::replace_wallet_for_test(destination.as_path(), b"new-complete-wallet").expect("existing Wallet replacement must succeed");
assert_eq!(std::fs::read(destination.as_path()).expect("replaced Wallet must be readable"), b"new-complete-wallet");
}

View File

@@ -1,9 +1,9 @@
// file: crates/ksp-wallet-lib/unit_tests/security.rs
// version: 2
// version: 3
//! Adversarial security canaries for native `.kspwallet` V1.
use base64::Engine; // rust-rules: derive-import
use base64::Engine; // rust-rules: trait-import
const FULL_VECTOR: &[u8] = include_bytes!("../tests/fixtures/kspwallet_v1_full_vector.json");
@@ -27,10 +27,8 @@ fn owner_signed_regions_reject_canonical_tampering_before_unlock() {
fn owner_signature_verification_precedes_owner_and_view_password_kdf() {
let tampered = tamper_base64url_field(FULL_VECTOR, "/metadata/ciphertext");
let runtime = runtime();
let owner = runtime.block_on(crate::open_wallet_owner_v1(tampered.as_slice(), crate::OwnerPassword::new(std::string::String::new())));
assert_eq!(owner.expect_err("tampered state must fail before an empty OWNER password reaches Argon2").code(), crate::ERROR_CODE_AUTHENTICATION_FAILED);
let view = runtime.block_on(crate::open_wallet_view_v1(tampered.as_slice(), crate::ViewPassword::new(std::string::String::new())));
assert_eq!(view.expect_err("tampered state must fail before an empty VIEW password reaches Argon2").code(), crate::ERROR_CODE_AUTHENTICATION_FAILED);
}
@@ -40,12 +38,10 @@ fn view_credential_tampering_does_not_forge_owner_state_and_cannot_unlock_view()
let tampered = tamper_base64url_field(FULL_VECTOR, "/key_slots/1/wrap/ciphertext");
let locked = crate::inspect_locked_wallet_v1(tampered.as_slice()).expect("VIEW wrapping credentials are intentionally outside the OWNER state transcript");
assert!(locked.view_enabled());
let runtime = runtime();
let owner =
runtime.block_on(crate::open_wallet_owner_v1(tampered.as_slice(), crate::OwnerPassword::new(std::string::String::from("pre005-owner-password"))));
assert!(owner.is_ok(), "VIEW credential tampering must not invalidate OWNER unlock");
let view = runtime.block_on(crate::open_wallet_view_v1(tampered.as_slice(), crate::ViewPassword::new(std::string::String::from("pre005-view-password"))));
assert_eq!(view.expect_err("tampered VIEW wrapping must not unlock metadata").code(), crate::ERROR_CODE_VIEW_UNLOCK_FAILED);
}
@@ -55,13 +51,11 @@ fn unlock_failures_do_not_echo_password_material() {
let owner_canary = "OWNER-PASSWORD-LEAK-CANARY";
let view_canary = "VIEW-PASSWORD-LEAK-CANARY";
let runtime = runtime();
let owner = runtime.block_on(crate::open_wallet_owner_v1(FULL_VECTOR, crate::OwnerPassword::new(std::string::String::from(owner_canary))));
let owner_error = owner.expect_err("wrong OWNER password must fail");
assert_eq!(owner_error.code(), crate::ERROR_CODE_OWNER_UNLOCK_FAILED);
assert!(!owner_error.to_string().contains(owner_canary));
assert!(!format!("{owner_error:?}").contains(owner_canary));
let view = runtime.block_on(crate::open_wallet_view_v1(FULL_VECTOR, crate::ViewPassword::new(std::string::String::from(view_canary))));
let view_error = view.expect_err("wrong VIEW password must fail");
assert_eq!(view_error.code(), crate::ERROR_CODE_VIEW_UNLOCK_FAILED);

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-wallet-lib/unit_tests/transcript.rs
// version: 2
// version: 3
use base64::Engine; // rust-rules: derive-import
use base64::Engine; // rust-rules: trait-import
const FIXTURE: &[u8] = include_bytes!("../tests/fixtures/kspwallet_v1_wire_only.json");

View File

@@ -1,9 +1,9 @@
// file: crates/ksp-wallet-lib/unit_tests/transfer.rs
// version: 2
// version: 3
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt; // rust-rules: derive-import
use zeroize::Zeroize; // rust-rules: derive-import
use std::os::unix::fs::PermissionsExt; // rust-rules: trait-import
use zeroize::Zeroize; // rust-rules: trait-import
fn runtime() -> tokio::runtime::Runtime {
return tokio::runtime::Builder::new_current_thread().build().expect("Wallet transfer test runtime must build");
@@ -41,7 +41,6 @@ fn in_memory_inspection_accepts_cli_json_and_canonical_base58_without_exposing_s
crate::inspect_wallet_transfer(json.as_slice(), crate::WalletTransferFormat::SolanaCliJson).expect("Solana CLI JSON inspection must succeed");
let base58_inspection =
crate::inspect_wallet_transfer(base58.as_bytes(), crate::WalletTransferFormat::SolanaKeypairBase58).expect("canonical Base58 inspection must succeed");
assert_eq!(json_inspection.pubkey(), base58_inspection.pubkey());
assert_eq!(json_inspection.format(), crate::WalletTransferFormat::SolanaCliJson);
assert_eq!(base58_inspection.format(), crate::WalletTransferFormat::SolanaKeypairBase58);
@@ -63,7 +62,6 @@ fn external_seed7_transfer_canary_matches_known_solana_keypair_encodings() {
assert_eq!(actual_keypair_bytes, expected_keypair_bytes);
let mut actual_base58 = keypair.to_base58_string();
assert_eq!(actual_base58, expected_base58);
let mut json = serde_json::to_vec(expected_keypair_bytes.as_slice()).expect("external keypair canary must encode as JSON");
let json_inspection =
crate::inspect_wallet_transfer(json.as_slice(), crate::WalletTransferFormat::SolanaCliJson).expect("external Solana CLI JSON canary must inspect");
@@ -71,7 +69,6 @@ fn external_seed7_transfer_canary_matches_known_solana_keypair_encodings() {
.expect("external Base58 canary must inspect");
assert_eq!(json_inspection.pubkey().to_string(), expected_pubkey);
assert_eq!(base58_inspection.pubkey().to_string(), expected_pubkey);
json.zeroize();
actual_base58.zeroize();
actual_keypair_bytes.zeroize();
@@ -84,7 +81,6 @@ fn transfer_decoders_reject_short_inconsistent_or_noncanonical_sources() {
crate::inspect_wallet_transfer(short_json, crate::WalletTransferFormat::SolanaCliJson).expect_err("short Solana CLI JSON must fail").code(),
crate::ERROR_CODE_KEY_MATERIAL_INVALID
);
let keypair = test_keypair();
let mut bytes = keypair.to_bytes();
bytes[63] ^= 0x01;
@@ -97,7 +93,6 @@ fn transfer_decoders_reject_short_inconsistent_or_noncanonical_sources() {
);
bytes.zeroize();
inconsistent_json.zeroize();
assert_eq!(
crate::inspect_wallet_transfer(b"not valid base58 !!!", crate::WalletTransferFormat::SolanaKeypairBase58)
.expect_err("invalid Base58 must fail")
@@ -136,7 +131,6 @@ fn cli_json_import_creates_new_no_clobber_wallet_with_imported_identity_and_meta
.expect("Solana CLI JSON import must succeed");
assert_eq!(owner.pubkey(), expected.pubkey());
assert_eq!(owner.alias(), std::option::Option::Some("imported-wallet"));
let second_password = crate::OwnerPassword::new(std::string::String::from("pre008-second-owner-password"));
let second = runtime.block_on(crate::import_wallet_transfer_v1(
destination.as_path(),
@@ -172,7 +166,6 @@ fn transfer_file_import_is_non_destructive_and_bounded() {
))
.expect("transfer-file import must succeed");
assert_eq!(std::fs::read(source_path.as_path()).expect("source must survive import"), original);
let oversized_path = directory.path().join("oversized.json");
std::fs::write(oversized_path.as_path(), std::vec![b'1'; crate::KSPWALLET_TRANSFER_MAX_SOLANA_CLI_JSON_BYTES + 1])
.expect("oversized transfer fixture must be writable");
@@ -202,13 +195,11 @@ fn owner_exports_cli_json_and_base58_that_roundtrip_to_the_same_imported_keypair
crate::WalletCreateMetadataV1::default(),
))
.expect("Base58 import must succeed");
let mut exported_json = owner.export_transfer(crate::WalletTransferFormat::SolanaCliJson).expect("OWNER JSON export must succeed");
let mut decoded_json: std::vec::Vec<u8> = serde_json::from_slice(exported_json.as_slice()).expect("OWNER JSON export must be valid JSON");
assert_eq!(decoded_json.as_slice(), expected_bytes.as_slice());
let mut exported_base58 = owner.export_transfer(crate::WalletTransferFormat::SolanaKeypairBase58).expect("OWNER Base58 export must succeed");
assert_eq!(exported_base58.as_slice(), source_base58.as_bytes());
exported_json.zeroize();
decoded_json.zeroize();
exported_base58.zeroize();
@@ -233,7 +224,6 @@ fn owner_transfer_file_export_is_no_clobber() {
let second = runtime.block_on(owner.export_transfer_file(export_path.as_path(), crate::WalletTransferFormat::SolanaCliJson));
assert_eq!(second.expect_err("OWNER transfer-file export must not overwrite").code(), crate::ERROR_CODE_DESTINATION_EXISTS);
assert_eq!(std::fs::read(export_path.as_path()).expect("first export must remain intact"), first);
#[cfg(unix)]
{
let mode = std::fs::metadata(export_path.as_path()).expect("export metadata must be readable").permissions().mode() & 0o777;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-wallet-lib/unit_tests/wallet.rs
// version: 3
// version: 4
const FULL_VECTOR: &[u8] = include_bytes!("../tests/fixtures/kspwallet_v1_full_vector.json");
const FULL_VECTOR_META: &[u8] = include_bytes!("../tests/fixtures/kspwallet_v1_full_vector_meta.json");
@@ -25,7 +25,6 @@ fn externally_generated_full_vector_opens_view_and_owner_independently() -> ksp_
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let view_result = runtime.block_on(crate::open_wallet_view_v1(FULL_VECTOR, crate::ViewPassword::new(vector.view_password_utf8)));
let view = match view_result {
std::result::Result::Ok(value) => value,
@@ -35,7 +34,6 @@ fn externally_generated_full_vector_opens_view_and_owner_independently() -> ksp_
assert_eq!(view.pubkey().to_string(), vector.expected_pubkey);
assert_eq!(view.alias(), std::option::Option::Some(vector.expected_alias.as_str()));
assert_note_texts(view.notes(), vector.expected_notes.as_slice());
let owner_result = runtime.block_on(crate::open_wallet_owner_v1(FULL_VECTOR, crate::OwnerPassword::new(vector.owner_password_utf8)));
let owner = match owner_result {
std::result::Result::Ok(value) => value,
@@ -58,21 +56,18 @@ fn wrong_passwords_do_not_cross_unlock_capabilities() -> ksp_core_lib::Result<()
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wrong_view = runtime.block_on(crate::open_wallet_view_v1(FULL_VECTOR, crate::ViewPassword::new(std::string::String::from("wrong-view-password"))));
let wrong_view_error = match wrong_view {
std::result::Result::Ok(_) => return std::result::Result::Err(test_error("wrong VIEW password unexpectedly unlocked Wallet")),
std::result::Result::Err(error) => error,
};
assert_eq!(wrong_view_error.code(), crate::ERROR_CODE_VIEW_UNLOCK_FAILED);
let wrong_owner = runtime.block_on(crate::open_wallet_owner_v1(FULL_VECTOR, crate::OwnerPassword::new(std::string::String::from("wrong-owner-password"))));
let wrong_owner_error = match wrong_owner {
std::result::Result::Ok(_) => return std::result::Result::Err(test_error("wrong OWNER password unexpectedly unlocked Wallet")),
std::result::Result::Err(error) => error,
};
assert_eq!(wrong_owner_error.code(), crate::ERROR_CODE_OWNER_UNLOCK_FAILED);
let view_as_owner = runtime.block_on(crate::open_wallet_owner_v1(FULL_VECTOR, crate::OwnerPassword::new(vector.view_password_utf8)));
let view_as_owner_error = match view_as_owner {
std::result::Result::Ok(_) => return std::result::Result::Err(test_error("VIEW password unexpectedly unlocked OWNER")),
@@ -112,7 +107,6 @@ fn view_wrap_can_change_without_breaking_owner_authenticated_state() -> ksp_core
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
assert!(inspected.view_enabled());
let runtime = match test_runtime() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -121,7 +115,6 @@ fn view_wrap_can_change_without_breaking_owner_authenticated_state() -> ksp_core
if let std::result::Result::Err(error) = owner_result {
return std::result::Result::Err(error);
}
let view_result = runtime.block_on(crate::open_wallet_view_v1(tampered.as_slice(), crate::ViewPassword::new(vector.view_password_utf8)));
let view_error = match view_result {
std::result::Result::Ok(_) => return std::result::Result::Err(test_error("modified VIEW wrapping unexpectedly unlocked metadata")),
@@ -197,7 +190,6 @@ fn create_uses_calibrated_defaults_and_keeps_locked_projection_private() -> ksp_
std::option::Option::None => return std::result::Result::Err(test_error("created Wallet is missing initial note")),
};
assert_eq!(created_note.text(), note.as_str());
let locked_bytes = match owner.to_json_bytes() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -209,7 +201,6 @@ fn create_uses_calibrated_defaults_and_keeps_locked_projection_private() -> ksp_
assert!(!locked_text.contains(alias.as_str()));
assert!(!locked_text.contains(note.as_str()));
assert!(!locked_text.contains(owner.pubkey().to_string().as_str()));
let envelope = match crate::KspWalletEnvelopeV1::parse_json(locked_bytes.as_slice()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-wallet-lib/unit_tests/wire.rs
// version: 3
// version: 4
const FIXTURE: &[u8] = include_bytes!("../tests/fixtures/kspwallet_v1_wire_only.json");
@@ -16,7 +16,6 @@ fn strict_v1_fixture_parses_and_round_trips_semantically() -> ksp_core_lib::Resu
assert_eq!(parsed.owner_control().payload_version(), 1);
assert_eq!(parsed.metadata().payload_version(), 1);
assert_eq!(parsed.secret().payload_version(), 1);
let serialized = match parsed.to_json_bytes() {
std::result::Result::Ok(serialized) => serialized,
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -89,7 +88,6 @@ fn zero_or_pathological_kdf_parameters_are_rejected_before_crypto() {
std::result::Result::Ok(_) => return,
};
assert_eq!(zero_error.code(), crate::ERROR_CODE_CRYPTO_PARAMETERS_INVALID);
let high_source = std::string::String::from_utf8_lossy(FIXTURE).replacen("\"memory_kib\": 65536", "\"memory_kib\": 1048577", 1);
let high_result = crate::KspWalletEnvelopeV1::parse_json(high_source.as_bytes());
assert!(high_result.is_err());