1use crate::identifiers::{KindId, PolicyVersion};
6use crate::input::Input;
7use crate::policy::{ApplyMode, ConsumePoint, PolicyDecision, QueueMode, WakeMode};
8
9pub const DEFAULT_POLICY_VERSION: PolicyVersion = PolicyVersion(1);
11
12fn pd(
14 apply_mode: ApplyMode,
15 wake_mode: WakeMode,
16 queue_mode: QueueMode,
17 consume_point: ConsumePoint,
18 record_transcript: bool,
19) -> PolicyDecision {
20 PolicyDecision {
21 apply_mode,
22 wake_mode,
23 queue_mode,
24 consume_point,
25 record_transcript,
26 emit_operator_content: record_transcript,
27 policy_version: DEFAULT_POLICY_VERSION,
28 }
29}
30
31pub struct DefaultPolicyTable;
33
34impl DefaultPolicyTable {
35 pub fn resolve(input: &Input, runtime_idle: bool) -> PolicyDecision {
37 let kind = input.kind_id();
38 Self::resolve_by_kind(&kind, runtime_idle)
39 }
40
41 pub fn resolve_by_kind(kind: &KindId, runtime_idle: bool) -> PolicyDecision {
43 match (kind.0.as_str(), runtime_idle) {
44 ("prompt", true) => pd(
46 ApplyMode::StageRunStart,
47 WakeMode::WakeIfIdle,
48 QueueMode::Fifo,
49 ConsumePoint::OnRunComplete,
50 true,
51 ),
52 ("prompt", false) => pd(
53 ApplyMode::StageRunStart,
54 WakeMode::None,
55 QueueMode::Fifo,
56 ConsumePoint::OnRunComplete,
57 true,
58 ),
59
60 ("peer_message", true) => pd(
62 ApplyMode::StageRunStart,
63 WakeMode::WakeIfIdle,
64 QueueMode::Fifo,
65 ConsumePoint::OnRunComplete,
66 true,
67 ),
68 ("peer_message", false) => pd(
69 ApplyMode::StageRunStart,
70 WakeMode::None,
71 QueueMode::Fifo,
72 ConsumePoint::OnRunComplete,
73 true,
74 ),
75
76 ("peer_request", true) => pd(
78 ApplyMode::StageRunStart,
79 WakeMode::WakeIfIdle,
80 QueueMode::Fifo,
81 ConsumePoint::OnRunComplete,
82 true,
83 ),
84 ("peer_request", false) => pd(
85 ApplyMode::StageRunStart,
86 WakeMode::None,
87 QueueMode::Fifo,
88 ConsumePoint::OnRunComplete,
89 true,
90 ),
91
92 ("peer_response_progress", true) => pd(
94 ApplyMode::StageRunBoundary,
95 WakeMode::None,
96 QueueMode::Coalesce,
97 ConsumePoint::OnRunComplete,
98 true,
99 ),
100 ("peer_response_progress", false) => pd(
101 ApplyMode::StageRunBoundary,
102 WakeMode::None,
103 QueueMode::Coalesce,
104 ConsumePoint::OnRunComplete,
105 true,
106 ),
107
108 ("peer_response_terminal", true) => pd(
110 ApplyMode::StageRunStart,
111 WakeMode::WakeIfIdle,
112 QueueMode::Fifo,
113 ConsumePoint::OnRunComplete,
114 true,
115 ),
116 ("peer_response_terminal", false) => pd(
117 ApplyMode::StageRunStart,
118 WakeMode::None,
119 QueueMode::Fifo,
120 ConsumePoint::OnRunComplete,
121 true,
122 ),
123
124 ("flow_step", true) => pd(
126 ApplyMode::StageRunStart,
127 WakeMode::WakeIfIdle,
128 QueueMode::Fifo,
129 ConsumePoint::OnRunComplete,
130 true,
131 ),
132 ("flow_step", false) => pd(
133 ApplyMode::StageRunStart,
134 WakeMode::None,
135 QueueMode::Fifo,
136 ConsumePoint::OnRunComplete,
137 true,
138 ),
139
140 ("external_event", true) => pd(
142 ApplyMode::StageRunStart,
143 WakeMode::WakeIfIdle,
144 QueueMode::Fifo,
145 ConsumePoint::OnRunComplete,
146 true,
147 ),
148 ("external_event", false) => pd(
149 ApplyMode::StageRunStart,
150 WakeMode::None,
151 QueueMode::Fifo,
152 ConsumePoint::OnRunComplete,
153 true,
154 ),
155
156 ("system_generated", true | false) => pd(
158 ApplyMode::InjectNow,
159 WakeMode::None,
160 QueueMode::None,
161 ConsumePoint::OnAccept,
162 true,
163 ),
164
165 ("projected", true) => pd(
167 ApplyMode::Ignore,
168 WakeMode::None,
169 QueueMode::None,
170 ConsumePoint::OnAccept,
171 false,
172 ),
173 ("projected", false) => pd(
174 ApplyMode::Ignore,
175 WakeMode::None,
176 QueueMode::Coalesce,
177 ConsumePoint::OnAccept,
178 false,
179 ),
180
181 (_, _) => pd(
183 ApplyMode::StageRunStart,
184 WakeMode::None,
185 QueueMode::Fifo,
186 ConsumePoint::OnRunComplete,
187 true,
188 ),
189 }
190 }
191}
192
193#[cfg(test)]
194#[allow(clippy::unwrap_used)]
195mod tests {
196 use super::*;
197
198 fn assert_cell(
199 kind: &str,
200 idle: bool,
201 expected_apply: ApplyMode,
202 expected_wake: WakeMode,
203 expected_queue: QueueMode,
204 expected_consume: ConsumePoint,
205 expected_transcript: bool,
206 ) {
207 let decision = DefaultPolicyTable::resolve_by_kind(&KindId::new(kind), idle);
208 assert_eq!(
209 decision.apply_mode, expected_apply,
210 "kind={kind}, idle={idle}: apply_mode"
211 );
212 assert_eq!(
213 decision.wake_mode, expected_wake,
214 "kind={kind}, idle={idle}: wake_mode"
215 );
216 assert_eq!(
217 decision.queue_mode, expected_queue,
218 "kind={kind}, idle={idle}: queue_mode"
219 );
220 assert_eq!(
221 decision.consume_point, expected_consume,
222 "kind={kind}, idle={idle}: consume_point"
223 );
224 assert_eq!(
225 decision.record_transcript, expected_transcript,
226 "kind={kind}, idle={idle}: record_transcript"
227 );
228 }
229
230 #[test]
231 fn prompt_idle() {
232 assert_cell(
233 "prompt",
234 true,
235 ApplyMode::StageRunStart,
236 WakeMode::WakeIfIdle,
237 QueueMode::Fifo,
238 ConsumePoint::OnRunComplete,
239 true,
240 );
241 }
242 #[test]
243 fn prompt_running() {
244 assert_cell(
245 "prompt",
246 false,
247 ApplyMode::StageRunStart,
248 WakeMode::None,
249 QueueMode::Fifo,
250 ConsumePoint::OnRunComplete,
251 true,
252 );
253 }
254 #[test]
255 fn peer_message_idle() {
256 assert_cell(
257 "peer_message",
258 true,
259 ApplyMode::StageRunStart,
260 WakeMode::WakeIfIdle,
261 QueueMode::Fifo,
262 ConsumePoint::OnRunComplete,
263 true,
264 );
265 }
266 #[test]
267 fn peer_message_running() {
268 assert_cell(
269 "peer_message",
270 false,
271 ApplyMode::StageRunStart,
272 WakeMode::None,
273 QueueMode::Fifo,
274 ConsumePoint::OnRunComplete,
275 true,
276 );
277 }
278 #[test]
279 fn peer_request_idle() {
280 assert_cell(
281 "peer_request",
282 true,
283 ApplyMode::StageRunStart,
284 WakeMode::WakeIfIdle,
285 QueueMode::Fifo,
286 ConsumePoint::OnRunComplete,
287 true,
288 );
289 }
290 #[test]
291 fn peer_request_running() {
292 assert_cell(
293 "peer_request",
294 false,
295 ApplyMode::StageRunStart,
296 WakeMode::None,
297 QueueMode::Fifo,
298 ConsumePoint::OnRunComplete,
299 true,
300 );
301 }
302 #[test]
303 fn peer_response_progress_idle() {
304 assert_cell(
305 "peer_response_progress",
306 true,
307 ApplyMode::StageRunBoundary,
308 WakeMode::None,
309 QueueMode::Coalesce,
310 ConsumePoint::OnRunComplete,
311 true,
312 );
313 }
314 #[test]
315 fn peer_response_progress_running() {
316 assert_cell(
317 "peer_response_progress",
318 false,
319 ApplyMode::StageRunBoundary,
320 WakeMode::None,
321 QueueMode::Coalesce,
322 ConsumePoint::OnRunComplete,
323 true,
324 );
325 }
326 #[test]
327 fn peer_response_terminal_idle() {
328 assert_cell(
329 "peer_response_terminal",
330 true,
331 ApplyMode::StageRunStart,
332 WakeMode::WakeIfIdle,
333 QueueMode::Fifo,
334 ConsumePoint::OnRunComplete,
335 true,
336 );
337 }
338 #[test]
339 fn peer_response_terminal_running() {
340 assert_cell(
341 "peer_response_terminal",
342 false,
343 ApplyMode::StageRunStart,
344 WakeMode::None,
345 QueueMode::Fifo,
346 ConsumePoint::OnRunComplete,
347 true,
348 );
349 }
350 #[test]
351 fn flow_step_idle() {
352 assert_cell(
353 "flow_step",
354 true,
355 ApplyMode::StageRunStart,
356 WakeMode::WakeIfIdle,
357 QueueMode::Fifo,
358 ConsumePoint::OnRunComplete,
359 true,
360 );
361 }
362 #[test]
363 fn flow_step_running() {
364 assert_cell(
365 "flow_step",
366 false,
367 ApplyMode::StageRunStart,
368 WakeMode::None,
369 QueueMode::Fifo,
370 ConsumePoint::OnRunComplete,
371 true,
372 );
373 }
374 #[test]
375 fn external_event_idle() {
376 assert_cell(
377 "external_event",
378 true,
379 ApplyMode::StageRunStart,
380 WakeMode::WakeIfIdle,
381 QueueMode::Fifo,
382 ConsumePoint::OnRunComplete,
383 true,
384 );
385 }
386 #[test]
387 fn external_event_running() {
388 assert_cell(
389 "external_event",
390 false,
391 ApplyMode::StageRunStart,
392 WakeMode::None,
393 QueueMode::Fifo,
394 ConsumePoint::OnRunComplete,
395 true,
396 );
397 }
398 #[test]
399 fn system_generated_idle() {
400 assert_cell(
401 "system_generated",
402 true,
403 ApplyMode::InjectNow,
404 WakeMode::None,
405 QueueMode::None,
406 ConsumePoint::OnAccept,
407 true,
408 );
409 }
410 #[test]
411 fn system_generated_running() {
412 assert_cell(
413 "system_generated",
414 false,
415 ApplyMode::InjectNow,
416 WakeMode::None,
417 QueueMode::None,
418 ConsumePoint::OnAccept,
419 true,
420 );
421 }
422 #[test]
423 fn projected_idle() {
424 assert_cell(
425 "projected",
426 true,
427 ApplyMode::Ignore,
428 WakeMode::None,
429 QueueMode::None,
430 ConsumePoint::OnAccept,
431 false,
432 );
433 }
434 #[test]
435 fn projected_running() {
436 assert_cell(
437 "projected",
438 false,
439 ApplyMode::Ignore,
440 WakeMode::None,
441 QueueMode::Coalesce,
442 ConsumePoint::OnAccept,
443 false,
444 );
445 }
446
447 #[test]
448 fn resolve_via_input_object() {
449 use crate::input::*;
450 use chrono::Utc;
451 use meerkat_core::lifecycle::InputId;
452
453 let header = InputHeader {
454 id: InputId::new(),
455 timestamp: Utc::now(),
456 source: InputOrigin::Operator,
457 durability: InputDurability::Durable,
458 visibility: InputVisibility::default(),
459 idempotency_key: None,
460 supersession_key: None,
461 correlation_id: None,
462 };
463 let input = Input::Prompt(PromptInput {
464 header,
465 text: "hello".into(),
466 turn_metadata: None,
467 });
468 let decision = DefaultPolicyTable::resolve(&input, true);
469 assert_eq!(decision.apply_mode, ApplyMode::StageRunStart);
470 assert_eq!(decision.wake_mode, WakeMode::WakeIfIdle);
471 }
472}