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        let _gate_guard = match gate {
645            Some(ref gate) => Some(gate.lock().await),
646            None => None,
647        };
648
649        let staged_dsl = self
650            .stage_session_dsl_transition(
651                &session_id,
652                crate::meerkat_machine::dsl::MeerkatMachineInput::Retire {
653                    session_id: crate::meerkat_machine::dsl::SessionId::from_domain(&session_id),
654                },
655                "Retire",
656            )
657            .await
658            .map_err(RuntimeControlPlaneError::Internal)?;
659
660        let mut drv = driver.lock().await;
661        let mut report = match Box::pin(machine_retire(&mut drv)).await {
662            Ok(report) => report,
663            Err(err) => {
664                drv.sync_control_projection_from_dsl_authority();
665                return Err(RuntimeControlPlaneError::Internal(err.to_string()));
666            }
667        };
668        drop(drv);
669
670        let mut commit_error = None;
671        if let Err(reason) = self
672            .commit_session_dsl_transition_preserving_committed_state(
673                &session_id,
674                staged_dsl,
675                "Retire",
676            )
677            .await
678        {
679            driver
680                .lock()
681                .await
682                .sync_control_projection_from_dsl_authority();
683            commit_error = Some(reason);
684        }
685
686        if report.inputs_pending_drain > 0 {
687            if let Some(ref tx) = wake_tx
688                && tx.send(()).await.is_ok()
689            {
690                if let Some(reason) = commit_error {
691                    return Err(RuntimeControlPlaneError::Internal(reason));
692                }
693                return Ok(report);
694            }
695
696            let mut drv = driver.lock().await;
697            let abandoned = drv
698                .abandon_pending_inputs(crate::input_state::InputAbandonReason::Retired)
699                .await
700                .map_err(|err| RuntimeControlPlaneError::Internal(err.to_string()))?;
701            drop(drv);
702            let result_class =
703                crate::meerkat_machine::driver::machine_resolve_runtime_terminated_completion_result(
704                    &driver,
705                )
706                .await
707                .map_err(|err| RuntimeControlPlaneError::Internal(err.to_string()))?;
708            let mut comp = completions.lock().await;
709            comp.resolve_all_runtime_terminated("retired without runtime loop", result_class);
710            report.inputs_abandoned += abandoned;
711            report.inputs_pending_drain = 0;
712        }
713        if let Some(reason) = commit_error {
714            return Err(RuntimeControlPlaneError::Internal(reason));
715        }
716        Ok(report)
717    }
718}
719
720#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
721#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
722impl crate::traits::RuntimeControlPlane for MeerkatMachine {
723    async fn ingest(
724        &self,
725        runtime_id: &LogicalRuntimeId,
726        input: Input,
727    ) -> Result<AcceptOutcome, RuntimeControlPlaneError> {
728        match self
729            .execute_meerkat_machine_command(
730                None,
731                MeerkatMachineCommand::Ingest {
732                    runtime_id: runtime_id.clone(),
733                    input,
734                },
735            )
736            .await
737            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
738        {
739            MeerkatMachineCommandResult::AcceptOutcome(outcome) => Ok(outcome),
740            other => Err(RuntimeControlPlaneError::Internal(format!(
741                "unexpected MeerkatMachineCommandResult for ingest: {other:?}"
742            ))),
743        }
744    }
745
746    async fn publish_event(
747        &self,
748        event: crate::runtime_event::RuntimeEventEnvelope,
749    ) -> Result<(), RuntimeControlPlaneError> {
750        match self
751            .execute_meerkat_machine_command(None, MeerkatMachineCommand::PublishEvent { event })
752            .await
753            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
754        {
755            MeerkatMachineCommandResult::Unit => Ok(()),
756            other => Err(RuntimeControlPlaneError::Internal(format!(
757                "unexpected MeerkatMachineCommandResult for publish_event: {other:?}"
758            ))),
759        }
760    }
761
762    async fn retire(
763        &self,
764        runtime_id: &LogicalRuntimeId,
765    ) -> Result<RetireReport, RuntimeControlPlaneError> {
766        self.retire_runtime_control_plane(runtime_id).await
767    }
768
769    async fn recycle(
770        &self,
771        runtime_id: &LogicalRuntimeId,
772    ) -> Result<RecycleReport, RuntimeControlPlaneError> {
773        match self
774            .execute_meerkat_machine_command(
775                None,
776                MeerkatMachineCommand::Recycle {
777                    runtime_id: runtime_id.clone(),
778                },
779            )
780            .await
781            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
782        {
783            MeerkatMachineCommandResult::RecycleReport(report) => Ok(report),
784            other => Err(RuntimeControlPlaneError::Internal(format!(
785                "unexpected MeerkatMachineCommandResult for recycle: {other:?}"
786            ))),
787        }
788    }
789
790    async fn reset(
791        &self,
792        runtime_id: &LogicalRuntimeId,
793    ) -> Result<crate::traits::ResetReport, RuntimeControlPlaneError> {
794        match self
795            .execute_meerkat_machine_command(
796                None,
797                MeerkatMachineCommand::Reset {
798                    runtime_id: runtime_id.clone(),
799                },
800            )
801            .await
802            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
803        {
804            MeerkatMachineCommandResult::ResetReport(report) => Ok(report),
805            other => Err(RuntimeControlPlaneError::Internal(format!(
806                "unexpected MeerkatMachineCommandResult for reset: {other:?}"
807            ))),
808        }
809    }
810
811    async fn recover(
812        &self,
813        runtime_id: &LogicalRuntimeId,
814    ) -> Result<RecoveryReport, RuntimeControlPlaneError> {
815        match self
816            .execute_meerkat_machine_command(
817                None,
818                MeerkatMachineCommand::Recover {
819                    runtime_id: runtime_id.clone(),
820                },
821            )
822            .await
823            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
824        {
825            MeerkatMachineCommandResult::RecoveryReport(report) => Ok(report),
826            other => Err(RuntimeControlPlaneError::Internal(format!(
827                "unexpected MeerkatMachineCommandResult for recover: {other:?}"
828            ))),
829        }
830    }
831
832    async fn destroy(
833        &self,
834        runtime_id: &LogicalRuntimeId,
835    ) -> Result<DestroyReport, RuntimeControlPlaneError> {
836        match self
837            .execute_meerkat_machine_command(
838                None,
839                MeerkatMachineCommand::Destroy {
840                    runtime_id: runtime_id.clone(),
841                },
842            )
843            .await
844            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
845        {
846            MeerkatMachineCommandResult::DestroyReport(report) => Ok(report),
847            other => Err(RuntimeControlPlaneError::Internal(format!(
848                "unexpected MeerkatMachineCommandResult for destroy: {other:?}"
849            ))),
850        }
851    }
852
853    async fn runtime_state(
854        &self,
855        runtime_id: &LogicalRuntimeId,
856    ) -> Result<RuntimeState, RuntimeControlPlaneError> {
857        match self
858            .execute_meerkat_machine_command(
859                None,
860                MeerkatMachineCommand::RuntimeState {
861                    runtime_id: runtime_id.clone(),
862                },
863            )
864            .await
865            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
866        {
867            MeerkatMachineCommandResult::RuntimeState(state) => Ok(state),
868            other => Err(RuntimeControlPlaneError::Internal(format!(
869                "unexpected MeerkatMachineCommandResult for runtime_state: {other:?}"
870            ))),
871        }
872    }
873
874    async fn load_boundary_receipt(
875        &self,
876        runtime_id: &LogicalRuntimeId,
877        run_id: &RunId,
878        sequence: u64,
879    ) -> Result<Option<meerkat_core::lifecycle::RunBoundaryReceipt>, RuntimeControlPlaneError> {
880        match self
881            .execute_meerkat_machine_command(
882                None,
883                MeerkatMachineCommand::LoadBoundaryReceipt {
884                    runtime_id: runtime_id.clone(),
885                    run_id: run_id.clone(),
886                    sequence,
887                },
888            )
889            .await
890            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
891        {
892            MeerkatMachineCommandResult::BoundaryReceipt(receipt) => Ok(receipt),
893            other => Err(RuntimeControlPlaneError::Internal(format!(
894                "unexpected MeerkatMachineCommandResult for load_boundary_receipt: {other:?}"
895            ))),
896        }
897    }
898}
899
900#[cfg(test)]
901#[allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
902mod tests {
903    use super::*;
904
905    /// Row #45 gate: control-plane not-found must map to the dedicated
906    /// `RuntimeDriverError::NotFound` carrying the runtime id, NOT to
907    /// `NotReady { state: Destroyed }` (which conflates never-existed/absent
908    /// with a torn-down lifecycle).
909    #[test]
910    fn control_plane_not_found_maps_to_driver_not_found() {
911        let runtime_id = LogicalRuntimeId("missing-runtime".to_string());
912        let mapped = MeerkatMachine::driver_error_from_control_plane_error(
913            RuntimeControlPlaneError::NotFound(runtime_id.clone()),
914        );
915
916        match mapped {
917            RuntimeDriverError::NotFound {
918                runtime_id: mapped_id,
919            } => assert_eq!(mapped_id, runtime_id),
920            other => panic!(
921                "expected RuntimeDriverError::NotFound, got {other:?} (must not collapse absence into NotReady/Destroyed)"
922            ),
923        }
924    }
925
926    /// Guard the negative half explicitly: the not-found mapping must never
927    /// surface as `NotReady { state: Destroyed }`.
928    #[test]
929    fn control_plane_not_found_is_not_destroyed_not_ready() {
930        let mapped = MeerkatMachine::driver_error_from_control_plane_error(
931            RuntimeControlPlaneError::NotFound(LogicalRuntimeId("missing-runtime".to_string())),
932        );
933
934        assert!(
935            !matches!(
936                mapped,
937                RuntimeDriverError::NotReady {
938                    state: RuntimeState::Destroyed
939                }
940            ),
941            "not-found must not be laundered into NotReady{{Destroyed}}"
942        );
943    }
944}