Skip to main content

meerkat_runtime/
accept.rs

1//! §14 AcceptOutcome — result of accepting an input.
2
3use meerkat_core::lifecycle::InputId;
4use serde::{Deserialize, Serialize};
5
6use crate::input_state::InputState;
7use crate::policy::PolicyDecision;
8
9/// Outcome of `RuntimeDriver::accept_input()`.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(tag = "outcome_type", rename_all = "snake_case")]
12#[non_exhaustive]
13#[allow(clippy::large_enum_variant)]
14pub enum AcceptOutcome {
15    /// Input was accepted and processing has begun.
16    Accepted {
17        /// The assigned input ID.
18        input_id: InputId,
19        /// The policy decision applied to this input.
20        policy: PolicyDecision,
21        /// Current input state.
22        state: InputState,
23    },
24    /// Input was deduplicated (idempotency key matched an existing input).
25    Deduplicated {
26        /// The new input ID that was deduplicated.
27        input_id: InputId,
28        /// The existing input ID that was matched.
29        existing_id: InputId,
30    },
31    /// Input was rejected (validation failed, durability violation, etc.).
32    Rejected {
33        /// Why the input was rejected.
34        reason: String,
35    },
36}
37
38impl AcceptOutcome {
39    /// Check if the input was accepted.
40    pub fn is_accepted(&self) -> bool {
41        matches!(self, Self::Accepted { .. })
42    }
43
44    /// Check if the input was deduplicated.
45    pub fn is_deduplicated(&self) -> bool {
46        matches!(self, Self::Deduplicated { .. })
47    }
48
49    /// Check if the input was rejected.
50    pub fn is_rejected(&self) -> bool {
51        matches!(self, Self::Rejected { .. })
52    }
53}
54
55#[cfg(test)]
56#[allow(clippy::unwrap_used)]
57mod tests {
58    use super::*;
59    use crate::identifiers::PolicyVersion;
60    use crate::policy::{ApplyMode, ConsumePoint, QueueMode, WakeMode};
61
62    #[test]
63    fn accepted_serde() {
64        let outcome = AcceptOutcome::Accepted {
65            input_id: InputId::new(),
66            policy: PolicyDecision {
67                apply_mode: ApplyMode::StageRunStart,
68                wake_mode: WakeMode::WakeIfIdle,
69                queue_mode: QueueMode::Fifo,
70                consume_point: ConsumePoint::OnRunComplete,
71                record_transcript: true,
72                emit_operator_content: true,
73                policy_version: PolicyVersion(1),
74            },
75            state: InputState::new_accepted(InputId::new()),
76        };
77        let json = serde_json::to_value(&outcome).unwrap();
78        assert_eq!(json["outcome_type"], "accepted");
79        let parsed: AcceptOutcome = serde_json::from_value(json).unwrap();
80        assert!(parsed.is_accepted());
81        assert!(!parsed.is_deduplicated());
82        assert!(!parsed.is_rejected());
83    }
84
85    #[test]
86    fn deduplicated_serde() {
87        let outcome = AcceptOutcome::Deduplicated {
88            input_id: InputId::new(),
89            existing_id: InputId::new(),
90        };
91        let json = serde_json::to_value(&outcome).unwrap();
92        assert_eq!(json["outcome_type"], "deduplicated");
93        let parsed: AcceptOutcome = serde_json::from_value(json).unwrap();
94        assert!(parsed.is_deduplicated());
95    }
96
97    #[test]
98    fn rejected_serde() {
99        let outcome = AcceptOutcome::Rejected {
100            reason: "durability violation".into(),
101        };
102        let json = serde_json::to_value(&outcome).unwrap();
103        assert_eq!(json["outcome_type"], "rejected");
104        let parsed: AcceptOutcome = serde_json::from_value(json).unwrap();
105        assert!(parsed.is_rejected());
106    }
107}