Skip to main content

agentic_contract/
contract_engine.rs

1//! Core engine wrapping ContractFile.
2
3use chrono::Utc;
4use serde::{Deserialize, Serialize};
5
6use crate::approval::{
7    ApprovalDecision, ApprovalRequest, ApprovalRule, ApprovalStatus, DecisionType,
8};
9use crate::condition::{Condition, ConditionStatus};
10use crate::error::{ContractError, ContractResult};
11use crate::file_format::ContractFile;
12use crate::inventions::*;
13use crate::obligation::{Obligation, ObligationStatus};
14use crate::policy::{Policy, PolicyAction, PolicyScope};
15use crate::risk_limit::RiskLimit;
16use crate::violation::{Violation, ViolationSeverity};
17use crate::ContractId;
18
19/// Core engine for contract operations.
20pub struct ContractEngine {
21    /// In-memory contract file.
22    pub file: ContractFile,
23}
24
25impl ContractEngine {
26    /// Create a new engine with an empty contract file.
27    pub fn new() -> Self {
28        Self {
29            file: ContractFile::new(),
30        }
31    }
32
33    /// Create from an existing contract file.
34    pub fn from_file(file: ContractFile) -> Self {
35        Self { file }
36    }
37
38    /// Open from path.
39    pub fn open(path: impl Into<std::path::PathBuf>) -> ContractResult<Self> {
40        let file = ContractFile::open(path)?;
41        Ok(Self { file })
42    }
43
44    /// Save the contract file.
45    pub fn save(&self) -> ContractResult<()> {
46        self.file.save()
47    }
48
49    // ── Policies ───────────────────────────────────────────────────
50
51    /// Add a policy.
52    pub fn add_policy(&mut self, policy: Policy) -> ContractId {
53        let id = policy.id;
54        self.file.policies.push(policy);
55        id
56    }
57
58    /// Check if an action is allowed under current policies.
59    ///
60    /// Returns the most restrictive applicable policy action.
61    pub fn check_policy(&self, action_type: &str, scope: PolicyScope) -> PolicyAction {
62        let mut result = PolicyAction::Allow;
63
64        for policy in &self.file.policies {
65            if !policy.is_active() {
66                continue;
67            }
68
69            // Check scope compatibility
70            if policy.scope == PolicyScope::Global || policy.scope == scope {
71                // Check if the policy's conditions mention this action type
72                let label_lower = policy.label.to_lowercase();
73                let action_lower = action_type.to_lowercase();
74
75                if label_lower.contains(&action_lower) || action_lower.contains(&label_lower) {
76                    // Apply most restrictive action
77                    match policy.action {
78                        PolicyAction::Deny => return PolicyAction::Deny,
79                        PolicyAction::RequireApproval => result = PolicyAction::RequireApproval,
80                        PolicyAction::AuditOnly if result == PolicyAction::Allow => {
81                            result = PolicyAction::AuditOnly
82                        }
83                        _ => {}
84                    }
85                }
86            }
87        }
88
89        result
90    }
91
92    /// List policies, optionally filtered by scope.
93    pub fn list_policies(&self, scope: Option<PolicyScope>) -> Vec<&Policy> {
94        self.file
95            .policies
96            .iter()
97            .filter(|p| scope.is_none() || Some(p.scope) == scope)
98            .collect()
99    }
100
101    /// Get a policy by ID.
102    pub fn get_policy(&self, id: ContractId) -> ContractResult<&Policy> {
103        self.file
104            .find_policy(id)
105            .ok_or_else(|| ContractError::NotFound(format!("Policy {}", id)))
106    }
107
108    // ── Risk Limits ────────────────────────────────────────────────
109
110    /// Add a risk limit.
111    pub fn add_risk_limit(&mut self, limit: RiskLimit) -> ContractId {
112        let id = limit.id;
113        self.file.risk_limits.push(limit);
114        id
115    }
116
117    /// Check if an action would exceed any risk limits.
118    ///
119    /// Returns the first limit that would be exceeded, or None if OK.
120    pub fn check_risk_limit(&self, label_pattern: &str, amount: f64) -> Option<&RiskLimit> {
121        let pattern = label_pattern.to_lowercase();
122        self.file.risk_limits.iter().find(|limit| {
123            limit.label.to_lowercase().contains(&pattern) && limit.would_exceed(amount)
124        })
125    }
126
127    /// Increment a risk limit by label.
128    pub fn increment_risk_limit(&mut self, id: ContractId, amount: f64) -> ContractResult<()> {
129        let limit = self
130            .file
131            .find_risk_limit_mut(id)
132            .ok_or_else(|| ContractError::NotFound(format!("RiskLimit {}", id)))?;
133        limit.increment(amount);
134        Ok(())
135    }
136
137    /// List all risk limits.
138    pub fn list_risk_limits(&self) -> &[RiskLimit] {
139        &self.file.risk_limits
140    }
141
142    // ── Approvals ──────────────────────────────────────────────────
143
144    /// Add an approval rule.
145    pub fn add_approval_rule(&mut self, rule: ApprovalRule) -> ContractId {
146        let id = rule.id;
147        self.file.approval_rules.push(rule);
148        id
149    }
150
151    /// Create an approval request.
152    pub fn request_approval(
153        &mut self,
154        rule_id: ContractId,
155        action_description: impl Into<String>,
156        requestor: impl Into<String>,
157    ) -> ContractResult<ContractId> {
158        // Verify rule exists
159        if !self.file.approval_rules.iter().any(|r| r.id == rule_id) {
160            return Err(ContractError::NotFound(format!("ApprovalRule {}", rule_id)));
161        }
162
163        let request = ApprovalRequest::new(rule_id, action_description, requestor);
164        let id = request.id;
165        self.file.approval_requests.push(request);
166        Ok(id)
167    }
168
169    /// Decide on an approval request.
170    pub fn decide_approval(
171        &mut self,
172        request_id: ContractId,
173        decision: DecisionType,
174        decider: impl Into<String>,
175        reason: impl Into<String>,
176    ) -> ContractResult<ContractId> {
177        // Update the request status
178        let request = self
179            .file
180            .approval_requests
181            .iter_mut()
182            .find(|r| r.id == request_id)
183            .ok_or_else(|| ContractError::NotFound(format!("ApprovalRequest {}", request_id)))?;
184
185        request.status = match decision {
186            DecisionType::Approve => ApprovalStatus::Approved,
187            DecisionType::Deny => ApprovalStatus::Denied,
188        };
189
190        let approval_decision = ApprovalDecision::new(request_id, decision, decider, reason);
191        let id = approval_decision.id;
192        self.file.approval_decisions.push(approval_decision);
193        Ok(id)
194    }
195
196    /// List approval requests, optionally filtered by status.
197    pub fn list_approval_requests(&self, status: Option<ApprovalStatus>) -> Vec<&ApprovalRequest> {
198        self.file
199            .approval_requests
200            .iter()
201            .filter(|r| status.is_none() || Some(r.status) == status)
202            .collect()
203    }
204
205    // ── Conditions ─────────────────────────────────────────────────
206
207    /// Add a condition.
208    pub fn add_condition(&mut self, condition: Condition) -> ContractId {
209        let id = condition.id;
210        self.file.conditions.push(condition);
211        id
212    }
213
214    /// Evaluate a condition (simplified: just check the status).
215    pub fn evaluate_condition(&self, id: ContractId) -> ContractResult<ConditionStatus> {
216        let condition = self
217            .file
218            .conditions
219            .iter()
220            .find(|c| c.id == id)
221            .ok_or_else(|| ContractError::NotFound(format!("Condition {}", id)))?;
222        Ok(condition.status)
223    }
224
225    /// List all conditions.
226    pub fn list_conditions(&self) -> &[Condition] {
227        &self.file.conditions
228    }
229
230    // ── Obligations ────────────────────────────────────────────────
231
232    /// Add an obligation.
233    pub fn add_obligation(&mut self, obligation: Obligation) -> ContractId {
234        let id = obligation.id;
235        self.file.obligations.push(obligation);
236        id
237    }
238
239    /// Check obligation status.
240    pub fn check_obligation(&self, id: ContractId) -> ContractResult<ObligationStatus> {
241        let obligation = self
242            .file
243            .find_obligation(id)
244            .ok_or_else(|| ContractError::NotFound(format!("Obligation {}", id)))?;
245
246        if obligation.is_overdue() {
247            Ok(ObligationStatus::Overdue)
248        } else {
249            Ok(obligation.status)
250        }
251    }
252
253    /// Fulfill an obligation.
254    pub fn fulfill_obligation(&mut self, id: ContractId) -> ContractResult<()> {
255        let obligation = self
256            .file
257            .find_obligation_mut(id)
258            .ok_or_else(|| ContractError::NotFound(format!("Obligation {}", id)))?;
259        obligation.fulfill();
260        Ok(())
261    }
262
263    /// List obligations, optionally filtered by status.
264    pub fn list_obligations(&self, status: Option<ObligationStatus>) -> Vec<&Obligation> {
265        self.file
266            .obligations
267            .iter()
268            .filter(|o| status.is_none() || Some(o.status) == status)
269            .collect()
270    }
271
272    // ── Violations ─────────────────────────────────────────────────
273
274    /// Report a violation.
275    pub fn report_violation(&mut self, violation: Violation) -> ContractId {
276        let id = violation.id;
277        self.file.violations.push(violation);
278        id
279    }
280
281    /// List violations, optionally filtered by severity.
282    pub fn list_violations(&self, severity: Option<ViolationSeverity>) -> Vec<&Violation> {
283        self.file
284            .violations
285            .iter()
286            .filter(|v| severity.is_none() || Some(v.severity) == severity)
287            .collect()
288    }
289
290    // ── Inventions ─────────────────────────────────────────────────
291
292    // ── 1. Policy Omniscience ─────────────────────────────────────
293
294    /// Get complete visibility into all applicable policies for an agent.
295    pub fn policy_omniscience(&self, agent_id: &str, context: &str) -> PolicyOmniscience {
296        let mut allowed = Vec::new();
297        let mut denied = Vec::new();
298        let mut conditional = Vec::new();
299
300        for policy in &self.file.policies {
301            if !policy.is_active() {
302                continue;
303            }
304            let entry = PermissionEntry {
305                action: policy.label.clone(),
306                policy_id: policy.id,
307                policy_label: policy.label.clone(),
308                reason: if policy.description.is_empty() {
309                    format!("{:?} policy", policy.action)
310                } else {
311                    policy.description.clone()
312                },
313                scope: format!("{:?}", policy.scope),
314            };
315            match policy.action {
316                PolicyAction::Allow => allowed.push(entry),
317                PolicyAction::Deny => denied.push(entry),
318                PolicyAction::RequireApproval => conditional.push(entry),
319                PolicyAction::AuditOnly => allowed.push(entry),
320            }
321        }
322
323        let total = (allowed.len() + denied.len() + conditional.len()) as u32;
324        PolicyOmniscience {
325            id: ContractId::new(),
326            agent_id: agent_id.to_string(),
327            context: context.to_string(),
328            allowed_actions: allowed,
329            denied_actions: denied,
330            conditional_actions: conditional,
331            total_permissions: total,
332            queried_at: Utc::now(),
333        }
334    }
335
336    // ── 2. Risk Prophecy ──────────────────────────────────────────
337
338    /// Predict future risk budget usage.
339    pub fn risk_prophecy(&self, agent_id: &str, forecast_window_secs: i64) -> RiskProphecy {
340        let mut projections = Vec::new();
341        let mut total_risk = 0.0;
342        let mut recommendations = Vec::new();
343
344        for limit in &self.file.risk_limits {
345            let usage = limit.usage_ratio();
346            // Linear projection: if current usage is X% over elapsed time,
347            // project forward at the same rate
348            let projected = (usage * 1.5).min(1.0);
349            let exceed_prob = if projected > 0.8 {
350                ((projected - 0.8) / 0.2).min(1.0)
351            } else {
352                0.0
353            };
354            let time_until = if usage > 0.0 && usage < 1.0 {
355                Some(((1.0 - usage) / usage * forecast_window_secs as f64) as i64)
356            } else {
357                None
358            };
359
360            if usage > 0.7 {
361                recommendations.push(format!(
362                    "Risk limit '{}' at {:.0}% — consider increasing max or reducing usage",
363                    limit.label,
364                    usage * 100.0
365                ));
366            }
367
368            total_risk += usage;
369            projections.push(RiskProjection {
370                limit_id: limit.id,
371                limit_label: limit.label.clone(),
372                current_usage: usage,
373                projected_usage: projected,
374                exceed_probability: exceed_prob,
375                time_until_limit_secs: time_until,
376            });
377        }
378
379        let count = projections.len().max(1) as f64;
380        RiskProphecy {
381            id: ContractId::new(),
382            agent_id: agent_id.to_string(),
383            forecast_window_secs,
384            projections,
385            overall_risk_score: (total_risk / count).min(1.0),
386            recommendations,
387            prophesied_at: Utc::now(),
388        }
389    }
390
391    // ── 3. Approval Telepathy ─────────────────────────────────────
392
393    /// Predict approval likelihood for an action.
394    pub fn approval_telepathy(&self, action: &str) -> ApprovalTelepathy {
395        let total_requests = self.file.approval_requests.len() as f64;
396        let approved_count = self
397            .file
398            .approval_requests
399            .iter()
400            .filter(|r| r.status == ApprovalStatus::Approved)
401            .count() as f64;
402        let historical_rate = if total_requests > 0.0 {
403            approved_count / total_requests
404        } else {
405            0.5 // no history, assume 50/50
406        };
407
408        // Check if the action would be denied by policy
409        let policy_result = self.check_policy(action, PolicyScope::Global);
410        let probability = match policy_result {
411            PolicyAction::Deny => 0.1,
412            PolicyAction::RequireApproval => historical_rate * 0.8,
413            PolicyAction::Allow => 0.95,
414            PolicyAction::AuditOnly => 0.9,
415        };
416
417        let mut suggestions = Vec::new();
418        if probability < 0.5 {
419            suggestions.push(ApprovalSuggestion {
420                modification: "Reduce scope of the action".to_string(),
421                new_probability: (probability + 0.2).min(1.0),
422                effort: "low".to_string(),
423            });
424            suggestions.push(ApprovalSuggestion {
425                modification: "Add risk mitigation documentation".to_string(),
426                new_probability: (probability + 0.3).min(1.0),
427                effort: "medium".to_string(),
428            });
429        }
430
431        ApprovalTelepathy {
432            id: ContractId::new(),
433            action: action.to_string(),
434            approval_probability: probability,
435            likely_approvers: vec!["admin".to_string()],
436            estimated_response_secs: 300,
437            suggestions,
438            historical_approval_rate: historical_rate,
439            predicted_at: Utc::now(),
440        }
441    }
442
443    // ── 4. Obligation Clairvoyance ────────────────────────────────
444
445    /// Forecast upcoming obligations and identify scheduling conflicts.
446    pub fn obligation_clairvoyance(
447        &self,
448        agent_id: &str,
449        window_secs: i64,
450    ) -> ObligationClairvoyance {
451        let now = Utc::now();
452        let mut upcoming = Vec::new();
453        let mut optimal_order = Vec::new();
454
455        for obligation in &self.file.obligations {
456            if obligation.status != ObligationStatus::Pending {
457                continue;
458            }
459            let time_remaining = obligation
460                .deadline
461                .map(|d| (d - now).num_seconds())
462                .filter(|&s| s > 0 && s <= window_secs);
463
464            let miss_risk = if let Some(remaining) = time_remaining {
465                // Higher risk for closer deadlines
466                1.0 - (remaining as f64 / window_secs as f64).min(1.0)
467            } else if obligation.deadline.is_some() {
468                // Already overdue
469                1.0
470            } else {
471                0.1 // No deadline
472            };
473
474            upcoming.push(ObligationForecast {
475                obligation_id: obligation.id,
476                label: obligation.label.clone(),
477                deadline: obligation.deadline,
478                time_remaining_secs: time_remaining,
479                estimated_effort_minutes: 30,
480                depends_on: Vec::new(),
481                miss_risk,
482            });
483            optimal_order.push(obligation.id);
484        }
485
486        // Sort by miss_risk descending for optimal order
487        upcoming.sort_by(|a, b| {
488            b.miss_risk
489                .partial_cmp(&a.miss_risk)
490                .unwrap_or(std::cmp::Ordering::Equal)
491        });
492        optimal_order = upcoming.iter().map(|f| f.obligation_id).collect();
493
494        ObligationClairvoyance {
495            id: ContractId::new(),
496            agent_id: agent_id.to_string(),
497            window_secs,
498            upcoming,
499            conflicts: Vec::new(),
500            optimal_order,
501            projected_at: Utc::now(),
502        }
503    }
504
505    // ── 5. Violation Precognition ─────────────────────────────────
506
507    /// Detect potential violations before they occur.
508    pub fn violation_precognition(&self, planned_action: &str) -> ViolationPrecognition {
509        let mut at_risk_policies = Vec::new();
510        let mut at_risk_limits = Vec::new();
511        let mut safe_alternatives = Vec::new();
512
513        for policy in &self.file.policies {
514            if !policy.is_active() {
515                continue;
516            }
517            if policy.action == PolicyAction::Deny {
518                let label_lower = policy.label.to_lowercase();
519                let action_lower = planned_action.to_lowercase();
520                if label_lower.contains(&action_lower) || action_lower.contains(&label_lower) {
521                    at_risk_policies.push(PolicyRisk {
522                        policy_id: policy.id,
523                        policy_label: policy.label.clone(),
524                        probability: 0.9,
525                        trigger: format!("Action '{}' matches deny policy", planned_action),
526                    });
527                }
528            }
529        }
530
531        for limit in &self.file.risk_limits {
532            let headroom = limit.remaining();
533            if limit.usage_ratio() > 0.8 {
534                at_risk_limits.push(LimitRisk {
535                    limit_id: limit.id,
536                    limit_label: limit.label.clone(),
537                    headroom,
538                    projected_usage: limit.usage_ratio() + 0.1,
539                });
540            }
541        }
542
543        if !at_risk_policies.is_empty() {
544            safe_alternatives.push(format!(
545                "Modify '{}' to avoid policy conflicts",
546                planned_action
547            ));
548            safe_alternatives.push("Request pre-approval before proceeding".to_string());
549        }
550
551        let violation_probability = if at_risk_policies.is_empty() && at_risk_limits.is_empty() {
552            0.05
553        } else {
554            let max_policy = at_risk_policies
555                .iter()
556                .map(|p| p.probability)
557                .fold(0.0f64, f64::max);
558            let max_limit = if at_risk_limits.is_empty() { 0.0 } else { 0.7 };
559            max_policy.max(max_limit)
560        };
561
562        ViolationPrecognition {
563            id: ContractId::new(),
564            planned_action: planned_action.to_string(),
565            at_risk_policies,
566            at_risk_limits,
567            safe_alternatives,
568            violation_probability,
569            analyzed_at: Utc::now(),
570        }
571    }
572
573    // ── 6. Contract Crystallization ───────────────────────────────
574
575    /// Generate contract policies from a high-level intent description.
576    pub fn crystallize_contract(&self, intent: &str) -> CrystallizedContract {
577        let intent_lower = intent.to_lowercase();
578        let mut policies = Vec::new();
579        let mut risk_limits = Vec::new();
580        let mut edge_cases = Vec::new();
581
582        // Pattern-match intent to generate policies
583        if intent_lower.contains("budget") || intent_lower.contains("spend") {
584            policies.push(CrystallizedPolicy {
585                label: "Budget control policy".to_string(),
586                scope: "global".to_string(),
587                action: "require_approval".to_string(),
588                rationale: "Spending actions should require approval".to_string(),
589            });
590            risk_limits.push(CrystallizedRiskLimit {
591                label: "Spending budget".to_string(),
592                max_value: 1000.0,
593                limit_type: "budget".to_string(),
594                rationale: "Cap total spending to prevent overruns".to_string(),
595            });
596            edge_cases.push("What happens when budget is exactly at limit?".to_string());
597        }
598
599        if intent_lower.contains("deploy") || intent_lower.contains("production") {
600            policies.push(CrystallizedPolicy {
601                label: "Production deployment gate".to_string(),
602                scope: "global".to_string(),
603                action: "require_approval".to_string(),
604                rationale: "Production deployments need human approval".to_string(),
605            });
606            edge_cases.push("Emergency hotfix deployment scenario".to_string());
607        }
608
609        if intent_lower.contains("rate") || intent_lower.contains("api") {
610            risk_limits.push(CrystallizedRiskLimit {
611                label: "API rate limit".to_string(),
612                max_value: 100.0,
613                limit_type: "rate".to_string(),
614                rationale: "Prevent API abuse".to_string(),
615            });
616        }
617
618        if intent_lower.contains("safe") || intent_lower.contains("restrict") {
619            policies.push(CrystallizedPolicy {
620                label: "Restrictive default policy".to_string(),
621                scope: "global".to_string(),
622                action: "deny".to_string(),
623                rationale: "Default deny for unrecognized actions".to_string(),
624            });
625        }
626
627        // Always add a baseline policy
628        if policies.is_empty() {
629            policies.push(CrystallizedPolicy {
630                label: format!("Governance policy for: {}", intent),
631                scope: "global".to_string(),
632                action: "audit_only".to_string(),
633                rationale: "Baseline governance with audit trail".to_string(),
634            });
635        }
636
637        let confidence = if policies.len() > 1 { 0.75 } else { 0.5 };
638
639        CrystallizedContract {
640            id: ContractId::new(),
641            intent: intent.to_string(),
642            policies,
643            risk_limits,
644            approval_workflows: Vec::new(),
645            edge_cases,
646            confidence,
647            crystallized_at: Utc::now(),
648        }
649    }
650
651    // ── 7. Policy DNA ─────────────────────────────────────────────
652
653    /// Extract the genetic representation of a policy.
654    pub fn extract_policy_dna(&self, policy_id: ContractId) -> ContractResult<PolicyDna> {
655        let policy = self.get_policy(policy_id)?;
656
657        let scope_breadth = match policy.scope {
658            PolicyScope::Global => 1.0,
659            PolicyScope::Session => 0.5,
660            PolicyScope::Agent => 0.3,
661        };
662        let restriction = match policy.action {
663            PolicyAction::Deny => 1.0,
664            PolicyAction::RequireApproval => 0.7,
665            PolicyAction::AuditOnly => 0.3,
666            PolicyAction::Allow => 0.0,
667        };
668
669        let genes = vec![
670            PolicyGene {
671                name: "scope_breadth".to_string(),
672                value: scope_breadth,
673                dominant: scope_breadth > 0.5,
674            },
675            PolicyGene {
676                name: "restriction_level".to_string(),
677                value: restriction,
678                dominant: restriction > 0.5,
679            },
680            PolicyGene {
681                name: "tag_complexity".to_string(),
682                value: (policy.tags.len() as f64 / 10.0).min(1.0),
683                dominant: false,
684            },
685        ];
686
687        // Fitness based on violation count for this policy
688        let violations_for_policy = self
689            .file
690            .violations
691            .iter()
692            .filter(|v| v.policy_id == Some(policy_id))
693            .count();
694        let fitness = if violations_for_policy == 0 {
695            0.9
696        } else {
697            (1.0 - violations_for_policy as f64 * 0.1).max(0.1)
698        };
699
700        Ok(PolicyDna {
701            id: ContractId::new(),
702            policy_id,
703            genes,
704            fitness,
705            generation: 1,
706            mutations: Vec::new(),
707            extracted_at: Utc::now(),
708        })
709    }
710
711    // ── 8. Trust Gradients ────────────────────────────────────────
712
713    /// Evaluate an action with trust-weighted policy assessment.
714    pub fn evaluate_trust_gradient(&self, agent_id: &str, action: &str) -> TrustGradient {
715        // Compute trust based on violation history
716        let agent_violations = self
717            .file
718            .violations
719            .iter()
720            .filter(|v| v.actor == agent_id)
721            .count();
722        let trust_factor = (1.0 - agent_violations as f64 * 0.15).max(0.0);
723
724        let monitoring_level = if trust_factor > 0.8 {
725            MonitoringLevel::Minimal
726        } else if trust_factor > 0.5 {
727            MonitoringLevel::Standard
728        } else if trust_factor > 0.2 {
729            MonitoringLevel::Enhanced
730        } else {
731            MonitoringLevel::FullAudit
732        };
733
734        let factors = vec![
735            TrustFactor {
736                name: "violation_history".to_string(),
737                weight: 0.5,
738                score: trust_factor,
739                trend: 0.0,
740            },
741            TrustFactor {
742                name: "policy_compliance".to_string(),
743                weight: 0.3,
744                score: if agent_violations == 0 { 1.0 } else { 0.5 },
745                trend: 0.0,
746            },
747            TrustFactor {
748                name: "approval_track_record".to_string(),
749                weight: 0.2,
750                score: 0.7,
751                trend: 0.0,
752            },
753        ];
754
755        TrustGradient {
756            id: ContractId::new(),
757            agent_id: agent_id.to_string(),
758            action: action.to_string(),
759            trust_factor,
760            confidence: 0.7,
761            monitoring_level,
762            auto_revoke_threshold: 0.2,
763            contributing_factors: factors,
764            evaluated_at: Utc::now(),
765        }
766    }
767
768    // ── 9. Collective Contracts ───────────────────────────────────
769
770    /// Create a multi-party collective governance contract.
771    pub fn create_collective_contract(
772        &self,
773        parties: Vec<(&str, &str)>,
774        arbitration_method: ArbitrationMethod,
775    ) -> CollectiveContract {
776        let party_list: Vec<ContractParty> = parties
777            .iter()
778            .map(|(id, name)| ContractParty {
779                party_id: id.to_string(),
780                name: name.to_string(),
781                role: "member".to_string(),
782                signed: false,
783                signed_at: None,
784            })
785            .collect();
786        let required = party_list.len() as u32;
787
788        CollectiveContract {
789            id: ContractId::new(),
790            parties: party_list,
791            shared_policies: self.file.policies.iter().map(|p| p.id).collect(),
792            arbitration: ArbitrationRules {
793                method: arbitration_method,
794                timeout_secs: 86400,
795                arbitrator: None,
796            },
797            status: CollectiveStatus::Pending,
798            signatures: 0,
799            required_signatures: required,
800            created_at: Utc::now(),
801        }
802    }
803
804    // ── 10. Temporal Contracts ────────────────────────────────────
805
806    /// Create a time-evolving contract with governance transitions.
807    pub fn create_temporal_contract(
808        &self,
809        label: &str,
810        initial_level: GovernanceLevel,
811    ) -> TemporalContract {
812        TemporalContract {
813            id: ContractId::new(),
814            label: label.to_string(),
815            initial_level,
816            current_level: initial_level,
817            transitions: Vec::new(),
818            transition_conditions: Vec::new(),
819            performance_history: Vec::new(),
820            created_at: Utc::now(),
821        }
822    }
823
824    // ── 11. Contract Inheritance ──────────────────────────────────
825
826    /// Create a hierarchical parent-child contract relationship.
827    pub fn create_contract_inheritance(
828        &self,
829        parent_id: ContractId,
830        child_id: ContractId,
831        propagate: bool,
832    ) -> ContractResult<ContractInheritance> {
833        // Verify both exist
834        self.get_policy(parent_id)?;
835        self.get_policy(child_id)?;
836
837        let inherited_policies = vec![parent_id];
838        Ok(ContractInheritance {
839            id: ContractId::new(),
840            parent_id,
841            child_id,
842            inherited_policies,
843            overrides: Vec::new(),
844            propagate_changes: propagate,
845            created_at: Utc::now(),
846        })
847    }
848
849    // ── 12. Smart Escalation ─────────────────────────────────────
850
851    /// Route an approval request to the optimal approver.
852    pub fn smart_escalate(&self, description: &str, urgency: f64) -> SmartEscalation {
853        let fallback = vec![
854            EscalationTarget {
855                approver_id: "admin".to_string(),
856                name: "System Admin".to_string(),
857                availability: 0.9,
858                avg_response_secs: 300,
859                approval_rate: 0.8,
860            },
861            EscalationTarget {
862                approver_id: "manager".to_string(),
863                name: "Team Manager".to_string(),
864                availability: 0.7,
865                avg_response_secs: 600,
866                approval_rate: 0.7,
867            },
868        ];
869
870        let recommended = if urgency > 0.8 {
871            "admin".to_string()
872        } else {
873            "manager".to_string()
874        };
875
876        let routing_reason = if urgency > 0.8 {
877            "High urgency — routing to admin for fastest response".to_string()
878        } else {
879            "Standard urgency — routing to team manager".to_string()
880        };
881
882        SmartEscalation {
883            id: ContractId::new(),
884            request_description: description.to_string(),
885            urgency,
886            recommended_approver: recommended,
887            routing_reason,
888            fallback_chain: fallback,
889            estimated_response_secs: if urgency > 0.8 { 300 } else { 600 },
890            confidence: 0.75,
891            routed_at: Utc::now(),
892        }
893    }
894
895    // ── 13. Violation Archaeology ─────────────────────────────────
896
897    /// Analyze violation patterns to identify root causes.
898    pub fn violation_archaeology(&self, agent_id: &str, window_secs: i64) -> ViolationArchaeology {
899        let now = Utc::now();
900        let agent_violations: Vec<&Violation> = self
901            .file
902            .violations
903            .iter()
904            .filter(|v| v.actor == agent_id && (now - v.detected_at).num_seconds() <= window_secs)
905            .collect();
906
907        // Cluster by severity
908        let mut severity_counts: std::collections::HashMap<String, u32> =
909            std::collections::HashMap::new();
910        for v in &agent_violations {
911            *severity_counts
912                .entry(format!("{:?}", v.severity))
913                .or_insert(0) += 1;
914        }
915
916        let clusters: Vec<ViolationCluster> = severity_counts
917            .iter()
918            .map(|(severity, count)| ViolationCluster {
919                label: format!("{} violations", severity),
920                count: *count,
921                severity: severity.clone(),
922                time_pattern: None,
923                context_pattern: None,
924            })
925            .collect();
926
927        let root_causes = if !agent_violations.is_empty() {
928            vec![RootCause {
929                hypothesis: "Agent may be exceeding operational boundaries".to_string(),
930                confidence: 0.6,
931                evidence: agent_violations
932                    .iter()
933                    .take(3)
934                    .map(|v| v.description.clone())
935                    .collect(),
936                factors: vec!["Policy awareness".to_string(), "Rate limiting".to_string()],
937            }]
938        } else {
939            Vec::new()
940        };
941
942        let recommendations = if !agent_violations.is_empty() {
943            vec![
944                Remediation {
945                    action: "Review and update agent policy awareness".to_string(),
946                    expected_impact: "Reduce violations by 40%".to_string(),
947                    effort: "medium".to_string(),
948                    priority: 1,
949                },
950                Remediation {
951                    action: "Add pre-action violation checks".to_string(),
952                    expected_impact: "Prevent repeat violations".to_string(),
953                    effort: "low".to_string(),
954                    priority: 2,
955                },
956            ]
957        } else {
958            Vec::new()
959        };
960
961        ViolationArchaeology {
962            id: ContractId::new(),
963            agent_id: agent_id.to_string(),
964            window_secs,
965            clusters,
966            root_causes,
967            recommendations,
968            policy_adjustments: Vec::new(),
969            analyzed_at: Utc::now(),
970        }
971    }
972
973    // ── 14. Contract Simulation ───────────────────────────────────
974
975    /// Simulate contract behavior across multiple scenarios.
976    pub fn simulate_contract(&self, scenario_count: u32) -> ContractSimulation {
977        let total_policies = self.file.policies.len() as f64;
978        let deny_count = self
979            .file
980            .policies
981            .iter()
982            .filter(|p| p.action == PolicyAction::Deny && p.is_active())
983            .count() as f64;
984        let approval_count = self
985            .file
986            .policies
987            .iter()
988            .filter(|p| p.action == PolicyAction::RequireApproval && p.is_active())
989            .count() as f64;
990
991        let denial_rate = if total_policies > 0.0 {
992            deny_count / total_policies
993        } else {
994            0.0
995        };
996        let approval_rate = 1.0 - denial_rate;
997
998        // Check for potential deadlocks (conflicting policies)
999        let mut deadlocks = Vec::new();
1000        for (i, p1) in self.file.policies.iter().enumerate() {
1001            for p2 in self.file.policies.iter().skip(i + 1) {
1002                if p1.action == PolicyAction::Allow
1003                    && p2.action == PolicyAction::Deny
1004                    && p1.scope == p2.scope
1005                {
1006                    deadlocks.push(SimulationDeadlock {
1007                        description: format!(
1008                            "Policy '{}' allows but '{}' denies in same scope",
1009                            p1.label, p2.label
1010                        ),
1011                        policies_involved: vec![p1.id, p2.id],
1012                        resolution: "Clarify policy precedence rules".to_string(),
1013                    });
1014                }
1015            }
1016        }
1017
1018        let risk_breach_rate = self
1019            .file
1020            .risk_limits
1021            .iter()
1022            .filter(|l| l.usage_ratio() > 0.9)
1023            .count() as f64
1024            / self.file.risk_limits.len().max(1) as f64;
1025
1026        let health = (approval_rate * 0.4
1027            + (1.0 - risk_breach_rate) * 0.3
1028            + if deadlocks.is_empty() { 0.3 } else { 0.0 })
1029        .min(1.0);
1030
1031        let mut edge_cases = Vec::new();
1032        if approval_count > 0.0 && self.file.approval_rules.is_empty() {
1033            edge_cases.push(SimulationEdgeCase {
1034                description: "Policies require approval but no approval rules exist".to_string(),
1035                current_behavior: "Requests will fail with missing rule".to_string(),
1036                suggested_fix: "Add approval rules for require_approval policies".to_string(),
1037            });
1038        }
1039
1040        ContractSimulation {
1041            id: ContractId::new(),
1042            scenario_count,
1043            approval_rate,
1044            denial_rate,
1045            risk_breach_rate,
1046            deadlocks,
1047            edge_cases,
1048            health_score: health,
1049            simulated_at: Utc::now(),
1050        }
1051    }
1052
1053    // ── 15. Federated Governance ──────────────────────────────────
1054
1055    /// Create cross-organizational federated governance.
1056    pub fn create_federated_governance(
1057        &self,
1058        name: &str,
1059        members: Vec<(&str, &str)>,
1060        transparency: TransparencyLevel,
1061    ) -> FederatedGovernance {
1062        let member_list: Vec<FederationMember> = members
1063            .iter()
1064            .map(|(id, org_name)| FederationMember {
1065                org_id: id.to_string(),
1066                name: org_name.to_string(),
1067                contributed_policies: 0,
1068                trust_level: 0.5,
1069                ratified: false,
1070            })
1071            .collect();
1072
1073        FederatedGovernance {
1074            id: ContractId::new(),
1075            name: name.to_string(),
1076            members: member_list,
1077            shared_policies: Vec::new(),
1078            transparency,
1079            status: FederationStatus::Forming,
1080            created_at: Utc::now(),
1081        }
1082    }
1083
1084    // ── 16. Self-Healing Contracts ────────────────────────────────
1085
1086    /// Create a contract that automatically adapts to violations.
1087    pub fn create_self_healing_contract(
1088        &self,
1089        base_contract_id: ContractId,
1090    ) -> ContractResult<SelfHealingContract> {
1091        // Verify base contract exists
1092        self.get_policy(base_contract_id)?;
1093
1094        let healing_rules = vec![
1095            HealingRule {
1096                trigger: HealingTrigger::RepeatedViolation { count: 3 },
1097                action: HealingAction::TightenPolicy,
1098                cooldown_secs: 3600,
1099                last_triggered: None,
1100            },
1101            HealingRule {
1102                trigger: HealingTrigger::PerfectRecord {
1103                    duration_secs: 604800,
1104                },
1105                action: HealingAction::RelaxPolicy,
1106                cooldown_secs: 86400,
1107                last_triggered: None,
1108            },
1109            HealingRule {
1110                trigger: HealingTrigger::RiskApproaching { threshold: 0.9 },
1111                action: HealingAction::AddMonitoring,
1112                cooldown_secs: 1800,
1113                last_triggered: None,
1114            },
1115        ];
1116
1117        Ok(SelfHealingContract {
1118            id: ContractId::new(),
1119            base_contract_id,
1120            healing_rules,
1121            healing_history: Vec::new(),
1122            adaptation_level: AdaptationLevel::Original,
1123            health_score: 1.0,
1124            created_at: Utc::now(),
1125        })
1126    }
1127
1128    // ── Stats ──────────────────────────────────────────────────────
1129
1130    /// Get summary statistics.
1131    pub fn stats(&self) -> ContractStats {
1132        ContractStats {
1133            policy_count: self.file.policies.len(),
1134            active_policy_count: self.file.policies.iter().filter(|p| p.is_active()).count(),
1135            risk_limit_count: self.file.risk_limits.len(),
1136            approval_rule_count: self.file.approval_rules.len(),
1137            pending_approval_count: self
1138                .file
1139                .approval_requests
1140                .iter()
1141                .filter(|r| r.is_pending())
1142                .count(),
1143            condition_count: self.file.conditions.len(),
1144            obligation_count: self.file.obligations.len(),
1145            pending_obligation_count: self
1146                .file
1147                .obligations
1148                .iter()
1149                .filter(|o| o.status == ObligationStatus::Pending)
1150                .count(),
1151            violation_count: self.file.violations.len(),
1152            critical_violation_count: self
1153                .file
1154                .violations
1155                .iter()
1156                .filter(|v| v.severity >= ViolationSeverity::Critical)
1157                .count(),
1158            total_entities: self.file.total_entities(),
1159        }
1160    }
1161}
1162
1163impl Default for ContractEngine {
1164    fn default() -> Self {
1165        Self::new()
1166    }
1167}
1168
1169/// Summary statistics for a contract store.
1170#[derive(Debug, Clone, Serialize, Deserialize)]
1171pub struct ContractStats {
1172    pub policy_count: usize,
1173    pub active_policy_count: usize,
1174    pub risk_limit_count: usize,
1175    pub approval_rule_count: usize,
1176    pub pending_approval_count: usize,
1177    pub condition_count: usize,
1178    pub obligation_count: usize,
1179    pub pending_obligation_count: usize,
1180    pub violation_count: usize,
1181    pub critical_violation_count: usize,
1182    pub total_entities: usize,
1183}
1184
1185#[cfg(test)]
1186mod tests {
1187    use super::*;
1188    use crate::risk_limit::LimitType;
1189
1190    #[test]
1191    fn test_engine_lifecycle() {
1192        let mut engine = ContractEngine::new();
1193
1194        // Add policy
1195        let policy = Policy::new(
1196            "No deploys on Friday",
1197            PolicyScope::Global,
1198            PolicyAction::Deny,
1199        );
1200        let policy_id = engine.add_policy(policy);
1201        assert!(engine.get_policy(policy_id).is_ok());
1202
1203        // Check policy
1204        let action = engine.check_policy("deploy", PolicyScope::Global);
1205        assert_eq!(action, PolicyAction::Deny);
1206
1207        // Add risk limit
1208        let limit = RiskLimit::new("API calls", LimitType::Rate, 100.0);
1209        let limit_id = engine.add_risk_limit(limit);
1210        assert!(engine.check_risk_limit("api", 50.0).is_none());
1211
1212        engine.increment_risk_limit(limit_id, 90.0).unwrap();
1213        assert!(engine.check_risk_limit("api", 20.0).is_some());
1214
1215        // Stats
1216        let stats = engine.stats();
1217        assert_eq!(stats.policy_count, 1);
1218        assert_eq!(stats.risk_limit_count, 1);
1219    }
1220
1221    #[test]
1222    fn test_approval_workflow() {
1223        let mut engine = ContractEngine::new();
1224
1225        let rule = ApprovalRule::new("Deploy approval", "deploy:*");
1226        let rule_id = engine.add_approval_rule(rule);
1227
1228        let request_id = engine
1229            .request_approval(rule_id, "Deploy v2.0 to production", "agent_1")
1230            .unwrap();
1231
1232        let pending = engine.list_approval_requests(Some(ApprovalStatus::Pending));
1233        assert_eq!(pending.len(), 1);
1234
1235        engine
1236            .decide_approval(request_id, DecisionType::Approve, "admin", "LGTM")
1237            .unwrap();
1238
1239        let approved = engine.list_approval_requests(Some(ApprovalStatus::Approved));
1240        assert_eq!(approved.len(), 1);
1241    }
1242
1243    #[test]
1244    fn test_violation_reporting() {
1245        let mut engine = ContractEngine::new();
1246
1247        let v = Violation::new("Rate limit exceeded", ViolationSeverity::Warning, "agent_1");
1248        engine.report_violation(v);
1249
1250        let v2 = Violation::new(
1251            "Unauthorized access",
1252            ViolationSeverity::Critical,
1253            "agent_2",
1254        );
1255        engine.report_violation(v2);
1256
1257        assert_eq!(engine.list_violations(None).len(), 2);
1258        assert_eq!(
1259            engine
1260                .list_violations(Some(ViolationSeverity::Critical))
1261                .len(),
1262            1
1263        );
1264
1265        let stats = engine.stats();
1266        assert_eq!(stats.critical_violation_count, 1);
1267    }
1268}