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