Skip to main content

meerkat_runtime/
policy_table.rs

1//! Generated admission-policy projection facade.
2//!
3//! Policy/default facts are resolved by `MeerkatMachine::ResolveAdmissionPlan`.
4//! This module remains as a compatibility facade for tests and older callers
5//! that ask for a `PolicyDecision` directly.
6
7use 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    // The shell mirrors the machine's emitted projection: it threads the typed
58    // input facts (kind, requested lane, continuation kind) into
59    // `ResolveAdmissionPlan` and returns whatever lane/runtime-semantics the
60    // machine emits. No shell-side reclassification or override.
61    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                // #24: read the machine-emitted idle-steer normalization directly
136                // (`Option<InputLane>` -> `HandlingMode`); the shell normalizer
137                // `idle_steer_execution_handling_mode` is deleted.
138                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}