Skip to main content

lenso_service/
reliability_report.rs

1use crate::{EffectiveReliabilityValues, ReliabilityProfile, ReliabilityProfileOverrides};
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use utoipa::ToSchema;
5
6pub const RELIABILITY_REPORT_PROTOCOL: &str = "lenso.reliability-report.v1";
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
9#[serde(rename_all = "snake_case")]
10pub enum ReliabilityServiceState {
11    Healthy,
12    Degraded,
13    Unavailable,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
17#[serde(rename_all = "snake_case")]
18pub enum ReliabilityCheckState {
19    Met,
20    Breached,
21    Unknown,
22    Allowed,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, ToSchema)]
26#[serde(rename_all = "snake_case")]
27pub enum ReliabilityIssueCode {
28    ServiceRuntimeUnavailable,
29    DependencyCriticalUnavailable,
30    DependencyDegradableUnavailable,
31    DependencyObservationMissing,
32    QueueBacklogLimitExceeded,
33    WorkflowBacklogLimitExceeded,
34    TimerLagLimitExceeded,
35    RetryExhaustionLimitExceeded,
36    CompensationPressureLimitExceeded,
37    AvailabilityTargetBreached,
38    LatencyTargetBreached,
39    ErrorBudgetLimitExceeded,
40    ReliabilityObservationMissing,
41    ReliabilityObservationSourceUnavailable,
42    ReliabilityStoreObservationUnavailable,
43}
44
45#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
46#[serde(rename_all = "camelCase")]
47pub struct ReliabilityCheck {
48    pub code: String,
49    pub state: ReliabilityCheckState,
50    pub observed: Value,
51    pub expected: Value,
52    #[serde(default, skip_serializing_if = "Vec::is_empty")]
53    pub evidence_references: Vec<String>,
54    #[serde(default, skip_serializing_if = "Option::is_none")]
55    pub issue_code: Option<ReliabilityIssueCode>,
56    #[serde(default, skip_serializing_if = "Vec::is_empty")]
57    pub next_actions: Vec<String>,
58}
59
60#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
61#[serde(rename_all = "camelCase")]
62pub struct ActiveDegradedMode {
63    pub dependency_id: String,
64    pub mode: String,
65    pub evidence_references: Vec<String>,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
69#[serde(rename_all = "camelCase")]
70pub struct ReliabilityHealthResult {
71    pub healthy: bool,
72    pub semantics: String,
73    pub issue_codes: Vec<ReliabilityIssueCode>,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
77#[serde(rename_all = "camelCase")]
78pub struct ReliabilityEnforcementBoundary {
79    pub reports_only: bool,
80    pub blocks_production_promotion: bool,
81    pub executes_canary_policy: bool,
82    pub triggers_automated_rollback: bool,
83}
84
85impl Default for ReliabilityEnforcementBoundary {
86    fn default() -> Self {
87        Self {
88            reports_only: true,
89            blocks_production_promotion: false,
90            executes_canary_policy: false,
91            triggers_automated_rollback: false,
92        }
93    }
94}
95
96#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
97#[serde(rename_all = "camelCase")]
98pub struct ReliabilityReport {
99    pub protocol: String,
100    pub service_id: String,
101    pub contract_id: String,
102    pub contract_version: String,
103    pub profile: ReliabilityProfile,
104    #[serde(default)]
105    pub overrides: ReliabilityProfileOverrides,
106    pub effective_values: EffectiveReliabilityValues,
107    pub state: ReliabilityServiceState,
108    pub liveness: ReliabilityHealthResult,
109    pub readiness: ReliabilityHealthResult,
110    pub active_degraded_modes: Vec<ActiveDegradedMode>,
111    pub checks: Vec<ReliabilityCheck>,
112    pub enforcement: ReliabilityEnforcementBoundary,
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use serde_json::json;
119
120    #[test]
121    fn v1_report_without_explicit_overrides_remains_readable() {
122        let report = serde_json::from_value::<ReliabilityReport>(json!({
123            "protocol": RELIABILITY_REPORT_PROTOCOL,
124            "serviceId": "support-sla",
125            "contractId": "support-reliability",
126            "contractVersion": "v1",
127            "profile": "standard",
128            "effectiveValues": {
129                "availabilityTargetBasisPoints": 9950,
130                "latencyTargetMs": 1000,
131                "queueBacklogLimit": 100,
132                "workflowBacklogLimit": 100,
133                "timerLagLimitMs": 1000,
134                "retryExhaustionLimit": 5,
135                "compensationPressureLimit": 5,
136                "errorBudget": "rolling_30d",
137                "errorBudgetConsumedLimitBasisPoints": 10000,
138                "readiness": "serving",
139                "liveness": "process_running"
140            },
141            "state": "healthy",
142            "liveness": {
143                "healthy": true,
144                "semantics": "process_running",
145                "issueCodes": []
146            },
147            "readiness": {
148                "healthy": true,
149                "semantics": "serving",
150                "issueCodes": []
151            },
152            "activeDegradedModes": [],
153            "checks": [],
154            "enforcement": {
155                "reportsOnly": true,
156                "blocksProductionPromotion": false,
157                "executesCanaryPolicy": false,
158                "triggersAutomatedRollback": false
159            }
160        }))
161        .expect("legacy v1 report should deserialize");
162
163        assert_eq!(report.overrides, ReliabilityProfileOverrides::default());
164    }
165}