Skip to main content

meerkat_runtime/
lib.rs

1//! meerkat-runtime — v9 runtime control-plane for Meerkat agent lifecycle.
2//!
3//! This crate implements the runtime/control-plane layer of the v9 Canonical
4//! Lifecycle specification. It sits between surfaces (CLI, RPC, REST, MCP)
5//! and core (`meerkat-core`), managing:
6//!
7//! - Input acceptance, validation, and queueing
8//! - InputState lifecycle tracking
9//! - Policy resolution (what to do with each input)
10//! - Runtime state machine (Initializing ↔ Idle ↔ Attached ↔ Running ↔ Retired/Stopped/Destroyed)
11//! - Retire/recycle/reset lifecycle operations
12//! - RuntimeEvent observability
13//!
14//! Core-facing types (RunPrimitive, RunEvent, CoreExecutor, etc.) live in
15//! `meerkat-core::lifecycle`. This crate contains everything else.
16
17#![cfg_attr(
18    test,
19    allow(
20        dead_code,
21        unused_imports,
22        clippy::expect_used,
23        clippy::large_futures,
24        clippy::needless_borrow,
25        clippy::panic,
26        clippy::redundant_closure_for_method_calls,
27        clippy::redundant_clone,
28        clippy::type_complexity,
29        clippy::unnecessary_to_owned,
30        clippy::unwrap_used
31    )
32)]
33
34#[cfg(target_arch = "wasm32")]
35pub mod tokio {
36    pub use tokio_with_wasm::alias::*;
37}
38
39#[cfg(not(target_arch = "wasm32"))]
40pub use ::tokio;
41
42pub mod accept;
43pub mod auth_machine;
44pub mod coalescing;
45pub mod comms_bridge;
46pub mod comms_drain;
47pub mod comms_trust_reconcile;
48pub mod completion;
49pub mod composition;
50pub(crate) mod control_plane;
51pub mod delivery_inbox;
52pub mod driver;
53pub(crate) mod effect;
54#[doc(hidden)]
55pub mod generated;
56pub mod handles;
57pub mod identifiers;
58pub mod ingress_types;
59pub mod input;
60pub mod input_ledger;
61pub mod input_scope;
62pub mod input_state;
63pub mod interrupt_public_result;
64pub mod meerkat_machine;
65pub(crate) mod meerkat_machine_types;
66pub mod member_live;
67pub mod member_observation;
68pub mod mob_adapter;
69pub mod mob_operator_authority;
70pub mod ops_lifecycle;
71pub mod peer_handling_mode;
72pub mod policy;
73pub mod policy_table;
74#[allow(unused_imports)]
75#[path = "generated/protocol_auth_lease_lifecycle_publication.rs"]
76pub mod protocol_auth_lease_lifecycle_publication;
77#[allow(unused_imports)]
78#[path = "generated/protocol_auth_release_oauth_flow_drain.rs"]
79pub mod protocol_auth_release_oauth_flow_drain;
80#[allow(unused_imports)]
81#[path = "generated/protocol_comms_trust_reconcile.rs"]
82pub mod protocol_comms_trust_reconcile;
83#[allow(unused_imports)]
84#[path = "generated/protocol_supervisor_trust_publish.rs"]
85pub mod protocol_supervisor_trust_publish;
86#[allow(unused_imports)]
87#[path = "generated/protocol_supervisor_trust_revoke.rs"]
88pub mod protocol_supervisor_trust_revoke;
89pub(crate) mod queue;
90pub mod runtime_event;
91pub(crate) mod runtime_loop;
92pub mod runtime_state;
93pub mod service_ext;
94pub(crate) mod silent_intent;
95pub mod store;
96pub mod terminal_status;
97pub mod traits;
98
99use meerkat_core::lifecycle::run_primitive::RuntimeTurnMetadata as RuntimeStampedTurnMetadata;
100use std::any::Any;
101use std::sync::Arc;
102
103pub(crate) struct SessionRuntimeBindingsAuthority {
104    pub(crate) session_id: meerkat_core::SessionId,
105    pub(crate) epoch_id: meerkat_core::RuntimeEpochId,
106    pub(crate) dsl_authority: Arc<std::sync::Mutex<meerkat_machine::dsl::MeerkatMachineAuthority>>,
107    pub(crate) teardown_gate: Arc<handles::HandleTeardownGate>,
108    pub(crate) materialization_claim_id: Option<uuid::Uuid>,
109    pub(crate) materialization_claim_state:
110        Arc<std::sync::Mutex<RuntimeActorMaterializationClaimState>>,
111    /// Compatibility capability for cloneable `prepare_bindings()` results.
112    /// It is minted only while the registration is unattached and does not
113    /// itself reserve the exact materialization claim. `begin_*` atomically
114    /// converts it into a one-shot claim if the window is still vacant.
115    pub(crate) legacy_actor_materialization_generation: Option<u64>,
116    pub(crate) release_materialization_claim_on_drop: bool,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120pub(crate) enum RuntimeActorMaterializationClaimPhase {
121    Vacant,
122    Prepared,
123    Staged,
124    ActorCreating,
125    ActorMaterializedPendingCommit,
126    RetainedActor,
127    Aborting,
128}
129
130pub(crate) struct RuntimeActorMaterializationClaimState {
131    pub(crate) current: Option<uuid::Uuid>,
132    pub(crate) phase: RuntimeActorMaterializationClaimPhase,
133    /// True only when this registry entry was inserted by a unique,
134    /// rollback-owning actor materialization that has not yet attached an
135    /// executor. A successor unique prepare inherits this exact rollback
136    /// authority; cloneable compatibility bindings and pre-existing committed
137    /// registrations never gain it.
138    pub(crate) rollback_registration_available: bool,
139    /// Monotonic incarnation of the cloneable compatibility binding window.
140    /// Executor attachment increments this under the same mutex as the claim
141    /// phase, permanently fencing bindings that escaped an older window.
142    pub(crate) legacy_capability_generation: u64,
143    pub(crate) changed: Arc<crate::tokio::sync::Notify>,
144}
145
146impl RuntimeActorMaterializationClaimState {
147    pub(crate) fn new(rollback_registration_available: bool) -> Self {
148        Self {
149            current: None,
150            phase: RuntimeActorMaterializationClaimPhase::Vacant,
151            rollback_registration_available,
152            legacy_capability_generation: 0,
153            changed: Arc::new(crate::tokio::sync::Notify::new()),
154        }
155    }
156
157    pub(crate) fn exact_claim_is(
158        &self,
159        claim_id: uuid::Uuid,
160        phases: &[RuntimeActorMaterializationClaimPhase],
161    ) -> bool {
162        self.current == Some(claim_id) && phases.contains(&self.phase)
163    }
164}
165
166impl Drop for SessionRuntimeBindingsAuthority {
167    fn drop(&mut self) {
168        if !self.release_materialization_claim_on_drop {
169            return;
170        }
171        let Some(claim_id) = self.materialization_claim_id else {
172            return;
173        };
174        let changed = {
175            let mut state = self
176                .materialization_claim_state
177                .lock()
178                .unwrap_or_else(std::sync::PoisonError::into_inner);
179            if !state.exact_claim_is(
180                claim_id,
181                &[
182                    RuntimeActorMaterializationClaimPhase::Prepared,
183                    RuntimeActorMaterializationClaimPhase::Staged,
184                ],
185            ) {
186                return;
187            }
188            state.current = None;
189            state.phase = RuntimeActorMaterializationClaimPhase::Vacant;
190            Arc::clone(&state.changed)
191        };
192        changed.notify_waiters();
193    }
194}
195
196// Constructor mirrors the opaque session-binding authority payload exactly;
197// keeping each carrier explicit prevents partial or reordered minting.
198#[allow(clippy::too_many_arguments)]
199pub(crate) fn session_runtime_bindings_authority(
200    session_id: meerkat_core::SessionId,
201    epoch_id: meerkat_core::RuntimeEpochId,
202    dsl_authority: Arc<std::sync::Mutex<meerkat_machine::dsl::MeerkatMachineAuthority>>,
203    teardown_gate: Arc<handles::HandleTeardownGate>,
204    materialization_claim_id: Option<uuid::Uuid>,
205    materialization_claim_state: Arc<std::sync::Mutex<RuntimeActorMaterializationClaimState>>,
206    legacy_actor_materialization_generation: Option<u64>,
207    release_materialization_claim_on_drop: bool,
208) -> Arc<dyn Any + Send + Sync> {
209    Arc::new(SessionRuntimeBindingsAuthority {
210        session_id,
211        epoch_id,
212        dsl_authority,
213        teardown_gate,
214        materialization_claim_id,
215        materialization_claim_state,
216        legacy_actor_materialization_generation,
217        release_materialization_claim_on_drop,
218    })
219}
220
221#[allow(clippy::too_many_arguments)]
222pub(crate) fn local_session_runtime_bindings_authority(
223    session_id: meerkat_core::SessionId,
224    epoch_id: meerkat_core::RuntimeEpochId,
225    dsl_authority: Arc<std::sync::Mutex<meerkat_machine::dsl::MeerkatMachineAuthority>>,
226    teardown_gate: Arc<handles::HandleTeardownGate>,
227    materialization_claim_id: Option<uuid::Uuid>,
228    materialization_claim_state: Arc<std::sync::Mutex<RuntimeActorMaterializationClaimState>>,
229    legacy_actor_materialization_generation: Option<u64>,
230    release_materialization_claim_on_drop: bool,
231) -> Arc<dyn Any + Send + Sync> {
232    session_runtime_bindings_authority(
233        session_id,
234        epoch_id,
235        dsl_authority,
236        teardown_gate,
237        materialization_claim_id,
238        materialization_claim_state,
239        legacy_actor_materialization_generation,
240        release_materialization_claim_on_drop,
241    )
242}
243
244pub fn session_runtime_bindings_have_machine_authority(
245    bindings: &meerkat_core::SessionRuntimeBindings,
246) -> bool {
247    bindings
248        .__runtime_authority()
249        .is::<SessionRuntimeBindingsAuthority>()
250}
251
252#[derive(Debug, thiserror::Error)]
253pub enum RuntimeActorMaterializationError {
254    #[error("invalid runtime binding materialization authority: {0}")]
255    InvalidAuthority(String),
256    #[error("runtime binding registration no longer admits actor materialization")]
257    RegistrationClosed,
258}
259
260/// Exclusive actor-create permit for one exact prepared runtime binding.
261///
262/// The persistent session service acquires this immediately before it starts
263/// building the live actor. Dropping an uncommitted permit restores the prior
264/// prepared/staged phase; committing it records that the actor exists but is
265/// still owned by the surrounding materialization transaction until executor
266/// attachment or an explicit retained-actor commit.
267pub struct RuntimeActorMaterializationPermit {
268    claim_id: uuid::Uuid,
269    claim_state: Arc<std::sync::Mutex<RuntimeActorMaterializationClaimState>>,
270    session_id: meerkat_core::SessionId,
271    epoch_id: meerkat_core::RuntimeEpochId,
272    dsl_authority: Arc<std::sync::Mutex<meerkat_machine::dsl::MeerkatMachineAuthority>>,
273    teardown_gate: Arc<handles::HandleTeardownGate>,
274    previous_phase: RuntimeActorMaterializationClaimPhase,
275    transactional: bool,
276    phase_policy: RuntimeActorMaterializationPhasePolicy,
277    _mutation_guard: Option<crate::tokio::sync::OwnedMutexGuard<()>>,
278    committed: bool,
279}
280
281#[derive(Debug, Clone, Copy, PartialEq, Eq)]
282enum RuntimeActorMaterializationPhasePolicy {
283    RejectRetired,
284    RequireArchivedRevivalBoundary,
285}
286
287impl RuntimeActorMaterializationPermit {
288    pub fn commit(mut self) -> Result<(), RuntimeActorMaterializationError> {
289        let generated_authority = self
290            .dsl_authority
291            .lock()
292            .unwrap_or_else(std::sync::PoisonError::into_inner);
293        validate_materialization_registration_authority(
294            &self.session_id,
295            &self.epoch_id,
296            &self.teardown_gate,
297            &generated_authority,
298            self.phase_policy,
299        )?;
300        let mut state = self
301            .claim_state
302            .lock()
303            .unwrap_or_else(std::sync::PoisonError::into_inner);
304        if !state.exact_claim_is(
305            self.claim_id,
306            &[RuntimeActorMaterializationClaimPhase::ActorCreating],
307        ) {
308            return Err(RuntimeActorMaterializationError::RegistrationClosed);
309        }
310        let changed = if self.transactional {
311            state.phase = RuntimeActorMaterializationClaimPhase::ActorMaterializedPendingCommit;
312            None
313        } else {
314            state.current = None;
315            state.phase = RuntimeActorMaterializationClaimPhase::RetainedActor;
316            state.rollback_registration_available = false;
317            Some(Arc::clone(&state.changed))
318        };
319        self.committed = true;
320        drop(state);
321        if let Some(changed) = changed {
322            changed.notify_waiters();
323        }
324        Ok(())
325    }
326}
327
328impl Drop for RuntimeActorMaterializationPermit {
329    fn drop(&mut self) {
330        if self.committed {
331            return;
332        }
333        let mut state = self
334            .claim_state
335            .lock()
336            .unwrap_or_else(std::sync::PoisonError::into_inner);
337        if state.exact_claim_is(
338            self.claim_id,
339            &[RuntimeActorMaterializationClaimPhase::ActorCreating],
340        ) {
341            if self.transactional {
342                state.phase = self.previous_phase;
343            } else {
344                state.current = None;
345                state.phase = RuntimeActorMaterializationClaimPhase::Vacant;
346                Arc::clone(&state.changed).notify_waiters();
347            }
348        }
349    }
350}
351
352fn validated_session_runtime_bindings_authority(
353    bindings: &meerkat_core::SessionRuntimeBindings,
354) -> Result<&SessionRuntimeBindingsAuthority, RuntimeActorMaterializationError> {
355    let authority = bindings
356        .__runtime_authority()
357        .downcast_ref::<SessionRuntimeBindingsAuthority>()
358        .ok_or_else(|| {
359            RuntimeActorMaterializationError::InvalidAuthority(
360                "session runtime bindings lack MeerkatMachine authority".to_string(),
361            )
362        })?;
363    if bindings.session_id() != &authority.session_id || bindings.epoch_id() != &authority.epoch_id
364    {
365        return Err(RuntimeActorMaterializationError::InvalidAuthority(
366            "session runtime binding identity does not match its machine authority".into(),
367        ));
368    }
369    Ok(authority)
370}
371
372fn validate_materialization_registration_authority(
373    session_id: &meerkat_core::SessionId,
374    epoch_id: &meerkat_core::RuntimeEpochId,
375    teardown_gate: &Arc<handles::HandleTeardownGate>,
376    generated_authority: &meerkat_machine::dsl::MeerkatMachineAuthority,
377    phase_policy: RuntimeActorMaterializationPhasePolicy,
378) -> Result<(), RuntimeActorMaterializationError> {
379    let state = generated_authority.state();
380    let expected_session_id = meerkat_machine::dsl::SessionId::from_domain(session_id);
381    let expected_epoch_id = meerkat_machine::dsl::RuntimeEpochId::from_domain(epoch_id);
382    let runtime_phase =
383        meerkat_machine::dsl_authority::runtime_phase_from_authority(generated_authority);
384    if !teardown_gate.is_open()
385        || state.session_id.as_ref() != Some(&expected_session_id)
386        || state.registration_phase == meerkat_machine::dsl::RegistrationPhase::Draining
387        || matches!(
388            runtime_phase,
389            crate::runtime_state::RuntimeState::Stopped
390                | crate::runtime_state::RuntimeState::Destroyed
391        )
392        || match phase_policy {
393            RuntimeActorMaterializationPhasePolicy::RejectRetired => {
394                runtime_phase == crate::runtime_state::RuntimeState::Retired
395            }
396            RuntimeActorMaterializationPhasePolicy::RequireArchivedRevivalBoundary => !matches!(
397                runtime_phase,
398                crate::runtime_state::RuntimeState::Retired
399                    | crate::runtime_state::RuntimeState::Idle
400            ),
401        }
402        || state
403            .active_runtime_epoch_id
404            .as_ref()
405            .is_some_and(|epoch_id| epoch_id != &expected_epoch_id)
406    {
407        return Err(RuntimeActorMaterializationError::RegistrationClosed);
408    }
409    Ok(())
410}
411
412/// Begin exclusive construction of the live actor for an exact prepared
413/// binding. This is the cancellation-safe successor to the read-only validator.
414pub fn begin_session_runtime_actor_materialization(
415    bindings: &meerkat_core::SessionRuntimeBindings,
416) -> Result<RuntimeActorMaterializationPermit, RuntimeActorMaterializationError> {
417    begin_session_runtime_actor_materialization_with_phase_policy(
418        bindings,
419        RuntimeActorMaterializationPhasePolicy::RejectRetired,
420        None,
421    )
422}
423
424/// Begin actor construction for the exact machine-authorized
425/// Archived+Retired revival midpoint.
426///
427/// The public archived-resume service path remains closed; only a caller that
428/// already holds MeerkatMachine's session-control capability may construct the
429/// temporary live actor that the same revival transaction will promote to
430/// Active+Idle before executor attachment.
431pub async fn begin_session_runtime_actor_materialization_for_archived_resume(
432    bindings: &meerkat_core::SessionRuntimeBindings,
433    authorization: crate::meerkat_machine::ArchivedSessionActorMaterializationAuthorization,
434) -> Result<RuntimeActorMaterializationPermit, RuntimeActorMaterializationError> {
435    authorization.begin(bindings).await
436}
437
438fn begin_session_runtime_actor_materialization_with_phase_policy(
439    bindings: &meerkat_core::SessionRuntimeBindings,
440    phase_policy: RuntimeActorMaterializationPhasePolicy,
441    mutation_guard: Option<crate::tokio::sync::OwnedMutexGuard<()>>,
442) -> Result<RuntimeActorMaterializationPermit, RuntimeActorMaterializationError> {
443    let authority = validated_session_runtime_bindings_authority(bindings)?;
444    let generated_authority = authority
445        .dsl_authority
446        .lock()
447        .unwrap_or_else(std::sync::PoisonError::into_inner);
448    validate_materialization_registration_authority(
449        &authority.session_id,
450        &authority.epoch_id,
451        &authority.teardown_gate,
452        &generated_authority,
453        phase_policy,
454    )?;
455    let (claim_id, previous_phase, transactional) = {
456        let mut state = authority
457            .materialization_claim_state
458            .lock()
459            .unwrap_or_else(std::sync::PoisonError::into_inner);
460        if let Some(claim_id) = authority.materialization_claim_id {
461            if !state.exact_claim_is(
462                claim_id,
463                &[
464                    RuntimeActorMaterializationClaimPhase::Prepared,
465                    RuntimeActorMaterializationClaimPhase::Staged,
466                ],
467            ) {
468                return Err(RuntimeActorMaterializationError::RegistrationClosed);
469            }
470            let previous = state.phase;
471            state.phase = RuntimeActorMaterializationClaimPhase::ActorCreating;
472            (claim_id, previous, true)
473        } else if authority.legacy_actor_materialization_generation
474            == Some(state.legacy_capability_generation)
475            && state.current.is_none()
476            && state.phase == RuntimeActorMaterializationClaimPhase::Vacant
477        {
478            let claim_id = uuid::Uuid::new_v4();
479            state.current = Some(claim_id);
480            state.phase = RuntimeActorMaterializationClaimPhase::ActorCreating;
481            (
482                claim_id,
483                RuntimeActorMaterializationClaimPhase::Vacant,
484                false,
485            )
486        } else {
487            return Err(RuntimeActorMaterializationError::RegistrationClosed);
488        }
489    };
490    drop(generated_authority);
491    Ok(RuntimeActorMaterializationPermit {
492        claim_id,
493        claim_state: Arc::clone(&authority.materialization_claim_state),
494        session_id: authority.session_id.clone(),
495        epoch_id: authority.epoch_id.clone(),
496        dsl_authority: Arc::clone(&authority.dsl_authority),
497        teardown_gate: Arc::clone(&authority.teardown_gate),
498        previous_phase,
499        transactional,
500        phase_policy,
501        _mutation_guard: mutation_guard,
502        committed: false,
503    })
504}
505
506// Re-exports for convenience
507pub use accept::{AcceptOutcome, RejectReason};
508pub use coalescing::{
509    AggregateDescriptor, CoalescingResult, SupersessionScope, check_supersession,
510    create_aggregate_input, is_coalescing_eligible,
511};
512pub use completion::{
513    CompletionCleanupObservation, CompletionHandle, CompletionOutcome, CompletionWaitError,
514};
515pub use delivery_inbox::{
516    RuntimeDeliveryError, RuntimeDeliveryId, RuntimeDeliveryInbox, RuntimeDeliveryKind,
517    RuntimeDeliveryReceipt, RuntimeDeliveryRecord, RuntimeDeliverySubmission,
518};
519pub use driver::{EphemeralRuntimeDriver, PersistentRuntimeDriver, PostAdmissionSignal};
520pub use handles::{
521    HandleDslAuthority, RuntimeAuthLeaseHandle, RuntimeCommsDrainHandle,
522    RuntimeExternalToolSurfaceHandle, RuntimeInteractionStreamHandle,
523    RuntimeMcpServerLifecycleHandle, RuntimeModelRoutingHandle, RuntimePeerCommsHandle,
524    RuntimePeerInteractionHandle, RuntimeSessionAdmissionHandle, RuntimeSessionContextHandle,
525    RuntimeTurnStateHandle,
526};
527pub use identifiers::{
528    CausationId, ConversationId, CorrelationId, EventCodeId, IdempotencyKey, InputKind, KindId,
529    LogicalRuntimeId, PolicyVersion, ProjectionRuleId, RuntimeEventId, SchemaId, SupersessionKey,
530};
531pub use ingress_types::{ContentShape, RequestId, ReservationKey};
532pub use input::{
533    ContinuationInput, ContinuationKind, ExternalEventInput, FlowStepInput, Input, InputDurability,
534    InputHeader, InputOrigin, InputVisibility, OperationInput, PeerConvention, PeerInput,
535    PromptInput, ResponseProgressPhase, ResponseTerminalStatus, peer_response_terminal_input,
536    response_terminal_status_from_wire,
537};
538pub use input_ledger::InputLedger;
539pub use input_scope::InputScope;
540pub use input_state::{
541    InputAbandonReason, InputLifecycleState, InputState, InputStateEvent, InputStateHistoryEntry,
542    InputTerminalOutcome, PolicySnapshot, ReconstructionSource,
543};
544pub use meerkat_core::types::HandlingMode;
545pub use meerkat_machine::{
546    ArchivedSessionActorMaterializationAuthorization,
547    CommittedRuntimeExecutorAttachmentPublicationLease, CommsDrainMode, CommsDrainPhase,
548    DrainExitReason, EnsureRuntimeExecutorAttachment, LocalSessionMaterializationMode,
549    MachineServiceTurnCommitLease, MachineServiceTurnIdentity, MachineSessionArchiveLease,
550    MachineSessionControlAuthority, MeerkatConsumerSurface, MeerkatMachine, PeerIngressOwner,
551    PendingRuntimeExecutorAttachment, PreparedArchivedResumeCommitLease,
552    PreparedAttachedSessionActorRecovery, PreparedRuntimeExecutorAttachmentRetirement,
553    PreparedSessionMaterialization, PromotedArchivedResumeCommitLease, RuntimeBindingsError,
554    RuntimeCleanupTaskSpawner, RuntimeExecutorAttachmentRetirementCompletion,
555    RuntimeExecutorAttachmentWitness, RuntimeLifecycleFacts, RuntimeLoopQueueAdmissionPlan,
556    RuntimeSessionLifecycleObservation, RuntimeSessionRegistrationOutcome,
557    RuntimeSessionRegistrationWitness, StandaloneSessionRuntimeAuthorities,
558    classify_runtime_lifecycle_state, classify_runtime_loop_queue_admission,
559    standalone_session_runtime_authorities, standalone_tool_visibility_owner,
560};
561pub use meerkat_machine_types::{
562    HydratedSessionLlmState, ImageOperationRoutingRequest, ImageOperationRoutingResult,
563    ModelRoutingApprovalDisposition, ModelRoutingRealtimePolicy, ResolvedSessionLlmReconfigure,
564    SessionLlmCapabilitySurface, SessionLlmCapabilitySurfaceStatus, SessionLlmReconfigureHost,
565    SessionLlmReconfigureReport, SessionLlmReconfigureRequest, SessionToolVisibilityDelta,
566};
567#[doc(hidden)]
568pub use meerkat_machine_types::{
569    MeerkatAdmittedInputSnapshot, MeerkatArchiveSnapshot, MeerkatBindingSnapshot,
570    MeerkatCompletionWaiterSnapshot, MeerkatCompletionWaitersSnapshot, MeerkatControlSnapshot,
571    MeerkatCursorSnapshot, MeerkatDrainSnapshot, MeerkatDriverKind, MeerkatInputsSnapshot,
572    MeerkatMachineCatalogInput, MeerkatMachineCommandClassification,
573    MeerkatMachineCommandClassificationRecord, MeerkatMachineCommandVariant,
574    MeerkatMachineFieldlessRuntimeInternalInput, MeerkatMachineRuntimeInternalClassificationRecord,
575    MeerkatMachineRuntimeInternalInput, MeerkatMachineRuntimeInternalReason,
576    MeerkatMachineShellMechanicReason, MeerkatMachineSpineSnapshot, MeerkatOpsSnapshot,
577    OffDrainResponder, SupervisorBridgeCommandAdmissionRoute,
578    SupervisorBridgeCommandClassificationRecord, SupervisorBridgeCommandKind,
579    SupervisorBridgeCommandRealization, canonical_meerkat_machine_command_classifications,
580    canonical_meerkat_machine_command_input_variant_manifest,
581    canonical_meerkat_machine_command_manifest,
582    canonical_meerkat_machine_runtime_internal_classifications,
583    canonical_meerkat_machine_runtime_internal_fieldless_input_variant_manifest,
584    canonical_meerkat_machine_runtime_internal_input_variant_manifest,
585    canonical_meerkat_machine_runtime_internal_manifest,
586    canonical_supervisor_bridge_command_classifications,
587};
588pub use ops_lifecycle::{
589    OpsLifecycleConfig, OpsLifecyclePersistenceRequest, PersistedOpsSnapshot,
590    RuntimeOpsLifecycleRegistry,
591};
592
593#[cfg(all(not(target_arch = "wasm32"), any(test, feature = "test-support")))]
594#[doc(hidden)]
595pub fn test_peer_comms_handle() -> Arc<dyn meerkat_core::handles::PeerCommsHandle> {
596    test_peer_comms_handle_with_silent(std::iter::empty::<String>())
597}
598
599#[cfg(all(not(target_arch = "wasm32"), any(test, feature = "test-support")))]
600#[doc(hidden)]
601#[allow(clippy::expect_used)]
602pub fn test_peer_comms_handle_with_silent<I, S>(
603    silent_intents: I,
604) -> Arc<dyn meerkat_core::handles::PeerCommsHandle>
605where
606    I: IntoIterator<Item = S>,
607    S: Into<String>,
608{
609    let silent_intents = silent_intents
610        .into_iter()
611        .map(Into::into)
612        .collect::<Vec<_>>();
613    std::thread::spawn(move || {
614        let runtime = tokio::runtime::Builder::new_current_thread()
615            .enable_all()
616            .build()
617            .expect("test peer-comms runtime should build");
618        runtime.block_on(async move {
619            let machine = MeerkatMachine::ephemeral();
620            let session_id = meerkat_core::SessionId::new();
621            let bindings = machine
622                .prepare_bindings(session_id.clone())
623                .await
624                .expect("generated MeerkatMachine should prepare test peer-comms bindings");
625            if !silent_intents.is_empty() {
626                machine
627                    .set_session_silent_intents(&session_id, silent_intents)
628                    .await
629                    .expect("set silent intents");
630            }
631            Arc::clone(bindings.peer_comms())
632        })
633    })
634    .join()
635    .expect("test peer-comms authority thread should finish")
636}
637
638#[cfg(all(not(target_arch = "wasm32"), any(test, feature = "test-support")))]
639#[doc(hidden)]
640#[allow(clippy::expect_used)]
641pub fn test_peer_input_candidate_from_interaction(
642    interaction: meerkat_core::interaction::InboxInteraction,
643    peer_id: meerkat_core::comms::PeerId,
644) -> meerkat_core::interaction::PeerInputCandidate {
645    use meerkat_core::interaction::{
646        InteractionContent, InteractionId, PeerIngressEnvelopeFacts, PeerIngressEnvelopeKind,
647        PeerIngressFact, PeerIngressIdentity,
648    };
649
650    let handle = test_peer_comms_handle();
651    let facts = PeerIngressEnvelopeFacts {
652        item_id: interaction.id.to_string(),
653        from_peer: interaction.from.clone(),
654        from_peer_id: peer_id,
655        kind: match &interaction.content {
656            InteractionContent::Message { body, .. }
657            | InteractionContent::IncarnationFencedMessage { body, .. } => {
658                PeerIngressEnvelopeKind::Message { body: body.clone() }
659            }
660            InteractionContent::Request { intent, params, .. } => {
661                PeerIngressEnvelopeKind::Request {
662                    intent: intent.clone(),
663                    params: params.clone(),
664                }
665            }
666            InteractionContent::Response {
667                in_reply_to,
668                status,
669                result,
670                ..
671            } => PeerIngressEnvelopeKind::Response {
672                in_reply_to: in_reply_to.to_string(),
673                status: *status,
674                result: result.clone(),
675            },
676        },
677    };
678    let admission = handle
679        .classify_external_envelope(facts)
680        .expect("generated peer-comms authority should classify test interaction");
681    // R084: the admitted sender identity comes from the machine-echoed
682    // canonical peer id on the classification effect, not the local input.
683    let canonical_from_peer_id = admission
684        .from_peer_id
685        .expect("generated envelope classification should echo the canonical sender peer id");
686    let classification = admission.classification;
687    let convention = match &interaction.content {
688        InteractionContent::Message { .. }
689        | InteractionContent::IncarnationFencedMessage { .. } => {
690            meerkat_core::PeerIngressConvention::Message
691        }
692        InteractionContent::Request { intent, .. } => {
693            if let Some(kind) = classification.lifecycle_kind {
694                let peer = admission
695                    .lifecycle_peer
696                    .clone()
697                    .expect("generated lifecycle classification should include a peer subject");
698                meerkat_core::PeerIngressConvention::Lifecycle { kind, peer }
699            } else {
700                let request_id = admission
701                    .request_id
702                    .clone()
703                    .expect("generated request classification should include request id");
704                meerkat_core::PeerIngressConvention::Request {
705                    request_id,
706                    intent: intent.clone(),
707                }
708            }
709        }
710        InteractionContent::Response { status, .. } => {
711            let in_reply_to = admission
712                .request_id
713                .as_deref()
714                .and_then(|id| uuid::Uuid::parse_str(id).ok())
715                .map(InteractionId)
716                .expect("generated response classification should include in-reply-to id");
717            meerkat_core::PeerIngressConvention::Response {
718                in_reply_to,
719                status: *status,
720            }
721        }
722    };
723    let ingress = PeerIngressFact::peer(
724        interaction.id,
725        classification.class,
726        classification.kind,
727        Some(classification.auth),
728        PeerIngressIdentity::new(canonical_from_peer_id, interaction.from.clone(), convention),
729    );
730    let mut candidate = meerkat_core::interaction::PeerInputCandidate::new(
731        interaction,
732        ingress,
733        admission.lifecycle_peer,
734    );
735    candidate.response_terminality = classification.response_terminality;
736    candidate
737}
738
739/// Stamp prompt turn metadata with the runtime-owned input semantics.
740///
741/// This helper exists for runtime-backed service-turn paths that already hold
742/// machine admission and must pass a runtime-classified prompt turn into the
743/// session layer. New prompt materialization should prefer `MeerkatMachine`
744/// input admission so the machine creates this metadata directly.
745pub fn runtime_stamped_prompt_turn_metadata(
746    metadata: Option<RuntimeStampedTurnMetadata>,
747) -> RuntimeStampedTurnMetadata {
748    let input = Input::Prompt(PromptInput::from_content_input(
749        meerkat_core::ContentInput::Text(String::new()),
750        metadata,
751    ));
752    let semantics = runtime_prompt_semantics_from_machine(&input);
753    runtime_loop::for_input(&input, semantics)
754}
755
756#[allow(clippy::expect_used)]
757fn runtime_prompt_semantics_from_machine(input: &Input) -> ingress_types::RuntimeInputSemantics {
758    let mut authority = meerkat_machine::dsl_authority::new_initialized_authority(
759        "generated runtime prompt machine authority must initialize",
760    );
761    let transition = meerkat_machine::dsl::MeerkatMachineMutator::apply(
762        &mut authority,
763        meerkat_machine::dsl::MeerkatMachineInput::ResolveAdmissionPlan {
764            input_id: input.id().to_string(),
765            input_kind: meerkat_machine::dsl::AdmissionInputKind::from(input.kind()),
766            requested_lane: input
767                .handling_mode()
768                .map(meerkat_machine::dsl::InputLane::from),
769            continuation_kind: meerkat_machine::dsl::AdmissionContinuationKind::from(
770                input.continuation_kind(),
771            ),
772            silent_intent_match: false,
773            existing_superseded_input_id: None,
774            runtime_running: false,
775            active_turn_boundary_available: false,
776            without_wake: false,
777        },
778    )
779    .expect("generated admission authority must accept runtime prompt metadata");
780
781    transition
782        .into_effects()
783        .into_iter()
784        .find_map(|effect| match effect {
785            meerkat_machine::dsl::MeerkatMachineEffect::AdmissionResolved {
786                runtime_boundary,
787                runtime_execution_kind,
788                runtime_peer_response_terminal_apply_intent,
789                live_interrupt_required,
790                ..
791            } => Some(ingress_types::RuntimeInputSemantics {
792                boundary: runtime_boundary.into(),
793                execution_kind: runtime_execution_kind.into(),
794                execution_handling_mode: None,
795                peer_response_terminal_apply_intent: runtime_peer_response_terminal_apply_intent
796                    .map(Into::into),
797                live_interrupt_required,
798            }),
799            _ => None,
800        })
801        .expect("generated admission authority must emit prompt runtime semantics")
802}
803
804#[cfg(test)]
805mod runtime_prompt_metadata_tests {
806    #[test]
807    fn runtime_stamped_prompt_turn_metadata_uses_generated_prompt_semantics() {
808        let metadata = super::runtime_stamped_prompt_turn_metadata(None);
809        assert_eq!(
810            metadata.execution_kind,
811            Some(meerkat_core::lifecycle::RuntimeExecutionKind::ContentTurn)
812        );
813        assert!(metadata.peer_response_terminal_apply_intent.is_none());
814    }
815}
816
817#[doc(hidden)]
818pub mod machine_schema_exports {
819    pub fn meerkat_machine_schema() -> meerkat_machine_schema::MachineSchema {
820        meerkat_machine_schema::catalog::dsl::meerkat_machine_schema_metadata()
821            .attach_to(crate::meerkat_machine::dsl::MeerkatMachineState::schema())
822    }
823
824    pub fn auth_machine_schema() -> meerkat_machine_schema::MachineSchema {
825        meerkat_machine_schema::catalog::dsl::auth_machine_schema_metadata()
826            .attach_to(crate::auth_machine::dsl::AuthMachineState::schema())
827    }
828}
829pub use interrupt_public_result::{
830    UserInterruptObservation, UserInterruptPublicResult, resolve_user_interrupt_public_result,
831};
832pub use peer_handling_mode::{PeerHandlingModeError, validate_peer_handling_mode};
833pub use policy::{
834    ApplyMode, ConsumePoint, DrainPolicy, PolicyDecision, QueueMode, RoutingDisposition, WakeMode,
835};
836pub use policy_table::{DefaultPolicyTable, generated_default_policy_version};
837pub use runtime_event::{
838    InputLifecycleEvent, RunLifecycleEvent, RuntimeEvent, RuntimeEventEnvelope,
839    RuntimeProjectionEvent, RuntimeStateChangeEvent, RuntimeTopologyEvent,
840};
841pub use runtime_state::{RuntimeState, RuntimeStateTransitionError};
842pub use service_ext::SessionServiceRuntimeExt;
843#[cfg(feature = "sqlite-store")]
844pub use store::SqliteRuntimeStore;
845pub use store::{
846    InMemoryRuntimeStore, InputStateRow, RuntimeDeliveryAuthorityCasOutcome,
847    RuntimeDeliveryAuthorityRecord, RuntimeDeliveryStoreRecord, RuntimeStore, RuntimeStoreError,
848    RuntimeStoreWriteFence, RuntimeStoreWriteFenceOutcome, SessionDelta,
849};
850pub use traits::{
851    DestroyReport, RecoveryReport, RecycleReport, ResetReport, RetireReport, RuntimeControlPlane,
852    RuntimeControlPlaneError, RuntimeDriver, RuntimeDriverError,
853};