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