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