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 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
514impl 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 #[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 #[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}