1use std::collections::{BTreeSet, HashMap, HashSet};
20use std::future::Future;
21use std::pin::Pin;
22use std::sync::Arc;
23use std::sync::RwLock as StdRwLock;
24#[cfg(not(target_arch = "wasm32"))]
25use std::sync::{Mutex as StdMutex, OnceLock, Weak};
26
27use meerkat_core::lifecycle::{InputId, RunId};
28use meerkat_core::tool_scope::ToolScopeTurnOverlay;
29use meerkat_core::types::SessionId;
30use meerkat_core::{BlobId, BlobPayload, BlobRef, BlobStore, BlobStoreError};
31use meerkat_core::{
32 DeferredToolLoadAuthority, SessionToolVisibilityState, ToolFilter, ToolScopeApplyError,
33 ToolScopeRevision, ToolScopeStageError, ToolVisibilityOwner, ToolVisibilityWitness,
34};
35
36use crate::accept::AcceptOutcome;
37use crate::driver::ephemeral::EphemeralRuntimeDriver;
38use crate::driver::persistent::PersistentRuntimeDriver;
39use crate::identifiers::LogicalRuntimeId;
40use crate::input::Input;
41use crate::input_state::{
42 InputAbandonReason, InputLifecycleState, InputStateSeed, InputTerminalOutcome,
43};
44use crate::meerkat_machine_types::{
45 HydratedSessionLlmState, MeerkatAdmittedInputSnapshot, MeerkatArchiveSnapshot,
46 MeerkatBindingSnapshot, MeerkatCompletionWaiterSnapshot, MeerkatCompletionWaitersSnapshot,
47 MeerkatControlSnapshot, MeerkatCursorSnapshot, MeerkatDrainSnapshot, MeerkatDriverKind,
48 MeerkatFormalStateProjection, MeerkatInputsSnapshot, MeerkatLedgerSnapshot,
49 MeerkatMachineCommand, MeerkatMachineCommandError, MeerkatMachineCommandResult,
50 MeerkatMachineRunFailure, MeerkatMachineSpineSnapshot, MeerkatOpsSnapshot,
51 SessionLlmCapabilityDelta, SessionLlmCapabilitySurface, SessionLlmReconfigureHost,
52 SessionLlmReconfigureReport, SessionLlmReconfigureRequest, SessionToolVisibilityDelta,
53};
54use crate::runtime_state::RuntimeState;
55use crate::service_ext::SessionServiceRuntimeExt;
56use crate::store::RuntimeStore;
57use crate::tokio;
58use crate::tokio::sync::{Mutex, RwLock, mpsc};
59#[cfg(test)]
60use crate::traits::RuntimeDriver;
61use crate::traits::{
62 DestroyReport, RecoveryReport, RecycleReport, ResetReport, RetireReport,
63 RuntimeControlPlaneError, RuntimeDriverError,
64};
65
66#[allow(clippy::expect_used)]
67pub(crate) fn recover_projected_authority(
68 state: dsl::MeerkatMachineState,
69 context: &'static str,
70) -> dsl::MeerkatMachineAuthority {
71 dsl::MeerkatMachineAuthority::recover_from_state(state).expect(context)
72}
73
74struct ToolVisibilityOwnerGeneratedAuthorityBridgeToken;
75
76static TOOL_VISIBILITY_OWNER_GENERATED_AUTHORITY_BRIDGE_TOKEN:
77 ToolVisibilityOwnerGeneratedAuthorityBridgeToken =
78 ToolVisibilityOwnerGeneratedAuthorityBridgeToken;
79
80fn tool_visibility_owner_generated_authority_bridge_token()
81-> &'static (dyn std::any::Any + Send + Sync) {
82 &TOOL_VISIBILITY_OWNER_GENERATED_AUTHORITY_BRIDGE_TOKEN
83}
84
85#[doc(hidden)]
86#[allow(improper_ctypes_definitions, unsafe_code)]
87#[unsafe(export_name = concat!(
88 "__meerkat_runtime_generated_authority_bridge_token_is_valid_v1_tool_visibility_owner_",
89 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
90))]
91pub extern "Rust" fn tool_visibility_owner_generated_authority_bridge_token_is_valid(
92 token: &(dyn std::any::Any + Send + Sync),
93) -> bool {
94 token.is::<ToolVisibilityOwnerGeneratedAuthorityBridgeToken>()
95}
96
97fn generated_tool_visibility_owner(
98 owner: Arc<dyn ToolVisibilityOwner>,
99) -> Result<meerkat_core::GeneratedToolVisibilityOwner, String> {
100 #[allow(improper_ctypes_definitions, unsafe_code)]
101 unsafe extern "Rust" {
102 #[link_name = concat!(
103 "__meerkat_core_runtime_generated_tool_visibility_owner_build_v1_",
104 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
105 )]
106 fn core_runtime_generated_tool_visibility_owner_build(
107 token: &'static (dyn std::any::Any + Send + Sync),
108 owner: Arc<dyn ToolVisibilityOwner>,
109 ) -> Result<meerkat_core::GeneratedToolVisibilityOwner, String>;
110 }
111 #[allow(unsafe_code)]
112 unsafe {
113 core_runtime_generated_tool_visibility_owner_build(
114 tool_visibility_owner_generated_authority_bridge_token(),
115 owner,
116 )
117 }
118}
119
120#[derive(Clone)]
128pub struct StandaloneSessionRuntimeAuthorities {
129 tool_visibility_owner: meerkat_core::GeneratedToolVisibilityOwner,
130 turn_state: Arc<dyn meerkat_core::TurnStateHandle>,
131 model_routing: Arc<dyn meerkat_core::handles::ModelRoutingHandle>,
132 #[cfg(test)]
133 model_routing_test: Arc<crate::handles::RuntimeModelRoutingHandle>,
134}
135
136impl StandaloneSessionRuntimeAuthorities {
137 pub fn tool_visibility_owner(&self) -> &meerkat_core::GeneratedToolVisibilityOwner {
138 &self.tool_visibility_owner
139 }
140
141 pub fn turn_state(&self) -> &Arc<dyn meerkat_core::TurnStateHandle> {
142 &self.turn_state
143 }
144
145 pub fn model_routing(&self) -> &Arc<dyn meerkat_core::handles::ModelRoutingHandle> {
146 &self.model_routing
147 }
148
149 #[cfg(test)]
150 pub(crate) fn commit_sticky_model_fallback_for_test(
151 &self,
152 previous_identity: &meerkat_core::SessionLlmIdentity,
153 target_identity: &meerkat_core::SessionLlmIdentity,
154 target_profile: &meerkat_core::ModelProfileWitness,
155 visibility_plan: &meerkat_core::handles::StickyModelFallbackVisibilityPlan,
156 retry_attempt: u32,
157 ) -> Result<(), meerkat_core::handles::DslTransitionError> {
158 self.model_routing_test
159 .commit_sticky_model_fallback_for_test(
160 previous_identity,
161 target_identity,
162 target_profile,
163 visibility_plan,
164 retry_attempt,
165 )
166 }
167}
168
169pub fn standalone_session_runtime_authorities(
175 session_id: &SessionId,
176 current_identity: &meerkat_core::SessionLlmIdentity,
177 model_profile: Option<&meerkat_core::model_profile::ModelProfile>,
178 capability_base_filter: &ToolFilter,
179) -> Result<StandaloneSessionRuntimeAuthorities, String> {
180 let mut authority = dsl_authority::recover_authority_from_runtime_observation(
181 session_id,
182 RuntimeState::Idle,
183 None,
184 None,
185 None,
186 BTreeSet::new(),
187 None,
188 None,
189 None,
190 )
191 .map_err(|err| dsl_authority::map_error(err, "standalone visibility authority"))?;
192 let (current_capability_surface, current_capability_surface_status) = match model_profile {
193 Some(profile) => (
194 Some(dsl::SessionLlmCapabilitySurface {
195 supports_temperature: profile.supports_temperature,
196 supports_thinking: profile.supports_thinking,
197 supports_reasoning: profile.supports_reasoning,
198 inline_video: profile.inline_video,
199 vision: profile.vision,
200 image_input: profile.image_input,
201 image_tool_results: profile.image_tool_results,
202 supports_web_search: profile.supports_web_search,
203 image_generation: profile.image_generation,
204 realtime: profile.realtime,
205 call_timeout_secs: profile.call_timeout_secs,
206 }),
207 dsl::SessionLlmCapabilitySurfaceStatus::Resolved,
208 ),
209 None => (None, dsl::SessionLlmCapabilitySurfaceStatus::Unresolved),
210 };
211 dsl::MeerkatMachineMutator::apply(
212 &mut authority,
213 dsl::MeerkatMachineInput::HydrateSessionLlmState {
214 current_identity: dsl::SessionLlmIdentity::from_domain(current_identity),
215 current_capability_surface,
216 current_capability_surface_status,
217 current_capability_base_filter: dsl::ToolFilter::from_domain(capability_base_filter),
218 },
219 )
220 .map_err(|err| dsl_authority::map_error(err, "standalone visibility hydration"))?;
221 dsl::MeerkatMachineMutator::apply(
222 &mut authority,
223 dsl::MeerkatMachineInput::SetModelRoutingBaseline {
224 baseline_model: current_identity.model.clone(),
225 realtime_capable: model_profile.is_some_and(|profile| profile.realtime),
226 },
227 )
228 .map_err(|err| dsl_authority::map_error(err, "standalone model routing baseline"))?;
229 let authority = Arc::new(std::sync::Mutex::new(authority));
230 let owner = Arc::new(MachineToolVisibilityOwner::new());
231 owner.bind_dsl_authority(Arc::clone(&authority));
232 let shared_handle_authority = Arc::new(crate::handles::HandleDslAuthority::from_shared(
233 Arc::clone(&authority),
234 ));
235 let tool_visibility_owner =
236 generated_tool_visibility_owner(Arc::clone(&owner) as Arc<dyn ToolVisibilityOwner>)?;
237 let turn_state = Arc::new(crate::handles::RuntimeTurnStateHandle::standalone(
238 Arc::clone(&shared_handle_authority),
239 session_id.clone(),
240 )) as Arc<dyn meerkat_core::TurnStateHandle>;
241 let runtime_model_routing = Arc::new(
242 crate::handles::RuntimeModelRoutingHandle::new_with_visibility_owner(
243 shared_handle_authority,
244 owner,
245 ),
246 );
247 let model_routing =
248 Arc::clone(&runtime_model_routing) as Arc<dyn meerkat_core::handles::ModelRoutingHandle>;
249 Ok(StandaloneSessionRuntimeAuthorities {
250 tool_visibility_owner,
251 turn_state,
252 model_routing,
253 #[cfg(test)]
254 model_routing_test: runtime_model_routing,
255 })
256}
257
258pub fn standalone_tool_visibility_owner(
262 session_id: &SessionId,
263 current_identity: &meerkat_core::SessionLlmIdentity,
264 model_profile: Option<&meerkat_core::model_profile::ModelProfile>,
265 capability_base_filter: &ToolFilter,
266) -> Result<meerkat_core::GeneratedToolVisibilityOwner, String> {
267 standalone_session_runtime_authorities(
268 session_id,
269 current_identity,
270 model_profile,
271 capability_base_filter,
272 )
273 .map(|authorities| authorities.tool_visibility_owner)
274}
275
276#[derive(Debug, thiserror::Error)]
278pub enum RuntimeBindingsError {
279 #[error("session {0} not found in runtime adapter after registration")]
281 SessionNotFound(SessionId),
282 #[error("failed to prepare runtime bindings for session {0}: {1}")]
284 PrepareFailed(SessionId, String),
285}
286
287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
289pub struct InputPublicStateProjection {
290 pub lifecycle_state: dsl::InputPublicLifecycleState,
291 pub terminal_outcome: Option<dsl::InputPublicTerminalOutcome>,
292}
293
294#[derive(Debug, Clone, Copy, PartialEq, Eq)]
297pub struct RuntimeLifecycleFacts {
298 pub terminality: dsl::RuntimeLifecycleTerminality,
299 pub input_admission: dsl::RuntimeInputAdmission,
300 pub queue_admission: dsl::RuntimeQueueAdmission,
301 pub prepare_admission: dsl::RuntimePrepareAdmission,
302 pub ingress_admission: dsl::RuntimeIngressAdmission,
303}
304
305impl RuntimeLifecycleFacts {
306 #[must_use]
307 pub fn can_accept_input(self) -> bool {
308 self.input_admission == dsl::RuntimeInputAdmission::AcceptsInput
309 }
310
311 #[must_use]
312 pub fn can_process_queue(self) -> bool {
313 self.queue_admission == dsl::RuntimeQueueAdmission::ProcessesQueue
314 }
315
316 #[must_use]
317 pub fn can_prepare_run(self) -> bool {
318 self.prepare_admission == dsl::RuntimePrepareAdmission::Ready
319 }
320
321 #[must_use]
322 pub fn is_terminal(self) -> bool {
323 self.terminality == dsl::RuntimeLifecycleTerminality::Terminal
324 }
325}
326
327#[derive(Debug, Clone, Copy, PartialEq, Eq)]
330pub struct RuntimeLoopQueueAdmissionPlan {
331 pub queue_admission: dsl::RuntimeQueueAdmission,
332 pub run_binding: dsl::RuntimeLoopRunBinding,
333}
334
335impl RuntimeLoopQueueAdmissionPlan {
336 #[must_use]
337 pub fn can_process_queue(self) -> bool {
338 self.queue_admission == dsl::RuntimeQueueAdmission::ProcessesQueue
339 }
340
341 #[must_use]
342 pub fn uses_prebound_run(self) -> bool {
343 self.run_binding == dsl::RuntimeLoopRunBinding::UsePrebound
344 }
345}
346
347pub fn classify_runtime_lifecycle_state(
351 state: RuntimeState,
352) -> Result<RuntimeLifecycleFacts, String> {
353 let observed_state = dsl_authority::observed_runtime_lifecycle_state(state);
354 let mut authority = projection_authority();
355 let transition = dsl::MeerkatMachineMutator::apply(
356 &mut authority,
357 dsl::MeerkatMachineInput::ClassifyRuntimeLifecycleState {
358 state: observed_state,
359 },
360 )
361 .map_err(|err| {
362 format!("MeerkatMachine rejected runtime lifecycle classification for {state}: {err}")
363 })?;
364
365 transition
366 .into_effects()
367 .into_iter()
368 .find_map(|effect| match effect {
369 dsl::MeerkatMachineEffect::RuntimeLifecycleStateClassified {
370 state,
371 terminality,
372 input_admission,
373 queue_admission,
374 prepare_admission,
375 ingress_admission,
376 } if state == observed_state => Some(RuntimeLifecycleFacts {
377 terminality,
378 input_admission,
379 queue_admission,
380 prepare_admission,
381 ingress_admission,
382 }),
383 _ => None,
384 })
385 .ok_or_else(|| {
386 format!("MeerkatMachine emitted no runtime lifecycle classification for {state}")
387 })
388}
389
390pub fn classify_runtime_lifecycle_durable_state(
394 state: RuntimeState,
395) -> Result<RuntimeState, String> {
396 let observed_state = dsl_authority::observed_runtime_lifecycle_state(state);
397 let mut authority = projection_authority();
398 let transition = dsl::MeerkatMachineMutator::apply(
399 &mut authority,
400 dsl::MeerkatMachineInput::ClassifyRuntimeLifecycleDurability {
401 state: observed_state,
402 },
403 )
404 .map_err(|err| {
405 format!(
406 "MeerkatMachine rejected runtime lifecycle durability classification for {state}: {err}"
407 )
408 })?;
409
410 transition
411 .into_effects()
412 .into_iter()
413 .find_map(|effect| match effect {
414 dsl::MeerkatMachineEffect::RuntimeLifecycleDurabilityClassified {
415 state,
416 durable_state,
417 } if state == observed_state => Some(
418 dsl_authority::runtime_state_from_observed_lifecycle_state(durable_state),
419 ),
420 _ => None,
421 })
422 .ok_or_else(|| {
423 format!(
424 "MeerkatMachine emitted no runtime lifecycle durability classification for {state}"
425 )
426 })
427}
428
429pub fn classify_runtime_loop_queue_admission(
434 state: RuntimeState,
435 current_run_bound: bool,
436) -> Result<RuntimeLoopQueueAdmissionPlan, String> {
437 let observed_state = dsl_authority::observed_runtime_lifecycle_state(state);
438 let mut authority = projection_authority();
439 let transition = dsl::MeerkatMachineMutator::apply(
440 &mut authority,
441 dsl::MeerkatMachineInput::ClassifyRuntimeLoopQueueAdmission {
442 state: observed_state,
443 current_run_bound,
444 },
445 )
446 .map_err(|err| {
447 format!(
448 "MeerkatMachine rejected runtime-loop queue admission for {state} with current_run_bound={current_run_bound}: {err}"
449 )
450 })?;
451
452 transition
453 .into_effects()
454 .into_iter()
455 .find_map(|effect| match effect {
456 dsl::MeerkatMachineEffect::RuntimeLoopQueueAdmissionClassified {
457 state,
458 current_run_bound: observed_current_run_bound,
459 queue_admission,
460 run_binding,
461 } if state == observed_state && observed_current_run_bound == current_run_bound => {
462 Some(RuntimeLoopQueueAdmissionPlan {
463 queue_admission,
464 run_binding,
465 })
466 }
467 _ => None,
468 })
469 .ok_or_else(|| {
470 format!(
471 "MeerkatMachine emitted no runtime-loop queue admission for {state} with current_run_bound={current_run_bound}"
472 )
473 })
474}
475
476#[derive(Debug, Clone, Copy, PartialEq, Eq)]
484pub struct VisibleRuntimePhasePlan {
485 pub publish_control: bool,
486 pub selected_raw_phase: RuntimeState,
487 pub visible_phase: RuntimeState,
488}
489
490pub fn resolve_visible_runtime_phase(
498 dsl_phase: RuntimeState,
499 dsl_pre_run_phase: Option<RuntimeState>,
500 control_phase: RuntimeState,
501 control_pre_run_phase: Option<RuntimeState>,
502 has_runtime_persistence: bool,
503) -> Result<VisibleRuntimePhasePlan, String> {
504 let observed_dsl = dsl_authority::observed_runtime_lifecycle_state(dsl_phase);
505 let observed_control = dsl_authority::observed_runtime_lifecycle_state(control_phase);
506 let observed_dsl_pre_run =
507 dsl_pre_run_phase.map(dsl_authority::observed_runtime_lifecycle_state);
508 let observed_control_pre_run =
509 control_pre_run_phase.map(dsl_authority::observed_runtime_lifecycle_state);
510 let mut authority = projection_authority();
511 let transition = dsl::MeerkatMachineMutator::apply(
512 &mut authority,
513 dsl::MeerkatMachineInput::ResolveVisibleRuntimePhase {
514 dsl_phase: observed_dsl,
515 dsl_pre_run_phase: observed_dsl_pre_run,
516 control_phase: observed_control,
517 control_pre_run_phase: observed_control_pre_run,
518 has_runtime_persistence,
519 },
520 )
521 .map_err(|err| {
522 format!(
523 "MeerkatMachine rejected visible runtime phase resolution \
524 (dsl={dsl_phase}, control={control_phase}, persistence={has_runtime_persistence}): {err}"
525 )
526 })?;
527
528 transition
529 .into_effects()
530 .into_iter()
531 .find_map(|effect| match effect {
532 dsl::MeerkatMachineEffect::VisibleRuntimePhaseResolved {
533 publish_control,
534 selected_raw_phase,
535 visible_phase,
536 } => Some(VisibleRuntimePhasePlan {
537 publish_control,
538 selected_raw_phase: dsl_authority::runtime_state_from_observed_lifecycle_state(
539 selected_raw_phase,
540 ),
541 visible_phase: dsl_authority::runtime_state_from_observed_lifecycle_state(
542 visible_phase,
543 ),
544 }),
545 _ => None,
546 })
547 .ok_or_else(|| {
548 format!(
549 "MeerkatMachine emitted no visible runtime phase resolution \
550 (dsl={dsl_phase}, control={control_phase}, persistence={has_runtime_persistence})"
551 )
552 })
553}
554
555pub fn resolve_input_public_lifecycle_projection(
558 input_id: &InputId,
559 phase: InputLifecycleState,
560) -> Result<dsl::InputPublicLifecycleState, String> {
561 let input_key = input_id.to_string();
562 let mut authority = projection_authority();
563 let transition = dsl::MeerkatMachineMutator::apply(
564 &mut authority,
565 dsl::MeerkatMachineInput::ResolveInputPublicLifecycle {
566 input_id: input_key.clone(),
567 phase: observed_input_phase(phase),
568 },
569 )
570 .map_err(|err| {
571 format!("MeerkatMachine rejected public lifecycle projection for '{input_id}': {err}")
572 })?;
573
574 transition
575 .into_effects()
576 .into_iter()
577 .find_map(|effect| match effect {
578 dsl::MeerkatMachineEffect::InputPublicLifecycleResolved { input_id, phase }
579 if input_id == input_key =>
580 {
581 Some(phase)
582 }
583 _ => None,
584 })
585 .ok_or_else(|| {
586 format!("MeerkatMachine emitted no public lifecycle projection for '{input_id}'")
587 })
588}
589
590pub fn resolve_input_public_state_projection(
593 input_id: &InputId,
594 seed: &InputStateSeed,
595) -> Result<InputPublicStateProjection, String> {
596 let lifecycle_state = resolve_input_public_lifecycle_projection(input_id, seed.phase)?;
597 let terminal_outcome = resolve_input_public_terminal_projection(input_id, seed)?;
598 Ok(InputPublicStateProjection {
599 lifecycle_state,
600 terminal_outcome,
601 })
602}
603
604pub(crate) fn input_seed_behavioral_terminality_via_authority(
605 input_id: &InputId,
606 seed: &InputStateSeed,
607) -> Result<bool, String> {
608 classify_input_behavioral_terminality(input_id, seed.phase, seed.terminal_outcome.as_ref())
609}
610
611pub(crate) fn input_phase_behavioral_terminality_via_authority(
612 input_id: &InputId,
613 phase: InputLifecycleState,
614 terminal_outcome: Option<InputTerminalOutcome>,
615) -> Result<bool, String> {
616 classify_input_behavioral_terminality(input_id, phase, terminal_outcome.as_ref())
617}
618
619pub(crate) fn authorize_stored_input_state_seed(
622 input_id: &InputId,
623 seed: &InputStateSeed,
624) -> Result<(), String> {
625 let input_key = input_id.to_string();
626 let (terminal_kind, superseded_by, aggregate_id, abandon_reason, abandon_attempt_count) =
627 input_seed_terminal_parts(seed)?;
628 let mut authority = projection_authority();
629 let transition = dsl::MeerkatMachineMutator::apply(
630 &mut authority,
631 dsl::MeerkatMachineInput::AuthorizeStoredInputStateSeed {
632 input_id: input_key.clone(),
633 phase: observed_input_phase(seed.phase),
634 terminal_kind,
635 superseded_by,
636 aggregate_id,
637 abandon_reason,
638 abandon_attempt_count,
639 attempt_count: u64::from(seed.attempt_count),
640 run_id: seed.last_run_id.as_ref().map(dsl::RunId::from_domain),
641 boundary_sequence: seed.last_boundary_sequence,
642 admission_sequence: seed.admission_sequence,
643 recovery_lane: seed.recovery_lane.map(dsl::InputLane::from),
644 },
645 )
646 .map_err(|err| {
647 format!("MeerkatMachine rejected stored input-state seed for '{input_id}': {err}")
648 })?;
649
650 transition
651 .into_effects()
652 .into_iter()
653 .find_map(|effect| match effect {
654 dsl::MeerkatMachineEffect::StoredInputStateSeedAuthorized { input_id }
655 if input_id == input_key =>
656 {
657 Some(())
658 }
659 _ => None,
660 })
661 .ok_or_else(|| {
662 format!("MeerkatMachine emitted no stored input-state seed authority for '{input_id}'")
663 })
664}
665
666fn classify_input_behavioral_terminality(
667 input_id: &InputId,
668 phase: InputLifecycleState,
669 terminal_outcome: Option<&InputTerminalOutcome>,
670) -> Result<bool, String> {
671 let input_key = input_id.to_string();
672 let (terminal_kind, abandon_reason) = input_terminality_parts(terminal_outcome);
673 let mut authority = projection_authority();
674 let transition = dsl::MeerkatMachineMutator::apply(
675 &mut authority,
676 dsl::MeerkatMachineInput::ClassifyInputTerminality {
677 input_id: input_key.clone(),
678 phase: observed_input_phase(phase),
679 terminal_kind,
680 abandon_reason,
681 },
682 )
683 .map_err(|err| {
684 format!("MeerkatMachine rejected behavioral input terminality for '{input_id}': {err}")
685 })?;
686
687 let mut terminality = None;
688 for effect in transition.into_effects() {
689 match effect {
690 dsl::MeerkatMachineEffect::InputBehavioralTerminalityResolved {
691 input_id,
692 terminal,
693 } if input_id == input_key => terminality = Some(terminal),
694 other => {
695 return Err(format!(
696 "MeerkatMachine emitted unexpected behavioral input terminality effect for '{input_id}': {other:?}"
697 ));
698 }
699 }
700 }
701 terminality.ok_or_else(|| {
702 format!("MeerkatMachine emitted no behavioral input terminality for '{input_id}'")
703 })
704}
705
706fn resolve_input_public_terminal_projection(
707 input_id: &InputId,
708 seed: &InputStateSeed,
709) -> Result<Option<dsl::InputPublicTerminalOutcome>, String> {
710 let input_key = input_id.to_string();
711 let (terminal_kind, abandon_reason) = input_terminality_parts(seed.terminal_outcome.as_ref());
712 let mut authority = projection_authority();
713 let transition = dsl::MeerkatMachineMutator::apply(
714 &mut authority,
715 dsl::MeerkatMachineInput::ResolveInputPublicTerminalOutcome {
716 input_id: input_key.clone(),
717 phase: observed_input_phase(seed.phase),
718 terminal_kind,
719 abandon_reason,
720 },
721 )
722 .map_err(|err| {
723 format!("MeerkatMachine rejected public terminal projection for '{input_id}': {err}")
724 })?;
725
726 transition
727 .into_effects()
728 .into_iter()
729 .find_map(|effect| match effect {
730 dsl::MeerkatMachineEffect::InputPublicTerminalOutcomeResolved {
731 input_id,
732 terminal_outcome,
733 } if input_id == input_key => Some(terminal_outcome),
734 _ => None,
735 })
736 .ok_or_else(|| {
737 format!("MeerkatMachine emitted no public terminal projection for '{input_id}'")
738 })
739}
740
741fn projection_authority() -> dsl::MeerkatMachineAuthority {
742 dsl_authority::new_initialized_authority("projection authority must initialize")
743}
744
745#[cfg(feature = "live")]
746fn live_unbound_rejection_authority() -> crate::driver::ephemeral::SharedIngressDslAuthority {
747 Arc::new(std::sync::Mutex::new(
748 dsl_authority::new_initialized_authority(
749 "live unbound rejection authority must initialize",
750 ),
751 ))
752}
753
754fn observed_input_phase(phase: InputLifecycleState) -> dsl::RecoveredInputObservedPhase {
755 match phase {
756 InputLifecycleState::Accepted => dsl::RecoveredInputObservedPhase::Accepted,
757 InputLifecycleState::Queued => dsl::RecoveredInputObservedPhase::Queued,
758 InputLifecycleState::Staged => dsl::RecoveredInputObservedPhase::Staged,
759 InputLifecycleState::Applied => dsl::RecoveredInputObservedPhase::Applied,
760 InputLifecycleState::AppliedPendingConsumption => {
761 dsl::RecoveredInputObservedPhase::AppliedPendingConsumption
762 }
763 InputLifecycleState::Consumed => dsl::RecoveredInputObservedPhase::Consumed,
764 InputLifecycleState::Superseded => dsl::RecoveredInputObservedPhase::Superseded,
765 InputLifecycleState::Coalesced => dsl::RecoveredInputObservedPhase::Coalesced,
766 InputLifecycleState::Abandoned => dsl::RecoveredInputObservedPhase::Abandoned,
767 }
768}
769
770type InputSeedTerminalParts = (
771 Option<dsl::InputTerminalKind>,
772 Option<String>,
773 Option<String>,
774 Option<dsl::InputAbandonReason>,
775 u64,
776);
777
778fn input_seed_terminal_parts(seed: &InputStateSeed) -> Result<InputSeedTerminalParts, String> {
779 match seed.terminal_outcome.as_ref() {
780 None => Ok((None, None, None, None, 0)),
781 Some(InputTerminalOutcome::Consumed) => {
782 Ok((Some(dsl::InputTerminalKind::Consumed), None, None, None, 0))
783 }
784 Some(InputTerminalOutcome::Superseded { superseded_by }) => Ok((
785 Some(dsl::InputTerminalKind::Superseded),
786 Some(superseded_by.to_string()),
787 None,
788 None,
789 0,
790 )),
791 Some(InputTerminalOutcome::Coalesced { aggregate_id }) => Ok((
792 Some(dsl::InputTerminalKind::Coalesced),
793 None,
794 Some(aggregate_id.to_string()),
795 None,
796 0,
797 )),
798 Some(InputTerminalOutcome::Abandoned { reason }) => {
799 let abandon_attempt_count = match reason {
800 InputAbandonReason::MaxAttemptsExhausted { attempts } => u64::from(*attempts),
801 _ => u64::from(seed.attempt_count),
802 };
803 Ok((
804 Some(dsl::InputTerminalKind::Abandoned),
805 None,
806 None,
807 input_terminality_parts(seed.terminal_outcome.as_ref()).1,
808 abandon_attempt_count,
809 ))
810 }
811 }
812}
813
814fn input_terminality_parts(
815 outcome: Option<&InputTerminalOutcome>,
816) -> (
817 Option<dsl::InputTerminalKind>,
818 Option<dsl::InputAbandonReason>,
819) {
820 match outcome {
821 None => (None, None),
822 Some(InputTerminalOutcome::Consumed) => (Some(dsl::InputTerminalKind::Consumed), None),
823 Some(InputTerminalOutcome::Superseded { .. }) => {
824 (Some(dsl::InputTerminalKind::Superseded), None)
825 }
826 Some(InputTerminalOutcome::Coalesced { .. }) => {
827 (Some(dsl::InputTerminalKind::Coalesced), None)
828 }
829 Some(InputTerminalOutcome::Abandoned { reason }) => (
830 Some(dsl::InputTerminalKind::Abandoned),
831 Some(match reason {
832 InputAbandonReason::Retired => dsl::InputAbandonReason::Retired,
833 InputAbandonReason::Reset => dsl::InputAbandonReason::Reset,
834 InputAbandonReason::Stopped => dsl::InputAbandonReason::Stopped,
835 InputAbandonReason::Destroyed => dsl::InputAbandonReason::Destroyed,
836 InputAbandonReason::Cancelled => dsl::InputAbandonReason::Cancelled,
837 InputAbandonReason::MaxAttemptsExhausted { .. } => {
838 dsl::InputAbandonReason::MaxAttemptsExhausted
839 }
840 }),
841 ),
842 }
843}
844
845#[derive(Debug, Default)]
846struct UnavailableBlobStore;
847
848impl UnavailableBlobStore {
849 fn error() -> BlobStoreError {
850 BlobStoreError::Unsupported(
851 "persistent runtime constructed without blob store; blob-backed inputs require a BlobStore"
852 .to_string(),
853 )
854 }
855}
856
857#[cfg(not(target_arch = "wasm32"))]
858struct PersistentAuthAuthorityBundle {
859 store: StdMutex<Weak<dyn RuntimeStore>>,
860 auth_lease: Arc<crate::handles::RuntimeAuthLeaseHandle>,
861 oauth_flows: Arc<crate::handles::RuntimeOAuthFlowHandle>,
862}
863
864#[cfg(not(target_arch = "wasm32"))]
865#[derive(Debug, Clone, PartialEq, Eq, Hash)]
866enum PersistentAuthAuthorityKey {
867 Durable(String),
868 Process(usize),
869}
870
871#[cfg(not(target_arch = "wasm32"))]
872static PERSISTENT_AUTH_AUTHORITIES: OnceLock<
873 StdMutex<HashMap<PersistentAuthAuthorityKey, Arc<PersistentAuthAuthorityBundle>>>,
874> = OnceLock::new();
875
876#[cfg(not(target_arch = "wasm32"))]
877fn runtime_store_identity(store: &Arc<dyn RuntimeStore>) -> PersistentAuthAuthorityKey {
878 store
879 .auth_authority_key()
880 .map(PersistentAuthAuthorityKey::Durable)
881 .unwrap_or_else(|| {
882 PersistentAuthAuthorityKey::Process(Arc::as_ptr(store).cast::<()>() as usize)
883 })
884}
885
886fn runtime_stores_share_authority(a: &Arc<dyn RuntimeStore>, b: &Arc<dyn RuntimeStore>) -> bool {
887 match (a.auth_authority_key(), b.auth_authority_key()) {
888 (Some(a), Some(b)) => a == b,
889 _ => Arc::ptr_eq(a, b),
890 }
891}
892
893fn generated_runtime_auth_lease_handle(
894 handle: Arc<crate::handles::RuntimeAuthLeaseHandle>,
895) -> meerkat_core::handles::GeneratedAuthLeaseHandle {
896 #[allow(clippy::expect_used)]
897 crate::protocol_auth_lease_lifecycle_publication::generated_auth_lease_handle(handle)
898 .expect("runtime AuthLeaseHandle must be certified by generated AuthMachine authority")
899}
900
901#[cfg(not(target_arch = "wasm32"))]
902fn persistent_auth_authorities(
903 store: &Arc<dyn RuntimeStore>,
904) -> Arc<PersistentAuthAuthorityBundle> {
905 let key = runtime_store_identity(store);
906 let authorities = PERSISTENT_AUTH_AUTHORITIES.get_or_init(|| StdMutex::new(HashMap::new()));
907 let mut authorities = authorities
908 .lock()
909 .unwrap_or_else(std::sync::PoisonError::into_inner);
910 if let Some(existing) = authorities.get(&key) {
911 let stored_store_alive = existing
912 .store
913 .lock()
914 .unwrap_or_else(std::sync::PoisonError::into_inner)
915 .upgrade()
916 .is_some();
917 if matches!(key, PersistentAuthAuthorityKey::Durable(_)) || stored_store_alive {
918 existing.oauth_flows.bind_persistent_store(store);
919 *existing
920 .store
921 .lock()
922 .unwrap_or_else(std::sync::PoisonError::into_inner) = Arc::downgrade(store);
923 return Arc::clone(existing);
924 }
925 }
926 let auth_lease = Arc::new(crate::handles::RuntimeAuthLeaseHandle::new());
927 let oauth_flows = Arc::new(
928 crate::handles::RuntimeOAuthFlowHandle::new_with_persistent_store_and_auth_lease(
929 std::time::Duration::from_secs(10 * 60),
930 Arc::clone(&auth_lease),
931 store,
932 ),
933 );
934 let bundle = Arc::new(PersistentAuthAuthorityBundle {
935 store: StdMutex::new(Arc::downgrade(store)),
936 auth_lease,
937 oauth_flows,
938 });
939 authorities.insert(key, Arc::clone(&bundle));
940 bundle
941}
942
943#[cfg(all(test, not(target_arch = "wasm32")))]
944pub(crate) fn clear_persistent_auth_authorities_for_test() {
945 if let Some(authorities) = PERSISTENT_AUTH_AUTHORITIES.get() {
946 authorities
947 .lock()
948 .unwrap_or_else(std::sync::PoisonError::into_inner)
949 .clear();
950 }
951}
952
953#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
954#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
955impl BlobStore for UnavailableBlobStore {
956 async fn put_image(&self, _media_type: &str, _data: &str) -> Result<BlobRef, BlobStoreError> {
957 Err(Self::error())
958 }
959
960 async fn get(&self, _blob_id: &BlobId) -> Result<BlobPayload, BlobStoreError> {
961 Err(Self::error())
962 }
963
964 async fn delete(&self, _blob_id: &BlobId) -> Result<(), BlobStoreError> {
965 Err(Self::error())
966 }
967
968 async fn exists(&self, _blob_id: &BlobId) -> Result<bool, BlobStoreError> {
969 Err(Self::error())
970 }
971
972 fn is_persistent(&self) -> bool {
973 false
974 }
975}
976
977#[cfg(not(target_arch = "wasm32"))]
978type MeerkatMachineCommandFuture<'a> = Pin<
979 Box<
980 dyn Future<Output = Result<MeerkatMachineCommandResult, MeerkatMachineCommandError>>
981 + Send
982 + 'a,
983 >,
984>;
985
986#[cfg(target_arch = "wasm32")]
987type MeerkatMachineCommandFuture<'a> = Pin<
988 Box<dyn Future<Output = Result<MeerkatMachineCommandResult, MeerkatMachineCommandError>> + 'a>,
989>;
990
991pub(crate) use driver::{
992 DriverEntry, SharedCompletionRegistry, SharedDriver, cancel_runtime_loop_run,
993 commit_runtime_loop_run, fail_machine_run, fail_runtime_loop_run,
994 machine_authorize_runtime_loop_batch, machine_batch_primitive_projections,
995 machine_batch_runtime_semantics, machine_commit_prepared_destroy,
996 machine_commit_service_turn_terminal_receipt, machine_prepare_bindings_projection,
997 machine_prepare_destroy, machine_recover_ephemeral_driver, machine_recover_persistent_driver,
998 machine_recycle_preserving_work, machine_reset, machine_retire, machine_stop_runtime,
999 prepare_runtime_loop_batch_start,
1000};
1001
1002pub(crate) mod driver;
1003
1004mod comms_drain;
1005pub mod composition;
1006mod dispatch_control;
1007mod dispatch_drain;
1008mod dispatch_ingress;
1009mod dispatch_session;
1010#[allow(unused_variables, dead_code, clippy::cmp_owned)]
1011#[allow(clippy::assign_op_pattern)]
1012pub mod dsl;
1013pub(crate) mod dsl_authority;
1014mod dsl_effects;
1015mod llm_reconfigure;
1016mod runtime_control;
1017mod session_management;
1018mod traits;
1019mod visibility;
1020
1021pub use composition::{MeerkatCompositionSignalDispatcher, MeerkatConsumerSurface};
1022
1023pub use comms_drain::{
1024 CommsDrainMode, CommsDrainPhase, DrainExitReason, PeerEndpointStageError, PeerIngressOwner,
1025 SupervisorBinding, SupervisorBindingStageError,
1026};
1027pub(crate) use comms_drain::{
1028 CommsDrainSlot, GeneratedSupervisorBinding, GeneratedSupervisorRotationReceipt,
1029 GeneratedSupervisorRotationSubmit, SupervisorAuthorizeAdmission, SupervisorBindAdmission,
1030 SupervisorBridgeCommandAdmission, SupervisorRotationObservation, SupervisorRotationSubmission,
1031 SupervisorRotationTaskSlot,
1032};
1033pub(crate) use dsl_effects::{DslTransitionEffects, apply_dsl_transition_on_authority};
1034pub(crate) use visibility::MachineToolVisibilityOwner;
1035
1036struct StagedSessionDslInput {
1037 previous_snapshot: dsl::MeerkatMachineAuthoritySnapshot,
1038 committed_snapshot: dsl::MeerkatMachineAuthoritySnapshot,
1039 effects: DslTransitionEffects,
1040}
1041
1042impl StagedSessionDslInput {
1043 fn revived_stopped_session(&self) -> bool {
1050 self.effects.as_slice().iter().any(|effect| {
1051 matches!(
1052 effect,
1053 dsl::MeerkatMachineEffect::RuntimeNotice {
1054 kind: dsl::RuntimeNoticeKind::Recover,
1055 ..
1056 }
1057 )
1058 })
1059 }
1060
1061 fn has_routed_signal_effect(&self) -> bool {
1066 self.effects
1067 .as_slice()
1068 .iter()
1069 .any(|effect| composition::lift_routed_signal(effect).is_some())
1070 }
1071}
1072
1073#[derive(Clone, Copy)]
1074enum CommittedEffectDispatchFailure {
1075 PreserveCommittedDslState,
1076}
1077
1078type UnregisterTeardownResult = Result<(), RuntimeDriverError>;
1079type RuntimeStopCleanupResult = Result<(), RuntimeDriverError>;
1080
1081#[derive(Clone)]
1083struct UnregisterTeardownCoordinator {
1084 epoch_id: meerkat_core::RuntimeEpochId,
1085 coordinator_id: uuid::Uuid,
1086 result_rx: crate::tokio::sync::watch::Receiver<Option<UnregisterTeardownResult>>,
1087}
1088
1089#[derive(Clone)]
1093struct RuntimeStopCleanupCoordinator {
1094 epoch_id: meerkat_core::RuntimeEpochId,
1095 coordinator_id: uuid::Uuid,
1096 teardown_slot: Option<Arc<crate::runtime_loop::RuntimeLoopTeardownSlot>>,
1097 completion_authority: Arc<
1098 crate::tokio::sync::Mutex<
1099 Option<crate::meerkat_machine::driver::RuntimeCompletionResultAuthority>,
1100 >,
1101 >,
1102 result_rx: crate::tokio::sync::watch::Receiver<Option<RuntimeStopCleanupResult>>,
1103}
1104
1105#[derive(Clone)]
1106struct PendingUnregisterFinalization {
1107 durability_authority: session_management::RuntimeOpsLifecycleDurabilityAuthority,
1108 committed_snapshot: dsl::MeerkatMachineAuthoritySnapshot,
1109}
1110
1111struct UnregisterTeardownMechanicalObservations {
1112 runtime_loop_forced_abort: std::sync::atomic::AtomicBool,
1113 comms_drain_forced_abort: std::sync::atomic::AtomicBool,
1114}
1115
1116#[cfg(not(target_arch = "wasm32"))]
1120struct OpsLifecyclePersistenceWorker {
1121 handle: std::thread::JoinHandle<()>,
1122}
1123
1124#[cfg(target_arch = "wasm32")]
1125struct OpsLifecyclePersistenceWorker {
1126 handle: crate::tokio::task::JoinHandle<()>,
1127}
1128
1129impl UnregisterTeardownMechanicalObservations {
1130 fn new() -> Self {
1131 Self {
1132 runtime_loop_forced_abort: std::sync::atomic::AtomicBool::new(false),
1133 comms_drain_forced_abort: std::sync::atomic::AtomicBool::new(false),
1134 }
1135 }
1136
1137 fn from_durable_process_recovery(
1138 progress: Option<&crate::store::MachineUnregisterProgressSnapshot>,
1139 ) -> Self {
1140 let observations = Self::new();
1141 if let Some(progress) = progress {
1142 observations.runtime_loop_forced_abort.store(
1146 progress.runtime_loop_drain_pending(),
1147 std::sync::atomic::Ordering::Release,
1148 );
1149 observations.comms_drain_forced_abort.store(
1150 progress.comms_drain_exit_pending(),
1151 std::sync::atomic::Ordering::Release,
1152 );
1153 }
1154 observations
1155 }
1156}
1157
1158struct RuntimeSessionEntry {
1160 runtime_id: LogicalRuntimeId,
1162 mutation_gate: Arc<Mutex<()>>,
1174 supervisor_rotation_task: Arc<SupervisorRotationTaskSlot>,
1177 driver: SharedDriver,
1179 control_projection: Arc<StdRwLock<crate::driver::ephemeral::RuntimeControlProjection>>,
1185 ops_lifecycle: Arc<crate::ops_lifecycle::RuntimeOpsLifecycleRegistry>,
1187 ops_lifecycle_persistence_worker: Option<OpsLifecyclePersistenceWorker>,
1191 epoch_id: meerkat_core::RuntimeEpochId,
1193 handle_teardown_gate: Arc<crate::handles::HandleTeardownGate>,
1198 cursor_state: Arc<meerkat_core::EpochCursorState>,
1200 completions: SharedCompletionRegistry,
1202 tool_visibility_owner: Arc<MachineToolVisibilityOwner>,
1204 attachment_slot: RuntimeLoopAttachmentSlot,
1209 runtime_loop_teardown: Option<Arc<crate::runtime_loop::RuntimeLoopTeardownSlot>>,
1216 unregister_coordinator: Option<UnregisterTeardownCoordinator>,
1218 runtime_stop_cleanup_coordinator: Option<RuntimeStopCleanupCoordinator>,
1221 pending_unregister_finalization: Option<PendingUnregisterFinalization>,
1226 unregister_teardown_observations: Arc<UnregisterTeardownMechanicalObservations>,
1229 provisional_interrupt_handle:
1232 Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorInterruptHandle>>,
1233 dsl_authority: Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
1243 drain_slot: CommsDrainSlot,
1253}
1254
1255struct RuntimeLoopAttachment {
1260 wake_tx: mpsc::Sender<()>,
1261 effect_tx: mpsc::Sender<crate::effect::RuntimeEffect>,
1262 boundary_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>>,
1263 interrupt_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorInterruptHandle>>,
1264 loop_handle: tokio::task::JoinHandle<()>,
1265}
1266
1267enum RuntimeLoopAttachmentSlot {
1269 Empty,
1270 Attached(RuntimeLoopAttachment),
1271}
1272
1273impl RuntimeSessionEntry {
1274 fn dsl_mutation_blocked_by_unregister(
1275 &self,
1276 session_id: &SessionId,
1277 ) -> Option<RuntimeDriverError> {
1278 if self.pending_unregister_finalization.is_some() {
1279 return Some(RuntimeDriverError::UnregisterFinalizationOutcomeUnknown {
1280 reason: format!(
1281 "session {session_id} retains an ambiguous unregister finalization; retry unregister before applying any other lifecycle mutation"
1282 ),
1283 });
1284 }
1285 self.handle_teardown_gate
1286 .is_closed()
1287 .then(|| RuntimeDriverError::ValidationFailed {
1288 reason: format!("session {session_id} is a teardown-only unregister retry anchor"),
1289 })
1290 }
1291
1292 fn registration_blocked_by_unregister(
1293 &self,
1294 session_id: &SessionId,
1295 ) -> Option<RuntimeDriverError> {
1296 if let Some(error) = self.dsl_mutation_blocked_by_unregister(session_id) {
1297 return Some(error);
1298 }
1299 if self.unregister_coordinator.is_some() {
1300 return Some(RuntimeDriverError::NotReady {
1301 state: self.control_snapshot().phase,
1302 });
1303 }
1304 let Some(coordinator) = self.runtime_stop_cleanup_coordinator.as_ref() else {
1305 return None;
1306 };
1307 match coordinator.result_rx.borrow().clone() {
1308 None => Some(RuntimeDriverError::RuntimeStopInProgress {
1309 runtime_id: self.runtime_id.clone(),
1310 }),
1311 Some(Ok(())) => None,
1312 Some(Err(error)) => Some(error),
1313 }
1314 }
1315
1316 fn control_snapshot(&self) -> crate::driver::ephemeral::RuntimeControlProjection {
1317 self.control_projection
1318 .read()
1319 .map(|guard| guard.clone())
1320 .unwrap_or_else(|poisoned| {
1321 tracing::error!("runtime control projection lock poisoned");
1322 poisoned.into_inner().clone()
1323 })
1324 }
1325
1326 fn attachment_is_live(&self) -> bool {
1327 match &self.attachment_slot {
1328 RuntimeLoopAttachmentSlot::Attached(attachment) => {
1329 !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed()
1330 }
1331 RuntimeLoopAttachmentSlot::Empty => false,
1332 }
1333 }
1334
1335 fn generated_executor_registration_active(&self) -> bool {
1336 let authority = self
1337 .dsl_authority
1338 .lock()
1339 .unwrap_or_else(std::sync::PoisonError::into_inner);
1340 matches!(
1341 authority.state().registration_phase,
1342 dsl::RegistrationPhase::Active
1343 )
1344 }
1345
1346 fn generated_executor_registration_has_viable_attachment(&self) -> bool {
1347 self.generated_executor_registration_active()
1348 && match &self.attachment_slot {
1349 RuntimeLoopAttachmentSlot::Empty => true,
1350 RuntimeLoopAttachmentSlot::Attached(_) => self.attachment_is_live(),
1351 }
1352 }
1353
1354 fn close_handle_teardown_gate(&self) {
1355 let _guard = self
1356 .dsl_authority
1357 .lock()
1358 .unwrap_or_else(std::sync::PoisonError::into_inner);
1359 self.handle_teardown_gate.close();
1360 }
1361
1362 fn generated_executor_registration_active_or_draining(&self) -> bool {
1370 let authority = self
1371 .dsl_authority
1372 .lock()
1373 .unwrap_or_else(std::sync::PoisonError::into_inner);
1374 matches!(
1375 authority.state().registration_phase,
1376 dsl::RegistrationPhase::Active | dsl::RegistrationPhase::Draining
1377 )
1378 }
1379
1380 fn generated_stop_deferred(&self) -> bool {
1381 self.dsl_authority
1382 .lock()
1383 .unwrap_or_else(std::sync::PoisonError::into_inner)
1384 .state()
1385 .runtime_stop_deferred
1386 }
1387
1388 fn stage_generated_executor_registration_claim(
1389 &self,
1390 session_id: &SessionId,
1391 ) -> Result<StagedSessionDslInput, String> {
1392 let staged = MeerkatMachine::stage_dsl_transition_on_authority(
1393 &self.dsl_authority,
1394 dsl::MeerkatMachineInput::EnsureSessionWithExecutor {
1395 session_id: dsl::SessionId::from_domain(session_id),
1396 },
1397 "EnsureSessionWithExecutor",
1398 )?;
1399 if self.generated_executor_registration_active() {
1400 Ok(staged)
1401 } else {
1402 let mut authority = self
1403 .dsl_authority
1404 .lock()
1405 .unwrap_or_else(std::sync::PoisonError::into_inner);
1406 authority.restore_snapshot(staged.previous_snapshot);
1407 Err("generated MeerkatMachine did not grant active executor registration".into())
1408 }
1409 }
1410
1411 fn stage_generated_executor_exit_observation(&self) -> Result<StagedSessionDslInput, String> {
1412 MeerkatMachine::stage_runtime_owner_dsl_transition_on_authority(
1413 &self.dsl_authority,
1414 crate::meerkat_machine_types::MeerkatMachineFieldlessRuntimeInternalInput::RuntimeExecutorExited,
1415 )
1416 }
1417
1418 fn has_live_attachment(&self) -> bool {
1421 self.attachment_is_live()
1422 }
1423
1424 fn attach_runtime_loop(
1425 &mut self,
1426 wake_tx: mpsc::Sender<()>,
1427 effect_tx: mpsc::Sender<crate::effect::RuntimeEffect>,
1428 boundary_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>>,
1429 interrupt_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorInterruptHandle>>,
1430 spawned_loop: crate::runtime_loop::SpawnedRuntimeLoop,
1431 ) {
1432 let crate::runtime_loop::SpawnedRuntimeLoop {
1433 loop_handle,
1434 teardown_slot,
1435 startup: _,
1436 } = spawned_loop;
1437 self.provisional_interrupt_handle = None;
1438 self.runtime_stop_cleanup_coordinator = None;
1442 self.runtime_loop_teardown = Some(Arc::clone(&teardown_slot));
1443 self.attachment_slot = RuntimeLoopAttachmentSlot::Attached(RuntimeLoopAttachment {
1444 wake_tx,
1445 effect_tx,
1446 boundary_handle,
1447 interrupt_handle,
1448 loop_handle,
1449 });
1450 }
1451
1452 fn take_runtime_loop_attachment(&mut self) -> Option<RuntimeLoopAttachment> {
1460 match std::mem::replace(&mut self.attachment_slot, RuntimeLoopAttachmentSlot::Empty) {
1461 RuntimeLoopAttachmentSlot::Attached(attachment) => Some(attachment),
1462 RuntimeLoopAttachmentSlot::Empty => None,
1463 }
1464 }
1465
1466 fn clear_dead_attachment(&mut self) -> bool {
1467 if matches!(self.attachment_slot, RuntimeLoopAttachmentSlot::Attached(_))
1468 && !self.attachment_is_live()
1469 {
1470 self.attachment_slot = RuntimeLoopAttachmentSlot::Empty;
1471 return true;
1472 }
1473 false
1474 }
1475
1476 fn retire_completed_runtime_stop_after_revival(
1477 &mut self,
1478 session_id: &SessionId,
1479 ) -> Result<(), RuntimeDriverError> {
1480 if !matches!(self.attachment_slot, RuntimeLoopAttachmentSlot::Empty) {
1481 return Err(RuntimeDriverError::Internal(format!(
1482 "revived session {session_id} still carries a runtime-loop attachment"
1483 )));
1484 }
1485 match self.runtime_stop_cleanup_coordinator.as_ref() {
1486 None if self.runtime_loop_teardown.is_none() => return Ok(()),
1487 None => {
1488 return Err(RuntimeDriverError::Internal(format!(
1489 "revived session {session_id} carries a teardown slot without its stop coordinator"
1490 )));
1491 }
1492 Some(coordinator) => match coordinator.result_rx.borrow().clone() {
1493 Some(Ok(())) => {}
1494 None => {
1495 return Err(RuntimeDriverError::RuntimeStopInProgress {
1496 runtime_id: self.runtime_id.clone(),
1497 });
1498 }
1499 Some(Err(error)) => return Err(error),
1500 },
1501 }
1502 self.runtime_stop_cleanup_coordinator = None;
1503 self.runtime_loop_teardown = None;
1504 Ok(())
1505 }
1506
1507 fn wake_sender(&self) -> Option<mpsc::Sender<()>> {
1508 match &self.attachment_slot {
1509 RuntimeLoopAttachmentSlot::Attached(attachment)
1510 if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed() =>
1511 {
1512 Some(attachment.wake_tx.clone())
1513 }
1514 _ => None,
1515 }
1516 }
1517
1518 fn effect_sender(&self) -> Option<mpsc::Sender<crate::effect::RuntimeEffect>> {
1519 match &self.attachment_slot {
1520 RuntimeLoopAttachmentSlot::Attached(attachment)
1521 if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed() =>
1522 {
1523 Some(attachment.effect_tx.clone())
1524 }
1525 _ => None,
1526 }
1527 }
1528
1529 fn boundary_handle(
1530 &self,
1531 ) -> Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>> {
1532 match &self.attachment_slot {
1533 RuntimeLoopAttachmentSlot::Attached(attachment)
1534 if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed() =>
1535 {
1536 attachment.boundary_handle.clone()
1537 }
1538 _ => None,
1539 }
1540 }
1541
1542 fn interrupt_handle(
1543 &self,
1544 ) -> Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorInterruptHandle>> {
1545 match &self.attachment_slot {
1546 RuntimeLoopAttachmentSlot::Attached(attachment)
1547 if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed() =>
1548 {
1549 attachment.interrupt_handle.clone()
1550 }
1551 _ => self.provisional_interrupt_handle.clone(),
1552 }
1553 }
1554
1555 fn install_provisional_interrupt_handle(
1556 &mut self,
1557 handle: Arc<dyn meerkat_core::lifecycle::CoreExecutorInterruptHandle>,
1558 ) {
1559 if !self.attachment_is_live() {
1560 self.provisional_interrupt_handle = Some(handle);
1561 }
1562 }
1563}
1564
1565impl MeerkatMachine {
1566 #[cfg(test)]
1567 pub(crate) async fn model_routing_handle_for_test(
1568 &self,
1569 session_id: &SessionId,
1570 ) -> Option<Arc<crate::handles::RuntimeModelRoutingHandle>> {
1571 let (dsl_authority, visibility_owner) = {
1572 let sessions = self.sessions.read().await;
1573 let entry = sessions.get(session_id)?;
1574 (
1575 Arc::clone(&entry.dsl_authority),
1576 Arc::clone(&entry.tool_visibility_owner),
1577 )
1578 };
1579 Some(Arc::new(
1580 crate::handles::RuntimeModelRoutingHandle::new_with_visibility_owner(
1581 Arc::new(crate::handles::HandleDslAuthority::from_shared(
1582 dsl_authority,
1583 )),
1584 visibility_owner,
1585 ),
1586 ))
1587 }
1588
1589 async fn session_mutation_gate(&self, session_id: &SessionId) -> Option<Arc<Mutex<()>>> {
1595 let sessions = self.sessions.read().await;
1596 sessions
1597 .get(session_id)
1598 .map(|entry| Arc::clone(&entry.mutation_gate))
1599 }
1600
1601 async fn lock_current_session_mutation_gate(
1602 &self,
1603 session_id: &SessionId,
1604 ) -> Option<crate::tokio::sync::OwnedMutexGuard<()>> {
1605 loop {
1606 let gate = self.session_mutation_gate(session_id).await?;
1607 let gate_guard = Arc::clone(&gate).lock_owned().await;
1608 let sessions = self.sessions.read().await;
1609 let entry = sessions.get(session_id)?;
1610 if Arc::ptr_eq(&entry.mutation_gate, &gate) {
1611 return Some(gate_guard);
1612 }
1613 }
1614 }
1615
1616 async fn lock_registration_gate(
1617 &self,
1618 session_id: &SessionId,
1619 ) -> Result<crate::tokio::sync::OwnedMutexGuard<()>, RuntimeDriverError> {
1620 let gate_guard = self
1621 .lock_current_session_mutation_gate(session_id)
1622 .await
1623 .ok_or(RuntimeDriverError::NotReady {
1624 state: RuntimeState::Destroyed,
1625 })?;
1626 let blocked = {
1627 let sessions = self.sessions.read().await;
1628 let entry = sessions
1629 .get(session_id)
1630 .ok_or(RuntimeDriverError::NotReady {
1631 state: RuntimeState::Destroyed,
1632 })?;
1633 entry.registration_blocked_by_unregister(session_id)
1634 };
1635 if let Some(error) = blocked {
1636 return Err(error);
1637 }
1638 Ok(gate_guard)
1639 }
1640
1641 pub(crate) async fn lock_current_session_driver_gate(
1642 &self,
1643 session_id: &SessionId,
1644 driver: &SharedDriver,
1645 ) -> Result<crate::tokio::sync::OwnedMutexGuard<()>, RuntimeDriverError> {
1646 let gate_guard = self
1647 .lock_current_session_mutation_gate(session_id)
1648 .await
1649 .ok_or(RuntimeDriverError::NotReady {
1650 state: RuntimeState::Destroyed,
1651 })?;
1652 {
1653 let sessions = self.sessions.read().await;
1654 let entry = sessions
1655 .get(session_id)
1656 .ok_or(RuntimeDriverError::NotReady {
1657 state: RuntimeState::Destroyed,
1658 })?;
1659 if !Arc::ptr_eq(&entry.driver, driver) {
1660 return Err(RuntimeDriverError::NotReady {
1661 state: RuntimeState::Destroyed,
1662 });
1663 }
1664 }
1665 Ok(gate_guard)
1666 }
1667
1668 pub(crate) async fn lock_current_runtime_loop_driver_authority(
1669 &self,
1670 session_id: &SessionId,
1671 driver: &SharedDriver,
1672 ) -> Result<crate::tokio::sync::OwnedMutexGuard<()>, RuntimeDriverError> {
1673 let gate_guard = self
1674 .lock_current_session_driver_gate(session_id, driver)
1675 .await?;
1676 {
1677 let sessions = self.sessions.read().await;
1678 let entry = sessions
1679 .get(session_id)
1680 .ok_or(RuntimeDriverError::NotReady {
1681 state: RuntimeState::Destroyed,
1682 })?;
1683 if !entry.generated_executor_registration_active_or_draining() {
1684 return Err(RuntimeDriverError::ValidationFailed {
1685 reason:
1686 "generated MeerkatMachine has no active runtime-loop executor registration"
1687 .to_string(),
1688 });
1689 }
1690 }
1691 Ok(gate_guard)
1692 }
1693
1694 pub(crate) async fn current_runtime_stop_cleanup_in_progress(
1695 &self,
1696 session_id: &SessionId,
1697 driver: &SharedDriver,
1698 ) -> Result<bool, RuntimeDriverError> {
1699 let sessions = self.sessions.read().await;
1700 let entry = sessions
1701 .get(session_id)
1702 .ok_or(RuntimeDriverError::NotReady {
1703 state: RuntimeState::Destroyed,
1704 })?;
1705 if !Arc::ptr_eq(&entry.driver, driver) {
1706 return Err(RuntimeDriverError::NotReady {
1707 state: RuntimeState::Destroyed,
1708 });
1709 }
1710 let Some(coordinator) = entry.runtime_stop_cleanup_coordinator.as_ref() else {
1711 return Ok(false);
1712 };
1713 if coordinator.epoch_id != entry.epoch_id {
1714 return Err(RuntimeDriverError::Internal(format!(
1715 "stale runtime-stop cleanup coordinator epoch for session {session_id}"
1716 )));
1717 }
1718 Ok(coordinator.result_rx.borrow().is_none())
1719 }
1720
1721 async fn session_dsl_authority(
1722 &self,
1723 session_id: &SessionId,
1724 ) -> Result<Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>, String> {
1725 let sessions = self.sessions.read().await;
1726 sessions
1727 .get(session_id)
1728 .map(|entry| Arc::clone(&entry.dsl_authority))
1729 .ok_or_else(|| {
1730 RuntimeDriverError::NotReady {
1731 state: RuntimeState::Destroyed,
1732 }
1733 .to_string()
1734 })
1735 }
1736
1737 #[cfg(any(test, feature = "test-support"))]
1738 async fn session_handle_teardown_gate(
1739 &self,
1740 session_id: &SessionId,
1741 ) -> Result<Arc<crate::handles::HandleTeardownGate>, String> {
1742 let sessions = self.sessions.read().await;
1743 sessions
1744 .get(session_id)
1745 .map(|entry| Arc::clone(&entry.handle_teardown_gate))
1746 .ok_or_else(|| {
1747 RuntimeDriverError::NotReady {
1748 state: RuntimeState::Destroyed,
1749 }
1750 .to_string()
1751 })
1752 }
1753
1754 #[cfg(any(test, feature = "test-support"))]
1762 pub async fn test_install_session_peer_comms_handle_on_runtime(
1763 &self,
1764 session_id: &SessionId,
1765 runtime: &(dyn meerkat_core::handles::PeerCommsInstallTarget + '_),
1766 ) -> Result<(), String> {
1767 let dsl = self
1768 .session_dsl_authority(session_id)
1769 .await
1770 .map_err(|error| format!("session dsl authority unavailable: {error}"))?;
1771 let teardown_gate = self
1772 .session_handle_teardown_gate(session_id)
1773 .await
1774 .map_err(|error| format!("session handle teardown gate unavailable: {error}"))?;
1775 let handle = std::sync::Arc::new(
1776 crate::handles::HandleDslAuthority::from_shared_with_teardown_gate(dsl, teardown_gate),
1777 );
1778 crate::handles::RuntimePeerCommsHandle::install_generated_on(handle, runtime)
1779 }
1780
1781 fn preview_dsl_input_on_state(
1782 state: &dsl::MeerkatMachineState,
1783 input: dsl::MeerkatMachineInput,
1784 context: &str,
1785 ) -> Result<Vec<dsl::MeerkatMachineEffect>, String> {
1786 let mut preview = dsl::MeerkatMachineAuthority::recover_from_state(state.clone())
1787 .map_err(|err| dsl_authority::map_error(err, context))?;
1788 dsl::MeerkatMachineMutator::apply(&mut preview, input)
1789 .map(|transition| transition.into_effects())
1790 .map_err(|err| dsl_authority::map_error(err, context))
1791 }
1792
1793 async fn preview_session_dsl_input(
1794 &self,
1795 session_id: &SessionId,
1796 input: dsl::MeerkatMachineInput,
1797 context: &str,
1798 ) -> Result<Vec<dsl::MeerkatMachineEffect>, String> {
1799 let authority = self.session_dsl_authority(session_id).await?;
1800 let state = {
1801 let authority = authority
1802 .lock()
1803 .unwrap_or_else(std::sync::PoisonError::into_inner);
1804 authority.state().clone()
1805 };
1806 Self::preview_dsl_input_on_state(&state, input, context)
1807 }
1808
1809 async fn session_dsl_state(
1810 &self,
1811 session_id: &SessionId,
1812 ) -> Result<dsl::MeerkatMachineState, RuntimeControlPlaneError> {
1813 let authority = self
1814 .session_dsl_authority(session_id)
1815 .await
1816 .map_err(RuntimeControlPlaneError::Internal)?;
1817 let authority = authority
1818 .lock()
1819 .unwrap_or_else(std::sync::PoisonError::into_inner);
1820 Ok(authority.state().clone())
1821 }
1822
1823 async fn commit_session_dsl_transition(
1824 &self,
1825 session_id: &SessionId,
1826 staged: StagedSessionDslInput,
1827 context: &str,
1828 ) -> Result<(), String> {
1829 self.commit_session_dsl_transition_with_dispatch_failure(
1830 session_id,
1831 staged,
1832 context,
1833 CommittedEffectDispatchFailure::PreserveCommittedDslState,
1834 )
1835 .await
1836 }
1837
1838 async fn commit_session_dsl_transition_preserving_committed_state(
1839 &self,
1840 session_id: &SessionId,
1841 staged: StagedSessionDslInput,
1842 context: &str,
1843 ) -> Result<(), String> {
1844 self.commit_session_dsl_transition_with_dispatch_failure(
1845 session_id,
1846 staged,
1847 context,
1848 CommittedEffectDispatchFailure::PreserveCommittedDslState,
1849 )
1850 .await
1851 }
1852
1853 async fn commit_session_dsl_transition_with_dispatch_failure(
1854 &self,
1855 _session_id: &SessionId,
1856 staged: StagedSessionDslInput,
1857 context: &str,
1858 dispatch_failure: CommittedEffectDispatchFailure,
1859 ) -> Result<(), String> {
1860 if let Err(error) = self
1861 .dispatch_routed_signals_from_effects(&staged.effects)
1862 .await
1863 {
1864 let CommittedEffectDispatchFailure::PreserveCommittedDslState = dispatch_failure;
1865 return Err(format!(
1866 "DSL authority ({context}): committed effect dispatch failed: {error}"
1867 ));
1868 }
1869 Ok(())
1870 }
1871
1872 async fn dispatch_routed_signals_from_effects(
1873 &self,
1874 effects: &[dsl::MeerkatMachineEffect],
1875 ) -> Result<(), String> {
1876 let dispatcher = {
1877 self.composition_signal_dispatcher
1878 .read()
1879 .unwrap_or_else(std::sync::PoisonError::into_inner)
1880 .clone()
1881 };
1882 let Some(dispatcher) = dispatcher else {
1883 return Ok(());
1884 };
1885
1886 for effect in effects {
1887 if let Some(signal) = composition::lift_routed_signal(effect) {
1888 composition::dispatch_routed_signal(&dispatcher, signal).await?;
1889 }
1890 }
1891 Ok(())
1892 }
1893
1894 async fn clear_dead_runtime_attachment(&self, session_id: &SessionId) {
1895 let mut sessions = self.sessions.write().await;
1896 if let Some(entry) = sessions.get_mut(session_id) {
1897 let cleared = entry.clear_dead_attachment();
1898 if cleared && let Err(error) = entry.stage_generated_executor_exit_observation() {
1899 tracing::warn!(
1900 %session_id,
1901 error = %error,
1902 "generated MeerkatMachine rejected executor-exit observation while clearing dead attachment"
1903 );
1904 }
1905 }
1906 }
1907
1908 async fn dispatch_cancel_after_boundary_runtime_effect(
1909 &self,
1910 session_id: &SessionId,
1911 effect_tx: Option<mpsc::Sender<crate::effect::RuntimeEffect>>,
1912 boundary_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>>,
1913 projected_effect: crate::effect::ProjectedRuntimeEffect,
1914 context: &str,
1915 ) -> Result<(), RuntimeDriverError> {
1916 let Some(effect_tx) = effect_tx else {
1917 let state = self
1918 .existing_session_runtime_state(session_id)
1919 .await
1920 .unwrap_or(RuntimeState::Destroyed);
1921 return Err(RuntimeDriverError::NotReady { state });
1922 };
1923
1924 let reason = projected_effect.reason().to_string();
1925 if let Some(boundary_handle) = boundary_handle {
1926 boundary_handle
1927 .cancel_after_boundary(reason)
1928 .await
1929 .map_err(|err| {
1930 RuntimeDriverError::Internal(format!(
1931 "{context}: failed to apply live boundary cancel: {err}"
1932 ))
1933 })?;
1934 }
1935
1936 match effect_tx.send(projected_effect.into_effect()).await {
1937 Ok(()) => Ok(()),
1938 Err(_) => {
1939 self.clear_dead_runtime_attachment(session_id).await;
1940 Err(RuntimeDriverError::NotReady {
1941 state: RuntimeState::Idle,
1942 })
1943 }
1944 }
1945 }
1946
1947 async fn restore_session_dsl_state(
1948 &self,
1949 session_id: &SessionId,
1950 snapshot: dsl::MeerkatMachineAuthoritySnapshot,
1951 ) {
1952 if let Ok(authority) = self.session_dsl_authority(session_id).await {
1953 Self::restore_dsl_authority_snapshot(&authority, snapshot);
1954 }
1955 }
1956
1957 async fn restore_session_dsl_state_if_current(
1958 &self,
1959 session_id: &SessionId,
1960 expected_current: dsl::MeerkatMachineAuthoritySnapshot,
1961 restore: dsl::MeerkatMachineAuthoritySnapshot,
1962 ) -> bool {
1963 let Ok(authority) = self.session_dsl_authority(session_id).await else {
1964 return false;
1965 };
1966 Self::restore_dsl_authority_snapshot_if_current(&authority, expected_current, restore)
1967 }
1968
1969 fn restore_dsl_authority_snapshot(
1970 authority: &Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
1971 snapshot: dsl::MeerkatMachineAuthoritySnapshot,
1972 ) {
1973 let mut authority = authority
1974 .lock()
1975 .unwrap_or_else(std::sync::PoisonError::into_inner);
1976 authority.restore_snapshot(snapshot);
1977 }
1978
1979 fn restore_dsl_authority_snapshot_if_current(
1980 authority: &Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
1981 expected_current: dsl::MeerkatMachineAuthoritySnapshot,
1982 restore: dsl::MeerkatMachineAuthoritySnapshot,
1983 ) -> bool {
1984 let mut authority = authority
1985 .lock()
1986 .unwrap_or_else(std::sync::PoisonError::into_inner);
1987 let current = authority.snapshot();
1988 if current.state() == expected_current.state() {
1989 authority.restore_snapshot(restore);
1990 true
1991 } else {
1992 false
1993 }
1994 }
1995}
1996
1997#[derive(Debug, Clone, Copy)]
2000pub struct MachineSessionControlAuthority {
2001 _private: (),
2002}
2003
2004#[cfg(feature = "live")]
2005struct LiveOpenAdmissionGeneratedAuthorityBridgeToken;
2006
2007#[cfg(feature = "live")]
2008struct LiveCloseResultGeneratedAuthorityBridgeToken;
2009
2010#[cfg(feature = "live")]
2011struct LiveChannelStatusResultGeneratedAuthorityBridgeToken;
2012
2013#[cfg(feature = "live")]
2014static LIVE_OPEN_ADMISSION_GENERATED_AUTHORITY_BRIDGE_TOKEN:
2015 LiveOpenAdmissionGeneratedAuthorityBridgeToken = LiveOpenAdmissionGeneratedAuthorityBridgeToken;
2016
2017#[cfg(feature = "live")]
2018static LIVE_CLOSE_RESULT_GENERATED_AUTHORITY_BRIDGE_TOKEN:
2019 LiveCloseResultGeneratedAuthorityBridgeToken = LiveCloseResultGeneratedAuthorityBridgeToken;
2020
2021#[cfg(feature = "live")]
2022static LIVE_CHANNEL_STATUS_RESULT_GENERATED_AUTHORITY_BRIDGE_TOKEN:
2023 LiveChannelStatusResultGeneratedAuthorityBridgeToken =
2024 LiveChannelStatusResultGeneratedAuthorityBridgeToken;
2025
2026#[cfg(feature = "live")]
2027fn live_open_admission_generated_authority_bridge_token()
2028-> &'static (dyn std::any::Any + Send + Sync) {
2029 &LIVE_OPEN_ADMISSION_GENERATED_AUTHORITY_BRIDGE_TOKEN
2030}
2031
2032#[cfg(feature = "live")]
2033fn live_close_result_generated_authority_bridge_token() -> &'static (dyn std::any::Any + Send + Sync)
2034{
2035 &LIVE_CLOSE_RESULT_GENERATED_AUTHORITY_BRIDGE_TOKEN
2036}
2037
2038#[cfg(feature = "live")]
2039fn live_channel_status_result_generated_authority_bridge_token()
2040-> &'static (dyn std::any::Any + Send + Sync) {
2041 &LIVE_CHANNEL_STATUS_RESULT_GENERATED_AUTHORITY_BRIDGE_TOKEN
2042}
2043
2044#[cfg(feature = "live")]
2045#[doc(hidden)]
2046#[allow(improper_ctypes_definitions, unsafe_code)]
2047#[unsafe(export_name = concat!(
2048 "__meerkat_runtime_generated_authority_bridge_token_is_valid_v1_live_open_admission_",
2049 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
2050))]
2051pub extern "Rust" fn live_open_admission_generated_authority_bridge_token_is_valid(
2052 token: &(dyn std::any::Any + Send + Sync),
2053) -> bool {
2054 token.is::<LiveOpenAdmissionGeneratedAuthorityBridgeToken>()
2055}
2056
2057#[cfg(feature = "live")]
2058#[doc(hidden)]
2059#[allow(improper_ctypes_definitions, unsafe_code)]
2060#[unsafe(export_name = concat!(
2061 "__meerkat_runtime_generated_authority_bridge_token_is_valid_v1_live_close_result_",
2062 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
2063))]
2064pub extern "Rust" fn live_close_result_generated_authority_bridge_token_is_valid(
2065 token: &(dyn std::any::Any + Send + Sync),
2066) -> bool {
2067 token.is::<LiveCloseResultGeneratedAuthorityBridgeToken>()
2068}
2069
2070#[cfg(feature = "live")]
2071#[doc(hidden)]
2072#[allow(improper_ctypes_definitions, unsafe_code)]
2073#[unsafe(export_name = concat!(
2074 "__meerkat_runtime_generated_authority_bridge_token_is_valid_v1_live_channel_status_result_",
2075 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
2076))]
2077pub extern "Rust" fn live_channel_status_result_generated_authority_bridge_token_is_valid(
2078 token: &(dyn std::any::Any + Send + Sync),
2079) -> bool {
2080 token.is::<LiveChannelStatusResultGeneratedAuthorityBridgeToken>()
2081}
2082
2083#[cfg(feature = "live")]
2084fn build_live_channel_open_authority(
2085 session_id: SessionId,
2086 channel_id: meerkat_live::LiveChannelId,
2087 sequence: u64,
2088) -> Result<meerkat_live::LiveChannelOpenAuthority, String> {
2089 #[allow(improper_ctypes_definitions, unsafe_code)]
2090 unsafe extern "Rust" {
2091 #[link_name = concat!(
2092 "__meerkat_live_runtime_generated_live_channel_open_authority_build_v1_",
2093 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
2094 )]
2095 fn live_generated_channel_open_authority_build(
2096 token: &'static (dyn std::any::Any + Send + Sync),
2097 session_id: SessionId,
2098 channel_id: meerkat_live::LiveChannelId,
2099 sequence: u64,
2100 ) -> Result<meerkat_live::LiveChannelOpenAuthority, String>;
2101 }
2102 #[allow(unsafe_code)]
2103 unsafe {
2104 live_generated_channel_open_authority_build(
2105 live_open_admission_generated_authority_bridge_token(),
2106 session_id,
2107 channel_id,
2108 sequence,
2109 )
2110 }
2111}
2112
2113#[cfg(feature = "live")]
2114fn build_live_channel_close_commit_authority(
2115 channel_id: String,
2116 close_sequence: u64,
2117) -> Result<meerkat_live::LiveChannelCloseCommitAuthority, String> {
2118 #[allow(improper_ctypes_definitions, unsafe_code)]
2119 unsafe extern "Rust" {
2120 #[link_name = concat!(
2121 "__meerkat_live_runtime_generated_live_channel_close_commit_authority_build_v1_",
2122 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
2123 )]
2124 fn live_generated_channel_close_commit_authority_build(
2125 token: &'static (dyn std::any::Any + Send + Sync),
2126 channel_id: String,
2127 close_sequence: u64,
2128 ) -> Result<meerkat_live::LiveChannelCloseCommitAuthority, String>;
2129 }
2130 #[allow(unsafe_code)]
2131 unsafe {
2132 live_generated_channel_close_commit_authority_build(
2133 live_close_result_generated_authority_bridge_token(),
2134 channel_id,
2135 close_sequence,
2136 )
2137 }
2138}
2139
2140#[cfg(feature = "live")]
2141fn build_live_channel_status_commit_authority(
2142 channel_id: String,
2143 status_observation_sequence: u64,
2144) -> Result<meerkat_live::LiveChannelStatusCommitAuthority, String> {
2145 #[allow(improper_ctypes_definitions, unsafe_code)]
2146 unsafe extern "Rust" {
2147 #[link_name = concat!(
2148 "__meerkat_live_runtime_generated_live_channel_status_commit_authority_build_v1_",
2149 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
2150 )]
2151 fn live_generated_channel_status_commit_authority_build(
2152 token: &'static (dyn std::any::Any + Send + Sync),
2153 channel_id: String,
2154 status_observation_sequence: u64,
2155 ) -> Result<meerkat_live::LiveChannelStatusCommitAuthority, String>;
2156 }
2157 #[allow(unsafe_code)]
2158 unsafe {
2159 live_generated_channel_status_commit_authority_build(
2160 live_channel_status_result_generated_authority_bridge_token(),
2161 channel_id,
2162 status_observation_sequence,
2163 )
2164 }
2165}
2166
2167#[derive(Debug, Clone)]
2174#[cfg(feature = "live")]
2175pub struct LiveOpenAdmissionAuthority {
2176 session_id: SessionId,
2177 channel_id: meerkat_live::LiveChannelId,
2178 admitted: bool,
2179 rejection: Option<dsl::LiveOpenAdmissionRejection>,
2180 bound_llm_identity: Option<meerkat_core::SessionLlmIdentity>,
2181 sequence: u64,
2182 channel_open_authority: Option<meerkat_live::LiveChannelOpenAuthority>,
2183}
2184
2185#[cfg(feature = "live")]
2186impl LiveOpenAdmissionAuthority {
2187 pub(crate) fn from_generated_effect(
2188 session_id: SessionId,
2189 channel_id: meerkat_live::LiveChannelId,
2190 admitted: bool,
2191 rejection: Option<dsl::LiveOpenAdmissionRejection>,
2192 bound_llm_identity: Option<dsl::SessionLlmIdentity>,
2193 sequence: u64,
2194 ) -> Result<Self, String> {
2195 let bound_llm_identity = match (admitted, bound_llm_identity) {
2196 (true, Some(identity)) => Some(identity.try_into()?),
2197 (true, None) => {
2198 return Err(
2199 "generated live-open admission was admitted without bound LLM identity"
2200 .to_string(),
2201 );
2202 }
2203 (false, _) => None,
2204 };
2205 let channel_open_authority = if admitted {
2206 Some(build_live_channel_open_authority(
2207 session_id.clone(),
2208 channel_id.clone(),
2209 sequence,
2210 )?)
2211 } else {
2212 None
2213 };
2214 Ok(Self {
2215 session_id,
2216 channel_id,
2217 admitted,
2218 rejection,
2219 bound_llm_identity,
2220 sequence,
2221 channel_open_authority,
2222 })
2223 }
2224
2225 #[must_use]
2226 pub fn session_id(&self) -> &SessionId {
2227 &self.session_id
2228 }
2229
2230 #[must_use]
2231 pub fn channel_id(&self) -> &meerkat_live::LiveChannelId {
2232 &self.channel_id
2233 }
2234
2235 #[must_use]
2236 pub fn admitted(&self) -> bool {
2237 self.admitted
2238 }
2239
2240 #[must_use]
2241 pub fn rejection(&self) -> Option<dsl::LiveOpenAdmissionRejection> {
2242 self.rejection
2243 }
2244
2245 #[must_use]
2246 pub fn bound_llm_identity(&self) -> Option<&meerkat_core::SessionLlmIdentity> {
2247 self.bound_llm_identity.as_ref()
2248 }
2249
2250 #[must_use]
2251 pub fn sequence(&self) -> u64 {
2252 self.sequence
2253 }
2254
2255 #[must_use]
2256 pub fn channel_open_authority(&self) -> Option<&meerkat_live::LiveChannelOpenAuthority> {
2257 self.channel_open_authority.as_ref()
2258 }
2259}
2260
2261#[derive(Debug, Clone, PartialEq, Eq)]
2268#[cfg(feature = "live")]
2269pub struct LiveRefreshResultAuthority {
2270 pub status: dsl::LiveRefreshPublicStatus,
2271 pub sequence: u64,
2272 pub queue_acceptance_sequence: u64,
2273}
2274
2275#[derive(Debug, Clone)]
2280#[cfg(feature = "live")]
2281pub struct LiveCloseResultAuthority {
2282 pub status: dsl::LiveClosePublicStatus,
2283 pub sequence: u64,
2284 pub close_observation_sequence: u64,
2285 channel_close_commit_authority: Option<meerkat_live::LiveChannelCloseCommitAuthority>,
2286}
2287
2288#[cfg(feature = "live")]
2289impl LiveCloseResultAuthority {
2290 pub(crate) fn from_generated_effect(
2291 channel_id: String,
2292 status: dsl::LiveClosePublicStatus,
2293 sequence: u64,
2294 close_observation_sequence: u64,
2295 ) -> Result<Self, String> {
2296 let channel_close_commit_authority = match status {
2297 dsl::LiveClosePublicStatus::Closed => Some(build_live_channel_close_commit_authority(
2298 channel_id,
2299 close_observation_sequence,
2300 )?),
2301 };
2302 Ok(Self {
2303 status,
2304 sequence,
2305 close_observation_sequence,
2306 channel_close_commit_authority,
2307 })
2308 }
2309
2310 #[must_use]
2311 pub fn channel_close_commit_authority(
2312 &self,
2313 ) -> Option<&meerkat_live::LiveChannelCloseCommitAuthority> {
2314 self.channel_close_commit_authority.as_ref()
2315 }
2316
2317 #[must_use]
2318 pub fn into_channel_close_commit_authority(
2319 self,
2320 ) -> Option<meerkat_live::LiveChannelCloseCommitAuthority> {
2321 self.channel_close_commit_authority
2322 }
2323}
2324
2325#[derive(Debug, Clone, PartialEq, Eq)]
2331#[cfg(feature = "live")]
2332pub struct LiveCommandResultAuthority {
2333 pub command: dsl::LiveCommandPublicKind,
2334 pub sequence: u64,
2335 pub command_acceptance_sequence: u64,
2336}
2337
2338#[derive(Debug, Clone, PartialEq, Eq)]
2345#[cfg(feature = "live")]
2346pub struct LiveCommandRejectionAuthority {
2347 pub command: dsl::LiveCommandPublicKind,
2348 pub rejection: dsl::LiveCommandRejectionReason,
2349 pub public_error_class: dsl::LiveCommandRejectionPublicErrorClass,
2350 pub sequence: u64,
2351}
2352
2353#[derive(Debug, Clone, PartialEq, Eq)]
2360#[cfg(feature = "live")]
2361pub struct LiveChannelRequestRejectionAuthority {
2362 pub request: dsl::LiveChannelRequestPublicKind,
2363 pub rejection: dsl::LiveChannelRequestRejectionReason,
2364 pub public_error_class: dsl::LiveChannelRequestRejectionPublicErrorClass,
2365 pub sequence: u64,
2366}
2367
2368#[derive(Debug, Clone, PartialEq, Eq)]
2375#[cfg(feature = "live")]
2376pub struct LiveWebrtcTokenAuthority {
2377 pub token: String,
2378 pub expires_at_ms: u64,
2379 pub sequence: u64,
2380}
2381
2382#[derive(Debug, Clone, PartialEq, Eq)]
2388#[cfg(feature = "live")]
2389pub struct LiveWebrtcAnswerAdmissionAuthority {
2390 pub admitted: bool,
2391 pub rejection: Option<dsl::LiveWebrtcAnswerAdmissionRejection>,
2392 pub public_error_class: Option<dsl::LiveChannelRequestRejectionPublicErrorClass>,
2393 pub sequence: u64,
2394}
2395
2396#[derive(Debug, Clone, PartialEq, Eq)]
2403#[cfg(feature = "live")]
2404pub struct LiveWebrtcAnswerResultAuthority {
2405 pub status: dsl::LiveWebrtcAnswerPublicStatus,
2406 pub answered: bool,
2407 pub sequence: u64,
2408 pub answer_observation_sequence: u64,
2409}
2410
2411#[derive(Debug, Clone, PartialEq, Eq)]
2418#[cfg(feature = "live")]
2419pub struct LiveWebsocketTokenAuthority {
2420 pub token: String,
2421 pub expires_at_ms: u64,
2422 pub sequence: u64,
2423}
2424
2425#[derive(Debug, Clone, PartialEq, Eq)]
2431#[cfg(feature = "live")]
2432pub struct LiveWebsocketTokenAdmissionAuthority {
2433 pub admitted: bool,
2434 pub rejection: Option<dsl::LiveWebsocketTokenAdmissionRejection>,
2435 pub public_error_class: Option<dsl::LiveWebsocketTokenAdmissionPublicErrorClass>,
2436 pub sequence: u64,
2437}
2438
2439#[derive(Debug, Clone)]
2445#[cfg(feature = "live")]
2446pub struct LiveChannelStatusAuthority {
2447 pub status: dsl::LiveChannelPublicStatus,
2448 pub sequence: u64,
2449 pub status_observation_sequence: u64,
2450 pub degradation_reason: Option<dsl::LiveChannelDegradationReason>,
2451 pub degradation_detail: Option<String>,
2452 pub channel_status_commit_authority: Option<meerkat_live::LiveChannelStatusCommitAuthority>,
2453}
2454
2455#[cfg(feature = "live")]
2456impl LiveChannelStatusAuthority {
2457 pub(crate) fn from_generated_effect(
2458 channel_id: String,
2459 status: dsl::LiveChannelPublicStatus,
2460 sequence: u64,
2461 status_observation_sequence: u64,
2462 degradation_reason: Option<dsl::LiveChannelDegradationReason>,
2463 degradation_detail: Option<String>,
2464 ) -> Result<Self, String> {
2465 Ok(Self {
2466 status,
2467 sequence,
2468 status_observation_sequence,
2469 degradation_reason,
2470 degradation_detail,
2471 channel_status_commit_authority: Some(build_live_channel_status_commit_authority(
2472 channel_id,
2473 status_observation_sequence,
2474 )?),
2475 })
2476 }
2477
2478 #[must_use]
2479 pub fn channel_status_commit_authority(
2480 &self,
2481 ) -> Option<&meerkat_live::LiveChannelStatusCommitAuthority> {
2482 self.channel_status_commit_authority.as_ref()
2483 }
2484
2485 #[must_use]
2486 pub fn into_channel_status_commit_authority(
2487 self,
2488 ) -> Option<meerkat_live::LiveChannelStatusCommitAuthority> {
2489 self.channel_status_commit_authority
2490 }
2491}
2492
2493#[doc(hidden)]
2500pub struct MeerkatMachineShared {
2501 sessions: RwLock<HashMap<SessionId, RuntimeSessionEntry>>,
2503 store: Option<Arc<dyn RuntimeStore>>,
2505 blob_store: Option<Arc<dyn BlobStore>>,
2507 llm_reconfigure_host: StdRwLock<Option<Arc<dyn SessionLlmReconfigureHost>>>,
2509 auth_lease: StdRwLock<meerkat_core::handles::GeneratedAuthLeaseHandle>,
2512 #[cfg(not(target_arch = "wasm32"))]
2515 oauth_flows: StdRwLock<Arc<dyn meerkat_auth_core::oauth_flow::OAuthFlowAuthority>>,
2516 #[cfg(feature = "live")]
2520 live_unbound_rejection_authority: crate::driver::ephemeral::SharedIngressDslAuthority,
2521 session_claims: Arc<crate::handles::RuntimeSessionClaimRegistry>,
2528 composition_signal_dispatcher:
2532 StdRwLock<Option<composition::MeerkatCompositionSignalDispatcher>>,
2533}
2534
2535#[derive(Clone)]
2542pub struct MeerkatMachine {
2543 shared: Arc<MeerkatMachineShared>,
2544}
2545
2546impl std::ops::Deref for MeerkatMachine {
2547 type Target = MeerkatMachineShared;
2548
2549 fn deref(&self) -> &Self::Target {
2550 self.shared.as_ref()
2551 }
2552}
2553
2554impl MeerkatMachine {
2555 #[must_use]
2558 pub fn session_control_authority(&self) -> MachineSessionControlAuthority {
2559 MachineSessionControlAuthority { _private: () }
2560 }
2561
2562 #[must_use]
2567 pub fn shares_runtime_persistence_with(&self, other: &Self) -> bool {
2568 match (&self.store, &other.store) {
2569 (None, None) => true,
2570 (Some(a), Some(b)) => runtime_stores_share_authority(a, b),
2571 _ => false,
2572 }
2573 }
2574
2575 #[must_use]
2578 pub fn shares_runtime_store_authority(&self, store: &Arc<dyn RuntimeStore>) -> bool {
2579 self.store
2580 .as_ref()
2581 .is_some_and(|machine_store| runtime_stores_share_authority(machine_store, store))
2582 }
2583
2584 #[must_use]
2586 pub fn has_runtime_persistence(&self) -> bool {
2587 self.store.is_some()
2588 }
2589
2590 fn normalize_destroyed_error(err: RuntimeDriverError) -> RuntimeDriverError {
2591 match err {
2592 RuntimeDriverError::NotReady {
2593 state: RuntimeState::Destroyed,
2594 } => RuntimeDriverError::Destroyed,
2595 other => other,
2596 }
2597 }
2598
2599 pub fn ephemeral() -> Self {
2601 let auth_lease = Arc::new(crate::handles::RuntimeAuthLeaseHandle::new());
2602 #[cfg(not(target_arch = "wasm32"))]
2603 let oauth_flows = Arc::new(crate::handles::RuntimeOAuthFlowHandle::new_with_auth_lease(
2604 std::time::Duration::from_secs(10 * 60),
2605 Arc::clone(&auth_lease),
2606 ));
2607 let auth_lease = generated_runtime_auth_lease_handle(auth_lease);
2608 Self {
2609 shared: Arc::new(MeerkatMachineShared {
2610 sessions: RwLock::new(HashMap::new()),
2611 store: None,
2612 blob_store: None,
2613 llm_reconfigure_host: StdRwLock::new(None),
2614 auth_lease: StdRwLock::new(auth_lease),
2615 #[cfg(not(target_arch = "wasm32"))]
2616 oauth_flows: StdRwLock::new(oauth_flows),
2617 #[cfg(feature = "live")]
2618 live_unbound_rejection_authority: live_unbound_rejection_authority(),
2619 session_claims: Arc::new(crate::handles::RuntimeSessionClaimRegistry::new()),
2620 composition_signal_dispatcher: StdRwLock::new(None),
2621 }),
2622 }
2623 }
2624
2625 pub fn persistent(store: Arc<dyn RuntimeStore>, blob_store: Arc<dyn BlobStore>) -> Self {
2627 #[cfg(not(target_arch = "wasm32"))]
2628 let (auth_lease, oauth_flows) = {
2629 let authorities = persistent_auth_authorities(&store);
2630 (
2631 Arc::clone(&authorities.auth_lease),
2632 Arc::clone(&authorities.oauth_flows),
2633 )
2634 };
2635 #[cfg(target_arch = "wasm32")]
2636 let auth_lease = Arc::new(crate::handles::RuntimeAuthLeaseHandle::new());
2637 let auth_lease = generated_runtime_auth_lease_handle(auth_lease);
2638 Self {
2639 shared: Arc::new(MeerkatMachineShared {
2640 sessions: RwLock::new(HashMap::new()),
2641 store: Some(store),
2642 blob_store: Some(blob_store),
2643 llm_reconfigure_host: StdRwLock::new(None),
2644 auth_lease: StdRwLock::new(auth_lease),
2645 #[cfg(not(target_arch = "wasm32"))]
2646 oauth_flows: StdRwLock::new(oauth_flows),
2647 #[cfg(feature = "live")]
2648 live_unbound_rejection_authority: live_unbound_rejection_authority(),
2649 session_claims: Arc::new(crate::handles::RuntimeSessionClaimRegistry::new()),
2650 composition_signal_dispatcher: StdRwLock::new(None),
2651 }),
2652 }
2653 }
2654
2655 pub fn persistent_without_blobs(store: Arc<dyn RuntimeStore>) -> Self {
2661 #[cfg(not(target_arch = "wasm32"))]
2662 let (auth_lease, oauth_flows) = {
2663 let authorities = persistent_auth_authorities(&store);
2664 (
2665 Arc::clone(&authorities.auth_lease),
2666 Arc::clone(&authorities.oauth_flows),
2667 )
2668 };
2669 #[cfg(target_arch = "wasm32")]
2670 let auth_lease = Arc::new(crate::handles::RuntimeAuthLeaseHandle::new());
2671 let auth_lease = generated_runtime_auth_lease_handle(auth_lease);
2672 Self {
2673 shared: Arc::new(MeerkatMachineShared {
2674 sessions: RwLock::new(HashMap::new()),
2675 store: Some(store),
2676 blob_store: Some(Arc::new(UnavailableBlobStore)),
2677 llm_reconfigure_host: StdRwLock::new(None),
2678 auth_lease: StdRwLock::new(auth_lease),
2679 #[cfg(not(target_arch = "wasm32"))]
2680 oauth_flows: StdRwLock::new(oauth_flows),
2681 #[cfg(feature = "live")]
2682 live_unbound_rejection_authority: live_unbound_rejection_authority(),
2683 session_claims: Arc::new(crate::handles::RuntimeSessionClaimRegistry::new()),
2684 composition_signal_dispatcher: StdRwLock::new(None),
2685 }),
2686 }
2687 }
2688
2689 pub fn auth_lease_handle(&self) -> Arc<dyn meerkat_core::handles::AuthLeaseHandle> {
2692 self.generated_auth_lease_handle().clone_handle()
2693 }
2694
2695 pub fn generated_auth_lease_handle(&self) -> meerkat_core::handles::GeneratedAuthLeaseHandle {
2698 self.auth_lease
2699 .read()
2700 .unwrap_or_else(std::sync::PoisonError::into_inner)
2701 .clone()
2702 }
2703
2704 pub fn set_auth_lease_handle(&self, handle: Arc<crate::handles::RuntimeAuthLeaseHandle>) {
2710 self.set_runtime_auth_lease_handle(handle);
2711 }
2712
2713 #[cfg(not(target_arch = "wasm32"))]
2719 pub fn set_auth_lease_handle_with_oauth_flow_authority(
2720 &self,
2721 handle: Arc<crate::handles::RuntimeAuthLeaseHandle>,
2722 oauth_flows: Arc<dyn meerkat_auth_core::oauth_flow::OAuthFlowAuthority>,
2723 ) {
2724 *self
2725 .oauth_flows
2726 .write()
2727 .unwrap_or_else(std::sync::PoisonError::into_inner) = oauth_flows;
2728 let handle = generated_runtime_auth_lease_handle(handle);
2729 *self
2730 .auth_lease
2731 .write()
2732 .unwrap_or_else(std::sync::PoisonError::into_inner) = handle;
2733 }
2734
2735 pub fn set_runtime_auth_lease_handle(
2738 &self,
2739 handle: Arc<crate::handles::RuntimeAuthLeaseHandle>,
2740 ) {
2741 #[cfg(not(target_arch = "wasm32"))]
2742 {
2743 *self
2744 .oauth_flows
2745 .write()
2746 .unwrap_or_else(std::sync::PoisonError::into_inner) =
2747 Arc::new(crate::handles::RuntimeOAuthFlowHandle::new_with_auth_lease(
2748 std::time::Duration::from_secs(10 * 60),
2749 Arc::clone(&handle),
2750 ));
2751 }
2752 let handle = generated_runtime_auth_lease_handle(handle);
2753 *self
2754 .auth_lease
2755 .write()
2756 .unwrap_or_else(std::sync::PoisonError::into_inner) = handle;
2757 }
2758
2759 #[cfg(not(target_arch = "wasm32"))]
2762 pub fn oauth_flow_authority(
2763 &self,
2764 ) -> Arc<dyn meerkat_auth_core::oauth_flow::OAuthFlowAuthority> {
2765 Arc::clone(
2766 &self
2767 .oauth_flows
2768 .read()
2769 .unwrap_or_else(std::sync::PoisonError::into_inner),
2770 )
2771 }
2772
2773 pub fn session_claim_handle(&self) -> Arc<dyn meerkat_core::handles::SessionClaimHandle> {
2778 Arc::clone(&self.session_claims) as Arc<dyn meerkat_core::handles::SessionClaimHandle>
2779 }
2780
2781 pub fn set_composition_signal_dispatcher(
2784 &self,
2785 dispatcher: composition::MeerkatCompositionSignalDispatcher,
2786 ) {
2787 let mut slot = self
2788 .composition_signal_dispatcher
2789 .write()
2790 .unwrap_or_else(std::sync::PoisonError::into_inner);
2791 *slot = Some(dispatcher);
2792 }
2793
2794 pub(crate) async fn apply_routed_meerkat_input(
2805 &self,
2806 session_id: &SessionId,
2807 input: dsl::MeerkatMachineInput,
2808 ) -> Result<(), dsl_authority::DslTransitionRefusal> {
2809 let _gate_guard = self
2810 .lock_current_session_mutation_gate(session_id)
2811 .await
2812 .ok_or_else(|| {
2813 dsl_authority::DslTransitionRefusal::other(
2814 "routed_session_not_registered",
2815 format!(
2816 "session `{session_id}` is not registered with this MeerkatMachine; \
2817 cannot deliver routed input"
2818 ),
2819 )
2820 })?;
2821 self.apply_routed_session_dsl_input(session_id, input, "RoutedMeerkatInput")
2822 .await
2823 .map(|_| ())
2824 }
2825
2826 #[cfg(test)]
2827 pub(crate) async fn debug_shared_ingress_authorities(
2828 &self,
2829 session_id: &SessionId,
2830 ) -> Option<(
2831 Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
2832 crate::driver::ephemeral::SharedIngressDslAuthority,
2833 )> {
2834 let sessions = self.sessions.read().await;
2835 let entry = sessions.get(session_id)?;
2836 let session_authority = Arc::clone(&entry.dsl_authority);
2837 let driver = entry.driver.lock().await;
2838 Some((session_authority, driver.shared_dsl_authority()))
2839 }
2840
2841 fn make_driver(
2843 &self,
2844 runtime_id: LogicalRuntimeId,
2845 dsl_authority: crate::driver::ephemeral::SharedIngressDslAuthority,
2846 initial_runtime_state: RuntimeState,
2847 ) -> DriverEntry {
2848 let control_projection = Arc::new(StdRwLock::new(
2849 crate::driver::ephemeral::RuntimeControlProjection {
2850 phase: initial_runtime_state,
2851 current_run_id: None,
2852 pre_run_phase: None,
2853 },
2854 ));
2855 match (&self.store, &self.blob_store) {
2856 (Some(store), Some(blob_store)) => {
2857 DriverEntry::Persistent(PersistentRuntimeDriver::new_with_control(
2858 runtime_id,
2859 store.clone(),
2860 blob_store.clone(),
2861 control_projection,
2862 dsl_authority,
2863 ))
2864 }
2865 _ => DriverEntry::Ephemeral(EphemeralRuntimeDriver::new_with_control_and_dsl(
2866 runtime_id,
2867 control_projection,
2868 dsl_authority,
2869 )),
2870 }
2871 }
2872
2873 async fn recover_or_create_ops_state(
2880 &self,
2881 session_id: &SessionId,
2882 runtime_id: &LogicalRuntimeId,
2883 ) -> Result<
2884 (
2885 Arc<crate::ops_lifecycle::RuntimeOpsLifecycleRegistry>,
2886 meerkat_core::RuntimeEpochId,
2887 Arc<meerkat_core::EpochCursorState>,
2888 ),
2889 RuntimeDriverError,
2890 > {
2891 if let Some(ref store) = self.store {
2892 match store.load_ops_lifecycle(runtime_id).await {
2893 Ok(Some(snapshot)) => {
2894 let recovered_epoch = snapshot.epoch_id.clone();
2895 let recovered_ops_count = snapshot.completion_entries.len();
2896 let registry =
2897 match crate::ops_lifecycle::RuntimeOpsLifecycleRegistry::from_recovered(
2898 snapshot,
2899 ) {
2900 Ok(registry) => registry,
2901 Err(err) => {
2902 tracing::error!(
2903 %session_id,
2904 %runtime_id,
2905 error = %err,
2906 "failed to recover ops lifecycle through generated authority"
2907 );
2908 return Err(RuntimeDriverError::Internal(format!(
2909 "failed to recover ops lifecycle through generated authority: {err}"
2910 )));
2911 }
2912 };
2913 let recovered_cursor_snapshot = registry.completion_cursor_snapshot();
2914 let recovered_cursors = meerkat_core::EpochCursorState::from_recovered(
2915 recovered_cursor_snapshot.agent_applied_cursor,
2916 recovered_cursor_snapshot.runtime_observed_seq,
2917 recovered_cursor_snapshot.runtime_last_injected_seq,
2918 );
2919 tracing::info!(
2920 %session_id,
2921 %runtime_id,
2922 epoch_id = %recovered_epoch,
2923 recovered_ops = recovered_ops_count,
2924 "ops lifecycle recovered from durable store (same epoch)"
2925 );
2926 return Ok((
2927 Arc::new(registry),
2928 recovered_epoch,
2929 Arc::new(recovered_cursors),
2930 ));
2931 }
2932 Ok(None) => {}
2933 Err(err) => {
2934 tracing::error!(
2935 %session_id,
2936 %runtime_id,
2937 error = %err,
2938 "failed to load ops lifecycle from durable store"
2939 );
2940 return Err(RuntimeDriverError::Internal(format!(
2941 "failed to load ops lifecycle from durable store: {err}"
2942 )));
2943 }
2944 }
2945 tracing::debug!(%session_id, "no persisted ops lifecycle; fresh epoch");
2946 Ok((
2947 Arc::new(crate::ops_lifecycle::RuntimeOpsLifecycleRegistry::new()),
2948 meerkat_core::RuntimeEpochId::new(),
2949 Arc::new(meerkat_core::EpochCursorState::new()),
2950 ))
2951 } else {
2952 Ok((
2953 Arc::new(crate::ops_lifecycle::RuntimeOpsLifecycleRegistry::new()),
2954 meerkat_core::RuntimeEpochId::new(),
2955 Arc::new(meerkat_core::EpochCursorState::new()),
2956 ))
2957 }
2958 }
2959
2960 fn fresh_ops_state() -> (
2961 Arc<crate::ops_lifecycle::RuntimeOpsLifecycleRegistry>,
2962 meerkat_core::RuntimeEpochId,
2963 Arc<meerkat_core::EpochCursorState>,
2964 ) {
2965 let registry = Arc::new(crate::ops_lifecycle::RuntimeOpsLifecycleRegistry::new());
2966 let epoch = meerkat_core::RuntimeEpochId::new();
2967 let cursors = Arc::new(meerkat_core::EpochCursorState::new());
2968 (registry, epoch, cursors)
2969 }
2970
2971 #[allow(clippy::large_futures)]
2972 fn execute_meerkat_machine_command(
2973 &self,
2974 self_handle: Option<Arc<Self>>,
2975 command: MeerkatMachineCommand,
2976 ) -> MeerkatMachineCommandFuture<'_> {
2977 Box::pin(async move {
2978 match command {
2979 MeerkatMachineCommand::EnsureSessionWithExecutor { .. } => {
2980 let self_handle = self_handle.ok_or_else(|| {
2981 MeerkatMachineCommandError::Driver(RuntimeDriverError::Internal(
2982 "EnsureSessionWithExecutor requires Arc<Self> machine handle".into(),
2983 ))
2984 })?;
2985 self_handle
2986 .execute_meerkat_machine_ensure_session_command(command)
2987 .await
2988 .map_err(Into::into)
2989 }
2990 MeerkatMachineCommand::RegisterSession { .. }
2991 | MeerkatMachineCommand::UnregisterSession { .. }
2992 | MeerkatMachineCommand::SetSilentIntents { .. }
2993 | MeerkatMachineCommand::CancelAfterBoundary { .. }
2994 | MeerkatMachineCommand::StopRuntimeExecutor { .. }
2995 | MeerkatMachineCommand::CommitServiceTurnTerminalReceipt { .. }
2996 | MeerkatMachineCommand::ContainsSession { .. }
2997 | MeerkatMachineCommand::SessionHasExecutor { .. }
2998 | MeerkatMachineCommand::SessionHasComms { .. }
2999 | MeerkatMachineCommand::OpsLifecycleRegistry { .. }
3000 | MeerkatMachineCommand::PrepareBindings { .. }
3001 | MeerkatMachineCommand::PrepareLocalSessionBindings { .. }
3002 | MeerkatMachineCommand::InputState { .. }
3003 | MeerkatMachineCommand::InputStateByIdempotencyKey { .. }
3004 | MeerkatMachineCommand::InteractionTerminalStatus { .. }
3005 | MeerkatMachineCommand::RunTerminalStatus { .. }
3006 | MeerkatMachineCommand::ListActiveInputs { .. }
3007 | MeerkatMachineCommand::ReconfigureSessionLlmIdentity { .. }
3008 | MeerkatMachineCommand::StagePersistentFilter { .. }
3009 | MeerkatMachineCommand::RequestDeferredTools { .. }
3010 | MeerkatMachineCommand::PublishCommittedVisibleSet { .. } => self
3011 .execute_meerkat_machine_session_command(command)
3012 .await
3013 .map_err(Into::into),
3014 MeerkatMachineCommand::SetPeerIngressContext { .. }
3015 | MeerkatMachineCommand::NotifyDrainExited { .. } => {
3016 let self_handle = self_handle.ok_or_else(|| {
3017 MeerkatMachineCommandError::Driver(RuntimeDriverError::Internal(
3018 "drain command requires Arc<Self> machine handle".into(),
3019 ))
3020 })?;
3021 self_handle
3022 .execute_meerkat_machine_drain_command(command)
3023 .await
3024 .map_err(Into::into)
3025 }
3026 MeerkatMachineCommand::AbortAll
3027 | MeerkatMachineCommand::Abort { .. }
3028 | MeerkatMachineCommand::Wait { .. } => self
3029 .execute_meerkat_machine_drain_local_command(command)
3030 .await
3031 .map_err(Into::into),
3032 MeerkatMachineCommand::Ingest { .. }
3033 | MeerkatMachineCommand::PublishEvent { .. }
3034 | MeerkatMachineCommand::Retire { .. }
3035 | MeerkatMachineCommand::Recycle { .. }
3036 | MeerkatMachineCommand::Reset { .. }
3037 | MeerkatMachineCommand::Recover { .. }
3038 | MeerkatMachineCommand::Destroy { .. }
3039 | MeerkatMachineCommand::RuntimeState { .. }
3040 | MeerkatMachineCommand::ResolvedSessionLlmCapabilities { .. }
3041 | MeerkatMachineCommand::ConfigureModelRoutingBaseline { .. }
3042 | MeerkatMachineCommand::SessionModelRoutingStatus { .. }
3043 | MeerkatMachineCommand::RequestSwitchTurn { .. }
3044 | MeerkatMachineCommand::AdmitModelRoutingAssistantTurn { .. }
3045 | MeerkatMachineCommand::BeginImageOperation { .. }
3046 | MeerkatMachineCommand::DenyImageOperationPlan { .. }
3047 | MeerkatMachineCommand::ActivateImageOperationOverride { .. }
3048 | MeerkatMachineCommand::ClassifyImageOperationTerminal { .. }
3049 | MeerkatMachineCommand::CompleteImageOperation { .. }
3050 | MeerkatMachineCommand::RestoreImageOperationOverride { .. }
3051 | MeerkatMachineCommand::LoadBoundaryReceipt { .. } => self
3052 .execute_meerkat_machine_control_command(command)
3053 .await
3054 .map_err(Into::into),
3055 MeerkatMachineCommand::AcceptWithCompletion { .. }
3056 | MeerkatMachineCommand::AcceptWithoutWake { .. } => self
3057 .execute_meerkat_machine_ingress_command(command)
3058 .await
3059 .map_err(Into::into),
3060 }
3061 })
3062 }
3063
3064 pub async fn register_session(
3071 &self,
3072 session_id: SessionId,
3073 ) -> Result<(), RuntimeControlPlaneError> {
3074 match self
3075 .execute_meerkat_machine_command(
3076 None,
3077 MeerkatMachineCommand::RegisterSession { session_id },
3078 )
3079 .await
3080 .map_err(MeerkatMachine::control_plane_error_from_command_error)?
3081 {
3082 MeerkatMachineCommandResult::Unit => Ok(()),
3083 other => Err(RuntimeControlPlaneError::Internal(format!(
3084 "register_session: unexpected command result variant: {other:?}"
3085 ))),
3086 }
3087 }
3088}
3089
3090#[cfg(test)]
3091#[allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
3092#[path = "../meerkat_machine_tests.rs"]
3093mod tests;