rightkit_agent_engine_protocol/
effect_settlement.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum ExternalEffectSettlement {
11 NotStarted,
12 Running,
13 Succeeded,
14 Failed,
15 InterruptedUnknown,
16}
17
18impl ExternalEffectSettlement {
19 pub fn may_have_executed(self) -> bool {
20 matches!(
21 self,
22 Self::Running | Self::Succeeded | Self::Failed | Self::InterruptedUnknown
23 )
24 }
25
26 pub fn allows_automatic_non_idempotent_replay(self) -> bool {
30 matches!(self, Self::NotStarted)
31 }
32
33 pub fn requires_reconciliation(self) -> bool {
34 matches!(self, Self::InterruptedUnknown)
35 }
36}
37
38#[cfg(test)]
39mod tests {
40 use super::*;
41
42 #[test]
43 fn interrupted_unknown_is_never_blindly_replayed() {
44 let state = ExternalEffectSettlement::InterruptedUnknown;
45 assert!(state.may_have_executed());
46 assert!(state.requires_reconciliation());
47 assert!(!state.allows_automatic_non_idempotent_replay());
48 }
49
50 #[test]
51 fn only_known_not_started_is_default_replay_safe() {
52 for state in [
53 ExternalEffectSettlement::Running,
54 ExternalEffectSettlement::Succeeded,
55 ExternalEffectSettlement::Failed,
56 ExternalEffectSettlement::InterruptedUnknown,
57 ] {
58 assert!(!state.allows_automatic_non_idempotent_replay());
59 }
60 assert!(ExternalEffectSettlement::NotStarted.allows_automatic_non_idempotent_replay());
61 }
62}