Skip to main content

mockforge_foundation/
workspace_promotion.rs

1//! Workspace promotion types
2//!
3//! Extracted from `mockforge-core::workspace::{mock_environment, scenario_promotion}`
4//! (Phase 6 / A10).
5//!
6//! Only the simple enum types live here. The richer `PromotionRequest`,
7//! `PromotionHistory`, and `PromotionService` trait stay in `mockforge-core`
8//! because their field shapes vary across consumers.
9
10use serde::{Deserialize, Serialize};
11
12/// Mock environment names — used to scope behavior, chaos, and promotion workflows.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(rename_all = "lowercase")]
15pub enum MockEnvironmentName {
16    /// Development environment - typically permissive, high chaos for testing
17    Dev,
18    /// Test environment - balanced settings for integration testing
19    Test,
20    /// Production-like environment - strict settings, minimal chaos
21    Prod,
22}
23
24impl MockEnvironmentName {
25    /// Convert to string.
26    pub fn as_str(&self) -> &'static str {
27        match self {
28            MockEnvironmentName::Dev => "dev",
29            MockEnvironmentName::Test => "test",
30            MockEnvironmentName::Prod => "prod",
31        }
32    }
33
34    /// Parse from string.
35    #[allow(clippy::should_implement_trait)]
36    pub fn from_str(s: &str) -> Option<Self> {
37        match s.to_lowercase().as_str() {
38            "dev" => Some(MockEnvironmentName::Dev),
39            "test" => Some(MockEnvironmentName::Test),
40            "prod" => Some(MockEnvironmentName::Prod),
41            _ => None,
42        }
43    }
44
45    /// Get all environment names in promotion order.
46    pub fn promotion_order() -> Vec<Self> {
47        vec![
48            MockEnvironmentName::Dev,
49            MockEnvironmentName::Test,
50            MockEnvironmentName::Prod,
51        ]
52    }
53
54    /// Get the next environment in promotion order.
55    pub fn next(&self) -> Option<Self> {
56        match self {
57            MockEnvironmentName::Dev => Some(MockEnvironmentName::Test),
58            MockEnvironmentName::Test => Some(MockEnvironmentName::Prod),
59            MockEnvironmentName::Prod => None,
60        }
61    }
62
63    /// Get the previous environment in promotion order.
64    pub fn previous(&self) -> Option<Self> {
65        match self {
66            MockEnvironmentName::Dev => None,
67            MockEnvironmentName::Test => Some(MockEnvironmentName::Dev),
68            MockEnvironmentName::Prod => Some(MockEnvironmentName::Test),
69        }
70    }
71}
72
73impl std::fmt::Display for MockEnvironmentName {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        write!(f, "{}", self.as_str())
76    }
77}
78
79/// Type of entity being promoted.
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
81#[serde(rename_all = "lowercase")]
82pub enum PromotionEntityType {
83    /// Scenario promotion
84    Scenario,
85    /// Persona promotion
86    Persona,
87    /// Configuration promotion (reality, chaos, drift budget)
88    Config,
89}
90
91impl std::fmt::Display for PromotionEntityType {
92    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93        match self {
94            PromotionEntityType::Scenario => write!(f, "scenario"),
95            PromotionEntityType::Persona => write!(f, "persona"),
96            PromotionEntityType::Config => write!(f, "config"),
97        }
98    }
99}
100
101/// Trait for services that can perform promotions.
102///
103/// Allows pipeline steps and other consumers to trigger promotions without
104/// creating circular dependencies between crates.
105#[allow(clippy::too_many_arguments)]
106#[async_trait::async_trait]
107pub trait PromotionService: Send + Sync {
108    /// Promote an entity from one environment to another.
109    async fn promote_entity(
110        &self,
111        workspace_id: uuid::Uuid,
112        entity_type: PromotionEntityType,
113        entity_id: String,
114        entity_version: Option<String>,
115        from_environment: MockEnvironmentName,
116        to_environment: MockEnvironmentName,
117        promoted_by: uuid::Uuid,
118        comments: Option<String>,
119    ) -> crate::Result<uuid::Uuid>;
120}
121
122/// Promotion status
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
124#[serde(rename_all = "lowercase")]
125pub enum PromotionStatus {
126    /// Promotion is pending approval
127    Pending,
128    /// Promotion has been approved
129    Approved,
130    /// Promotion has been rejected
131    Rejected,
132    /// Promotion has been completed
133    Completed,
134    /// Promotion failed
135    Failed,
136}
137
138impl std::fmt::Display for PromotionStatus {
139    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140        match self {
141            PromotionStatus::Pending => write!(f, "pending"),
142            PromotionStatus::Approved => write!(f, "approved"),
143            PromotionStatus::Rejected => write!(f, "rejected"),
144            PromotionStatus::Completed => write!(f, "completed"),
145            PromotionStatus::Failed => write!(f, "failed"),
146        }
147    }
148}
149
150/// Generic promotion request that supports scenarios, personas, and configs
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct PromotionRequest {
153    /// Entity type being promoted
154    pub entity_type: PromotionEntityType,
155    /// Entity ID to promote (scenario ID, persona ID, or "config" for config promotion)
156    pub entity_id: String,
157    /// Entity version (for scenarios/personas) or config snapshot ID (for configs)
158    pub entity_version: Option<String>,
159    /// Workspace ID
160    pub workspace_id: String,
161    /// Source environment
162    pub from_environment: MockEnvironmentName,
163    /// Target environment
164    pub to_environment: MockEnvironmentName,
165    /// Whether this requires approval
166    pub requires_approval: bool,
167    /// Reason why approval is required
168    pub approval_required_reason: Option<String>,
169    /// Comments from promoter
170    pub comments: Option<String>,
171    /// Additional metadata for the promotion (e.g., config changes diff)
172    #[serde(default)]
173    pub metadata: std::collections::HashMap<String, serde_json::Value>,
174}
175
176/// Promotion history for an entity
177#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct PromotionHistory {
179    /// Entity type
180    pub entity_type: PromotionEntityType,
181    /// Entity ID
182    pub entity_id: String,
183    /// Workspace ID
184    pub workspace_id: String,
185    /// List of promotions in chronological order
186    pub promotions: Vec<PromotionHistoryEntry>,
187}
188
189/// Single promotion history entry
190#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct PromotionHistoryEntry {
192    /// Promotion ID
193    pub promotion_id: String,
194    /// Entity type
195    pub entity_type: PromotionEntityType,
196    /// Entity ID
197    pub entity_id: String,
198    /// Entity version (for scenarios/personas) or config snapshot ID (for configs)
199    pub entity_version: Option<String>,
200    /// From environment
201    pub from_environment: MockEnvironmentName,
202    /// To environment
203    pub to_environment: MockEnvironmentName,
204    /// Promoted by user ID
205    pub promoted_by: String,
206    /// Approved by user ID (if applicable)
207    pub approved_by: Option<String>,
208    /// Status
209    pub status: PromotionStatus,
210    /// Timestamp
211    pub timestamp: chrono::DateTime<chrono::Utc>,
212    /// Comments
213    pub comments: Option<String>,
214    /// GitOps PR URL if created
215    pub pr_url: Option<String>,
216    /// Additional metadata (e.g., config changes diff)
217    #[serde(default)]
218    pub metadata: std::collections::HashMap<String, serde_json::Value>,
219}