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#[derive(Clone)]
1837struct MachineCleanupTaskSpawner {
1838 #[cfg(not(target_arch = "wasm32"))]
1839 handle: crate::tokio::runtime::Handle,
1840}
1841
1842#[doc(hidden)]
1845#[derive(Clone)]
1846pub struct RuntimeCleanupTaskSpawner {
1847 inner: MachineCleanupTaskSpawner,
1848}
1849
1850impl RuntimeCleanupTaskSpawner {
1851 pub fn acquire() -> Result<Self, RuntimeDriverError> {
1852 Ok(Self {
1853 inner: MachineCleanupTaskSpawner::acquire()?,
1854 })
1855 }
1856
1857 #[cfg(not(target_arch = "wasm32"))]
1858 pub fn spawn_detached<F>(&self, future: F)
1859 where
1860 F: Future<Output = ()> + Send + 'static,
1861 {
1862 self.inner.spawn(future);
1863 }
1864
1865 #[cfg(target_arch = "wasm32")]
1866 pub fn spawn_detached<F>(&self, future: F)
1867 where
1868 F: Future<Output = ()> + 'static,
1869 {
1870 self.inner.spawn(future);
1871 }
1872}
1873
1874impl MachineCleanupTaskSpawner {
1875 #[cfg(not(target_arch = "wasm32"))]
1876 fn acquire() -> Result<Self, RuntimeDriverError> {
1877 let slot = MACHINE_CLEANUP_RUNTIME.get_or_init(|| StdMutex::new(None));
1878 let mut runtime = slot
1879 .lock()
1880 .unwrap_or_else(std::sync::PoisonError::into_inner);
1881 if let Some(runtime) = runtime.as_ref() {
1882 return Ok(Self {
1883 handle: runtime.handle().clone(),
1884 });
1885 }
1886
1887 let candidate = crate::tokio::runtime::Builder::new_multi_thread()
1888 .worker_threads(1)
1889 .thread_name("meerkat-machine-cleanup")
1890 .enable_all()
1891 .build()
1892 .map_err(|error| {
1893 RuntimeDriverError::Internal(format!(
1894 "failed to start machine cleanup runtime: {error}"
1895 ))
1896 })?;
1897 let handle = candidate.handle().clone();
1898 *runtime = Some(candidate);
1899 Ok(Self { handle })
1900 }
1901
1902 #[cfg(target_arch = "wasm32")]
1903 fn acquire() -> Result<Self, RuntimeDriverError> {
1904 Ok(Self {})
1905 }
1906
1907 #[cfg(not(target_arch = "wasm32"))]
1908 fn spawn<F, T>(&self, future: F) -> crate::tokio::task::JoinHandle<T>
1909 where
1910 F: Future<Output = T> + Send + 'static,
1911 T: Send + 'static,
1912 {
1913 self.handle.spawn(future)
1914 }
1915
1916 #[cfg(target_arch = "wasm32")]
1917 fn spawn<F, T>(&self, future: F) -> crate::tokio::task::JoinHandle<T>
1918 where
1919 F: Future<Output = T> + 'static,
1920 T: 'static,
1921 {
1922 crate::tokio::spawn(future)
1923 }
1924}
1925
1926impl std::fmt::Debug for EnsureRuntimeExecutorAttachment {
1927 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1928 match self {
1929 Self::Existing(witness) => formatter.debug_tuple("Existing").field(witness).finish(),
1930 Self::Pending(pending) => formatter.debug_tuple("Pending").field(pending).finish(),
1931 }
1932 }
1933}
1934
1935#[must_use = "dropping a pending runtime attachment aborts that exact attachment"]
1944pub struct PendingRuntimeExecutorAttachment {
1945 machine: Arc<MeerkatMachine>,
1946 witness: RuntimeExecutorAttachmentWitness,
1947 mutation_guard: Option<crate::tokio::sync::OwnedMutexGuard<()>>,
1948 cleanup_spawner: MachineCleanupTaskSpawner,
1949 should_wake: bool,
1950 persist_lifecycle_on_commit: bool,
1951 armed: bool,
1952}
1953
1954#[must_use = "dropping a retained runtime attachment aborts that exact attachment"]
1963pub struct CommittedRuntimeExecutorAttachmentPublicationLease {
1964 machine: Arc<MeerkatMachine>,
1965 witness: RuntimeExecutorAttachmentWitness,
1966 mutation_guard: Option<crate::tokio::sync::OwnedMutexGuard<()>>,
1967 cleanup_spawner: MachineCleanupTaskSpawner,
1968 should_wake: bool,
1969 persist_lifecycle_on_commit: bool,
1970 armed: bool,
1971}
1972
1973#[must_use = "dropping a prepared runtime attachment retirement starts exact retirement"]
1983pub struct PreparedRuntimeExecutorAttachmentRetirement {
1984 machine: Arc<MeerkatMachine>,
1985 witness: RuntimeExecutorAttachmentWitness,
1986 mutation_guard: Option<crate::tokio::sync::OwnedMutexGuard<()>>,
1987 cleanup_spawner: MachineCleanupTaskSpawner,
1988 armed: bool,
1989}
1990
1991#[must_use = "drop is cancellation-safe, but await completion to observe retirement errors"]
1997pub struct RuntimeExecutorAttachmentRetirementCompletion {
1998 result_rx: crate::tokio::sync::oneshot::Receiver<Result<bool, RuntimeDriverError>>,
1999}
2000
2001impl RuntimeExecutorAttachmentRetirementCompletion {
2002 fn new(
2003 result_rx: crate::tokio::sync::oneshot::Receiver<Result<bool, RuntimeDriverError>>,
2004 ) -> Self {
2005 Self { result_rx }
2006 }
2007
2008 pub async fn wait(self) -> Result<bool, RuntimeDriverError> {
2009 self.result_rx.await.map_err(|error| {
2010 RuntimeDriverError::Internal(format!(
2011 "owned exact runtime attachment retirement ended without a result: {error}"
2012 ))
2013 })?
2014 }
2015}
2016
2017impl PreparedRuntimeExecutorAttachmentRetirement {
2018 fn new(
2019 machine: Arc<MeerkatMachine>,
2020 witness: RuntimeExecutorAttachmentWitness,
2021 mutation_guard: crate::tokio::sync::OwnedMutexGuard<()>,
2022 cleanup_spawner: MachineCleanupTaskSpawner,
2023 ) -> Self {
2024 Self {
2025 machine,
2026 witness,
2027 mutation_guard: Some(mutation_guard),
2028 cleanup_spawner,
2029 armed: true,
2030 }
2031 }
2032
2033 pub fn witness(&self) -> &RuntimeExecutorAttachmentWitness {
2034 &self.witness
2035 }
2036
2037 pub fn commit(
2041 mut self,
2042 ) -> Result<RuntimeExecutorAttachmentRetirementCompletion, RuntimeDriverError> {
2043 let guard = self.mutation_guard.take().ok_or_else(|| {
2044 RuntimeDriverError::Internal(
2045 "prepared exact attachment retirement lost its mutation fence".to_string(),
2046 )
2047 })?;
2048 let completion = self
2049 .machine
2050 .spawn_executor_attachment_retirement_with_guard(
2051 self.witness.clone(),
2052 guard,
2053 self.cleanup_spawner.clone(),
2054 );
2055 self.armed = false;
2056 Ok(completion)
2057 }
2058
2059 pub async fn commit_under_runtime_turn_finalization_boundary(
2064 mut self,
2065 ) -> Result<bool, RuntimeDriverError> {
2066 self.machine
2067 .complete_executor_attachment_cleanup_under_runtime_turn_boundary(&self.witness)
2068 .await?;
2069 let guard = self.mutation_guard.take().ok_or_else(|| {
2070 RuntimeDriverError::Internal(
2071 "prepared exact attachment retirement lost its mutation fence".to_string(),
2072 )
2073 })?;
2074 self.armed = false;
2075 self.machine
2076 .unregister_executor_attachment_if_current_with_guard(self.witness.clone(), guard)
2077 .await
2078 }
2079}
2080
2081impl Drop for PreparedRuntimeExecutorAttachmentRetirement {
2082 fn drop(&mut self) {
2083 if !self.armed {
2084 return;
2085 }
2086 let Some(guard) = self.mutation_guard.take() else {
2087 return;
2088 };
2089 let _completion = self
2090 .machine
2091 .spawn_executor_attachment_retirement_with_guard(
2092 self.witness.clone(),
2093 guard,
2094 self.cleanup_spawner.clone(),
2095 );
2096 self.armed = false;
2097 }
2098}
2099
2100impl std::fmt::Debug for PendingRuntimeExecutorAttachment {
2101 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2102 formatter
2103 .debug_struct("PendingRuntimeExecutorAttachment")
2104 .field("witness", &self.witness)
2105 .field("should_wake", &self.should_wake)
2106 .field("armed", &self.armed)
2107 .finish()
2108 }
2109}
2110
2111impl PendingRuntimeExecutorAttachment {
2112 fn new(
2113 machine: Arc<MeerkatMachine>,
2114 witness: RuntimeExecutorAttachmentWitness,
2115 mutation_guard: crate::tokio::sync::OwnedMutexGuard<()>,
2116 cleanup_spawner: MachineCleanupTaskSpawner,
2117 should_wake: bool,
2118 persist_lifecycle_on_commit: bool,
2119 ) -> Self {
2120 Self {
2121 machine,
2122 witness,
2123 mutation_guard: Some(mutation_guard),
2124 cleanup_spawner,
2125 should_wake,
2126 persist_lifecycle_on_commit,
2127 armed: true,
2128 }
2129 }
2130
2131 pub fn witness(&self) -> &RuntimeExecutorAttachmentWitness {
2132 &self.witness
2133 }
2134
2135 #[doc(hidden)]
2136 pub fn cleanup_task_spawner(&self) -> RuntimeCleanupTaskSpawner {
2137 RuntimeCleanupTaskSpawner {
2138 inner: self.cleanup_spawner.clone(),
2139 }
2140 }
2141
2142 pub async fn commit(self) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError> {
2143 self.commit_with_predecessor_mode(|_| Ok(()), false).await
2144 }
2145
2146 pub async fn commit_replacing_predecessor(
2157 self,
2158 ) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError> {
2159 self.commit_with_predecessor_mode(|_| Ok(()), true).await
2160 }
2161
2162 pub async fn commit_with<F>(
2170 self,
2171 on_committed: F,
2172 ) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError>
2173 where
2174 F: FnOnce(&RuntimeExecutorAttachmentWitness) -> Result<(), RuntimeDriverError>
2175 + Send
2176 + 'static,
2177 {
2178 self.commit_with_predecessor_mode(on_committed, false).await
2179 }
2180
2181 pub async fn commit_with_replacing_predecessor<F>(
2190 self,
2191 on_committed: F,
2192 ) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError>
2193 where
2194 F: FnOnce(&RuntimeExecutorAttachmentWitness) -> Result<(), RuntimeDriverError>
2195 + Send
2196 + 'static,
2197 {
2198 self.commit_with_predecessor_mode(on_committed, true).await
2199 }
2200
2201 async fn commit_with_predecessor_mode<F>(
2202 self,
2203 on_committed: F,
2204 replaces_predecessor: bool,
2205 ) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError>
2206 where
2207 F: FnOnce(&RuntimeExecutorAttachmentWitness) -> Result<(), RuntimeDriverError>
2208 + Send
2209 + 'static,
2210 {
2211 let cleanup_spawner = self.cleanup_spawner.clone();
2212 let completion = cleanup_spawner.spawn(async move {
2213 let mut pending = self;
2214 let result = pending
2215 .try_commit_with(on_committed, false, replaces_predecessor)
2216 .await;
2217 match result {
2218 Ok(witness) => Ok(witness),
2219 Err(commit_error) => match pending.abort().await {
2220 Ok(()) => Err(commit_error),
2221 Err(cleanup_error) => Err(RuntimeDriverError::Internal(format!(
2222 "{commit_error}; exact attachment cleanup also failed: {cleanup_error}"
2223 ))),
2224 },
2225 }
2226 });
2227 completion.await.map_err(|error| {
2228 RuntimeDriverError::Internal(format!(
2229 "owned runtime attachment commit ended without a result: {error}"
2230 ))
2231 })?
2232 }
2233
2234 pub async fn try_commit_with_under_runtime_turn_finalization_boundary<F>(
2247 &mut self,
2248 replaces_predecessor: bool,
2249 on_committed: F,
2250 ) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError>
2251 where
2252 F: FnOnce(&RuntimeExecutorAttachmentWitness) -> Result<(), RuntimeDriverError>,
2253 {
2254 self.try_commit_with(on_committed, false, replaces_predecessor)
2255 .await
2256 }
2257
2258 pub async fn try_commit_with_retained_publication_lease_under_runtime_turn_finalization_boundary(
2263 &mut self,
2264 ) -> Result<CommittedRuntimeExecutorAttachmentPublicationLease, RuntimeDriverError> {
2265 let witness = self.try_commit_with(|_| Ok(()), true, false).await?;
2266 let mutation_guard = self.mutation_guard.take().ok_or_else(|| {
2267 RuntimeDriverError::Internal(
2268 "committed attachment lost its retained publication mutation fence".to_string(),
2269 )
2270 })?;
2271 Ok(CommittedRuntimeExecutorAttachmentPublicationLease {
2274 machine: Arc::clone(&self.machine),
2275 witness,
2276 mutation_guard: Some(mutation_guard),
2277 cleanup_spawner: self.cleanup_spawner.clone(),
2278 should_wake: self.should_wake,
2279 persist_lifecycle_on_commit: self.persist_lifecycle_on_commit,
2280 armed: true,
2281 })
2282 }
2283
2284 async fn try_commit_with<F>(
2285 &mut self,
2286 on_committed: F,
2287 retain_mutation_guard: bool,
2288 replaces_predecessor: bool,
2289 ) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError>
2290 where
2291 F: FnOnce(&RuntimeExecutorAttachmentWitness) -> Result<(), RuntimeDriverError>,
2292 {
2293 let guard = self.mutation_guard.as_ref().ok_or_else(|| {
2294 RuntimeDriverError::Internal(
2295 "pending runtime attachment lost its mutation fence before commit".to_string(),
2296 )
2297 })?;
2298 if retain_mutation_guard && replaces_predecessor {
2299 return Err(RuntimeDriverError::Internal(
2300 "predecessor request terminalization belongs to final serving publication"
2301 .to_string(),
2302 ));
2303 }
2304 let predecessor_request_state = if replaces_predecessor {
2305 let sessions = self.machine.sessions.read().await;
2306 let entry =
2307 sessions
2308 .get(self.witness.session_id())
2309 .ok_or(RuntimeDriverError::NotReady {
2310 state: RuntimeState::Destroyed,
2311 })?;
2312 let exact_pending = entry.epoch_id == self.witness.epoch_id
2313 && matches!(
2314 &entry.attachment_slot,
2315 RuntimeLoopAttachmentSlot::Pending(attachment)
2316 if attachment.id == self.witness.attachment_id
2317 && !attachment.wake_tx.is_closed()
2318 && !attachment.effect_tx.is_closed()
2319 );
2320 if !exact_pending {
2321 return Err(RuntimeDriverError::StaleAuthority {
2322 reason: format!(
2323 "replacement attachment for session {} changed before waiter fencing",
2324 self.witness.session_id()
2325 ),
2326 });
2327 }
2328 Some((
2329 entry.driver.clone(),
2330 entry.completions.clone(),
2331 entry.publication_handle(),
2332 ))
2333 } else {
2334 None
2335 };
2336 let mut predecessor_terminal_publication = None;
2337 if let Some((driver, completions, publication_handle)) = predecessor_request_state {
2338 const REPLACED_REASON: &str =
2339 "executor attachment was replaced before request completion";
2340 let (predecessor_input_ids, candidate_owner_input_id) = {
2341 let mut driver = driver.lock().await;
2342 let predecessor_input_ids = driver.as_driver().active_input_ids();
2343 if predecessor_input_ids.is_empty() {
2344 (predecessor_input_ids, None)
2345 } else {
2346 let prepared = driver.prepare_runless_runtime_terminated_interaction_outboxes(
2351 &predecessor_input_ids,
2352 REPLACED_REASON.to_string(),
2353 )?;
2354 if let Err(error) = driver
2355 .abandon_pending_inputs(crate::input_state::InputAbandonReason::Cancelled)
2356 .await
2357 {
2358 driver.rollback_prepared_runless_interaction_terminal_outboxes(prepared);
2359 return Err(error);
2360 }
2361 let candidate_owner_input_id = crate::meerkat_machine::driver::DriverEntry::commit_prepared_runless_interaction_terminal_outboxes(prepared);
2362 (predecessor_input_ids, candidate_owner_input_id)
2363 }
2364 };
2365 if !predecessor_input_ids.is_empty() {
2366 completions.lock().await.fail_inputs(
2373 predecessor_input_ids.iter().cloned(),
2374 crate::completion::CompletionWaitError::AttachmentReplaced,
2375 );
2376 self.should_wake = false;
2377 if let Some(candidate_owner_input_id) = candidate_owner_input_id {
2378 predecessor_terminal_publication = Some((
2379 driver,
2380 publication_handle,
2381 predecessor_input_ids,
2382 candidate_owner_input_id,
2383 ));
2384 }
2385 }
2386 }
2387 let result = self
2388 .machine
2389 .commit_pending_executor_attachment(
2390 &self.witness,
2391 guard,
2392 self.should_wake,
2393 self.persist_lifecycle_on_commit,
2394 retain_mutation_guard,
2395 on_committed,
2396 )
2397 .await;
2398 if result.is_ok() {
2399 if !retain_mutation_guard {
2400 self.mutation_guard.take();
2401 }
2402 self.armed = false;
2403 if let Some((driver, publication_handle, input_ids, candidate_owner_input_id)) =
2404 predecessor_terminal_publication
2405 {
2406 let _terminal_publication = self.cleanup_spawner.spawn(async move {
2412 let deadline = Instant::now() + std::time::Duration::from_secs(5);
2413 if let Err(error) = crate::control_plane::publish_and_resolve_runless_runtime_termination_before(
2414 &driver,
2415 None,
2416 publication_handle.as_deref(),
2417 &input_ids,
2418 Some(&candidate_owner_input_id),
2419 "executor attachment was replaced before request completion",
2420 Some(deadline),
2421 )
2422 .await
2423 {
2424 tracing::warn!(
2425 %error,
2426 "replacement terminal publication remains durably pending"
2427 );
2428 }
2429 });
2430 }
2431 }
2432 result
2433 }
2434
2435 pub async fn abort(mut self) -> Result<(), RuntimeDriverError> {
2436 let guard = self.mutation_guard.take().ok_or_else(|| {
2437 RuntimeDriverError::Internal(
2438 "pending runtime attachment lost its mutation fence before abort".to_string(),
2439 )
2440 })?;
2441 let machine = Arc::clone(&self.machine);
2442 let witness = self.witness.clone();
2443 let completion = self.cleanup_spawner.spawn(async move {
2444 machine
2445 .abort_pending_executor_attachment(witness, guard)
2446 .await
2447 });
2448 self.armed = false;
2449 completion.await.map_err(|error| {
2450 RuntimeDriverError::Internal(format!(
2451 "owned runtime attachment abort ended without a result: {error}"
2452 ))
2453 })?
2454 }
2455
2456 pub async fn abort_under_runtime_turn_finalization_boundary(
2460 mut self,
2461 ) -> Result<(), RuntimeDriverError> {
2462 self.machine
2463 .complete_executor_attachment_cleanup_under_runtime_turn_boundary(&self.witness)
2464 .await?;
2465 let guard = self.mutation_guard.take().ok_or_else(|| {
2466 RuntimeDriverError::Internal(
2467 "pending runtime attachment lost its mutation fence before boundary-owned abort"
2468 .to_string(),
2469 )
2470 })?;
2471 let machine = Arc::clone(&self.machine);
2472 let witness = self.witness.clone();
2473 let completion = self.cleanup_spawner.spawn(async move {
2474 machine
2475 .abort_pending_executor_attachment(witness, guard)
2476 .await
2477 });
2478 self.armed = false;
2479 completion.await.map_err(|error| {
2480 RuntimeDriverError::Internal(format!(
2481 "owned boundary-aware runtime attachment abort ended without a result: {error}"
2482 ))
2483 })?
2484 }
2485}
2486
2487impl CommittedRuntimeExecutorAttachmentPublicationLease {
2488 pub fn witness(&self) -> &RuntimeExecutorAttachmentWitness {
2489 &self.witness
2490 }
2491
2492 #[doc(hidden)]
2493 pub fn cleanup_task_spawner(&self) -> RuntimeCleanupTaskSpawner {
2494 RuntimeCleanupTaskSpawner {
2495 inner: self.cleanup_spawner.clone(),
2496 }
2497 }
2498
2499 pub async fn abandon_recovered_predecessor_inputs(
2511 &mut self,
2512 ) -> Result<usize, RuntimeDriverError> {
2513 let _guard = self.mutation_guard.as_ref().ok_or_else(|| {
2514 RuntimeDriverError::Internal(
2515 "retained attachment lost its mutation fence before predecessor abandonment"
2516 .to_string(),
2517 )
2518 })?;
2519 let (driver, completions) = {
2520 let sessions = self.machine.sessions.read().await;
2521 let entry = sessions.get(self.witness.session_id()).ok_or_else(|| {
2522 RuntimeDriverError::StaleAuthority {
2523 reason: format!(
2524 "recovered predecessor session {} disappeared before abandonment",
2525 self.witness.session_id()
2526 ),
2527 }
2528 })?;
2529 let exact_pending = entry.epoch_id == self.witness.epoch_id
2530 && matches!(
2531 &entry.attachment_slot,
2532 RuntimeLoopAttachmentSlot::Pending(attachment)
2533 if attachment.id == self.witness.attachment_id
2534 && !attachment.wake_tx.is_closed()
2535 && !attachment.effect_tx.is_closed()
2536 );
2537 if !exact_pending {
2538 return Err(RuntimeDriverError::StaleAuthority {
2539 reason: format!(
2540 "recovered predecessor attachment for session {} changed before abandonment",
2541 self.witness.session_id()
2542 ),
2543 });
2544 }
2545 (entry.driver.clone(), entry.completions.clone())
2546 };
2547
2548 let predecessor_input_ids = {
2549 let mut driver = driver.lock().await;
2550 let predecessor_input_ids = driver.as_driver().active_input_ids();
2551 if predecessor_input_ids.is_empty() {
2552 return Ok(0);
2553 }
2554 let abandoned = driver
2555 .abandon_pending_inputs(crate::input_state::InputAbandonReason::Cancelled)
2556 .await?;
2557 if abandoned != predecessor_input_ids.len() {
2558 return Err(RuntimeDriverError::Internal(format!(
2559 "recovered predecessor abandonment for session {} terminalized {abandoned} of {} active inputs",
2560 self.witness.session_id(),
2561 predecessor_input_ids.len()
2562 )));
2563 }
2564 for input_id in &predecessor_input_ids {
2565 let stored = driver
2566 .as_driver()
2567 .stored_input_state(input_id)
2568 .ok_or_else(|| {
2569 RuntimeDriverError::Internal(format!(
2570 "recovered predecessor input {input_id} disappeared after abandonment"
2571 ))
2572 })?;
2573 if !matches!(
2574 stored.seed.terminal_outcome,
2575 Some(crate::input_state::InputTerminalOutcome::Abandoned {
2576 reason: crate::input_state::InputAbandonReason::Cancelled,
2577 })
2578 ) || stored.state.interaction_terminal_outbox.is_some()
2579 {
2580 return Err(RuntimeDriverError::Internal(format!(
2581 "recovered predecessor input {input_id} did not reach silent Cancelled abandonment"
2582 )));
2583 }
2584 }
2585 predecessor_input_ids
2586 };
2587 let abandoned = predecessor_input_ids.len();
2588 completions.lock().await.fail_inputs(
2589 predecessor_input_ids,
2590 crate::completion::CompletionWaitError::AttachmentReplaced,
2591 );
2592 Ok(abandoned)
2593 }
2594
2595 pub async fn commit_with<F>(
2598 mut self,
2599 on_committed: F,
2600 ) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError>
2601 where
2602 F: FnOnce(&RuntimeExecutorAttachmentWitness) -> Result<(), RuntimeDriverError>,
2603 {
2604 let guard = self.mutation_guard.as_ref().ok_or_else(|| {
2605 RuntimeDriverError::Internal(
2606 "retained attachment publication lost its mutation fence".to_string(),
2607 )
2608 })?;
2609 let witness = self
2610 .machine
2611 .commit_retained_executor_attachment_publication(
2612 &self.witness,
2613 guard,
2614 self.should_wake,
2615 self.persist_lifecycle_on_commit,
2616 move |witness, _| on_committed(witness),
2617 )
2618 .await?;
2619 self.mutation_guard.take();
2620 self.armed = false;
2621 Ok(witness)
2622 }
2623
2624 pub async fn commit_with_member_residency<F>(
2627 &mut self,
2628 residency_update: MemberResidencyUpdate,
2629 incarnation: meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation,
2630 tracked_turn_journal: Option<Arc<dyn crate::member_observation::TrackedTurnJournal>>,
2631 on_committed: F,
2632 ) -> Result<(RuntimeExecutorAttachmentWitness, MemberResidencyPublication), RuntimeDriverError>
2633 where
2634 F: FnOnce(&RuntimeExecutorAttachmentWitness) -> Result<(), RuntimeDriverError>,
2635 {
2636 residency_update.validate_for_retained_attachment(
2637 &self.machine,
2638 &self.witness,
2639 &incarnation,
2640 )?;
2641 let guard = self.mutation_guard.as_ref().ok_or_else(|| {
2642 RuntimeDriverError::Internal(
2643 "retained host publication lost its mutation fence".to_string(),
2644 )
2645 })?;
2646 let mut staged_residency_publication = None;
2647 let commit = self
2648 .machine
2649 .commit_retained_executor_attachment_publication(
2650 &self.witness,
2651 guard,
2652 self.should_wake,
2653 self.persist_lifecycle_on_commit,
2654 |witness, session_mutation_gate| {
2655 on_committed(witness)?;
2656 staged_residency_publication =
2657 Some(residency_update.stage_under_retained_mutation_gate(
2658 incarnation,
2659 tracked_turn_journal,
2660 session_mutation_gate,
2661 )?);
2662 Ok(())
2663 },
2664 )
2665 .await;
2666 let witness = match commit {
2667 Ok(witness) => witness,
2668 Err(error) => {
2669 drop(staged_residency_publication);
2673 return Err(error);
2674 }
2675 };
2676 let residency_publication = staged_residency_publication
2677 .take()
2678 .ok_or_else(|| {
2679 RuntimeDriverError::Internal(format!(
2680 "retained host publication for session {} committed without residency",
2681 self.witness.session_id()
2682 ))
2683 })?
2684 .finalize()?;
2685 self.mutation_guard.take();
2686 self.armed = false;
2687 Ok((witness, residency_publication))
2688 }
2689
2690 pub fn begin_abort(
2699 mut self,
2700 ) -> Result<RuntimeExecutorAttachmentRetirementCompletion, RuntimeDriverError> {
2701 let guard = self.mutation_guard.take().ok_or_else(|| {
2702 RuntimeDriverError::Internal(
2703 "committed publication lease lost its mutation fence before abort".to_string(),
2704 )
2705 })?;
2706 let completion = self
2707 .machine
2708 .spawn_executor_attachment_retirement_with_guard(
2709 self.witness.clone(),
2710 guard,
2711 self.cleanup_spawner.clone(),
2712 );
2713 self.armed = false;
2714 Ok(completion)
2715 }
2716
2717 pub async fn abort_under_runtime_turn_finalization_boundary(
2719 mut self,
2720 ) -> Result<bool, RuntimeDriverError> {
2721 self.machine
2722 .complete_executor_attachment_cleanup_under_runtime_turn_boundary(&self.witness)
2723 .await?;
2724 let guard = self.mutation_guard.take().ok_or_else(|| {
2725 RuntimeDriverError::Internal(
2726 "committed publication lease lost its mutation fence before abort".to_string(),
2727 )
2728 })?;
2729 let machine = Arc::clone(&self.machine);
2730 let witness = self.witness.clone();
2731 let completion = self.cleanup_spawner.spawn(async move {
2732 machine
2733 .unregister_executor_attachment_if_current_with_guard(witness, guard)
2734 .await
2735 });
2736 self.armed = false;
2737 completion.await.map_err(|error| {
2738 RuntimeDriverError::Internal(format!(
2739 "owned committed attachment abort ended without a result: {error}"
2740 ))
2741 })?
2742 }
2743}
2744
2745impl Drop for CommittedRuntimeExecutorAttachmentPublicationLease {
2746 fn drop(&mut self) {
2747 if !self.armed {
2748 return;
2749 }
2750 let Some(guard) = self.mutation_guard.take() else {
2751 return;
2752 };
2753 self.armed = false;
2754 let machine = Arc::clone(&self.machine);
2755 let witness = self.witness.clone();
2756 self.cleanup_spawner.spawn(async move {
2757 if let Err(error) = machine
2758 .unregister_executor_attachment_if_current_with_guard(witness.clone(), guard)
2759 .await
2760 {
2761 tracing::warn!(
2762 session_id = %witness.session_id(),
2763 %error,
2764 "drop-time committed attachment publication abort failed"
2765 );
2766 }
2767 });
2768 }
2769}
2770
2771impl Drop for PendingRuntimeExecutorAttachment {
2772 fn drop(&mut self) {
2773 if !self.armed {
2774 return;
2775 }
2776 let Some(guard) = self.mutation_guard.take() else {
2777 return;
2778 };
2779 self.armed = false;
2780 let machine = Arc::clone(&self.machine);
2781 let witness = self.witness.clone();
2782 self.cleanup_spawner.spawn(async move {
2783 if let Err(error) = machine
2784 .abort_pending_executor_attachment(witness.clone(), guard)
2785 .await
2786 {
2787 tracing::warn!(
2788 session_id = %witness.session_id(),
2789 %error,
2790 "drop-time exact runtime attachment abort failed"
2791 );
2792 }
2793 });
2794 }
2795}
2796
2797impl std::fmt::Debug for PreparedSessionMaterialization {
2798 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2799 formatter
2800 .debug_struct("PreparedSessionMaterialization")
2801 .field("session_id", self.bindings.session_id())
2802 .field("epoch_id", self.bindings.epoch_id())
2803 .field("claim_id", &self.claim_id)
2804 .field("armed", &self.armed)
2805 .finish()
2806 }
2807}
2808
2809impl std::fmt::Debug for PreparedAttachedSessionActorRecovery {
2810 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2811 formatter
2812 .debug_struct("PreparedAttachedSessionActorRecovery")
2813 .field("session_id", self.bindings.session_id())
2814 .field("epoch_id", self.bindings.epoch_id())
2815 .field("witness", &self.witness)
2816 .field("armed", &self.armed)
2817 .finish()
2818 }
2819}
2820
2821impl PreparedAttachedSessionActorRecovery {
2822 fn new(
2823 bindings: meerkat_core::SessionRuntimeBindings,
2824 witness: RuntimeExecutorAttachmentWitness,
2825 claim_id: uuid::Uuid,
2826 claim_state: Arc<std::sync::Mutex<crate::RuntimeActorMaterializationClaimState>>,
2827 previous_phase: crate::RuntimeActorMaterializationClaimPhase,
2828 mutation_guard: crate::tokio::sync::OwnedMutexGuard<()>,
2829 ) -> Self {
2830 Self {
2831 bindings,
2832 witness,
2833 claim_id,
2834 claim_state,
2835 previous_phase,
2836 mutation_guard: Some(mutation_guard),
2837 armed: true,
2838 }
2839 }
2840
2841 pub fn bindings(&self) -> &meerkat_core::SessionRuntimeBindings {
2842 &self.bindings
2843 }
2844
2845 pub fn bindings_clone(&self) -> meerkat_core::SessionRuntimeBindings {
2846 self.bindings.clone()
2847 }
2848
2849 pub fn witness(&self) -> &RuntimeExecutorAttachmentWitness {
2850 &self.witness
2851 }
2852
2853 pub fn commit_actor(&mut self) -> Result<(), RuntimeDriverError> {
2856 if !self.armed {
2857 return Err(RuntimeDriverError::StaleAuthority {
2858 reason: "attached actor-recovery lease is no longer active".to_string(),
2859 });
2860 }
2861 let changed = {
2862 let mut state = self
2863 .claim_state
2864 .lock()
2865 .unwrap_or_else(std::sync::PoisonError::into_inner);
2866 if !state.exact_claim_is(
2867 self.claim_id,
2868 &[crate::RuntimeActorMaterializationClaimPhase::ActorMaterializedPendingCommit],
2869 ) {
2870 return Err(RuntimeDriverError::StaleAuthority {
2871 reason: format!(
2872 "actor recovery for session {} did not retain its exact materialization claim",
2873 self.bindings.session_id()
2874 ),
2875 });
2876 }
2877 state.current = None;
2878 state.phase = crate::RuntimeActorMaterializationClaimPhase::RetainedActor;
2879 Arc::clone(&state.changed)
2880 };
2881 changed.notify_waiters();
2882 self.armed = false;
2883 self.mutation_guard.take();
2884 Ok(())
2885 }
2886}
2887
2888impl Drop for PreparedAttachedSessionActorRecovery {
2889 fn drop(&mut self) {
2890 if !self.armed {
2891 return;
2892 }
2893 let changed = {
2894 let mut state = self
2895 .claim_state
2896 .lock()
2897 .unwrap_or_else(std::sync::PoisonError::into_inner);
2898 if state.current == Some(self.claim_id) {
2899 state.current = None;
2900 state.phase = if state.phase
2901 == crate::RuntimeActorMaterializationClaimPhase::ActorMaterializedPendingCommit
2902 {
2903 crate::RuntimeActorMaterializationClaimPhase::RetainedActor
2904 } else {
2905 self.previous_phase
2906 };
2907 Some(Arc::clone(&state.changed))
2908 } else {
2909 None
2910 }
2911 };
2912 if let Some(changed) = changed {
2913 changed.notify_waiters();
2914 }
2915 self.armed = false;
2916 self.mutation_guard.take();
2917 }
2918}
2919
2920impl PreparedSessionMaterialization {
2921 fn new(
2922 machine: Arc<MeerkatMachine>,
2923 bindings: meerkat_core::SessionRuntimeBindings,
2924 claim_id: uuid::Uuid,
2925 cleanup_spawner: MachineCleanupTaskSpawner,
2926 ) -> Result<Self, RuntimeDriverError> {
2927 let authority = bindings
2928 .__runtime_authority()
2929 .downcast_ref::<crate::SessionRuntimeBindingsAuthority>()
2930 .ok_or_else(|| RuntimeDriverError::ValidationFailed {
2931 reason: "prepared materialization lacks MeerkatMachine authority".to_string(),
2932 })?;
2933 if authority.materialization_claim_id != Some(claim_id) {
2934 return Err(RuntimeDriverError::StaleAuthority {
2935 reason: "prepared materialization binding lost its exact claim".to_string(),
2936 });
2937 }
2938 let claim_state = Arc::clone(&authority.materialization_claim_state);
2939 Ok(Self {
2940 machine,
2941 bindings,
2942 claim_id,
2943 claim_state,
2944 cleanup_spawner,
2945 archived_resume_authorization_issued: false,
2946 armed: true,
2947 })
2948 }
2949
2950 pub fn bindings(&self) -> &meerkat_core::SessionRuntimeBindings {
2951 &self.bindings
2952 }
2953
2954 pub fn bindings_clone(&self) -> meerkat_core::SessionRuntimeBindings {
2955 self.bindings.clone()
2956 }
2957
2958 pub fn session_id(&self) -> &SessionId {
2959 self.bindings.session_id()
2960 }
2961
2962 #[doc(hidden)]
2963 pub fn cleanup_task_spawner(&self) -> RuntimeCleanupTaskSpawner {
2964 RuntimeCleanupTaskSpawner {
2965 inner: self.cleanup_spawner.clone(),
2966 }
2967 }
2968
2969 #[must_use]
2972 pub async fn owns_current_materialization_claim(&self) -> bool {
2973 if !self.armed {
2974 return false;
2975 }
2976 let Some(_mutation_guard) = self
2977 .machine
2978 .lock_current_session_mutation_gate(self.session_id())
2979 .await
2980 else {
2981 return false;
2982 };
2983 let Ok(authority) = crate::validated_session_runtime_bindings_authority(&self.bindings)
2984 else {
2985 return false;
2986 };
2987 let sessions = self.machine.sessions.read().await;
2988 sessions.get(self.session_id()).is_some_and(|entry| {
2989 entry.epoch_id == *self.bindings.epoch_id()
2990 && Arc::ptr_eq(&entry.materialization_claim_state, &self.claim_state)
2991 && Arc::ptr_eq(&entry.dsl_authority, &authority.dsl_authority)
2992 && Arc::ptr_eq(&entry.handle_teardown_gate, &authority.teardown_gate)
2993 && !entry.physical_attachment_is_live()
2994 && self
2995 .claim_state
2996 .lock()
2997 .unwrap_or_else(std::sync::PoisonError::into_inner)
2998 .exact_claim_is(
2999 self.claim_id,
3000 &[
3001 crate::RuntimeActorMaterializationClaimPhase::Prepared,
3002 crate::RuntimeActorMaterializationClaimPhase::Staged,
3003 crate::RuntimeActorMaterializationClaimPhase::ActorCreating,
3004 crate::RuntimeActorMaterializationClaimPhase::ActorMaterializedPendingCommit,
3005 crate::RuntimeActorMaterializationClaimPhase::Aborting,
3006 ],
3007 )
3008 })
3009 }
3010
3011 pub fn archived_resume_authorization(
3019 &mut self,
3020 ) -> Result<ArchivedSessionActorMaterializationAuthorization, RuntimeDriverError> {
3021 if !self.armed {
3022 return Err(RuntimeDriverError::StaleAuthority {
3023 reason: format!(
3024 "prepared materialization for session {} no longer owns archived-resume authority",
3025 self.session_id()
3026 ),
3027 });
3028 }
3029 if self.archived_resume_authorization_issued {
3030 return Err(RuntimeDriverError::StaleAuthority {
3031 reason: format!(
3032 "prepared materialization for session {} already issued its one-shot archived-resume authorization",
3033 self.session_id()
3034 ),
3035 });
3036 }
3037 let authority = self
3038 .bindings
3039 .__runtime_authority()
3040 .downcast_ref::<crate::SessionRuntimeBindingsAuthority>()
3041 .ok_or_else(|| RuntimeDriverError::ValidationFailed {
3042 reason: "prepared archived-resume materialization lacks machine authority"
3043 .to_string(),
3044 })?;
3045 if authority.materialization_claim_id != Some(self.claim_id)
3046 || !Arc::ptr_eq(&authority.materialization_claim_state, &self.claim_state)
3047 {
3048 return Err(RuntimeDriverError::StaleAuthority {
3049 reason: format!(
3050 "prepared archived-resume claim for session {} is no longer exact",
3051 self.session_id()
3052 ),
3053 });
3054 }
3055 let authorization = ArchivedSessionActorMaterializationAuthorization {
3056 machine: Arc::clone(&self.machine),
3057 session_id: self.session_id().clone(),
3058 epoch_id: self.bindings.epoch_id().clone(),
3059 claim_id: self.claim_id,
3060 claim_state: Arc::clone(&self.claim_state),
3061 dsl_authority: Arc::clone(&authority.dsl_authority),
3062 teardown_gate: Arc::clone(&authority.teardown_gate),
3063 };
3064 self.archived_resume_authorization_issued = true;
3065 Ok(authorization)
3066 }
3067
3068 pub async fn acquire_archived_resume_commit_lease(
3073 &self,
3074 ) -> Result<PreparedArchivedResumeCommitLease, RuntimeDriverError> {
3075 if !self.armed || !self.archived_resume_authorization_issued {
3076 return Err(RuntimeDriverError::StaleAuthority {
3077 reason: format!(
3078 "prepared materialization for session {} does not own post-actor archived-resume authority",
3079 self.session_id()
3080 ),
3081 });
3082 }
3083 let mutation_guard = self
3084 .machine
3085 .lock_current_session_mutation_gate(self.session_id())
3086 .await
3087 .ok_or_else(|| RuntimeDriverError::StaleAuthority {
3088 reason: format!(
3089 "archived-resume session {} disappeared before commit lease acquisition",
3090 self.session_id()
3091 ),
3092 })?;
3093 let exact = {
3094 let sessions = self.machine.sessions.read().await;
3095 sessions.get(self.session_id()).is_some_and(|entry| {
3096 entry.epoch_id == *self.bindings.epoch_id()
3097 && Arc::ptr_eq(&entry.materialization_claim_state, &self.claim_state)
3098 && !entry.physical_attachment_is_live()
3099 && matches!(
3100 entry.control_snapshot().phase,
3101 RuntimeState::Retired | RuntimeState::Idle
3102 )
3103 && self
3104 .claim_state
3105 .lock()
3106 .unwrap_or_else(std::sync::PoisonError::into_inner)
3107 .exact_claim_is(
3108 self.claim_id,
3109 &[crate::RuntimeActorMaterializationClaimPhase::ActorMaterializedPendingCommit],
3110 )
3111 })
3112 };
3113 if !exact {
3114 return Err(RuntimeDriverError::StaleAuthority {
3115 reason: format!(
3116 "archived-resume session {} lost its exact quiescent post-actor claim",
3117 self.session_id()
3118 ),
3119 });
3120 }
3121 Ok(PreparedArchivedResumeCommitLease {
3122 machine: Arc::clone(&self.machine),
3123 session_id: self.session_id().clone(),
3124 epoch_id: self.bindings.epoch_id().clone(),
3125 claim_id: self.claim_id,
3126 claim_state: Arc::clone(&self.claim_state),
3127 _mutation_guard: mutation_guard,
3128 })
3129 }
3130
3131 pub async fn ensure_executor_attachment<F>(
3136 &mut self,
3137 executor_factory: F,
3138 ) -> Result<EnsureRuntimeExecutorAttachment, RuntimeDriverError>
3139 where
3140 F: FnOnce(
3141 RuntimeExecutorAttachmentWitness,
3142 ) -> Box<dyn meerkat_core::lifecycle::CoreExecutor>
3143 + Send
3144 + 'static,
3145 {
3146 self.ensure_executor_attachment_with_boundary_mode(executor_factory, false)
3147 .await
3148 }
3149
3150 #[doc(hidden)]
3157 pub async fn ensure_executor_attachment_under_runtime_turn_finalization_boundary<F>(
3158 &mut self,
3159 executor_factory: F,
3160 ) -> Result<EnsureRuntimeExecutorAttachment, RuntimeDriverError>
3161 where
3162 F: FnOnce(
3163 RuntimeExecutorAttachmentWitness,
3164 ) -> Box<dyn meerkat_core::lifecycle::CoreExecutor>
3165 + Send
3166 + 'static,
3167 {
3168 self.ensure_executor_attachment_with_boundary_mode(executor_factory, true)
3169 .await
3170 }
3171
3172 async fn ensure_executor_attachment_with_boundary_mode<F>(
3173 &mut self,
3174 executor_factory: F,
3175 turn_finalization_boundary_already_held: bool,
3176 ) -> Result<EnsureRuntimeExecutorAttachment, RuntimeDriverError>
3177 where
3178 F: FnOnce(
3179 RuntimeExecutorAttachmentWitness,
3180 ) -> Box<dyn meerkat_core::lifecycle::CoreExecutor>
3181 + Send
3182 + 'static,
3183 {
3184 if !self.armed {
3185 return Err(RuntimeDriverError::StaleAuthority {
3186 reason: format!(
3187 "prepared materialization for session {} no longer owns attachment authority",
3188 self.session_id()
3189 ),
3190 });
3191 }
3192 let expected_claim = RuntimeExecutorAttachmentMaterializationClaim {
3193 claim_id: self.claim_id,
3194 claim_state: Arc::clone(&self.claim_state),
3195 epoch_id: self.bindings.epoch_id().clone(),
3196 };
3197 let outcome = self
3198 .machine
3199 .ensure_session_with_executor_factory_for_materialization(
3200 self.session_id().clone(),
3201 expected_claim,
3202 turn_finalization_boundary_already_held,
3203 executor_factory,
3204 )
3205 .await?;
3206 if matches!(&outcome, EnsureRuntimeExecutorAttachment::Pending(_)) {
3207 self.armed = false;
3211 }
3212 Ok(outcome)
3213 }
3214
3215 fn request_abort(&self) -> bool {
3216 let mut state = self
3217 .claim_state
3218 .lock()
3219 .unwrap_or_else(std::sync::PoisonError::into_inner);
3220 if !state.exact_claim_is(
3221 self.claim_id,
3222 &[
3223 crate::RuntimeActorMaterializationClaimPhase::Prepared,
3224 crate::RuntimeActorMaterializationClaimPhase::Staged,
3225 crate::RuntimeActorMaterializationClaimPhase::ActorCreating,
3226 crate::RuntimeActorMaterializationClaimPhase::ActorMaterializedPendingCommit,
3227 crate::RuntimeActorMaterializationClaimPhase::Aborting,
3228 ],
3229 ) {
3230 return false;
3231 }
3232 state.phase = crate::RuntimeActorMaterializationClaimPhase::Aborting;
3233 true
3234 }
3235
3236 pub async fn rollback_now(&mut self) -> Result<bool, RuntimeDriverError> {
3240 self.rollback_now_with_turn_finalization_boundary(false)
3241 .await
3242 }
3243
3244 pub async fn rollback_now_under_turn_finalization_boundary(
3247 &mut self,
3248 ) -> Result<bool, RuntimeDriverError> {
3249 self.rollback_now_with_turn_finalization_boundary(true)
3250 .await
3251 }
3252
3253 async fn rollback_now_with_turn_finalization_boundary(
3254 &mut self,
3255 turn_finalization_boundary_already_held: bool,
3256 ) -> Result<bool, RuntimeDriverError> {
3257 if !self.armed {
3258 return Ok(false);
3259 }
3260 if !self.request_abort() {
3261 self.armed = false;
3262 return Ok(false);
3263 }
3264 let rolled_back = self
3265 .machine
3266 .abort_prepared_session_materialization_claim(
3267 self.bindings.session_id(),
3268 self.claim_id,
3269 Some(self.bindings.epoch_id()),
3270 Some(&self.claim_state),
3271 turn_finalization_boundary_already_held,
3272 )
3273 .await?;
3274 self.armed = false;
3275 Ok(rolled_back)
3276 }
3277
3278 pub async fn commit_staged(&mut self) -> Result<(), RuntimeDriverError> {
3282 self.machine
3283 .commit_prepared_session_materialization_staged(&self.bindings)
3284 .await?;
3285 self.armed = false;
3286 Ok(())
3287 }
3288
3289 pub async fn commit_actor_unattached(&mut self) -> Result<(), RuntimeDriverError> {
3293 self.machine
3294 .commit_prepared_session_actor_unattached(&self.bindings)
3295 .await?;
3296 self.armed = false;
3297 Ok(())
3298 }
3299}
3300
3301impl Drop for PreparedSessionMaterialization {
3302 fn drop(&mut self) {
3303 if !self.armed || !self.request_abort() {
3304 return;
3305 }
3306 let machine = Arc::clone(&self.machine);
3307 let session_id = self.bindings.session_id().clone();
3308 let epoch_id = self.bindings.epoch_id().clone();
3309 let claim_id = self.claim_id;
3310 let claim_state = Arc::clone(&self.claim_state);
3311 self.cleanup_spawner.spawn(async move {
3312 if let Err(error) = machine
3313 .abort_prepared_session_materialization_claim(
3314 &session_id,
3315 claim_id,
3316 Some(&epoch_id),
3317 Some(&claim_state),
3318 false,
3319 )
3320 .await
3321 {
3322 tracing::warn!(
3323 %session_id,
3324 %error,
3325 "drop-time exact prepared materialization rollback failed"
3326 );
3327 }
3328 });
3329 }
3330}
3331
3332pub(super) struct PendingPreparedMaterialization {
3333 machine: Arc<MeerkatMachine>,
3334 session_id: SessionId,
3335 claim_id: uuid::Uuid,
3336 cleanup_spawner: MachineCleanupTaskSpawner,
3337 claim_state_slot: Arc<
3338 std::sync::Mutex<
3339 Option<Arc<std::sync::Mutex<crate::RuntimeActorMaterializationClaimState>>>,
3340 >,
3341 >,
3342 armed: bool,
3343}
3344
3345impl PendingPreparedMaterialization {
3346 pub(super) fn new(
3347 machine: Arc<MeerkatMachine>,
3348 session_id: SessionId,
3349 claim_id: uuid::Uuid,
3350 ) -> Result<Self, RuntimeDriverError> {
3351 Ok(Self {
3352 machine,
3353 session_id,
3354 claim_id,
3355 cleanup_spawner: MachineCleanupTaskSpawner::acquire()?,
3356 claim_state_slot: Arc::new(std::sync::Mutex::new(None)),
3357 armed: true,
3358 })
3359 }
3360
3361 fn cleanup_spawner(&self) -> MachineCleanupTaskSpawner {
3362 self.cleanup_spawner.clone()
3363 }
3364
3365 pub(super) fn claim_state_slot(
3366 &self,
3367 ) -> Arc<
3368 std::sync::Mutex<
3369 Option<Arc<std::sync::Mutex<crate::RuntimeActorMaterializationClaimState>>>,
3370 >,
3371 > {
3372 Arc::clone(&self.claim_state_slot)
3373 }
3374
3375 pub(super) fn disarm(&mut self) {
3376 self.armed = false;
3377 }
3378}
3379
3380impl Drop for PendingPreparedMaterialization {
3381 fn drop(&mut self) {
3382 if !self.armed {
3383 return;
3384 }
3385 let claim_state = self
3386 .claim_state_slot
3387 .lock()
3388 .unwrap_or_else(std::sync::PoisonError::into_inner)
3389 .clone();
3390 if let Some(claim_state) = claim_state {
3391 let mut state = claim_state
3392 .lock()
3393 .unwrap_or_else(std::sync::PoisonError::into_inner);
3394 if state.current == Some(self.claim_id)
3395 && state.phase != crate::RuntimeActorMaterializationClaimPhase::RetainedActor
3396 {
3397 state.phase = crate::RuntimeActorMaterializationClaimPhase::Aborting;
3398 }
3399 }
3400 let machine = Arc::clone(&self.machine);
3401 let session_id = self.session_id.clone();
3402 let claim_id = self.claim_id;
3403 self.cleanup_spawner.spawn(async move {
3404 if let Err(error) = machine
3405 .abort_prepared_session_materialization_claim(
3406 &session_id,
3407 claim_id,
3408 None,
3409 None,
3410 false,
3411 )
3412 .await
3413 {
3414 tracing::warn!(
3415 %session_id,
3416 %error,
3417 "cancelled binding preparation exact rollback failed"
3418 );
3419 }
3420 });
3421 }
3422}
3423
3424pub struct MachineServiceTurnCommitLease {
3432 session_id: SessionId,
3433 driver: SharedDriver,
3434 _mutation_guard: crate::tokio::sync::OwnedMutexGuard<()>,
3435}
3436
3437pub struct MachineServiceTurnIdentity {
3441 session_id: SessionId,
3442 driver: SharedDriver,
3443}
3444
3445impl MachineSessionArchiveLease {
3446 pub fn owns_recovered_registration(&self) -> bool {
3449 self.recovered_registration_for_archive
3450 }
3451
3452 pub async fn runtime_state(&self) -> RuntimeState {
3455 self.driver.lock().await.runtime_state()
3456 }
3457
3458 pub async fn runtime_is_retired(&self) -> bool {
3462 self.runtime_state().await == RuntimeState::Retired
3463 }
3464}
3465
3466#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3471struct RuntimeLoopAttachmentId(uuid::Uuid);
3472
3473impl RuntimeLoopAttachmentId {
3474 fn new() -> Self {
3475 Self(uuid::Uuid::new_v4())
3476 }
3477}
3478
3479struct RuntimeEffectDispatchAttachmentWitness {
3483 mutation_gate: Arc<Mutex<()>>,
3484 driver: SharedDriver,
3485 dsl_authority: Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
3486 attachment_id: RuntimeLoopAttachmentId,
3487 effect_tx: mpsc::Sender<crate::effect::RuntimeEffect>,
3488}
3489
3490struct RuntimeLiveBoundaryAttachmentWitness {
3494 mutation_gate: Arc<Mutex<()>>,
3495 driver: SharedDriver,
3496 dsl_authority: Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
3497 attachment_id: RuntimeLoopAttachmentId,
3498 boundary_handle: Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>,
3499}
3500
3501struct RuntimeAcceptedBoundaryCancelPlan {
3502 witness: RuntimeEffectDispatchAttachmentWitness,
3503 boundary_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>>,
3504 pending_dispatch: PendingBoundaryCancelDispatchGuard,
3505 expected_run_id: RunId,
3506 projected_effect: crate::effect::ProjectedRuntimeEffect,
3507 dispatch_generation: u64,
3508 dispatch_lifecycle_phase: dsl::MeerkatPhase,
3509}
3510
3511#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3512enum BoundaryCancelDispatchState {
3513 Current,
3514 ConsumedConverged,
3515 SameGenerationOrphan,
3516 Superseded,
3517}
3518
3519struct RuntimeEffectDispatchMemberAuthority {
3520 lease: MemberEffectAuthorityLease,
3521 expected_member: Option<meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation>,
3522}
3523
3524struct PendingBoundaryCancelDispatchGuard {
3527 dsl_authority: Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
3528 dispatch_generation: u64,
3529 armed: bool,
3530}
3531
3532impl PendingBoundaryCancelDispatchGuard {
3533 fn new(
3534 dsl_authority: Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
3535 dispatch_generation: u64,
3536 ) -> Self {
3537 Self {
3538 dsl_authority,
3539 dispatch_generation,
3540 armed: true,
3541 }
3542 }
3543
3544 fn disarm(&mut self) {
3545 self.armed = false;
3546 }
3547}
3548
3549impl Drop for PendingBoundaryCancelDispatchGuard {
3550 fn drop(&mut self) {
3551 if self.armed
3552 && let Err(error) = MeerkatMachine::abort_dsl_boundary_cancel_dispatch_if_current(
3553 &self.dsl_authority,
3554 self.dispatch_generation,
3555 )
3556 {
3557 tracing::error!(
3558 dispatch_generation = self.dispatch_generation,
3559 error = %error,
3560 "failed to compensate an abandoned boundary-cancel dispatch"
3561 );
3562 }
3563 }
3564}
3565
3566struct AcceptedIngressFallbackWakeGuard {
3569 wake_tx: Option<mpsc::Sender<()>>,
3570 armed: bool,
3571}
3572
3573impl AcceptedIngressFallbackWakeGuard {
3574 fn new(wake_tx: Option<mpsc::Sender<()>>, armed: bool) -> Self {
3575 Self { wake_tx, armed }
3576 }
3577
3578 fn disarm(&mut self) {
3579 self.armed = false;
3580 }
3581}
3582
3583impl Drop for AcceptedIngressFallbackWakeGuard {
3584 fn drop(&mut self) {
3585 if self.armed
3586 && let Some(wake_tx) = self.wake_tx.as_ref()
3587 {
3588 let _ = wake_tx.try_send(());
3589 }
3590 }
3591}
3592
3593struct RuntimeLoopAttachment {
3594 id: RuntimeLoopAttachmentId,
3595 wake_tx: mpsc::Sender<()>,
3596 effect_tx: mpsc::Sender<crate::effect::RuntimeEffect>,
3597 serving_release: Option<crate::runtime_loop::RuntimeLoopServingRelease>,
3598 boundary_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>>,
3599 interrupt_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorInterruptHandle>>,
3600 loop_handle: tokio::task::JoinHandle<()>,
3601}
3602
3603enum RuntimeLoopAttachmentSlot {
3605 Empty,
3606 Pending(RuntimeLoopAttachment),
3607 Attached(RuntimeLoopAttachment),
3608}
3609
3610impl RuntimeSessionEntry {
3611 fn dsl_mutation_blocked_by_unregister(
3612 &self,
3613 session_id: &SessionId,
3614 ) -> Option<RuntimeDriverError> {
3615 if self.pending_unregister_finalization.is_some() {
3616 return Some(RuntimeDriverError::UnregisterFinalizationOutcomeUnknown {
3617 reason: format!(
3618 "session {session_id} retains an ambiguous unregister finalization; retry unregister before applying any other lifecycle mutation"
3619 ),
3620 });
3621 }
3622 self.handle_teardown_gate
3623 .is_closed()
3624 .then(|| RuntimeDriverError::ValidationFailed {
3625 reason: format!("session {session_id} is a teardown-only unregister retry anchor"),
3626 })
3627 }
3628
3629 fn registration_blocked_by_unregister(
3630 &self,
3631 session_id: &SessionId,
3632 ) -> Option<RuntimeDriverError> {
3633 if let Some(error) = self.dsl_mutation_blocked_by_unregister(session_id) {
3634 return Some(error);
3635 }
3636 if self.unregister_coordinator.is_some() {
3637 return Some(RuntimeDriverError::NotReady {
3638 state: self.control_snapshot().phase,
3639 });
3640 }
3641 let Some(coordinator) = self.runtime_stop_cleanup_coordinator.as_ref() else {
3642 return None;
3643 };
3644 match coordinator.result_rx.borrow().clone() {
3645 None => Some(RuntimeDriverError::RuntimeStopInProgress {
3646 runtime_id: self.runtime_id.clone(),
3647 }),
3648 Some(Ok(())) => None,
3649 Some(Err(error)) => Some(error),
3650 }
3651 }
3652
3653 fn control_snapshot(&self) -> crate::driver::ephemeral::RuntimeControlProjection {
3654 self.control_projection
3655 .read()
3656 .map(|guard| guard.clone())
3657 .unwrap_or_else(|poisoned| {
3658 tracing::error!("runtime control projection lock poisoned");
3659 poisoned.into_inner().clone()
3660 })
3661 }
3662
3663 fn sync_control_projection_from_dsl_authority(&self) {
3664 let next = {
3665 let authority = self
3666 .dsl_authority
3667 .lock()
3668 .unwrap_or_else(std::sync::PoisonError::into_inner);
3669 crate::driver::ephemeral::RuntimeControlProjection {
3670 phase: crate::meerkat_machine::dsl_authority::runtime_phase_from_authority(
3671 &authority,
3672 ),
3673 current_run_id:
3674 crate::meerkat_machine::dsl_authority::current_run_id_from_authority(&authority),
3675 pre_run_phase: crate::meerkat_machine::dsl_authority::pre_run_phase_from_authority(
3676 &authority,
3677 ),
3678 }
3679 };
3680 *self
3681 .control_projection
3682 .write()
3683 .unwrap_or_else(std::sync::PoisonError::into_inner) = next;
3684 }
3685
3686 fn physical_attachment_is_live(&self) -> bool {
3687 match &self.attachment_slot {
3688 RuntimeLoopAttachmentSlot::Pending(attachment)
3689 | RuntimeLoopAttachmentSlot::Attached(attachment) => {
3690 !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed()
3691 }
3692 RuntimeLoopAttachmentSlot::Empty => false,
3693 }
3694 }
3695
3696 fn attachment_is_live(&self) -> bool {
3697 matches!(
3698 &self.attachment_slot,
3699 RuntimeLoopAttachmentSlot::Attached(attachment)
3700 if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed()
3701 )
3702 }
3703
3704 fn generated_executor_registration_active(&self) -> bool {
3705 let authority = self
3706 .dsl_authority
3707 .lock()
3708 .unwrap_or_else(std::sync::PoisonError::into_inner);
3709 matches!(
3710 authority.state().registration_phase,
3711 dsl::RegistrationPhase::Active
3712 )
3713 }
3714
3715 fn generated_executor_registration_has_viable_attachment(&self) -> bool {
3716 self.generated_executor_registration_active()
3717 && match &self.attachment_slot {
3718 RuntimeLoopAttachmentSlot::Empty => true,
3719 RuntimeLoopAttachmentSlot::Pending(_) | RuntimeLoopAttachmentSlot::Attached(_) => {
3720 self.physical_attachment_is_live()
3721 }
3722 }
3723 }
3724
3725 fn close_handle_teardown_gate(&self) {
3726 let _guard = self
3727 .dsl_authority
3728 .lock()
3729 .unwrap_or_else(std::sync::PoisonError::into_inner);
3730 self.handle_teardown_gate.close();
3731 }
3732
3733 fn generated_executor_registration_active_or_draining(&self) -> bool {
3741 let authority = self
3742 .dsl_authority
3743 .lock()
3744 .unwrap_or_else(std::sync::PoisonError::into_inner);
3745 matches!(
3746 authority.state().registration_phase,
3747 dsl::RegistrationPhase::Active | dsl::RegistrationPhase::Draining
3748 )
3749 }
3750
3751 fn generated_service_turn_binding_open(&self, session_id: &SessionId) -> bool {
3752 let authority = self
3753 .dsl_authority
3754 .lock()
3755 .unwrap_or_else(std::sync::PoisonError::into_inner);
3756 let state = authority.state();
3757 self.handle_teardown_gate.is_open()
3758 && state.session_id.as_ref() == Some(&dsl::SessionId::from_domain(session_id))
3759 && state.registration_phase != dsl::RegistrationPhase::Draining
3760 }
3761
3762 fn generated_stop_deferred(&self) -> bool {
3763 self.dsl_authority
3764 .lock()
3765 .unwrap_or_else(std::sync::PoisonError::into_inner)
3766 .state()
3767 .runtime_stop_deferred
3768 }
3769
3770 fn stage_generated_executor_registration_claim(
3771 &self,
3772 session_id: &SessionId,
3773 ) -> Result<StagedSessionDslInput, String> {
3774 let staged = MeerkatMachine::stage_dsl_transition_on_authority(
3775 &self.dsl_authority,
3776 dsl::MeerkatMachineInput::EnsureSessionWithExecutor {
3777 session_id: dsl::SessionId::from_domain(session_id),
3778 },
3779 "EnsureSessionWithExecutor",
3780 )?;
3781 if self.generated_executor_registration_active() {
3782 Ok(staged)
3783 } else {
3784 let mut authority = self
3785 .dsl_authority
3786 .lock()
3787 .unwrap_or_else(std::sync::PoisonError::into_inner);
3788 authority.restore_snapshot(staged.previous_snapshot);
3789 Err("generated MeerkatMachine did not grant active executor registration".into())
3790 }
3791 }
3792
3793 fn stage_generated_executor_exit_observation(&self) -> Result<StagedSessionDslInput, String> {
3794 MeerkatMachine::stage_runtime_owner_dsl_transition_on_authority(
3795 &self.dsl_authority,
3796 crate::meerkat_machine_types::MeerkatMachineFieldlessRuntimeInternalInput::RuntimeExecutorExited,
3797 )
3798 }
3799
3800 fn has_live_attachment(&self) -> bool {
3803 self.attachment_is_live()
3804 }
3805
3806 #[allow(clippy::too_many_arguments)]
3809 fn attach_runtime_loop(
3810 &mut self,
3811 id: RuntimeLoopAttachmentId,
3812 wake_tx: mpsc::Sender<()>,
3813 effect_tx: mpsc::Sender<crate::effect::RuntimeEffect>,
3814 serving_release: crate::runtime_loop::RuntimeLoopServingRelease,
3815 boundary_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>>,
3816 interrupt_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorInterruptHandle>>,
3817 publication_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorPublicationHandle>>,
3818 post_stop_cleanup_handle: Option<
3819 Arc<dyn meerkat_core::lifecycle::CoreExecutorPostStopCleanupHandle>,
3820 >,
3821 machine_managed_post_stop_unregister: bool,
3822 expected_materialization_claim: Option<&RuntimeExecutorAttachmentMaterializationClaim>,
3823 spawned_loop: crate::runtime_loop::SpawnedRuntimeLoop,
3824 ) -> Result<(), crate::runtime_loop::SpawnedRuntimeLoop> {
3825 if spawned_loop.startup_authority_transfer.is_some()
3826 || spawned_loop.serving_release.is_some()
3827 {
3828 return Err(spawned_loop);
3829 }
3830 let claim_changed = {
3831 let mut state = self
3832 .materialization_claim_state
3833 .lock()
3834 .unwrap_or_else(std::sync::PoisonError::into_inner);
3835 match expected_materialization_claim {
3836 Some(expected)
3837 if Arc::ptr_eq(
3838 &self.materialization_claim_state,
3839 &expected.claim_state,
3840 ) && state.exact_claim_is(
3841 expected.claim_id,
3842 &[crate::RuntimeActorMaterializationClaimPhase::ActorMaterializedPendingCommit],
3843 ) => {}
3844 Some(_) => return Err(spawned_loop),
3845 None
3846 if state.current.is_none()
3847 && matches!(
3848 state.phase,
3849 crate::RuntimeActorMaterializationClaimPhase::Vacant
3850 | crate::RuntimeActorMaterializationClaimPhase::RetainedActor
3851 ) => {}
3852 None => return Err(spawned_loop),
3853 }
3854 let Some(next_legacy_generation) = state.legacy_capability_generation.checked_add(1)
3855 else {
3856 return Err(spawned_loop);
3857 };
3858 state.legacy_capability_generation = next_legacy_generation;
3859 let claim_changed = state.current.take().is_some();
3860 state.phase = crate::RuntimeActorMaterializationClaimPhase::Vacant;
3861 state.rollback_registration_available = false;
3862 claim_changed.then(|| Arc::clone(&state.changed))
3863 };
3864 if let Some(changed) = claim_changed {
3865 changed.notify_waiters();
3866 }
3867 let crate::runtime_loop::SpawnedRuntimeLoop {
3868 loop_handle,
3869 teardown_slot,
3870 startup: _,
3871 startup_authority_transfer: _,
3872 serving_release: _,
3873 } = spawned_loop;
3874 self.provisional_interrupt_handle = None;
3875 self.provisional_materialization_claim_id = None;
3876 self.runtime_stop_cleanup_coordinator = None;
3877 self.runtime_loop_teardown = Some(Arc::clone(&teardown_slot));
3878 self.publication_handle = publication_handle;
3879 self.post_stop_cleanup_handle = post_stop_cleanup_handle;
3880 self.post_stop_cleanup_attachment_id = machine_managed_post_stop_unregister.then_some(id);
3881 self.post_stop_cleanup_complete = !machine_managed_post_stop_unregister;
3882 self.post_stop_cleanup_gate = Arc::new(Mutex::new(()));
3883 self.attachment_slot = RuntimeLoopAttachmentSlot::Pending(RuntimeLoopAttachment {
3884 id,
3885 wake_tx,
3886 effect_tx,
3887 serving_release: Some(serving_release),
3888 boundary_handle,
3889 interrupt_handle,
3890 loop_handle,
3891 });
3892 Ok(())
3893 }
3894
3895 fn owns_runtime_loop_attachment(&self, expected: RuntimeLoopAttachmentId) -> bool {
3896 matches!(
3897 &self.attachment_slot,
3898 RuntimeLoopAttachmentSlot::Pending(attachment)
3899 | RuntimeLoopAttachmentSlot::Attached(attachment)
3900 if attachment.id == expected
3901 )
3902 }
3903
3904 fn live_attachment_id(&self) -> Option<RuntimeLoopAttachmentId> {
3905 match &self.attachment_slot {
3906 RuntimeLoopAttachmentSlot::Pending(attachment)
3907 | RuntimeLoopAttachmentSlot::Attached(attachment)
3908 if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed() =>
3909 {
3910 Some(attachment.id)
3911 }
3912 _ => None,
3913 }
3914 }
3915
3916 fn take_runtime_loop_attachment(&mut self) -> Option<RuntimeLoopAttachment> {
3924 match std::mem::replace(&mut self.attachment_slot, RuntimeLoopAttachmentSlot::Empty) {
3925 RuntimeLoopAttachmentSlot::Pending(attachment) => Some(attachment),
3926 RuntimeLoopAttachmentSlot::Attached(attachment) => Some(attachment),
3927 RuntimeLoopAttachmentSlot::Empty => None,
3928 }
3929 }
3930
3931 fn clear_dead_attachment(&mut self) -> bool {
3932 if matches!(
3933 self.attachment_slot,
3934 RuntimeLoopAttachmentSlot::Pending(_) | RuntimeLoopAttachmentSlot::Attached(_)
3935 ) && !self.physical_attachment_is_live()
3936 {
3937 self.attachment_slot = RuntimeLoopAttachmentSlot::Empty;
3938 return true;
3939 }
3940 false
3941 }
3942
3943 fn retire_completed_runtime_stop_after_revival(
3944 &mut self,
3945 session_id: &SessionId,
3946 ) -> Result<(), RuntimeDriverError> {
3947 if !matches!(self.attachment_slot, RuntimeLoopAttachmentSlot::Empty) {
3948 return Err(RuntimeDriverError::Internal(format!(
3949 "revived session {session_id} still carries a runtime-loop attachment"
3950 )));
3951 }
3952 match self.runtime_stop_cleanup_coordinator.as_ref() {
3953 None if self.runtime_loop_teardown.is_none() => return Ok(()),
3954 None => {
3955 return Err(RuntimeDriverError::Internal(format!(
3956 "revived session {session_id} carries a teardown slot without its stop coordinator"
3957 )));
3958 }
3959 Some(coordinator) => {
3960 let coordinator_slot_is_current = match (
3961 coordinator.teardown_slot.as_ref(),
3962 self.runtime_loop_teardown.as_ref(),
3963 ) {
3964 (Some(coordinator_slot), Some(current_slot)) => {
3965 Arc::ptr_eq(coordinator_slot, current_slot)
3966 }
3967 (None, None) => true,
3968 _ => false,
3969 };
3970 if !coordinator_slot_is_current {
3971 return Err(RuntimeDriverError::Internal(format!(
3972 "revived session {session_id} carries a stale runtime-stop teardown owner"
3973 )));
3974 }
3975 match coordinator.result_rx.borrow().clone() {
3976 Some(Ok(())) => {}
3977 None => {
3978 return Err(RuntimeDriverError::RuntimeStopInProgress {
3979 runtime_id: self.runtime_id.clone(),
3980 });
3981 }
3982 Some(Err(error)) => return Err(error),
3983 }
3984 }
3985 }
3986 self.runtime_stop_cleanup_coordinator = None;
3987 self.runtime_loop_teardown = None;
3988 Ok(())
3989 }
3990
3991 fn wake_sender(&self) -> Option<mpsc::Sender<()>> {
3992 match &self.attachment_slot {
3993 RuntimeLoopAttachmentSlot::Attached(attachment)
3994 if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed() =>
3995 {
3996 Some(attachment.wake_tx.clone())
3997 }
3998 _ => None,
3999 }
4000 }
4001
4002 fn effect_sender(&self) -> Option<mpsc::Sender<crate::effect::RuntimeEffect>> {
4003 match &self.attachment_slot {
4004 RuntimeLoopAttachmentSlot::Attached(attachment)
4005 if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed() =>
4006 {
4007 Some(attachment.effect_tx.clone())
4008 }
4009 _ => None,
4010 }
4011 }
4012
4013 fn boundary_handle(
4014 &self,
4015 ) -> Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>> {
4016 match &self.attachment_slot {
4017 RuntimeLoopAttachmentSlot::Attached(attachment)
4018 if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed() =>
4019 {
4020 attachment.boundary_handle.clone()
4021 }
4022 _ => None,
4023 }
4024 }
4025
4026 fn interrupt_handle(
4027 &self,
4028 ) -> Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorInterruptHandle>> {
4029 match &self.attachment_slot {
4030 RuntimeLoopAttachmentSlot::Attached(attachment)
4031 if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed() =>
4032 {
4033 attachment.interrupt_handle.clone()
4034 }
4035 _ => self.provisional_interrupt_handle.clone(),
4036 }
4037 }
4038
4039 fn publication_handle(
4040 &self,
4041 ) -> Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorPublicationHandle>> {
4042 self.publication_handle.clone()
4043 }
4044
4045 fn install_provisional_interrupt_handle(
4046 &mut self,
4047 claim_id: uuid::Uuid,
4048 handle: Arc<dyn meerkat_core::lifecycle::CoreExecutorInterruptHandle>,
4049 ) {
4050 if !self.physical_attachment_is_live() {
4051 self.provisional_materialization_claim_id = Some(claim_id);
4052 self.provisional_interrupt_handle = Some(handle);
4053 }
4054 }
4055
4056 fn install_provisional_post_stop_cleanup_handle(
4057 &mut self,
4058 claim_id: uuid::Uuid,
4059 handle: Arc<dyn meerkat_core::lifecycle::CoreExecutorPostStopCleanupHandle>,
4060 ) {
4061 if !self.physical_attachment_is_live() {
4062 self.provisional_materialization_claim_id = Some(claim_id);
4063 self.post_stop_cleanup_handle = Some(handle);
4064 self.post_stop_cleanup_attachment_id = Some(RuntimeLoopAttachmentId::new());
4065 self.post_stop_cleanup_complete = false;
4066 self.post_stop_cleanup_gate = Arc::new(Mutex::new(()));
4067 }
4068 }
4069}
4070
4071impl MeerkatMachine {
4072 #[cfg(test)]
4073 pub(crate) async fn model_routing_handle_for_test(
4074 &self,
4075 session_id: &SessionId,
4076 ) -> Option<Arc<crate::handles::RuntimeModelRoutingHandle>> {
4077 let (dsl_authority, visibility_owner) = {
4078 let sessions = self.sessions.read().await;
4079 let entry = sessions.get(session_id)?;
4080 (
4081 Arc::clone(&entry.dsl_authority),
4082 Arc::clone(&entry.tool_visibility_owner),
4083 )
4084 };
4085 Some(Arc::new(
4086 crate::handles::RuntimeModelRoutingHandle::new_with_visibility_owner(
4087 Arc::new(crate::handles::HandleDslAuthority::from_shared(
4088 dsl_authority,
4089 )),
4090 visibility_owner,
4091 ),
4092 ))
4093 }
4094
4095 async fn session_mutation_gate(&self, session_id: &SessionId) -> Option<Arc<Mutex<()>>> {
4101 let sessions = self.sessions.read().await;
4102 sessions
4103 .get(session_id)
4104 .map(|entry| Arc::clone(&entry.mutation_gate))
4105 }
4106
4107 async fn lock_current_session_mutation_gate(
4108 &self,
4109 session_id: &SessionId,
4110 ) -> Option<crate::tokio::sync::OwnedMutexGuard<()>> {
4111 loop {
4112 let gate = self.session_mutation_gate(session_id).await?;
4113 let gate_guard = Arc::clone(&gate).lock_owned().await;
4114 let sessions = self.sessions.read().await;
4115 let entry = sessions.get(session_id)?;
4116 if Arc::ptr_eq(&entry.mutation_gate, &gate) {
4117 return Some(gate_guard);
4118 }
4119 }
4120 }
4121
4122 #[cfg(any(test, feature = "test-support"))]
4126 #[doc(hidden)]
4127 pub fn arm_runtime_loop_before_queue_authority_test_hook(
4128 &self,
4129 session_id: SessionId,
4130 ) -> (
4131 crate::tokio::sync::oneshot::Receiver<()>,
4132 crate::tokio::sync::oneshot::Sender<()>,
4133 ) {
4134 let (entered_tx, entered_rx) = crate::tokio::sync::oneshot::channel();
4135 let (release_tx, release_rx) = crate::tokio::sync::oneshot::channel();
4136 let mut hook = self
4137 .test_runtime_loop_before_queue_authority
4138 .lock()
4139 .unwrap_or_else(std::sync::PoisonError::into_inner);
4140 assert!(
4141 hook.is_none(),
4142 "runtime-loop queue-authority test hook already armed"
4143 );
4144 *hook = Some((session_id, entered_tx, release_rx));
4145 (entered_rx, release_tx)
4146 }
4147
4148 #[cfg(any(test, feature = "test-support"))]
4149 pub(crate) async fn run_runtime_loop_before_queue_authority_test_hook(
4150 &self,
4151 session_id: &SessionId,
4152 ) {
4153 let armed = {
4154 let mut hook = self
4155 .test_runtime_loop_before_queue_authority
4156 .lock()
4157 .unwrap_or_else(std::sync::PoisonError::into_inner);
4158 if hook
4159 .as_ref()
4160 .is_some_and(|(armed_session_id, _, _)| armed_session_id == session_id)
4161 {
4162 hook.take()
4163 } else {
4164 None
4165 }
4166 };
4167 if let Some((_, entered_tx, release_rx)) = armed {
4168 let _ = entered_tx.send(());
4169 let _ = release_rx.await;
4170 }
4171 }
4172
4173 #[cfg(test)]
4174 fn arm_control_command_after_logical_lookup_test_hook(
4175 &self,
4176 kind: ControlCommandLookupTestKind,
4177 session_id: SessionId,
4178 ) -> (
4179 crate::tokio::sync::oneshot::Receiver<()>,
4180 crate::tokio::sync::oneshot::Sender<()>,
4181 ) {
4182 let (entered_tx, entered_rx) = crate::tokio::sync::oneshot::channel();
4183 let (release_tx, release_rx) = crate::tokio::sync::oneshot::channel();
4184 let mut hook = self
4185 .test_control_command_after_logical_lookup
4186 .lock()
4187 .unwrap_or_else(std::sync::PoisonError::into_inner);
4188 assert!(
4189 hook.is_none(),
4190 "control-command lookup test hook already armed"
4191 );
4192 *hook = Some((kind, session_id, entered_tx, release_rx));
4193 (entered_rx, release_tx)
4194 }
4195
4196 #[cfg(test)]
4197 async fn run_control_command_after_logical_lookup_test_hook(
4198 &self,
4199 kind: ControlCommandLookupTestKind,
4200 session_id: &SessionId,
4201 ) {
4202 let armed = {
4203 let mut hook = self
4204 .test_control_command_after_logical_lookup
4205 .lock()
4206 .unwrap_or_else(std::sync::PoisonError::into_inner);
4207 if hook
4208 .as_ref()
4209 .is_some_and(|(armed_kind, armed_session_id, _, _)| {
4210 *armed_kind == kind && armed_session_id == session_id
4211 })
4212 {
4213 hook.take()
4214 } else {
4215 None
4216 }
4217 };
4218 if let Some((_, _, entered_tx, release_rx)) = armed {
4219 let _ = entered_tx.send(());
4220 let _ = release_rx.await;
4221 }
4222 }
4223
4224 async fn lock_registration_gate(
4225 &self,
4226 session_id: &SessionId,
4227 ) -> Result<crate::tokio::sync::OwnedMutexGuard<()>, RuntimeDriverError> {
4228 let gate_guard = self
4229 .lock_current_session_mutation_gate(session_id)
4230 .await
4231 .ok_or(RuntimeDriverError::NotReady {
4232 state: RuntimeState::Destroyed,
4233 })?;
4234 let blocked = {
4235 let sessions = self.sessions.read().await;
4236 let entry = sessions
4237 .get(session_id)
4238 .ok_or(RuntimeDriverError::NotReady {
4239 state: RuntimeState::Destroyed,
4240 })?;
4241 entry.registration_blocked_by_unregister(session_id)
4242 };
4243 if let Some(error) = blocked {
4244 return Err(error);
4245 }
4246 Ok(gate_guard)
4247 }
4248
4249 #[cfg(feature = "live")]
4250 async fn session_live_lifecycle_gate(&self, session_id: &SessionId) -> Option<Arc<Mutex<()>>> {
4251 let sessions = self.sessions.read().await;
4252 sessions
4253 .get(session_id)
4254 .map(|entry| Arc::clone(&entry.live_lifecycle_gate))
4255 }
4256
4257 #[cfg(feature = "live")]
4258 async fn lock_current_session_live_lifecycle_gate(
4259 &self,
4260 session_id: &SessionId,
4261 ) -> Option<(Arc<Mutex<()>>, crate::tokio::sync::OwnedMutexGuard<()>)> {
4262 loop {
4263 let gate = self.session_live_lifecycle_gate(session_id).await?;
4264 let guard = Arc::clone(&gate).lock_owned().await;
4265 let sessions = self.sessions.read().await;
4266 let entry = sessions.get(session_id)?;
4267 if Arc::ptr_eq(&entry.live_lifecycle_gate, &gate) {
4268 return Some((gate, guard));
4269 }
4270 }
4271 }
4272
4273 #[cfg(feature = "live")]
4274 async fn lock_session_mutation_gate_for_live_lifecycle_lease(
4275 &self,
4276 session_id: &SessionId,
4277 lease: &crate::member_live::MemberLiveLifecycleLease,
4278 ) -> Option<crate::tokio::sync::OwnedMutexGuard<()>> {
4279 let mutation_gate = {
4280 let sessions = self.sessions.read().await;
4281 let entry = sessions.get(session_id)?;
4282 if !lease.matches_gate(&entry.live_lifecycle_gate) {
4283 return None;
4284 }
4285 Arc::clone(&entry.mutation_gate)
4286 };
4287 let guard = Arc::clone(&mutation_gate).lock_owned().await;
4288 let sessions = self.sessions.read().await;
4289 let entry = sessions.get(session_id)?;
4290 if lease.matches_gate(&entry.live_lifecycle_gate)
4291 && Arc::ptr_eq(&entry.mutation_gate, &mutation_gate)
4292 {
4293 Some(guard)
4294 } else {
4295 None
4296 }
4297 }
4298
4299 pub(crate) async fn lock_current_session_driver_gate(
4300 &self,
4301 session_id: &SessionId,
4302 driver: &SharedDriver,
4303 ) -> Result<crate::tokio::sync::OwnedMutexGuard<()>, RuntimeDriverError> {
4304 let gate_guard = self
4305 .lock_current_session_mutation_gate(session_id)
4306 .await
4307 .ok_or(RuntimeDriverError::NotReady {
4308 state: RuntimeState::Destroyed,
4309 })?;
4310 {
4311 let sessions = self.sessions.read().await;
4312 let entry = sessions
4313 .get(session_id)
4314 .ok_or(RuntimeDriverError::NotReady {
4315 state: RuntimeState::Destroyed,
4316 })?;
4317 if !Arc::ptr_eq(&entry.driver, driver) {
4318 return Err(RuntimeDriverError::NotReady {
4319 state: RuntimeState::Destroyed,
4320 });
4321 }
4322 }
4323 Ok(gate_guard)
4324 }
4325
4326 pub(crate) async fn lock_current_runtime_loop_driver_authority(
4327 &self,
4328 session_id: &SessionId,
4329 driver: &SharedDriver,
4330 ) -> Result<crate::tokio::sync::OwnedMutexGuard<()>, RuntimeDriverError> {
4331 let gate_guard = self
4332 .lock_current_session_driver_gate(session_id, driver)
4333 .await?;
4334 {
4335 let sessions = self.sessions.read().await;
4336 let entry = sessions
4337 .get(session_id)
4338 .ok_or(RuntimeDriverError::NotReady {
4339 state: RuntimeState::Destroyed,
4340 })?;
4341 if !entry.generated_executor_registration_active_or_draining() {
4342 return Err(RuntimeDriverError::ValidationFailed {
4343 reason:
4344 "generated MeerkatMachine has no active runtime-loop executor registration"
4345 .to_string(),
4346 });
4347 }
4348 }
4349 Ok(gate_guard)
4350 }
4351
4352 pub(crate) async fn current_runtime_stop_cleanup_in_progress(
4353 &self,
4354 session_id: &SessionId,
4355 driver: &SharedDriver,
4356 ) -> Result<bool, RuntimeDriverError> {
4357 let sessions = self.sessions.read().await;
4358 let entry = sessions
4359 .get(session_id)
4360 .ok_or(RuntimeDriverError::NotReady {
4361 state: RuntimeState::Destroyed,
4362 })?;
4363 if !Arc::ptr_eq(&entry.driver, driver) {
4364 return Err(RuntimeDriverError::NotReady {
4365 state: RuntimeState::Destroyed,
4366 });
4367 }
4368 let Some(coordinator) = entry.runtime_stop_cleanup_coordinator.as_ref() else {
4369 return Ok(false);
4370 };
4371 if coordinator.epoch_id != entry.epoch_id {
4372 return Err(RuntimeDriverError::Internal(format!(
4373 "stale runtime-stop cleanup coordinator epoch for session {session_id}"
4374 )));
4375 }
4376 Ok(coordinator.result_rx.borrow().is_none())
4377 }
4378
4379 async fn session_dsl_authority(
4380 &self,
4381 session_id: &SessionId,
4382 ) -> Result<Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>, String> {
4383 let sessions = self.sessions.read().await;
4384 sessions
4385 .get(session_id)
4386 .map(|entry| Arc::clone(&entry.dsl_authority))
4387 .ok_or_else(|| {
4388 RuntimeDriverError::NotReady {
4389 state: RuntimeState::Destroyed,
4390 }
4391 .to_string()
4392 })
4393 }
4394
4395 #[cfg(any(test, feature = "test-support"))]
4396 async fn session_handle_teardown_gate(
4397 &self,
4398 session_id: &SessionId,
4399 ) -> Result<Arc<crate::handles::HandleTeardownGate>, String> {
4400 let sessions = self.sessions.read().await;
4401 sessions
4402 .get(session_id)
4403 .map(|entry| Arc::clone(&entry.handle_teardown_gate))
4404 .ok_or_else(|| {
4405 RuntimeDriverError::NotReady {
4406 state: RuntimeState::Destroyed,
4407 }
4408 .to_string()
4409 })
4410 }
4411
4412 #[cfg(any(test, feature = "test-support"))]
4420 pub async fn test_install_session_peer_comms_handle_on_runtime(
4421 &self,
4422 session_id: &SessionId,
4423 runtime: &(dyn meerkat_core::handles::PeerCommsInstallTarget + '_),
4424 ) -> Result<(), String> {
4425 let dsl = self
4426 .session_dsl_authority(session_id)
4427 .await
4428 .map_err(|error| format!("session dsl authority unavailable: {error}"))?;
4429 let teardown_gate = self
4430 .session_handle_teardown_gate(session_id)
4431 .await
4432 .map_err(|error| format!("session handle teardown gate unavailable: {error}"))?;
4433 let handle = std::sync::Arc::new(
4434 crate::handles::HandleDslAuthority::from_shared_with_teardown_gate(dsl, teardown_gate),
4435 );
4436 crate::handles::RuntimePeerCommsHandle::install_generated_on(handle, runtime)
4437 }
4438
4439 fn preview_dsl_input_on_state(
4440 state: &dsl::MeerkatMachineState,
4441 input: dsl::MeerkatMachineInput,
4442 context: &str,
4443 ) -> Result<Vec<dsl::MeerkatMachineEffect>, String> {
4444 let mut preview = dsl::MeerkatMachineAuthority::recover_from_state(state.clone())
4445 .map_err(|err| dsl_authority::map_error(err, context))?;
4446 dsl::MeerkatMachineMutator::apply(&mut preview, input)
4447 .map(|transition| transition.into_effects())
4448 .map_err(|err| dsl_authority::map_error(err, context))
4449 }
4450
4451 async fn preview_session_dsl_input(
4452 &self,
4453 session_id: &SessionId,
4454 input: dsl::MeerkatMachineInput,
4455 context: &str,
4456 ) -> Result<Vec<dsl::MeerkatMachineEffect>, String> {
4457 let authority = self.session_dsl_authority(session_id).await?;
4458 let state = {
4459 let authority = authority
4460 .lock()
4461 .unwrap_or_else(std::sync::PoisonError::into_inner);
4462 authority.state().clone()
4463 };
4464 Self::preview_dsl_input_on_state(&state, input, context)
4465 }
4466
4467 async fn session_dsl_state(
4468 &self,
4469 session_id: &SessionId,
4470 ) -> Result<dsl::MeerkatMachineState, RuntimeControlPlaneError> {
4471 let authority = self
4472 .session_dsl_authority(session_id)
4473 .await
4474 .map_err(RuntimeControlPlaneError::Internal)?;
4475 let authority = authority
4476 .lock()
4477 .unwrap_or_else(std::sync::PoisonError::into_inner);
4478 Ok(authority.state().clone())
4479 }
4480
4481 async fn commit_session_dsl_transition(
4482 &self,
4483 session_id: &SessionId,
4484 staged: StagedSessionDslInput,
4485 context: &str,
4486 ) -> Result<(), String> {
4487 self.commit_session_dsl_transition_with_dispatch_failure(
4488 session_id,
4489 staged,
4490 context,
4491 CommittedEffectDispatchFailure::PreserveCommittedDslState,
4492 )
4493 .await
4494 }
4495
4496 async fn commit_session_dsl_transition_preserving_committed_state(
4497 &self,
4498 session_id: &SessionId,
4499 staged: StagedSessionDslInput,
4500 context: &str,
4501 ) -> Result<(), String> {
4502 self.commit_session_dsl_transition_with_dispatch_failure(
4503 session_id,
4504 staged,
4505 context,
4506 CommittedEffectDispatchFailure::PreserveCommittedDslState,
4507 )
4508 .await
4509 }
4510
4511 async fn commit_session_dsl_transition_with_dispatch_failure(
4512 &self,
4513 _session_id: &SessionId,
4514 staged: StagedSessionDslInput,
4515 context: &str,
4516 dispatch_failure: CommittedEffectDispatchFailure,
4517 ) -> Result<(), String> {
4518 if let Err(error) = self
4519 .dispatch_routed_signals_from_effects(&staged.effects)
4520 .await
4521 {
4522 let CommittedEffectDispatchFailure::PreserveCommittedDslState = dispatch_failure;
4523 return Err(format!(
4524 "DSL authority ({context}): committed effect dispatch failed: {error}"
4525 ));
4526 }
4527 Ok(())
4528 }
4529
4530 async fn dispatch_routed_signals_from_effects(
4531 &self,
4532 effects: &[dsl::MeerkatMachineEffect],
4533 ) -> Result<(), String> {
4534 let dispatcher = {
4535 self.composition_signal_dispatcher
4536 .read()
4537 .unwrap_or_else(std::sync::PoisonError::into_inner)
4538 .clone()
4539 };
4540 let Some(dispatcher) = dispatcher else {
4541 return Ok(());
4542 };
4543
4544 for effect in effects {
4545 if let Some(signal) = composition::lift_routed_signal(effect) {
4546 composition::dispatch_routed_signal(&dispatcher, signal).await?;
4547 }
4548 }
4549 Ok(())
4550 }
4551
4552 #[allow(clippy::too_many_arguments)]
4555 async fn dispatch_cancel_after_boundary_runtime_effect(
4556 &self,
4557 session_id: &SessionId,
4558 witness: RuntimeEffectDispatchAttachmentWitness,
4559 held_mutation_gate: crate::tokio::sync::OwnedMutexGuard<()>,
4560 boundary_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>>,
4561 member_authority: Option<RuntimeEffectDispatchMemberAuthority>,
4562 pending_dispatch: PendingBoundaryCancelDispatchGuard,
4563 expected_run_id: Option<&RunId>,
4564 projected_effect: crate::effect::ProjectedRuntimeEffect,
4565 dispatch_generation: u64,
4566 dispatch_lifecycle_phase: dsl::MeerkatPhase,
4567 context: &str,
4568 ) -> Result<crate::tokio::sync::OwnedMutexGuard<()>, RuntimeDriverError> {
4569 let cleanup_spawner = MachineCleanupTaskSpawner::acquire()?;
4570 let machine = self.clone();
4571 let session_id = session_id.clone();
4572 let expected_run_id = expected_run_id.cloned();
4573 let context = context.to_string();
4574 let completion = cleanup_spawner.spawn(async move {
4575 machine
4576 .dispatch_cancel_after_boundary_runtime_effect_owned(
4577 &session_id,
4578 witness,
4579 held_mutation_gate,
4580 boundary_handle,
4581 member_authority,
4582 pending_dispatch,
4583 expected_run_id.as_ref(),
4584 projected_effect,
4585 dispatch_generation,
4586 dispatch_lifecycle_phase,
4587 &context,
4588 )
4589 .await
4590 });
4591 completion.await.map_err(|error| {
4592 RuntimeDriverError::Internal(format!(
4593 "process-owned boundary-effect dispatch ended without a result: {error}"
4594 ))
4595 })?
4596 }
4597
4598 #[allow(clippy::too_many_arguments)]
4601 async fn dispatch_cancel_after_boundary_runtime_effect_owned(
4602 &self,
4603 session_id: &SessionId,
4604 witness: RuntimeEffectDispatchAttachmentWitness,
4605 held_mutation_gate: crate::tokio::sync::OwnedMutexGuard<()>,
4606 boundary_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>>,
4607 member_authority: Option<RuntimeEffectDispatchMemberAuthority>,
4608 mut pending_dispatch: PendingBoundaryCancelDispatchGuard,
4609 expected_run_id: Option<&RunId>,
4610 projected_effect: crate::effect::ProjectedRuntimeEffect,
4611 dispatch_generation: u64,
4612 dispatch_lifecycle_phase: dsl::MeerkatPhase,
4613 context: &str,
4614 ) -> Result<crate::tokio::sync::OwnedMutexGuard<()>, RuntimeDriverError> {
4615 drop(held_mutation_gate);
4620 let live_dispatch_result = self
4621 .dispatch_cancel_after_boundary_live_handle(
4622 boundary_handle,
4623 expected_run_id,
4624 &projected_effect,
4625 context,
4626 )
4627 .await;
4628
4629 let pre_reserve_state = Self::classify_dsl_boundary_cancel_dispatch(
4633 &witness.dsl_authority,
4634 dispatch_generation,
4635 &dispatch_lifecycle_phase,
4636 expected_run_id,
4637 );
4638 let effect_permit = if live_dispatch_result.is_ok()
4639 && pre_reserve_state == BoundaryCancelDispatchState::Current
4640 {
4641 Some(witness.effect_tx.clone().reserve_owned().await)
4642 } else {
4643 None
4644 };
4645
4646 let gate_guard = Arc::clone(&witness.mutation_gate).lock_owned().await;
4647 let current_effect_tx = {
4648 let sessions = self.sessions.read().await;
4649 match sessions.get(session_id) {
4650 None => Err(RuntimeDriverError::StaleAuthority {
4651 reason: format!(
4652 "{context}: runtime session disappeared during boundary callback"
4653 ),
4654 }),
4655 Some(entry)
4656 if !Arc::ptr_eq(&entry.mutation_gate, &witness.mutation_gate)
4657 || !Arc::ptr_eq(&entry.driver, &witness.driver)
4658 || !Arc::ptr_eq(&entry.dsl_authority, &witness.dsl_authority)
4659 || !entry.owns_runtime_loop_attachment(witness.attachment_id) =>
4660 {
4661 Err(RuntimeDriverError::StaleAuthority {
4662 reason: format!(
4663 "{context}: runtime attachment changed during boundary callback"
4664 ),
4665 })
4666 }
4667 Some(entry) => match entry.effect_sender() {
4668 None => Err(RuntimeDriverError::StaleAuthority {
4669 reason: format!(
4670 "{context}: runtime effect channel disappeared during boundary callback"
4671 ),
4672 }),
4673 Some(current_effect_tx)
4674 if !witness.effect_tx.same_channel(¤t_effect_tx) =>
4675 {
4676 Err(RuntimeDriverError::StaleAuthority {
4677 reason: format!(
4678 "{context}: runtime effect channel changed during boundary callback"
4679 ),
4680 })
4681 }
4682 Some(current_effect_tx) => Ok(current_effect_tx),
4683 },
4684 }
4685 };
4686 let current_effect_tx = current_effect_tx?;
4687
4688 if let Some(member_authority) = &member_authority {
4689 self.validate_member_effect_authority_lease_current(
4690 session_id,
4691 &member_authority.lease,
4692 member_authority.expected_member.as_ref(),
4693 )?;
4694 }
4695
4696 if let Err(error) = live_dispatch_result {
4702 return Err(error);
4703 }
4704
4705 match Self::classify_dsl_boundary_cancel_dispatch(
4710 &witness.dsl_authority,
4711 dispatch_generation,
4712 &dispatch_lifecycle_phase,
4713 expected_run_id,
4714 ) {
4715 BoundaryCancelDispatchState::ConsumedConverged => return Ok(gate_guard),
4716 BoundaryCancelDispatchState::SameGenerationOrphan
4717 | BoundaryCancelDispatchState::Superseded => {
4718 return Err(RuntimeDriverError::StaleAuthority {
4719 reason: format!(
4720 "{context}: boundary-cancel dispatch authority changed during callback"
4721 ),
4722 });
4723 }
4724 BoundaryCancelDispatchState::Current => {}
4725 }
4726
4727 let state = self
4728 .existing_session_runtime_state(session_id)
4729 .await
4730 .unwrap_or(RuntimeState::Destroyed);
4731 self.reject_unregistration_drain_ingress(session_id, state)
4732 .await?;
4733
4734 debug_assert!(witness.effect_tx.same_channel(¤t_effect_tx));
4735 let effect_permit = effect_permit
4736 .ok_or_else(|| RuntimeDriverError::StaleAuthority {
4737 reason: format!(
4738 "{context}: boundary-cancel dispatch became current after capacity reservation was skipped"
4739 ),
4740 })?
4741 .map_err(|_| RuntimeDriverError::NotReady {
4742 state: RuntimeState::Idle,
4743 })?;
4744 let _effect_tx = effect_permit.send(projected_effect.into_effect());
4745 pending_dispatch.disarm();
4746 Ok(gate_guard)
4747 }
4748
4749 async fn dispatch_accepted_ingress_boundary_work(
4754 &self,
4755 session_id: &SessionId,
4756 held_mutation_gate: crate::tokio::sync::OwnedMutexGuard<()>,
4757 cancel_plan: Option<RuntimeAcceptedBoundaryCancelPlan>,
4758 _completions: crate::meerkat_machine::driver::SharedCompletionRegistry,
4759 wake_tx: Option<mpsc::Sender<()>>,
4760 should_wake: bool,
4761 ) -> Result<crate::tokio::sync::OwnedMutexGuard<()>, RuntimeDriverError> {
4762 let cleanup_spawner = MachineCleanupTaskSpawner::acquire()?;
4763 let machine = self.clone();
4764 let session_id = session_id.clone();
4765 let completion = cleanup_spawner.spawn(async move {
4766 let mut gate_guard = held_mutation_gate;
4767 if let Some(cancel_plan) = cancel_plan {
4768 gate_guard = machine
4769 .dispatch_cancel_after_boundary_runtime_effect_owned(
4770 &session_id,
4771 cancel_plan.witness,
4772 gate_guard,
4773 cancel_plan.boundary_handle,
4774 None,
4775 cancel_plan.pending_dispatch,
4776 Some(&cancel_plan.expected_run_id),
4777 cancel_plan.projected_effect,
4778 cancel_plan.dispatch_generation,
4779 cancel_plan.dispatch_lifecycle_phase,
4780 "AcceptWithCompletion",
4781 )
4782 .await?;
4783 }
4784 if should_wake && let Some(wake_tx) = wake_tx {
4785 let _ = wake_tx.try_send(());
4786 }
4787 Ok(gate_guard)
4788 });
4789 completion.await.map_err(|error| {
4790 RuntimeDriverError::Internal(format!(
4791 "process-owned accepted-ingress boundary transaction ended without a result: {error}"
4792 ))
4793 })?
4794 }
4795
4796 async fn dispatch_cancel_after_boundary_live_handle(
4802 &self,
4803 boundary_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>>,
4804 expected_run_id: Option<&RunId>,
4805 projected_effect: &crate::effect::ProjectedRuntimeEffect,
4806 context: &str,
4807 ) -> Result<(), RuntimeDriverError> {
4808 let reason = projected_effect.reason().to_string();
4809 if let (Some(boundary_handle), Some(expected_run_id)) = (boundary_handle, expected_run_id) {
4814 boundary_handle
4815 .cancel_after_boundary(expected_run_id, reason)
4816 .await
4817 .map_err(|err| {
4818 RuntimeDriverError::Internal(format!(
4819 "{context}: failed to apply live boundary cancel: {err}"
4820 ))
4821 })?;
4822 }
4823 Ok(())
4824 }
4825
4826 async fn restore_session_dsl_state(
4827 &self,
4828 session_id: &SessionId,
4829 snapshot: dsl::MeerkatMachineAuthoritySnapshot,
4830 ) {
4831 if let Ok(authority) = self.session_dsl_authority(session_id).await {
4832 Self::restore_dsl_authority_snapshot(&authority, snapshot);
4833 }
4834 }
4835
4836 async fn restore_session_dsl_state_if_current(
4837 &self,
4838 session_id: &SessionId,
4839 expected_current: dsl::MeerkatMachineAuthoritySnapshot,
4840 restore: dsl::MeerkatMachineAuthoritySnapshot,
4841 ) -> bool {
4842 let Ok(authority) = self.session_dsl_authority(session_id).await else {
4843 return false;
4844 };
4845 Self::restore_dsl_authority_snapshot_if_current(&authority, expected_current, restore)
4846 }
4847
4848 fn classify_dsl_boundary_cancel_dispatch(
4849 authority: &Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
4850 dispatch_generation: u64,
4851 dispatch_lifecycle_phase: &dsl::MeerkatPhase,
4852 expected_run_id: Option<&RunId>,
4853 ) -> BoundaryCancelDispatchState {
4854 let authority = authority
4855 .lock()
4856 .unwrap_or_else(std::sync::PoisonError::into_inner);
4857 let state = authority.state();
4858 if state.boundary_cancel_dispatch_generation != dispatch_generation {
4859 return BoundaryCancelDispatchState::Superseded;
4860 }
4861 if !state.boundary_cancel_dispatch_pending {
4862 return BoundaryCancelDispatchState::ConsumedConverged;
4863 }
4864 let expected_dsl_run_id = expected_run_id.map(dsl::RunId::from_domain);
4865 if state.lifecycle_phase == *dispatch_lifecycle_phase
4866 && state.current_run_id.as_ref() == expected_dsl_run_id.as_ref()
4867 {
4868 BoundaryCancelDispatchState::Current
4869 } else {
4870 BoundaryCancelDispatchState::SameGenerationOrphan
4871 }
4872 }
4873
4874 fn abort_dsl_boundary_cancel_dispatch_if_current(
4881 authority: &Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
4882 dispatch_generation: u64,
4883 ) -> Result<bool, RuntimeDriverError> {
4884 let mut authority = authority
4885 .lock()
4886 .unwrap_or_else(std::sync::PoisonError::into_inner);
4887 if !authority.state().boundary_cancel_dispatch_pending
4888 || authority.state().boundary_cancel_dispatch_generation != dispatch_generation
4889 {
4890 return Ok(false);
4891 }
4892 dsl::MeerkatMachineMutator::apply(
4893 &mut *authority,
4894 dsl::MeerkatMachineInput::AbortCancelAfterBoundaryDispatch {
4895 dispatch_generation,
4896 },
4897 )
4898 .map(|_| true)
4899 .map_err(|err| {
4900 RuntimeDriverError::Internal(dsl_authority::map_error(
4901 err,
4902 "AbortCancelAfterBoundaryDispatch",
4903 ))
4904 })
4905 }
4906
4907 fn restore_dsl_authority_snapshot(
4908 authority: &Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
4909 snapshot: dsl::MeerkatMachineAuthoritySnapshot,
4910 ) {
4911 let mut authority = authority
4912 .lock()
4913 .unwrap_or_else(std::sync::PoisonError::into_inner);
4914 authority.restore_snapshot(snapshot);
4915 }
4916
4917 fn restore_dsl_authority_snapshot_if_current(
4918 authority: &Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
4919 expected_current: dsl::MeerkatMachineAuthoritySnapshot,
4920 restore: dsl::MeerkatMachineAuthoritySnapshot,
4921 ) -> bool {
4922 let mut authority = authority
4923 .lock()
4924 .unwrap_or_else(std::sync::PoisonError::into_inner);
4925 let current = authority.snapshot();
4926 if current.state() == expected_current.state() {
4927 authority.restore_snapshot(restore);
4928 true
4929 } else {
4930 false
4931 }
4932 }
4933}
4934
4935#[derive(Debug, Clone, Copy)]
4938pub struct MachineSessionControlAuthority {
4939 _private: (),
4940}
4941
4942#[cfg(feature = "live")]
4943struct LiveOpenAdmissionGeneratedAuthorityBridgeToken;
4944
4945#[cfg(feature = "live")]
4946struct LiveCloseResultGeneratedAuthorityBridgeToken;
4947
4948#[cfg(feature = "live")]
4949struct LiveChannelStatusResultGeneratedAuthorityBridgeToken;
4950
4951#[cfg(feature = "live")]
4952static LIVE_OPEN_ADMISSION_GENERATED_AUTHORITY_BRIDGE_TOKEN:
4953 LiveOpenAdmissionGeneratedAuthorityBridgeToken = LiveOpenAdmissionGeneratedAuthorityBridgeToken;
4954
4955#[cfg(feature = "live")]
4956static LIVE_CLOSE_RESULT_GENERATED_AUTHORITY_BRIDGE_TOKEN:
4957 LiveCloseResultGeneratedAuthorityBridgeToken = LiveCloseResultGeneratedAuthorityBridgeToken;
4958
4959#[cfg(feature = "live")]
4960static LIVE_CHANNEL_STATUS_RESULT_GENERATED_AUTHORITY_BRIDGE_TOKEN:
4961 LiveChannelStatusResultGeneratedAuthorityBridgeToken =
4962 LiveChannelStatusResultGeneratedAuthorityBridgeToken;
4963
4964#[cfg(feature = "live")]
4965fn live_open_admission_generated_authority_bridge_token()
4966-> &'static (dyn std::any::Any + Send + Sync) {
4967 &LIVE_OPEN_ADMISSION_GENERATED_AUTHORITY_BRIDGE_TOKEN
4968}
4969
4970#[cfg(feature = "live")]
4971fn live_close_result_generated_authority_bridge_token() -> &'static (dyn std::any::Any + Send + Sync)
4972{
4973 &LIVE_CLOSE_RESULT_GENERATED_AUTHORITY_BRIDGE_TOKEN
4974}
4975
4976#[cfg(feature = "live")]
4977fn live_channel_status_result_generated_authority_bridge_token()
4978-> &'static (dyn std::any::Any + Send + Sync) {
4979 &LIVE_CHANNEL_STATUS_RESULT_GENERATED_AUTHORITY_BRIDGE_TOKEN
4980}
4981
4982#[cfg(feature = "live")]
4983#[doc(hidden)]
4984#[allow(improper_ctypes_definitions, unsafe_code)]
4985#[unsafe(export_name = concat!(
4986 "__meerkat_runtime_generated_authority_bridge_token_is_valid_v1_live_open_admission_",
4987 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
4988))]
4989pub extern "Rust" fn live_open_admission_generated_authority_bridge_token_is_valid(
4990 token: &(dyn std::any::Any + Send + Sync),
4991) -> bool {
4992 token.is::<LiveOpenAdmissionGeneratedAuthorityBridgeToken>()
4993}
4994
4995#[cfg(feature = "live")]
4996#[doc(hidden)]
4997#[allow(improper_ctypes_definitions, unsafe_code)]
4998#[unsafe(export_name = concat!(
4999 "__meerkat_runtime_generated_authority_bridge_token_is_valid_v1_live_close_result_",
5000 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
5001))]
5002pub extern "Rust" fn live_close_result_generated_authority_bridge_token_is_valid(
5003 token: &(dyn std::any::Any + Send + Sync),
5004) -> bool {
5005 token.is::<LiveCloseResultGeneratedAuthorityBridgeToken>()
5006}
5007
5008#[cfg(feature = "live")]
5009#[doc(hidden)]
5010#[allow(improper_ctypes_definitions, unsafe_code)]
5011#[unsafe(export_name = concat!(
5012 "__meerkat_runtime_generated_authority_bridge_token_is_valid_v1_live_channel_status_result_",
5013 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
5014))]
5015pub extern "Rust" fn live_channel_status_result_generated_authority_bridge_token_is_valid(
5016 token: &(dyn std::any::Any + Send + Sync),
5017) -> bool {
5018 token.is::<LiveChannelStatusResultGeneratedAuthorityBridgeToken>()
5019}
5020
5021#[cfg(feature = "live")]
5022fn build_live_channel_open_authority(
5023 session_id: SessionId,
5024 channel_id: meerkat_live::LiveChannelId,
5025 sequence: u64,
5026) -> Result<meerkat_live::LiveChannelOpenAuthority, String> {
5027 #[allow(improper_ctypes_definitions, unsafe_code)]
5028 unsafe extern "Rust" {
5029 #[link_name = concat!(
5030 "__meerkat_live_runtime_generated_live_channel_open_authority_build_v1_",
5031 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
5032 )]
5033 fn live_generated_channel_open_authority_build(
5034 token: &'static (dyn std::any::Any + Send + Sync),
5035 session_id: SessionId,
5036 channel_id: meerkat_live::LiveChannelId,
5037 sequence: u64,
5038 ) -> Result<meerkat_live::LiveChannelOpenAuthority, String>;
5039 }
5040 #[allow(unsafe_code)]
5041 unsafe {
5042 live_generated_channel_open_authority_build(
5043 live_open_admission_generated_authority_bridge_token(),
5044 session_id,
5045 channel_id,
5046 sequence,
5047 )
5048 }
5049}
5050
5051#[cfg(feature = "live")]
5052fn build_live_channel_close_commit_authority(
5053 channel_id: String,
5054 close_sequence: u64,
5055) -> Result<meerkat_live::LiveChannelCloseCommitAuthority, String> {
5056 #[allow(improper_ctypes_definitions, unsafe_code)]
5057 unsafe extern "Rust" {
5058 #[link_name = concat!(
5059 "__meerkat_live_runtime_generated_live_channel_close_commit_authority_build_v1_",
5060 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
5061 )]
5062 fn live_generated_channel_close_commit_authority_build(
5063 token: &'static (dyn std::any::Any + Send + Sync),
5064 channel_id: String,
5065 close_sequence: u64,
5066 ) -> Result<meerkat_live::LiveChannelCloseCommitAuthority, String>;
5067 }
5068 #[allow(unsafe_code)]
5069 unsafe {
5070 live_generated_channel_close_commit_authority_build(
5071 live_close_result_generated_authority_bridge_token(),
5072 channel_id,
5073 close_sequence,
5074 )
5075 }
5076}
5077
5078#[cfg(feature = "live")]
5079fn build_live_channel_status_commit_authority(
5080 channel_id: String,
5081 status_observation_sequence: u64,
5082) -> Result<meerkat_live::LiveChannelStatusCommitAuthority, String> {
5083 #[allow(improper_ctypes_definitions, unsafe_code)]
5084 unsafe extern "Rust" {
5085 #[link_name = concat!(
5086 "__meerkat_live_runtime_generated_live_channel_status_commit_authority_build_v1_",
5087 env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
5088 )]
5089 fn live_generated_channel_status_commit_authority_build(
5090 token: &'static (dyn std::any::Any + Send + Sync),
5091 channel_id: String,
5092 status_observation_sequence: u64,
5093 ) -> Result<meerkat_live::LiveChannelStatusCommitAuthority, String>;
5094 }
5095 #[allow(unsafe_code)]
5096 unsafe {
5097 live_generated_channel_status_commit_authority_build(
5098 live_channel_status_result_generated_authority_bridge_token(),
5099 channel_id,
5100 status_observation_sequence,
5101 )
5102 }
5103}
5104
5105#[derive(Debug, Clone)]
5112#[cfg(feature = "live")]
5113pub struct LiveOpenAdmissionAuthority {
5114 session_id: SessionId,
5115 channel_id: meerkat_live::LiveChannelId,
5116 admitted: bool,
5117 rejection: Option<dsl::LiveOpenAdmissionRejection>,
5118 bound_llm_identity: Option<meerkat_core::SessionLlmIdentity>,
5119 sequence: u64,
5120 channel_open_authority: Option<meerkat_live::LiveChannelOpenAuthority>,
5121}
5122
5123#[cfg(feature = "live")]
5124impl LiveOpenAdmissionAuthority {
5125 pub(crate) fn from_generated_effect(
5126 session_id: SessionId,
5127 channel_id: meerkat_live::LiveChannelId,
5128 admitted: bool,
5129 rejection: Option<dsl::LiveOpenAdmissionRejection>,
5130 bound_llm_identity: Option<dsl::SessionLlmIdentity>,
5131 sequence: u64,
5132 ) -> Result<Self, String> {
5133 let bound_llm_identity = match (admitted, bound_llm_identity) {
5134 (true, Some(identity)) => Some(identity.try_into()?),
5135 (true, None) => {
5136 return Err(
5137 "generated live-open admission was admitted without bound LLM identity"
5138 .to_string(),
5139 );
5140 }
5141 (false, _) => None,
5142 };
5143 let channel_open_authority = if admitted {
5144 Some(build_live_channel_open_authority(
5145 session_id.clone(),
5146 channel_id.clone(),
5147 sequence,
5148 )?)
5149 } else {
5150 None
5151 };
5152 Ok(Self {
5153 session_id,
5154 channel_id,
5155 admitted,
5156 rejection,
5157 bound_llm_identity,
5158 sequence,
5159 channel_open_authority,
5160 })
5161 }
5162
5163 #[must_use]
5164 pub fn session_id(&self) -> &SessionId {
5165 &self.session_id
5166 }
5167
5168 #[must_use]
5169 pub fn channel_id(&self) -> &meerkat_live::LiveChannelId {
5170 &self.channel_id
5171 }
5172
5173 #[must_use]
5174 pub fn admitted(&self) -> bool {
5175 self.admitted
5176 }
5177
5178 #[must_use]
5179 pub fn rejection(&self) -> Option<dsl::LiveOpenAdmissionRejection> {
5180 self.rejection
5181 }
5182
5183 #[must_use]
5184 pub fn bound_llm_identity(&self) -> Option<&meerkat_core::SessionLlmIdentity> {
5185 self.bound_llm_identity.as_ref()
5186 }
5187
5188 #[must_use]
5189 pub fn sequence(&self) -> u64 {
5190 self.sequence
5191 }
5192
5193 #[must_use]
5194 pub fn channel_open_authority(&self) -> Option<&meerkat_live::LiveChannelOpenAuthority> {
5195 self.channel_open_authority.as_ref()
5196 }
5197}
5198
5199#[derive(Debug, Clone, PartialEq, Eq)]
5206#[cfg(feature = "live")]
5207pub struct LiveRefreshResultAuthority {
5208 pub status: dsl::LiveRefreshPublicStatus,
5209 pub sequence: u64,
5210 pub queue_acceptance_sequence: u64,
5211}
5212
5213#[derive(Debug, Clone)]
5218#[cfg(feature = "live")]
5219pub struct LiveCloseResultAuthority {
5220 pub status: dsl::LiveClosePublicStatus,
5221 pub sequence: u64,
5222 pub close_observation_sequence: u64,
5223 channel_close_commit_authority: Option<meerkat_live::LiveChannelCloseCommitAuthority>,
5224}
5225
5226#[cfg(feature = "live")]
5227impl LiveCloseResultAuthority {
5228 pub(crate) fn from_generated_effect(
5229 channel_id: String,
5230 status: dsl::LiveClosePublicStatus,
5231 sequence: u64,
5232 close_observation_sequence: u64,
5233 ) -> Result<Self, String> {
5234 let channel_close_commit_authority = match status {
5235 dsl::LiveClosePublicStatus::Closed => Some(build_live_channel_close_commit_authority(
5236 channel_id,
5237 close_observation_sequence,
5238 )?),
5239 };
5240 Ok(Self {
5241 status,
5242 sequence,
5243 close_observation_sequence,
5244 channel_close_commit_authority,
5245 })
5246 }
5247
5248 #[must_use]
5249 pub fn channel_close_commit_authority(
5250 &self,
5251 ) -> Option<&meerkat_live::LiveChannelCloseCommitAuthority> {
5252 self.channel_close_commit_authority.as_ref()
5253 }
5254
5255 #[must_use]
5256 pub fn into_channel_close_commit_authority(
5257 self,
5258 ) -> Option<meerkat_live::LiveChannelCloseCommitAuthority> {
5259 self.channel_close_commit_authority
5260 }
5261}
5262
5263#[derive(Debug, Clone, PartialEq, Eq)]
5269#[cfg(feature = "live")]
5270pub struct LiveCommandResultAuthority {
5271 pub command: dsl::LiveCommandPublicKind,
5272 pub sequence: u64,
5273 pub command_acceptance_sequence: u64,
5274}
5275
5276#[derive(Debug, Clone, PartialEq, Eq)]
5283#[cfg(feature = "live")]
5284pub struct LiveCommandRejectionAuthority {
5285 pub command: dsl::LiveCommandPublicKind,
5286 pub rejection: dsl::LiveCommandRejectionReason,
5287 pub public_error_class: dsl::LiveCommandRejectionPublicErrorClass,
5288 pub sequence: u64,
5289}
5290
5291#[derive(Debug, Clone, PartialEq, Eq)]
5298#[cfg(feature = "live")]
5299pub struct LiveChannelRequestRejectionAuthority {
5300 pub request: dsl::LiveChannelRequestPublicKind,
5301 pub rejection: dsl::LiveChannelRequestRejectionReason,
5302 pub public_error_class: dsl::LiveChannelRequestRejectionPublicErrorClass,
5303 pub sequence: u64,
5304}
5305
5306#[derive(Debug, Clone, PartialEq, Eq)]
5313#[cfg(feature = "live")]
5314pub struct LiveWebrtcTokenAuthority {
5315 pub token: String,
5316 pub expires_at_ms: u64,
5317 pub sequence: u64,
5318}
5319
5320#[derive(Debug, Clone, PartialEq, Eq)]
5326#[cfg(feature = "live")]
5327pub struct LiveWebrtcAnswerAdmissionAuthority {
5328 pub admitted: bool,
5329 pub rejection: Option<dsl::LiveWebrtcAnswerAdmissionRejection>,
5330 pub public_error_class: Option<dsl::LiveChannelRequestRejectionPublicErrorClass>,
5331 pub sequence: u64,
5332}
5333
5334#[derive(Debug, Clone, PartialEq, Eq)]
5341#[cfg(feature = "live")]
5342pub struct LiveWebrtcAnswerResultAuthority {
5343 pub status: dsl::LiveWebrtcAnswerPublicStatus,
5344 pub answered: bool,
5345 pub sequence: u64,
5346 pub answer_observation_sequence: u64,
5347}
5348
5349#[derive(Debug, Clone, PartialEq, Eq)]
5356#[cfg(feature = "live")]
5357pub struct LiveWebsocketTokenAuthority {
5358 pub token: String,
5359 pub expires_at_ms: u64,
5360 pub sequence: u64,
5361}
5362
5363#[derive(Debug, Clone, PartialEq, Eq)]
5369#[cfg(feature = "live")]
5370pub struct LiveWebsocketTokenAdmissionAuthority {
5371 pub admitted: bool,
5372 pub rejection: Option<dsl::LiveWebsocketTokenAdmissionRejection>,
5373 pub public_error_class: Option<dsl::LiveWebsocketTokenAdmissionPublicErrorClass>,
5374 pub sequence: u64,
5375}
5376
5377#[derive(Debug, Clone)]
5383#[cfg(feature = "live")]
5384pub struct LiveChannelStatusAuthority {
5385 pub status: dsl::LiveChannelPublicStatus,
5386 pub sequence: u64,
5387 pub status_observation_sequence: u64,
5388 pub degradation_reason: Option<dsl::LiveChannelDegradationReason>,
5389 pub degradation_detail: Option<String>,
5390 pub channel_status_commit_authority: Option<meerkat_live::LiveChannelStatusCommitAuthority>,
5391}
5392
5393#[cfg(feature = "live")]
5394impl LiveChannelStatusAuthority {
5395 pub(crate) fn from_generated_effect(
5396 channel_id: String,
5397 status: dsl::LiveChannelPublicStatus,
5398 sequence: u64,
5399 status_observation_sequence: u64,
5400 degradation_reason: Option<dsl::LiveChannelDegradationReason>,
5401 degradation_detail: Option<String>,
5402 ) -> Result<Self, String> {
5403 Ok(Self {
5404 status,
5405 sequence,
5406 status_observation_sequence,
5407 degradation_reason,
5408 degradation_detail,
5409 channel_status_commit_authority: Some(build_live_channel_status_commit_authority(
5410 channel_id,
5411 status_observation_sequence,
5412 )?),
5413 })
5414 }
5415
5416 #[must_use]
5417 pub fn channel_status_commit_authority(
5418 &self,
5419 ) -> Option<&meerkat_live::LiveChannelStatusCommitAuthority> {
5420 self.channel_status_commit_authority.as_ref()
5421 }
5422
5423 #[must_use]
5424 pub fn into_channel_status_commit_authority(
5425 self,
5426 ) -> Option<meerkat_live::LiveChannelStatusCommitAuthority> {
5427 self.channel_status_commit_authority
5428 }
5429}
5430
5431#[doc(hidden)]
5438pub struct MeerkatMachineShared {
5439 sessions: RwLock<HashMap<SessionId, RuntimeSessionEntry>>,
5441 registration_transaction_slots: StdRwLock<HashMap<SessionId, std::sync::Weak<Mutex<()>>>>,
5446 store: Option<Arc<dyn RuntimeStore>>,
5448 blob_store: Option<Arc<dyn BlobStore>>,
5450 llm_reconfigure_host: StdRwLock<Option<Arc<dyn SessionLlmReconfigureHost>>>,
5452 member_observation_host:
5457 StdRwLock<Option<Arc<dyn crate::member_observation::MemberObservationHost>>>,
5458 member_live_host: StdRwLock<Option<Arc<dyn crate::member_live::MemberLiveHost>>>,
5464 live_commands_served: std::sync::atomic::AtomicU64,
5468 member_incarnation_slots: StdRwLock<HashMap<SessionId, Arc<MemberResidencySlot>>>,
5479 auth_lease: StdRwLock<meerkat_core::handles::GeneratedAuthLeaseHandle>,
5482 #[cfg(not(target_arch = "wasm32"))]
5485 oauth_flows: StdRwLock<Arc<dyn meerkat_auth_core::oauth_flow::OAuthFlowAuthority>>,
5486 #[cfg(feature = "live")]
5490 live_unbound_rejection_authority: crate::driver::ephemeral::SharedIngressDslAuthority,
5491 session_claims: Arc<crate::handles::RuntimeSessionClaimRegistry>,
5498 composition_signal_dispatcher:
5502 StdRwLock<Option<composition::MeerkatCompositionSignalDispatcher>>,
5503 #[cfg(feature = "test-support")]
5507 test_stop_executor_after_ensure: std::sync::atomic::AtomicBool,
5508 #[cfg(feature = "test-support")]
5512 test_pause_executor_after_ensure: std::sync::atomic::AtomicBool,
5513 #[cfg(feature = "test-support")]
5514 test_executor_after_ensure_pause_reached: crate::tokio::sync::Notify,
5515 #[cfg(feature = "test-support")]
5516 test_executor_after_ensure_pause_release: crate::tokio::sync::Notify,
5517 #[cfg(test)]
5520 test_fenced_accept_after_lease: StdMutex<
5521 Option<(
5522 crate::tokio::sync::oneshot::Sender<()>,
5523 crate::tokio::sync::oneshot::Receiver<()>,
5524 )>,
5525 >,
5526 #[cfg(test)]
5531 test_registration_transaction_contention_probe:
5532 StdMutex<Option<(SessionId, crate::tokio::sync::oneshot::Sender<bool>)>>,
5533 #[cfg(test)]
5537 test_fail_post_stop_unregister_after_fence: StdMutex<Option<SessionId>>,
5538 #[cfg(any(test, feature = "test-support"))]
5542 test_runtime_loop_before_queue_authority: StdMutex<
5543 Option<(
5544 SessionId,
5545 crate::tokio::sync::oneshot::Sender<()>,
5546 crate::tokio::sync::oneshot::Receiver<()>,
5547 )>,
5548 >,
5549 #[cfg(test)]
5554 test_control_command_after_logical_lookup: StdMutex<
5555 Option<(
5556 ControlCommandLookupTestKind,
5557 SessionId,
5558 crate::tokio::sync::oneshot::Sender<()>,
5559 crate::tokio::sync::oneshot::Receiver<()>,
5560 )>,
5561 >,
5562}
5563
5564#[cfg(test)]
5565#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5566enum ControlCommandLookupTestKind {
5567 PublishEvent,
5568 Recycle,
5569 Recover,
5570 Destroy,
5571 Retire,
5572}
5573
5574pub struct MemberResidencyUpdate {
5581 adapter: Arc<MeerkatMachine>,
5582 session_id: SessionId,
5583 slot: Arc<MemberResidencySlot>,
5584 slot_guard: Option<crate::tokio::sync::OwnedMutexGuard<()>>,
5585 committed: bool,
5586}
5587
5588pub struct MemberResidencyPublication {
5596 _slot_guard: crate::tokio::sync::OwnedMutexGuard<()>,
5597}
5598
5599struct StagedMemberResidencyPublication {
5607 slot: Arc<MemberResidencySlot>,
5608 slot_guard: Option<crate::tokio::sync::OwnedMutexGuard<()>>,
5609}
5610
5611impl StagedMemberResidencyPublication {
5612 fn finalize(mut self) -> Result<MemberResidencyPublication, RuntimeDriverError> {
5613 let slot_guard = self.slot_guard.take().ok_or_else(|| {
5614 RuntimeDriverError::Internal(
5615 "staged member residency lost its exclusive slot guard".to_string(),
5616 )
5617 })?;
5618 Ok(MemberResidencyPublication {
5619 _slot_guard: slot_guard,
5620 })
5621 }
5622}
5623
5624impl Drop for StagedMemberResidencyPublication {
5625 fn drop(&mut self) {
5626 if self.slot_guard.is_none() {
5627 return;
5628 }
5629 *self
5630 .slot
5631 .state
5632 .write()
5633 .unwrap_or_else(std::sync::PoisonError::into_inner) =
5634 MemberResidencyState::VacantPlaced;
5635 }
5636}
5637
5638impl MemberResidencyUpdate {
5639 fn validate_for_retained_attachment(
5640 &self,
5641 adapter: &Arc<MeerkatMachine>,
5642 witness: &RuntimeExecutorAttachmentWitness,
5643 incarnation: &meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation,
5644 ) -> Result<(), RuntimeDriverError> {
5645 if !Arc::ptr_eq(&self.adapter, adapter) {
5646 return Err(RuntimeDriverError::StaleAuthority {
5647 reason: "member residency update belongs to another machine".to_string(),
5648 });
5649 }
5650 if &self.session_id != witness.session_id() {
5651 return Err(RuntimeDriverError::StaleAuthority {
5652 reason: format!(
5653 "member residency update for session {} cannot publish attachment for {}",
5654 self.session_id,
5655 witness.session_id()
5656 ),
5657 });
5658 }
5659 if self.slot_guard.is_none() {
5660 return Err(RuntimeDriverError::Internal(
5661 "member residency update lost its publication guard".to_string(),
5662 ));
5663 }
5664 self.adapter
5665 .validate_member_incarnation_registration(&self.session_id, incarnation)
5666 }
5667
5668 fn stage_under_retained_mutation_gate(
5669 mut self,
5670 incarnation: meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation,
5671 tracked_turn_journal: Option<Arc<dyn crate::member_observation::TrackedTurnJournal>>,
5672 session_mutation_gate: Arc<crate::tokio::sync::Mutex<()>>,
5673 ) -> Result<StagedMemberResidencyPublication, RuntimeDriverError> {
5674 let slot_guard = self.slot_guard.take().ok_or_else(|| {
5675 RuntimeDriverError::Internal(
5676 "member residency update lost its publication guard".to_string(),
5677 )
5678 })?;
5679 *self
5680 .slot
5681 .state
5682 .write()
5683 .unwrap_or_else(std::sync::PoisonError::into_inner) =
5684 MemberResidencyState::Placed(MemberIncarnationRegistration {
5685 incarnation,
5686 session_mutation_gate,
5687 tracked_turn_journal,
5688 });
5689 self.committed = true;
5690 Ok(StagedMemberResidencyPublication {
5691 slot: Arc::clone(&self.slot),
5692 slot_guard: Some(slot_guard),
5693 })
5694 }
5695
5696 pub async fn commit(
5699 self,
5700 incarnation: meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation,
5701 tracked_turn_journal: Option<Arc<dyn crate::member_observation::TrackedTurnJournal>>,
5702 ) -> Result<MemberResidencyPublication, RuntimeDriverError> {
5703 self.adapter
5704 .validate_member_incarnation_registration(&self.session_id, &incarnation)?;
5705 let session_mutation_gate = self
5706 .adapter
5707 .lock_current_session_mutation_gate(&self.session_id)
5708 .await
5709 .ok_or(RuntimeDriverError::NotReady {
5710 state: RuntimeState::Destroyed,
5711 })?;
5712 let gate = {
5713 let sessions = self.adapter.sessions.read().await;
5714 Arc::clone(
5715 &sessions
5716 .get(&self.session_id)
5717 .ok_or(RuntimeDriverError::NotReady {
5718 state: RuntimeState::Destroyed,
5719 })?
5720 .mutation_gate,
5721 )
5722 };
5723 let publication = self
5724 .stage_under_retained_mutation_gate(incarnation, tracked_turn_journal, gate)
5725 .and_then(StagedMemberResidencyPublication::finalize);
5726 drop(session_mutation_gate);
5727 publication
5728 }
5729
5730 pub fn vacate(mut self) -> Result<MemberResidencyPublication, RuntimeDriverError> {
5733 let slot_guard = self.slot_guard.take().ok_or_else(|| {
5734 RuntimeDriverError::Internal(
5735 "member residency update lost its vacancy publication guard".to_string(),
5736 )
5737 })?;
5738 *self
5739 .slot
5740 .state
5741 .write()
5742 .unwrap_or_else(std::sync::PoisonError::into_inner) =
5743 MemberResidencyState::VacantPlaced;
5744 self.committed = true;
5745 Ok(MemberResidencyPublication {
5746 _slot_guard: slot_guard,
5747 })
5748 }
5749}
5750
5751impl Drop for MemberResidencyUpdate {
5752 fn drop(&mut self) {
5753 if self.committed {
5754 return;
5755 }
5756 *self
5757 .slot
5758 .state
5759 .write()
5760 .unwrap_or_else(std::sync::PoisonError::into_inner) =
5761 MemberResidencyState::VacantPlaced;
5762 }
5763}
5764
5765#[derive(Clone)]
5772pub struct MeerkatMachine {
5773 shared: Arc<MeerkatMachineShared>,
5774}
5775
5776impl std::ops::Deref for MeerkatMachine {
5777 type Target = MeerkatMachineShared;
5778
5779 fn deref(&self) -> &Self::Target {
5780 self.shared.as_ref()
5781 }
5782}
5783
5784impl MeerkatMachine {
5785 fn session_registration_transaction_slot(&self, session_id: &SessionId) -> Arc<Mutex<()>> {
5795 let mut slots = self
5796 .registration_transaction_slots
5797 .write()
5798 .unwrap_or_else(std::sync::PoisonError::into_inner);
5799 if let Some(slot) = slots.get(session_id).and_then(std::sync::Weak::upgrade) {
5800 return slot;
5801 }
5802 slots.retain(|_, slot| slot.upgrade().is_some());
5803 let slot = Arc::new(Mutex::new(()));
5804 slots.insert(session_id.clone(), Arc::downgrade(&slot));
5805 slot
5806 }
5807
5808 async fn lock_session_registration_transaction(
5809 &self,
5810 session_id: &SessionId,
5811 ) -> crate::tokio::sync::OwnedMutexGuard<()> {
5812 let slot = self.session_registration_transaction_slot(session_id);
5813 #[cfg(test)]
5814 let contention_probe = {
5815 let mut probe = self
5816 .test_registration_transaction_contention_probe
5817 .lock()
5818 .unwrap_or_else(std::sync::PoisonError::into_inner);
5819 if probe
5820 .as_ref()
5821 .is_some_and(|(probe_session_id, _)| probe_session_id == session_id)
5822 {
5823 probe.take().map(|(_, sender)| sender)
5824 } else {
5825 None
5826 }
5827 };
5828 #[cfg(test)]
5829 if let Some(contention_probe) = contention_probe {
5830 return match Arc::clone(&slot).try_lock_owned() {
5831 Ok(guard) => {
5832 let _ = contention_probe.send(false);
5833 guard
5834 }
5835 Err(_) => {
5836 let _ = contention_probe.send(true);
5837 slot.lock_owned().await
5838 }
5839 };
5840 }
5841 slot.lock_owned().await
5842 }
5843
5844 #[cfg(test)]
5845 pub(crate) fn probe_next_session_registration_transaction_contention_for_test(
5846 &self,
5847 session_id: &SessionId,
5848 ) -> crate::tokio::sync::oneshot::Receiver<bool> {
5849 let (sender, receiver) = crate::tokio::sync::oneshot::channel();
5850 *self
5851 .test_registration_transaction_contention_probe
5852 .lock()
5853 .unwrap_or_else(std::sync::PoisonError::into_inner) =
5854 Some((session_id.clone(), sender));
5855 receiver
5856 }
5857
5858 pub async fn begin_member_residency_update(
5861 self: &Arc<Self>,
5862 session_id: SessionId,
5863 ) -> MemberResidencyUpdate {
5864 let slot = self.member_incarnation_slot(&session_id);
5865 let slot_guard = Arc::clone(&slot.gate).lock_owned().await;
5866 *slot
5867 .state
5868 .write()
5869 .unwrap_or_else(std::sync::PoisonError::into_inner) =
5870 MemberResidencyState::VacantPlaced;
5871 MemberResidencyUpdate {
5872 adapter: Arc::clone(self),
5873 session_id,
5874 slot,
5875 slot_guard: Some(slot_guard),
5876 committed: false,
5877 }
5878 }
5879
5880 #[must_use]
5883 pub fn session_control_authority(&self) -> MachineSessionControlAuthority {
5884 MachineSessionControlAuthority { _private: () }
5885 }
5886
5887 pub fn set_member_observation_host(
5892 &self,
5893 host: Arc<dyn crate::member_observation::MemberObservationHost>,
5894 ) {
5895 *self
5896 .member_observation_host
5897 .write()
5898 .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(host);
5899 }
5900
5901 pub(crate) fn member_observation_host(
5903 &self,
5904 ) -> Option<Arc<dyn crate::member_observation::MemberObservationHost>> {
5905 self.member_observation_host
5906 .read()
5907 .unwrap_or_else(std::sync::PoisonError::into_inner)
5908 .clone()
5909 }
5910
5911 pub fn set_member_live_host(&self, host: Arc<dyn crate::member_live::MemberLiveHost>) {
5916 *self
5917 .member_live_host
5918 .write()
5919 .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(host);
5920 }
5921
5922 pub(crate) fn member_live_host(&self) -> Option<Arc<dyn crate::member_live::MemberLiveHost>> {
5924 self.member_live_host
5925 .read()
5926 .unwrap_or_else(std::sync::PoisonError::into_inner)
5927 .clone()
5928 }
5929
5930 #[cfg(feature = "live")]
5934 pub async fn acquire_live_open_lifecycle_lease(
5935 &self,
5936 session_id: &meerkat_core::types::SessionId,
5937 ) -> Result<crate::member_live::MemberLiveLifecycleLease, RuntimeDriverError> {
5938 let (gate, guard) = self
5939 .lock_current_session_live_lifecycle_gate(session_id)
5940 .await
5941 .ok_or(RuntimeDriverError::NotReady {
5942 state: RuntimeState::Destroyed,
5943 })?;
5944 Ok(crate::member_live::MemberLiveLifecycleLease::new(
5945 session_id.clone(),
5946 gate,
5947 guard,
5948 ))
5949 }
5950
5951 #[cfg(feature = "live")]
5956 pub async fn acquire_member_live_disposal_lease(
5957 &self,
5958 session_id: &meerkat_core::types::SessionId,
5959 ) -> Result<crate::member_live::MemberLiveLifecycleLease, crate::member_live::MemberLiveError>
5960 {
5961 let (gate, guard) = self
5962 .lock_current_session_live_lifecycle_gate(session_id)
5963 .await
5964 .ok_or_else(|| crate::member_live::MemberLiveError::Unavailable {
5965 reason: format!(
5966 "member-live lifecycle gate is unavailable for session {session_id}"
5967 ),
5968 })?;
5969 let lease =
5970 crate::member_live::MemberLiveLifecycleLease::new(session_id.clone(), gate, guard);
5971
5972 self.prove_member_live_absence_while_lease_held(session_id, &lease)
5973 .await?;
5974 Ok(lease)
5975 }
5976
5977 #[cfg(feature = "live")]
5983 async fn prove_member_live_absence_while_lease_held(
5984 &self,
5985 session_id: &meerkat_core::types::SessionId,
5986 lease: &crate::member_live::MemberLiveLifecycleLease,
5987 ) -> Result<(), crate::member_live::MemberLiveError> {
5988 if lease.session_id() != session_id {
5989 return Err(crate::member_live::MemberLiveError::Internal {
5990 reason: format!(
5991 "member-live lifecycle lease for session {} cannot dispose session {session_id}",
5992 lease.session_id()
5993 ),
5994 });
5995 }
5996 let lease_is_current = {
5997 let sessions = self.sessions.read().await;
5998 sessions
5999 .get(session_id)
6000 .is_some_and(|entry| lease.matches_gate(&entry.live_lifecycle_gate))
6001 };
6002 if !lease_is_current {
6003 return Err(crate::member_live::MemberLiveError::Unavailable {
6004 reason: format!("member-live lifecycle lease is stale for session {session_id}"),
6005 });
6006 }
6007
6008 let Some(channel_id) = self.live_active_channel_for_session(session_id).await else {
6009 return Ok(());
6010 };
6011 let Some(host) = self.member_live_host() else {
6012 return Err(crate::member_live::MemberLiveError::Unavailable {
6013 reason: format!(
6014 "session {session_id} still owns live channel '{channel_id}', but no transport-neutral live cleanup host is composed"
6015 ),
6016 });
6017 };
6018 let timeout = crate::member_live::MEMBER_LIVE_DISPOSAL_CEILING;
6019 match crate::tokio::time::timeout(timeout, host.close(session_id, channel_id.as_str()))
6020 .await
6021 {
6022 Ok(Ok(_)) => {}
6023 Ok(Err(error)) => return Err(error),
6024 Err(_) => {
6025 return Err(crate::member_live::MemberLiveError::Unavailable {
6026 reason: format!(
6027 "member-live disposal close for channel '{channel_id}' timed out after {}ms for session {session_id}",
6028 timeout.as_millis()
6029 ),
6030 });
6031 }
6032 }
6033 if let Some(still_active) = self.live_active_channel_for_session(session_id).await {
6034 return Err(crate::member_live::MemberLiveError::Internal {
6035 reason: format!(
6036 "member-live close returned success but machine channel '{still_active}' remains active for session {session_id}"
6037 ),
6038 });
6039 }
6040 Ok(())
6041 }
6042
6043 #[cfg(feature = "live")]
6056 pub async fn close_member_live_channel_for_disposal(
6057 &self,
6058 session_id: &meerkat_core::types::SessionId,
6059 ) -> Result<(), crate::member_live::MemberLiveError> {
6060 let _lease = self.acquire_member_live_disposal_lease(session_id).await?;
6061 Ok(())
6062 }
6063
6064 pub(crate) fn note_live_command_served(&self) {
6068 self.live_commands_served
6069 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6070 }
6071
6072 pub fn live_commands_served(&self) -> u64 {
6074 self.live_commands_served
6075 .load(std::sync::atomic::Ordering::Relaxed)
6076 }
6077
6078 pub fn tracked_turn_journal(
6082 &self,
6083 session_id: &SessionId,
6084 ) -> Option<Arc<dyn crate::member_observation::TrackedTurnJournal>> {
6085 let slot = self
6086 .member_incarnation_slots
6087 .read()
6088 .unwrap_or_else(std::sync::PoisonError::into_inner)
6089 .get(session_id)
6090 .cloned()?;
6091 let state = slot
6092 .state
6093 .read()
6094 .unwrap_or_else(std::sync::PoisonError::into_inner);
6095 match &*state {
6096 MemberResidencyState::Placed(registration) => registration.tracked_turn_journal.clone(),
6097 MemberResidencyState::PeerOnly | MemberResidencyState::VacantPlaced => None,
6098 }
6099 }
6100
6101 fn validate_member_incarnation_registration(
6102 &self,
6103 session_id: &SessionId,
6104 incarnation: &meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation,
6105 ) -> Result<(), RuntimeDriverError> {
6106 if incarnation.mob_id.is_empty()
6107 || incarnation.agent_identity.is_empty()
6108 || incarnation.host_id.is_empty()
6109 || incarnation.member_session_id != session_id.to_string()
6110 || incarnation.binding_generation == 0
6111 || incarnation.fence_token == 0
6112 {
6113 return Err(RuntimeDriverError::ValidationFailed {
6114 reason: format!(
6115 "member incarnation registration requires complete identities plus nonzero host-binding generation and fence for session '{session_id}': {incarnation:?}"
6116 ),
6117 });
6118 }
6119 Ok(())
6120 }
6121
6122 pub fn member_incarnation(
6127 &self,
6128 session_id: &SessionId,
6129 ) -> Option<meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation> {
6130 let slot = self
6131 .member_incarnation_slots
6132 .read()
6133 .unwrap_or_else(std::sync::PoisonError::into_inner)
6134 .get(session_id)
6135 .cloned()?;
6136 let state = slot
6137 .state
6138 .read()
6139 .unwrap_or_else(std::sync::PoisonError::into_inner);
6140 match &*state {
6141 MemberResidencyState::Placed(registration) => Some(registration.incarnation.clone()),
6142 MemberResidencyState::PeerOnly | MemberResidencyState::VacantPlaced => None,
6143 }
6144 }
6145
6146 #[cfg(test)]
6147 pub(crate) fn test_member_residency_is_vacant(&self, session_id: &SessionId) -> bool {
6148 self.member_incarnation_slots
6149 .read()
6150 .unwrap_or_else(std::sync::PoisonError::into_inner)
6151 .get(session_id)
6152 .is_some_and(|slot| {
6153 matches!(
6154 &*slot
6155 .state
6156 .read()
6157 .unwrap_or_else(std::sync::PoisonError::into_inner),
6158 MemberResidencyState::VacantPlaced
6159 )
6160 })
6161 }
6162
6163 fn member_incarnation_slot(&self, session_id: &SessionId) -> Arc<MemberResidencySlot> {
6164 if let Some(slot) = self
6165 .member_incarnation_slots
6166 .read()
6167 .unwrap_or_else(std::sync::PoisonError::into_inner)
6168 .get(session_id)
6169 .cloned()
6170 {
6171 return slot;
6172 }
6173 Arc::clone(
6174 self.member_incarnation_slots
6175 .write()
6176 .unwrap_or_else(std::sync::PoisonError::into_inner)
6177 .entry(session_id.clone())
6178 .or_insert_with(|| Arc::new(MemberResidencySlot::peer_only())),
6179 )
6180 }
6181
6182 pub(crate) async fn lock_member_effect_authority(
6188 &self,
6189 session_id: &SessionId,
6190 expected: &meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation,
6191 ) -> Result<MemberEffectAuthorityGuard, RuntimeDriverError> {
6192 self.lock_optional_member_effect_authority(session_id, Some(expected))
6193 .await
6194 }
6195
6196 pub(crate) async fn lock_optional_member_effect_authority(
6201 &self,
6202 session_id: &SessionId,
6203 expected: Option<&meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation>,
6204 ) -> Result<MemberEffectAuthorityGuard, RuntimeDriverError> {
6205 let lease = self
6206 .acquire_member_effect_authority_lease(session_id, expected)
6207 .await?;
6208 let MemberEffectAuthorityLease {
6209 slot_guard,
6210 session_mutation_gate,
6211 ..
6212 } = lease;
6213 let session_guard = Arc::clone(&session_mutation_gate).lock_owned().await;
6214 {
6215 let sessions = self.sessions.read().await;
6216 let Some(entry) = sessions.get(session_id) else {
6217 return Err(RuntimeDriverError::StaleAuthority {
6218 reason: format!(
6219 "member effect expected incarnation {expected:?}; runtime session is absent"
6220 ),
6221 });
6222 };
6223 if !Arc::ptr_eq(&entry.mutation_gate, &session_mutation_gate) {
6224 return Err(RuntimeDriverError::StaleAuthority {
6225 reason: format!(
6226 "member effect expected incarnation {expected:?}; runtime session was replaced"
6227 ),
6228 });
6229 }
6230 }
6231 let state = self
6232 .existing_session_runtime_state(session_id)
6233 .await
6234 .unwrap_or(RuntimeState::Destroyed);
6235 self.reject_unregistration_drain_ingress(session_id, state)
6236 .await?;
6237 Ok(MemberEffectAuthorityGuard {
6238 _slot_guard: slot_guard,
6239 _session_guard: session_guard,
6240 })
6241 }
6242
6243 async fn acquire_member_effect_authority_lease(
6244 &self,
6245 session_id: &SessionId,
6246 expected: Option<&meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation>,
6247 ) -> Result<MemberEffectAuthorityLease, RuntimeDriverError> {
6248 let slot = self.member_incarnation_slot(session_id);
6249 let slot_guard = Arc::clone(&slot.gate).lock_owned().await;
6250 let registered_gate = {
6251 let state = slot
6252 .state
6253 .read()
6254 .unwrap_or_else(std::sync::PoisonError::into_inner);
6255 match (expected, &*state) {
6256 (Some(expected), MemberResidencyState::Placed(registration))
6257 if ®istration.incarnation == expected =>
6258 {
6259 Some(Arc::clone(®istration.session_mutation_gate))
6260 }
6261 (Some(expected), MemberResidencyState::Placed(registration)) => {
6262 return Err(RuntimeDriverError::StaleAuthority {
6263 reason: format!(
6264 "member effect expected incarnation {expected:?}; current is {:?}",
6265 registration.incarnation
6266 ),
6267 });
6268 }
6269 (
6270 Some(expected),
6271 MemberResidencyState::PeerOnly | MemberResidencyState::VacantPlaced,
6272 ) => {
6273 return Err(RuntimeDriverError::StaleAuthority {
6274 reason: format!(
6275 "member effect expected incarnation {expected:?}; current residency is absent"
6276 ),
6277 });
6278 }
6279 (None, MemberResidencyState::Placed(registration)) => {
6280 return Err(RuntimeDriverError::StaleAuthority {
6281 reason: format!(
6282 "peer-only member effect expected no host residency; current is {:?}",
6283 registration.incarnation
6284 ),
6285 });
6286 }
6287 (None, MemberResidencyState::VacantPlaced) => {
6288 return Err(RuntimeDriverError::StaleAuthority {
6289 reason: "peer-only member effect cannot address a vacant placed residency"
6290 .to_string(),
6291 });
6292 }
6293 (None, MemberResidencyState::PeerOnly) => None,
6294 }
6295 };
6296 let registered_gate = match registered_gate {
6297 Some(gate) => gate,
6298 None => self.session_mutation_gate(session_id).await.ok_or(
6299 RuntimeDriverError::NotReady {
6300 state: RuntimeState::Destroyed,
6301 },
6302 )?,
6303 };
6304 {
6305 let sessions = self.sessions.read().await;
6306 let Some(entry) = sessions.get(session_id) else {
6307 return Err(RuntimeDriverError::StaleAuthority {
6308 reason: format!(
6309 "member effect expected incarnation {expected:?}; runtime session is absent"
6310 ),
6311 });
6312 };
6313 if !Arc::ptr_eq(&entry.mutation_gate, ®istered_gate) {
6314 return Err(RuntimeDriverError::StaleAuthority {
6315 reason: format!(
6316 "member effect expected incarnation {expected:?}; runtime session was replaced"
6317 ),
6318 });
6319 }
6320 }
6321 Ok(MemberEffectAuthorityLease {
6322 slot,
6323 slot_guard,
6324 session_mutation_gate: registered_gate,
6325 })
6326 }
6327
6328 fn validate_member_effect_authority_lease_current(
6332 &self,
6333 session_id: &SessionId,
6334 lease: &MemberEffectAuthorityLease,
6335 expected: Option<&meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation>,
6336 ) -> Result<(), RuntimeDriverError> {
6337 let slot_is_current = self
6338 .member_incarnation_slots
6339 .read()
6340 .unwrap_or_else(std::sync::PoisonError::into_inner)
6341 .get(session_id)
6342 .is_some_and(|slot| Arc::ptr_eq(slot, &lease.slot));
6343 if !slot_is_current {
6344 return Err(RuntimeDriverError::StaleAuthority {
6345 reason: "member effect residency slot was replaced".to_string(),
6346 });
6347 }
6348
6349 let state = lease
6350 .slot
6351 .state
6352 .read()
6353 .unwrap_or_else(std::sync::PoisonError::into_inner);
6354 match (expected, &*state) {
6355 (Some(expected), MemberResidencyState::Placed(registration))
6356 if ®istration.incarnation == expected
6357 && Arc::ptr_eq(
6358 ®istration.session_mutation_gate,
6359 &lease.session_mutation_gate,
6360 ) =>
6361 {
6362 Ok(())
6363 }
6364 (Some(expected), MemberResidencyState::Placed(registration)) => {
6365 Err(RuntimeDriverError::StaleAuthority {
6366 reason: format!(
6367 "member effect expected incarnation {expected:?}; current is {:?}",
6368 registration.incarnation
6369 ),
6370 })
6371 }
6372 (
6373 Some(expected),
6374 MemberResidencyState::PeerOnly | MemberResidencyState::VacantPlaced,
6375 ) => Err(RuntimeDriverError::StaleAuthority {
6376 reason: format!(
6377 "member effect expected incarnation {expected:?}; current residency is absent"
6378 ),
6379 }),
6380 (None, MemberResidencyState::PeerOnly) => Ok(()),
6381 (None, MemberResidencyState::Placed(registration)) => {
6382 Err(RuntimeDriverError::StaleAuthority {
6383 reason: format!(
6384 "peer-only member effect expected no host residency; current is {:?}",
6385 registration.incarnation
6386 ),
6387 })
6388 }
6389 (None, MemberResidencyState::VacantPlaced) => Err(RuntimeDriverError::StaleAuthority {
6390 reason: "peer-only member effect cannot address a vacant placed residency"
6391 .to_string(),
6392 }),
6393 }
6394 }
6395
6396 #[must_use]
6401 pub fn shares_runtime_persistence_with(&self, other: &Self) -> bool {
6402 match (&self.store, &other.store) {
6403 (None, None) => true,
6404 (Some(a), Some(b)) => runtime_stores_share_authority(a, b),
6405 _ => false,
6406 }
6407 }
6408
6409 #[must_use]
6412 pub fn shares_runtime_store_authority(&self, store: &Arc<dyn RuntimeStore>) -> bool {
6413 self.store
6414 .as_ref()
6415 .is_some_and(|machine_store| runtime_stores_share_authority(machine_store, store))
6416 }
6417
6418 #[must_use]
6420 pub fn has_runtime_persistence(&self) -> bool {
6421 self.store.is_some()
6422 }
6423
6424 fn normalize_destroyed_error(err: RuntimeDriverError) -> RuntimeDriverError {
6425 match err {
6426 RuntimeDriverError::NotReady {
6427 state: RuntimeState::Destroyed,
6428 } => RuntimeDriverError::Destroyed,
6429 other => other,
6430 }
6431 }
6432
6433 pub fn ephemeral() -> Self {
6435 let auth_lease = Arc::new(crate::handles::RuntimeAuthLeaseHandle::new());
6436 #[cfg(not(target_arch = "wasm32"))]
6437 let oauth_flows = Arc::new(crate::handles::RuntimeOAuthFlowHandle::new_with_auth_lease(
6438 std::time::Duration::from_secs(10 * 60),
6439 Arc::clone(&auth_lease),
6440 ));
6441 let auth_lease = generated_runtime_auth_lease_handle(auth_lease);
6442 Self {
6443 shared: Arc::new(MeerkatMachineShared {
6444 sessions: RwLock::new(HashMap::new()),
6445 registration_transaction_slots: StdRwLock::new(HashMap::new()),
6446 store: None,
6447 blob_store: None,
6448 llm_reconfigure_host: StdRwLock::new(None),
6449 member_observation_host: StdRwLock::new(None),
6450 member_live_host: StdRwLock::new(None),
6451 live_commands_served: std::sync::atomic::AtomicU64::new(0),
6452 member_incarnation_slots: StdRwLock::new(HashMap::new()),
6453 auth_lease: StdRwLock::new(auth_lease),
6454 #[cfg(not(target_arch = "wasm32"))]
6455 oauth_flows: StdRwLock::new(oauth_flows),
6456 #[cfg(feature = "live")]
6457 live_unbound_rejection_authority: live_unbound_rejection_authority(),
6458 session_claims: Arc::new(crate::handles::RuntimeSessionClaimRegistry::new()),
6459 composition_signal_dispatcher: StdRwLock::new(None),
6460 #[cfg(feature = "test-support")]
6461 test_stop_executor_after_ensure: std::sync::atomic::AtomicBool::new(false),
6462 #[cfg(feature = "test-support")]
6463 test_pause_executor_after_ensure: std::sync::atomic::AtomicBool::new(false),
6464 #[cfg(feature = "test-support")]
6465 test_executor_after_ensure_pause_reached: crate::tokio::sync::Notify::new(),
6466 #[cfg(feature = "test-support")]
6467 test_executor_after_ensure_pause_release: crate::tokio::sync::Notify::new(),
6468 #[cfg(test)]
6469 test_fenced_accept_after_lease: StdMutex::new(None),
6470 #[cfg(test)]
6471 test_registration_transaction_contention_probe: StdMutex::new(None),
6472 #[cfg(test)]
6473 test_fail_post_stop_unregister_after_fence: StdMutex::new(None),
6474 #[cfg(any(test, feature = "test-support"))]
6475 test_runtime_loop_before_queue_authority: StdMutex::new(None),
6476 #[cfg(test)]
6477 test_control_command_after_logical_lookup: StdMutex::new(None),
6478 }),
6479 }
6480 }
6481
6482 pub fn persistent(store: Arc<dyn RuntimeStore>, blob_store: Arc<dyn BlobStore>) -> Self {
6489 #[cfg(not(target_arch = "wasm32"))]
6490 let (auth_lease, oauth_flows) = {
6491 let authorities = persistent_auth_authorities(&store);
6492 (
6493 Arc::clone(&authorities.auth_lease),
6494 Arc::clone(&authorities.oauth_flows),
6495 )
6496 };
6497 #[cfg(target_arch = "wasm32")]
6498 let auth_lease = Arc::new(crate::handles::RuntimeAuthLeaseHandle::new());
6499 let auth_lease = generated_runtime_auth_lease_handle(auth_lease);
6500 Self {
6501 shared: Arc::new(MeerkatMachineShared {
6502 sessions: RwLock::new(HashMap::new()),
6503 registration_transaction_slots: StdRwLock::new(HashMap::new()),
6504 store: Some(store),
6505 blob_store: Some(blob_store),
6506 llm_reconfigure_host: StdRwLock::new(None),
6507 member_observation_host: StdRwLock::new(None),
6508 member_live_host: StdRwLock::new(None),
6509 live_commands_served: std::sync::atomic::AtomicU64::new(0),
6510 member_incarnation_slots: StdRwLock::new(HashMap::new()),
6511 auth_lease: StdRwLock::new(auth_lease),
6512 #[cfg(not(target_arch = "wasm32"))]
6513 oauth_flows: StdRwLock::new(oauth_flows),
6514 #[cfg(feature = "live")]
6515 live_unbound_rejection_authority: live_unbound_rejection_authority(),
6516 session_claims: Arc::new(crate::handles::RuntimeSessionClaimRegistry::new()),
6517 composition_signal_dispatcher: StdRwLock::new(None),
6518 #[cfg(feature = "test-support")]
6519 test_stop_executor_after_ensure: std::sync::atomic::AtomicBool::new(false),
6520 #[cfg(feature = "test-support")]
6521 test_pause_executor_after_ensure: std::sync::atomic::AtomicBool::new(false),
6522 #[cfg(feature = "test-support")]
6523 test_executor_after_ensure_pause_reached: crate::tokio::sync::Notify::new(),
6524 #[cfg(feature = "test-support")]
6525 test_executor_after_ensure_pause_release: crate::tokio::sync::Notify::new(),
6526 #[cfg(test)]
6527 test_fenced_accept_after_lease: StdMutex::new(None),
6528 #[cfg(test)]
6529 test_registration_transaction_contention_probe: StdMutex::new(None),
6530 #[cfg(test)]
6531 test_fail_post_stop_unregister_after_fence: StdMutex::new(None),
6532 #[cfg(any(test, feature = "test-support"))]
6533 test_runtime_loop_before_queue_authority: StdMutex::new(None),
6534 #[cfg(test)]
6535 test_control_command_after_logical_lookup: StdMutex::new(None),
6536 }),
6537 }
6538 }
6539
6540 pub fn persistent_without_blobs(store: Arc<dyn RuntimeStore>) -> Self {
6547 #[cfg(not(target_arch = "wasm32"))]
6548 let (auth_lease, oauth_flows) = {
6549 let authorities = persistent_auth_authorities(&store);
6550 (
6551 Arc::clone(&authorities.auth_lease),
6552 Arc::clone(&authorities.oauth_flows),
6553 )
6554 };
6555 #[cfg(target_arch = "wasm32")]
6556 let auth_lease = Arc::new(crate::handles::RuntimeAuthLeaseHandle::new());
6557 let auth_lease = generated_runtime_auth_lease_handle(auth_lease);
6558 Self {
6559 shared: Arc::new(MeerkatMachineShared {
6560 sessions: RwLock::new(HashMap::new()),
6561 registration_transaction_slots: StdRwLock::new(HashMap::new()),
6562 store: Some(store),
6563 blob_store: Some(Arc::new(UnavailableBlobStore)),
6564 llm_reconfigure_host: StdRwLock::new(None),
6565 member_observation_host: StdRwLock::new(None),
6566 member_live_host: StdRwLock::new(None),
6567 live_commands_served: std::sync::atomic::AtomicU64::new(0),
6568 member_incarnation_slots: StdRwLock::new(HashMap::new()),
6569 auth_lease: StdRwLock::new(auth_lease),
6570 #[cfg(not(target_arch = "wasm32"))]
6571 oauth_flows: StdRwLock::new(oauth_flows),
6572 #[cfg(feature = "live")]
6573 live_unbound_rejection_authority: live_unbound_rejection_authority(),
6574 session_claims: Arc::new(crate::handles::RuntimeSessionClaimRegistry::new()),
6575 composition_signal_dispatcher: StdRwLock::new(None),
6576 #[cfg(feature = "test-support")]
6577 test_stop_executor_after_ensure: std::sync::atomic::AtomicBool::new(false),
6578 #[cfg(feature = "test-support")]
6579 test_pause_executor_after_ensure: std::sync::atomic::AtomicBool::new(false),
6580 #[cfg(feature = "test-support")]
6581 test_executor_after_ensure_pause_reached: crate::tokio::sync::Notify::new(),
6582 #[cfg(feature = "test-support")]
6583 test_executor_after_ensure_pause_release: crate::tokio::sync::Notify::new(),
6584 #[cfg(test)]
6585 test_fenced_accept_after_lease: StdMutex::new(None),
6586 #[cfg(test)]
6587 test_registration_transaction_contention_probe: StdMutex::new(None),
6588 #[cfg(test)]
6589 test_fail_post_stop_unregister_after_fence: StdMutex::new(None),
6590 #[cfg(any(test, feature = "test-support"))]
6591 test_runtime_loop_before_queue_authority: StdMutex::new(None),
6592 #[cfg(test)]
6593 test_control_command_after_logical_lookup: StdMutex::new(None),
6594 }),
6595 }
6596 }
6597
6598 pub fn auth_lease_handle(&self) -> Arc<dyn meerkat_core::handles::AuthLeaseHandle> {
6601 self.generated_auth_lease_handle().clone_handle()
6602 }
6603
6604 pub fn generated_auth_lease_handle(&self) -> meerkat_core::handles::GeneratedAuthLeaseHandle {
6607 self.auth_lease
6608 .read()
6609 .unwrap_or_else(std::sync::PoisonError::into_inner)
6610 .clone()
6611 }
6612
6613 pub fn set_auth_lease_handle(&self, handle: Arc<crate::handles::RuntimeAuthLeaseHandle>) {
6619 self.set_runtime_auth_lease_handle(handle);
6620 }
6621
6622 #[cfg(not(target_arch = "wasm32"))]
6628 pub fn set_auth_lease_handle_with_oauth_flow_authority(
6629 &self,
6630 handle: Arc<crate::handles::RuntimeAuthLeaseHandle>,
6631 oauth_flows: Arc<dyn meerkat_auth_core::oauth_flow::OAuthFlowAuthority>,
6632 ) {
6633 *self
6634 .oauth_flows
6635 .write()
6636 .unwrap_or_else(std::sync::PoisonError::into_inner) = oauth_flows;
6637 let handle = generated_runtime_auth_lease_handle(handle);
6638 *self
6639 .auth_lease
6640 .write()
6641 .unwrap_or_else(std::sync::PoisonError::into_inner) = handle;
6642 }
6643
6644 pub fn set_runtime_auth_lease_handle(
6647 &self,
6648 handle: Arc<crate::handles::RuntimeAuthLeaseHandle>,
6649 ) {
6650 #[cfg(not(target_arch = "wasm32"))]
6651 {
6652 *self
6653 .oauth_flows
6654 .write()
6655 .unwrap_or_else(std::sync::PoisonError::into_inner) =
6656 Arc::new(crate::handles::RuntimeOAuthFlowHandle::new_with_auth_lease(
6657 std::time::Duration::from_secs(10 * 60),
6658 Arc::clone(&handle),
6659 ));
6660 }
6661 let handle = generated_runtime_auth_lease_handle(handle);
6662 *self
6663 .auth_lease
6664 .write()
6665 .unwrap_or_else(std::sync::PoisonError::into_inner) = handle;
6666 }
6667
6668 #[cfg(not(target_arch = "wasm32"))]
6671 pub fn oauth_flow_authority(
6672 &self,
6673 ) -> Arc<dyn meerkat_auth_core::oauth_flow::OAuthFlowAuthority> {
6674 Arc::clone(
6675 &self
6676 .oauth_flows
6677 .read()
6678 .unwrap_or_else(std::sync::PoisonError::into_inner),
6679 )
6680 }
6681
6682 pub fn session_claim_handle(&self) -> Arc<dyn meerkat_core::handles::SessionClaimHandle> {
6687 Arc::clone(&self.session_claims) as Arc<dyn meerkat_core::handles::SessionClaimHandle>
6688 }
6689
6690 pub fn set_composition_signal_dispatcher(
6693 &self,
6694 dispatcher: composition::MeerkatCompositionSignalDispatcher,
6695 ) {
6696 let mut slot = self
6697 .composition_signal_dispatcher
6698 .write()
6699 .unwrap_or_else(std::sync::PoisonError::into_inner);
6700 *slot = Some(dispatcher);
6701 }
6702
6703 pub(crate) async fn apply_routed_meerkat_input(
6714 &self,
6715 session_id: &SessionId,
6716 input: dsl::MeerkatMachineInput,
6717 ) -> Result<(), dsl_authority::DslTransitionRefusal> {
6718 let _gate_guard = self
6719 .lock_current_session_mutation_gate(session_id)
6720 .await
6721 .ok_or_else(|| {
6722 dsl_authority::DslTransitionRefusal::other(
6723 "routed_session_not_registered",
6724 format!(
6725 "session `{session_id}` is not registered with this MeerkatMachine; \
6726 cannot deliver routed input"
6727 ),
6728 )
6729 })?;
6730 self.apply_routed_session_dsl_input(session_id, input, "RoutedMeerkatInput")
6731 .await
6732 .map(|_| ())
6733 }
6734
6735 #[cfg(test)]
6736 pub(crate) async fn debug_shared_ingress_authorities(
6737 &self,
6738 session_id: &SessionId,
6739 ) -> Option<(
6740 Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
6741 crate::driver::ephemeral::SharedIngressDslAuthority,
6742 )> {
6743 let sessions = self.sessions.read().await;
6744 let entry = sessions.get(session_id)?;
6745 let session_authority = Arc::clone(&entry.dsl_authority);
6746 let driver = entry.driver.lock().await;
6747 Some((session_authority, driver.shared_dsl_authority()))
6748 }
6749
6750 fn make_driver(
6752 &self,
6753 runtime_id: LogicalRuntimeId,
6754 dsl_authority: crate::driver::ephemeral::SharedIngressDslAuthority,
6755 initial_runtime_state: RuntimeState,
6756 ) -> DriverEntry {
6757 let control_projection = Arc::new(StdRwLock::new(
6758 crate::driver::ephemeral::RuntimeControlProjection {
6759 phase: initial_runtime_state,
6760 current_run_id: None,
6761 pre_run_phase: None,
6762 },
6763 ));
6764 match (&self.store, &self.blob_store) {
6765 (Some(store), Some(blob_store)) => {
6766 DriverEntry::Persistent(PersistentRuntimeDriver::new_with_control(
6767 runtime_id,
6768 store.clone(),
6769 blob_store.clone(),
6770 control_projection,
6771 dsl_authority,
6772 ))
6773 }
6774 _ => DriverEntry::Ephemeral(EphemeralRuntimeDriver::new_with_control_and_dsl(
6775 runtime_id,
6776 control_projection,
6777 dsl_authority,
6778 )),
6779 }
6780 }
6781
6782 async fn recover_or_create_ops_state(
6789 &self,
6790 session_id: &SessionId,
6791 runtime_id: &LogicalRuntimeId,
6792 ) -> Result<
6793 (
6794 Arc<crate::ops_lifecycle::RuntimeOpsLifecycleRegistry>,
6795 meerkat_core::RuntimeEpochId,
6796 Arc<meerkat_core::EpochCursorState>,
6797 ),
6798 RuntimeDriverError,
6799 > {
6800 if let Some(ref store) = self.store {
6801 let (registry, epoch_id, cursor_state) = Self::fresh_ops_state();
6802 let initial_snapshot = registry
6808 .capture_persistence_snapshot(epoch_id.clone(), cursor_state.as_ref())
6809 .map_err(|error| {
6810 RuntimeDriverError::Internal(format!(
6811 "failed to capture initial ops lifecycle authority for session {session_id}: {error}"
6812 ))
6813 })?;
6814 let canonical_snapshot = store
6815 .initialize_ops_lifecycle_if_absent(runtime_id, &initial_snapshot)
6816 .await
6817 .map_err(|error| {
6818 RuntimeDriverError::Internal(format!(
6819 "failed to initialize ops lifecycle authority for session {session_id}: {error}"
6820 ))
6821 })?;
6822 let recovered_epoch = canonical_snapshot.epoch_id.clone();
6823 let initialized = recovered_epoch == epoch_id;
6824 let recovered_ops_count = canonical_snapshot.completion_entries.len();
6825 let registry = crate::ops_lifecycle::RuntimeOpsLifecycleRegistry::from_recovered(
6826 canonical_snapshot,
6827 )
6828 .map_err(|error| {
6829 tracing::error!(
6830 %session_id,
6831 %runtime_id,
6832 error = %error,
6833 "failed to recover ops lifecycle through generated authority"
6834 );
6835 RuntimeDriverError::Internal(format!(
6836 "failed to recover ops lifecycle through generated authority: {error}"
6837 ))
6838 })?;
6839 let recovered_cursor_snapshot = registry.completion_cursor_snapshot();
6840 let recovered_cursors = meerkat_core::EpochCursorState::from_recovered(
6841 recovered_cursor_snapshot.agent_applied_cursor,
6842 recovered_cursor_snapshot.runtime_observed_seq,
6843 recovered_cursor_snapshot.runtime_last_injected_seq,
6844 );
6845 tracing::info!(
6846 %session_id,
6847 %runtime_id,
6848 epoch_id = %recovered_epoch,
6849 recovered_ops = recovered_ops_count,
6850 initialized,
6851 "ops lifecycle authority selected from durable store"
6852 );
6853 Ok((
6854 Arc::new(registry),
6855 recovered_epoch,
6856 Arc::new(recovered_cursors),
6857 ))
6858 } else {
6859 Ok((
6860 Arc::new(crate::ops_lifecycle::RuntimeOpsLifecycleRegistry::new()),
6861 meerkat_core::RuntimeEpochId::new(),
6862 Arc::new(meerkat_core::EpochCursorState::new()),
6863 ))
6864 }
6865 }
6866
6867 fn fresh_ops_state() -> (
6868 Arc<crate::ops_lifecycle::RuntimeOpsLifecycleRegistry>,
6869 meerkat_core::RuntimeEpochId,
6870 Arc<meerkat_core::EpochCursorState>,
6871 ) {
6872 let registry = Arc::new(crate::ops_lifecycle::RuntimeOpsLifecycleRegistry::new());
6873 let epoch = meerkat_core::RuntimeEpochId::new();
6874 let cursors = Arc::new(meerkat_core::EpochCursorState::new());
6875 (registry, epoch, cursors)
6876 }
6877
6878 #[allow(clippy::large_futures)]
6879 fn execute_meerkat_machine_command(
6880 &self,
6881 self_handle: Option<Arc<Self>>,
6882 command: MeerkatMachineCommand,
6883 ) -> MeerkatMachineCommandFuture<'_> {
6884 Box::pin(async move {
6885 match command {
6886 MeerkatMachineCommand::EnsureSessionWithExecutor { .. } => {
6887 let self_handle = self_handle.ok_or_else(|| {
6888 MeerkatMachineCommandError::Driver(RuntimeDriverError::Internal(
6889 "EnsureSessionWithExecutor requires Arc<Self> machine handle".into(),
6890 ))
6891 })?;
6892 self_handle
6893 .execute_meerkat_machine_ensure_session_command(command)
6894 .await
6895 .map_err(Into::into)
6896 }
6897 MeerkatMachineCommand::RegisterSession { .. }
6898 | MeerkatMachineCommand::UnregisterSession { .. }
6899 | MeerkatMachineCommand::SetSilentIntents { .. }
6900 | MeerkatMachineCommand::CancelAfterBoundary { .. }
6901 | MeerkatMachineCommand::StopRuntimeExecutor { .. }
6902 | MeerkatMachineCommand::CommitServiceTurnTerminalReceipt { .. }
6903 | MeerkatMachineCommand::ContainsSession { .. }
6904 | MeerkatMachineCommand::SessionHasExecutor { .. }
6905 | MeerkatMachineCommand::SessionHasComms { .. }
6906 | MeerkatMachineCommand::OpsLifecycleRegistry { .. }
6907 | MeerkatMachineCommand::PrepareBindings { .. }
6908 | MeerkatMachineCommand::PrepareLocalSessionBindings { .. }
6909 | MeerkatMachineCommand::InputState { .. }
6910 | MeerkatMachineCommand::InputStateByIdempotencyKey { .. }
6911 | MeerkatMachineCommand::InteractionTerminalStatus { .. }
6912 | MeerkatMachineCommand::RunTerminalStatus { .. }
6913 | MeerkatMachineCommand::ListActiveInputs { .. }
6914 | MeerkatMachineCommand::ReconfigureSessionLlmIdentity { .. }
6915 | MeerkatMachineCommand::StagePersistentFilter { .. }
6916 | MeerkatMachineCommand::RequestDeferredTools { .. }
6917 | MeerkatMachineCommand::PublishCommittedVisibleSet { .. } => self
6918 .execute_meerkat_machine_session_command(command)
6919 .await
6920 .map_err(Into::into),
6921 MeerkatMachineCommand::SetPeerIngressContext { .. }
6922 | MeerkatMachineCommand::NotifyDrainExited { .. } => {
6923 let self_handle = self_handle.ok_or_else(|| {
6924 MeerkatMachineCommandError::Driver(RuntimeDriverError::Internal(
6925 "drain command requires Arc<Self> machine handle".into(),
6926 ))
6927 })?;
6928 self_handle
6929 .execute_meerkat_machine_drain_command(command)
6930 .await
6931 .map_err(Into::into)
6932 }
6933 MeerkatMachineCommand::AbortAll
6934 | MeerkatMachineCommand::Abort { .. }
6935 | MeerkatMachineCommand::Wait { .. } => self
6936 .execute_meerkat_machine_drain_local_command(command)
6937 .await
6938 .map_err(Into::into),
6939 MeerkatMachineCommand::Ingest { .. }
6940 | MeerkatMachineCommand::PublishEvent { .. }
6941 | MeerkatMachineCommand::Retire { .. }
6942 | MeerkatMachineCommand::Recycle { .. }
6943 | MeerkatMachineCommand::Reset { .. }
6944 | MeerkatMachineCommand::Recover { .. }
6945 | MeerkatMachineCommand::Destroy { .. }
6946 | MeerkatMachineCommand::RuntimeState { .. }
6947 | MeerkatMachineCommand::ResolvedSessionLlmCapabilities { .. }
6948 | MeerkatMachineCommand::ConfigureModelRoutingBaseline { .. }
6949 | MeerkatMachineCommand::SessionModelRoutingStatus { .. }
6950 | MeerkatMachineCommand::RequestSwitchTurn { .. }
6951 | MeerkatMachineCommand::AdmitModelRoutingAssistantTurn { .. }
6952 | MeerkatMachineCommand::BeginImageOperation { .. }
6953 | MeerkatMachineCommand::DenyImageOperationPlan { .. }
6954 | MeerkatMachineCommand::ActivateImageOperationOverride { .. }
6955 | MeerkatMachineCommand::ClassifyImageOperationTerminal { .. }
6956 | MeerkatMachineCommand::CompleteImageOperation { .. }
6957 | MeerkatMachineCommand::RestoreImageOperationOverride { .. }
6958 | MeerkatMachineCommand::LoadBoundaryReceipt { .. } => self
6959 .execute_meerkat_machine_control_command(command)
6960 .await
6961 .map_err(Into::into),
6962 MeerkatMachineCommand::AcceptWithCompletion { .. }
6963 | MeerkatMachineCommand::AcceptWithoutWake { .. } => self
6964 .execute_meerkat_machine_ingress_command(command)
6965 .await
6966 .map_err(Into::into),
6967 }
6968 })
6969 }
6970
6971 pub async fn register_session(
6978 &self,
6979 session_id: SessionId,
6980 ) -> Result<(), RuntimeControlPlaneError> {
6981 match self
6982 .execute_meerkat_machine_command(
6983 None,
6984 MeerkatMachineCommand::RegisterSession { session_id },
6985 )
6986 .await
6987 .map_err(MeerkatMachine::control_plane_error_from_command_error)?
6988 {
6989 MeerkatMachineCommandResult::Unit => Ok(()),
6990 other => Err(RuntimeControlPlaneError::Internal(format!(
6991 "register_session: unexpected command result variant: {other:?}"
6992 ))),
6993 }
6994 }
6995}
6996
6997#[cfg(test)]
6998#[allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
6999#[path = "../meerkat_machine_tests.rs"]
7000mod tests;