Skip to main content

meerkat_runtime/meerkat_machine/
traits.rs

1use super::*;
2use crate::input_state::StoredInputState;
3
4#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
5#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
6impl SessionServiceRuntimeExt for MeerkatMachine {
7    async fn accept_input(
8        &self,
9        session_id: &SessionId,
10        input: Input,
11    ) -> Result<AcceptOutcome, RuntimeDriverError> {
12        match self
13            .execute_meerkat_machine_command(
14                None,
15                MeerkatMachineCommand::AcceptWithCompletion {
16                    session_id: session_id.clone(),
17                    input,
18                    register_completion: false,
19                },
20            )
21            .await
22            .map_err(MeerkatMachine::driver_error_from_command_error)?
23        {
24            MeerkatMachineCommandResult::AcceptWithCompletion {
25                outcome,
26                handle: _,
27                admission_signal: _,
28            } => Ok(outcome),
29            other => Err(RuntimeDriverError::Internal(format!(
30                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::accept_input: {other:?}"
31            ))),
32        }
33    }
34
35    async fn accept_input_with_completion(
36        &self,
37        session_id: &SessionId,
38        input: Input,
39    ) -> Result<(AcceptOutcome, Option<crate::completion::CompletionHandle>), RuntimeDriverError>
40    {
41        tracing::debug!(
42            session_id = %session_id,
43            input_id = %input.id(),
44            "SessionServiceRuntimeExt::accept_input_with_completion entered"
45        );
46        self.accept_input_with_completion_boxed(session_id, input)
47            .await
48    }
49
50    async fn runtime_state(
51        &self,
52        session_id: &SessionId,
53    ) -> Result<RuntimeState, RuntimeDriverError> {
54        let runtime_id = MeerkatMachine::logical_runtime_id(session_id);
55        match self
56            .execute_meerkat_machine_command(
57                None,
58                MeerkatMachineCommand::RuntimeState { runtime_id },
59            )
60            .await
61            .map_err(MeerkatMachine::driver_error_from_command_error)?
62        {
63            MeerkatMachineCommandResult::RuntimeState(state) => Ok(state),
64            other => Err(RuntimeDriverError::Internal(format!(
65                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::runtime_state: {other:?}"
66            ))),
67        }
68    }
69
70    async fn retire_runtime(
71        &self,
72        session_id: &SessionId,
73    ) -> Result<RetireReport, RuntimeDriverError> {
74        let runtime_id = MeerkatMachine::logical_runtime_id(session_id);
75        match self
76            .execute_meerkat_machine_command(None, MeerkatMachineCommand::Retire { runtime_id })
77            .await
78            .map_err(MeerkatMachine::driver_error_from_command_error)?
79        {
80            MeerkatMachineCommandResult::RetireReport(report) => Ok(report),
81            other => Err(RuntimeDriverError::Internal(format!(
82                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::retire_runtime: {other:?}"
83            ))),
84        }
85    }
86
87    async fn reset_runtime(
88        &self,
89        session_id: &SessionId,
90    ) -> Result<ResetReport, RuntimeDriverError> {
91        let runtime_id = MeerkatMachine::logical_runtime_id(session_id);
92        match self
93            .execute_meerkat_machine_command(None, MeerkatMachineCommand::Reset { runtime_id })
94            .await
95            .map_err(MeerkatMachine::driver_error_from_command_error)?
96        {
97            MeerkatMachineCommandResult::ResetReport(report) => Ok(report),
98            other => Err(RuntimeDriverError::Internal(format!(
99                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::reset_runtime: {other:?}"
100            ))),
101        }
102    }
103
104    async fn input_state(
105        &self,
106        session_id: &SessionId,
107        input_id: &InputId,
108    ) -> Result<Option<StoredInputState>, RuntimeDriverError> {
109        match self
110            .execute_meerkat_machine_command(
111                None,
112                MeerkatMachineCommand::InputState {
113                    session_id: session_id.clone(),
114                    input_id: input_id.clone(),
115                },
116            )
117            .await
118            .map_err(MeerkatMachine::driver_error_from_command_error)?
119        {
120            MeerkatMachineCommandResult::InputState(state) => Ok(state),
121            other => Err(RuntimeDriverError::Internal(format!(
122                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::input_state: {other:?}"
123            ))),
124        }
125    }
126
127    async fn input_state_by_idempotency_key(
128        &self,
129        session_id: &SessionId,
130        idempotency_key: &str,
131    ) -> Result<Option<StoredInputState>, RuntimeDriverError> {
132        match self
133            .execute_meerkat_machine_command(
134                None,
135                MeerkatMachineCommand::InputStateByIdempotencyKey {
136                    session_id: session_id.clone(),
137                    idempotency_key: idempotency_key.to_string(),
138                },
139            )
140            .await
141            .map_err(MeerkatMachine::driver_error_from_command_error)?
142        {
143            MeerkatMachineCommandResult::InputState(state) => Ok(state),
144            other => Err(RuntimeDriverError::Internal(format!(
145                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::input_state_by_idempotency_key: {other:?}"
146            ))),
147        }
148    }
149
150    async fn interaction_terminal_status(
151        &self,
152        session_id: &SessionId,
153        selector: crate::terminal_status::InteractionSelector,
154    ) -> Result<
155        Option<crate::terminal_status::Sourced<crate::terminal_status::InteractionTerminalReport>>,
156        RuntimeDriverError,
157    > {
158        match self
159            .execute_meerkat_machine_command(
160                None,
161                MeerkatMachineCommand::InteractionTerminalStatus {
162                    session_id: session_id.clone(),
163                    selector,
164                },
165            )
166            .await
167            .map_err(MeerkatMachine::driver_error_from_command_error)?
168        {
169            MeerkatMachineCommandResult::InteractionTerminalStatus(report) => Ok(report),
170            other => Err(RuntimeDriverError::Internal(format!(
171                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::interaction_terminal_status: {other:?}"
172            ))),
173        }
174    }
175
176    async fn run_terminal_status(
177        &self,
178        session_id: &SessionId,
179        run_id: &meerkat_core::lifecycle::RunId,
180    ) -> Result<
181        crate::terminal_status::Sourced<crate::terminal_status::RunTerminalReport>,
182        RuntimeDriverError,
183    > {
184        match self
185            .execute_meerkat_machine_command(
186                None,
187                MeerkatMachineCommand::RunTerminalStatus {
188                    session_id: session_id.clone(),
189                    run_id: run_id.clone(),
190                },
191            )
192            .await
193            .map_err(MeerkatMachine::driver_error_from_command_error)?
194        {
195            MeerkatMachineCommandResult::RunTerminalStatus(report) => Ok(report),
196            other => Err(RuntimeDriverError::Internal(format!(
197                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::run_terminal_status: {other:?}"
198            ))),
199        }
200    }
201
202    async fn list_active_inputs(
203        &self,
204        session_id: &SessionId,
205    ) -> Result<Vec<InputId>, RuntimeDriverError> {
206        match self
207            .execute_meerkat_machine_command(
208                None,
209                MeerkatMachineCommand::ListActiveInputs {
210                    session_id: session_id.clone(),
211                },
212            )
213            .await
214            .map_err(MeerkatMachine::driver_error_from_command_error)?
215        {
216            MeerkatMachineCommandResult::ActiveInputs(inputs) => Ok(inputs),
217            other => Err(RuntimeDriverError::Internal(format!(
218                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::list_active_inputs: {other:?}"
219            ))),
220        }
221    }
222
223    async fn reconfigure_session_llm_identity(
224        &self,
225        session_id: &SessionId,
226        request: SessionLlmReconfigureRequest,
227    ) -> Result<SessionLlmReconfigureReport, RuntimeDriverError> {
228        let command = self
229            .prepare_reconfigure_session_llm_command(session_id, request)
230            .await?;
231        match self
232            .execute_meerkat_machine_command(None, command)
233            .await
234            .map_err(MeerkatMachine::driver_error_from_command_error)?
235        {
236            MeerkatMachineCommandResult::LlmReconfigured(report) => Ok(report),
237            other => Err(RuntimeDriverError::Internal(format!(
238                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::reconfigure_session_llm_identity: {other:?}"
239            ))),
240        }
241    }
242
243    async fn resolved_session_llm_capabilities(
244        &self,
245        session_id: &SessionId,
246    ) -> Result<Option<SessionLlmCapabilitySurface>, RuntimeDriverError> {
247        match self
248            .execute_meerkat_machine_command(
249                None,
250                MeerkatMachineCommand::ResolvedSessionLlmCapabilities {
251                    session_id: session_id.clone(),
252                },
253            )
254            .await
255            .map_err(MeerkatMachine::driver_error_from_command_error)?
256        {
257            MeerkatMachineCommandResult::ResolvedSessionLlmCapabilities(capabilities) => {
258                Ok(capabilities)
259            }
260            other => Err(RuntimeDriverError::Internal(format!(
261                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::resolved_session_llm_capabilities: {other:?}"
262            ))),
263        }
264    }
265
266    async fn configure_model_routing_baseline(
267        &self,
268        session_id: &SessionId,
269        baseline_model: meerkat_core::lifecycle::run_primitive::ModelId,
270        realtime_capable: bool,
271    ) -> Result<(), RuntimeDriverError> {
272        match self
273            .execute_meerkat_machine_command(
274                None,
275                MeerkatMachineCommand::ConfigureModelRoutingBaseline {
276                    session_id: session_id.clone(),
277                    baseline_model,
278                    realtime_capable,
279                },
280            )
281            .await
282            .map_err(MeerkatMachine::driver_error_from_command_error)?
283        {
284            MeerkatMachineCommandResult::Unit => Ok(()),
285            other => Err(RuntimeDriverError::Internal(format!(
286                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::configure_model_routing_baseline: {other:?}"
287            ))),
288        }
289    }
290
291    async fn session_model_routing_status(
292        &self,
293        session_id: &SessionId,
294    ) -> Result<meerkat_core::image_generation::SessionModelRoutingStatus, RuntimeDriverError> {
295        match self
296            .execute_meerkat_machine_command(
297                None,
298                MeerkatMachineCommand::SessionModelRoutingStatus {
299                    session_id: session_id.clone(),
300                },
301            )
302            .await
303            .map_err(MeerkatMachine::driver_error_from_command_error)?
304        {
305            MeerkatMachineCommandResult::SessionModelRoutingStatus(status) => Ok(status),
306            other => Err(RuntimeDriverError::Internal(format!(
307                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::session_model_routing_status: {other:?}"
308            ))),
309        }
310    }
311
312    async fn request_switch_turn(
313        &self,
314        session_id: &SessionId,
315        request: crate::meerkat_machine_types::SwitchTurnRequest,
316    ) -> Result<meerkat_core::image_generation::SwitchTurnControlResult, RuntimeDriverError> {
317        match self
318            .execute_meerkat_machine_command(
319                None,
320                MeerkatMachineCommand::RequestSwitchTurn {
321                    session_id: session_id.clone(),
322                    request: Box::new(request),
323                },
324            )
325            .await
326            .map_err(MeerkatMachine::driver_error_from_command_error)?
327        {
328            MeerkatMachineCommandResult::SwitchTurnControlResult(result) => Ok(result),
329            other => Err(RuntimeDriverError::Internal(format!(
330                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::request_switch_turn: {other:?}"
331            ))),
332        }
333    }
334
335    async fn admit_model_routing_assistant_turn(
336        &self,
337        session_id: &SessionId,
338    ) -> Result<(), RuntimeDriverError> {
339        match self
340            .execute_meerkat_machine_command(
341                None,
342                MeerkatMachineCommand::AdmitModelRoutingAssistantTurn {
343                    session_id: session_id.clone(),
344                },
345            )
346            .await
347            .map_err(MeerkatMachine::driver_error_from_command_error)?
348        {
349            MeerkatMachineCommandResult::Unit => Ok(()),
350            other => Err(RuntimeDriverError::Internal(format!(
351                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::admit_model_routing_assistant_turn: {other:?}"
352            ))),
353        }
354    }
355
356    async fn begin_image_operation(
357        &self,
358        session_id: &SessionId,
359        request: crate::meerkat_machine_types::ImageOperationRoutingRequest,
360    ) -> Result<crate::meerkat_machine_types::ImageOperationRoutingResult, RuntimeDriverError> {
361        match self
362            .execute_meerkat_machine_command(
363                None,
364                MeerkatMachineCommand::BeginImageOperation {
365                    session_id: session_id.clone(),
366                    request: Box::new(request),
367                },
368            )
369            .await
370            .map_err(MeerkatMachine::driver_error_from_command_error)?
371        {
372            MeerkatMachineCommandResult::ImageOperationRoutingResult(result) => Ok(result),
373            other => Err(RuntimeDriverError::Internal(format!(
374                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::begin_image_operation: {other:?}"
375            ))),
376        }
377    }
378
379    async fn deny_image_operation_plan(
380        &self,
381        session_id: &SessionId,
382        operation_id: meerkat_core::image_generation::ImageOperationId,
383        reason: meerkat_core::image_generation::ImageOperationDenialReason,
384    ) -> Result<meerkat_core::image_generation::ImageOperationPhase, RuntimeDriverError> {
385        match self
386            .execute_meerkat_machine_command(
387                None,
388                MeerkatMachineCommand::DenyImageOperationPlan {
389                    session_id: session_id.clone(),
390                    operation_id,
391                    reason,
392                },
393            )
394            .await
395            .map_err(MeerkatMachine::driver_error_from_command_error)?
396        {
397            MeerkatMachineCommandResult::ImageOperationPhase(phase) => Ok(phase),
398            other => Err(RuntimeDriverError::Internal(format!(
399                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::deny_image_operation_plan: {other:?}"
400            ))),
401        }
402    }
403
404    async fn activate_image_operation_override(
405        &self,
406        session_id: &SessionId,
407        operation_id: meerkat_core::image_generation::ImageOperationId,
408    ) -> Result<meerkat_core::image_generation::ImageOperationPhase, RuntimeDriverError> {
409        match self
410            .execute_meerkat_machine_command(
411                None,
412                MeerkatMachineCommand::ActivateImageOperationOverride {
413                    session_id: session_id.clone(),
414                    operation_id,
415                },
416            )
417            .await
418            .map_err(MeerkatMachine::driver_error_from_command_error)?
419        {
420            MeerkatMachineCommandResult::ImageOperationPhase(phase) => Ok(phase),
421            other => Err(RuntimeDriverError::Internal(format!(
422                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::activate_image_operation_override: {other:?}"
423            ))),
424        }
425    }
426
427    async fn complete_image_operation(
428        &self,
429        session_id: &SessionId,
430        operation_id: meerkat_core::image_generation::ImageOperationId,
431        terminal: meerkat_core::image_generation::ImageOperationTerminalClass,
432    ) -> Result<meerkat_core::image_generation::ImageOperationPhase, RuntimeDriverError> {
433        match self
434            .execute_meerkat_machine_command(
435                None,
436                MeerkatMachineCommand::CompleteImageOperation {
437                    session_id: session_id.clone(),
438                    operation_id,
439                    terminal,
440                },
441            )
442            .await
443            .map_err(MeerkatMachine::driver_error_from_command_error)?
444        {
445            MeerkatMachineCommandResult::ImageOperationPhase(phase) => Ok(phase),
446            other => Err(RuntimeDriverError::Internal(format!(
447                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::complete_image_operation: {other:?}"
448            ))),
449        }
450    }
451
452    async fn classify_image_operation_terminal(
453        &self,
454        session_id: &SessionId,
455        operation_id: meerkat_core::image_generation::ImageOperationId,
456        observation: meerkat_core::image_generation::ImageProviderTerminalObservation,
457        provider_text: meerkat_core::image_generation::ProviderTextDisposition,
458    ) -> Result<meerkat_core::image_generation::ImageOperationTerminalClass, RuntimeDriverError>
459    {
460        match self
461            .execute_meerkat_machine_command(
462                None,
463                MeerkatMachineCommand::ClassifyImageOperationTerminal {
464                    session_id: session_id.clone(),
465                    operation_id,
466                    observation,
467                    provider_text,
468                },
469            )
470            .await
471            .map_err(MeerkatMachine::driver_error_from_command_error)?
472        {
473            MeerkatMachineCommandResult::ImageOperationTerminalClass(terminal) => Ok(terminal),
474            other => Err(RuntimeDriverError::Internal(format!(
475                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::classify_image_operation_terminal: {other:?}"
476            ))),
477        }
478    }
479
480    async fn restore_image_operation_override(
481        &self,
482        session_id: &SessionId,
483        operation_id: meerkat_core::image_generation::ImageOperationId,
484    ) -> Result<meerkat_core::image_generation::ImageOperationPhase, RuntimeDriverError> {
485        match self
486            .execute_meerkat_machine_command(
487                None,
488                MeerkatMachineCommand::RestoreImageOperationOverride {
489                    session_id: session_id.clone(),
490                    operation_id,
491                },
492            )
493            .await
494            .map_err(MeerkatMachine::driver_error_from_command_error)?
495        {
496            MeerkatMachineCommandResult::ImageOperationPhase(phase) => Ok(phase),
497            other => Err(RuntimeDriverError::Internal(format!(
498                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::restore_image_operation_override: {other:?}"
499            ))),
500        }
501    }
502}
503
504// ---------------------------------------------------------------------------
505// RuntimeControlPlane implementation
506// ---------------------------------------------------------------------------
507
508impl MeerkatMachine {
509    pub(crate) fn logical_runtime_id(session_id: &SessionId) -> LogicalRuntimeId {
510        LogicalRuntimeId::for_session(session_id)
511    }
512
513    pub(super) fn post_admission_signal_from_effects(
514        effects: &[crate::meerkat_machine::dsl::MeerkatMachineEffect],
515    ) -> crate::driver::ephemeral::PostAdmissionSignal {
516        effects
517            .iter()
518            .find_map(|effect| match effect {
519                crate::meerkat_machine::dsl::MeerkatMachineEffect::PostAdmissionSignal {
520                    signal,
521                } => Some(match signal {
522                    crate::meerkat_machine::dsl::PostAdmissionSignalKind::WakeLoop => {
523                        crate::driver::ephemeral::PostAdmissionSignal::WakeLoop
524                    }
525                    crate::meerkat_machine::dsl::PostAdmissionSignalKind::InterruptYielding => {
526                        crate::driver::ephemeral::PostAdmissionSignal::InterruptYielding
527                    }
528                    crate::meerkat_machine::dsl::PostAdmissionSignalKind::RequestImmediateProcessing => {
529                        crate::driver::ephemeral::PostAdmissionSignal::RequestImmediateProcessing
530                    }
531                }),
532                _ => None,
533            })
534            .unwrap_or(crate::driver::ephemeral::PostAdmissionSignal::None)
535    }
536
537    pub(super) fn driver_error_from_command_error(
538        err: MeerkatMachineCommandError,
539    ) -> RuntimeDriverError {
540        match err {
541            MeerkatMachineCommandError::Driver(err) => err,
542            MeerkatMachineCommandError::Control(err) => {
543                Self::driver_error_from_control_plane_error(err)
544            }
545        }
546    }
547
548    pub(super) fn control_plane_error_from_command_error(
549        err: MeerkatMachineCommandError,
550    ) -> RuntimeControlPlaneError {
551        match err {
552            MeerkatMachineCommandError::Control(err) => err,
553            MeerkatMachineCommandError::Driver(err) => {
554                RuntimeControlPlaneError::Internal(err.to_string())
555            }
556        }
557    }
558
559    pub(super) fn driver_error_from_control_plane_error(
560        err: RuntimeControlPlaneError,
561    ) -> RuntimeDriverError {
562        match err {
563            RuntimeControlPlaneError::NotFound(runtime_id) => {
564                RuntimeDriverError::NotFound { runtime_id }
565            }
566            RuntimeControlPlaneError::InvalidState { state } => {
567                RuntimeDriverError::NotReady { state }
568            }
569            RuntimeControlPlaneError::StoreError(message)
570            | RuntimeControlPlaneError::Internal(message) => RuntimeDriverError::Internal(message),
571        }
572    }
573
574    /// Resolve a LogicalRuntimeId to a registered SessionId for internal lookup.
575    pub(super) async fn resolve_session_id(
576        &self,
577        runtime_id: &LogicalRuntimeId,
578    ) -> Result<SessionId, RuntimeControlPlaneError> {
579        let sessions = self.sessions.read().await;
580        sessions
581            .iter()
582            .find_map(|(session_id, entry)| {
583                (&entry.runtime_id == runtime_id).then(|| session_id.clone())
584            })
585            .ok_or_else(|| RuntimeControlPlaneError::NotFound(runtime_id.clone()))
586    }
587
588    pub(super) async fn existing_session_runtime_state(
589        &self,
590        session_id: &SessionId,
591    ) -> Option<RuntimeState> {
592        let sessions = self.sessions.read().await;
593        let entry = sessions.get(session_id)?;
594        // DSL remains the transition authority for live, non-terminal states.
595        // Persistent drivers use the published control projection as the
596        // visibility barrier when DSL has crossed a run-return or terminal
597        // lifecycle boundary before the durable commit has published it.
598        let control = entry.control_snapshot();
599        let authority = entry
600            .dsl_authority
601            .lock()
602            .unwrap_or_else(std::sync::PoisonError::into_inner);
603        let dsl_phase = dsl_authority::runtime_phase_from_authority(&authority);
604        let dsl_pre_run_phase = dsl_authority::pre_run_phase_from_authority(&authority);
605        // The visible-phase arbitration verdict is machine-owned: mirror the
606        // generated `selected_raw_phase` (the chosen phase without the
607        // visibility rewrite). The classifier is total over the pure
608        // observations, so a failure is structurally unreachable; if it ever
609        // arises we fail closed to the most-terminal phase rather than re-derive
610        // a disposition in the shell.
611        match crate::meerkat_machine::resolve_visible_runtime_phase(
612            dsl_phase,
613            dsl_pre_run_phase,
614            control.phase,
615            control.pre_run_phase,
616            self.has_runtime_persistence(),
617        ) {
618            Ok(plan) => Some(plan.selected_raw_phase),
619            Err(reason) => {
620                tracing::error!(%session_id, %reason, "MeerkatMachine visible runtime phase resolution failed; failing closed to Destroyed");
621                Some(RuntimeState::Destroyed)
622            }
623        }
624    }
625
626    pub(super) async fn existing_session_visible_runtime_state(
627        &self,
628        session_id: &SessionId,
629    ) -> Option<RuntimeState> {
630        let sessions = self.sessions.read().await;
631        let entry = sessions.get(session_id)?;
632        let control = entry.control_snapshot();
633        let authority = entry
634            .dsl_authority
635            .lock()
636            .unwrap_or_else(std::sync::PoisonError::into_inner);
637        let dsl_phase = dsl_authority::runtime_phase_from_authority(&authority);
638        let dsl_pre_run_phase = dsl_authority::pre_run_phase_from_authority(&authority);
639        // Mirror the machine-owned `visible_phase` verdict (the externally-
640        // visible phase after the Running+pre_run(Retired)->Retired rewrite).
641        // The classifier is total; a failure is structurally unreachable and
642        // fails closed to the most-terminal phase rather than re-deriving in the
643        // shell.
644        match crate::meerkat_machine::resolve_visible_runtime_phase(
645            dsl_phase,
646            dsl_pre_run_phase,
647            control.phase,
648            control.pre_run_phase,
649            self.has_runtime_persistence(),
650        ) {
651            Ok(plan) => Some(plan.visible_phase),
652            Err(reason) => {
653                tracing::error!(%session_id, %reason, "MeerkatMachine visible runtime phase resolution failed; failing closed to Destroyed");
654                Some(RuntimeState::Destroyed)
655            }
656        }
657    }
658
659    /// Look up the session entry for a runtime ID, returning a control-plane error
660    /// if not found.
661    pub(super) async fn lookup_entry(
662        &self,
663        runtime_id: &LogicalRuntimeId,
664    ) -> Result<
665        (
666            SessionId,
667            SharedDriver,
668            SharedCompletionRegistry,
669            Option<mpsc::Sender<()>>,
670        ),
671        RuntimeControlPlaneError,
672    > {
673        let sessions = self.sessions.read().await;
674        let (session_id, entry) = sessions
675            .iter()
676            .find(|(_, entry)| &entry.runtime_id == runtime_id)
677            .ok_or_else(|| RuntimeControlPlaneError::NotFound(runtime_id.clone()))?;
678        Ok((
679            session_id.clone(),
680            entry.driver.clone(),
681            entry.completions.clone(),
682            entry.wake_sender(),
683        ))
684    }
685
686    pub async fn retire_runtime_control_plane(
687        &self,
688        runtime_id: &LogicalRuntimeId,
689    ) -> Result<RetireReport, RuntimeControlPlaneError> {
690        tracing::info!(
691            runtime_id = %runtime_id,
692            "MeerkatMachine::retire_runtime_control_plane start"
693        );
694        let (session_id, driver, completions, wake_tx) = self.lookup_entry(runtime_id).await?;
695        let gate = self.session_mutation_gate(&session_id).await;
696        // Bounded acquisition (defense in depth for the stop-under-gate
697        // deadlock class): the gate is only ever held for short critical
698        // sections, so a long wait means another task is parked while
699        // holding it — a bug in THAT task. Fail with a typed busy error so
700        // callers (e.g. a single-task mob actor) fast-fail instead of
701        // wedging every subsequent command behind an unbounded lock wait.
702        let _gate_guard = match gate {
703            Some(ref gate) => Some(
704                crate::tokio::time::timeout(
705                    std::time::Duration::from_secs(30),
706                    gate.lock(),
707                )
708                .await
709                .map_err(|_| {
710                    RuntimeControlPlaneError::Internal(format!(
711                        "retire for session {session_id} timed out acquiring the session                          mutation gate after 30s; the gate holder is likely deadlocked                          (stop-under-gate class) — retire can be retried"
712                    ))
713                })?,
714            ),
715            None => None,
716        };
717
718        let staged_dsl = self
719            .stage_session_dsl_transition(
720                &session_id,
721                crate::meerkat_machine::dsl::MeerkatMachineInput::Retire {
722                    session_id: crate::meerkat_machine::dsl::SessionId::from_domain(&session_id),
723                },
724                "Retire",
725            )
726            .await
727            .map_err(RuntimeControlPlaneError::Internal)?;
728
729        let mut drv = driver.lock().await;
730        let mut report = match Box::pin(machine_retire(&mut drv)).await {
731            Ok(report) => report,
732            Err(err) => {
733                drv.sync_control_projection_from_dsl_authority();
734                return Err(RuntimeControlPlaneError::Internal(err.to_string()));
735            }
736        };
737        drop(drv);
738
739        let mut commit_error = None;
740        if let Err(reason) = self
741            .commit_session_dsl_transition_preserving_committed_state(
742                &session_id,
743                staged_dsl,
744                "Retire",
745            )
746            .await
747        {
748            driver
749                .lock()
750                .await
751                .sync_control_projection_from_dsl_authority();
752            commit_error = Some(reason);
753        }
754
755        if report.inputs_pending_drain > 0 {
756            if let Some(ref tx) = wake_tx
757                && tx.send(()).await.is_ok()
758            {
759                if let Some(reason) = commit_error {
760                    return Err(RuntimeControlPlaneError::Internal(reason));
761                }
762                return Ok(report);
763            }
764
765            let mut drv = driver.lock().await;
766            let abandoned = drv
767                .abandon_pending_inputs(crate::input_state::InputAbandonReason::Retired)
768                .await
769                .map_err(|err| RuntimeControlPlaneError::Internal(err.to_string()))?;
770            drop(drv);
771            let result_class =
772                crate::meerkat_machine::driver::machine_resolve_runtime_terminated_completion_result(
773                    &driver,
774                )
775                .await
776                .map_err(|err| RuntimeControlPlaneError::Internal(err.to_string()))?;
777            let mut comp = completions.lock().await;
778            comp.resolve_all_runtime_terminated("retired without runtime loop", result_class);
779            report.inputs_abandoned += abandoned;
780            report.inputs_pending_drain = 0;
781        }
782        if let Some(reason) = commit_error {
783            return Err(RuntimeControlPlaneError::Internal(reason));
784        }
785        Ok(report)
786    }
787}
788
789#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
790#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
791impl crate::traits::RuntimeControlPlane for MeerkatMachine {
792    async fn ingest(
793        &self,
794        runtime_id: &LogicalRuntimeId,
795        input: Input,
796    ) -> Result<AcceptOutcome, RuntimeControlPlaneError> {
797        match self
798            .execute_meerkat_machine_command(
799                None,
800                MeerkatMachineCommand::Ingest {
801                    runtime_id: runtime_id.clone(),
802                    input,
803                },
804            )
805            .await
806            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
807        {
808            MeerkatMachineCommandResult::AcceptOutcome(outcome) => Ok(outcome),
809            other => Err(RuntimeControlPlaneError::Internal(format!(
810                "unexpected MeerkatMachineCommandResult for ingest: {other:?}"
811            ))),
812        }
813    }
814
815    async fn publish_event(
816        &self,
817        event: crate::runtime_event::RuntimeEventEnvelope,
818    ) -> Result<(), RuntimeControlPlaneError> {
819        match self
820            .execute_meerkat_machine_command(None, MeerkatMachineCommand::PublishEvent { event })
821            .await
822            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
823        {
824            MeerkatMachineCommandResult::Unit => Ok(()),
825            other => Err(RuntimeControlPlaneError::Internal(format!(
826                "unexpected MeerkatMachineCommandResult for publish_event: {other:?}"
827            ))),
828        }
829    }
830
831    async fn retire(
832        &self,
833        runtime_id: &LogicalRuntimeId,
834    ) -> Result<RetireReport, RuntimeControlPlaneError> {
835        self.retire_runtime_control_plane(runtime_id).await
836    }
837
838    async fn recycle(
839        &self,
840        runtime_id: &LogicalRuntimeId,
841    ) -> Result<RecycleReport, RuntimeControlPlaneError> {
842        match self
843            .execute_meerkat_machine_command(
844                None,
845                MeerkatMachineCommand::Recycle {
846                    runtime_id: runtime_id.clone(),
847                },
848            )
849            .await
850            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
851        {
852            MeerkatMachineCommandResult::RecycleReport(report) => Ok(report),
853            other => Err(RuntimeControlPlaneError::Internal(format!(
854                "unexpected MeerkatMachineCommandResult for recycle: {other:?}"
855            ))),
856        }
857    }
858
859    async fn reset(
860        &self,
861        runtime_id: &LogicalRuntimeId,
862    ) -> Result<crate::traits::ResetReport, RuntimeControlPlaneError> {
863        match self
864            .execute_meerkat_machine_command(
865                None,
866                MeerkatMachineCommand::Reset {
867                    runtime_id: runtime_id.clone(),
868                },
869            )
870            .await
871            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
872        {
873            MeerkatMachineCommandResult::ResetReport(report) => Ok(report),
874            other => Err(RuntimeControlPlaneError::Internal(format!(
875                "unexpected MeerkatMachineCommandResult for reset: {other:?}"
876            ))),
877        }
878    }
879
880    async fn recover(
881        &self,
882        runtime_id: &LogicalRuntimeId,
883    ) -> Result<RecoveryReport, RuntimeControlPlaneError> {
884        match self
885            .execute_meerkat_machine_command(
886                None,
887                MeerkatMachineCommand::Recover {
888                    runtime_id: runtime_id.clone(),
889                },
890            )
891            .await
892            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
893        {
894            MeerkatMachineCommandResult::RecoveryReport(report) => Ok(report),
895            other => Err(RuntimeControlPlaneError::Internal(format!(
896                "unexpected MeerkatMachineCommandResult for recover: {other:?}"
897            ))),
898        }
899    }
900
901    async fn destroy(
902        &self,
903        runtime_id: &LogicalRuntimeId,
904    ) -> Result<DestroyReport, RuntimeControlPlaneError> {
905        match self
906            .execute_meerkat_machine_command(
907                None,
908                MeerkatMachineCommand::Destroy {
909                    runtime_id: runtime_id.clone(),
910                },
911            )
912            .await
913            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
914        {
915            MeerkatMachineCommandResult::DestroyReport(report) => Ok(report),
916            other => Err(RuntimeControlPlaneError::Internal(format!(
917                "unexpected MeerkatMachineCommandResult for destroy: {other:?}"
918            ))),
919        }
920    }
921
922    async fn runtime_state(
923        &self,
924        runtime_id: &LogicalRuntimeId,
925    ) -> Result<RuntimeState, RuntimeControlPlaneError> {
926        match self
927            .execute_meerkat_machine_command(
928                None,
929                MeerkatMachineCommand::RuntimeState {
930                    runtime_id: runtime_id.clone(),
931                },
932            )
933            .await
934            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
935        {
936            MeerkatMachineCommandResult::RuntimeState(state) => Ok(state),
937            other => Err(RuntimeControlPlaneError::Internal(format!(
938                "unexpected MeerkatMachineCommandResult for runtime_state: {other:?}"
939            ))),
940        }
941    }
942
943    async fn load_boundary_receipt(
944        &self,
945        runtime_id: &LogicalRuntimeId,
946        run_id: &RunId,
947        sequence: u64,
948    ) -> Result<Option<meerkat_core::lifecycle::RunBoundaryReceipt>, RuntimeControlPlaneError> {
949        match self
950            .execute_meerkat_machine_command(
951                None,
952                MeerkatMachineCommand::LoadBoundaryReceipt {
953                    runtime_id: runtime_id.clone(),
954                    run_id: run_id.clone(),
955                    sequence,
956                },
957            )
958            .await
959            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
960        {
961            MeerkatMachineCommandResult::BoundaryReceipt(receipt) => Ok(receipt),
962            other => Err(RuntimeControlPlaneError::Internal(format!(
963                "unexpected MeerkatMachineCommandResult for load_boundary_receipt: {other:?}"
964            ))),
965        }
966    }
967}
968
969#[cfg(test)]
970#[allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
971mod tests {
972    use super::*;
973
974    /// Row #45 gate: control-plane not-found must map to the dedicated
975    /// `RuntimeDriverError::NotFound` carrying the runtime id, NOT to
976    /// `NotReady { state: Destroyed }` (which conflates never-existed/absent
977    /// with a torn-down lifecycle).
978    #[test]
979    fn control_plane_not_found_maps_to_driver_not_found() {
980        let runtime_id = LogicalRuntimeId("missing-runtime".to_string());
981        let mapped = MeerkatMachine::driver_error_from_control_plane_error(
982            RuntimeControlPlaneError::NotFound(runtime_id.clone()),
983        );
984
985        match mapped {
986            RuntimeDriverError::NotFound {
987                runtime_id: mapped_id,
988            } => assert_eq!(mapped_id, runtime_id),
989            other => panic!(
990                "expected RuntimeDriverError::NotFound, got {other:?} (must not collapse absence into NotReady/Destroyed)"
991            ),
992        }
993    }
994
995    /// Guard the negative half explicitly: the not-found mapping must never
996    /// surface as `NotReady { state: Destroyed }`.
997    #[test]
998    fn control_plane_not_found_is_not_destroyed_not_ready() {
999        let mapped = MeerkatMachine::driver_error_from_control_plane_error(
1000            RuntimeControlPlaneError::NotFound(LogicalRuntimeId("missing-runtime".to_string())),
1001        );
1002
1003        assert!(
1004            !matches!(
1005                mapped,
1006                RuntimeDriverError::NotReady {
1007                    state: RuntimeState::Destroyed
1008                }
1009            ),
1010            "not-found must not be laundered into NotReady{{Destroyed}}"
1011        );
1012    }
1013}