Skip to main content

meerkat_runtime/
meerkat_machine_types.rs

1//! Meerkat runtime command/result and snapshot support types.
2//!
3//! The authority surface now lives in `meerkat_machine.rs`; this module holds
4//! the supporting command/result enums and the durable diagnostic snapshots that
5//! remain useful after cutover.
6
7use std::collections::BTreeMap;
8use std::sync::Arc;
9
10use crate::meerkat_machine::{CommsDrainMode, CommsDrainPhase, DrainExitReason, dsl};
11use indexmap::IndexSet;
12use meerkat_core::RuntimeEpochId;
13use meerkat_core::agent::CommsRuntime;
14use meerkat_core::image_generation::{
15    ImageOperationApprovalReason, ImageOperationDenialReason, ImageOperationId,
16    ImageOperationPhase, ImageOperationTerminalClass, SessionModelRoutingStatus,
17    SwitchTurnApprovalReason, SwitchTurnControlResult, SwitchTurnIntent, SwitchTurnRequestId,
18};
19use meerkat_core::lifecycle::WaitRequestId;
20use meerkat_core::lifecycle::core_executor::CoreApplyOutput;
21use meerkat_core::lifecycle::core_executor::CoreExecutor;
22use meerkat_core::lifecycle::run_primitive::{ModelId, RunPrimitive};
23use meerkat_core::lifecycle::{InputId, RunId};
24use meerkat_core::lifecycle::{RunBoundaryReceipt, RunId as LifecycleRunId};
25use meerkat_core::ops::OperationId;
26use meerkat_core::ops_lifecycle::OperationLifecycleSnapshot;
27use meerkat_core::types::HandlingMode;
28use meerkat_core::types::SessionId;
29use meerkat_machine_derive::CommandManifest;
30use meerkat_machine_schema::catalog::dsl::meerkat_machine::MeerkatMachineInputVariant;
31use serde::{Deserialize, Serialize};
32
33use crate::AcceptOutcome;
34use crate::identifiers::LogicalRuntimeId;
35use crate::ingress_types::{ContentShape, RequestId, ReservationKey};
36use crate::input::Input;
37use crate::input_state::InputLifecycleState;
38use crate::input_state::InputTerminalOutcome;
39use crate::input_state::StoredInputState;
40use crate::runtime_event::RuntimeEventEnvelope;
41use crate::runtime_state::RuntimeState;
42use crate::traits::{
43    DestroyReport, RecoveryReport, RecycleReport, ResetReport, RetireReport,
44    RuntimeControlPlaneError, RuntimeDriverError,
45};
46
47#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
48#[serde(rename_all = "snake_case")]
49pub struct SessionLlmReconfigureRequest {
50    #[serde(default, skip_serializing_if = "Option::is_none")]
51    pub model: Option<String>,
52    #[serde(default, skip_serializing_if = "Option::is_none")]
53    pub provider: Option<String>,
54    #[serde(default, skip_serializing_if = "Option::is_none")]
55    pub provider_params: Option<serde_json::Value>,
56    /// Explicitly clear the session's durable provider params. This is
57    /// distinct from omitting `provider_params`, which inherits the current
58    /// value for compatibility.
59    #[serde(default, skip_serializing_if = "is_false")]
60    pub clear_provider_params: bool,
61    /// Optional realm-scoped connection override. When present, the
62    /// hot-swap uses this binding to resolve credentials; when absent,
63    /// the session's existing `SessionLlmIdentity.auth_binding` is preserved
64    /// unless `clear_auth_binding` is true.
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub auth_binding: Option<meerkat_core::AuthBindingRef>,
67    /// Explicitly clear the session's durable auth binding reference. This is
68    /// distinct from omitting `auth_binding`, which inherits the current
69    /// binding for compatibility.
70    #[serde(default, skip_serializing_if = "is_false")]
71    pub clear_auth_binding: bool,
72}
73
74fn is_false(value: &bool) -> bool {
75    !*value
76}
77
78#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
79#[serde(rename_all = "snake_case")]
80pub enum SessionLlmCapabilitySurfaceStatus {
81    Resolved,
82    #[default]
83    Unresolved,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
87#[serde(rename_all = "snake_case")]
88pub struct SessionLlmCapabilitySurface {
89    pub supports_temperature: bool,
90    pub supports_thinking: bool,
91    pub supports_reasoning: bool,
92    pub inline_video: bool,
93    pub vision: bool,
94    #[serde(default)]
95    pub image_input: bool,
96    pub image_tool_results: bool,
97    pub supports_web_search: bool,
98    #[serde(default)]
99    pub image_generation: bool,
100    /// Whether the resolved model exposes a realtime bidirectional streaming
101    /// transport. Drives capability-based auto attach/detach in
102    /// `reconfigure_live_topology` and `apply_capability_driven_realtime_transport`.
103    #[serde(default)]
104    pub realtime: bool,
105    #[serde(default, skip_serializing_if = "Option::is_none")]
106    pub call_timeout_secs: Option<u64>,
107}
108
109impl SessionLlmCapabilitySurface {
110    #[must_use]
111    pub fn to_wire_resolved(&self) -> meerkat_contracts::WireResolvedModelCapabilities {
112        meerkat_contracts::WireResolvedModelCapabilities {
113            vision: self.vision,
114            image_input: self.image_input,
115            image_tool_results: self.image_tool_results,
116            inline_video: self.inline_video,
117            realtime: self.realtime,
118            web_search: self.supports_web_search,
119            image_generation: self.image_generation,
120        }
121    }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub struct SessionLlmCapabilityDelta {
126    pub previous: Option<SessionLlmCapabilitySurface>,
127    pub current: Option<SessionLlmCapabilitySurface>,
128    pub changed: bool,
129}
130
131#[derive(Debug, Clone, PartialEq, Eq)]
132pub struct SessionToolVisibilityDelta {
133    pub previous_capability_base_filter: meerkat_core::ToolFilter,
134    pub current_capability_base_filter: meerkat_core::ToolFilter,
135    pub committed_visible_set_changed: bool,
136    pub revision_bumped: bool,
137}
138
139#[derive(Debug, Clone, PartialEq)]
140pub struct SessionLlmReconfigureReport {
141    pub previous_identity: meerkat_core::SessionLlmIdentity,
142    pub new_identity: meerkat_core::SessionLlmIdentity,
143    pub capability_delta: SessionLlmCapabilityDelta,
144    pub tool_visibility_delta: SessionToolVisibilityDelta,
145    pub rollback_occurred: bool,
146}
147
148#[derive(Debug, Clone)]
149pub struct HydratedSessionLlmState {
150    pub current_identity: meerkat_core::SessionLlmIdentity,
151    pub current_visibility_state: meerkat_core::SessionToolVisibilityState,
152    pub current_capability_surface: Option<SessionLlmCapabilitySurface>,
153    pub capability_surface_status: SessionLlmCapabilitySurfaceStatus,
154    pub base_tool_names: std::collections::BTreeSet<String>,
155}
156
157#[derive(Debug, Clone)]
158pub struct ResolvedSessionLlmReconfigure {
159    pub target_identity: meerkat_core::SessionLlmIdentity,
160    pub target_capability_surface: SessionLlmCapabilitySurface,
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
164pub enum ModelRoutingApprovalDisposition {
165    NotRequired,
166    Approved,
167    DeniedByUser,
168    RequiredButUnavailable,
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub struct ModelRoutingRealtimePolicy {
173    pub target_realtime_capable: bool,
174    pub allow_realtime_detach: bool,
175}
176
177#[derive(Debug, Clone, PartialEq, Eq)]
178pub struct SwitchTurnRequest {
179    pub request_id: SwitchTurnRequestId,
180    pub intent: SwitchTurnIntent,
181    pub target_realtime: ModelRoutingRealtimePolicy,
182    pub approval: ModelRoutingApprovalDisposition,
183    pub approval_reason: Option<SwitchTurnApprovalReason>,
184}
185
186#[derive(Debug, Clone, PartialEq, Eq)]
187pub struct ImageOperationRoutingRequest {
188    pub operation_id: ImageOperationId,
189    pub target_model: ModelId,
190    pub target_realtime: ModelRoutingRealtimePolicy,
191    pub approval: ModelRoutingApprovalDisposition,
192    pub approval_reason: Option<ImageOperationApprovalReason>,
193    pub requires_scoped_override: bool,
194}
195
196#[derive(Debug, Clone, PartialEq, Eq)]
197pub enum ImageOperationRoutingResult {
198    Accepted {
199        operation_id: ImageOperationId,
200        phase: ImageOperationPhase,
201    },
202    Denied {
203        operation_id: ImageOperationId,
204        reason: ImageOperationDenialReason,
205    },
206}
207
208#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
209#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
210pub trait SessionLlmReconfigureHost: Send + Sync {
211    async fn hydrate_session_llm_state(
212        &self,
213        session_id: &SessionId,
214    ) -> Result<HydratedSessionLlmState, RuntimeDriverError>;
215
216    async fn resolve_target_session_llm_identity(
217        &self,
218        request: &SessionLlmReconfigureRequest,
219        current_identity: &meerkat_core::SessionLlmIdentity,
220    ) -> Result<ResolvedSessionLlmReconfigure, RuntimeDriverError>;
221
222    async fn apply_live_session_llm_identity(
223        &self,
224        session_id: &SessionId,
225        identity: &meerkat_core::SessionLlmIdentity,
226    ) -> Result<(), RuntimeDriverError>;
227
228    async fn apply_live_session_tool_visibility_state(
229        &self,
230        session_id: &SessionId,
231        visibility_state: Option<meerkat_core::SessionToolVisibilityState>,
232    ) -> Result<(), RuntimeDriverError>;
233
234    async fn persist_live_session(&self, session_id: &SessionId) -> Result<(), RuntimeDriverError>;
235
236    async fn discard_live_session(&self, session_id: &SessionId) -> Result<(), RuntimeDriverError>;
237}
238
239#[derive(Debug, thiserror::Error)]
240pub(crate) enum MeerkatMachineCommandError {
241    #[error(transparent)]
242    Driver(#[from] RuntimeDriverError),
243    #[error(transparent)]
244    Control(#[from] RuntimeControlPlaneError),
245}
246
247/// Unified internal Meerkat machine command surface.
248///
249/// This replaces the old per-domain dispatch split (session, drain,
250/// drain-local, control, ingress, legacy-run) while keeping the public helper
251/// methods and external runtime/machine surface unchanged.
252#[derive(CommandManifest)]
253#[allow(clippy::large_enum_variant)]
254pub(crate) enum MeerkatMachineCommand {
255    RegisterSession {
256        session_id: SessionId,
257    },
258    UnregisterSession {
259        session_id: SessionId,
260    },
261    EnsureSessionWithExecutor {
262        session_id: SessionId,
263        executor: Box<dyn CoreExecutor>,
264    },
265    SetSilentIntents {
266        session_id: SessionId,
267        intents: Vec<String>,
268    },
269    CancelAfterBoundary {
270        session_id: SessionId,
271    },
272    StopRuntimeExecutor {
273        session_id: SessionId,
274        reason: String,
275    },
276    CommitServiceTurnTerminalReceipt {
277        session_id: SessionId,
278    },
279    ContainsSession {
280        session_id: SessionId,
281    },
282    SessionHasExecutor {
283        session_id: SessionId,
284    },
285    SessionHasComms {
286        session_id: SessionId,
287    },
288    OpsLifecycleRegistry {
289        session_id: SessionId,
290    },
291    PrepareBindings {
292        session_id: SessionId,
293    },
294    PrepareLocalSessionBindings {
295        session_id: SessionId,
296    },
297    InputState {
298        session_id: SessionId,
299        input_id: InputId,
300    },
301    ListActiveInputs {
302        session_id: SessionId,
303    },
304    ReconfigureSessionLlmIdentity {
305        session_id: SessionId,
306        previous_identity: Box<meerkat_core::SessionLlmIdentity>,
307        previous_visibility_state: Box<meerkat_core::SessionToolVisibilityState>,
308        previous_capability_surface: Option<SessionLlmCapabilitySurface>,
309        previous_capability_surface_status: SessionLlmCapabilitySurfaceStatus,
310        target_identity: Box<meerkat_core::SessionLlmIdentity>,
311        target_capability_surface: Box<SessionLlmCapabilitySurface>,
312        next_visibility_state: Box<meerkat_core::SessionToolVisibilityState>,
313        next_capability_base_filter: meerkat_core::ToolFilter,
314        next_active_visibility_revision: u64,
315        tool_visibility_delta: Box<SessionToolVisibilityDelta>,
316    },
317    StagePersistentFilter {
318        session_id: SessionId,
319        filter: meerkat_core::ToolFilter,
320        witnesses: std::collections::BTreeMap<String, meerkat_core::ToolVisibilityWitness>,
321    },
322    RequestDeferredTools {
323        session_id: SessionId,
324        authorities: Vec<meerkat_core::DeferredToolLoadAuthority>,
325    },
326    /// Publish the committed visible tool set through the machine dispatch.
327    ///
328    /// TLA+ source: VisibleSurfacesMatchAppliedStateInvariant —
329    /// the visible-set publication must route through the canonical command
330    /// path and be gated on session existence and non-Destroyed state.
331    PublishCommittedVisibleSet {
332        session_id: SessionId,
333        visibility_state: Box<meerkat_core::SessionToolVisibilityState>,
334    },
335    SetPeerIngressContext {
336        session_id: SessionId,
337        keep_alive: bool,
338        comms_runtime: Option<Arc<dyn CommsRuntime>>,
339        /// Mob-owned path sets this to the spawning mob's id so the DSL
340        /// transitions to `PeerIngressOwnerKind::MobOwned` rather than
341        /// `SessionOwned`. Session-owned and detach paths leave it `None`.
342        mob_id: Option<crate::meerkat_machine::dsl::MobId>,
343    },
344    NotifyDrainExited {
345        session_id: SessionId,
346        reason: DrainExitReason,
347    },
348    AbortAll,
349    Abort {
350        session_id: SessionId,
351    },
352    Wait {
353        session_id: SessionId,
354    },
355    Ingest {
356        runtime_id: LogicalRuntimeId,
357        input: Input,
358    },
359    PublishEvent {
360        event: RuntimeEventEnvelope,
361    },
362    Retire {
363        runtime_id: LogicalRuntimeId,
364    },
365    Recycle {
366        runtime_id: LogicalRuntimeId,
367    },
368    Reset {
369        runtime_id: LogicalRuntimeId,
370    },
371    Recover {
372        runtime_id: LogicalRuntimeId,
373    },
374    Destroy {
375        runtime_id: LogicalRuntimeId,
376    },
377    RuntimeState {
378        runtime_id: LogicalRuntimeId,
379    },
380    ResolvedSessionLlmCapabilities {
381        session_id: SessionId,
382    },
383    ConfigureModelRoutingBaseline {
384        session_id: SessionId,
385        baseline_model: ModelId,
386        realtime_capable: bool,
387    },
388    SessionModelRoutingStatus {
389        session_id: SessionId,
390    },
391    RequestSwitchTurn {
392        session_id: SessionId,
393        request: Box<SwitchTurnRequest>,
394    },
395    AdmitModelRoutingAssistantTurn {
396        session_id: SessionId,
397    },
398    BeginImageOperation {
399        session_id: SessionId,
400        request: Box<ImageOperationRoutingRequest>,
401    },
402    ActivateImageOperationOverride {
403        session_id: SessionId,
404        operation_id: ImageOperationId,
405    },
406    CompleteImageOperation {
407        session_id: SessionId,
408        operation_id: ImageOperationId,
409        terminal: ImageOperationTerminalClass,
410    },
411    RestoreImageOperationOverride {
412        session_id: SessionId,
413        operation_id: ImageOperationId,
414    },
415    LoadBoundaryReceipt {
416        runtime_id: LogicalRuntimeId,
417        run_id: LifecycleRunId,
418        sequence: u64,
419    },
420    AcceptWithCompletion {
421        session_id: SessionId,
422        input: Input,
423    },
424    AcceptWithoutWake {
425        session_id: SessionId,
426        input: Input,
427    },
428    Prepare {
429        session_id: SessionId,
430        input: Input,
431    },
432    Commit {
433        session_id: SessionId,
434        input_id: InputId,
435        run_id: RunId,
436        output: CoreApplyOutput,
437    },
438    Fail {
439        session_id: SessionId,
440        run_id: RunId,
441        failure: MeerkatMachineRunFailure,
442    },
443}
444
445#[derive(Debug, Clone)]
446pub(crate) struct MeerkatMachineRunFailure {
447    pub terminal_outcome: meerkat_core::TurnTerminalOutcome,
448    pub terminal_cause_kind: meerkat_core::TurnTerminalCauseKind,
449    pub error: String,
450}
451
452impl MeerkatMachineRunFailure {
453    pub(crate) fn new(
454        terminal_cause_kind: meerkat_core::TurnTerminalCauseKind,
455        error: impl Into<String>,
456    ) -> Self {
457        Self::terminal(
458            meerkat_core::TurnTerminalOutcome::Failed,
459            terminal_cause_kind,
460            error,
461        )
462    }
463
464    pub(crate) fn terminal(
465        terminal_outcome: meerkat_core::TurnTerminalOutcome,
466        terminal_cause_kind: meerkat_core::TurnTerminalCauseKind,
467        error: impl Into<String>,
468    ) -> Self {
469        Self {
470            terminal_outcome,
471            terminal_cause_kind,
472            error: error.into(),
473        }
474    }
475}
476
477#[derive(Debug)]
478pub(crate) struct MeerkatMachineRunPrepared {
479    pub input_id: InputId,
480    pub run_id: RunId,
481    pub primitive: RunPrimitive,
482}
483
484#[derive(Debug)]
485#[allow(clippy::large_enum_variant)]
486pub(crate) enum MeerkatMachineCommandResult {
487    AcceptOutcome(AcceptOutcome),
488    AcceptWithCompletion {
489        outcome: AcceptOutcome,
490        handle: Option<crate::completion::CompletionHandle>,
491        #[cfg_attr(not(test), allow(dead_code))]
492        admission_signal: crate::driver::ephemeral::PostAdmissionSignal,
493    },
494    Unit,
495    Bool(bool),
496    Spawned(bool),
497    OpsLifecycleRegistry(Option<Arc<crate::ops_lifecycle::RuntimeOpsLifecycleRegistry>>),
498    Bindings(meerkat_core::SessionRuntimeBindings),
499    InputState(Option<StoredInputState>),
500    ActiveInputs(Vec<InputId>),
501    LlmReconfigured(SessionLlmReconfigureReport),
502    VisibilityRevision(meerkat_core::ToolScopeRevision),
503    VisibilityPublished(meerkat_core::SessionToolVisibilityState),
504    RetireReport(RetireReport),
505    RecycleReport(RecycleReport),
506    ResetReport(ResetReport),
507    RecoveryReport(RecoveryReport),
508    DestroyReport(DestroyReport),
509    RuntimeState(RuntimeState),
510    ResolvedSessionLlmCapabilities(Option<SessionLlmCapabilitySurface>),
511    SessionModelRoutingStatus(SessionModelRoutingStatus),
512    SwitchTurnControlResult(SwitchTurnControlResult),
513    ImageOperationRoutingResult(ImageOperationRoutingResult),
514    ImageOperationPhase(ImageOperationPhase),
515    BoundaryReceipt(Option<RunBoundaryReceipt>),
516    Prepared(MeerkatMachineRunPrepared),
517}
518
519#[doc(hidden)]
520#[must_use]
521pub fn canonical_meerkat_machine_command_manifest() -> IndexSet<&'static str> {
522    canonical_meerkat_machine_command_input_variant_manifest()
523        .into_iter()
524        .map(|variant| variant.as_str())
525        .collect()
526}
527
528#[doc(hidden)]
529#[must_use]
530pub fn canonical_meerkat_machine_command_input_variant_manifest()
531-> IndexSet<MeerkatMachineInputVariant> {
532    canonical_meerkat_machine_command_classifications()
533        .into_iter()
534        .flat_map(|record| record.classification.catalog_input_variants())
535        .collect()
536}
537
538#[doc(hidden)]
539#[must_use]
540pub fn canonical_meerkat_machine_runtime_internal_manifest() -> IndexSet<&'static str> {
541    canonical_meerkat_machine_runtime_internal_input_variant_manifest()
542        .into_iter()
543        .map(|variant| variant.as_str())
544        .collect()
545}
546
547#[doc(hidden)]
548#[must_use]
549pub fn canonical_meerkat_machine_runtime_internal_input_variant_manifest()
550-> IndexSet<MeerkatMachineInputVariant> {
551    canonical_meerkat_machine_runtime_internal_classifications()
552        .into_iter()
553        .map(|record| record.input.input_variant())
554        .collect()
555}
556
557#[doc(hidden)]
558#[must_use]
559pub fn canonical_meerkat_machine_runtime_internal_fieldless_input_variant_manifest()
560-> IndexSet<MeerkatMachineInputVariant> {
561    MeerkatMachineFieldlessRuntimeInternalInput::ALL
562        .iter()
563        .copied()
564        .map(MeerkatMachineFieldlessRuntimeInternalInput::input_variant)
565        .collect()
566}
567
568macro_rules! meerkat_machine_runtime_internal_inputs {
569    ($($reason:ident => [$($variant:ident),+ $(,)?]),+ $(,)?) => {
570        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
571        pub enum MeerkatMachineRuntimeInternalInput {
572            $($($variant),+),+
573        }
574
575        impl MeerkatMachineRuntimeInternalInput {
576            pub const ALL: &'static [Self] = &[
577                $($(Self::$variant),+),+
578            ];
579
580            pub const CLASSIFICATIONS: &'static [MeerkatMachineRuntimeInternalClassificationRecord] = &[
581                $($(
582                    MeerkatMachineRuntimeInternalClassificationRecord {
583                        input: Self::$variant,
584                        reason: MeerkatMachineRuntimeInternalReason::$reason,
585                    },
586                )+)+
587            ];
588
589            #[must_use]
590            pub const fn input_variant(self) -> MeerkatMachineInputVariant {
591                match self {
592                    $($(Self::$variant => MeerkatMachineInputVariant::$variant,)+)+
593                }
594            }
595
596            #[must_use]
597            pub const fn reason(self) -> MeerkatMachineRuntimeInternalReason {
598                match self {
599                    $(
600                        $(Self::$variant)|+ => MeerkatMachineRuntimeInternalReason::$reason,
601                    )+
602                }
603            }
604        }
605    };
606}
607
608#[derive(Debug, Clone, Copy, PartialEq, Eq)]
609pub enum MeerkatMachineRuntimeInternalReason {
610    InputQueueLifecycle,
611    OperationLifecycle,
612    RunExecutionLifecycle,
613    CancellationLifecycle,
614    LiveTopologyReconfiguration,
615    InteractionStreamLifecycle,
616    CommsIngressLifecycle,
617    SupervisorTrustLifecycle,
618    PeerRequestLifecycle,
619    VisibilityAuthorityLifecycle,
620    ExtractionLifecycle,
621    McpServerLifecycle,
622    ModelRoutingLifecycle,
623    ExternalSurfaceLifecycle,
624    FailureRecoveryLifecycle,
625    UserInterruptDispatch,
626}
627
628#[derive(Debug, Clone, Copy, PartialEq, Eq)]
629pub struct MeerkatMachineRuntimeInternalClassificationRecord {
630    pub input: MeerkatMachineRuntimeInternalInput,
631    pub reason: MeerkatMachineRuntimeInternalReason,
632}
633
634meerkat_machine_runtime_internal_inputs!(
635    InputQueueLifecycle => [
636        AbandonInput,
637        AdvanceSessionContext,
638        BudgetExhausted,
639        ChangeLane,
640        CoalesceInput,
641        ConsumeInput,
642        ConsumeOnAccept,
643        MarkApplied,
644        MarkAppliedPendingConsumption,
645        QueueAccepted,
646        RecoverInputLifecycle,
647        RetryRequested,
648        RollbackStaged,
649        StageForRun,
650        StartConversationRun,
651        StartImmediateAppend,
652        StartImmediateContext,
653        SteerAccepted,
654        SupersedeInput,
655    ],
656    OperationLifecycle => [
657        AbortOp,
658        CancelOp,
659        CancelWaitAll,
660        CompleteOp,
661        FailOp,
662        IncrementAttemptCount,
663        OpsBarrierSatisfied,
664        PeerReadyOp,
665        ProgressReportedOp,
666        RegisterOp,
667        RegisterPendingOps,
668        RequestWaitAll,
669        RetireCompletedOp,
670        RetireRequestedOp,
671        SatisfyWaitAll,
672        StartOp,
673        TerminateOp,
674    ],
675    RunExecutionLifecycle => [
676        AcknowledgeTerminal,
677        BoundaryComplete,
678        BoundaryContinue,
679        LlmReturnedTerminal,
680        LlmReturnedToolCalls,
681        PrimitiveApplied,
682        RecordBoundarySeq,
683        RollbackRun,
684        RunCompleted,
685        RunFailed,
686        RuntimeExecutorExited,
687        TimeBudgetExceeded,
688        ToolCallsResolved,
689        TurnLimitReached,
690    ],
691    CancellationLifecycle => [
692        CancelNow,
693        CancelRun,
694        CancellationObserved,
695        ForceCancelNoRun,
696        RequestCancelAfterBoundary,
697        RunCancelled,
698    ],
699    LiveTopologyReconfiguration => [
700        CompleteUntilChangedSwitchTurnReconfigure,
701    ],
702    InteractionStreamLifecycle => [
703        InteractionStreamAttached,
704        InteractionStreamClosedEarly,
705        InteractionStreamCompleted,
706        InteractionStreamExpired,
707        InteractionStreamReserved,
708    ],
709    CommsIngressLifecycle => [
710        AddDirectPeerEndpoint,
711        ApplyMobPeerOverlay,
712        AttachMobIngress,
713        AttachSessionIngress,
714        BindSupervisor,
715        ClearLocalEndpoint,
716        DetachIngress,
717        DrainExitedClean,
718        DrainExitedRespawnable,
719        PublishLocalEndpoint,
720        RemoveDirectPeerEndpoint,
721        SpawnDrain,
722        StopDrain,
723    ],
724    SupervisorTrustLifecycle => [
725        AuthorizeSupervisor,
726        RevokeSupervisor,
727        SupervisorTrustEdgePublishFailed,
728        SupervisorTrustEdgePublished,
729        SupervisorTrustEdgeRevokeFailed,
730        SupervisorTrustEdgeRevoked,
731    ],
732    PeerRequestLifecycle => [
733        PeerRequestReceived,
734        PeerRequestSent,
735        PeerRequestTimedOut,
736        PeerResponseProgressArrived,
737        PeerResponseReplied,
738        PeerResponseTerminalArrived,
739    ],
740    VisibilityAuthorityLifecycle => [
741        CommitDeferredNames,
742        CommitVisibilityFilter,
743        StageDeferredNames,
744        StageVisibilityFilter,
745        SyncVisibilityRevisions,
746    ],
747    ExtractionLifecycle => [
748        EnterExtraction,
749        ExtractionFailed,
750        ExtractionStart,
751        ExtractionValidationFailed,
752        ExtractionValidationPassed,
753    ],
754    McpServerLifecycle => [
755        McpServerConnectPending,
756        McpServerConnected,
757        McpServerDisconnected,
758        McpServerFailed,
759        McpServerReload,
760    ],
761    ModelRoutingLifecycle => [
762        ModelRoutingStatus,
763        RequestFiniteSwitchTurn,
764        RequestUntilChangedSwitchTurn,
765        SetModelRoutingBaseline,
766    ],
767    ExternalSurfaceLifecycle => [
768        SurfaceApplyBoundary,
769        SurfaceCallFinished,
770        SurfaceCallStarted,
771        SurfaceFinalizeRemovalClean,
772        SurfaceFinalizeRemovalForced,
773        SurfaceMarkPendingFailed,
774        SurfaceMarkPendingSucceeded,
775        SurfaceRegister,
776        SurfaceShutdown,
777        SurfaceSnapshotAligned,
778        SurfaceStageAdd,
779        SurfaceStageReload,
780        SurfaceStageRemove,
781    ],
782    FailureRecoveryLifecycle => [
783        FatalFailure,
784        RecoverableFailure,
785    ],
786    UserInterruptDispatch => [
787        InterruptCurrentRun,
788    ],
789);
790
791macro_rules! meerkat_machine_fieldless_runtime_internal_inputs {
792    ($($authority:ident => [$($variant:ident),+ $(,)?]),+ $(,)?) => {
793        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
794        pub enum MeerkatMachineFieldlessRuntimeInternalInput {
795            $($($variant),+),+
796        }
797
798        impl MeerkatMachineFieldlessRuntimeInternalInput {
799            pub const ALL: &'static [Self] = &[
800                $($(Self::$variant),+),+
801            ];
802
803            #[must_use]
804            pub const fn runtime_internal_input(self) -> MeerkatMachineRuntimeInternalInput {
805                match self {
806                    $($(Self::$variant => MeerkatMachineRuntimeInternalInput::$variant,)+)+
807                }
808            }
809
810            #[must_use]
811            pub const fn input_variant(self) -> MeerkatMachineInputVariant {
812                self.runtime_internal_input().input_variant()
813            }
814
815            #[must_use]
816            pub const fn authority(self) -> MeerkatMachineFieldlessRuntimeInternalAuthority {
817                match self {
818                    $(
819                        $(Self::$variant)|+ => MeerkatMachineFieldlessRuntimeInternalAuthority::$authority,
820                    )+
821                }
822            }
823
824            #[must_use]
825            pub const fn requires_typed_runtime_internal_stager(self) -> bool {
826                matches!(
827                    self.authority(),
828                    MeerkatMachineFieldlessRuntimeInternalAuthority::UserInterruptDispatch
829                )
830            }
831
832            pub(crate) const fn dsl_input_variant(self) -> dsl::MeerkatMachineInputVariant {
833                match self {
834                    $($(Self::$variant => dsl::MeerkatMachineInputVariant::$variant,)+)+
835                }
836            }
837
838            pub(crate) fn dsl_input(self) -> dsl::MeerkatMachineInput {
839                match self {
840                    $($(Self::$variant => dsl::MeerkatMachineInput::$variant,)+)+
841                }
842            }
843
844            pub(crate) fn from_dsl_input_variant(
845                variant: dsl::MeerkatMachineInputVariant,
846            ) -> Option<Self> {
847                Self::ALL
848                    .iter()
849                    .copied()
850                    .find(|input| input.dsl_input_variant() == variant)
851            }
852
853            pub(crate) fn reject_raw_dsl_input(
854                input: &dsl::MeerkatMachineInput,
855            ) -> Result<(), String> {
856                if let Some(fieldless) = Self::from_dsl_input_variant(input.variant())
857                    && fieldless.requires_typed_runtime_internal_stager()
858                {
859                    let variant = fieldless.input_variant();
860                    return Err(format!(
861                        "fieldless runtime-internal input {variant:?} must use typed runtime-internal staging authority"
862                    ));
863                }
864                Ok(())
865            }
866        }
867    };
868}
869
870meerkat_machine_fieldless_runtime_internal_inputs!(
871    RuntimeOwner => [
872        RuntimeExecutorExited,
873        PrimitiveApplied,
874        LlmReturnedTerminal,
875        ToolCallsResolved,
876        BoundaryContinue,
877        BoundaryComplete,
878        ExtractionStart,
879        ExtractionValidationPassed,
880        CancelNow,
881        RequestCancelAfterBoundary,
882        CancellationObserved,
883        TurnLimitReached,
884        BudgetExhausted,
885        TimeBudgetExceeded,
886        ForceCancelNoRun,
887        CancelWaitAll,
888        StopDrain,
889        DrainExitedClean,
890        DrainExitedRespawnable,
891        SurfaceShutdown,
892        DetachIngress,
893        ClearLocalEndpoint,
894    ],
895    UserInterruptDispatch => [
896        InterruptCurrentRun,
897    ],
898);
899
900#[derive(Debug, Clone, Copy, PartialEq, Eq)]
901pub enum MeerkatMachineFieldlessRuntimeInternalAuthority {
902    RuntimeOwner,
903    UserInterruptDispatch,
904}
905
906#[doc(hidden)]
907#[must_use]
908pub fn canonical_meerkat_machine_runtime_internal_classifications()
909-> Vec<MeerkatMachineRuntimeInternalClassificationRecord> {
910    MeerkatMachineRuntimeInternalInput::CLASSIFICATIONS.to_vec()
911}
912
913#[derive(Debug, Clone, Copy, PartialEq, Eq)]
914pub enum MeerkatMachineCommandClassification {
915    CatalogInput(MeerkatMachineCatalogInput),
916    CatalogInputs(&'static [MeerkatMachineCatalogInput]),
917    ShellMechanic(MeerkatMachineShellMechanicReason),
918}
919
920impl MeerkatMachineCommandClassification {
921    #[must_use]
922    pub fn catalog_inputs(self) -> Vec<MeerkatMachineCatalogInput> {
923        match self {
924            Self::CatalogInput(input) => vec![input],
925            Self::CatalogInputs(inputs) => inputs.to_vec(),
926            Self::ShellMechanic(_) => Vec::new(),
927        }
928    }
929
930    #[must_use]
931    pub fn catalog_input_variants(self) -> Vec<MeerkatMachineInputVariant> {
932        self.catalog_inputs()
933            .into_iter()
934            .map(MeerkatMachineCatalogInput::input_variant)
935            .collect()
936    }
937}
938
939#[derive(Debug, Clone, Copy, PartialEq, Eq)]
940pub enum MeerkatMachineCatalogInput {
941    RegisterSession,
942    UnregisterSession,
943    EnsureSessionWithExecutor,
944    SetSilentIntents,
945    CancelAfterBoundary,
946    StopRuntimeExecutor,
947    ServiceTurnCommitted,
948    ContainsSession,
949    SessionHasExecutor,
950    SessionHasComms,
951    OpsLifecycleRegistry,
952    PrepareBindings,
953    InputState,
954    ListActiveInputs,
955    ReconfigureSessionLlmIdentity,
956    StagePersistentFilter,
957    RequestDeferredTools,
958    PublishCommittedVisibleSet,
959    SetPeerIngressContext,
960    NotifyDrainExited,
961    AbortAll,
962    Abort,
963    Wait,
964    Ingest,
965    PublishEvent,
966    Retire,
967    Recycle,
968    Reset,
969    Recover,
970    Destroy,
971    RuntimeState,
972    ModelRoutingStatus,
973    SetModelRoutingBaseline,
974    RequestFiniteSwitchTurn,
975    RequestUntilChangedSwitchTurn,
976    AdmitModelRoutingAssistantTurn,
977    BeginImageOperation,
978    ActivateImageOperationOverride,
979    CompleteImageOperation,
980    RestoreImageOperationOverride,
981    LoadBoundaryReceipt,
982    AcceptWithCompletion,
983    AcceptWithoutWake,
984    Prepare,
985    Commit,
986    Fail,
987}
988
989impl MeerkatMachineCatalogInput {
990    pub const ALL: &'static [Self] = &[
991        Self::RegisterSession,
992        Self::UnregisterSession,
993        Self::EnsureSessionWithExecutor,
994        Self::SetSilentIntents,
995        Self::CancelAfterBoundary,
996        Self::StopRuntimeExecutor,
997        Self::ServiceTurnCommitted,
998        Self::ContainsSession,
999        Self::SessionHasExecutor,
1000        Self::SessionHasComms,
1001        Self::OpsLifecycleRegistry,
1002        Self::PrepareBindings,
1003        Self::InputState,
1004        Self::ListActiveInputs,
1005        Self::ReconfigureSessionLlmIdentity,
1006        Self::StagePersistentFilter,
1007        Self::RequestDeferredTools,
1008        Self::PublishCommittedVisibleSet,
1009        Self::SetPeerIngressContext,
1010        Self::NotifyDrainExited,
1011        Self::AbortAll,
1012        Self::Abort,
1013        Self::Wait,
1014        Self::Ingest,
1015        Self::PublishEvent,
1016        Self::Retire,
1017        Self::Recycle,
1018        Self::Reset,
1019        Self::Recover,
1020        Self::Destroy,
1021        Self::RuntimeState,
1022        Self::ModelRoutingStatus,
1023        Self::SetModelRoutingBaseline,
1024        Self::RequestFiniteSwitchTurn,
1025        Self::RequestUntilChangedSwitchTurn,
1026        Self::AdmitModelRoutingAssistantTurn,
1027        Self::BeginImageOperation,
1028        Self::ActivateImageOperationOverride,
1029        Self::CompleteImageOperation,
1030        Self::RestoreImageOperationOverride,
1031        Self::LoadBoundaryReceipt,
1032        Self::AcceptWithCompletion,
1033        Self::AcceptWithoutWake,
1034        Self::Prepare,
1035        Self::Commit,
1036        Self::Fail,
1037    ];
1038
1039    #[must_use]
1040    pub const fn input_variant(self) -> MeerkatMachineInputVariant {
1041        match self {
1042            Self::RegisterSession => MeerkatMachineInputVariant::RegisterSession,
1043            Self::UnregisterSession => MeerkatMachineInputVariant::UnregisterSession,
1044            Self::EnsureSessionWithExecutor => {
1045                MeerkatMachineInputVariant::EnsureSessionWithExecutor
1046            }
1047            Self::SetSilentIntents => MeerkatMachineInputVariant::SetSilentIntents,
1048            Self::CancelAfterBoundary => MeerkatMachineInputVariant::CancelAfterBoundary,
1049            Self::StopRuntimeExecutor => MeerkatMachineInputVariant::StopRuntimeExecutor,
1050            Self::ServiceTurnCommitted => MeerkatMachineInputVariant::ServiceTurnCommitted,
1051            Self::ContainsSession => MeerkatMachineInputVariant::ContainsSession,
1052            Self::SessionHasExecutor => MeerkatMachineInputVariant::SessionHasExecutor,
1053            Self::SessionHasComms => MeerkatMachineInputVariant::SessionHasComms,
1054            Self::OpsLifecycleRegistry => MeerkatMachineInputVariant::OpsLifecycleRegistry,
1055            Self::PrepareBindings => MeerkatMachineInputVariant::PrepareBindings,
1056            Self::InputState => MeerkatMachineInputVariant::InputState,
1057            Self::ListActiveInputs => MeerkatMachineInputVariant::ListActiveInputs,
1058            Self::ReconfigureSessionLlmIdentity => {
1059                MeerkatMachineInputVariant::ReconfigureSessionLlmIdentity
1060            }
1061            Self::StagePersistentFilter => MeerkatMachineInputVariant::StagePersistentFilter,
1062            Self::RequestDeferredTools => MeerkatMachineInputVariant::RequestDeferredTools,
1063            Self::PublishCommittedVisibleSet => {
1064                MeerkatMachineInputVariant::PublishCommittedVisibleSet
1065            }
1066            Self::SetPeerIngressContext => MeerkatMachineInputVariant::SetPeerIngressContext,
1067            Self::NotifyDrainExited => MeerkatMachineInputVariant::NotifyDrainExited,
1068            Self::AbortAll => MeerkatMachineInputVariant::AbortAll,
1069            Self::Abort => MeerkatMachineInputVariant::Abort,
1070            Self::Wait => MeerkatMachineInputVariant::Wait,
1071            Self::Ingest => MeerkatMachineInputVariant::Ingest,
1072            Self::PublishEvent => MeerkatMachineInputVariant::PublishEvent,
1073            Self::Retire => MeerkatMachineInputVariant::Retire,
1074            Self::Recycle => MeerkatMachineInputVariant::Recycle,
1075            Self::Reset => MeerkatMachineInputVariant::Reset,
1076            Self::Recover => MeerkatMachineInputVariant::Recover,
1077            Self::Destroy => MeerkatMachineInputVariant::Destroy,
1078            Self::RuntimeState => MeerkatMachineInputVariant::RuntimeState,
1079            Self::ModelRoutingStatus => MeerkatMachineInputVariant::ModelRoutingStatus,
1080            Self::SetModelRoutingBaseline => MeerkatMachineInputVariant::SetModelRoutingBaseline,
1081            Self::RequestFiniteSwitchTurn => MeerkatMachineInputVariant::RequestFiniteSwitchTurn,
1082            Self::RequestUntilChangedSwitchTurn => {
1083                MeerkatMachineInputVariant::RequestUntilChangedSwitchTurn
1084            }
1085            Self::AdmitModelRoutingAssistantTurn => {
1086                MeerkatMachineInputVariant::AdmitModelRoutingAssistantTurn
1087            }
1088            Self::BeginImageOperation => MeerkatMachineInputVariant::BeginImageOperation,
1089            Self::ActivateImageOperationOverride => {
1090                MeerkatMachineInputVariant::ActivateImageOperationOverride
1091            }
1092            Self::CompleteImageOperation => MeerkatMachineInputVariant::CompleteImageOperation,
1093            Self::RestoreImageOperationOverride => {
1094                MeerkatMachineInputVariant::RestoreImageOperationOverride
1095            }
1096            Self::LoadBoundaryReceipt => MeerkatMachineInputVariant::LoadBoundaryReceipt,
1097            Self::AcceptWithCompletion => MeerkatMachineInputVariant::AcceptWithCompletion,
1098            Self::AcceptWithoutWake => MeerkatMachineInputVariant::AcceptWithoutWake,
1099            Self::Prepare => MeerkatMachineInputVariant::Prepare,
1100            Self::Commit => MeerkatMachineInputVariant::Commit,
1101            Self::Fail => MeerkatMachineInputVariant::Fail,
1102        }
1103    }
1104
1105    #[must_use]
1106    pub const fn as_str(self) -> &'static str {
1107        match self {
1108            Self::RegisterSession => "RegisterSession",
1109            Self::UnregisterSession => "UnregisterSession",
1110            Self::EnsureSessionWithExecutor => "EnsureSessionWithExecutor",
1111            Self::SetSilentIntents => "SetSilentIntents",
1112            Self::CancelAfterBoundary => "CancelAfterBoundary",
1113            Self::StopRuntimeExecutor => "StopRuntimeExecutor",
1114            Self::ServiceTurnCommitted => "ServiceTurnCommitted",
1115            Self::ContainsSession => "ContainsSession",
1116            Self::SessionHasExecutor => "SessionHasExecutor",
1117            Self::SessionHasComms => "SessionHasComms",
1118            Self::OpsLifecycleRegistry => "OpsLifecycleRegistry",
1119            Self::PrepareBindings => "PrepareBindings",
1120            Self::InputState => "InputState",
1121            Self::ListActiveInputs => "ListActiveInputs",
1122            Self::ReconfigureSessionLlmIdentity => "ReconfigureSessionLlmIdentity",
1123            Self::StagePersistentFilter => "StagePersistentFilter",
1124            Self::RequestDeferredTools => "RequestDeferredTools",
1125            Self::PublishCommittedVisibleSet => "PublishCommittedVisibleSet",
1126            Self::SetPeerIngressContext => "SetPeerIngressContext",
1127            Self::NotifyDrainExited => "NotifyDrainExited",
1128            Self::AbortAll => "AbortAll",
1129            Self::Abort => "Abort",
1130            Self::Wait => "Wait",
1131            Self::Ingest => "Ingest",
1132            Self::PublishEvent => "PublishEvent",
1133            Self::Retire => "Retire",
1134            Self::Recycle => "Recycle",
1135            Self::Reset => "Reset",
1136            Self::Recover => "Recover",
1137            Self::Destroy => "Destroy",
1138            Self::RuntimeState => "RuntimeState",
1139            Self::ModelRoutingStatus => "ModelRoutingStatus",
1140            Self::SetModelRoutingBaseline => "SetModelRoutingBaseline",
1141            Self::RequestFiniteSwitchTurn => "RequestFiniteSwitchTurn",
1142            Self::RequestUntilChangedSwitchTurn => "RequestUntilChangedSwitchTurn",
1143            Self::AdmitModelRoutingAssistantTurn => "AdmitModelRoutingAssistantTurn",
1144            Self::BeginImageOperation => "BeginImageOperation",
1145            Self::ActivateImageOperationOverride => "ActivateImageOperationOverride",
1146            Self::CompleteImageOperation => "CompleteImageOperation",
1147            Self::RestoreImageOperationOverride => "RestoreImageOperationOverride",
1148            Self::LoadBoundaryReceipt => "LoadBoundaryReceipt",
1149            Self::AcceptWithCompletion => "AcceptWithCompletion",
1150            Self::AcceptWithoutWake => "AcceptWithoutWake",
1151            Self::Prepare => "Prepare",
1152            Self::Commit => "Commit",
1153            Self::Fail => "Fail",
1154        }
1155    }
1156}
1157
1158impl MeerkatMachineCommandVariant {
1159    #[must_use]
1160    pub const fn catalog_input(self) -> Option<MeerkatMachineCatalogInput> {
1161        match self {
1162            Self::ConfigureModelRoutingBaseline
1163            | Self::RequestSwitchTurn
1164            | Self::ResolvedSessionLlmCapabilities
1165            | Self::SessionModelRoutingStatus
1166            | Self::PrepareLocalSessionBindings => None,
1167            Self::RegisterSession => Some(MeerkatMachineCatalogInput::RegisterSession),
1168            Self::UnregisterSession => Some(MeerkatMachineCatalogInput::UnregisterSession),
1169            Self::EnsureSessionWithExecutor => {
1170                Some(MeerkatMachineCatalogInput::EnsureSessionWithExecutor)
1171            }
1172            Self::SetSilentIntents => Some(MeerkatMachineCatalogInput::SetSilentIntents),
1173            Self::CancelAfterBoundary => Some(MeerkatMachineCatalogInput::CancelAfterBoundary),
1174            Self::StopRuntimeExecutor => Some(MeerkatMachineCatalogInput::StopRuntimeExecutor),
1175            Self::CommitServiceTurnTerminalReceipt => {
1176                Some(MeerkatMachineCatalogInput::ServiceTurnCommitted)
1177            }
1178            Self::ContainsSession => Some(MeerkatMachineCatalogInput::ContainsSession),
1179            Self::SessionHasExecutor => Some(MeerkatMachineCatalogInput::SessionHasExecutor),
1180            Self::SessionHasComms => Some(MeerkatMachineCatalogInput::SessionHasComms),
1181            Self::OpsLifecycleRegistry => Some(MeerkatMachineCatalogInput::OpsLifecycleRegistry),
1182            Self::PrepareBindings => Some(MeerkatMachineCatalogInput::PrepareBindings),
1183            Self::InputState => Some(MeerkatMachineCatalogInput::InputState),
1184            Self::ListActiveInputs => Some(MeerkatMachineCatalogInput::ListActiveInputs),
1185            Self::ReconfigureSessionLlmIdentity => {
1186                Some(MeerkatMachineCatalogInput::ReconfigureSessionLlmIdentity)
1187            }
1188            Self::StagePersistentFilter => Some(MeerkatMachineCatalogInput::StagePersistentFilter),
1189            Self::RequestDeferredTools => Some(MeerkatMachineCatalogInput::RequestDeferredTools),
1190            Self::PublishCommittedVisibleSet => {
1191                Some(MeerkatMachineCatalogInput::PublishCommittedVisibleSet)
1192            }
1193            Self::SetPeerIngressContext => Some(MeerkatMachineCatalogInput::SetPeerIngressContext),
1194            Self::NotifyDrainExited => Some(MeerkatMachineCatalogInput::NotifyDrainExited),
1195            Self::AbortAll => Some(MeerkatMachineCatalogInput::AbortAll),
1196            Self::Abort => Some(MeerkatMachineCatalogInput::Abort),
1197            Self::Wait => Some(MeerkatMachineCatalogInput::Wait),
1198            Self::Ingest => Some(MeerkatMachineCatalogInput::Ingest),
1199            Self::PublishEvent => Some(MeerkatMachineCatalogInput::PublishEvent),
1200            Self::Retire => Some(MeerkatMachineCatalogInput::Retire),
1201            Self::Recycle => Some(MeerkatMachineCatalogInput::Recycle),
1202            Self::Reset => Some(MeerkatMachineCatalogInput::Reset),
1203            Self::Recover => Some(MeerkatMachineCatalogInput::Recover),
1204            Self::Destroy => Some(MeerkatMachineCatalogInput::Destroy),
1205            Self::RuntimeState => Some(MeerkatMachineCatalogInput::RuntimeState),
1206            Self::AdmitModelRoutingAssistantTurn => {
1207                Some(MeerkatMachineCatalogInput::AdmitModelRoutingAssistantTurn)
1208            }
1209            Self::BeginImageOperation => Some(MeerkatMachineCatalogInput::BeginImageOperation),
1210            Self::ActivateImageOperationOverride => {
1211                Some(MeerkatMachineCatalogInput::ActivateImageOperationOverride)
1212            }
1213            Self::CompleteImageOperation => {
1214                Some(MeerkatMachineCatalogInput::CompleteImageOperation)
1215            }
1216            Self::RestoreImageOperationOverride => {
1217                Some(MeerkatMachineCatalogInput::RestoreImageOperationOverride)
1218            }
1219            Self::LoadBoundaryReceipt => Some(MeerkatMachineCatalogInput::LoadBoundaryReceipt),
1220            Self::AcceptWithCompletion => Some(MeerkatMachineCatalogInput::AcceptWithCompletion),
1221            Self::AcceptWithoutWake => Some(MeerkatMachineCatalogInput::AcceptWithoutWake),
1222            Self::Prepare => Some(MeerkatMachineCatalogInput::Prepare),
1223            Self::Commit => Some(MeerkatMachineCatalogInput::Commit),
1224            Self::Fail => Some(MeerkatMachineCatalogInput::Fail),
1225        }
1226    }
1227}
1228
1229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1230pub enum MeerkatMachineShellMechanicReason {
1231    ModelRoutingShellConfiguration,
1232    TurnControlOverlayRequest,
1233    RealtimeTransportObservation,
1234    SessionModelRoutingObservation,
1235    LocalSessionBindingBootstrap,
1236}
1237
1238#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1239pub struct MeerkatMachineCommandClassificationRecord {
1240    pub command: MeerkatMachineCommandVariant,
1241    pub classification: MeerkatMachineCommandClassification,
1242}
1243
1244#[doc(hidden)]
1245#[must_use]
1246pub fn canonical_meerkat_machine_command_classifications()
1247-> Vec<MeerkatMachineCommandClassificationRecord> {
1248    MeerkatMachineCommand::command_variant_manifest()
1249        .iter()
1250        .copied()
1251        .map(|variant| MeerkatMachineCommandClassificationRecord {
1252            command: variant,
1253            classification: meerkat_machine_command_classification(variant),
1254        })
1255        .collect()
1256}
1257
1258const fn meerkat_machine_command_classification(
1259    variant: MeerkatMachineCommandVariant,
1260) -> MeerkatMachineCommandClassification {
1261    match variant {
1262        MeerkatMachineCommandVariant::ConfigureModelRoutingBaseline => {
1263            MeerkatMachineCommandClassification::CatalogInput(
1264                MeerkatMachineCatalogInput::SetModelRoutingBaseline,
1265            )
1266        }
1267        MeerkatMachineCommandVariant::RequestSwitchTurn => {
1268            MeerkatMachineCommandClassification::CatalogInputs(&[
1269                MeerkatMachineCatalogInput::RequestFiniteSwitchTurn,
1270                MeerkatMachineCatalogInput::RequestUntilChangedSwitchTurn,
1271            ])
1272        }
1273        MeerkatMachineCommandVariant::ResolvedSessionLlmCapabilities => {
1274            MeerkatMachineCommandClassification::ShellMechanic(
1275                MeerkatMachineShellMechanicReason::SessionModelRoutingObservation,
1276            )
1277        }
1278        MeerkatMachineCommandVariant::SessionModelRoutingStatus => {
1279            MeerkatMachineCommandClassification::CatalogInput(
1280                MeerkatMachineCatalogInput::ModelRoutingStatus,
1281            )
1282        }
1283        MeerkatMachineCommandVariant::PrepareLocalSessionBindings => {
1284            MeerkatMachineCommandClassification::ShellMechanic(
1285                MeerkatMachineShellMechanicReason::LocalSessionBindingBootstrap,
1286            )
1287        }
1288        MeerkatMachineCommandVariant::RegisterSession => {
1289            MeerkatMachineCommandClassification::CatalogInput(
1290                MeerkatMachineCatalogInput::RegisterSession,
1291            )
1292        }
1293        MeerkatMachineCommandVariant::UnregisterSession => {
1294            MeerkatMachineCommandClassification::CatalogInput(
1295                MeerkatMachineCatalogInput::UnregisterSession,
1296            )
1297        }
1298        MeerkatMachineCommandVariant::EnsureSessionWithExecutor => {
1299            MeerkatMachineCommandClassification::CatalogInput(
1300                MeerkatMachineCatalogInput::EnsureSessionWithExecutor,
1301            )
1302        }
1303        MeerkatMachineCommandVariant::SetSilentIntents => {
1304            MeerkatMachineCommandClassification::CatalogInput(
1305                MeerkatMachineCatalogInput::SetSilentIntents,
1306            )
1307        }
1308        MeerkatMachineCommandVariant::CancelAfterBoundary => {
1309            MeerkatMachineCommandClassification::CatalogInput(
1310                MeerkatMachineCatalogInput::CancelAfterBoundary,
1311            )
1312        }
1313        MeerkatMachineCommandVariant::StopRuntimeExecutor => {
1314            MeerkatMachineCommandClassification::CatalogInput(
1315                MeerkatMachineCatalogInput::StopRuntimeExecutor,
1316            )
1317        }
1318        MeerkatMachineCommandVariant::CommitServiceTurnTerminalReceipt => {
1319            MeerkatMachineCommandClassification::CatalogInput(
1320                MeerkatMachineCatalogInput::ServiceTurnCommitted,
1321            )
1322        }
1323        MeerkatMachineCommandVariant::ContainsSession => {
1324            MeerkatMachineCommandClassification::CatalogInput(
1325                MeerkatMachineCatalogInput::ContainsSession,
1326            )
1327        }
1328        MeerkatMachineCommandVariant::SessionHasExecutor => {
1329            MeerkatMachineCommandClassification::CatalogInput(
1330                MeerkatMachineCatalogInput::SessionHasExecutor,
1331            )
1332        }
1333        MeerkatMachineCommandVariant::SessionHasComms => {
1334            MeerkatMachineCommandClassification::CatalogInput(
1335                MeerkatMachineCatalogInput::SessionHasComms,
1336            )
1337        }
1338        MeerkatMachineCommandVariant::OpsLifecycleRegistry => {
1339            MeerkatMachineCommandClassification::CatalogInput(
1340                MeerkatMachineCatalogInput::OpsLifecycleRegistry,
1341            )
1342        }
1343        MeerkatMachineCommandVariant::PrepareBindings => {
1344            MeerkatMachineCommandClassification::CatalogInput(
1345                MeerkatMachineCatalogInput::PrepareBindings,
1346            )
1347        }
1348        MeerkatMachineCommandVariant::InputState => {
1349            MeerkatMachineCommandClassification::CatalogInput(
1350                MeerkatMachineCatalogInput::InputState,
1351            )
1352        }
1353        MeerkatMachineCommandVariant::ListActiveInputs => {
1354            MeerkatMachineCommandClassification::CatalogInput(
1355                MeerkatMachineCatalogInput::ListActiveInputs,
1356            )
1357        }
1358        MeerkatMachineCommandVariant::ReconfigureSessionLlmIdentity => {
1359            MeerkatMachineCommandClassification::CatalogInput(
1360                MeerkatMachineCatalogInput::ReconfigureSessionLlmIdentity,
1361            )
1362        }
1363        MeerkatMachineCommandVariant::StagePersistentFilter => {
1364            MeerkatMachineCommandClassification::CatalogInput(
1365                MeerkatMachineCatalogInput::StagePersistentFilter,
1366            )
1367        }
1368        MeerkatMachineCommandVariant::RequestDeferredTools => {
1369            MeerkatMachineCommandClassification::CatalogInput(
1370                MeerkatMachineCatalogInput::RequestDeferredTools,
1371            )
1372        }
1373        MeerkatMachineCommandVariant::PublishCommittedVisibleSet => {
1374            MeerkatMachineCommandClassification::CatalogInput(
1375                MeerkatMachineCatalogInput::PublishCommittedVisibleSet,
1376            )
1377        }
1378        MeerkatMachineCommandVariant::SetPeerIngressContext => {
1379            MeerkatMachineCommandClassification::CatalogInput(
1380                MeerkatMachineCatalogInput::SetPeerIngressContext,
1381            )
1382        }
1383        MeerkatMachineCommandVariant::NotifyDrainExited => {
1384            MeerkatMachineCommandClassification::CatalogInput(
1385                MeerkatMachineCatalogInput::NotifyDrainExited,
1386            )
1387        }
1388        MeerkatMachineCommandVariant::AbortAll => {
1389            MeerkatMachineCommandClassification::CatalogInput(MeerkatMachineCatalogInput::AbortAll)
1390        }
1391        MeerkatMachineCommandVariant::Abort => {
1392            MeerkatMachineCommandClassification::CatalogInput(MeerkatMachineCatalogInput::Abort)
1393        }
1394        MeerkatMachineCommandVariant::Wait => {
1395            MeerkatMachineCommandClassification::CatalogInput(MeerkatMachineCatalogInput::Wait)
1396        }
1397        MeerkatMachineCommandVariant::Ingest => {
1398            MeerkatMachineCommandClassification::CatalogInput(MeerkatMachineCatalogInput::Ingest)
1399        }
1400        MeerkatMachineCommandVariant::PublishEvent => {
1401            MeerkatMachineCommandClassification::CatalogInput(
1402                MeerkatMachineCatalogInput::PublishEvent,
1403            )
1404        }
1405        MeerkatMachineCommandVariant::Retire => {
1406            MeerkatMachineCommandClassification::CatalogInput(MeerkatMachineCatalogInput::Retire)
1407        }
1408        MeerkatMachineCommandVariant::Recycle => {
1409            MeerkatMachineCommandClassification::CatalogInput(MeerkatMachineCatalogInput::Recycle)
1410        }
1411        MeerkatMachineCommandVariant::Reset => {
1412            MeerkatMachineCommandClassification::CatalogInput(MeerkatMachineCatalogInput::Reset)
1413        }
1414        MeerkatMachineCommandVariant::Recover => {
1415            MeerkatMachineCommandClassification::CatalogInput(MeerkatMachineCatalogInput::Recover)
1416        }
1417        MeerkatMachineCommandVariant::Destroy => {
1418            MeerkatMachineCommandClassification::CatalogInput(MeerkatMachineCatalogInput::Destroy)
1419        }
1420        MeerkatMachineCommandVariant::RuntimeState => {
1421            MeerkatMachineCommandClassification::CatalogInput(
1422                MeerkatMachineCatalogInput::RuntimeState,
1423            )
1424        }
1425        MeerkatMachineCommandVariant::AdmitModelRoutingAssistantTurn => {
1426            MeerkatMachineCommandClassification::CatalogInput(
1427                MeerkatMachineCatalogInput::AdmitModelRoutingAssistantTurn,
1428            )
1429        }
1430        MeerkatMachineCommandVariant::BeginImageOperation => {
1431            MeerkatMachineCommandClassification::CatalogInput(
1432                MeerkatMachineCatalogInput::BeginImageOperation,
1433            )
1434        }
1435        MeerkatMachineCommandVariant::ActivateImageOperationOverride => {
1436            MeerkatMachineCommandClassification::CatalogInput(
1437                MeerkatMachineCatalogInput::ActivateImageOperationOverride,
1438            )
1439        }
1440        MeerkatMachineCommandVariant::CompleteImageOperation => {
1441            MeerkatMachineCommandClassification::CatalogInput(
1442                MeerkatMachineCatalogInput::CompleteImageOperation,
1443            )
1444        }
1445        MeerkatMachineCommandVariant::RestoreImageOperationOverride => {
1446            MeerkatMachineCommandClassification::CatalogInput(
1447                MeerkatMachineCatalogInput::RestoreImageOperationOverride,
1448            )
1449        }
1450        MeerkatMachineCommandVariant::LoadBoundaryReceipt => {
1451            MeerkatMachineCommandClassification::CatalogInput(
1452                MeerkatMachineCatalogInput::LoadBoundaryReceipt,
1453            )
1454        }
1455        MeerkatMachineCommandVariant::AcceptWithCompletion => {
1456            MeerkatMachineCommandClassification::CatalogInput(
1457                MeerkatMachineCatalogInput::AcceptWithCompletion,
1458            )
1459        }
1460        MeerkatMachineCommandVariant::AcceptWithoutWake => {
1461            MeerkatMachineCommandClassification::CatalogInput(
1462                MeerkatMachineCatalogInput::AcceptWithoutWake,
1463            )
1464        }
1465        MeerkatMachineCommandVariant::Prepare => {
1466            MeerkatMachineCommandClassification::CatalogInput(MeerkatMachineCatalogInput::Prepare)
1467        }
1468        MeerkatMachineCommandVariant::Commit => {
1469            MeerkatMachineCommandClassification::CatalogInput(MeerkatMachineCatalogInput::Commit)
1470        }
1471        MeerkatMachineCommandVariant::Fail => {
1472            MeerkatMachineCommandClassification::CatalogInput(MeerkatMachineCatalogInput::Fail)
1473        }
1474    }
1475}
1476
1477/// Snapshot of completion waiters registered for one input.
1478///
1479/// This is a supporting-carrier view, not canonical semantic truth.
1480#[derive(Debug, Clone, PartialEq, Eq)]
1481pub struct MeerkatCompletionWaiterSnapshot {
1482    pub input_id: InputId,
1483    pub waiter_count: usize,
1484}
1485
1486/// Snapshot of the runtime completion waiter carrier.
1487///
1488/// Completion waiters are supporting carrier state rather than primary
1489/// authority, but they remain useful for diagnostics and recovery inspection.
1490#[derive(Debug, Clone, PartialEq, Eq)]
1491pub struct MeerkatCompletionWaitersSnapshot {
1492    pub input_count: usize,
1493    pub waiter_count: usize,
1494    pub waiting_inputs: Vec<MeerkatCompletionWaiterSnapshot>,
1495}
1496
1497/// Runtime driver flavor for a registered Meerkat session entry.
1498#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1499pub enum MeerkatDriverKind {
1500    Ephemeral,
1501    Persistent,
1502}
1503
1504/// Snapshot of the hidden runtime epoch cursor state.
1505#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1506pub struct MeerkatCursorSnapshot {
1507    pub agent_applied_cursor: u64,
1508    pub runtime_observed_seq: u64,
1509    pub runtime_last_injected_seq: u64,
1510}
1511
1512/// Snapshot of the hidden runtime binding for one Meerkat session.
1513#[derive(Debug, Clone)]
1514pub struct MeerkatBindingSnapshot {
1515    pub session_id: SessionId,
1516    pub runtime_id: LogicalRuntimeId,
1517    pub driver_kind: MeerkatDriverKind,
1518    pub driver_present: bool,
1519    pub completions_present: bool,
1520    pub ops_registry_present: bool,
1521    pub attachment_live: bool,
1522    pub epoch_id: RuntimeEpochId,
1523    pub cursor_state: MeerkatCursorSnapshot,
1524}
1525
1526/// Snapshot of runtime control-plane truth for one session.
1527#[derive(Debug, Clone)]
1528pub struct MeerkatControlSnapshot {
1529    pub phase: RuntimeState,
1530    pub current_run_id: Option<RunId>,
1531    pub pre_run_phase: Option<RuntimeState>,
1532}
1533
1534/// Snapshot of one admitted runtime input.
1535#[derive(Debug, Clone)]
1536pub struct MeerkatAdmittedInputSnapshot {
1537    pub input_id: InputId,
1538    pub content_shape: Option<ContentShape>,
1539    pub request_id: Option<RequestId>,
1540    pub reservation_key: Option<ReservationKey>,
1541    pub handling_mode: Option<HandlingMode>,
1542    pub lifecycle: Option<InputLifecycleState>,
1543    pub terminal_outcome: Option<InputTerminalOutcome>,
1544    pub last_run_id: Option<RunId>,
1545    pub last_boundary_sequence: Option<u64>,
1546    pub is_prompt: bool,
1547}
1548
1549/// Snapshot of runtime ingress truth for one session.
1550#[derive(Debug, Clone)]
1551pub struct MeerkatInputsSnapshot {
1552    pub admission_order: Vec<MeerkatAdmittedInputSnapshot>,
1553    pub queue: Vec<InputId>,
1554    pub steer_queue: Vec<InputId>,
1555    pub current_run_id: Option<RunId>,
1556    pub current_run_contributors: Vec<InputId>,
1557    pub post_admission_signal: String,
1558    pub silent_intent_overrides: Vec<String>,
1559}
1560
1561/// Snapshot of the canonical input-ledger carrier for one session.
1562///
1563/// These counts sit below the top-level Meerkat phase machine, but they drive
1564/// the exact values returned by control-plane reports such as
1565/// `DestroyReport.inputs_abandoned`.
1566#[derive(Debug, Clone)]
1567pub struct MeerkatLedgerSnapshot {
1568    pub input_count: usize,
1569    pub non_terminal_count: usize,
1570    pub accepted_count: usize,
1571    pub queued_count: usize,
1572    pub staged_count: usize,
1573    pub applied_count: usize,
1574    pub applied_pending_consumption_count: usize,
1575    pub consumed_count: usize,
1576    pub superseded_count: usize,
1577    pub coalesced_count: usize,
1578    pub abandoned_count: usize,
1579}
1580
1581/// Snapshot of runtime-owned async operation truth for one session.
1582#[derive(Debug, Clone)]
1583pub struct MeerkatOpsSnapshot {
1584    pub operation_count: usize,
1585    pub active_count: usize,
1586    pub wait_request_id: Option<WaitRequestId>,
1587    pub pending_wait_present: bool,
1588    pub pending_wait_request_id: Option<WaitRequestId>,
1589    pub wait_operation_ids: Vec<OperationId>,
1590    pub operations: Vec<OperationLifecycleSnapshot>,
1591}
1592
1593/// Snapshot of comms-drain lifecycle truth for one session.
1594#[derive(Debug, Clone)]
1595pub struct MeerkatDrainSnapshot {
1596    pub slot_present: bool,
1597    pub phase: Option<CommsDrainPhase>,
1598    pub mode: Option<CommsDrainMode>,
1599    pub handle_present: bool,
1600}
1601
1602/// Schema-aligned projection of the Meerkat formal state derived from the
1603/// live runtime.
1604///
1605/// This stays diagnostic and intentionally stringifies field values so the
1606/// parity harness can compare full field vectors even where the runtime does
1607/// not expose a first-class Rust type for every formal field.
1608#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1609pub struct MeerkatFormalStateProjection {
1610    /// Formal fields that have a live runtime-backed projection today.
1611    pub available_fields: BTreeMap<String, String>,
1612    /// Formal fields that still have no canonical runtime source.
1613    pub unavailable_fields: Vec<String>,
1614}
1615
1616/// Diagnostic snapshot of the current Meerkat runtime spine.
1617///
1618/// This is an observational scaffold over the existing runtime-owned Meerkat
1619/// regions. It is intentionally not the final MeerkatMachine reducer.
1620#[derive(Debug, Clone)]
1621pub struct MeerkatMachineSpineSnapshot {
1622    pub binding: MeerkatBindingSnapshot,
1623    pub control: MeerkatControlSnapshot,
1624    pub inputs: MeerkatInputsSnapshot,
1625    pub ledger: MeerkatLedgerSnapshot,
1626    pub completion_waiters: MeerkatCompletionWaitersSnapshot,
1627    pub ops: MeerkatOpsSnapshot,
1628    pub drain: MeerkatDrainSnapshot,
1629    pub formal_state: MeerkatFormalStateProjection,
1630}
1631
1632impl MeerkatMachineSpineSnapshot {
1633    /// Validate TLA+ structural invariants against the current spine snapshot.
1634    ///
1635    /// Returns `Ok(())` if all invariants hold, or `Err(violations)` with a
1636    /// list of human-readable violation descriptions. This is release-mode
1637    /// validation — not `debug_assert`.
1638    pub fn validate_spine_invariants(&self) -> Result<(), Vec<String>> {
1639        let mut violations = Vec::new();
1640
1641        // --- Control/binding invariants ---
1642
1643        // RunningHasActiveRunInvariant: Running => HasActiveRun
1644        if self.control.phase == RuntimeState::Running && self.control.current_run_id.is_none() {
1645            violations
1646                .push("RunningHasActiveRunInvariant: phase is Running but no active run_id".into());
1647        }
1648
1649        // ActiveRunPhaseInvariant: HasActiveRun => phase in {Running, Retired}
1650        if self.control.current_run_id.is_some()
1651            && !matches!(
1652                self.control.phase,
1653                RuntimeState::Running | RuntimeState::Retired
1654            )
1655        {
1656            violations.push(format!(
1657                "ActiveRunPhaseInvariant: active run_id present but phase is {:?}",
1658                self.control.phase
1659            ));
1660        }
1661
1662        // DestroyedShapeInvariant: Destroyed => empty queues, no waiting inputs
1663        if self.control.phase == RuntimeState::Destroyed {
1664            if !self.inputs.queue.is_empty() {
1665                violations.push("DestroyedShapeInvariant: Destroyed but queue is non-empty".into());
1666            }
1667            if !self.inputs.steer_queue.is_empty() {
1668                violations
1669                    .push("DestroyedShapeInvariant: Destroyed but steer_queue is non-empty".into());
1670            }
1671            if self.completion_waiters.input_count > 0 {
1672                violations.push(
1673                    "DestroyedShapeInvariant: Destroyed but completion waiters remain".into(),
1674                );
1675            }
1676        }
1677
1678        // --- Input invariants ---
1679
1680        // QueueSteerDisjointInvariant
1681        let queue_set: std::collections::HashSet<_> = self.inputs.queue.iter().collect();
1682        let steer_set: std::collections::HashSet<_> = self.inputs.steer_queue.iter().collect();
1683        if !queue_set.is_disjoint(&steer_set) {
1684            violations
1685                .push("QueueSteerDisjointInvariant: queue and steer_queue share entries".into());
1686        }
1687
1688        // QueueHandlingInvariant: all queue entries must have handling_mode=Queue, lifecycle=Queued
1689        for qid in &self.inputs.queue {
1690            if let Some(snap) = self
1691                .inputs
1692                .admission_order
1693                .iter()
1694                .find(|a| &a.input_id == qid)
1695            {
1696                if snap.handling_mode != Some(HandlingMode::Queue) {
1697                    violations.push(format!(
1698                        "QueueHandlingInvariant: queue entry {qid} has handling_mode {:?}",
1699                        snap.handling_mode
1700                    ));
1701                }
1702                if snap.lifecycle != Some(InputLifecycleState::Queued) {
1703                    violations.push(format!(
1704                        "QueueHandlingInvariant: queue entry {qid} has lifecycle {:?}",
1705                        snap.lifecycle
1706                    ));
1707                }
1708            }
1709        }
1710
1711        // SteerHandlingInvariant: all steer_queue entries must have handling_mode=Steer, lifecycle=Queued
1712        for sid in &self.inputs.steer_queue {
1713            if let Some(snap) = self
1714                .inputs
1715                .admission_order
1716                .iter()
1717                .find(|a| &a.input_id == sid)
1718            {
1719                if snap.handling_mode != Some(HandlingMode::Steer) {
1720                    violations.push(format!(
1721                        "SteerHandlingInvariant: steer_queue entry {sid} has handling_mode {:?}",
1722                        snap.handling_mode
1723                    ));
1724                }
1725                if snap.lifecycle != Some(InputLifecycleState::Queued) {
1726                    violations.push(format!(
1727                        "SteerHandlingInvariant: steer_queue entry {sid} has lifecycle {:?}",
1728                        snap.lifecycle
1729                    ));
1730                }
1731            }
1732        }
1733
1734        // ContributorLifecycleInvariant: all current_run_contributors must
1735        // still belong to the active run lifecycle slice.
1736        for cid in &self.inputs.current_run_contributors {
1737            if let Some(snap) = self
1738                .inputs
1739                .admission_order
1740                .iter()
1741                .find(|a| &a.input_id == cid)
1742                && !matches!(
1743                    snap.lifecycle,
1744                    Some(
1745                        InputLifecycleState::Staged
1746                            | InputLifecycleState::Applied
1747                            | InputLifecycleState::AppliedPendingConsumption
1748                    )
1749                )
1750            {
1751                violations.push(format!(
1752                    "ContributorLifecycleInvariant: contributor {cid} has lifecycle {:?}",
1753                    snap.lifecycle
1754                ));
1755            }
1756        }
1757
1758        // TerminalInputsNotQueuedInvariant: terminal inputs must not be in queue or steer_queue
1759        for snap in &self.inputs.admission_order {
1760            if snap.terminal_outcome.is_some() {
1761                if queue_set.contains(&snap.input_id) {
1762                    violations.push(format!(
1763                        "TerminalInputsNotQueuedInvariant: terminal input {} in queue",
1764                        snap.input_id
1765                    ));
1766                }
1767                if steer_set.contains(&snap.input_id) {
1768                    violations.push(format!(
1769                        "TerminalInputsNotQueuedInvariant: terminal input {} in steer_queue",
1770                        snap.input_id
1771                    ));
1772                }
1773            }
1774        }
1775
1776        // CurrentRunContributorsInvariant: HasActiveRun => contributors non-empty
1777        if self.control.current_run_id.is_some() && self.inputs.current_run_contributors.is_empty()
1778        {
1779            violations
1780                .push("CurrentRunContributorsInvariant: active run but no contributors".into());
1781        }
1782
1783        // ContributorRunIdentityInvariant: when control owns an active run,
1784        // every current contributor must point at that same run in the
1785        // ingress bookkeeping.
1786        if let Some(control_run_id) = &self.control.current_run_id {
1787            for cid in &self.inputs.current_run_contributors {
1788                if let Some(snap) = self
1789                    .inputs
1790                    .admission_order
1791                    .iter()
1792                    .find(|a| &a.input_id == cid)
1793                    && snap.last_run_id.as_ref() != Some(control_run_id)
1794                {
1795                    violations.push(format!(
1796                        "ContributorRunIdentityInvariant: contributor {cid} has last_run_id {:?}, expected {:?}",
1797                        snap.last_run_id, control_run_id
1798                    ));
1799                }
1800            }
1801        }
1802
1803        // --- Ops invariants ---
1804
1805        // WaitAllAlignmentInvariant
1806        let wait_active = self.ops.wait_request_id.is_some();
1807        if wait_active && self.ops.wait_operation_ids.is_empty() {
1808            violations
1809                .push("WaitAllAlignmentInvariant: wait_active but no wait_operation_ids".into());
1810        }
1811        if !wait_active && !self.ops.wait_operation_ids.is_empty() {
1812            violations.push(
1813                "WaitAllAlignmentInvariant: wait_operation_ids present but no wait_request_id"
1814                    .into(),
1815            );
1816        }
1817
1818        // --- Drain invariants ---
1819
1820        // DrainBindingInvariant: drain.phase != Inactive => binding.live
1821        if let Some(phase) = self.drain.phase {
1822            if phase != CommsDrainPhase::Inactive && !self.binding.attachment_live {
1823                // binding.live maps to session being registered (it is, since we have a snapshot)
1824                // This invariant is about the binding being live, which is always true if we have
1825                // a snapshot. Skip this check since getting a snapshot implies the session exists.
1826            }
1827            let _ = phase; // suppress unused warning
1828        }
1829
1830        // DrainModeInvariant: drain.phase != Inactive => mode is set and not Disabled
1831        if let Some(phase) = self.drain.phase
1832            && phase != CommsDrainPhase::Inactive
1833            && self.drain.mode.is_none()
1834        {
1835            violations.push("DrainModeInvariant: drain.phase is active but mode is None".into());
1836        }
1837
1838        if violations.is_empty() {
1839            Ok(())
1840        } else {
1841            Err(violations)
1842        }
1843    }
1844}