1use 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
19pub struct ContractEngine {
21 pub file: ContractFile,
23}
24
25impl ContractEngine {
26 pub fn new() -> Self {
28 Self {
29 file: ContractFile::new(),
30 }
31 }
32
33 pub fn from_file(file: ContractFile) -> Self {
35 Self { file }
36 }
37
38 pub fn open(path: impl Into<std::path::PathBuf>) -> ContractResult<Self> {
40 let file = ContractFile::open(path)?;
41 Ok(Self { file })
42 }
43
44 pub fn save(&self) -> ContractResult<()> {
46 self.file.save()
47 }
48
49 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 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 if policy.scope == PolicyScope::Global || policy.scope == scope {
71 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 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 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 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 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 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 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 pub fn list_risk_limits(&self) -> &[RiskLimit] {
139 &self.file.risk_limits
140 }
141
142 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 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 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 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 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 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 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 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 pub fn list_conditions(&self) -> &[Condition] {
227 &self.file.conditions
228 }
229
230 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 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 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 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 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 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 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 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 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 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 };
407
408 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 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 1.0 - (remaining as f64 / window_secs as f64).min(1.0)
467 } else if obligation.deadline.is_some() {
468 1.0
470 } else {
471 0.1 };
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 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 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 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 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 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 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 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 pub fn evaluate_trust_gradient(&self, agent_id: &str, action: &str) -> TrustGradient {
715 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 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 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 pub fn create_contract_inheritance(
828 &self,
829 parent_id: ContractId,
830 child_id: ContractId,
831 propagate: bool,
832 ) -> ContractResult<ContractInheritance> {
833 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 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 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 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 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 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 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 pub fn create_self_healing_contract(
1088 &self,
1089 base_contract_id: ContractId,
1090 ) -> ContractResult<SelfHealingContract> {
1091 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 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#[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 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 let action = engine.check_policy("deploy", PolicyScope::Global);
1205 assert_eq!(action, PolicyAction::Deny);
1206
1207 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 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}