Skip to main content

scirs2_core/enterprise/
deployment.rs

1//! Enterprise deployment configuration and utilities.
2//!
3//! Provides types for configuring deployment targets (Docker, Kubernetes, AWS Lambda,
4//! bare metal), defining performance SLA baselines, and performing runtime health checks.
5//!
6//! # Performance SLA
7//!
8//! SLA baselines define the maximum acceptable latency and minimum throughput for
9//! key operations across the SciRS2 ecosystem. These values are conservative targets
10//! measured on a reference platform (4-core x86_64, 16 GB RAM).
11//!
12//! ```rust
13//! use scirs2_core::enterprise::deployment::{default_sla_baselines, SlaCategory};
14//!
15//! let baselines = default_sla_baselines();
16//! let linalg: Vec<_> = baselines.iter()
17//!     .filter(|s| matches!(s.category, SlaCategory::LinearAlgebra))
18//!     .collect();
19//! assert!(!linalg.is_empty());
20//! ```
21//!
22//! # Deployment Health
23//!
24//! ```rust
25//! use scirs2_core::enterprise::deployment::health_check;
26//!
27//! let health = health_check();
28//! assert!(!health.version.is_empty());
29//! assert!(health.crates_available.contains(&"scirs2-core".to_string()));
30//! ```
31
32use std::collections::HashSet;
33use std::time::SystemTime;
34
35/// Deployment target configuration.
36///
37/// Represents the environment where SciRS2 is deployed. Each variant captures
38/// the minimum configuration needed to describe the target.
39#[derive(Debug, Clone)]
40#[non_exhaustive]
41pub enum DeploymentTarget {
42    /// Docker container deployment.
43    Docker {
44        /// Docker image tag (e.g. `"scirs2:0.4.0"`).
45        image_tag: String,
46        /// Optional resource limits in MB.
47        memory_limit_mb: Option<u64>,
48    },
49    /// Kubernetes deployment.
50    Kubernetes {
51        /// Kubernetes namespace.
52        namespace: String,
53        /// Number of pod replicas.
54        replicas: u32,
55        /// Resource request CPU in millicores.
56        cpu_request_millicores: Option<u32>,
57        /// Resource request memory in MiB.
58        memory_request_mib: Option<u32>,
59    },
60    /// AWS Lambda (or similar serverless) deployment.
61    AwsLambda {
62        /// Memory allocation in MB.
63        memory_mb: u32,
64        /// Function timeout in seconds.
65        timeout_secs: u32,
66    },
67    /// Azure Functions deployment.
68    AzureFunctions {
69        /// App service plan tier.
70        plan_tier: String,
71        /// Maximum burst instance count.
72        max_instances: u32,
73    },
74    /// Google Cloud Run deployment.
75    CloudRun {
76        /// Maximum concurrent requests per container.
77        max_concurrency: u32,
78        /// CPU allocation (e.g. 1, 2, 4).
79        cpu: u32,
80        /// Memory in MiB.
81        memory_mib: u32,
82    },
83    /// Bare metal or VM deployment.
84    BareMetal {
85        /// Host address or identifier.
86        host: String,
87    },
88}
89
90impl DeploymentTarget {
91    /// Returns a human-readable description of the deployment target.
92    pub fn description(&self) -> String {
93        match self {
94            Self::Docker { image_tag, .. } => format!("Docker container: {image_tag}"),
95            Self::Kubernetes {
96                namespace,
97                replicas,
98                ..
99            } => format!("Kubernetes: {namespace} ({replicas} replicas)"),
100            Self::AwsLambda {
101                memory_mb,
102                timeout_secs,
103            } => format!("AWS Lambda: {memory_mb}MB, {timeout_secs}s timeout"),
104            Self::AzureFunctions {
105                plan_tier,
106                max_instances,
107            } => format!("Azure Functions: {plan_tier} (max {max_instances} instances)"),
108            Self::CloudRun {
109                max_concurrency,
110                cpu,
111                memory_mib,
112            } => format!("Cloud Run: {cpu} CPU, {memory_mib}MiB, concurrency {max_concurrency}"),
113            Self::BareMetal { host } => format!("Bare metal: {host}"),
114            #[allow(unreachable_patterns)]
115            _ => "Unknown deployment target".to_string(),
116        }
117    }
118}
119
120/// Category of an SLA baseline measurement.
121#[derive(Debug, Clone, PartialEq, Eq, Hash)]
122#[non_exhaustive]
123pub enum SlaCategory {
124    /// Linear algebra operations (matmul, SVD, solve, etc.).
125    LinearAlgebra,
126    /// FFT operations.
127    Fft,
128    /// Statistical operations.
129    Statistics,
130    /// Signal processing operations.
131    Signal,
132    /// Sparse matrix operations.
133    Sparse,
134    /// Integration operations.
135    Integration,
136    /// Interpolation operations.
137    Interpolation,
138    /// Optimization operations.
139    Optimization,
140}
141
142impl core::fmt::Display for SlaCategory {
143    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
144        match self {
145            Self::LinearAlgebra => write!(f, "linalg"),
146            Self::Fft => write!(f, "fft"),
147            Self::Statistics => write!(f, "stats"),
148            Self::Signal => write!(f, "signal"),
149            Self::Sparse => write!(f, "sparse"),
150            Self::Integration => write!(f, "integrate"),
151            Self::Interpolation => write!(f, "interpolate"),
152            Self::Optimization => write!(f, "optimize"),
153            #[allow(unreachable_patterns)]
154            _ => write!(f, "unknown"),
155        }
156    }
157}
158
159/// Performance SLA definition for a single operation.
160///
161/// Defines the maximum acceptable latency and optional throughput guarantee
162/// for a specific computational operation. SLA values are conservative
163/// (generous) so that they can be reliably met on the reference platform.
164#[derive(Debug, Clone)]
165pub struct PerformanceSla {
166    /// The SLA category (crate area).
167    pub category: SlaCategory,
168    /// Human-readable operation name (e.g. `"matmul_1000x1000"`).
169    pub operation: String,
170    /// Maximum acceptable wall-clock latency in milliseconds.
171    pub max_latency_ms: u64,
172    /// Minimum throughput in operations per second, if applicable.
173    pub throughput_ops_per_sec: Option<f64>,
174    /// 99th-percentile latency in milliseconds, if measured.
175    pub p99_latency_ms: Option<u64>,
176    /// Description of the workload.
177    pub description: String,
178}
179
180impl Default for PerformanceSla {
181    fn default() -> Self {
182        Self {
183            category: SlaCategory::LinearAlgebra,
184            operation: String::new(),
185            max_latency_ms: 0,
186            throughput_ops_per_sec: None,
187            p99_latency_ms: None,
188            description: String::new(),
189        }
190    }
191}
192
193/// Returns the default SLA baselines for SciRS2 v0.4.0.
194///
195/// All latency values are conservative upper bounds measured on the reference
196/// platform (Ubuntu 22.04, 4-core x86_64, 16 GB RAM). Production deployments
197/// on higher-spec hardware should comfortably exceed these targets.
198///
199/// # Returns
200///
201/// A `Vec<PerformanceSla>` containing SLA entries for all major crate areas.
202pub fn default_sla_baselines() -> Vec<PerformanceSla> {
203    vec![
204        // Linear algebra
205        PerformanceSla {
206            category: SlaCategory::LinearAlgebra,
207            operation: "matmul_1000x1000".into(),
208            max_latency_ms: 500,
209            throughput_ops_per_sec: Some(2.0),
210            p99_latency_ms: Some(600),
211            description: "Dense matrix multiply (1000x1000 f64)".into(),
212        },
213        PerformanceSla {
214            category: SlaCategory::LinearAlgebra,
215            operation: "det_100x100".into(),
216            max_latency_ms: 10,
217            throughput_ops_per_sec: Some(100.0),
218            p99_latency_ms: Some(15),
219            description: "Determinant of 100x100 f64 matrix".into(),
220        },
221        PerformanceSla {
222            category: SlaCategory::LinearAlgebra,
223            operation: "svd_500x500".into(),
224            max_latency_ms: 2000,
225            throughput_ops_per_sec: Some(0.5),
226            p99_latency_ms: Some(2500),
227            description: "Full SVD of 500x500 f64 matrix".into(),
228        },
229        PerformanceSla {
230            category: SlaCategory::LinearAlgebra,
231            operation: "solve_1000x1000".into(),
232            max_latency_ms: 500,
233            throughput_ops_per_sec: Some(2.0),
234            p99_latency_ms: Some(600),
235            description: "Dense linear solve (1000x1000 f64)".into(),
236        },
237        PerformanceSla {
238            category: SlaCategory::LinearAlgebra,
239            operation: "cholesky_1000x1000".into(),
240            max_latency_ms: 300,
241            throughput_ops_per_sec: Some(3.0),
242            p99_latency_ms: Some(400),
243            description: "Cholesky decomposition (1000x1000 SPD f64)".into(),
244        },
245        // FFT
246        PerformanceSla {
247            category: SlaCategory::Fft,
248            operation: "fft_1m_points".into(),
249            max_latency_ms: 100,
250            throughput_ops_per_sec: Some(10.0),
251            p99_latency_ms: Some(130),
252            description: "Complex FFT of 2^20 (1M) points".into(),
253        },
254        PerformanceSla {
255            category: SlaCategory::Fft,
256            operation: "fft_64k_points".into(),
257            max_latency_ms: 10,
258            throughput_ops_per_sec: Some(100.0),
259            p99_latency_ms: Some(15),
260            description: "Complex FFT of 2^16 (64K) points".into(),
261        },
262        PerformanceSla {
263            category: SlaCategory::Fft,
264            operation: "batch_fft_1000x1024".into(),
265            max_latency_ms: 200,
266            throughput_ops_per_sec: Some(5.0),
267            p99_latency_ms: Some(250),
268            description: "Batch FFT: 1000 transforms of length 1024".into(),
269        },
270        PerformanceSla {
271            category: SlaCategory::Fft,
272            operation: "rfft_1m_points".into(),
273            max_latency_ms: 60,
274            throughput_ops_per_sec: Some(15.0),
275            p99_latency_ms: Some(80),
276            description: "Real-valued FFT of 2^20 (1M) points".into(),
277        },
278        // Statistics
279        PerformanceSla {
280            category: SlaCategory::Statistics,
281            operation: "normal_pdf_1m".into(),
282            max_latency_ms: 50,
283            throughput_ops_per_sec: Some(20.0),
284            p99_latency_ms: Some(65),
285            description: "Normal PDF evaluated at 1M points".into(),
286        },
287        PerformanceSla {
288            category: SlaCategory::Statistics,
289            operation: "linreg_10k_100feat".into(),
290            max_latency_ms: 500,
291            throughput_ops_per_sec: Some(2.0),
292            p99_latency_ms: Some(600),
293            description: "Linear regression: 10K samples, 100 features".into(),
294        },
295        PerformanceSla {
296            category: SlaCategory::Statistics,
297            operation: "kde_10k_points".into(),
298            max_latency_ms: 200,
299            throughput_ops_per_sec: Some(5.0),
300            p99_latency_ms: Some(250),
301            description: "Kernel density estimation on 10K points".into(),
302        },
303        // Signal
304        PerformanceSla {
305            category: SlaCategory::Signal,
306            operation: "fir_64tap_1m".into(),
307            max_latency_ms: 100,
308            throughput_ops_per_sec: Some(10.0),
309            p99_latency_ms: Some(130),
310            description: "FIR filter: 64 taps, 1M samples".into(),
311        },
312        PerformanceSla {
313            category: SlaCategory::Signal,
314            operation: "stft_1m_1024win".into(),
315            max_latency_ms: 500,
316            throughput_ops_per_sec: Some(2.0),
317            p99_latency_ms: Some(600),
318            description: "STFT: 1M samples, 1024-sample window".into(),
319        },
320        PerformanceSla {
321            category: SlaCategory::Signal,
322            operation: "iir_8pole_1m".into(),
323            max_latency_ms: 50,
324            throughput_ops_per_sec: Some(20.0),
325            p99_latency_ms: Some(65),
326            description: "IIR filter: 8-pole Butterworth, 1M samples".into(),
327        },
328        // Sparse
329        PerformanceSla {
330            category: SlaCategory::Sparse,
331            operation: "spmv_100k_1m".into(),
332            max_latency_ms: 10,
333            throughput_ops_per_sec: Some(100.0),
334            p99_latency_ms: Some(15),
335            description: "Sparse matrix-vector multiply: 100K x 100K, 1M nnz".into(),
336        },
337        PerformanceSla {
338            category: SlaCategory::Sparse,
339            operation: "cg_10k".into(),
340            max_latency_ms: 1000,
341            throughput_ops_per_sec: Some(1.0),
342            p99_latency_ms: Some(1200),
343            description: "Conjugate gradient solve: 10K x 10K sparse SPD".into(),
344        },
345        PerformanceSla {
346            category: SlaCategory::Sparse,
347            operation: "sparse_lu_10k".into(),
348            max_latency_ms: 2000,
349            throughput_ops_per_sec: Some(0.5),
350            p99_latency_ms: Some(2500),
351            description: "Sparse LU factorization: 10K x 10K".into(),
352        },
353        // Integration
354        PerformanceSla {
355            category: SlaCategory::Integration,
356            operation: "quad_1k_points".into(),
357            max_latency_ms: 5,
358            throughput_ops_per_sec: Some(200.0),
359            p99_latency_ms: Some(8),
360            description: "Adaptive quadrature with 1K evaluation points".into(),
361        },
362        PerformanceSla {
363            category: SlaCategory::Integration,
364            operation: "ode_rk45_10k_steps".into(),
365            max_latency_ms: 100,
366            throughput_ops_per_sec: Some(10.0),
367            p99_latency_ms: Some(130),
368            description: "RK45 ODE solver: 10K adaptive steps".into(),
369        },
370        // Interpolation
371        PerformanceSla {
372            category: SlaCategory::Interpolation,
373            operation: "cubic_spline_10k".into(),
374            max_latency_ms: 20,
375            throughput_ops_per_sec: Some(50.0),
376            p99_latency_ms: Some(30),
377            description: "Cubic spline interpolation: 10K knots".into(),
378        },
379        // Optimization
380        PerformanceSla {
381            category: SlaCategory::Optimization,
382            operation: "lbfgs_100d".into(),
383            max_latency_ms: 200,
384            throughput_ops_per_sec: Some(5.0),
385            p99_latency_ms: Some(250),
386            description: "L-BFGS optimization: 100 dimensions, Rosenbrock".into(),
387        },
388        PerformanceSla {
389            category: SlaCategory::Optimization,
390            operation: "nelder_mead_50d".into(),
391            max_latency_ms: 500,
392            throughput_ops_per_sec: Some(2.0),
393            p99_latency_ms: Some(600),
394            description: "Nelder-Mead: 50 dimensions".into(),
395        },
396    ]
397}
398
399/// Validates that a set of SLA baselines has no duplicate operation names.
400///
401/// # Errors
402///
403/// Returns `Err` with the duplicate operation name if any duplicates are found.
404pub fn validate_sla_uniqueness(baselines: &[PerformanceSla]) -> Result<(), String> {
405    let mut seen = HashSet::new();
406    for sla in baselines {
407        if !seen.insert(&sla.operation) {
408            return Err(format!("Duplicate SLA operation: {}", sla.operation));
409        }
410    }
411    Ok(())
412}
413
414/// Runtime health check result.
415#[derive(Debug, Clone)]
416pub struct DeploymentHealth {
417    /// SciRS2 version string.
418    pub version: String,
419    /// List of available (compiled-in) crate names.
420    pub crates_available: Vec<String>,
421    /// Process uptime in seconds (approximate; measured from first call).
422    pub uptime_secs: u64,
423    /// Approximate current heap usage in bytes (platform-dependent estimate).
424    pub memory_usage_bytes: usize,
425    /// Timestamp of the health check (seconds since UNIX epoch).
426    pub timestamp_epoch_secs: u64,
427}
428
429/// Performs a deployment health check.
430///
431/// Returns a [`DeploymentHealth`] snapshot capturing the current version,
432/// available crates, and approximate resource usage.
433pub fn health_check() -> DeploymentHealth {
434    use std::sync::OnceLock;
435
436    static START_TIME: OnceLock<SystemTime> = OnceLock::new();
437    let start = START_TIME.get_or_init(SystemTime::now);
438
439    let uptime = SystemTime::now()
440        .duration_since(*start)
441        .unwrap_or_default()
442        .as_secs();
443
444    let now_epoch = SystemTime::now()
445        .duration_since(SystemTime::UNIX_EPOCH)
446        .unwrap_or_default()
447        .as_secs();
448
449    // Enumerate crates that are always compiled in the workspace.
450    // This is a static list; feature-gated crates are included when their
451    // feature is enabled.
452    let mut crates_available = vec![
453        "scirs2-core".to_string(),
454        "scirs2-linalg".to_string(),
455        "scirs2-stats".to_string(),
456        "scirs2-signal".to_string(),
457        "scirs2-fft".to_string(),
458        "scirs2-sparse".to_string(),
459        "scirs2-optimize".to_string(),
460        "scirs2-integrate".to_string(),
461        "scirs2-interpolate".to_string(),
462        "scirs2-special".to_string(),
463        "scirs2-cluster".to_string(),
464        "scirs2-io".to_string(),
465        "scirs2-graph".to_string(),
466        "scirs2-neural".to_string(),
467        "scirs2-series".to_string(),
468        "scirs2-text".to_string(),
469        "scirs2-vision".to_string(),
470        "scirs2-metrics".to_string(),
471        "scirs2-ndimage".to_string(),
472        "scirs2-transform".to_string(),
473        "scirs2-datasets".to_string(),
474        "scirs2-wasm".to_string(),
475    ];
476    crates_available.sort();
477
478    DeploymentHealth {
479        version: env!("CARGO_PKG_VERSION").to_string(),
480        crates_available,
481        uptime_secs: uptime,
482        memory_usage_bytes: 0, // No portable way to query heap without allocator hooks
483        timestamp_epoch_secs: now_epoch,
484    }
485}
486
487/// Recommended container resource limits for common workload profiles.
488#[derive(Debug, Clone)]
489pub struct ResourceRecommendation {
490    /// Workload profile name.
491    pub profile: String,
492    /// Recommended CPU cores.
493    pub cpu_cores: u32,
494    /// Recommended memory in MiB.
495    pub memory_mib: u32,
496    /// Recommended disk in MiB (for temporary files).
497    pub disk_mib: u32,
498    /// Description of the workload profile.
499    pub description: String,
500}
501
502/// Returns resource recommendations for common deployment profiles.
503pub fn resource_recommendations() -> Vec<ResourceRecommendation> {
504    vec![
505        ResourceRecommendation {
506            profile: "lightweight".into(),
507            cpu_cores: 2,
508            memory_mib: 2048,
509            disk_mib: 512,
510            description: "Statistical computations, small-scale signal processing".into(),
511        },
512        ResourceRecommendation {
513            profile: "standard".into(),
514            cpu_cores: 4,
515            memory_mib: 8192,
516            disk_mib: 2048,
517            description: "General scientific computing, moderate linear algebra".into(),
518        },
519        ResourceRecommendation {
520            profile: "compute_intensive".into(),
521            cpu_cores: 8,
522            memory_mib: 32768,
523            disk_mib: 8192,
524            description: "Large-scale linalg, neural network training, optimization".into(),
525        },
526        ResourceRecommendation {
527            profile: "memory_intensive".into(),
528            cpu_cores: 4,
529            memory_mib: 65536,
530            disk_mib: 16384,
531            description: "Large sparse systems, out-of-core processing, big datasets".into(),
532        },
533    ]
534}
535
536#[cfg(test)]
537mod tests {
538    use super::*;
539
540    #[test]
541    fn test_default_sla_baselines_not_empty() {
542        let baselines = default_sla_baselines();
543        assert!(!baselines.is_empty(), "SLA baselines must not be empty");
544        assert!(
545            baselines.len() >= 15,
546            "Expected at least 15 SLA entries, got {}",
547            baselines.len()
548        );
549    }
550
551    #[test]
552    fn test_sla_values_positive() {
553        for sla in default_sla_baselines() {
554            assert!(
555                sla.max_latency_ms > 0,
556                "SLA {} has zero max_latency_ms",
557                sla.operation
558            );
559            if let Some(throughput) = sla.throughput_ops_per_sec {
560                assert!(
561                    throughput > 0.0,
562                    "SLA {} has non-positive throughput",
563                    sla.operation
564                );
565            }
566            if let Some(p99) = sla.p99_latency_ms {
567                assert!(
568                    p99 >= sla.max_latency_ms,
569                    "SLA {} has p99 ({}) < max_latency ({})",
570                    sla.operation,
571                    p99,
572                    sla.max_latency_ms
573                );
574            }
575        }
576    }
577
578    #[test]
579    fn test_sla_operation_names_unique() {
580        let baselines = default_sla_baselines();
581        let result = validate_sla_uniqueness(&baselines);
582        assert!(
583            result.is_ok(),
584            "Duplicate SLA operation: {:?}",
585            result.err()
586        );
587    }
588
589    #[test]
590    fn test_sla_all_categories_covered() {
591        let baselines = default_sla_baselines();
592        let categories: HashSet<_> = baselines.iter().map(|s| s.category.clone()).collect();
593        assert!(categories.contains(&SlaCategory::LinearAlgebra));
594        assert!(categories.contains(&SlaCategory::Fft));
595        assert!(categories.contains(&SlaCategory::Statistics));
596        assert!(categories.contains(&SlaCategory::Signal));
597        assert!(categories.contains(&SlaCategory::Sparse));
598        assert!(categories.contains(&SlaCategory::Integration));
599        assert!(categories.contains(&SlaCategory::Interpolation));
600        assert!(categories.contains(&SlaCategory::Optimization));
601    }
602
603    #[test]
604    fn test_health_check() {
605        let health = health_check();
606        assert!(!health.version.is_empty(), "Version must not be empty");
607        assert!(
608            !health.crates_available.is_empty(),
609            "Crates list must not be empty"
610        );
611        assert!(
612            health.crates_available.contains(&"scirs2-core".to_string()),
613            "scirs2-core must be in crates list"
614        );
615        assert!(
616            health.timestamp_epoch_secs > 0,
617            "Timestamp must be positive"
618        );
619    }
620
621    #[test]
622    fn test_deployment_target_variants() {
623        let targets = vec![
624            DeploymentTarget::Docker {
625                image_tag: "scirs2:0.4.0".into(),
626                memory_limit_mb: Some(4096),
627            },
628            DeploymentTarget::Kubernetes {
629                namespace: "production".into(),
630                replicas: 3,
631                cpu_request_millicores: Some(2000),
632                memory_request_mib: Some(8192),
633            },
634            DeploymentTarget::AwsLambda {
635                memory_mb: 1024,
636                timeout_secs: 300,
637            },
638            DeploymentTarget::AzureFunctions {
639                plan_tier: "Premium".into(),
640                max_instances: 10,
641            },
642            DeploymentTarget::CloudRun {
643                max_concurrency: 80,
644                cpu: 4,
645                memory_mib: 8192,
646            },
647            DeploymentTarget::BareMetal {
648                host: "compute-01.example.com".into(),
649            },
650        ];
651        for target in &targets {
652            let desc = target.description();
653            assert!(!desc.is_empty(), "Description must not be empty");
654        }
655    }
656
657    #[test]
658    fn test_deployment_target_description_content() {
659        let docker = DeploymentTarget::Docker {
660            image_tag: "myimg:latest".into(),
661            memory_limit_mb: None,
662        };
663        assert!(docker.description().contains("myimg:latest"));
664
665        let k8s = DeploymentTarget::Kubernetes {
666            namespace: "ml-prod".into(),
667            replicas: 5,
668            cpu_request_millicores: None,
669            memory_request_mib: None,
670        };
671        assert!(k8s.description().contains("ml-prod"));
672        assert!(k8s.description().contains("5"));
673    }
674
675    #[test]
676    fn test_resource_recommendations() {
677        let recs = resource_recommendations();
678        assert!(recs.len() >= 3, "Expected at least 3 resource profiles");
679        for rec in &recs {
680            assert!(!rec.profile.is_empty());
681            assert!(rec.cpu_cores > 0);
682            assert!(rec.memory_mib > 0);
683        }
684    }
685
686    #[test]
687    fn test_sla_category_display() {
688        assert_eq!(SlaCategory::LinearAlgebra.to_string(), "linalg");
689        assert_eq!(SlaCategory::Fft.to_string(), "fft");
690        assert_eq!(SlaCategory::Statistics.to_string(), "stats");
691        assert_eq!(SlaCategory::Signal.to_string(), "signal");
692        assert_eq!(SlaCategory::Sparse.to_string(), "sparse");
693        assert_eq!(SlaCategory::Integration.to_string(), "integrate");
694        assert_eq!(SlaCategory::Interpolation.to_string(), "interpolate");
695        assert_eq!(SlaCategory::Optimization.to_string(), "optimize");
696    }
697
698    #[test]
699    fn test_performance_sla_default() {
700        let sla = PerformanceSla::default();
701        assert!(sla.operation.is_empty());
702        assert_eq!(sla.max_latency_ms, 0);
703        assert!(sla.throughput_ops_per_sec.is_none());
704        assert!(sla.p99_latency_ms.is_none());
705    }
706}