1use serde::{Deserialize, Serialize};
8
9use crate::identifiers::PolicyVersion;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "snake_case")]
14#[non_exhaustive]
15pub enum ApplyMode {
16 StageRunStart,
18 StageRunBoundary,
20 InjectNow,
22 Ignore,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(rename_all = "snake_case")]
29#[non_exhaustive]
30pub enum WakeMode {
31 WakeIfIdle,
33 None,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
39#[serde(rename_all = "snake_case")]
40#[non_exhaustive]
41pub enum QueueMode {
42 None,
44 Fifo,
46 Coalesce,
48 Supersede,
50 Priority,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
56#[serde(rename_all = "snake_case")]
57#[non_exhaustive]
58pub enum ConsumePoint {
59 OnAccept,
61 OnApply,
63 OnRunStart,
65 OnRunComplete,
67 ExplicitAck,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
73#[serde(rename_all = "snake_case")]
74#[non_exhaustive]
75pub enum DrainPolicy {
76 #[default]
77 QueueNextTurn,
78 SteerBatch,
79 Immediate,
80 Ignore,
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
85#[serde(rename_all = "snake_case")]
86#[non_exhaustive]
87pub enum RoutingDisposition {
88 #[default]
89 Queue,
90 Steer,
91 Immediate,
92 Drop,
93}
94
95#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
97pub struct PolicyDecision {
98 pub apply_mode: ApplyMode,
100 pub wake_mode: WakeMode,
102 pub queue_mode: QueueMode,
104 pub consume_point: ConsumePoint,
106 #[serde(default)]
108 pub drain_policy: DrainPolicy,
109 #[serde(default)]
111 pub routing_disposition: RoutingDisposition,
112 #[serde(default = "default_true")]
114 pub record_transcript: bool,
115 #[serde(default = "default_true")]
117 pub emit_operator_content: bool,
118 pub policy_version: PolicyVersion,
120}
121
122fn default_true() -> bool {
123 true
124}
125
126#[cfg(test)]
127#[allow(clippy::unwrap_used)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn apply_mode_serde() {
133 for mode in [
134 ApplyMode::StageRunStart,
135 ApplyMode::StageRunBoundary,
136 ApplyMode::InjectNow,
137 ApplyMode::Ignore,
138 ] {
139 let json = serde_json::to_value(mode).unwrap();
140 let parsed: ApplyMode = serde_json::from_value(json).unwrap();
141 assert_eq!(mode, parsed);
142 }
143 }
144
145 #[test]
146 fn wake_mode_serde() {
147 for mode in [WakeMode::WakeIfIdle, WakeMode::None] {
148 let json = serde_json::to_value(mode).unwrap();
149 let parsed: WakeMode = serde_json::from_value(json).unwrap();
150 assert_eq!(mode, parsed);
151 }
152 }
153
154 #[test]
155 fn queue_mode_serde() {
156 for mode in [
157 QueueMode::None,
158 QueueMode::Fifo,
159 QueueMode::Coalesce,
160 QueueMode::Supersede,
161 QueueMode::Priority,
162 ] {
163 let json = serde_json::to_value(mode).unwrap();
164 let parsed: QueueMode = serde_json::from_value(json).unwrap();
165 assert_eq!(mode, parsed);
166 }
167 }
168
169 #[test]
170 fn consume_point_serde() {
171 for point in [
172 ConsumePoint::OnAccept,
173 ConsumePoint::OnApply,
174 ConsumePoint::OnRunStart,
175 ConsumePoint::OnRunComplete,
176 ConsumePoint::ExplicitAck,
177 ] {
178 let json = serde_json::to_value(point).unwrap();
179 let parsed: ConsumePoint = serde_json::from_value(json).unwrap();
180 assert_eq!(point, parsed);
181 }
182 }
183
184 #[test]
185 fn drain_policy_serde() {
186 for policy in [
187 DrainPolicy::QueueNextTurn,
188 DrainPolicy::SteerBatch,
189 DrainPolicy::Immediate,
190 DrainPolicy::Ignore,
191 ] {
192 let json = serde_json::to_value(policy).unwrap();
193 let parsed: DrainPolicy = serde_json::from_value(json).unwrap();
194 assert_eq!(policy, parsed);
195 }
196 }
197
198 #[test]
199 fn routing_disposition_serde() {
200 for disposition in [
201 RoutingDisposition::Queue,
202 RoutingDisposition::Steer,
203 RoutingDisposition::Immediate,
204 RoutingDisposition::Drop,
205 ] {
206 let json = serde_json::to_value(disposition).unwrap();
207 let parsed: RoutingDisposition = serde_json::from_value(json).unwrap();
208 assert_eq!(disposition, parsed);
209 }
210 }
211
212 #[test]
213 fn policy_decision_serde_roundtrip() {
214 let decision = PolicyDecision {
215 apply_mode: ApplyMode::StageRunStart,
216 wake_mode: WakeMode::WakeIfIdle,
217 queue_mode: QueueMode::Fifo,
218 consume_point: ConsumePoint::OnRunComplete,
219 drain_policy: DrainPolicy::QueueNextTurn,
220 routing_disposition: RoutingDisposition::Queue,
221 record_transcript: true,
222 emit_operator_content: true,
223 policy_version: PolicyVersion(1),
224 };
225 let json = serde_json::to_value(&decision).unwrap();
226 let parsed: PolicyDecision = serde_json::from_value(json).unwrap();
227 assert_eq!(decision, parsed);
228 }
229
230 #[test]
231 fn policy_decision_ignore_on_accept() {
232 let decision = PolicyDecision {
233 apply_mode: ApplyMode::Ignore,
234 wake_mode: WakeMode::None,
235 queue_mode: QueueMode::None,
236 consume_point: ConsumePoint::OnAccept,
237 drain_policy: DrainPolicy::Ignore,
238 routing_disposition: RoutingDisposition::Drop,
239 record_transcript: false,
240 emit_operator_content: false,
241 policy_version: PolicyVersion(1),
242 };
243 let json = serde_json::to_value(&decision).unwrap();
244 let parsed: PolicyDecision = serde_json::from_value(json).unwrap();
245 assert_eq!(decision, parsed);
246 }
247
248 #[test]
249 fn record_transcript_defaults_true() {
250 let json = serde_json::json!({
251 "apply_mode": "stage_run_start",
252 "wake_mode": "wake_if_idle",
253 "queue_mode": "fifo",
254 "consume_point": "on_run_complete",
255 "policy_version": 1
256 });
257 let parsed: PolicyDecision = serde_json::from_value(json).unwrap();
258 assert!(parsed.record_transcript);
259 assert!(parsed.emit_operator_content);
260 }
261}