1use super::*;
2
3#[cfg(not(target_arch = "wasm32"))]
4type AcceptInputWithCompletionFuture<'a> = std::pin::Pin<
5 Box<
6 dyn std::future::Future<
7 Output = Result<
8 (AcceptOutcome, Option<crate::completion::CompletionHandle>),
9 RuntimeDriverError,
10 >,
11 > + Send
12 + 'a,
13 >,
14>;
15
16#[cfg(target_arch = "wasm32")]
17type AcceptInputWithCompletionFuture<'a> = std::pin::Pin<
18 Box<
19 dyn std::future::Future<
20 Output = Result<
21 (AcceptOutcome, Option<crate::completion::CompletionHandle>),
22 RuntimeDriverError,
23 >,
24 > + 'a,
25 >,
26>;
27
28#[cfg(feature = "live")]
29fn dsl_live_channel_status_from_observation(
30 status: &meerkat_core::live_adapter::LiveAdapterStatus,
31) -> (
32 crate::meerkat_machine::dsl::LiveChannelPublicStatus,
33 Option<crate::meerkat_machine::dsl::LiveChannelDegradationReason>,
34 Option<String>,
35) {
36 use crate::meerkat_machine::dsl::{
37 LiveChannelDegradationReason as DslReason, LiveChannelPublicStatus as DslStatus,
38 };
39 use meerkat_core::live_adapter::LiveAdapterStatus;
40
41 match status {
42 LiveAdapterStatus::Idle => (DslStatus::Idle, None, None),
43 LiveAdapterStatus::Opening => (DslStatus::Opening, None, None),
44 LiveAdapterStatus::Ready => (DslStatus::Ready, None, None),
45 LiveAdapterStatus::Closing => (DslStatus::Closing, None, None),
46 LiveAdapterStatus::Closed => (DslStatus::Closed, None, None),
47 LiveAdapterStatus::Degraded { reason } => {
48 let (reason, detail) = dsl_live_channel_degradation_reason(reason);
49 (DslStatus::Degraded, Some(reason), detail)
50 }
51 other => (
52 DslStatus::Degraded,
53 Some(DslReason::Unknown),
54 Some(format!("{other:?}")),
55 ),
56 }
57}
58
59#[cfg(feature = "live")]
60fn dsl_live_channel_degradation_reason(
61 reason: &meerkat_core::live_adapter::LiveDegradationReason,
62) -> (
63 crate::meerkat_machine::dsl::LiveChannelDegradationReason,
64 Option<String>,
65) {
66 use crate::meerkat_machine::dsl::LiveChannelDegradationReason as DslReason;
67 use meerkat_core::live_adapter::LiveDegradationReason;
68
69 match reason {
70 LiveDegradationReason::RateLimited => (DslReason::RateLimited, None),
71 LiveDegradationReason::ProviderThrottled => (DslReason::ProviderThrottled, None),
72 LiveDegradationReason::NetworkUnstable => (DslReason::NetworkUnstable, None),
73 LiveDegradationReason::Other { detail } => {
74 (DslReason::Other, Some(detail.clone().into_owned()))
75 }
76 other => (DslReason::Unknown, Some(format!("{other:?}"))),
77 }
78}
79
80#[cfg(feature = "live")]
81fn dsl_live_command_kind(
82 kind: meerkat_live::LiveCommandAcceptanceKind,
83) -> crate::meerkat_machine::dsl::LiveCommandPublicKind {
84 match kind {
85 meerkat_live::LiveCommandAcceptanceKind::SendInput => {
86 crate::meerkat_machine::dsl::LiveCommandPublicKind::SendInput
87 }
88 meerkat_live::LiveCommandAcceptanceKind::CommitInput => {
89 crate::meerkat_machine::dsl::LiveCommandPublicKind::CommitInput
90 }
91 meerkat_live::LiveCommandAcceptanceKind::Interrupt => {
92 crate::meerkat_machine::dsl::LiveCommandPublicKind::Interrupt
93 }
94 meerkat_live::LiveCommandAcceptanceKind::TruncateAssistantOutput => {
95 crate::meerkat_machine::dsl::LiveCommandPublicKind::TruncateAssistantOutput
96 }
97 }
98}
99
100#[cfg(feature = "live")]
101fn dsl_live_command_rejection_reason(
102 error: &meerkat_live::LiveAdapterHostError,
103) -> crate::meerkat_machine::dsl::LiveCommandRejectionReason {
104 use crate::meerkat_machine::dsl::LiveCommandRejectionReason as DslReason;
105 use meerkat_live::LiveAdapterHostError;
106
107 match error {
108 LiveAdapterHostError::ChannelNotFound(_) => DslReason::ChannelNotFound,
109 LiveAdapterHostError::NoAdapter(_) => DslReason::NoAdapter,
110 LiveAdapterHostError::ChannelNotReady(_, _) => DslReason::ChannelNotReady,
111 LiveAdapterHostError::UnsupportedCommand(_) => DslReason::UnsupportedCommand,
112 LiveAdapterHostError::AdapterError(_) => DslReason::AdapterError,
113 _ => DslReason::InternalHostError,
114 }
115}
116
117#[cfg(feature = "live")]
118fn dsl_live_channel_request_rejection_reason(
119 error: &meerkat_live::LiveAdapterHostError,
120) -> crate::meerkat_machine::dsl::LiveChannelRequestRejectionReason {
121 use crate::meerkat_machine::dsl::LiveChannelRequestRejectionReason as DslReason;
122 use meerkat_live::LiveAdapterHostError;
123
124 match error {
125 LiveAdapterHostError::ChannelNotFound(_) => DslReason::ChannelNotFound,
126 LiveAdapterHostError::NoAdapter(_) => DslReason::NoAdapter,
127 _ => DslReason::InternalHostError,
128 }
129}
130
131#[cfg(feature = "live")]
132fn extract_live_websocket_token_admission(
133 effects: &[crate::meerkat_machine::dsl::MeerkatMachineEffect],
134 session_id: &str,
135 channel_id: &str,
136 token: &str,
137 transition: &str,
138) -> Result<LiveWebsocketTokenAdmissionAuthority, RuntimeDriverError> {
139 effects
140 .iter()
141 .find_map(|effect| match effect {
142 crate::meerkat_machine::dsl::MeerkatMachineEffect::LiveWebsocketTokenAdmissionResolved {
143 session_id: effect_session_id,
144 channel_id: effect_channel_id,
145 token: effect_token,
146 admitted,
147 rejection,
148 public_error_class,
149 sequence,
150 } if effect_session_id == session_id
151 && effect_channel_id == channel_id
152 && effect_token == token =>
153 {
154 Some(LiveWebsocketTokenAdmissionAuthority {
155 admitted: *admitted,
156 rejection: *rejection,
157 public_error_class: *public_error_class,
158 sequence: *sequence,
159 })
160 }
161 _ => None,
162 })
163 .ok_or_else(|| {
164 RuntimeDriverError::Internal(format!(
165 "{transition} for channel '{channel_id}' emitted no LiveWebsocketTokenAdmissionResolved effect"
166 ))
167 })
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
174pub struct RuntimeCompletionCleanupAuthority {
175 pub action: crate::meerkat_machine::dsl::RuntimeCompletionCleanupAction,
176 pub pre_admission_action: crate::meerkat_machine::dsl::RuntimeCompletionPreAdmissionAction,
177 pub outcome: crate::meerkat_machine::dsl::RuntimeCompletionObservedOutcome,
178 pub live_session: crate::meerkat_machine::dsl::RuntimeCompletionLiveSessionObservation,
179 pub archived_by_authority: bool,
180}
181
182impl RuntimeCompletionCleanupAuthority {
183 pub fn requires_runtime_cleanup(self) -> bool {
184 matches!(
185 self.action,
186 crate::meerkat_machine::dsl::RuntimeCompletionCleanupAction::CleanupRuntime
187 )
188 }
189
190 pub fn releases_pre_admission(self) -> bool {
191 matches!(
192 self.pre_admission_action,
193 crate::meerkat_machine::dsl::RuntimeCompletionPreAdmissionAction::ReleasePreAdmission
194 )
195 }
196}
197
198#[derive(Debug, Clone, Copy, PartialEq, Eq)]
199struct RuntimeCompletionCleanupEffect {
200 action: crate::meerkat_machine::dsl::RuntimeCompletionCleanupAction,
201 pre_admission_action: crate::meerkat_machine::dsl::RuntimeCompletionPreAdmissionAction,
202}
203
204fn runtime_completion_cleanup_effect_from_effects(
205 session_id: &SessionId,
206 effects: &[crate::meerkat_machine::dsl::MeerkatMachineEffect],
207) -> Result<RuntimeCompletionCleanupEffect, RuntimeDriverError> {
208 let expected_session_id = crate::meerkat_machine::dsl::SessionId::from_domain(session_id);
209 effects
210 .iter()
211 .find_map(|effect| match effect {
212 crate::meerkat_machine::dsl::MeerkatMachineEffect::RuntimeCompletionCleanupResolved {
213 session_id: effect_session_id,
214 action,
215 pre_admission_action,
216 } if effect_session_id == &expected_session_id => Some(RuntimeCompletionCleanupEffect {
217 action: *action,
218 pre_admission_action: *pre_admission_action,
219 }),
220 _ => None,
221 })
222 .ok_or_else(|| {
223 RuntimeDriverError::Internal(format!(
224 "ResolveRuntimeCompletionCleanup for session '{session_id}' emitted no RuntimeCompletionCleanupResolved effect"
225 ))
226 })
227}
228
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
233pub struct RuntimeCompletionWaitFailureAuthority {
234 pub failure: crate::meerkat_machine::dsl::RuntimeCompletionWaitFailureObservation,
235 pub pre_admission_action: crate::meerkat_machine::dsl::RuntimeCompletionPreAdmissionAction,
236 pub public_error_class:
237 crate::meerkat_machine::dsl::RuntimeCompletionWaitFailurePublicErrorClass,
238 pub public_reason: crate::meerkat_machine::dsl::RuntimeCompletionWaitFailurePublicReason,
239 pub resumable: bool,
240}
241
242impl RuntimeCompletionWaitFailureAuthority {
243 pub fn releases_pre_admission(self) -> bool {
244 matches!(
245 self.pre_admission_action,
246 crate::meerkat_machine::dsl::RuntimeCompletionPreAdmissionAction::ReleasePreAdmission
247 )
248 }
249}
250
251fn runtime_completion_wait_failure_authority_from_effects(
252 session_id: &SessionId,
253 failure: crate::meerkat_machine::dsl::RuntimeCompletionWaitFailureObservation,
254 effects: &[crate::meerkat_machine::dsl::MeerkatMachineEffect],
255) -> Result<RuntimeCompletionWaitFailureAuthority, RuntimeDriverError> {
256 let expected_session_id = crate::meerkat_machine::dsl::SessionId::from_domain(session_id);
257 effects
258 .iter()
259 .find_map(|effect| match effect {
260 crate::meerkat_machine::dsl::MeerkatMachineEffect::RuntimeCompletionWaitFailureResolved {
261 session_id: effect_session_id,
262 failure: effect_failure,
263 pre_admission_action,
264 public_error_class,
265 public_reason,
266 resumable,
267 } if effect_session_id == &expected_session_id && *effect_failure == failure => {
268 Some(RuntimeCompletionWaitFailureAuthority {
269 failure: *effect_failure,
270 pre_admission_action: *pre_admission_action,
271 public_error_class: *public_error_class,
272 public_reason: *public_reason,
273 resumable: *resumable,
274 })
275 }
276 _ => None,
277 })
278 .ok_or_else(|| {
279 RuntimeDriverError::Internal(format!(
280 "ResolveRuntimeCompletionWaitFailure for session '{session_id}' emitted no RuntimeCompletionWaitFailureResolved effect"
281 ))
282 })
283}
284
285impl MeerkatMachine {
286 pub async fn resolve_runtime_completion_cleanup(
287 &self,
288 session_id: &SessionId,
289 observation: crate::completion::CompletionCleanupObservation,
290 archived_by_authority: bool,
291 live_session: crate::meerkat_machine::dsl::RuntimeCompletionLiveSessionObservation,
292 ) -> Result<RuntimeCompletionCleanupAuthority, RuntimeDriverError> {
293 let observed_outcome = observation.observed_outcome();
294 let input =
295 crate::meerkat_machine::dsl::MeerkatMachineInput::ResolveRuntimeCompletionCleanup {
296 session_id: crate::meerkat_machine::dsl::SessionId::from_domain(session_id),
297 observation_session_id: crate::meerkat_machine::dsl::SessionId::from_domain(
298 observation.owner_session_id(),
299 ),
300 observation_agent_runtime_id: observation.owner_agent_runtime_id().cloned(),
301 observation_fence_token: observation.owner_fence_token(),
302 observation_runtime_generation: observation.owner_runtime_generation(),
303 observation_runtime_epoch_id: observation.owner_runtime_epoch_id().cloned(),
304 outcome: observed_outcome,
305 archived_by_authority,
306 live_session,
307 };
308 let effects = self
309 .preview_session_dsl_input(session_id, input, "ResolveRuntimeCompletionCleanup")
310 .await
311 .map_err(|reason| RuntimeDriverError::ValidationFailed { reason })?;
312 let cleanup_effect = runtime_completion_cleanup_effect_from_effects(session_id, &effects)?;
313 Ok(RuntimeCompletionCleanupAuthority {
314 action: cleanup_effect.action,
315 pre_admission_action: cleanup_effect.pre_admission_action,
316 outcome: observed_outcome,
317 live_session,
318 archived_by_authority,
319 })
320 }
321
322 pub async fn resolve_runtime_completion_wait_failure(
323 &self,
324 session_id: &SessionId,
325 error: &crate::completion::CompletionWaitError,
326 ) -> Result<RuntimeCompletionWaitFailureAuthority, RuntimeDriverError> {
327 let failure = error.wait_failure_observation();
328 let input =
329 crate::meerkat_machine::dsl::MeerkatMachineInput::ResolveRuntimeCompletionWaitFailure {
330 session_id: crate::meerkat_machine::dsl::SessionId::from_domain(session_id),
331 failure,
332 };
333 let effects = self
334 .preview_session_dsl_input(session_id, input, "ResolveRuntimeCompletionWaitFailure")
335 .await
336 .map_err(|reason| RuntimeDriverError::ValidationFailed { reason })?;
337 runtime_completion_wait_failure_authority_from_effects(session_id, failure, &effects)
338 }
339
340 #[cfg(feature = "live")]
341 pub async fn resolve_live_open_admission(
342 &self,
343 session_id: &SessionId,
344 channel_id: &meerkat_live::LiveChannelId,
345 llm_identity: &meerkat_core::SessionLlmIdentity,
346 ) -> Result<LiveOpenAdmissionAuthority, RuntimeDriverError> {
347 let channel_id_string = channel_id.to_string();
348 let (_, effects) = self
349 .apply_session_dsl_input(
350 session_id,
351 crate::meerkat_machine::dsl::MeerkatMachineInput::ResolveLiveOpenAdmission {
352 session_id: session_id.to_string(),
353 channel_id: channel_id_string.clone(),
354 llm_identity: crate::meerkat_machine::dsl::SessionLlmIdentity::from_domain(
355 llm_identity,
356 ),
357 },
358 "ResolveLiveOpenAdmission",
359 )
360 .await
361 .map_err(|reason| RuntimeDriverError::ValidationFailed { reason })?;
362
363 let authority = effects.as_slice().iter().find_map(|effect| match effect {
364 crate::meerkat_machine::dsl::MeerkatMachineEffect::LiveOpenAdmissionResolved {
365 session_id: effect_session_id,
366 channel_id: effect_channel_id,
367 bound_llm_identity,
368 admitted,
369 rejection,
370 sequence,
371 } if *effect_session_id == session_id.to_string()
372 && *effect_channel_id == channel_id_string =>
373 {
374 Some(LiveOpenAdmissionAuthority::from_generated_effect(
375 session_id.clone(),
376 channel_id.clone(),
377 *admitted,
378 *rejection,
379 bound_llm_identity.clone(),
380 *sequence,
381 ))
382 }
383 _ => None,
384 });
385 match authority {
386 Some(authority) => authority.map_err(RuntimeDriverError::Internal),
387 None => Err(RuntimeDriverError::Internal(format!(
388 "ResolveLiveOpenAdmission for channel '{channel_id_string}' emitted no LiveOpenAdmissionResolved effect"
389 ))),
390 }
391 }
392
393 #[cfg(feature = "live")]
394 pub async fn live_channel_bound_llm_identity(
395 &self,
396 session_id: &SessionId,
397 channel_id: &meerkat_live::LiveChannelId,
398 ) -> Result<Option<meerkat_core::SessionLlmIdentity>, RuntimeDriverError> {
399 let state = self.session_dsl_state(session_id).await.map_err(|reason| {
400 RuntimeDriverError::ValidationFailed {
401 reason: reason.to_string(),
402 }
403 })?;
404 state
405 .live_channel_identity_by_channel
406 .get(&channel_id.to_string())
407 .cloned()
408 .map(meerkat_core::SessionLlmIdentity::try_from)
409 .transpose()
410 .map_err(RuntimeDriverError::Internal)
411 }
412
413 #[cfg(feature = "live")]
414 pub async fn abandon_live_open_admission(
415 &self,
416 session_id: &SessionId,
417 channel_id: &meerkat_live::LiveChannelId,
418 ) -> Result<(), RuntimeDriverError> {
419 self.apply_session_dsl_input(
420 session_id,
421 crate::meerkat_machine::dsl::MeerkatMachineInput::AbandonLiveOpenAdmission {
422 session_id: session_id.to_string(),
423 channel_id: channel_id.to_string(),
424 },
425 "AbandonLiveOpenAdmission",
426 )
427 .await
428 .map(|_| ())
429 .map_err(|reason| RuntimeDriverError::ValidationFailed { reason })
430 }
431
432 #[cfg(feature = "live")]
433 pub async fn live_channel_is_active_for_session(
434 &self,
435 session_id: &SessionId,
436 channel_id: &meerkat_live::LiveChannelId,
437 ) -> bool {
438 self.session_dsl_state(session_id)
439 .await
440 .ok()
441 .and_then(|state| {
442 state
443 .live_active_channel_by_session
444 .get(&session_id.to_string())
445 .cloned()
446 })
447 .is_some_and(|active| active == channel_id.to_string())
448 }
449
450 #[cfg(feature = "live")]
451 pub async fn live_session_for_active_channel(
452 &self,
453 channel_id: &meerkat_live::LiveChannelId,
454 ) -> Option<SessionId> {
455 let channel_id = channel_id.to_string();
456 let session_ids = {
457 let sessions = self.sessions.read().await;
458 sessions.keys().cloned().collect::<Vec<_>>()
459 };
460
461 for session_id in session_ids {
462 let Ok(state) = self.session_dsl_state(&session_id).await else {
463 continue;
464 };
465 if state
466 .live_channel_session_by_channel
467 .get(&channel_id)
468 .is_some_and(|owner| owner == &session_id.to_string())
469 {
470 return Some(session_id);
471 }
472 }
473 None
474 }
475
476 #[cfg(feature = "live")]
480 pub async fn live_session_for_status_channel(
481 &self,
482 channel_id: &meerkat_live::LiveChannelId,
483 ) -> Option<SessionId> {
484 let channel_id = channel_id.to_string();
485 let session_ids = {
486 let sessions = self.sessions.read().await;
487 sessions.keys().cloned().collect::<Vec<_>>()
488 };
489
490 for session_id in session_ids {
491 let Ok(state) = self.session_dsl_state(&session_id).await else {
492 continue;
493 };
494 if state
495 .live_channel_session_by_channel
496 .get(&channel_id)
497 .is_some_and(|owner| owner == &session_id.to_string())
498 || state.live_close_status_by_channel.contains_key(&channel_id)
499 {
500 return Some(session_id);
501 }
502 }
503 None
504 }
505
506 #[cfg(feature = "live")]
510 pub async fn live_session_for_webrtc_token(&self, token: &str) -> Option<SessionId> {
511 let session_ids = {
512 let sessions = self.sessions.read().await;
513 sessions.keys().cloned().collect::<Vec<_>>()
514 };
515
516 for session_id in session_ids {
517 let Ok(state) = self.session_dsl_state(&session_id).await else {
518 continue;
519 };
520 if state.live_webrtc_token_channel_by_token.contains_key(token) {
521 return Some(session_id);
522 }
523 }
524 None
525 }
526
527 #[cfg(feature = "live")]
531 pub async fn live_session_for_websocket_token(&self, token: &str) -> Option<SessionId> {
532 let session_ids = {
533 let sessions = self.sessions.read().await;
534 sessions.keys().cloned().collect::<Vec<_>>()
535 };
536
537 for session_id in session_ids {
538 let Ok(state) = self.session_dsl_state(&session_id).await else {
539 continue;
540 };
541 if state
542 .live_websocket_token_channel_by_token
543 .contains_key(token)
544 {
545 return Some(session_id);
546 }
547 }
548 None
549 }
550
551 #[cfg(feature = "live")]
552 pub async fn live_active_channel_for_session(
553 &self,
554 session_id: &SessionId,
555 ) -> Option<meerkat_live::LiveChannelId> {
556 self.session_dsl_state(session_id)
557 .await
558 .ok()
559 .and_then(|state| {
560 state
561 .live_active_channel_by_session
562 .get(&session_id.to_string())
563 .cloned()
564 })
565 .map(meerkat_live::LiveChannelId::new)
566 }
567
568 #[cfg(feature = "live")]
569 pub async fn resolve_live_refresh_queued_result(
570 &self,
571 session_id: &SessionId,
572 acceptance: &meerkat_live::LiveRefreshQueueAcceptance,
573 ) -> Result<LiveRefreshResultAuthority, RuntimeDriverError> {
574 let channel_id = acceptance.channel_id().to_string();
575 let (_, effects) = self
576 .apply_session_dsl_input(
577 session_id,
578 crate::meerkat_machine::dsl::MeerkatMachineInput::RecordLiveRefreshQueued {
579 channel_id: channel_id.clone(),
580 queue_acceptance_sequence: acceptance.acceptance_sequence(),
581 },
582 "RecordLiveRefreshQueued",
583 )
584 .await
585 .map_err(|reason| RuntimeDriverError::ValidationFailed { reason })?;
586
587 effects
588 .as_slice()
589 .iter()
590 .find_map(|effect| match effect {
591 crate::meerkat_machine::dsl::MeerkatMachineEffect::LiveRefreshResultResolved {
592 channel_id: effect_channel_id,
593 status,
594 sequence,
595 queue_acceptance_sequence,
596 } if *effect_channel_id == channel_id => Some(LiveRefreshResultAuthority {
597 status: *status,
598 sequence: *sequence,
599 queue_acceptance_sequence: *queue_acceptance_sequence,
600 }),
601 _ => None,
602 })
603 .ok_or_else(|| {
604 RuntimeDriverError::Internal(format!(
605 "RecordLiveRefreshQueued for channel '{channel_id}' emitted no LiveRefreshResultResolved effect"
606 ))
607 })
608 }
609
610 #[cfg(feature = "live")]
611 pub async fn resolve_live_close_result(
612 &self,
613 session_id: &SessionId,
614 observation: &meerkat_live::LiveChannelCloseObservation,
615 ) -> Result<LiveCloseResultAuthority, RuntimeDriverError> {
616 let channel_id = observation.channel_id().to_string();
617 let (_, effects) = self
618 .apply_session_dsl_input(
619 session_id,
620 crate::meerkat_machine::dsl::MeerkatMachineInput::RecordLiveCloseClosed {
621 session_id: session_id.to_string(),
622 channel_id: channel_id.clone(),
623 close_observation_sequence: observation.close_sequence(),
624 },
625 "RecordLiveCloseClosed",
626 )
627 .await
628 .map_err(|reason| RuntimeDriverError::ValidationFailed { reason })?;
629
630 let authority = effects.as_slice().iter().find_map(|effect| match effect {
631 crate::meerkat_machine::dsl::MeerkatMachineEffect::LiveCloseResultResolved {
632 channel_id: effect_channel_id,
633 status,
634 sequence,
635 close_observation_sequence,
636 } if *effect_channel_id == channel_id
637 && *close_observation_sequence == observation.close_sequence() =>
638 {
639 Some(LiveCloseResultAuthority::from_generated_effect(
640 channel_id.clone(),
641 *status,
642 *sequence,
643 *close_observation_sequence,
644 ))
645 }
646 _ => None,
647 });
648 match authority {
649 Some(authority) => authority.map_err(RuntimeDriverError::Internal),
650 None => Err(RuntimeDriverError::Internal(format!(
651 "RecordLiveCloseClosed for channel '{channel_id}' emitted no LiveCloseResultResolved effect"
652 ))),
653 }
654 }
655
656 #[cfg(feature = "live")]
657 pub async fn resolve_live_command_result(
658 &self,
659 session_id: &SessionId,
660 acceptance: &meerkat_live::LiveCommandQueueAcceptance,
661 ) -> Result<LiveCommandResultAuthority, RuntimeDriverError> {
662 let channel_id = acceptance.channel_id().to_string();
663 let command = dsl_live_command_kind(acceptance.kind());
664 let (_, effects) = self
665 .apply_session_dsl_input(
666 session_id,
667 crate::meerkat_machine::dsl::MeerkatMachineInput::RecordLiveCommandAccepted {
668 channel_id: channel_id.clone(),
669 command,
670 command_acceptance_sequence: acceptance.acceptance_sequence(),
671 },
672 "RecordLiveCommandAccepted",
673 )
674 .await
675 .map_err(|reason| RuntimeDriverError::ValidationFailed { reason })?;
676
677 effects
678 .as_slice()
679 .iter()
680 .find_map(|effect| match effect {
681 crate::meerkat_machine::dsl::MeerkatMachineEffect::LiveCommandResultResolved {
682 channel_id: effect_channel_id,
683 command: effect_command,
684 sequence,
685 command_acceptance_sequence,
686 } if *effect_channel_id == channel_id
687 && *effect_command == command
688 && *command_acceptance_sequence == acceptance.acceptance_sequence() =>
689 {
690 Some(LiveCommandResultAuthority {
691 command: *effect_command,
692 sequence: *sequence,
693 command_acceptance_sequence: *command_acceptance_sequence,
694 })
695 }
696 _ => None,
697 })
698 .ok_or_else(|| {
699 RuntimeDriverError::Internal(format!(
700 "RecordLiveCommandAccepted for channel '{channel_id}' emitted no LiveCommandResultResolved effect"
701 ))
702 })
703 }
704
705 #[cfg(feature = "live")]
706 pub async fn resolve_live_command_rejection_result(
707 &self,
708 session_id: &SessionId,
709 channel_id: &meerkat_live::LiveChannelId,
710 command: crate::meerkat_machine::dsl::LiveCommandPublicKind,
711 error: &meerkat_live::LiveAdapterHostError,
712 ) -> Result<LiveCommandRejectionAuthority, RuntimeDriverError> {
713 let channel_id = channel_id.to_string();
714 let rejection = dsl_live_command_rejection_reason(error);
715 let (_, effects) = self
716 .apply_session_dsl_input(
717 session_id,
718 crate::meerkat_machine::dsl::MeerkatMachineInput::RecordLiveCommandRejected {
719 channel_id: channel_id.clone(),
720 command,
721 rejection,
722 },
723 "RecordLiveCommandRejected",
724 )
725 .await
726 .map_err(|reason| RuntimeDriverError::ValidationFailed { reason })?;
727
728 effects
729 .as_slice()
730 .iter()
731 .find_map(|effect| match effect {
732 crate::meerkat_machine::dsl::MeerkatMachineEffect::LiveCommandRejectionResolved {
733 channel_id: effect_channel_id,
734 command: effect_command,
735 rejection: effect_rejection,
736 public_error_class,
737 sequence,
738 } if *effect_channel_id == channel_id
739 && *effect_command == command
740 && *effect_rejection == rejection =>
741 {
742 Some(LiveCommandRejectionAuthority {
743 command: *effect_command,
744 rejection: *effect_rejection,
745 public_error_class: *public_error_class,
746 sequence: *sequence,
747 })
748 }
749 _ => None,
750 })
751 .ok_or_else(|| {
752 RuntimeDriverError::Internal(format!(
753 "RecordLiveCommandRejected for channel '{channel_id}' emitted no LiveCommandRejectionResolved effect"
754 ))
755 })
756 }
757
758 #[cfg(feature = "live")]
759 pub async fn resolve_unbound_live_command_rejection_result(
760 &self,
761 channel_id: &meerkat_live::LiveChannelId,
762 command: crate::meerkat_machine::dsl::LiveCommandPublicKind,
763 ) -> Result<LiveCommandRejectionAuthority, RuntimeDriverError> {
764 let channel_id = channel_id.to_string();
765 let rejection = crate::meerkat_machine::dsl::LiveCommandRejectionReason::ChannelNotFound;
766 let effects = apply_dsl_transition_on_authority(
767 &self.live_unbound_rejection_authority,
768 crate::meerkat_machine::dsl::MeerkatMachineInput::RecordLiveCommandRejected {
769 channel_id: channel_id.clone(),
770 command,
771 rejection,
772 },
773 "RecordLiveCommandRejected:UnboundChannel",
774 )
775 .map_err(|reason| RuntimeDriverError::ValidationFailed { reason })?;
776
777 effects
778 .as_slice()
779 .iter()
780 .find_map(|effect| match effect {
781 crate::meerkat_machine::dsl::MeerkatMachineEffect::LiveCommandRejectionResolved {
782 channel_id: effect_channel_id,
783 command: effect_command,
784 rejection: effect_rejection,
785 public_error_class,
786 sequence,
787 } if *effect_channel_id == channel_id
788 && *effect_command == command
789 && *effect_rejection == rejection =>
790 {
791 Some(LiveCommandRejectionAuthority {
792 command: *effect_command,
793 rejection: *effect_rejection,
794 public_error_class: *public_error_class,
795 sequence: *sequence,
796 })
797 }
798 _ => None,
799 })
800 .ok_or_else(|| {
801 RuntimeDriverError::Internal(format!(
802 "RecordLiveCommandRejected for unbound channel '{channel_id}' emitted no LiveCommandRejectionResolved effect"
803 ))
804 })
805 }
806
807 #[cfg(feature = "live")]
808 pub async fn resolve_live_channel_request_rejection_result(
809 &self,
810 session_id: &SessionId,
811 channel_id: &meerkat_live::LiveChannelId,
812 request: crate::meerkat_machine::dsl::LiveChannelRequestPublicKind,
813 error: &meerkat_live::LiveAdapterHostError,
814 ) -> Result<LiveChannelRequestRejectionAuthority, RuntimeDriverError> {
815 self.resolve_live_channel_request_rejection_reason_result(
816 session_id,
817 channel_id,
818 request,
819 dsl_live_channel_request_rejection_reason(error),
820 )
821 .await
822 }
823
824 #[cfg(feature = "live")]
825 pub async fn resolve_live_channel_request_rejection_reason_result(
826 &self,
827 session_id: &SessionId,
828 channel_id: &meerkat_live::LiveChannelId,
829 request: crate::meerkat_machine::dsl::LiveChannelRequestPublicKind,
830 rejection: crate::meerkat_machine::dsl::LiveChannelRequestRejectionReason,
831 ) -> Result<LiveChannelRequestRejectionAuthority, RuntimeDriverError> {
832 let channel_id = channel_id.to_string();
833 let (_, effects) = self
834 .apply_session_dsl_input(
835 session_id,
836 crate::meerkat_machine::dsl::MeerkatMachineInput::RecordLiveChannelRequestRejected {
837 channel_id: channel_id.clone(),
838 request,
839 rejection,
840 },
841 "RecordLiveChannelRequestRejected",
842 )
843 .await
844 .map_err(|reason| RuntimeDriverError::ValidationFailed { reason })?;
845
846 effects
847 .as_slice()
848 .iter()
849 .find_map(|effect| match effect {
850 crate::meerkat_machine::dsl::MeerkatMachineEffect::LiveChannelRequestRejectionResolved {
851 channel_id: effect_channel_id,
852 request: effect_request,
853 rejection: effect_rejection,
854 public_error_class,
855 sequence,
856 } if *effect_channel_id == channel_id
857 && *effect_request == request
858 && *effect_rejection == rejection =>
859 {
860 Some(LiveChannelRequestRejectionAuthority {
861 request: *effect_request,
862 rejection: *effect_rejection,
863 public_error_class: *public_error_class,
864 sequence: *sequence,
865 })
866 }
867 _ => None,
868 })
869 .ok_or_else(|| {
870 RuntimeDriverError::Internal(format!(
871 "RecordLiveChannelRequestRejected for channel '{channel_id}' emitted no LiveChannelRequestRejectionResolved effect"
872 ))
873 })
874 }
875
876 #[cfg(feature = "live")]
877 pub async fn resolve_unbound_live_channel_request_rejection_result(
878 &self,
879 channel_id: &meerkat_live::LiveChannelId,
880 request: crate::meerkat_machine::dsl::LiveChannelRequestPublicKind,
881 ) -> Result<LiveChannelRequestRejectionAuthority, RuntimeDriverError> {
882 let channel_id = channel_id.to_string();
883 let rejection =
884 crate::meerkat_machine::dsl::LiveChannelRequestRejectionReason::ChannelNotFound;
885 let effects = apply_dsl_transition_on_authority(
886 &self.live_unbound_rejection_authority,
887 crate::meerkat_machine::dsl::MeerkatMachineInput::RecordLiveChannelRequestRejected {
888 channel_id: channel_id.clone(),
889 request,
890 rejection,
891 },
892 "RecordLiveChannelRequestRejected:UnboundChannel",
893 )
894 .map_err(|reason| RuntimeDriverError::ValidationFailed { reason })?;
895
896 effects
897 .as_slice()
898 .iter()
899 .find_map(|effect| match effect {
900 crate::meerkat_machine::dsl::MeerkatMachineEffect::LiveChannelRequestRejectionResolved {
901 channel_id: effect_channel_id,
902 request: effect_request,
903 rejection: effect_rejection,
904 public_error_class,
905 sequence,
906 } if *effect_channel_id == channel_id
907 && *effect_request == request
908 && *effect_rejection == rejection =>
909 {
910 Some(LiveChannelRequestRejectionAuthority {
911 request: *effect_request,
912 rejection: *effect_rejection,
913 public_error_class: *public_error_class,
914 sequence: *sequence,
915 })
916 }
917 _ => None,
918 })
919 .ok_or_else(|| {
920 RuntimeDriverError::Internal(format!(
921 "RecordLiveChannelRequestRejected for unbound channel '{channel_id}' emitted no LiveChannelRequestRejectionResolved effect"
922 ))
923 })
924 }
925
926 #[cfg(feature = "live")]
927 pub async fn record_live_webrtc_token_issued(
928 &self,
929 session_id: &SessionId,
930 channel_id: &meerkat_live::LiveChannelId,
931 token: &str,
932 issued_at_ms: u64,
933 ttl_ms: u64,
934 ) -> Result<LiveWebrtcTokenAuthority, RuntimeDriverError> {
935 let channel_id = channel_id.to_string();
936 let token = token.to_string();
937 let (_, effects) = self
938 .apply_session_dsl_input(
939 session_id,
940 crate::meerkat_machine::dsl::MeerkatMachineInput::RecordLiveWebrtcTokenIssued {
941 session_id: session_id.to_string(),
942 channel_id: channel_id.clone(),
943 token: token.clone(),
944 issued_at_ms,
945 ttl_ms,
946 },
947 "RecordLiveWebrtcTokenIssued",
948 )
949 .await
950 .map_err(|reason| RuntimeDriverError::ValidationFailed { reason })?;
951
952 effects
953 .as_slice()
954 .iter()
955 .find_map(|effect| match effect {
956 crate::meerkat_machine::dsl::MeerkatMachineEffect::LiveWebrtcTokenIssued {
957 session_id: effect_session_id,
958 channel_id: effect_channel_id,
959 token: effect_token,
960 expires_at_ms,
961 sequence,
962 } if *effect_session_id == session_id.to_string()
963 && *effect_channel_id == channel_id
964 && *effect_token == token =>
965 {
966 Some(LiveWebrtcTokenAuthority {
967 token: effect_token.clone(),
968 expires_at_ms: *expires_at_ms,
969 sequence: *sequence,
970 })
971 }
972 _ => None,
973 })
974 .ok_or_else(|| {
975 RuntimeDriverError::Internal(format!(
976 "RecordLiveWebrtcTokenIssued for channel '{channel_id}' emitted no LiveWebrtcTokenIssued effect"
977 ))
978 })
979 }
980
981 #[cfg(feature = "live")]
982 pub async fn resolve_live_webrtc_answer_admission(
983 &self,
984 session_id: &SessionId,
985 channel_id: &meerkat_live::LiveChannelId,
986 token: &str,
987 observed_at_ms: u64,
988 ) -> Result<LiveWebrtcAnswerAdmissionAuthority, RuntimeDriverError> {
989 let channel_id = channel_id.to_string();
990 let token = token.to_string();
991 let (_, effects) = self
992 .apply_session_dsl_input(
993 session_id,
994 crate::meerkat_machine::dsl::MeerkatMachineInput::ResolveLiveWebrtcAnswerAdmission {
995 session_id: session_id.to_string(),
996 channel_id: channel_id.clone(),
997 token: token.clone(),
998 observed_at_ms,
999 },
1000 "ResolveLiveWebrtcAnswerAdmission",
1001 )
1002 .await
1003 .map_err(|reason| RuntimeDriverError::ValidationFailed { reason })?;
1004
1005 effects
1006 .as_slice()
1007 .iter()
1008 .find_map(|effect| match effect {
1009 crate::meerkat_machine::dsl::MeerkatMachineEffect::LiveWebrtcAnswerAdmissionResolved {
1010 session_id: effect_session_id,
1011 channel_id: effect_channel_id,
1012 token: effect_token,
1013 admitted,
1014 rejection,
1015 public_error_class,
1016 sequence,
1017 } if *effect_session_id == session_id.to_string()
1018 && *effect_channel_id == channel_id
1019 && *effect_token == token =>
1020 {
1021 Some(LiveWebrtcAnswerAdmissionAuthority {
1022 admitted: *admitted,
1023 rejection: *rejection,
1024 public_error_class: *public_error_class,
1025 sequence: *sequence,
1026 })
1027 }
1028 _ => None,
1029 })
1030 .ok_or_else(|| {
1031 RuntimeDriverError::Internal(format!(
1032 "ResolveLiveWebrtcAnswerAdmission for channel '{channel_id}' emitted no LiveWebrtcAnswerAdmissionResolved effect"
1033 ))
1034 })
1035 }
1036
1037 #[cfg(feature = "live")]
1038 pub async fn resolve_live_webrtc_answer_result(
1039 &self,
1040 session_id: &SessionId,
1041 channel_id: &meerkat_live::LiveChannelId,
1042 answer_observation_sequence: u64,
1043 ) -> Result<LiveWebrtcAnswerResultAuthority, RuntimeDriverError> {
1044 let channel_id = channel_id.to_string();
1045 let (_, effects) = self
1046 .apply_session_dsl_input(
1047 session_id,
1048 crate::meerkat_machine::dsl::MeerkatMachineInput::RecordLiveWebrtcAnswerAccepted {
1049 session_id: session_id.to_string(),
1050 channel_id: channel_id.clone(),
1051 answer_observation_sequence,
1052 },
1053 "RecordLiveWebrtcAnswerAccepted",
1054 )
1055 .await
1056 .map_err(|reason| RuntimeDriverError::ValidationFailed { reason })?;
1057
1058 effects
1059 .as_slice()
1060 .iter()
1061 .find_map(|effect| match effect {
1062 crate::meerkat_machine::dsl::MeerkatMachineEffect::LiveWebrtcAnswerResultResolved {
1063 channel_id: effect_channel_id,
1064 status,
1065 answered,
1066 sequence,
1067 answer_observation_sequence: effect_observation_sequence,
1068 } if *effect_channel_id == channel_id
1069 && *effect_observation_sequence == answer_observation_sequence =>
1070 {
1071 Some(LiveWebrtcAnswerResultAuthority {
1072 status: *status,
1073 answered: *answered,
1074 sequence: *sequence,
1075 answer_observation_sequence: *effect_observation_sequence,
1076 })
1077 }
1078 _ => None,
1079 })
1080 .ok_or_else(|| {
1081 RuntimeDriverError::Internal(format!(
1082 "RecordLiveWebrtcAnswerAccepted for channel '{channel_id}' emitted no LiveWebrtcAnswerResultResolved effect"
1083 ))
1084 })
1085 }
1086
1087 #[cfg(feature = "live")]
1088 pub async fn record_live_websocket_token_issued(
1089 &self,
1090 session_id: &SessionId,
1091 channel_id: &meerkat_live::LiveChannelId,
1092 token: &str,
1093 issued_at_ms: u64,
1094 ttl_ms: u64,
1095 ) -> Result<LiveWebsocketTokenAuthority, RuntimeDriverError> {
1096 let channel_id = channel_id.to_string();
1097 let token = token.to_string();
1098 let (_, effects) = self
1099 .apply_session_dsl_input(
1100 session_id,
1101 crate::meerkat_machine::dsl::MeerkatMachineInput::RecordLiveWebsocketTokenIssued {
1102 session_id: session_id.to_string(),
1103 channel_id: channel_id.clone(),
1104 token: token.clone(),
1105 issued_at_ms,
1106 ttl_ms,
1107 },
1108 "RecordLiveWebsocketTokenIssued",
1109 )
1110 .await
1111 .map_err(|reason| RuntimeDriverError::ValidationFailed { reason })?;
1112
1113 effects
1114 .as_slice()
1115 .iter()
1116 .find_map(|effect| match effect {
1117 crate::meerkat_machine::dsl::MeerkatMachineEffect::LiveWebsocketTokenIssued {
1118 session_id: effect_session_id,
1119 channel_id: effect_channel_id,
1120 token: effect_token,
1121 expires_at_ms,
1122 sequence,
1123 } if *effect_session_id == session_id.to_string()
1124 && *effect_channel_id == channel_id
1125 && *effect_token == token =>
1126 {
1127 Some(LiveWebsocketTokenAuthority {
1128 token: effect_token.clone(),
1129 expires_at_ms: *expires_at_ms,
1130 sequence: *sequence,
1131 })
1132 }
1133 _ => None,
1134 })
1135 .ok_or_else(|| {
1136 RuntimeDriverError::Internal(format!(
1137 "RecordLiveWebsocketTokenIssued for channel '{channel_id}' emitted no LiveWebsocketTokenIssued effect"
1138 ))
1139 })
1140 }
1141
1142 #[cfg(feature = "live")]
1143 pub async fn resolve_live_websocket_token_admission(
1144 &self,
1145 session_id: &SessionId,
1146 channel_id: &meerkat_live::LiveChannelId,
1147 token: &str,
1148 observed_at_ms: u64,
1149 ) -> Result<LiveWebsocketTokenAdmissionAuthority, RuntimeDriverError> {
1150 let channel_id = channel_id.to_string();
1151 let token = token.to_string();
1152 let (_, effects) = self
1153 .apply_session_dsl_input(
1154 session_id,
1155 crate::meerkat_machine::dsl::MeerkatMachineInput::ResolveLiveWebsocketTokenAdmission {
1156 session_id: session_id.to_string(),
1157 channel_id: channel_id.clone(),
1158 token: token.clone(),
1159 observed_at_ms,
1160 },
1161 "ResolveLiveWebsocketTokenAdmission",
1162 )
1163 .await
1164 .map_err(|reason| RuntimeDriverError::ValidationFailed { reason })?;
1165
1166 extract_live_websocket_token_admission(
1167 effects.as_slice(),
1168 &session_id.to_string(),
1169 &channel_id,
1170 &token,
1171 "ResolveLiveWebsocketTokenAdmission",
1172 )
1173 }
1174
1175 #[cfg(feature = "live")]
1176 pub async fn resolve_unbound_live_websocket_token_admission(
1177 &self,
1178 channel_id: &meerkat_live::LiveChannelId,
1179 token: &str,
1180 observed_at_ms: u64,
1181 ) -> Result<LiveWebsocketTokenAdmissionAuthority, RuntimeDriverError> {
1182 let channel_id = channel_id.to_string();
1183 let token = token.to_string();
1184 let effects = apply_dsl_transition_on_authority(
1185 &self.live_unbound_rejection_authority,
1186 crate::meerkat_machine::dsl::MeerkatMachineInput::ResolveLiveWebsocketTokenAdmission {
1187 session_id: String::new(),
1188 channel_id: channel_id.clone(),
1189 token: token.clone(),
1190 observed_at_ms,
1191 },
1192 "ResolveLiveWebsocketTokenAdmission:UnboundChannel",
1193 )
1194 .map_err(|reason| RuntimeDriverError::ValidationFailed { reason })?;
1195
1196 extract_live_websocket_token_admission(
1197 effects.as_slice(),
1198 "",
1199 &channel_id,
1200 &token,
1201 "ResolveLiveWebsocketTokenAdmission:UnboundChannel",
1202 )
1203 }
1204
1205 #[cfg(feature = "live")]
1206 pub async fn resolve_live_channel_status_result(
1207 &self,
1208 session_id: &SessionId,
1209 observation: &meerkat_live::LiveChannelStatusObservation,
1210 ) -> Result<LiveChannelStatusAuthority, RuntimeDriverError> {
1211 let channel_id = observation.channel_id().to_string();
1212 let (status, degradation_reason, degradation_detail) =
1213 dsl_live_channel_status_from_observation(observation.status());
1214 let (_, effects) = self
1215 .apply_session_dsl_input(
1216 session_id,
1217 crate::meerkat_machine::dsl::MeerkatMachineInput::RecordLiveChannelStatus {
1218 channel_id: channel_id.clone(),
1219 status,
1220 status_observation_sequence: observation.observation_sequence(),
1221 degradation_reason,
1222 degradation_detail: degradation_detail.clone(),
1223 },
1224 "RecordLiveChannelStatus",
1225 )
1226 .await
1227 .map_err(|reason| RuntimeDriverError::ValidationFailed { reason })?;
1228
1229 let authority = effects.as_slice().iter().find_map(|effect| match effect {
1230 crate::meerkat_machine::dsl::MeerkatMachineEffect::LiveChannelStatusResolved {
1231 channel_id: effect_channel_id,
1232 status,
1233 sequence,
1234 status_observation_sequence,
1235 degradation_reason,
1236 degradation_detail,
1237 } if *effect_channel_id == channel_id
1238 && *status_observation_sequence == observation.observation_sequence() =>
1239 {
1240 Some(LiveChannelStatusAuthority::from_generated_effect(
1241 effect_channel_id.clone(),
1242 *status,
1243 *sequence,
1244 *status_observation_sequence,
1245 *degradation_reason,
1246 degradation_detail.clone(),
1247 ))
1248 }
1249 _ => None,
1250 });
1251 match authority {
1252 Some(Ok(authority)) => Ok(authority),
1253 Some(Err(reason)) => Err(RuntimeDriverError::Internal(reason)),
1254 None => Err(RuntimeDriverError::Internal(format!(
1255 "RecordLiveChannelStatus for channel '{channel_id}' emitted no LiveChannelStatusResolved effect"
1256 ))),
1257 }
1258 }
1259
1260 pub(super) async fn cancel_after_boundary_inner(
1261 &self,
1262 session_id: &SessionId,
1263 ) -> Result<(), RuntimeDriverError> {
1264 let (effect_tx, boundary_handle, projected_effect, previous_snapshot, committed_snapshot) = {
1265 let Some(_gate_guard) = self.lock_current_session_mutation_gate(session_id).await
1266 else {
1267 return Err(RuntimeDriverError::NotReady {
1268 state: RuntimeState::Destroyed,
1269 });
1270 };
1271 let staged = match self
1272 .stage_session_dsl_transition(
1273 session_id,
1274 crate::meerkat_machine::dsl::MeerkatMachineInput::CancelAfterBoundary {
1275 reason: "boundary cancel".to_string(),
1276 },
1277 "CancelAfterBoundary",
1278 )
1279 .await
1280 {
1281 Ok(staged) => staged,
1282 Err(_) => {
1283 let state = self
1288 .existing_session_runtime_state(session_id)
1289 .await
1290 .unwrap_or(RuntimeState::Destroyed);
1291 if state == RuntimeState::Destroyed {
1292 return Err(RuntimeDriverError::Destroyed);
1293 }
1294 return Err(RuntimeDriverError::NotReady { state });
1295 }
1296 };
1297 let projected_effect =
1298 crate::effect::runtime_effect_projection_optional_from_dsl_effects(&staged.effects)
1299 .map_err(RuntimeDriverError::Internal)?;
1300 let Some(projected_effect) = projected_effect else {
1301 let already_pending = staged.effects.as_slice().iter().any(|effect| {
1308 matches!(
1309 effect,
1310 crate::meerkat_machine::dsl::MeerkatMachineEffect::BoundaryCancelAlreadyPending
1311 )
1312 });
1313 if !already_pending {
1314 return Err(RuntimeDriverError::Internal(
1315 "CancelAfterBoundary emitted neither a RuntimeEffectFact nor BoundaryCancelAlreadyPending"
1316 .to_string(),
1317 ));
1318 }
1319 return Ok(());
1320 };
1321
1322 let sessions = self.sessions.read().await;
1323 let entry = sessions
1324 .get(session_id)
1325 .ok_or(RuntimeDriverError::NotReady {
1326 state: RuntimeState::Destroyed,
1327 })?;
1328 (
1329 entry.effect_sender(),
1330 entry.boundary_handle(),
1331 projected_effect,
1332 staged.previous_snapshot,
1333 staged.committed_snapshot,
1334 )
1335 };
1336
1337 if let Err(err) = self
1338 .dispatch_cancel_after_boundary_runtime_effect(
1339 session_id,
1340 effect_tx,
1341 boundary_handle,
1342 projected_effect,
1343 "CancelAfterBoundary",
1344 )
1345 .await
1346 {
1347 self.restore_session_dsl_state_if_current(
1348 session_id,
1349 committed_snapshot,
1350 previous_snapshot,
1351 )
1352 .await;
1353 return Err(err);
1354 }
1355
1356 Ok(())
1357 }
1358
1359 pub async fn stop_runtime_executor(
1363 &self,
1364 session_id: &SessionId,
1365 reason: impl Into<String>,
1366 ) -> Result<(), RuntimeDriverError> {
1367 self.execute_meerkat_machine_command(
1368 None,
1369 MeerkatMachineCommand::StopRuntimeExecutor {
1370 session_id: session_id.clone(),
1371 reason: reason.into(),
1372 },
1373 )
1374 .await
1375 .map_err(MeerkatMachine::driver_error_from_command_error)
1376 .map(|_| ())
1377 }
1378
1379 pub(super) async fn stop_runtime_executor_inner(
1380 &self,
1381 session_id: &SessionId,
1382 reason: String,
1383 ) -> Result<(), RuntimeDriverError> {
1384 let (driver, effect_tx, effect) = {
1385 let Some(gate) = self.session_mutation_gate(session_id).await else {
1386 return Err(RuntimeDriverError::NotReady {
1387 state: RuntimeState::Destroyed,
1388 });
1389 };
1390 let gate_guard = Arc::clone(&gate).lock_owned().await;
1391 let staged = match self
1392 .stage_session_dsl_transition(
1393 session_id,
1394 crate::meerkat_machine::dsl::MeerkatMachineInput::StopRuntimeExecutor {
1395 reason,
1396 },
1397 "StopRuntimeExecutor",
1398 )
1399 .await
1400 {
1401 Ok(staged) => staged,
1402 Err(reason) => {
1403 return Err(self
1406 .classify_session_dsl_rejection(session_id, reason)
1407 .await);
1408 }
1409 };
1410 let projected_effect =
1411 crate::effect::runtime_effect_projection_from_dsl_effects(&staged.effects)
1412 .map_err(RuntimeDriverError::Internal)?;
1413
1414 let (driver, effect_tx) = {
1415 let sessions = self.sessions.read().await;
1416 let entry = sessions
1417 .get(session_id)
1418 .ok_or(RuntimeDriverError::NotReady {
1419 state: RuntimeState::Destroyed,
1420 })?;
1421 if !Arc::ptr_eq(&entry.mutation_gate, &gate) {
1422 return Err(RuntimeDriverError::NotReady {
1423 state: RuntimeState::Destroyed,
1424 });
1425 }
1426 (entry.driver.clone(), entry.effect_sender())
1427 };
1428 drop(gate_guard);
1429 (driver, effect_tx, projected_effect.into_effect())
1430 };
1431
1432 if let Some(effect_tx) = effect_tx
1433 && effect_tx.send(effect).await.is_ok()
1434 {
1435 let stopped = tokio::time::timeout(std::time::Duration::from_millis(200), async {
1436 loop {
1437 let state = {
1438 let sessions = self.sessions.read().await;
1439 let entry =
1440 sessions
1441 .get(session_id)
1442 .ok_or(RuntimeDriverError::NotReady {
1443 state: RuntimeState::Destroyed,
1444 })?;
1445 if !Arc::ptr_eq(&entry.driver, &driver) {
1446 return Err(RuntimeDriverError::NotReady {
1447 state: RuntimeState::Destroyed,
1448 });
1449 }
1450 entry.control_snapshot().phase
1451 };
1452 match state {
1453 RuntimeState::Stopped => return Ok(()),
1454 RuntimeState::Destroyed => {
1455 return Err(RuntimeDriverError::NotReady {
1456 state: RuntimeState::Destroyed,
1457 });
1458 }
1459 _ => tokio::time::sleep(std::time::Duration::from_millis(10)).await,
1460 }
1461 }
1462 })
1463 .await;
1464 match stopped {
1465 Ok(result) => result?,
1466 Err(_) => {
1467 let authority = self
1468 .session_dsl_authority(session_id)
1469 .await
1470 .map_err(|reason| RuntimeDriverError::ValidationFailed { reason })?;
1471 let generated_stop_state = authority
1472 .lock()
1473 .unwrap_or_else(std::sync::PoisonError::into_inner)
1474 .state()
1475 .clone();
1476 if generated_stop_state.runtime_stop_deferred
1477 || generated_stop_state.lifecycle_phase
1478 == crate::meerkat_machine::dsl::MeerkatPhase::Stopped
1479 {
1480 return Ok(());
1481 }
1482 return Err(RuntimeDriverError::ValidationFailed {
1483 reason: "StopRuntimeExecutor effect was accepted but generated authority did not reach stopped"
1484 .to_string(),
1485 });
1486 }
1487 }
1488
1489 let _gate_guard = self
1490 .lock_current_session_driver_gate(session_id, &driver)
1491 .await?;
1492 let final_state = {
1493 let sessions = self.sessions.read().await;
1494 sessions
1495 .get(session_id)
1496 .ok_or(RuntimeDriverError::NotReady {
1497 state: RuntimeState::Destroyed,
1498 })?
1499 .control_snapshot()
1500 .phase
1501 };
1502 if !matches!(final_state, RuntimeState::Stopped) {
1503 return Err(RuntimeDriverError::ValidationFailed {
1504 reason: format!(
1505 "StopRuntimeExecutor effect completed without generated stopped authority: {final_state}"
1506 ),
1507 });
1508 }
1509
1510 return Ok(());
1511 }
1512
1513 let (driver, _gate_guard) = self
1514 .current_session_driver_with_authority(session_id)
1515 .await?;
1516 let completions = {
1517 let sessions = self.sessions.read().await;
1518 sessions
1519 .get(session_id)
1520 .ok_or(RuntimeDriverError::NotReady {
1521 state: RuntimeState::Destroyed,
1522 })?
1523 .completions
1524 .clone()
1525 };
1526 crate::control_plane::terminalize_async_stop(&driver, Some(&completions)).await?;
1527
1528 self.clear_dead_runtime_attachment(session_id).await;
1531 Ok(())
1532 }
1533
1534 pub async fn accept_input_with_completion(
1544 &self,
1545 session_id: &SessionId,
1546 input: Input,
1547 ) -> Result<(AcceptOutcome, Option<crate::completion::CompletionHandle>), RuntimeDriverError>
1548 {
1549 self.accept_input_with_completion_boxed(session_id, input)
1550 .await
1551 }
1552
1553 pub fn accept_input_with_completion_boxed<'a>(
1554 &'a self,
1555 session_id: &'a SessionId,
1556 input: Input,
1557 ) -> AcceptInputWithCompletionFuture<'a> {
1558 let input_id = input.id().clone();
1559 self.accept_boxed_input_with_completion(session_id, Box::new(input), input_id)
1560 }
1561
1562 pub fn accept_boxed_input_with_completion<'a>(
1563 &'a self,
1564 session_id: &'a SessionId,
1565 input: Box<Input>,
1566 _input_id: InputId,
1567 ) -> AcceptInputWithCompletionFuture<'a> {
1568 let session_id = session_id.clone();
1569 Box::pin(async move {
1570 let input = *input;
1571 match self
1572 .execute_meerkat_machine_ingress_command(
1573 MeerkatMachineCommand::AcceptWithCompletion {
1574 session_id: session_id.clone(),
1575 input,
1576 register_completion: true,
1577 },
1578 )
1579 .await?
1580 {
1581 MeerkatMachineCommandResult::AcceptWithCompletion {
1582 outcome,
1583 handle,
1584 admission_signal: _,
1585 } => Ok((outcome, handle)),
1586 other => Err(RuntimeDriverError::Internal(format!(
1587 "unexpected command result for accept_input_with_completion: {other:?}"
1588 ))),
1589 }
1590 })
1591 }
1592
1593 pub async fn accept_input_without_wake(
1599 &self,
1600 session_id: &SessionId,
1601 input: Input,
1602 ) -> Result<AcceptOutcome, RuntimeDriverError> {
1603 match self
1604 .execute_meerkat_machine_command(
1605 None,
1606 MeerkatMachineCommand::AcceptWithoutWake {
1607 session_id: session_id.clone(),
1608 input,
1609 },
1610 )
1611 .await
1612 .map_err(MeerkatMachine::driver_error_from_command_error)?
1613 {
1614 MeerkatMachineCommandResult::AcceptOutcome(outcome) => Ok(outcome),
1615 other => Err(RuntimeDriverError::Internal(format!(
1616 "unexpected command result for accept_input_without_wake: {other:?}"
1617 ))),
1618 }
1619 }
1620
1621 pub async fn ops_lifecycle_registry(
1623 &self,
1624 session_id: &SessionId,
1625 ) -> Option<Arc<crate::ops_lifecycle::RuntimeOpsLifecycleRegistry>> {
1626 match self
1627 .execute_meerkat_machine_command(
1628 None,
1629 MeerkatMachineCommand::OpsLifecycleRegistry {
1630 session_id: session_id.clone(),
1631 },
1632 )
1633 .await
1634 {
1635 Ok(MeerkatMachineCommandResult::OpsLifecycleRegistry(registry)) => registry,
1636 Ok(_) => {
1637 tracing::error!("ops_lifecycle_registry: unexpected command result variant");
1638 None
1639 }
1640 Err(_) => None,
1641 }
1642 }
1643
1644 pub async fn prepare_bindings(
1654 &self,
1655 session_id: SessionId,
1656 ) -> Result<meerkat_core::SessionRuntimeBindings, RuntimeBindingsError> {
1657 match Box::pin(self.prepare_session_runtime_bindings(
1658 session_id.clone(),
1659 super::dispatch_session::SessionBindingPreparation::AuthoritativeRuntimeBinding,
1660 ))
1661 .await
1662 {
1663 Ok(MeerkatMachineCommandResult::Bindings(bindings)) => Ok(bindings),
1664 Ok(_) => {
1665 tracing::error!("prepare_bindings: unexpected command result variant");
1666 Err(RuntimeBindingsError::SessionNotFound(session_id))
1667 }
1668 Err(err) => Err(RuntimeBindingsError::PrepareFailed(
1669 session_id,
1670 err.to_string(),
1671 )),
1672 }
1673 }
1674
1675 pub async fn prepare_local_session_bindings(
1684 &self,
1685 session_id: SessionId,
1686 ) -> Result<meerkat_core::SessionRuntimeBindings, RuntimeBindingsError> {
1687 match Box::pin(self.prepare_session_runtime_bindings(
1688 session_id.clone(),
1689 super::dispatch_session::SessionBindingPreparation::LocalSessionResources,
1690 ))
1691 .await
1692 {
1693 Ok(MeerkatMachineCommandResult::Bindings(bindings)) => Ok(bindings),
1694 Ok(_) => {
1695 tracing::error!(
1696 "prepare_local_session_bindings: unexpected command result variant"
1697 );
1698 Err(RuntimeBindingsError::SessionNotFound(session_id))
1699 }
1700 Err(_) => Err(RuntimeBindingsError::SessionNotFound(session_id)),
1701 }
1702 }
1703}