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::time_compat::Instant;
29use meerkat_core::tool_scope::ToolScopeTurnOverlay;
30use meerkat_core::types::SessionId;
31use meerkat_core::{BlobId, BlobPayload, BlobRef, BlobStore, BlobStoreError};
32use meerkat_core::{
33 DeferredToolLoadAuthority, SessionToolVisibilityState, ToolFilter, ToolScopeApplyError,
34 ToolScopeRevision, ToolScopeStageError, ToolVisibilityOwner, ToolVisibilityWitness,
35};
36
37use crate::accept::AcceptOutcome;
38use crate::driver::ephemeral::EphemeralRuntimeDriver;
39use crate::driver::persistent::PersistentRuntimeDriver;
40use crate::identifiers::LogicalRuntimeId;
41use crate::input::Input;
42use crate::input_state::{
43 InputAbandonReason, InputLifecycleState, InputStateSeed, InputTerminalOutcome,
44};
45use crate::meerkat_machine_types::{
46 HydratedSessionLlmState, MeerkatAdmittedInputSnapshot, MeerkatArchiveSnapshot,
47 MeerkatBindingSnapshot, MeerkatCompletionWaiterSnapshot, MeerkatCompletionWaitersSnapshot,
48 MeerkatControlSnapshot, MeerkatCursorSnapshot, MeerkatDrainSnapshot, MeerkatDriverKind,
49 MeerkatFormalStateProjection, MeerkatInputsSnapshot, MeerkatLedgerSnapshot,
50 MeerkatMachineCommand, MeerkatMachineCommandError, MeerkatMachineCommandResult,
51 MeerkatMachineRunFailure, MeerkatMachineSpineSnapshot, MeerkatOpsSnapshot,
52 MemberResidencyExpectation, SessionLlmCapabilityDelta, SessionLlmCapabilitySurface,
53 SessionLlmReconfigureHost, SessionLlmReconfigureReport, SessionLlmReconfigureRequest,
54 SessionToolVisibilityDelta,
55};
56use crate::runtime_state::RuntimeState;
57use crate::service_ext::SessionServiceRuntimeExt;
58use crate::store::RuntimeStore;
59use crate::tokio;
60use crate::tokio::sync::{Mutex, RwLock, mpsc};
61#[cfg(test)]
62use crate::traits::RuntimeDriver;
63use crate::traits::{
64 DestroyReport, RecoveryReport, RecycleReport, ResetReport, RetireReport,
65 RuntimeControlPlaneError, RuntimeDriverError,
66};
67
68#[allow(clippy::expect_used)]
69pub(crate) fn recover_projected_authority(
70 state: dsl::MeerkatMachineState,
71 context: &'static str,
72) -> dsl::MeerkatMachineAuthority {
73 dsl::MeerkatMachineAuthority::recover_from_state(state).expect(context)
74}
75
76struct ToolVisibilityOwnerGeneratedAuthorityBridgeToken;
77
78static TOOL_VISIBILITY_OWNER_GENERATED_AUTHORITY_BRIDGE_TOKEN:
79 ToolVisibilityOwnerGeneratedAuthorityBridgeToken =
80 ToolVisibilityOwnerGeneratedAuthorityBridgeToken;
81
82fn tool_visibility_owner_generated_authority_bridge_token()
83-> &'static (dyn std::any::Any + Send + Sync) {
84 &TOOL_VISIBILITY_OWNER_GENERATED_AUTHORITY_BRIDGE_TOKEN
85}
86
87#[doc(hidden)]
88#[allow(improper_ctypes_definitions, unsafe_code)]
89#[unsafe(export_name = concat!(
90 "__meerkat_runtime_generated_authority_bridge_token_is_valid_v1_tool_visibility_owner_",
91 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
92))]
93pub extern "Rust" fn tool_visibility_owner_generated_authority_bridge_token_is_valid(
94 token: &(dyn std::any::Any + Send + Sync),
95) -> bool {
96 token.is::<ToolVisibilityOwnerGeneratedAuthorityBridgeToken>()
97}
98
99fn generated_tool_visibility_owner(
100 owner: Arc<dyn ToolVisibilityOwner>,
101) -> Result<meerkat_core::GeneratedToolVisibilityOwner, String> {
102 #[allow(improper_ctypes_definitions, unsafe_code)]
103 unsafe extern "Rust" {
104 #[link_name = concat!(
105 "__meerkat_core_runtime_generated_tool_visibility_owner_build_v1_",
106 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
107 )]
108 fn core_runtime_generated_tool_visibility_owner_build(
109 token: &'static (dyn std::any::Any + Send + Sync),
110 owner: Arc<dyn ToolVisibilityOwner>,
111 ) -> Result<meerkat_core::GeneratedToolVisibilityOwner, String>;
112 }
113 #[allow(unsafe_code)]
114 unsafe {
115 core_runtime_generated_tool_visibility_owner_build(
116 tool_visibility_owner_generated_authority_bridge_token(),
117 owner,
118 )
119 }
120}
121
122#[derive(Clone)]
130pub struct StandaloneSessionRuntimeAuthorities {
131 tool_visibility_owner: meerkat_core::GeneratedToolVisibilityOwner,
132 turn_state: Arc<dyn meerkat_core::TurnStateHandle>,
133 model_routing: Arc<dyn meerkat_core::handles::ModelRoutingHandle>,
134 #[cfg(test)]
135 model_routing_test: Arc<crate::handles::RuntimeModelRoutingHandle>,
136}
137
138impl StandaloneSessionRuntimeAuthorities {
139 pub fn tool_visibility_owner(&self) -> &meerkat_core::GeneratedToolVisibilityOwner {
140 &self.tool_visibility_owner
141 }
142
143 pub fn turn_state(&self) -> &Arc<dyn meerkat_core::TurnStateHandle> {
144 &self.turn_state
145 }
146
147 pub fn model_routing(&self) -> &Arc<dyn meerkat_core::handles::ModelRoutingHandle> {
148 &self.model_routing
149 }
150
151 #[cfg(test)]
152 pub(crate) fn commit_sticky_model_fallback_for_test(
153 &self,
154 previous_identity: &meerkat_core::SessionLlmIdentity,
155 target_identity: &meerkat_core::SessionLlmIdentity,
156 target_profile: &meerkat_core::ModelProfileWitness,
157 visibility_plan: &meerkat_core::handles::StickyModelFallbackVisibilityPlan,
158 retry_attempt: u32,
159 ) -> Result<(), meerkat_core::handles::DslTransitionError> {
160 self.model_routing_test
161 .commit_sticky_model_fallback_for_test(
162 previous_identity,
163 target_identity,
164 target_profile,
165 visibility_plan,
166 retry_attempt,
167 )
168 }
169}
170
171pub fn standalone_session_runtime_authorities(
177 session_id: &SessionId,
178 current_identity: &meerkat_core::SessionLlmIdentity,
179 model_profile: Option<&meerkat_core::model_profile::ModelProfile>,
180 capability_base_filter: &ToolFilter,
181) -> Result<StandaloneSessionRuntimeAuthorities, String> {
182 let mut authority = dsl_authority::new_registered_authority(session_id)
183 .map_err(|err| dsl_authority::map_error(err, "standalone visibility authority"))?;
184 let (current_capability_surface, current_capability_surface_status) = match model_profile {
185 Some(profile) => (
186 Some(dsl::SessionLlmCapabilitySurface {
187 supports_temperature: profile.supports_temperature,
188 supports_thinking: profile.supports_thinking,
189 supports_reasoning: profile.supports_reasoning,
190 inline_video: profile.inline_video,
191 vision: profile.vision,
192 image_input: profile.image_input,
193 image_tool_results: profile.image_tool_results,
194 supports_web_search: profile.supports_web_search,
195 image_generation: profile.image_generation,
196 realtime: profile.realtime,
197 call_timeout_secs: profile.call_timeout_secs,
198 }),
199 dsl::SessionLlmCapabilitySurfaceStatus::Resolved,
200 ),
201 None => (None, dsl::SessionLlmCapabilitySurfaceStatus::Unresolved),
202 };
203 dsl::MeerkatMachineMutator::apply(
204 &mut authority,
205 dsl::MeerkatMachineInput::HydrateSessionLlmState {
206 current_identity: dsl::SessionLlmIdentity::from_domain(current_identity),
207 current_capability_surface,
208 current_capability_surface_status,
209 current_capability_base_filter: dsl::ToolFilter::from_domain(capability_base_filter),
210 },
211 )
212 .map_err(|err| dsl_authority::map_error(err, "standalone visibility hydration"))?;
213 dsl::MeerkatMachineMutator::apply(
214 &mut authority,
215 dsl::MeerkatMachineInput::SetModelRoutingBaseline {
216 baseline_model: current_identity.model.clone(),
217 realtime_capable: model_profile.is_some_and(|profile| profile.realtime),
218 },
219 )
220 .map_err(|err| dsl_authority::map_error(err, "standalone model routing baseline"))?;
221 let authority = Arc::new(std::sync::Mutex::new(authority));
222 let owner = Arc::new(MachineToolVisibilityOwner::new());
223 owner.bind_dsl_authority(Arc::clone(&authority));
224 let shared_handle_authority = Arc::new(crate::handles::HandleDslAuthority::from_shared(
225 Arc::clone(&authority),
226 ));
227 let tool_visibility_owner =
228 generated_tool_visibility_owner(Arc::clone(&owner) as Arc<dyn ToolVisibilityOwner>)?;
229 let turn_state = Arc::new(crate::handles::RuntimeTurnStateHandle::standalone(
230 Arc::clone(&shared_handle_authority),
231 session_id.clone(),
232 )) as Arc<dyn meerkat_core::TurnStateHandle>;
233 let runtime_model_routing = Arc::new(
234 crate::handles::RuntimeModelRoutingHandle::new_with_visibility_owner(
235 shared_handle_authority,
236 owner,
237 ),
238 );
239 let model_routing =
240 Arc::clone(&runtime_model_routing) as Arc<dyn meerkat_core::handles::ModelRoutingHandle>;
241 Ok(StandaloneSessionRuntimeAuthorities {
242 tool_visibility_owner,
243 turn_state,
244 model_routing,
245 #[cfg(test)]
246 model_routing_test: runtime_model_routing,
247 })
248}
249
250pub fn standalone_tool_visibility_owner(
254 session_id: &SessionId,
255 current_identity: &meerkat_core::SessionLlmIdentity,
256 model_profile: Option<&meerkat_core::model_profile::ModelProfile>,
257 capability_base_filter: &ToolFilter,
258) -> Result<meerkat_core::GeneratedToolVisibilityOwner, String> {
259 standalone_session_runtime_authorities(
260 session_id,
261 current_identity,
262 model_profile,
263 capability_base_filter,
264 )
265 .map(|authorities| authorities.tool_visibility_owner)
266}
267
268#[derive(Debug, thiserror::Error)]
270pub enum RuntimeBindingsError {
271 #[error("session {0} not found in runtime adapter after registration")]
273 SessionNotFound(SessionId),
274 #[error("failed to prepare runtime bindings for session {0}: {1}")]
276 PrepareFailed(SessionId, String),
277}
278
279#[derive(Debug, Clone, Copy, PartialEq, Eq)]
281pub struct InputPublicStateProjection {
282 pub lifecycle_state: dsl::InputPublicLifecycleState,
283 pub terminal_outcome: Option<dsl::InputPublicTerminalOutcome>,
284}
285
286#[derive(Debug, Clone, Copy, PartialEq, Eq)]
289pub struct RuntimeLifecycleFacts {
290 pub terminality: dsl::RuntimeLifecycleTerminality,
291 pub input_admission: dsl::RuntimeInputAdmission,
292 pub queue_admission: dsl::RuntimeQueueAdmission,
293 pub prepare_admission: dsl::RuntimePrepareAdmission,
294 pub ingress_admission: dsl::RuntimeIngressAdmission,
295}
296
297impl RuntimeLifecycleFacts {
298 #[must_use]
299 pub fn can_accept_input(self) -> bool {
300 self.input_admission == dsl::RuntimeInputAdmission::AcceptsInput
301 }
302
303 #[must_use]
304 pub fn can_process_queue(self) -> bool {
305 self.queue_admission == dsl::RuntimeQueueAdmission::ProcessesQueue
306 }
307
308 #[must_use]
309 pub fn can_prepare_run(self) -> bool {
310 self.prepare_admission == dsl::RuntimePrepareAdmission::Ready
311 }
312
313 #[must_use]
314 pub fn is_terminal(self) -> bool {
315 self.terminality == dsl::RuntimeLifecycleTerminality::Terminal
316 }
317}
318
319#[derive(Debug, Clone, Copy, PartialEq, Eq)]
322pub struct RuntimeLoopQueueAdmissionPlan {
323 pub queue_admission: dsl::RuntimeQueueAdmission,
324 pub run_binding: dsl::RuntimeLoopRunBinding,
325}
326
327impl RuntimeLoopQueueAdmissionPlan {
328 #[must_use]
329 pub fn can_process_queue(self) -> bool {
330 self.queue_admission == dsl::RuntimeQueueAdmission::ProcessesQueue
331 }
332
333 #[must_use]
334 pub fn uses_prebound_run(self) -> bool {
335 self.run_binding == dsl::RuntimeLoopRunBinding::UsePrebound
336 }
337}
338
339pub fn classify_runtime_lifecycle_state(
343 state: RuntimeState,
344) -> Result<RuntimeLifecycleFacts, String> {
345 let observed_state = dsl_authority::observed_runtime_lifecycle_state(state);
346 let mut authority = projection_authority();
347 let transition = dsl::MeerkatMachineMutator::apply(
348 &mut authority,
349 dsl::MeerkatMachineInput::ClassifyRuntimeLifecycleState {
350 state: observed_state,
351 },
352 )
353 .map_err(|err| {
354 format!("MeerkatMachine rejected runtime lifecycle classification for {state}: {err}")
355 })?;
356
357 transition
358 .into_effects()
359 .into_iter()
360 .find_map(|effect| match effect {
361 dsl::MeerkatMachineEffect::RuntimeLifecycleStateClassified {
362 state,
363 terminality,
364 input_admission,
365 queue_admission,
366 prepare_admission,
367 ingress_admission,
368 } if state == observed_state => Some(RuntimeLifecycleFacts {
369 terminality,
370 input_admission,
371 queue_admission,
372 prepare_admission,
373 ingress_admission,
374 }),
375 _ => None,
376 })
377 .ok_or_else(|| {
378 format!("MeerkatMachine emitted no runtime lifecycle classification for {state}")
379 })
380}
381
382pub fn classify_runtime_lifecycle_durable_state(
386 state: RuntimeState,
387) -> Result<RuntimeState, String> {
388 classify_runtime_lifecycle_durable_state_with_pre_run_phase(state, None)
389}
390
391pub(crate) fn classify_runtime_lifecycle_durable_state_with_pre_run_phase(
395 state: RuntimeState,
396 pre_run_phase: Option<RuntimeState>,
397) -> Result<RuntimeState, String> {
398 let observed_state = dsl_authority::observed_runtime_lifecycle_state(state);
399 let pre_run_phase = pre_run_phase.and_then(dsl_authority::pre_run_phase_from_runtime_state);
400 let mut authority = projection_authority();
401 let transition = dsl::MeerkatMachineMutator::apply(
402 &mut authority,
403 dsl::MeerkatMachineInput::ClassifyRuntimeLifecycleDurability {
404 state: observed_state,
405 pre_run_phase,
406 },
407 )
408 .map_err(|err| {
409 format!(
410 "MeerkatMachine rejected runtime lifecycle durability classification for {state}: {err}"
411 )
412 })?;
413
414 transition
415 .into_effects()
416 .into_iter()
417 .find_map(|effect| match effect {
418 dsl::MeerkatMachineEffect::RuntimeLifecycleDurabilityClassified {
419 state,
420 durable_state,
421 } if state == observed_state => Some(
422 dsl_authority::runtime_state_from_observed_lifecycle_state(durable_state),
423 ),
424 _ => None,
425 })
426 .ok_or_else(|| {
427 format!(
428 "MeerkatMachine emitted no runtime lifecycle durability classification for {state}"
429 )
430 })
431}
432
433pub fn classify_runtime_loop_queue_admission(
438 state: RuntimeState,
439 current_run_bound: bool,
440) -> Result<RuntimeLoopQueueAdmissionPlan, String> {
441 let observed_state = dsl_authority::observed_runtime_lifecycle_state(state);
442 let mut authority = projection_authority();
443 let transition = dsl::MeerkatMachineMutator::apply(
444 &mut authority,
445 dsl::MeerkatMachineInput::ClassifyRuntimeLoopQueueAdmission {
446 state: observed_state,
447 current_run_bound,
448 },
449 )
450 .map_err(|err| {
451 format!(
452 "MeerkatMachine rejected runtime-loop queue admission for {state} with current_run_bound={current_run_bound}: {err}"
453 )
454 })?;
455
456 transition
457 .into_effects()
458 .into_iter()
459 .find_map(|effect| match effect {
460 dsl::MeerkatMachineEffect::RuntimeLoopQueueAdmissionClassified {
461 state,
462 current_run_bound: observed_current_run_bound,
463 queue_admission,
464 run_binding,
465 } if state == observed_state && observed_current_run_bound == current_run_bound => {
466 Some(RuntimeLoopQueueAdmissionPlan {
467 queue_admission,
468 run_binding,
469 })
470 }
471 _ => None,
472 })
473 .ok_or_else(|| {
474 format!(
475 "MeerkatMachine emitted no runtime-loop queue admission for {state} with current_run_bound={current_run_bound}"
476 )
477 })
478}
479
480#[derive(Debug, Clone, Copy, PartialEq, Eq)]
488pub struct VisibleRuntimePhasePlan {
489 pub publish_control: bool,
490 pub selected_raw_phase: RuntimeState,
491 pub visible_phase: RuntimeState,
492}
493
494pub fn resolve_visible_runtime_phase(
502 dsl_phase: RuntimeState,
503 dsl_pre_run_phase: Option<RuntimeState>,
504 control_phase: RuntimeState,
505 control_pre_run_phase: Option<RuntimeState>,
506 has_runtime_persistence: bool,
507) -> Result<VisibleRuntimePhasePlan, String> {
508 let observed_dsl = dsl_authority::observed_runtime_lifecycle_state(dsl_phase);
509 let observed_control = dsl_authority::observed_runtime_lifecycle_state(control_phase);
510 let observed_dsl_pre_run =
511 dsl_pre_run_phase.map(dsl_authority::observed_runtime_lifecycle_state);
512 let observed_control_pre_run =
513 control_pre_run_phase.map(dsl_authority::observed_runtime_lifecycle_state);
514 let mut authority = projection_authority();
515 let transition = dsl::MeerkatMachineMutator::apply(
516 &mut authority,
517 dsl::MeerkatMachineInput::ResolveVisibleRuntimePhase {
518 dsl_phase: observed_dsl,
519 dsl_pre_run_phase: observed_dsl_pre_run,
520 control_phase: observed_control,
521 control_pre_run_phase: observed_control_pre_run,
522 has_runtime_persistence,
523 },
524 )
525 .map_err(|err| {
526 format!(
527 "MeerkatMachine rejected visible runtime phase resolution \
528 (dsl={dsl_phase}, control={control_phase}, persistence={has_runtime_persistence}): {err}"
529 )
530 })?;
531
532 transition
533 .into_effects()
534 .into_iter()
535 .find_map(|effect| match effect {
536 dsl::MeerkatMachineEffect::VisibleRuntimePhaseResolved {
537 publish_control,
538 selected_raw_phase,
539 visible_phase,
540 } => Some(VisibleRuntimePhasePlan {
541 publish_control,
542 selected_raw_phase: dsl_authority::runtime_state_from_observed_lifecycle_state(
543 selected_raw_phase,
544 ),
545 visible_phase: dsl_authority::runtime_state_from_observed_lifecycle_state(
546 visible_phase,
547 ),
548 }),
549 _ => None,
550 })
551 .ok_or_else(|| {
552 format!(
553 "MeerkatMachine emitted no visible runtime phase resolution \
554 (dsl={dsl_phase}, control={control_phase}, persistence={has_runtime_persistence})"
555 )
556 })
557}
558
559pub fn resolve_input_public_lifecycle_projection(
562 input_id: &InputId,
563 phase: InputLifecycleState,
564) -> Result<dsl::InputPublicLifecycleState, String> {
565 let input_key = input_id.to_string();
566 let mut authority = projection_authority();
567 let transition = dsl::MeerkatMachineMutator::apply(
568 &mut authority,
569 dsl::MeerkatMachineInput::ResolveInputPublicLifecycle {
570 input_id: input_key.clone(),
571 phase: observed_input_phase(phase),
572 },
573 )
574 .map_err(|err| {
575 format!("MeerkatMachine rejected public lifecycle projection for '{input_id}': {err}")
576 })?;
577
578 transition
579 .into_effects()
580 .into_iter()
581 .find_map(|effect| match effect {
582 dsl::MeerkatMachineEffect::InputPublicLifecycleResolved { input_id, phase }
583 if input_id == input_key =>
584 {
585 Some(phase)
586 }
587 _ => None,
588 })
589 .ok_or_else(|| {
590 format!("MeerkatMachine emitted no public lifecycle projection for '{input_id}'")
591 })
592}
593
594pub fn resolve_input_public_state_projection(
597 input_id: &InputId,
598 seed: &InputStateSeed,
599) -> Result<InputPublicStateProjection, String> {
600 let lifecycle_state = resolve_input_public_lifecycle_projection(input_id, seed.phase)?;
601 let terminal_outcome = resolve_input_public_terminal_projection(input_id, seed)?;
602 Ok(InputPublicStateProjection {
603 lifecycle_state,
604 terminal_outcome,
605 })
606}
607
608pub(crate) fn input_seed_behavioral_terminality_via_authority(
609 input_id: &InputId,
610 seed: &InputStateSeed,
611) -> Result<bool, String> {
612 classify_input_behavioral_terminality(input_id, seed.phase, seed.terminal_outcome.as_ref())
613}
614
615pub(crate) fn input_phase_behavioral_terminality_via_authority(
616 input_id: &InputId,
617 phase: InputLifecycleState,
618 terminal_outcome: Option<InputTerminalOutcome>,
619) -> Result<bool, String> {
620 classify_input_behavioral_terminality(input_id, phase, terminal_outcome.as_ref())
621}
622
623pub(crate) fn authorize_stored_input_state_seed(
626 input_id: &InputId,
627 seed: &InputStateSeed,
628) -> Result<(), String> {
629 let input_key = input_id.to_string();
630 let (terminal_kind, superseded_by, aggregate_id, abandon_reason, abandon_attempt_count) =
631 input_seed_terminal_parts(seed)?;
632 let mut authority = projection_authority();
633 let transition = dsl::MeerkatMachineMutator::apply(
634 &mut authority,
635 dsl::MeerkatMachineInput::AuthorizeStoredInputStateSeed {
636 input_id: input_key.clone(),
637 phase: observed_input_phase(seed.phase),
638 terminal_kind,
639 superseded_by,
640 aggregate_id,
641 abandon_reason,
642 abandon_attempt_count,
643 attempt_count: u64::from(seed.attempt_count),
644 run_id: seed.last_run_id.as_ref().map(dsl::RunId::from_domain),
645 boundary_sequence: seed.last_boundary_sequence,
646 admission_sequence: seed.admission_sequence,
647 recovery_lane: seed.recovery_lane.map(dsl::InputLane::from),
648 },
649 )
650 .map_err(|err| {
651 format!("MeerkatMachine rejected stored input-state seed for '{input_id}': {err}")
652 })?;
653
654 transition
655 .into_effects()
656 .into_iter()
657 .find_map(|effect| match effect {
658 dsl::MeerkatMachineEffect::StoredInputStateSeedAuthorized { input_id }
659 if input_id == input_key =>
660 {
661 Some(())
662 }
663 _ => None,
664 })
665 .ok_or_else(|| {
666 format!("MeerkatMachine emitted no stored input-state seed authority for '{input_id}'")
667 })
668}
669
670fn classify_input_behavioral_terminality(
671 input_id: &InputId,
672 phase: InputLifecycleState,
673 terminal_outcome: Option<&InputTerminalOutcome>,
674) -> Result<bool, String> {
675 let input_key = input_id.to_string();
676 let (terminal_kind, abandon_reason) = input_terminality_parts(terminal_outcome);
677 let mut authority = projection_authority();
678 let transition = dsl::MeerkatMachineMutator::apply(
679 &mut authority,
680 dsl::MeerkatMachineInput::ClassifyInputTerminality {
681 input_id: input_key.clone(),
682 phase: observed_input_phase(phase),
683 terminal_kind,
684 abandon_reason,
685 },
686 )
687 .map_err(|err| {
688 format!("MeerkatMachine rejected behavioral input terminality for '{input_id}': {err}")
689 })?;
690
691 let mut terminality = None;
692 for effect in transition.into_effects() {
693 match effect {
694 dsl::MeerkatMachineEffect::InputBehavioralTerminalityResolved {
695 input_id,
696 terminal,
697 } if input_id == input_key => terminality = Some(terminal),
698 other => {
699 return Err(format!(
700 "MeerkatMachine emitted unexpected behavioral input terminality effect for '{input_id}': {other:?}"
701 ));
702 }
703 }
704 }
705 terminality.ok_or_else(|| {
706 format!("MeerkatMachine emitted no behavioral input terminality for '{input_id}'")
707 })
708}
709
710fn resolve_input_public_terminal_projection(
711 input_id: &InputId,
712 seed: &InputStateSeed,
713) -> Result<Option<dsl::InputPublicTerminalOutcome>, String> {
714 let input_key = input_id.to_string();
715 let (terminal_kind, abandon_reason) = input_terminality_parts(seed.terminal_outcome.as_ref());
716 let mut authority = projection_authority();
717 let transition = dsl::MeerkatMachineMutator::apply(
718 &mut authority,
719 dsl::MeerkatMachineInput::ResolveInputPublicTerminalOutcome {
720 input_id: input_key.clone(),
721 phase: observed_input_phase(seed.phase),
722 terminal_kind,
723 abandon_reason,
724 },
725 )
726 .map_err(|err| {
727 format!("MeerkatMachine rejected public terminal projection for '{input_id}': {err}")
728 })?;
729
730 transition
731 .into_effects()
732 .into_iter()
733 .find_map(|effect| match effect {
734 dsl::MeerkatMachineEffect::InputPublicTerminalOutcomeResolved {
735 input_id,
736 terminal_outcome,
737 } if input_id == input_key => Some(terminal_outcome),
738 _ => None,
739 })
740 .ok_or_else(|| {
741 format!("MeerkatMachine emitted no public terminal projection for '{input_id}'")
742 })
743}
744
745fn projection_authority() -> dsl::MeerkatMachineAuthority {
746 dsl_authority::new_initialized_authority("projection authority must initialize")
747}
748
749#[cfg(feature = "live")]
750fn live_unbound_rejection_authority() -> crate::driver::ephemeral::SharedIngressDslAuthority {
751 Arc::new(std::sync::Mutex::new(
752 dsl_authority::new_initialized_authority(
753 "live unbound rejection authority must initialize",
754 ),
755 ))
756}
757
758fn observed_input_phase(phase: InputLifecycleState) -> dsl::RecoveredInputObservedPhase {
759 match phase {
760 InputLifecycleState::Accepted => dsl::RecoveredInputObservedPhase::Accepted,
761 InputLifecycleState::Queued => dsl::RecoveredInputObservedPhase::Queued,
762 InputLifecycleState::Staged => dsl::RecoveredInputObservedPhase::Staged,
763 InputLifecycleState::Applied => dsl::RecoveredInputObservedPhase::Applied,
764 InputLifecycleState::AppliedPendingConsumption => {
765 dsl::RecoveredInputObservedPhase::AppliedPendingConsumption
766 }
767 InputLifecycleState::Consumed => dsl::RecoveredInputObservedPhase::Consumed,
768 InputLifecycleState::Superseded => dsl::RecoveredInputObservedPhase::Superseded,
769 InputLifecycleState::Coalesced => dsl::RecoveredInputObservedPhase::Coalesced,
770 InputLifecycleState::Abandoned => dsl::RecoveredInputObservedPhase::Abandoned,
771 }
772}
773
774type InputSeedTerminalParts = (
775 Option<dsl::InputTerminalKind>,
776 Option<String>,
777 Option<String>,
778 Option<dsl::InputAbandonReason>,
779 u64,
780);
781
782fn input_seed_terminal_parts(seed: &InputStateSeed) -> Result<InputSeedTerminalParts, String> {
783 match seed.terminal_outcome.as_ref() {
784 None => Ok((None, None, None, None, 0)),
785 Some(InputTerminalOutcome::Consumed) => {
786 Ok((Some(dsl::InputTerminalKind::Consumed), None, None, None, 0))
787 }
788 Some(InputTerminalOutcome::Superseded { superseded_by }) => Ok((
789 Some(dsl::InputTerminalKind::Superseded),
790 Some(superseded_by.to_string()),
791 None,
792 None,
793 0,
794 )),
795 Some(InputTerminalOutcome::Coalesced { aggregate_id }) => Ok((
796 Some(dsl::InputTerminalKind::Coalesced),
797 None,
798 Some(aggregate_id.to_string()),
799 None,
800 0,
801 )),
802 Some(InputTerminalOutcome::Abandoned { reason }) => {
803 let abandon_attempt_count = match reason {
804 InputAbandonReason::MaxAttemptsExhausted { attempts } => u64::from(*attempts),
805 _ => u64::from(seed.attempt_count),
806 };
807 Ok((
808 Some(dsl::InputTerminalKind::Abandoned),
809 None,
810 None,
811 input_terminality_parts(seed.terminal_outcome.as_ref()).1,
812 abandon_attempt_count,
813 ))
814 }
815 }
816}
817
818fn input_terminality_parts(
819 outcome: Option<&InputTerminalOutcome>,
820) -> (
821 Option<dsl::InputTerminalKind>,
822 Option<dsl::InputAbandonReason>,
823) {
824 match outcome {
825 None => (None, None),
826 Some(InputTerminalOutcome::Consumed) => (Some(dsl::InputTerminalKind::Consumed), None),
827 Some(InputTerminalOutcome::Superseded { .. }) => {
828 (Some(dsl::InputTerminalKind::Superseded), None)
829 }
830 Some(InputTerminalOutcome::Coalesced { .. }) => {
831 (Some(dsl::InputTerminalKind::Coalesced), None)
832 }
833 Some(InputTerminalOutcome::Abandoned { reason }) => (
834 Some(dsl::InputTerminalKind::Abandoned),
835 Some(match reason {
836 InputAbandonReason::Retired => dsl::InputAbandonReason::Retired,
837 InputAbandonReason::Reset => dsl::InputAbandonReason::Reset,
838 InputAbandonReason::Stopped => dsl::InputAbandonReason::Stopped,
839 InputAbandonReason::Destroyed => dsl::InputAbandonReason::Destroyed,
840 InputAbandonReason::Cancelled => dsl::InputAbandonReason::Cancelled,
841 InputAbandonReason::MaxAttemptsExhausted { .. } => {
842 dsl::InputAbandonReason::MaxAttemptsExhausted
843 }
844 }),
845 ),
846 }
847}
848
849#[derive(Debug, Default)]
850struct UnavailableBlobStore;
851
852impl UnavailableBlobStore {
853 fn error() -> BlobStoreError {
854 BlobStoreError::Unsupported(
855 "persistent runtime constructed without blob store; blob-backed inputs require a BlobStore"
856 .to_string(),
857 )
858 }
859}
860
861#[cfg(not(target_arch = "wasm32"))]
862struct PersistentAuthAuthorityBundle {
863 store: StdMutex<Weak<dyn RuntimeStore>>,
864 auth_lease: Arc<crate::handles::RuntimeAuthLeaseHandle>,
865 oauth_flows: Arc<crate::handles::RuntimeOAuthFlowHandle>,
866}
867
868#[cfg(not(target_arch = "wasm32"))]
869#[derive(Debug, Clone, PartialEq, Eq, Hash)]
870enum PersistentAuthAuthorityKey {
871 Durable(String),
872 Process(usize),
873}
874
875#[cfg(not(target_arch = "wasm32"))]
876static PERSISTENT_AUTH_AUTHORITIES: OnceLock<
877 StdMutex<HashMap<PersistentAuthAuthorityKey, Arc<PersistentAuthAuthorityBundle>>>,
878> = OnceLock::new();
879
880#[cfg(not(target_arch = "wasm32"))]
881fn runtime_store_identity(store: &Arc<dyn RuntimeStore>) -> PersistentAuthAuthorityKey {
882 store
883 .auth_authority_key()
884 .map(PersistentAuthAuthorityKey::Durable)
885 .unwrap_or_else(|| {
886 PersistentAuthAuthorityKey::Process(Arc::as_ptr(store).cast::<()>() as usize)
887 })
888}
889
890fn runtime_stores_share_authority(a: &Arc<dyn RuntimeStore>, b: &Arc<dyn RuntimeStore>) -> bool {
891 match (a.auth_authority_key(), b.auth_authority_key()) {
892 (Some(a), Some(b)) => a == b,
893 _ => Arc::ptr_eq(a, b),
894 }
895}
896
897fn generated_runtime_auth_lease_handle(
898 handle: Arc<crate::handles::RuntimeAuthLeaseHandle>,
899) -> meerkat_core::handles::GeneratedAuthLeaseHandle {
900 #[allow(clippy::expect_used)]
901 crate::protocol_auth_lease_lifecycle_publication::generated_auth_lease_handle(handle)
902 .expect("runtime AuthLeaseHandle must be certified by generated AuthMachine authority")
903}
904
905#[cfg(not(target_arch = "wasm32"))]
906fn persistent_auth_authorities(
907 store: &Arc<dyn RuntimeStore>,
908) -> Arc<PersistentAuthAuthorityBundle> {
909 let key = runtime_store_identity(store);
910 let authorities = PERSISTENT_AUTH_AUTHORITIES.get_or_init(|| StdMutex::new(HashMap::new()));
911 let mut authorities = authorities
912 .lock()
913 .unwrap_or_else(std::sync::PoisonError::into_inner);
914 if let Some(existing) = authorities.get(&key) {
915 let stored_store_alive = existing
916 .store
917 .lock()
918 .unwrap_or_else(std::sync::PoisonError::into_inner)
919 .upgrade()
920 .is_some();
921 if matches!(key, PersistentAuthAuthorityKey::Durable(_)) || stored_store_alive {
922 existing.oauth_flows.bind_persistent_store(store);
923 *existing
924 .store
925 .lock()
926 .unwrap_or_else(std::sync::PoisonError::into_inner) = Arc::downgrade(store);
927 return Arc::clone(existing);
928 }
929 }
930 let auth_lease = Arc::new(crate::handles::RuntimeAuthLeaseHandle::new());
931 let oauth_flows = Arc::new(
932 crate::handles::RuntimeOAuthFlowHandle::new_with_persistent_store_and_auth_lease(
933 std::time::Duration::from_secs(10 * 60),
934 Arc::clone(&auth_lease),
935 store,
936 ),
937 );
938 let bundle = Arc::new(PersistentAuthAuthorityBundle {
939 store: StdMutex::new(Arc::downgrade(store)),
940 auth_lease,
941 oauth_flows,
942 });
943 authorities.insert(key, Arc::clone(&bundle));
944 bundle
945}
946
947#[cfg(all(test, not(target_arch = "wasm32")))]
948pub(crate) fn clear_persistent_auth_authorities_for_test() {
949 if let Some(authorities) = PERSISTENT_AUTH_AUTHORITIES.get() {
950 authorities
951 .lock()
952 .unwrap_or_else(std::sync::PoisonError::into_inner)
953 .clear();
954 }
955}
956
957#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
958#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
959impl BlobStore for UnavailableBlobStore {
960 async fn put_image(&self, _media_type: &str, _data: &str) -> Result<BlobRef, BlobStoreError> {
961 Err(Self::error())
962 }
963
964 async fn get(&self, _blob_id: &BlobId) -> Result<BlobPayload, BlobStoreError> {
965 Err(Self::error())
966 }
967
968 async fn delete(&self, _blob_id: &BlobId) -> Result<(), BlobStoreError> {
969 Err(Self::error())
970 }
971
972 async fn exists(&self, _blob_id: &BlobId) -> Result<bool, BlobStoreError> {
973 Err(Self::error())
974 }
975
976 fn is_persistent(&self) -> bool {
977 false
978 }
979}
980
981#[cfg(not(target_arch = "wasm32"))]
982type MeerkatMachineCommandFuture<'a> = Pin<
983 Box<
984 dyn Future<Output = Result<MeerkatMachineCommandResult, MeerkatMachineCommandError>>
985 + Send
986 + 'a,
987 >,
988>;
989
990#[cfg(target_arch = "wasm32")]
991type MeerkatMachineCommandFuture<'a> = Pin<
992 Box<dyn Future<Output = Result<MeerkatMachineCommandResult, MeerkatMachineCommandError>> + 'a>,
993>;
994
995#[cfg(test)]
996pub(crate) use driver::fail_machine_run;
997pub(crate) use driver::{
998 DriverEntry, SharedCompletionRegistry, SharedDriver, cancel_runtime_loop_run,
999 commit_machine_terminal_run, commit_runtime_loop_run, fail_runtime_loop_run,
1000 machine_authorize_runtime_loop_batch, machine_batch_primitive_projections,
1001 machine_batch_runtime_semantics, machine_commit_prepared_destroy,
1002 machine_commit_service_turn_terminal_receipt, machine_prepare_bindings_projection,
1003 machine_prepare_destroy, machine_recover_ephemeral_driver, machine_recover_persistent_driver,
1004 machine_recover_persistent_inputs, machine_recover_persistent_inputs_from_observed,
1005 machine_recycle_preserving_work, machine_reset, machine_retire, machine_stop_runtime,
1006 prepare_runtime_loop_batch_start,
1007};
1008
1009pub(crate) mod driver;
1010
1011mod comms_drain;
1012pub mod composition;
1013mod dispatch_control;
1014mod dispatch_drain;
1015mod dispatch_ingress;
1016mod dispatch_session;
1017#[allow(unused_variables, dead_code, clippy::cmp_owned)]
1018#[allow(clippy::assign_op_pattern)]
1019pub mod dsl;
1020pub(crate) mod dsl_authority;
1021mod dsl_effects;
1022mod llm_reconfigure;
1023mod runtime_control;
1024mod session_management;
1025mod traits;
1026mod visibility;
1027
1028pub(crate) use session_management::{
1029 DeleteOpsFinalizationAuthority, RetainOpsFinalizationAuthority,
1030};
1031
1032pub use composition::{MeerkatCompositionSignalDispatcher, MeerkatConsumerSurface};
1033
1034pub use comms_drain::{
1035 CommsDrainMode, CommsDrainPhase, DrainExitReason, PeerEndpointStageError, PeerIngressOwner,
1036 SupervisorBinding, SupervisorBindingStageError,
1037};
1038pub(crate) use comms_drain::{
1039 CommsDrainSlot, GeneratedSupervisorBinding, GeneratedSupervisorRotationReceipt,
1040 GeneratedSupervisorRotationSubmit, SupervisorAuthorizeAdmission, SupervisorBindAdmission,
1041 SupervisorBridgeCommandAdmission, SupervisorRotationObservation, SupervisorRotationSubmission,
1042 SupervisorRotationTaskSlot,
1043};
1044pub(crate) use dsl_effects::{DslTransitionEffects, apply_dsl_transition_on_authority};
1045pub(crate) use visibility::MachineToolVisibilityOwner;
1046
1047struct StagedSessionDslInput {
1048 previous_snapshot: dsl::MeerkatMachineAuthoritySnapshot,
1049 committed_snapshot: dsl::MeerkatMachineAuthoritySnapshot,
1050 effects: DslTransitionEffects,
1051}
1052
1053impl StagedSessionDslInput {
1054 fn revived_stopped_session(&self) -> bool {
1061 self.effects.as_slice().iter().any(|effect| {
1062 matches!(
1063 effect,
1064 dsl::MeerkatMachineEffect::RuntimeNotice {
1065 kind: dsl::RuntimeNoticeKind::Recover,
1066 ..
1067 }
1068 )
1069 })
1070 }
1071
1072 fn has_routed_signal_effect(&self) -> bool {
1077 self.effects
1078 .as_slice()
1079 .iter()
1080 .any(|effect| composition::lift_routed_signal(effect).is_some())
1081 }
1082}
1083
1084#[derive(Clone, Copy)]
1085enum CommittedEffectDispatchFailure {
1086 PreserveCommittedDslState,
1087}
1088
1089type UnregisterTeardownResult = Result<(), RuntimeDriverError>;
1090type RuntimeStopCleanupResult = Result<(), RuntimeDriverError>;
1091
1092#[derive(Clone)]
1094struct UnregisterTeardownCoordinator {
1095 epoch_id: meerkat_core::RuntimeEpochId,
1096 coordinator_id: uuid::Uuid,
1097 result_rx: crate::tokio::sync::watch::Receiver<Option<UnregisterTeardownResult>>,
1098}
1099
1100#[derive(Clone)]
1104struct RuntimeStopCleanupCoordinator {
1105 epoch_id: meerkat_core::RuntimeEpochId,
1106 coordinator_id: uuid::Uuid,
1107 teardown_slot: Option<Arc<crate::runtime_loop::RuntimeLoopTeardownSlot>>,
1108 result_rx: crate::tokio::sync::watch::Receiver<Option<RuntimeStopCleanupResult>>,
1109}
1110
1111#[derive(Clone)]
1112struct PendingUnregisterFinalization {
1113 finalization_id: uuid::Uuid,
1114 durability_authority: session_management::RuntimeOpsLifecycleDurabilityAuthority,
1115 committed_snapshot: dsl::MeerkatMachineAuthoritySnapshot,
1116}
1117
1118struct UnregisterTeardownMechanicalObservations {
1119 runtime_loop_forced_abort: std::sync::atomic::AtomicBool,
1120 comms_drain_forced_abort: std::sync::atomic::AtomicBool,
1121}
1122
1123#[cfg(not(target_arch = "wasm32"))]
1127struct OpsLifecyclePersistenceWorker {
1128 handle: std::thread::JoinHandle<()>,
1129}
1130
1131#[cfg(target_arch = "wasm32")]
1132struct OpsLifecyclePersistenceWorker {
1133 handle: crate::tokio::task::JoinHandle<()>,
1134}
1135
1136impl UnregisterTeardownMechanicalObservations {
1137 fn new() -> Self {
1138 Self {
1139 runtime_loop_forced_abort: std::sync::atomic::AtomicBool::new(false),
1140 comms_drain_forced_abort: std::sync::atomic::AtomicBool::new(false),
1141 }
1142 }
1143
1144 fn from_durable_process_recovery(
1145 progress: Option<&crate::store::MachineUnregisterProgressSnapshot>,
1146 ) -> Self {
1147 let observations = Self::new();
1148 if let Some(progress) = progress {
1149 observations.runtime_loop_forced_abort.store(
1153 progress.runtime_loop_drain_pending(),
1154 std::sync::atomic::Ordering::Release,
1155 );
1156 observations.comms_drain_forced_abort.store(
1157 progress.comms_drain_exit_pending(),
1158 std::sync::atomic::Ordering::Release,
1159 );
1160 }
1161 observations
1162 }
1163}
1164
1165struct RuntimeSessionEntry {
1167 runtime_id: LogicalRuntimeId,
1169 mutation_gate: Arc<Mutex<()>>,
1181 #[cfg(feature = "live")]
1186 live_lifecycle_gate: Arc<Mutex<()>>,
1187 supervisor_rotation_task: Arc<SupervisorRotationTaskSlot>,
1190 driver: SharedDriver,
1192 control_projection: Arc<StdRwLock<crate::driver::ephemeral::RuntimeControlProjection>>,
1198 ops_lifecycle: Arc<crate::ops_lifecycle::RuntimeOpsLifecycleRegistry>,
1200 ops_lifecycle_persistence_worker: Option<OpsLifecyclePersistenceWorker>,
1204 epoch_id: meerkat_core::RuntimeEpochId,
1206 handle_teardown_gate: Arc<crate::handles::HandleTeardownGate>,
1211 materialization_claim_state:
1216 Arc<std::sync::Mutex<crate::RuntimeActorMaterializationClaimState>>,
1217 cursor_state: Arc<meerkat_core::EpochCursorState>,
1219 completions: SharedCompletionRegistry,
1221 tool_visibility_owner: Arc<MachineToolVisibilityOwner>,
1223 canonical_runtime_bindings: Option<meerkat_core::SessionRuntimeBindings>,
1230 attachment_slot: RuntimeLoopAttachmentSlot,
1235 runtime_loop_teardown: Option<Arc<crate::runtime_loop::RuntimeLoopTeardownSlot>>,
1242 unregister_coordinator: Option<UnregisterTeardownCoordinator>,
1244 runtime_stop_cleanup_coordinator: Option<RuntimeStopCleanupCoordinator>,
1247 pending_revival_lifecycle_persist: Arc<std::sync::atomic::AtomicBool>,
1251 pending_unregister_finalization: Option<PendingUnregisterFinalization>,
1256 unregister_teardown_observations: Arc<UnregisterTeardownMechanicalObservations>,
1259 publication_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorPublicationHandle>>,
1265 post_stop_cleanup_handle:
1269 Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorPostStopCleanupHandle>>,
1270 post_stop_cleanup_attachment_id: Option<RuntimeLoopAttachmentId>,
1272 post_stop_cleanup_complete: bool,
1274 post_stop_cleanup_gate: Arc<Mutex<()>>,
1280 provisional_interrupt_handle:
1283 Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorInterruptHandle>>,
1284 provisional_materialization_claim_id: Option<uuid::Uuid>,
1288 dsl_authority: Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
1298 drain_slot: CommsDrainSlot,
1308}
1309
1310#[derive(Clone)]
1311struct MemberIncarnationRegistration {
1312 incarnation: meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation,
1313 session_mutation_gate: Arc<Mutex<()>>,
1314 tracked_turn_journal: Option<Arc<dyn crate::member_observation::TrackedTurnJournal>>,
1315}
1316
1317enum MemberResidencyState {
1318 PeerOnly,
1320 VacantPlaced,
1323 Placed(MemberIncarnationRegistration),
1325}
1326
1327struct MemberResidencySlot {
1328 gate: Arc<Mutex<()>>,
1329 state: StdRwLock<MemberResidencyState>,
1330}
1331
1332struct MemberEffectAuthorityLease {
1333 slot: Arc<MemberResidencySlot>,
1334 slot_guard: crate::tokio::sync::OwnedMutexGuard<()>,
1335 session_mutation_gate: Arc<Mutex<()>>,
1336}
1337
1338pub(crate) struct MemberEffectAuthorityGuard {
1343 _slot_guard: crate::tokio::sync::OwnedMutexGuard<()>,
1344 _session_guard: crate::tokio::sync::OwnedMutexGuard<()>,
1345}
1346
1347impl MemberResidencySlot {
1348 fn peer_only() -> Self {
1349 Self {
1350 gate: Arc::new(Mutex::new(())),
1351 state: StdRwLock::new(MemberResidencyState::PeerOnly),
1352 }
1353 }
1354}
1355
1356pub struct MachineSessionArchiveLease {
1361 session_id: SessionId,
1362 runtime_id: LogicalRuntimeId,
1363 driver: SharedDriver,
1364 completions: SharedCompletionRegistry,
1365 wake_tx: Option<mpsc::Sender<()>>,
1366 publication_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorPublicationHandle>>,
1367 recovered_registration_for_archive: bool,
1373 _registration_transaction_guard: crate::tokio::sync::OwnedMutexGuard<()>,
1377 _live_lifecycle_lease: Option<crate::member_live::MemberLiveLifecycleLease>,
1380 _mutation_guard: crate::tokio::sync::OwnedMutexGuard<()>,
1381}
1382
1383#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1386pub enum LocalSessionMaterializationMode {
1387 #[default]
1390 Ordinary,
1391 MissingLiveRevival,
1394}
1395
1396pub struct PreparedSessionMaterialization {
1403 machine: Arc<MeerkatMachine>,
1404 bindings: meerkat_core::SessionRuntimeBindings,
1405 claim_id: uuid::Uuid,
1406 claim_state: Arc<std::sync::Mutex<crate::RuntimeActorMaterializationClaimState>>,
1407 cleanup_spawner: MachineCleanupTaskSpawner,
1408 archived_resume_authorization_issued: bool,
1409 armed: bool,
1410}
1411
1412pub struct PreparedArchivedResumeCommitLease {
1419 machine: Arc<MeerkatMachine>,
1420 session_id: SessionId,
1421 epoch_id: meerkat_core::RuntimeEpochId,
1422 claim_id: uuid::Uuid,
1423 claim_state: Arc<std::sync::Mutex<crate::RuntimeActorMaterializationClaimState>>,
1424 _mutation_guard: crate::tokio::sync::OwnedMutexGuard<()>,
1425}
1426
1427pub struct PromotedArchivedResumeCommitLease {
1434 prepared: PreparedArchivedResumeCommitLease,
1435}
1436
1437impl PreparedArchivedResumeCommitLease {
1438 pub fn session_id(&self) -> &SessionId {
1439 &self.session_id
1440 }
1441
1442 pub fn confirm_document_promoted(
1447 self,
1448 runtime_store: &Arc<dyn crate::RuntimeStore>,
1449 ) -> Result<PromotedArchivedResumeCommitLease, RuntimeDriverError> {
1450 if !self.machine.shares_runtime_store_authority(runtime_store) {
1451 return Err(RuntimeDriverError::StaleAuthority {
1452 reason: format!(
1453 "archived document promotion for session {} used a runtime store not owned by the prepared machine",
1454 self.session_id
1455 ),
1456 });
1457 }
1458 Ok(PromotedArchivedResumeCommitLease { prepared: self })
1459 }
1460}
1461
1462impl PromotedArchivedResumeCommitLease {
1463 pub fn session_id(&self) -> &SessionId {
1464 self.prepared.session_id()
1465 }
1466
1467 pub async fn reset_retired_runtime(&mut self) -> Result<ResetReport, RuntimeDriverError> {
1470 self.prepared
1471 .machine
1472 .reset_runtime_for_promoted_archived_resume(self)
1473 .await
1474 }
1475}
1476
1477#[must_use = "archived-resume authorization must be consumed by actor materialization"]
1486pub struct ArchivedSessionActorMaterializationAuthorization {
1487 machine: Arc<MeerkatMachine>,
1488 session_id: SessionId,
1489 epoch_id: meerkat_core::RuntimeEpochId,
1490 claim_id: uuid::Uuid,
1491 claim_state: Arc<std::sync::Mutex<crate::RuntimeActorMaterializationClaimState>>,
1492 dsl_authority: Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
1493 teardown_gate: Arc<crate::handles::HandleTeardownGate>,
1494}
1495
1496impl ArchivedSessionActorMaterializationAuthorization {
1497 pub(crate) async fn begin(
1498 self,
1499 bindings: &meerkat_core::SessionRuntimeBindings,
1500 ) -> Result<crate::RuntimeActorMaterializationPermit, crate::RuntimeActorMaterializationError>
1501 {
1502 let binding_authority = crate::validated_session_runtime_bindings_authority(bindings)?;
1503 if bindings.session_id() != &self.session_id
1504 || bindings.epoch_id() != &self.epoch_id
1505 || binding_authority.materialization_claim_id != Some(self.claim_id)
1506 || !Arc::ptr_eq(
1507 &binding_authority.materialization_claim_state,
1508 &self.claim_state,
1509 )
1510 || !Arc::ptr_eq(&binding_authority.dsl_authority, &self.dsl_authority)
1511 || !Arc::ptr_eq(&binding_authority.teardown_gate, &self.teardown_gate)
1512 {
1513 return Err(crate::RuntimeActorMaterializationError::InvalidAuthority(
1514 "archived-resume authorization does not match the exact prepared binding"
1515 .to_string(),
1516 ));
1517 }
1518
1519 let mutation_guard = self
1520 .machine
1521 .lock_current_session_mutation_gate(&self.session_id)
1522 .await
1523 .ok_or(crate::RuntimeActorMaterializationError::RegistrationClosed)?;
1524 {
1525 let sessions = self.machine.sessions.read().await;
1526 let entry = sessions
1527 .get(&self.session_id)
1528 .ok_or(crate::RuntimeActorMaterializationError::RegistrationClosed)?;
1529 let exact_registration = entry.epoch_id == self.epoch_id
1530 && Arc::ptr_eq(&entry.materialization_claim_state, &self.claim_state)
1531 && Arc::ptr_eq(&entry.dsl_authority, &self.dsl_authority)
1532 && Arc::ptr_eq(&entry.handle_teardown_gate, &self.teardown_gate)
1533 && entry.provisional_materialization_claim_id == Some(self.claim_id)
1534 && !entry.physical_attachment_is_live();
1535 let exact_claim = self
1536 .claim_state
1537 .lock()
1538 .unwrap_or_else(std::sync::PoisonError::into_inner)
1539 .exact_claim_is(
1540 self.claim_id,
1541 &[
1542 crate::RuntimeActorMaterializationClaimPhase::Prepared,
1543 crate::RuntimeActorMaterializationClaimPhase::Staged,
1544 ],
1545 );
1546 if !exact_registration || !exact_claim {
1547 return Err(crate::RuntimeActorMaterializationError::RegistrationClosed);
1548 }
1549 }
1550
1551 crate::begin_session_runtime_actor_materialization_with_phase_policy(
1552 bindings,
1553 crate::RuntimeActorMaterializationPhasePolicy::RequireArchivedRevivalBoundary,
1554 Some(mutation_guard),
1555 )
1556 }
1557}
1558
1559pub struct PreparedAttachedSessionActorRecovery {
1567 bindings: meerkat_core::SessionRuntimeBindings,
1568 witness: RuntimeExecutorAttachmentWitness,
1569 claim_id: uuid::Uuid,
1570 claim_state: Arc<std::sync::Mutex<crate::RuntimeActorMaterializationClaimState>>,
1571 previous_phase: crate::RuntimeActorMaterializationClaimPhase,
1572 mutation_guard: Option<crate::tokio::sync::OwnedMutexGuard<()>>,
1573 armed: bool,
1574}
1575
1576#[derive(Clone)]
1581struct RuntimeExecutorAttachmentMaterializationClaim {
1582 claim_id: uuid::Uuid,
1583 claim_state: Arc<std::sync::Mutex<crate::RuntimeActorMaterializationClaimState>>,
1584 epoch_id: meerkat_core::RuntimeEpochId,
1585}
1586
1587#[derive(Clone)]
1598pub struct RuntimeSessionRegistrationWitness {
1599 machine: std::sync::Weak<MeerkatMachineShared>,
1600 session_id: SessionId,
1601 epoch_id: meerkat_core::RuntimeEpochId,
1602 registration_gate: std::sync::Weak<Mutex<()>>,
1603}
1604
1605impl RuntimeSessionRegistrationWitness {
1606 fn new(
1607 machine: std::sync::Weak<MeerkatMachineShared>,
1608 session_id: SessionId,
1609 epoch_id: meerkat_core::RuntimeEpochId,
1610 registration_gate: std::sync::Weak<Mutex<()>>,
1611 ) -> Self {
1612 Self {
1613 machine,
1614 session_id,
1615 epoch_id,
1616 registration_gate,
1617 }
1618 }
1619
1620 pub fn session_id(&self) -> &SessionId {
1621 &self.session_id
1622 }
1623
1624 pub fn epoch_id(&self) -> &meerkat_core::RuntimeEpochId {
1625 &self.epoch_id
1626 }
1627
1628 fn belongs_to(&self, machine: &MeerkatMachine) -> bool {
1629 std::ptr::eq(self.machine.as_ptr(), Arc::as_ptr(&machine.shared))
1630 }
1631
1632 fn matches_entry(&self, entry: &RuntimeSessionEntry) -> bool {
1633 self.registration_gate
1634 .upgrade()
1635 .is_some_and(|gate| Arc::ptr_eq(&gate, &entry.mutation_gate))
1636 }
1637
1638 fn registration_gate(&self) -> Option<Arc<Mutex<()>>> {
1639 self.registration_gate.upgrade()
1640 }
1641}
1642
1643impl std::fmt::Debug for RuntimeSessionRegistrationWitness {
1644 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1645 formatter
1646 .debug_struct("RuntimeSessionRegistrationWitness")
1647 .field("session_id", &self.session_id)
1648 .field("epoch_id", &self.epoch_id)
1649 .finish_non_exhaustive()
1650 }
1651}
1652
1653impl PartialEq for RuntimeSessionRegistrationWitness {
1654 fn eq(&self, other: &Self) -> bool {
1655 self.session_id == other.session_id
1656 && self.epoch_id == other.epoch_id
1657 && std::sync::Weak::ptr_eq(&self.machine, &other.machine)
1658 && std::sync::Weak::ptr_eq(&self.registration_gate, &other.registration_gate)
1659 }
1660}
1661
1662impl Eq for RuntimeSessionRegistrationWitness {}
1663
1664#[derive(Debug, Clone, PartialEq, Eq)]
1669pub struct RuntimeSessionLifecycleObservation {
1670 session_id: SessionId,
1671 runtime_id: LogicalRuntimeId,
1672 lifecycle: crate::store::MachineLifecycleObservation,
1673}
1674
1675impl RuntimeSessionLifecycleObservation {
1676 fn new(
1677 session_id: SessionId,
1678 runtime_id: LogicalRuntimeId,
1679 lifecycle: crate::store::MachineLifecycleObservation,
1680 ) -> Self {
1681 Self {
1682 session_id,
1683 runtime_id,
1684 lifecycle,
1685 }
1686 }
1687
1688 #[must_use]
1689 pub fn session_id(&self) -> &SessionId {
1690 &self.session_id
1691 }
1692
1693 #[must_use]
1694 pub fn runtime_id(&self) -> &LogicalRuntimeId {
1695 &self.runtime_id
1696 }
1697
1698 #[must_use]
1699 pub fn lifecycle(&self) -> &crate::store::MachineLifecycleObservation {
1700 &self.lifecycle
1701 }
1702}
1703
1704#[derive(Debug, Clone, PartialEq, Eq)]
1710pub enum RuntimeSessionRegistrationOutcome {
1711 Applied {
1714 registration: RuntimeSessionRegistrationWitness,
1715 lifecycle: RuntimeSessionLifecycleObservation,
1716 },
1717 AlreadyExact {
1721 registration: RuntimeSessionRegistrationWitness,
1722 lifecycle: RuntimeSessionLifecycleObservation,
1723 },
1724 Conflict {
1727 current: RuntimeSessionLifecycleObservation,
1728 reason: String,
1729 },
1730 RepairBlocked {
1732 evidence_digest: Option<String>,
1733 reason: String,
1734 },
1735 Backoff { reason: String },
1738}
1739
1740#[derive(Clone)]
1748pub struct RuntimeExecutorAttachmentWitness {
1749 machine: std::sync::Weak<MeerkatMachineShared>,
1750 session_id: SessionId,
1751 epoch_id: meerkat_core::RuntimeEpochId,
1752 attachment_id: RuntimeLoopAttachmentId,
1753}
1754
1755impl RuntimeExecutorAttachmentWitness {
1756 fn new(
1757 machine: std::sync::Weak<MeerkatMachineShared>,
1758 session_id: SessionId,
1759 epoch_id: meerkat_core::RuntimeEpochId,
1760 attachment_id: RuntimeLoopAttachmentId,
1761 ) -> Self {
1762 Self {
1763 machine,
1764 session_id,
1765 epoch_id,
1766 attachment_id,
1767 }
1768 }
1769
1770 pub fn session_id(&self) -> &SessionId {
1771 &self.session_id
1772 }
1773
1774 pub fn epoch_id(&self) -> &meerkat_core::RuntimeEpochId {
1775 &self.epoch_id
1776 }
1777
1778 fn belongs_to(&self, machine: &MeerkatMachine) -> bool {
1779 std::ptr::eq(self.machine.as_ptr(), Arc::as_ptr(&machine.shared))
1780 }
1781}
1782
1783impl std::fmt::Debug for RuntimeExecutorAttachmentWitness {
1784 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1785 formatter
1786 .debug_struct("RuntimeExecutorAttachmentWitness")
1787 .field("session_id", &self.session_id)
1788 .field("epoch_id", &self.epoch_id)
1789 .finish_non_exhaustive()
1790 }
1791}
1792
1793impl PartialEq for RuntimeExecutorAttachmentWitness {
1794 fn eq(&self, other: &Self) -> bool {
1795 self.session_id == other.session_id
1796 && self.epoch_id == other.epoch_id
1797 && self.attachment_id == other.attachment_id
1798 && std::sync::Weak::ptr_eq(&self.machine, &other.machine)
1799 }
1800}
1801
1802impl Eq for RuntimeExecutorAttachmentWitness {}
1803
1804pub enum EnsureRuntimeExecutorAttachment {
1811 Existing(RuntimeExecutorAttachmentWitness),
1812 Pending(PendingRuntimeExecutorAttachment),
1813}
1814
1815#[cfg(not(target_arch = "wasm32"))]
1827static MACHINE_CLEANUP_RUNTIME: OnceLock<StdMutex<Option<crate::tokio::runtime::Runtime>>> =
1828 OnceLock::new();
1829
1830#[cfg(not(target_arch = "wasm32"))]
1835const MACHINE_CLEANUP_THREAD_STACK_SIZE: usize = 16 * 1024 * 1024;
1836
1837#[derive(Clone)]
1844struct MachineCleanupTaskSpawner {
1845 #[cfg(not(target_arch = "wasm32"))]
1846 handle: crate::tokio::runtime::Handle,
1847}
1848
1849#[doc(hidden)]
1852#[derive(Clone)]
1853pub struct RuntimeCleanupTaskSpawner {
1854 inner: MachineCleanupTaskSpawner,
1855}
1856
1857impl RuntimeCleanupTaskSpawner {
1858 pub fn acquire() -> Result<Self, RuntimeDriverError> {
1859 Ok(Self {
1860 inner: MachineCleanupTaskSpawner::acquire()?,
1861 })
1862 }
1863
1864 #[cfg(not(target_arch = "wasm32"))]
1865 pub fn spawn_detached<F>(&self, future: F)
1866 where
1867 F: Future<Output = ()> + Send + 'static,
1868 {
1869 self.inner.spawn(future);
1870 }
1871
1872 #[cfg(target_arch = "wasm32")]
1873 pub fn spawn_detached<F>(&self, future: F)
1874 where
1875 F: Future<Output = ()> + 'static,
1876 {
1877 self.inner.spawn(future);
1878 }
1879}
1880
1881impl MachineCleanupTaskSpawner {
1882 #[cfg(not(target_arch = "wasm32"))]
1883 fn acquire() -> Result<Self, RuntimeDriverError> {
1884 let slot = MACHINE_CLEANUP_RUNTIME.get_or_init(|| StdMutex::new(None));
1885 let mut runtime = slot
1886 .lock()
1887 .unwrap_or_else(std::sync::PoisonError::into_inner);
1888 if let Some(runtime) = runtime.as_ref() {
1889 return Ok(Self {
1890 handle: runtime.handle().clone(),
1891 });
1892 }
1893
1894 let candidate = crate::tokio::runtime::Builder::new_multi_thread()
1895 .worker_threads(1)
1896 .thread_name("meerkat-machine-cleanup")
1897 .thread_stack_size(MACHINE_CLEANUP_THREAD_STACK_SIZE)
1898 .enable_all()
1899 .build()
1900 .map_err(|error| {
1901 RuntimeDriverError::Internal(format!(
1902 "failed to start machine cleanup runtime: {error}"
1903 ))
1904 })?;
1905 let handle = candidate.handle().clone();
1906 *runtime = Some(candidate);
1907 Ok(Self { handle })
1908 }
1909
1910 #[cfg(target_arch = "wasm32")]
1911 fn acquire() -> Result<Self, RuntimeDriverError> {
1912 Ok(Self {})
1913 }
1914
1915 #[cfg(not(target_arch = "wasm32"))]
1916 fn spawn<F, T>(&self, future: F) -> crate::tokio::task::JoinHandle<T>
1917 where
1918 F: Future<Output = T> + Send + 'static,
1919 T: Send + 'static,
1920 {
1921 self.handle.spawn(future)
1922 }
1923
1924 #[cfg(target_arch = "wasm32")]
1925 fn spawn<F, T>(&self, future: F) -> crate::tokio::task::JoinHandle<T>
1926 where
1927 F: Future<Output = T> + 'static,
1928 T: 'static,
1929 {
1930 crate::tokio::spawn(future)
1931 }
1932}
1933
1934impl std::fmt::Debug for EnsureRuntimeExecutorAttachment {
1935 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1936 match self {
1937 Self::Existing(witness) => formatter.debug_tuple("Existing").field(witness).finish(),
1938 Self::Pending(pending) => formatter.debug_tuple("Pending").field(pending).finish(),
1939 }
1940 }
1941}
1942
1943#[must_use = "dropping a pending runtime attachment aborts that exact attachment"]
1952pub struct PendingRuntimeExecutorAttachment {
1953 machine: Arc<MeerkatMachine>,
1954 witness: RuntimeExecutorAttachmentWitness,
1955 mutation_guard: Option<crate::tokio::sync::OwnedMutexGuard<()>>,
1956 cleanup_spawner: MachineCleanupTaskSpawner,
1957 should_wake: bool,
1958 persist_lifecycle_on_commit: bool,
1959 armed: bool,
1960}
1961
1962#[must_use = "dropping a retained runtime attachment aborts that exact attachment"]
1971pub struct CommittedRuntimeExecutorAttachmentPublicationLease {
1972 machine: Arc<MeerkatMachine>,
1973 witness: RuntimeExecutorAttachmentWitness,
1974 mutation_guard: Option<crate::tokio::sync::OwnedMutexGuard<()>>,
1975 cleanup_spawner: MachineCleanupTaskSpawner,
1976 should_wake: bool,
1977 persist_lifecycle_on_commit: bool,
1978 armed: bool,
1979}
1980
1981#[must_use = "dropping a prepared runtime attachment retirement starts exact retirement"]
1991pub struct PreparedRuntimeExecutorAttachmentRetirement {
1992 machine: Arc<MeerkatMachine>,
1993 witness: RuntimeExecutorAttachmentWitness,
1994 mutation_guard: Option<crate::tokio::sync::OwnedMutexGuard<()>>,
1995 cleanup_spawner: MachineCleanupTaskSpawner,
1996 armed: bool,
1997}
1998
1999#[must_use = "drop is cancellation-safe, but await completion to observe retirement errors"]
2005pub struct RuntimeExecutorAttachmentRetirementCompletion {
2006 result_rx: crate::tokio::sync::oneshot::Receiver<Result<bool, RuntimeDriverError>>,
2007}
2008
2009impl RuntimeExecutorAttachmentRetirementCompletion {
2010 fn new(
2011 result_rx: crate::tokio::sync::oneshot::Receiver<Result<bool, RuntimeDriverError>>,
2012 ) -> Self {
2013 Self { result_rx }
2014 }
2015
2016 pub async fn wait(self) -> Result<bool, RuntimeDriverError> {
2017 self.result_rx.await.map_err(|error| {
2018 RuntimeDriverError::Internal(format!(
2019 "owned exact runtime attachment retirement ended without a result: {error}"
2020 ))
2021 })?
2022 }
2023}
2024
2025impl PreparedRuntimeExecutorAttachmentRetirement {
2026 fn new(
2027 machine: Arc<MeerkatMachine>,
2028 witness: RuntimeExecutorAttachmentWitness,
2029 mutation_guard: crate::tokio::sync::OwnedMutexGuard<()>,
2030 cleanup_spawner: MachineCleanupTaskSpawner,
2031 ) -> Self {
2032 Self {
2033 machine,
2034 witness,
2035 mutation_guard: Some(mutation_guard),
2036 cleanup_spawner,
2037 armed: true,
2038 }
2039 }
2040
2041 pub fn witness(&self) -> &RuntimeExecutorAttachmentWitness {
2042 &self.witness
2043 }
2044
2045 pub fn commit(
2049 mut self,
2050 ) -> Result<RuntimeExecutorAttachmentRetirementCompletion, RuntimeDriverError> {
2051 let guard = self.mutation_guard.take().ok_or_else(|| {
2052 RuntimeDriverError::Internal(
2053 "prepared exact attachment retirement lost its mutation fence".to_string(),
2054 )
2055 })?;
2056 let completion = self
2057 .machine
2058 .spawn_executor_attachment_retirement_with_guard(
2059 self.witness.clone(),
2060 guard,
2061 self.cleanup_spawner.clone(),
2062 );
2063 self.armed = false;
2064 Ok(completion)
2065 }
2066
2067 pub async fn commit_under_runtime_turn_finalization_boundary(
2072 mut self,
2073 ) -> Result<bool, RuntimeDriverError> {
2074 self.machine
2075 .complete_executor_attachment_cleanup_under_runtime_turn_boundary(&self.witness)
2076 .await?;
2077 let guard = self.mutation_guard.take().ok_or_else(|| {
2078 RuntimeDriverError::Internal(
2079 "prepared exact attachment retirement lost its mutation fence".to_string(),
2080 )
2081 })?;
2082 self.armed = false;
2083 self.machine
2084 .unregister_executor_attachment_if_current_with_guard(self.witness.clone(), guard)
2085 .await
2086 }
2087}
2088
2089impl Drop for PreparedRuntimeExecutorAttachmentRetirement {
2090 fn drop(&mut self) {
2091 if !self.armed {
2092 return;
2093 }
2094 let Some(guard) = self.mutation_guard.take() else {
2095 return;
2096 };
2097 let _completion = self
2098 .machine
2099 .spawn_executor_attachment_retirement_with_guard(
2100 self.witness.clone(),
2101 guard,
2102 self.cleanup_spawner.clone(),
2103 );
2104 self.armed = false;
2105 }
2106}
2107
2108impl std::fmt::Debug for PendingRuntimeExecutorAttachment {
2109 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2110 formatter
2111 .debug_struct("PendingRuntimeExecutorAttachment")
2112 .field("witness", &self.witness)
2113 .field("should_wake", &self.should_wake)
2114 .field("armed", &self.armed)
2115 .finish()
2116 }
2117}
2118
2119impl PendingRuntimeExecutorAttachment {
2120 fn new(
2121 machine: Arc<MeerkatMachine>,
2122 witness: RuntimeExecutorAttachmentWitness,
2123 mutation_guard: crate::tokio::sync::OwnedMutexGuard<()>,
2124 cleanup_spawner: MachineCleanupTaskSpawner,
2125 should_wake: bool,
2126 persist_lifecycle_on_commit: bool,
2127 ) -> Self {
2128 Self {
2129 machine,
2130 witness,
2131 mutation_guard: Some(mutation_guard),
2132 cleanup_spawner,
2133 should_wake,
2134 persist_lifecycle_on_commit,
2135 armed: true,
2136 }
2137 }
2138
2139 pub fn witness(&self) -> &RuntimeExecutorAttachmentWitness {
2140 &self.witness
2141 }
2142
2143 #[doc(hidden)]
2144 pub fn cleanup_task_spawner(&self) -> RuntimeCleanupTaskSpawner {
2145 RuntimeCleanupTaskSpawner {
2146 inner: self.cleanup_spawner.clone(),
2147 }
2148 }
2149
2150 pub async fn commit(self) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError> {
2151 self.commit_with_predecessor_mode(|_| Ok(()), false).await
2152 }
2153
2154 pub async fn commit_replacing_predecessor(
2165 self,
2166 ) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError> {
2167 self.commit_with_predecessor_mode(|_| Ok(()), true).await
2168 }
2169
2170 pub async fn commit_with<F>(
2178 self,
2179 on_committed: F,
2180 ) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError>
2181 where
2182 F: FnOnce(&RuntimeExecutorAttachmentWitness) -> Result<(), RuntimeDriverError>
2183 + Send
2184 + 'static,
2185 {
2186 self.commit_with_predecessor_mode(on_committed, false).await
2187 }
2188
2189 pub async fn commit_with_replacing_predecessor<F>(
2198 self,
2199 on_committed: F,
2200 ) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError>
2201 where
2202 F: FnOnce(&RuntimeExecutorAttachmentWitness) -> Result<(), RuntimeDriverError>
2203 + Send
2204 + 'static,
2205 {
2206 self.commit_with_predecessor_mode(on_committed, true).await
2207 }
2208
2209 async fn commit_with_predecessor_mode<F>(
2210 self,
2211 on_committed: F,
2212 replaces_predecessor: bool,
2213 ) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError>
2214 where
2215 F: FnOnce(&RuntimeExecutorAttachmentWitness) -> Result<(), RuntimeDriverError>
2216 + Send
2217 + 'static,
2218 {
2219 let cleanup_spawner = self.cleanup_spawner.clone();
2220 let completion = cleanup_spawner.spawn(async move {
2221 let mut pending = self;
2222 let result = pending
2223 .try_commit_with(on_committed, false, replaces_predecessor)
2224 .await;
2225 match result {
2226 Ok(witness) => Ok(witness),
2227 Err(commit_error) => match pending.abort().await {
2228 Ok(()) => Err(commit_error),
2229 Err(cleanup_error) => Err(RuntimeDriverError::Internal(format!(
2230 "{commit_error}; exact attachment cleanup also failed: {cleanup_error}"
2231 ))),
2232 },
2233 }
2234 });
2235 completion.await.map_err(|error| {
2236 RuntimeDriverError::Internal(format!(
2237 "owned runtime attachment commit ended without a result: {error}"
2238 ))
2239 })?
2240 }
2241
2242 pub async fn try_commit_with_under_runtime_turn_finalization_boundary<F>(
2255 &mut self,
2256 replaces_predecessor: bool,
2257 on_committed: F,
2258 ) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError>
2259 where
2260 F: FnOnce(&RuntimeExecutorAttachmentWitness) -> Result<(), RuntimeDriverError>,
2261 {
2262 self.try_commit_with(on_committed, false, replaces_predecessor)
2263 .await
2264 }
2265
2266 pub async fn try_commit_with_retained_publication_lease_under_runtime_turn_finalization_boundary(
2271 &mut self,
2272 ) -> Result<CommittedRuntimeExecutorAttachmentPublicationLease, RuntimeDriverError> {
2273 let witness = self.try_commit_with(|_| Ok(()), true, false).await?;
2274 let mutation_guard = self.mutation_guard.take().ok_or_else(|| {
2275 RuntimeDriverError::Internal(
2276 "committed attachment lost its retained publication mutation fence".to_string(),
2277 )
2278 })?;
2279 Ok(CommittedRuntimeExecutorAttachmentPublicationLease {
2282 machine: Arc::clone(&self.machine),
2283 witness,
2284 mutation_guard: Some(mutation_guard),
2285 cleanup_spawner: self.cleanup_spawner.clone(),
2286 should_wake: self.should_wake,
2287 persist_lifecycle_on_commit: self.persist_lifecycle_on_commit,
2288 armed: true,
2289 })
2290 }
2291
2292 async fn try_commit_with<F>(
2293 &mut self,
2294 on_committed: F,
2295 retain_mutation_guard: bool,
2296 replaces_predecessor: bool,
2297 ) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError>
2298 where
2299 F: FnOnce(&RuntimeExecutorAttachmentWitness) -> Result<(), RuntimeDriverError>,
2300 {
2301 let guard = self.mutation_guard.as_ref().ok_or_else(|| {
2302 RuntimeDriverError::Internal(
2303 "pending runtime attachment lost its mutation fence before commit".to_string(),
2304 )
2305 })?;
2306 if retain_mutation_guard && replaces_predecessor {
2307 return Err(RuntimeDriverError::Internal(
2308 "predecessor request terminalization belongs to final serving publication"
2309 .to_string(),
2310 ));
2311 }
2312 let predecessor_request_state = if replaces_predecessor {
2313 let sessions = self.machine.sessions.read().await;
2314 let entry =
2315 sessions
2316 .get(self.witness.session_id())
2317 .ok_or(RuntimeDriverError::NotReady {
2318 state: RuntimeState::Destroyed,
2319 })?;
2320 let exact_pending = entry.epoch_id == self.witness.epoch_id
2321 && matches!(
2322 &entry.attachment_slot,
2323 RuntimeLoopAttachmentSlot::Pending(attachment)
2324 if attachment.id == self.witness.attachment_id
2325 && !attachment.wake_tx.is_closed()
2326 && !attachment.effect_tx.is_closed()
2327 );
2328 if !exact_pending {
2329 return Err(RuntimeDriverError::StaleAuthority {
2330 reason: format!(
2331 "replacement attachment for session {} changed before waiter fencing",
2332 self.witness.session_id()
2333 ),
2334 });
2335 }
2336 Some((
2337 entry.driver.clone(),
2338 entry.completions.clone(),
2339 entry.publication_handle(),
2340 ))
2341 } else {
2342 None
2343 };
2344 let mut predecessor_terminal_publication = None;
2345 if let Some((driver, completions, publication_handle)) = predecessor_request_state {
2346 const REPLACED_REASON: &str =
2347 "executor attachment was replaced before request completion";
2348 let (predecessor_input_ids, candidate_owner_input_id) = {
2349 let mut driver = driver.lock().await;
2350 let predecessor_input_ids = driver.as_driver().active_input_ids();
2351 if predecessor_input_ids.is_empty() {
2352 (predecessor_input_ids, None)
2353 } else {
2354 let prepared = driver.prepare_runless_runtime_terminated_interaction_outboxes(
2359 &predecessor_input_ids,
2360 REPLACED_REASON.to_string(),
2361 )?;
2362 if let Err(error) = driver
2363 .abandon_pending_inputs(crate::input_state::InputAbandonReason::Cancelled)
2364 .await
2365 {
2366 driver.rollback_prepared_runless_interaction_terminal_outboxes(prepared);
2367 return Err(error);
2368 }
2369 let candidate_owner_input_id = crate::meerkat_machine::driver::DriverEntry::commit_prepared_runless_interaction_terminal_outboxes(prepared);
2370 (predecessor_input_ids, candidate_owner_input_id)
2371 }
2372 };
2373 if !predecessor_input_ids.is_empty() {
2374 completions.lock().await.fail_inputs(
2381 predecessor_input_ids.iter().cloned(),
2382 crate::completion::CompletionWaitError::AttachmentReplaced,
2383 );
2384 self.should_wake = false;
2385 if let Some(candidate_owner_input_id) = candidate_owner_input_id {
2386 predecessor_terminal_publication = Some((
2387 driver,
2388 publication_handle,
2389 predecessor_input_ids,
2390 candidate_owner_input_id,
2391 ));
2392 }
2393 }
2394 }
2395 let result = self
2396 .machine
2397 .commit_pending_executor_attachment(
2398 &self.witness,
2399 guard,
2400 self.should_wake,
2401 self.persist_lifecycle_on_commit,
2402 retain_mutation_guard,
2403 on_committed,
2404 )
2405 .await;
2406 if result.is_ok() {
2407 if !retain_mutation_guard {
2408 self.mutation_guard.take();
2409 }
2410 self.armed = false;
2411 if let Some((driver, publication_handle, input_ids, candidate_owner_input_id)) =
2412 predecessor_terminal_publication
2413 {
2414 let _terminal_publication = self.cleanup_spawner.spawn(async move {
2420 let deadline = Instant::now() + std::time::Duration::from_secs(5);
2421 if let Err(error) = crate::control_plane::publish_and_resolve_runless_runtime_termination_before(
2422 &driver,
2423 None,
2424 publication_handle.as_deref(),
2425 &input_ids,
2426 Some(&candidate_owner_input_id),
2427 "executor attachment was replaced before request completion",
2428 Some(deadline),
2429 )
2430 .await
2431 {
2432 tracing::warn!(
2433 %error,
2434 "replacement terminal publication remains durably pending"
2435 );
2436 }
2437 });
2438 }
2439 }
2440 result
2441 }
2442
2443 pub async fn abort(mut self) -> Result<(), RuntimeDriverError> {
2444 let guard = self.mutation_guard.take().ok_or_else(|| {
2445 RuntimeDriverError::Internal(
2446 "pending runtime attachment lost its mutation fence before abort".to_string(),
2447 )
2448 })?;
2449 let machine = Arc::clone(&self.machine);
2450 let witness = self.witness.clone();
2451 let completion = self.cleanup_spawner.spawn(async move {
2452 machine
2453 .abort_pending_executor_attachment(witness, guard)
2454 .await
2455 });
2456 self.armed = false;
2457 completion.await.map_err(|error| {
2458 RuntimeDriverError::Internal(format!(
2459 "owned runtime attachment abort ended without a result: {error}"
2460 ))
2461 })?
2462 }
2463
2464 pub async fn abort_under_runtime_turn_finalization_boundary(
2468 mut self,
2469 ) -> Result<(), RuntimeDriverError> {
2470 self.machine
2471 .complete_executor_attachment_cleanup_under_runtime_turn_boundary(&self.witness)
2472 .await?;
2473 let guard = self.mutation_guard.take().ok_or_else(|| {
2474 RuntimeDriverError::Internal(
2475 "pending runtime attachment lost its mutation fence before boundary-owned abort"
2476 .to_string(),
2477 )
2478 })?;
2479 let machine = Arc::clone(&self.machine);
2480 let witness = self.witness.clone();
2481 let completion = self.cleanup_spawner.spawn(async move {
2482 machine
2483 .abort_pending_executor_attachment(witness, guard)
2484 .await
2485 });
2486 self.armed = false;
2487 completion.await.map_err(|error| {
2488 RuntimeDriverError::Internal(format!(
2489 "owned boundary-aware runtime attachment abort ended without a result: {error}"
2490 ))
2491 })?
2492 }
2493}
2494
2495impl CommittedRuntimeExecutorAttachmentPublicationLease {
2496 pub fn witness(&self) -> &RuntimeExecutorAttachmentWitness {
2497 &self.witness
2498 }
2499
2500 #[doc(hidden)]
2501 pub fn cleanup_task_spawner(&self) -> RuntimeCleanupTaskSpawner {
2502 RuntimeCleanupTaskSpawner {
2503 inner: self.cleanup_spawner.clone(),
2504 }
2505 }
2506
2507 pub async fn abandon_recovered_predecessor_inputs(
2519 &mut self,
2520 ) -> Result<usize, RuntimeDriverError> {
2521 let _guard = self.mutation_guard.as_ref().ok_or_else(|| {
2522 RuntimeDriverError::Internal(
2523 "retained attachment lost its mutation fence before predecessor abandonment"
2524 .to_string(),
2525 )
2526 })?;
2527 let (driver, completions) = {
2528 let sessions = self.machine.sessions.read().await;
2529 let entry = sessions.get(self.witness.session_id()).ok_or_else(|| {
2530 RuntimeDriverError::StaleAuthority {
2531 reason: format!(
2532 "recovered predecessor session {} disappeared before abandonment",
2533 self.witness.session_id()
2534 ),
2535 }
2536 })?;
2537 let exact_pending = entry.epoch_id == self.witness.epoch_id
2538 && matches!(
2539 &entry.attachment_slot,
2540 RuntimeLoopAttachmentSlot::Pending(attachment)
2541 if attachment.id == self.witness.attachment_id
2542 && !attachment.wake_tx.is_closed()
2543 && !attachment.effect_tx.is_closed()
2544 );
2545 if !exact_pending {
2546 return Err(RuntimeDriverError::StaleAuthority {
2547 reason: format!(
2548 "recovered predecessor attachment for session {} changed before abandonment",
2549 self.witness.session_id()
2550 ),
2551 });
2552 }
2553 (entry.driver.clone(), entry.completions.clone())
2554 };
2555
2556 let predecessor_input_ids = {
2557 let mut driver = driver.lock().await;
2558 let predecessor_input_ids = driver.as_driver().active_input_ids();
2559 if predecessor_input_ids.is_empty() {
2560 return Ok(0);
2561 }
2562 let abandoned = driver
2563 .abandon_pending_inputs(crate::input_state::InputAbandonReason::Cancelled)
2564 .await?;
2565 if abandoned != predecessor_input_ids.len() {
2566 return Err(RuntimeDriverError::Internal(format!(
2567 "recovered predecessor abandonment for session {} terminalized {abandoned} of {} active inputs",
2568 self.witness.session_id(),
2569 predecessor_input_ids.len()
2570 )));
2571 }
2572 for input_id in &predecessor_input_ids {
2573 let stored = driver
2574 .as_driver()
2575 .stored_input_state(input_id)
2576 .ok_or_else(|| {
2577 RuntimeDriverError::Internal(format!(
2578 "recovered predecessor input {input_id} disappeared after abandonment"
2579 ))
2580 })?;
2581 if !matches!(
2582 stored.seed.terminal_outcome,
2583 Some(crate::input_state::InputTerminalOutcome::Abandoned {
2584 reason: crate::input_state::InputAbandonReason::Cancelled,
2585 })
2586 ) || stored.state.interaction_terminal_outbox.is_some()
2587 {
2588 return Err(RuntimeDriverError::Internal(format!(
2589 "recovered predecessor input {input_id} did not reach silent Cancelled abandonment"
2590 )));
2591 }
2592 }
2593 predecessor_input_ids
2594 };
2595 let abandoned = predecessor_input_ids.len();
2596 completions.lock().await.fail_inputs(
2597 predecessor_input_ids,
2598 crate::completion::CompletionWaitError::AttachmentReplaced,
2599 );
2600 Ok(abandoned)
2601 }
2602
2603 pub async fn commit_with<F>(
2606 mut self,
2607 on_committed: F,
2608 ) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError>
2609 where
2610 F: FnOnce(&RuntimeExecutorAttachmentWitness) -> Result<(), RuntimeDriverError>,
2611 {
2612 let guard = self.mutation_guard.as_ref().ok_or_else(|| {
2613 RuntimeDriverError::Internal(
2614 "retained attachment publication lost its mutation fence".to_string(),
2615 )
2616 })?;
2617 let witness = self
2618 .machine
2619 .commit_retained_executor_attachment_publication(
2620 &self.witness,
2621 guard,
2622 self.should_wake,
2623 self.persist_lifecycle_on_commit,
2624 move |witness, _| on_committed(witness),
2625 )
2626 .await?;
2627 self.mutation_guard.take();
2628 self.armed = false;
2629 Ok(witness)
2630 }
2631
2632 pub async fn commit_with_member_residency<F>(
2635 &mut self,
2636 residency_update: MemberResidencyUpdate,
2637 incarnation: meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation,
2638 tracked_turn_journal: Option<Arc<dyn crate::member_observation::TrackedTurnJournal>>,
2639 on_committed: F,
2640 ) -> Result<(RuntimeExecutorAttachmentWitness, MemberResidencyPublication), RuntimeDriverError>
2641 where
2642 F: FnOnce(&RuntimeExecutorAttachmentWitness) -> Result<(), RuntimeDriverError>,
2643 {
2644 residency_update.validate_for_retained_attachment(
2645 &self.machine,
2646 &self.witness,
2647 &incarnation,
2648 )?;
2649 let guard = self.mutation_guard.as_ref().ok_or_else(|| {
2650 RuntimeDriverError::Internal(
2651 "retained host publication lost its mutation fence".to_string(),
2652 )
2653 })?;
2654 let mut staged_residency_publication = None;
2655 let commit = self
2656 .machine
2657 .commit_retained_executor_attachment_publication(
2658 &self.witness,
2659 guard,
2660 self.should_wake,
2661 self.persist_lifecycle_on_commit,
2662 |witness, session_mutation_gate| {
2663 on_committed(witness)?;
2664 staged_residency_publication =
2665 Some(residency_update.stage_under_retained_mutation_gate(
2666 incarnation,
2667 tracked_turn_journal,
2668 session_mutation_gate,
2669 )?);
2670 Ok(())
2671 },
2672 )
2673 .await;
2674 let witness = match commit {
2675 Ok(witness) => witness,
2676 Err(error) => {
2677 drop(staged_residency_publication);
2681 return Err(error);
2682 }
2683 };
2684 let residency_publication = staged_residency_publication
2685 .take()
2686 .ok_or_else(|| {
2687 RuntimeDriverError::Internal(format!(
2688 "retained host publication for session {} committed without residency",
2689 self.witness.session_id()
2690 ))
2691 })?
2692 .finalize()?;
2693 self.mutation_guard.take();
2694 self.armed = false;
2695 Ok((witness, residency_publication))
2696 }
2697
2698 pub fn begin_abort(
2707 mut self,
2708 ) -> Result<RuntimeExecutorAttachmentRetirementCompletion, RuntimeDriverError> {
2709 let guard = self.mutation_guard.take().ok_or_else(|| {
2710 RuntimeDriverError::Internal(
2711 "committed publication lease lost its mutation fence before abort".to_string(),
2712 )
2713 })?;
2714 let completion = self
2715 .machine
2716 .spawn_executor_attachment_retirement_with_guard(
2717 self.witness.clone(),
2718 guard,
2719 self.cleanup_spawner.clone(),
2720 );
2721 self.armed = false;
2722 Ok(completion)
2723 }
2724
2725 pub async fn abort_under_runtime_turn_finalization_boundary(
2727 mut self,
2728 ) -> Result<bool, RuntimeDriverError> {
2729 self.machine
2730 .complete_executor_attachment_cleanup_under_runtime_turn_boundary(&self.witness)
2731 .await?;
2732 let guard = self.mutation_guard.take().ok_or_else(|| {
2733 RuntimeDriverError::Internal(
2734 "committed publication lease lost its mutation fence before abort".to_string(),
2735 )
2736 })?;
2737 let machine = Arc::clone(&self.machine);
2738 let witness = self.witness.clone();
2739 let completion = self.cleanup_spawner.spawn(async move {
2740 machine
2741 .unregister_executor_attachment_if_current_with_guard(witness, guard)
2742 .await
2743 });
2744 self.armed = false;
2745 completion.await.map_err(|error| {
2746 RuntimeDriverError::Internal(format!(
2747 "owned committed attachment abort ended without a result: {error}"
2748 ))
2749 })?
2750 }
2751}
2752
2753impl Drop for CommittedRuntimeExecutorAttachmentPublicationLease {
2754 fn drop(&mut self) {
2755 if !self.armed {
2756 return;
2757 }
2758 let Some(guard) = self.mutation_guard.take() else {
2759 return;
2760 };
2761 self.armed = false;
2762 let machine = Arc::clone(&self.machine);
2763 let witness = self.witness.clone();
2764 self.cleanup_spawner.spawn(async move {
2765 if let Err(error) = machine
2766 .unregister_executor_attachment_if_current_with_guard(witness.clone(), guard)
2767 .await
2768 {
2769 tracing::warn!(
2770 session_id = %witness.session_id(),
2771 %error,
2772 "drop-time committed attachment publication abort failed"
2773 );
2774 }
2775 });
2776 }
2777}
2778
2779impl Drop for PendingRuntimeExecutorAttachment {
2780 fn drop(&mut self) {
2781 if !self.armed {
2782 return;
2783 }
2784 let Some(guard) = self.mutation_guard.take() else {
2785 return;
2786 };
2787 self.armed = false;
2788 let machine = Arc::clone(&self.machine);
2789 let witness = self.witness.clone();
2790 self.cleanup_spawner.spawn(async move {
2791 if let Err(error) = machine
2792 .abort_pending_executor_attachment(witness.clone(), guard)
2793 .await
2794 {
2795 tracing::warn!(
2796 session_id = %witness.session_id(),
2797 %error,
2798 "drop-time exact runtime attachment abort failed"
2799 );
2800 }
2801 });
2802 }
2803}
2804
2805impl std::fmt::Debug for PreparedSessionMaterialization {
2806 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2807 formatter
2808 .debug_struct("PreparedSessionMaterialization")
2809 .field("session_id", self.bindings.session_id())
2810 .field("epoch_id", self.bindings.epoch_id())
2811 .field("claim_id", &self.claim_id)
2812 .field("armed", &self.armed)
2813 .finish()
2814 }
2815}
2816
2817impl std::fmt::Debug for PreparedAttachedSessionActorRecovery {
2818 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2819 formatter
2820 .debug_struct("PreparedAttachedSessionActorRecovery")
2821 .field("session_id", self.bindings.session_id())
2822 .field("epoch_id", self.bindings.epoch_id())
2823 .field("witness", &self.witness)
2824 .field("armed", &self.armed)
2825 .finish()
2826 }
2827}
2828
2829impl PreparedAttachedSessionActorRecovery {
2830 fn new(
2831 bindings: meerkat_core::SessionRuntimeBindings,
2832 witness: RuntimeExecutorAttachmentWitness,
2833 claim_id: uuid::Uuid,
2834 claim_state: Arc<std::sync::Mutex<crate::RuntimeActorMaterializationClaimState>>,
2835 previous_phase: crate::RuntimeActorMaterializationClaimPhase,
2836 mutation_guard: crate::tokio::sync::OwnedMutexGuard<()>,
2837 ) -> Self {
2838 Self {
2839 bindings,
2840 witness,
2841 claim_id,
2842 claim_state,
2843 previous_phase,
2844 mutation_guard: Some(mutation_guard),
2845 armed: true,
2846 }
2847 }
2848
2849 pub fn bindings(&self) -> &meerkat_core::SessionRuntimeBindings {
2850 &self.bindings
2851 }
2852
2853 pub fn bindings_clone(&self) -> meerkat_core::SessionRuntimeBindings {
2854 self.bindings.clone()
2855 }
2856
2857 pub fn witness(&self) -> &RuntimeExecutorAttachmentWitness {
2858 &self.witness
2859 }
2860
2861 pub fn commit_actor(&mut self) -> Result<(), RuntimeDriverError> {
2864 if !self.armed {
2865 return Err(RuntimeDriverError::StaleAuthority {
2866 reason: "attached actor-recovery lease is no longer active".to_string(),
2867 });
2868 }
2869 let changed = {
2870 let mut state = self
2871 .claim_state
2872 .lock()
2873 .unwrap_or_else(std::sync::PoisonError::into_inner);
2874 if !state.exact_claim_is(
2875 self.claim_id,
2876 &[crate::RuntimeActorMaterializationClaimPhase::ActorMaterializedPendingCommit],
2877 ) {
2878 return Err(RuntimeDriverError::StaleAuthority {
2879 reason: format!(
2880 "actor recovery for session {} did not retain its exact materialization claim",
2881 self.bindings.session_id()
2882 ),
2883 });
2884 }
2885 state.current = None;
2886 state.phase = crate::RuntimeActorMaterializationClaimPhase::RetainedActor;
2887 Arc::clone(&state.changed)
2888 };
2889 changed.notify_waiters();
2890 self.armed = false;
2891 self.mutation_guard.take();
2892 Ok(())
2893 }
2894}
2895
2896impl Drop for PreparedAttachedSessionActorRecovery {
2897 fn drop(&mut self) {
2898 if !self.armed {
2899 return;
2900 }
2901 let changed = {
2902 let mut state = self
2903 .claim_state
2904 .lock()
2905 .unwrap_or_else(std::sync::PoisonError::into_inner);
2906 if state.current == Some(self.claim_id) {
2907 state.current = None;
2908 state.phase = if state.phase
2909 == crate::RuntimeActorMaterializationClaimPhase::ActorMaterializedPendingCommit
2910 {
2911 crate::RuntimeActorMaterializationClaimPhase::RetainedActor
2912 } else {
2913 self.previous_phase
2914 };
2915 Some(Arc::clone(&state.changed))
2916 } else {
2917 None
2918 }
2919 };
2920 if let Some(changed) = changed {
2921 changed.notify_waiters();
2922 }
2923 self.armed = false;
2924 self.mutation_guard.take();
2925 }
2926}
2927
2928impl PreparedSessionMaterialization {
2929 fn new(
2930 machine: Arc<MeerkatMachine>,
2931 bindings: meerkat_core::SessionRuntimeBindings,
2932 claim_id: uuid::Uuid,
2933 cleanup_spawner: MachineCleanupTaskSpawner,
2934 ) -> Result<Self, RuntimeDriverError> {
2935 let authority = bindings
2936 .__runtime_authority()
2937 .downcast_ref::<crate::SessionRuntimeBindingsAuthority>()
2938 .ok_or_else(|| RuntimeDriverError::ValidationFailed {
2939 reason: "prepared materialization lacks MeerkatMachine authority".to_string(),
2940 })?;
2941 if authority.materialization_claim_id != Some(claim_id) {
2942 return Err(RuntimeDriverError::StaleAuthority {
2943 reason: "prepared materialization binding lost its exact claim".to_string(),
2944 });
2945 }
2946 let claim_state = Arc::clone(&authority.materialization_claim_state);
2947 Ok(Self {
2948 machine,
2949 bindings,
2950 claim_id,
2951 claim_state,
2952 cleanup_spawner,
2953 archived_resume_authorization_issued: false,
2954 armed: true,
2955 })
2956 }
2957
2958 pub fn bindings(&self) -> &meerkat_core::SessionRuntimeBindings {
2959 &self.bindings
2960 }
2961
2962 pub fn bindings_clone(&self) -> meerkat_core::SessionRuntimeBindings {
2963 self.bindings.clone()
2964 }
2965
2966 pub fn session_id(&self) -> &SessionId {
2967 self.bindings.session_id()
2968 }
2969
2970 #[doc(hidden)]
2971 pub fn cleanup_task_spawner(&self) -> RuntimeCleanupTaskSpawner {
2972 RuntimeCleanupTaskSpawner {
2973 inner: self.cleanup_spawner.clone(),
2974 }
2975 }
2976
2977 #[must_use]
2980 pub async fn owns_current_materialization_claim(&self) -> bool {
2981 if !self.armed {
2982 return false;
2983 }
2984 let Some(_mutation_guard) = self
2985 .machine
2986 .lock_current_session_mutation_gate(self.session_id())
2987 .await
2988 else {
2989 return false;
2990 };
2991 let Ok(authority) = crate::validated_session_runtime_bindings_authority(&self.bindings)
2992 else {
2993 return false;
2994 };
2995 let sessions = self.machine.sessions.read().await;
2996 sessions.get(self.session_id()).is_some_and(|entry| {
2997 entry.epoch_id == *self.bindings.epoch_id()
2998 && Arc::ptr_eq(&entry.materialization_claim_state, &self.claim_state)
2999 && Arc::ptr_eq(&entry.dsl_authority, &authority.dsl_authority)
3000 && Arc::ptr_eq(&entry.handle_teardown_gate, &authority.teardown_gate)
3001 && !entry.physical_attachment_is_live()
3002 && self
3003 .claim_state
3004 .lock()
3005 .unwrap_or_else(std::sync::PoisonError::into_inner)
3006 .exact_claim_is(
3007 self.claim_id,
3008 &[
3009 crate::RuntimeActorMaterializationClaimPhase::Prepared,
3010 crate::RuntimeActorMaterializationClaimPhase::Staged,
3011 crate::RuntimeActorMaterializationClaimPhase::ActorCreating,
3012 crate::RuntimeActorMaterializationClaimPhase::ActorMaterializedPendingCommit,
3013 crate::RuntimeActorMaterializationClaimPhase::Aborting,
3014 ],
3015 )
3016 })
3017 }
3018
3019 pub fn archived_resume_authorization(
3027 &mut self,
3028 ) -> Result<ArchivedSessionActorMaterializationAuthorization, RuntimeDriverError> {
3029 if !self.armed {
3030 return Err(RuntimeDriverError::StaleAuthority {
3031 reason: format!(
3032 "prepared materialization for session {} no longer owns archived-resume authority",
3033 self.session_id()
3034 ),
3035 });
3036 }
3037 if self.archived_resume_authorization_issued {
3038 return Err(RuntimeDriverError::StaleAuthority {
3039 reason: format!(
3040 "prepared materialization for session {} already issued its one-shot archived-resume authorization",
3041 self.session_id()
3042 ),
3043 });
3044 }
3045 let authority = self
3046 .bindings
3047 .__runtime_authority()
3048 .downcast_ref::<crate::SessionRuntimeBindingsAuthority>()
3049 .ok_or_else(|| RuntimeDriverError::ValidationFailed {
3050 reason: "prepared archived-resume materialization lacks machine authority"
3051 .to_string(),
3052 })?;
3053 if authority.materialization_claim_id != Some(self.claim_id)
3054 || !Arc::ptr_eq(&authority.materialization_claim_state, &self.claim_state)
3055 {
3056 return Err(RuntimeDriverError::StaleAuthority {
3057 reason: format!(
3058 "prepared archived-resume claim for session {} is no longer exact",
3059 self.session_id()
3060 ),
3061 });
3062 }
3063 let authorization = ArchivedSessionActorMaterializationAuthorization {
3064 machine: Arc::clone(&self.machine),
3065 session_id: self.session_id().clone(),
3066 epoch_id: self.bindings.epoch_id().clone(),
3067 claim_id: self.claim_id,
3068 claim_state: Arc::clone(&self.claim_state),
3069 dsl_authority: Arc::clone(&authority.dsl_authority),
3070 teardown_gate: Arc::clone(&authority.teardown_gate),
3071 };
3072 self.archived_resume_authorization_issued = true;
3073 Ok(authorization)
3074 }
3075
3076 pub async fn acquire_archived_resume_commit_lease(
3081 &self,
3082 ) -> Result<PreparedArchivedResumeCommitLease, RuntimeDriverError> {
3083 if !self.armed || !self.archived_resume_authorization_issued {
3084 return Err(RuntimeDriverError::StaleAuthority {
3085 reason: format!(
3086 "prepared materialization for session {} does not own post-actor archived-resume authority",
3087 self.session_id()
3088 ),
3089 });
3090 }
3091 let mutation_guard = self
3092 .machine
3093 .lock_current_session_mutation_gate(self.session_id())
3094 .await
3095 .ok_or_else(|| RuntimeDriverError::StaleAuthority {
3096 reason: format!(
3097 "archived-resume session {} disappeared before commit lease acquisition",
3098 self.session_id()
3099 ),
3100 })?;
3101 let exact = {
3102 let sessions = self.machine.sessions.read().await;
3103 sessions.get(self.session_id()).is_some_and(|entry| {
3104 entry.epoch_id == *self.bindings.epoch_id()
3105 && Arc::ptr_eq(&entry.materialization_claim_state, &self.claim_state)
3106 && !entry.physical_attachment_is_live()
3107 && matches!(
3108 entry.control_snapshot().phase,
3109 RuntimeState::Retired | RuntimeState::Idle
3110 )
3111 && self
3112 .claim_state
3113 .lock()
3114 .unwrap_or_else(std::sync::PoisonError::into_inner)
3115 .exact_claim_is(
3116 self.claim_id,
3117 &[crate::RuntimeActorMaterializationClaimPhase::ActorMaterializedPendingCommit],
3118 )
3119 })
3120 };
3121 if !exact {
3122 return Err(RuntimeDriverError::StaleAuthority {
3123 reason: format!(
3124 "archived-resume session {} lost its exact quiescent post-actor claim",
3125 self.session_id()
3126 ),
3127 });
3128 }
3129 Ok(PreparedArchivedResumeCommitLease {
3130 machine: Arc::clone(&self.machine),
3131 session_id: self.session_id().clone(),
3132 epoch_id: self.bindings.epoch_id().clone(),
3133 claim_id: self.claim_id,
3134 claim_state: Arc::clone(&self.claim_state),
3135 _mutation_guard: mutation_guard,
3136 })
3137 }
3138
3139 pub async fn ensure_executor_attachment<F>(
3144 &mut self,
3145 executor_factory: F,
3146 ) -> Result<EnsureRuntimeExecutorAttachment, RuntimeDriverError>
3147 where
3148 F: FnOnce(
3149 RuntimeExecutorAttachmentWitness,
3150 ) -> Box<dyn meerkat_core::lifecycle::CoreExecutor>
3151 + Send
3152 + 'static,
3153 {
3154 self.ensure_executor_attachment_with_boundary_mode(executor_factory, false)
3155 .await
3156 }
3157
3158 #[doc(hidden)]
3165 pub async fn ensure_executor_attachment_under_runtime_turn_finalization_boundary<F>(
3166 &mut self,
3167 executor_factory: F,
3168 ) -> Result<EnsureRuntimeExecutorAttachment, RuntimeDriverError>
3169 where
3170 F: FnOnce(
3171 RuntimeExecutorAttachmentWitness,
3172 ) -> Box<dyn meerkat_core::lifecycle::CoreExecutor>
3173 + Send
3174 + 'static,
3175 {
3176 self.ensure_executor_attachment_with_boundary_mode(executor_factory, true)
3177 .await
3178 }
3179
3180 async fn ensure_executor_attachment_with_boundary_mode<F>(
3181 &mut self,
3182 executor_factory: F,
3183 turn_finalization_boundary_already_held: bool,
3184 ) -> Result<EnsureRuntimeExecutorAttachment, RuntimeDriverError>
3185 where
3186 F: FnOnce(
3187 RuntimeExecutorAttachmentWitness,
3188 ) -> Box<dyn meerkat_core::lifecycle::CoreExecutor>
3189 + Send
3190 + 'static,
3191 {
3192 if !self.armed {
3193 return Err(RuntimeDriverError::StaleAuthority {
3194 reason: format!(
3195 "prepared materialization for session {} no longer owns attachment authority",
3196 self.session_id()
3197 ),
3198 });
3199 }
3200 let expected_claim = RuntimeExecutorAttachmentMaterializationClaim {
3201 claim_id: self.claim_id,
3202 claim_state: Arc::clone(&self.claim_state),
3203 epoch_id: self.bindings.epoch_id().clone(),
3204 };
3205 let outcome = self
3206 .machine
3207 .ensure_session_with_executor_factory_for_materialization(
3208 self.session_id().clone(),
3209 expected_claim,
3210 turn_finalization_boundary_already_held,
3211 executor_factory,
3212 )
3213 .await?;
3214 if matches!(&outcome, EnsureRuntimeExecutorAttachment::Pending(_)) {
3215 self.armed = false;
3219 }
3220 Ok(outcome)
3221 }
3222
3223 fn request_abort(&self) -> bool {
3224 let mut state = self
3225 .claim_state
3226 .lock()
3227 .unwrap_or_else(std::sync::PoisonError::into_inner);
3228 if !state.exact_claim_is(
3229 self.claim_id,
3230 &[
3231 crate::RuntimeActorMaterializationClaimPhase::Prepared,
3232 crate::RuntimeActorMaterializationClaimPhase::Staged,
3233 crate::RuntimeActorMaterializationClaimPhase::ActorCreating,
3234 crate::RuntimeActorMaterializationClaimPhase::ActorMaterializedPendingCommit,
3235 crate::RuntimeActorMaterializationClaimPhase::Aborting,
3236 ],
3237 ) {
3238 return false;
3239 }
3240 state.phase = crate::RuntimeActorMaterializationClaimPhase::Aborting;
3241 true
3242 }
3243
3244 pub async fn rollback_now(&mut self) -> Result<bool, RuntimeDriverError> {
3248 self.rollback_now_with_turn_finalization_boundary(false)
3249 .await
3250 }
3251
3252 pub async fn rollback_now_under_turn_finalization_boundary(
3255 &mut self,
3256 ) -> Result<bool, RuntimeDriverError> {
3257 self.rollback_now_with_turn_finalization_boundary(true)
3258 .await
3259 }
3260
3261 async fn rollback_now_with_turn_finalization_boundary(
3262 &mut self,
3263 turn_finalization_boundary_already_held: bool,
3264 ) -> Result<bool, RuntimeDriverError> {
3265 if !self.armed {
3266 return Ok(false);
3267 }
3268 if !self.request_abort() {
3269 self.armed = false;
3270 return Ok(false);
3271 }
3272 let rolled_back = self
3273 .machine
3274 .abort_prepared_session_materialization_claim(
3275 self.bindings.session_id(),
3276 self.claim_id,
3277 Some(self.bindings.epoch_id()),
3278 Some(&self.claim_state),
3279 turn_finalization_boundary_already_held,
3280 )
3281 .await?;
3282 self.armed = false;
3283 Ok(rolled_back)
3284 }
3285
3286 pub async fn commit_staged(&mut self) -> Result<(), RuntimeDriverError> {
3290 self.machine
3291 .commit_prepared_session_materialization_staged(&self.bindings)
3292 .await?;
3293 self.armed = false;
3294 Ok(())
3295 }
3296
3297 pub async fn commit_actor_unattached(&mut self) -> Result<(), RuntimeDriverError> {
3301 self.machine
3302 .commit_prepared_session_actor_unattached(&self.bindings)
3303 .await?;
3304 self.armed = false;
3305 Ok(())
3306 }
3307}
3308
3309impl Drop for PreparedSessionMaterialization {
3310 fn drop(&mut self) {
3311 if !self.armed || !self.request_abort() {
3312 return;
3313 }
3314 let machine = Arc::clone(&self.machine);
3315 let session_id = self.bindings.session_id().clone();
3316 let epoch_id = self.bindings.epoch_id().clone();
3317 let claim_id = self.claim_id;
3318 let claim_state = Arc::clone(&self.claim_state);
3319 self.cleanup_spawner.spawn(async move {
3320 if let Err(error) = machine
3321 .abort_prepared_session_materialization_claim(
3322 &session_id,
3323 claim_id,
3324 Some(&epoch_id),
3325 Some(&claim_state),
3326 false,
3327 )
3328 .await
3329 {
3330 tracing::warn!(
3331 %session_id,
3332 %error,
3333 "drop-time exact prepared materialization rollback failed"
3334 );
3335 }
3336 });
3337 }
3338}
3339
3340pub(super) struct PendingPreparedMaterialization {
3341 machine: Arc<MeerkatMachine>,
3342 session_id: SessionId,
3343 claim_id: uuid::Uuid,
3344 cleanup_spawner: MachineCleanupTaskSpawner,
3345 claim_state_slot: Arc<
3346 std::sync::Mutex<
3347 Option<Arc<std::sync::Mutex<crate::RuntimeActorMaterializationClaimState>>>,
3348 >,
3349 >,
3350 armed: bool,
3351}
3352
3353impl PendingPreparedMaterialization {
3354 pub(super) fn new(
3355 machine: Arc<MeerkatMachine>,
3356 session_id: SessionId,
3357 claim_id: uuid::Uuid,
3358 ) -> Result<Self, RuntimeDriverError> {
3359 Ok(Self {
3360 machine,
3361 session_id,
3362 claim_id,
3363 cleanup_spawner: MachineCleanupTaskSpawner::acquire()?,
3364 claim_state_slot: Arc::new(std::sync::Mutex::new(None)),
3365 armed: true,
3366 })
3367 }
3368
3369 fn cleanup_spawner(&self) -> MachineCleanupTaskSpawner {
3370 self.cleanup_spawner.clone()
3371 }
3372
3373 pub(super) fn claim_state_slot(
3374 &self,
3375 ) -> Arc<
3376 std::sync::Mutex<
3377 Option<Arc<std::sync::Mutex<crate::RuntimeActorMaterializationClaimState>>>,
3378 >,
3379 > {
3380 Arc::clone(&self.claim_state_slot)
3381 }
3382
3383 pub(super) fn disarm(&mut self) {
3384 self.armed = false;
3385 }
3386}
3387
3388impl Drop for PendingPreparedMaterialization {
3389 fn drop(&mut self) {
3390 if !self.armed {
3391 return;
3392 }
3393 let claim_state = self
3394 .claim_state_slot
3395 .lock()
3396 .unwrap_or_else(std::sync::PoisonError::into_inner)
3397 .clone();
3398 if let Some(claim_state) = claim_state {
3399 let mut state = claim_state
3400 .lock()
3401 .unwrap_or_else(std::sync::PoisonError::into_inner);
3402 if state.current == Some(self.claim_id)
3403 && state.phase != crate::RuntimeActorMaterializationClaimPhase::RetainedActor
3404 {
3405 state.phase = crate::RuntimeActorMaterializationClaimPhase::Aborting;
3406 }
3407 }
3408 let machine = Arc::clone(&self.machine);
3409 let session_id = self.session_id.clone();
3410 let claim_id = self.claim_id;
3411 self.cleanup_spawner.spawn(async move {
3412 if let Err(error) = machine
3413 .abort_prepared_session_materialization_claim(
3414 &session_id,
3415 claim_id,
3416 None,
3417 None,
3418 false,
3419 )
3420 .await
3421 {
3422 tracing::warn!(
3423 %session_id,
3424 %error,
3425 "cancelled binding preparation exact rollback failed"
3426 );
3427 }
3428 });
3429 }
3430}
3431
3432pub struct MachineServiceTurnCommitLease {
3440 session_id: SessionId,
3441 driver: SharedDriver,
3442 _mutation_guard: crate::tokio::sync::OwnedMutexGuard<()>,
3443}
3444
3445pub struct MachineServiceTurnIdentity {
3449 session_id: SessionId,
3450 driver: SharedDriver,
3451}
3452
3453impl MachineSessionArchiveLease {
3454 pub fn owns_recovered_registration(&self) -> bool {
3457 self.recovered_registration_for_archive
3458 }
3459
3460 pub async fn runtime_state(&self) -> RuntimeState {
3463 self.driver.lock().await.runtime_state()
3464 }
3465
3466 pub async fn runtime_is_retired(&self) -> bool {
3470 self.runtime_state().await == RuntimeState::Retired
3471 }
3472}
3473
3474#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3479struct RuntimeLoopAttachmentId(uuid::Uuid);
3480
3481impl RuntimeLoopAttachmentId {
3482 fn new() -> Self {
3483 Self(uuid::Uuid::new_v4())
3484 }
3485}
3486
3487struct RuntimeEffectDispatchAttachmentWitness {
3491 mutation_gate: Arc<Mutex<()>>,
3492 driver: SharedDriver,
3493 dsl_authority: Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
3494 attachment_id: RuntimeLoopAttachmentId,
3495 effect_tx: mpsc::Sender<crate::effect::RuntimeEffect>,
3496}
3497
3498struct RuntimeLiveBoundaryAttachmentWitness {
3502 mutation_gate: Arc<Mutex<()>>,
3503 driver: SharedDriver,
3504 dsl_authority: Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
3505 attachment_id: RuntimeLoopAttachmentId,
3506 boundary_handle: Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>,
3507}
3508
3509struct RuntimeAcceptedBoundaryCancelPlan {
3510 witness: RuntimeEffectDispatchAttachmentWitness,
3511 boundary_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>>,
3512 pending_dispatch: PendingBoundaryCancelDispatchGuard,
3513 expected_run_id: RunId,
3514 projected_effect: crate::effect::ProjectedRuntimeEffect,
3515 dispatch_generation: u64,
3516 dispatch_lifecycle_phase: dsl::MeerkatPhase,
3517}
3518
3519#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3520enum BoundaryCancelDispatchState {
3521 Current,
3522 ConsumedConverged,
3523 SameGenerationOrphan,
3524 Superseded,
3525}
3526
3527struct RuntimeEffectDispatchMemberAuthority {
3528 lease: MemberEffectAuthorityLease,
3529 expected_member: Option<meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation>,
3530}
3531
3532struct PendingBoundaryCancelDispatchGuard {
3535 dsl_authority: Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
3536 dispatch_generation: u64,
3537 armed: bool,
3538}
3539
3540impl PendingBoundaryCancelDispatchGuard {
3541 fn new(
3542 dsl_authority: Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
3543 dispatch_generation: u64,
3544 ) -> Self {
3545 Self {
3546 dsl_authority,
3547 dispatch_generation,
3548 armed: true,
3549 }
3550 }
3551
3552 fn disarm(&mut self) {
3553 self.armed = false;
3554 }
3555}
3556
3557impl Drop for PendingBoundaryCancelDispatchGuard {
3558 fn drop(&mut self) {
3559 if self.armed
3560 && let Err(error) = MeerkatMachine::abort_dsl_boundary_cancel_dispatch_if_current(
3561 &self.dsl_authority,
3562 self.dispatch_generation,
3563 )
3564 {
3565 tracing::error!(
3566 dispatch_generation = self.dispatch_generation,
3567 error = %error,
3568 "failed to compensate an abandoned boundary-cancel dispatch"
3569 );
3570 }
3571 }
3572}
3573
3574struct AcceptedIngressFallbackWakeGuard {
3577 wake_tx: Option<mpsc::Sender<()>>,
3578 armed: bool,
3579}
3580
3581impl AcceptedIngressFallbackWakeGuard {
3582 fn new(wake_tx: Option<mpsc::Sender<()>>, armed: bool) -> Self {
3583 Self { wake_tx, armed }
3584 }
3585
3586 fn disarm(&mut self) {
3587 self.armed = false;
3588 }
3589}
3590
3591impl Drop for AcceptedIngressFallbackWakeGuard {
3592 fn drop(&mut self) {
3593 if self.armed
3594 && let Some(wake_tx) = self.wake_tx.as_ref()
3595 {
3596 let _ = wake_tx.try_send(());
3597 }
3598 }
3599}
3600
3601struct RuntimeLoopAttachment {
3602 id: RuntimeLoopAttachmentId,
3603 wake_tx: mpsc::Sender<()>,
3604 effect_tx: mpsc::Sender<crate::effect::RuntimeEffect>,
3605 serving_release: Option<crate::runtime_loop::RuntimeLoopServingRelease>,
3606 boundary_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>>,
3607 interrupt_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorInterruptHandle>>,
3608 loop_handle: tokio::task::JoinHandle<()>,
3609}
3610
3611enum RuntimeLoopAttachmentSlot {
3613 Empty,
3614 Pending(RuntimeLoopAttachment),
3615 Attached(RuntimeLoopAttachment),
3616}
3617
3618impl RuntimeSessionEntry {
3619 fn dsl_mutation_blocked_by_unregister(
3620 &self,
3621 session_id: &SessionId,
3622 ) -> Option<RuntimeDriverError> {
3623 if self.pending_unregister_finalization.is_some() {
3624 return Some(RuntimeDriverError::UnregisterFinalizationOutcomeUnknown {
3625 reason: format!(
3626 "session {session_id} retains an ambiguous unregister finalization; retry unregister before applying any other lifecycle mutation"
3627 ),
3628 });
3629 }
3630 self.handle_teardown_gate
3631 .is_closed()
3632 .then(|| RuntimeDriverError::ValidationFailed {
3633 reason: format!("session {session_id} is a teardown-only unregister retry anchor"),
3634 })
3635 }
3636
3637 fn registration_blocked_by_unregister(
3638 &self,
3639 session_id: &SessionId,
3640 ) -> Option<RuntimeDriverError> {
3641 if let Some(error) = self.dsl_mutation_blocked_by_unregister(session_id) {
3642 return Some(error);
3643 }
3644 if self.unregister_coordinator.is_some() {
3645 return Some(RuntimeDriverError::NotReady {
3646 state: self.control_snapshot().phase,
3647 });
3648 }
3649 let Some(coordinator) = self.runtime_stop_cleanup_coordinator.as_ref() else {
3650 return None;
3651 };
3652 match coordinator.result_rx.borrow().clone() {
3653 None => Some(RuntimeDriverError::RuntimeStopInProgress {
3654 runtime_id: self.runtime_id.clone(),
3655 }),
3656 Some(Ok(())) => None,
3657 Some(Err(error)) => Some(error),
3658 }
3659 }
3660
3661 fn control_snapshot(&self) -> crate::driver::ephemeral::RuntimeControlProjection {
3662 self.control_projection
3663 .read()
3664 .map(|guard| guard.clone())
3665 .unwrap_or_else(|poisoned| {
3666 tracing::error!("runtime control projection lock poisoned");
3667 poisoned.into_inner().clone()
3668 })
3669 }
3670
3671 fn sync_control_projection_from_dsl_authority(&self) {
3672 let next = {
3673 let authority = self
3674 .dsl_authority
3675 .lock()
3676 .unwrap_or_else(std::sync::PoisonError::into_inner);
3677 crate::driver::ephemeral::RuntimeControlProjection {
3678 phase: crate::meerkat_machine::dsl_authority::runtime_phase_from_authority(
3679 &authority,
3680 ),
3681 current_run_id:
3682 crate::meerkat_machine::dsl_authority::current_run_id_from_authority(&authority),
3683 pre_run_phase: crate::meerkat_machine::dsl_authority::pre_run_phase_from_authority(
3684 &authority,
3685 ),
3686 }
3687 };
3688 *self
3689 .control_projection
3690 .write()
3691 .unwrap_or_else(std::sync::PoisonError::into_inner) = next;
3692 }
3693
3694 fn physical_attachment_is_live(&self) -> bool {
3695 match &self.attachment_slot {
3696 RuntimeLoopAttachmentSlot::Pending(attachment)
3697 | RuntimeLoopAttachmentSlot::Attached(attachment) => {
3698 !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed()
3699 }
3700 RuntimeLoopAttachmentSlot::Empty => false,
3701 }
3702 }
3703
3704 fn attachment_is_live(&self) -> bool {
3705 matches!(
3706 &self.attachment_slot,
3707 RuntimeLoopAttachmentSlot::Attached(attachment)
3708 if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed()
3709 )
3710 }
3711
3712 fn generated_executor_registration_active(&self) -> bool {
3713 let authority = self
3714 .dsl_authority
3715 .lock()
3716 .unwrap_or_else(std::sync::PoisonError::into_inner);
3717 matches!(
3718 authority.state().registration_phase,
3719 dsl::RegistrationPhase::Active
3720 )
3721 }
3722
3723 fn generated_executor_registration_has_viable_attachment(&self) -> bool {
3724 self.generated_executor_registration_active()
3725 && match &self.attachment_slot {
3726 RuntimeLoopAttachmentSlot::Empty => true,
3727 RuntimeLoopAttachmentSlot::Pending(_) | RuntimeLoopAttachmentSlot::Attached(_) => {
3728 self.physical_attachment_is_live()
3729 }
3730 }
3731 }
3732
3733 fn close_handle_teardown_gate(&self) {
3734 let _guard = self
3735 .dsl_authority
3736 .lock()
3737 .unwrap_or_else(std::sync::PoisonError::into_inner);
3738 self.handle_teardown_gate.close();
3739 }
3740
3741 fn generated_executor_registration_active_or_draining(&self) -> bool {
3749 let authority = self
3750 .dsl_authority
3751 .lock()
3752 .unwrap_or_else(std::sync::PoisonError::into_inner);
3753 matches!(
3754 authority.state().registration_phase,
3755 dsl::RegistrationPhase::Active | dsl::RegistrationPhase::Draining
3756 )
3757 }
3758
3759 fn generated_service_turn_binding_open(&self, session_id: &SessionId) -> bool {
3760 let authority = self
3761 .dsl_authority
3762 .lock()
3763 .unwrap_or_else(std::sync::PoisonError::into_inner);
3764 let state = authority.state();
3765 self.handle_teardown_gate.is_open()
3766 && state.session_id.as_ref() == Some(&dsl::SessionId::from_domain(session_id))
3767 && state.registration_phase != dsl::RegistrationPhase::Draining
3768 }
3769
3770 fn generated_stop_deferred(&self) -> bool {
3771 self.dsl_authority
3772 .lock()
3773 .unwrap_or_else(std::sync::PoisonError::into_inner)
3774 .state()
3775 .runtime_stop_deferred
3776 }
3777
3778 fn stage_generated_executor_registration_claim(
3779 &self,
3780 session_id: &SessionId,
3781 ) -> Result<StagedSessionDslInput, String> {
3782 let staged = MeerkatMachine::stage_dsl_transition_on_authority(
3783 &self.dsl_authority,
3784 dsl::MeerkatMachineInput::EnsureSessionWithExecutor {
3785 session_id: dsl::SessionId::from_domain(session_id),
3786 },
3787 "EnsureSessionWithExecutor",
3788 )?;
3789 if self.generated_executor_registration_active() {
3790 Ok(staged)
3791 } else {
3792 let mut authority = self
3793 .dsl_authority
3794 .lock()
3795 .unwrap_or_else(std::sync::PoisonError::into_inner);
3796 authority.restore_snapshot(staged.previous_snapshot);
3797 Err("generated MeerkatMachine did not grant active executor registration".into())
3798 }
3799 }
3800
3801 fn stage_generated_executor_exit_observation(&self) -> Result<StagedSessionDslInput, String> {
3802 MeerkatMachine::stage_runtime_owner_dsl_transition_on_authority(
3803 &self.dsl_authority,
3804 crate::meerkat_machine_types::MeerkatMachineFieldlessRuntimeInternalInput::RuntimeExecutorExited,
3805 )
3806 }
3807
3808 fn has_live_attachment(&self) -> bool {
3811 self.attachment_is_live()
3812 }
3813
3814 #[allow(clippy::too_many_arguments)]
3817 fn attach_runtime_loop(
3818 &mut self,
3819 id: RuntimeLoopAttachmentId,
3820 wake_tx: mpsc::Sender<()>,
3821 effect_tx: mpsc::Sender<crate::effect::RuntimeEffect>,
3822 serving_release: crate::runtime_loop::RuntimeLoopServingRelease,
3823 boundary_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>>,
3824 interrupt_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorInterruptHandle>>,
3825 publication_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorPublicationHandle>>,
3826 post_stop_cleanup_handle: Option<
3827 Arc<dyn meerkat_core::lifecycle::CoreExecutorPostStopCleanupHandle>,
3828 >,
3829 machine_managed_post_stop_unregister: bool,
3830 expected_materialization_claim: Option<&RuntimeExecutorAttachmentMaterializationClaim>,
3831 spawned_loop: crate::runtime_loop::SpawnedRuntimeLoop,
3832 ) -> Result<(), crate::runtime_loop::SpawnedRuntimeLoop> {
3833 if spawned_loop.startup_authority_transfer.is_some()
3834 || spawned_loop.serving_release.is_some()
3835 {
3836 return Err(spawned_loop);
3837 }
3838 let claim_changed = {
3839 let mut state = self
3840 .materialization_claim_state
3841 .lock()
3842 .unwrap_or_else(std::sync::PoisonError::into_inner);
3843 match expected_materialization_claim {
3844 Some(expected)
3845 if Arc::ptr_eq(
3846 &self.materialization_claim_state,
3847 &expected.claim_state,
3848 ) && state.exact_claim_is(
3849 expected.claim_id,
3850 &[crate::RuntimeActorMaterializationClaimPhase::ActorMaterializedPendingCommit],
3851 ) => {}
3852 Some(_) => return Err(spawned_loop),
3853 None
3854 if state.current.is_none()
3855 && matches!(
3856 state.phase,
3857 crate::RuntimeActorMaterializationClaimPhase::Vacant
3858 | crate::RuntimeActorMaterializationClaimPhase::RetainedActor
3859 ) => {}
3860 None => return Err(spawned_loop),
3861 }
3862 let Some(next_legacy_generation) = state.legacy_capability_generation.checked_add(1)
3863 else {
3864 return Err(spawned_loop);
3865 };
3866 state.legacy_capability_generation = next_legacy_generation;
3867 let claim_changed = state.current.take().is_some();
3868 state.phase = crate::RuntimeActorMaterializationClaimPhase::Vacant;
3869 state.rollback_registration_available = false;
3870 claim_changed.then(|| Arc::clone(&state.changed))
3871 };
3872 if let Some(changed) = claim_changed {
3873 changed.notify_waiters();
3874 }
3875 let crate::runtime_loop::SpawnedRuntimeLoop {
3876 loop_handle,
3877 teardown_slot,
3878 startup: _,
3879 startup_authority_transfer: _,
3880 serving_release: _,
3881 } = spawned_loop;
3882 self.provisional_interrupt_handle = None;
3883 self.provisional_materialization_claim_id = None;
3884 self.runtime_stop_cleanup_coordinator = None;
3885 self.runtime_loop_teardown = Some(Arc::clone(&teardown_slot));
3886 self.publication_handle = publication_handle;
3887 self.post_stop_cleanup_handle = post_stop_cleanup_handle;
3888 self.post_stop_cleanup_attachment_id = machine_managed_post_stop_unregister.then_some(id);
3889 self.post_stop_cleanup_complete = !machine_managed_post_stop_unregister;
3890 self.post_stop_cleanup_gate = Arc::new(Mutex::new(()));
3891 self.attachment_slot = RuntimeLoopAttachmentSlot::Pending(RuntimeLoopAttachment {
3892 id,
3893 wake_tx,
3894 effect_tx,
3895 serving_release: Some(serving_release),
3896 boundary_handle,
3897 interrupt_handle,
3898 loop_handle,
3899 });
3900 Ok(())
3901 }
3902
3903 fn owns_runtime_loop_attachment(&self, expected: RuntimeLoopAttachmentId) -> bool {
3904 matches!(
3905 &self.attachment_slot,
3906 RuntimeLoopAttachmentSlot::Pending(attachment)
3907 | RuntimeLoopAttachmentSlot::Attached(attachment)
3908 if attachment.id == expected
3909 )
3910 }
3911
3912 fn live_attachment_id(&self) -> Option<RuntimeLoopAttachmentId> {
3913 match &self.attachment_slot {
3914 RuntimeLoopAttachmentSlot::Pending(attachment)
3915 | RuntimeLoopAttachmentSlot::Attached(attachment)
3916 if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed() =>
3917 {
3918 Some(attachment.id)
3919 }
3920 _ => None,
3921 }
3922 }
3923
3924 fn take_runtime_loop_attachment(&mut self) -> Option<RuntimeLoopAttachment> {
3932 match std::mem::replace(&mut self.attachment_slot, RuntimeLoopAttachmentSlot::Empty) {
3933 RuntimeLoopAttachmentSlot::Pending(attachment) => Some(attachment),
3934 RuntimeLoopAttachmentSlot::Attached(attachment) => Some(attachment),
3935 RuntimeLoopAttachmentSlot::Empty => None,
3936 }
3937 }
3938
3939 fn clear_dead_attachment(&mut self) -> bool {
3940 if matches!(
3941 self.attachment_slot,
3942 RuntimeLoopAttachmentSlot::Pending(_) | RuntimeLoopAttachmentSlot::Attached(_)
3943 ) && !self.physical_attachment_is_live()
3944 {
3945 self.attachment_slot = RuntimeLoopAttachmentSlot::Empty;
3946 return true;
3947 }
3948 false
3949 }
3950
3951 fn retire_completed_runtime_stop_after_revival(
3952 &mut self,
3953 session_id: &SessionId,
3954 ) -> Result<(), RuntimeDriverError> {
3955 if !matches!(self.attachment_slot, RuntimeLoopAttachmentSlot::Empty) {
3956 return Err(RuntimeDriverError::Internal(format!(
3957 "revived session {session_id} still carries a runtime-loop attachment"
3958 )));
3959 }
3960 match self.runtime_stop_cleanup_coordinator.as_ref() {
3961 None if self.runtime_loop_teardown.is_none() => return Ok(()),
3962 None => {
3963 return Err(RuntimeDriverError::Internal(format!(
3964 "revived session {session_id} carries a teardown slot without its stop coordinator"
3965 )));
3966 }
3967 Some(coordinator) => {
3968 let coordinator_slot_is_current = match (
3969 coordinator.teardown_slot.as_ref(),
3970 self.runtime_loop_teardown.as_ref(),
3971 ) {
3972 (Some(coordinator_slot), Some(current_slot)) => {
3973 Arc::ptr_eq(coordinator_slot, current_slot)
3974 }
3975 (None, None) => true,
3976 _ => false,
3977 };
3978 if !coordinator_slot_is_current {
3979 return Err(RuntimeDriverError::Internal(format!(
3980 "revived session {session_id} carries a stale runtime-stop teardown owner"
3981 )));
3982 }
3983 match coordinator.result_rx.borrow().clone() {
3984 Some(Ok(())) => {}
3985 None => {
3986 return Err(RuntimeDriverError::RuntimeStopInProgress {
3987 runtime_id: self.runtime_id.clone(),
3988 });
3989 }
3990 Some(Err(error)) => return Err(error),
3991 }
3992 }
3993 }
3994 self.runtime_stop_cleanup_coordinator = None;
3995 self.runtime_loop_teardown = None;
3996 Ok(())
3997 }
3998
3999 fn wake_sender(&self) -> Option<mpsc::Sender<()>> {
4000 match &self.attachment_slot {
4001 RuntimeLoopAttachmentSlot::Attached(attachment)
4002 if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed() =>
4003 {
4004 Some(attachment.wake_tx.clone())
4005 }
4006 _ => None,
4007 }
4008 }
4009
4010 fn effect_sender(&self) -> Option<mpsc::Sender<crate::effect::RuntimeEffect>> {
4011 match &self.attachment_slot {
4012 RuntimeLoopAttachmentSlot::Attached(attachment)
4013 if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed() =>
4014 {
4015 Some(attachment.effect_tx.clone())
4016 }
4017 _ => None,
4018 }
4019 }
4020
4021 fn boundary_handle(
4022 &self,
4023 ) -> Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>> {
4024 match &self.attachment_slot {
4025 RuntimeLoopAttachmentSlot::Attached(attachment)
4026 if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed() =>
4027 {
4028 attachment.boundary_handle.clone()
4029 }
4030 _ => None,
4031 }
4032 }
4033
4034 fn interrupt_handle(
4035 &self,
4036 ) -> Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorInterruptHandle>> {
4037 match &self.attachment_slot {
4038 RuntimeLoopAttachmentSlot::Attached(attachment)
4039 if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed() =>
4040 {
4041 attachment.interrupt_handle.clone()
4042 }
4043 _ => self.provisional_interrupt_handle.clone(),
4044 }
4045 }
4046
4047 fn publication_handle(
4048 &self,
4049 ) -> Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorPublicationHandle>> {
4050 self.publication_handle.clone()
4051 }
4052
4053 fn install_provisional_interrupt_handle(
4054 &mut self,
4055 claim_id: uuid::Uuid,
4056 handle: Arc<dyn meerkat_core::lifecycle::CoreExecutorInterruptHandle>,
4057 ) {
4058 if !self.physical_attachment_is_live() {
4059 self.provisional_materialization_claim_id = Some(claim_id);
4060 self.provisional_interrupt_handle = Some(handle);
4061 }
4062 }
4063
4064 fn install_provisional_post_stop_cleanup_handle(
4065 &mut self,
4066 claim_id: uuid::Uuid,
4067 handle: Arc<dyn meerkat_core::lifecycle::CoreExecutorPostStopCleanupHandle>,
4068 ) {
4069 if !self.physical_attachment_is_live() {
4070 self.provisional_materialization_claim_id = Some(claim_id);
4071 self.post_stop_cleanup_handle = Some(handle);
4072 self.post_stop_cleanup_attachment_id = Some(RuntimeLoopAttachmentId::new());
4073 self.post_stop_cleanup_complete = false;
4074 self.post_stop_cleanup_gate = Arc::new(Mutex::new(()));
4075 }
4076 }
4077}
4078
4079impl MeerkatMachine {
4080 #[cfg(test)]
4081 pub(crate) async fn model_routing_handle_for_test(
4082 &self,
4083 session_id: &SessionId,
4084 ) -> Option<Arc<crate::handles::RuntimeModelRoutingHandle>> {
4085 let (dsl_authority, visibility_owner) = {
4086 let sessions = self.sessions.read().await;
4087 let entry = sessions.get(session_id)?;
4088 (
4089 Arc::clone(&entry.dsl_authority),
4090 Arc::clone(&entry.tool_visibility_owner),
4091 )
4092 };
4093 Some(Arc::new(
4094 crate::handles::RuntimeModelRoutingHandle::new_with_visibility_owner(
4095 Arc::new(crate::handles::HandleDslAuthority::from_shared(
4096 dsl_authority,
4097 )),
4098 visibility_owner,
4099 ),
4100 ))
4101 }
4102
4103 async fn session_mutation_gate(&self, session_id: &SessionId) -> Option<Arc<Mutex<()>>> {
4109 let sessions = self.sessions.read().await;
4110 sessions
4111 .get(session_id)
4112 .map(|entry| Arc::clone(&entry.mutation_gate))
4113 }
4114
4115 async fn lock_current_session_mutation_gate(
4116 &self,
4117 session_id: &SessionId,
4118 ) -> Option<crate::tokio::sync::OwnedMutexGuard<()>> {
4119 loop {
4120 let gate = self.session_mutation_gate(session_id).await?;
4121 let gate_guard = Arc::clone(&gate).lock_owned().await;
4122 let sessions = self.sessions.read().await;
4123 let entry = sessions.get(session_id)?;
4124 if Arc::ptr_eq(&entry.mutation_gate, &gate) {
4125 return Some(gate_guard);
4126 }
4127 }
4128 }
4129
4130 #[cfg(any(test, feature = "test-support"))]
4134 #[doc(hidden)]
4135 pub fn arm_runtime_loop_before_queue_authority_test_hook(
4136 &self,
4137 session_id: SessionId,
4138 ) -> (
4139 crate::tokio::sync::oneshot::Receiver<()>,
4140 crate::tokio::sync::oneshot::Sender<()>,
4141 ) {
4142 let (entered_tx, entered_rx) = crate::tokio::sync::oneshot::channel();
4143 let (release_tx, release_rx) = crate::tokio::sync::oneshot::channel();
4144 let mut hook = self
4145 .test_runtime_loop_before_queue_authority
4146 .lock()
4147 .unwrap_or_else(std::sync::PoisonError::into_inner);
4148 assert!(
4149 hook.is_none(),
4150 "runtime-loop queue-authority test hook already armed"
4151 );
4152 *hook = Some((session_id, entered_tx, release_rx));
4153 (entered_rx, release_tx)
4154 }
4155
4156 #[cfg(any(test, feature = "test-support"))]
4157 pub(crate) async fn run_runtime_loop_before_queue_authority_test_hook(
4158 &self,
4159 session_id: &SessionId,
4160 ) {
4161 let armed = {
4162 let mut hook = self
4163 .test_runtime_loop_before_queue_authority
4164 .lock()
4165 .unwrap_or_else(std::sync::PoisonError::into_inner);
4166 if hook
4167 .as_ref()
4168 .is_some_and(|(armed_session_id, _, _)| armed_session_id == session_id)
4169 {
4170 hook.take()
4171 } else {
4172 None
4173 }
4174 };
4175 if let Some((_, entered_tx, release_rx)) = armed {
4176 let _ = entered_tx.send(());
4177 let _ = release_rx.await;
4178 }
4179 }
4180
4181 #[cfg(test)]
4182 fn arm_control_command_after_logical_lookup_test_hook(
4183 &self,
4184 kind: ControlCommandLookupTestKind,
4185 session_id: SessionId,
4186 ) -> (
4187 crate::tokio::sync::oneshot::Receiver<()>,
4188 crate::tokio::sync::oneshot::Sender<()>,
4189 ) {
4190 let (entered_tx, entered_rx) = crate::tokio::sync::oneshot::channel();
4191 let (release_tx, release_rx) = crate::tokio::sync::oneshot::channel();
4192 let mut hook = self
4193 .test_control_command_after_logical_lookup
4194 .lock()
4195 .unwrap_or_else(std::sync::PoisonError::into_inner);
4196 assert!(
4197 hook.is_none(),
4198 "control-command lookup test hook already armed"
4199 );
4200 *hook = Some((kind, session_id, entered_tx, release_rx));
4201 (entered_rx, release_tx)
4202 }
4203
4204 #[cfg(test)]
4205 async fn run_control_command_after_logical_lookup_test_hook(
4206 &self,
4207 kind: ControlCommandLookupTestKind,
4208 session_id: &SessionId,
4209 ) {
4210 let armed = {
4211 let mut hook = self
4212 .test_control_command_after_logical_lookup
4213 .lock()
4214 .unwrap_or_else(std::sync::PoisonError::into_inner);
4215 if hook
4216 .as_ref()
4217 .is_some_and(|(armed_kind, armed_session_id, _, _)| {
4218 *armed_kind == kind && armed_session_id == session_id
4219 })
4220 {
4221 hook.take()
4222 } else {
4223 None
4224 }
4225 };
4226 if let Some((_, _, entered_tx, release_rx)) = armed {
4227 let _ = entered_tx.send(());
4228 let _ = release_rx.await;
4229 }
4230 }
4231
4232 async fn lock_registration_gate(
4233 &self,
4234 session_id: &SessionId,
4235 ) -> Result<crate::tokio::sync::OwnedMutexGuard<()>, RuntimeDriverError> {
4236 let gate_guard = self
4237 .lock_current_session_mutation_gate(session_id)
4238 .await
4239 .ok_or(RuntimeDriverError::NotReady {
4240 state: RuntimeState::Destroyed,
4241 })?;
4242 let blocked = {
4243 let sessions = self.sessions.read().await;
4244 let entry = sessions
4245 .get(session_id)
4246 .ok_or(RuntimeDriverError::NotReady {
4247 state: RuntimeState::Destroyed,
4248 })?;
4249 entry.registration_blocked_by_unregister(session_id)
4250 };
4251 if let Some(error) = blocked {
4252 return Err(error);
4253 }
4254 Ok(gate_guard)
4255 }
4256
4257 #[cfg(feature = "live")]
4258 async fn session_live_lifecycle_gate(&self, session_id: &SessionId) -> Option<Arc<Mutex<()>>> {
4259 let sessions = self.sessions.read().await;
4260 sessions
4261 .get(session_id)
4262 .map(|entry| Arc::clone(&entry.live_lifecycle_gate))
4263 }
4264
4265 #[cfg(feature = "live")]
4266 async fn lock_current_session_live_lifecycle_gate(
4267 &self,
4268 session_id: &SessionId,
4269 ) -> Option<(Arc<Mutex<()>>, crate::tokio::sync::OwnedMutexGuard<()>)> {
4270 loop {
4271 let gate = self.session_live_lifecycle_gate(session_id).await?;
4272 let guard = Arc::clone(&gate).lock_owned().await;
4273 let sessions = self.sessions.read().await;
4274 let entry = sessions.get(session_id)?;
4275 if Arc::ptr_eq(&entry.live_lifecycle_gate, &gate) {
4276 return Some((gate, guard));
4277 }
4278 }
4279 }
4280
4281 #[cfg(feature = "live")]
4282 async fn lock_session_mutation_gate_for_live_lifecycle_lease(
4283 &self,
4284 session_id: &SessionId,
4285 lease: &crate::member_live::MemberLiveLifecycleLease,
4286 ) -> Option<crate::tokio::sync::OwnedMutexGuard<()>> {
4287 let mutation_gate = {
4288 let sessions = self.sessions.read().await;
4289 let entry = sessions.get(session_id)?;
4290 if !lease.matches_gate(&entry.live_lifecycle_gate) {
4291 return None;
4292 }
4293 Arc::clone(&entry.mutation_gate)
4294 };
4295 let guard = Arc::clone(&mutation_gate).lock_owned().await;
4296 let sessions = self.sessions.read().await;
4297 let entry = sessions.get(session_id)?;
4298 if lease.matches_gate(&entry.live_lifecycle_gate)
4299 && Arc::ptr_eq(&entry.mutation_gate, &mutation_gate)
4300 {
4301 Some(guard)
4302 } else {
4303 None
4304 }
4305 }
4306
4307 pub(crate) async fn lock_current_session_driver_gate(
4308 &self,
4309 session_id: &SessionId,
4310 driver: &SharedDriver,
4311 ) -> Result<crate::tokio::sync::OwnedMutexGuard<()>, RuntimeDriverError> {
4312 let gate_guard = self
4313 .lock_current_session_mutation_gate(session_id)
4314 .await
4315 .ok_or(RuntimeDriverError::NotReady {
4316 state: RuntimeState::Destroyed,
4317 })?;
4318 {
4319 let sessions = self.sessions.read().await;
4320 let entry = sessions
4321 .get(session_id)
4322 .ok_or(RuntimeDriverError::NotReady {
4323 state: RuntimeState::Destroyed,
4324 })?;
4325 if !Arc::ptr_eq(&entry.driver, driver) {
4326 return Err(RuntimeDriverError::NotReady {
4327 state: RuntimeState::Destroyed,
4328 });
4329 }
4330 }
4331 Ok(gate_guard)
4332 }
4333
4334 pub(crate) async fn lock_current_runtime_loop_driver_authority(
4335 &self,
4336 session_id: &SessionId,
4337 driver: &SharedDriver,
4338 ) -> Result<crate::tokio::sync::OwnedMutexGuard<()>, RuntimeDriverError> {
4339 let gate_guard = self
4340 .lock_current_session_driver_gate(session_id, driver)
4341 .await?;
4342 {
4343 let sessions = self.sessions.read().await;
4344 let entry = sessions
4345 .get(session_id)
4346 .ok_or(RuntimeDriverError::NotReady {
4347 state: RuntimeState::Destroyed,
4348 })?;
4349 if !entry.generated_executor_registration_active_or_draining() {
4350 return Err(RuntimeDriverError::ValidationFailed {
4351 reason:
4352 "generated MeerkatMachine has no active runtime-loop executor registration"
4353 .to_string(),
4354 });
4355 }
4356 }
4357 Ok(gate_guard)
4358 }
4359
4360 pub(crate) async fn current_runtime_stop_cleanup_in_progress(
4361 &self,
4362 session_id: &SessionId,
4363 driver: &SharedDriver,
4364 ) -> Result<bool, RuntimeDriverError> {
4365 let sessions = self.sessions.read().await;
4366 let entry = sessions
4367 .get(session_id)
4368 .ok_or(RuntimeDriverError::NotReady {
4369 state: RuntimeState::Destroyed,
4370 })?;
4371 if !Arc::ptr_eq(&entry.driver, driver) {
4372 return Err(RuntimeDriverError::NotReady {
4373 state: RuntimeState::Destroyed,
4374 });
4375 }
4376 let Some(coordinator) = entry.runtime_stop_cleanup_coordinator.as_ref() else {
4377 return Ok(false);
4378 };
4379 if coordinator.epoch_id != entry.epoch_id {
4380 return Err(RuntimeDriverError::Internal(format!(
4381 "stale runtime-stop cleanup coordinator epoch for session {session_id}"
4382 )));
4383 }
4384 Ok(coordinator.result_rx.borrow().is_none())
4385 }
4386
4387 async fn session_dsl_authority(
4388 &self,
4389 session_id: &SessionId,
4390 ) -> Result<Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>, String> {
4391 let sessions = self.sessions.read().await;
4392 sessions
4393 .get(session_id)
4394 .map(|entry| Arc::clone(&entry.dsl_authority))
4395 .ok_or_else(|| {
4396 RuntimeDriverError::NotReady {
4397 state: RuntimeState::Destroyed,
4398 }
4399 .to_string()
4400 })
4401 }
4402
4403 #[cfg(any(test, feature = "test-support"))]
4404 async fn session_handle_teardown_gate(
4405 &self,
4406 session_id: &SessionId,
4407 ) -> Result<Arc<crate::handles::HandleTeardownGate>, String> {
4408 let sessions = self.sessions.read().await;
4409 sessions
4410 .get(session_id)
4411 .map(|entry| Arc::clone(&entry.handle_teardown_gate))
4412 .ok_or_else(|| {
4413 RuntimeDriverError::NotReady {
4414 state: RuntimeState::Destroyed,
4415 }
4416 .to_string()
4417 })
4418 }
4419
4420 #[cfg(any(test, feature = "test-support"))]
4428 pub async fn test_install_session_peer_comms_handle_on_runtime(
4429 &self,
4430 session_id: &SessionId,
4431 runtime: &(dyn meerkat_core::handles::PeerCommsInstallTarget + '_),
4432 ) -> Result<(), String> {
4433 let dsl = self
4434 .session_dsl_authority(session_id)
4435 .await
4436 .map_err(|error| format!("session dsl authority unavailable: {error}"))?;
4437 let teardown_gate = self
4438 .session_handle_teardown_gate(session_id)
4439 .await
4440 .map_err(|error| format!("session handle teardown gate unavailable: {error}"))?;
4441 let handle = std::sync::Arc::new(
4442 crate::handles::HandleDslAuthority::from_shared_with_teardown_gate(dsl, teardown_gate),
4443 );
4444 crate::handles::RuntimePeerCommsHandle::install_generated_on(handle, runtime)
4445 }
4446
4447 fn preview_dsl_input_on_state(
4448 state: &dsl::MeerkatMachineState,
4449 input: dsl::MeerkatMachineInput,
4450 context: &str,
4451 ) -> Result<Vec<dsl::MeerkatMachineEffect>, String> {
4452 let mut preview = dsl::MeerkatMachineAuthority::recover_from_state(state.clone())
4453 .map_err(|err| dsl_authority::map_error(err, context))?;
4454 dsl::MeerkatMachineMutator::apply(&mut preview, input)
4455 .map(|transition| transition.into_effects())
4456 .map_err(|err| dsl_authority::map_error(err, context))
4457 }
4458
4459 async fn preview_session_dsl_input(
4460 &self,
4461 session_id: &SessionId,
4462 input: dsl::MeerkatMachineInput,
4463 context: &str,
4464 ) -> Result<Vec<dsl::MeerkatMachineEffect>, String> {
4465 let authority = self.session_dsl_authority(session_id).await?;
4466 let state = {
4467 let authority = authority
4468 .lock()
4469 .unwrap_or_else(std::sync::PoisonError::into_inner);
4470 authority.state().clone()
4471 };
4472 Self::preview_dsl_input_on_state(&state, input, context)
4473 }
4474
4475 async fn session_dsl_state(
4476 &self,
4477 session_id: &SessionId,
4478 ) -> Result<dsl::MeerkatMachineState, RuntimeControlPlaneError> {
4479 let authority = self
4480 .session_dsl_authority(session_id)
4481 .await
4482 .map_err(RuntimeControlPlaneError::Internal)?;
4483 let authority = authority
4484 .lock()
4485 .unwrap_or_else(std::sync::PoisonError::into_inner);
4486 Ok(authority.state().clone())
4487 }
4488
4489 async fn commit_session_dsl_transition(
4490 &self,
4491 session_id: &SessionId,
4492 staged: StagedSessionDslInput,
4493 context: &str,
4494 ) -> Result<(), String> {
4495 self.commit_session_dsl_transition_with_dispatch_failure(
4496 session_id,
4497 staged,
4498 context,
4499 CommittedEffectDispatchFailure::PreserveCommittedDslState,
4500 )
4501 .await
4502 }
4503
4504 async fn commit_session_dsl_transition_preserving_committed_state(
4505 &self,
4506 session_id: &SessionId,
4507 staged: StagedSessionDslInput,
4508 context: &str,
4509 ) -> Result<(), String> {
4510 self.commit_session_dsl_transition_with_dispatch_failure(
4511 session_id,
4512 staged,
4513 context,
4514 CommittedEffectDispatchFailure::PreserveCommittedDslState,
4515 )
4516 .await
4517 }
4518
4519 async fn commit_session_dsl_transition_with_dispatch_failure(
4520 &self,
4521 _session_id: &SessionId,
4522 staged: StagedSessionDslInput,
4523 context: &str,
4524 dispatch_failure: CommittedEffectDispatchFailure,
4525 ) -> Result<(), String> {
4526 if let Err(error) = self
4527 .dispatch_routed_signals_from_effects(&staged.effects)
4528 .await
4529 {
4530 let CommittedEffectDispatchFailure::PreserveCommittedDslState = dispatch_failure;
4531 return Err(format!(
4532 "DSL authority ({context}): committed effect dispatch failed: {error}"
4533 ));
4534 }
4535 Ok(())
4536 }
4537
4538 async fn dispatch_routed_signals_from_effects(
4539 &self,
4540 effects: &[dsl::MeerkatMachineEffect],
4541 ) -> Result<(), String> {
4542 let dispatcher = {
4543 self.composition_signal_dispatcher
4544 .read()
4545 .unwrap_or_else(std::sync::PoisonError::into_inner)
4546 .clone()
4547 };
4548 let Some(dispatcher) = dispatcher else {
4549 return Ok(());
4550 };
4551
4552 for effect in effects {
4553 if let Some(signal) = composition::lift_routed_signal(effect) {
4554 composition::dispatch_routed_signal(&dispatcher, signal).await?;
4555 }
4556 }
4557 Ok(())
4558 }
4559
4560 #[allow(clippy::too_many_arguments)]
4563 async fn dispatch_cancel_after_boundary_runtime_effect(
4564 &self,
4565 session_id: &SessionId,
4566 witness: RuntimeEffectDispatchAttachmentWitness,
4567 held_mutation_gate: crate::tokio::sync::OwnedMutexGuard<()>,
4568 boundary_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>>,
4569 member_authority: Option<RuntimeEffectDispatchMemberAuthority>,
4570 pending_dispatch: PendingBoundaryCancelDispatchGuard,
4571 expected_run_id: Option<&RunId>,
4572 projected_effect: crate::effect::ProjectedRuntimeEffect,
4573 dispatch_generation: u64,
4574 dispatch_lifecycle_phase: dsl::MeerkatPhase,
4575 context: &str,
4576 ) -> Result<crate::tokio::sync::OwnedMutexGuard<()>, RuntimeDriverError> {
4577 let cleanup_spawner = MachineCleanupTaskSpawner::acquire()?;
4578 let machine = self.clone();
4579 let session_id = session_id.clone();
4580 let expected_run_id = expected_run_id.cloned();
4581 let context = context.to_string();
4582 let completion = cleanup_spawner.spawn(async move {
4583 machine
4584 .dispatch_cancel_after_boundary_runtime_effect_owned(
4585 &session_id,
4586 witness,
4587 held_mutation_gate,
4588 boundary_handle,
4589 member_authority,
4590 pending_dispatch,
4591 expected_run_id.as_ref(),
4592 projected_effect,
4593 dispatch_generation,
4594 dispatch_lifecycle_phase,
4595 &context,
4596 )
4597 .await
4598 });
4599 completion.await.map_err(|error| {
4600 RuntimeDriverError::Internal(format!(
4601 "process-owned boundary-effect dispatch ended without a result: {error}"
4602 ))
4603 })?
4604 }
4605
4606 #[allow(clippy::too_many_arguments)]
4609 async fn dispatch_cancel_after_boundary_runtime_effect_owned(
4610 &self,
4611 session_id: &SessionId,
4612 witness: RuntimeEffectDispatchAttachmentWitness,
4613 held_mutation_gate: crate::tokio::sync::OwnedMutexGuard<()>,
4614 boundary_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>>,
4615 member_authority: Option<RuntimeEffectDispatchMemberAuthority>,
4616 mut pending_dispatch: PendingBoundaryCancelDispatchGuard,
4617 expected_run_id: Option<&RunId>,
4618 projected_effect: crate::effect::ProjectedRuntimeEffect,
4619 dispatch_generation: u64,
4620 dispatch_lifecycle_phase: dsl::MeerkatPhase,
4621 context: &str,
4622 ) -> Result<crate::tokio::sync::OwnedMutexGuard<()>, RuntimeDriverError> {
4623 drop(held_mutation_gate);
4628 let live_dispatch_result = self
4629 .dispatch_cancel_after_boundary_live_handle(
4630 boundary_handle,
4631 expected_run_id,
4632 &projected_effect,
4633 context,
4634 )
4635 .await;
4636
4637 let pre_reserve_state = Self::classify_dsl_boundary_cancel_dispatch(
4641 &witness.dsl_authority,
4642 dispatch_generation,
4643 &dispatch_lifecycle_phase,
4644 expected_run_id,
4645 );
4646 let effect_permit = if live_dispatch_result.is_ok()
4647 && pre_reserve_state == BoundaryCancelDispatchState::Current
4648 {
4649 Some(witness.effect_tx.clone().reserve_owned().await)
4650 } else {
4651 None
4652 };
4653
4654 let gate_guard = Arc::clone(&witness.mutation_gate).lock_owned().await;
4655 let current_effect_tx = {
4656 let sessions = self.sessions.read().await;
4657 match sessions.get(session_id) {
4658 None => Err(RuntimeDriverError::StaleAuthority {
4659 reason: format!(
4660 "{context}: runtime session disappeared during boundary callback"
4661 ),
4662 }),
4663 Some(entry)
4664 if !Arc::ptr_eq(&entry.mutation_gate, &witness.mutation_gate)
4665 || !Arc::ptr_eq(&entry.driver, &witness.driver)
4666 || !Arc::ptr_eq(&entry.dsl_authority, &witness.dsl_authority)
4667 || !entry.owns_runtime_loop_attachment(witness.attachment_id) =>
4668 {
4669 Err(RuntimeDriverError::StaleAuthority {
4670 reason: format!(
4671 "{context}: runtime attachment changed during boundary callback"
4672 ),
4673 })
4674 }
4675 Some(entry) => match entry.effect_sender() {
4676 None => Err(RuntimeDriverError::StaleAuthority {
4677 reason: format!(
4678 "{context}: runtime effect channel disappeared during boundary callback"
4679 ),
4680 }),
4681 Some(current_effect_tx)
4682 if !witness.effect_tx.same_channel(¤t_effect_tx) =>
4683 {
4684 Err(RuntimeDriverError::StaleAuthority {
4685 reason: format!(
4686 "{context}: runtime effect channel changed during boundary callback"
4687 ),
4688 })
4689 }
4690 Some(current_effect_tx) => Ok(current_effect_tx),
4691 },
4692 }
4693 };
4694 let current_effect_tx = current_effect_tx?;
4695
4696 if let Some(member_authority) = &member_authority {
4697 self.validate_member_effect_authority_lease_current(
4698 session_id,
4699 &member_authority.lease,
4700 member_authority.expected_member.as_ref(),
4701 )?;
4702 }
4703
4704 if let Err(error) = live_dispatch_result {
4710 return Err(error);
4711 }
4712
4713 match Self::classify_dsl_boundary_cancel_dispatch(
4718 &witness.dsl_authority,
4719 dispatch_generation,
4720 &dispatch_lifecycle_phase,
4721 expected_run_id,
4722 ) {
4723 BoundaryCancelDispatchState::ConsumedConverged => return Ok(gate_guard),
4724 BoundaryCancelDispatchState::SameGenerationOrphan
4725 | BoundaryCancelDispatchState::Superseded => {
4726 return Err(RuntimeDriverError::StaleAuthority {
4727 reason: format!(
4728 "{context}: boundary-cancel dispatch authority changed during callback"
4729 ),
4730 });
4731 }
4732 BoundaryCancelDispatchState::Current => {}
4733 }
4734
4735 let state = self
4736 .existing_session_runtime_state(session_id)
4737 .await
4738 .unwrap_or(RuntimeState::Destroyed);
4739 self.reject_unregistration_drain_ingress(session_id, state)
4740 .await?;
4741
4742 debug_assert!(witness.effect_tx.same_channel(¤t_effect_tx));
4743 let effect_permit = effect_permit
4744 .ok_or_else(|| RuntimeDriverError::StaleAuthority {
4745 reason: format!(
4746 "{context}: boundary-cancel dispatch became current after capacity reservation was skipped"
4747 ),
4748 })?
4749 .map_err(|_| RuntimeDriverError::NotReady {
4750 state: RuntimeState::Idle,
4751 })?;
4752 let _effect_tx = effect_permit.send(projected_effect.into_effect());
4753 pending_dispatch.disarm();
4754 Ok(gate_guard)
4755 }
4756
4757 async fn dispatch_accepted_ingress_boundary_work(
4762 &self,
4763 session_id: &SessionId,
4764 held_mutation_gate: crate::tokio::sync::OwnedMutexGuard<()>,
4765 cancel_plan: Option<RuntimeAcceptedBoundaryCancelPlan>,
4766 _completions: crate::meerkat_machine::driver::SharedCompletionRegistry,
4767 wake_tx: Option<mpsc::Sender<()>>,
4768 should_wake: bool,
4769 ) -> Result<crate::tokio::sync::OwnedMutexGuard<()>, RuntimeDriverError> {
4770 let cleanup_spawner = MachineCleanupTaskSpawner::acquire()?;
4771 let machine = self.clone();
4772 let session_id = session_id.clone();
4773 let completion = cleanup_spawner.spawn(async move {
4774 let mut gate_guard = held_mutation_gate;
4775 if let Some(cancel_plan) = cancel_plan {
4776 gate_guard = machine
4777 .dispatch_cancel_after_boundary_runtime_effect_owned(
4778 &session_id,
4779 cancel_plan.witness,
4780 gate_guard,
4781 cancel_plan.boundary_handle,
4782 None,
4783 cancel_plan.pending_dispatch,
4784 Some(&cancel_plan.expected_run_id),
4785 cancel_plan.projected_effect,
4786 cancel_plan.dispatch_generation,
4787 cancel_plan.dispatch_lifecycle_phase,
4788 "AcceptWithCompletion",
4789 )
4790 .await?;
4791 }
4792 if should_wake && let Some(wake_tx) = wake_tx {
4793 let _ = wake_tx.try_send(());
4794 }
4795 Ok(gate_guard)
4796 });
4797 completion.await.map_err(|error| {
4798 RuntimeDriverError::Internal(format!(
4799 "process-owned accepted-ingress boundary transaction ended without a result: {error}"
4800 ))
4801 })?
4802 }
4803
4804 async fn dispatch_cancel_after_boundary_live_handle(
4810 &self,
4811 boundary_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>>,
4812 expected_run_id: Option<&RunId>,
4813 projected_effect: &crate::effect::ProjectedRuntimeEffect,
4814 context: &str,
4815 ) -> Result<(), RuntimeDriverError> {
4816 let reason = projected_effect.reason().to_string();
4817 if let (Some(boundary_handle), Some(expected_run_id)) = (boundary_handle, expected_run_id) {
4822 boundary_handle
4823 .cancel_after_boundary(expected_run_id, reason)
4824 .await
4825 .map_err(|err| {
4826 RuntimeDriverError::Internal(format!(
4827 "{context}: failed to apply live boundary cancel: {err}"
4828 ))
4829 })?;
4830 }
4831 Ok(())
4832 }
4833
4834 async fn restore_session_dsl_state(
4835 &self,
4836 session_id: &SessionId,
4837 snapshot: dsl::MeerkatMachineAuthoritySnapshot,
4838 ) {
4839 if let Ok(authority) = self.session_dsl_authority(session_id).await {
4840 Self::restore_dsl_authority_snapshot(&authority, snapshot);
4841 }
4842 }
4843
4844 async fn restore_session_dsl_state_if_current(
4845 &self,
4846 session_id: &SessionId,
4847 expected_current: dsl::MeerkatMachineAuthoritySnapshot,
4848 restore: dsl::MeerkatMachineAuthoritySnapshot,
4849 ) -> bool {
4850 let Ok(authority) = self.session_dsl_authority(session_id).await else {
4851 return false;
4852 };
4853 Self::restore_dsl_authority_snapshot_if_current(&authority, expected_current, restore)
4854 }
4855
4856 fn classify_dsl_boundary_cancel_dispatch(
4857 authority: &Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
4858 dispatch_generation: u64,
4859 dispatch_lifecycle_phase: &dsl::MeerkatPhase,
4860 expected_run_id: Option<&RunId>,
4861 ) -> BoundaryCancelDispatchState {
4862 let authority = authority
4863 .lock()
4864 .unwrap_or_else(std::sync::PoisonError::into_inner);
4865 let state = authority.state();
4866 if state.boundary_cancel_dispatch_generation != dispatch_generation {
4867 return BoundaryCancelDispatchState::Superseded;
4868 }
4869 if !state.boundary_cancel_dispatch_pending {
4870 return BoundaryCancelDispatchState::ConsumedConverged;
4871 }
4872 let expected_dsl_run_id = expected_run_id.map(dsl::RunId::from_domain);
4873 if state.lifecycle_phase == *dispatch_lifecycle_phase
4874 && state.current_run_id.as_ref() == expected_dsl_run_id.as_ref()
4875 {
4876 BoundaryCancelDispatchState::Current
4877 } else {
4878 BoundaryCancelDispatchState::SameGenerationOrphan
4879 }
4880 }
4881
4882 fn abort_dsl_boundary_cancel_dispatch_if_current(
4889 authority: &Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
4890 dispatch_generation: u64,
4891 ) -> Result<bool, RuntimeDriverError> {
4892 let mut authority = authority
4893 .lock()
4894 .unwrap_or_else(std::sync::PoisonError::into_inner);
4895 if !authority.state().boundary_cancel_dispatch_pending
4896 || authority.state().boundary_cancel_dispatch_generation != dispatch_generation
4897 {
4898 return Ok(false);
4899 }
4900 dsl::MeerkatMachineMutator::apply(
4901 &mut *authority,
4902 dsl::MeerkatMachineInput::AbortCancelAfterBoundaryDispatch {
4903 dispatch_generation,
4904 },
4905 )
4906 .map(|_| true)
4907 .map_err(|err| {
4908 RuntimeDriverError::Internal(dsl_authority::map_error(
4909 err,
4910 "AbortCancelAfterBoundaryDispatch",
4911 ))
4912 })
4913 }
4914
4915 fn restore_dsl_authority_snapshot(
4916 authority: &Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
4917 snapshot: dsl::MeerkatMachineAuthoritySnapshot,
4918 ) {
4919 let mut authority = authority
4920 .lock()
4921 .unwrap_or_else(std::sync::PoisonError::into_inner);
4922 authority.restore_snapshot(snapshot);
4923 }
4924
4925 fn restore_dsl_authority_snapshot_if_current(
4926 authority: &Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
4927 expected_current: dsl::MeerkatMachineAuthoritySnapshot,
4928 restore: dsl::MeerkatMachineAuthoritySnapshot,
4929 ) -> bool {
4930 let mut authority = authority
4931 .lock()
4932 .unwrap_or_else(std::sync::PoisonError::into_inner);
4933 let current = authority.snapshot();
4934 if current.state() == expected_current.state() {
4935 authority.restore_snapshot(restore);
4936 true
4937 } else {
4938 false
4939 }
4940 }
4941}
4942
4943#[derive(Debug, Clone, Copy)]
4946pub struct MachineSessionControlAuthority {
4947 _private: (),
4948}
4949
4950#[cfg(feature = "live")]
4951struct LiveOpenAdmissionGeneratedAuthorityBridgeToken;
4952
4953#[cfg(feature = "live")]
4954struct LiveCloseResultGeneratedAuthorityBridgeToken;
4955
4956#[cfg(feature = "live")]
4957struct LiveChannelStatusResultGeneratedAuthorityBridgeToken;
4958
4959#[cfg(feature = "live")]
4960static LIVE_OPEN_ADMISSION_GENERATED_AUTHORITY_BRIDGE_TOKEN:
4961 LiveOpenAdmissionGeneratedAuthorityBridgeToken = LiveOpenAdmissionGeneratedAuthorityBridgeToken;
4962
4963#[cfg(feature = "live")]
4964static LIVE_CLOSE_RESULT_GENERATED_AUTHORITY_BRIDGE_TOKEN:
4965 LiveCloseResultGeneratedAuthorityBridgeToken = LiveCloseResultGeneratedAuthorityBridgeToken;
4966
4967#[cfg(feature = "live")]
4968static LIVE_CHANNEL_STATUS_RESULT_GENERATED_AUTHORITY_BRIDGE_TOKEN:
4969 LiveChannelStatusResultGeneratedAuthorityBridgeToken =
4970 LiveChannelStatusResultGeneratedAuthorityBridgeToken;
4971
4972#[cfg(feature = "live")]
4973fn live_open_admission_generated_authority_bridge_token()
4974-> &'static (dyn std::any::Any + Send + Sync) {
4975 &LIVE_OPEN_ADMISSION_GENERATED_AUTHORITY_BRIDGE_TOKEN
4976}
4977
4978#[cfg(feature = "live")]
4979fn live_close_result_generated_authority_bridge_token() -> &'static (dyn std::any::Any + Send + Sync)
4980{
4981 &LIVE_CLOSE_RESULT_GENERATED_AUTHORITY_BRIDGE_TOKEN
4982}
4983
4984#[cfg(feature = "live")]
4985fn live_channel_status_result_generated_authority_bridge_token()
4986-> &'static (dyn std::any::Any + Send + Sync) {
4987 &LIVE_CHANNEL_STATUS_RESULT_GENERATED_AUTHORITY_BRIDGE_TOKEN
4988}
4989
4990#[cfg(feature = "live")]
4991#[doc(hidden)]
4992#[allow(improper_ctypes_definitions, unsafe_code)]
4993#[unsafe(export_name = concat!(
4994 "__meerkat_runtime_generated_authority_bridge_token_is_valid_v1_live_open_admission_",
4995 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
4996))]
4997pub extern "Rust" fn live_open_admission_generated_authority_bridge_token_is_valid(
4998 token: &(dyn std::any::Any + Send + Sync),
4999) -> bool {
5000 token.is::<LiveOpenAdmissionGeneratedAuthorityBridgeToken>()
5001}
5002
5003#[cfg(feature = "live")]
5004#[doc(hidden)]
5005#[allow(improper_ctypes_definitions, unsafe_code)]
5006#[unsafe(export_name = concat!(
5007 "__meerkat_runtime_generated_authority_bridge_token_is_valid_v1_live_close_result_",
5008 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
5009))]
5010pub extern "Rust" fn live_close_result_generated_authority_bridge_token_is_valid(
5011 token: &(dyn std::any::Any + Send + Sync),
5012) -> bool {
5013 token.is::<LiveCloseResultGeneratedAuthorityBridgeToken>()
5014}
5015
5016#[cfg(feature = "live")]
5017#[doc(hidden)]
5018#[allow(improper_ctypes_definitions, unsafe_code)]
5019#[unsafe(export_name = concat!(
5020 "__meerkat_runtime_generated_authority_bridge_token_is_valid_v1_live_channel_status_result_",
5021 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
5022))]
5023pub extern "Rust" fn live_channel_status_result_generated_authority_bridge_token_is_valid(
5024 token: &(dyn std::any::Any + Send + Sync),
5025) -> bool {
5026 token.is::<LiveChannelStatusResultGeneratedAuthorityBridgeToken>()
5027}
5028
5029#[cfg(feature = "live")]
5030fn build_live_channel_open_authority(
5031 session_id: SessionId,
5032 channel_id: meerkat_live::LiveChannelId,
5033 sequence: u64,
5034) -> Result<meerkat_live::LiveChannelOpenAuthority, String> {
5035 #[allow(improper_ctypes_definitions, unsafe_code)]
5036 unsafe extern "Rust" {
5037 #[link_name = concat!(
5038 "__meerkat_live_runtime_generated_live_channel_open_authority_build_v1_",
5039 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
5040 )]
5041 fn live_generated_channel_open_authority_build(
5042 token: &'static (dyn std::any::Any + Send + Sync),
5043 session_id: SessionId,
5044 channel_id: meerkat_live::LiveChannelId,
5045 sequence: u64,
5046 ) -> Result<meerkat_live::LiveChannelOpenAuthority, String>;
5047 }
5048 #[allow(unsafe_code)]
5049 unsafe {
5050 live_generated_channel_open_authority_build(
5051 live_open_admission_generated_authority_bridge_token(),
5052 session_id,
5053 channel_id,
5054 sequence,
5055 )
5056 }
5057}
5058
5059#[cfg(feature = "live")]
5060fn build_live_channel_close_commit_authority(
5061 channel_id: String,
5062 close_sequence: u64,
5063) -> Result<meerkat_live::LiveChannelCloseCommitAuthority, String> {
5064 #[allow(improper_ctypes_definitions, unsafe_code)]
5065 unsafe extern "Rust" {
5066 #[link_name = concat!(
5067 "__meerkat_live_runtime_generated_live_channel_close_commit_authority_build_v1_",
5068 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
5069 )]
5070 fn live_generated_channel_close_commit_authority_build(
5071 token: &'static (dyn std::any::Any + Send + Sync),
5072 channel_id: String,
5073 close_sequence: u64,
5074 ) -> Result<meerkat_live::LiveChannelCloseCommitAuthority, String>;
5075 }
5076 #[allow(unsafe_code)]
5077 unsafe {
5078 live_generated_channel_close_commit_authority_build(
5079 live_close_result_generated_authority_bridge_token(),
5080 channel_id,
5081 close_sequence,
5082 )
5083 }
5084}
5085
5086#[cfg(feature = "live")]
5087fn build_live_channel_status_commit_authority(
5088 channel_id: String,
5089 status_observation_sequence: u64,
5090) -> Result<meerkat_live::LiveChannelStatusCommitAuthority, String> {
5091 #[allow(improper_ctypes_definitions, unsafe_code)]
5092 unsafe extern "Rust" {
5093 #[link_name = concat!(
5094 "__meerkat_live_runtime_generated_live_channel_status_commit_authority_build_v1_",
5095 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
5096 )]
5097 fn live_generated_channel_status_commit_authority_build(
5098 token: &'static (dyn std::any::Any + Send + Sync),
5099 channel_id: String,
5100 status_observation_sequence: u64,
5101 ) -> Result<meerkat_live::LiveChannelStatusCommitAuthority, String>;
5102 }
5103 #[allow(unsafe_code)]
5104 unsafe {
5105 live_generated_channel_status_commit_authority_build(
5106 live_channel_status_result_generated_authority_bridge_token(),
5107 channel_id,
5108 status_observation_sequence,
5109 )
5110 }
5111}
5112
5113#[derive(Debug, Clone)]
5120#[cfg(feature = "live")]
5121pub struct LiveOpenAdmissionAuthority {
5122 session_id: SessionId,
5123 channel_id: meerkat_live::LiveChannelId,
5124 admitted: bool,
5125 rejection: Option<dsl::LiveOpenAdmissionRejection>,
5126 bound_llm_identity: Option<meerkat_core::SessionLlmIdentity>,
5127 sequence: u64,
5128 channel_open_authority: Option<meerkat_live::LiveChannelOpenAuthority>,
5129}
5130
5131#[cfg(feature = "live")]
5132impl LiveOpenAdmissionAuthority {
5133 pub(crate) fn from_generated_effect(
5134 session_id: SessionId,
5135 channel_id: meerkat_live::LiveChannelId,
5136 admitted: bool,
5137 rejection: Option<dsl::LiveOpenAdmissionRejection>,
5138 bound_llm_identity: Option<dsl::SessionLlmIdentity>,
5139 sequence: u64,
5140 ) -> Result<Self, String> {
5141 let bound_llm_identity = match (admitted, bound_llm_identity) {
5142 (true, Some(identity)) => Some(identity.try_into()?),
5143 (true, None) => {
5144 return Err(
5145 "generated live-open admission was admitted without bound LLM identity"
5146 .to_string(),
5147 );
5148 }
5149 (false, _) => None,
5150 };
5151 let channel_open_authority = if admitted {
5152 Some(build_live_channel_open_authority(
5153 session_id.clone(),
5154 channel_id.clone(),
5155 sequence,
5156 )?)
5157 } else {
5158 None
5159 };
5160 Ok(Self {
5161 session_id,
5162 channel_id,
5163 admitted,
5164 rejection,
5165 bound_llm_identity,
5166 sequence,
5167 channel_open_authority,
5168 })
5169 }
5170
5171 #[must_use]
5172 pub fn session_id(&self) -> &SessionId {
5173 &self.session_id
5174 }
5175
5176 #[must_use]
5177 pub fn channel_id(&self) -> &meerkat_live::LiveChannelId {
5178 &self.channel_id
5179 }
5180
5181 #[must_use]
5182 pub fn admitted(&self) -> bool {
5183 self.admitted
5184 }
5185
5186 #[must_use]
5187 pub fn rejection(&self) -> Option<dsl::LiveOpenAdmissionRejection> {
5188 self.rejection
5189 }
5190
5191 #[must_use]
5192 pub fn bound_llm_identity(&self) -> Option<&meerkat_core::SessionLlmIdentity> {
5193 self.bound_llm_identity.as_ref()
5194 }
5195
5196 #[must_use]
5197 pub fn sequence(&self) -> u64 {
5198 self.sequence
5199 }
5200
5201 #[must_use]
5202 pub fn channel_open_authority(&self) -> Option<&meerkat_live::LiveChannelOpenAuthority> {
5203 self.channel_open_authority.as_ref()
5204 }
5205}
5206
5207#[derive(Debug, Clone, PartialEq, Eq)]
5214#[cfg(feature = "live")]
5215pub struct LiveRefreshResultAuthority {
5216 pub status: dsl::LiveRefreshPublicStatus,
5217 pub sequence: u64,
5218 pub queue_acceptance_sequence: u64,
5219}
5220
5221#[derive(Debug, Clone)]
5226#[cfg(feature = "live")]
5227pub struct LiveCloseResultAuthority {
5228 pub status: dsl::LiveClosePublicStatus,
5229 pub sequence: u64,
5230 pub close_observation_sequence: u64,
5231 channel_close_commit_authority: Option<meerkat_live::LiveChannelCloseCommitAuthority>,
5232}
5233
5234#[cfg(feature = "live")]
5235impl LiveCloseResultAuthority {
5236 pub(crate) fn from_generated_effect(
5237 channel_id: String,
5238 status: dsl::LiveClosePublicStatus,
5239 sequence: u64,
5240 close_observation_sequence: u64,
5241 ) -> Result<Self, String> {
5242 let channel_close_commit_authority = match status {
5243 dsl::LiveClosePublicStatus::Closed => Some(build_live_channel_close_commit_authority(
5244 channel_id,
5245 close_observation_sequence,
5246 )?),
5247 };
5248 Ok(Self {
5249 status,
5250 sequence,
5251 close_observation_sequence,
5252 channel_close_commit_authority,
5253 })
5254 }
5255
5256 #[must_use]
5257 pub fn channel_close_commit_authority(
5258 &self,
5259 ) -> Option<&meerkat_live::LiveChannelCloseCommitAuthority> {
5260 self.channel_close_commit_authority.as_ref()
5261 }
5262
5263 #[must_use]
5264 pub fn into_channel_close_commit_authority(
5265 self,
5266 ) -> Option<meerkat_live::LiveChannelCloseCommitAuthority> {
5267 self.channel_close_commit_authority
5268 }
5269}
5270
5271#[derive(Debug, Clone, PartialEq, Eq)]
5277#[cfg(feature = "live")]
5278pub struct LiveCommandResultAuthority {
5279 pub command: dsl::LiveCommandPublicKind,
5280 pub sequence: u64,
5281 pub command_acceptance_sequence: u64,
5282}
5283
5284#[derive(Debug, Clone, PartialEq, Eq)]
5291#[cfg(feature = "live")]
5292pub struct LiveCommandRejectionAuthority {
5293 pub command: dsl::LiveCommandPublicKind,
5294 pub rejection: dsl::LiveCommandRejectionReason,
5295 pub public_error_class: dsl::LiveCommandRejectionPublicErrorClass,
5296 pub sequence: u64,
5297}
5298
5299#[derive(Debug, Clone, PartialEq, Eq)]
5306#[cfg(feature = "live")]
5307pub struct LiveChannelRequestRejectionAuthority {
5308 pub request: dsl::LiveChannelRequestPublicKind,
5309 pub rejection: dsl::LiveChannelRequestRejectionReason,
5310 pub public_error_class: dsl::LiveChannelRequestRejectionPublicErrorClass,
5311 pub sequence: u64,
5312}
5313
5314#[derive(Debug, Clone, PartialEq, Eq)]
5321#[cfg(feature = "live")]
5322pub struct LiveWebrtcTokenAuthority {
5323 pub token: String,
5324 pub expires_at_ms: u64,
5325 pub sequence: u64,
5326}
5327
5328#[derive(Debug, Clone, PartialEq, Eq)]
5334#[cfg(feature = "live")]
5335pub struct LiveWebrtcAnswerAdmissionAuthority {
5336 pub admitted: bool,
5337 pub rejection: Option<dsl::LiveWebrtcAnswerAdmissionRejection>,
5338 pub public_error_class: Option<dsl::LiveChannelRequestRejectionPublicErrorClass>,
5339 pub sequence: u64,
5340}
5341
5342#[derive(Debug, Clone, PartialEq, Eq)]
5349#[cfg(feature = "live")]
5350pub struct LiveWebrtcAnswerResultAuthority {
5351 pub status: dsl::LiveWebrtcAnswerPublicStatus,
5352 pub answered: bool,
5353 pub sequence: u64,
5354 pub answer_observation_sequence: u64,
5355}
5356
5357#[derive(Debug, Clone, PartialEq, Eq)]
5364#[cfg(feature = "live")]
5365pub struct LiveWebsocketTokenAuthority {
5366 pub token: String,
5367 pub expires_at_ms: u64,
5368 pub sequence: u64,
5369}
5370
5371#[derive(Debug, Clone, PartialEq, Eq)]
5377#[cfg(feature = "live")]
5378pub struct LiveWebsocketTokenAdmissionAuthority {
5379 pub admitted: bool,
5380 pub rejection: Option<dsl::LiveWebsocketTokenAdmissionRejection>,
5381 pub public_error_class: Option<dsl::LiveWebsocketTokenAdmissionPublicErrorClass>,
5382 pub sequence: u64,
5383}
5384
5385#[derive(Debug, Clone)]
5391#[cfg(feature = "live")]
5392pub struct LiveChannelStatusAuthority {
5393 pub status: dsl::LiveChannelPublicStatus,
5394 pub sequence: u64,
5395 pub status_observation_sequence: u64,
5396 pub degradation_reason: Option<dsl::LiveChannelDegradationReason>,
5397 pub degradation_detail: Option<String>,
5398 pub channel_status_commit_authority: Option<meerkat_live::LiveChannelStatusCommitAuthority>,
5399}
5400
5401#[cfg(feature = "live")]
5402impl LiveChannelStatusAuthority {
5403 pub(crate) fn from_generated_effect(
5404 channel_id: String,
5405 status: dsl::LiveChannelPublicStatus,
5406 sequence: u64,
5407 status_observation_sequence: u64,
5408 degradation_reason: Option<dsl::LiveChannelDegradationReason>,
5409 degradation_detail: Option<String>,
5410 ) -> Result<Self, String> {
5411 Ok(Self {
5412 status,
5413 sequence,
5414 status_observation_sequence,
5415 degradation_reason,
5416 degradation_detail,
5417 channel_status_commit_authority: Some(build_live_channel_status_commit_authority(
5418 channel_id,
5419 status_observation_sequence,
5420 )?),
5421 })
5422 }
5423
5424 #[must_use]
5425 pub fn channel_status_commit_authority(
5426 &self,
5427 ) -> Option<&meerkat_live::LiveChannelStatusCommitAuthority> {
5428 self.channel_status_commit_authority.as_ref()
5429 }
5430
5431 #[must_use]
5432 pub fn into_channel_status_commit_authority(
5433 self,
5434 ) -> Option<meerkat_live::LiveChannelStatusCommitAuthority> {
5435 self.channel_status_commit_authority
5436 }
5437}
5438
5439#[doc(hidden)]
5446pub struct MeerkatMachineShared {
5447 sessions: RwLock<HashMap<SessionId, RuntimeSessionEntry>>,
5449 registration_transaction_slots: StdRwLock<HashMap<SessionId, std::sync::Weak<Mutex<()>>>>,
5454 store: Option<Arc<dyn RuntimeStore>>,
5456 blob_store: Option<Arc<dyn BlobStore>>,
5458 llm_reconfigure_host: StdRwLock<Option<Arc<dyn SessionLlmReconfigureHost>>>,
5460 member_observation_host:
5465 StdRwLock<Option<Arc<dyn crate::member_observation::MemberObservationHost>>>,
5466 member_live_host: StdRwLock<Option<Arc<dyn crate::member_live::MemberLiveHost>>>,
5472 live_commands_served: std::sync::atomic::AtomicU64,
5476 member_incarnation_slots: StdRwLock<HashMap<SessionId, Arc<MemberResidencySlot>>>,
5487 auth_lease: StdRwLock<meerkat_core::handles::GeneratedAuthLeaseHandle>,
5490 #[cfg(not(target_arch = "wasm32"))]
5493 oauth_flows: StdRwLock<Arc<dyn meerkat_auth_core::oauth_flow::OAuthFlowAuthority>>,
5494 #[cfg(feature = "live")]
5498 live_unbound_rejection_authority: crate::driver::ephemeral::SharedIngressDslAuthority,
5499 session_claims: Arc<crate::handles::RuntimeSessionClaimRegistry>,
5506 composition_signal_dispatcher:
5510 StdRwLock<Option<composition::MeerkatCompositionSignalDispatcher>>,
5511 #[cfg(feature = "test-support")]
5515 test_stop_executor_after_ensure: std::sync::atomic::AtomicBool,
5516 #[cfg(feature = "test-support")]
5520 test_pause_executor_after_ensure: std::sync::atomic::AtomicBool,
5521 #[cfg(feature = "test-support")]
5522 test_executor_after_ensure_pause_reached: crate::tokio::sync::Notify,
5523 #[cfg(feature = "test-support")]
5524 test_executor_after_ensure_pause_release: crate::tokio::sync::Notify,
5525 #[cfg(test)]
5528 test_fenced_accept_after_lease: StdMutex<
5529 Option<(
5530 crate::tokio::sync::oneshot::Sender<()>,
5531 crate::tokio::sync::oneshot::Receiver<()>,
5532 )>,
5533 >,
5534 #[cfg(test)]
5539 test_registration_transaction_contention_probe:
5540 StdMutex<Option<(SessionId, crate::tokio::sync::oneshot::Sender<bool>)>>,
5541 #[cfg(test)]
5545 test_fail_post_stop_unregister_after_fence: StdMutex<Option<SessionId>>,
5546 #[cfg(any(test, feature = "test-support"))]
5550 test_runtime_loop_before_queue_authority: StdMutex<
5551 Option<(
5552 SessionId,
5553 crate::tokio::sync::oneshot::Sender<()>,
5554 crate::tokio::sync::oneshot::Receiver<()>,
5555 )>,
5556 >,
5557 #[cfg(test)]
5562 test_control_command_after_logical_lookup: StdMutex<
5563 Option<(
5564 ControlCommandLookupTestKind,
5565 SessionId,
5566 crate::tokio::sync::oneshot::Sender<()>,
5567 crate::tokio::sync::oneshot::Receiver<()>,
5568 )>,
5569 >,
5570}
5571
5572#[cfg(test)]
5573#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5574enum ControlCommandLookupTestKind {
5575 PublishEvent,
5576 Recycle,
5577 Recover,
5578 Destroy,
5579 Retire,
5580}
5581
5582pub struct MemberResidencyUpdate {
5589 adapter: Arc<MeerkatMachine>,
5590 session_id: SessionId,
5591 slot: Arc<MemberResidencySlot>,
5592 slot_guard: Option<crate::tokio::sync::OwnedMutexGuard<()>>,
5593 committed: bool,
5594}
5595
5596pub struct MemberResidencyPublication {
5604 _slot_guard: crate::tokio::sync::OwnedMutexGuard<()>,
5605}
5606
5607struct StagedMemberResidencyPublication {
5615 slot: Arc<MemberResidencySlot>,
5616 slot_guard: Option<crate::tokio::sync::OwnedMutexGuard<()>>,
5617}
5618
5619impl StagedMemberResidencyPublication {
5620 fn finalize(mut self) -> Result<MemberResidencyPublication, RuntimeDriverError> {
5621 let slot_guard = self.slot_guard.take().ok_or_else(|| {
5622 RuntimeDriverError::Internal(
5623 "staged member residency lost its exclusive slot guard".to_string(),
5624 )
5625 })?;
5626 Ok(MemberResidencyPublication {
5627 _slot_guard: slot_guard,
5628 })
5629 }
5630}
5631
5632impl Drop for StagedMemberResidencyPublication {
5633 fn drop(&mut self) {
5634 if self.slot_guard.is_none() {
5635 return;
5636 }
5637 *self
5638 .slot
5639 .state
5640 .write()
5641 .unwrap_or_else(std::sync::PoisonError::into_inner) =
5642 MemberResidencyState::VacantPlaced;
5643 }
5644}
5645
5646impl MemberResidencyUpdate {
5647 fn validate_for_retained_attachment(
5648 &self,
5649 adapter: &Arc<MeerkatMachine>,
5650 witness: &RuntimeExecutorAttachmentWitness,
5651 incarnation: &meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation,
5652 ) -> Result<(), RuntimeDriverError> {
5653 if !Arc::ptr_eq(&self.adapter, adapter) {
5654 return Err(RuntimeDriverError::StaleAuthority {
5655 reason: "member residency update belongs to another machine".to_string(),
5656 });
5657 }
5658 if &self.session_id != witness.session_id() {
5659 return Err(RuntimeDriverError::StaleAuthority {
5660 reason: format!(
5661 "member residency update for session {} cannot publish attachment for {}",
5662 self.session_id,
5663 witness.session_id()
5664 ),
5665 });
5666 }
5667 if self.slot_guard.is_none() {
5668 return Err(RuntimeDriverError::Internal(
5669 "member residency update lost its publication guard".to_string(),
5670 ));
5671 }
5672 self.adapter
5673 .validate_member_incarnation_registration(&self.session_id, incarnation)
5674 }
5675
5676 fn stage_under_retained_mutation_gate(
5677 mut self,
5678 incarnation: meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation,
5679 tracked_turn_journal: Option<Arc<dyn crate::member_observation::TrackedTurnJournal>>,
5680 session_mutation_gate: Arc<crate::tokio::sync::Mutex<()>>,
5681 ) -> Result<StagedMemberResidencyPublication, RuntimeDriverError> {
5682 let slot_guard = self.slot_guard.take().ok_or_else(|| {
5683 RuntimeDriverError::Internal(
5684 "member residency update lost its publication guard".to_string(),
5685 )
5686 })?;
5687 *self
5688 .slot
5689 .state
5690 .write()
5691 .unwrap_or_else(std::sync::PoisonError::into_inner) =
5692 MemberResidencyState::Placed(MemberIncarnationRegistration {
5693 incarnation,
5694 session_mutation_gate,
5695 tracked_turn_journal,
5696 });
5697 self.committed = true;
5698 Ok(StagedMemberResidencyPublication {
5699 slot: Arc::clone(&self.slot),
5700 slot_guard: Some(slot_guard),
5701 })
5702 }
5703
5704 pub async fn commit(
5707 self,
5708 incarnation: meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation,
5709 tracked_turn_journal: Option<Arc<dyn crate::member_observation::TrackedTurnJournal>>,
5710 ) -> Result<MemberResidencyPublication, RuntimeDriverError> {
5711 self.adapter
5712 .validate_member_incarnation_registration(&self.session_id, &incarnation)?;
5713 let session_mutation_gate = self
5714 .adapter
5715 .lock_current_session_mutation_gate(&self.session_id)
5716 .await
5717 .ok_or(RuntimeDriverError::NotReady {
5718 state: RuntimeState::Destroyed,
5719 })?;
5720 let gate = {
5721 let sessions = self.adapter.sessions.read().await;
5722 Arc::clone(
5723 &sessions
5724 .get(&self.session_id)
5725 .ok_or(RuntimeDriverError::NotReady {
5726 state: RuntimeState::Destroyed,
5727 })?
5728 .mutation_gate,
5729 )
5730 };
5731 let publication = self
5732 .stage_under_retained_mutation_gate(incarnation, tracked_turn_journal, gate)
5733 .and_then(StagedMemberResidencyPublication::finalize);
5734 drop(session_mutation_gate);
5735 publication
5736 }
5737
5738 pub fn vacate(mut self) -> Result<MemberResidencyPublication, RuntimeDriverError> {
5741 let slot_guard = self.slot_guard.take().ok_or_else(|| {
5742 RuntimeDriverError::Internal(
5743 "member residency update lost its vacancy publication guard".to_string(),
5744 )
5745 })?;
5746 *self
5747 .slot
5748 .state
5749 .write()
5750 .unwrap_or_else(std::sync::PoisonError::into_inner) =
5751 MemberResidencyState::VacantPlaced;
5752 self.committed = true;
5753 Ok(MemberResidencyPublication {
5754 _slot_guard: slot_guard,
5755 })
5756 }
5757}
5758
5759impl Drop for MemberResidencyUpdate {
5760 fn drop(&mut self) {
5761 if self.committed {
5762 return;
5763 }
5764 *self
5765 .slot
5766 .state
5767 .write()
5768 .unwrap_or_else(std::sync::PoisonError::into_inner) =
5769 MemberResidencyState::VacantPlaced;
5770 }
5771}
5772
5773#[derive(Clone)]
5780pub struct MeerkatMachine {
5781 shared: Arc<MeerkatMachineShared>,
5782}
5783
5784impl std::ops::Deref for MeerkatMachine {
5785 type Target = MeerkatMachineShared;
5786
5787 fn deref(&self) -> &Self::Target {
5788 self.shared.as_ref()
5789 }
5790}
5791
5792impl MeerkatMachine {
5793 fn session_registration_transaction_slot(&self, session_id: &SessionId) -> Arc<Mutex<()>> {
5803 let mut slots = self
5804 .registration_transaction_slots
5805 .write()
5806 .unwrap_or_else(std::sync::PoisonError::into_inner);
5807 if let Some(slot) = slots.get(session_id).and_then(std::sync::Weak::upgrade) {
5808 return slot;
5809 }
5810 slots.retain(|_, slot| slot.upgrade().is_some());
5811 let slot = Arc::new(Mutex::new(()));
5812 slots.insert(session_id.clone(), Arc::downgrade(&slot));
5813 slot
5814 }
5815
5816 async fn lock_session_registration_transaction(
5817 &self,
5818 session_id: &SessionId,
5819 ) -> crate::tokio::sync::OwnedMutexGuard<()> {
5820 let slot = self.session_registration_transaction_slot(session_id);
5821 #[cfg(test)]
5822 let contention_probe = {
5823 let mut probe = self
5824 .test_registration_transaction_contention_probe
5825 .lock()
5826 .unwrap_or_else(std::sync::PoisonError::into_inner);
5827 if probe
5828 .as_ref()
5829 .is_some_and(|(probe_session_id, _)| probe_session_id == session_id)
5830 {
5831 probe.take().map(|(_, sender)| sender)
5832 } else {
5833 None
5834 }
5835 };
5836 #[cfg(test)]
5837 if let Some(contention_probe) = contention_probe {
5838 return match Arc::clone(&slot).try_lock_owned() {
5839 Ok(guard) => {
5840 let _ = contention_probe.send(false);
5841 guard
5842 }
5843 Err(_) => {
5844 let _ = contention_probe.send(true);
5845 slot.lock_owned().await
5846 }
5847 };
5848 }
5849 slot.lock_owned().await
5850 }
5851
5852 #[cfg(test)]
5853 pub(crate) fn probe_next_session_registration_transaction_contention_for_test(
5854 &self,
5855 session_id: &SessionId,
5856 ) -> crate::tokio::sync::oneshot::Receiver<bool> {
5857 let (sender, receiver) = crate::tokio::sync::oneshot::channel();
5858 *self
5859 .test_registration_transaction_contention_probe
5860 .lock()
5861 .unwrap_or_else(std::sync::PoisonError::into_inner) =
5862 Some((session_id.clone(), sender));
5863 receiver
5864 }
5865
5866 pub async fn begin_member_residency_update(
5869 self: &Arc<Self>,
5870 session_id: SessionId,
5871 ) -> MemberResidencyUpdate {
5872 let slot = self.member_incarnation_slot(&session_id);
5873 let slot_guard = Arc::clone(&slot.gate).lock_owned().await;
5874 *slot
5875 .state
5876 .write()
5877 .unwrap_or_else(std::sync::PoisonError::into_inner) =
5878 MemberResidencyState::VacantPlaced;
5879 MemberResidencyUpdate {
5880 adapter: Arc::clone(self),
5881 session_id,
5882 slot,
5883 slot_guard: Some(slot_guard),
5884 committed: false,
5885 }
5886 }
5887
5888 #[must_use]
5891 pub fn session_control_authority(&self) -> MachineSessionControlAuthority {
5892 MachineSessionControlAuthority { _private: () }
5893 }
5894
5895 pub fn set_member_observation_host(
5900 &self,
5901 host: Arc<dyn crate::member_observation::MemberObservationHost>,
5902 ) {
5903 *self
5904 .member_observation_host
5905 .write()
5906 .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(host);
5907 }
5908
5909 pub(crate) fn member_observation_host(
5911 &self,
5912 ) -> Option<Arc<dyn crate::member_observation::MemberObservationHost>> {
5913 self.member_observation_host
5914 .read()
5915 .unwrap_or_else(std::sync::PoisonError::into_inner)
5916 .clone()
5917 }
5918
5919 pub fn set_member_live_host(&self, host: Arc<dyn crate::member_live::MemberLiveHost>) {
5924 *self
5925 .member_live_host
5926 .write()
5927 .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(host);
5928 }
5929
5930 pub(crate) fn member_live_host(&self) -> Option<Arc<dyn crate::member_live::MemberLiveHost>> {
5932 self.member_live_host
5933 .read()
5934 .unwrap_or_else(std::sync::PoisonError::into_inner)
5935 .clone()
5936 }
5937
5938 #[cfg(feature = "live")]
5942 pub async fn acquire_live_open_lifecycle_lease(
5943 &self,
5944 session_id: &meerkat_core::types::SessionId,
5945 ) -> Result<crate::member_live::MemberLiveLifecycleLease, RuntimeDriverError> {
5946 let (gate, guard) = self
5947 .lock_current_session_live_lifecycle_gate(session_id)
5948 .await
5949 .ok_or(RuntimeDriverError::NotReady {
5950 state: RuntimeState::Destroyed,
5951 })?;
5952 Ok(crate::member_live::MemberLiveLifecycleLease::new(
5953 session_id.clone(),
5954 gate,
5955 guard,
5956 ))
5957 }
5958
5959 #[cfg(feature = "live")]
5964 pub async fn acquire_member_live_disposal_lease(
5965 &self,
5966 session_id: &meerkat_core::types::SessionId,
5967 ) -> Result<crate::member_live::MemberLiveLifecycleLease, crate::member_live::MemberLiveError>
5968 {
5969 let (gate, guard) = self
5970 .lock_current_session_live_lifecycle_gate(session_id)
5971 .await
5972 .ok_or_else(|| crate::member_live::MemberLiveError::Unavailable {
5973 reason: format!(
5974 "member-live lifecycle gate is unavailable for session {session_id}"
5975 ),
5976 })?;
5977 let lease =
5978 crate::member_live::MemberLiveLifecycleLease::new(session_id.clone(), gate, guard);
5979
5980 self.prove_member_live_absence_while_lease_held(session_id, &lease)
5981 .await?;
5982 Ok(lease)
5983 }
5984
5985 #[cfg(feature = "live")]
5991 async fn prove_member_live_absence_while_lease_held(
5992 &self,
5993 session_id: &meerkat_core::types::SessionId,
5994 lease: &crate::member_live::MemberLiveLifecycleLease,
5995 ) -> Result<(), crate::member_live::MemberLiveError> {
5996 if lease.session_id() != session_id {
5997 return Err(crate::member_live::MemberLiveError::Internal {
5998 reason: format!(
5999 "member-live lifecycle lease for session {} cannot dispose session {session_id}",
6000 lease.session_id()
6001 ),
6002 });
6003 }
6004 let lease_is_current = {
6005 let sessions = self.sessions.read().await;
6006 sessions
6007 .get(session_id)
6008 .is_some_and(|entry| lease.matches_gate(&entry.live_lifecycle_gate))
6009 };
6010 if !lease_is_current {
6011 return Err(crate::member_live::MemberLiveError::Unavailable {
6012 reason: format!("member-live lifecycle lease is stale for session {session_id}"),
6013 });
6014 }
6015
6016 let Some(channel_id) = self.live_active_channel_for_session(session_id).await else {
6017 return Ok(());
6018 };
6019 let Some(host) = self.member_live_host() else {
6020 return Err(crate::member_live::MemberLiveError::Unavailable {
6021 reason: format!(
6022 "session {session_id} still owns live channel '{channel_id}', but no transport-neutral live cleanup host is composed"
6023 ),
6024 });
6025 };
6026 let timeout = crate::member_live::MEMBER_LIVE_DISPOSAL_CEILING;
6027 match crate::tokio::time::timeout(timeout, host.close(session_id, channel_id.as_str()))
6028 .await
6029 {
6030 Ok(Ok(_)) => {}
6031 Ok(Err(error)) => return Err(error),
6032 Err(_) => {
6033 return Err(crate::member_live::MemberLiveError::Unavailable {
6034 reason: format!(
6035 "member-live disposal close for channel '{channel_id}' timed out after {}ms for session {session_id}",
6036 timeout.as_millis()
6037 ),
6038 });
6039 }
6040 }
6041 if let Some(still_active) = self.live_active_channel_for_session(session_id).await {
6042 return Err(crate::member_live::MemberLiveError::Internal {
6043 reason: format!(
6044 "member-live close returned success but machine channel '{still_active}' remains active for session {session_id}"
6045 ),
6046 });
6047 }
6048 Ok(())
6049 }
6050
6051 #[cfg(feature = "live")]
6064 pub async fn close_member_live_channel_for_disposal(
6065 &self,
6066 session_id: &meerkat_core::types::SessionId,
6067 ) -> Result<(), crate::member_live::MemberLiveError> {
6068 let _lease = self.acquire_member_live_disposal_lease(session_id).await?;
6069 Ok(())
6070 }
6071
6072 pub(crate) fn note_live_command_served(&self) {
6076 self.live_commands_served
6077 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6078 }
6079
6080 pub fn live_commands_served(&self) -> u64 {
6082 self.live_commands_served
6083 .load(std::sync::atomic::Ordering::Relaxed)
6084 }
6085
6086 pub fn tracked_turn_journal(
6090 &self,
6091 session_id: &SessionId,
6092 ) -> Option<Arc<dyn crate::member_observation::TrackedTurnJournal>> {
6093 let slot = self
6094 .member_incarnation_slots
6095 .read()
6096 .unwrap_or_else(std::sync::PoisonError::into_inner)
6097 .get(session_id)
6098 .cloned()?;
6099 let state = slot
6100 .state
6101 .read()
6102 .unwrap_or_else(std::sync::PoisonError::into_inner);
6103 match &*state {
6104 MemberResidencyState::Placed(registration) => registration.tracked_turn_journal.clone(),
6105 MemberResidencyState::PeerOnly | MemberResidencyState::VacantPlaced => None,
6106 }
6107 }
6108
6109 fn validate_member_incarnation_registration(
6110 &self,
6111 session_id: &SessionId,
6112 incarnation: &meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation,
6113 ) -> Result<(), RuntimeDriverError> {
6114 if incarnation.mob_id.is_empty()
6115 || incarnation.agent_identity.is_empty()
6116 || incarnation.host_id.is_empty()
6117 || incarnation.member_session_id != session_id.to_string()
6118 || incarnation.binding_generation == 0
6119 || incarnation.fence_token == 0
6120 {
6121 return Err(RuntimeDriverError::ValidationFailed {
6122 reason: format!(
6123 "member incarnation registration requires complete identities plus nonzero host-binding generation and fence for session '{session_id}': {incarnation:?}"
6124 ),
6125 });
6126 }
6127 Ok(())
6128 }
6129
6130 pub fn member_incarnation(
6135 &self,
6136 session_id: &SessionId,
6137 ) -> Option<meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation> {
6138 let slot = self
6139 .member_incarnation_slots
6140 .read()
6141 .unwrap_or_else(std::sync::PoisonError::into_inner)
6142 .get(session_id)
6143 .cloned()?;
6144 let state = slot
6145 .state
6146 .read()
6147 .unwrap_or_else(std::sync::PoisonError::into_inner);
6148 match &*state {
6149 MemberResidencyState::Placed(registration) => Some(registration.incarnation.clone()),
6150 MemberResidencyState::PeerOnly | MemberResidencyState::VacantPlaced => None,
6151 }
6152 }
6153
6154 #[cfg(test)]
6155 pub(crate) fn test_member_residency_is_vacant(&self, session_id: &SessionId) -> bool {
6156 self.member_incarnation_slots
6157 .read()
6158 .unwrap_or_else(std::sync::PoisonError::into_inner)
6159 .get(session_id)
6160 .is_some_and(|slot| {
6161 matches!(
6162 &*slot
6163 .state
6164 .read()
6165 .unwrap_or_else(std::sync::PoisonError::into_inner),
6166 MemberResidencyState::VacantPlaced
6167 )
6168 })
6169 }
6170
6171 fn member_incarnation_slot(&self, session_id: &SessionId) -> Arc<MemberResidencySlot> {
6172 if let Some(slot) = self
6173 .member_incarnation_slots
6174 .read()
6175 .unwrap_or_else(std::sync::PoisonError::into_inner)
6176 .get(session_id)
6177 .cloned()
6178 {
6179 return slot;
6180 }
6181 Arc::clone(
6182 self.member_incarnation_slots
6183 .write()
6184 .unwrap_or_else(std::sync::PoisonError::into_inner)
6185 .entry(session_id.clone())
6186 .or_insert_with(|| Arc::new(MemberResidencySlot::peer_only())),
6187 )
6188 }
6189
6190 pub(crate) async fn lock_member_effect_authority(
6196 &self,
6197 session_id: &SessionId,
6198 expected: &meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation,
6199 ) -> Result<MemberEffectAuthorityGuard, RuntimeDriverError> {
6200 self.lock_optional_member_effect_authority(session_id, Some(expected))
6201 .await
6202 }
6203
6204 pub(crate) async fn lock_optional_member_effect_authority(
6209 &self,
6210 session_id: &SessionId,
6211 expected: Option<&meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation>,
6212 ) -> Result<MemberEffectAuthorityGuard, RuntimeDriverError> {
6213 let lease = self
6214 .acquire_member_effect_authority_lease(session_id, expected)
6215 .await?;
6216 let MemberEffectAuthorityLease {
6217 slot_guard,
6218 session_mutation_gate,
6219 ..
6220 } = lease;
6221 let session_guard = Arc::clone(&session_mutation_gate).lock_owned().await;
6222 {
6223 let sessions = self.sessions.read().await;
6224 let Some(entry) = sessions.get(session_id) else {
6225 return Err(RuntimeDriverError::StaleAuthority {
6226 reason: format!(
6227 "member effect expected incarnation {expected:?}; runtime session is absent"
6228 ),
6229 });
6230 };
6231 if !Arc::ptr_eq(&entry.mutation_gate, &session_mutation_gate) {
6232 return Err(RuntimeDriverError::StaleAuthority {
6233 reason: format!(
6234 "member effect expected incarnation {expected:?}; runtime session was replaced"
6235 ),
6236 });
6237 }
6238 }
6239 let state = self
6240 .existing_session_runtime_state(session_id)
6241 .await
6242 .unwrap_or(RuntimeState::Destroyed);
6243 self.reject_unregistration_drain_ingress(session_id, state)
6244 .await?;
6245 Ok(MemberEffectAuthorityGuard {
6246 _slot_guard: slot_guard,
6247 _session_guard: session_guard,
6248 })
6249 }
6250
6251 async fn acquire_member_effect_authority_lease(
6252 &self,
6253 session_id: &SessionId,
6254 expected: Option<&meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation>,
6255 ) -> Result<MemberEffectAuthorityLease, RuntimeDriverError> {
6256 let slot = self.member_incarnation_slot(session_id);
6257 let slot_guard = Arc::clone(&slot.gate).lock_owned().await;
6258 let registered_gate = {
6259 let state = slot
6260 .state
6261 .read()
6262 .unwrap_or_else(std::sync::PoisonError::into_inner);
6263 match (expected, &*state) {
6264 (Some(expected), MemberResidencyState::Placed(registration))
6265 if ®istration.incarnation == expected =>
6266 {
6267 Some(Arc::clone(®istration.session_mutation_gate))
6268 }
6269 (Some(expected), MemberResidencyState::Placed(registration)) => {
6270 return Err(RuntimeDriverError::StaleAuthority {
6271 reason: format!(
6272 "member effect expected incarnation {expected:?}; current is {:?}",
6273 registration.incarnation
6274 ),
6275 });
6276 }
6277 (
6278 Some(expected),
6279 MemberResidencyState::PeerOnly | MemberResidencyState::VacantPlaced,
6280 ) => {
6281 return Err(RuntimeDriverError::StaleAuthority {
6282 reason: format!(
6283 "member effect expected incarnation {expected:?}; current residency is absent"
6284 ),
6285 });
6286 }
6287 (None, MemberResidencyState::Placed(registration)) => {
6288 return Err(RuntimeDriverError::StaleAuthority {
6289 reason: format!(
6290 "peer-only member effect expected no host residency; current is {:?}",
6291 registration.incarnation
6292 ),
6293 });
6294 }
6295 (None, MemberResidencyState::VacantPlaced) => {
6296 return Err(RuntimeDriverError::StaleAuthority {
6297 reason: "peer-only member effect cannot address a vacant placed residency"
6298 .to_string(),
6299 });
6300 }
6301 (None, MemberResidencyState::PeerOnly) => None,
6302 }
6303 };
6304 let registered_gate = match registered_gate {
6305 Some(gate) => gate,
6306 None => self.session_mutation_gate(session_id).await.ok_or(
6307 RuntimeDriverError::NotReady {
6308 state: RuntimeState::Destroyed,
6309 },
6310 )?,
6311 };
6312 {
6313 let sessions = self.sessions.read().await;
6314 let Some(entry) = sessions.get(session_id) else {
6315 return Err(RuntimeDriverError::StaleAuthority {
6316 reason: format!(
6317 "member effect expected incarnation {expected:?}; runtime session is absent"
6318 ),
6319 });
6320 };
6321 if !Arc::ptr_eq(&entry.mutation_gate, ®istered_gate) {
6322 return Err(RuntimeDriverError::StaleAuthority {
6323 reason: format!(
6324 "member effect expected incarnation {expected:?}; runtime session was replaced"
6325 ),
6326 });
6327 }
6328 }
6329 Ok(MemberEffectAuthorityLease {
6330 slot,
6331 slot_guard,
6332 session_mutation_gate: registered_gate,
6333 })
6334 }
6335
6336 fn validate_member_effect_authority_lease_current(
6340 &self,
6341 session_id: &SessionId,
6342 lease: &MemberEffectAuthorityLease,
6343 expected: Option<&meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation>,
6344 ) -> Result<(), RuntimeDriverError> {
6345 let slot_is_current = self
6346 .member_incarnation_slots
6347 .read()
6348 .unwrap_or_else(std::sync::PoisonError::into_inner)
6349 .get(session_id)
6350 .is_some_and(|slot| Arc::ptr_eq(slot, &lease.slot));
6351 if !slot_is_current {
6352 return Err(RuntimeDriverError::StaleAuthority {
6353 reason: "member effect residency slot was replaced".to_string(),
6354 });
6355 }
6356
6357 let state = lease
6358 .slot
6359 .state
6360 .read()
6361 .unwrap_or_else(std::sync::PoisonError::into_inner);
6362 match (expected, &*state) {
6363 (Some(expected), MemberResidencyState::Placed(registration))
6364 if ®istration.incarnation == expected
6365 && Arc::ptr_eq(
6366 ®istration.session_mutation_gate,
6367 &lease.session_mutation_gate,
6368 ) =>
6369 {
6370 Ok(())
6371 }
6372 (Some(expected), MemberResidencyState::Placed(registration)) => {
6373 Err(RuntimeDriverError::StaleAuthority {
6374 reason: format!(
6375 "member effect expected incarnation {expected:?}; current is {:?}",
6376 registration.incarnation
6377 ),
6378 })
6379 }
6380 (
6381 Some(expected),
6382 MemberResidencyState::PeerOnly | MemberResidencyState::VacantPlaced,
6383 ) => Err(RuntimeDriverError::StaleAuthority {
6384 reason: format!(
6385 "member effect expected incarnation {expected:?}; current residency is absent"
6386 ),
6387 }),
6388 (None, MemberResidencyState::PeerOnly) => Ok(()),
6389 (None, MemberResidencyState::Placed(registration)) => {
6390 Err(RuntimeDriverError::StaleAuthority {
6391 reason: format!(
6392 "peer-only member effect expected no host residency; current is {:?}",
6393 registration.incarnation
6394 ),
6395 })
6396 }
6397 (None, MemberResidencyState::VacantPlaced) => Err(RuntimeDriverError::StaleAuthority {
6398 reason: "peer-only member effect cannot address a vacant placed residency"
6399 .to_string(),
6400 }),
6401 }
6402 }
6403
6404 #[must_use]
6409 pub fn shares_runtime_persistence_with(&self, other: &Self) -> bool {
6410 match (&self.store, &other.store) {
6411 (None, None) => true,
6412 (Some(a), Some(b)) => runtime_stores_share_authority(a, b),
6413 _ => false,
6414 }
6415 }
6416
6417 #[must_use]
6420 pub fn shares_runtime_store_authority(&self, store: &Arc<dyn RuntimeStore>) -> bool {
6421 self.store
6422 .as_ref()
6423 .is_some_and(|machine_store| runtime_stores_share_authority(machine_store, store))
6424 }
6425
6426 #[must_use]
6428 pub fn has_runtime_persistence(&self) -> bool {
6429 self.store.is_some()
6430 }
6431
6432 fn normalize_destroyed_error(err: RuntimeDriverError) -> RuntimeDriverError {
6433 match err {
6434 RuntimeDriverError::NotReady {
6435 state: RuntimeState::Destroyed,
6436 } => RuntimeDriverError::Destroyed,
6437 other => other,
6438 }
6439 }
6440
6441 pub fn ephemeral() -> Self {
6443 let auth_lease = Arc::new(crate::handles::RuntimeAuthLeaseHandle::new());
6444 #[cfg(not(target_arch = "wasm32"))]
6445 let oauth_flows = Arc::new(crate::handles::RuntimeOAuthFlowHandle::new_with_auth_lease(
6446 std::time::Duration::from_secs(10 * 60),
6447 Arc::clone(&auth_lease),
6448 ));
6449 let auth_lease = generated_runtime_auth_lease_handle(auth_lease);
6450 Self {
6451 shared: Arc::new(MeerkatMachineShared {
6452 sessions: RwLock::new(HashMap::new()),
6453 registration_transaction_slots: StdRwLock::new(HashMap::new()),
6454 store: None,
6455 blob_store: None,
6456 llm_reconfigure_host: StdRwLock::new(None),
6457 member_observation_host: StdRwLock::new(None),
6458 member_live_host: StdRwLock::new(None),
6459 live_commands_served: std::sync::atomic::AtomicU64::new(0),
6460 member_incarnation_slots: StdRwLock::new(HashMap::new()),
6461 auth_lease: StdRwLock::new(auth_lease),
6462 #[cfg(not(target_arch = "wasm32"))]
6463 oauth_flows: StdRwLock::new(oauth_flows),
6464 #[cfg(feature = "live")]
6465 live_unbound_rejection_authority: live_unbound_rejection_authority(),
6466 session_claims: Arc::new(crate::handles::RuntimeSessionClaimRegistry::new()),
6467 composition_signal_dispatcher: StdRwLock::new(None),
6468 #[cfg(feature = "test-support")]
6469 test_stop_executor_after_ensure: std::sync::atomic::AtomicBool::new(false),
6470 #[cfg(feature = "test-support")]
6471 test_pause_executor_after_ensure: std::sync::atomic::AtomicBool::new(false),
6472 #[cfg(feature = "test-support")]
6473 test_executor_after_ensure_pause_reached: crate::tokio::sync::Notify::new(),
6474 #[cfg(feature = "test-support")]
6475 test_executor_after_ensure_pause_release: crate::tokio::sync::Notify::new(),
6476 #[cfg(test)]
6477 test_fenced_accept_after_lease: StdMutex::new(None),
6478 #[cfg(test)]
6479 test_registration_transaction_contention_probe: StdMutex::new(None),
6480 #[cfg(test)]
6481 test_fail_post_stop_unregister_after_fence: StdMutex::new(None),
6482 #[cfg(any(test, feature = "test-support"))]
6483 test_runtime_loop_before_queue_authority: StdMutex::new(None),
6484 #[cfg(test)]
6485 test_control_command_after_logical_lookup: StdMutex::new(None),
6486 }),
6487 }
6488 }
6489
6490 pub fn persistent(store: Arc<dyn RuntimeStore>, blob_store: Arc<dyn BlobStore>) -> Self {
6497 #[cfg(not(target_arch = "wasm32"))]
6498 let (auth_lease, oauth_flows) = {
6499 let authorities = persistent_auth_authorities(&store);
6500 (
6501 Arc::clone(&authorities.auth_lease),
6502 Arc::clone(&authorities.oauth_flows),
6503 )
6504 };
6505 #[cfg(target_arch = "wasm32")]
6506 let auth_lease = Arc::new(crate::handles::RuntimeAuthLeaseHandle::new());
6507 let auth_lease = generated_runtime_auth_lease_handle(auth_lease);
6508 Self {
6509 shared: Arc::new(MeerkatMachineShared {
6510 sessions: RwLock::new(HashMap::new()),
6511 registration_transaction_slots: StdRwLock::new(HashMap::new()),
6512 store: Some(store),
6513 blob_store: Some(blob_store),
6514 llm_reconfigure_host: StdRwLock::new(None),
6515 member_observation_host: StdRwLock::new(None),
6516 member_live_host: StdRwLock::new(None),
6517 live_commands_served: std::sync::atomic::AtomicU64::new(0),
6518 member_incarnation_slots: StdRwLock::new(HashMap::new()),
6519 auth_lease: StdRwLock::new(auth_lease),
6520 #[cfg(not(target_arch = "wasm32"))]
6521 oauth_flows: StdRwLock::new(oauth_flows),
6522 #[cfg(feature = "live")]
6523 live_unbound_rejection_authority: live_unbound_rejection_authority(),
6524 session_claims: Arc::new(crate::handles::RuntimeSessionClaimRegistry::new()),
6525 composition_signal_dispatcher: StdRwLock::new(None),
6526 #[cfg(feature = "test-support")]
6527 test_stop_executor_after_ensure: std::sync::atomic::AtomicBool::new(false),
6528 #[cfg(feature = "test-support")]
6529 test_pause_executor_after_ensure: std::sync::atomic::AtomicBool::new(false),
6530 #[cfg(feature = "test-support")]
6531 test_executor_after_ensure_pause_reached: crate::tokio::sync::Notify::new(),
6532 #[cfg(feature = "test-support")]
6533 test_executor_after_ensure_pause_release: crate::tokio::sync::Notify::new(),
6534 #[cfg(test)]
6535 test_fenced_accept_after_lease: StdMutex::new(None),
6536 #[cfg(test)]
6537 test_registration_transaction_contention_probe: StdMutex::new(None),
6538 #[cfg(test)]
6539 test_fail_post_stop_unregister_after_fence: StdMutex::new(None),
6540 #[cfg(any(test, feature = "test-support"))]
6541 test_runtime_loop_before_queue_authority: StdMutex::new(None),
6542 #[cfg(test)]
6543 test_control_command_after_logical_lookup: StdMutex::new(None),
6544 }),
6545 }
6546 }
6547
6548 pub fn persistent_without_blobs(store: Arc<dyn RuntimeStore>) -> Self {
6555 #[cfg(not(target_arch = "wasm32"))]
6556 let (auth_lease, oauth_flows) = {
6557 let authorities = persistent_auth_authorities(&store);
6558 (
6559 Arc::clone(&authorities.auth_lease),
6560 Arc::clone(&authorities.oauth_flows),
6561 )
6562 };
6563 #[cfg(target_arch = "wasm32")]
6564 let auth_lease = Arc::new(crate::handles::RuntimeAuthLeaseHandle::new());
6565 let auth_lease = generated_runtime_auth_lease_handle(auth_lease);
6566 Self {
6567 shared: Arc::new(MeerkatMachineShared {
6568 sessions: RwLock::new(HashMap::new()),
6569 registration_transaction_slots: StdRwLock::new(HashMap::new()),
6570 store: Some(store),
6571 blob_store: Some(Arc::new(UnavailableBlobStore)),
6572 llm_reconfigure_host: StdRwLock::new(None),
6573 member_observation_host: StdRwLock::new(None),
6574 member_live_host: StdRwLock::new(None),
6575 live_commands_served: std::sync::atomic::AtomicU64::new(0),
6576 member_incarnation_slots: StdRwLock::new(HashMap::new()),
6577 auth_lease: StdRwLock::new(auth_lease),
6578 #[cfg(not(target_arch = "wasm32"))]
6579 oauth_flows: StdRwLock::new(oauth_flows),
6580 #[cfg(feature = "live")]
6581 live_unbound_rejection_authority: live_unbound_rejection_authority(),
6582 session_claims: Arc::new(crate::handles::RuntimeSessionClaimRegistry::new()),
6583 composition_signal_dispatcher: StdRwLock::new(None),
6584 #[cfg(feature = "test-support")]
6585 test_stop_executor_after_ensure: std::sync::atomic::AtomicBool::new(false),
6586 #[cfg(feature = "test-support")]
6587 test_pause_executor_after_ensure: std::sync::atomic::AtomicBool::new(false),
6588 #[cfg(feature = "test-support")]
6589 test_executor_after_ensure_pause_reached: crate::tokio::sync::Notify::new(),
6590 #[cfg(feature = "test-support")]
6591 test_executor_after_ensure_pause_release: crate::tokio::sync::Notify::new(),
6592 #[cfg(test)]
6593 test_fenced_accept_after_lease: StdMutex::new(None),
6594 #[cfg(test)]
6595 test_registration_transaction_contention_probe: StdMutex::new(None),
6596 #[cfg(test)]
6597 test_fail_post_stop_unregister_after_fence: StdMutex::new(None),
6598 #[cfg(any(test, feature = "test-support"))]
6599 test_runtime_loop_before_queue_authority: StdMutex::new(None),
6600 #[cfg(test)]
6601 test_control_command_after_logical_lookup: StdMutex::new(None),
6602 }),
6603 }
6604 }
6605
6606 pub fn auth_lease_handle(&self) -> Arc<dyn meerkat_core::handles::AuthLeaseHandle> {
6609 self.generated_auth_lease_handle().clone_handle()
6610 }
6611
6612 pub fn generated_auth_lease_handle(&self) -> meerkat_core::handles::GeneratedAuthLeaseHandle {
6615 self.auth_lease
6616 .read()
6617 .unwrap_or_else(std::sync::PoisonError::into_inner)
6618 .clone()
6619 }
6620
6621 pub fn set_auth_lease_handle(&self, handle: Arc<crate::handles::RuntimeAuthLeaseHandle>) {
6627 self.set_runtime_auth_lease_handle(handle);
6628 }
6629
6630 #[cfg(not(target_arch = "wasm32"))]
6636 pub fn set_auth_lease_handle_with_oauth_flow_authority(
6637 &self,
6638 handle: Arc<crate::handles::RuntimeAuthLeaseHandle>,
6639 oauth_flows: Arc<dyn meerkat_auth_core::oauth_flow::OAuthFlowAuthority>,
6640 ) {
6641 *self
6642 .oauth_flows
6643 .write()
6644 .unwrap_or_else(std::sync::PoisonError::into_inner) = oauth_flows;
6645 let handle = generated_runtime_auth_lease_handle(handle);
6646 *self
6647 .auth_lease
6648 .write()
6649 .unwrap_or_else(std::sync::PoisonError::into_inner) = handle;
6650 }
6651
6652 pub fn set_runtime_auth_lease_handle(
6655 &self,
6656 handle: Arc<crate::handles::RuntimeAuthLeaseHandle>,
6657 ) {
6658 #[cfg(not(target_arch = "wasm32"))]
6659 {
6660 *self
6661 .oauth_flows
6662 .write()
6663 .unwrap_or_else(std::sync::PoisonError::into_inner) =
6664 Arc::new(crate::handles::RuntimeOAuthFlowHandle::new_with_auth_lease(
6665 std::time::Duration::from_secs(10 * 60),
6666 Arc::clone(&handle),
6667 ));
6668 }
6669 let handle = generated_runtime_auth_lease_handle(handle);
6670 *self
6671 .auth_lease
6672 .write()
6673 .unwrap_or_else(std::sync::PoisonError::into_inner) = handle;
6674 }
6675
6676 #[cfg(not(target_arch = "wasm32"))]
6679 pub fn oauth_flow_authority(
6680 &self,
6681 ) -> Arc<dyn meerkat_auth_core::oauth_flow::OAuthFlowAuthority> {
6682 Arc::clone(
6683 &self
6684 .oauth_flows
6685 .read()
6686 .unwrap_or_else(std::sync::PoisonError::into_inner),
6687 )
6688 }
6689
6690 pub fn session_claim_handle(&self) -> Arc<dyn meerkat_core::handles::SessionClaimHandle> {
6695 Arc::clone(&self.session_claims) as Arc<dyn meerkat_core::handles::SessionClaimHandle>
6696 }
6697
6698 pub fn set_composition_signal_dispatcher(
6701 &self,
6702 dispatcher: composition::MeerkatCompositionSignalDispatcher,
6703 ) {
6704 let mut slot = self
6705 .composition_signal_dispatcher
6706 .write()
6707 .unwrap_or_else(std::sync::PoisonError::into_inner);
6708 *slot = Some(dispatcher);
6709 }
6710
6711 pub(crate) async fn apply_routed_meerkat_input(
6722 &self,
6723 session_id: &SessionId,
6724 input: dsl::MeerkatMachineInput,
6725 ) -> Result<(), dsl_authority::DslTransitionRefusal> {
6726 let _gate_guard = self
6727 .lock_current_session_mutation_gate(session_id)
6728 .await
6729 .ok_or_else(|| {
6730 dsl_authority::DslTransitionRefusal::other(
6731 "routed_session_not_registered",
6732 format!(
6733 "session `{session_id}` is not registered with this MeerkatMachine; \
6734 cannot deliver routed input"
6735 ),
6736 )
6737 })?;
6738 self.apply_routed_session_dsl_input(session_id, input, "RoutedMeerkatInput")
6739 .await
6740 .map(|_| ())
6741 }
6742
6743 #[cfg(test)]
6744 pub(crate) async fn debug_shared_ingress_authorities(
6745 &self,
6746 session_id: &SessionId,
6747 ) -> Option<(
6748 Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
6749 crate::driver::ephemeral::SharedIngressDslAuthority,
6750 )> {
6751 let sessions = self.sessions.read().await;
6752 let entry = sessions.get(session_id)?;
6753 let session_authority = Arc::clone(&entry.dsl_authority);
6754 let driver = entry.driver.lock().await;
6755 Some((session_authority, driver.shared_dsl_authority()))
6756 }
6757
6758 fn make_driver(
6760 &self,
6761 runtime_id: LogicalRuntimeId,
6762 dsl_authority: crate::driver::ephemeral::SharedIngressDslAuthority,
6763 initial_runtime_state: RuntimeState,
6764 ) -> DriverEntry {
6765 let control_projection = Arc::new(StdRwLock::new(
6766 crate::driver::ephemeral::RuntimeControlProjection {
6767 phase: initial_runtime_state,
6768 current_run_id: None,
6769 pre_run_phase: None,
6770 },
6771 ));
6772 match (&self.store, &self.blob_store) {
6773 (Some(store), Some(blob_store)) => {
6774 DriverEntry::Persistent(PersistentRuntimeDriver::new_with_control(
6775 runtime_id,
6776 store.clone(),
6777 blob_store.clone(),
6778 control_projection,
6779 dsl_authority,
6780 ))
6781 }
6782 _ => DriverEntry::Ephemeral(EphemeralRuntimeDriver::new_with_control_and_dsl(
6783 runtime_id,
6784 control_projection,
6785 dsl_authority,
6786 )),
6787 }
6788 }
6789
6790 async fn recover_or_create_ops_state(
6797 &self,
6798 session_id: &SessionId,
6799 runtime_id: &LogicalRuntimeId,
6800 ) -> Result<
6801 (
6802 Arc<crate::ops_lifecycle::RuntimeOpsLifecycleRegistry>,
6803 meerkat_core::RuntimeEpochId,
6804 Arc<meerkat_core::EpochCursorState>,
6805 ),
6806 RuntimeDriverError,
6807 > {
6808 if let Some(ref store) = self.store {
6809 let (registry, epoch_id, cursor_state) = Self::fresh_ops_state();
6810 let initial_snapshot = registry
6816 .capture_persistence_snapshot(epoch_id.clone(), cursor_state.as_ref())
6817 .map_err(|error| {
6818 RuntimeDriverError::Internal(format!(
6819 "failed to capture initial ops lifecycle authority for session {session_id}: {error}"
6820 ))
6821 })?;
6822 let canonical_snapshot = store
6823 .initialize_ops_lifecycle_if_absent(runtime_id, &initial_snapshot)
6824 .await
6825 .map_err(|error| {
6826 RuntimeDriverError::Internal(format!(
6827 "failed to initialize ops lifecycle authority for session {session_id}: {error}"
6828 ))
6829 })?;
6830 let recovered_epoch = canonical_snapshot.epoch_id.clone();
6831 let initialized = recovered_epoch == epoch_id;
6832 let recovered_ops_count = canonical_snapshot.completion_entries.len();
6833 let registry = crate::ops_lifecycle::RuntimeOpsLifecycleRegistry::from_recovered(
6834 canonical_snapshot,
6835 )
6836 .map_err(|error| {
6837 tracing::error!(
6838 %session_id,
6839 %runtime_id,
6840 error = %error,
6841 "failed to recover ops lifecycle through generated authority"
6842 );
6843 RuntimeDriverError::Internal(format!(
6844 "failed to recover ops lifecycle through generated authority: {error}"
6845 ))
6846 })?;
6847 let recovered_cursor_snapshot = registry.completion_cursor_snapshot();
6848 let recovered_cursors = meerkat_core::EpochCursorState::from_recovered(
6849 recovered_cursor_snapshot.agent_applied_cursor,
6850 recovered_cursor_snapshot.runtime_observed_seq,
6851 recovered_cursor_snapshot.runtime_last_injected_seq,
6852 );
6853 tracing::info!(
6854 %session_id,
6855 %runtime_id,
6856 epoch_id = %recovered_epoch,
6857 recovered_ops = recovered_ops_count,
6858 initialized,
6859 "ops lifecycle authority selected from durable store"
6860 );
6861 Ok((
6862 Arc::new(registry),
6863 recovered_epoch,
6864 Arc::new(recovered_cursors),
6865 ))
6866 } else {
6867 Ok((
6868 Arc::new(crate::ops_lifecycle::RuntimeOpsLifecycleRegistry::new()),
6869 meerkat_core::RuntimeEpochId::new(),
6870 Arc::new(meerkat_core::EpochCursorState::new()),
6871 ))
6872 }
6873 }
6874
6875 fn fresh_ops_state() -> (
6876 Arc<crate::ops_lifecycle::RuntimeOpsLifecycleRegistry>,
6877 meerkat_core::RuntimeEpochId,
6878 Arc<meerkat_core::EpochCursorState>,
6879 ) {
6880 let registry = Arc::new(crate::ops_lifecycle::RuntimeOpsLifecycleRegistry::new());
6881 let epoch = meerkat_core::RuntimeEpochId::new();
6882 let cursors = Arc::new(meerkat_core::EpochCursorState::new());
6883 (registry, epoch, cursors)
6884 }
6885
6886 #[allow(clippy::large_futures)]
6887 fn execute_meerkat_machine_command(
6888 &self,
6889 self_handle: Option<Arc<Self>>,
6890 command: MeerkatMachineCommand,
6891 ) -> MeerkatMachineCommandFuture<'_> {
6892 Box::pin(async move {
6893 match command {
6894 MeerkatMachineCommand::EnsureSessionWithExecutor { .. } => {
6895 let self_handle = self_handle.ok_or_else(|| {
6896 MeerkatMachineCommandError::Driver(RuntimeDriverError::Internal(
6897 "EnsureSessionWithExecutor requires Arc<Self> machine handle".into(),
6898 ))
6899 })?;
6900 self_handle
6901 .execute_meerkat_machine_ensure_session_command(command)
6902 .await
6903 .map_err(Into::into)
6904 }
6905 MeerkatMachineCommand::RegisterSession { .. }
6906 | MeerkatMachineCommand::UnregisterSession { .. }
6907 | MeerkatMachineCommand::SetSilentIntents { .. }
6908 | MeerkatMachineCommand::CancelAfterBoundary { .. }
6909 | MeerkatMachineCommand::StopRuntimeExecutor { .. }
6910 | MeerkatMachineCommand::CommitServiceTurnTerminalReceipt { .. }
6911 | MeerkatMachineCommand::ContainsSession { .. }
6912 | MeerkatMachineCommand::SessionHasExecutor { .. }
6913 | MeerkatMachineCommand::SessionHasComms { .. }
6914 | MeerkatMachineCommand::OpsLifecycleRegistry { .. }
6915 | MeerkatMachineCommand::PrepareBindings { .. }
6916 | MeerkatMachineCommand::PrepareLocalSessionBindings { .. }
6917 | MeerkatMachineCommand::InputState { .. }
6918 | MeerkatMachineCommand::InputStateByIdempotencyKey { .. }
6919 | MeerkatMachineCommand::InteractionTerminalStatus { .. }
6920 | MeerkatMachineCommand::RunTerminalStatus { .. }
6921 | MeerkatMachineCommand::ListActiveInputs { .. }
6922 | MeerkatMachineCommand::ReconfigureSessionLlmIdentity { .. }
6923 | MeerkatMachineCommand::StagePersistentFilter { .. }
6924 | MeerkatMachineCommand::RequestDeferredTools { .. }
6925 | MeerkatMachineCommand::PublishCommittedVisibleSet { .. } => self
6926 .execute_meerkat_machine_session_command(command)
6927 .await
6928 .map_err(Into::into),
6929 MeerkatMachineCommand::SetPeerIngressContext { .. }
6930 | MeerkatMachineCommand::NotifyDrainExited { .. } => {
6931 let self_handle = self_handle.ok_or_else(|| {
6932 MeerkatMachineCommandError::Driver(RuntimeDriverError::Internal(
6933 "drain command requires Arc<Self> machine handle".into(),
6934 ))
6935 })?;
6936 self_handle
6937 .execute_meerkat_machine_drain_command(command)
6938 .await
6939 .map_err(Into::into)
6940 }
6941 MeerkatMachineCommand::AbortAll
6942 | MeerkatMachineCommand::Abort { .. }
6943 | MeerkatMachineCommand::Wait { .. } => self
6944 .execute_meerkat_machine_drain_local_command(command)
6945 .await
6946 .map_err(Into::into),
6947 MeerkatMachineCommand::Ingest { .. }
6948 | MeerkatMachineCommand::PublishEvent { .. }
6949 | MeerkatMachineCommand::Retire { .. }
6950 | MeerkatMachineCommand::Recycle { .. }
6951 | MeerkatMachineCommand::Reset { .. }
6952 | MeerkatMachineCommand::Recover { .. }
6953 | MeerkatMachineCommand::Destroy { .. }
6954 | MeerkatMachineCommand::RuntimeState { .. }
6955 | MeerkatMachineCommand::ResolvedSessionLlmCapabilities { .. }
6956 | MeerkatMachineCommand::ConfigureModelRoutingBaseline { .. }
6957 | MeerkatMachineCommand::SessionModelRoutingStatus { .. }
6958 | MeerkatMachineCommand::RequestSwitchTurn { .. }
6959 | MeerkatMachineCommand::AdmitModelRoutingAssistantTurn { .. }
6960 | MeerkatMachineCommand::BeginImageOperation { .. }
6961 | MeerkatMachineCommand::DenyImageOperationPlan { .. }
6962 | MeerkatMachineCommand::ActivateImageOperationOverride { .. }
6963 | MeerkatMachineCommand::ClassifyImageOperationTerminal { .. }
6964 | MeerkatMachineCommand::CompleteImageOperation { .. }
6965 | MeerkatMachineCommand::RestoreImageOperationOverride { .. }
6966 | MeerkatMachineCommand::LoadBoundaryReceipt { .. } => self
6967 .execute_meerkat_machine_control_command(command)
6968 .await
6969 .map_err(Into::into),
6970 MeerkatMachineCommand::AcceptWithCompletion { .. }
6971 | MeerkatMachineCommand::AcceptWithoutWake { .. } => self
6972 .execute_meerkat_machine_ingress_command(command)
6973 .await
6974 .map_err(Into::into),
6975 }
6976 })
6977 }
6978
6979 pub async fn register_session(
6986 &self,
6987 session_id: SessionId,
6988 ) -> Result<(), RuntimeControlPlaneError> {
6989 match self
6990 .execute_meerkat_machine_command(
6991 None,
6992 MeerkatMachineCommand::RegisterSession { session_id },
6993 )
6994 .await
6995 .map_err(MeerkatMachine::control_plane_error_from_command_error)?
6996 {
6997 MeerkatMachineCommandResult::Unit => Ok(()),
6998 other => Err(RuntimeControlPlaneError::Internal(format!(
6999 "register_session: unexpected command result variant: {other:?}"
7000 ))),
7001 }
7002 }
7003}
7004
7005#[cfg(test)]
7006#[allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
7007#[path = "../meerkat_machine_tests.rs"]
7008mod tests;