From 2fa1deb90ac14576e06ac3035e5abbf50de34b51 Mon Sep 17 00:00:00 2001 From: Celestial Date: Sat, 11 Apr 2026 09:57:06 +0200 Subject: [PATCH 1/2] docs(runtime): add delivery-profile ADR and simplification ARD --- crates/sof-observer/src/app/config/base.rs | 48 ++- crates/sof-observer/src/runtime.rs | 54 +++ crates/sof-support/src/bench.rs | 21 + crates/sof-support/src/bytes.rs | 27 ++ crates/sof-support/src/collections_support.rs | 32 ++ crates/sof-support/src/env_support.rs | 11 + crates/sof-support/src/lib.rs | 339 +--------------- crates/sof-support/src/short_vec.rs | 119 ++++++ crates/sof-support/src/time_support.rs | 100 +++++ docs/architecture/README.md | 2 + .../adr/0013-runtime-delivery-profiles.md | 363 ++++++++++++++++++ ...ification-without-capability-regression.md | 223 +++++++++++ 12 files changed, 1010 insertions(+), 329 deletions(-) create mode 100644 crates/sof-support/src/bench.rs create mode 100644 crates/sof-support/src/bytes.rs create mode 100644 crates/sof-support/src/collections_support.rs create mode 100644 crates/sof-support/src/env_support.rs create mode 100644 crates/sof-support/src/short_vec.rs create mode 100644 crates/sof-support/src/time_support.rs create mode 100644 docs/architecture/adr/0013-runtime-delivery-profiles.md create mode 100644 docs/architecture/ard/0010-simplification-without-capability-regression.md diff --git a/crates/sof-observer/src/app/config/base.rs b/crates/sof-observer/src/app/config/base.rs index 55107f27..9ec3ed7d 100644 --- a/crates/sof-observer/src/app/config/base.rs +++ b/crates/sof-observer/src/app/config/base.rs @@ -3,7 +3,9 @@ use std::{num::NonZeroUsize, path::PathBuf}; use super::{read_bool_env, read_env_var}; use crate::{ framework::{DerivedStateReplayBackend, DerivedStateReplayDurability}, - runtime::{DerivedStateReplayConfig, DerivedStateRuntimeConfig, ShredTrustMode}, + runtime::{ + DerivedStateReplayConfig, DerivedStateRuntimeConfig, RuntimeDeliveryProfile, ShredTrustMode, + }, }; fn read_optional_bool_env(name: &str) -> Option { @@ -19,6 +21,13 @@ pub fn read_shred_trust_mode() -> ShredTrustMode { } } +pub fn read_runtime_delivery_profile() -> Option { + read_env_var("SOF_RUNTIME_DELIVERY_PROFILE") + .filter(|value| !value.trim().is_empty()) + .as_deref() + .and_then(RuntimeDeliveryProfile::from_config_value) +} + pub fn read_worker_threads() -> usize { read_env_var("SOF_WORKER_THREADS") .and_then(|value| value.parse::().ok()) @@ -360,3 +369,40 @@ pub fn read_derived_state_runtime_config() -> DerivedStateRuntimeConfig { }, } } + +#[cfg(test)] +mod tests { + use super::read_runtime_delivery_profile; + use crate::{ + runtime::RuntimeDeliveryProfile, runtime_env::with_runtime_env_overrides_for_test, + }; + + #[test] + fn runtime_delivery_profile_parses_known_values() { + with_runtime_env_overrides_for_test( + [( + "SOF_RUNTIME_DELIVERY_PROFILE".to_owned(), + "delivery_disciplined".to_owned(), + )], + || { + assert_eq!( + read_runtime_delivery_profile(), + Some(RuntimeDeliveryProfile::DeliveryDisciplined) + ); + }, + ); + } + + #[test] + fn runtime_delivery_profile_rejects_unknown_values() { + with_runtime_env_overrides_for_test( + [( + "SOF_RUNTIME_DELIVERY_PROFILE".to_owned(), + "unknown".to_owned(), + )], + || { + assert_eq!(read_runtime_delivery_profile(), None); + }, + ); + } +} diff --git a/crates/sof-observer/src/runtime.rs b/crates/sof-observer/src/runtime.rs index 8388768e..4283fad7 100644 --- a/crates/sof-observer/src/runtime.rs +++ b/crates/sof-observer/src/runtime.rs @@ -177,6 +177,41 @@ pub struct RuntimeObservabilityConfig { pub bind_addr: Option, } +/// Typed runtime-level downstream delivery bias. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub enum RuntimeDeliveryProfile { + /// Preserve current SOF behavior: tight bounded queues and freshness-first shedding. + #[default] + LatencyOptimized, + /// Keep ingest bounded while allowing more buffering and earlier pressure signaling. + Balanced, + /// Favor stronger downstream ordering and drain discipline on runtime-owned non-hot lanes. + DeliveryDisciplined, +} + +impl RuntimeDeliveryProfile { + /// Returns env-string representation used by `SOF_RUNTIME_DELIVERY_PROFILE`. + #[must_use] + pub const fn as_str(self) -> &'static str { + match self { + Self::LatencyOptimized => "latency_optimized", + Self::Balanced => "balanced", + Self::DeliveryDisciplined => "delivery_disciplined", + } + } + + /// Parses config/env string into one typed delivery profile. + #[must_use] + pub fn from_config_value(value: &str) -> Option { + match value { + "latency_optimized" | "latency-optimized" => Some(Self::LatencyOptimized), + "balanced" => Some(Self::Balanced), + "delivery_disciplined" | "delivery-disciplined" => Some(Self::DeliveryDisciplined), + _ => None, + } + } +} + /// Explicit trust posture for raw-shred ingest. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ShredTrustMode { @@ -382,6 +417,12 @@ impl RuntimeSetup { } } + /// Sets `SOF_RUNTIME_DELIVERY_PROFILE`. + #[must_use] + pub fn with_runtime_delivery_profile(self, profile: RuntimeDeliveryProfile) -> Self { + self.with_env("SOF_RUNTIME_DELIVERY_PROFILE", profile.as_str()) + } + /// Sets `SOF_GOSSIP_ENTRYPOINT` from a list of entrypoints. #[must_use] pub fn with_gossip_entrypoints(self, gossip_entrypoints: I) -> Self @@ -4530,6 +4571,19 @@ mod tests { ); } + #[test] + fn typed_runtime_delivery_profile_uses_expected_strings() { + let setup = + RuntimeSetup::new().with_runtime_delivery_profile(RuntimeDeliveryProfile::Balanced); + assert_eq!( + setup.env_overrides.last(), + Some(&( + String::from("SOF_RUNTIME_DELIVERY_PROFILE"), + String::from("balanced"), + )) + ); + } + #[test] fn disabled_observability_config_is_not_serialized() { let setup = diff --git a/crates/sof-support/src/bench.rs b/crates/sof-support/src/bench.rs new file mode 100644 index 00000000..1fa5d6ef --- /dev/null +++ b/crates/sof-support/src/bench.rs @@ -0,0 +1,21 @@ +use std::time::Duration; + +/// Reads a positive profiling iteration count from `SOF_PROFILE_ITERATIONS`. +#[must_use] +pub fn profile_iterations(default: usize) -> usize { + crate::env_support::read_positive_usize("SOF_PROFILE_ITERATIONS", default) +} + +/// Returns the average nanoseconds spent per iteration. +#[must_use] +pub fn avg_ns_per_iteration(elapsed: Duration, iterations: I) -> u128 +where + I: TryInto, +{ + let iterations = iterations + .try_into() + .ok() + .filter(|value| *value > 0) + .unwrap_or(1); + elapsed.as_nanos().checked_div(iterations).unwrap_or(0) +} diff --git a/crates/sof-support/src/bytes.rs b/crates/sof-support/src/bytes.rs new file mode 100644 index 00000000..50c2907b --- /dev/null +++ b/crates/sof-support/src/bytes.rs @@ -0,0 +1,27 @@ +use sof_types::{PubkeyBytes, SignatureBytes}; + +/// Converts one 64-byte signature slice into `SignatureBytes`. +/// +/// # Errors +/// +/// Returns the error produced by `on_error` when `bytes` is not exactly 64 bytes long. +pub fn signature_bytes_from_slice(bytes: &[u8], on_error: F) -> Result +where + F: FnOnce() -> E, +{ + let raw: [u8; 64] = bytes.try_into().map_err(|_error| on_error())?; + Ok(SignatureBytes::from(raw)) +} + +/// Converts one 32-byte pubkey slice into `PubkeyBytes`. +/// +/// # Errors +/// +/// Returns the error produced by `on_error` when `bytes` is not exactly 32 bytes long. +pub fn pubkey_bytes_from_slice(bytes: &[u8], on_error: F) -> Result +where + F: FnOnce() -> E, +{ + let raw: [u8; 32] = bytes.try_into().map_err(|_error| on_error())?; + Ok(PubkeyBytes::from(raw)) +} diff --git a/crates/sof-support/src/collections_support.rs b/crates/sof-support/src/collections_support.rs new file mode 100644 index 00000000..a4683d37 --- /dev/null +++ b/crates/sof-support/src/collections_support.rs @@ -0,0 +1,32 @@ +use std::collections::HashMap; + +/// Prunes one slot-keyed map down to the retained recent window once the threshold is crossed. +pub fn prune_recent_slots( + slot_states: &mut HashMap, + slot: u64, + retained_lag: u64, + prune_threshold: usize, +) { + if slot_states.len() <= prune_threshold { + return; + } + let slot_floor = slot.saturating_sub(retained_lag); + slot_states.retain(|tracked_slot, _| *tracked_slot >= slot_floor); +} + +#[cfg(test)] +mod tests { + use super::prune_recent_slots; + + #[test] + fn prune_recent_slots_drops_old_entries_after_threshold() { + let mut slot_states = (0_u64..10_u64).map(|slot| (slot, slot)).collect(); + + prune_recent_slots(&mut slot_states, 9, 3, 4); + + assert_eq!(slot_states.len(), 4); + assert!(!slot_states.contains_key(&5)); + assert!(slot_states.contains_key(&6)); + assert!(slot_states.contains_key(&9)); + } +} diff --git a/crates/sof-support/src/env_support.rs b/crates/sof-support/src/env_support.rs new file mode 100644 index 00000000..5a3c0c9d --- /dev/null +++ b/crates/sof-support/src/env_support.rs @@ -0,0 +1,11 @@ +use std::env; + +/// Reads one positive `usize` from an environment variable, or returns `default`. +#[must_use] +pub fn read_positive_usize(name: &str, default: usize) -> usize { + env::var(name) + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|value| *value > 0) + .unwrap_or(default) +} diff --git a/crates/sof-support/src/lib.rs b/crates/sof-support/src/lib.rs index 82e14880..679afcef 100644 --- a/crates/sof-support/src/lib.rs +++ b/crates/sof-support/src/lib.rs @@ -1,331 +1,14 @@ //! Shared internal support helpers for SOF workspace crates. -use std::{env, time::Duration}; - -use sof_types::{PubkeyBytes, SignatureBytes}; - -/// Benchmark helper utilities reused across SOF profiling fixtures. -pub mod bench { - use super::Duration; - - /// Reads a positive profiling iteration count from `SOF_PROFILE_ITERATIONS`. - #[must_use] - pub fn profile_iterations(default: usize) -> usize { - super::env_support::read_positive_usize("SOF_PROFILE_ITERATIONS", default) - } - - /// Returns the average nanoseconds spent per iteration. - #[must_use] - pub fn avg_ns_per_iteration(elapsed: Duration, iterations: I) -> u128 - where - I: TryInto, - { - let iterations = iterations - .try_into() - .ok() - .filter(|value| *value > 0) - .unwrap_or(1); - elapsed.as_nanos().checked_div(iterations).unwrap_or(0) - } -} - -/// Environment parsing helpers reused across profiling fixtures and tests. -pub mod env_support { - use super::env; - - /// Reads one positive `usize` from an environment variable, or returns `default`. - #[must_use] - pub fn read_positive_usize(name: &str, default: usize) -> usize { - env::var(name) - .ok() - .and_then(|value| value.parse::().ok()) - .filter(|value| *value > 0) - .unwrap_or(default) - } -} - -/// Collection helpers reused across runtime caches and provider adapters. -pub mod collections_support { - use std::collections::HashMap; - - /// Prunes one slot-keyed map down to the retained recent window once the threshold is crossed. - pub fn prune_recent_slots( - slot_states: &mut HashMap, - slot: u64, - retained_lag: u64, - prune_threshold: usize, - ) { - if slot_states.len() <= prune_threshold { - return; - } - let slot_floor = slot.saturating_sub(retained_lag); - slot_states.retain(|tracked_slot, _| *tracked_slot >= slot_floor); - } -} - +/// Benchmark helpers reused across profiling fixtures and microbenches. +pub mod bench; /// Typed byte-slice conversion helpers reused across provider adapters. -pub mod bytes { - use super::{PubkeyBytes, SignatureBytes}; - - /// Converts one 64-byte signature slice into `SignatureBytes`. - /// - /// # Errors - /// - /// Returns the error produced by `on_error` when `bytes` is not exactly 64 bytes long. - pub fn signature_bytes_from_slice(bytes: &[u8], on_error: F) -> Result - where - F: FnOnce() -> E, - { - let raw: [u8; 64] = bytes.try_into().map_err(|_error| on_error())?; - Ok(SignatureBytes::from(raw)) - } - - /// Converts one 32-byte pubkey slice into `PubkeyBytes`. - /// - /// # Errors - /// - /// Returns the error produced by `on_error` when `bytes` is not exactly 32 bytes long. - pub fn pubkey_bytes_from_slice(bytes: &[u8], on_error: F) -> Result - where - F: FnOnce() -> E, - { - let raw: [u8; 32] = bytes.try_into().map_err(|_error| on_error())?; - Ok(PubkeyBytes::from(raw)) - } -} - -/// Short-vector parsing helpers reused across serialized Solana payload readers. -pub mod short_vec { - /// Partial short-vector decode failure. - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub enum ShortVecDecodeError { - /// The payload ended before the short-vector length was fully decoded. - Incomplete, - /// The payload encoded an invalid short-vector length. - Invalid, - } - - /// Decodes one Solana short-vector length from `payload`. - #[must_use] - pub fn decode_short_u16_len(payload: &[u8], offset: &mut usize) -> Option { - let mut value = 0_usize; - let mut shift = 0_u32; - for byte_index in 0..3 { - let byte = usize::from(*payload.get(*offset)?); - *offset = (*offset).saturating_add(1); - value |= (byte & 0x7f) << shift; - if byte & 0x80 == 0 { - return Some(value); - } - shift = shift.saturating_add(7); - if byte_index == 2 { - return None; - } - } - None - } - - /// Decodes one Solana short-vector length prefix from the start of `payload`. - /// - /// Returns the decoded length together with the payload offset immediately - /// after the prefix bytes. - #[must_use] - pub fn decode_short_u16_len_prefix(payload: &[u8]) -> Option<(usize, usize)> { - let mut offset = 0; - let value = decode_short_u16_len(payload, &mut offset)?; - Some((value, offset)) - } - - /// Decodes one Solana short-vector length from a possibly partial payload. - /// - /// # Errors - /// - /// Returns [`ShortVecDecodeError::Incomplete`] when the payload ends before - /// the length is fully decoded, and [`ShortVecDecodeError::Invalid`] for an - /// invalid short-vector encoding. - pub fn decode_short_u16_len_partial( - payload: &[u8], - offset: &mut usize, - ) -> Result { - let mut value = 0_usize; - let mut shift = 0_u32; - for byte_index in 0..3 { - let byte = usize::from( - *payload - .get(*offset) - .ok_or(ShortVecDecodeError::Incomplete)?, - ); - *offset = (*offset) - .checked_add(1) - .ok_or(ShortVecDecodeError::Invalid)?; - value |= (byte & 0x7f) << shift; - if byte & 0x80 == 0 { - return Ok(value); - } - shift = shift.saturating_add(7); - if byte_index == 2 { - return Err(ShortVecDecodeError::Invalid); - } - } - Err(ShortVecDecodeError::Invalid) - } -} - -/// Duration helpers reused across transport adapters. -pub mod time_support { - use super::Duration; - - /// Returns whole seconds rounded up, preserving non-zero sub-second values. - #[must_use] - pub const fn duration_secs_ceil(duration: Duration) -> u64 { - let secs = duration.as_secs(); - if duration.subsec_nanos() == 0 { - secs - } else { - secs.saturating_add(1) - } - } - - /// Returns one duration in whole milliseconds, saturating at `u64::MAX`. - #[must_use] - pub fn duration_millis_u64(duration: Duration) -> u64 { - duration.as_millis().min(u128::from(u64::MAX)) as u64 - } - - /// Returns `duration` unless it is zero, in which case returns `fallback`. - #[must_use] - pub const fn nonzero_duration_or(duration: Duration, fallback: Duration) -> Duration { - if duration.is_zero() { - fallback - } else { - duration - } - } - - /// Returns the current Unix timestamp in milliseconds, saturating at `u64::MAX`. - #[must_use] - pub fn current_unix_ms() -> u64 { - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map_or(0, duration_millis_u64) - } - - /// Returns the current Unix timestamp in whole seconds. - #[must_use] - pub fn current_unix_secs() -> u64 { - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map_or(0, |duration| duration.as_secs()) - } - - /// Returns the current Unix timestamp in nanoseconds as one `u128`. - #[must_use] - pub fn current_unix_nanos() -> u128 { - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map_or(0, |duration| duration.as_nanos()) - } -} - -#[cfg(test)] -mod tests { - use std::time::Duration; - - use super::collections_support::prune_recent_slots; - use super::short_vec::{ - ShortVecDecodeError, decode_short_u16_len, decode_short_u16_len_partial, - decode_short_u16_len_prefix, - }; - use super::time_support::{ - current_unix_ms, current_unix_nanos, current_unix_secs, duration_millis_u64, - duration_secs_ceil, nonzero_duration_or, - }; - - #[test] - fn duration_secs_ceil_rounds_subsecond_values_up() { - assert_eq!(duration_secs_ceil(Duration::from_secs(2)), 2); - assert_eq!(duration_secs_ceil(Duration::from_millis(1)), 1); - assert_eq!(duration_secs_ceil(Duration::from_millis(1500)), 2); - } - - #[test] - fn current_unix_ms_is_monotonic_enough_for_smoke_check() { - let first = current_unix_ms(); - let second = current_unix_ms(); - assert!(second >= first); - } - - #[test] - fn duration_millis_u64_saturates() { - assert_eq!(duration_millis_u64(Duration::from_millis(7)), 7); - assert_eq!(duration_millis_u64(Duration::MAX), u64::MAX); - } - - #[test] - fn nonzero_duration_or_clamps_zero() { - assert_eq!( - nonzero_duration_or(Duration::ZERO, Duration::from_secs(7)), - Duration::from_secs(7) - ); - assert_eq!( - nonzero_duration_or(Duration::from_millis(5), Duration::from_secs(7)), - Duration::from_millis(5) - ); - } - - #[test] - fn current_unix_time_helpers_are_nonzero_or_zero_safely() { - assert!(current_unix_secs() <= current_unix_ms() / 1_000 + 1); - assert!(current_unix_nanos() / 1_000_000 <= u128::from(current_unix_ms()) + 1); - } - - #[test] - fn short_vec_decode_matches_compact_lengths() { - let mut single_byte_offset = 0; - assert_eq!( - decode_short_u16_len(&[0x7f], &mut single_byte_offset), - Some(127) - ); - assert_eq!(single_byte_offset, 1); - - let mut two_byte_offset = 0; - assert_eq!( - decode_short_u16_len(&[0x80, 0x01], &mut two_byte_offset), - Some(128) - ); - assert_eq!(two_byte_offset, 2); - } - - #[test] - fn short_vec_decode_partial_distinguishes_incomplete_and_invalid() { - let mut incomplete_offset = 0; - assert_eq!( - decode_short_u16_len_partial(&[0x80], &mut incomplete_offset), - Err(ShortVecDecodeError::Incomplete) - ); - - let mut invalid_offset = 0; - assert_eq!( - decode_short_u16_len_partial(&[0x80, 0x80, 0x80], &mut invalid_offset), - Err(ShortVecDecodeError::Invalid) - ); - } - - #[test] - fn short_vec_decode_prefix_returns_offset() { - assert_eq!(decode_short_u16_len_prefix(&[0x7f]), Some((127, 1))); - assert_eq!(decode_short_u16_len_prefix(&[0x80, 0x01]), Some((128, 2))); - } - - #[test] - fn prune_recent_slots_drops_old_entries_after_threshold() { - let mut slot_states = (0_u64..10_u64).map(|slot| (slot, slot)).collect(); - - prune_recent_slots(&mut slot_states, 9, 3, 4); - - assert_eq!(slot_states.len(), 4); - assert!(!slot_states.contains_key(&5)); - assert!(slot_states.contains_key(&6)); - assert!(slot_states.contains_key(&9)); - } -} +pub mod bytes; +/// Collection helpers reused across runtime caches and provider adapters. +pub mod collections_support; +/// Environment parsing helpers reused across profiling fixtures and tests. +pub mod env_support; +/// Solana short-vector parsing helpers reused across serialized payload readers. +pub mod short_vec; +/// Duration and wall-clock helpers reused across transport adapters. +pub mod time_support; diff --git a/crates/sof-support/src/short_vec.rs b/crates/sof-support/src/short_vec.rs new file mode 100644 index 00000000..6da60126 --- /dev/null +++ b/crates/sof-support/src/short_vec.rs @@ -0,0 +1,119 @@ +/// Partial short-vector decode failure. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ShortVecDecodeError { + /// The payload ended before the short-vector length was fully decoded. + Incomplete, + /// The payload encoded an invalid short-vector length. + Invalid, +} + +/// Decodes one Solana short-vector length from `payload`. +#[must_use] +pub fn decode_short_u16_len(payload: &[u8], offset: &mut usize) -> Option { + let mut value = 0_usize; + let mut shift = 0_u32; + for byte_index in 0..3 { + let byte = usize::from(*payload.get(*offset)?); + *offset = (*offset).saturating_add(1); + value |= (byte & 0x7f) << shift; + if byte & 0x80 == 0 { + return Some(value); + } + shift = shift.saturating_add(7); + if byte_index == 2 { + return None; + } + } + None +} + +/// Decodes one Solana short-vector length prefix from start of `payload`. +/// +/// Returns decoded length together with payload offset immediately +/// after prefix bytes. +#[must_use] +pub fn decode_short_u16_len_prefix(payload: &[u8]) -> Option<(usize, usize)> { + let mut offset = 0; + let value = decode_short_u16_len(payload, &mut offset)?; + Some((value, offset)) +} + +/// Decodes one Solana short-vector length from possibly partial payload. +/// +/// # Errors +/// +/// Returns [`ShortVecDecodeError::Incomplete`] when payload ends before +/// length is fully decoded, and [`ShortVecDecodeError::Invalid`] for invalid +/// short-vector encoding. +pub fn decode_short_u16_len_partial( + payload: &[u8], + offset: &mut usize, +) -> Result { + let mut value = 0_usize; + let mut shift = 0_u32; + for byte_index in 0..3 { + let byte = usize::from( + *payload + .get(*offset) + .ok_or(ShortVecDecodeError::Incomplete)?, + ); + *offset = (*offset) + .checked_add(1) + .ok_or(ShortVecDecodeError::Invalid)?; + value |= (byte & 0x7f) << shift; + if byte & 0x80 == 0 { + return Ok(value); + } + shift = shift.saturating_add(7); + if byte_index == 2 { + return Err(ShortVecDecodeError::Invalid); + } + } + Err(ShortVecDecodeError::Invalid) +} + +#[cfg(test)] +mod tests { + use super::{ + ShortVecDecodeError, decode_short_u16_len, decode_short_u16_len_partial, + decode_short_u16_len_prefix, + }; + + #[test] + fn short_vec_decode_matches_compact_lengths() { + let mut single_byte_offset = 0; + assert_eq!( + decode_short_u16_len(&[0x7f], &mut single_byte_offset), + Some(127) + ); + assert_eq!(single_byte_offset, 1); + + let mut two_byte_offset = 0; + assert_eq!( + decode_short_u16_len(&[0x80, 0x01], &mut two_byte_offset), + Some(128) + ); + assert_eq!(two_byte_offset, 2); + } + + #[test] + fn short_vec_decode_partial_distinguishes_incomplete_and_invalid() { + let mut incomplete_offset = 0; + assert_eq!( + decode_short_u16_len_partial(&[0x80], &mut incomplete_offset), + Err(ShortVecDecodeError::Incomplete) + ); + + let mut invalid_offset = 0; + assert_eq!( + decode_short_u16_len_partial(&[0x80, 0x80, 0x80], &mut invalid_offset), + Err(ShortVecDecodeError::Invalid) + ); + } + + #[test] + fn short_vec_decode_prefix_returns_offset() { + assert_eq!(decode_short_u16_len_prefix(&[0x7f]), Some((127, 1))); + assert_eq!(decode_short_u16_len_prefix(&[0x80, 0x01]), Some((128, 2))); + } +} diff --git a/crates/sof-support/src/time_support.rs b/crates/sof-support/src/time_support.rs new file mode 100644 index 00000000..1954f7ed --- /dev/null +++ b/crates/sof-support/src/time_support.rs @@ -0,0 +1,100 @@ +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +/// Returns whole seconds rounded up, preserving non-zero sub-second values. +#[must_use] +pub const fn duration_secs_ceil(duration: Duration) -> u64 { + let secs = duration.as_secs(); + if duration.subsec_nanos() == 0 { + secs + } else { + secs.saturating_add(1) + } +} + +/// Returns one duration in whole milliseconds, saturating at `u64::MAX`. +#[must_use] +pub fn duration_millis_u64(duration: Duration) -> u64 { + duration.as_millis().min(u128::from(u64::MAX)) as u64 +} + +/// Returns `duration` unless it is zero, in which case returns `fallback`. +#[must_use] +pub const fn nonzero_duration_or(duration: Duration, fallback: Duration) -> Duration { + if duration.is_zero() { + fallback + } else { + duration + } +} + +/// Returns current Unix timestamp in milliseconds, saturating at `u64::MAX`. +#[must_use] +pub fn current_unix_ms() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_or(0, duration_millis_u64) +} + +/// Returns current Unix timestamp in whole seconds. +#[must_use] +pub fn current_unix_secs() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_or(0, |duration| duration.as_secs()) +} + +/// Returns current Unix timestamp in nanoseconds as one `u128`. +#[must_use] +pub fn current_unix_nanos() -> u128 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_or(0, |duration| duration.as_nanos()) +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use super::{ + current_unix_ms, current_unix_nanos, current_unix_secs, duration_millis_u64, + duration_secs_ceil, nonzero_duration_or, + }; + + #[test] + fn duration_secs_ceil_rounds_subsecond_values_up() { + assert_eq!(duration_secs_ceil(Duration::from_secs(2)), 2); + assert_eq!(duration_secs_ceil(Duration::from_millis(1)), 1); + assert_eq!(duration_secs_ceil(Duration::from_millis(1500)), 2); + } + + #[test] + fn current_unix_ms_is_monotonic_enough_for_smoke_check() { + let first = current_unix_ms(); + let second = current_unix_ms(); + assert!(second >= first); + } + + #[test] + fn duration_millis_u64_saturates() { + assert_eq!(duration_millis_u64(Duration::from_millis(7)), 7); + assert_eq!(duration_millis_u64(Duration::MAX), u64::MAX); + } + + #[test] + fn nonzero_duration_or_clamps_zero() { + assert_eq!( + nonzero_duration_or(Duration::ZERO, Duration::from_secs(7)), + Duration::from_secs(7) + ); + assert_eq!( + nonzero_duration_or(Duration::from_millis(5), Duration::from_secs(7)), + Duration::from_millis(5) + ); + } + + #[test] + fn current_unix_time_helpers_are_nonzero_or_zero_safely() { + assert!(current_unix_secs() <= current_unix_ms() / 1_000 + 1); + assert!(current_unix_nanos() / 1_000_000 <= u128::from(current_unix_ms()) + 1); + } +} diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 493fe2d9..10e722d9 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -36,6 +36,7 @@ User-facing crate docs: - ADR-0010: `adr/0010-dedicated-derived-state-feed.md` (Implemented) - ADR-0011: `adr/0011-plugin-config-and-lifecycle-hooks.md` (Implemented) - ADR-0012: `adr/0012-runtime-owned-observability-endpoints.md` (Implemented) +- ADR-0013: `adr/0013-runtime-delivery-profiles.md` (Proposed) - Framework Hooks: `framework-plugin-hooks.md` - Derived-State Extension Contract: `derived-state-extension-contract.md` - Derived-State Feed Contract: `derived-state-feed-contract.md` @@ -50,3 +51,4 @@ User-facing crate docs: - ARD-0007: `ard/0007-infrastructure-composition-and-runtime-model.md` - ARD-0008: `ard/0008-observability-and-operability-standards.md` - ARD-0009: `ard/0009-adr-process-and-governance.md` +- ARD-0010: `ard/0010-simplification-without-capability-regression.md` diff --git a/docs/architecture/adr/0013-runtime-delivery-profiles.md b/docs/architecture/adr/0013-runtime-delivery-profiles.md new file mode 100644 index 00000000..49367fe7 --- /dev/null +++ b/docs/architecture/adr/0013-runtime-delivery-profiles.md @@ -0,0 +1,363 @@ +# ADR-0013: Runtime Delivery Profiles + +- Status: Proposed +- Date: 2026-04-11 + +## Context + +SOF's current runtime model has a clear bias: + +- protect ingest first +- keep queues bounded +- avoid stalling hot paths on slow downstream consumers +- prefer freshness over completeness on observational surfaces under pressure + +That bias is correct for low-latency Solana workloads, but it is not the right operating posture +for every consumer shape. + +Some adopters primarily want: + +- the lowest possible callback latency for trading/searcher paths +- more controlled degradation for monitoring and analytics paths +- stronger downstream ordering and drain discipline for restart-safe stateful consumers + +Today those tradeoffs are mostly implicit in one runtime philosophy. That has two problems: + +1. adopters must accept one bundled bias even when their workload is different +2. opening the wrong level of configurability would turn the runtime into an untestable pile of + independent toggles + +The requirement is therefore not "make everything configurable". The requirement is: + +- preserve one coherent runtime model +- make the runtime's delivery bias explicit +- keep the ingest hot path bounded and protected in every supported mode + +This ADR also aligns with: + +- ADR-0008: runtime-owned extension and ingress boundaries remain host-owned +- ADR-0009 and ADR-0010: derived-state remains the replay-safe and rollback-aware contract +- ARD-0007: runtime orchestration and queueing policy remain explicit infrastructure decisions + +## Decision + +SOF will expose one typed runtime policy enum that selects the runtime's downstream delivery bias. + +Type name: + +- `RuntimeDeliveryProfile` + +Initial variants: + +- `LatencyOptimized` +- `Balanced` +- `DeliveryDisciplined` + +This enum is a policy bundle, not a bag of independent low-level toggles. + +Naming note: + +- `DeliveryDisciplined` is preferred over `DeliveryOptimized` because it signals stronger + ordering/drain discipline without implying durability, completeness, or upgraded upstream + guarantees. + +The profile selection MUST: + +- be represented as a typed enum in Rust +- be mirrored as a typed enum in TypeScript and Python SDKs +- fail configuration loading on unknown values instead of silently falling back +- apply at the runtime-instance level, not per hook or per plugin in the first version + +The profile selection MUST NOT: + +- change the fundamental ingest ownership model +- allow unbounded queues +- promise lossless behavior on surfaces that are observational by design +- turn plugin callbacks into the replay-safe contract + +The default profile MUST preserve current SOF behavior as closely as possible. That means the +first implementation default is: + +- `LatencyOptimized` + +## Profile Invariants + +Across all `RuntimeDeliveryProfile` variants, the following remain true: + +- ingress remains bounded +- ingest is never blocked by observational consumers +- plugins and runtime extensions are not the replay-safe contract +- derived-state remains the restart-safe, rollback-aware surface +- no profile upgrades upstream provider durability or transport semantics +- no profile permits unbounded memory growth + +## Operational Definitions + +Terms used in this ADR: + +- hot lanes: ingress-adjacent or latency-sensitive paths that continue to protect ingest budget in + every profile +- non-hot lanes: runtime-owned downstream queues whose delivery discipline may change by profile + +`Backpressure` in this ADR means only runtime-owned downstream admission control on non-hot lanes. +It may include: + +- producer-side waiting on non-hot owned queues +- reduced concurrency or serialized draining +- temporary suppression of non-critical fanout +- slowdown of extension or consumer work admission + +It does not mean: + +- blocking provider ingest +- stalling socket ownership or protocol parsing +- claiming upstream systems now offer stronger retention + +`Ordering` in this ADR is scoped explicitly: + +- within a lane: queue and callback order for one runtime-owned lane +- across lanes: no global total ordering is introduced by any profile +- callbacks for same event: callbacks on same owned lane follow that lane's policy; callbacks on + different lanes may still observe and complete in different orders +- first version: sequential or reduced-concurrency behavior is profile-driven runtime config, not a + per-plugin or per-hook override + +## Delivery Profile Semantics + +The following sections define the minimum contract for each profile. Exact numeric queue sizes, +concurrency limits, and timeout budgets remain implementation details, but the behavioral meaning +must stay stable. + +### `LatencyOptimized` + +This is the current SOF-style operating posture. + +Required behavior: + +- ingest never waits on plugin or extension consumers +- bounded queues remain tight +- observational lanes may drop new work under pressure +- no producer-side waiting is introduced on downstream-owned queues +- concurrent downstream dispatch is allowed where that reduces latency and does not violate an + existing ordering contract +- lane-local ordering is preserved only where an existing queue contract already provides it +- no cross-lane ordering guarantee is introduced +- shutdown drain budgets stay short and explicit +- freshness is preferred over completeness on observational surfaces + +Intended use: + +- bots +- searchers +- latency-sensitive monitors +- callback-driven consumers that care more about newest data than full retention + +### `Balanced` + +This is the general-purpose profile for users who still want bounded latency but want less +aggressive shedding. + +Required behavior: + +- ingest still does not wait on slow downstream consumers +- queue budgets are larger than `LatencyOptimized` +- pressure signaling and telemetry are stricter and earlier +- on non-hot owned lanes, runtime may slow admission, reduce concurrency, or temporarily suppress + non-critical fanout before shedding +- downstream dispatch should prefer stable lane-local FIFO ordering where practical on non-hot lanes +- no cross-lane ordering guarantee is introduced +- observational lanes may still shed work, but only after stronger buffering and clearer pressure + escalation than `LatencyOptimized` +- shutdown drain budgets are longer than `LatencyOptimized`, but still bounded + +Intended use: + +- mixed production services +- monitoring systems +- user-facing services that want controlled degradation without paying full delivery cost + +### `DeliveryDisciplined` + +This is the strongest downstream delivery discipline SOF can honestly provide without violating its +bounded-ingest model. + +Required behavior: + +- ingest ownership and boundedness remain intact +- downstream queues are the largest allowed by the selected profile set +- on non-hot owned lanes, runtime should exhaust bounded admission slowdown, reduced concurrency, + serialized draining, and temporary non-critical fanout suppression before shedding owned work +- within a non-hot owned lane, deterministic FIFO ordering is the default unless a lane-specific + contract explicitly says otherwise +- callbacks on same non-hot owned lane should default to serialized draining unless that lane + already has an explicit concurrency contract +- no cross-lane ordering guarantee is introduced, including for callbacks triggered by same event +- shutdown drain behavior is strongest here, but still bounded +- pressure must surface before shedding +- where a consumer needs replay, rollback, or restart safety, the runtime should direct that user + to derived-state contracts rather than claiming plugin callbacks are now durable + +Non-guarantees: + +- this profile does not mean "no data loss" +- it does not mean "high retention with eventual completeness" +- it does not upgrade upstream provider guarantees +- it does not make websocket feeds stronger than websocket +- it does not make observational plugin callbacks equivalent to derived-state + +Intended use: + +- stateful consumers that value ordered downstream processing +- analytics/monitoring systems that prefer higher retention and more predictable degradation +- adopters willing to pay more latency and resource cost for stronger runtime discipline + +## Scope of Influence + +`RuntimeDeliveryProfile` is allowed to influence: + +- plugin dispatch queue sizing and overflow behavior +- runtime-extension worker queue sizing and overflow behavior +- downstream admission control on non-hot lanes, including producer-side waits to owned queues, + reduced concurrency, serialized draining, and temporary suppression of non-critical fanout +- downstream ordering and concurrency defaults on non-hot lanes +- drain budgets during shutdown +- observability thresholds, warning levels, and degradation signaling +- derived-state consumer buffering defaults, warning thresholds, and checkpoint-adjacent queue + sizes only where runtime already owns those mechanics + +`RuntimeDeliveryProfile` is not allowed to influence: + +- raw ingest ownership of sockets or provider streams +- boundedness of ingress queues +- protocol parsing correctness rules +- replay, checkpoint, or cursor semantics already defined by derived-state contracts +- state-correctness posture or rollback meaning of derived-state surfaces +- transport capability limits inherited from provider families + +Guardrail: + +> Runtime profiles change SOF's downstream queueing, admission, ordering, and drain defaults. They +> do not change ingest boundedness, replay guarantees, or provider durability. + +## Configuration Surface + +The first supported shape is one runtime-level field: + +```rust +pub enum RuntimeDeliveryProfile { + LatencyOptimized, + Balanced, + DeliveryDisciplined, +} +``` + +This profile should live in typed runtime configuration, not as a stringly bag of booleans. + +Allowed configuration entry points: + +- Rust config builders and typed config structs +- generated TypeScript and Python SDK config types +- environment or file decoding layers that parse into the typed enum and reject unknown values + +The first version MUST NOT add: + +- per-hook profile selection +- per-plugin fairness toggles +- arbitrary queue-policy combinators +- hidden fallback from one profile to another + +Future narrower overrides, if ever added, require a follow-up ADR and must be justified by a real +measured workload rather than speculative flexibility. + +## Guarantees and Non-Guarantees + +The docs for this feature must define, per profile: + +- whether work may be dropped under pressure +- which owned lanes may apply producer-side waiting or other admission slowdown +- which runtime-owned admission slowdowns are allowed before shedding +- ordering expectations within a lane +- explicit absence of global ordering across lanes +- concurrency expectations, including same-event callback behavior across lanes +- shutdown drain expectations +- memory and latency tradeoffs +- the boundary between observational surfaces and replay-safe surfaces + +The docs must not claim: + +- exactly-once delivery +- universal losslessness +- fairness guarantees that are not actually enforced +- replay guarantees outside derived-state + +## Alternatives Considered + +### 1. Many independent runtime toggles + +Rejected because it would make the runtime harder to reason about, harder to test, and easier to +misconfigure. SOF needs legible operating modes, not policy chaos. + +### 2. Keep one hard-coded policy forever + +Rejected because SOF already serves multiple workload shapes, and one implicit bias makes adoption +harder for consumers who want stronger downstream discipline. + +### 3. Expose only queue sizes without semantic profiles + +Rejected because raw numeric knobs do not communicate the contract. Operators need named behavior +modes with explicit guarantees and non-guarantees. + +### 4. Keep `DeliveryOptimized` naming + +Rejected because users could still read it as "higher retention" or "almost durable". +`DeliveryDisciplined` better matches actual contract: stronger downstream discipline under bounded +ingest, not durable delivery. + +### 5. Promise a "durable" or "no loss" profile + +Rejected because SOF cannot honestly guarantee that across all surfaces and provider families. + +## Consequences + +Positive: + +- SOF becomes easier to adopt across more workload types without splitting into separate runtimes +- the runtime bias becomes explicit and reviewable +- TS/Python and Rust integrations can share one typed policy model +- operational tradeoffs become documentable and testable + +Trade-offs: + +- testing burden increases because the runtime now has multiple supported operating profiles +- benchmark and soak validation must cover profile differences where behavior changes +- some users may still ask for per-plugin tuning that this ADR deliberately does not allow + +## Migration and Rollout + +The first implementation should roll out in stages: + +1. introduce the typed enum and keep `LatencyOptimized` as the default +2. document exact profile contracts before widening knobs +3. add targeted regression coverage for shedding, admission slowdown, drain, and ordering semantics +4. expose the same enum through TS/Python SDK generation +5. only after the global profile is stable, evaluate whether any narrower override is justified + +Rollback strategy: + +- if profile semantics prove incoherent or too expensive to verify, keep the enum internal or + remove non-default variants before a stable release that documents them publicly + +## Compliance and Verification + +Implementation work for this ADR is not complete until the following exist: + +- regression tests that prove the documented drop or drain behavior for each profile +- explicit observability for pressure escalation and shedding decisions +- docs that define profile guarantees and non-guarantees without marketing language +- TS/Python enum generation that preserves typed selection and typed failure on unknown values +- validation that no profile allows ingest to be stalled by slow downstream consumers +- behavioral regression scenarios covering burst pressure on plugin lanes, slow consumer on a + non-hot lane, shutdown with queued work, provider reconnect plus backlog, mixed transaction and + general queue pressure, and derived-state buffering/warning behavior where profile influence is + allowed +- soak or benchmark coverage where profile-specific queueing behavior materially differs from default diff --git a/docs/architecture/ard/0010-simplification-without-capability-regression.md b/docs/architecture/ard/0010-simplification-without-capability-regression.md new file mode 100644 index 00000000..7f29fad8 --- /dev/null +++ b/docs/architecture/ard/0010-simplification-without-capability-regression.md @@ -0,0 +1,223 @@ +# ARD-0010: Simplification Without Capability Regression + +- Version: 1.0 +- Date: 2026-04-11 +- Applies to: `sof` runtime APIs, workspace crate layout, configuration surface, and runtime-facing docs + +## Goals + +- Reliability: simplification must not weaken correctness, replay, or backpressure guarantees. +- Efficiency: reduce code and API friction without adding runtime churn or hidden work. +- Speed: preserve or improve hot-path latency and throughput. +- Maintainability: make module boundaries, naming, and config intent easier to understand. + +## Problem statement + +SOF is intentionally infrastructure-shaped. Some of that complexity is real: + +- raw shred ingest +- processed provider-stream ingest +- bounded multicore dispatch +- queue pressure and degradation handling +- replay-aware derived-state +- health and readiness semantics +- multiple trust and delivery postures + +But not all current complexity is equally valuable. + +SOF should become easier to read, configure, and embed without pretending the runtime itself is +simple. The requirement is therefore: + +- remove accidental complexity +- preserve necessary complexity +- never trade away measured performance or documented semantics for aesthetic cleanup + +## Simplification principles + +- Simplify public shape first. +- Keep hot paths explicit. +- Prefer typed policies over many loosely-related knobs. +- Prefer literal names over generic helpers. +- Prefer narrower composition roots over cross-cutting wiring spread across many files. +- Preserve semantic distinctions that protect correctness. + +## Required distinction: real complexity vs accidental complexity + +The following are considered real complexity and MUST NOT be abstracted away beyond recognition: + +- ingress ownership and bounded admission +- packet/shred/FEC/dataset reconstruction stages +- replay-safe derived-state boundaries +- queue ownership, drop policy, and ordering semantics +- trust posture and verification choices +- provider capability differences +- shutdown and drain behavior + +The following are considered accidental complexity and SHOULD be reduced aggressively: + +- oversized files with unrelated concerns +- vague module names +- duplicated env/config parsing patterns +- stringly configuration where typed policy is possible +- redundant builder methods with unclear precedence +- multiple parallel ways to express the same runtime choice +- examples that require too much substrate knowledge for common paths + +## Allowed simplification targets + +### 1. Module and file layout + +- Split oversized files by explicit concern. +- Make `lib.rs` and top-level `mod.rs` files routing surfaces, not implementation dumps. +- Group runtime code by stable responsibility instead of by historical growth. +- Prefer file names that describe one concept directly. + +Examples: + +- support helpers split into named modules +- runtime config parsing isolated from runtime execution +- downstream dispatch policy separated from provider-specific startup wiring + +### 2. Public API shape + +- Add narrower builders and typed config bundles for common setups. +- Keep advanced escape hatches available behind explicit low-level APIs. +- Reduce duplicate entrypoints that express the same behavior in slightly different forms. +- Standardize naming across Rust, TypeScript, and Python where the same concept exists. + +### 3. Configuration surface + +- Collapse knob clusters into typed policy enums or typed config structs when the knobs represent + one operating posture. +- Reject unknown config values explicitly. +- Keep defaults safe, explicit, and documented. +- Preserve advanced overrides only when they map to real verified workload needs. + +### 4. Documentation and examples + +- Provide one obvious path for common use cases. +- Keep advanced operational detail in separate focused docs. +- State guarantees and non-guarantees explicitly. +- Show runtime choice boundaries instead of implying that all modes are equivalent. + +## Forbidden simplification patterns + +The following are architecture regressions even if they reduce line count: + +- replacing typed runtime contracts with unstructured maps or boolean bags +- collapsing observational plugin delivery and replay-safe derived-state into one surface +- hiding queue drop behavior, ordering rules, or trust posture behind vague “smart defaults” +- introducing unbounded queues to make APIs feel simpler +- moving hot-path behavior behind generic trait object stacks or allocation-heavy adapters without + measurement proving no regression +- merging provider families in a way that erases real capability differences +- removing explicit lifecycle states, drain policy, or startup validation in the name of ergonomics + +## Safe simplification patterns + +The following are preferred: + +- policy bundling: + - example: one typed runtime delivery profile instead of many downstream queue toggles +- facade layering: + - simple API for common paths, explicit lower-level API for advanced paths +- module extraction: + - move coherent utility or policy code into named files without changing semantics +- typed parsing: + - parse env/file values into typed enums and structs early, fail fast on invalid values +- composition-root cleanup: + - keep orchestration in infra/app runtime layers, not spread through domain slices +- benchmark-backed refactor: + - refactor internals only with before/after latency and throughput evidence on affected paths + +## Performance guardrails + +Simplification work MUST follow these rules: + +- hot-path changes require benchmark or profile evidence +- abstraction added to hot paths must justify itself with measured neutrality or benefit +- allocations, copies, queue hops, and dynamic dispatch added to hot paths are suspect by default +- code motion that is semantics-preserving but performance-neutral is allowed +- code motion that improves readability but regresses critical paths is rejected + +Relevant hot paths include at minimum: + +- packet ingest +- shred parsing and verification +- FEC recovery and dataset reconstruction +- transaction extraction and classification +- inline-critical plugin delivery +- provider-stream transaction ingest + +## Stability and semantics guardrails + +Simplification work MUST preserve: + +- bounded ingress and bounded downstream memory growth +- explicit overflow behavior +- typed failure surfaces where they already exist +- trust-mode semantics +- provider capability validation +- replay and checkpoint meaning for derived-state +- runtime health, readiness, and degradation signaling + +If a simplification changes any of those semantics, it is not a refactor. It is an architecture +change and requires a separate ADR. + +## Preferred migration strategy + +Simplification should usually proceed in this order: + +1. document current semantics before changing structure +2. split files and isolate concerns without behavior changes +3. add typed policy/config facades above existing behavior +4. deprecate duplicate or confusing surfaces +5. only then evaluate deeper runtime-policy consolidation + +This order matters because it keeps verification local and reduces accidental semantic drift. + +## Acceptance criteria for simplification work + +A simplification change is acceptable only if all of the following are true: + +1. feature surface is preserved or intentionally superseded with clear migration +2. public semantics are at least as explicit as before +3. critical-path performance is preserved or improved on affected paths +4. failure, replay, and backpressure behavior remain verified +5. code layout or API clarity is materially better, not merely different + +## Required verification + +Depending on scope, simplification work should include: + +- unit tests for moved logic +- regression tests for config parsing and precedence +- integration tests for startup, shutdown, and degraded-runtime behavior +- perf or benchmark comparisons for affected hot paths +- fuzz coverage where parser or wire-surface logic changes +- doc updates for any user-visible naming or configuration changes + +## Heuristics for deciding whether to simplify + +Simplify when: + +- one concept appears in too many places +- multiple APIs express one runtime choice +- file size hides coherent responsibilities +- users can make semantically invalid combinations too easily +- docs need too much prose to explain one ordinary path + +Do not simplify when: + +- the complexity is enforcing a real runtime boundary +- the complexity is carrying a measured performance optimization +- the complexity exists to make failure modes explicit +- the “simpler” version would blur capability differences or correctness contracts + +## Exit criteria + +1. Simplification proposals state what accidental complexity is being removed and what real + complexity remains intentionally explicit. +2. Hot-path refactors include performance verification. +3. Public API or config simplification preserves typed semantics and explicit failure behavior. +4. Module/layout cleanup leaves code easier to navigate with no hidden behavioral changes. From 5c56fae56566a24178289e631995c6f62eb9762f Mon Sep 17 00:00:00 2001 From: Celestial Date: Sat, 11 Apr 2026 10:19:18 +0200 Subject: [PATCH 2/2] fix(ci): remove unused runtime delivery parser stub --- crates/sof-observer/src/app/config/base.rs | 48 +--------------------- 1 file changed, 1 insertion(+), 47 deletions(-) diff --git a/crates/sof-observer/src/app/config/base.rs b/crates/sof-observer/src/app/config/base.rs index 9ec3ed7d..55107f27 100644 --- a/crates/sof-observer/src/app/config/base.rs +++ b/crates/sof-observer/src/app/config/base.rs @@ -3,9 +3,7 @@ use std::{num::NonZeroUsize, path::PathBuf}; use super::{read_bool_env, read_env_var}; use crate::{ framework::{DerivedStateReplayBackend, DerivedStateReplayDurability}, - runtime::{ - DerivedStateReplayConfig, DerivedStateRuntimeConfig, RuntimeDeliveryProfile, ShredTrustMode, - }, + runtime::{DerivedStateReplayConfig, DerivedStateRuntimeConfig, ShredTrustMode}, }; fn read_optional_bool_env(name: &str) -> Option { @@ -21,13 +19,6 @@ pub fn read_shred_trust_mode() -> ShredTrustMode { } } -pub fn read_runtime_delivery_profile() -> Option { - read_env_var("SOF_RUNTIME_DELIVERY_PROFILE") - .filter(|value| !value.trim().is_empty()) - .as_deref() - .and_then(RuntimeDeliveryProfile::from_config_value) -} - pub fn read_worker_threads() -> usize { read_env_var("SOF_WORKER_THREADS") .and_then(|value| value.parse::().ok()) @@ -369,40 +360,3 @@ pub fn read_derived_state_runtime_config() -> DerivedStateRuntimeConfig { }, } } - -#[cfg(test)] -mod tests { - use super::read_runtime_delivery_profile; - use crate::{ - runtime::RuntimeDeliveryProfile, runtime_env::with_runtime_env_overrides_for_test, - }; - - #[test] - fn runtime_delivery_profile_parses_known_values() { - with_runtime_env_overrides_for_test( - [( - "SOF_RUNTIME_DELIVERY_PROFILE".to_owned(), - "delivery_disciplined".to_owned(), - )], - || { - assert_eq!( - read_runtime_delivery_profile(), - Some(RuntimeDeliveryProfile::DeliveryDisciplined) - ); - }, - ); - } - - #[test] - fn runtime_delivery_profile_rejects_unknown_values() { - with_runtime_env_overrides_for_test( - [( - "SOF_RUNTIME_DELIVERY_PROFILE".to_owned(), - "unknown".to_owned(), - )], - || { - assert_eq!(read_runtime_delivery_profile(), None); - }, - ); - } -}