Skip to main content

meerkat_runtime/meerkat_machine/
runtime_control.rs

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/// Machine-generated authority for runtime cleanup after a completion waiter
171/// resolves. The action is projected from a generated DSL effect; surfaces use
172/// this wrapper instead of matching completion outcomes locally.
173#[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/// Machine-generated authority for mechanical completion-waiter failures.
230/// The generated effect owns both admission release and the public failure
231/// class/reason; surfaces only map these closed values to transport envelopes.
232#[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    /// Read-only routing projection over generated live channel status
477    /// authority. Active channels route through the active binding; closed
478    /// retained channels route through the machine-owned close result map.
479    #[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    /// Read-only routing projection over generated WebRTC token-owner state.
507    /// Admission still occurs only when the selected machine resolves the
508    /// typed admission input.
509    #[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    /// Read-only routing projection over generated WebSocket token-owner
528    /// state. The token lookup selects which machine receives the admission
529    /// input; it does not decide token validity or public result class.
530    #[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                    // Stage-first classification (dispatch_user_interrupt
1284                    // shape): the machine rejected the input; a Destroyed
1285                    // binding surfaces as the terminal `Destroyed` truth,
1286                    // every other phase as `NotReady`.
1287                    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                // Machine-owned convergence: a boundary-cancel dispatch is
1302                // already outstanding, so the machine took the typed
1303                // AlreadyPending arm (no RuntimeEffectFact). The request is
1304                // satisfied by the outstanding dispatch — and this bound is
1305                // what stops a boundary handle that re-enters
1306                // `cancel_after_boundary` from recursing unboundedly.
1307                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    /// Stop the attached runtime executor through the out-of-band control
1360    /// channel. When no loop is attached yet, a stop command is applied directly
1361    /// against the driver so queued work is still terminated consistently.
1362    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                    // Stage-first classification: a rejection on a Destroyed
1404                    // binding surfaces as the terminal `Destroyed` truth.
1405                    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        // No live effect sender was available for this stop path. Scrub any
1529        // dead attachment capabilities that may still be published.
1530        self.clear_dead_runtime_attachment(session_id).await;
1531        Ok(())
1532    }
1533
1534    /// Accept an input and return a completion handle that resolves when the
1535    /// input reaches a terminal state (Consumed or Abandoned).
1536    ///
1537    /// Returns `(AcceptOutcome, Option<CompletionHandle>)`:
1538    /// - `(Accepted, Some(handle))` — await handle for result
1539    /// - `(Accepted, None)` — input reached a terminal state during admission
1540    /// - `(Deduplicated, Some(handle))` — joined in-flight waiter
1541    /// - `(Deduplicated, None)` — input already terminal; no waiter needed
1542    /// - `(Rejected, _)` — returned as `Err(ValidationFailed)`
1543    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    /// Accept an input but intentionally do not wake the runtime loop.
1594    ///
1595    /// This is reserved for explicitly queued-only surface contracts that
1596    /// stage work for the next turn boundary instead of waking an idle session
1597    /// immediately.
1598    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    /// Get the shared ops lifecycle registry for a session/runtime instance.
1622    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    /// Prepare canonical runtime bindings for a session.
1645    ///
1646    /// This is the single canonical helper that replaces the hand-rolled
1647    /// `register_session()` + `ops_lifecycle_registry()` + manual threading
1648    /// dance. All runtime-backed surfaces should call this instead.
1649    ///
1650    /// The method is idempotent: if the session is already registered, it
1651    /// returns bindings from the existing entry. The epoch_id is stable
1652    /// across repeated calls for the same session.
1653    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    /// Prepare factory-consumable session runtime resources without emitting
1676    /// cross-machine binding signals.
1677    ///
1678    /// Mob provisioning uses this to pre-create the session-owned handle bundle
1679    /// before `MobMachine::Spawn` has committed the member runtime id. The
1680    /// authoritative mob binding is routed later through
1681    /// `RequestRuntimeBinding -> PrepareBindings`, which emits the typed
1682    /// `RuntimeBound` signal with the mob-owned `AgentRuntimeId` and fence.
1683    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}