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 DrainPolicy {
79 #[default]
80 QueueNextTurn,
81 SteerBatch,
82 Immediate,
83 Ignore,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
88#[serde(rename_all = "snake_case")]
89#[non_exhaustive]
90pub enum RoutingDisposition {
91 #[default]
92 Queue,
93 Steer,
94 Immediate,
95 Drop,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100pub struct PolicyDecision {
101 pub apply_mode: ApplyMode,
103 pub wake_mode: WakeMode,
105 pub queue_mode: QueueMode,
107 pub consume_point: ConsumePoint,
109 #[serde(default)]
111 pub drain_policy: DrainPolicy,
112 #[serde(default)]
114 pub routing_disposition: RoutingDisposition,
115 #[serde(default = "default_true")]
117 pub record_transcript: bool,
118 #[serde(default = "default_true")]
120 pub emit_operator_content: bool,
121 pub policy_version: PolicyVersion,
123}
124
125fn default_true() -> bool {
126 true
127}
128
129#[cfg(test)]
130#[allow(clippy::unwrap_used)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn apply_mode_serde() {
136 for mode in [
137 ApplyMode::StageRunStart,
138 ApplyMode::StageRunBoundary,
139 ApplyMode::InjectNow,
140 ApplyMode::Ignore,
141 ] {
142 let json = serde_json::to_value(mode).unwrap();
143 let parsed: ApplyMode = serde_json::from_value(json).unwrap();
144 assert_eq!(mode, parsed);
145 }
146 }
147
148 #[test]
149 fn wake_mode_serde() {
150 for mode in [
151 WakeMode::WakeIfIdle,
152 WakeMode::InterruptYielding,
153 WakeMode::None,
154 ] {
155 let json = serde_json::to_value(mode).unwrap();
156 let parsed: WakeMode = serde_json::from_value(json).unwrap();
157 assert_eq!(mode, parsed);
158 }
159 }
160
161 #[test]
162 fn queue_mode_serde() {
163 for mode in [
164 QueueMode::None,
165 QueueMode::Fifo,
166 QueueMode::Coalesce,
167 QueueMode::Supersede,
168 QueueMode::Priority,
169 ] {
170 let json = serde_json::to_value(mode).unwrap();
171 let parsed: QueueMode = serde_json::from_value(json).unwrap();
172 assert_eq!(mode, parsed);
173 }
174 }
175
176 #[test]
177 fn consume_point_serde() {
178 for point in [
179 ConsumePoint::OnAccept,
180 ConsumePoint::OnApply,
181 ConsumePoint::OnRunStart,
182 ConsumePoint::OnRunComplete,
183 ConsumePoint::ExplicitAck,
184 ] {
185 let json = serde_json::to_value(point).unwrap();
186 let parsed: ConsumePoint = serde_json::from_value(json).unwrap();
187 assert_eq!(point, parsed);
188 }
189 }
190
191 #[test]
192 fn drain_policy_serde() {
193 for policy in [
194 DrainPolicy::QueueNextTurn,
195 DrainPolicy::SteerBatch,
196 DrainPolicy::Immediate,
197 DrainPolicy::Ignore,
198 ] {
199 let json = serde_json::to_value(policy).unwrap();
200 let parsed: DrainPolicy = serde_json::from_value(json).unwrap();
201 assert_eq!(policy, parsed);
202 }
203 }
204
205 #[test]
206 fn routing_disposition_serde() {
207 for disposition in [
208 RoutingDisposition::Queue,
209 RoutingDisposition::Steer,
210 RoutingDisposition::Immediate,
211 RoutingDisposition::Drop,
212 ] {
213 let json = serde_json::to_value(disposition).unwrap();
214 let parsed: RoutingDisposition = serde_json::from_value(json).unwrap();
215 assert_eq!(disposition, parsed);
216 }
217 }
218
219 #[test]
220 fn policy_decision_serde_roundtrip() {
221 let decision = PolicyDecision {
222 apply_mode: ApplyMode::StageRunStart,
223 wake_mode: WakeMode::WakeIfIdle,
224 queue_mode: QueueMode::Fifo,
225 consume_point: ConsumePoint::OnRunComplete,
226 drain_policy: DrainPolicy::QueueNextTurn,
227 routing_disposition: RoutingDisposition::Queue,
228 record_transcript: true,
229 emit_operator_content: true,
230 policy_version: PolicyVersion(1),
231 };
232 let json = serde_json::to_value(&decision).unwrap();
233 let parsed: PolicyDecision = serde_json::from_value(json).unwrap();
234 assert_eq!(decision, parsed);
235 }
236
237 #[test]
238 fn policy_decision_ignore_on_accept() {
239 let decision = PolicyDecision {
240 apply_mode: ApplyMode::Ignore,
241 wake_mode: WakeMode::None,
242 queue_mode: QueueMode::None,
243 consume_point: ConsumePoint::OnAccept,
244 drain_policy: DrainPolicy::Ignore,
245 routing_disposition: RoutingDisposition::Drop,
246 record_transcript: false,
247 emit_operator_content: false,
248 policy_version: PolicyVersion(1),
249 };
250 let json = serde_json::to_value(&decision).unwrap();
251 let parsed: PolicyDecision = serde_json::from_value(json).unwrap();
252 assert_eq!(decision, parsed);
253 }
254
255 #[test]
256 fn record_transcript_defaults_true() {
257 let json = serde_json::json!({
258 "apply_mode": "stage_run_start",
259 "wake_mode": "wake_if_idle",
260 "queue_mode": "fifo",
261 "consume_point": "on_run_complete",
262 "policy_version": 1
263 });
264 let parsed: PolicyDecision = serde_json::from_value(json).unwrap();
265 assert!(parsed.record_transcript);
266 assert!(parsed.emit_operator_content);
267 }
268}