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}