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