1use std::collections::BTreeSet;
8
9use crate::identifiers::{InputKind, KindId, LogicalRuntimeId, PolicyVersion};
10use crate::ingress_types::RuntimeInputSemantics;
11use crate::input::Input;
12use crate::meerkat_machine::dsl as mm_dsl;
13use crate::policy::PolicyDecision;
14use crate::runtime_state::RuntimeState;
15use meerkat_core::lifecycle::RunId;
16use meerkat_core::types::SessionId;
17
18pub struct DefaultPolicyTable;
19
20impl DefaultPolicyTable {
21 #[allow(clippy::expect_used)]
22 pub fn resolve(input: &Input, runtime_idle: bool) -> PolicyDecision {
23 Self::try_resolve(input, runtime_idle)
24 .expect("generated admission authority must resolve compatibility policy projection")
25 }
26
27 pub fn try_resolve(input: &Input, runtime_idle: bool) -> Result<PolicyDecision, String> {
28 generated_admission_projection_for_input(input, runtime_idle)
29 .map(|projection| projection.policy)
30 }
31
32 #[allow(clippy::expect_used)]
33 pub fn resolve_by_kind(kind: KindId, runtime_idle: bool) -> PolicyDecision {
34 Self::try_resolve_by_kind(kind, runtime_idle)
35 .expect("generated admission authority must resolve compatibility policy projection")
36 }
37
38 pub fn try_resolve_by_kind(kind: KindId, runtime_idle: bool) -> Result<PolicyDecision, String> {
39 generated_admission_projection_for_kind(kind, runtime_idle)
40 .map(|projection| projection.policy)
41 }
42}
43
44pub fn generated_default_policy_version() -> PolicyVersion {
45 DefaultPolicyTable::resolve_by_kind(KindId::new(InputKind::Prompt), true).policy_version
46}
47
48pub(crate) struct GeneratedAdmissionProjection {
49 pub policy: PolicyDecision,
50 pub runtime_semantics: RuntimeInputSemantics,
51}
52
53pub(crate) fn generated_admission_projection_for_input(
54 input: &Input,
55 runtime_idle: bool,
56) -> Result<GeneratedAdmissionProjection, String> {
57 generated_admission_projection(
62 input.id().to_string(),
63 input.kind(),
64 input.handling_mode().map(mm_dsl::InputLane::from),
65 mm_dsl::AdmissionContinuationKind::from(input.continuation_kind()),
66 runtime_idle,
67 )
68}
69
70pub(crate) fn generated_admission_projection_for_kind(
71 kind: KindId,
72 runtime_idle: bool,
73) -> Result<GeneratedAdmissionProjection, String> {
74 generated_admission_projection(
75 format!("policy-projection:{:?}", kind.kind()),
76 kind.kind(),
77 None,
78 mm_dsl::AdmissionContinuationKind::Ordinary,
79 runtime_idle,
80 )
81}
82
83fn generated_admission_projection(
84 input_id: String,
85 input_kind: InputKind,
86 requested_lane: Option<mm_dsl::InputLane>,
87 continuation_kind: mm_dsl::AdmissionContinuationKind,
88 runtime_idle: bool,
89) -> Result<GeneratedAdmissionProjection, String> {
90 let mut authority = projection_authority(runtime_idle)?;
91 let transition = mm_dsl::MeerkatMachineMutator::apply(
92 &mut authority,
93 mm_dsl::MeerkatMachineInput::ResolveAdmissionPlan {
94 input_id: input_id.clone(),
95 input_kind: mm_dsl::AdmissionInputKind::from(input_kind),
96 requested_lane,
97 continuation_kind,
98 silent_intent_match: false,
99 existing_superseded_input_id: None,
100 runtime_running: !runtime_idle,
101 active_turn_boundary_available: false,
102 without_wake: false,
103 },
104 )
105 .map_err(|err| {
106 format!("generated admission authority rejected policy projection for '{input_id}': {err}")
107 })?;
108
109 transition
110 .into_effects()
111 .into_iter()
112 .find_map(|effect| match effect {
113 mm_dsl::MeerkatMachineEffect::AdmissionResolved {
114 input_id: effect_input_id,
115 policy_version,
116 policy_apply_mode,
117 policy_wake_mode,
118 policy_queue_mode,
119 policy_consume_point,
120 policy_drain_policy,
121 policy_routing_disposition,
122 runtime_boundary,
123 runtime_execution_kind,
124 runtime_peer_response_terminal_apply_intent,
125 record_transcript,
126 execution_handling_mode,
127 live_interrupt_required,
128 ..
129 } if effect_input_id == input_id => {
130 let apply_mode: crate::policy::ApplyMode = policy_apply_mode.into();
131 let boundary: meerkat_core::lifecycle::run_primitive::RunApplyBoundary =
132 runtime_boundary.into();
133 let routing_disposition: crate::policy::RoutingDisposition =
134 policy_routing_disposition.into();
135 let execution_handling_mode = execution_handling_mode.map(|lane| match lane {
139 mm_dsl::InputLane::Queue => meerkat_core::types::HandlingMode::Queue,
140 mm_dsl::InputLane::Steer => meerkat_core::types::HandlingMode::Steer,
141 });
142 Some(GeneratedAdmissionProjection {
143 policy: PolicyDecision {
144 apply_mode,
145 wake_mode: policy_wake_mode.into(),
146 queue_mode: policy_queue_mode.into(),
147 consume_point: policy_consume_point.into(),
148 drain_policy: policy_drain_policy.into(),
149 routing_disposition,
150 record_transcript,
151 emit_operator_content: record_transcript,
152 policy_version: PolicyVersion(policy_version),
153 },
154 runtime_semantics: RuntimeInputSemantics {
155 boundary,
156 execution_kind: runtime_execution_kind.into(),
157 execution_handling_mode,
158 peer_response_terminal_apply_intent:
159 runtime_peer_response_terminal_apply_intent.map(Into::into),
160 live_interrupt_required,
161 },
162 })
163 }
164 _ => None,
165 })
166 .ok_or_else(|| {
167 format!("generated admission authority emitted no policy projection for '{input_id}'")
168 })
169}
170
171fn projection_authority(runtime_idle: bool) -> Result<mm_dsl::MeerkatMachineAuthority, String> {
172 let session_id = SessionId::new();
173 let runtime_id = LogicalRuntimeId::new("policy-projection");
174 let run_id = (!runtime_idle).then(RunId::new);
175 let pre_run_phase = (!runtime_idle).then_some(RuntimeState::Attached);
176 crate::meerkat_machine::dsl_authority::recover_authority_from_runtime_observation(
177 &session_id,
178 if runtime_idle {
179 RuntimeState::Idle
180 } else {
181 RuntimeState::Running
182 },
183 Some(&runtime_id),
184 run_id.as_ref(),
185 pre_run_phase,
186 BTreeSet::new(),
187 None,
188 None,
189 None,
190 )
191 .map_err(|err| format!("generated admission authority recovery failed: {err}"))
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use crate::policy::{ApplyMode, WakeMode};
198
199 #[test]
200 fn compatibility_projection_uses_generated_admission_authority() {
201 let idle = DefaultPolicyTable::resolve_by_kind(KindId::new(InputKind::PeerMessage), true);
202 let running =
203 DefaultPolicyTable::resolve_by_kind(KindId::new(InputKind::PeerMessage), false);
204
205 assert_eq!(idle.apply_mode, ApplyMode::StageRunStart);
206 assert_eq!(idle.wake_mode, WakeMode::WakeIfIdle);
207 assert_eq!(running.apply_mode, ApplyMode::StageRunStart);
208 assert_eq!(running.wake_mode, WakeMode::InterruptYielding);
209 assert_eq!(generated_default_policy_version(), idle.policy_version);
210 }
211}