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