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 InterruptYielding,
36 None,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(rename_all = "snake_case")]
43#[non_exhaustive]
44pub enum QueueMode {
45 None,
47 Fifo,
49 Coalesce,
51 Supersede,
53 Priority,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
59#[serde(rename_all = "snake_case")]
60#[non_exhaustive]
61pub enum ConsumePoint {
62 OnAccept,
64 OnApply,
66 OnRunStart,
68 OnRunComplete,
70 ExplicitAck,
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
76#[serde(rename_all = "snake_case")]
77#[non_exhaustive]
78pub enum InterruptPolicy {
79 #[default]
80 None,
81 InterruptYielding,
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
86#[serde(rename_all = "snake_case")]
87#[non_exhaustive]
88pub enum DrainPolicy {
89 #[default]
90 QueueNextTurn,
91 SteerBatch,
92 Immediate,
93 Ignore,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
98#[serde(rename_all = "snake_case")]
99#[non_exhaustive]
100pub enum RoutingDisposition {
101 #[default]
102 Queue,
103 Steer,
104 Immediate,
105 Drop,
106}
107
108#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
110pub struct PolicyDecision {
111 pub apply_mode: ApplyMode,
113 pub wake_mode: WakeMode,
115 pub queue_mode: QueueMode,
117 pub consume_point: ConsumePoint,
119 #[serde(default)]
121 pub interrupt_policy: InterruptPolicy,
122 #[serde(default)]
124 pub drain_policy: DrainPolicy,
125 #[serde(default)]
127 pub routing_disposition: RoutingDisposition,
128 #[serde(default = "default_true")]
130 pub record_transcript: bool,
131 #[serde(default = "default_true")]
133 pub emit_operator_content: bool,
134 pub policy_version: PolicyVersion,
136}
137
138fn default_true() -> bool {
139 true
140}
141
142#[cfg(test)]
143#[allow(clippy::unwrap_used)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn apply_mode_serde() {
149 for mode in [
150 ApplyMode::StageRunStart,
151 ApplyMode::StageRunBoundary,
152 ApplyMode::InjectNow,
153 ApplyMode::Ignore,
154 ] {
155 let json = serde_json::to_value(mode).unwrap();
156 let parsed: ApplyMode = serde_json::from_value(json).unwrap();
157 assert_eq!(mode, parsed);
158 }
159 }
160
161 #[test]
162 fn wake_mode_serde() {
163 for mode in [
164 WakeMode::WakeIfIdle,
165 WakeMode::InterruptYielding,
166 WakeMode::None,
167 ] {
168 let json = serde_json::to_value(mode).unwrap();
169 let parsed: WakeMode = serde_json::from_value(json).unwrap();
170 assert_eq!(mode, parsed);
171 }
172 }
173
174 #[test]
175 fn queue_mode_serde() {
176 for mode in [
177 QueueMode::None,
178 QueueMode::Fifo,
179 QueueMode::Coalesce,
180 QueueMode::Supersede,
181 QueueMode::Priority,
182 ] {
183 let json = serde_json::to_value(mode).unwrap();
184 let parsed: QueueMode = serde_json::from_value(json).unwrap();
185 assert_eq!(mode, parsed);
186 }
187 }
188
189 #[test]
190 fn consume_point_serde() {
191 for point in [
192 ConsumePoint::OnAccept,
193 ConsumePoint::OnApply,
194 ConsumePoint::OnRunStart,
195 ConsumePoint::OnRunComplete,
196 ConsumePoint::ExplicitAck,
197 ] {
198 let json = serde_json::to_value(point).unwrap();
199 let parsed: ConsumePoint = serde_json::from_value(json).unwrap();
200 assert_eq!(point, parsed);
201 }
202 }
203
204 #[test]
205 fn interrupt_policy_serde() {
206 for policy in [InterruptPolicy::None, InterruptPolicy::InterruptYielding] {
207 let json = serde_json::to_value(policy).unwrap();
208 let parsed: InterruptPolicy = serde_json::from_value(json).unwrap();
209 assert_eq!(policy, parsed);
210 }
211 }
212
213 #[test]
214 fn drain_policy_serde() {
215 for policy in [
216 DrainPolicy::QueueNextTurn,
217 DrainPolicy::SteerBatch,
218 DrainPolicy::Immediate,
219 DrainPolicy::Ignore,
220 ] {
221 let json = serde_json::to_value(policy).unwrap();
222 let parsed: DrainPolicy = serde_json::from_value(json).unwrap();
223 assert_eq!(policy, parsed);
224 }
225 }
226
227 #[test]
228 fn routing_disposition_serde() {
229 for disposition in [
230 RoutingDisposition::Queue,
231 RoutingDisposition::Steer,
232 RoutingDisposition::Immediate,
233 RoutingDisposition::Drop,
234 ] {
235 let json = serde_json::to_value(disposition).unwrap();
236 let parsed: RoutingDisposition = serde_json::from_value(json).unwrap();
237 assert_eq!(disposition, parsed);
238 }
239 }
240
241 #[test]
242 fn policy_decision_serde_roundtrip() {
243 let decision = PolicyDecision {
244 apply_mode: ApplyMode::StageRunStart,
245 wake_mode: WakeMode::WakeIfIdle,
246 queue_mode: QueueMode::Fifo,
247 consume_point: ConsumePoint::OnRunComplete,
248 interrupt_policy: InterruptPolicy::None,
249 drain_policy: DrainPolicy::QueueNextTurn,
250 routing_disposition: RoutingDisposition::Queue,
251 record_transcript: true,
252 emit_operator_content: true,
253 policy_version: PolicyVersion(1),
254 };
255 let json = serde_json::to_value(&decision).unwrap();
256 let parsed: PolicyDecision = serde_json::from_value(json).unwrap();
257 assert_eq!(decision, parsed);
258 }
259
260 #[test]
261 fn policy_decision_ignore_on_accept() {
262 let decision = PolicyDecision {
263 apply_mode: ApplyMode::Ignore,
264 wake_mode: WakeMode::None,
265 queue_mode: QueueMode::None,
266 consume_point: ConsumePoint::OnAccept,
267 interrupt_policy: InterruptPolicy::None,
268 drain_policy: DrainPolicy::Ignore,
269 routing_disposition: RoutingDisposition::Drop,
270 record_transcript: false,
271 emit_operator_content: false,
272 policy_version: PolicyVersion(1),
273 };
274 let json = serde_json::to_value(&decision).unwrap();
275 let parsed: PolicyDecision = serde_json::from_value(json).unwrap();
276 assert_eq!(decision, parsed);
277 }
278
279 #[test]
280 fn record_transcript_defaults_true() {
281 let json = serde_json::json!({
282 "apply_mode": "stage_run_start",
283 "wake_mode": "wake_if_idle",
284 "queue_mode": "fifo",
285 "consume_point": "on_run_complete",
286 "policy_version": 1
287 });
288 let parsed: PolicyDecision = serde_json::from_value(json).unwrap();
289 assert!(parsed.record_transcript);
290 assert!(parsed.emit_operator_content);
291 }
292}