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(
64 ApplyMode::StageRunStart,
65 WakeMode::WakeIfIdle,
66 QueueMode::Fifo,
67 ConsumePoint::OnRunComplete,
68 true,
69 ),
70 ("peer_message", false) => pd(
71 ApplyMode::StageRunStart,
72 WakeMode::InterruptYielding,
73 QueueMode::Fifo,
74 ConsumePoint::OnRunComplete,
75 true,
76 ),
77
78 ("peer_request", true) => pd(
80 ApplyMode::StageRunStart,
81 WakeMode::WakeIfIdle,
82 QueueMode::Fifo,
83 ConsumePoint::OnRunComplete,
84 true,
85 ),
86 ("peer_request", false) => pd(
87 ApplyMode::StageRunStart,
88 WakeMode::InterruptYielding,
89 QueueMode::Fifo,
90 ConsumePoint::OnRunComplete,
91 true,
92 ),
93
94 ("peer_response_progress", true) => pd(
96 ApplyMode::StageRunBoundary,
97 WakeMode::None,
98 QueueMode::Coalesce,
99 ConsumePoint::OnRunComplete,
100 true,
101 ),
102 ("peer_response_progress", false) => pd(
103 ApplyMode::StageRunBoundary,
104 WakeMode::None,
105 QueueMode::Coalesce,
106 ConsumePoint::OnRunComplete,
107 true,
108 ),
109
110 ("peer_response_terminal", true) => pd(
112 ApplyMode::StageRunStart,
113 WakeMode::WakeIfIdle,
114 QueueMode::Fifo,
115 ConsumePoint::OnRunComplete,
116 true,
117 ),
118 ("peer_response_terminal", false) => pd(
119 ApplyMode::StageRunStart,
120 WakeMode::None,
121 QueueMode::Fifo,
122 ConsumePoint::OnRunComplete,
123 true,
124 ),
125
126 ("flow_step", true) => pd(
128 ApplyMode::StageRunStart,
129 WakeMode::WakeIfIdle,
130 QueueMode::Fifo,
131 ConsumePoint::OnRunComplete,
132 true,
133 ),
134 ("flow_step", false) => pd(
135 ApplyMode::StageRunStart,
136 WakeMode::None,
137 QueueMode::Fifo,
138 ConsumePoint::OnRunComplete,
139 true,
140 ),
141
142 ("external_event", true) => pd(
144 ApplyMode::StageRunStart,
145 WakeMode::WakeIfIdle,
146 QueueMode::Fifo,
147 ConsumePoint::OnRunComplete,
148 true,
149 ),
150 ("external_event", false) => pd(
151 ApplyMode::StageRunStart,
152 WakeMode::None,
153 QueueMode::Fifo,
154 ConsumePoint::OnRunComplete,
155 true,
156 ),
157
158 ("system_generated", true | false) => pd(
160 ApplyMode::InjectNow,
161 WakeMode::None,
162 QueueMode::None,
163 ConsumePoint::OnAccept,
164 true,
165 ),
166
167 ("projected", true) => pd(
169 ApplyMode::Ignore,
170 WakeMode::None,
171 QueueMode::None,
172 ConsumePoint::OnAccept,
173 false,
174 ),
175 ("projected", false) => pd(
176 ApplyMode::Ignore,
177 WakeMode::None,
178 QueueMode::Coalesce,
179 ConsumePoint::OnAccept,
180 false,
181 ),
182
183 (_, _) => pd(
185 ApplyMode::StageRunStart,
186 WakeMode::None,
187 QueueMode::Fifo,
188 ConsumePoint::OnRunComplete,
189 true,
190 ),
191 }
192 }
193}
194
195#[cfg(test)]
196#[allow(clippy::unwrap_used)]
197mod tests {
198 use super::*;
199
200 fn assert_cell(
201 kind: &str,
202 idle: bool,
203 expected_apply: ApplyMode,
204 expected_wake: WakeMode,
205 expected_queue: QueueMode,
206 expected_consume: ConsumePoint,
207 expected_transcript: bool,
208 ) {
209 let decision = DefaultPolicyTable::resolve_by_kind(&KindId::new(kind), idle);
210 assert_eq!(
211 decision.apply_mode, expected_apply,
212 "kind={kind}, idle={idle}: apply_mode"
213 );
214 assert_eq!(
215 decision.wake_mode, expected_wake,
216 "kind={kind}, idle={idle}: wake_mode"
217 );
218 assert_eq!(
219 decision.queue_mode, expected_queue,
220 "kind={kind}, idle={idle}: queue_mode"
221 );
222 assert_eq!(
223 decision.consume_point, expected_consume,
224 "kind={kind}, idle={idle}: consume_point"
225 );
226 assert_eq!(
227 decision.record_transcript, expected_transcript,
228 "kind={kind}, idle={idle}: record_transcript"
229 );
230 }
231
232 #[test]
233 fn prompt_idle() {
234 assert_cell(
235 "prompt",
236 true,
237 ApplyMode::StageRunStart,
238 WakeMode::WakeIfIdle,
239 QueueMode::Fifo,
240 ConsumePoint::OnRunComplete,
241 true,
242 );
243 }
244 #[test]
245 fn prompt_running() {
246 assert_cell(
247 "prompt",
248 false,
249 ApplyMode::StageRunStart,
250 WakeMode::None,
251 QueueMode::Fifo,
252 ConsumePoint::OnRunComplete,
253 true,
254 );
255 }
256 #[test]
257 fn peer_message_idle() {
258 assert_cell(
259 "peer_message",
260 true,
261 ApplyMode::StageRunStart,
262 WakeMode::WakeIfIdle,
263 QueueMode::Fifo,
264 ConsumePoint::OnRunComplete,
265 true,
266 );
267 }
268 #[test]
269 fn peer_message_running() {
270 assert_cell(
271 "peer_message",
272 false,
273 ApplyMode::StageRunStart,
274 WakeMode::InterruptYielding,
275 QueueMode::Fifo,
276 ConsumePoint::OnRunComplete,
277 true,
278 );
279 }
280 #[test]
281 fn peer_request_idle() {
282 assert_cell(
283 "peer_request",
284 true,
285 ApplyMode::StageRunStart,
286 WakeMode::WakeIfIdle,
287 QueueMode::Fifo,
288 ConsumePoint::OnRunComplete,
289 true,
290 );
291 }
292 #[test]
293 fn peer_request_running() {
294 assert_cell(
295 "peer_request",
296 false,
297 ApplyMode::StageRunStart,
298 WakeMode::InterruptYielding,
299 QueueMode::Fifo,
300 ConsumePoint::OnRunComplete,
301 true,
302 );
303 }
304 #[test]
305 fn peer_response_progress_idle() {
306 assert_cell(
307 "peer_response_progress",
308 true,
309 ApplyMode::StageRunBoundary,
310 WakeMode::None,
311 QueueMode::Coalesce,
312 ConsumePoint::OnRunComplete,
313 true,
314 );
315 }
316 #[test]
317 fn peer_response_progress_running() {
318 assert_cell(
319 "peer_response_progress",
320 false,
321 ApplyMode::StageRunBoundary,
322 WakeMode::None,
323 QueueMode::Coalesce,
324 ConsumePoint::OnRunComplete,
325 true,
326 );
327 }
328 #[test]
329 fn peer_response_terminal_idle() {
330 assert_cell(
331 "peer_response_terminal",
332 true,
333 ApplyMode::StageRunStart,
334 WakeMode::WakeIfIdle,
335 QueueMode::Fifo,
336 ConsumePoint::OnRunComplete,
337 true,
338 );
339 }
340 #[test]
341 fn peer_response_terminal_running() {
342 assert_cell(
343 "peer_response_terminal",
344 false,
345 ApplyMode::StageRunStart,
346 WakeMode::None,
347 QueueMode::Fifo,
348 ConsumePoint::OnRunComplete,
349 true,
350 );
351 }
352 #[test]
353 fn flow_step_idle() {
354 assert_cell(
355 "flow_step",
356 true,
357 ApplyMode::StageRunStart,
358 WakeMode::WakeIfIdle,
359 QueueMode::Fifo,
360 ConsumePoint::OnRunComplete,
361 true,
362 );
363 }
364 #[test]
365 fn flow_step_running() {
366 assert_cell(
367 "flow_step",
368 false,
369 ApplyMode::StageRunStart,
370 WakeMode::None,
371 QueueMode::Fifo,
372 ConsumePoint::OnRunComplete,
373 true,
374 );
375 }
376 #[test]
377 fn external_event_idle() {
378 assert_cell(
379 "external_event",
380 true,
381 ApplyMode::StageRunStart,
382 WakeMode::WakeIfIdle,
383 QueueMode::Fifo,
384 ConsumePoint::OnRunComplete,
385 true,
386 );
387 }
388 #[test]
389 fn external_event_running() {
390 assert_cell(
391 "external_event",
392 false,
393 ApplyMode::StageRunStart,
394 WakeMode::None,
395 QueueMode::Fifo,
396 ConsumePoint::OnRunComplete,
397 true,
398 );
399 }
400 #[test]
401 fn system_generated_idle() {
402 assert_cell(
403 "system_generated",
404 true,
405 ApplyMode::InjectNow,
406 WakeMode::None,
407 QueueMode::None,
408 ConsumePoint::OnAccept,
409 true,
410 );
411 }
412 #[test]
413 fn system_generated_running() {
414 assert_cell(
415 "system_generated",
416 false,
417 ApplyMode::InjectNow,
418 WakeMode::None,
419 QueueMode::None,
420 ConsumePoint::OnAccept,
421 true,
422 );
423 }
424 #[test]
425 fn projected_idle() {
426 assert_cell(
427 "projected",
428 true,
429 ApplyMode::Ignore,
430 WakeMode::None,
431 QueueMode::None,
432 ConsumePoint::OnAccept,
433 false,
434 );
435 }
436 #[test]
437 fn projected_running() {
438 assert_cell(
439 "projected",
440 false,
441 ApplyMode::Ignore,
442 WakeMode::None,
443 QueueMode::Coalesce,
444 ConsumePoint::OnAccept,
445 false,
446 );
447 }
448
449 #[test]
450 fn resolve_via_input_object() {
451 use crate::input::*;
452 use chrono::Utc;
453 use meerkat_core::lifecycle::InputId;
454
455 let header = InputHeader {
456 id: InputId::new(),
457 timestamp: Utc::now(),
458 source: InputOrigin::Operator,
459 durability: InputDurability::Durable,
460 visibility: InputVisibility::default(),
461 idempotency_key: None,
462 supersession_key: None,
463 correlation_id: None,
464 };
465 let input = Input::Prompt(PromptInput {
466 header,
467 text: "hello".into(),
468 blocks: None,
469 turn_metadata: None,
470 });
471 let decision = DefaultPolicyTable::resolve(&input, true);
472 assert_eq!(decision.apply_mode, ApplyMode::StageRunStart);
473 assert_eq!(decision.wake_mode, WakeMode::WakeIfIdle);
474 }
475
476 #[test]
477 fn peer_message_running_interrupts_yielding() {
478 let decision = DefaultPolicyTable::resolve_by_kind(&KindId::new("peer_message"), false);
481 assert_eq!(
482 decision.wake_mode,
483 WakeMode::InterruptYielding,
484 "peer_message while running must use InterruptYielding"
485 );
486 assert_ne!(
488 decision.wake_mode,
489 WakeMode::WakeIfIdle,
490 "peer_message while running must not use WakeIfIdle"
491 );
492 }
493
494 #[test]
495 fn peer_request_running_interrupts_yielding() {
496 let decision = DefaultPolicyTable::resolve_by_kind(&KindId::new("peer_request"), false);
498 assert_eq!(
499 decision.wake_mode,
500 WakeMode::InterruptYielding,
501 "peer_request while running must use InterruptYielding"
502 );
503 }
504
505 #[test]
506 fn peer_message_idle_still_wakes() {
507 let decision = DefaultPolicyTable::resolve_by_kind(&KindId::new("peer_message"), true);
509 assert_eq!(
510 decision.wake_mode,
511 WakeMode::WakeIfIdle,
512 "peer_message while idle must use WakeIfIdle"
513 );
514 }
515
516 #[test]
517 fn peer_request_idle_still_wakes() {
518 let decision = DefaultPolicyTable::resolve_by_kind(&KindId::new("peer_request"), true);
520 assert_eq!(
521 decision.wake_mode,
522 WakeMode::WakeIfIdle,
523 "peer_request while idle must use WakeIfIdle"
524 );
525 }
526}