diff --git a/Cargo.toml b/Cargo.toml index ccdf6af..e8bb5e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ resolver = "3" members = ["crates/ksp-app-config-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"] [workspace.package] -version = "0.2.11-pre.2.fix.2" +version = "0.2.11-pre.2.fix.3" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" diff --git a/crates/ksp-offchain-transport-lib/src/error.rs b/crates/ksp-offchain-transport-lib/src/error.rs index 0d67b68..d0d38ec 100644 --- a/crates/ksp-offchain-transport-lib/src/error.rs +++ b/crates/ksp-offchain-transport-lib/src/error.rs @@ -1,13 +1,17 @@ // file: crates/ksp-offchain-transport-lib/src/error.rs -// version: 2 +// version: 3 -/// Stable off-chain transport error for an invalid exact decimal price. -pub const ERROR_CODE_PRICE_DECIMAL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("offchain_transport", "price_decimal_invalid"); -/// Stable off-chain transport error for an invalid market-price provider descriptor. -pub const ERROR_CODE_PROVIDER_DESCRIPTOR_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("offchain_transport", "provider_descriptor_invalid"); -/// Stable off-chain transport error for an invalid market-price provider identifier. -pub const ERROR_CODE_PROVIDER_ID_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("offchain_transport", "provider_id_invalid"); +/// Stable off-chain transport error for an invalid exact market-price decimal. +pub const ERROR_CODE_MARKET_PRICE_DECIMAL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("offchain_transport", "market_price_decimal_invalid"); /// Stable off-chain transport error for an invalid normalized market-price observation. -pub const ERROR_CODE_PROVIDER_OBSERVATION_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("offchain_transport", "provider_observation_invalid"); +pub const ERROR_CODE_MARKET_PRICE_OBSERVATION_INVALID: ksp_core_lib::ErrorCode = + ksp_core_lib::ErrorCode::new("offchain_transport", "market_price_observation_invalid"); +/// Stable off-chain transport error for an invalid market-price provider descriptor. +pub const ERROR_CODE_MARKET_PRICE_PROVIDER_DESCRIPTOR_INVALID: ksp_core_lib::ErrorCode = + ksp_core_lib::ErrorCode::new("offchain_transport", "market_price_provider_descriptor_invalid"); +/// Stable off-chain transport error for an invalid market-price provider identifier. +pub const ERROR_CODE_MARKET_PRICE_PROVIDER_ID_INVALID: ksp_core_lib::ErrorCode = + ksp_core_lib::ErrorCode::new("offchain_transport", "market_price_provider_id_invalid"); /// Stable off-chain transport error for invalid common market-price provider settings. -pub const ERROR_CODE_PROVIDER_SETTINGS_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("offchain_transport", "provider_settings_invalid"); +pub const ERROR_CODE_MARKET_PRICE_PROVIDER_SETTINGS_INVALID: ksp_core_lib::ErrorCode = + ksp_core_lib::ErrorCode::new("offchain_transport", "market_price_provider_settings_invalid"); diff --git a/crates/ksp-offchain-transport-lib/src/lib.rs b/crates/ksp-offchain-transport-lib/src/lib.rs index 95196ba..90e1ea1 100644 --- a/crates/ksp-offchain-transport-lib/src/lib.rs +++ b/crates/ksp-offchain-transport-lib/src/lib.rs @@ -1,5 +1,5 @@ // file: crates/ksp-offchain-transport-lib/src/lib.rs -// version: 2 +// version: 3 #![warn(missing_docs)] #![deny(unreachable_pub)] @@ -21,61 +21,61 @@ mod market_price_provider; mod market_price_settings; /// Stable error code for an invalid exact decimal price. -pub use self::error::ERROR_CODE_PRICE_DECIMAL_INVALID; -/// Stable error code for an invalid provider descriptor. -pub use self::error::ERROR_CODE_PROVIDER_DESCRIPTOR_INVALID; -/// Stable error code for an invalid provider identifier. -pub use self::error::ERROR_CODE_PROVIDER_ID_INVALID; +pub use self::error::ERROR_CODE_MARKET_PRICE_DECIMAL_INVALID; /// Stable error code for an invalid provider-neutral observation. -pub use self::error::ERROR_CODE_PROVIDER_OBSERVATION_INVALID; +pub use self::error::ERROR_CODE_MARKET_PRICE_OBSERVATION_INVALID; +/// Stable error code for an invalid provider descriptor. +pub use self::error::ERROR_CODE_MARKET_PRICE_PROVIDER_DESCRIPTOR_INVALID; +/// Stable error code for an invalid provider identifier. +pub use self::error::ERROR_CODE_MARKET_PRICE_PROVIDER_ID_INVALID; /// Stable error code for invalid common provider settings. -pub use self::error::ERROR_CODE_PROVIDER_SETTINGS_INVALID; +pub use self::error::ERROR_CODE_MARKET_PRICE_PROVIDER_SETTINGS_INVALID; /// Maximum accepted byte length for one textual decimal input. -pub use self::market_price_decimal::PRICE_DECIMAL_MAX_INPUT_BYTES; +pub use self::market_price_decimal::MARKET_PRICE_DECIMAL_MAX_INPUT_BYTES; /// Maximum decimal scale retained by the canonical SOL/USD price representation. -pub use self::market_price_decimal::PRICE_DECIMAL_MAX_SCALE; -/// Exact positive decimal value used by the public off-chain price contract. -pub use self::market_price_decimal::PriceDecimal; +pub use self::market_price_decimal::MARKET_PRICE_DECIMAL_MAX_SCALE; +/// Exact positive decimal value used by the public market-price contract. +pub use self::market_price_decimal::MarketPriceDecimal; /// Maximum safe provenance length attached to one normalized observation. -pub use self::market_price_observation::PRICE_PROVENANCE_MAX_BYTES; -/// Safe bounded provenance supplied by one provider adapter. -pub use self::market_price_observation::PriceProvenance; -/// Millisecond UTC timestamp used for request, receipt, provider and cooldown projections. -pub use self::market_price_observation::PriceTimestamp; +pub use self::market_price_observation::MARKET_PRICE_PROVENANCE_MAX_BYTES; /// Public V1 SOL/USD observation normalized by Off-chain Transport. -pub use self::market_price_observation::SolUsdPriceObservation; +pub use self::market_price_observation::MarketPriceObservation; +/// Safe bounded provenance supplied by one provider adapter. +pub use self::market_price_observation::MarketPriceProvenance; +/// Millisecond UTC timestamp used for request, receipt, provider and cooldown projections. +pub use self::market_price_observation::MarketPriceTimestamp; /// Maximum provider display-name length accepted by descriptors. -pub use self::market_price_provider::PRICE_PROVIDER_DISPLAY_NAME_MAX_BYTES; +pub use self::market_price_provider::MARKET_PRICE_PROVIDER_DISPLAY_NAME_MAX_BYTES; /// Maximum opaque provider identifier length accepted by the public contract. -pub use self::market_price_provider::PRICE_PROVIDER_ID_MAX_BYTES; +pub use self::market_price_provider::MARKET_PRICE_PROVIDER_ID_MAX_BYTES; /// Only price pair exposed by the `0.2.11` V1 public contract. -pub use self::market_price_provider::PricePair; +pub use self::market_price_provider::MarketPricePair; /// Generic authentication capability exposed by a configured provider descriptor. -pub use self::market_price_provider::PriceProviderAuthMode; +pub use self::market_price_provider::MarketPriceProviderAuthMode; /// Generic runtime availability state exposed without provider-specific error parsing. -pub use self::market_price_provider::PriceProviderAvailability; +pub use self::market_price_provider::MarketPriceProviderAvailability; /// Provider capability and presentation descriptor consumed by provider-agnostic callers. -pub use self::market_price_provider::PriceProviderDescriptor; +pub use self::market_price_provider::MarketPriceProviderDescriptor; /// Opaque validated provider identifier owned by Off-chain Transport. -pub use self::market_price_provider::PriceProviderId; +pub use self::market_price_provider::MarketPriceProviderId; /// Long-term provider quota descriptor that is informational rather than an authoritative local counter. -pub use self::market_price_provider::PriceProviderLongTermQuota; +pub use self::market_price_provider::MarketPriceProviderLongTermQuota; /// Period used by a documented long-term provider quota. -pub use self::market_price_provider::PriceProviderQuotaPeriod; +pub use self::market_price_provider::MarketPriceProviderQuotaPeriod; /// Unit used by a documented long-term provider quota. -pub use self::market_price_provider::PriceProviderQuotaUnit; +pub use self::market_price_provider::MarketPriceProviderQuotaUnit; /// Generic provider request-limit capability. -pub use self::market_price_provider::PriceProviderRateLimit; +pub use self::market_price_provider::MarketPriceProviderRateLimit; /// Shape of one generic provider request-limit capability. -pub use self::market_price_provider::PriceProviderRateLimitKind; +pub use self::market_price_provider::MarketPriceProviderRateLimitKind; /// Scope to which a provider documents one request limit. -pub use self::market_price_provider::PriceProviderRateLimitScope; +pub use self::market_price_provider::MarketPriceProviderRateLimitScope; /// Current provider-neutral runtime state projection. -pub use self::market_price_provider::PriceProviderState; -/// Price semantics retained so consumers never assume all providers report equivalent market values. -pub use self::market_price_provider::PriceSemantics; +pub use self::market_price_provider::MarketPriceProviderState; +/// Market-price semantics retained so consumers never assume all providers report equivalent market values. +pub use self::market_price_provider::MarketPriceSemantics; /// Common provider settings shared by provider-specific runtime settings. -pub use self::market_price_settings::PriceProviderCommonSettings; +pub use self::market_price_settings::MarketPriceProviderCommonSettings; /// Owning tracing target for events emitted by Off-chain Transport. pub(crate) use self::constants::TRACING_TARGET; diff --git a/crates/ksp-offchain-transport-lib/src/market_price_decimal.rs b/crates/ksp-offchain-transport-lib/src/market_price_decimal.rs index e57b054..aa184c4 100644 --- a/crates/ksp-offchain-transport-lib/src/market_price_decimal.rs +++ b/crates/ksp-offchain-transport-lib/src/market_price_decimal.rs @@ -1,25 +1,25 @@ // file: crates/ksp-offchain-transport-lib/src/market_price_decimal.rs -// version: 2 +// version: 3 /// Maximum accepted UTF-8 byte length for one textual decimal input. -pub const PRICE_DECIMAL_MAX_INPUT_BYTES: usize = 96; +pub const MARKET_PRICE_DECIMAL_MAX_INPUT_BYTES: usize = 96; /// Maximum scale retained by the canonical exact decimal representation. -pub const PRICE_DECIMAL_MAX_SCALE: u8 = 18; +pub const MARKET_PRICE_DECIMAL_MAX_SCALE: u8 = 18; /// Exact positive decimal value used for one successful V1 SOL/USD observation. /// /// The value is represented as a positive `u128` coefficient plus a bounded decimal scale. Trailing fractional zeroes are removed during construction, and /// Serde serialization always emits the canonical decimal string instead of an IEEE-754 number. #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct PriceDecimal { +pub struct MarketPriceDecimal { coefficient: u128, scale: u8, } -impl PriceDecimal { +impl MarketPriceDecimal { /// Parses a positive decimal or bounded scientific-notation value without converting through `f64`. pub fn parse(source: &str) -> ksp_core_lib::Result { - if source.is_empty() || source.len() > crate::PRICE_DECIMAL_MAX_INPUT_BYTES || source.trim() != source { + if source.is_empty() || source.len() > crate::MARKET_PRICE_DECIMAL_MAX_INPUT_BYTES || source.trim() != source { return std::result::Result::Err(invalid_decimal_error()); } let (significand, exponent) = match split_exponent(source) { @@ -49,7 +49,7 @@ impl PriceDecimal { }; effective_scale = 0; } - if effective_scale > i32::from(crate::PRICE_DECIMAL_MAX_SCALE) { + if effective_scale > i32::from(crate::MARKET_PRICE_DECIMAL_MAX_SCALE) { return std::result::Result::Err(invalid_decimal_error()); } let mut scale = match u8::try_from(effective_scale) { @@ -92,21 +92,21 @@ impl PriceDecimal { } } -impl std::fmt::Display for PriceDecimal { +impl std::fmt::Display for MarketPriceDecimal { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter.write_str(self.to_canonical_string().as_str()); } } -impl std::str::FromStr for PriceDecimal { +impl std::str::FromStr for MarketPriceDecimal { type Err = ksp_core_lib::Error; fn from_str(source: &str) -> std::result::Result { - return crate::PriceDecimal::parse(source); + return crate::MarketPriceDecimal::parse(source); } } -impl serde::Serialize for PriceDecimal { +impl serde::Serialize for MarketPriceDecimal { fn serialize(&self, serializer: S) -> std::result::Result where S: serde::Serializer, @@ -115,19 +115,19 @@ impl serde::Serialize for PriceDecimal { } } -impl<'de> serde::Deserialize<'de> for PriceDecimal { +impl<'de> serde::Deserialize<'de> for MarketPriceDecimal { fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { - return deserializer.deserialize_str(PriceDecimalVisitor); + return deserializer.deserialize_str(MarketPriceDecimalVisitor); } } -struct PriceDecimalVisitor; +struct MarketPriceDecimalVisitor; -impl<'de> serde::de::Visitor<'de> for PriceDecimalVisitor { - type Value = crate::PriceDecimal; +impl<'de> serde::de::Visitor<'de> for MarketPriceDecimalVisitor { + type Value = crate::MarketPriceDecimal; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter.write_str("a canonicalizable positive decimal string"); @@ -137,9 +137,9 @@ impl<'de> serde::de::Visitor<'de> for PriceDecimalVisitor { where E: serde::de::Error, { - return match crate::PriceDecimal::parse(value) { + return match crate::MarketPriceDecimal::parse(value) { std::result::Result::Ok(decimal) => std::result::Result::Ok(decimal), - std::result::Result::Err(_) => std::result::Result::Err(E::custom("invalid KSP price decimal")), + std::result::Result::Err(_) => std::result::Result::Err(E::custom("invalid KSP market-price decimal")), }; } } @@ -157,7 +157,7 @@ fn checked_multiply_power_of_ten(mut value: u128, exponent: u32) -> std::option: } fn invalid_decimal_error() -> ksp_core_lib::Error { - return ksp_core_lib::Error::new(crate::ERROR_CODE_PRICE_DECIMAL_INVALID, "invalid exact price decimal"); + return ksp_core_lib::Error::new(crate::ERROR_CODE_MARKET_PRICE_DECIMAL_INVALID, "invalid exact market-price decimal"); } fn significand_digits(significand: &str) -> std::result::Result<(std::string::String, u8), ()> { diff --git a/crates/ksp-offchain-transport-lib/src/market_price_observation.rs b/crates/ksp-offchain-transport-lib/src/market_price_observation.rs index 87f600a..318c92e 100644 --- a/crates/ksp-offchain-transport-lib/src/market_price_observation.rs +++ b/crates/ksp-offchain-transport-lib/src/market_price_observation.rs @@ -1,16 +1,16 @@ // file: crates/ksp-offchain-transport-lib/src/market_price_observation.rs -// version: 2 +// version: 3 /// Maximum UTF-8 byte length accepted for safe provider provenance. -pub const PRICE_PROVENANCE_MAX_BYTES: usize = 256; +pub const MARKET_PRICE_PROVENANCE_MAX_BYTES: usize = 256; /// Millisecond UTC timestamp used by provider-neutral public projections. #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize)] -pub struct PriceTimestamp { +pub struct MarketPriceTimestamp { unix_millis: u64, } -impl PriceTimestamp { +impl MarketPriceTimestamp { /// Creates a UTC timestamp from whole milliseconds since Unix epoch. #[must_use] pub const fn from_unix_millis(unix_millis: u64) -> Self { @@ -27,9 +27,9 @@ impl PriceTimestamp { /// Safe bounded provenance supplied by one provider adapter. #[derive(Clone, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)] #[serde(try_from = "std::string::String", into = "std::string::String")] -pub struct PriceProvenance(std::string::String); +pub struct MarketPriceProvenance(std::string::String); -impl PriceProvenance { +impl MarketPriceProvenance { /// Creates bounded non-empty provenance without accepting control characters. pub fn new(value: impl std::convert::Into) -> ksp_core_lib::Result { let value = value.into(); @@ -46,49 +46,49 @@ impl PriceProvenance { } } -impl std::convert::TryFrom for PriceProvenance { +impl std::convert::TryFrom for MarketPriceProvenance { type Error = ksp_core_lib::Error; fn try_from(value: std::string::String) -> std::result::Result { - return crate::PriceProvenance::new(value); + return crate::MarketPriceProvenance::new(value); } } -impl std::convert::From for std::string::String { - fn from(value: PriceProvenance) -> Self { +impl std::convert::From for std::string::String { + fn from(value: MarketPriceProvenance) -> Self { return value.0; } } /// Public V1 SOL/USD observation normalized by Off-chain Transport. #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] -pub struct SolUsdPriceObservation { - pair: crate::PricePair, - price: crate::PriceDecimal, - provider_id: crate::PriceProviderId, - provider_timestamp: std::option::Option, - provenance: crate::PriceProvenance, - received_at: crate::PriceTimestamp, - request_started_at: crate::PriceTimestamp, - semantics: crate::PriceSemantics, +pub struct MarketPriceObservation { + pair: crate::MarketPricePair, + price: crate::MarketPriceDecimal, + provider_id: crate::MarketPriceProviderId, + provider_timestamp: std::option::Option, + provenance: crate::MarketPriceProvenance, + received_at: crate::MarketPriceTimestamp, + request_started_at: crate::MarketPriceTimestamp, + semantics: crate::MarketPriceSemantics, } -impl SolUsdPriceObservation { +impl MarketPriceObservation { /// Creates one successful normalized SOL/USD observation. pub fn new( - provider_id: crate::PriceProviderId, - price: crate::PriceDecimal, - semantics: crate::PriceSemantics, - request_started_at: crate::PriceTimestamp, - received_at: crate::PriceTimestamp, - provider_timestamp: std::option::Option, - provenance: crate::PriceProvenance, + provider_id: crate::MarketPriceProviderId, + price: crate::MarketPriceDecimal, + semantics: crate::MarketPriceSemantics, + request_started_at: crate::MarketPriceTimestamp, + received_at: crate::MarketPriceTimestamp, + provider_timestamp: std::option::Option, + provenance: crate::MarketPriceProvenance, ) -> ksp_core_lib::Result { if received_at < request_started_at { return std::result::Result::Err(observation_error()); } return std::result::Result::Ok(Self { - pair: crate::PricePair::SolUsd, + pair: crate::MarketPricePair::SolUsd, price, provider_id, provider_timestamp, @@ -101,59 +101,59 @@ impl SolUsdPriceObservation { /// Returns the only V1 pair represented by this observation. #[must_use] - pub const fn pair(&self) -> crate::PricePair { + pub const fn pair(&self) -> crate::MarketPricePair { return self.pair; } /// Returns the exact positive SOL/USD price. #[must_use] - pub const fn price(&self) -> crate::PriceDecimal { + pub const fn price(&self) -> crate::MarketPriceDecimal { return self.price; } /// Returns the opaque provider identifier. #[must_use] - pub const fn provider_id(&self) -> &crate::PriceProviderId { + pub const fn provider_id(&self) -> &crate::MarketPriceProviderId { return &self.provider_id; } /// Returns a provider timestamp only when the provider adapter has a real price-time field. #[must_use] - pub const fn provider_timestamp(&self) -> std::option::Option { + pub const fn provider_timestamp(&self) -> std::option::Option { return self.provider_timestamp; } /// Returns safe provider provenance. #[must_use] - pub const fn provenance(&self) -> &crate::PriceProvenance { + pub const fn provenance(&self) -> &crate::MarketPriceProvenance { return &self.provenance; } /// Returns the KSP wall-clock receipt timestamp. #[must_use] - pub const fn received_at(&self) -> crate::PriceTimestamp { + pub const fn received_at(&self) -> crate::MarketPriceTimestamp { return self.received_at; } /// Returns the KSP wall-clock request-start timestamp. #[must_use] - pub const fn request_started_at(&self) -> crate::PriceTimestamp { + pub const fn request_started_at(&self) -> crate::MarketPriceTimestamp { return self.request_started_at; } /// Returns the provider-specific semantic class retained by the normalized observation. #[must_use] - pub const fn semantics(&self) -> crate::PriceSemantics { + pub const fn semantics(&self) -> crate::MarketPriceSemantics { return self.semantics; } } fn observation_error() -> ksp_core_lib::Error { - return ksp_core_lib::Error::new(crate::ERROR_CODE_PROVIDER_OBSERVATION_INVALID, "invalid off-chain price observation"); + return ksp_core_lib::Error::new(crate::ERROR_CODE_MARKET_PRICE_OBSERVATION_INVALID, "invalid off-chain market-price observation"); } fn valid_provenance(value: &str) -> bool { - if value.is_empty() || value.len() > crate::PRICE_PROVENANCE_MAX_BYTES || value.trim() != value { + if value.is_empty() || value.len() > crate::MARKET_PRICE_PROVENANCE_MAX_BYTES || value.trim() != value { return false; } for character in value.chars() { diff --git a/crates/ksp-offchain-transport-lib/src/market_price_provider.rs b/crates/ksp-offchain-transport-lib/src/market_price_provider.rs index fd54fe0..2708b4b 100644 --- a/crates/ksp-offchain-transport-lib/src/market_price_provider.rs +++ b/crates/ksp-offchain-transport-lib/src/market_price_provider.rs @@ -1,20 +1,20 @@ // file: crates/ksp-offchain-transport-lib/src/market_price_provider.rs -// version: 2 +// version: 3 /// Maximum UTF-8 byte length of one provider display name. -pub const PRICE_PROVIDER_DISPLAY_NAME_MAX_BYTES: usize = 96; +pub const MARKET_PRICE_PROVIDER_DISPLAY_NAME_MAX_BYTES: usize = 96; /// Maximum byte length of one opaque provider identifier. -pub const PRICE_PROVIDER_ID_MAX_BYTES: usize = 64; +pub const MARKET_PRICE_PROVIDER_ID_MAX_BYTES: usize = 64; /// Only price pair exposed by the `0.2.11` V1 public contract. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "snake_case")] -pub enum PricePair { +pub enum MarketPricePair { /// Native SOL quoted directly in US dollars according to one provider's documented semantics. SolUsd, } -impl PricePair { +impl MarketPricePair { /// Returns the stable human-readable pair code. #[must_use] pub const fn code(&self) -> &'static str { @@ -24,10 +24,10 @@ impl PricePair { } } -/// Price semantics retained so normalized observations do not imply cross-provider equivalence. +/// Market-price semantics retained so normalized observations do not imply cross-provider equivalence. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "snake_case")] -pub enum PriceSemantics { +pub enum MarketPriceSemantics { /// Aggregated market price produced by a multi-market data provider. AggregatedMarket, /// USD price associated with one explicitly configured DEX pair. @@ -43,7 +43,7 @@ pub enum PriceSemantics { /// Generic authentication capability exposed by one configured provider. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "snake_case")] -pub enum PriceProviderAuthMode { +pub enum MarketPriceProviderAuthMode { /// No credential is required for the configured access mode. None, /// Provider accepts an API key but also supports an unauthenticated mode selected by configuration. @@ -55,7 +55,7 @@ pub enum PriceProviderAuthMode { /// Scope to which a provider documents a request limit. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "snake_case")] -pub enum PriceProviderRateLimitScope { +pub enum MarketPriceProviderRateLimitScope { /// Limit is associated with the configured account or API key. Account, /// Limit is associated with the source IP address. @@ -69,7 +69,7 @@ pub enum PriceProviderRateLimitScope { /// Shape of one generic provider request-limit capability. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Serialize)] #[serde(rename_all = "snake_case")] -pub enum PriceProviderRateLimitKind { +pub enum MarketPriceProviderRateLimitKind { /// Dynamic or server-driven limit that cannot be represented as one safe fixed local cadence. Dynamic, /// Locally enforceable fixed request budget over a documented window. @@ -78,23 +78,28 @@ pub enum PriceProviderRateLimitKind { /// Generic provider request-limit capability with validated fixed-limit values. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Serialize)] -pub struct PriceProviderRateLimit { +pub struct MarketPriceProviderRateLimit { burst: std::option::Option, - kind: crate::PriceProviderRateLimitKind, + kind: crate::MarketPriceProviderRateLimitKind, requests: std::option::Option, - scope: crate::PriceProviderRateLimitScope, + scope: crate::MarketPriceProviderRateLimitScope, window_seconds: std::option::Option, } -impl PriceProviderRateLimit { +impl MarketPriceProviderRateLimit { /// Creates a validated fixed request limit. - pub fn fixed(requests: u32, window_seconds: u32, burst: std::option::Option, scope: crate::PriceProviderRateLimitScope) -> ksp_core_lib::Result { + pub fn fixed( + requests: u32, + window_seconds: u32, + burst: std::option::Option, + scope: crate::MarketPriceProviderRateLimitScope, + ) -> ksp_core_lib::Result { if requests == 0 || window_seconds == 0 || burst == std::option::Option::Some(0) { return std::result::Result::Err(provider_descriptor_error()); } return std::result::Result::Ok(Self { burst, - kind: crate::PriceProviderRateLimitKind::Fixed, + kind: crate::MarketPriceProviderRateLimitKind::Fixed, requests: std::option::Option::Some(requests), scope, window_seconds: std::option::Option::Some(window_seconds), @@ -103,10 +108,10 @@ impl PriceProviderRateLimit { /// Creates a dynamic/server-driven request-limit descriptor. #[must_use] - pub const fn dynamic(scope: crate::PriceProviderRateLimitScope) -> Self { + pub const fn dynamic(scope: crate::MarketPriceProviderRateLimitScope) -> Self { return Self { burst: std::option::Option::None, - kind: crate::PriceProviderRateLimitKind::Dynamic, + kind: crate::MarketPriceProviderRateLimitKind::Dynamic, requests: std::option::Option::None, scope, window_seconds: std::option::Option::None, @@ -121,7 +126,7 @@ impl PriceProviderRateLimit { /// Returns whether the limit is fixed or dynamic/server-driven. #[must_use] - pub const fn kind(&self) -> crate::PriceProviderRateLimitKind { + pub const fn kind(&self) -> crate::MarketPriceProviderRateLimitKind { return self.kind; } @@ -133,7 +138,7 @@ impl PriceProviderRateLimit { /// Returns the documented limit scope. #[must_use] - pub const fn scope(&self) -> crate::PriceProviderRateLimitScope { + pub const fn scope(&self) -> crate::MarketPriceProviderRateLimitScope { return self.scope; } @@ -147,7 +152,7 @@ impl PriceProviderRateLimit { /// Period used by one documented long-term provider quota. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "snake_case")] -pub enum PriceProviderQuotaPeriod { +pub enum MarketPriceProviderQuotaPeriod { /// Quota resets on a provider-defined daily period. Day, /// Quota resets on a provider-defined monthly period. @@ -157,7 +162,7 @@ pub enum PriceProviderQuotaPeriod { /// Unit used by one documented long-term provider quota. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "snake_case")] -pub enum PriceProviderQuotaUnit { +pub enum MarketPriceProviderQuotaUnit { /// Provider-specific credits, not assumed to equal HTTP requests. Credits, /// HTTP/API requests. @@ -166,15 +171,15 @@ pub enum PriceProviderQuotaUnit { /// Long-term provider quota descriptor exposed as non-authoritative capability metadata. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Serialize)] -pub struct PriceProviderLongTermQuota { +pub struct MarketPriceProviderLongTermQuota { amount: u64, - period: crate::PriceProviderQuotaPeriod, - unit: crate::PriceProviderQuotaUnit, + period: crate::MarketPriceProviderQuotaPeriod, + unit: crate::MarketPriceProviderQuotaUnit, } -impl PriceProviderLongTermQuota { +impl MarketPriceProviderLongTermQuota { /// Creates a non-zero documented quota descriptor. - pub fn new(amount: u64, period: crate::PriceProviderQuotaPeriod, unit: crate::PriceProviderQuotaUnit) -> ksp_core_lib::Result { + pub fn new(amount: u64, period: crate::MarketPriceProviderQuotaPeriod, unit: crate::MarketPriceProviderQuotaUnit) -> ksp_core_lib::Result { if amount == 0 { return std::result::Result::Err(provider_descriptor_error()); } @@ -189,13 +194,13 @@ impl PriceProviderLongTermQuota { /// Returns the provider-defined quota period. #[must_use] - pub const fn period(&self) -> crate::PriceProviderQuotaPeriod { + pub const fn period(&self) -> crate::MarketPriceProviderQuotaPeriod { return self.period; } /// Returns the documented quota unit. #[must_use] - pub const fn unit(&self) -> crate::PriceProviderQuotaUnit { + pub const fn unit(&self) -> crate::MarketPriceProviderQuotaUnit { return self.unit; } } @@ -203,9 +208,9 @@ impl PriceProviderLongTermQuota { /// Opaque validated provider identifier owned by Off-chain Transport. #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize)] #[serde(try_from = "std::string::String", into = "std::string::String")] -pub struct PriceProviderId(std::string::String); +pub struct MarketPriceProviderId(std::string::String); -impl PriceProviderId { +impl MarketPriceProviderId { /// Creates one bounded stable provider identifier. pub fn new(value: impl std::convert::Into) -> ksp_core_lib::Result { ksp_logging_lib::trace!(target: crate::TRACING_TARGET, "validating market-price provider identifier"); @@ -213,7 +218,7 @@ impl PriceProviderId { if !valid_provider_id(value.as_str()) { ksp_logging_lib::warn!(target: crate::TRACING_TARGET, field = "provider_id", "rejected invalid market-price provider identifier"); return std::result::Result::Err(ksp_core_lib::Error::new( - crate::ERROR_CODE_PROVIDER_ID_INVALID, + crate::ERROR_CODE_MARKET_PRICE_PROVIDER_ID_INVALID, "invalid off-chain market-price provider identifier", )); } @@ -227,47 +232,47 @@ impl PriceProviderId { } } -impl std::fmt::Display for PriceProviderId { +impl std::fmt::Display for MarketPriceProviderId { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter.write_str(self.0.as_str()); } } -impl std::convert::TryFrom for PriceProviderId { +impl std::convert::TryFrom for MarketPriceProviderId { type Error = ksp_core_lib::Error; fn try_from(value: std::string::String) -> std::result::Result { - return crate::PriceProviderId::new(value); + return crate::MarketPriceProviderId::new(value); } } -impl std::convert::From for std::string::String { - fn from(value: PriceProviderId) -> Self { +impl std::convert::From for std::string::String { + fn from(value: MarketPriceProviderId) -> Self { return value.0; } } /// Provider capability and presentation descriptor consumed by provider-agnostic callers. #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] -pub struct PriceProviderDescriptor { - auth_mode: crate::PriceProviderAuthMode, +pub struct MarketPriceProviderDescriptor { + auth_mode: crate::MarketPriceProviderAuthMode, display_name: std::string::String, - id: crate::PriceProviderId, - long_term_quota: std::option::Option, - rate_limit: crate::PriceProviderRateLimit, - semantics: crate::PriceSemantics, + id: crate::MarketPriceProviderId, + long_term_quota: std::option::Option, + rate_limit: crate::MarketPriceProviderRateLimit, + semantics: crate::MarketPriceSemantics, supports_sol_usd: bool, } -impl PriceProviderDescriptor { +impl MarketPriceProviderDescriptor { /// Creates a validated provider-neutral descriptor. pub fn new( - id: crate::PriceProviderId, + id: crate::MarketPriceProviderId, display_name: impl std::convert::Into, - semantics: crate::PriceSemantics, - auth_mode: crate::PriceProviderAuthMode, - rate_limit: crate::PriceProviderRateLimit, - long_term_quota: std::option::Option, + semantics: crate::MarketPriceSemantics, + auth_mode: crate::MarketPriceProviderAuthMode, + rate_limit: crate::MarketPriceProviderRateLimit, + long_term_quota: std::option::Option, supports_sol_usd: bool, ) -> ksp_core_lib::Result { ksp_logging_lib::trace!(target: crate::TRACING_TARGET, "validating market-price provider descriptor"); @@ -281,7 +286,7 @@ impl PriceProviderDescriptor { /// Returns the configured authentication capability. #[must_use] - pub const fn auth_mode(&self) -> crate::PriceProviderAuthMode { + pub const fn auth_mode(&self) -> crate::MarketPriceProviderAuthMode { return self.auth_mode; } @@ -293,25 +298,25 @@ impl PriceProviderDescriptor { /// Returns the opaque provider identifier. #[must_use] - pub const fn id(&self) -> &crate::PriceProviderId { + pub const fn id(&self) -> &crate::MarketPriceProviderId { return &self.id; } /// Returns optional long-term quota metadata without exposing a local remaining counter. #[must_use] - pub const fn long_term_quota(&self) -> std::option::Option { + pub const fn long_term_quota(&self) -> std::option::Option { return self.long_term_quota; } /// Returns the configured request-limit capability. #[must_use] - pub const fn rate_limit(&self) -> crate::PriceProviderRateLimit { + pub const fn rate_limit(&self) -> crate::MarketPriceProviderRateLimit { return self.rate_limit; } /// Returns the documented price semantics. #[must_use] - pub const fn semantics(&self) -> crate::PriceSemantics { + pub const fn semantics(&self) -> crate::MarketPriceSemantics { return self.semantics; } @@ -325,13 +330,13 @@ impl PriceProviderDescriptor { /// Generic runtime availability state exposed without provider-specific error parsing. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)] #[serde(tag = "state", rename_all = "snake_case")] -pub enum PriceProviderAvailability { +pub enum MarketPriceProviderAvailability { /// Required authentication material is unavailable or rejected. AuthenticationUnavailable, /// Provider is locally cooling down until the supplied timestamp. CoolingDown { /// Earliest known wall-clock timestamp at which a new attempt may be admitted. - retry_at: crate::PriceTimestamp, + retry_at: crate::MarketPriceTimestamp, }, /// Provider is disabled by runtime configuration. Disabled, @@ -344,43 +349,43 @@ pub enum PriceProviderAvailability { /// Transport/provider failure is transient; retry time is present only when actually known. TemporarilyUnavailable { /// Optional next retry timestamp derived from safe runtime/provider information. - retry_at: std::option::Option, + retry_at: std::option::Option, }, } /// Current provider-neutral runtime state projection. #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] -pub struct PriceProviderState { - availability: crate::PriceProviderAvailability, - provider_id: crate::PriceProviderId, +pub struct MarketPriceProviderState { + availability: crate::MarketPriceProviderAvailability, + provider_id: crate::MarketPriceProviderId, } -impl PriceProviderState { +impl MarketPriceProviderState { /// Creates one generic state projection for a configured provider. #[must_use] - pub fn new(provider_id: crate::PriceProviderId, availability: crate::PriceProviderAvailability) -> Self { + pub fn new(provider_id: crate::MarketPriceProviderId, availability: crate::MarketPriceProviderAvailability) -> Self { return Self { availability, provider_id }; } /// Returns the generic availability classification. #[must_use] - pub const fn availability(&self) -> crate::PriceProviderAvailability { + pub const fn availability(&self) -> crate::MarketPriceProviderAvailability { return self.availability; } /// Returns the opaque provider identifier. #[must_use] - pub const fn provider_id(&self) -> &crate::PriceProviderId { + pub const fn provider_id(&self) -> &crate::MarketPriceProviderId { return &self.provider_id; } } fn provider_descriptor_error() -> ksp_core_lib::Error { - return ksp_core_lib::Error::new(crate::ERROR_CODE_PROVIDER_DESCRIPTOR_INVALID, "invalid off-chain market-price provider descriptor"); + return ksp_core_lib::Error::new(crate::ERROR_CODE_MARKET_PRICE_PROVIDER_DESCRIPTOR_INVALID, "invalid off-chain market-price provider descriptor"); } fn valid_display_name(value: &str) -> bool { - if value.is_empty() || value.len() > crate::PRICE_PROVIDER_DISPLAY_NAME_MAX_BYTES || value.trim() != value { + if value.is_empty() || value.len() > crate::MARKET_PRICE_PROVIDER_DISPLAY_NAME_MAX_BYTES || value.trim() != value { return false; } for character in value.chars() { @@ -392,7 +397,7 @@ fn valid_display_name(value: &str) -> bool { } fn valid_provider_id(value: &str) -> bool { - if value.is_empty() || value.len() > crate::PRICE_PROVIDER_ID_MAX_BYTES { + if value.is_empty() || value.len() > crate::MARKET_PRICE_PROVIDER_ID_MAX_BYTES { return false; } for byte in value.bytes() { diff --git a/crates/ksp-offchain-transport-lib/src/market_price_settings.rs b/crates/ksp-offchain-transport-lib/src/market_price_settings.rs index fd4c353..5529c09 100644 --- a/crates/ksp-offchain-transport-lib/src/market_price_settings.rs +++ b/crates/ksp-offchain-transport-lib/src/market_price_settings.rs @@ -1,20 +1,20 @@ // file: crates/ksp-offchain-transport-lib/src/market_price_settings.rs -// version: 2 +// version: 3 /// Common provider settings embedded by future provider-specific runtime settings. /// /// Provider-specific credentials, pair selectors and access modes intentionally do not live here because providers without those capabilities must not be /// forced into artificial fields. #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] -pub struct PriceProviderCommonSettings { +pub struct MarketPriceProviderCommonSettings { enabled: bool, - provider_id: crate::PriceProviderId, + provider_id: crate::MarketPriceProviderId, } -impl PriceProviderCommonSettings { +impl MarketPriceProviderCommonSettings { /// Creates common settings for one uniquely identified runtime provider instance. #[must_use] - pub fn new(provider_id: crate::PriceProviderId, enabled: bool) -> Self { + pub fn new(provider_id: crate::MarketPriceProviderId, enabled: bool) -> Self { ksp_logging_lib::trace!(target: crate::TRACING_TARGET, enabled = enabled, "constructed common market-price provider settings"); return Self { enabled, provider_id }; } @@ -27,7 +27,7 @@ impl PriceProviderCommonSettings { /// Returns the opaque runtime provider identifier. #[must_use] - pub const fn provider_id(&self) -> &crate::PriceProviderId { + pub const fn provider_id(&self) -> &crate::MarketPriceProviderId { return &self.provider_id; } } diff --git a/crates/ksp-offchain-transport-lib/tests/public_api.rs b/crates/ksp-offchain-transport-lib/tests/public_api.rs index 24754dd..a904c46 100644 --- a/crates/ksp-offchain-transport-lib/tests/public_api.rs +++ b/crates/ksp-offchain-transport-lib/tests/public_api.rs @@ -1,5 +1,5 @@ // file: crates/ksp-offchain-transport-lib/tests/public_api.rs -// version: 2 +// version: 3 #![warn(missing_docs)] #![deny(unreachable_pub)] @@ -8,34 +8,34 @@ //! Public API canaries for the first Off-chain Transport market-price capability family. #[test] -fn public_pre_002_price_foundation_is_available_from_crate_root() -> ksp_core_lib::Result<()> { - assert_eq!(ksp_offchain_transport_lib::PricePair::SolUsd.code(), "SOL/USD"); - assert_eq!(ksp_offchain_transport_lib::PRICE_DECIMAL_MAX_SCALE, 18); - let price = match ksp_offchain_transport_lib::PriceDecimal::parse("201.2500") { +fn public_pre_002_market_price_foundation_is_available_from_crate_root() -> ksp_core_lib::Result<()> { + assert_eq!(ksp_offchain_transport_lib::MarketPricePair::SolUsd.code(), "SOL/USD"); + assert_eq!(ksp_offchain_transport_lib::MARKET_PRICE_DECIMAL_MAX_SCALE, 18); + let price = match ksp_offchain_transport_lib::MarketPriceDecimal::parse("201.2500") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; assert_eq!(price.to_canonical_string(), "201.25"); - let id = match ksp_offchain_transport_lib::PriceProviderId::new("provider-canary") { + let id = match ksp_offchain_transport_lib::MarketPriceProviderId::new("provider-canary") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - let common = ksp_offchain_transport_lib::PriceProviderCommonSettings::new(id.clone(), true); + let common = ksp_offchain_transport_lib::MarketPriceProviderCommonSettings::new(id.clone(), true); assert_eq!(common.provider_id(), &id); - let rate_limit = match ksp_offchain_transport_lib::PriceProviderRateLimit::fixed( + let rate_limit = match ksp_offchain_transport_lib::MarketPriceProviderRateLimit::fixed( 1, 1, std::option::Option::None, - ksp_offchain_transport_lib::PriceProviderRateLimitScope::Ip, + ksp_offchain_transport_lib::MarketPriceProviderRateLimitScope::Ip, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - let descriptor = match ksp_offchain_transport_lib::PriceProviderDescriptor::new( + let descriptor = match ksp_offchain_transport_lib::MarketPriceProviderDescriptor::new( id.clone(), "Provider Canary", - ksp_offchain_transport_lib::PriceSemantics::AggregatedMarket, - ksp_offchain_transport_lib::PriceProviderAuthMode::None, + ksp_offchain_transport_lib::MarketPriceSemantics::AggregatedMarket, + ksp_offchain_transport_lib::MarketPriceProviderAuthMode::None, rate_limit, std::option::Option::None, true, @@ -44,15 +44,15 @@ fn public_pre_002_price_foundation_is_available_from_crate_root() -> ksp_core_li std::result::Result::Err(error) => return std::result::Result::Err(error), }; assert!(descriptor.supports_sol_usd()); - let provenance = match ksp_offchain_transport_lib::PriceProvenance::new("canary") { + let provenance = match ksp_offchain_transport_lib::MarketPriceProvenance::new("canary") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - let timestamp = ksp_offchain_transport_lib::PriceTimestamp::from_unix_millis(1); - let observation = match ksp_offchain_transport_lib::SolUsdPriceObservation::new( + let timestamp = ksp_offchain_transport_lib::MarketPriceTimestamp::from_unix_millis(1); + let observation = match ksp_offchain_transport_lib::MarketPriceObservation::new( id, price, - ksp_offchain_transport_lib::PriceSemantics::AggregatedMarket, + ksp_offchain_transport_lib::MarketPriceSemantics::AggregatedMarket, timestamp, timestamp, std::option::Option::None, @@ -68,11 +68,11 @@ fn public_pre_002_price_foundation_is_available_from_crate_root() -> ksp_core_li #[test] fn offchain_error_codes_use_owned_domain() { let codes = [ - ksp_offchain_transport_lib::ERROR_CODE_PRICE_DECIMAL_INVALID, - ksp_offchain_transport_lib::ERROR_CODE_PROVIDER_DESCRIPTOR_INVALID, - ksp_offchain_transport_lib::ERROR_CODE_PROVIDER_ID_INVALID, - ksp_offchain_transport_lib::ERROR_CODE_PROVIDER_OBSERVATION_INVALID, - ksp_offchain_transport_lib::ERROR_CODE_PROVIDER_SETTINGS_INVALID, + ksp_offchain_transport_lib::ERROR_CODE_MARKET_PRICE_DECIMAL_INVALID, + ksp_offchain_transport_lib::ERROR_CODE_MARKET_PRICE_PROVIDER_DESCRIPTOR_INVALID, + ksp_offchain_transport_lib::ERROR_CODE_MARKET_PRICE_PROVIDER_ID_INVALID, + ksp_offchain_transport_lib::ERROR_CODE_MARKET_PRICE_OBSERVATION_INVALID, + ksp_offchain_transport_lib::ERROR_CODE_MARKET_PRICE_PROVIDER_SETTINGS_INVALID, ]; for code in codes { assert_eq!(code.domain(), "offchain_transport"); diff --git a/crates/ksp-offchain-transport-lib/unit_tests/market_price_decimal.rs b/crates/ksp-offchain-transport-lib/unit_tests/market_price_decimal.rs index ad7d34c..063e4a8 100644 --- a/crates/ksp-offchain-transport-lib/unit_tests/market_price_decimal.rs +++ b/crates/ksp-offchain-transport-lib/unit_tests/market_price_decimal.rs @@ -1,5 +1,5 @@ // file: crates/ksp-offchain-transport-lib/unit_tests/market_price_decimal.rs -// version: 2 +// version: 3 #[test] fn decimal_normalizes_fractional_and_scientific_forms_without_f64() -> ksp_core_lib::Result<()> { @@ -11,7 +11,7 @@ fn decimal_normalizes_fractional_and_scientific_forms_without_f64() -> ksp_core_ ("1e-3", "0.001", 1, 3), ]; for (source, expected, coefficient, scale) in cases { - let value = match crate::PriceDecimal::parse(source) { + let value = match crate::MarketPriceDecimal::parse(source) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; @@ -26,34 +26,34 @@ fn decimal_normalizes_fractional_and_scientific_forms_without_f64() -> ksp_core_ fn decimal_rejects_zero_negative_nonfinite_excessive_scale_and_overflow() { let invalid = ["0", "0.000", "-1", "+1", "NaN", "inf", "1e-19", "1e129", "340282366920938463463374607431768211456", " 1", "1 ", ".1", "1."]; for source in invalid { - let result = crate::PriceDecimal::parse(source); + let result = crate::MarketPriceDecimal::parse(source); assert!(result.is_err()); if let std::result::Result::Err(error) = result { - assert_eq!(error.code(), crate::ERROR_CODE_PRICE_DECIMAL_INVALID); + assert_eq!(error.code(), crate::ERROR_CODE_MARKET_PRICE_DECIMAL_INVALID); } } } #[test] fn decimal_serde_is_canonical_string_and_round_trips_exactly() -> ksp_core_lib::Result<()> { - let value = match crate::PriceDecimal::parse("123.4500") { + let value = match crate::MarketPriceDecimal::parse("123.4500") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let encoded = match serde_json::to_string(&value) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { - return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_PRICE_DECIMAL_INVALID, "test serialization failed")); + return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_MARKET_PRICE_DECIMAL_INVALID, "test serialization failed")); }, }; assert_eq!(encoded, "\"123.45\""); - let decoded: crate::PriceDecimal = match serde_json::from_str(encoded.as_str()) { + let decoded: crate::MarketPriceDecimal = match serde_json::from_str(encoded.as_str()) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { - return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_PRICE_DECIMAL_INVALID, "test deserialization failed")); + return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_MARKET_PRICE_DECIMAL_INVALID, "test deserialization failed")); }, }; assert_eq!(decoded, value); - assert!(serde_json::from_str::("123.45").is_err()); + assert!(serde_json::from_str::("123.45").is_err()); return std::result::Result::Ok(()); } diff --git a/crates/ksp-offchain-transport-lib/unit_tests/market_price_observation.rs b/crates/ksp-offchain-transport-lib/unit_tests/market_price_observation.rs index 927ac14..1c5eac0 100644 --- a/crates/ksp-offchain-transport-lib/unit_tests/market_price_observation.rs +++ b/crates/ksp-offchain-transport-lib/unit_tests/market_price_observation.rs @@ -1,26 +1,26 @@ // file: crates/ksp-offchain-transport-lib/unit_tests/market_price_observation.rs -// version: 2 +// version: 3 #[test] fn observation_preserves_pair_exact_price_semantics_timestamps_and_safe_provenance() -> ksp_core_lib::Result<()> { - let provider_id = match crate::PriceProviderId::new("kraken") { + let provider_id = match crate::MarketPriceProviderId::new("kraken") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - let price = match crate::PriceDecimal::parse("204.125") { + let price = match crate::MarketPriceDecimal::parse("204.125") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - let provenance = match crate::PriceProvenance::new("market=SOL/USD") { + let provenance = match crate::MarketPriceProvenance::new("market=SOL/USD") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - let started = crate::PriceTimestamp::from_unix_millis(10_000); - let received = crate::PriceTimestamp::from_unix_millis(10_250); - let observation = match crate::SolUsdPriceObservation::new( + let started = crate::MarketPriceTimestamp::from_unix_millis(10_000); + let received = crate::MarketPriceTimestamp::from_unix_millis(10_250); + let observation = match crate::MarketPriceObservation::new( provider_id, price, - crate::PriceSemantics::ExchangeLastTrade, + crate::MarketPriceSemantics::ExchangeLastTrade, started, received, std::option::Option::None, @@ -29,10 +29,10 @@ fn observation_preserves_pair_exact_price_semantics_timestamps_and_safe_provenan std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - assert_eq!(observation.pair(), crate::PricePair::SolUsd); + assert_eq!(observation.pair(), crate::MarketPricePair::SolUsd); assert_eq!(observation.pair().code(), "SOL/USD"); assert_eq!(observation.price(), price); - assert_eq!(observation.semantics(), crate::PriceSemantics::ExchangeLastTrade); + assert_eq!(observation.semantics(), crate::MarketPriceSemantics::ExchangeLastTrade); assert_eq!(observation.request_started_at(), started); assert_eq!(observation.received_at(), received); assert_eq!(observation.provider_timestamp(), std::option::Option::None); @@ -42,25 +42,25 @@ fn observation_preserves_pair_exact_price_semantics_timestamps_and_safe_provenan #[test] fn observation_rejects_reversed_ksp_timestamps_and_unsafe_provenance() -> ksp_core_lib::Result<()> { - assert!(crate::PriceProvenance::new("line\nbreak").is_err()); - let provider_id = match crate::PriceProviderId::new("coingecko") { + assert!(crate::MarketPriceProvenance::new("line\nbreak").is_err()); + let provider_id = match crate::MarketPriceProviderId::new("coingecko") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - let price = match crate::PriceDecimal::parse("200") { + let price = match crate::MarketPriceDecimal::parse("200") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - let provenance = match crate::PriceProvenance::new("asset=solana") { + let provenance = match crate::MarketPriceProvenance::new("asset=solana") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - let result = crate::SolUsdPriceObservation::new( + let result = crate::MarketPriceObservation::new( provider_id, price, - crate::PriceSemantics::AggregatedMarket, - crate::PriceTimestamp::from_unix_millis(2), - crate::PriceTimestamp::from_unix_millis(1), + crate::MarketPriceSemantics::AggregatedMarket, + crate::MarketPriceTimestamp::from_unix_millis(2), + crate::MarketPriceTimestamp::from_unix_millis(1), std::option::Option::None, provenance, ); diff --git a/crates/ksp-offchain-transport-lib/unit_tests/market_price_provider.rs b/crates/ksp-offchain-transport-lib/unit_tests/market_price_provider.rs index 1f9a076..dec34f7 100644 --- a/crates/ksp-offchain-transport-lib/unit_tests/market_price_provider.rs +++ b/crates/ksp-offchain-transport-lib/unit_tests/market_price_provider.rs @@ -1,39 +1,43 @@ // file: crates/ksp-offchain-transport-lib/unit_tests/market_price_provider.rs -// version: 2 +// version: 3 #[test] fn provider_id_is_opaque_bounded_and_stable() -> ksp_core_lib::Result<()> { - let id = match crate::PriceProviderId::new("coingecko-main") { + let id = match crate::MarketPriceProviderId::new("coingecko-main") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; assert_eq!(id.as_str(), "coingecko-main"); assert_eq!(id.to_string(), "coingecko-main"); for invalid in ["", "CoinGecko", "coin gecko", "coin/gecko", "é"] { - assert!(crate::PriceProviderId::new(invalid).is_err()); + assert!(crate::MarketPriceProviderId::new(invalid).is_err()); } return std::result::Result::Ok(()); } #[test] fn provider_descriptor_preserves_semantics_auth_limits_and_informational_quota() -> ksp_core_lib::Result<()> { - let id = match crate::PriceProviderId::new("provider-a") { + let id = match crate::MarketPriceProviderId::new("provider-a") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - let rate_limit = match crate::PriceProviderRateLimit::fixed(1, 2, std::option::Option::None, crate::PriceProviderRateLimitScope::Ip) { + let rate_limit = match crate::MarketPriceProviderRateLimit::fixed(1, 2, std::option::Option::None, crate::MarketPriceProviderRateLimitScope::Ip) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - let quota = match crate::PriceProviderLongTermQuota::new(10_000, crate::PriceProviderQuotaPeriod::Month, crate::PriceProviderQuotaUnit::Credits) { + let quota = match crate::MarketPriceProviderLongTermQuota::new( + 10_000, + crate::MarketPriceProviderQuotaPeriod::Month, + crate::MarketPriceProviderQuotaUnit::Credits, + ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - let descriptor = match crate::PriceProviderDescriptor::new( + let descriptor = match crate::MarketPriceProviderDescriptor::new( id, "Provider A", - crate::PriceSemantics::AggregatedMarket, - crate::PriceProviderAuthMode::OptionalApiKey, + crate::MarketPriceSemantics::AggregatedMarket, + crate::MarketPriceProviderAuthMode::OptionalApiKey, rate_limit, std::option::Option::Some(quota), true, @@ -42,8 +46,8 @@ fn provider_descriptor_preserves_semantics_auth_limits_and_informational_quota() std::result::Result::Err(error) => return std::result::Result::Err(error), }; assert_eq!(descriptor.display_name(), "Provider A"); - assert_eq!(descriptor.semantics(), crate::PriceSemantics::AggregatedMarket); - assert_eq!(descriptor.auth_mode(), crate::PriceProviderAuthMode::OptionalApiKey); + assert_eq!(descriptor.semantics(), crate::MarketPriceSemantics::AggregatedMarket); + assert_eq!(descriptor.auth_mode(), crate::MarketPriceProviderAuthMode::OptionalApiKey); assert_eq!(descriptor.rate_limit(), rate_limit); assert_eq!(descriptor.long_term_quota(), std::option::Option::Some(quota)); assert!(descriptor.supports_sol_usd()); @@ -52,12 +56,12 @@ fn provider_descriptor_preserves_semantics_auth_limits_and_informational_quota() #[test] fn provider_limit_descriptors_reject_zero_and_model_dynamic_scope() { - assert!(crate::PriceProviderRateLimit::fixed(0, 1, std::option::Option::None, crate::PriceProviderRateLimitScope::Ip).is_err()); - assert!(crate::PriceProviderRateLimit::fixed(1, 0, std::option::Option::None, crate::PriceProviderRateLimitScope::Ip).is_err()); - assert!(crate::PriceProviderRateLimit::fixed(1, 1, std::option::Option::Some(0), crate::PriceProviderRateLimitScope::Ip).is_err()); - let dynamic = crate::PriceProviderRateLimit::dynamic(crate::PriceProviderRateLimitScope::Ip); - assert_eq!(dynamic.kind(), crate::PriceProviderRateLimitKind::Dynamic); - assert_eq!(dynamic.scope(), crate::PriceProviderRateLimitScope::Ip); + assert!(crate::MarketPriceProviderRateLimit::fixed(0, 1, std::option::Option::None, crate::MarketPriceProviderRateLimitScope::Ip).is_err()); + assert!(crate::MarketPriceProviderRateLimit::fixed(1, 0, std::option::Option::None, crate::MarketPriceProviderRateLimitScope::Ip).is_err()); + assert!(crate::MarketPriceProviderRateLimit::fixed(1, 1, std::option::Option::Some(0), crate::MarketPriceProviderRateLimitScope::Ip).is_err()); + let dynamic = crate::MarketPriceProviderRateLimit::dynamic(crate::MarketPriceProviderRateLimitScope::Ip); + assert_eq!(dynamic.kind(), crate::MarketPriceProviderRateLimitKind::Dynamic); + assert_eq!(dynamic.scope(), crate::MarketPriceProviderRateLimitScope::Ip); assert_eq!(dynamic.requests(), std::option::Option::None); assert_eq!(dynamic.window_seconds(), std::option::Option::None); assert_eq!(dynamic.burst(), std::option::Option::None); @@ -65,13 +69,13 @@ fn provider_limit_descriptors_reject_zero_and_model_dynamic_scope() { #[test] fn provider_availability_keeps_cooldown_and_outage_distinct() -> ksp_core_lib::Result<()> { - let id = match crate::PriceProviderId::new("jupiter") { + let id = match crate::MarketPriceProviderId::new("jupiter") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - let retry_at = crate::PriceTimestamp::from_unix_millis(1_777_777_777_000); - let state = crate::PriceProviderState::new(id, crate::PriceProviderAvailability::CoolingDown { retry_at }); - assert_eq!(state.availability(), crate::PriceProviderAvailability::CoolingDown { retry_at }); - assert_ne!(state.availability(), crate::PriceProviderAvailability::TemporarilyUnavailable { retry_at: std::option::Option::Some(retry_at) }); + let retry_at = crate::MarketPriceTimestamp::from_unix_millis(1_777_777_777_000); + let state = crate::MarketPriceProviderState::new(id, crate::MarketPriceProviderAvailability::CoolingDown { retry_at }); + assert_eq!(state.availability(), crate::MarketPriceProviderAvailability::CoolingDown { retry_at }); + assert_ne!(state.availability(), crate::MarketPriceProviderAvailability::TemporarilyUnavailable { retry_at: std::option::Option::Some(retry_at) }); return std::result::Result::Ok(()); } diff --git a/crates/ksp-offchain-transport-lib/unit_tests/market_price_settings.rs b/crates/ksp-offchain-transport-lib/unit_tests/market_price_settings.rs index f8587e4..499fd3e 100644 --- a/crates/ksp-offchain-transport-lib/unit_tests/market_price_settings.rs +++ b/crates/ksp-offchain-transport-lib/unit_tests/market_price_settings.rs @@ -1,19 +1,19 @@ // file: crates/ksp-offchain-transport-lib/unit_tests/market_price_settings.rs -// version: 2 +// version: 3 #[test] fn common_settings_contain_only_generic_identity_and_enablement() -> ksp_core_lib::Result<()> { - let provider_id = match crate::PriceProviderId::new("coinpaprika") { + let provider_id = match crate::MarketPriceProviderId::new("coinpaprika") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - let settings = crate::PriceProviderCommonSettings::new(provider_id, true); + let settings = crate::MarketPriceProviderCommonSettings::new(provider_id, true); assert!(settings.enabled()); assert_eq!(settings.provider_id().as_str(), "coinpaprika"); let serialized = match serde_json::to_value(&settings) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { - return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_PROVIDER_SETTINGS_INVALID, "test serialization failed")); + return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_MARKET_PRICE_PROVIDER_SETTINGS_INVALID, "test serialization failed")); }, }; assert!(serialized.get("api_key").is_none()); diff --git a/deltas/0.2.11/pre.002-fix.003.md b/deltas/0.2.11/pre.002-fix.003.md new file mode 100644 index 0000000..0fb068b --- /dev/null +++ b/deltas/0.2.11/pre.002-fix.003.md @@ -0,0 +1,185 @@ + + + +# Delta `0.2.11-pre.002-fix.003` — nommage public `MarketPrice*` + +## 1. Base requise + +Ce correctif s'applique exclusivement après : + +```text +v0.2.10 ++ 0.2.11-pre.001 ++ 0.2.11-pre.001-fix.001 ++ 0.2.11-pre.002 ++ 0.2.11-pre.002-fix.001 ++ 0.2.11-pre.002-fix.002 +``` + +La version Cargo attendue avant application est : + +```text +0.2.11-pre.2.fix.2 +``` + +## 2. Motif du correctif + +`pre.002-fix.001` a correctement classé la première capacité de `ksp-offchain-transport-lib` sous les modules privés `market_price_*`, mais sa façade crate-root conservait des noms publics génériques `Price*`. + +Cette asymétrie créerait une dette publique dès l'apparition d'autres familles hétérogènes, notamment : + +```text +swap_quote_* +metadata_* +autres données off-chain futures +``` + +Des noms tels que `PriceProviderId`, `PriceProviderState` ou `PriceTimestamp` pourraient alors être interprétés comme des abstractions crate-wide alors qu'ils ne sont actuellement prouvés que pour la famille `market_price`. + +Le correctif applique donc la même frontière de responsabilité aux modules privés et à la façade publique avant l'introduction du runtime HTTP de `pre.003`. + +## 3. Convention publique retenue + +Toute API spécifique à la famille `market_price` porte désormais le préfixe : + +```text +MarketPrice* +``` + +et toute constante correspondante : + +```text +MARKET_PRICE_* +ERROR_CODE_MARKET_PRICE_* +``` + +Exemples principaux : + +```text +PriceDecimal -> MarketPriceDecimal +SolUsdPriceObservation -> MarketPriceObservation +PriceTimestamp -> MarketPriceTimestamp +PriceProvenance -> MarketPriceProvenance +PricePair -> MarketPricePair +PriceSemantics -> MarketPriceSemantics +PriceProviderId -> MarketPriceProviderId +PriceProviderDescriptor -> MarketPriceProviderDescriptor +PriceProviderRateLimit -> MarketPriceProviderRateLimit +PriceProviderAvailability -> MarketPriceProviderAvailability +PriceProviderState -> MarketPriceProviderState +PriceProviderCommonSettings -> MarketPriceProviderCommonSettings +``` + +Les enums de rate-limit/quota/auth suivent la même règle. + +Aucun alias public `Price*` n'est conservé. La release est encore en prerelease et conserver ces aliases figerait précisément l'ambiguïté que ce correctif doit supprimer. + +## 4. Observation V1 + +`SolUsdPriceObservation` devient `MarketPriceObservation` plutôt que `MarketPriceSolUsdObservation`. + +La paire réellement supportée reste portée explicitement par : + +```text +MarketPricePair::SolUsd +``` + +Cette forme évite un nouveau renommage du type observation si une version ultérieure étend proprement la famille `market_price` à d'autres paires, tout en conservant le gate V1 strictement limité à SOL/USD. + +## 5. Codes d'erreur + +Les identifiants publics et les codes textuels spécifiques à cette famille sont également namespacés : + +```text +market_price_decimal_invalid +market_price_provider_descriptor_invalid +market_price_provider_id_invalid +market_price_observation_invalid +market_price_provider_settings_invalid +``` + +Le domaine `ksp_core_lib::ErrorCode` reste : + +```text +offchain_transport +``` + +Une future famille peut ainsi ajouter ses propres codes sans collision sémantique avec `market_price`. + +## 6. Version technique + +Le correctif modifie du Rust public. `VER-ID-007` et `VER-ID-010` imposent donc : + +```text +workspace.package.version = 0.2.11-pre.2.fix.3 +``` + +## 7. Validation opérateur acquise avant ce fix + +L'état `0.2.11-pre.002-fix.002` a été validé par l'opérateur le `2026-08-25` avec : + +```text +cargo fmt --all exécuté +python3 scripts/audit_rust_workspace_rules.py clean +python3 scripts/audit_markdown_tables.py clean, 116 tables / 104 files +cargo check --workspace PASS +cargo test -p ksp-offchain-transport-lib PASS, 10 unit + 1 boundary + 2 public API +cargo clippy --workspace --all-targets PASS +``` + +`cargo test --workspace` n'a pas été rejoué après `pre.002-fix.002` et n'est donc pas déclaré PASS pour cet état exact. + +## 8. Validations demandées après application + +```bash +cargo fmt --all +python3 scripts/audit_rust_workspace_rules.py +python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.2.11 +cargo check --workspace +cargo clippy --workspace --all-targets +cargo test -p ksp-offchain-transport-lib +cargo test --workspace +``` + +Le canari public doit notamment compiler exclusivement avec les noms `MarketPrice*` et ne plus trouver de surface publique `Price*` historique. + +## 9. Scope inchangé + +Ce correctif reste strictement rattaché à la fondation `pre.002` : + +```text +aucun reqwest +aucun client HTTP +aucun limiter actif +aucun adapter provider +aucun credential provider +aucune nouvelle paire +aucune quote Jupiter +``` + +`pre.003` reste propriétaire du premier runtime HTTP commun. + +## 10. Fichiers modifiés + +```text +Cargo.toml +crates/ksp-offchain-transport-lib/src/error.rs +crates/ksp-offchain-transport-lib/src/lib.rs +crates/ksp-offchain-transport-lib/src/market_price_decimal.rs +crates/ksp-offchain-transport-lib/src/market_price_observation.rs +crates/ksp-offchain-transport-lib/src/market_price_provider.rs +crates/ksp-offchain-transport-lib/src/market_price_settings.rs +crates/ksp-offchain-transport-lib/tests/public_api.rs +crates/ksp-offchain-transport-lib/unit_tests/market_price_decimal.rs +crates/ksp-offchain-transport-lib/unit_tests/market_price_observation.rs +crates/ksp-offchain-transport-lib/unit_tests/market_price_provider.rs +crates/ksp-offchain-transport-lib/unit_tests/market_price_settings.rs +docs/plans/018-V0_2_11_OFFCHAIN_PRICE_TRANSPORT_PLAN.md +docs/validation/014-V0_2_11_OFFCHAIN_PRICE_TRANSPORT.md +``` + +## 11. Fichier ajouté + +```text +deltas/0.2.11/pre.002-fix.003.md +``` diff --git a/docs/plans/018-V0_2_11_OFFCHAIN_PRICE_TRANSPORT_PLAN.md b/docs/plans/018-V0_2_11_OFFCHAIN_PRICE_TRANSPORT_PLAN.md index 2ce7727..af3e22a 100644 --- a/docs/plans/018-V0_2_11_OFFCHAIN_PRICE_TRANSPORT_PLAN.md +++ b/docs/plans/018-V0_2_11_OFFCHAIN_PRICE_TRANSPORT_PLAN.md @@ -1,9 +1,9 @@ - + # Plan `0.2.11` — Off-chain price transport SOL/USD multi-provider -**Statut courant : `0.2.11-pre.002-fix.002` ferme le dernier écart Clippy de la fondation avant `pre.003`. Les rustdocs, la façade Logging KSP et la taxonomie interne `market_price_*` restent acquises ; le contrat V1 reste limité à SOL/USD et aucun client HTTP/provider n'est encore ajouté.** +**Statut courant : `0.2.11-pre.002-fix.003` ferme le nommage public de la famille `market_price` avant `pre.003`. Les types, constantes et codes d'erreur publics spécifiques à cette famille portent désormais le préfixe `MarketPrice` / `MARKET_PRICE`, afin d'éviter une collision future avec `swap_quote_*` ou une autre capacité off-chain. Le contrat V1 reste limité à SOL/USD et aucun client HTTP/provider n'est encore ajouté.** ## 1. Base et autorité @@ -110,6 +110,8 @@ swap_quote_* réservé aux futures quotes montant/route, uniq Les fichiers génériques `decimal.rs`, `observation.rs`, `provider.rs` et `settings.rs` introduits initialement par `pre.002` sont donc corrigés avant toute extension runtime : la fonctionnalité actuelle est préfixée `market_price_*`. Les futurs domaines ne doivent pas être ajoutés dans ces modules uniquement parce qu'ils utilisent eux aussi un provider HTTP. +La même frontière s'applique à la **façade publique**. À partir de `pre.002-fix.003`, tout symbole public spécifique à cette famille porte un nom explicitement `MarketPrice*` ou `MARKET_PRICE_*` : `MarketPriceDecimal`, `MarketPriceObservation`, `MarketPriceProviderId`, `MarketPriceProviderDescriptor`, `MarketPriceProviderRateLimit`, etc. Aucun alias public `Price*` n'est conservé pendant cette prerelease, car il figerait précisément l'ambiguïté que le correctif supprime. Une future famille `swap_quote_*` devra employer son propre préfixe public `SwapQuote*`; une abstraction sans préfixe de famille ne pourra être créée que lorsqu'un invariant réellement partagé aura été démontré. + La règle est **responsabilité avant taille** : un module commun n'est promu au niveau crate-wide que lorsqu'au moins deux familles réelles partagent le même invariant. Ainsi, le HTTP, le scheduling ou une identité provider pourront être factorisés plus tard si leur partage est effectivement démontré ; aucune abstraction générique n'est créée aujourd'hui uniquement pour anticiper Jupiter Quotes. ### 3.2 Lecture, écriture et verbes HTTP @@ -339,7 +341,7 @@ Le choix d'une paire explicite évite d'introduire silencieusement une politique Le gate rejette `f64` comme représentation canonique publique du prix. -Décision matérialisée en `pre.002` : `PriceDecimal` est le type décimal KSP borné, sérialisable sans perte et construit depuis la représentation textuelle du provider. +Décision matérialisée en `pre.002` : `MarketPriceDecimal` est le type décimal KSP borné, sérialisable sans perte et construit depuis la représentation textuelle du provider. Contrat V1 exact : @@ -358,7 +360,7 @@ serialization canonique décimale en chaîne Le wire d'un provider peut être JSON number ou string. L'adapter le convertit vers le type KSP sans utiliser `as_f64()` comme vérité canonique. -`PriceDecimal` sérialise toujours sa valeur canonique sous forme de chaîne décimale non scientifique. Le zéro est invalide pour ce type puisqu'il représente exclusivement un prix réussi ; l'absence de prix reste donc hors de la valeur numérique elle-même. +`MarketPriceDecimal` sérialise toujours sa valeur canonique sous forme de chaîne décimale non scientifique. Le zéro est invalide pour ce type puisqu'il représente exclusivement un prix réussi ; l'absence de prix reste donc hors de la valeur numérique elle-même. Cette solution évite d'ajouter une crate decimal uniquement pour V1 et prépare le passage frontend sans perte IEEE-754. @@ -376,7 +378,7 @@ instant provider optionnel lorsqu'il existe réellement provenance provider sûre et bornée ``` -`pre.002` matérialise ce contrat avec `SolUsdPriceObservation`, `PriceTimestamp` (millisecondes UTC depuis Unix epoch) et `PriceProvenance` bornée à 256 octets sans caractères de contrôle. Le constructeur d'observation rejette un instant de réception antérieur au départ de requête. +`pre.002` matérialise ce contrat avec `MarketPriceObservation`, `MarketPriceTimestamp` (millisecondes UTC depuis Unix epoch) et `MarketPriceProvenance` bornée à 256 octets sans caractères de contrôle. Le constructeur d'observation rejette un instant de réception antérieur au départ de requête. Règles : @@ -410,7 +412,7 @@ limitation générique support SOL/USD ``` -`pre.002` fixe `PriceProviderId`, `PriceProviderDescriptor`, `PriceProviderAuthMode`, `PriceProviderRateLimit`, `PriceProviderLongTermQuota` et leurs enums de scope/période/unité. Les limites fixes exigent un budget et une fenêtre non nuls ; les limites dynamiques restent explicitement distinctes. Les quotas longs termes sont descriptifs et ne deviennent jamais un compteur local de quota restant. +`pre.002` fixe `MarketPriceProviderId`, `MarketPriceProviderDescriptor`, `MarketPriceProviderAuthMode`, `MarketPriceProviderRateLimit`, `MarketPriceProviderLongTermQuota` et leurs enums de scope/période/unité. Les limites fixes exigent un budget et une fenêtre non nuls ; les limites dynamiques restent explicitement distinctes. Les quotas longs termes sont descriptifs et ne deviennent jamais un compteur local de quota restant. L'état runtime peut représenter au minimum : @@ -424,7 +426,7 @@ misconfigured disabled ``` -`PriceProviderAvailability` et `PriceProviderState` matérialisent cette projection générique dès `pre.002`; le registry et les transitions runtime effectives restent prévus en `pre.007`. +`MarketPriceProviderAvailability` et `MarketPriceProviderState` matérialisent cette projection générique dès `pre.002`; le registry et les transitions runtime effectives restent prévus en `pre.007`. Les noms Rust de cette fondation sont désormais matérialisés ; aucun consumer ne doit parser des strings d'erreur pour connaître cet état. @@ -662,7 +664,7 @@ Remplacement du forecast tabulaire par des sous-sections éditables, clarificati **Statut : réalisé.** -Création de la crate et de sa façade publique initiale : `PriceDecimal`, paire SOL/USD, observation/provenance/timestamps, identifiants/descriptors provider, capacités auth/rate-limit/quota, settings communs minimaux et états d'availability provider-neutral. Aucun HTTP, limiter actif, credential provider ou adapter wire n'est avancé depuis `pre.003+`. +Création de la crate et de sa façade publique initiale : `MarketPriceDecimal`, paire SOL/USD, observation/provenance/timestamps, identifiants/descriptors provider, capacités auth/rate-limit/quota, settings communs minimaux et états d'availability provider-neutral. Aucun HTTP, limiter actif, credential provider ou adapter wire n'est avancé depuis `pre.003+`. #### `pre.002-fix.001` — Rustdoc, Logging et taxonomie Off-chain @@ -676,6 +678,12 @@ Correction des deux warnings `missing_docs` des tests d'intégration, ajout de ` Correction du canari `dependency_boundary` pour respecter `-D clippy::implicit-return`, sans changement de contrat ni de comportement runtime. La version technique workspace devient `0.2.11-pre.2.fix.2`. +#### `pre.002-fix.003` — Nommage public `MarketPrice*` + +**Statut : réalisé.** + +Préfixage de toute la surface publique spécifique à la famille `market_price` en `MarketPrice*` / `MARKET_PRICE_*`, y compris observation, décimal, timestamps, provenance, provider settings/descriptors/limits/availability et codes d'erreur. Aucun alias `Price*` n'est conservé : le correctif évite de devoir renommer ces API lorsque d'autres familles telles que `swap_quote_*` seront ajoutées. La version technique workspace devient `0.2.11-pre.2.fix.3`. + ### `pre.003` — HTTP REST commun et rate limiting **Statut : planifié.** diff --git a/docs/validation/014-V0_2_11_OFFCHAIN_PRICE_TRANSPORT.md b/docs/validation/014-V0_2_11_OFFCHAIN_PRICE_TRANSPORT.md index 96a92c9..9fa781a 100644 --- a/docs/validation/014-V0_2_11_OFFCHAIN_PRICE_TRANSPORT.md +++ b/docs/validation/014-V0_2_11_OFFCHAIN_PRICE_TRANSPORT.md @@ -1,5 +1,5 @@ - + # Validation `0.2.11` — Off-chain price transport SOL/USD @@ -46,19 +46,19 @@ Les statuts `PLANNED` restent non validés tant que la tranche correspondante n' ## 3. Gate `0.2.11-pre.002` -| Critère | Statut | Preuve | -|----------------------------------------------------|--------|----------------------------------------------------| -| crate `ksp-offchain-transport-lib` créée | PASS | membre workspace + package dédié | -| version workspace synchronisée sur `0.2.11-pre.2` | PASS | `Cargo.toml` racine | -| dépendance inverse vers Config absente | PASS | manifest crate | -| `reqwest` et SDK providers absents de la fondation | PASS | manifest crate | -| `PriceDecimal` exact sans canon `f64` | PASS | coefficient `u128`, scale 18, sérialisation string | -| SOL/USD seule paire publique V1 | PASS | `PricePair::SolUsd` + `SolUsdPriceObservation` | -| identifiant/descripteur provider opaques | PASS | `PriceProviderId` + `PriceProviderDescriptor` | -| auth/rate-limit/quota représentables génériquement | PASS | types provider-neutral dédiés | -| settings communs sans faux endpoint/API key | PASS | `PriceProviderCommonSettings` | -| availability provider-neutral représentable | PASS | `PriceProviderAvailability` + `PriceProviderState` | -| HTTP actif ou adapter provider ajouté | N/A | explicitement réservé à `pre.003+` | +| Critère | Statut | Preuve | +|----------------------------------------------------|--------|----------------------------------------------------------------| +| crate `ksp-offchain-transport-lib` créée | PASS | membre workspace + package dédié | +| version workspace synchronisée sur `0.2.11-pre.2` | PASS | `Cargo.toml` racine | +| dépendance inverse vers Config absente | PASS | manifest crate | +| `reqwest` et SDK providers absents de la fondation | PASS | manifest crate | +| `MarketPriceDecimal` exact sans canon `f64` | PASS | coefficient `u128`, scale 18, sérialisation string | +| SOL/USD seule paire publique V1 | PASS | `MarketPricePair::SolUsd` + `MarketPriceObservation` | +| identifiant/descripteur provider opaques | PASS | `MarketPriceProviderId` + `MarketPriceProviderDescriptor` | +| auth/rate-limit/quota représentables génériquement | PASS | types provider-neutral dédiés | +| settings communs sans faux endpoint/API key | PASS | `MarketPriceProviderCommonSettings` | +| availability provider-neutral représentable | PASS | `MarketPriceProviderAvailability` + `MarketPriceProviderState` | +| HTTP actif ou adapter provider ajouté | N/A | explicitement réservé à `pre.003+` | ## 3.1 Taxonomie Off-chain et observabilité @@ -81,6 +81,28 @@ Les statuts `PLANNED` restent non validés tant que la tranche correspondante n' Le gate `cargo clippy --workspace --all-targets` de `pre.002-fix.001` a détecté un unique écart `clippy::implicit-return` dans le canari `tests/dependency_boundary.rs`. Le correctif ajoute le `return` explicite attendu dans la closure de `Iterator::any` et synchronise `workspace.package.version` sur `0.2.11-pre.2.fix.2`. Aucun contrat public, comportement runtime ou scope provider n'est modifié. +Les validations opérateur du `2026-08-25` sur `pre.002-fix.002` sont : `cargo fmt --all` PASS, audit Rust PASS, audit Markdown PASS, `cargo check --workspace` PASS, `cargo test -p ksp-offchain-transport-lib` PASS avec 13 tests exécutés, et `cargo clippy --workspace --all-targets` PASS. `cargo test --workspace` n'a pas été rejoué après ce fix et n'est donc pas déclaré PASS pour cet état exact. + +## 3.3 Correctif `0.2.11-pre.002-fix.003` + +Le nommage public initial `Price*` restait trop générique alors que les modules qui possèdent ces contrats sont explicitement la famille `market_price_*`. Le correctif synchronise `workspace.package.version` sur `0.2.11-pre.2.fix.3` et renomme sans alias de compatibilité toute la surface publique spécifique à cette famille en `MarketPrice*` / `MARKET_PRICE_*`. + +Le contrat attendu après application est : + +```text +MarketPriceDecimal +MarketPriceObservation +MarketPriceTimestamp +MarketPriceProvenance +MarketPricePair +MarketPriceSemantics +MarketPriceProvider* +MARKET_PRICE_* +ERROR_CODE_MARKET_PRICE_* +``` + +Cette correction reste dans la responsabilité de `pre.002` : elle stabilise la fondation et sa façade avant l'introduction du HTTP runtime en `pre.003`. + ## 4. Matrice provider prévue | Provider | SOL/USD V1 | Gratuit V1 | Mode auth prévu | Test déterministe | Smoke live | Statut courant |