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