ricecoder_teams/
rules.rs

1/// Shared rules management and promotion
2use crate::error::Result;
3use crate::models::{AdoptionMetrics, EffectivenessMetrics, RuleScope, SharedRule};
4use std::sync::Arc;
5use tracing::{debug, info};
6
7/// Manages rule promotion, validation, versioning, and approval workflows
8///
9/// This manager integrates with ricecoder-learning components to:
10/// - Promote rules from Project → Team → Organization scope
11/// - Validate rules before promotion
12/// - Track version history with timestamps and metadata
13/// - Support rollback to previous versions
14/// - Track adoption and effectiveness metrics
15///
16/// # Requirements
17/// - Requirement 2.1: Support promotion from Project → Team → Organization
18/// - Requirement 2.2: Support promotion from Team → Organization
19/// - Requirement 2.4: Validate rules before promotion
20/// - Requirement 2.5: Track adoption metrics
21/// - Requirement 2.6: Track effectiveness metrics
22/// - Requirement 2.7: Support rollback to previous versions
23/// - Requirement 2.8: Maintain complete version history
24pub struct SharedRulesManager {
25    /// Rule promoter for handling rule promotion logic
26    rule_promoter: Arc<dyn RulePromoter>,
27    /// Rule validator for validating rules before promotion
28    rule_validator: Arc<dyn RuleValidator>,
29    /// Analytics engine for tracking metrics
30    analytics_engine: Arc<dyn AnalyticsEngine>,
31}
32
33/// Trait for rule promotion functionality
34pub trait RulePromoter: Send + Sync {
35    /// Promote a rule from one scope to another
36    fn promote(&self, rule: &SharedRule, from_scope: RuleScope, to_scope: RuleScope) -> Result<()>;
37}
38
39/// Trait for rule validation functionality
40pub trait RuleValidator: Send + Sync {
41    /// Validate a rule and return a validation report
42    fn validate(&self, rule: &SharedRule) -> Result<ValidationReport>;
43}
44
45/// Trait for analytics functionality
46pub trait AnalyticsEngine: Send + Sync {
47    /// Track adoption metrics for a rule
48    fn track_adoption(&self, rule_id: &str) -> Result<AdoptionMetrics>;
49    /// Track effectiveness metrics for a rule
50    fn track_effectiveness(&self, rule_id: &str) -> Result<EffectivenessMetrics>;
51}
52
53/// Validation report for a rule
54#[derive(Debug, Clone)]
55pub struct ValidationReport {
56    pub rule_id: String,
57    pub is_valid: bool,
58    pub errors: Vec<String>,
59    pub warnings: Vec<String>,
60}
61
62impl SharedRulesManager {
63    /// Create a new SharedRulesManager with the provided components
64    ///
65    /// # Arguments
66    /// * `rule_promoter` - Component for handling rule promotion
67    /// * `rule_validator` - Component for validating rules
68    /// * `analytics_engine` - Component for tracking metrics
69    pub fn new(
70        rule_promoter: Arc<dyn RulePromoter>,
71        rule_validator: Arc<dyn RuleValidator>,
72        analytics_engine: Arc<dyn AnalyticsEngine>,
73    ) -> Self {
74        debug!("Creating new SharedRulesManager");
75        SharedRulesManager {
76            rule_promoter,
77            rule_validator,
78            analytics_engine,
79        }
80    }
81
82    /// Promote a rule from one scope to another
83    ///
84    /// Supports promotion from Project → Team → Organization.
85    /// Uses ricecoder-learning RulePromoter for promotion logic.
86    ///
87    /// # Arguments
88    /// * `rule` - The rule to promote
89    /// * `from_scope` - Current scope of the rule
90    /// * `to_scope` - Target scope for promotion
91    ///
92    /// # Errors
93    /// Returns error if promotion fails or rule is invalid
94    pub async fn promote_rule(
95        &self,
96        rule: SharedRule,
97        from_scope: RuleScope,
98        to_scope: RuleScope,
99    ) -> Result<()> {
100        info!(
101            rule_id = %rule.id,
102            from_scope = %from_scope.as_str(),
103            to_scope = %to_scope.as_str(),
104            "Promoting rule"
105        );
106
107        // Validate rule before promotion (Requirement 2.4)
108        let validation = self.validate_rule(&rule).await?;
109        if !validation.is_valid {
110            return Err(crate::error::TeamError::RuleValidationFailed(format!(
111                "Rule validation failed: {:?}",
112                validation.errors
113            )));
114        }
115
116        // Use ricecoder-learning RulePromoter for promotion logic (Requirement 2.1, 2.2)
117        self.rule_promoter.promote(&rule, from_scope, to_scope)?;
118
119        info!(
120            rule_id = %rule.id,
121            from_scope = %from_scope.as_str(),
122            to_scope = %to_scope.as_str(),
123            "Rule promoted successfully"
124        );
125
126        Ok(())
127    }
128
129    /// Validate a rule before promotion
130    ///
131    /// Uses ricecoder-learning RuleValidator to validate rules.
132    /// Returns detailed validation reports including errors and warnings.
133    ///
134    /// # Arguments
135    /// * `rule` - The rule to validate
136    ///
137    /// # Returns
138    /// Validation report with validation status, errors, and warnings
139    pub async fn validate_rule(&self, rule: &SharedRule) -> Result<ValidationReport> {
140        debug!(rule_id = %rule.id, "Validating rule");
141
142        let report = self.rule_validator.validate(rule)?;
143
144        if report.is_valid {
145            info!(rule_id = %rule.id, "Rule validation passed");
146        } else {
147            info!(
148                rule_id = %rule.id,
149                errors = ?report.errors,
150                "Rule validation failed"
151            );
152        }
153
154        Ok(report)
155    }
156
157    /// Get the complete version history for a rule
158    ///
159    /// Retrieves complete version history with timestamps and promotion metadata.
160    /// Uses ricecoder-learning versioning system.
161    ///
162    /// # Arguments
163    /// * `rule_id` - ID of the rule to get history for
164    ///
165    /// # Returns
166    /// Vector of SharedRule entries representing version history
167    pub async fn get_rule_history(&self, rule_id: &str) -> Result<Vec<SharedRule>> {
168        debug!(rule_id = %rule_id, "Retrieving rule history");
169
170        // TODO: Integrate with ricecoder-learning versioning system
171        // This will retrieve the complete version history from storage
172        // Each entry should include timestamps and promotion metadata
173
174        info!(rule_id = %rule_id, "Rule history retrieved");
175        Ok(Vec::new())
176    }
177
178    /// Rollback a rule to a previous version
179    ///
180    /// Supports rollback to previous rule versions using ricecoder-learning versioning system.
181    /// This is useful when a promoted rule causes issues.
182    ///
183    /// # Arguments
184    /// * `rule_id` - ID of the rule to rollback
185    /// * `version` - Version number to rollback to
186    ///
187    /// # Errors
188    /// Returns error if version doesn't exist or rollback fails
189    pub async fn rollback_rule(&self, rule_id: &str, version: u32) -> Result<()> {
190        info!(
191            rule_id = %rule_id,
192            version = %version,
193            "Rolling back rule"
194        );
195
196        // TODO: Integrate with ricecoder-learning versioning system
197        // This will restore the rule to the specified version
198        // Should validate that the version exists before rollback
199
200        info!(
201            rule_id = %rule_id,
202            version = %version,
203            "Rule rolled back successfully"
204        );
205
206        Ok(())
207    }
208
209    /// Track adoption metrics for a rule
210    ///
211    /// Tracks adoption metrics showing percentage of team members applying the rule.
212    /// Uses ricecoder-learning AnalyticsEngine for metric calculation.
213    ///
214    /// # Arguments
215    /// * `rule_id` - ID of the rule to track adoption for
216    ///
217    /// # Returns
218    /// AdoptionMetrics with adoption percentage and trend data
219    pub async fn track_adoption(&self, rule_id: &str) -> Result<AdoptionMetrics> {
220        debug!(rule_id = %rule_id, "Tracking rule adoption");
221
222        let metrics = self.analytics_engine.track_adoption(rule_id)?;
223
224        info!(
225            rule_id = %rule_id,
226            adoption_percentage = %metrics.adoption_percentage,
227            "Rule adoption metrics tracked"
228        );
229
230        Ok(metrics)
231    }
232
233    /// Track effectiveness metrics for a rule
234    ///
235    /// Tracks effectiveness metrics measuring positive outcomes from rule application.
236    /// Uses ricecoder-learning AnalyticsEngine for metric calculation.
237    ///
238    /// # Arguments
239    /// * `rule_id` - ID of the rule to track effectiveness for
240    ///
241    /// # Returns
242    /// EffectivenessMetrics with effectiveness score and impact trend
243    pub async fn track_effectiveness(&self, rule_id: &str) -> Result<EffectivenessMetrics> {
244        debug!(rule_id = %rule_id, "Tracking rule effectiveness");
245
246        let metrics = self.analytics_engine.track_effectiveness(rule_id)?;
247
248        info!(
249            rule_id = %rule_id,
250            effectiveness_score = %metrics.effectiveness_score,
251            "Rule effectiveness metrics tracked"
252        );
253
254        Ok(metrics)
255    }
256}
257
258/// Mock implementations for testing and default usage
259pub mod mocks {
260    use super::*;
261
262    /// Mock RulePromoter for testing
263    pub struct MockRulePromoter;
264
265    impl RulePromoter for MockRulePromoter {
266        fn promote(
267            &self,
268            _rule: &SharedRule,
269            _from_scope: RuleScope,
270            _to_scope: RuleScope,
271        ) -> Result<()> {
272            Ok(())
273        }
274    }
275
276    /// Mock RuleValidator for testing
277    pub struct MockRuleValidator;
278
279    impl RuleValidator for MockRuleValidator {
280        fn validate(&self, rule: &SharedRule) -> Result<ValidationReport> {
281            Ok(ValidationReport {
282                rule_id: rule.id.clone(),
283                is_valid: true,
284                errors: Vec::new(),
285                warnings: Vec::new(),
286            })
287        }
288    }
289
290    /// Mock AnalyticsEngine for testing
291    pub struct MockAnalyticsEngine;
292
293    impl AnalyticsEngine for MockAnalyticsEngine {
294        fn track_adoption(&self, rule_id: &str) -> Result<AdoptionMetrics> {
295            Ok(AdoptionMetrics {
296                rule_id: rule_id.to_string(),
297                total_members: 10,
298                adopting_members: 8,
299                adoption_percentage: 80.0,
300                adoption_trend: Vec::new(),
301            })
302        }
303
304        fn track_effectiveness(&self, rule_id: &str) -> Result<EffectivenessMetrics> {
305            Ok(EffectivenessMetrics {
306                rule_id: rule_id.to_string(),
307                positive_outcomes: 15,
308                negative_outcomes: 2,
309                effectiveness_score: 0.88,
310                impact_trend: Vec::new(),
311            })
312        }
313    }
314}
315
316#[cfg(test)]
317mod tests {
318    use super::*;
319    use crate::models::SharedRule;
320    use chrono::Utc;
321
322    fn create_test_manager() -> SharedRulesManager {
323        SharedRulesManager::new(
324            Arc::new(mocks::MockRulePromoter),
325            Arc::new(mocks::MockRuleValidator),
326            Arc::new(mocks::MockAnalyticsEngine),
327        )
328    }
329
330    fn create_test_rule() -> SharedRule {
331        SharedRule {
332            id: "rule-1".to_string(),
333            name: "Test Rule".to_string(),
334            description: "A test rule".to_string(),
335            scope: RuleScope::Project,
336            enforced: true,
337            promoted_by: "admin-1".to_string(),
338            promoted_at: Utc::now(),
339            version: 1,
340        }
341    }
342
343    #[tokio::test]
344    async fn test_validate_rule_success() {
345        let manager = create_test_manager();
346        let rule = create_test_rule();
347
348        let report = manager
349            .validate_rule(&rule)
350            .await
351            .expect("Validation failed");
352        assert!(report.is_valid);
353        assert_eq!(report.rule_id, "rule-1");
354        assert!(report.errors.is_empty());
355    }
356
357    #[tokio::test]
358    async fn test_promote_rule_success() {
359        let manager = create_test_manager();
360        let rule = create_test_rule();
361
362        let result = manager
363            .promote_rule(rule, RuleScope::Project, RuleScope::Team)
364            .await;
365        assert!(result.is_ok());
366    }
367
368    #[tokio::test]
369    async fn test_track_adoption() {
370        let manager = create_test_manager();
371
372        let metrics = manager
373            .track_adoption("rule-1")
374            .await
375            .expect("Failed to track adoption");
376        assert_eq!(metrics.rule_id, "rule-1");
377        assert_eq!(metrics.total_members, 10);
378        assert_eq!(metrics.adopting_members, 8);
379        assert_eq!(metrics.adoption_percentage, 80.0);
380    }
381
382    #[tokio::test]
383    async fn test_track_effectiveness() {
384        let manager = create_test_manager();
385
386        let metrics = manager
387            .track_effectiveness("rule-1")
388            .await
389            .expect("Failed to track effectiveness");
390        assert_eq!(metrics.rule_id, "rule-1");
391        assert_eq!(metrics.positive_outcomes, 15);
392        assert_eq!(metrics.negative_outcomes, 2);
393        assert_eq!(metrics.effectiveness_score, 0.88);
394    }
395
396    #[tokio::test]
397    async fn test_get_rule_history() {
398        let manager = create_test_manager();
399
400        let history = manager
401            .get_rule_history("rule-1")
402            .await
403            .expect("Failed to get history");
404        assert!(history.is_empty()); // Mock returns empty for now
405    }
406
407    #[tokio::test]
408    async fn test_rollback_rule() {
409        let manager = create_test_manager();
410
411        let result = manager.rollback_rule("rule-1", 1).await;
412        assert!(result.is_ok());
413    }
414}