vex_adversarial/
consensus.rs1use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Vote {
9 pub agent_id: Uuid,
11 pub agrees: bool,
13 pub confidence: f64,
15 pub reasoning: Option<String>,
17}
18
19impl Vote {
20 pub fn new(agent_id: &str, agrees: bool, confidence: f64) -> Self {
23 use sha2::{Digest, Sha256};
24
25 let mut hasher = Sha256::new();
27 hasher.update(agent_id.as_bytes());
28 let hash = hasher.finalize();
29
30 let mut bytes = [0u8; 16];
32 bytes.copy_from_slice(&hash[..16]);
33
34 Self {
35 agent_id: Uuid::from_bytes(bytes),
36 agrees,
37 confidence,
38 reasoning: None,
39 }
40 }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
45pub enum ConsensusProtocol {
46 Majority,
48 SuperMajority,
50 Unanimous,
52 WeightedConfidence,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct Consensus {
59 pub protocol: ConsensusProtocol,
61 pub votes: Vec<Vote>,
63 pub reached: bool,
65 pub decision: Option<bool>,
67 pub confidence: f64,
69}
70
71impl Consensus {
72 pub fn new(protocol: ConsensusProtocol) -> Self {
74 Self {
75 protocol,
76 votes: Vec::new(),
77 reached: false,
78 decision: None,
79 confidence: 0.0,
80 }
81 }
82
83 pub fn add_vote(&mut self, vote: Vote) {
85 self.votes.push(vote);
86 }
87
88 pub fn evaluate(&mut self) {
90 if self.votes.is_empty() {
91 return;
92 }
93
94 let total = self.votes.len() as f64;
95 let agrees: f64 = self.votes.iter().filter(|v| v.agrees).count() as f64;
96 if total == 0.0 {
97 self.reached = false;
98 self.decision = None;
99 return;
100 }
101
102 let agree_ratio = agrees / total;
103
104 let (reached, decision) = match self.protocol {
105 ConsensusProtocol::Majority => (agree_ratio != 0.5, Some(agree_ratio > 0.5)),
106 ConsensusProtocol::SuperMajority => {
107 if agree_ratio > 0.66 {
108 (true, Some(true))
109 } else if agree_ratio < 0.34 {
110 (true, Some(false))
111 } else {
112 (false, None)
113 }
114 }
115 ConsensusProtocol::Unanimous => {
116 if agree_ratio == 1.0 {
117 (true, Some(true))
118 } else if agree_ratio == 0.0 {
119 (true, Some(false))
120 } else {
121 (false, None)
122 }
123 }
124 ConsensusProtocol::WeightedConfidence => {
125 let weighted_agree: f64 = self
126 .votes
127 .iter()
128 .filter(|v| v.agrees)
129 .map(|v| v.confidence)
130 .sum();
131 let weighted_disagree: f64 = self
132 .votes
133 .iter()
134 .filter(|v| !v.agrees)
135 .map(|v| v.confidence)
136 .sum();
137 let total_confidence = weighted_agree + weighted_disagree;
138
139 if total_confidence > 0.0 {
140 let weighted_ratio = weighted_agree / total_confidence;
141 (true, Some(weighted_ratio > 0.5))
142 } else {
143 (false, None)
144 }
145 }
146 };
147
148 self.reached = reached;
149 self.decision = decision;
150 if total == 0.0 {
151 self.confidence = 0.0;
152 } else {
153 self.confidence = self.votes.iter().map(|v| v.confidence).sum::<f64>() / total;
154 }
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn test_majority_consensus() {
164 let mut consensus = Consensus::new(ConsensusProtocol::Majority);
165
166 consensus.add_vote(Vote {
167 agent_id: Uuid::new_v4(),
168 agrees: true,
169 confidence: 0.9,
170 reasoning: None,
171 });
172 consensus.add_vote(Vote {
173 agent_id: Uuid::new_v4(),
174 agrees: true,
175 confidence: 0.8,
176 reasoning: None,
177 });
178 consensus.add_vote(Vote {
179 agent_id: Uuid::new_v4(),
180 agrees: false,
181 confidence: 0.7,
182 reasoning: None,
183 });
184
185 consensus.evaluate();
186
187 assert!(consensus.reached);
188 assert_eq!(consensus.decision, Some(true));
189 }
190
191 #[test]
192 fn test_unanimous_fails() {
193 let mut consensus = Consensus::new(ConsensusProtocol::Unanimous);
194
195 consensus.add_vote(Vote {
196 agent_id: Uuid::new_v4(),
197 agrees: true,
198 confidence: 0.9,
199 reasoning: None,
200 });
201 consensus.add_vote(Vote {
202 agent_id: Uuid::new_v4(),
203 agrees: false,
204 confidence: 0.8,
205 reasoning: None,
206 });
207
208 consensus.evaluate();
209
210 assert!(!consensus.reached);
211 assert_eq!(consensus.decision, None);
212 }
213}