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