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                    member_residency: MemberResidencyExpectation::Unfenced,
20                    expected_attachment: None,
21                },
22            )
23            .await
24            .map_err(MeerkatMachine::driver_error_from_command_error)?
25        {
26            MeerkatMachineCommandResult::AcceptWithCompletion {
27                outcome,
28                handle: _,
29                admission_signal: _,
30            } => Ok(outcome),
31            other => Err(RuntimeDriverError::Internal(format!(
32                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::accept_input: {other:?}"
33            ))),
34        }
35    }
36
37    async fn accept_input_with_completion(
38        &self,
39        session_id: &SessionId,
40        input: Input,
41    ) -> Result<(AcceptOutcome, Option<crate::completion::CompletionHandle>), RuntimeDriverError>
42    {
43        tracing::debug!(
44            session_id = %session_id,
45            input_id = %input.id(),
46            "SessionServiceRuntimeExt::accept_input_with_completion entered"
47        );
48        self.accept_input_with_completion_boxed(session_id, input)
49            .await
50    }
51
52    async fn runtime_state(
53        &self,
54        session_id: &SessionId,
55    ) -> Result<RuntimeState, RuntimeDriverError> {
56        let runtime_id = MeerkatMachine::logical_runtime_id(session_id);
57        match self
58            .execute_meerkat_machine_command(
59                None,
60                MeerkatMachineCommand::RuntimeState { runtime_id },
61            )
62            .await
63            .map_err(MeerkatMachine::driver_error_from_command_error)?
64        {
65            MeerkatMachineCommandResult::RuntimeState(state) => Ok(state),
66            other => Err(RuntimeDriverError::Internal(format!(
67                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::runtime_state: {other:?}"
68            ))),
69        }
70    }
71
72    async fn retire_runtime(
73        &self,
74        session_id: &SessionId,
75    ) -> Result<RetireReport, RuntimeDriverError> {
76        let runtime_id = MeerkatMachine::logical_runtime_id(session_id);
77        match self
78            .execute_meerkat_machine_command(None, MeerkatMachineCommand::Retire { runtime_id })
79            .await
80            .map_err(MeerkatMachine::driver_error_from_command_error)?
81        {
82            MeerkatMachineCommandResult::RetireReport(report) => Ok(report),
83            other => Err(RuntimeDriverError::Internal(format!(
84                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::retire_runtime: {other:?}"
85            ))),
86        }
87    }
88
89    async fn reset_runtime(
90        &self,
91        session_id: &SessionId,
92    ) -> Result<ResetReport, RuntimeDriverError> {
93        let runtime_id = MeerkatMachine::logical_runtime_id(session_id);
94        match self
95            .execute_meerkat_machine_command(None, MeerkatMachineCommand::Reset { runtime_id })
96            .await
97            .map_err(MeerkatMachine::driver_error_from_command_error)?
98        {
99            MeerkatMachineCommandResult::ResetReport(report) => Ok(report),
100            other => Err(RuntimeDriverError::Internal(format!(
101                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::reset_runtime: {other:?}"
102            ))),
103        }
104    }
105
106    async fn input_state(
107        &self,
108        session_id: &SessionId,
109        input_id: &InputId,
110    ) -> Result<Option<StoredInputState>, RuntimeDriverError> {
111        match self
112            .execute_meerkat_machine_command(
113                None,
114                MeerkatMachineCommand::InputState {
115                    session_id: session_id.clone(),
116                    input_id: input_id.clone(),
117                },
118            )
119            .await
120            .map_err(MeerkatMachine::driver_error_from_command_error)?
121        {
122            MeerkatMachineCommandResult::InputState(state) => Ok(state),
123            other => Err(RuntimeDriverError::Internal(format!(
124                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::input_state: {other:?}"
125            ))),
126        }
127    }
128
129    async fn input_state_by_idempotency_key(
130        &self,
131        session_id: &SessionId,
132        idempotency_key: &str,
133    ) -> Result<Option<StoredInputState>, RuntimeDriverError> {
134        match self
135            .execute_meerkat_machine_command(
136                None,
137                MeerkatMachineCommand::InputStateByIdempotencyKey {
138                    session_id: session_id.clone(),
139                    idempotency_key: idempotency_key.to_string(),
140                },
141            )
142            .await
143            .map_err(MeerkatMachine::driver_error_from_command_error)?
144        {
145            MeerkatMachineCommandResult::InputState(state) => Ok(state),
146            other => Err(RuntimeDriverError::Internal(format!(
147                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::input_state_by_idempotency_key: {other:?}"
148            ))),
149        }
150    }
151
152    async fn interaction_terminal_status(
153        &self,
154        session_id: &SessionId,
155        selector: crate::terminal_status::InteractionSelector,
156    ) -> Result<
157        Option<crate::terminal_status::Sourced<crate::terminal_status::InteractionTerminalReport>>,
158        RuntimeDriverError,
159    > {
160        match self
161            .execute_meerkat_machine_command(
162                None,
163                MeerkatMachineCommand::InteractionTerminalStatus {
164                    session_id: session_id.clone(),
165                    selector,
166                },
167            )
168            .await
169            .map_err(MeerkatMachine::driver_error_from_command_error)?
170        {
171            MeerkatMachineCommandResult::InteractionTerminalStatus(report) => Ok(report),
172            other => Err(RuntimeDriverError::Internal(format!(
173                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::interaction_terminal_status: {other:?}"
174            ))),
175        }
176    }
177
178    async fn run_terminal_status(
179        &self,
180        session_id: &SessionId,
181        run_id: &meerkat_core::lifecycle::RunId,
182    ) -> Result<
183        crate::terminal_status::Sourced<crate::terminal_status::RunTerminalReport>,
184        RuntimeDriverError,
185    > {
186        match self
187            .execute_meerkat_machine_command(
188                None,
189                MeerkatMachineCommand::RunTerminalStatus {
190                    session_id: session_id.clone(),
191                    run_id: run_id.clone(),
192                },
193            )
194            .await
195            .map_err(MeerkatMachine::driver_error_from_command_error)?
196        {
197            MeerkatMachineCommandResult::RunTerminalStatus(report) => Ok(report),
198            other => Err(RuntimeDriverError::Internal(format!(
199                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::run_terminal_status: {other:?}"
200            ))),
201        }
202    }
203
204    async fn list_active_inputs(
205        &self,
206        session_id: &SessionId,
207    ) -> Result<Vec<InputId>, RuntimeDriverError> {
208        match self
209            .execute_meerkat_machine_command(
210                None,
211                MeerkatMachineCommand::ListActiveInputs {
212                    session_id: session_id.clone(),
213                },
214            )
215            .await
216            .map_err(MeerkatMachine::driver_error_from_command_error)?
217        {
218            MeerkatMachineCommandResult::ActiveInputs(inputs) => Ok(inputs),
219            other => Err(RuntimeDriverError::Internal(format!(
220                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::list_active_inputs: {other:?}"
221            ))),
222        }
223    }
224
225    async fn reconfigure_session_llm_identity(
226        &self,
227        session_id: &SessionId,
228        request: SessionLlmReconfigureRequest,
229    ) -> Result<SessionLlmReconfigureReport, RuntimeDriverError> {
230        let host = self.llm_reconfigure_host()?;
231        let _turn_finalization_guard = host.acquire_turn_finalization_boundary(session_id).await?;
232        self.reconfigure_session_llm_identity_under_turn_finalization_boundary(session_id, request)
233            .await
234    }
235
236    async fn resolved_session_llm_capabilities(
237        &self,
238        session_id: &SessionId,
239    ) -> Result<Option<SessionLlmCapabilitySurface>, RuntimeDriverError> {
240        match self
241            .execute_meerkat_machine_command(
242                None,
243                MeerkatMachineCommand::ResolvedSessionLlmCapabilities {
244                    session_id: session_id.clone(),
245                },
246            )
247            .await
248            .map_err(MeerkatMachine::driver_error_from_command_error)?
249        {
250            MeerkatMachineCommandResult::ResolvedSessionLlmCapabilities(capabilities) => {
251                Ok(capabilities)
252            }
253            other => Err(RuntimeDriverError::Internal(format!(
254                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::resolved_session_llm_capabilities: {other:?}"
255            ))),
256        }
257    }
258
259    async fn configure_model_routing_baseline(
260        &self,
261        session_id: &SessionId,
262        baseline_model: meerkat_core::lifecycle::run_primitive::ModelId,
263        realtime_capable: bool,
264    ) -> Result<(), RuntimeDriverError> {
265        match self
266            .execute_meerkat_machine_command(
267                None,
268                MeerkatMachineCommand::ConfigureModelRoutingBaseline {
269                    session_id: session_id.clone(),
270                    baseline_model,
271                    realtime_capable,
272                },
273            )
274            .await
275            .map_err(MeerkatMachine::driver_error_from_command_error)?
276        {
277            MeerkatMachineCommandResult::Unit => Ok(()),
278            other => Err(RuntimeDriverError::Internal(format!(
279                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::configure_model_routing_baseline: {other:?}"
280            ))),
281        }
282    }
283
284    async fn session_model_routing_status(
285        &self,
286        session_id: &SessionId,
287    ) -> Result<meerkat_core::image_generation::SessionModelRoutingStatus, RuntimeDriverError> {
288        match self
289            .execute_meerkat_machine_command(
290                None,
291                MeerkatMachineCommand::SessionModelRoutingStatus {
292                    session_id: session_id.clone(),
293                },
294            )
295            .await
296            .map_err(MeerkatMachine::driver_error_from_command_error)?
297        {
298            MeerkatMachineCommandResult::SessionModelRoutingStatus(status) => Ok(status),
299            other => Err(RuntimeDriverError::Internal(format!(
300                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::session_model_routing_status: {other:?}"
301            ))),
302        }
303    }
304
305    async fn request_switch_turn(
306        &self,
307        session_id: &SessionId,
308        request: crate::meerkat_machine_types::SwitchTurnRequest,
309    ) -> Result<meerkat_core::image_generation::SwitchTurnControlResult, RuntimeDriverError> {
310        // UntilChanged performs a live LLM reconfigure inside the generated
311        // switch transaction. Enclose the complete routing + live mutation +
312        // persistence sequence in the same stable service boundary as direct
313        // reconfigure; the nested host methods deliberately acquire recovery
314        // only while the machine mutation gate is held.
315        let _turn_finalization_guard = if matches!(
316            &request.intent.duration,
317            meerkat_core::image_generation::SwitchTurnDuration::UntilChanged
318        ) {
319            Some(
320                self.llm_reconfigure_host()?
321                    .acquire_turn_finalization_boundary(session_id)
322                    .await?,
323            )
324        } else {
325            None
326        };
327        match self
328            .execute_meerkat_machine_command(
329                None,
330                MeerkatMachineCommand::RequestSwitchTurn {
331                    session_id: session_id.clone(),
332                    request: Box::new(request),
333                },
334            )
335            .await
336            .map_err(MeerkatMachine::driver_error_from_command_error)?
337        {
338            MeerkatMachineCommandResult::SwitchTurnControlResult(result) => Ok(result),
339            other => Err(RuntimeDriverError::Internal(format!(
340                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::request_switch_turn: {other:?}"
341            ))),
342        }
343    }
344
345    async fn admit_model_routing_assistant_turn(
346        &self,
347        session_id: &SessionId,
348    ) -> Result<(), RuntimeDriverError> {
349        match self
350            .execute_meerkat_machine_command(
351                None,
352                MeerkatMachineCommand::AdmitModelRoutingAssistantTurn {
353                    session_id: session_id.clone(),
354                },
355            )
356            .await
357            .map_err(MeerkatMachine::driver_error_from_command_error)?
358        {
359            MeerkatMachineCommandResult::Unit => Ok(()),
360            other => Err(RuntimeDriverError::Internal(format!(
361                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::admit_model_routing_assistant_turn: {other:?}"
362            ))),
363        }
364    }
365
366    async fn begin_image_operation(
367        &self,
368        session_id: &SessionId,
369        request: crate::meerkat_machine_types::ImageOperationRoutingRequest,
370    ) -> Result<crate::meerkat_machine_types::ImageOperationRoutingResult, RuntimeDriverError> {
371        match self
372            .execute_meerkat_machine_command(
373                None,
374                MeerkatMachineCommand::BeginImageOperation {
375                    session_id: session_id.clone(),
376                    request: Box::new(request),
377                },
378            )
379            .await
380            .map_err(MeerkatMachine::driver_error_from_command_error)?
381        {
382            MeerkatMachineCommandResult::ImageOperationRoutingResult(result) => Ok(result),
383            other => Err(RuntimeDriverError::Internal(format!(
384                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::begin_image_operation: {other:?}"
385            ))),
386        }
387    }
388
389    async fn deny_image_operation_plan(
390        &self,
391        session_id: &SessionId,
392        operation_id: meerkat_core::image_generation::ImageOperationId,
393        reason: meerkat_core::image_generation::ImageOperationDenialReason,
394    ) -> Result<meerkat_core::image_generation::ImageOperationPhase, RuntimeDriverError> {
395        match self
396            .execute_meerkat_machine_command(
397                None,
398                MeerkatMachineCommand::DenyImageOperationPlan {
399                    session_id: session_id.clone(),
400                    operation_id,
401                    reason,
402                },
403            )
404            .await
405            .map_err(MeerkatMachine::driver_error_from_command_error)?
406        {
407            MeerkatMachineCommandResult::ImageOperationPhase(phase) => Ok(phase),
408            other => Err(RuntimeDriverError::Internal(format!(
409                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::deny_image_operation_plan: {other:?}"
410            ))),
411        }
412    }
413
414    async fn activate_image_operation_override(
415        &self,
416        session_id: &SessionId,
417        operation_id: meerkat_core::image_generation::ImageOperationId,
418    ) -> Result<meerkat_core::image_generation::ImageOperationPhase, RuntimeDriverError> {
419        match self
420            .execute_meerkat_machine_command(
421                None,
422                MeerkatMachineCommand::ActivateImageOperationOverride {
423                    session_id: session_id.clone(),
424                    operation_id,
425                },
426            )
427            .await
428            .map_err(MeerkatMachine::driver_error_from_command_error)?
429        {
430            MeerkatMachineCommandResult::ImageOperationPhase(phase) => Ok(phase),
431            other => Err(RuntimeDriverError::Internal(format!(
432                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::activate_image_operation_override: {other:?}"
433            ))),
434        }
435    }
436
437    async fn complete_image_operation(
438        &self,
439        session_id: &SessionId,
440        operation_id: meerkat_core::image_generation::ImageOperationId,
441        terminal: meerkat_core::image_generation::ImageOperationTerminalClass,
442    ) -> Result<meerkat_core::image_generation::ImageOperationPhase, RuntimeDriverError> {
443        match self
444            .execute_meerkat_machine_command(
445                None,
446                MeerkatMachineCommand::CompleteImageOperation {
447                    session_id: session_id.clone(),
448                    operation_id,
449                    terminal,
450                },
451            )
452            .await
453            .map_err(MeerkatMachine::driver_error_from_command_error)?
454        {
455            MeerkatMachineCommandResult::ImageOperationPhase(phase) => Ok(phase),
456            other => Err(RuntimeDriverError::Internal(format!(
457                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::complete_image_operation: {other:?}"
458            ))),
459        }
460    }
461
462    async fn classify_image_operation_terminal(
463        &self,
464        session_id: &SessionId,
465        operation_id: meerkat_core::image_generation::ImageOperationId,
466        observation: meerkat_core::image_generation::ImageProviderTerminalObservation,
467        provider_text: meerkat_core::image_generation::ProviderTextDisposition,
468    ) -> Result<meerkat_core::image_generation::ImageOperationTerminalClass, RuntimeDriverError>
469    {
470        match self
471            .execute_meerkat_machine_command(
472                None,
473                MeerkatMachineCommand::ClassifyImageOperationTerminal {
474                    session_id: session_id.clone(),
475                    operation_id,
476                    observation,
477                    provider_text,
478                },
479            )
480            .await
481            .map_err(MeerkatMachine::driver_error_from_command_error)?
482        {
483            MeerkatMachineCommandResult::ImageOperationTerminalClass(terminal) => Ok(terminal),
484            other => Err(RuntimeDriverError::Internal(format!(
485                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::classify_image_operation_terminal: {other:?}"
486            ))),
487        }
488    }
489
490    async fn restore_image_operation_override(
491        &self,
492        session_id: &SessionId,
493        operation_id: meerkat_core::image_generation::ImageOperationId,
494    ) -> Result<meerkat_core::image_generation::ImageOperationPhase, RuntimeDriverError> {
495        match self
496            .execute_meerkat_machine_command(
497                None,
498                MeerkatMachineCommand::RestoreImageOperationOverride {
499                    session_id: session_id.clone(),
500                    operation_id,
501                },
502            )
503            .await
504            .map_err(MeerkatMachine::driver_error_from_command_error)?
505        {
506            MeerkatMachineCommandResult::ImageOperationPhase(phase) => Ok(phase),
507            other => Err(RuntimeDriverError::Internal(format!(
508                "unexpected MeerkatMachineCommandResult for SessionServiceRuntimeExt::restore_image_operation_override: {other:?}"
509            ))),
510        }
511    }
512}
513
514// ---------------------------------------------------------------------------
515// RuntimeControlPlane implementation
516// ---------------------------------------------------------------------------
517
518impl MeerkatMachine {
519    pub(crate) fn logical_runtime_id(session_id: &SessionId) -> LogicalRuntimeId {
520        LogicalRuntimeId::for_session(session_id)
521    }
522
523    pub(super) fn post_admission_signal_from_effects(
524        effects: &[crate::meerkat_machine::dsl::MeerkatMachineEffect],
525    ) -> crate::driver::ephemeral::PostAdmissionSignal {
526        effects
527            .iter()
528            .find_map(|effect| match effect {
529                crate::meerkat_machine::dsl::MeerkatMachineEffect::PostAdmissionSignal {
530                    signal,
531                } => Some(match signal {
532                    crate::meerkat_machine::dsl::PostAdmissionSignalKind::WakeLoop => {
533                        crate::driver::ephemeral::PostAdmissionSignal::WakeLoop
534                    }
535                    crate::meerkat_machine::dsl::PostAdmissionSignalKind::InterruptYielding => {
536                        crate::driver::ephemeral::PostAdmissionSignal::InterruptYielding
537                    }
538                    crate::meerkat_machine::dsl::PostAdmissionSignalKind::RequestImmediateProcessing => {
539                        crate::driver::ephemeral::PostAdmissionSignal::RequestImmediateProcessing
540                    }
541                }),
542                _ => None,
543            })
544            .unwrap_or(crate::driver::ephemeral::PostAdmissionSignal::None)
545    }
546
547    pub(super) fn driver_error_from_command_error(
548        err: MeerkatMachineCommandError,
549    ) -> RuntimeDriverError {
550        match err {
551            MeerkatMachineCommandError::Driver(err) => err,
552            MeerkatMachineCommandError::Control(err) => {
553                Self::driver_error_from_control_plane_error(err)
554            }
555        }
556    }
557
558    pub(super) fn control_plane_error_from_command_error(
559        err: MeerkatMachineCommandError,
560    ) -> RuntimeControlPlaneError {
561        match err {
562            MeerkatMachineCommandError::Control(err) => err,
563            MeerkatMachineCommandError::Driver(err) => {
564                RuntimeControlPlaneError::Internal(err.to_string())
565            }
566        }
567    }
568
569    pub(super) fn driver_error_from_control_plane_error(
570        err: RuntimeControlPlaneError,
571    ) -> RuntimeDriverError {
572        match err {
573            RuntimeControlPlaneError::NotFound(runtime_id) => {
574                RuntimeDriverError::NotFound { runtime_id }
575            }
576            RuntimeControlPlaneError::InvalidState { state } => {
577                RuntimeDriverError::NotReady { state }
578            }
579            RuntimeControlPlaneError::StoreError(message)
580            | RuntimeControlPlaneError::Internal(message) => RuntimeDriverError::Internal(message),
581        }
582    }
583
584    /// Resolve a LogicalRuntimeId to a registered SessionId for internal lookup.
585    pub(super) async fn resolve_session_id(
586        &self,
587        runtime_id: &LogicalRuntimeId,
588    ) -> Result<SessionId, RuntimeControlPlaneError> {
589        let sessions = self.sessions.read().await;
590        sessions
591            .iter()
592            .find_map(|(session_id, entry)| {
593                (&entry.runtime_id == runtime_id).then(|| session_id.clone())
594            })
595            .ok_or_else(|| RuntimeControlPlaneError::NotFound(runtime_id.clone()))
596    }
597
598    pub(super) async fn existing_session_runtime_state(
599        &self,
600        session_id: &SessionId,
601    ) -> Option<RuntimeState> {
602        let sessions = self.sessions.read().await;
603        let entry = sessions.get(session_id)?;
604        // DSL remains the transition authority for live, non-terminal states.
605        // Persistent drivers use the published control projection as the
606        // visibility barrier when DSL has crossed a run-return or terminal
607        // lifecycle boundary before the durable commit has published it.
608        let control = entry.control_snapshot();
609        let authority = entry
610            .dsl_authority
611            .lock()
612            .unwrap_or_else(std::sync::PoisonError::into_inner);
613        let dsl_phase = dsl_authority::runtime_phase_from_authority(&authority);
614        let dsl_pre_run_phase = dsl_authority::pre_run_phase_from_authority(&authority);
615        // The visible-phase arbitration verdict is machine-owned: mirror the
616        // generated `selected_raw_phase` (the chosen phase without the
617        // visibility rewrite). The classifier is total over the pure
618        // observations, so a failure is structurally unreachable; if it ever
619        // arises we fail closed to the most-terminal phase rather than re-derive
620        // a disposition in the shell.
621        match crate::meerkat_machine::resolve_visible_runtime_phase(
622            dsl_phase,
623            dsl_pre_run_phase,
624            control.phase,
625            control.pre_run_phase,
626            self.has_runtime_persistence(),
627        ) {
628            Ok(plan) => Some(plan.selected_raw_phase),
629            Err(reason) => {
630                tracing::error!(%session_id, %reason, "MeerkatMachine visible runtime phase resolution failed; failing closed to Destroyed");
631                Some(RuntimeState::Destroyed)
632            }
633        }
634    }
635
636    pub(super) async fn existing_session_visible_runtime_state(
637        &self,
638        session_id: &SessionId,
639    ) -> Option<RuntimeState> {
640        let sessions = self.sessions.read().await;
641        let entry = sessions.get(session_id)?;
642        let control = entry.control_snapshot();
643        let authority = entry
644            .dsl_authority
645            .lock()
646            .unwrap_or_else(std::sync::PoisonError::into_inner);
647        let dsl_phase = dsl_authority::runtime_phase_from_authority(&authority);
648        let dsl_pre_run_phase = dsl_authority::pre_run_phase_from_authority(&authority);
649        // Mirror the machine-owned `visible_phase` verdict (the externally-
650        // visible phase after the Running+pre_run(Retired)->Retired rewrite).
651        // The classifier is total; a failure is structurally unreachable and
652        // fails closed to the most-terminal phase rather than re-deriving in the
653        // shell.
654        match crate::meerkat_machine::resolve_visible_runtime_phase(
655            dsl_phase,
656            dsl_pre_run_phase,
657            control.phase,
658            control.pre_run_phase,
659            self.has_runtime_persistence(),
660        ) {
661            Ok(plan) => Some(plan.visible_phase),
662            Err(reason) => {
663                tracing::error!(%session_id, %reason, "MeerkatMachine visible runtime phase resolution failed; failing closed to Destroyed");
664                Some(RuntimeState::Destroyed)
665            }
666        }
667    }
668
669    /// Look up the session entry for a runtime ID, returning a control-plane error
670    /// if not found.
671    pub(super) async fn lookup_entry(
672        &self,
673        runtime_id: &LogicalRuntimeId,
674    ) -> Result<
675        (
676            SessionId,
677            SharedDriver,
678            SharedCompletionRegistry,
679            Option<mpsc::Sender<()>>,
680        ),
681        RuntimeControlPlaneError,
682    > {
683        let sessions = self.sessions.read().await;
684        let (session_id, entry) = sessions
685            .iter()
686            .find(|(_, entry)| &entry.runtime_id == runtime_id)
687            .ok_or_else(|| RuntimeControlPlaneError::NotFound(runtime_id.clone()))?;
688        Ok((
689            session_id.clone(),
690            entry.driver.clone(),
691            entry.completions.clone(),
692            entry.wake_sender(),
693        ))
694    }
695
696    /// Re-capture every archive/retire handle only after the exact current
697    /// session mutation gate is held. A pending executor attachment can become
698    /// attached while a lifecycle command waits for M; in particular, its wake
699    /// sender must not remain the pre-M `None` snapshot.
700    async fn capture_archive_lease_entry_under_mutation_guard(
701        &self,
702        runtime_id: &LogicalRuntimeId,
703        session_id: &SessionId,
704        expected_driver: &SharedDriver,
705        _mutation_guard: &crate::tokio::sync::OwnedMutexGuard<()>,
706    ) -> Result<
707        (
708            SharedDriver,
709            SharedCompletionRegistry,
710            Option<mpsc::Sender<()>>,
711            Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorPublicationHandle>>,
712        ),
713        RuntimeControlPlaneError,
714    > {
715        let sessions = self.sessions.read().await;
716        let entry = sessions.get(session_id).ok_or_else(|| {
717            RuntimeControlPlaneError::Internal(format!(
718                "runtime {runtime_id} disappeared while its archive/retire mutation gate was held"
719            ))
720        })?;
721        if &entry.runtime_id != runtime_id || !Arc::ptr_eq(&entry.driver, expected_driver) {
722            return Err(RuntimeControlPlaneError::Internal(format!(
723                "runtime {runtime_id} changed authority while its archive/retire mutation gate was held"
724            )));
725        }
726        Ok((
727            Arc::clone(&entry.driver),
728            Arc::clone(&entry.completions),
729            entry.wake_sender(),
730            entry.publication_handle(),
731        ))
732    }
733
734    /// Fail a lifecycle operation before it attempts the live/mutation gates
735    /// when this session is already inside exact unregister convergence.
736    ///
737    /// Callers own the stable registration transaction. A recovered Draining
738    /// retry anchor may have no process-local coordinator after restart; in
739    /// that case transfer retry to the process-lifetime cleanup executor and
740    /// still return immediately. Archive owns the outer turn-finalization
741    /// boundary here, so waiting for unregister would invert that boundary
742    /// with the unregister worker's post-stop callback.
743    async fn reject_unregister_overlap_under_registration_transaction(
744        &self,
745        session_id: &SessionId,
746    ) -> Result<(), RuntimeControlPlaneError> {
747        let (blocked, coordinator_present, pending_finalization, runtime_state) = {
748            let sessions = self.sessions.read().await;
749            let entry = sessions.get(session_id).ok_or_else(|| {
750                RuntimeControlPlaneError::NotFound(LogicalRuntimeId::for_session(session_id))
751            })?;
752            let registration_phase = entry
753                .dsl_authority
754                .lock()
755                .unwrap_or_else(std::sync::PoisonError::into_inner)
756                .state()
757                .registration_phase;
758            let coordinator_present = entry.unregister_coordinator.is_some();
759            let pending_finalization = entry.pending_unregister_finalization.is_some();
760            (
761                coordinator_present
762                    || pending_finalization
763                    || registration_phase
764                        == crate::meerkat_machine::dsl::RegistrationPhase::Draining,
765                coordinator_present,
766                pending_finalization,
767                entry.control_snapshot().phase,
768            )
769        };
770        if !blocked {
771            return Ok(());
772        }
773        if !coordinator_present && !pending_finalization {
774            let cleanup_spawner = super::MachineCleanupTaskSpawner::acquire()
775                .map_err(|error| RuntimeControlPlaneError::Internal(error.to_string()))?;
776            let machine = self.clone();
777            let retry_session_id = session_id.clone();
778            drop(cleanup_spawner.spawn(async move {
779                if let Err(error) = machine.try_unregister_session(&retry_session_id).await {
780                    tracing::warn!(
781                        session_id = %retry_session_id,
782                        %error,
783                        "cold unregister retry started by lifecycle overlap failed"
784                    );
785                }
786            }));
787        }
788        if pending_finalization {
789            return Err(RuntimeControlPlaneError::Internal(
790                RuntimeDriverError::UnregisterFinalizationOutcomeUnknown {
791                    reason: format!(
792                        "session {session_id} retains an ambiguous unregister finalization; retry unregister before applying any other lifecycle mutation"
793                    ),
794                }
795                .to_string(),
796            ));
797        }
798        Err(RuntimeControlPlaneError::InvalidState {
799            state: runtime_state,
800        })
801    }
802
803    /// Acquire the current session mutation authority for an archive before
804    /// the session layer takes its recovery/checkpointer gates.
805    pub async fn prepare_session_archive_lease(
806        &self,
807        session_id: &SessionId,
808    ) -> Result<Option<super::MachineSessionArchiveLease>, RuntimeControlPlaneError> {
809        let runtime_id = LogicalRuntimeId::for_session(session_id);
810        // Archive's outer turn-finalization boundary is already held. Take
811        // the stable absent-entry transaction before observing the map or
812        // durable lifecycle so a delayed capture can neither dispose nor
813        // retire a same-SessionId replacement.
814        let registration_transaction_guard =
815            self.lock_session_registration_transaction(session_id).await;
816        let mut recovered_registration_for_archive = false;
817        let (resolved_session_id, driver, _, _) = match self.lookup_entry(&runtime_id).await {
818            Ok(parts) => parts,
819            Err(RuntimeControlPlaneError::NotFound(_)) => {
820                // A process restart can leave unfinished runtime realization
821                // without a live session entry. Recover only that lifecycle
822                // residue before deciding archive has no runtime half. A
823                // checkpoint snapshot is session content authority and is
824                // intentionally not consulted here; a clean unbound Idle row
825                // likewise belongs to the dead process and is already absent.
826                let Some(store) = self.store.as_ref() else {
827                    return Ok(None);
828                };
829                let durable_lifecycle =
830                    crate::store::load_machine_lifecycle(store.as_ref(), &runtime_id)
831                        .await
832                        .map_err(|error| RuntimeControlPlaneError::Internal(error.to_string()))?;
833                if !durable_lifecycle.as_ref().is_some_and(
834                    super::session_management::machine_lifecycle_has_runtime_archive_residue,
835                ) {
836                    return Ok(None);
837                }
838                // Archive recovery must preserve the durable lifecycle
839                // authority exactly long enough to drain any terminal
840                // outboxes owned by its last placement. The public
841                // RegisterSession command intentionally revives Stopped
842                // to Idle and clears that epoch tuple; doing so here would
843                // destroy the witness required for exact outbox adoption.
844                // Recover the entry mechanically, without applying the
845                // user-facing revival transition.
846                recovered_registration_for_archive = self
847                    .register_session_inner_under_registration_transaction(session_id.clone(), None)
848                    .await
849                    .map_err(|error| RuntimeControlPlaneError::Internal(error.to_string()))?
850                    .inserted();
851                self.lookup_entry(&runtime_id).await?
852            }
853            Err(error) => return Err(error),
854        };
855        if &resolved_session_id != session_id {
856            return Err(RuntimeControlPlaneError::Internal(format!(
857                "runtime {runtime_id} resolved to unexpected session {resolved_session_id} while archiving {session_id}"
858            )));
859        }
860        self.reject_unregister_overlap_under_registration_transaction(&resolved_session_id)
861            .await?;
862        #[cfg(test)]
863        self.run_control_command_after_logical_lookup_test_hook(
864            ControlCommandLookupTestKind::Retire,
865            &resolved_session_id,
866        )
867        .await;
868        #[cfg(feature = "live")]
869        let live_lifecycle_lease = Some(
870            self.acquire_member_live_disposal_lease(&resolved_session_id)
871                .await
872                .map_err(|error| RuntimeControlPlaneError::Internal(error.to_string()))?,
873        );
874        #[cfg(not(feature = "live"))]
875        let live_lifecycle_lease = None;
876        let mutation_guard = self
877            .lock_current_session_driver_gate(&resolved_session_id, &driver)
878            .await
879            .map_err(|error| RuntimeControlPlaneError::Internal(error.to_string()))?;
880        let (driver, completions, wake_tx, publication_handle) = self
881            .capture_archive_lease_entry_under_mutation_guard(
882                &runtime_id,
883                &resolved_session_id,
884                &driver,
885                &mutation_guard,
886            )
887            .await?;
888        Ok(Some(super::MachineSessionArchiveLease {
889            session_id: resolved_session_id,
890            runtime_id,
891            driver,
892            completions,
893            wake_tx,
894            publication_handle,
895            recovered_registration_for_archive,
896            _registration_transaction_guard: registration_transaction_guard,
897            _live_lifecycle_lease: live_lifecycle_lease,
898            _mutation_guard: mutation_guard,
899        }))
900    }
901
902    /// Capture the exact runtime entry before a direct SessionService turn.
903    /// The identity carries no lock; the session layer separately owns its
904    /// stable turn-finalization boundary while the actor executes.
905    pub async fn capture_service_turn_identity(
906        &self,
907        session_id: &SessionId,
908    ) -> Result<super::MachineServiceTurnIdentity, RuntimeDriverError> {
909        let driver = {
910            let sessions = self.sessions.read().await;
911            let entry = sessions
912                .get(session_id)
913                .ok_or(RuntimeDriverError::NotReady {
914                    state: RuntimeState::Destroyed,
915                })?;
916            if !entry.generated_service_turn_binding_open(session_id) {
917                return Err(RuntimeDriverError::NotReady {
918                    state: RuntimeState::Destroyed,
919                });
920            }
921            Arc::clone(&entry.driver)
922        };
923        Ok(super::MachineServiceTurnIdentity {
924            session_id: session_id.clone(),
925            driver,
926        })
927    }
928
929    /// Acquire exact mutation authority for the terminal commit of a direct
930    /// SessionService turn.
931    ///
932    /// Callers must not hold the session recovery gate while awaiting this
933    /// lease. Once acquired, they may take recovery and commit/checkpoint in
934    /// the global machine-mutation -> recovery order.
935    pub async fn prepare_service_turn_commit_lease(
936        &self,
937        turn_identity: &super::MachineServiceTurnIdentity,
938    ) -> Result<super::MachineServiceTurnCommitLease, RuntimeDriverError> {
939        let session_id = &turn_identity.session_id;
940        let driver = Arc::clone(&turn_identity.driver);
941        let mutation_guard = self
942            .lock_current_session_driver_gate(session_id, &driver)
943            .await?;
944        let registration_open = {
945            let sessions = self.sessions.read().await;
946            sessions.get(session_id).is_some_and(|entry| {
947                Arc::ptr_eq(&entry.driver, &driver)
948                    && entry.generated_service_turn_binding_open(session_id)
949            })
950        };
951        if !registration_open {
952            return Err(RuntimeDriverError::NotReady {
953                state: RuntimeState::Destroyed,
954            });
955        }
956        Ok(super::MachineServiceTurnCommitLease {
957            session_id: session_id.clone(),
958            driver,
959            _mutation_guard: mutation_guard,
960        })
961    }
962
963    /// Commit a direct service-turn terminal through an already-held exact
964    /// mutation lease. The lease remains live so the caller can checkpoint the
965    /// committed snapshot while retaining the same authority interval.
966    pub async fn commit_service_turn_terminal_receipt_with_lease(
967        &self,
968        lease: &mut super::MachineServiceTurnCommitLease,
969        session_snapshot: Vec<u8>,
970    ) -> Result<(), RuntimeDriverError> {
971        let still_current = {
972            let sessions = self.sessions.read().await;
973            sessions.get(&lease.session_id).is_some_and(|entry| {
974                Arc::ptr_eq(&entry.driver, &lease.driver)
975                    && entry.generated_service_turn_binding_open(&lease.session_id)
976            })
977        };
978        if !still_current {
979            return Err(RuntimeDriverError::NotReady {
980                state: RuntimeState::Destroyed,
981            });
982        }
983        let receipt_result = {
984            let mut driver = lease.driver.lock().await;
985            machine_commit_service_turn_terminal_receipt(&mut driver, session_snapshot).await
986        };
987        if let Err(error) = receipt_result {
988            return Err(self
989                .classify_session_driver_rejection(&lease.session_id, error)
990                .await);
991        }
992        Ok(())
993    }
994
995    /// Compare-and-remove the reconstructable in-memory registration inserted
996    /// by archive preparation itself.
997    ///
998    /// This is intentionally not the generated `UnregisterSession` path: the
999    /// durable runtime is already `Retired` or `Destroyed`, and archive cleanup
1000    /// must not rewrite that terminal truth. The exact runtime and driver
1001    /// identity prevent cleanup from removing a registration that predated the
1002    /// archive or replaced its recovered incarnation.
1003    async fn remove_archive_recovered_registration_exact(
1004        &self,
1005        session_id: &SessionId,
1006        runtime_id: &LogicalRuntimeId,
1007        driver: &SharedDriver,
1008    ) -> Result<(), RuntimeControlPlaneError> {
1009        let state = driver.lock().await.runtime_state();
1010        if !matches!(state, RuntimeState::Retired | RuntimeState::Destroyed) {
1011            return Err(RuntimeControlPlaneError::InvalidState { state });
1012        }
1013
1014        let removed = {
1015            let mut sessions = self.sessions.write().await;
1016            let Some(entry) = sessions.get(session_id) else {
1017                // Another terminal cleanup already removed the reconstructable
1018                // entry. Durable truth remains untouched, so this is converged.
1019                return Ok(());
1020            };
1021            if &entry.runtime_id != runtime_id || !Arc::ptr_eq(&entry.driver, driver) {
1022                return Err(RuntimeControlPlaneError::Internal(format!(
1023                    "archive-recovered runtime {runtime_id} was replaced before quiescent cleanup"
1024                )));
1025            }
1026            if entry.wake_sender().is_some() || entry.publication_handle().is_some() {
1027                return Err(RuntimeControlPlaneError::Internal(format!(
1028                    "archive-recovered quiescent runtime {runtime_id} acquired a live attachment before cleanup"
1029                )));
1030            }
1031            sessions.remove(session_id)
1032        };
1033        drop(removed);
1034        Ok(())
1035    }
1036
1037    /// Release a quiescent archive lease and discard only the reconstructable
1038    /// in-memory registration inserted by archive preparation itself.
1039    pub async fn release_quiescent_session_archive_lease(
1040        &self,
1041        lease: super::MachineSessionArchiveLease,
1042    ) -> Result<(), RuntimeControlPlaneError> {
1043        let super::MachineSessionArchiveLease {
1044            session_id,
1045            runtime_id,
1046            driver,
1047            completions: _,
1048            wake_tx,
1049            publication_handle,
1050            recovered_registration_for_archive,
1051            _registration_transaction_guard,
1052            _live_lifecycle_lease,
1053            _mutation_guard,
1054        } = lease;
1055
1056        if !recovered_registration_for_archive {
1057            return Ok(());
1058        }
1059
1060        if wake_tx.is_some() || publication_handle.is_some() {
1061            return Err(RuntimeControlPlaneError::Internal(format!(
1062                "archive-recovered quiescent runtime {runtime_id} acquired a live attachment before cleanup"
1063            )));
1064        }
1065        self.remove_archive_recovered_registration_exact(&session_id, &runtime_id, &driver)
1066            .await
1067    }
1068
1069    /// Realize Retire using a previously acquired archive lease without
1070    /// reacquiring the per-session mutation gate.
1071    pub async fn retire_session_with_archive_lease(
1072        &self,
1073        lease: super::MachineSessionArchiveLease,
1074    ) -> Result<RetireReport, RuntimeControlPlaneError> {
1075        self.realize_retire_with_archive_lease(lease, None).await
1076    }
1077
1078    /// Drain durable runless terminals while an archive lease still owns the
1079    /// session mutation gate. Archive calls this before its document verdict,
1080    /// so a prior crash after runtime terminalization cannot be hidden behind
1081    /// an `AlreadyArchived` document result on retry.
1082    pub async fn drain_session_archive_lease_terminals(
1083        &self,
1084        lease: &super::MachineSessionArchiveLease,
1085        archive_publication_handle: Option<
1086            &dyn meerkat_core::lifecycle::CoreExecutorPublicationHandle,
1087        >,
1088    ) -> Result<(), RuntimeControlPlaneError> {
1089        let publication_handle = lease
1090            .publication_handle
1091            .as_deref()
1092            .or(archive_publication_handle);
1093        crate::control_plane::drain_recovered_runless_runtime_terminations(
1094            &lease.driver,
1095            Some(&lease.completions),
1096            publication_handle,
1097        )
1098        .await
1099        .map_err(|error| RuntimeControlPlaneError::Internal(error.to_string()))
1100    }
1101
1102    /// Archive-only sibling that supplies a borrowed, quiescent stored-session
1103    /// publisher when the restarted runtime has no attached executor. The
1104    /// lease-retained live publisher always wins when present.
1105    pub async fn retire_session_with_archive_lease_and_publication_handle(
1106        &self,
1107        lease: super::MachineSessionArchiveLease,
1108        publication_handle: &dyn meerkat_core::lifecycle::CoreExecutorPublicationHandle,
1109    ) -> Result<RetireReport, RuntimeControlPlaneError> {
1110        self.realize_retire_with_archive_lease(lease, Some(publication_handle))
1111            .await
1112    }
1113
1114    pub async fn retire_runtime_control_plane(
1115        &self,
1116        runtime_id: &LogicalRuntimeId,
1117    ) -> Result<RetireReport, RuntimeControlPlaneError> {
1118        // Resolve only the transaction key optimistically. The authoritative
1119        // entry capture happens after the stable registration transaction is
1120        // held, so an old entry can never dispose a replacement's live state.
1121        let (session_id, _, _, _) = self.lookup_entry(runtime_id).await?;
1122        let registration_transaction_guard = self
1123            .lock_session_registration_transaction(&session_id)
1124            .await;
1125        let (resolved_session_id, driver, _, _) = self.lookup_entry(runtime_id).await?;
1126        if resolved_session_id != session_id {
1127            return Err(RuntimeControlPlaneError::Internal(format!(
1128                "runtime {runtime_id} changed session identity from {session_id} to {resolved_session_id} during retirement"
1129            )));
1130        }
1131        self.reject_unregister_overlap_under_registration_transaction(&resolved_session_id)
1132            .await?;
1133        #[cfg(test)]
1134        self.run_control_command_after_logical_lookup_test_hook(
1135            ControlCommandLookupTestKind::Retire,
1136            &resolved_session_id,
1137        )
1138        .await;
1139        #[cfg(feature = "live")]
1140        let live_lifecycle_lease = Some(
1141            self.acquire_member_live_disposal_lease(&session_id)
1142                .await
1143                .map_err(|error| RuntimeControlPlaneError::Internal(error.to_string()))?,
1144        );
1145        #[cfg(not(feature = "live"))]
1146        let live_lifecycle_lease = None;
1147        let mutation_guard = self
1148            .lock_current_session_driver_gate(&session_id, &driver)
1149            .await
1150            .map_err(|error| RuntimeControlPlaneError::Internal(error.to_string()))?;
1151        let (driver, completions, wake_tx, publication_handle) = self
1152            .capture_archive_lease_entry_under_mutation_guard(
1153                runtime_id,
1154                &resolved_session_id,
1155                &driver,
1156                &mutation_guard,
1157            )
1158            .await?;
1159        let lease = super::MachineSessionArchiveLease {
1160            session_id: resolved_session_id,
1161            runtime_id: runtime_id.clone(),
1162            driver,
1163            completions,
1164            wake_tx,
1165            publication_handle,
1166            recovered_registration_for_archive: false,
1167            _registration_transaction_guard: registration_transaction_guard,
1168            _live_lifecycle_lease: live_lifecycle_lease,
1169            _mutation_guard: mutation_guard,
1170        };
1171        self.realize_retire_with_archive_lease(lease, None).await
1172    }
1173
1174    async fn realize_retire_with_archive_lease(
1175        &self,
1176        lease: super::MachineSessionArchiveLease,
1177        archive_publication_handle: Option<
1178            &dyn meerkat_core::lifecycle::CoreExecutorPublicationHandle,
1179        >,
1180    ) -> Result<RetireReport, RuntimeControlPlaneError> {
1181        let super::MachineSessionArchiveLease {
1182            session_id,
1183            runtime_id,
1184            driver,
1185            completions,
1186            wake_tx,
1187            publication_handle,
1188            recovered_registration_for_archive,
1189            _registration_transaction_guard,
1190            _live_lifecycle_lease,
1191            _mutation_guard,
1192        } = lease;
1193        let retained_publication_handle = publication_handle;
1194        let publication_handle = retained_publication_handle
1195            .as_deref()
1196            .or(archive_publication_handle);
1197        tracing::info!(
1198            runtime_id = %runtime_id,
1199            "MeerkatMachine::retire_runtime_control_plane start"
1200        );
1201
1202        let staged_dsl = self
1203            .stage_session_dsl_transition(
1204                &session_id,
1205                crate::meerkat_machine::dsl::MeerkatMachineInput::Retire {
1206                    session_id: crate::meerkat_machine::dsl::SessionId::from_domain(&session_id),
1207                },
1208                "Retire",
1209            )
1210            .await
1211            .map_err(RuntimeControlPlaneError::Internal)?;
1212
1213        let mut drv = driver.lock().await;
1214        let mut report = match Box::pin(machine_retire(&mut drv)).await {
1215            Ok(report) => report,
1216            Err(err) => {
1217                drop(drv);
1218                let restored = self
1219                    .restore_session_dsl_state_if_current(
1220                        &session_id,
1221                        staged_dsl.committed_snapshot.clone(),
1222                        staged_dsl.previous_snapshot.clone(),
1223                    )
1224                    .await;
1225                driver
1226                    .lock()
1227                    .await
1228                    .sync_control_projection_from_dsl_authority();
1229                let detail = if restored {
1230                    err.to_string()
1231                } else {
1232                    format!(
1233                        "{err}; archive retire realization failed to restore the staged runtime authority"
1234                    )
1235                };
1236                return Err(RuntimeControlPlaneError::Internal(detail));
1237            }
1238        };
1239        drop(drv);
1240
1241        let mut commit_error = None;
1242        if let Err(reason) = self
1243            .commit_session_dsl_transition_preserving_committed_state(
1244                &session_id,
1245                staged_dsl,
1246                "Retire",
1247            )
1248            .await
1249        {
1250            driver
1251                .lock()
1252                .await
1253                .sync_control_projection_from_dsl_authority();
1254            commit_error = Some(reason);
1255        }
1256
1257        crate::control_plane::drain_recovered_runless_runtime_terminations(
1258            &driver,
1259            Some(&completions),
1260            publication_handle,
1261        )
1262        .await
1263        .map_err(|error| RuntimeControlPlaneError::Internal(error.to_string()))?;
1264
1265        if report.inputs_pending_drain > 0 {
1266            if let Some(ref tx) = wake_tx
1267                && tx.send(()).await.is_ok()
1268            {
1269                if let Some(reason) = commit_error {
1270                    return Err(RuntimeControlPlaneError::Internal(reason));
1271                }
1272                return Ok(report);
1273            }
1274
1275            let reason = "retired without runtime loop";
1276            let (abandoned, completion_input_ids, candidate_owner_input_id) = {
1277                let mut drv = driver.lock().await;
1278                let completion_input_ids = drv.as_driver().active_input_ids();
1279                let prepared = drv
1280                    .prepare_runless_runtime_terminated_interaction_outboxes(
1281                        &completion_input_ids,
1282                        reason.to_string(),
1283                    )
1284                    .map_err(|error| RuntimeControlPlaneError::Internal(error.to_string()))?;
1285                let abandoned = match drv
1286                    .abandon_pending_inputs(crate::input_state::InputAbandonReason::Retired)
1287                    .await
1288                {
1289                    Ok(abandoned) => abandoned,
1290                    Err(error) => {
1291                        drv.rollback_prepared_runless_interaction_terminal_outboxes(prepared);
1292                        return Err(RuntimeControlPlaneError::Internal(error.to_string()));
1293                    }
1294                };
1295                let candidate_owner_input_id =
1296                    crate::meerkat_machine::driver::DriverEntry::commit_prepared_runless_interaction_terminal_outboxes(prepared);
1297                (abandoned, completion_input_ids, candidate_owner_input_id)
1298            };
1299            crate::control_plane::publish_and_resolve_runless_runtime_termination(
1300                &driver,
1301                Some(&completions),
1302                publication_handle,
1303                &completion_input_ids,
1304                candidate_owner_input_id.as_ref(),
1305                reason,
1306            )
1307            .await
1308            .map_err(|error| RuntimeControlPlaneError::Internal(error.to_string()))?;
1309            report.inputs_abandoned += abandoned;
1310            report.inputs_pending_drain = 0;
1311        }
1312        if let Some(reason) = commit_error {
1313            return Err(RuntimeControlPlaneError::Internal(reason));
1314        }
1315        if recovered_registration_for_archive {
1316            self.remove_archive_recovered_registration_exact(&session_id, &runtime_id, &driver)
1317                .await?;
1318        }
1319        Ok(report)
1320    }
1321}
1322
1323#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
1324#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
1325impl crate::traits::RuntimeControlPlane for MeerkatMachine {
1326    async fn ingest(
1327        &self,
1328        runtime_id: &LogicalRuntimeId,
1329        input: Input,
1330    ) -> Result<AcceptOutcome, RuntimeControlPlaneError> {
1331        match self
1332            .execute_meerkat_machine_command(
1333                None,
1334                MeerkatMachineCommand::Ingest {
1335                    runtime_id: runtime_id.clone(),
1336                    input,
1337                },
1338            )
1339            .await
1340            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
1341        {
1342            MeerkatMachineCommandResult::AcceptOutcome(outcome) => Ok(outcome),
1343            other => Err(RuntimeControlPlaneError::Internal(format!(
1344                "unexpected MeerkatMachineCommandResult for ingest: {other:?}"
1345            ))),
1346        }
1347    }
1348
1349    async fn publish_event(
1350        &self,
1351        event: crate::runtime_event::RuntimeEventEnvelope,
1352    ) -> Result<(), RuntimeControlPlaneError> {
1353        match self
1354            .execute_meerkat_machine_command(None, MeerkatMachineCommand::PublishEvent { event })
1355            .await
1356            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
1357        {
1358            MeerkatMachineCommandResult::Unit => Ok(()),
1359            other => Err(RuntimeControlPlaneError::Internal(format!(
1360                "unexpected MeerkatMachineCommandResult for publish_event: {other:?}"
1361            ))),
1362        }
1363    }
1364
1365    async fn retire(
1366        &self,
1367        runtime_id: &LogicalRuntimeId,
1368    ) -> Result<RetireReport, RuntimeControlPlaneError> {
1369        self.retire_runtime_control_plane(runtime_id).await
1370    }
1371
1372    async fn recycle(
1373        &self,
1374        runtime_id: &LogicalRuntimeId,
1375    ) -> Result<RecycleReport, RuntimeControlPlaneError> {
1376        match self
1377            .execute_meerkat_machine_command(
1378                None,
1379                MeerkatMachineCommand::Recycle {
1380                    runtime_id: runtime_id.clone(),
1381                },
1382            )
1383            .await
1384            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
1385        {
1386            MeerkatMachineCommandResult::RecycleReport(report) => Ok(report),
1387            other => Err(RuntimeControlPlaneError::Internal(format!(
1388                "unexpected MeerkatMachineCommandResult for recycle: {other:?}"
1389            ))),
1390        }
1391    }
1392
1393    async fn reset(
1394        &self,
1395        runtime_id: &LogicalRuntimeId,
1396    ) -> Result<crate::traits::ResetReport, RuntimeControlPlaneError> {
1397        match self
1398            .execute_meerkat_machine_command(
1399                None,
1400                MeerkatMachineCommand::Reset {
1401                    runtime_id: runtime_id.clone(),
1402                },
1403            )
1404            .await
1405            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
1406        {
1407            MeerkatMachineCommandResult::ResetReport(report) => Ok(report),
1408            other => Err(RuntimeControlPlaneError::Internal(format!(
1409                "unexpected MeerkatMachineCommandResult for reset: {other:?}"
1410            ))),
1411        }
1412    }
1413
1414    async fn recover(
1415        &self,
1416        runtime_id: &LogicalRuntimeId,
1417    ) -> Result<RecoveryReport, RuntimeControlPlaneError> {
1418        match self
1419            .execute_meerkat_machine_command(
1420                None,
1421                MeerkatMachineCommand::Recover {
1422                    runtime_id: runtime_id.clone(),
1423                },
1424            )
1425            .await
1426            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
1427        {
1428            MeerkatMachineCommandResult::RecoveryReport(report) => Ok(report),
1429            other => Err(RuntimeControlPlaneError::Internal(format!(
1430                "unexpected MeerkatMachineCommandResult for recover: {other:?}"
1431            ))),
1432        }
1433    }
1434
1435    async fn destroy(
1436        &self,
1437        runtime_id: &LogicalRuntimeId,
1438    ) -> Result<DestroyReport, RuntimeControlPlaneError> {
1439        match self
1440            .execute_meerkat_machine_command(
1441                None,
1442                MeerkatMachineCommand::Destroy {
1443                    runtime_id: runtime_id.clone(),
1444                },
1445            )
1446            .await
1447            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
1448        {
1449            MeerkatMachineCommandResult::DestroyReport(report) => Ok(report),
1450            other => Err(RuntimeControlPlaneError::Internal(format!(
1451                "unexpected MeerkatMachineCommandResult for destroy: {other:?}"
1452            ))),
1453        }
1454    }
1455
1456    async fn runtime_state(
1457        &self,
1458        runtime_id: &LogicalRuntimeId,
1459    ) -> Result<RuntimeState, RuntimeControlPlaneError> {
1460        match self
1461            .execute_meerkat_machine_command(
1462                None,
1463                MeerkatMachineCommand::RuntimeState {
1464                    runtime_id: runtime_id.clone(),
1465                },
1466            )
1467            .await
1468            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
1469        {
1470            MeerkatMachineCommandResult::RuntimeState(state) => Ok(state),
1471            other => Err(RuntimeControlPlaneError::Internal(format!(
1472                "unexpected MeerkatMachineCommandResult for runtime_state: {other:?}"
1473            ))),
1474        }
1475    }
1476
1477    async fn load_boundary_receipt(
1478        &self,
1479        runtime_id: &LogicalRuntimeId,
1480        run_id: &RunId,
1481        sequence: u64,
1482    ) -> Result<Option<meerkat_core::lifecycle::RunBoundaryReceipt>, RuntimeControlPlaneError> {
1483        match self
1484            .execute_meerkat_machine_command(
1485                None,
1486                MeerkatMachineCommand::LoadBoundaryReceipt {
1487                    runtime_id: runtime_id.clone(),
1488                    run_id: run_id.clone(),
1489                    sequence,
1490                },
1491            )
1492            .await
1493            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
1494        {
1495            MeerkatMachineCommandResult::BoundaryReceipt(receipt) => Ok(receipt),
1496            other => Err(RuntimeControlPlaneError::Internal(format!(
1497                "unexpected MeerkatMachineCommandResult for load_boundary_receipt: {other:?}"
1498            ))),
1499        }
1500    }
1501}
1502
1503#[cfg(test)]
1504#[allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
1505mod tests {
1506    use super::*;
1507
1508    /// Row #45 gate: control-plane not-found must map to the dedicated
1509    /// `RuntimeDriverError::NotFound` carrying the runtime id, NOT to
1510    /// `NotReady { state: Destroyed }` (which conflates never-existed/absent
1511    /// with a torn-down lifecycle).
1512    #[test]
1513    fn control_plane_not_found_maps_to_driver_not_found() {
1514        let runtime_id = LogicalRuntimeId("missing-runtime".to_string());
1515        let mapped = MeerkatMachine::driver_error_from_control_plane_error(
1516            RuntimeControlPlaneError::NotFound(runtime_id.clone()),
1517        );
1518
1519        match mapped {
1520            RuntimeDriverError::NotFound {
1521                runtime_id: mapped_id,
1522            } => assert_eq!(mapped_id, runtime_id),
1523            other => panic!(
1524                "expected RuntimeDriverError::NotFound, got {other:?} (must not collapse absence into NotReady/Destroyed)"
1525            ),
1526        }
1527    }
1528
1529    /// Guard the negative half explicitly: the not-found mapping must never
1530    /// surface as `NotReady { state: Destroyed }`.
1531    #[test]
1532    fn control_plane_not_found_is_not_destroyed_not_ready() {
1533        let mapped = MeerkatMachine::driver_error_from_control_plane_error(
1534            RuntimeControlPlaneError::NotFound(LogicalRuntimeId("missing-runtime".to_string())),
1535        );
1536
1537        assert!(
1538            !matches!(
1539                mapped,
1540                RuntimeDriverError::NotReady {
1541                    state: RuntimeState::Destroyed
1542                }
1543            ),
1544            "not-found must not be laundered into NotReady{{Destroyed}}"
1545        );
1546    }
1547}