Skip to main content

meerkat_runtime/meerkat_machine/
mod.rs

1//! MeerkatMachine — session-scoped execution kernel.
2//!
3//! One of two kernels in the Meerkat two-kernel architecture:
4//!
5//! - **MeerkatMachine** (this module) owns session-scoped runtime state:
6//!   input ingress, run lifecycle, completion waiters, async-ops registry,
7//!   comms drain, and tool visibility publication. All mutations flow through
8//!   one unified internal reducer, gated by TLA+-derived precondition guards.
9//!
10//! - **MobMachine** (`meerkat-mob`) owns mob-scoped orchestration: roster,
11//!   flow frames, delegation, and inter-member wiring.
12//!
13//! MeerkatMachine lives in `meerkat-runtime` so `meerkat-session` does not
14//! depend on runtime execution internals. When a session registers a
15//! `CoreExecutor`, a background `RuntimeLoop` task is spawned. Input acceptance
16//! queues through the driver; wake signals the loop; the loop dequeues, stages,
17//! applies via `CoreExecutor`, and marks inputs consumed.
18
19use 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/// Shared generated authorities for a standalone facade session.
123///
124/// Standalone has no runtime loop, but turn recovery, model routing, and tool
125/// visibility still describe one session machine. Keeping all three handles on
126/// the same authority is required for sticky fallback: the turn handle admits
127/// ErrorRecovery and the routing handle commits identity + visibility against
128/// that exact state.
129#[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
171/// Build the shared generated authority bundle for a standalone session.
172///
173/// Standalone sessions do not have a runtime loop, but durable tool visibility
174/// and sticky model fallback are still machine facts. This bundle gives those
175/// sessions the same single-authority path used by runtime-backed sessions.
176pub 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
250/// Build only the standalone visibility projection for tool-only hosts.
251/// AgentFactory uses [`standalone_session_runtime_authorities`] so turn,
252/// routing, and visibility never split across private authorities.
253pub 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/// Error type for [`MeerkatMachine::prepare_bindings`].
269#[derive(Debug, thiserror::Error)]
270pub enum RuntimeBindingsError {
271    /// Session was not found after registration (should not happen in practice).
272    #[error("session {0} not found in runtime adapter after registration")]
273    SessionNotFound(SessionId),
274    /// Machine-owned binding preparation failed before bindings were published.
275    #[error("failed to prepare runtime bindings for session {0}: {1}")]
276    PrepareFailed(SessionId, String),
277}
278
279/// Generated public projection for an input-state seed.
280#[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/// Runtime lifecycle/admission facts emitted by generated MeerkatMachine
287/// authority for a public runtime-state projection.
288#[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/// Runtime-loop queue-drain admission feedback emitted by generated
320/// MeerkatMachine authority.
321#[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
339/// Classify runtime lifecycle/admission facts through generated
340/// MeerkatMachine authority. Callers provide only the observed state variant;
341/// all behavior-affecting facts come back as generated typed feedback.
342pub 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
382/// Classify the store-visible durable runtime lifecycle state through
383/// generated MeerkatMachine authority. The caller supplies only the live
384/// observed state; generated feedback decides the recovery projection.
385pub 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
391/// Classify the durable lifecycle while retaining the live run's coarse
392/// pre-run phase. Running itself is process-local, but a run admitted from a
393/// retired runtime must remain durably retired after cold recovery.
394pub(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
433/// Classify runtime-loop queue admission through generated MeerkatMachine
434/// authority. The caller provides the observed runtime state and the structural
435/// fact that a current run id is bound; generated feedback decides whether the
436/// queue may drain and whether that bound run id must be reused.
437pub 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/// Machine-owned arbitration verdict between the live DSL lifecycle phase and
481/// the durable control projection, emitted by generated MeerkatMachine
482/// authority. `publish_control` is the terminal-precedence decision (the
483/// published control projection supersedes the live DSL phase);
484/// `selected_raw_phase` is the chosen phase without the visibility rewrite;
485/// `visible_phase` is the externally-visible phase after the
486/// Running+pre_run(Retired)->Retired rewrite. The shell mirrors all three.
487#[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
494/// Resolve the authoritative/visible runtime phase through generated
495/// MeerkatMachine authority. The shell feeds only the five pure
496/// [`RuntimeState`] observations it already holds; the machine owns BOTH the
497/// terminal-precedence `publish_control` policy AND the
498/// Running+pre_run(Retired)->Retired visibility rewrite. The shell mirrors the
499/// emitted verdict and re-derives nothing, failing closed if no verdict is
500/// emitted.
501pub 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
559/// Resolve the public lifecycle class for a machine-derived input phase
560/// through generated MeerkatMachine authority.
561pub 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
594/// Resolve public lifecycle and terminal result classes for a machine-derived
595/// input-state seed through generated MeerkatMachine authority.
596pub 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
623/// Authorize DSL-owned input-state seed facts before they are written to a
624/// runtime store.
625pub(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    /// True when the committed transition was a machine-owned revival of a
1055    /// stopped session (`RegisterSessionResumesStopped` /
1056    /// `EnsureSessionWithExecutorStopped`): the machine emits the typed
1057    /// `RuntimeNotice { kind: Recover }` effect, and the shell keys the
1058    /// durable lifecycle persist on it so a revived session is never left
1059    /// durably `Stopped` for cross-process readers.
1060    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    /// Whether committing this already-staged transition could publish a
1073    /// cross-machine seam signal. A caller may restore `previous_snapshot`
1074    /// after dispatch failure only when this is false: once any routed signal
1075    /// may have escaped, rolling local authority back would split truth.
1076    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/// Joinable result channel for the one owned unregister saga of an epoch.
1093#[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/// Joinable result channel for the one owned ordinary-stop cleanup operation
1101/// of an epoch. Completed results remain installed until a successful resume
1102/// replaces the executor, or an explicit stop/unregister authorizes a retry.
1103#[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/// Owned persistence worker for one runtime's ops epoch. The unregister saga
1124/// closes the registry producer and joins this worker before the atomic store
1125/// finalization can retire the epoch.
1126#[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            // A pending producer obligation recovered in a fresh process has
1150            // lost its original JoinHandle. Closing it now is necessarily a
1151            // process-loss/forced disposition, never evidence of clean drain.
1152            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
1165/// Per-session state: driver + generated authority binding + shell handles.
1166struct RuntimeSessionEntry {
1167    /// Canonical runtime control-plane identity for this registered session.
1168    runtime_id: LogicalRuntimeId,
1169    /// Per-session mutation gate.
1170    ///
1171    /// Serializes same-session mutating commands across the full
1172    /// DSL-stage → driver-mutate → DSL-sync span. Without this gate,
1173    /// two concurrent commands on the same session can interleave between
1174    /// the DSL projection sync (which releases `sessions` lock) and the
1175    /// driver mutation (which acquires `driver` lock independently).
1176    ///
1177    /// This is NOT a replacement for `sessions` RwLock or `driver` Mutex —
1178    /// it is an additional serialization point that spans the entire
1179    /// multi-step mutation window.
1180    mutation_gate: Arc<Mutex<()>>,
1181    /// Serializes the complete live-open materialization window against a
1182    /// lifecycle owner's physical-absence proof + terminal marker window.
1183    /// This is separate from `mutation_gate`: live orchestration calls back
1184    /// into ordinary DSL mutations while it holds this lease.
1185    #[cfg(feature = "live")]
1186    live_lifecycle_gate: Arc<Mutex<()>>,
1187    /// Session-owned liveness driver for the currently pending supervisor
1188    /// rotation. Durable operation state remains in generated authority.
1189    supervisor_rotation_task: Arc<SupervisorRotationTaskSlot>,
1190    /// Shared driver handle (accessed by both adapter methods and RuntimeLoop).
1191    driver: SharedDriver,
1192    /// Canonical coarse control projection for this session.
1193    ///
1194    /// The driver reads this to realize shell mechanics, but machine-facing
1195    /// queries should publish from this shared cell rather than treating the
1196    /// driver shell as the source of lifecycle truth.
1197    control_projection: Arc<StdRwLock<crate::driver::ephemeral::RuntimeControlProjection>>,
1198    /// Shared async-operation lifecycle registry for this runtime/session.
1199    ops_lifecycle: Arc<crate::ops_lifecycle::RuntimeOpsLifecycleRegistry>,
1200    /// Joinable durability worker for `ops_lifecycle`. Never detached: final
1201    /// unregister must prove it has observed the closed producer before the
1202    /// store records the epoch tombstone.
1203    ops_lifecycle_persistence_worker: Option<OpsLifecyclePersistenceWorker>,
1204    /// Runtime epoch identity — stable across rebuilds, rotated on reset/restart-without-recovery.
1205    epoch_id: meerkat_core::RuntimeEpochId,
1206    /// Mechanical close gate for handles minted from this session entry.
1207    ///
1208    /// The DSL still owns runtime terminality; this gate only invalidates cloned
1209    /// cross-crate handles after the entry is torn down.
1210    handle_teardown_gate: Arc<crate::handles::HandleTeardownGate>,
1211    /// Exact non-clone materialization claimant. Compatibility bindings do not
1212    /// reserve this slot; actor begin, unique preparation, attachment, and
1213    /// teardown transition it synchronously so no claimant can steal or roll
1214    /// back another transaction.
1215    materialization_claim_state:
1216        Arc<std::sync::Mutex<crate::RuntimeActorMaterializationClaimState>>,
1217    /// Shared consumer cursor state for the epoch.
1218    cursor_state: Arc<meerkat_core::EpochCursorState>,
1219    /// Completion waiters (accessed by accept_input_with_completion and RuntimeLoop).
1220    completions: SharedCompletionRegistry,
1221    /// Canonical durable visibility owner for this session.
1222    tool_visibility_owner: Arc<MachineToolVisibilityOwner>,
1223    /// One canonical epoch-local mechanical handle bundle.
1224    ///
1225    /// Actor reconstruction for an unchanged attachment clones these exact
1226    /// Arcs and replaces only its one-shot materialization authority. Minting
1227    /// fresh surface handles for the same epoch would replay MCP lifecycle
1228    /// transitions and split handle identity from the attached executor.
1229    canonical_runtime_bindings: Option<meerkat_core::SessionRuntimeBindings>,
1230    /// Runtime-loop channel publication slot.
1231    ///
1232    /// This is mechanical shell state only. The generated `MeerkatMachine`
1233    /// `registration_phase` is the semantic executor registration authority.
1234    attachment_slot: RuntimeLoopAttachmentSlot,
1235    /// Exact executor-cleanup handoff retained across unregister retries.
1236    ///
1237    /// The attachment channels and loop JoinHandle are consumed by the first
1238    /// teardown attempt, but a failing external cleanup restores its executor
1239    /// into this slot so the next machine-owned saga can retry without
1240    /// fabricating quiescence.
1241    runtime_loop_teardown: Option<Arc<crate::runtime_loop::RuntimeLoopTeardownSlot>>,
1242    /// Current epoch's owned unregister saga, if one is active.
1243    unregister_coordinator: Option<UnregisterTeardownCoordinator>,
1244    /// Current epoch's single ordinary-stop cleanup owner. Unlike unregister,
1245    /// its successful terminal leaves this registration in generated Stopped.
1246    runtime_stop_cleanup_coordinator: Option<RuntimeStopCleanupCoordinator>,
1247    /// A missing-live materialization normalized stale executor authority but
1248    /// has not yet committed its replacement attachment. The next exact
1249    /// attachment commit owns the matching durable lifecycle publication.
1250    pending_revival_lifecycle_persist: Arc<std::sync::atomic::AtomicBool>,
1251    /// Retry witness installed before the final generated UnregisterSession
1252    /// transition. If an owned saga panics after that transition changes the
1253    /// live projection to Queuing/session_id=None, the next saga resumes the
1254    /// same atomic store commit instead of trying to BeginUnregister again.
1255    pending_unregister_finalization: Option<PendingUnregisterFinalization>,
1256    /// Cancellation-safe shell observations waiting to be committed through
1257    /// the matching generated unregister feedback inputs.
1258    unregister_teardown_observations: Arc<UnregisterTeardownMechanicalObservations>,
1259    /// Durable-session publication capability retained independently of the
1260    /// loop channels. Unregister detaches those channels before its final
1261    /// terminal sweep; publication retries must still use the same owning
1262    /// session surface rather than dropping the outbox or inventing another
1263    /// publisher.
1264    publication_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorPublicationHandle>>,
1265    /// Cloneable surface/service teardown retained across loop detachment so
1266    /// external unregister and retry-finalize paths can finish the same exact
1267    /// cleanup transaction.
1268    post_stop_cleanup_handle:
1269        Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorPostStopCleanupHandle>>,
1270    /// Attachment incarnation authorized to run `post_stop_cleanup_handle`.
1271    post_stop_cleanup_attachment_id: Option<RuntimeLoopAttachmentId>,
1272    /// Whether the retained cleanup completed for that incarnation.
1273    post_stop_cleanup_complete: bool,
1274    /// Serializes cloneable cleanup attempts for the current attachment while
1275    /// the machine mutation gate is deliberately released. External
1276    /// unregister, loop-owned unregister, and final-unregister retry may race
1277    /// in `Draining`; exactly one of them may call the surface cleanup handle
1278    /// at a time.
1279    post_stop_cleanup_gate: Arc<Mutex<()>>,
1280    /// Temporary live interrupt capability for prepared, session-owned turns
1281    /// that run before the runtime loop attachment is published.
1282    provisional_interrupt_handle:
1283        Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorInterruptHandle>>,
1284    /// Exact prepared-materialization transaction that installed the
1285    /// provisional interrupt/cleanup pair. A stale binding cannot replace or
1286    /// clear another transaction's provisional ownership.
1287    provisional_materialization_claim_id: Option<uuid::Uuid>,
1288    /// DSL authority for coarse lifecycle phase transitions.
1289    /// Sync field — validates transitions, writes back phase.
1290    ///
1291    /// `Arc<std::sync::Mutex<_>>` so cross-crate handle impls
1292    /// (`meerkat-runtime::handles::*`) can share the same underlying authority
1293    /// from a sync context without awaiting the outer `sessions` tokio lock.
1294    /// The Arc heap-allocates the authority's large expanded state (31 fields
1295    /// including several Maps/Sets) so holding a reference to a
1296    /// `RuntimeSessionEntry` does not bloat async future sizes.
1297    dsl_authority: Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
1298    /// Per-session comms drain lifecycle slot.
1299    ///
1300    /// Collapsed from the sibling `MeerkatMachine.comms_drain_slots:
1301    /// RwLock<HashMap<SessionId, CommsDrainSlot>>` in wave-c C-H2 (F5 in
1302    /// docs/wave-c-prep/state-scope-audit.md) — keeping the slot here
1303    /// makes "session exists" a single HashMap insertion and eliminates
1304    /// the class of bugs where the sibling map and the session map
1305    /// could fall out of sync across a registration/unregistration
1306    /// boundary.
1307    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    /// Ordinary runtime session with no host-placement authority.
1319    PeerOnly,
1320    /// A host-owned session id whose placed incarnation is absent, released,
1321    /// or in cutover. This must never be treated as peer-only.
1322    VacantPlaced,
1323    /// Exact current host placement plus its runtime/journal authorities.
1324    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
1338/// Exact effect interval for a host-member residency. The stable residency
1339/// slot blocks G1→G2/vacate publication while the captured session guard
1340/// blocks unregister/same-SessionId runtime replacement. Neither authority
1341/// may be dropped before the caller's effect completes.
1342pub(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
1356/// Non-cloneable lease that holds one session's mutation authority across a
1357/// cross-crate archive transaction. The session service acquires this before
1358/// its recovery/checkpointer gates, then realizes Retire through the captured
1359/// driver without re-locking the mutation gate.
1360pub 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    /// True only when archive recovery itself inserted the in-memory runtime
1368    /// registration from durable authority. A quiescent terminal lease with
1369    /// this witness may remove that reconstructable registration without
1370    /// touching durable lifecycle truth; a registration that predated archive
1371    /// must never be removed through that cleanup path.
1372    recovered_registration_for_archive: bool,
1373    /// Stable absent-entry/register/unregister transaction boundary. It is
1374    /// acquired before the live and mutation leases and retained through any
1375    /// archive-only removal of the recovered entry.
1376    _registration_transaction_guard: crate::tokio::sync::OwnedMutexGuard<()>,
1377    /// Shared live/lifecycle boundary retained from physical absence proof
1378    /// through the durable archive/retire marker.
1379    _live_lifecycle_lease: Option<crate::member_live::MemberLiveLifecycleLease>,
1380    _mutation_guard: crate::tokio::sync::OwnedMutexGuard<()>,
1381}
1382
1383/// Selects whether local materialization preserves ordinary authority or
1384/// performs the narrow machine-owned missing-live normalization first.
1385#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1386pub enum LocalSessionMaterializationMode {
1387    /// Prepare local session resources without replacing a machine-authorized
1388    /// executor binding.
1389    #[default]
1390    Ordinary,
1391    /// Normalize the narrow ownerless executor shape authorized by Mob
1392    /// missing-live revival before preparing its replacement attachment.
1393    MissingLiveRevival,
1394}
1395
1396/// Unique, cancellation-safe owner of one prepared session materialization.
1397///
1398/// Runtime bindings remain cloneable factory data; this lease is deliberately
1399/// non-clone so rollback ownership can only move between orchestration layers by
1400/// an explicit Rust move. Dropping an armed lease synchronously fences executor
1401/// attachment, then schedules exact provisional cleanup/registration rollback.
1402pub 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
1412/// Exact post-actor commit authority for an Archived+Retired materialization.
1413///
1414/// This non-clone lease retains the machine mutation gate while the session
1415/// owner promotes its durable document and while the runtime performs the
1416/// matching Retired -> Idle transition. A broad session-control token cannot
1417/// cross either boundary while an exact materialization claim is outstanding.
1418pub 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
1427/// Type-state proof that the session owner durably promoted the archived
1428/// document through the same runtime store authority as this exact machine
1429/// attachment claim.
1430///
1431/// Only this promoted form can realize Retired -> Idle, making the required
1432/// document-before-runtime ordering explicit at the API boundary.
1433pub struct PromotedArchivedResumeCommitLease {
1434    prepared: PreparedArchivedResumeCommitLease,
1435}
1436
1437impl PreparedArchivedResumeCommitLease {
1438    pub fn session_id(&self) -> &SessionId {
1439        &self.session_id
1440    }
1441
1442    /// Convert the exact prepared lease into its post-document-promotion
1443    /// type state. The session service calls this only after its durable
1444    /// document write succeeds, and the shared-store check prevents a foreign
1445    /// service from authorizing this machine's runtime reset.
1446    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    /// Realize the exact Retired -> Idle half after durable document
1468    /// promotion has been certified by the session owner.
1469    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/// One-shot authorization to construct an actor at the Archived+Retired
1478/// revival midpoint for one exact prepared machine registration.
1479///
1480/// This is deliberately non-cloneable and non-constructible outside the
1481/// machine. It carries the exact claim and registration identities, while
1482/// [`begin_session_runtime_actor_materialization_for_archived_resume`](crate::begin_session_runtime_actor_materialization_for_archived_resume)
1483/// acquires the current machine mutation gate and revalidates those identities
1484/// plus the exact `Retired` lifecycle phase before actor construction starts.
1485#[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
1559/// Exact actor-only recovery lease for a session whose executor attachment is
1560/// already committed and serving.
1561///
1562/// The lease holds the machine mutation gate for that attachment, so service
1563/// actor reconstruction cannot race executor replacement or unregister. It
1564/// owns no registration/attachment rollback authority and can never spawn a
1565/// second runtime loop.
1566pub 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/// Exact actor-materialization authority that may be consumed by one executor
1577/// attachment attempt. This remains crate-private: surfaces transfer the
1578/// non-clone [`PreparedSessionMaterialization`] lease rather than minting or
1579/// comparing claim identifiers themselves.
1580#[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/// Opaque identity for one exact runtime-session registration.
1588///
1589/// Unlike [`RuntimeExecutorAttachmentWitness`], this witness deliberately
1590/// carries no executor identity. It exists for machine-owned cleanup of a
1591/// terminal registration that never published an attachment (for example, a
1592/// registration materialized only to recover its durable ops lifecycle).
1593/// Callers may clone and compare the witness, but only this machine can use it
1594/// to admit exact compare-and-remove teardown. Durable epoch identity alone is
1595/// not exact because an epoch may survive an in-process entry rebuild; the
1596/// private weak mutation-gate identity distinguishes those incarnations.
1597#[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/// Exact durable lifecycle observation for one session registration target.
1665///
1666/// Construction is owned by MeerkatMachine so a raw-content version observed
1667/// for one runtime id cannot be replayed as the precondition for another.
1668#[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/// Result of one observation-bound, externally fenced cold registration.
1705///
1706/// The durable lifecycle observation is target-local runtime content. The
1707/// external write fence is evaluated separately and is never encoded into that
1708/// content or its version.
1709#[derive(Debug, Clone, PartialEq, Eq)]
1710pub enum RuntimeSessionRegistrationOutcome {
1711    /// The exact observed lifecycle row was replaced with the canonical fresh
1712    /// Idle shell and the matching live registration was published.
1713    Applied {
1714        registration: RuntimeSessionRegistrationWitness,
1715        lifecycle: RuntimeSessionLifecycleObservation,
1716    },
1717    /// The exact durable row was already the canonical fresh Idle shell. Its
1718    /// external fence was still checked and the matching live registration was
1719    /// published.
1720    AlreadyExact {
1721        registration: RuntimeSessionRegistrationWitness,
1722        lifecycle: RuntimeSessionLifecycleObservation,
1723    },
1724    /// Either the target observation or the external write authority changed.
1725    /// No live registration was published by this invocation.
1726    Conflict {
1727        current: RuntimeSessionLifecycleObservation,
1728        reason: String,
1729    },
1730    /// The exact row or required store capability cannot be repaired safely.
1731    RepairBlocked {
1732        evidence_digest: Option<String>,
1733        reason: String,
1734    },
1735    /// Observation, fence admission, or target persistence was temporarily
1736    /// unavailable. The caller should re-observe and retry.
1737    Backoff { reason: String },
1738}
1739
1740/// Opaque identity for one exact runtime-executor attachment.
1741///
1742/// The machine mints this witness before constructing the executor so
1743/// surface-owned cleanup handles can retain the same identity.  It is
1744/// deliberately non-serializable and exposes no raw attachment identifier;
1745/// callers may clone and compare it, but cannot manufacture authority over a
1746/// replacement attachment.
1747#[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
1804/// Exact result of ensuring a runtime executor.
1805///
1806/// `Existing` proves a committed, serving attachment already owns the
1807/// session. `Pending` owns a newly attached/startup-reconciled executor whose
1808/// serving capabilities remain unpublished until the surface commits its
1809/// exact sidecars.
1810pub enum EnsureRuntimeExecutorAttachment {
1811    Existing(RuntimeExecutorAttachmentWitness),
1812    Pending(PendingRuntimeExecutorAttachment),
1813}
1814
1815/// Process-lifetime executor for machine cleanup work transferred out of an
1816/// RAII lease.
1817///
1818/// Native Tokio handles do allow cross-thread spawning, but once their owning
1819/// application runtime is dropped a newly spawned task is immediately
1820/// cancelled. Exact attachment cleanup must outlive both the task that opened
1821/// the lease and that task's runtime, so native builds use one runtime owned by
1822/// this crate for the rest of the process. WebAssembly already has a
1823/// process-wide JavaScript event loop and `tokio_with_wasm::spawn` is not tied
1824/// to an application-owned Tokio runtime. The slot caches only a successfully
1825/// built runtime: resource exhaustion during the first attempt is retryable.
1826#[cfg(not(target_arch = "wasm32"))]
1827static MACHINE_CLEANUP_RUNTIME: OnceLock<StdMutex<Option<crate::tokio::runtime::Runtime>>> =
1828    OnceLock::new();
1829
1830// Machine cleanup can restore and reduce the same generated DSL state as the
1831// host runtime. Keep its worker on the repository-wide runtime stack budget;
1832// Tokio's platform default (2 MiB on the affected hosts) is not sufficient
1833// for large fleet-restore authorities.
1834#[cfg(not(target_arch = "wasm32"))]
1835const MACHINE_CLEANUP_THREAD_STACK_SIZE: usize = 16 * 1024 * 1024;
1836
1837/// Cloneable proof that cancellation-safe machine cleanup can be dispatched.
1838///
1839/// Every RAII owner with asynchronous cleanup acquires and stores this before
1840/// it receives cleanup authority. Its `spawn` operation is therefore
1841/// infallible with respect to runtime initialization and never depends on the
1842/// caller's ambient runtime.
1843#[derive(Clone)]
1844struct MachineCleanupTaskSpawner {
1845    #[cfg(not(target_arch = "wasm32"))]
1846    handle: crate::tokio::runtime::Handle,
1847}
1848
1849/// Process-lifetime cleanup dispatcher for surface transactions that must
1850/// outlive the ambient Tokio runtime which opened them.
1851#[doc(hidden)]
1852#[derive(Clone)]
1853pub struct RuntimeCleanupTaskSpawner {
1854    inner: MachineCleanupTaskSpawner,
1855}
1856
1857impl RuntimeCleanupTaskSpawner {
1858    pub fn acquire() -> Result<Self, RuntimeDriverError> {
1859        Ok(Self {
1860            inner: MachineCleanupTaskSpawner::acquire()?,
1861        })
1862    }
1863
1864    #[cfg(not(target_arch = "wasm32"))]
1865    pub fn spawn_detached<F>(&self, future: F)
1866    where
1867        F: Future<Output = ()> + Send + 'static,
1868    {
1869        self.inner.spawn(future);
1870    }
1871
1872    #[cfg(target_arch = "wasm32")]
1873    pub fn spawn_detached<F>(&self, future: F)
1874    where
1875        F: Future<Output = ()> + 'static,
1876    {
1877        self.inner.spawn(future);
1878    }
1879}
1880
1881impl MachineCleanupTaskSpawner {
1882    #[cfg(not(target_arch = "wasm32"))]
1883    fn acquire() -> Result<Self, RuntimeDriverError> {
1884        let slot = MACHINE_CLEANUP_RUNTIME.get_or_init(|| StdMutex::new(None));
1885        let mut runtime = slot
1886            .lock()
1887            .unwrap_or_else(std::sync::PoisonError::into_inner);
1888        if let Some(runtime) = runtime.as_ref() {
1889            return Ok(Self {
1890                handle: runtime.handle().clone(),
1891            });
1892        }
1893
1894        let candidate = crate::tokio::runtime::Builder::new_multi_thread()
1895            .worker_threads(1)
1896            .thread_name("meerkat-machine-cleanup")
1897            .thread_stack_size(MACHINE_CLEANUP_THREAD_STACK_SIZE)
1898            .enable_all()
1899            .build()
1900            .map_err(|error| {
1901                RuntimeDriverError::Internal(format!(
1902                    "failed to start machine cleanup runtime: {error}"
1903                ))
1904            })?;
1905        let handle = candidate.handle().clone();
1906        *runtime = Some(candidate);
1907        Ok(Self { handle })
1908    }
1909
1910    #[cfg(target_arch = "wasm32")]
1911    fn acquire() -> Result<Self, RuntimeDriverError> {
1912        Ok(Self {})
1913    }
1914
1915    #[cfg(not(target_arch = "wasm32"))]
1916    fn spawn<F, T>(&self, future: F) -> crate::tokio::task::JoinHandle<T>
1917    where
1918        F: Future<Output = T> + Send + 'static,
1919        T: Send + 'static,
1920    {
1921        self.handle.spawn(future)
1922    }
1923
1924    #[cfg(target_arch = "wasm32")]
1925    fn spawn<F, T>(&self, future: F) -> crate::tokio::task::JoinHandle<T>
1926    where
1927        F: Future<Output = T> + 'static,
1928        T: 'static,
1929    {
1930        crate::tokio::spawn(future)
1931    }
1932}
1933
1934impl std::fmt::Debug for EnsureRuntimeExecutorAttachment {
1935    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1936        match self {
1937            Self::Existing(witness) => formatter.debug_tuple("Existing").field(witness).finish(),
1938            Self::Pending(pending) => formatter.debug_tuple("Pending").field(pending).finish(),
1939        }
1940    }
1941}
1942
1943/// Non-cloneable commit lease for one exact newly attached executor.
1944///
1945/// The runtime loop has completed startup reconciliation, but the machine
1946/// withholds serving publication and wake effects while this lease is armed.
1947/// A surface may publish sidecars tagged with [`Self::witness`] and then call
1948/// [`Self::commit`]. Dropping the lease transfers its mutation fence into an
1949/// independently owned exact-unregister saga, so caller cancellation cannot
1950/// strand a live executor or tear down a same-SessionId replacement.
1951#[must_use = "dropping a pending runtime attachment aborts that exact attachment"]
1952pub struct PendingRuntimeExecutorAttachment {
1953    machine: Arc<MeerkatMachine>,
1954    witness: RuntimeExecutorAttachmentWitness,
1955    mutation_guard: Option<crate::tokio::sync::OwnedMutexGuard<()>>,
1956    cleanup_spawner: MachineCleanupTaskSpawner,
1957    should_wake: bool,
1958    persist_lifecycle_on_commit: bool,
1959    armed: bool,
1960}
1961
1962/// Exact post-startup publication fence retained by a surface until its own
1963/// durable ownership row is committed.
1964///
1965/// The runtime loop remains mechanically pending and non-serving while the
1966/// same M guard is held. The surface must either activate its sidecar through
1967/// [`Self::commit_with`] or retire the exact attachment. Drop transfers
1968/// retirement to the owned cleanup runtime, so cancellation cannot publish a
1969/// dead host residency.
1970#[must_use = "dropping a retained runtime attachment aborts that exact attachment"]
1971pub struct CommittedRuntimeExecutorAttachmentPublicationLease {
1972    machine: Arc<MeerkatMachine>,
1973    witness: RuntimeExecutorAttachmentWitness,
1974    mutation_guard: Option<crate::tokio::sync::OwnedMutexGuard<()>>,
1975    cleanup_spawner: MachineCleanupTaskSpawner,
1976    should_wake: bool,
1977    persist_lifecycle_on_commit: bool,
1978    armed: bool,
1979}
1980
1981/// Exact, cancellation-safe retirement intent for one committed executor
1982/// attachment.
1983///
1984/// The caller must acquire the service turn-finalization boundary before the
1985/// machine creates this lease. While armed, the retained machine mutation gate
1986/// prevents same-SessionId replacement before canonical unregister reaches
1987/// its attachment-local post-stop actor cleanup.
1988/// Dropping the lease starts the same exact retirement saga as [`Self::commit`]
1989/// so cancellation cannot strand the serving executor or its actor.
1990#[must_use = "dropping a prepared runtime attachment retirement starts exact retirement"]
1991pub struct PreparedRuntimeExecutorAttachmentRetirement {
1992    machine: Arc<MeerkatMachine>,
1993    witness: RuntimeExecutorAttachmentWitness,
1994    mutation_guard: Option<crate::tokio::sync::OwnedMutexGuard<()>>,
1995    cleanup_spawner: MachineCleanupTaskSpawner,
1996    armed: bool,
1997}
1998
1999/// Completion handle for an independently owned exact-attachment retirement.
2000///
2001/// Dropping this handle never cancels retirement. Callers that hold the
2002/// service turn-finalization boundary must release it before awaiting because
2003/// machine unregister reacquires that boundary for post-stop cleanup.
2004#[must_use = "drop is cancellation-safe, but await completion to observe retirement errors"]
2005pub struct RuntimeExecutorAttachmentRetirementCompletion {
2006    result_rx: crate::tokio::sync::oneshot::Receiver<Result<bool, RuntimeDriverError>>,
2007}
2008
2009impl RuntimeExecutorAttachmentRetirementCompletion {
2010    fn new(
2011        result_rx: crate::tokio::sync::oneshot::Receiver<Result<bool, RuntimeDriverError>>,
2012    ) -> Self {
2013        Self { result_rx }
2014    }
2015
2016    pub async fn wait(self) -> Result<bool, RuntimeDriverError> {
2017        self.result_rx.await.map_err(|error| {
2018            RuntimeDriverError::Internal(format!(
2019                "owned exact runtime attachment retirement ended without a result: {error}"
2020            ))
2021        })?
2022    }
2023}
2024
2025impl PreparedRuntimeExecutorAttachmentRetirement {
2026    fn new(
2027        machine: Arc<MeerkatMachine>,
2028        witness: RuntimeExecutorAttachmentWitness,
2029        mutation_guard: crate::tokio::sync::OwnedMutexGuard<()>,
2030        cleanup_spawner: MachineCleanupTaskSpawner,
2031    ) -> Self {
2032        Self {
2033            machine,
2034            witness,
2035            mutation_guard: Some(mutation_guard),
2036            cleanup_spawner,
2037            armed: true,
2038        }
2039    }
2040
2041    pub fn witness(&self) -> &RuntimeExecutorAttachmentWitness {
2042        &self.witness
2043    }
2044
2045    /// Transfer the retained mutation fence into the independently owned
2046    /// exact-unregister saga. This method is synchronous so the shared surface
2047    /// can release its service boundary before awaiting completion.
2048    pub fn commit(
2049        mut self,
2050    ) -> Result<RuntimeExecutorAttachmentRetirementCompletion, RuntimeDriverError> {
2051        let guard = self.mutation_guard.take().ok_or_else(|| {
2052            RuntimeDriverError::Internal(
2053                "prepared exact attachment retirement lost its mutation fence".to_string(),
2054            )
2055        })?;
2056        let completion = self
2057            .machine
2058            .spawn_executor_attachment_retirement_with_guard(
2059                self.witness.clone(),
2060                guard,
2061                self.cleanup_spawner.clone(),
2062            );
2063        self.armed = false;
2064        Ok(completion)
2065    }
2066
2067    /// Complete exact attachment retirement while the caller retains the
2068    /// service turn-finalization boundary that preceded this lease's M guard.
2069    /// The attachment-local actor cleanup therefore runs in B -> M -> R order;
2070    /// canonical unregister observes it complete and cannot re-enter B.
2071    pub async fn commit_under_runtime_turn_finalization_boundary(
2072        mut self,
2073    ) -> Result<bool, RuntimeDriverError> {
2074        self.machine
2075            .complete_executor_attachment_cleanup_under_runtime_turn_boundary(&self.witness)
2076            .await?;
2077        let guard = self.mutation_guard.take().ok_or_else(|| {
2078            RuntimeDriverError::Internal(
2079                "prepared exact attachment retirement lost its mutation fence".to_string(),
2080            )
2081        })?;
2082        self.armed = false;
2083        self.machine
2084            .unregister_executor_attachment_if_current_with_guard(self.witness.clone(), guard)
2085            .await
2086    }
2087}
2088
2089impl Drop for PreparedRuntimeExecutorAttachmentRetirement {
2090    fn drop(&mut self) {
2091        if !self.armed {
2092            return;
2093        }
2094        let Some(guard) = self.mutation_guard.take() else {
2095            return;
2096        };
2097        let _completion = self
2098            .machine
2099            .spawn_executor_attachment_retirement_with_guard(
2100                self.witness.clone(),
2101                guard,
2102                self.cleanup_spawner.clone(),
2103            );
2104        self.armed = false;
2105    }
2106}
2107
2108impl std::fmt::Debug for PendingRuntimeExecutorAttachment {
2109    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2110        formatter
2111            .debug_struct("PendingRuntimeExecutorAttachment")
2112            .field("witness", &self.witness)
2113            .field("should_wake", &self.should_wake)
2114            .field("armed", &self.armed)
2115            .finish()
2116    }
2117}
2118
2119impl PendingRuntimeExecutorAttachment {
2120    fn new(
2121        machine: Arc<MeerkatMachine>,
2122        witness: RuntimeExecutorAttachmentWitness,
2123        mutation_guard: crate::tokio::sync::OwnedMutexGuard<()>,
2124        cleanup_spawner: MachineCleanupTaskSpawner,
2125        should_wake: bool,
2126        persist_lifecycle_on_commit: bool,
2127    ) -> Self {
2128        Self {
2129            machine,
2130            witness,
2131            mutation_guard: Some(mutation_guard),
2132            cleanup_spawner,
2133            should_wake,
2134            persist_lifecycle_on_commit,
2135            armed: true,
2136        }
2137    }
2138
2139    pub fn witness(&self) -> &RuntimeExecutorAttachmentWitness {
2140        &self.witness
2141    }
2142
2143    #[doc(hidden)]
2144    pub fn cleanup_task_spawner(&self) -> RuntimeCleanupTaskSpawner {
2145        RuntimeCleanupTaskSpawner {
2146            inner: self.cleanup_spawner.clone(),
2147        }
2148    }
2149
2150    pub async fn commit(self) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError> {
2151        self.commit_with_predecessor_mode(|_| Ok(()), false).await
2152    }
2153
2154    /// Commit this exact attachment as the successor to an explicitly retired
2155    /// predecessor in the same process-local runtime entry.
2156    ///
2157    /// The machine publishes the successor and, while still retaining the
2158    /// session mutation gate, fails waiters for every active predecessor input
2159    /// with
2160    /// [`crate::completion::CompletionWaitError::AttachmentReplaced`]. The
2161    /// predecessor's queued work is durably cancelled; an explicit retry is a
2162    /// new request owned by the successor. Fresh materialization must use
2163    /// [`Self::commit`] instead.
2164    pub async fn commit_replacing_predecessor(
2165        self,
2166    ) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError> {
2167        self.commit_with_predecessor_mode(|_| Ok(()), true).await
2168    }
2169
2170    /// Commit the exact attachment and run one synchronous surface publication
2171    /// hook at the same linearization point, before the machine slot becomes
2172    /// observable as serving and before queued work is released.
2173    ///
2174    /// The hook must not call back into `MeerkatMachine`; it is intended for
2175    /// infallible local publication such as flipping an already-installed
2176    /// sidecar's active bit at the same linearization point.
2177    pub async fn commit_with<F>(
2178        self,
2179        on_committed: F,
2180    ) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError>
2181    where
2182        F: FnOnce(&RuntimeExecutorAttachmentWitness) -> Result<(), RuntimeDriverError>
2183            + Send
2184            + 'static,
2185    {
2186        self.commit_with_predecessor_mode(on_committed, false).await
2187    }
2188
2189    /// Commit a replacement attachment and synchronously activate one
2190    /// preinstalled, witness-tagged sidecar at the serving linearization point.
2191    ///
2192    /// This is the replacement sibling of [`Self::commit_with`]. The hook must
2193    /// remain infallible/local and must not call back into `MeerkatMachine`;
2194    /// asynchronous map publication belongs outside M. Before the hook runs,
2195    /// every request owned by the predecessor is terminalized and fenced from
2196    /// the replacement attachment.
2197    pub async fn commit_with_replacing_predecessor<F>(
2198        self,
2199        on_committed: F,
2200    ) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError>
2201    where
2202        F: FnOnce(&RuntimeExecutorAttachmentWitness) -> Result<(), RuntimeDriverError>
2203            + Send
2204            + 'static,
2205    {
2206        self.commit_with_predecessor_mode(on_committed, true).await
2207    }
2208
2209    async fn commit_with_predecessor_mode<F>(
2210        self,
2211        on_committed: F,
2212        replaces_predecessor: bool,
2213    ) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError>
2214    where
2215        F: FnOnce(&RuntimeExecutorAttachmentWitness) -> Result<(), RuntimeDriverError>
2216            + Send
2217            + 'static,
2218    {
2219        let cleanup_spawner = self.cleanup_spawner.clone();
2220        let completion = cleanup_spawner.spawn(async move {
2221            let mut pending = self;
2222            let result = pending
2223                .try_commit_with(on_committed, false, replaces_predecessor)
2224                .await;
2225            match result {
2226                Ok(witness) => Ok(witness),
2227                Err(commit_error) => match pending.abort().await {
2228                    Ok(()) => Err(commit_error),
2229                    Err(cleanup_error) => Err(RuntimeDriverError::Internal(format!(
2230                        "{commit_error}; exact attachment cleanup also failed: {cleanup_error}"
2231                    ))),
2232                },
2233            }
2234        });
2235        completion.await.map_err(|error| {
2236            RuntimeDriverError::Internal(format!(
2237                "owned runtime attachment commit ended without a result: {error}"
2238            ))
2239        })?
2240    }
2241
2242    /// Attempt the final attachment publication while retaining this lease's
2243    /// exact mutation fence on rejection.
2244    ///
2245    /// This is the boundary-owned transaction seam. The caller already owns
2246    /// the service turn-finalization boundary, so a failed attempt can invoke
2247    /// [`Self::abort_under_runtime_turn_finalization_boundary`] without ever
2248    /// opening a B/M gap. Cancellation leaves this lease armed; its `Drop`
2249    /// starts ordinary exact cleanup after the caller's boundary is released.
2250    /// When `replaces_predecessor` is true, every active predecessor input is
2251    /// durably cancelled and its waiter receives
2252    /// [`crate::completion::CompletionWaitError::AttachmentReplaced`] before B
2253    /// becomes visible. No queued request is transferred between attachments.
2254    pub async fn try_commit_with_under_runtime_turn_finalization_boundary<F>(
2255        &mut self,
2256        replaces_predecessor: bool,
2257        on_committed: F,
2258    ) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError>
2259    where
2260        F: FnOnce(&RuntimeExecutorAttachmentWitness) -> Result<(), RuntimeDriverError>,
2261    {
2262        self.try_commit_with(on_committed, false, replaces_predecessor)
2263            .await
2264    }
2265
2266    /// Retain durable lifecycle commit, this exact M guard, and the
2267    /// runtime-loop serving token for a later surface publication decision.
2268    /// The sidecar and runtime loop remain non-admitting until the returned
2269    /// lease is committed.
2270    pub async fn try_commit_with_retained_publication_lease_under_runtime_turn_finalization_boundary(
2271        &mut self,
2272    ) -> Result<CommittedRuntimeExecutorAttachmentPublicationLease, RuntimeDriverError> {
2273        let witness = self.try_commit_with(|_| Ok(()), true, false).await?;
2274        let mutation_guard = self.mutation_guard.take().ok_or_else(|| {
2275            RuntimeDriverError::Internal(
2276                "committed attachment lost its retained publication mutation fence".to_string(),
2277            )
2278        })?;
2279        // `try_commit_with` disarms Pending on success; transfer the exact
2280        // guard into the publication lease instead of releasing it.
2281        Ok(CommittedRuntimeExecutorAttachmentPublicationLease {
2282            machine: Arc::clone(&self.machine),
2283            witness,
2284            mutation_guard: Some(mutation_guard),
2285            cleanup_spawner: self.cleanup_spawner.clone(),
2286            should_wake: self.should_wake,
2287            persist_lifecycle_on_commit: self.persist_lifecycle_on_commit,
2288            armed: true,
2289        })
2290    }
2291
2292    async fn try_commit_with<F>(
2293        &mut self,
2294        on_committed: F,
2295        retain_mutation_guard: bool,
2296        replaces_predecessor: bool,
2297    ) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError>
2298    where
2299        F: FnOnce(&RuntimeExecutorAttachmentWitness) -> Result<(), RuntimeDriverError>,
2300    {
2301        let guard = self.mutation_guard.as_ref().ok_or_else(|| {
2302            RuntimeDriverError::Internal(
2303                "pending runtime attachment lost its mutation fence before commit".to_string(),
2304            )
2305        })?;
2306        if retain_mutation_guard && replaces_predecessor {
2307            return Err(RuntimeDriverError::Internal(
2308                "predecessor request terminalization belongs to final serving publication"
2309                    .to_string(),
2310            ));
2311        }
2312        let predecessor_request_state = if replaces_predecessor {
2313            let sessions = self.machine.sessions.read().await;
2314            let entry =
2315                sessions
2316                    .get(self.witness.session_id())
2317                    .ok_or(RuntimeDriverError::NotReady {
2318                        state: RuntimeState::Destroyed,
2319                    })?;
2320            let exact_pending = entry.epoch_id == self.witness.epoch_id
2321                && matches!(
2322                    &entry.attachment_slot,
2323                    RuntimeLoopAttachmentSlot::Pending(attachment)
2324                        if attachment.id == self.witness.attachment_id
2325                            && !attachment.wake_tx.is_closed()
2326                            && !attachment.effect_tx.is_closed()
2327                );
2328            if !exact_pending {
2329                return Err(RuntimeDriverError::StaleAuthority {
2330                    reason: format!(
2331                        "replacement attachment for session {} changed before waiter fencing",
2332                        self.witness.session_id()
2333                    ),
2334                });
2335            }
2336            Some((
2337                entry.driver.clone(),
2338                entry.completions.clone(),
2339                entry.publication_handle(),
2340            ))
2341        } else {
2342            None
2343        };
2344        let mut predecessor_terminal_publication = None;
2345        if let Some((driver, completions, publication_handle)) = predecessor_request_state {
2346            const REPLACED_REASON: &str =
2347                "executor attachment was replaced before request completion";
2348            let (predecessor_input_ids, candidate_owner_input_id) = {
2349                let mut driver = driver.lock().await;
2350                let predecessor_input_ids = driver.as_driver().active_input_ids();
2351                if predecessor_input_ids.is_empty() {
2352                    (predecessor_input_ids, None)
2353                } else {
2354                    // The outbox candidates and Cancelled input terminals are
2355                    // persisted by one driver checkpoint. Replacement may
2356                    // later fail, but A's requests remain validly terminal and
2357                    // a future attachment can replay any unpublished terminal.
2358                    let prepared = driver.prepare_runless_runtime_terminated_interaction_outboxes(
2359                        &predecessor_input_ids,
2360                        REPLACED_REASON.to_string(),
2361                    )?;
2362                    if let Err(error) = driver
2363                        .abandon_pending_inputs(crate::input_state::InputAbandonReason::Cancelled)
2364                        .await
2365                    {
2366                        driver.rollback_prepared_runless_interaction_terminal_outboxes(prepared);
2367                        return Err(error);
2368                    }
2369                    let candidate_owner_input_id = crate::meerkat_machine::driver::DriverEntry::commit_prepared_runless_interaction_terminal_outboxes(prepared);
2370                    (predecessor_input_ids, candidate_owner_input_id)
2371                }
2372            };
2373            if !predecessor_input_ids.is_empty() {
2374                // M has been retained since B was installed Pending, so this
2375                // exact snapshot is the full A-era recipient set: no B waiter
2376                // can register yet. Fail only those recipients and stage
2377                // their already-durable terminal outboxes for publication
2378                // after M is released, without allowing B to report A's work
2379                // as a successful completion.
2380                completions.lock().await.fail_inputs(
2381                    predecessor_input_ids.iter().cloned(),
2382                    crate::completion::CompletionWaitError::AttachmentReplaced,
2383                );
2384                self.should_wake = false;
2385                if let Some(candidate_owner_input_id) = candidate_owner_input_id {
2386                    predecessor_terminal_publication = Some((
2387                        driver,
2388                        publication_handle,
2389                        predecessor_input_ids,
2390                        candidate_owner_input_id,
2391                    ));
2392                }
2393            }
2394        }
2395        let result = self
2396            .machine
2397            .commit_pending_executor_attachment(
2398                &self.witness,
2399                guard,
2400                self.should_wake,
2401                self.persist_lifecycle_on_commit,
2402                retain_mutation_guard,
2403                on_committed,
2404            )
2405            .await;
2406        if result.is_ok() {
2407            if !retain_mutation_guard {
2408                self.mutation_guard.take();
2409            }
2410            self.armed = false;
2411            if let Some((driver, publication_handle, input_ids, candidate_owner_input_id)) =
2412                predecessor_terminal_publication
2413            {
2414                // External publication must never pin M or delay B's serving
2415                // linearization. The terminal/input rows above are already
2416                // durable; this bounded best-effort handoff records receipts
2417                // after M is released. Failure leaves the exact outbox for a
2418                // later attachment's ordinary startup recovery.
2419                let _terminal_publication = self.cleanup_spawner.spawn(async move {
2420                    let deadline = Instant::now() + std::time::Duration::from_secs(5);
2421                    if let Err(error) = crate::control_plane::publish_and_resolve_runless_runtime_termination_before(
2422                        &driver,
2423                        None,
2424                        publication_handle.as_deref(),
2425                        &input_ids,
2426                        Some(&candidate_owner_input_id),
2427                        "executor attachment was replaced before request completion",
2428                        Some(deadline),
2429                    )
2430                    .await
2431                    {
2432                        tracing::warn!(
2433                            %error,
2434                            "replacement terminal publication remains durably pending"
2435                        );
2436                    }
2437                });
2438            }
2439        }
2440        result
2441    }
2442
2443    pub async fn abort(mut self) -> Result<(), RuntimeDriverError> {
2444        let guard = self.mutation_guard.take().ok_or_else(|| {
2445            RuntimeDriverError::Internal(
2446                "pending runtime attachment lost its mutation fence before abort".to_string(),
2447            )
2448        })?;
2449        let machine = Arc::clone(&self.machine);
2450        let witness = self.witness.clone();
2451        let completion = self.cleanup_spawner.spawn(async move {
2452            machine
2453                .abort_pending_executor_attachment(witness, guard)
2454                .await
2455        });
2456        self.armed = false;
2457        completion.await.map_err(|error| {
2458            RuntimeDriverError::Internal(format!(
2459                "owned runtime attachment abort ended without a result: {error}"
2460            ))
2461        })?
2462    }
2463
2464    /// Abort while the caller owns the stable session turn boundary. The
2465    /// stored attachment-local cleanup handle is completed through its
2466    /// non-reentrant variant before the exact unregister saga runs.
2467    pub async fn abort_under_runtime_turn_finalization_boundary(
2468        mut self,
2469    ) -> Result<(), RuntimeDriverError> {
2470        self.machine
2471            .complete_executor_attachment_cleanup_under_runtime_turn_boundary(&self.witness)
2472            .await?;
2473        let guard = self.mutation_guard.take().ok_or_else(|| {
2474            RuntimeDriverError::Internal(
2475                "pending runtime attachment lost its mutation fence before boundary-owned abort"
2476                    .to_string(),
2477            )
2478        })?;
2479        let machine = Arc::clone(&self.machine);
2480        let witness = self.witness.clone();
2481        let completion = self.cleanup_spawner.spawn(async move {
2482            machine
2483                .abort_pending_executor_attachment(witness, guard)
2484                .await
2485        });
2486        self.armed = false;
2487        completion.await.map_err(|error| {
2488            RuntimeDriverError::Internal(format!(
2489                "owned boundary-aware runtime attachment abort ended without a result: {error}"
2490            ))
2491        })?
2492    }
2493}
2494
2495impl CommittedRuntimeExecutorAttachmentPublicationLease {
2496    pub fn witness(&self) -> &RuntimeExecutorAttachmentWitness {
2497        &self.witness
2498    }
2499
2500    #[doc(hidden)]
2501    pub fn cleanup_task_spawner(&self) -> RuntimeCleanupTaskSpawner {
2502        RuntimeCleanupTaskSpawner {
2503            inner: self.cleanup_spawner.clone(),
2504        }
2505    }
2506
2507    /// Terminalize work recovered from an interrupted predecessor before this
2508    /// replacement attachment is allowed to serve.
2509    ///
2510    /// This is the narrow member-host revival seam. The retained mutation gate
2511    /// and the pending serving barrier make the driver's active-input snapshot
2512    /// the complete predecessor-owned request set: no successor request can be
2513    /// admitted yet. The inputs are durably abandoned and any process-local
2514    /// waiters fail mechanically with `AttachmentReplaced`, but no interaction
2515    /// terminal outbox is created. A remote controller therefore follows its
2516    /// existing typed timeout path instead of observing a terminal fabricated
2517    /// by replacement attachment B.
2518    pub async fn abandon_recovered_predecessor_inputs(
2519        &mut self,
2520    ) -> Result<usize, RuntimeDriverError> {
2521        let _guard = self.mutation_guard.as_ref().ok_or_else(|| {
2522            RuntimeDriverError::Internal(
2523                "retained attachment lost its mutation fence before predecessor abandonment"
2524                    .to_string(),
2525            )
2526        })?;
2527        let (driver, completions) = {
2528            let sessions = self.machine.sessions.read().await;
2529            let entry = sessions.get(self.witness.session_id()).ok_or_else(|| {
2530                RuntimeDriverError::StaleAuthority {
2531                    reason: format!(
2532                        "recovered predecessor session {} disappeared before abandonment",
2533                        self.witness.session_id()
2534                    ),
2535                }
2536            })?;
2537            let exact_pending = entry.epoch_id == self.witness.epoch_id
2538                && matches!(
2539                    &entry.attachment_slot,
2540                    RuntimeLoopAttachmentSlot::Pending(attachment)
2541                        if attachment.id == self.witness.attachment_id
2542                            && !attachment.wake_tx.is_closed()
2543                            && !attachment.effect_tx.is_closed()
2544                );
2545            if !exact_pending {
2546                return Err(RuntimeDriverError::StaleAuthority {
2547                    reason: format!(
2548                        "recovered predecessor attachment for session {} changed before abandonment",
2549                        self.witness.session_id()
2550                    ),
2551                });
2552            }
2553            (entry.driver.clone(), entry.completions.clone())
2554        };
2555
2556        let predecessor_input_ids = {
2557            let mut driver = driver.lock().await;
2558            let predecessor_input_ids = driver.as_driver().active_input_ids();
2559            if predecessor_input_ids.is_empty() {
2560                return Ok(0);
2561            }
2562            let abandoned = driver
2563                .abandon_pending_inputs(crate::input_state::InputAbandonReason::Cancelled)
2564                .await?;
2565            if abandoned != predecessor_input_ids.len() {
2566                return Err(RuntimeDriverError::Internal(format!(
2567                    "recovered predecessor abandonment for session {} terminalized {abandoned} of {} active inputs",
2568                    self.witness.session_id(),
2569                    predecessor_input_ids.len()
2570                )));
2571            }
2572            for input_id in &predecessor_input_ids {
2573                let stored = driver
2574                    .as_driver()
2575                    .stored_input_state(input_id)
2576                    .ok_or_else(|| {
2577                        RuntimeDriverError::Internal(format!(
2578                            "recovered predecessor input {input_id} disappeared after abandonment"
2579                        ))
2580                    })?;
2581                if !matches!(
2582                    stored.seed.terminal_outcome,
2583                    Some(crate::input_state::InputTerminalOutcome::Abandoned {
2584                        reason: crate::input_state::InputAbandonReason::Cancelled,
2585                    })
2586                ) || stored.state.interaction_terminal_outbox.is_some()
2587                {
2588                    return Err(RuntimeDriverError::Internal(format!(
2589                        "recovered predecessor input {input_id} did not reach silent Cancelled abandonment"
2590                    )));
2591                }
2592            }
2593            predecessor_input_ids
2594        };
2595        let abandoned = predecessor_input_ids.len();
2596        completions.lock().await.fail_inputs(
2597            predecessor_input_ids,
2598            crate::completion::CompletionWaitError::AttachmentReplaced,
2599        );
2600        Ok(abandoned)
2601    }
2602
2603    /// Publish the surface's final local state while the exact pending
2604    /// attachment still owns M, then make the attachment serving.
2605    pub async fn commit_with<F>(
2606        mut self,
2607        on_committed: F,
2608    ) -> Result<RuntimeExecutorAttachmentWitness, RuntimeDriverError>
2609    where
2610        F: FnOnce(&RuntimeExecutorAttachmentWitness) -> Result<(), RuntimeDriverError>,
2611    {
2612        let guard = self.mutation_guard.as_ref().ok_or_else(|| {
2613            RuntimeDriverError::Internal(
2614                "retained attachment publication lost its mutation fence".to_string(),
2615            )
2616        })?;
2617        let witness = self
2618            .machine
2619            .commit_retained_executor_attachment_publication(
2620                &self.witness,
2621                guard,
2622                self.should_wake,
2623                self.persist_lifecycle_on_commit,
2624                move |witness, _| on_committed(witness),
2625            )
2626            .await?;
2627        self.mutation_guard.take();
2628        self.armed = false;
2629        Ok(witness)
2630    }
2631
2632    /// Atomically publish the exact host residency and a surface sidecar at
2633    /// the retained M linearization point, then make the runtime loop serving.
2634    pub async fn commit_with_member_residency<F>(
2635        &mut self,
2636        residency_update: MemberResidencyUpdate,
2637        incarnation: meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation,
2638        tracked_turn_journal: Option<Arc<dyn crate::member_observation::TrackedTurnJournal>>,
2639        on_committed: F,
2640    ) -> Result<(RuntimeExecutorAttachmentWitness, MemberResidencyPublication), RuntimeDriverError>
2641    where
2642        F: FnOnce(&RuntimeExecutorAttachmentWitness) -> Result<(), RuntimeDriverError>,
2643    {
2644        residency_update.validate_for_retained_attachment(
2645            &self.machine,
2646            &self.witness,
2647            &incarnation,
2648        )?;
2649        let guard = self.mutation_guard.as_ref().ok_or_else(|| {
2650            RuntimeDriverError::Internal(
2651                "retained host publication lost its mutation fence".to_string(),
2652            )
2653        })?;
2654        let mut staged_residency_publication = None;
2655        let commit = self
2656            .machine
2657            .commit_retained_executor_attachment_publication(
2658                &self.witness,
2659                guard,
2660                self.should_wake,
2661                self.persist_lifecycle_on_commit,
2662                |witness, session_mutation_gate| {
2663                    on_committed(witness)?;
2664                    staged_residency_publication =
2665                        Some(residency_update.stage_under_retained_mutation_gate(
2666                            incarnation,
2667                            tracked_turn_journal,
2668                            session_mutation_gate,
2669                        )?);
2670                    Ok(())
2671                },
2672            )
2673            .await;
2674        let witness = match commit {
2675            Ok(witness) => witness,
2676            Err(error) => {
2677                // Roll residency back to VacantPlaced while this lease still
2678                // retains the exact M guard. The staged value also retains the
2679                // stable residency-slot guard until rollback is complete.
2680                drop(staged_residency_publication);
2681                return Err(error);
2682            }
2683        };
2684        let residency_publication = staged_residency_publication
2685            .take()
2686            .ok_or_else(|| {
2687                RuntimeDriverError::Internal(format!(
2688                    "retained host publication for session {} committed without residency",
2689                    self.witness.session_id()
2690                ))
2691            })?
2692            .finalize()?;
2693        self.mutation_guard.take();
2694        self.armed = false;
2695        Ok((witness, residency_publication))
2696    }
2697
2698    /// Transfer this exact retained attachment into the canonical unregister
2699    /// saga without waiting for service-owned post-stop cleanup.
2700    ///
2701    /// A caller that already owns the service turn-finalization boundary must
2702    /// release that boundary before awaiting the returned completion. The
2703    /// machine first commits Draining and terminalizes the executor; only then
2704    /// does its attachment-local cleanup callback reacquire the service
2705    /// boundary and remove the actor/sidecar assembly.
2706    pub fn begin_abort(
2707        mut self,
2708    ) -> Result<RuntimeExecutorAttachmentRetirementCompletion, RuntimeDriverError> {
2709        let guard = self.mutation_guard.take().ok_or_else(|| {
2710            RuntimeDriverError::Internal(
2711                "committed publication lease lost its mutation fence before abort".to_string(),
2712            )
2713        })?;
2714        let completion = self
2715            .machine
2716            .spawn_executor_attachment_retirement_with_guard(
2717                self.witness.clone(),
2718                guard,
2719                self.cleanup_spawner.clone(),
2720            );
2721        self.armed = false;
2722        Ok(completion)
2723    }
2724
2725    /// Retire this exact retained attachment while the surface retains B.
2726    pub async fn abort_under_runtime_turn_finalization_boundary(
2727        mut self,
2728    ) -> Result<bool, RuntimeDriverError> {
2729        self.machine
2730            .complete_executor_attachment_cleanup_under_runtime_turn_boundary(&self.witness)
2731            .await?;
2732        let guard = self.mutation_guard.take().ok_or_else(|| {
2733            RuntimeDriverError::Internal(
2734                "committed publication lease lost its mutation fence before abort".to_string(),
2735            )
2736        })?;
2737        let machine = Arc::clone(&self.machine);
2738        let witness = self.witness.clone();
2739        let completion = self.cleanup_spawner.spawn(async move {
2740            machine
2741                .unregister_executor_attachment_if_current_with_guard(witness, guard)
2742                .await
2743        });
2744        self.armed = false;
2745        completion.await.map_err(|error| {
2746            RuntimeDriverError::Internal(format!(
2747                "owned committed attachment abort ended without a result: {error}"
2748            ))
2749        })?
2750    }
2751}
2752
2753impl Drop for CommittedRuntimeExecutorAttachmentPublicationLease {
2754    fn drop(&mut self) {
2755        if !self.armed {
2756            return;
2757        }
2758        let Some(guard) = self.mutation_guard.take() else {
2759            return;
2760        };
2761        self.armed = false;
2762        let machine = Arc::clone(&self.machine);
2763        let witness = self.witness.clone();
2764        self.cleanup_spawner.spawn(async move {
2765            if let Err(error) = machine
2766                .unregister_executor_attachment_if_current_with_guard(witness.clone(), guard)
2767                .await
2768            {
2769                tracing::warn!(
2770                    session_id = %witness.session_id(),
2771                    %error,
2772                    "drop-time committed attachment publication abort failed"
2773                );
2774            }
2775        });
2776    }
2777}
2778
2779impl Drop for PendingRuntimeExecutorAttachment {
2780    fn drop(&mut self) {
2781        if !self.armed {
2782            return;
2783        }
2784        let Some(guard) = self.mutation_guard.take() else {
2785            return;
2786        };
2787        self.armed = false;
2788        let machine = Arc::clone(&self.machine);
2789        let witness = self.witness.clone();
2790        self.cleanup_spawner.spawn(async move {
2791            if let Err(error) = machine
2792                .abort_pending_executor_attachment(witness.clone(), guard)
2793                .await
2794            {
2795                tracing::warn!(
2796                    session_id = %witness.session_id(),
2797                    %error,
2798                    "drop-time exact runtime attachment abort failed"
2799                );
2800            }
2801        });
2802    }
2803}
2804
2805impl std::fmt::Debug for PreparedSessionMaterialization {
2806    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2807        formatter
2808            .debug_struct("PreparedSessionMaterialization")
2809            .field("session_id", self.bindings.session_id())
2810            .field("epoch_id", self.bindings.epoch_id())
2811            .field("claim_id", &self.claim_id)
2812            .field("armed", &self.armed)
2813            .finish()
2814    }
2815}
2816
2817impl std::fmt::Debug for PreparedAttachedSessionActorRecovery {
2818    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2819        formatter
2820            .debug_struct("PreparedAttachedSessionActorRecovery")
2821            .field("session_id", self.bindings.session_id())
2822            .field("epoch_id", self.bindings.epoch_id())
2823            .field("witness", &self.witness)
2824            .field("armed", &self.armed)
2825            .finish()
2826    }
2827}
2828
2829impl PreparedAttachedSessionActorRecovery {
2830    fn new(
2831        bindings: meerkat_core::SessionRuntimeBindings,
2832        witness: RuntimeExecutorAttachmentWitness,
2833        claim_id: uuid::Uuid,
2834        claim_state: Arc<std::sync::Mutex<crate::RuntimeActorMaterializationClaimState>>,
2835        previous_phase: crate::RuntimeActorMaterializationClaimPhase,
2836        mutation_guard: crate::tokio::sync::OwnedMutexGuard<()>,
2837    ) -> Self {
2838        Self {
2839            bindings,
2840            witness,
2841            claim_id,
2842            claim_state,
2843            previous_phase,
2844            mutation_guard: Some(mutation_guard),
2845            armed: true,
2846        }
2847    }
2848
2849    pub fn bindings(&self) -> &meerkat_core::SessionRuntimeBindings {
2850        &self.bindings
2851    }
2852
2853    pub fn bindings_clone(&self) -> meerkat_core::SessionRuntimeBindings {
2854        self.bindings.clone()
2855    }
2856
2857    pub fn witness(&self) -> &RuntimeExecutorAttachmentWitness {
2858        &self.witness
2859    }
2860
2861    /// Commit successful actor reconstruction while preserving the same exact
2862    /// serving executor attachment.
2863    pub fn commit_actor(&mut self) -> Result<(), RuntimeDriverError> {
2864        if !self.armed {
2865            return Err(RuntimeDriverError::StaleAuthority {
2866                reason: "attached actor-recovery lease is no longer active".to_string(),
2867            });
2868        }
2869        let changed = {
2870            let mut state = self
2871                .claim_state
2872                .lock()
2873                .unwrap_or_else(std::sync::PoisonError::into_inner);
2874            if !state.exact_claim_is(
2875                self.claim_id,
2876                &[crate::RuntimeActorMaterializationClaimPhase::ActorMaterializedPendingCommit],
2877            ) {
2878                return Err(RuntimeDriverError::StaleAuthority {
2879                    reason: format!(
2880                        "actor recovery for session {} did not retain its exact materialization claim",
2881                        self.bindings.session_id()
2882                    ),
2883                });
2884            }
2885            state.current = None;
2886            state.phase = crate::RuntimeActorMaterializationClaimPhase::RetainedActor;
2887            Arc::clone(&state.changed)
2888        };
2889        changed.notify_waiters();
2890        self.armed = false;
2891        self.mutation_guard.take();
2892        Ok(())
2893    }
2894}
2895
2896impl Drop for PreparedAttachedSessionActorRecovery {
2897    fn drop(&mut self) {
2898        if !self.armed {
2899            return;
2900        }
2901        let changed = {
2902            let mut state = self
2903                .claim_state
2904                .lock()
2905                .unwrap_or_else(std::sync::PoisonError::into_inner);
2906            if state.current == Some(self.claim_id) {
2907                state.current = None;
2908                state.phase = if state.phase
2909                    == crate::RuntimeActorMaterializationClaimPhase::ActorMaterializedPendingCommit
2910                {
2911                    crate::RuntimeActorMaterializationClaimPhase::RetainedActor
2912                } else {
2913                    self.previous_phase
2914                };
2915                Some(Arc::clone(&state.changed))
2916            } else {
2917                None
2918            }
2919        };
2920        if let Some(changed) = changed {
2921            changed.notify_waiters();
2922        }
2923        self.armed = false;
2924        self.mutation_guard.take();
2925    }
2926}
2927
2928impl PreparedSessionMaterialization {
2929    fn new(
2930        machine: Arc<MeerkatMachine>,
2931        bindings: meerkat_core::SessionRuntimeBindings,
2932        claim_id: uuid::Uuid,
2933        cleanup_spawner: MachineCleanupTaskSpawner,
2934    ) -> Result<Self, RuntimeDriverError> {
2935        let authority = bindings
2936            .__runtime_authority()
2937            .downcast_ref::<crate::SessionRuntimeBindingsAuthority>()
2938            .ok_or_else(|| RuntimeDriverError::ValidationFailed {
2939                reason: "prepared materialization lacks MeerkatMachine authority".to_string(),
2940            })?;
2941        if authority.materialization_claim_id != Some(claim_id) {
2942            return Err(RuntimeDriverError::StaleAuthority {
2943                reason: "prepared materialization binding lost its exact claim".to_string(),
2944            });
2945        }
2946        let claim_state = Arc::clone(&authority.materialization_claim_state);
2947        Ok(Self {
2948            machine,
2949            bindings,
2950            claim_id,
2951            claim_state,
2952            cleanup_spawner,
2953            archived_resume_authorization_issued: false,
2954            armed: true,
2955        })
2956    }
2957
2958    pub fn bindings(&self) -> &meerkat_core::SessionRuntimeBindings {
2959        &self.bindings
2960    }
2961
2962    pub fn bindings_clone(&self) -> meerkat_core::SessionRuntimeBindings {
2963        self.bindings.clone()
2964    }
2965
2966    pub fn session_id(&self) -> &SessionId {
2967        self.bindings.session_id()
2968    }
2969
2970    #[doc(hidden)]
2971    pub fn cleanup_task_spawner(&self) -> RuntimeCleanupTaskSpawner {
2972        RuntimeCleanupTaskSpawner {
2973            inner: self.cleanup_spawner.clone(),
2974        }
2975    }
2976
2977    /// Whether this lease still owns the exact process-local materialization
2978    /// claim that fences same-session actor creation and executor attachment.
2979    #[must_use]
2980    pub async fn owns_current_materialization_claim(&self) -> bool {
2981        if !self.armed {
2982            return false;
2983        }
2984        let Some(_mutation_guard) = self
2985            .machine
2986            .lock_current_session_mutation_gate(self.session_id())
2987            .await
2988        else {
2989            return false;
2990        };
2991        let Ok(authority) = crate::validated_session_runtime_bindings_authority(&self.bindings)
2992        else {
2993            return false;
2994        };
2995        let sessions = self.machine.sessions.read().await;
2996        sessions.get(self.session_id()).is_some_and(|entry| {
2997            entry.epoch_id == *self.bindings.epoch_id()
2998                && Arc::ptr_eq(&entry.materialization_claim_state, &self.claim_state)
2999                && Arc::ptr_eq(&entry.dsl_authority, &authority.dsl_authority)
3000                && Arc::ptr_eq(&entry.handle_teardown_gate, &authority.teardown_gate)
3001                && !entry.physical_attachment_is_live()
3002                && self
3003                    .claim_state
3004                    .lock()
3005                    .unwrap_or_else(std::sync::PoisonError::into_inner)
3006                    .exact_claim_is(
3007                        self.claim_id,
3008                        &[
3009                            crate::RuntimeActorMaterializationClaimPhase::Prepared,
3010                            crate::RuntimeActorMaterializationClaimPhase::Staged,
3011                            crate::RuntimeActorMaterializationClaimPhase::ActorCreating,
3012                            crate::RuntimeActorMaterializationClaimPhase::ActorMaterializedPendingCommit,
3013                            crate::RuntimeActorMaterializationClaimPhase::Aborting,
3014                        ],
3015                    )
3016        })
3017    }
3018
3019    /// Mint the non-cloneable authorization for actor construction at an
3020    /// exact Archived+Retired revival midpoint.
3021    ///
3022    /// The returned token is useful only while this prepared lease still owns
3023    /// the same claim. Consumption revalidates the current machine entry,
3024    /// provisional cleanup installation, and exact Retired phase under the
3025    /// session mutation gate.
3026    pub fn archived_resume_authorization(
3027        &mut self,
3028    ) -> Result<ArchivedSessionActorMaterializationAuthorization, RuntimeDriverError> {
3029        if !self.armed {
3030            return Err(RuntimeDriverError::StaleAuthority {
3031                reason: format!(
3032                    "prepared materialization for session {} no longer owns archived-resume authority",
3033                    self.session_id()
3034                ),
3035            });
3036        }
3037        if self.archived_resume_authorization_issued {
3038            return Err(RuntimeDriverError::StaleAuthority {
3039                reason: format!(
3040                    "prepared materialization for session {} already issued its one-shot archived-resume authorization",
3041                    self.session_id()
3042                ),
3043            });
3044        }
3045        let authority = self
3046            .bindings
3047            .__runtime_authority()
3048            .downcast_ref::<crate::SessionRuntimeBindingsAuthority>()
3049            .ok_or_else(|| RuntimeDriverError::ValidationFailed {
3050                reason: "prepared archived-resume materialization lacks machine authority"
3051                    .to_string(),
3052            })?;
3053        if authority.materialization_claim_id != Some(self.claim_id)
3054            || !Arc::ptr_eq(&authority.materialization_claim_state, &self.claim_state)
3055        {
3056            return Err(RuntimeDriverError::StaleAuthority {
3057                reason: format!(
3058                    "prepared archived-resume claim for session {} is no longer exact",
3059                    self.session_id()
3060                ),
3061            });
3062        }
3063        let authorization = ArchivedSessionActorMaterializationAuthorization {
3064            machine: Arc::clone(&self.machine),
3065            session_id: self.session_id().clone(),
3066            epoch_id: self.bindings.epoch_id().clone(),
3067            claim_id: self.claim_id,
3068            claim_state: Arc::clone(&self.claim_state),
3069            dsl_authority: Arc::clone(&authority.dsl_authority),
3070            teardown_gate: Arc::clone(&authority.teardown_gate),
3071        };
3072        self.archived_resume_authorization_issued = true;
3073        Ok(authorization)
3074    }
3075
3076    /// Acquire the exact post-actor commit lease for archived revival.
3077    /// Actor construction must already have consumed the one-shot
3078    /// authorization and committed the exact claim into
3079    /// `ActorMaterializedPendingCommit`.
3080    pub async fn acquire_archived_resume_commit_lease(
3081        &self,
3082    ) -> Result<PreparedArchivedResumeCommitLease, RuntimeDriverError> {
3083        if !self.armed || !self.archived_resume_authorization_issued {
3084            return Err(RuntimeDriverError::StaleAuthority {
3085                reason: format!(
3086                    "prepared materialization for session {} does not own post-actor archived-resume authority",
3087                    self.session_id()
3088                ),
3089            });
3090        }
3091        let mutation_guard = self
3092            .machine
3093            .lock_current_session_mutation_gate(self.session_id())
3094            .await
3095            .ok_or_else(|| RuntimeDriverError::StaleAuthority {
3096                reason: format!(
3097                    "archived-resume session {} disappeared before commit lease acquisition",
3098                    self.session_id()
3099                ),
3100            })?;
3101        let exact = {
3102            let sessions = self.machine.sessions.read().await;
3103            sessions.get(self.session_id()).is_some_and(|entry| {
3104                entry.epoch_id == *self.bindings.epoch_id()
3105                    && Arc::ptr_eq(&entry.materialization_claim_state, &self.claim_state)
3106                    && !entry.physical_attachment_is_live()
3107                    && matches!(
3108                        entry.control_snapshot().phase,
3109                        RuntimeState::Retired | RuntimeState::Idle
3110                    )
3111                    && self
3112                        .claim_state
3113                        .lock()
3114                        .unwrap_or_else(std::sync::PoisonError::into_inner)
3115                        .exact_claim_is(
3116                            self.claim_id,
3117                            &[crate::RuntimeActorMaterializationClaimPhase::ActorMaterializedPendingCommit],
3118                        )
3119            })
3120        };
3121        if !exact {
3122            return Err(RuntimeDriverError::StaleAuthority {
3123                reason: format!(
3124                    "archived-resume session {} lost its exact quiescent post-actor claim",
3125                    self.session_id()
3126                ),
3127            });
3128        }
3129        Ok(PreparedArchivedResumeCommitLease {
3130            machine: Arc::clone(&self.machine),
3131            session_id: self.session_id().clone(),
3132            epoch_id: self.bindings.epoch_id().clone(),
3133            claim_id: self.claim_id,
3134            claim_state: Arc::clone(&self.claim_state),
3135            _mutation_guard: mutation_guard,
3136        })
3137    }
3138
3139    /// Attach the executor that consumes this exact actor-materialization
3140    /// claim. Generic machine attachment is intentionally unable to cross an
3141    /// outstanding prepare/actor-create transaction; this method transfers
3142    /// that authority to the returned pending attachment lease.
3143    pub async fn ensure_executor_attachment<F>(
3144        &mut self,
3145        executor_factory: F,
3146    ) -> Result<EnsureRuntimeExecutorAttachment, RuntimeDriverError>
3147    where
3148        F: FnOnce(
3149                RuntimeExecutorAttachmentWitness,
3150            ) -> Box<dyn meerkat_core::lifecycle::CoreExecutor>
3151            + Send
3152            + 'static,
3153    {
3154        self.ensure_executor_attachment_with_boundary_mode(executor_factory, false)
3155            .await
3156    }
3157
3158    /// Attach the executor while the shared session facade already owns the
3159    /// service turn-finalization boundary.
3160    ///
3161    /// Startup rejection must use the non-reentrant post-stop cleanup path in
3162    /// this mode. Otherwise an unserved attachment can begin ordinary cleanup
3163    /// while retaining M and deadlock trying to reacquire the caller's B.
3164    #[doc(hidden)]
3165    pub async fn ensure_executor_attachment_under_runtime_turn_finalization_boundary<F>(
3166        &mut self,
3167        executor_factory: F,
3168    ) -> Result<EnsureRuntimeExecutorAttachment, RuntimeDriverError>
3169    where
3170        F: FnOnce(
3171                RuntimeExecutorAttachmentWitness,
3172            ) -> Box<dyn meerkat_core::lifecycle::CoreExecutor>
3173            + Send
3174            + 'static,
3175    {
3176        self.ensure_executor_attachment_with_boundary_mode(executor_factory, true)
3177            .await
3178    }
3179
3180    async fn ensure_executor_attachment_with_boundary_mode<F>(
3181        &mut self,
3182        executor_factory: F,
3183        turn_finalization_boundary_already_held: bool,
3184    ) -> Result<EnsureRuntimeExecutorAttachment, RuntimeDriverError>
3185    where
3186        F: FnOnce(
3187                RuntimeExecutorAttachmentWitness,
3188            ) -> Box<dyn meerkat_core::lifecycle::CoreExecutor>
3189            + Send
3190            + 'static,
3191    {
3192        if !self.armed {
3193            return Err(RuntimeDriverError::StaleAuthority {
3194                reason: format!(
3195                    "prepared materialization for session {} no longer owns attachment authority",
3196                    self.session_id()
3197                ),
3198            });
3199        }
3200        let expected_claim = RuntimeExecutorAttachmentMaterializationClaim {
3201            claim_id: self.claim_id,
3202            claim_state: Arc::clone(&self.claim_state),
3203            epoch_id: self.bindings.epoch_id().clone(),
3204        };
3205        let outcome = self
3206            .machine
3207            .ensure_session_with_executor_factory_for_materialization(
3208                self.session_id().clone(),
3209                expected_claim,
3210                turn_finalization_boundary_already_held,
3211                executor_factory,
3212            )
3213            .await?;
3214        if matches!(&outcome, EnsureRuntimeExecutorAttachment::Pending(_)) {
3215            // The exact machine attachment now owns rollback. Its publication
3216            // cleared this claim synchronously, so dropping this shell must not
3217            // start a second cleanup saga.
3218            self.armed = false;
3219        }
3220        Ok(outcome)
3221    }
3222
3223    fn request_abort(&self) -> bool {
3224        let mut state = self
3225            .claim_state
3226            .lock()
3227            .unwrap_or_else(std::sync::PoisonError::into_inner);
3228        if !state.exact_claim_is(
3229            self.claim_id,
3230            &[
3231                crate::RuntimeActorMaterializationClaimPhase::Prepared,
3232                crate::RuntimeActorMaterializationClaimPhase::Staged,
3233                crate::RuntimeActorMaterializationClaimPhase::ActorCreating,
3234                crate::RuntimeActorMaterializationClaimPhase::ActorMaterializedPendingCommit,
3235                crate::RuntimeActorMaterializationClaimPhase::Aborting,
3236            ],
3237        ) {
3238            return false;
3239        }
3240        state.phase = crate::RuntimeActorMaterializationClaimPhase::Aborting;
3241        true
3242    }
3243
3244    /// Roll back this exact materialization now. A pre-existing runtime entry is
3245    /// preserved after its exact provisional cleanup; an entry inserted for this
3246    /// transaction is fully unregistered.
3247    pub async fn rollback_now(&mut self) -> Result<bool, RuntimeDriverError> {
3248        self.rollback_now_with_turn_finalization_boundary(false)
3249            .await
3250    }
3251
3252    /// Exact rollback for a caller that already owns the persistent service's
3253    /// turn-finalization boundary.
3254    pub async fn rollback_now_under_turn_finalization_boundary(
3255        &mut self,
3256    ) -> Result<bool, RuntimeDriverError> {
3257        self.rollback_now_with_turn_finalization_boundary(true)
3258            .await
3259    }
3260
3261    async fn rollback_now_with_turn_finalization_boundary(
3262        &mut self,
3263        turn_finalization_boundary_already_held: bool,
3264    ) -> Result<bool, RuntimeDriverError> {
3265        if !self.armed {
3266            return Ok(false);
3267        }
3268        if !self.request_abort() {
3269            self.armed = false;
3270            return Ok(false);
3271        }
3272        let rolled_back = self
3273            .machine
3274            .abort_prepared_session_materialization_claim(
3275                self.bindings.session_id(),
3276                self.claim_id,
3277                Some(self.bindings.epoch_id()),
3278                Some(&self.claim_state),
3279                turn_finalization_boundary_already_held,
3280            )
3281            .await?;
3282        self.armed = false;
3283        Ok(rolled_back)
3284    }
3285
3286    /// Publish a staged-session owner. The staged registry must subsequently
3287    /// retain the exact bindings and call exact rollback when it abandons the
3288    /// slot; actor construction may later consume the same claim.
3289    pub async fn commit_staged(&mut self) -> Result<(), RuntimeDriverError> {
3290        self.machine
3291            .commit_prepared_session_materialization_staged(&self.bindings)
3292            .await?;
3293        self.armed = false;
3294        Ok(())
3295    }
3296
3297    /// Retain a successfully created actor before executor attachment. The
3298    /// provisional cleanup stays on the machine entry for raw unregister, while
3299    /// this transaction can no longer roll the registration back.
3300    pub async fn commit_actor_unattached(&mut self) -> Result<(), RuntimeDriverError> {
3301        self.machine
3302            .commit_prepared_session_actor_unattached(&self.bindings)
3303            .await?;
3304        self.armed = false;
3305        Ok(())
3306    }
3307}
3308
3309impl Drop for PreparedSessionMaterialization {
3310    fn drop(&mut self) {
3311        if !self.armed || !self.request_abort() {
3312            return;
3313        }
3314        let machine = Arc::clone(&self.machine);
3315        let session_id = self.bindings.session_id().clone();
3316        let epoch_id = self.bindings.epoch_id().clone();
3317        let claim_id = self.claim_id;
3318        let claim_state = Arc::clone(&self.claim_state);
3319        self.cleanup_spawner.spawn(async move {
3320            if let Err(error) = machine
3321                .abort_prepared_session_materialization_claim(
3322                    &session_id,
3323                    claim_id,
3324                    Some(&epoch_id),
3325                    Some(&claim_state),
3326                    false,
3327                )
3328                .await
3329            {
3330                tracing::warn!(
3331                    %session_id,
3332                    %error,
3333                    "drop-time exact prepared materialization rollback failed"
3334                );
3335            }
3336        });
3337    }
3338}
3339
3340pub(super) struct PendingPreparedMaterialization {
3341    machine: Arc<MeerkatMachine>,
3342    session_id: SessionId,
3343    claim_id: uuid::Uuid,
3344    cleanup_spawner: MachineCleanupTaskSpawner,
3345    claim_state_slot: Arc<
3346        std::sync::Mutex<
3347            Option<Arc<std::sync::Mutex<crate::RuntimeActorMaterializationClaimState>>>,
3348        >,
3349    >,
3350    armed: bool,
3351}
3352
3353impl PendingPreparedMaterialization {
3354    pub(super) fn new(
3355        machine: Arc<MeerkatMachine>,
3356        session_id: SessionId,
3357        claim_id: uuid::Uuid,
3358    ) -> Result<Self, RuntimeDriverError> {
3359        Ok(Self {
3360            machine,
3361            session_id,
3362            claim_id,
3363            cleanup_spawner: MachineCleanupTaskSpawner::acquire()?,
3364            claim_state_slot: Arc::new(std::sync::Mutex::new(None)),
3365            armed: true,
3366        })
3367    }
3368
3369    fn cleanup_spawner(&self) -> MachineCleanupTaskSpawner {
3370        self.cleanup_spawner.clone()
3371    }
3372
3373    pub(super) fn claim_state_slot(
3374        &self,
3375    ) -> Arc<
3376        std::sync::Mutex<
3377            Option<Arc<std::sync::Mutex<crate::RuntimeActorMaterializationClaimState>>>,
3378        >,
3379    > {
3380        Arc::clone(&self.claim_state_slot)
3381    }
3382
3383    pub(super) fn disarm(&mut self) {
3384        self.armed = false;
3385    }
3386}
3387
3388impl Drop for PendingPreparedMaterialization {
3389    fn drop(&mut self) {
3390        if !self.armed {
3391            return;
3392        }
3393        let claim_state = self
3394            .claim_state_slot
3395            .lock()
3396            .unwrap_or_else(std::sync::PoisonError::into_inner)
3397            .clone();
3398        if let Some(claim_state) = claim_state {
3399            let mut state = claim_state
3400                .lock()
3401                .unwrap_or_else(std::sync::PoisonError::into_inner);
3402            if state.current == Some(self.claim_id)
3403                && state.phase != crate::RuntimeActorMaterializationClaimPhase::RetainedActor
3404            {
3405                state.phase = crate::RuntimeActorMaterializationClaimPhase::Aborting;
3406            }
3407        }
3408        let machine = Arc::clone(&self.machine);
3409        let session_id = self.session_id.clone();
3410        let claim_id = self.claim_id;
3411        self.cleanup_spawner.spawn(async move {
3412            if let Err(error) = machine
3413                .abort_prepared_session_materialization_claim(
3414                    &session_id,
3415                    claim_id,
3416                    None,
3417                    None,
3418                    false,
3419                )
3420                .await
3421            {
3422                tracing::warn!(
3423                    %session_id,
3424                    %error,
3425                    "cancelled binding preparation exact rollback failed"
3426                );
3427            }
3428        });
3429    }
3430}
3431
3432/// Exact same-session mutation lease used to commit a direct SessionService
3433/// turn without reacquiring the machine gate while the session recovery gate
3434/// is held.
3435///
3436/// The session layer acquires this only after releasing its execution-time
3437/// recovery guard, then reacquires recovery in the canonical
3438/// machine-mutation -> recovery order for snapshot commit/checkpoint.
3439pub struct MachineServiceTurnCommitLease {
3440    session_id: SessionId,
3441    driver: SharedDriver,
3442    _mutation_guard: crate::tokio::sync::OwnedMutexGuard<()>,
3443}
3444
3445/// Exact runtime-entry identity captured before a direct SessionService turn.
3446/// It carries no lock; terminal commit later acquires mutation authority only
3447/// if this same driver is still current.
3448pub struct MachineServiceTurnIdentity {
3449    session_id: SessionId,
3450    driver: SharedDriver,
3451}
3452
3453impl MachineSessionArchiveLease {
3454    /// Whether archive preparation inserted the captured in-memory
3455    /// registration from durable authority.
3456    pub fn owns_recovered_registration(&self) -> bool {
3457        self.recovered_registration_for_archive
3458    }
3459
3460    /// Exact runtime lifecycle observed while this lease owns the session's
3461    /// mutation gate.
3462    pub async fn runtime_state(&self) -> RuntimeState {
3463        self.driver.lock().await.runtime_state()
3464    }
3465
3466    /// Whether the captured runtime half has already reached the durable
3467    /// archive terminal. The lease owns the session mutation gate, so this
3468    /// observation cannot race a lifecycle transition.
3469    pub async fn runtime_is_retired(&self) -> bool {
3470        self.runtime_state().await == RuntimeState::Retired
3471    }
3472}
3473
3474/// Capability bundle for an attached runtime loop.
3475///
3476/// Keep all loop-related handles together so "attached vs detached" cannot
3477/// drift into partially-populated shell state.
3478#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3479struct RuntimeLoopAttachmentId(uuid::Uuid);
3480
3481impl RuntimeLoopAttachmentId {
3482    fn new() -> Self {
3483        Self(uuid::Uuid::new_v4())
3484    }
3485}
3486
3487/// Mechanical proof captured while M is held for one executor-effect
3488/// dispatch. A callback may release M, but only this exact attachment may
3489/// receive the resulting effect after revalidation.
3490struct RuntimeEffectDispatchAttachmentWitness {
3491    mutation_gate: Arc<Mutex<()>>,
3492    driver: SharedDriver,
3493    dsl_authority: Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
3494    attachment_id: RuntimeLoopAttachmentId,
3495    effect_tx: mpsc::Sender<crate::effect::RuntimeEffect>,
3496}
3497
3498/// Exact executor attachment captured before a cooperative live-boundary
3499/// callback. Publication resumes only after every attachment-local component
3500/// is revalidated.
3501struct RuntimeLiveBoundaryAttachmentWitness {
3502    mutation_gate: Arc<Mutex<()>>,
3503    driver: SharedDriver,
3504    dsl_authority: Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
3505    attachment_id: RuntimeLoopAttachmentId,
3506    boundary_handle: Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>,
3507}
3508
3509struct RuntimeAcceptedBoundaryCancelPlan {
3510    witness: RuntimeEffectDispatchAttachmentWitness,
3511    boundary_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>>,
3512    pending_dispatch: PendingBoundaryCancelDispatchGuard,
3513    expected_run_id: RunId,
3514    projected_effect: crate::effect::ProjectedRuntimeEffect,
3515    dispatch_generation: u64,
3516    dispatch_lifecycle_phase: dsl::MeerkatPhase,
3517}
3518
3519#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3520enum BoundaryCancelDispatchState {
3521    Current,
3522    ConsumedConverged,
3523    SameGenerationOrphan,
3524    Superseded,
3525}
3526
3527struct RuntimeEffectDispatchMemberAuthority {
3528    lease: MemberEffectAuthorityLease,
3529    expected_member: Option<meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation>,
3530}
3531
3532/// Exact-generation compensation for a boundary dispatch abandoned before
3533/// enqueue. It can only clear the generation it captured.
3534struct PendingBoundaryCancelDispatchGuard {
3535    dsl_authority: Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
3536    dispatch_generation: u64,
3537    armed: bool,
3538}
3539
3540impl PendingBoundaryCancelDispatchGuard {
3541    fn new(
3542        dsl_authority: Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
3543        dispatch_generation: u64,
3544    ) -> Self {
3545        Self {
3546            dsl_authority,
3547            dispatch_generation,
3548            armed: true,
3549        }
3550    }
3551
3552    fn disarm(&mut self) {
3553        self.armed = false;
3554    }
3555}
3556
3557impl Drop for PendingBoundaryCancelDispatchGuard {
3558    fn drop(&mut self) {
3559        if self.armed
3560            && let Err(error) = MeerkatMachine::abort_dsl_boundary_cancel_dispatch_if_current(
3561                &self.dsl_authority,
3562                self.dispatch_generation,
3563            )
3564        {
3565            tracing::error!(
3566                dispatch_generation = self.dispatch_generation,
3567                error = %error,
3568                "failed to compensate an abandoned boundary-cancel dispatch"
3569            );
3570        }
3571    }
3572}
3573
3574/// Mechanical fallback for a durably accepted input whose later publication
3575/// step exits early. The wake channel belongs to the captured attachment.
3576struct AcceptedIngressFallbackWakeGuard {
3577    wake_tx: Option<mpsc::Sender<()>>,
3578    armed: bool,
3579}
3580
3581impl AcceptedIngressFallbackWakeGuard {
3582    fn new(wake_tx: Option<mpsc::Sender<()>>, armed: bool) -> Self {
3583        Self { wake_tx, armed }
3584    }
3585
3586    fn disarm(&mut self) {
3587        self.armed = false;
3588    }
3589}
3590
3591impl Drop for AcceptedIngressFallbackWakeGuard {
3592    fn drop(&mut self) {
3593        if self.armed
3594            && let Some(wake_tx) = self.wake_tx.as_ref()
3595        {
3596            let _ = wake_tx.try_send(());
3597        }
3598    }
3599}
3600
3601struct RuntimeLoopAttachment {
3602    id: RuntimeLoopAttachmentId,
3603    wake_tx: mpsc::Sender<()>,
3604    effect_tx: mpsc::Sender<crate::effect::RuntimeEffect>,
3605    serving_release: Option<crate::runtime_loop::RuntimeLoopServingRelease>,
3606    boundary_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>>,
3607    interrupt_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorInterruptHandle>>,
3608    loop_handle: tokio::task::JoinHandle<()>,
3609}
3610
3611/// Mechanical runtime-loop channel slot.
3612enum RuntimeLoopAttachmentSlot {
3613    Empty,
3614    Pending(RuntimeLoopAttachment),
3615    Attached(RuntimeLoopAttachment),
3616}
3617
3618impl RuntimeSessionEntry {
3619    fn dsl_mutation_blocked_by_unregister(
3620        &self,
3621        session_id: &SessionId,
3622    ) -> Option<RuntimeDriverError> {
3623        if self.pending_unregister_finalization.is_some() {
3624            return Some(RuntimeDriverError::UnregisterFinalizationOutcomeUnknown {
3625                reason: format!(
3626                    "session {session_id} retains an ambiguous unregister finalization; retry unregister before applying any other lifecycle mutation"
3627                ),
3628            });
3629        }
3630        self.handle_teardown_gate
3631            .is_closed()
3632            .then(|| RuntimeDriverError::ValidationFailed {
3633                reason: format!("session {session_id} is a teardown-only unregister retry anchor"),
3634            })
3635    }
3636
3637    fn registration_blocked_by_unregister(
3638        &self,
3639        session_id: &SessionId,
3640    ) -> Option<RuntimeDriverError> {
3641        if let Some(error) = self.dsl_mutation_blocked_by_unregister(session_id) {
3642            return Some(error);
3643        }
3644        if self.unregister_coordinator.is_some() {
3645            return Some(RuntimeDriverError::NotReady {
3646                state: self.control_snapshot().phase,
3647            });
3648        }
3649        let Some(coordinator) = self.runtime_stop_cleanup_coordinator.as_ref() else {
3650            return None;
3651        };
3652        match coordinator.result_rx.borrow().clone() {
3653            None => Some(RuntimeDriverError::RuntimeStopInProgress {
3654                runtime_id: self.runtime_id.clone(),
3655            }),
3656            Some(Ok(())) => None,
3657            Some(Err(error)) => Some(error),
3658        }
3659    }
3660
3661    fn control_snapshot(&self) -> crate::driver::ephemeral::RuntimeControlProjection {
3662        self.control_projection
3663            .read()
3664            .map(|guard| guard.clone())
3665            .unwrap_or_else(|poisoned| {
3666                tracing::error!("runtime control projection lock poisoned");
3667                poisoned.into_inner().clone()
3668            })
3669    }
3670
3671    fn sync_control_projection_from_dsl_authority(&self) {
3672        let next = {
3673            let authority = self
3674                .dsl_authority
3675                .lock()
3676                .unwrap_or_else(std::sync::PoisonError::into_inner);
3677            crate::driver::ephemeral::RuntimeControlProjection {
3678                phase: crate::meerkat_machine::dsl_authority::runtime_phase_from_authority(
3679                    &authority,
3680                ),
3681                current_run_id:
3682                    crate::meerkat_machine::dsl_authority::current_run_id_from_authority(&authority),
3683                pre_run_phase: crate::meerkat_machine::dsl_authority::pre_run_phase_from_authority(
3684                    &authority,
3685                ),
3686            }
3687        };
3688        *self
3689            .control_projection
3690            .write()
3691            .unwrap_or_else(std::sync::PoisonError::into_inner) = next;
3692    }
3693
3694    fn physical_attachment_is_live(&self) -> bool {
3695        match &self.attachment_slot {
3696            RuntimeLoopAttachmentSlot::Pending(attachment)
3697            | RuntimeLoopAttachmentSlot::Attached(attachment) => {
3698                !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed()
3699            }
3700            RuntimeLoopAttachmentSlot::Empty => false,
3701        }
3702    }
3703
3704    fn attachment_is_live(&self) -> bool {
3705        matches!(
3706            &self.attachment_slot,
3707            RuntimeLoopAttachmentSlot::Attached(attachment)
3708                if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed()
3709        )
3710    }
3711
3712    fn generated_executor_registration_active(&self) -> bool {
3713        let authority = self
3714            .dsl_authority
3715            .lock()
3716            .unwrap_or_else(std::sync::PoisonError::into_inner);
3717        matches!(
3718            authority.state().registration_phase,
3719            dsl::RegistrationPhase::Active
3720        )
3721    }
3722
3723    fn generated_executor_registration_has_viable_attachment(&self) -> bool {
3724        self.generated_executor_registration_active()
3725            && match &self.attachment_slot {
3726                RuntimeLoopAttachmentSlot::Empty => true,
3727                RuntimeLoopAttachmentSlot::Pending(_) | RuntimeLoopAttachmentSlot::Attached(_) => {
3728                    self.physical_attachment_is_live()
3729                }
3730            }
3731    }
3732
3733    fn close_handle_teardown_gate(&self) {
3734        let _guard = self
3735            .dsl_authority
3736            .lock()
3737            .unwrap_or_else(std::sync::PoisonError::into_inner);
3738        self.handle_teardown_gate.close();
3739    }
3740
3741    /// True while the runtime-loop executor registration is `Active` *or*
3742    /// `Draining`. The drain window (`BeginUnregisterSession` → final
3743    /// `UnregisterSession`) keeps the session registered so the in-flight run
3744    /// can still commit and resolve its completion waiters; the runtime-loop
3745    /// driver-authority gate must therefore admit `Draining`, while the
3746    /// registration *claim* check stays `Active`-only (no new attachment may be
3747    /// granted inside the drain window).
3748    fn generated_executor_registration_active_or_draining(&self) -> bool {
3749        let authority = self
3750            .dsl_authority
3751            .lock()
3752            .unwrap_or_else(std::sync::PoisonError::into_inner);
3753        matches!(
3754            authority.state().registration_phase,
3755            dsl::RegistrationPhase::Active | dsl::RegistrationPhase::Draining
3756        )
3757    }
3758
3759    fn generated_service_turn_binding_open(&self, session_id: &SessionId) -> bool {
3760        let authority = self
3761            .dsl_authority
3762            .lock()
3763            .unwrap_or_else(std::sync::PoisonError::into_inner);
3764        let state = authority.state();
3765        self.handle_teardown_gate.is_open()
3766            && state.session_id.as_ref() == Some(&dsl::SessionId::from_domain(session_id))
3767            && state.registration_phase != dsl::RegistrationPhase::Draining
3768    }
3769
3770    fn generated_stop_deferred(&self) -> bool {
3771        self.dsl_authority
3772            .lock()
3773            .unwrap_or_else(std::sync::PoisonError::into_inner)
3774            .state()
3775            .runtime_stop_deferred
3776    }
3777
3778    fn stage_generated_executor_registration_claim(
3779        &self,
3780        session_id: &SessionId,
3781    ) -> Result<StagedSessionDslInput, String> {
3782        let staged = MeerkatMachine::stage_dsl_transition_on_authority(
3783            &self.dsl_authority,
3784            dsl::MeerkatMachineInput::EnsureSessionWithExecutor {
3785                session_id: dsl::SessionId::from_domain(session_id),
3786            },
3787            "EnsureSessionWithExecutor",
3788        )?;
3789        if self.generated_executor_registration_active() {
3790            Ok(staged)
3791        } else {
3792            let mut authority = self
3793                .dsl_authority
3794                .lock()
3795                .unwrap_or_else(std::sync::PoisonError::into_inner);
3796            authority.restore_snapshot(staged.previous_snapshot);
3797            Err("generated MeerkatMachine did not grant active executor registration".into())
3798        }
3799    }
3800
3801    fn stage_generated_executor_exit_observation(&self) -> Result<StagedSessionDslInput, String> {
3802        MeerkatMachine::stage_runtime_owner_dsl_transition_on_authority(
3803            &self.dsl_authority,
3804            crate::meerkat_machine_types::MeerkatMachineFieldlessRuntimeInternalInput::RuntimeExecutorExited,
3805        )
3806    }
3807
3808    /// Returns `true` only if the executor is fully attached with live channels.
3809    /// Used by internal publish logic within `ensure_session_with_executor`.
3810    fn has_live_attachment(&self) -> bool {
3811        self.attachment_is_live()
3812    }
3813
3814    // Attachment publishes the complete independent capability set in one
3815    // atomic slot; bundling it earlier would create a second authority shape.
3816    #[allow(clippy::too_many_arguments)]
3817    fn attach_runtime_loop(
3818        &mut self,
3819        id: RuntimeLoopAttachmentId,
3820        wake_tx: mpsc::Sender<()>,
3821        effect_tx: mpsc::Sender<crate::effect::RuntimeEffect>,
3822        serving_release: crate::runtime_loop::RuntimeLoopServingRelease,
3823        boundary_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>>,
3824        interrupt_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorInterruptHandle>>,
3825        publication_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorPublicationHandle>>,
3826        post_stop_cleanup_handle: Option<
3827            Arc<dyn meerkat_core::lifecycle::CoreExecutorPostStopCleanupHandle>,
3828        >,
3829        machine_managed_post_stop_unregister: bool,
3830        expected_materialization_claim: Option<&RuntimeExecutorAttachmentMaterializationClaim>,
3831        spawned_loop: crate::runtime_loop::SpawnedRuntimeLoop,
3832    ) -> Result<(), crate::runtime_loop::SpawnedRuntimeLoop> {
3833        if spawned_loop.startup_authority_transfer.is_some()
3834            || spawned_loop.serving_release.is_some()
3835        {
3836            return Err(spawned_loop);
3837        }
3838        let claim_changed = {
3839            let mut state = self
3840                .materialization_claim_state
3841                .lock()
3842                .unwrap_or_else(std::sync::PoisonError::into_inner);
3843            match expected_materialization_claim {
3844                Some(expected)
3845                    if Arc::ptr_eq(
3846                        &self.materialization_claim_state,
3847                        &expected.claim_state,
3848                    ) && state.exact_claim_is(
3849                        expected.claim_id,
3850                        &[crate::RuntimeActorMaterializationClaimPhase::ActorMaterializedPendingCommit],
3851                    ) => {}
3852                Some(_) => return Err(spawned_loop),
3853                None
3854                    if state.current.is_none()
3855                        && matches!(
3856                            state.phase,
3857                            crate::RuntimeActorMaterializationClaimPhase::Vacant
3858                                | crate::RuntimeActorMaterializationClaimPhase::RetainedActor
3859                        ) => {}
3860                None => return Err(spawned_loop),
3861            }
3862            let Some(next_legacy_generation) = state.legacy_capability_generation.checked_add(1)
3863            else {
3864                return Err(spawned_loop);
3865            };
3866            state.legacy_capability_generation = next_legacy_generation;
3867            let claim_changed = state.current.take().is_some();
3868            state.phase = crate::RuntimeActorMaterializationClaimPhase::Vacant;
3869            state.rollback_registration_available = false;
3870            claim_changed.then(|| Arc::clone(&state.changed))
3871        };
3872        if let Some(changed) = claim_changed {
3873            changed.notify_waiters();
3874        }
3875        let crate::runtime_loop::SpawnedRuntimeLoop {
3876            loop_handle,
3877            teardown_slot,
3878            startup: _,
3879            startup_authority_transfer: _,
3880            serving_release: _,
3881        } = spawned_loop;
3882        self.provisional_interrupt_handle = None;
3883        self.provisional_materialization_claim_id = None;
3884        self.runtime_stop_cleanup_coordinator = None;
3885        self.runtime_loop_teardown = Some(Arc::clone(&teardown_slot));
3886        self.publication_handle = publication_handle;
3887        self.post_stop_cleanup_handle = post_stop_cleanup_handle;
3888        self.post_stop_cleanup_attachment_id = machine_managed_post_stop_unregister.then_some(id);
3889        self.post_stop_cleanup_complete = !machine_managed_post_stop_unregister;
3890        self.post_stop_cleanup_gate = Arc::new(Mutex::new(()));
3891        self.attachment_slot = RuntimeLoopAttachmentSlot::Pending(RuntimeLoopAttachment {
3892            id,
3893            wake_tx,
3894            effect_tx,
3895            serving_release: Some(serving_release),
3896            boundary_handle,
3897            interrupt_handle,
3898            loop_handle,
3899        });
3900        Ok(())
3901    }
3902
3903    fn owns_runtime_loop_attachment(&self, expected: RuntimeLoopAttachmentId) -> bool {
3904        matches!(
3905            &self.attachment_slot,
3906            RuntimeLoopAttachmentSlot::Pending(attachment)
3907                | RuntimeLoopAttachmentSlot::Attached(attachment)
3908                if attachment.id == expected
3909        )
3910    }
3911
3912    fn live_attachment_id(&self) -> Option<RuntimeLoopAttachmentId> {
3913        match &self.attachment_slot {
3914            RuntimeLoopAttachmentSlot::Pending(attachment)
3915            | RuntimeLoopAttachmentSlot::Attached(attachment)
3916                if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed() =>
3917            {
3918                Some(attachment.id)
3919            }
3920            _ => None,
3921        }
3922    }
3923
3924    /// Detach the runtime-loop channels, returning the loop's `JoinHandle` so a
3925    /// caller can await its quiescence.
3926    ///
3927    /// Dropping the returned `wake_tx`/`effect_tx` (held inside the attachment)
3928    /// closes the loop's receivers, which drives the loop through its canonical
3929    /// `StopRuntimeExecutor` + `RuntimeExecutorExited` exit. The slot is left
3930    /// `Empty`. Returns `None` when no loop is attached.
3931    fn take_runtime_loop_attachment(&mut self) -> Option<RuntimeLoopAttachment> {
3932        match std::mem::replace(&mut self.attachment_slot, RuntimeLoopAttachmentSlot::Empty) {
3933            RuntimeLoopAttachmentSlot::Pending(attachment) => Some(attachment),
3934            RuntimeLoopAttachmentSlot::Attached(attachment) => Some(attachment),
3935            RuntimeLoopAttachmentSlot::Empty => None,
3936        }
3937    }
3938
3939    fn clear_dead_attachment(&mut self) -> bool {
3940        if matches!(
3941            self.attachment_slot,
3942            RuntimeLoopAttachmentSlot::Pending(_) | RuntimeLoopAttachmentSlot::Attached(_)
3943        ) && !self.physical_attachment_is_live()
3944        {
3945            self.attachment_slot = RuntimeLoopAttachmentSlot::Empty;
3946            return true;
3947        }
3948        false
3949    }
3950
3951    fn retire_completed_runtime_stop_after_revival(
3952        &mut self,
3953        session_id: &SessionId,
3954    ) -> Result<(), RuntimeDriverError> {
3955        if !matches!(self.attachment_slot, RuntimeLoopAttachmentSlot::Empty) {
3956            return Err(RuntimeDriverError::Internal(format!(
3957                "revived session {session_id} still carries a runtime-loop attachment"
3958            )));
3959        }
3960        match self.runtime_stop_cleanup_coordinator.as_ref() {
3961            None if self.runtime_loop_teardown.is_none() => return Ok(()),
3962            None => {
3963                return Err(RuntimeDriverError::Internal(format!(
3964                    "revived session {session_id} carries a teardown slot without its stop coordinator"
3965                )));
3966            }
3967            Some(coordinator) => {
3968                let coordinator_slot_is_current = match (
3969                    coordinator.teardown_slot.as_ref(),
3970                    self.runtime_loop_teardown.as_ref(),
3971                ) {
3972                    (Some(coordinator_slot), Some(current_slot)) => {
3973                        Arc::ptr_eq(coordinator_slot, current_slot)
3974                    }
3975                    (None, None) => true,
3976                    _ => false,
3977                };
3978                if !coordinator_slot_is_current {
3979                    return Err(RuntimeDriverError::Internal(format!(
3980                        "revived session {session_id} carries a stale runtime-stop teardown owner"
3981                    )));
3982                }
3983                match coordinator.result_rx.borrow().clone() {
3984                    Some(Ok(())) => {}
3985                    None => {
3986                        return Err(RuntimeDriverError::RuntimeStopInProgress {
3987                            runtime_id: self.runtime_id.clone(),
3988                        });
3989                    }
3990                    Some(Err(error)) => return Err(error),
3991                }
3992            }
3993        }
3994        self.runtime_stop_cleanup_coordinator = None;
3995        self.runtime_loop_teardown = None;
3996        Ok(())
3997    }
3998
3999    fn wake_sender(&self) -> Option<mpsc::Sender<()>> {
4000        match &self.attachment_slot {
4001            RuntimeLoopAttachmentSlot::Attached(attachment)
4002                if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed() =>
4003            {
4004                Some(attachment.wake_tx.clone())
4005            }
4006            _ => None,
4007        }
4008    }
4009
4010    fn effect_sender(&self) -> Option<mpsc::Sender<crate::effect::RuntimeEffect>> {
4011        match &self.attachment_slot {
4012            RuntimeLoopAttachmentSlot::Attached(attachment)
4013                if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed() =>
4014            {
4015                Some(attachment.effect_tx.clone())
4016            }
4017            _ => None,
4018        }
4019    }
4020
4021    fn boundary_handle(
4022        &self,
4023    ) -> Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>> {
4024        match &self.attachment_slot {
4025            RuntimeLoopAttachmentSlot::Attached(attachment)
4026                if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed() =>
4027            {
4028                attachment.boundary_handle.clone()
4029            }
4030            _ => None,
4031        }
4032    }
4033
4034    fn interrupt_handle(
4035        &self,
4036    ) -> Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorInterruptHandle>> {
4037        match &self.attachment_slot {
4038            RuntimeLoopAttachmentSlot::Attached(attachment)
4039                if !attachment.wake_tx.is_closed() && !attachment.effect_tx.is_closed() =>
4040            {
4041                attachment.interrupt_handle.clone()
4042            }
4043            _ => self.provisional_interrupt_handle.clone(),
4044        }
4045    }
4046
4047    fn publication_handle(
4048        &self,
4049    ) -> Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorPublicationHandle>> {
4050        self.publication_handle.clone()
4051    }
4052
4053    fn install_provisional_interrupt_handle(
4054        &mut self,
4055        claim_id: uuid::Uuid,
4056        handle: Arc<dyn meerkat_core::lifecycle::CoreExecutorInterruptHandle>,
4057    ) {
4058        if !self.physical_attachment_is_live() {
4059            self.provisional_materialization_claim_id = Some(claim_id);
4060            self.provisional_interrupt_handle = Some(handle);
4061        }
4062    }
4063
4064    fn install_provisional_post_stop_cleanup_handle(
4065        &mut self,
4066        claim_id: uuid::Uuid,
4067        handle: Arc<dyn meerkat_core::lifecycle::CoreExecutorPostStopCleanupHandle>,
4068    ) {
4069        if !self.physical_attachment_is_live() {
4070            self.provisional_materialization_claim_id = Some(claim_id);
4071            self.post_stop_cleanup_handle = Some(handle);
4072            self.post_stop_cleanup_attachment_id = Some(RuntimeLoopAttachmentId::new());
4073            self.post_stop_cleanup_complete = false;
4074            self.post_stop_cleanup_gate = Arc::new(Mutex::new(()));
4075        }
4076    }
4077}
4078
4079impl MeerkatMachine {
4080    #[cfg(test)]
4081    pub(crate) async fn model_routing_handle_for_test(
4082        &self,
4083        session_id: &SessionId,
4084    ) -> Option<Arc<crate::handles::RuntimeModelRoutingHandle>> {
4085        let (dsl_authority, visibility_owner) = {
4086            let sessions = self.sessions.read().await;
4087            let entry = sessions.get(session_id)?;
4088            (
4089                Arc::clone(&entry.dsl_authority),
4090                Arc::clone(&entry.tool_visibility_owner),
4091            )
4092        };
4093        Some(Arc::new(
4094            crate::handles::RuntimeModelRoutingHandle::new_with_visibility_owner(
4095                Arc::new(crate::handles::HandleDslAuthority::from_shared(
4096                    dsl_authority,
4097                )),
4098                visibility_owner,
4099            ),
4100        ))
4101    }
4102
4103    /// Acquire the per-session mutation gate.
4104    ///
4105    /// Returns an `Arc<Mutex<()>>` that the caller must `.lock().await` and
4106    /// hold across the full DSL-stage → driver-mutate → DSL-sync span.
4107    /// Returns `None` if the session is not registered.
4108    async fn session_mutation_gate(&self, session_id: &SessionId) -> Option<Arc<Mutex<()>>> {
4109        let sessions = self.sessions.read().await;
4110        sessions
4111            .get(session_id)
4112            .map(|entry| Arc::clone(&entry.mutation_gate))
4113    }
4114
4115    async fn lock_current_session_mutation_gate(
4116        &self,
4117        session_id: &SessionId,
4118    ) -> Option<crate::tokio::sync::OwnedMutexGuard<()>> {
4119        loop {
4120            let gate = self.session_mutation_gate(session_id).await?;
4121            let gate_guard = Arc::clone(&gate).lock_owned().await;
4122            let sessions = self.sessions.read().await;
4123            let entry = sessions.get(session_id)?;
4124            if Arc::ptr_eq(&entry.mutation_gate, &gate) {
4125                return Some(gate_guard);
4126            }
4127        }
4128    }
4129
4130    /// Deterministically pause the runtime loop after its ready-effect drain
4131    /// and before queue authority is acquired. Exposed only by test builds so
4132    /// cross-crate integration tests can admit a complete same-boundary batch.
4133    #[cfg(any(test, feature = "test-support"))]
4134    #[doc(hidden)]
4135    pub fn arm_runtime_loop_before_queue_authority_test_hook(
4136        &self,
4137        session_id: SessionId,
4138    ) -> (
4139        crate::tokio::sync::oneshot::Receiver<()>,
4140        crate::tokio::sync::oneshot::Sender<()>,
4141    ) {
4142        let (entered_tx, entered_rx) = crate::tokio::sync::oneshot::channel();
4143        let (release_tx, release_rx) = crate::tokio::sync::oneshot::channel();
4144        let mut hook = self
4145            .test_runtime_loop_before_queue_authority
4146            .lock()
4147            .unwrap_or_else(std::sync::PoisonError::into_inner);
4148        assert!(
4149            hook.is_none(),
4150            "runtime-loop queue-authority test hook already armed"
4151        );
4152        *hook = Some((session_id, entered_tx, release_rx));
4153        (entered_rx, release_tx)
4154    }
4155
4156    #[cfg(any(test, feature = "test-support"))]
4157    pub(crate) async fn run_runtime_loop_before_queue_authority_test_hook(
4158        &self,
4159        session_id: &SessionId,
4160    ) {
4161        let armed = {
4162            let mut hook = self
4163                .test_runtime_loop_before_queue_authority
4164                .lock()
4165                .unwrap_or_else(std::sync::PoisonError::into_inner);
4166            if hook
4167                .as_ref()
4168                .is_some_and(|(armed_session_id, _, _)| armed_session_id == session_id)
4169            {
4170                hook.take()
4171            } else {
4172                None
4173            }
4174        };
4175        if let Some((_, entered_tx, release_rx)) = armed {
4176            let _ = entered_tx.send(());
4177            let _ = release_rx.await;
4178        }
4179    }
4180
4181    #[cfg(test)]
4182    fn arm_control_command_after_logical_lookup_test_hook(
4183        &self,
4184        kind: ControlCommandLookupTestKind,
4185        session_id: SessionId,
4186    ) -> (
4187        crate::tokio::sync::oneshot::Receiver<()>,
4188        crate::tokio::sync::oneshot::Sender<()>,
4189    ) {
4190        let (entered_tx, entered_rx) = crate::tokio::sync::oneshot::channel();
4191        let (release_tx, release_rx) = crate::tokio::sync::oneshot::channel();
4192        let mut hook = self
4193            .test_control_command_after_logical_lookup
4194            .lock()
4195            .unwrap_or_else(std::sync::PoisonError::into_inner);
4196        assert!(
4197            hook.is_none(),
4198            "control-command lookup test hook already armed"
4199        );
4200        *hook = Some((kind, session_id, entered_tx, release_rx));
4201        (entered_rx, release_tx)
4202    }
4203
4204    #[cfg(test)]
4205    async fn run_control_command_after_logical_lookup_test_hook(
4206        &self,
4207        kind: ControlCommandLookupTestKind,
4208        session_id: &SessionId,
4209    ) {
4210        let armed = {
4211            let mut hook = self
4212                .test_control_command_after_logical_lookup
4213                .lock()
4214                .unwrap_or_else(std::sync::PoisonError::into_inner);
4215            if hook
4216                .as_ref()
4217                .is_some_and(|(armed_kind, armed_session_id, _, _)| {
4218                    *armed_kind == kind && armed_session_id == session_id
4219                })
4220            {
4221                hook.take()
4222            } else {
4223                None
4224            }
4225        };
4226        if let Some((_, _, entered_tx, release_rx)) = armed {
4227            let _ = entered_tx.send(());
4228            let _ = release_rx.await;
4229        }
4230    }
4231
4232    async fn lock_registration_gate(
4233        &self,
4234        session_id: &SessionId,
4235    ) -> Result<crate::tokio::sync::OwnedMutexGuard<()>, RuntimeDriverError> {
4236        let gate_guard = self
4237            .lock_current_session_mutation_gate(session_id)
4238            .await
4239            .ok_or(RuntimeDriverError::NotReady {
4240                state: RuntimeState::Destroyed,
4241            })?;
4242        let blocked = {
4243            let sessions = self.sessions.read().await;
4244            let entry = sessions
4245                .get(session_id)
4246                .ok_or(RuntimeDriverError::NotReady {
4247                    state: RuntimeState::Destroyed,
4248                })?;
4249            entry.registration_blocked_by_unregister(session_id)
4250        };
4251        if let Some(error) = blocked {
4252            return Err(error);
4253        }
4254        Ok(gate_guard)
4255    }
4256
4257    #[cfg(feature = "live")]
4258    async fn session_live_lifecycle_gate(&self, session_id: &SessionId) -> Option<Arc<Mutex<()>>> {
4259        let sessions = self.sessions.read().await;
4260        sessions
4261            .get(session_id)
4262            .map(|entry| Arc::clone(&entry.live_lifecycle_gate))
4263    }
4264
4265    #[cfg(feature = "live")]
4266    async fn lock_current_session_live_lifecycle_gate(
4267        &self,
4268        session_id: &SessionId,
4269    ) -> Option<(Arc<Mutex<()>>, crate::tokio::sync::OwnedMutexGuard<()>)> {
4270        loop {
4271            let gate = self.session_live_lifecycle_gate(session_id).await?;
4272            let guard = Arc::clone(&gate).lock_owned().await;
4273            let sessions = self.sessions.read().await;
4274            let entry = sessions.get(session_id)?;
4275            if Arc::ptr_eq(&entry.live_lifecycle_gate, &gate) {
4276                return Some((gate, guard));
4277            }
4278        }
4279    }
4280
4281    #[cfg(feature = "live")]
4282    async fn lock_session_mutation_gate_for_live_lifecycle_lease(
4283        &self,
4284        session_id: &SessionId,
4285        lease: &crate::member_live::MemberLiveLifecycleLease,
4286    ) -> Option<crate::tokio::sync::OwnedMutexGuard<()>> {
4287        let mutation_gate = {
4288            let sessions = self.sessions.read().await;
4289            let entry = sessions.get(session_id)?;
4290            if !lease.matches_gate(&entry.live_lifecycle_gate) {
4291                return None;
4292            }
4293            Arc::clone(&entry.mutation_gate)
4294        };
4295        let guard = Arc::clone(&mutation_gate).lock_owned().await;
4296        let sessions = self.sessions.read().await;
4297        let entry = sessions.get(session_id)?;
4298        if lease.matches_gate(&entry.live_lifecycle_gate)
4299            && Arc::ptr_eq(&entry.mutation_gate, &mutation_gate)
4300        {
4301            Some(guard)
4302        } else {
4303            None
4304        }
4305    }
4306
4307    pub(crate) async fn lock_current_session_driver_gate(
4308        &self,
4309        session_id: &SessionId,
4310        driver: &SharedDriver,
4311    ) -> Result<crate::tokio::sync::OwnedMutexGuard<()>, RuntimeDriverError> {
4312        let gate_guard = self
4313            .lock_current_session_mutation_gate(session_id)
4314            .await
4315            .ok_or(RuntimeDriverError::NotReady {
4316                state: RuntimeState::Destroyed,
4317            })?;
4318        {
4319            let sessions = self.sessions.read().await;
4320            let entry = sessions
4321                .get(session_id)
4322                .ok_or(RuntimeDriverError::NotReady {
4323                    state: RuntimeState::Destroyed,
4324                })?;
4325            if !Arc::ptr_eq(&entry.driver, driver) {
4326                return Err(RuntimeDriverError::NotReady {
4327                    state: RuntimeState::Destroyed,
4328                });
4329            }
4330        }
4331        Ok(gate_guard)
4332    }
4333
4334    pub(crate) async fn lock_current_runtime_loop_driver_authority(
4335        &self,
4336        session_id: &SessionId,
4337        driver: &SharedDriver,
4338    ) -> Result<crate::tokio::sync::OwnedMutexGuard<()>, RuntimeDriverError> {
4339        let gate_guard = self
4340            .lock_current_session_driver_gate(session_id, driver)
4341            .await?;
4342        {
4343            let sessions = self.sessions.read().await;
4344            let entry = sessions
4345                .get(session_id)
4346                .ok_or(RuntimeDriverError::NotReady {
4347                    state: RuntimeState::Destroyed,
4348                })?;
4349            if !entry.generated_executor_registration_active_or_draining() {
4350                return Err(RuntimeDriverError::ValidationFailed {
4351                    reason:
4352                        "generated MeerkatMachine has no active runtime-loop executor registration"
4353                            .to_string(),
4354                });
4355            }
4356        }
4357        Ok(gate_guard)
4358    }
4359
4360    pub(crate) async fn current_runtime_stop_cleanup_in_progress(
4361        &self,
4362        session_id: &SessionId,
4363        driver: &SharedDriver,
4364    ) -> Result<bool, RuntimeDriverError> {
4365        let sessions = self.sessions.read().await;
4366        let entry = sessions
4367            .get(session_id)
4368            .ok_or(RuntimeDriverError::NotReady {
4369                state: RuntimeState::Destroyed,
4370            })?;
4371        if !Arc::ptr_eq(&entry.driver, driver) {
4372            return Err(RuntimeDriverError::NotReady {
4373                state: RuntimeState::Destroyed,
4374            });
4375        }
4376        let Some(coordinator) = entry.runtime_stop_cleanup_coordinator.as_ref() else {
4377            return Ok(false);
4378        };
4379        if coordinator.epoch_id != entry.epoch_id {
4380            return Err(RuntimeDriverError::Internal(format!(
4381                "stale runtime-stop cleanup coordinator epoch for session {session_id}"
4382            )));
4383        }
4384        Ok(coordinator.result_rx.borrow().is_none())
4385    }
4386
4387    async fn session_dsl_authority(
4388        &self,
4389        session_id: &SessionId,
4390    ) -> Result<Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>, String> {
4391        let sessions = self.sessions.read().await;
4392        sessions
4393            .get(session_id)
4394            .map(|entry| Arc::clone(&entry.dsl_authority))
4395            .ok_or_else(|| {
4396                RuntimeDriverError::NotReady {
4397                    state: RuntimeState::Destroyed,
4398                }
4399                .to_string()
4400            })
4401    }
4402
4403    #[cfg(any(test, feature = "test-support"))]
4404    async fn session_handle_teardown_gate(
4405        &self,
4406        session_id: &SessionId,
4407    ) -> Result<Arc<crate::handles::HandleTeardownGate>, String> {
4408        let sessions = self.sessions.read().await;
4409        sessions
4410            .get(session_id)
4411            .map(|entry| Arc::clone(&entry.handle_teardown_gate))
4412            .ok_or_else(|| {
4413                RuntimeDriverError::NotReady {
4414                    state: RuntimeState::Destroyed,
4415                }
4416                .to_string()
4417            })
4418    }
4419
4420    /// Test-support: install the session's generated peer-comms handle (and its
4421    /// owner token) onto a comms runtime, so the runtime accepts generated trust
4422    /// mutations minted from THIS adapter's session dsl authority. Mirrors what
4423    /// `prepare_session_runtime_bindings` does in production via
4424    /// `SessionRuntimeBindings`, for tests/harnesses that construct external
4425    /// member runtimes directly (e.g. the external-TCP production-drain smoke
4426    /// lane). Gated behind `test-support` so it never reaches a production build.
4427    #[cfg(any(test, feature = "test-support"))]
4428    pub async fn test_install_session_peer_comms_handle_on_runtime(
4429        &self,
4430        session_id: &SessionId,
4431        runtime: &(dyn meerkat_core::handles::PeerCommsInstallTarget + '_),
4432    ) -> Result<(), String> {
4433        let dsl = self
4434            .session_dsl_authority(session_id)
4435            .await
4436            .map_err(|error| format!("session dsl authority unavailable: {error}"))?;
4437        let teardown_gate = self
4438            .session_handle_teardown_gate(session_id)
4439            .await
4440            .map_err(|error| format!("session handle teardown gate unavailable: {error}"))?;
4441        let handle = std::sync::Arc::new(
4442            crate::handles::HandleDslAuthority::from_shared_with_teardown_gate(dsl, teardown_gate),
4443        );
4444        crate::handles::RuntimePeerCommsHandle::install_generated_on(handle, runtime)
4445    }
4446
4447    fn preview_dsl_input_on_state(
4448        state: &dsl::MeerkatMachineState,
4449        input: dsl::MeerkatMachineInput,
4450        context: &str,
4451    ) -> Result<Vec<dsl::MeerkatMachineEffect>, String> {
4452        let mut preview = dsl::MeerkatMachineAuthority::recover_from_state(state.clone())
4453            .map_err(|err| dsl_authority::map_error(err, context))?;
4454        dsl::MeerkatMachineMutator::apply(&mut preview, input)
4455            .map(|transition| transition.into_effects())
4456            .map_err(|err| dsl_authority::map_error(err, context))
4457    }
4458
4459    async fn preview_session_dsl_input(
4460        &self,
4461        session_id: &SessionId,
4462        input: dsl::MeerkatMachineInput,
4463        context: &str,
4464    ) -> Result<Vec<dsl::MeerkatMachineEffect>, String> {
4465        let authority = self.session_dsl_authority(session_id).await?;
4466        let state = {
4467            let authority = authority
4468                .lock()
4469                .unwrap_or_else(std::sync::PoisonError::into_inner);
4470            authority.state().clone()
4471        };
4472        Self::preview_dsl_input_on_state(&state, input, context)
4473    }
4474
4475    async fn session_dsl_state(
4476        &self,
4477        session_id: &SessionId,
4478    ) -> Result<dsl::MeerkatMachineState, RuntimeControlPlaneError> {
4479        let authority = self
4480            .session_dsl_authority(session_id)
4481            .await
4482            .map_err(RuntimeControlPlaneError::Internal)?;
4483        let authority = authority
4484            .lock()
4485            .unwrap_or_else(std::sync::PoisonError::into_inner);
4486        Ok(authority.state().clone())
4487    }
4488
4489    async fn commit_session_dsl_transition(
4490        &self,
4491        session_id: &SessionId,
4492        staged: StagedSessionDslInput,
4493        context: &str,
4494    ) -> Result<(), String> {
4495        self.commit_session_dsl_transition_with_dispatch_failure(
4496            session_id,
4497            staged,
4498            context,
4499            CommittedEffectDispatchFailure::PreserveCommittedDslState,
4500        )
4501        .await
4502    }
4503
4504    async fn commit_session_dsl_transition_preserving_committed_state(
4505        &self,
4506        session_id: &SessionId,
4507        staged: StagedSessionDslInput,
4508        context: &str,
4509    ) -> Result<(), String> {
4510        self.commit_session_dsl_transition_with_dispatch_failure(
4511            session_id,
4512            staged,
4513            context,
4514            CommittedEffectDispatchFailure::PreserveCommittedDslState,
4515        )
4516        .await
4517    }
4518
4519    async fn commit_session_dsl_transition_with_dispatch_failure(
4520        &self,
4521        _session_id: &SessionId,
4522        staged: StagedSessionDslInput,
4523        context: &str,
4524        dispatch_failure: CommittedEffectDispatchFailure,
4525    ) -> Result<(), String> {
4526        if let Err(error) = self
4527            .dispatch_routed_signals_from_effects(&staged.effects)
4528            .await
4529        {
4530            let CommittedEffectDispatchFailure::PreserveCommittedDslState = dispatch_failure;
4531            return Err(format!(
4532                "DSL authority ({context}): committed effect dispatch failed: {error}"
4533            ));
4534        }
4535        Ok(())
4536    }
4537
4538    async fn dispatch_routed_signals_from_effects(
4539        &self,
4540        effects: &[dsl::MeerkatMachineEffect],
4541    ) -> Result<(), String> {
4542        let dispatcher = {
4543            self.composition_signal_dispatcher
4544                .read()
4545                .unwrap_or_else(std::sync::PoisonError::into_inner)
4546                .clone()
4547        };
4548        let Some(dispatcher) = dispatcher else {
4549            return Ok(());
4550        };
4551
4552        for effect in effects {
4553            if let Some(signal) = composition::lift_routed_signal(effect) {
4554                composition::dispatch_routed_signal(&dispatcher, signal).await?;
4555            }
4556        }
4557        Ok(())
4558    }
4559
4560    // The wrapper transfers one exact, non-cloneable dispatch transaction to
4561    // process-owned cleanup; a params bag would duplicate that authority shape.
4562    #[allow(clippy::too_many_arguments)]
4563    async fn dispatch_cancel_after_boundary_runtime_effect(
4564        &self,
4565        session_id: &SessionId,
4566        witness: RuntimeEffectDispatchAttachmentWitness,
4567        held_mutation_gate: crate::tokio::sync::OwnedMutexGuard<()>,
4568        boundary_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>>,
4569        member_authority: Option<RuntimeEffectDispatchMemberAuthority>,
4570        pending_dispatch: PendingBoundaryCancelDispatchGuard,
4571        expected_run_id: Option<&RunId>,
4572        projected_effect: crate::effect::ProjectedRuntimeEffect,
4573        dispatch_generation: u64,
4574        dispatch_lifecycle_phase: dsl::MeerkatPhase,
4575        context: &str,
4576    ) -> Result<crate::tokio::sync::OwnedMutexGuard<()>, RuntimeDriverError> {
4577        let cleanup_spawner = MachineCleanupTaskSpawner::acquire()?;
4578        let machine = self.clone();
4579        let session_id = session_id.clone();
4580        let expected_run_id = expected_run_id.cloned();
4581        let context = context.to_string();
4582        let completion = cleanup_spawner.spawn(async move {
4583            machine
4584                .dispatch_cancel_after_boundary_runtime_effect_owned(
4585                    &session_id,
4586                    witness,
4587                    held_mutation_gate,
4588                    boundary_handle,
4589                    member_authority,
4590                    pending_dispatch,
4591                    expected_run_id.as_ref(),
4592                    projected_effect,
4593                    dispatch_generation,
4594                    dispatch_lifecycle_phase,
4595                    &context,
4596                )
4597                .await
4598        });
4599        completion.await.map_err(|error| {
4600            RuntimeDriverError::Internal(format!(
4601                "process-owned boundary-effect dispatch ended without a result: {error}"
4602            ))
4603        })?
4604    }
4605
4606    // Keep every exact attachment and pending-dispatch owner visible at the
4607    // cancellation-safe process boundary rather than re-packaging authority.
4608    #[allow(clippy::too_many_arguments)]
4609    async fn dispatch_cancel_after_boundary_runtime_effect_owned(
4610        &self,
4611        session_id: &SessionId,
4612        witness: RuntimeEffectDispatchAttachmentWitness,
4613        held_mutation_gate: crate::tokio::sync::OwnedMutexGuard<()>,
4614        boundary_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>>,
4615        member_authority: Option<RuntimeEffectDispatchMemberAuthority>,
4616        mut pending_dispatch: PendingBoundaryCancelDispatchGuard,
4617        expected_run_id: Option<&RunId>,
4618        projected_effect: crate::effect::ProjectedRuntimeEffect,
4619        dispatch_generation: u64,
4620        dispatch_lifecycle_phase: dsl::MeerkatPhase,
4621        context: &str,
4622    ) -> Result<crate::tokio::sync::OwnedMutexGuard<()>, RuntimeDriverError> {
4623        // Executor callbacks are allowed to route back into the machine. Drop
4624        // M before invoking one, then reacquire the exact captured gate and
4625        // prove that neither the logical session nor attachment incarnation
4626        // changed before the queued effect is published.
4627        drop(held_mutation_gate);
4628        let live_dispatch_result = self
4629            .dispatch_cancel_after_boundary_live_handle(
4630                boundary_handle,
4631                expected_run_id,
4632                &projected_effect,
4633                context,
4634            )
4635            .await;
4636
4637        // Reserve bounded-channel capacity without M. A wedged executor may
4638        // delay this process-owned transaction, but it cannot retain the
4639        // session mutation authority or block teardown/replacement.
4640        let pre_reserve_state = Self::classify_dsl_boundary_cancel_dispatch(
4641            &witness.dsl_authority,
4642            dispatch_generation,
4643            &dispatch_lifecycle_phase,
4644            expected_run_id,
4645        );
4646        let effect_permit = if live_dispatch_result.is_ok()
4647            && pre_reserve_state == BoundaryCancelDispatchState::Current
4648        {
4649            Some(witness.effect_tx.clone().reserve_owned().await)
4650        } else {
4651            None
4652        };
4653
4654        let gate_guard = Arc::clone(&witness.mutation_gate).lock_owned().await;
4655        let current_effect_tx = {
4656            let sessions = self.sessions.read().await;
4657            match sessions.get(session_id) {
4658                None => Err(RuntimeDriverError::StaleAuthority {
4659                    reason: format!(
4660                        "{context}: runtime session disappeared during boundary callback"
4661                    ),
4662                }),
4663                Some(entry)
4664                    if !Arc::ptr_eq(&entry.mutation_gate, &witness.mutation_gate)
4665                        || !Arc::ptr_eq(&entry.driver, &witness.driver)
4666                        || !Arc::ptr_eq(&entry.dsl_authority, &witness.dsl_authority)
4667                        || !entry.owns_runtime_loop_attachment(witness.attachment_id) =>
4668                {
4669                    Err(RuntimeDriverError::StaleAuthority {
4670                        reason: format!(
4671                            "{context}: runtime attachment changed during boundary callback"
4672                        ),
4673                    })
4674                }
4675                Some(entry) => match entry.effect_sender() {
4676                    None => Err(RuntimeDriverError::StaleAuthority {
4677                        reason: format!(
4678                            "{context}: runtime effect channel disappeared during boundary callback"
4679                        ),
4680                    }),
4681                    Some(current_effect_tx)
4682                        if !witness.effect_tx.same_channel(&current_effect_tx) =>
4683                    {
4684                        Err(RuntimeDriverError::StaleAuthority {
4685                            reason: format!(
4686                                "{context}: runtime effect channel changed during boundary callback"
4687                            ),
4688                        })
4689                    }
4690                    Some(current_effect_tx) => Ok(current_effect_tx),
4691                },
4692            }
4693        };
4694        let current_effect_tx = current_effect_tx?;
4695
4696        if let Some(member_authority) = &member_authority {
4697            self.validate_member_effect_authority_lease_current(
4698                session_id,
4699                &member_authority.lease,
4700                member_authority.expected_member.as_ref(),
4701            )?;
4702        }
4703
4704        // Callback failure is meaningful only while the captured attachment
4705        // remains current. If replacement B won while A's callback was
4706        // outside M, the exact checks above return StaleAuthority first so A's
4707        // result cannot be mistaken for B's or trigger a caller-side retry on
4708        // B under the old request.
4709        if let Err(error) = live_dispatch_result {
4710            return Err(error);
4711        }
4712
4713        // The live turn may have crossed its boundary while M was released.
4714        // That is successful convergence; do not replay the old effect into a
4715        // successor phase. A same-generation orphan in another phase is
4716        // cleared by the generated abort input.
4717        match Self::classify_dsl_boundary_cancel_dispatch(
4718            &witness.dsl_authority,
4719            dispatch_generation,
4720            &dispatch_lifecycle_phase,
4721            expected_run_id,
4722        ) {
4723            BoundaryCancelDispatchState::ConsumedConverged => return Ok(gate_guard),
4724            BoundaryCancelDispatchState::SameGenerationOrphan
4725            | BoundaryCancelDispatchState::Superseded => {
4726                return Err(RuntimeDriverError::StaleAuthority {
4727                    reason: format!(
4728                        "{context}: boundary-cancel dispatch authority changed during callback"
4729                    ),
4730                });
4731            }
4732            BoundaryCancelDispatchState::Current => {}
4733        }
4734
4735        let state = self
4736            .existing_session_runtime_state(session_id)
4737            .await
4738            .unwrap_or(RuntimeState::Destroyed);
4739        self.reject_unregistration_drain_ingress(session_id, state)
4740            .await?;
4741
4742        debug_assert!(witness.effect_tx.same_channel(&current_effect_tx));
4743        let effect_permit = effect_permit
4744            .ok_or_else(|| RuntimeDriverError::StaleAuthority {
4745                reason: format!(
4746                    "{context}: boundary-cancel dispatch became current after capacity reservation was skipped"
4747                ),
4748            })?
4749            .map_err(|_| RuntimeDriverError::NotReady {
4750                state: RuntimeState::Idle,
4751            })?;
4752        let _effect_tx = effect_permit.send(projected_effect.into_effect());
4753        pending_dispatch.disarm();
4754        Ok(gate_guard)
4755    }
4756
4757    /// Own all post-admission boundary work for an accepted ingress request.
4758    /// Once the driver and DSL have accepted the input, caller cancellation may
4759    /// drop only the acknowledgement: cancel dispatch and wake remain
4760    /// process-owned.
4761    async fn dispatch_accepted_ingress_boundary_work(
4762        &self,
4763        session_id: &SessionId,
4764        held_mutation_gate: crate::tokio::sync::OwnedMutexGuard<()>,
4765        cancel_plan: Option<RuntimeAcceptedBoundaryCancelPlan>,
4766        _completions: crate::meerkat_machine::driver::SharedCompletionRegistry,
4767        wake_tx: Option<mpsc::Sender<()>>,
4768        should_wake: bool,
4769    ) -> Result<crate::tokio::sync::OwnedMutexGuard<()>, RuntimeDriverError> {
4770        let cleanup_spawner = MachineCleanupTaskSpawner::acquire()?;
4771        let machine = self.clone();
4772        let session_id = session_id.clone();
4773        let completion = cleanup_spawner.spawn(async move {
4774            let mut gate_guard = held_mutation_gate;
4775            if let Some(cancel_plan) = cancel_plan {
4776                gate_guard = machine
4777                    .dispatch_cancel_after_boundary_runtime_effect_owned(
4778                        &session_id,
4779                        cancel_plan.witness,
4780                        gate_guard,
4781                        cancel_plan.boundary_handle,
4782                        None,
4783                        cancel_plan.pending_dispatch,
4784                        Some(&cancel_plan.expected_run_id),
4785                        cancel_plan.projected_effect,
4786                        cancel_plan.dispatch_generation,
4787                        cancel_plan.dispatch_lifecycle_phase,
4788                        "AcceptWithCompletion",
4789                    )
4790                    .await?;
4791            }
4792            if should_wake && let Some(wake_tx) = wake_tx {
4793                let _ = wake_tx.try_send(());
4794            }
4795            Ok(gate_guard)
4796        });
4797        completion.await.map_err(|error| {
4798            RuntimeDriverError::Internal(format!(
4799                "process-owned accepted-ingress boundary transaction ended without a result: {error}"
4800            ))
4801        })?
4802    }
4803
4804    /// Invoke the executor-owned boundary hook without holding a session
4805    /// mutation mutex. Embedders may route this hook back through
4806    /// `MeerkatMachine::cancel_after_boundary`; the already-pending machine
4807    /// fact bounds that re-entry, but only if the nested call can acquire the
4808    /// gate and observe it.
4809    async fn dispatch_cancel_after_boundary_live_handle(
4810        &self,
4811        boundary_handle: Option<Arc<dyn meerkat_core::lifecycle::CoreExecutorBoundaryHandle>>,
4812        expected_run_id: Option<&RunId>,
4813        projected_effect: &crate::effect::ProjectedRuntimeEffect,
4814        context: &str,
4815    ) -> Result<(), RuntimeDriverError> {
4816        let reason = projected_effect.reason().to_string();
4817        // The cloneable live handle is exact-run authority. Attached runtimes
4818        // legitimately have no run yet; their generated CancelAfterBoundary
4819        // dispatch still goes through the in-loop executor effect below, but
4820        // must not fabricate an ID for this live callback.
4821        if let (Some(boundary_handle), Some(expected_run_id)) = (boundary_handle, expected_run_id) {
4822            boundary_handle
4823                .cancel_after_boundary(expected_run_id, reason)
4824                .await
4825                .map_err(|err| {
4826                    RuntimeDriverError::Internal(format!(
4827                        "{context}: failed to apply live boundary cancel: {err}"
4828                    ))
4829                })?;
4830        }
4831        Ok(())
4832    }
4833
4834    async fn restore_session_dsl_state(
4835        &self,
4836        session_id: &SessionId,
4837        snapshot: dsl::MeerkatMachineAuthoritySnapshot,
4838    ) {
4839        if let Ok(authority) = self.session_dsl_authority(session_id).await {
4840            Self::restore_dsl_authority_snapshot(&authority, snapshot);
4841        }
4842    }
4843
4844    async fn restore_session_dsl_state_if_current(
4845        &self,
4846        session_id: &SessionId,
4847        expected_current: dsl::MeerkatMachineAuthoritySnapshot,
4848        restore: dsl::MeerkatMachineAuthoritySnapshot,
4849    ) -> bool {
4850        let Ok(authority) = self.session_dsl_authority(session_id).await else {
4851            return false;
4852        };
4853        Self::restore_dsl_authority_snapshot_if_current(&authority, expected_current, restore)
4854    }
4855
4856    fn classify_dsl_boundary_cancel_dispatch(
4857        authority: &Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
4858        dispatch_generation: u64,
4859        dispatch_lifecycle_phase: &dsl::MeerkatPhase,
4860        expected_run_id: Option<&RunId>,
4861    ) -> BoundaryCancelDispatchState {
4862        let authority = authority
4863            .lock()
4864            .unwrap_or_else(std::sync::PoisonError::into_inner);
4865        let state = authority.state();
4866        if state.boundary_cancel_dispatch_generation != dispatch_generation {
4867            return BoundaryCancelDispatchState::Superseded;
4868        }
4869        if !state.boundary_cancel_dispatch_pending {
4870            return BoundaryCancelDispatchState::ConsumedConverged;
4871        }
4872        let expected_dsl_run_id = expected_run_id.map(dsl::RunId::from_domain);
4873        if state.lifecycle_phase == *dispatch_lifecycle_phase
4874            && state.current_run_id.as_ref() == expected_dsl_run_id.as_ref()
4875        {
4876            BoundaryCancelDispatchState::Current
4877        } else {
4878            BoundaryCancelDispatchState::SameGenerationOrphan
4879        }
4880    }
4881
4882    /// Clear only the failed boundary-dispatch fact represented by
4883    /// `dispatch_generation`. This deliberately applies a generated input to
4884    /// the current authority instead of restoring a whole pre-callback
4885    /// snapshot: the live executor hook may have re-entered the machine and
4886    /// committed unrelated state while the session mutation gate was
4887    /// released.
4888    fn abort_dsl_boundary_cancel_dispatch_if_current(
4889        authority: &Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
4890        dispatch_generation: u64,
4891    ) -> Result<bool, RuntimeDriverError> {
4892        let mut authority = authority
4893            .lock()
4894            .unwrap_or_else(std::sync::PoisonError::into_inner);
4895        if !authority.state().boundary_cancel_dispatch_pending
4896            || authority.state().boundary_cancel_dispatch_generation != dispatch_generation
4897        {
4898            return Ok(false);
4899        }
4900        dsl::MeerkatMachineMutator::apply(
4901            &mut *authority,
4902            dsl::MeerkatMachineInput::AbortCancelAfterBoundaryDispatch {
4903                dispatch_generation,
4904            },
4905        )
4906        .map(|_| true)
4907        .map_err(|err| {
4908            RuntimeDriverError::Internal(dsl_authority::map_error(
4909                err,
4910                "AbortCancelAfterBoundaryDispatch",
4911            ))
4912        })
4913    }
4914
4915    fn restore_dsl_authority_snapshot(
4916        authority: &Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
4917        snapshot: dsl::MeerkatMachineAuthoritySnapshot,
4918    ) {
4919        let mut authority = authority
4920            .lock()
4921            .unwrap_or_else(std::sync::PoisonError::into_inner);
4922        authority.restore_snapshot(snapshot);
4923    }
4924
4925    fn restore_dsl_authority_snapshot_if_current(
4926        authority: &Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
4927        expected_current: dsl::MeerkatMachineAuthoritySnapshot,
4928        restore: dsl::MeerkatMachineAuthoritySnapshot,
4929    ) -> bool {
4930        let mut authority = authority
4931            .lock()
4932            .unwrap_or_else(std::sync::PoisonError::into_inner);
4933        let current = authority.snapshot();
4934        if current.state() == expected_current.state() {
4935            authority.restore_snapshot(restore);
4936            true
4937        } else {
4938            false
4939        }
4940    }
4941}
4942
4943/// Capability token proving a session-control mutation is routed through
4944/// `MeerkatMachine` authority instead of a public store-only service path.
4945#[derive(Debug, Clone, Copy)]
4946pub struct MachineSessionControlAuthority {
4947    _private: (),
4948}
4949
4950#[cfg(feature = "live")]
4951struct LiveOpenAdmissionGeneratedAuthorityBridgeToken;
4952
4953#[cfg(feature = "live")]
4954struct LiveCloseResultGeneratedAuthorityBridgeToken;
4955
4956#[cfg(feature = "live")]
4957struct LiveChannelStatusResultGeneratedAuthorityBridgeToken;
4958
4959#[cfg(feature = "live")]
4960static LIVE_OPEN_ADMISSION_GENERATED_AUTHORITY_BRIDGE_TOKEN:
4961    LiveOpenAdmissionGeneratedAuthorityBridgeToken = LiveOpenAdmissionGeneratedAuthorityBridgeToken;
4962
4963#[cfg(feature = "live")]
4964static LIVE_CLOSE_RESULT_GENERATED_AUTHORITY_BRIDGE_TOKEN:
4965    LiveCloseResultGeneratedAuthorityBridgeToken = LiveCloseResultGeneratedAuthorityBridgeToken;
4966
4967#[cfg(feature = "live")]
4968static LIVE_CHANNEL_STATUS_RESULT_GENERATED_AUTHORITY_BRIDGE_TOKEN:
4969    LiveChannelStatusResultGeneratedAuthorityBridgeToken =
4970    LiveChannelStatusResultGeneratedAuthorityBridgeToken;
4971
4972#[cfg(feature = "live")]
4973fn live_open_admission_generated_authority_bridge_token()
4974-> &'static (dyn std::any::Any + Send + Sync) {
4975    &LIVE_OPEN_ADMISSION_GENERATED_AUTHORITY_BRIDGE_TOKEN
4976}
4977
4978#[cfg(feature = "live")]
4979fn live_close_result_generated_authority_bridge_token() -> &'static (dyn std::any::Any + Send + Sync)
4980{
4981    &LIVE_CLOSE_RESULT_GENERATED_AUTHORITY_BRIDGE_TOKEN
4982}
4983
4984#[cfg(feature = "live")]
4985fn live_channel_status_result_generated_authority_bridge_token()
4986-> &'static (dyn std::any::Any + Send + Sync) {
4987    &LIVE_CHANNEL_STATUS_RESULT_GENERATED_AUTHORITY_BRIDGE_TOKEN
4988}
4989
4990#[cfg(feature = "live")]
4991#[doc(hidden)]
4992#[allow(improper_ctypes_definitions, unsafe_code)]
4993#[unsafe(export_name = concat!(
4994    "__meerkat_runtime_generated_authority_bridge_token_is_valid_v1_live_open_admission_",
4995    env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
4996))]
4997pub extern "Rust" fn live_open_admission_generated_authority_bridge_token_is_valid(
4998    token: &(dyn std::any::Any + Send + Sync),
4999) -> bool {
5000    token.is::<LiveOpenAdmissionGeneratedAuthorityBridgeToken>()
5001}
5002
5003#[cfg(feature = "live")]
5004#[doc(hidden)]
5005#[allow(improper_ctypes_definitions, unsafe_code)]
5006#[unsafe(export_name = concat!(
5007    "__meerkat_runtime_generated_authority_bridge_token_is_valid_v1_live_close_result_",
5008    env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
5009))]
5010pub extern "Rust" fn live_close_result_generated_authority_bridge_token_is_valid(
5011    token: &(dyn std::any::Any + Send + Sync),
5012) -> bool {
5013    token.is::<LiveCloseResultGeneratedAuthorityBridgeToken>()
5014}
5015
5016#[cfg(feature = "live")]
5017#[doc(hidden)]
5018#[allow(improper_ctypes_definitions, unsafe_code)]
5019#[unsafe(export_name = concat!(
5020    "__meerkat_runtime_generated_authority_bridge_token_is_valid_v1_live_channel_status_result_",
5021    env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
5022))]
5023pub extern "Rust" fn live_channel_status_result_generated_authority_bridge_token_is_valid(
5024    token: &(dyn std::any::Any + Send + Sync),
5025) -> bool {
5026    token.is::<LiveChannelStatusResultGeneratedAuthorityBridgeToken>()
5027}
5028
5029#[cfg(feature = "live")]
5030fn build_live_channel_open_authority(
5031    session_id: SessionId,
5032    channel_id: meerkat_live::LiveChannelId,
5033    sequence: u64,
5034) -> Result<meerkat_live::LiveChannelOpenAuthority, String> {
5035    #[allow(improper_ctypes_definitions, unsafe_code)]
5036    unsafe extern "Rust" {
5037        #[link_name = concat!(
5038            "__meerkat_live_runtime_generated_live_channel_open_authority_build_v1_",
5039            env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
5040        )]
5041        fn live_generated_channel_open_authority_build(
5042            token: &'static (dyn std::any::Any + Send + Sync),
5043            session_id: SessionId,
5044            channel_id: meerkat_live::LiveChannelId,
5045            sequence: u64,
5046        ) -> Result<meerkat_live::LiveChannelOpenAuthority, String>;
5047    }
5048    #[allow(unsafe_code)]
5049    unsafe {
5050        live_generated_channel_open_authority_build(
5051            live_open_admission_generated_authority_bridge_token(),
5052            session_id,
5053            channel_id,
5054            sequence,
5055        )
5056    }
5057}
5058
5059#[cfg(feature = "live")]
5060fn build_live_channel_close_commit_authority(
5061    channel_id: String,
5062    close_sequence: u64,
5063) -> Result<meerkat_live::LiveChannelCloseCommitAuthority, String> {
5064    #[allow(improper_ctypes_definitions, unsafe_code)]
5065    unsafe extern "Rust" {
5066        #[link_name = concat!(
5067            "__meerkat_live_runtime_generated_live_channel_close_commit_authority_build_v1_",
5068            env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
5069        )]
5070        fn live_generated_channel_close_commit_authority_build(
5071            token: &'static (dyn std::any::Any + Send + Sync),
5072            channel_id: String,
5073            close_sequence: u64,
5074        ) -> Result<meerkat_live::LiveChannelCloseCommitAuthority, String>;
5075    }
5076    #[allow(unsafe_code)]
5077    unsafe {
5078        live_generated_channel_close_commit_authority_build(
5079            live_close_result_generated_authority_bridge_token(),
5080            channel_id,
5081            close_sequence,
5082        )
5083    }
5084}
5085
5086#[cfg(feature = "live")]
5087fn build_live_channel_status_commit_authority(
5088    channel_id: String,
5089    status_observation_sequence: u64,
5090) -> Result<meerkat_live::LiveChannelStatusCommitAuthority, String> {
5091    #[allow(improper_ctypes_definitions, unsafe_code)]
5092    unsafe extern "Rust" {
5093        #[link_name = concat!(
5094            "__meerkat_live_runtime_generated_live_channel_status_commit_authority_build_v1_",
5095            env!("MEERKAT_GENERATED_AUTHORITY_BRIDGE_SYMBOL_SUFFIX")
5096        )]
5097        fn live_generated_channel_status_commit_authority_build(
5098            token: &'static (dyn std::any::Any + Send + Sync),
5099            channel_id: String,
5100            status_observation_sequence: u64,
5101        ) -> Result<meerkat_live::LiveChannelStatusCommitAuthority, String>;
5102    }
5103    #[allow(unsafe_code)]
5104    unsafe {
5105        live_generated_channel_status_commit_authority_build(
5106            live_channel_status_result_generated_authority_bridge_token(),
5107            channel_id,
5108            status_observation_sequence,
5109        )
5110    }
5111}
5112
5113/// Generated authority output for `live/open` admission.
5114///
5115/// Constructed only from `MeerkatMachineEffect::LiveOpenAdmissionResolved`.
5116/// The live host accepts this as a typed handoff before materializing
5117/// transport resources; it does not decide duplicate-session admission from
5118/// its local maps.
5119#[derive(Debug, Clone)]
5120#[cfg(feature = "live")]
5121pub struct LiveOpenAdmissionAuthority {
5122    session_id: SessionId,
5123    channel_id: meerkat_live::LiveChannelId,
5124    admitted: bool,
5125    rejection: Option<dsl::LiveOpenAdmissionRejection>,
5126    bound_llm_identity: Option<meerkat_core::SessionLlmIdentity>,
5127    sequence: u64,
5128    channel_open_authority: Option<meerkat_live::LiveChannelOpenAuthority>,
5129}
5130
5131#[cfg(feature = "live")]
5132impl LiveOpenAdmissionAuthority {
5133    pub(crate) fn from_generated_effect(
5134        session_id: SessionId,
5135        channel_id: meerkat_live::LiveChannelId,
5136        admitted: bool,
5137        rejection: Option<dsl::LiveOpenAdmissionRejection>,
5138        bound_llm_identity: Option<dsl::SessionLlmIdentity>,
5139        sequence: u64,
5140    ) -> Result<Self, String> {
5141        let bound_llm_identity = match (admitted, bound_llm_identity) {
5142            (true, Some(identity)) => Some(identity.try_into()?),
5143            (true, None) => {
5144                return Err(
5145                    "generated live-open admission was admitted without bound LLM identity"
5146                        .to_string(),
5147                );
5148            }
5149            (false, _) => None,
5150        };
5151        let channel_open_authority = if admitted {
5152            Some(build_live_channel_open_authority(
5153                session_id.clone(),
5154                channel_id.clone(),
5155                sequence,
5156            )?)
5157        } else {
5158            None
5159        };
5160        Ok(Self {
5161            session_id,
5162            channel_id,
5163            admitted,
5164            rejection,
5165            bound_llm_identity,
5166            sequence,
5167            channel_open_authority,
5168        })
5169    }
5170
5171    #[must_use]
5172    pub fn session_id(&self) -> &SessionId {
5173        &self.session_id
5174    }
5175
5176    #[must_use]
5177    pub fn channel_id(&self) -> &meerkat_live::LiveChannelId {
5178        &self.channel_id
5179    }
5180
5181    #[must_use]
5182    pub fn admitted(&self) -> bool {
5183        self.admitted
5184    }
5185
5186    #[must_use]
5187    pub fn rejection(&self) -> Option<dsl::LiveOpenAdmissionRejection> {
5188        self.rejection
5189    }
5190
5191    #[must_use]
5192    pub fn bound_llm_identity(&self) -> Option<&meerkat_core::SessionLlmIdentity> {
5193        self.bound_llm_identity.as_ref()
5194    }
5195
5196    #[must_use]
5197    pub fn sequence(&self) -> u64 {
5198        self.sequence
5199    }
5200
5201    #[must_use]
5202    pub fn channel_open_authority(&self) -> Option<&meerkat_live::LiveChannelOpenAuthority> {
5203        self.channel_open_authority.as_ref()
5204    }
5205}
5206
5207/// Generated authority output for the public `live/refresh` success result.
5208///
5209/// Constructed only from a `MeerkatMachineEffect::LiveRefreshResultResolved`
5210/// emitted after the live adapter command queue has accepted the refresh
5211/// handoff. RPC/SDK surfaces project this value to their wire result instead
5212/// of classifying the public status from host queue mechanics.
5213#[derive(Debug, Clone, PartialEq, Eq)]
5214#[cfg(feature = "live")]
5215pub struct LiveRefreshResultAuthority {
5216    pub status: dsl::LiveRefreshPublicStatus,
5217    pub sequence: u64,
5218    pub queue_acceptance_sequence: u64,
5219}
5220
5221/// Generated authority output for the public `live/close` success result.
5222///
5223/// Constructed only from a `MeerkatMachineEffect::LiveCloseResultResolved`
5224/// emitted after the live host supplies typed close-observation evidence.
5225#[derive(Debug, Clone)]
5226#[cfg(feature = "live")]
5227pub struct LiveCloseResultAuthority {
5228    pub status: dsl::LiveClosePublicStatus,
5229    pub sequence: u64,
5230    pub close_observation_sequence: u64,
5231    channel_close_commit_authority: Option<meerkat_live::LiveChannelCloseCommitAuthority>,
5232}
5233
5234#[cfg(feature = "live")]
5235impl LiveCloseResultAuthority {
5236    pub(crate) fn from_generated_effect(
5237        channel_id: String,
5238        status: dsl::LiveClosePublicStatus,
5239        sequence: u64,
5240        close_observation_sequence: u64,
5241    ) -> Result<Self, String> {
5242        let channel_close_commit_authority = match status {
5243            dsl::LiveClosePublicStatus::Closed => Some(build_live_channel_close_commit_authority(
5244                channel_id,
5245                close_observation_sequence,
5246            )?),
5247        };
5248        Ok(Self {
5249            status,
5250            sequence,
5251            close_observation_sequence,
5252            channel_close_commit_authority,
5253        })
5254    }
5255
5256    #[must_use]
5257    pub fn channel_close_commit_authority(
5258        &self,
5259    ) -> Option<&meerkat_live::LiveChannelCloseCommitAuthority> {
5260        self.channel_close_commit_authority.as_ref()
5261    }
5262
5263    #[must_use]
5264    pub fn into_channel_close_commit_authority(
5265        self,
5266    ) -> Option<meerkat_live::LiveChannelCloseCommitAuthority> {
5267        self.channel_close_commit_authority
5268    }
5269}
5270
5271/// Generated authority output for public live command success results.
5272///
5273/// Constructed only from a `MeerkatMachineEffect::LiveCommandResultResolved`
5274/// emitted after the live host supplies typed command queue-acceptance
5275/// evidence.
5276#[derive(Debug, Clone, PartialEq, Eq)]
5277#[cfg(feature = "live")]
5278pub struct LiveCommandResultAuthority {
5279    pub command: dsl::LiveCommandPublicKind,
5280    pub sequence: u64,
5281    pub command_acceptance_sequence: u64,
5282}
5283
5284/// Generated authority output for public live command rejection results.
5285///
5286/// Constructed only from a `MeerkatMachineEffect::LiveCommandRejectionResolved`
5287/// emitted after the live host supplies typed rejection evidence. RPC/SDK
5288/// surfaces project error classes from this value instead of matching host
5289/// errors directly.
5290#[derive(Debug, Clone, PartialEq, Eq)]
5291#[cfg(feature = "live")]
5292pub struct LiveCommandRejectionAuthority {
5293    pub command: dsl::LiveCommandPublicKind,
5294    pub rejection: dsl::LiveCommandRejectionReason,
5295    pub public_error_class: dsl::LiveCommandRejectionPublicErrorClass,
5296    pub sequence: u64,
5297}
5298
5299/// Generated authority output for public live channel control request
5300/// rejections.
5301///
5302/// Constructed only from a
5303/// `MeerkatMachineEffect::LiveChannelRequestRejectionResolved` emitted after
5304/// the live host supplies typed rejection evidence.
5305#[derive(Debug, Clone, PartialEq, Eq)]
5306#[cfg(feature = "live")]
5307pub struct LiveChannelRequestRejectionAuthority {
5308    pub request: dsl::LiveChannelRequestPublicKind,
5309    pub rejection: dsl::LiveChannelRequestRejectionReason,
5310    pub public_error_class: dsl::LiveChannelRequestRejectionPublicErrorClass,
5311    pub sequence: u64,
5312}
5313
5314/// Generated authority output for a WebRTC answer token issued by
5315/// MeerkatMachine.
5316///
5317/// Constructed only from `MeerkatMachineEffect::LiveWebrtcTokenIssued`.
5318/// The transport supplies random bearer material, but it is not returned to a
5319/// caller until the generated machine records the channel binding and expiry.
5320#[derive(Debug, Clone, PartialEq, Eq)]
5321#[cfg(feature = "live")]
5322pub struct LiveWebrtcTokenAuthority {
5323    pub token: String,
5324    pub expires_at_ms: u64,
5325    pub sequence: u64,
5326}
5327
5328/// Generated authority output for WebRTC answer token admission.
5329///
5330/// Constructed only from
5331/// `MeerkatMachineEffect::LiveWebrtcAnswerAdmissionResolved`. RPC signaling
5332/// proceeds to peer setup only when this effect admits the token.
5333#[derive(Debug, Clone, PartialEq, Eq)]
5334#[cfg(feature = "live")]
5335pub struct LiveWebrtcAnswerAdmissionAuthority {
5336    pub admitted: bool,
5337    pub rejection: Option<dsl::LiveWebrtcAnswerAdmissionRejection>,
5338    pub public_error_class: Option<dsl::LiveChannelRequestRejectionPublicErrorClass>,
5339    pub sequence: u64,
5340}
5341
5342/// Generated authority output for the public `live/webrtc/answer` success
5343/// class.
5344///
5345/// Constructed only from
5346/// `MeerkatMachineEffect::LiveWebrtcAnswerResultResolved` emitted after the
5347/// WebRTC transport supplies answer-observation evidence.
5348#[derive(Debug, Clone, PartialEq, Eq)]
5349#[cfg(feature = "live")]
5350pub struct LiveWebrtcAnswerResultAuthority {
5351    pub status: dsl::LiveWebrtcAnswerPublicStatus,
5352    pub answered: bool,
5353    pub sequence: u64,
5354    pub answer_observation_sequence: u64,
5355}
5356
5357/// Generated authority output for a WebSocket transport token issued by
5358/// MeerkatMachine.
5359///
5360/// Constructed only from `MeerkatMachineEffect::LiveWebsocketTokenIssued`.
5361/// The WebSocket transport supplies random bearer material, but it is not
5362/// returned until generated authority records channel binding and expiry.
5363#[derive(Debug, Clone, PartialEq, Eq)]
5364#[cfg(feature = "live")]
5365pub struct LiveWebsocketTokenAuthority {
5366    pub token: String,
5367    pub expires_at_ms: u64,
5368    pub sequence: u64,
5369}
5370
5371/// Generated authority output for WebSocket token admission.
5372///
5373/// Constructed only from
5374/// `MeerkatMachineEffect::LiveWebsocketTokenAdmissionResolved`. The WebSocket
5375/// transport upgrade proceeds only when this effect admits the token.
5376#[derive(Debug, Clone, PartialEq, Eq)]
5377#[cfg(feature = "live")]
5378pub struct LiveWebsocketTokenAdmissionAuthority {
5379    pub admitted: bool,
5380    pub rejection: Option<dsl::LiveWebsocketTokenAdmissionRejection>,
5381    pub public_error_class: Option<dsl::LiveWebsocketTokenAdmissionPublicErrorClass>,
5382    pub sequence: u64,
5383}
5384
5385/// Generated authority output for the public `live/status` result.
5386///
5387/// Constructed only from a `MeerkatMachineEffect::LiveChannelStatusResolved`
5388/// emitted after the live host supplies typed adapter-status observation
5389/// evidence.
5390#[derive(Debug, Clone)]
5391#[cfg(feature = "live")]
5392pub struct LiveChannelStatusAuthority {
5393    pub status: dsl::LiveChannelPublicStatus,
5394    pub sequence: u64,
5395    pub status_observation_sequence: u64,
5396    pub degradation_reason: Option<dsl::LiveChannelDegradationReason>,
5397    pub degradation_detail: Option<String>,
5398    pub channel_status_commit_authority: Option<meerkat_live::LiveChannelStatusCommitAuthority>,
5399}
5400
5401#[cfg(feature = "live")]
5402impl LiveChannelStatusAuthority {
5403    pub(crate) fn from_generated_effect(
5404        channel_id: String,
5405        status: dsl::LiveChannelPublicStatus,
5406        sequence: u64,
5407        status_observation_sequence: u64,
5408        degradation_reason: Option<dsl::LiveChannelDegradationReason>,
5409        degradation_detail: Option<String>,
5410    ) -> Result<Self, String> {
5411        Ok(Self {
5412            status,
5413            sequence,
5414            status_observation_sequence,
5415            degradation_reason,
5416            degradation_detail,
5417            channel_status_commit_authority: Some(build_live_channel_status_commit_authority(
5418                channel_id,
5419                status_observation_sequence,
5420            )?),
5421        })
5422    }
5423
5424    #[must_use]
5425    pub fn channel_status_commit_authority(
5426        &self,
5427    ) -> Option<&meerkat_live::LiveChannelStatusCommitAuthority> {
5428        self.channel_status_commit_authority.as_ref()
5429    }
5430
5431    #[must_use]
5432    pub fn into_channel_status_commit_authority(
5433        self,
5434    ) -> Option<meerkat_live::LiveChannelStatusCommitAuthority> {
5435        self.channel_status_commit_authority
5436    }
5437}
5438
5439/// Session-scoped execution kernel for the Meerkat runtime.
5440///
5441/// Owns per-session runtime state (driver, ops registry, completion waiters,
5442/// comms drain, epoch bindings) and routes all internal mutations through one
5443/// canonical command reducer, with smaller group handlers retained only as
5444/// implementation detail helpers.
5445#[doc(hidden)]
5446pub struct MeerkatMachineShared {
5447    /// Per-session entries.
5448    sessions: RwLock<HashMap<SessionId, RuntimeSessionEntry>>,
5449    /// Stable process-local serialization slots for session registration and
5450    /// final unregister publication. The index retains only weak references:
5451    /// every new lookup prunes dead slots, so historical session ids cannot
5452    /// accumulate while overlapping transactions still rendezvous on one gate.
5453    registration_transaction_slots: StdRwLock<HashMap<SessionId, std::sync::Weak<Mutex<()>>>>,
5454    /// Optional RuntimeStore for persistent drivers.
5455    store: Option<Arc<dyn RuntimeStore>>,
5456    /// Blob store used by persistent drivers for durable input externalization.
5457    blob_store: Option<Arc<dyn BlobStore>>,
5458    /// Runtime-owned shell seam for live session LLM reconfiguration I/O.
5459    llm_reconfigure_host: StdRwLock<Option<Arc<dyn SessionLlmReconfigureHost>>>,
5460    /// Machine-wide injected member-observation host (multi-host mobs
5461    /// DEC-P6E-2; the `llm_reconfigure_host` precedent). Serves the
5462    /// `ReadMemberHistory` / `PollMemberEvents` drain arms and directed-turn
5463    /// admission; absent ⇒ those arms reply typed `Unavailable`.
5464    member_observation_host:
5465        StdRwLock<Option<Arc<dyn crate::member_observation::MemberObservationHost>>>,
5466    /// Machine-wide injected member live host (multi-host mobs ADJ-P6B-1;
5467    /// the `member_observation_host` precedent). Serves the four
5468    /// member-addressed live drain arms; absent ⇒ those arms reply typed
5469    /// `LiveTransportUnavailable`. Shell composition slot, NOT machine
5470    /// state — no catalog delta.
5471    member_live_host: StdRwLock<Option<Arc<dyn crate::member_live::MemberLiveHost>>>,
5472    /// Count of live bridge commands that reached this member's drain arms
5473    /// (ADJ-P6B seam S3b). Deterministic lanes pin zero-bridge-traffic
5474    /// gates against it; mechanical observability, not machine state.
5475    live_commands_served: std::sync::atomic::AtomicU64,
5476    /// Exact host-materialized incarnation registered for each resident
5477    /// member session, paired with the runtime-session mutation gate that was
5478    /// current when that residency was installed.  Remembering the gate is
5479    /// what makes a reused `SessionId` distinguishable from the runtime entry
5480    /// that the member incarnation actually materialized.
5481    /// Stable, never-replaced serialization slot for each member session id.
5482    /// Registration replacement/removal and every incarnation-fenced live
5483    /// effect acquire this same slot.  Keeping the `Arc` after unregister is
5484    /// intentional: a delayed G1 effect and a concurrent G2 install must
5485    /// rendezvous even when there was a transient unregistered interval.
5486    member_incarnation_slots: StdRwLock<HashMap<SessionId, Arc<MemberResidencySlot>>>,
5487    /// AuthMachine lifecycle authority shared by runtime-backed auth
5488    /// resolution/refresh paths and public auth-status surfaces.
5489    auth_lease: StdRwLock<meerkat_core::handles::GeneratedAuthLeaseHandle>,
5490    /// OAuth login-flow lifecycle authority shared by public auth surfaces
5491    /// that operate through this runtime adapter.
5492    #[cfg(not(target_arch = "wasm32"))]
5493    oauth_flows: StdRwLock<Arc<dyn meerkat_auth_core::oauth_flow::OAuthFlowAuthority>>,
5494    /// Runtime-scoped generated authority for live control/command rejections
5495    /// that cannot be attributed to a session because generated active-channel
5496    /// ownership has no binding for the requested channel.
5497    #[cfg(feature = "live")]
5498    live_unbound_rejection_authority: crate::driver::ephemeral::SharedIngressDslAuthority,
5499    /// Canonical owner of "this session id is currently active" — replaces
5500    /// the deleted process-global `SESSION_IDENTITY_CLAIMS` static in the
5501    /// comms shell (dogma #2). Comms runtimes acquire a typed
5502    /// [`meerkat_core::handles::SessionClaim`] through this handle and hold
5503    /// it for their lifetime; the registry is scoped to this `MeerkatMachine`
5504    /// instance, so tests / multi-runtime processes get clean isolation.
5505    session_claims: Arc<crate::handles::RuntimeSessionClaimRegistry>,
5506    /// Optional typed signal dispatcher for MeerkatMachine lifecycle
5507    /// effects routed by `meerkat_mob_seam` into MobMachine observation
5508    /// signals.
5509    composition_signal_dispatcher:
5510        StdRwLock<Option<composition::MeerkatCompositionSignalDispatcher>>,
5511    /// One-shot deterministic fault for the materializer's executor-attach
5512    /// publication window. Test-support only; production builds compile the
5513    /// post-ensure hook to a no-op and carry no field.
5514    #[cfg(feature = "test-support")]
5515    test_stop_executor_after_ensure: std::sync::atomic::AtomicBool,
5516    /// Optional deterministic pause paired with the post-ensure stop fault.
5517    /// Cross-crate tests acquire competing surface boundaries immediately
5518    /// before the exact pending attachment is stopped and cleanup begins.
5519    #[cfg(feature = "test-support")]
5520    test_pause_executor_after_ensure: std::sync::atomic::AtomicBool,
5521    #[cfg(feature = "test-support")]
5522    test_executor_after_ensure_pause_reached: crate::tokio::sync::Notify,
5523    #[cfg(feature = "test-support")]
5524    test_executor_after_ensure_pause_release: crate::tokio::sync::Notify,
5525    /// Deterministic test gate after fenced input captures its residency slot
5526    /// and exact session gate but before it locks that session gate.
5527    #[cfg(test)]
5528    test_fenced_accept_after_lease: StdMutex<
5529        Option<(
5530            crate::tokio::sync::oneshot::Sender<()>,
5531            crate::tokio::sync::oneshot::Receiver<()>,
5532        )>,
5533    >,
5534    /// One-shot positive witness for registration-slot contention. The armed
5535    /// acquisition itself uses `try_lock_owned`, reports whether it found the
5536    /// exact stable slot held, then either returns that guard or waits on that
5537    /// same slot. Test-only mechanical observation, not lifecycle authority.
5538    #[cfg(test)]
5539    test_registration_transaction_contention_probe:
5540        StdMutex<Option<(SessionId, crate::tokio::sync::oneshot::Sender<bool>)>>,
5541    /// One-shot fault after a machine-managed executor hands its exact
5542    /// post-stop mutation fence into cleanup. Proves retry reacquires instead
5543    /// of retaining or fabricating that consumed guard.
5544    #[cfg(test)]
5545    test_fail_post_stop_unregister_after_fence: StdMutex<Option<SessionId>>,
5546    /// One-shot deterministic gate after the runtime loop's first ready-effect
5547    /// drain but before it acquires queue authority. Tests publish an executor
5548    /// effect in this exact gap and prove the consumed wake is retained.
5549    #[cfg(any(test, feature = "test-support"))]
5550    test_runtime_loop_before_queue_authority: StdMutex<
5551        Option<(
5552            SessionId,
5553            crate::tokio::sync::oneshot::Sender<()>,
5554            crate::tokio::sync::oneshot::Receiver<()>,
5555        )>,
5556    >,
5557    /// One-shot deterministic gate after a logical control command resolves
5558    /// its SessionId but before it acquires the current entry's mutation gate.
5559    /// Tests replace A with B in this window to prove the command captures no
5560    /// incarnation-local handles before M.
5561    #[cfg(test)]
5562    test_control_command_after_logical_lookup: StdMutex<
5563        Option<(
5564            ControlCommandLookupTestKind,
5565            SessionId,
5566            crate::tokio::sync::oneshot::Sender<()>,
5567            crate::tokio::sync::oneshot::Receiver<()>,
5568        )>,
5569    >,
5570}
5571
5572#[cfg(test)]
5573#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5574enum ControlCommandLookupTestKind {
5575    PublishEvent,
5576    Recycle,
5577    Recover,
5578    Destroy,
5579    Retire,
5580}
5581
5582/// Exclusive lifecycle transaction for one stable member-residency slot.
5583/// Host materialization/revival acquires this before an old runtime can be
5584/// quiesced or a replacement can become reachable, then carries it back to
5585/// the host actor's durable commit point. Dropping an uncommitted update
5586/// publishes a vacant placed slot, so stale authority can never reappear
5587/// after a failed cutover.
5588pub struct MemberResidencyUpdate {
5589    adapter: Arc<MeerkatMachine>,
5590    session_id: SessionId,
5591    slot: Arc<MemberResidencySlot>,
5592    slot_guard: Option<crate::tokio::sync::OwnedMutexGuard<()>>,
5593    committed: bool,
5594}
5595
5596/// Post-commit publication guard for the normal host-delivery path. The placed
5597/// registration and journal are already visible, but no member effect may
5598/// acquire the residency slot until the host actor has published its matching
5599/// observation projection/cache. If the host responder/runtime disappears
5600/// after its durable row and machine residency commit, those durable facts are
5601/// restart authority; the process-owned publication result may be dropped as
5602/// part of that runtime-catastrophe boundary.
5603pub struct MemberResidencyPublication {
5604    _slot_guard: crate::tokio::sync::OwnedMutexGuard<()>,
5605}
5606
5607/// Reversible publication used only while an exact attachment still owns M.
5608///
5609/// The placed residency is visible synchronously to the machine commit, but
5610/// dropping this value before `finalize` restores the host-owned vacancy while
5611/// the stable residency slot is still exclusively held. This is what lets a
5612/// failed runtime-loop serving release roll back every local publication at
5613/// the same linearization point.
5614struct StagedMemberResidencyPublication {
5615    slot: Arc<MemberResidencySlot>,
5616    slot_guard: Option<crate::tokio::sync::OwnedMutexGuard<()>>,
5617}
5618
5619impl StagedMemberResidencyPublication {
5620    fn finalize(mut self) -> Result<MemberResidencyPublication, RuntimeDriverError> {
5621        let slot_guard = self.slot_guard.take().ok_or_else(|| {
5622            RuntimeDriverError::Internal(
5623                "staged member residency lost its exclusive slot guard".to_string(),
5624            )
5625        })?;
5626        Ok(MemberResidencyPublication {
5627            _slot_guard: slot_guard,
5628        })
5629    }
5630}
5631
5632impl Drop for StagedMemberResidencyPublication {
5633    fn drop(&mut self) {
5634        if self.slot_guard.is_none() {
5635            return;
5636        }
5637        *self
5638            .slot
5639            .state
5640            .write()
5641            .unwrap_or_else(std::sync::PoisonError::into_inner) =
5642            MemberResidencyState::VacantPlaced;
5643    }
5644}
5645
5646impl MemberResidencyUpdate {
5647    fn validate_for_retained_attachment(
5648        &self,
5649        adapter: &Arc<MeerkatMachine>,
5650        witness: &RuntimeExecutorAttachmentWitness,
5651        incarnation: &meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation,
5652    ) -> Result<(), RuntimeDriverError> {
5653        if !Arc::ptr_eq(&self.adapter, adapter) {
5654            return Err(RuntimeDriverError::StaleAuthority {
5655                reason: "member residency update belongs to another machine".to_string(),
5656            });
5657        }
5658        if &self.session_id != witness.session_id() {
5659            return Err(RuntimeDriverError::StaleAuthority {
5660                reason: format!(
5661                    "member residency update for session {} cannot publish attachment for {}",
5662                    self.session_id,
5663                    witness.session_id()
5664                ),
5665            });
5666        }
5667        if self.slot_guard.is_none() {
5668            return Err(RuntimeDriverError::Internal(
5669                "member residency update lost its publication guard".to_string(),
5670            ));
5671        }
5672        self.adapter
5673            .validate_member_incarnation_registration(&self.session_id, incarnation)
5674    }
5675
5676    fn stage_under_retained_mutation_gate(
5677        mut self,
5678        incarnation: meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation,
5679        tracked_turn_journal: Option<Arc<dyn crate::member_observation::TrackedTurnJournal>>,
5680        session_mutation_gate: Arc<crate::tokio::sync::Mutex<()>>,
5681    ) -> Result<StagedMemberResidencyPublication, RuntimeDriverError> {
5682        let slot_guard = self.slot_guard.take().ok_or_else(|| {
5683            RuntimeDriverError::Internal(
5684                "member residency update lost its publication guard".to_string(),
5685            )
5686        })?;
5687        *self
5688            .slot
5689            .state
5690            .write()
5691            .unwrap_or_else(std::sync::PoisonError::into_inner) =
5692            MemberResidencyState::Placed(MemberIncarnationRegistration {
5693                incarnation,
5694                session_mutation_gate,
5695                tracked_turn_journal,
5696            });
5697        self.committed = true;
5698        Ok(StagedMemberResidencyPublication {
5699            slot: Arc::clone(&self.slot),
5700            slot_guard: Some(slot_guard),
5701        })
5702    }
5703
5704    /// Publish the exact placed residency and optional tracked-turn journal
5705    /// while the lifecycle slot is still exclusively held.
5706    pub async fn commit(
5707        self,
5708        incarnation: meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation,
5709        tracked_turn_journal: Option<Arc<dyn crate::member_observation::TrackedTurnJournal>>,
5710    ) -> Result<MemberResidencyPublication, RuntimeDriverError> {
5711        self.adapter
5712            .validate_member_incarnation_registration(&self.session_id, &incarnation)?;
5713        let session_mutation_gate = self
5714            .adapter
5715            .lock_current_session_mutation_gate(&self.session_id)
5716            .await
5717            .ok_or(RuntimeDriverError::NotReady {
5718                state: RuntimeState::Destroyed,
5719            })?;
5720        let gate = {
5721            let sessions = self.adapter.sessions.read().await;
5722            Arc::clone(
5723                &sessions
5724                    .get(&self.session_id)
5725                    .ok_or(RuntimeDriverError::NotReady {
5726                        state: RuntimeState::Destroyed,
5727                    })?
5728                    .mutation_gate,
5729            )
5730        };
5731        let publication = self
5732            .stage_under_retained_mutation_gate(incarnation, tracked_turn_journal, gate)
5733            .and_then(StagedMemberResidencyPublication::finalize);
5734        drop(session_mutation_gate);
5735        publication
5736    }
5737
5738    /// Publish the host-owned slot as VacantPlaced while retaining the gate
5739    /// until the host actor has removed the matching observation projection.
5740    pub fn vacate(mut self) -> Result<MemberResidencyPublication, RuntimeDriverError> {
5741        let slot_guard = self.slot_guard.take().ok_or_else(|| {
5742            RuntimeDriverError::Internal(
5743                "member residency update lost its vacancy publication guard".to_string(),
5744            )
5745        })?;
5746        *self
5747            .slot
5748            .state
5749            .write()
5750            .unwrap_or_else(std::sync::PoisonError::into_inner) =
5751            MemberResidencyState::VacantPlaced;
5752        self.committed = true;
5753        Ok(MemberResidencyPublication {
5754            _slot_guard: slot_guard,
5755        })
5756    }
5757}
5758
5759impl Drop for MemberResidencyUpdate {
5760    fn drop(&mut self) {
5761        if self.committed {
5762            return;
5763        }
5764        *self
5765            .slot
5766            .state
5767            .write()
5768            .unwrap_or_else(std::sync::PoisonError::into_inner) =
5769            MemberResidencyState::VacantPlaced;
5770    }
5771}
5772
5773/// Cloneable handle to the process-local runtime authority.
5774///
5775/// Clones share every machine-owned session fact and shell-mechanics lock.
5776/// The owned unregister coordinator uses a clone so dropping any individual
5777/// stop/unregister caller can never cancel teardown halfway through the
5778/// generated `Draining` window.
5779#[derive(Clone)]
5780pub struct MeerkatMachine {
5781    shared: Arc<MeerkatMachineShared>,
5782}
5783
5784impl std::ops::Deref for MeerkatMachine {
5785    type Target = MeerkatMachineShared;
5786
5787    fn deref(&self) -> &Self::Target {
5788        self.shared.as_ref()
5789    }
5790}
5791
5792impl MeerkatMachine {
5793    /// Return the stable transaction slot for one session id.
5794    ///
5795    /// Cold registration holds this slot across durable recovery/ops-epoch
5796    /// initialization and session-map publication. Unregister uses one section
5797    /// to install its exact coordinator and another around durable finalization
5798    /// plus compare-and-remove; the present coordinator/Draining entry fences
5799    /// the quiescence interval between them. A weak index preserves rendezvous
5800    /// for overlapping operations without retaining every session id for the
5801    /// lifetime of the machine.
5802    fn session_registration_transaction_slot(&self, session_id: &SessionId) -> Arc<Mutex<()>> {
5803        let mut slots = self
5804            .registration_transaction_slots
5805            .write()
5806            .unwrap_or_else(std::sync::PoisonError::into_inner);
5807        if let Some(slot) = slots.get(session_id).and_then(std::sync::Weak::upgrade) {
5808            return slot;
5809        }
5810        slots.retain(|_, slot| slot.upgrade().is_some());
5811        let slot = Arc::new(Mutex::new(()));
5812        slots.insert(session_id.clone(), Arc::downgrade(&slot));
5813        slot
5814    }
5815
5816    async fn lock_session_registration_transaction(
5817        &self,
5818        session_id: &SessionId,
5819    ) -> crate::tokio::sync::OwnedMutexGuard<()> {
5820        let slot = self.session_registration_transaction_slot(session_id);
5821        #[cfg(test)]
5822        let contention_probe = {
5823            let mut probe = self
5824                .test_registration_transaction_contention_probe
5825                .lock()
5826                .unwrap_or_else(std::sync::PoisonError::into_inner);
5827            if probe
5828                .as_ref()
5829                .is_some_and(|(probe_session_id, _)| probe_session_id == session_id)
5830            {
5831                probe.take().map(|(_, sender)| sender)
5832            } else {
5833                None
5834            }
5835        };
5836        #[cfg(test)]
5837        if let Some(contention_probe) = contention_probe {
5838            return match Arc::clone(&slot).try_lock_owned() {
5839                Ok(guard) => {
5840                    let _ = contention_probe.send(false);
5841                    guard
5842                }
5843                Err(_) => {
5844                    let _ = contention_probe.send(true);
5845                    slot.lock_owned().await
5846                }
5847            };
5848        }
5849        slot.lock_owned().await
5850    }
5851
5852    #[cfg(test)]
5853    pub(crate) fn probe_next_session_registration_transaction_contention_for_test(
5854        &self,
5855        session_id: &SessionId,
5856    ) -> crate::tokio::sync::oneshot::Receiver<bool> {
5857        let (sender, receiver) = crate::tokio::sync::oneshot::channel();
5858        *self
5859            .test_registration_transaction_contention_probe
5860            .lock()
5861            .unwrap_or_else(std::sync::PoisonError::into_inner) =
5862            Some((session_id.clone(), sender));
5863        receiver
5864    }
5865
5866    /// Begin an authority-owned placed-residency lifecycle update. The stable
5867    /// slot Arc is never removed, even when this transaction aborts.
5868    pub async fn begin_member_residency_update(
5869        self: &Arc<Self>,
5870        session_id: SessionId,
5871    ) -> MemberResidencyUpdate {
5872        let slot = self.member_incarnation_slot(&session_id);
5873        let slot_guard = Arc::clone(&slot.gate).lock_owned().await;
5874        *slot
5875            .state
5876            .write()
5877            .unwrap_or_else(std::sync::PoisonError::into_inner) =
5878            MemberResidencyState::VacantPlaced;
5879        MemberResidencyUpdate {
5880            adapter: Arc::clone(self),
5881            session_id,
5882            slot,
5883            slot_guard: Some(slot_guard),
5884            committed: false,
5885        }
5886    }
5887
5888    /// Capability token for store-only session-control mutations routed
5889    /// through this machine authority.
5890    #[must_use]
5891    pub fn session_control_authority(&self) -> MachineSessionControlAuthority {
5892        MachineSessionControlAuthority { _private: () }
5893    }
5894
5895    /// Install the machine-wide member-observation host (multi-host mobs
5896    /// DEC-P6E-2). Called by the composing surface (the mob host daemon);
5897    /// sessions resolve per call, so residency establishment needs no
5898    /// per-session install.
5899    pub fn set_member_observation_host(
5900        &self,
5901        host: Arc<dyn crate::member_observation::MemberObservationHost>,
5902    ) {
5903        *self
5904            .member_observation_host
5905            .write()
5906            .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(host);
5907    }
5908
5909    /// Resolve the injected member-observation host, if composed.
5910    pub(crate) fn member_observation_host(
5911        &self,
5912    ) -> Option<Arc<dyn crate::member_observation::MemberObservationHost>> {
5913        self.member_observation_host
5914            .read()
5915            .unwrap_or_else(std::sync::PoisonError::into_inner)
5916            .clone()
5917    }
5918
5919    /// Install the machine-wide member live host (multi-host mobs
5920    /// ADJ-P6B-1). Called by the composing surface (the mob host daemon,
5921    /// live-capable `rkat-rpc`); sessions resolve per call, so member
5922    /// materialization needs no per-session install.
5923    pub fn set_member_live_host(&self, host: Arc<dyn crate::member_live::MemberLiveHost>) {
5924        *self
5925            .member_live_host
5926            .write()
5927            .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(host);
5928    }
5929
5930    /// Resolve the injected member live host, if composed.
5931    pub(crate) fn member_live_host(&self) -> Option<Arc<dyn crate::member_live::MemberLiveHost>> {
5932        self.member_live_host
5933            .read()
5934            .unwrap_or_else(std::sync::PoisonError::into_inner)
5935            .clone()
5936    }
5937
5938    /// Acquire the session's live/lifecycle lease for a complete `live/open`
5939    /// materialization.  Every surface (RPC and mob member gateways) enters
5940    /// the same orchestration path and therefore the same machine-owned gate.
5941    #[cfg(feature = "live")]
5942    pub async fn acquire_live_open_lifecycle_lease(
5943        &self,
5944        session_id: &meerkat_core::types::SessionId,
5945    ) -> Result<crate::member_live::MemberLiveLifecycleLease, RuntimeDriverError> {
5946        let (gate, guard) = self
5947            .lock_current_session_live_lifecycle_gate(session_id)
5948            .await
5949            .ok_or(RuntimeDriverError::NotReady {
5950                state: RuntimeState::Destroyed,
5951            })?;
5952        Ok(crate::member_live::MemberLiveLifecycleLease::new(
5953            session_id.clone(),
5954            gate,
5955            guard,
5956        ))
5957    }
5958
5959    /// Acquire the same lease as `live/open`, prove physical live-channel
5960    /// absence, and return it still held.  The lifecycle owner must retain the
5961    /// returned value through its durable retire/archive marker; dropping it
5962    /// earlier would reopen the original RPC-open race.
5963    #[cfg(feature = "live")]
5964    pub async fn acquire_member_live_disposal_lease(
5965        &self,
5966        session_id: &meerkat_core::types::SessionId,
5967    ) -> Result<crate::member_live::MemberLiveLifecycleLease, crate::member_live::MemberLiveError>
5968    {
5969        let (gate, guard) = self
5970            .lock_current_session_live_lifecycle_gate(session_id)
5971            .await
5972            .ok_or_else(|| crate::member_live::MemberLiveError::Unavailable {
5973                reason: format!(
5974                    "member-live lifecycle gate is unavailable for session {session_id}"
5975                ),
5976            })?;
5977        let lease =
5978            crate::member_live::MemberLiveLifecycleLease::new(session_id.clone(), gate, guard);
5979
5980        self.prove_member_live_absence_while_lease_held(session_id, &lease)
5981            .await?;
5982        Ok(lease)
5983    }
5984
5985    /// Close any active channel while the caller retains this exact session's
5986    /// live/lifecycle lease. Exact construction rollback uses this split form:
5987    /// raw lease first, mutation gate second, identity revalidation third,
5988    /// physical absence proof last. That order cannot close a same-ID
5989    /// replacement before discovering that its epoch/claim changed.
5990    #[cfg(feature = "live")]
5991    async fn prove_member_live_absence_while_lease_held(
5992        &self,
5993        session_id: &meerkat_core::types::SessionId,
5994        lease: &crate::member_live::MemberLiveLifecycleLease,
5995    ) -> Result<(), crate::member_live::MemberLiveError> {
5996        if lease.session_id() != session_id {
5997            return Err(crate::member_live::MemberLiveError::Internal {
5998                reason: format!(
5999                    "member-live lifecycle lease for session {} cannot dispose session {session_id}",
6000                    lease.session_id()
6001                ),
6002            });
6003        }
6004        let lease_is_current = {
6005            let sessions = self.sessions.read().await;
6006            sessions
6007                .get(session_id)
6008                .is_some_and(|entry| lease.matches_gate(&entry.live_lifecycle_gate))
6009        };
6010        if !lease_is_current {
6011            return Err(crate::member_live::MemberLiveError::Unavailable {
6012                reason: format!("member-live lifecycle lease is stale for session {session_id}"),
6013            });
6014        }
6015
6016        let Some(channel_id) = self.live_active_channel_for_session(session_id).await else {
6017            return Ok(());
6018        };
6019        let Some(host) = self.member_live_host() else {
6020            return Err(crate::member_live::MemberLiveError::Unavailable {
6021                reason: format!(
6022                    "session {session_id} still owns live channel '{channel_id}', but no transport-neutral live cleanup host is composed"
6023                ),
6024            });
6025        };
6026        let timeout = crate::member_live::MEMBER_LIVE_DISPOSAL_CEILING;
6027        match crate::tokio::time::timeout(timeout, host.close(session_id, channel_id.as_str()))
6028            .await
6029        {
6030            Ok(Ok(_)) => {}
6031            Ok(Err(error)) => return Err(error),
6032            Err(_) => {
6033                return Err(crate::member_live::MemberLiveError::Unavailable {
6034                    reason: format!(
6035                        "member-live disposal close for channel '{channel_id}' timed out after {}ms for session {session_id}",
6036                        timeout.as_millis()
6037                    ),
6038                });
6039            }
6040        }
6041        if let Some(still_active) = self.live_active_channel_for_session(session_id).await {
6042            return Err(crate::member_live::MemberLiveError::Internal {
6043                reason: format!(
6044                    "member-live close returned success but machine channel '{still_active}' remains active for session {session_id}"
6045                ),
6046            });
6047        }
6048        Ok(())
6049    }
6050
6051    /// Prove this session has no active member-live channel before an owning
6052    /// lifecycle tears down its runtime/session binding. The machine owns the
6053    /// injected host slot, so disposal does not receive or cache a second live
6054    /// gateway. A missing host is accepted only when generated authority has
6055    /// no active channel; active generated ownership without a cleanup host is
6056    /// a composition error, never negative transport evidence.
6057    ///
6058    /// Each host call is bounded independently. Any timeout or ambiguous host
6059    /// failure is returned to the caller, which must keep its lifecycle marker
6060    /// uncommitted and retry. Machine-owned absence or a successful exact close
6061    /// proves absence; an unexpected host `ChannelNotFound` remains a custody
6062    /// mismatch rather than being laundered into success.
6063    #[cfg(feature = "live")]
6064    pub async fn close_member_live_channel_for_disposal(
6065        &self,
6066        session_id: &meerkat_core::types::SessionId,
6067    ) -> Result<(), crate::member_live::MemberLiveError> {
6068        let _lease = self.acquire_member_live_disposal_lease(session_id).await?;
6069        Ok(())
6070    }
6071
6072    /// Record one live bridge command arriving at this member's drain
6073    /// (counted at host resolution, before any reach — absent-slot rejects
6074    /// count too: the command DID reach the member).
6075    pub(crate) fn note_live_command_served(&self) {
6076        self.live_commands_served
6077            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6078    }
6079
6080    /// Live bridge commands served by this member's drain arms (S3b).
6081    pub fn live_commands_served(&self) -> u64 {
6082        self.live_commands_served
6083            .load(std::sync::atomic::Ordering::Relaxed)
6084    }
6085
6086    /// Resolve a session's tracked-turn journal, if registered. Public for
6087    /// member-host restart recovery: durable Pending rows must reattach the
6088    /// exact residency's journal without re-executing their input.
6089    pub fn tracked_turn_journal(
6090        &self,
6091        session_id: &SessionId,
6092    ) -> Option<Arc<dyn crate::member_observation::TrackedTurnJournal>> {
6093        let slot = self
6094            .member_incarnation_slots
6095            .read()
6096            .unwrap_or_else(std::sync::PoisonError::into_inner)
6097            .get(session_id)
6098            .cloned()?;
6099        let state = slot
6100            .state
6101            .read()
6102            .unwrap_or_else(std::sync::PoisonError::into_inner);
6103        match &*state {
6104            MemberResidencyState::Placed(registration) => registration.tracked_turn_journal.clone(),
6105            MemberResidencyState::PeerOnly | MemberResidencyState::VacantPlaced => None,
6106        }
6107    }
6108
6109    fn validate_member_incarnation_registration(
6110        &self,
6111        session_id: &SessionId,
6112        incarnation: &meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation,
6113    ) -> Result<(), RuntimeDriverError> {
6114        if incarnation.mob_id.is_empty()
6115            || incarnation.agent_identity.is_empty()
6116            || incarnation.host_id.is_empty()
6117            || incarnation.member_session_id != session_id.to_string()
6118            || incarnation.binding_generation == 0
6119            || incarnation.fence_token == 0
6120        {
6121            return Err(RuntimeDriverError::ValidationFailed {
6122                reason: format!(
6123                    "member incarnation registration requires complete identities plus nonzero host-binding generation and fence for session '{session_id}': {incarnation:?}"
6124                ),
6125            });
6126        }
6127        Ok(())
6128    }
6129
6130    /// Resolve the exact host-materialized incarnation for a resident
6131    /// session. `None` means either true PeerOnly or VacantPlaced; callers
6132    /// that need to authorize peer-only effects must use the residency lease,
6133    /// which distinguishes those states.
6134    pub fn member_incarnation(
6135        &self,
6136        session_id: &SessionId,
6137    ) -> Option<meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation> {
6138        let slot = self
6139            .member_incarnation_slots
6140            .read()
6141            .unwrap_or_else(std::sync::PoisonError::into_inner)
6142            .get(session_id)
6143            .cloned()?;
6144        let state = slot
6145            .state
6146            .read()
6147            .unwrap_or_else(std::sync::PoisonError::into_inner);
6148        match &*state {
6149            MemberResidencyState::Placed(registration) => Some(registration.incarnation.clone()),
6150            MemberResidencyState::PeerOnly | MemberResidencyState::VacantPlaced => None,
6151        }
6152    }
6153
6154    #[cfg(test)]
6155    pub(crate) fn test_member_residency_is_vacant(&self, session_id: &SessionId) -> bool {
6156        self.member_incarnation_slots
6157            .read()
6158            .unwrap_or_else(std::sync::PoisonError::into_inner)
6159            .get(session_id)
6160            .is_some_and(|slot| {
6161                matches!(
6162                    &*slot
6163                        .state
6164                        .read()
6165                        .unwrap_or_else(std::sync::PoisonError::into_inner),
6166                    MemberResidencyState::VacantPlaced
6167                )
6168            })
6169    }
6170
6171    fn member_incarnation_slot(&self, session_id: &SessionId) -> Arc<MemberResidencySlot> {
6172        if let Some(slot) = self
6173            .member_incarnation_slots
6174            .read()
6175            .unwrap_or_else(std::sync::PoisonError::into_inner)
6176            .get(session_id)
6177            .cloned()
6178        {
6179            return slot;
6180        }
6181        Arc::clone(
6182            self.member_incarnation_slots
6183                .write()
6184                .unwrap_or_else(std::sync::PoisonError::into_inner)
6185                .entry(session_id.clone())
6186                .or_insert_with(|| Arc::new(MemberResidencySlot::peer_only())),
6187        )
6188    }
6189
6190    /// Acquire the stable member-residency slot and the exact runtime-session
6191    /// gate installed with `expected`.  Holding both guards through the live
6192    /// effect makes validation and mutation one atomic authority interval:
6193    /// neither a G2 incarnation install nor a reused-session registration can
6194    /// overtake a G1 effect after its comparison.
6195    pub(crate) async fn lock_member_effect_authority(
6196        &self,
6197        session_id: &SessionId,
6198        expected: &meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation,
6199    ) -> Result<MemberEffectAuthorityGuard, RuntimeDriverError> {
6200        self.lock_optional_member_effect_authority(session_id, Some(expected))
6201            .await
6202    }
6203
6204    /// The peer-only sibling of [`Self::lock_member_effect_authority`]. A
6205    /// `None` expectation is still an authority claim: it holds the stable
6206    /// slot while proving that no host residency is registered, so a placed
6207    /// incarnation cannot appear between classification and effect.
6208    pub(crate) async fn lock_optional_member_effect_authority(
6209        &self,
6210        session_id: &SessionId,
6211        expected: Option<&meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation>,
6212    ) -> Result<MemberEffectAuthorityGuard, RuntimeDriverError> {
6213        let lease = self
6214            .acquire_member_effect_authority_lease(session_id, expected)
6215            .await?;
6216        let MemberEffectAuthorityLease {
6217            slot_guard,
6218            session_mutation_gate,
6219            ..
6220        } = lease;
6221        let session_guard = Arc::clone(&session_mutation_gate).lock_owned().await;
6222        {
6223            let sessions = self.sessions.read().await;
6224            let Some(entry) = sessions.get(session_id) else {
6225                return Err(RuntimeDriverError::StaleAuthority {
6226                    reason: format!(
6227                        "member effect expected incarnation {expected:?}; runtime session is absent"
6228                    ),
6229                });
6230            };
6231            if !Arc::ptr_eq(&entry.mutation_gate, &session_mutation_gate) {
6232                return Err(RuntimeDriverError::StaleAuthority {
6233                    reason: format!(
6234                        "member effect expected incarnation {expected:?}; runtime session was replaced"
6235                    ),
6236                });
6237            }
6238        }
6239        let state = self
6240            .existing_session_runtime_state(session_id)
6241            .await
6242            .unwrap_or(RuntimeState::Destroyed);
6243        self.reject_unregistration_drain_ingress(session_id, state)
6244            .await?;
6245        Ok(MemberEffectAuthorityGuard {
6246            _slot_guard: slot_guard,
6247            _session_guard: session_guard,
6248        })
6249    }
6250
6251    async fn acquire_member_effect_authority_lease(
6252        &self,
6253        session_id: &SessionId,
6254        expected: Option<&meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation>,
6255    ) -> Result<MemberEffectAuthorityLease, RuntimeDriverError> {
6256        let slot = self.member_incarnation_slot(session_id);
6257        let slot_guard = Arc::clone(&slot.gate).lock_owned().await;
6258        let registered_gate = {
6259            let state = slot
6260                .state
6261                .read()
6262                .unwrap_or_else(std::sync::PoisonError::into_inner);
6263            match (expected, &*state) {
6264                (Some(expected), MemberResidencyState::Placed(registration))
6265                    if &registration.incarnation == expected =>
6266                {
6267                    Some(Arc::clone(&registration.session_mutation_gate))
6268                }
6269                (Some(expected), MemberResidencyState::Placed(registration)) => {
6270                    return Err(RuntimeDriverError::StaleAuthority {
6271                        reason: format!(
6272                            "member effect expected incarnation {expected:?}; current is {:?}",
6273                            registration.incarnation
6274                        ),
6275                    });
6276                }
6277                (
6278                    Some(expected),
6279                    MemberResidencyState::PeerOnly | MemberResidencyState::VacantPlaced,
6280                ) => {
6281                    return Err(RuntimeDriverError::StaleAuthority {
6282                        reason: format!(
6283                            "member effect expected incarnation {expected:?}; current residency is absent"
6284                        ),
6285                    });
6286                }
6287                (None, MemberResidencyState::Placed(registration)) => {
6288                    return Err(RuntimeDriverError::StaleAuthority {
6289                        reason: format!(
6290                            "peer-only member effect expected no host residency; current is {:?}",
6291                            registration.incarnation
6292                        ),
6293                    });
6294                }
6295                (None, MemberResidencyState::VacantPlaced) => {
6296                    return Err(RuntimeDriverError::StaleAuthority {
6297                        reason: "peer-only member effect cannot address a vacant placed residency"
6298                            .to_string(),
6299                    });
6300                }
6301                (None, MemberResidencyState::PeerOnly) => None,
6302            }
6303        };
6304        let registered_gate = match registered_gate {
6305            Some(gate) => gate,
6306            None => self.session_mutation_gate(session_id).await.ok_or(
6307                RuntimeDriverError::NotReady {
6308                    state: RuntimeState::Destroyed,
6309                },
6310            )?,
6311        };
6312        {
6313            let sessions = self.sessions.read().await;
6314            let Some(entry) = sessions.get(session_id) else {
6315                return Err(RuntimeDriverError::StaleAuthority {
6316                    reason: format!(
6317                        "member effect expected incarnation {expected:?}; runtime session is absent"
6318                    ),
6319                });
6320            };
6321            if !Arc::ptr_eq(&entry.mutation_gate, &registered_gate) {
6322                return Err(RuntimeDriverError::StaleAuthority {
6323                    reason: format!(
6324                        "member effect expected incarnation {expected:?}; runtime session was replaced"
6325                    ),
6326                });
6327            }
6328        }
6329        Ok(MemberEffectAuthorityLease {
6330            slot,
6331            slot_guard,
6332            session_mutation_gate: registered_gate,
6333        })
6334    }
6335
6336    /// Revalidate the residency half of a retained member-effect lease without
6337    /// reacquiring its slot gate. The caller still owns `lease.slot_guard`, so
6338    /// this read is both race-free and preserves the slot -> session lock order.
6339    fn validate_member_effect_authority_lease_current(
6340        &self,
6341        session_id: &SessionId,
6342        lease: &MemberEffectAuthorityLease,
6343        expected: Option<&meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation>,
6344    ) -> Result<(), RuntimeDriverError> {
6345        let slot_is_current = self
6346            .member_incarnation_slots
6347            .read()
6348            .unwrap_or_else(std::sync::PoisonError::into_inner)
6349            .get(session_id)
6350            .is_some_and(|slot| Arc::ptr_eq(slot, &lease.slot));
6351        if !slot_is_current {
6352            return Err(RuntimeDriverError::StaleAuthority {
6353                reason: "member effect residency slot was replaced".to_string(),
6354            });
6355        }
6356
6357        let state = lease
6358            .slot
6359            .state
6360            .read()
6361            .unwrap_or_else(std::sync::PoisonError::into_inner);
6362        match (expected, &*state) {
6363            (Some(expected), MemberResidencyState::Placed(registration))
6364                if &registration.incarnation == expected
6365                    && Arc::ptr_eq(
6366                        &registration.session_mutation_gate,
6367                        &lease.session_mutation_gate,
6368                    ) =>
6369            {
6370                Ok(())
6371            }
6372            (Some(expected), MemberResidencyState::Placed(registration)) => {
6373                Err(RuntimeDriverError::StaleAuthority {
6374                    reason: format!(
6375                        "member effect expected incarnation {expected:?}; current is {:?}",
6376                        registration.incarnation
6377                    ),
6378                })
6379            }
6380            (
6381                Some(expected),
6382                MemberResidencyState::PeerOnly | MemberResidencyState::VacantPlaced,
6383            ) => Err(RuntimeDriverError::StaleAuthority {
6384                reason: format!(
6385                    "member effect expected incarnation {expected:?}; current residency is absent"
6386                ),
6387            }),
6388            (None, MemberResidencyState::PeerOnly) => Ok(()),
6389            (None, MemberResidencyState::Placed(registration)) => {
6390                Err(RuntimeDriverError::StaleAuthority {
6391                    reason: format!(
6392                        "peer-only member effect expected no host residency; current is {:?}",
6393                        registration.incarnation
6394                    ),
6395                })
6396            }
6397            (None, MemberResidencyState::VacantPlaced) => Err(RuntimeDriverError::StaleAuthority {
6398                reason: "peer-only member effect cannot address a vacant placed residency"
6399                    .to_string(),
6400            }),
6401        }
6402    }
6403
6404    /// Whether this adapter shares the same runtime persistence authority as
6405    /// another adapter. Runtime-backed composition surfaces use this to reject
6406    /// mismatched adapters before visible terminal events can outrun the store
6407    /// that owns their durable commit.
6408    #[must_use]
6409    pub fn shares_runtime_persistence_with(&self, other: &Self) -> bool {
6410        match (&self.store, &other.store) {
6411            (None, None) => true,
6412            (Some(a), Some(b)) => runtime_stores_share_authority(a, b),
6413            _ => false,
6414        }
6415    }
6416
6417    /// Whether this adapter owns the same runtime persistence authority as a
6418    /// concrete runtime store handle.
6419    #[must_use]
6420    pub fn shares_runtime_store_authority(&self, store: &Arc<dyn RuntimeStore>) -> bool {
6421        self.store
6422            .as_ref()
6423            .is_some_and(|machine_store| runtime_stores_share_authority(machine_store, store))
6424    }
6425
6426    /// Whether this adapter has a runtime persistence store.
6427    #[must_use]
6428    pub fn has_runtime_persistence(&self) -> bool {
6429        self.store.is_some()
6430    }
6431
6432    fn normalize_destroyed_error(err: RuntimeDriverError) -> RuntimeDriverError {
6433        match err {
6434            RuntimeDriverError::NotReady {
6435                state: RuntimeState::Destroyed,
6436            } => RuntimeDriverError::Destroyed,
6437            other => other,
6438        }
6439    }
6440
6441    /// Create an ephemeral adapter (all sessions use EphemeralRuntimeDriver).
6442    pub fn ephemeral() -> Self {
6443        let auth_lease = Arc::new(crate::handles::RuntimeAuthLeaseHandle::new());
6444        #[cfg(not(target_arch = "wasm32"))]
6445        let oauth_flows = Arc::new(crate::handles::RuntimeOAuthFlowHandle::new_with_auth_lease(
6446            std::time::Duration::from_secs(10 * 60),
6447            Arc::clone(&auth_lease),
6448        ));
6449        let auth_lease = generated_runtime_auth_lease_handle(auth_lease);
6450        Self {
6451            shared: Arc::new(MeerkatMachineShared {
6452                sessions: RwLock::new(HashMap::new()),
6453                registration_transaction_slots: StdRwLock::new(HashMap::new()),
6454                store: None,
6455                blob_store: None,
6456                llm_reconfigure_host: StdRwLock::new(None),
6457                member_observation_host: StdRwLock::new(None),
6458                member_live_host: StdRwLock::new(None),
6459                live_commands_served: std::sync::atomic::AtomicU64::new(0),
6460                member_incarnation_slots: StdRwLock::new(HashMap::new()),
6461                auth_lease: StdRwLock::new(auth_lease),
6462                #[cfg(not(target_arch = "wasm32"))]
6463                oauth_flows: StdRwLock::new(oauth_flows),
6464                #[cfg(feature = "live")]
6465                live_unbound_rejection_authority: live_unbound_rejection_authority(),
6466                session_claims: Arc::new(crate::handles::RuntimeSessionClaimRegistry::new()),
6467                composition_signal_dispatcher: StdRwLock::new(None),
6468                #[cfg(feature = "test-support")]
6469                test_stop_executor_after_ensure: std::sync::atomic::AtomicBool::new(false),
6470                #[cfg(feature = "test-support")]
6471                test_pause_executor_after_ensure: std::sync::atomic::AtomicBool::new(false),
6472                #[cfg(feature = "test-support")]
6473                test_executor_after_ensure_pause_reached: crate::tokio::sync::Notify::new(),
6474                #[cfg(feature = "test-support")]
6475                test_executor_after_ensure_pause_release: crate::tokio::sync::Notify::new(),
6476                #[cfg(test)]
6477                test_fenced_accept_after_lease: StdMutex::new(None),
6478                #[cfg(test)]
6479                test_registration_transaction_contention_probe: StdMutex::new(None),
6480                #[cfg(test)]
6481                test_fail_post_stop_unregister_after_fence: StdMutex::new(None),
6482                #[cfg(any(test, feature = "test-support"))]
6483                test_runtime_loop_before_queue_authority: StdMutex::new(None),
6484                #[cfg(test)]
6485                test_control_command_after_logical_lookup: StdMutex::new(None),
6486            }),
6487        }
6488    }
6489
6490    /// Create a persistent adapter with a RuntimeStore.
6491    ///
6492    /// One logical runtime id must have exactly one live `MeerkatMachine`
6493    /// authority. Separate machines may partition one store across distinct
6494    /// runtime ids, but must not concurrently register the same session. Share
6495    /// this machine (normally through `Arc`) when composing multiple surfaces.
6496    pub fn persistent(store: Arc<dyn RuntimeStore>, blob_store: Arc<dyn BlobStore>) -> Self {
6497        #[cfg(not(target_arch = "wasm32"))]
6498        let (auth_lease, oauth_flows) = {
6499            let authorities = persistent_auth_authorities(&store);
6500            (
6501                Arc::clone(&authorities.auth_lease),
6502                Arc::clone(&authorities.oauth_flows),
6503            )
6504        };
6505        #[cfg(target_arch = "wasm32")]
6506        let auth_lease = Arc::new(crate::handles::RuntimeAuthLeaseHandle::new());
6507        let auth_lease = generated_runtime_auth_lease_handle(auth_lease);
6508        Self {
6509            shared: Arc::new(MeerkatMachineShared {
6510                sessions: RwLock::new(HashMap::new()),
6511                registration_transaction_slots: StdRwLock::new(HashMap::new()),
6512                store: Some(store),
6513                blob_store: Some(blob_store),
6514                llm_reconfigure_host: StdRwLock::new(None),
6515                member_observation_host: StdRwLock::new(None),
6516                member_live_host: StdRwLock::new(None),
6517                live_commands_served: std::sync::atomic::AtomicU64::new(0),
6518                member_incarnation_slots: StdRwLock::new(HashMap::new()),
6519                auth_lease: StdRwLock::new(auth_lease),
6520                #[cfg(not(target_arch = "wasm32"))]
6521                oauth_flows: StdRwLock::new(oauth_flows),
6522                #[cfg(feature = "live")]
6523                live_unbound_rejection_authority: live_unbound_rejection_authority(),
6524                session_claims: Arc::new(crate::handles::RuntimeSessionClaimRegistry::new()),
6525                composition_signal_dispatcher: StdRwLock::new(None),
6526                #[cfg(feature = "test-support")]
6527                test_stop_executor_after_ensure: std::sync::atomic::AtomicBool::new(false),
6528                #[cfg(feature = "test-support")]
6529                test_pause_executor_after_ensure: std::sync::atomic::AtomicBool::new(false),
6530                #[cfg(feature = "test-support")]
6531                test_executor_after_ensure_pause_reached: crate::tokio::sync::Notify::new(),
6532                #[cfg(feature = "test-support")]
6533                test_executor_after_ensure_pause_release: crate::tokio::sync::Notify::new(),
6534                #[cfg(test)]
6535                test_fenced_accept_after_lease: StdMutex::new(None),
6536                #[cfg(test)]
6537                test_registration_transaction_contention_probe: StdMutex::new(None),
6538                #[cfg(test)]
6539                test_fail_post_stop_unregister_after_fence: StdMutex::new(None),
6540                #[cfg(any(test, feature = "test-support"))]
6541                test_runtime_loop_before_queue_authority: StdMutex::new(None),
6542                #[cfg(test)]
6543                test_control_command_after_logical_lookup: StdMutex::new(None),
6544            }),
6545        }
6546    }
6547
6548    /// Create a persistent adapter with a RuntimeStore but no blob store.
6549    ///
6550    /// The driver remains persistent for session state. Blob-backed inputs fail
6551    /// explicitly at the blob-store boundary until a real [`BlobStore`] is
6552    /// supplied. As with [`Self::persistent`], one logical runtime id must have
6553    /// exactly one live machine authority.
6554    pub fn persistent_without_blobs(store: Arc<dyn RuntimeStore>) -> Self {
6555        #[cfg(not(target_arch = "wasm32"))]
6556        let (auth_lease, oauth_flows) = {
6557            let authorities = persistent_auth_authorities(&store);
6558            (
6559                Arc::clone(&authorities.auth_lease),
6560                Arc::clone(&authorities.oauth_flows),
6561            )
6562        };
6563        #[cfg(target_arch = "wasm32")]
6564        let auth_lease = Arc::new(crate::handles::RuntimeAuthLeaseHandle::new());
6565        let auth_lease = generated_runtime_auth_lease_handle(auth_lease);
6566        Self {
6567            shared: Arc::new(MeerkatMachineShared {
6568                sessions: RwLock::new(HashMap::new()),
6569                registration_transaction_slots: StdRwLock::new(HashMap::new()),
6570                store: Some(store),
6571                blob_store: Some(Arc::new(UnavailableBlobStore)),
6572                llm_reconfigure_host: StdRwLock::new(None),
6573                member_observation_host: StdRwLock::new(None),
6574                member_live_host: StdRwLock::new(None),
6575                live_commands_served: std::sync::atomic::AtomicU64::new(0),
6576                member_incarnation_slots: StdRwLock::new(HashMap::new()),
6577                auth_lease: StdRwLock::new(auth_lease),
6578                #[cfg(not(target_arch = "wasm32"))]
6579                oauth_flows: StdRwLock::new(oauth_flows),
6580                #[cfg(feature = "live")]
6581                live_unbound_rejection_authority: live_unbound_rejection_authority(),
6582                session_claims: Arc::new(crate::handles::RuntimeSessionClaimRegistry::new()),
6583                composition_signal_dispatcher: StdRwLock::new(None),
6584                #[cfg(feature = "test-support")]
6585                test_stop_executor_after_ensure: std::sync::atomic::AtomicBool::new(false),
6586                #[cfg(feature = "test-support")]
6587                test_pause_executor_after_ensure: std::sync::atomic::AtomicBool::new(false),
6588                #[cfg(feature = "test-support")]
6589                test_executor_after_ensure_pause_reached: crate::tokio::sync::Notify::new(),
6590                #[cfg(feature = "test-support")]
6591                test_executor_after_ensure_pause_release: crate::tokio::sync::Notify::new(),
6592                #[cfg(test)]
6593                test_fenced_accept_after_lease: StdMutex::new(None),
6594                #[cfg(test)]
6595                test_registration_transaction_contention_probe: StdMutex::new(None),
6596                #[cfg(test)]
6597                test_fail_post_stop_unregister_after_fence: StdMutex::new(None),
6598                #[cfg(any(test, feature = "test-support"))]
6599                test_runtime_loop_before_queue_authority: StdMutex::new(None),
6600                #[cfg(test)]
6601                test_control_command_after_logical_lookup: StdMutex::new(None),
6602            }),
6603        }
6604    }
6605
6606    /// Shared auth lifecycle handle used by all runtime-backed session
6607    /// bindings created by this adapter.
6608    pub fn auth_lease_handle(&self) -> Arc<dyn meerkat_core::handles::AuthLeaseHandle> {
6609        self.generated_auth_lease_handle().clone_handle()
6610    }
6611
6612    /// Generated-authority-certified auth lifecycle handle used at factory and
6613    /// resolver seams that must reject arbitrary handwritten handles.
6614    pub fn generated_auth_lease_handle(&self) -> meerkat_core::handles::GeneratedAuthLeaseHandle {
6615        self.auth_lease
6616            .read()
6617            .unwrap_or_else(std::sync::PoisonError::into_inner)
6618            .clone()
6619    }
6620
6621    /// Install the auth lifecycle authority that public surfaces also read.
6622    ///
6623    /// Surfaces construct the adapter before all state fields are available, so
6624    /// this setter lets them align the adapter's runtime-backed traffic with
6625    /// the surface-visible status handle without creating a competing registry.
6626    pub fn set_auth_lease_handle(&self, handle: Arc<crate::handles::RuntimeAuthLeaseHandle>) {
6627        self.set_runtime_auth_lease_handle(handle);
6628    }
6629
6630    /// Install the runtime credential lifecycle handle together with an
6631    /// explicit OAuth login-flow authority.
6632    ///
6633    /// The credential side still has to be a generated AuthMachine authority;
6634    /// the explicit OAuth authority only controls login-flow test seams.
6635    #[cfg(not(target_arch = "wasm32"))]
6636    pub fn set_auth_lease_handle_with_oauth_flow_authority(
6637        &self,
6638        handle: Arc<crate::handles::RuntimeAuthLeaseHandle>,
6639        oauth_flows: Arc<dyn meerkat_auth_core::oauth_flow::OAuthFlowAuthority>,
6640    ) {
6641        *self
6642            .oauth_flows
6643            .write()
6644            .unwrap_or_else(std::sync::PoisonError::into_inner) = oauth_flows;
6645        let handle = generated_runtime_auth_lease_handle(handle);
6646        *self
6647            .auth_lease
6648            .write()
6649            .unwrap_or_else(std::sync::PoisonError::into_inner) = handle;
6650    }
6651
6652    /// Install a runtime AuthMachine authority shared by auth leases and OAuth
6653    /// login-flow lifecycle transitions.
6654    pub fn set_runtime_auth_lease_handle(
6655        &self,
6656        handle: Arc<crate::handles::RuntimeAuthLeaseHandle>,
6657    ) {
6658        #[cfg(not(target_arch = "wasm32"))]
6659        {
6660            *self
6661                .oauth_flows
6662                .write()
6663                .unwrap_or_else(std::sync::PoisonError::into_inner) =
6664                Arc::new(crate::handles::RuntimeOAuthFlowHandle::new_with_auth_lease(
6665                    std::time::Duration::from_secs(10 * 60),
6666                    Arc::clone(&handle),
6667                ));
6668        }
6669        let handle = generated_runtime_auth_lease_handle(handle);
6670        *self
6671            .auth_lease
6672            .write()
6673            .unwrap_or_else(std::sync::PoisonError::into_inner) = handle;
6674    }
6675
6676    /// Shared OAuth login-flow authority used by all auth surfaces that are
6677    /// backed by this runtime adapter.
6678    #[cfg(not(target_arch = "wasm32"))]
6679    pub fn oauth_flow_authority(
6680        &self,
6681    ) -> Arc<dyn meerkat_auth_core::oauth_flow::OAuthFlowAuthority> {
6682        Arc::clone(
6683            &self
6684                .oauth_flows
6685                .read()
6686                .unwrap_or_else(std::sync::PoisonError::into_inner),
6687        )
6688    }
6689
6690    /// The canonical session-identity claim handle owned by this
6691    /// `MeerkatMachine`. Comms runtimes wired through this machine acquire
6692    /// their session-id claim through it; the registry is scoped to this
6693    /// machine instance so tests / parallel runtimes do not collide.
6694    pub fn session_claim_handle(&self) -> Arc<dyn meerkat_core::handles::SessionClaimHandle> {
6695        Arc::clone(&self.session_claims) as Arc<dyn meerkat_core::handles::SessionClaimHandle>
6696    }
6697
6698    /// Attach the typed composition signal dispatcher used for
6699    /// MeerkatMachine -> MobMachine lifecycle observation routes.
6700    pub fn set_composition_signal_dispatcher(
6701        &self,
6702        dispatcher: composition::MeerkatCompositionSignalDispatcher,
6703    ) {
6704        let mut slot = self
6705            .composition_signal_dispatcher
6706            .write()
6707            .unwrap_or_else(std::sync::PoisonError::into_inner);
6708        *slot = Some(dispatcher);
6709    }
6710
6711    /// Apply a routed-input variant delivered by the `meerkat_mob_seam`
6712    /// composition dispatcher against the session's shared DSL authority.
6713    ///
6714    /// The caller is
6715    /// [`crate::meerkat_machine::composition::MeerkatConsumerSurface::apply_routed_input`];
6716    /// it has already projected producer fields into the typed
6717    /// [`dsl::MeerkatMachineInput`] shape. This method performs the
6718    /// session lookup + DSL-lock-scoped apply. A typed transition error
6719    /// from the kernel is surfaced as a `String` so the dispatcher can
6720    /// map it onto `DispatchRefusal::ConsumerRefused`.
6721    pub(crate) async fn apply_routed_meerkat_input(
6722        &self,
6723        session_id: &SessionId,
6724        input: dsl::MeerkatMachineInput,
6725    ) -> Result<(), dsl_authority::DslTransitionRefusal> {
6726        let _gate_guard = self
6727            .lock_current_session_mutation_gate(session_id)
6728            .await
6729            .ok_or_else(|| {
6730                dsl_authority::DslTransitionRefusal::other(
6731                    "routed_session_not_registered",
6732                    format!(
6733                        "session `{session_id}` is not registered with this MeerkatMachine; \
6734                         cannot deliver routed input"
6735                    ),
6736                )
6737            })?;
6738        self.apply_routed_session_dsl_input(session_id, input, "RoutedMeerkatInput")
6739            .await
6740            .map(|_| ())
6741    }
6742
6743    #[cfg(test)]
6744    pub(crate) async fn debug_shared_ingress_authorities(
6745        &self,
6746        session_id: &SessionId,
6747    ) -> Option<(
6748        Arc<std::sync::Mutex<dsl::MeerkatMachineAuthority>>,
6749        crate::driver::ephemeral::SharedIngressDslAuthority,
6750    )> {
6751        let sessions = self.sessions.read().await;
6752        let entry = sessions.get(session_id)?;
6753        let session_authority = Arc::clone(&entry.dsl_authority);
6754        let driver = entry.driver.lock().await;
6755        Some((session_authority, driver.shared_dsl_authority()))
6756    }
6757
6758    /// Create a driver entry for a session.
6759    fn make_driver(
6760        &self,
6761        runtime_id: LogicalRuntimeId,
6762        dsl_authority: crate::driver::ephemeral::SharedIngressDslAuthority,
6763        initial_runtime_state: RuntimeState,
6764    ) -> DriverEntry {
6765        let control_projection = Arc::new(StdRwLock::new(
6766            crate::driver::ephemeral::RuntimeControlProjection {
6767                phase: initial_runtime_state,
6768                current_run_id: None,
6769                pre_run_phase: None,
6770            },
6771        ));
6772        match (&self.store, &self.blob_store) {
6773            (Some(store), Some(blob_store)) => {
6774                DriverEntry::Persistent(PersistentRuntimeDriver::new_with_control(
6775                    runtime_id,
6776                    store.clone(),
6777                    blob_store.clone(),
6778                    control_projection,
6779                    dsl_authority,
6780                ))
6781            }
6782            _ => DriverEntry::Ephemeral(EphemeralRuntimeDriver::new_with_control_and_dsl(
6783                runtime_id,
6784                control_projection,
6785                dsl_authority,
6786            )),
6787        }
6788    }
6789
6790    /// Recover or create fresh ops lifecycle state for a session.
6791    ///
6792    /// This is the single canonical recovery seam. Both `register_session()`
6793    /// and `ensure_session_with_executor()`'s cold path call this to create
6794    /// epoch-local state. If a durable store is available, attempts to load
6795    /// the persisted snapshot; otherwise creates fresh state with a new epoch.
6796    async fn recover_or_create_ops_state(
6797        &self,
6798        session_id: &SessionId,
6799        runtime_id: &LogicalRuntimeId,
6800    ) -> Result<
6801        (
6802            Arc<crate::ops_lifecycle::RuntimeOpsLifecycleRegistry>,
6803            meerkat_core::RuntimeEpochId,
6804            Arc<meerkat_core::EpochCursorState>,
6805        ),
6806        RuntimeDriverError,
6807    > {
6808        if let Some(ref store) = self.store {
6809            let (registry, epoch_id, cursor_state) = Self::fresh_ops_state();
6810            // The epoch is an owner witness embedded in durable input/outbox
6811            // records. Atomically initialize its empty authority image before
6812            // bindings can escape. A competing cold registrar may win this
6813            // boundary; in that case we recover the winner's canonical image
6814            // rather than publishing two epochs for one logical runtime.
6815            let initial_snapshot = registry
6816                .capture_persistence_snapshot(epoch_id.clone(), cursor_state.as_ref())
6817                .map_err(|error| {
6818                    RuntimeDriverError::Internal(format!(
6819                        "failed to capture initial ops lifecycle authority for session {session_id}: {error}"
6820                    ))
6821                })?;
6822            let canonical_snapshot = store
6823                .initialize_ops_lifecycle_if_absent(runtime_id, &initial_snapshot)
6824                .await
6825                .map_err(|error| {
6826                    RuntimeDriverError::Internal(format!(
6827                        "failed to initialize ops lifecycle authority for session {session_id}: {error}"
6828                    ))
6829                })?;
6830            let recovered_epoch = canonical_snapshot.epoch_id.clone();
6831            let initialized = recovered_epoch == epoch_id;
6832            let recovered_ops_count = canonical_snapshot.completion_entries.len();
6833            let registry = crate::ops_lifecycle::RuntimeOpsLifecycleRegistry::from_recovered(
6834                canonical_snapshot,
6835            )
6836            .map_err(|error| {
6837                tracing::error!(
6838                    %session_id,
6839                    %runtime_id,
6840                    error = %error,
6841                    "failed to recover ops lifecycle through generated authority"
6842                );
6843                RuntimeDriverError::Internal(format!(
6844                    "failed to recover ops lifecycle through generated authority: {error}"
6845                ))
6846            })?;
6847            let recovered_cursor_snapshot = registry.completion_cursor_snapshot();
6848            let recovered_cursors = meerkat_core::EpochCursorState::from_recovered(
6849                recovered_cursor_snapshot.agent_applied_cursor,
6850                recovered_cursor_snapshot.runtime_observed_seq,
6851                recovered_cursor_snapshot.runtime_last_injected_seq,
6852            );
6853            tracing::info!(
6854                %session_id,
6855                %runtime_id,
6856                epoch_id = %recovered_epoch,
6857                recovered_ops = recovered_ops_count,
6858                initialized,
6859                "ops lifecycle authority selected from durable store"
6860            );
6861            Ok((
6862                Arc::new(registry),
6863                recovered_epoch,
6864                Arc::new(recovered_cursors),
6865            ))
6866        } else {
6867            Ok((
6868                Arc::new(crate::ops_lifecycle::RuntimeOpsLifecycleRegistry::new()),
6869                meerkat_core::RuntimeEpochId::new(),
6870                Arc::new(meerkat_core::EpochCursorState::new()),
6871            ))
6872        }
6873    }
6874
6875    fn fresh_ops_state() -> (
6876        Arc<crate::ops_lifecycle::RuntimeOpsLifecycleRegistry>,
6877        meerkat_core::RuntimeEpochId,
6878        Arc<meerkat_core::EpochCursorState>,
6879    ) {
6880        let registry = Arc::new(crate::ops_lifecycle::RuntimeOpsLifecycleRegistry::new());
6881        let epoch = meerkat_core::RuntimeEpochId::new();
6882        let cursors = Arc::new(meerkat_core::EpochCursorState::new());
6883        (registry, epoch, cursors)
6884    }
6885
6886    #[allow(clippy::large_futures)]
6887    fn execute_meerkat_machine_command(
6888        &self,
6889        self_handle: Option<Arc<Self>>,
6890        command: MeerkatMachineCommand,
6891    ) -> MeerkatMachineCommandFuture<'_> {
6892        Box::pin(async move {
6893            match command {
6894                MeerkatMachineCommand::EnsureSessionWithExecutor { .. } => {
6895                    let self_handle = self_handle.ok_or_else(|| {
6896                        MeerkatMachineCommandError::Driver(RuntimeDriverError::Internal(
6897                            "EnsureSessionWithExecutor requires Arc<Self> machine handle".into(),
6898                        ))
6899                    })?;
6900                    self_handle
6901                        .execute_meerkat_machine_ensure_session_command(command)
6902                        .await
6903                        .map_err(Into::into)
6904                }
6905                MeerkatMachineCommand::RegisterSession { .. }
6906                | MeerkatMachineCommand::UnregisterSession { .. }
6907                | MeerkatMachineCommand::SetSilentIntents { .. }
6908                | MeerkatMachineCommand::CancelAfterBoundary { .. }
6909                | MeerkatMachineCommand::StopRuntimeExecutor { .. }
6910                | MeerkatMachineCommand::CommitServiceTurnTerminalReceipt { .. }
6911                | MeerkatMachineCommand::ContainsSession { .. }
6912                | MeerkatMachineCommand::SessionHasExecutor { .. }
6913                | MeerkatMachineCommand::SessionHasComms { .. }
6914                | MeerkatMachineCommand::OpsLifecycleRegistry { .. }
6915                | MeerkatMachineCommand::PrepareBindings { .. }
6916                | MeerkatMachineCommand::PrepareLocalSessionBindings { .. }
6917                | MeerkatMachineCommand::InputState { .. }
6918                | MeerkatMachineCommand::InputStateByIdempotencyKey { .. }
6919                | MeerkatMachineCommand::InteractionTerminalStatus { .. }
6920                | MeerkatMachineCommand::RunTerminalStatus { .. }
6921                | MeerkatMachineCommand::ListActiveInputs { .. }
6922                | MeerkatMachineCommand::ReconfigureSessionLlmIdentity { .. }
6923                | MeerkatMachineCommand::StagePersistentFilter { .. }
6924                | MeerkatMachineCommand::RequestDeferredTools { .. }
6925                | MeerkatMachineCommand::PublishCommittedVisibleSet { .. } => self
6926                    .execute_meerkat_machine_session_command(command)
6927                    .await
6928                    .map_err(Into::into),
6929                MeerkatMachineCommand::SetPeerIngressContext { .. }
6930                | MeerkatMachineCommand::NotifyDrainExited { .. } => {
6931                    let self_handle = self_handle.ok_or_else(|| {
6932                        MeerkatMachineCommandError::Driver(RuntimeDriverError::Internal(
6933                            "drain command requires Arc<Self> machine handle".into(),
6934                        ))
6935                    })?;
6936                    self_handle
6937                        .execute_meerkat_machine_drain_command(command)
6938                        .await
6939                        .map_err(Into::into)
6940                }
6941                MeerkatMachineCommand::AbortAll
6942                | MeerkatMachineCommand::Abort { .. }
6943                | MeerkatMachineCommand::Wait { .. } => self
6944                    .execute_meerkat_machine_drain_local_command(command)
6945                    .await
6946                    .map_err(Into::into),
6947                MeerkatMachineCommand::Ingest { .. }
6948                | MeerkatMachineCommand::PublishEvent { .. }
6949                | MeerkatMachineCommand::Retire { .. }
6950                | MeerkatMachineCommand::Recycle { .. }
6951                | MeerkatMachineCommand::Reset { .. }
6952                | MeerkatMachineCommand::Recover { .. }
6953                | MeerkatMachineCommand::Destroy { .. }
6954                | MeerkatMachineCommand::RuntimeState { .. }
6955                | MeerkatMachineCommand::ResolvedSessionLlmCapabilities { .. }
6956                | MeerkatMachineCommand::ConfigureModelRoutingBaseline { .. }
6957                | MeerkatMachineCommand::SessionModelRoutingStatus { .. }
6958                | MeerkatMachineCommand::RequestSwitchTurn { .. }
6959                | MeerkatMachineCommand::AdmitModelRoutingAssistantTurn { .. }
6960                | MeerkatMachineCommand::BeginImageOperation { .. }
6961                | MeerkatMachineCommand::DenyImageOperationPlan { .. }
6962                | MeerkatMachineCommand::ActivateImageOperationOverride { .. }
6963                | MeerkatMachineCommand::ClassifyImageOperationTerminal { .. }
6964                | MeerkatMachineCommand::CompleteImageOperation { .. }
6965                | MeerkatMachineCommand::RestoreImageOperationOverride { .. }
6966                | MeerkatMachineCommand::LoadBoundaryReceipt { .. } => self
6967                    .execute_meerkat_machine_control_command(command)
6968                    .await
6969                    .map_err(Into::into),
6970                MeerkatMachineCommand::AcceptWithCompletion { .. }
6971                | MeerkatMachineCommand::AcceptWithoutWake { .. } => self
6972                    .execute_meerkat_machine_ingress_command(command)
6973                    .await
6974                    .map_err(Into::into),
6975            }
6976        })
6977    }
6978
6979    /// Register a runtime driver for a session (no RuntimeLoop — inputs queue but
6980    /// nothing processes them automatically). Useful for tests and legacy mode.
6981    ///
6982    /// Registration is a control-plane prerequisite: a failed register must not be
6983    /// laundered to success. The inner command can fail recovery, so the typed
6984    /// error is propagated to the caller rather than discarded.
6985    pub async fn register_session(
6986        &self,
6987        session_id: SessionId,
6988    ) -> Result<(), RuntimeControlPlaneError> {
6989        match self
6990            .execute_meerkat_machine_command(
6991                None,
6992                MeerkatMachineCommand::RegisterSession { session_id },
6993            )
6994            .await
6995            .map_err(MeerkatMachine::control_plane_error_from_command_error)?
6996        {
6997            MeerkatMachineCommandResult::Unit => Ok(()),
6998            other => Err(RuntimeControlPlaneError::Internal(format!(
6999                "register_session: unexpected command result variant: {other:?}"
7000            ))),
7001        }
7002    }
7003}
7004
7005#[cfg(test)]
7006#[allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
7007#[path = "../meerkat_machine_tests.rs"]
7008mod tests;