1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(Clone, Debug, Serialize, Deserialize, Default)]
7pub struct DecisionPolicyRules {
8 #[serde(default)]
9 pub voting: VotingRules,
10 #[serde(default)]
11 pub objection_handling: ObjectionHandlingRules,
12 #[serde(default)]
13 pub evaluation: EvaluationRules,
14 #[serde(default)]
15 pub commitment: CommitmentRules,
16}
17
18#[derive(Clone, Debug, Serialize, Deserialize)]
19pub struct VotingRules {
20 #[serde(default = "default_algorithm")]
21 pub algorithm: String,
22 #[serde(default = "default_threshold")]
23 pub threshold: f64,
24 #[serde(default)]
25 pub quorum: QuorumRules,
26 #[serde(default)]
27 pub weights: HashMap<String, f64>,
28}
29
30impl Default for VotingRules {
31 fn default() -> Self {
32 Self {
33 algorithm: default_algorithm(),
34 threshold: default_threshold(),
35 quorum: QuorumRules::default(),
36 weights: HashMap::new(),
37 }
38 }
39}
40
41fn default_algorithm() -> String {
42 "none".into()
43}
44
45fn default_threshold() -> f64 {
46 0.5
47}
48
49#[derive(Clone, Debug, Serialize, Deserialize)]
51pub struct QuorumRules {
52 #[serde(default = "default_quorum_type", rename = "type")]
53 pub quorum_type: String,
54 #[serde(default)]
55 pub value: f64,
56}
57
58impl Default for QuorumRules {
59 fn default() -> Self {
60 Self {
61 quorum_type: default_quorum_type(),
62 value: 0.0,
63 }
64 }
65}
66
67fn default_quorum_type() -> String {
68 "count".into()
69}
70
71#[derive(Clone, Debug, Serialize, Deserialize)]
72pub struct ObjectionHandlingRules {
73 #[serde(default, alias = "critical_severity_vetoes")]
75 pub critical_severity_vetoes: bool,
76 #[serde(default = "default_veto_threshold")]
77 pub veto_threshold: u32,
78 #[serde(default)]
85 pub critical_objection_action: CriticalObjectionAction,
86}
87
88impl Default for ObjectionHandlingRules {
89 fn default() -> Self {
90 Self {
91 critical_severity_vetoes: false,
92 veto_threshold: default_veto_threshold(),
93 critical_objection_action: CriticalObjectionAction::default(),
94 }
95 }
96}
97
98fn default_veto_threshold() -> u32 {
99 1
100}
101
102#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
104#[serde(rename_all = "snake_case")]
105pub enum CriticalObjectionAction {
106 #[default]
109 Deny,
110 FinalizeDecline,
113 Hold,
118}
119
120#[derive(Clone, Debug, Serialize, Deserialize)]
121pub struct EvaluationRules {
122 #[serde(default)]
123 pub required_before_voting: bool,
124 #[serde(default)]
125 pub minimum_confidence: f64,
126}
127
128impl Default for EvaluationRules {
129 fn default() -> Self {
130 Self {
131 required_before_voting: false,
132 minimum_confidence: 0.0,
133 }
134 }
135}
136
137pub use super::CommitmentRules;
142
143#[derive(Clone, Debug, Serialize, Deserialize, Default)]
146pub struct ProposalPolicyRules {
147 #[serde(default)]
148 pub acceptance: ProposalAcceptanceRules,
149 #[serde(default)]
150 pub counter_proposal: CounterProposalRules,
151 #[serde(default)]
152 pub rejection: RejectionRules,
153 #[serde(default)]
154 pub commitment: CommitmentRules,
155}
156
157#[derive(Clone, Debug, Serialize, Deserialize)]
158pub struct ProposalAcceptanceRules {
159 #[serde(default = "default_acceptance_criterion")]
160 pub criterion: String,
161}
162
163impl Default for ProposalAcceptanceRules {
164 fn default() -> Self {
165 Self {
166 criterion: default_acceptance_criterion(),
167 }
168 }
169}
170
171fn default_acceptance_criterion() -> String {
172 "all_parties".into()
173}
174
175#[derive(Clone, Debug, Default, Serialize, Deserialize)]
176pub struct CounterProposalRules {
177 #[serde(default)]
178 pub max_rounds: usize,
179}
180
181#[derive(Clone, Debug, Default, Serialize, Deserialize)]
182pub struct RejectionRules {
183 #[serde(default)]
184 pub terminal_on_any_reject: bool,
185}
186
187#[derive(Clone, Debug, Serialize, Deserialize, Default)]
190pub struct TaskPolicyRules {
191 #[serde(default)]
192 pub assignment: TaskAssignmentRules,
193 #[serde(default)]
194 pub completion: TaskCompletionRules,
195 #[serde(default)]
196 pub commitment: CommitmentRules,
197}
198
199#[derive(Clone, Debug, Default, Serialize, Deserialize)]
200pub struct TaskAssignmentRules {
201 #[serde(default)]
202 pub allow_reassignment_on_reject: bool,
203}
204
205#[derive(Clone, Debug, Default, Serialize, Deserialize)]
206pub struct TaskCompletionRules {
207 #[serde(default)]
208 pub require_output: bool,
209}
210
211#[derive(Clone, Debug, Serialize, Deserialize, Default)]
214pub struct HandoffPolicyRules {
215 #[serde(default)]
216 pub acceptance: HandoffAcceptanceRules,
217 #[serde(default)]
218 pub commitment: CommitmentRules,
219}
220
221#[derive(Clone, Debug, Default, Serialize, Deserialize)]
222pub struct HandoffAcceptanceRules {
223 #[serde(default)]
224 pub implicit_accept_timeout_ms: u64,
225}
226
227#[derive(Clone, Debug, Serialize, Deserialize, Default)]
230pub struct QuorumPolicyRules {
231 #[serde(default)]
232 pub threshold: QuorumThreshold,
233 #[serde(default)]
234 pub abstention: AbstentionRules,
235 #[serde(default)]
236 pub commitment: CommitmentRules,
237}
238
239#[derive(Clone, Debug, Serialize, Deserialize)]
241pub struct QuorumThreshold {
242 #[serde(default = "default_threshold_type", rename = "type")]
243 pub threshold_type: String,
244 #[serde(default)]
245 pub value: f64,
246}
247
248impl Default for QuorumThreshold {
249 fn default() -> Self {
250 Self {
251 threshold_type: default_threshold_type(),
252 value: 0.0,
253 }
254 }
255}
256
257fn default_threshold_type() -> String {
258 "n_of_m".into()
259}
260
261#[derive(Clone, Debug, Serialize, Deserialize)]
262pub struct AbstentionRules {
263 #[serde(default)]
264 pub counts_toward_quorum: bool,
265 #[serde(default = "default_interpretation")]
266 pub interpretation: String,
267}
268
269impl Default for AbstentionRules {
270 fn default() -> Self {
271 Self {
272 counts_toward_quorum: false,
273 interpretation: default_interpretation(),
274 }
275 }
276}
277
278fn default_interpretation() -> String {
279 "neutral".into()
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285
286 #[test]
287 fn decision_policy_rules_defaults() {
288 let rules = DecisionPolicyRules::default();
289 assert_eq!(rules.voting.algorithm, "none");
290 assert!((rules.voting.threshold - 0.5).abs() < f64::EPSILON);
291 assert_eq!(rules.voting.quorum.quorum_type, "count");
292 assert!(!rules.objection_handling.critical_severity_vetoes);
293 assert_eq!(rules.objection_handling.veto_threshold, 1);
294 assert_eq!(
295 rules.objection_handling.critical_objection_action,
296 CriticalObjectionAction::Deny
297 );
298 assert!(!rules.commitment.allow_decline_over_approval);
299 assert!(!rules.evaluation.required_before_voting);
300 assert!((rules.evaluation.minimum_confidence).abs() < f64::EPSILON);
301 assert_eq!(rules.commitment.authority, "initiator_only");
302 assert!(rules.commitment.designated_roles.is_empty());
303 assert!(!rules.commitment.require_vote_quorum);
304 }
305
306 #[test]
307 fn decision_policy_rules_deserialization() {
308 let json = serde_json::json!({
309 "voting": {
310 "algorithm": "majority",
311 "threshold": 0.6,
312 "quorum": { "type": "percentage", "value": 75.0 },
313 "weights": { "agent://fraud": 2.0, "agent://growth": 1.0 }
314 },
315 "objection_handling": {
316 "critical_severity_vetoes": true,
317 "veto_threshold": 2
318 },
319 "evaluation": {
320 "required_before_voting": true,
321 "minimum_confidence": 0.8
322 },
323 "commitment": {
324 "authority": "designated_role",
325 "designated_roles": ["agent://lead"],
326 "require_vote_quorum": true
327 }
328 });
329
330 let rules: DecisionPolicyRules = serde_json::from_value(json).unwrap();
331 assert_eq!(rules.voting.algorithm, "majority");
332 assert!((rules.voting.threshold - 0.6).abs() < f64::EPSILON);
333 assert_eq!(rules.voting.quorum.quorum_type, "percentage");
334 assert!((rules.voting.quorum.value - 75.0).abs() < f64::EPSILON);
335 assert_eq!(*rules.voting.weights.get("agent://fraud").unwrap(), 2.0);
336 assert!(rules.objection_handling.critical_severity_vetoes);
337 assert_eq!(rules.objection_handling.veto_threshold, 2);
338 assert!(rules.evaluation.required_before_voting);
339 assert!((rules.evaluation.minimum_confidence - 0.8).abs() < f64::EPSILON);
340 assert_eq!(rules.commitment.authority, "designated_role");
341 assert_eq!(rules.commitment.designated_roles, vec!["agent://lead"]);
342 assert!(rules.commitment.require_vote_quorum);
343 }
344
345 #[test]
346 fn partial_deserialization_fills_defaults() {
347 let json = serde_json::json!({
348 "voting": { "algorithm": "unanimous" }
349 });
350 let rules: DecisionPolicyRules = serde_json::from_value(json).unwrap();
351 assert_eq!(rules.voting.algorithm, "unanimous");
352 assert!((rules.voting.threshold - 0.5).abs() < f64::EPSILON);
353 assert!(!rules.objection_handling.critical_severity_vetoes);
354 assert_eq!(rules.objection_handling.veto_threshold, 1);
355 }
356
357 #[test]
358 fn proposal_policy_rules_defaults() {
359 let rules = ProposalPolicyRules::default();
360 assert_eq!(rules.acceptance.criterion, "all_parties");
361 assert_eq!(rules.counter_proposal.max_rounds, 0);
362 assert!(!rules.rejection.terminal_on_any_reject);
363 assert_eq!(rules.commitment.authority, "initiator_only");
364 }
365
366 #[test]
367 fn proposal_policy_rules_deserialization() {
368 let json = serde_json::json!({
369 "acceptance": { "criterion": "counterparty" },
370 "counter_proposal": { "max_rounds": 3 },
371 "rejection": { "terminal_on_any_reject": true },
372 "commitment": { "authority": "any_participant" }
373 });
374 let rules: ProposalPolicyRules = serde_json::from_value(json).unwrap();
375 assert_eq!(rules.acceptance.criterion, "counterparty");
376 assert_eq!(rules.counter_proposal.max_rounds, 3);
377 assert!(rules.rejection.terminal_on_any_reject);
378 assert_eq!(rules.commitment.authority, "any_participant");
379 }
380
381 #[test]
382 fn task_policy_rules_defaults() {
383 let rules = TaskPolicyRules::default();
384 assert!(!rules.assignment.allow_reassignment_on_reject);
385 assert!(!rules.completion.require_output);
386 assert_eq!(rules.commitment.authority, "initiator_only");
387 }
388
389 #[test]
390 fn task_policy_rules_deserialization() {
391 let json = serde_json::json!({
392 "assignment": { "allow_reassignment_on_reject": true },
393 "completion": { "require_output": true },
394 "commitment": { "authority": "initiator_only" }
395 });
396 let rules: TaskPolicyRules = serde_json::from_value(json).unwrap();
397 assert!(rules.assignment.allow_reassignment_on_reject);
398 assert!(rules.completion.require_output);
399 }
400
401 #[test]
402 fn handoff_policy_rules_defaults() {
403 let rules = HandoffPolicyRules::default();
404 assert_eq!(rules.acceptance.implicit_accept_timeout_ms, 0);
405 assert_eq!(rules.commitment.authority, "initiator_only");
406 }
407
408 #[test]
409 fn handoff_policy_rules_deserialization() {
410 let json = serde_json::json!({
411 "acceptance": { "implicit_accept_timeout_ms": 5000 },
412 "commitment": { "authority": "any_participant" }
413 });
414 let rules: HandoffPolicyRules = serde_json::from_value(json).unwrap();
415 assert_eq!(rules.acceptance.implicit_accept_timeout_ms, 5000);
416 assert_eq!(rules.commitment.authority, "any_participant");
417 }
418
419 #[test]
420 fn quorum_policy_rules_defaults() {
421 let rules = QuorumPolicyRules::default();
422 assert_eq!(rules.threshold.threshold_type, "n_of_m");
423 assert!((rules.threshold.value).abs() < f64::EPSILON);
424 assert!(!rules.abstention.counts_toward_quorum);
425 assert_eq!(rules.abstention.interpretation, "neutral");
426 assert_eq!(rules.commitment.authority, "initiator_only");
427 }
428
429 #[test]
430 fn quorum_policy_rules_deserialization() {
431 let json = serde_json::json!({
432 "threshold": { "type": "percentage", "value": 75.0 },
433 "abstention": { "counts_toward_quorum": true, "interpretation": "implicit_reject" },
434 "commitment": { "authority": "initiator_only" }
435 });
436 let rules: QuorumPolicyRules = serde_json::from_value(json).unwrap();
437 assert_eq!(rules.threshold.threshold_type, "percentage");
438 assert!((rules.threshold.value - 75.0).abs() < f64::EPSILON);
439 assert!(rules.abstention.counts_toward_quorum);
440 assert_eq!(rules.abstention.interpretation, "implicit_reject");
441 }
442}